4. Getting Started with GNATstack

Two steps are needed to analyze stack usage: 1) generation of the basic stack consumption and call-graph information, followed by 2) analysis and report generation.

The first step is to compile the application with the required options to generate the information about per-subprogram stack consumption and call-graph information. The -fcallgraph-info=su compiler switch outputs this information on a per-file basis in the common VCG format:

$ gprbuild main_unit.adb -cargs -fcallgraph-info=su

The use of this compiler switch generates a .ci file for every compilation unit.

The compiler can also generate information about dynamically allocated objects adding the da marker to the -fcallgraph-info compiler switch (-fcallgraph-info=su,da). This option helps locating the objects that introduce potentially unbounded stack requirements.

In the second step, GNATstack can then parse these files to generate the complete call graph decorated with stack usage information, so it can look for the longest paths in terms of stack usage:

$ gnatstack *.ci
[...]
Accumulated stack usage information for entry points

main : total 376 bytes
 +-> main
 +-> main_unit
 +-> process

4.1. Options for GNATstack

GNATstack is configurable with respect to the amount of information that we want to obtain. The set of options available is the following:

-a

Consider all subprograms as entry points.

-ca

Extract all possible cycles in the call graph.

-c n

Use n bytes frame for subprograms at the beginning of cycles. This can be used to increase visibility of cycles in reports.

-g

Generate internal debug information.

-aO dir

Load .ci files from dir.

-e entry_point[,...]

Name of symbols to be used as entry points for the analysis. It accepts both symbol and demangled names, and it performs a case-insensitive search.

-files=file

Take as arguments the files listed in text file file. Text file file may contain empty lines that are ignored. Each non empty line should contain the name of an existing file. Several such switches may be specified simultaneously.

-h or –help

Output a message explaining the usage of gnatstack.

-l n

Print the n subprograms requiring the biggest local stack usage. By default none will be displayed.

-o=x

Specifies the order for displaying the different call graphs. The options allowed for this qualifier are:

  • -o=s

    Select stack usage order (the default mode).

  • -o=a

    Select alphabetical order.

-p

Print all the subprograms that make up the worst-case path for every entry point (the default mode).

-pi

Print progress information (for GPS).

-q

Be quiet: do not display progress information.

-Q

Very quiet: do not display any console message except those indicating an error and the progress information.

-np

Do not print worst-case path for entry points.

-f file

Write the generated graph (VCG format) into file.

-r regexp

Any symbol matching the regular expression regexp will be considered as an entry point for the analysis. It accepts both symbol and demangled names, and it performs a case-insensitive pattern matching.

-d n

Set default stack size for unbounded (dynamic) frames to n bytes.

-u n

Set default stack size for unknown (external) calls to n bytes.

-v

Specify the amount of information to be displayed about the different subprograms. In verbose mode the full location of the subprogram will be part of the output, as well as detailed information about inaccurate data.

–version

Print version information.

-tx

Print potential targets for indirect and dispatching calls. The options allowed for this qualifier are:

  • -ta

    Print potential targets for both indirect and dispatching calls.

  • -ti

    Print potential targets for indirect calls.

  • -td

    Print potential targets for dispatching calls.

-Wx

Warning mode. The options allowed for this qualifier are:

  • -Wa

    Turn on all optional warnings.

  • -Wc

    Turn on warnings for cycles (recursion).

  • -Wu

    Turn on warnings for unbounded frames.

  • -We

    Turn on warnings for external calls.

  • -Wi

    Turn on warnings for indirect (including dispatching) calls.

-x

Generate file stack_usage.xml with the information in XML format.

-oc=name

Use the objcopy executable name to extract the .ci files from the object files. If may contain the prefix (such as powerpc-elf-objcopy) or the suffix (such as objcopyppc).

-s=section

Extract the .ci files from section named section in the object files.

-k

Keep the temporary files created by GNATstack (such as the .ci files extracted from the object files.

–target=tgt

Use tgt as program prefix used to build the application being analyzed. This value is only used to help retrieving the stack usage information for the run-time library.

–RTS=rts

Use rts as the run-time library used to build the application being analyzed. This value is only used to retrieve the stack usage information for the run-time library.

-Xname=val

Set project variable name to val. Several -X switches can be used simultaneously. If several -X switches specify the same name, only the last one is used. An external variable specified with a -X switch takes precedence over the value of the same name in the environment.

-Pprj

Get the GNATstack options from the specified project prj.

4.2. Use of Project Files

gnatstack supports the use of project files. There is an optional package in the project file for GNATstack (which is the package Stack) that has an unique attribute Switches. This attribute is a simple variable that contains a string list value representing the switches to be passed to gnatstack:

project Prj is
   package Stack is
      for Switches use ("-p");
   end Stack;
end Prj;

The following command will parse the project file prj.gpr and call gnatstack with the specified options:

$ gnatstack -P prj.gpr

When gnatstack is used with a project file, if gets the set of .ci files that correspond to the units that belong to the project and all projects in the project tree.

If one or more user-defined .ci files need to be taken into account for the analysis, the appropriate place to specify them is in the Switches attribute of the project file:

project Prj is
   package Stack is
      for Switches use (..., "user1.ci", "user2.ci");
   end Stack;
end Prj;

4.3. GNATstack output

The execution of GNATstack produces the maximum stack space required by every entry point (including tasks) in the application, together with the paths that lead to these stack needs:

$ gnatstack -P my_project.gpr

Accumulated stack usage information for entry points

main : total 408 bytes
 +-> main
 +-> main_unit
 +-> pck.process

The maximum stack consumption for the main entry point is 408 bytes, and this is a safe bound that can be trusted. The indicated call chain is the path with the longest stack consumption from all possible paths starting from main.

Within GNAT Programming Studio (GPS) the stack usage information is available via the Tools ‣ Stack Analysis ‣ Analyze Stack Usage menu. For each of the subprograms the information is displayed in the form of annotations containing local stack requirements, which refers to a single stack frame, and global stack requirements, which refers to the worst case (with respect to stack usage) call chain starting from the subprogram. In the previous example, local is the 80 bytes of the main subprogram, and global is 408 bytes (for the call chain where main calls Main_Unit and Main_Unit calls Process):

--  main
--
--  Stack usage: local: 80 bytes, global: 408 bytes

More details about the locations of the subprograms and their local stack frames is displayed using the -v switch:

$ gnatstack -v -P my_project.gpr
[...]

Accumulated stack usage information for entry points

main : total 408 bytes
 +-> main at main:b__main_unit.adb:118:4 : 80 bytes
 +-> main_unit at Main_Unit:main_unit.adb:3:1 : 16 bytes
 +-> pck.process at Process:pck.adb:2:4,Process:pck.ads:2:14 : 312 bytes

In this case, the first line indicates the total (global) stack requirement of 408 bytes for the whole call chain (adding up all the frames in the call chain), and the following lines contain the individual (local) stack frames for each of the subprograms in the call chain.

When the computed stack consumption cannot be safely bound (because of cycles, unbounded frames, external calls, or indirect calls) the total stack requirements contain a “+?” indication, plus a “*” indication in the frames containing missing information. For example, if we add a dynamic variable to the previous example we would have the following GNATstack report:

$ gnatstack -P my_project.gpr

Worst case analysis is *not* accurate because of unbounded frames.
Use -Wa for details.

Accumulated stack usage information for entry points

main : total 576+? bytes
 +-> main
 +-> main_unit
 +-> pck.process *

If we add the “-Wa” and “-v” options to GNATstack we get the following additional information:

$  gnatstack -v -Wa -P my_project.gpr
[...]

Worst case analysis is *not* accurate because of unbounded frames.

List of reachable subprograms with dynamic unbounded frames:

  In pck.process at Process:pck.adb:2:4,Process:pck.ads:2:14

Accumulated stack usage information for entry points

main : total 576+? bytes (unbounded)
 +-> main at main:b__main_unit.adb:118:4 : 80 bytes
 +-> main_unit at Main_Unit:main_unit.adb:3:1 : 16 bytes
 +-> pck.process at Process:pck.adb:2:4,Process:pck.ads:2:14 : 480+? bytes (unbounded)

This analysis indicates that the computed worst case stack usage is not accurate. There are 576 bytes needed, plus the amount required by the dynamic object declared by subprogram Process.

Within GPS, this missing information is expressed with annotations indicating the reasons behind the inaccuracy. For the main entry point we would have:

--  main
--
--  Stack usage: local: 80 bytes, global: 576 bytes (unbounded)

and for the Process subprogram we would have:

--  pck.process
--
--  Stack usage: local: 480 bytes (unbounded), global: 480 bytes (unbounded)

4.4. Information in Non-Loadable Section

The .ci file generated for every compilation unit can be written into the generated object as a non-loadable section. For example, it can be written into a section named .GNU.callgraph doing:

$ gcc -c -fcallgraph-info=su main_unit.adb
$ objcopy --add-section .GNU.callgraph=main_unit.ci main_unit.o
$ gnatstack main_unit.o

This section is not downloaded into the target so it does not affect the execution. The advantage is that it may facilitate the retrieval of the required .ci files (we simply need the list of object files that make up the application), and it may help guaranteeing the coherence between the call-graph and stack usage information and the corresponding object files.

The linker would merge (concatenate) the .ci sections corresponding to the object files being linked into the final executable (including those coming from library files).

GNATstack can later extract this section from the object files and use the information for the rest of the analysis. GNATstack uses the objcopy tool to extract the .ci files.

By default, GNATstack uses the native objcopy executable. It can be changed using the -oc option (see Options for GNATstack):

$ powerpc-elf-gcc -c -fcallgraph-info=su main_unit.adb
$ powerpc-elf-objcopy --add-section .GNU.callgraph=main_unit.ci main_unit.o
$ gnatstack -oc=powerpc-elf-objcopy main_unit.o

The section where the .ci files are stored can also be changed, and GNATstack provides an option (-s, see Options for GNATstack) to specify this section:

$ gcc -c -fcallgraph-info=su main_unit.adb
$ objcopy --add-section my_section=main_unit.ci main_unit.o
$ gnatstack -s=my_section main_unit.o