Tracking call frame usage
On the following pages, learn more about:
What do you want to do?
Read more about:
Call frame information overview
Call frame information (CFI) is information about the call frames. Typically, a call frame contains a return address, function arguments, saved register values, compiler temporaries, and local variables. Call frame information holds enough information about call frames to support two important features:
C-SPY can use call frame information to reconstruct the entire call chain from the current
PC(program counter) and show the values of local variables in each function in the call chain. This information is used, for example, in the Call Stack window.Call frame information can be used, together with information about possible calls for calculating the total stack usage in the application. Note that this feature might not be supported by the product you are using.
The compiler automatically generates call frame information for all C and C++ source code. Call frame information is also typically provided for each assembler routine in the system library. However, if you have other assembler routines and want to enable C-SPY to show the call stack when executing these routines, you must add the required call frame information annotations to your assembler source code. Stack usage can also be handled this way (by adding the required annotations for each function call), but you can also specify stack usage information for any routines in a stack usage control file (see The stack usage control file), which is typically easier.
Call frame information in more detail
You can add call frame information to assembler files by using cfi directives. You can use these to specify:
The start address of the call frame, which is referred to as the canonical frame address (CFA). There are two different types of call frames:
On a stack—stack frames. For stack frames the CFA is typically the value of the stack pointer after the return from the routine.
In static memory, as used in a static overlay system—static overlay frames. This type of call frame is not required by the Arm core and is therefore not supported.
How to find the return address.
How to restore various resources, like registers, when returning from the routine.
When adding the call frame information for each assembler module, you must:
Provide a names block where you describe the resources to be tracked.
Provide a common block where you define the resources to be tracked and specify their default values. This information must correspond to the calling convention used by the compiler.
Annotate the resources used in your source code, which in practice means that you describe the changes performed on the call frame. Typically, this includes information about when the stack pointer is changed, and when permanent registers are stored or restored on the stack.
To do this you must define a data block that encloses a continuous piece of source code where you specify rules for each resource to be tracked. When the descriptive power of the rules is not enough, you can instead use CFI expressions.
A full description of the calling convention might require extensive call frame information. In many cases, a more limited approach will suffice. The recommended way to create an assembler language routine that handles call frame information correctly is to start with a C skeleton function that you compile to generate assembler output. For an example, see Creating skeleton codethe IAR C/C++ Development documentation.
Defining a names block
A names block is used for declaring the resources available for a processor. Inside the names block, all resources that can be tracked are defined.
Start and end a names block with the directives:
CFI NAMESnameCFI ENDNAMESname
where name is the name of the block.
Only one names block can be open at a time.
Inside a names block, four different kinds of declarations can appear—a resource declaration, a stack frame declaration, a static overlay frame declaration, and a base address declaration:
To declare a resource, use one of the directives:
CFI RESOURCEresource:bitsCFI VIRTUALRESOURCEresource:bitsThe parameters are the name of the resource and the size of the resource in bits. The name must be one of the register names defined in the AEABI document that corresponds to the device architecture, either DWARF for the ARM architecture or DWARF for the Arm 64-bit architecture (AArch64). A virtual resource is a logical concept, in contrast to a physical resource such as a processor register. Virtual resources are usually used for the return address.
To declare more than one resource, separate them with commas.
A resource can also be a composite resource, made up of at least two parts. To declare the composition of a composite resource, use the directive:
CFI RESOURCEPARTS
resourcepart, part, …The parts are separated with commas. The resource and its parts must have been previously declared as resources, as described above.
To declare a stack frame CFA, use the directive:
CFI STACKFRAME
cfa resource typeThe parameters are the name of the stack frame CFA, the name of the associated resource (the stack pointer), and the memory type (to get the address space). To declare more than one stack frame CFA, separate them with commas.
When going back in the call stack, the value of the stack frame CFA is copied into the associated stack pointer resource to get a correct value for the previous function frame.
To declare a base address CFA, use the directive:
CFI BASEADDRESS
cfa typeThe parameters are the name of the CFA and the memory type. To declare more than one base address CFA, separate them with commas.
A base address CFA is used for conveniently handling a CFA. In contrast to the stack frame CFA, there is no associated stack pointer resource to restore.
Defining a common block
The common block is used for declaring the initial contents of all tracked resources. Normally, there is one common block for each calling convention used.
Start a common block with the directive:
CFI COMMONnameUSINGnamesblock
where name is the name of the new block and namesblock is the name of a previously defined names block.
Declare the return address column with the directive:
CFI RETURNADDRESSresourcetype
where resource is a resource defined in namesblock and type is the memory in which the calling function resides. You must declare the return address column for the common block.
Inside a common block, you can declare the initial value of a CFA or a resource by using the directives available for common blocks, see Call frame information directives for common blocks. For more information about how to use these directives, see Specifying rules for tracking resources and the stack depth and Using CFI expressions for tracking complex cases.
End a common block with the directive:
CFI ENDCOMMON namewhere name is the name used to start the common block.
Annotating your source code within a data block
The data block contains the actual tracking information for one continuous piece of code.
Start a data block with the directive:
CFI BLOCKnameUSINGcommonblock
where name is the name of the new block and commonblock is the name of a previously defined common block.
If the piece of code for the current data block is part of a defined function, specify the name of the function with the directive:
CFI FUNCTION labelwhere label is the code label starting the function.
If the piece of code for the current data block is not part of a function, specify this with the directive:
CFI NOFUNCTION
End a data block with the directive:
CFI ENDBLOCK namewhere name is the name used to start the data block.
Inside a data block, you can manipulate the values of the resources by using the directives available for data blocks, see Call frame information directives for data blocks. For more information on how to use these directives, see Specifying rules for tracking resources and the stack depth, and Using CFI expressions for tracking complex cases.
Specifying rules for tracking resources and the stack depth
To describe the tracking information for individual resources, two sets of simple rules with specialized syntax can be used:
Rules for tracking resources
CFIresource{ UNDEFINED | SAMEVALUE | CONCAT }CFIresource{resource| FRAME(cfa,offset) }Rules for tracking the stack depth (CFAs)
CFIcfa{ NOTUSED | USED }CFIcfa{resource|resource+constant|resource-constant}
You can use these rules both in common blocks to describe the initial information for resources and CFAs, and inside data blocks to describe changes to the information for resources or CFAs.
In those rare cases where the descriptive power of the simple rules are not enough, you can use a full CFI expression with dedicated operators to describe the information, see Using CFI expressions for tracking complex cases. However, whenever possible, you should always use a rule instead of a CFI expression.
Rules for tracking resources
The rules for resources conceptually describe where to find a resource when going back one call frame. For this reason, the item following the resource name in a CFI directive is referred to as the location of the resource.
To declare that a tracked resource is restored, in other words, already correctly located, use SAMEVALUE as the location. Conceptually, this declares that the resource does not have to be restored because it already contains the correct value. For example, to declare that a register R11 is restored to the same value, use the directive:
CFI R11 SAMEVALUE
To declare that a resource is not tracked, use UNDEFINED as location. Conceptually, this declares that the resource does not have to be restored (when going back one call frame) because it is not tracked. Usually it is only meaningful to use it to declare the initial location of a resource. For example, to declare that R11 is a scratch register and does not have to be restored, use the directive:
CFI R11 UNDEFINED
To declare that a resource is temporarily stored in another resource, use the resource name as its location. For example, to declare that a register R11 is temporarily located in a register R12 (and should be restored from that register), use the directive:
CFI R11 R12
To declare that a resource is currently located somewhere on the stack, use FRAME(cfa, offset) as location for the resource, where cfa is the CFA identifier to use as “frame pointer” and offset is an offset relative the CFA. For example, to declare that a register R11 is located at offset –4 counting from the frame pointer CFA_SP, use the directive:
CFI R11 FRAME(CFA_SP,-4)
For a composite resource there is one additional location, CONCAT, which declares that the location of the resource can be found by concatenating the resource parts for the composite resource. For example, consider a composite resource RET with resource parts RETLO and RETHI. To declare that the value of RET can be found by investigating and concatenating the resource parts, use the directive:
CFI RET CONCAT
This requires that at least one of the resource parts has a definition, using the rules described above.
Rules for tracking the stack depth (CFAs)
In contrast to the rules for resources, the rules for CFAs describe the address of the beginning of the call frame. The call frame often includes the return address pushed by the assembler call instruction. The CFA rules describe how to compute the address of the beginning of the current stack frame.
Each stack frame CFA is associated with a stack pointer. When going back one call frame, the associated stack pointer is restored to the current CFA. For stack frame CFAs, there are two possible rules—an offset from a resource (not necessarily the resource associated with the stack frame CFA) or NOTUSED.
To declare that a CFA is not used, and that the associated stack pointer should be tracked as a normal resource, use NOTUSED as the address of the CFA. For example, to declare that the CFA with the name CFA_SP is not used in this code block, use the directive:
CFI CFA_SP NOTUSED
To declare that a CFA has an address that is offset relative the value of a resource, specify the stack pointer and the offset. For example, to declare that the CFA with the name CFA_SP can be obtained by adding 4 to the value of the SP resource, use the directive:
CFI CFA_SP SP + 4
Using CFI expressions for tracking complex cases
You can use call frame information expressions (CFI expressions) when the descriptive power of the rules for resources and CFAs is not enough. However, you should always use a simple rule if there is one.
CFI expressions consist of operands and operators. Three sets of operators are allowed in a CFI expression:
Unary operators
Binary operators
Ternary operators
In most cases, they have an equivalent operator in the regular assembler expressions.
In this example, R12 is restored to its original value. However, instead of saving it, the effect of the two post increments is undone by the subtract instruction.
AddTwo:
cfi block addTwoBlock using myCommon
cfi function addTwo
cfi nocalls
cfi r12 samevalue
add @r12+, r13
cfi r12 sub(r12, 2)
add @r12+, r13
cfi r12 sub(r12, 4)
sub #4, r12
cfi r12 samevalue
ret
cfi endblock addTwoBlockFor more information about the syntax for using the operators in CFI expressions, see Call frame information directives for tracking resources and CFAs.
Stack usage analysis directives
The stack usage analysis directives (CFI FUNCALL, CFI TAILCALL, CFI INDIRECTCALL, and CFI NOCALLS) are used for building a call graph which is needed for stack usage analysis. These directives can be used only in data blocks. When the data block is a function block (in other words, when the CFI FUNCTION directive has been used in the data block), you should not specify a caller parameter. When a stack usage analysis directive is used in code that is shared between functions, you must use the caller parameter to specify which of the possible functions the information applies to.
The CFI FUNCALL, CFI TAILCALL, and CFI INDIRECTCALL directives must be placed immediately before the instruction that performs the call. The CFI NOCALLS directive can be placed anywhere in the data block.
Examples of using CFI directives
The following is an example specific to the Armcore. More examples can be obtained by generating assembler output when you compile a C source file.
Consider a Cortex-M3 device with its stack pointer R13, link register R14, and general purpose registers R0–R12. Register R0, R2, R3, and R12 will be used as scratch registers—these registers may be destroyed by a function call—whereas register R1 must be restored after the function call.
Consider the following short code sample with the corresponding call frame information. At entry, assume that the register R14 contains a 32-bit return address. The stack grows from high addresses toward zero. The CFA denotes the top of the call frame, in other words, the value of the stack pointer after returning from the function.
Address | CFA | R1 | R4-R11 | R14 | R0, R2, R3, R12 | Assembler code |
|---|---|---|---|---|---|---|
| R13 + 0 | SAME | SAME | SAME | Undefined |
|
| R13 + 8 | CFA - 8 | CFA- 4 |
| ||
|
| |||||
|
| |||||
| R13 + 0 | R0 | SAME |
| ||
| SAME |
|
Each row describes the state of the tracked resources before the execution of the instruction. As an example, for the MOV R1,R0 instruction, the original value of the R1 register is located in the R0 register, and the top of the function frame (the CFA column) is R13 + 0. The row at address 0000 is the initial row, and the result of the calling convention used for the function.
The R14 column is the return address column—in other words, the location of the return address. The R1 column has SAME in the initial row to indicate that the value of the R1 register will be restored to the same value it already has. Some of the registers are undefined because they do not need to be restored on exit from the function.
Defining the names block
The names block for the small example above would be:
cfi names ArmCore
cfi stackframe cfa r13 DATA
cfi resource r0:32, r1:32, r2:32, r3:32
cfi resource r4:32, r5:32, r6:32, r7:32
cfi resource r8:32, r9:32, r10:32, r11:32
cfi resource r12:32, r13:32, r14:32
cfi endnames ArmCoreDefining the common block
cfi common trivialCommon using ArmCore
cfi codealign 2
cfi dataalign 4
cfi returnaddress r14 CODE
cfi cfa r13+0
cfi default samevalue
cfi r0 undefined
cfi r2 undefined
cfi r3 undefined
cfi r12 undefined
cfi endcommon trivialCommonNote
R13 cannot be changed using a CFI directive because it is the resource associated with CFA.
Defining the data block
You should place the CFI directives at the point where the backtrace information has changed, in other words, immediately after the instruction that changes the backtrace information.
section MYCODE:CODE(2)
cfi block trivialBlock using trivialCommon
cfi function func1
thumb
func1 push {r1,lr}
cfi r1 frame(cfa, -8)
cfi r14 frame(cfa, -4)
cfi cfa r13+8
movs r1,#4
cfi funcall func2
bl func2
pop {r0,lr}
cfi r1 r0
cfi r14 samevalue
cfi cfa r13
mov r1,r0
cfi r1 samevalue
bx lr
cfi endblock trivialBlock
end