Skip to main content

IAR Embedded Workbench for Arm 9.70.x

Tracking call frame usage

In this section:

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:

  1. Provide a names block where you describe the resources to be tracked.

  2. 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.

  3. 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 NAMES name
CFI ENDNAMES name

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 RESOURCE resource : bits
    CFI VIRTUALRESOURCE resource : bits

    The 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 resource part, 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 type
    

    The 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 type
    

    The 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 COMMON name USING namesblock

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 RETURNADDRESS resource type

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 name

where 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 BLOCK name USING commonblock

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 label

where 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 name

where 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

    CFI resource { UNDEFINED | SAMEVALUE | CONCAT }

    CFI resource { resource | FRAME(cfa, offset) }

  • Rules for tracking the stack depth (CFAs)

    CFI cfa { NOTUSED | USED }

    CFI cfa { 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 addTwoBlock

For 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

00000000

R13 + 0

SAME

SAME

SAME

Undefined

PUSH {r1,lr}

00000002

R13 + 8

CFA - 8

CFA- 4

MOVS r1,#4

00000004

BL func2

00000008

POP {r0,lr}

0000000C

R13 + 0

R0

SAME

MOV r1,r0

0000000E

SAME

BX lr

Table 143. Code sample with backtrace rows and columns 


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 ArmCore
Defining 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 trivialCommon

Note

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