Application Note 188
ELF/DWARF Output Format Used 
by Keil LA Linker/Locater for ARM

Revision 1 - 10 Jan. 2005

This Application Note gives you information about the ELF/DWARF output generated by the Keil LA Linker/Locater for ARM.  This information is intended for anyone who wants use the debugging information generated by the Keil Development Suite for ARM.  It is assumed that you are familiar with ELF/DWARF Version 2 and 3 specifications.

Information in this file, the accompany manuals, and software is
Copyright © Keil Software, Inc and Keil Elektronik GmbH.
All rights reserved.


Revision History:

Rev 1: 10. Jan. 2005: Initial Version


Contents

  1. Overview
  2. Compile Units
  3. Generated ELF Sections
  4. Contact Details

Overview

The Keil LA Linker/Locater for ARM (that is part of the Keil Development Suite for ARM Version 2 or higher) generates an ELF/DWARF output file based on the following specification (available at http://dwarf.freestandards.org):

DWARF Debugging Information Format 
Version 3 Draft 9 (November 12, 2003)

All example outputs are created with the DisELF.EXE utility which is part of the Keil Development Suite for ARM. This Application Note explains only the implementation specific issues.

Compile Units

A compile unit usually refers to a source module of the user application.  The ELF output from the linker contains a special  compilation unit, called the TypeContainer. It contains all type entities of all other compilation units. The TypeContainer uses the following abbreviations and debug info:

.debug_abbrev:                                      .debug_info:
  DW_TAG_compile_unit
  DW_AT_name      DW_FORM_string       '<TypeContainer>'
  DW_AT_producer DW_FORM_string       'Keil LA Linker'

No other attributes are specified for the TypeContainer.  All type references from any other compilation unit uses references of the form:

  DW_AT_type DW_FORM_ref_addr        <offset into TypeContainer>

The type entities are generated on demand, therefore they do not necessarily reflect the order of their declaration.

Generated ELF Sections

The debug information is contained in different ELF-sections. A list of the generated sections is shown in the table below:

Elf-Section Description
.symtab symbol table, contains file level variables (global and local), function names and mode range symbols
.debug_loc location lists, used to specify the frame pointer for functions
.debug_line contains the line number information
.strtab string table, contains identifiers
.shstrtab a table containing the ELF-section names
.debug_abbrev contains the debug abbreviations.  Used as template to interpret the content of the .debug_info section
.debug_info the debug information, the layout is defined by the debug abbreviations.
.debug_frame Call frame information.  Used to find the return address at any point within a function.

Section .symtab 

The symbol table section contains the names of all file level global and static variables, functions and special mode range symbols in absolute format. The address and the size is also specified.  Symbols which are part of functions such as parameters and automatic names are not listed in this section.  They are listed in the .debug_info section with their location and type.

The mode range symbols are artificial, in that they are generated by the Linker.  With the help of the mode range symbols, the code disassembly has the required information to always show the correct instructions (arm vs. thumb vs. data), regardless of the current cpu mode. The following range mode symbols are used: 

Symbol Description
$a specifies arm mode range (32-Bit code)
$t specifies thumb mode range (16-Bit code)
$d specifies a constant or data range (no code)

Example:

    ...
      5: '$a', val=0x010000D4, size=96, info=0x02, shndx=SHN_ABS(0xFFF1)
      6: '$t', val=0x01000134, size=2, info=0x02, shndx=SHN_ABS(0xFFF1)
      7: '$d', val=0x01000138, size=16, info=0x01, shndx=SHN_ABS(0xFFF1)
    ...
    111: 'current', val=0x000004B0, size=20, info=0x11, shndx=SHN_ABS(0xFFF1)
    112: 'measurement_interval', val=0x000004C4, size=4, info=0x11, shndx=SHN_ABS(0xFFF1)
    113: 'mdisplay', val=0x000004C8, size=4, info=0x11, shndx=SHN_ABS(0xFFF1)
    114: 'startflag', val=0x000004CC, size=4, info=0x11, shndx=SHN_ABS(0xFFF1)
    115: 'save_record', val=0x000004D0, size=400, info=0x11, shndx=SHN_ABS(0xFFF1)
    116: 'sindex', val=0x00000660, size=4, info=0x11, shndx=SHN_ABS(0xFFF1)

Section .debug_loc

With the debug_loc entries, the current frame pointer (normally SP, alias R13) is specified. The frame base defines the start of the automatic variables on the stack. Additionally, there may be an offset in cases where the frame base register is modified (for example, due to PUSH instructions).

The generated info uses one of two formats:

Format Description
direct DW_AT_frame_base  DW_FORM_block1 framebase-reg  (frame base register is given directly)
indirect DW_AT_frame_base  DW_FORM_data4  Ref to location-list in .debug_loc  (frame base is specified via location list(s))

The generated output uses the indirect method for high level code, such as C functions, and the direct method for assembly language code.

Example:

void set_interval (char * buffer) {
  struct interval itime;                 /* temporary interval record         */
  int min, sec, msec;
  int args;                              /* number of arguments               */

 01000E6C  B530      PUSH        {R4-R5,LR}
 01000E6E  ---- Variable 'buffer' assigned to Register 'R0' ----
 01000E6E  B084      SUB         R13,#0x10

<< local frame is allocated: SP -= 0x10 >>
<< local frame starts @SP+#0 >>

 01000E70            ; SCOPE-START
 01000E70  A903      ADD         R1,R13,#0xC
 01000E72  B402      PUSH        {R1}

<< [1] PUSH: local frame starts @SP+#4 >>

 01000E2C  492A      LDR         R1,[R15,#168] ; PoolRef @0x1000ED8 ; ??S_5
  args = sscanf (buffer, "%d:%d.%d",     /* scan input line for               */
                 &min,                   /* minute, second and milliseconds   */
                 &sec,
                 &msec);
                                         /* check valid inputs                */
 01000E7A  F000      BL          sscanf?T  ; T=0x10010B9  (1)
 01000E7C  F91D      BL          sscanf?T  ; T=0x10010B9  (2)
 01000E7E  B001      ADD         R13,#0x4
 01000E80  ---- Variable 'args' assigned to Register 'R0' ----

<< [1] ADD SP,#0x04: local frame starts @SP+#0 >>

  if (sec > 59  ||  msec > 999  ||  args < 2  || args == EOF)  {
    printf (ERROR, "INVALID INTERVAL FORMAT");
  }
  else  {                                /* if inputs are valid then          */
    ...
    AIC_IECR = (1<<TC0_ID);              /* enable interrupts again           */
  }
 01000F1C  B004      ADD         R13,#0x10
 01000F1E  BD30      POP         {R4-R5,PC}
 01000F20            ; END 'set_interval?T'
}

The location list is specified by using the DW_AT_frame_base attribute value:

000b0e: Abbrev=13 DW_TAG_compile_unit(0x11)         // the enclosing compile unit
  000B0F:   DW_AT_name   'Mcommand.c'
  000B1A:   DW_AT_producer   'Keil CA Compiler'
  000B2B:   DW_AT_stmt_list   0x000001A5
  000B2F:   DW_AT_low_pc   0x01000D70               // low_pc: adjust value for the location list
  000B33:   DW_AT_high_pc   0x01000F20
  ......
  000c10: Abbrev=16 DW_TAG_subprogram(0x2E)
  000C11:   DW_AT_sibling   DW_FORM_ref4 0x18B (0xC8E)
  000C15:   DW_AT_name   'set_interval'
  000C22:   DW_AT_external   0x01
  000C23:   DW_AT_low_pc   0x01000E6C
  000C27:   DW_AT_high_pc   0x01000F20
  000C2B:   DW_AT_frame_base   0x0000011B           // Refers to .debug_loc [0x11B]
  000C2F:   DW_AT_type   DW_FORM_ref_addr 0x332
  ......

The corresponding debug_loc entries are as follows:

*** Section-Dump of '.debug_loc', pos=0x4F7A, size=1340
  ...
  00011B:  beg=0x0000FC, end=0x0001B0, DW_OP_reg  [R13]    // local frame starts at @SP+#0
  000126:  beg=0x000104, end=0x000110, DW_OP_breg [R13+4]  // local frame starts at @SP+#4
  ...

Please note that the debug_loc ranges (beg ... end) are relative to the lowest address of the enclosing compilation unit. Therefore, the 'beg' and 'end' values need to be adjusted by simply adding the 'low_pc'.  In our example, the compile unit 'Mcommand.C' contains the subprogram 'set_interval()' and the low_pc value of the compile unit is 0x1000D70.  This value must be added to the 'beg' and 'end' values to give the absolute physical address ranges.

Section .debug_frame

With the debug_frame entries, the current location of the function return address can be obtained.  This feature is typically used in debuggers to perform a 'Step out of current function'.  Depending on the current address within a function, the return address is either contained in the link register (R14 alias LR) or somewhere on the stack. The purpose is to retrieve the return address at any point within a function. In addition, the implementation also contains the start of local frame and the current stack depth.

The implementation uses two extra 'virtual' registers given to the DW_CFA_def_cfa attribute.  The registers R257 and R256 specify the stack depth and start of local frame:

Format Description
Reg=R14, Offs=0x0000 return address is @current frame base reg + 0x00 on Stack
Reg=R14, Offs=0x0010 return address is @current frame base reg + 0x10 on Stack
Reg=R257, Offs=0x0014 R257 used to give the current stack depth (0x14 in this example)
Reg=R256, Offs=0x0014 R256 used to give the start of the local frame (@current frame base reg + 0x14 in this example)

The current frame base register (normally 'SP' alias R13) is actually specified per function via '.debug_loc'.

The return address register is defined in a Common Information Entry, for example:

   CIE 0x000288: Len=16, CIE_id=0xFFFFFFFF, Vers=1
     Augment: ''
     CodeAlign: 1
     DataAlign: -4
     ReturnReg: R14       // register holding the return address

If the return address register is saved on the stack, then this situation is recorded with a frame description entry, for example:

   FDE 0x000327: Len=19, CIE=0x288, InitLoc=0x1000C74, Range=0x54    // Range: 0x1000C74 ... 0x1000C74+0x54
     DW_CFA_def_cfa Reg=R14, Offs=0x10                                                     // return address can be found @frame-base-reg + 0x10 on stack.

Example of a frame description for 'start of the local frame':

    DW_CFA_def_cfa Reg=R256, Offs=0x14  // Reg 256 denotes 'local frame offset', 0x14 bytes above the current value of the frame pointer

For example, the following frame description entry specifies the stack depth to be 4:

    DW_CFA_def_cfa Reg=R257, Offs=0x4    // Reg 257 denotes 'stack depth',  Offset means 'stack depth'


Example: Code example, only the instructions modifying the SP value and their successor are shown in the code. RA denotes the return address, LFS denotes the start of the local frame (the automatics) relative to the current frame pointer:

01000ADC          main:
01000ADC  B500      PUSH        {LR}              // RA := R14 (alias LR), LFS := <invalid>
01000ADE  B084      SUB         R13,#0x10         // RA := @SP+#0,  LFS := <invalid>
01000AE0  497C      LDR         R1,[R15,#496]     // RA := @SP+#16, LFS := @SP+#0
 .....
01000C00  B085      SUB         R13,#0x14         // RA := @SP+#16
01000C02  4669      MOV         R1,R13            // RA := @SP+#36, LFS := @SP+#20
 .....
01000C12  B005      ADD         R13,#0x14         // RA := @SP+#36, LFS := @SP+#20
01000C14  484A      LDR         R0,[R15,#296]     // RA := @SP+#16, LFS := @SP+#0
 .....
01000C60  B085      SUB         R13,#0x14         // RA := @SP+#16, LFS := @SP+#0
01000C62  4669      MOV         R1,R13            // RA := @SP+#36, LFS := @SP+#20
 .....
01000C72  B005      ADD         R13,#0x14         // RA := @SP+#36, LFS := @SP+#20
01000C74  4830      LDR         R0,[R15,#192]     // RA := @SP+#16, LFS := @SP+#0
 .....
01000CC6  B004      ADD         R13,#0x10         // RA := @SP+#16, LFS := @SP+#0
01000CC8  BD00      POP         {PC}              // RA := @SP+#0,  LFS := <invalid>
01000CCA            ; END 'main'

This is the generated frame information:

CIE 0x000288: Len=16, CIE_id=0xFFFFFFFF, Vers=1
  Augment: ''
  CodeAlign: 1
  DataAlign: -4
  ReturnReg: R14
  DW_CFA_def_cfa Reg=R13, Offs=0x0

FDE 0x00029C: Len=12, CIE=0x288, InitLoc=0x1000ADC, Range=0x1EE
FDE 0x0002AC: Len=19, CIE=0x288, InitLoc=0x1000ADE, Range=0x1EA
DW_CFA_def_cfa Reg=R257, Offs=0x4
DW_CFA_def_cfa Reg=R14, Offs=0x0

FDE 0x0002C3: Len=19, CIE=0x288, InitLoc=0x1000AE0, Range=0x1E8
DW_CFA_def_cfa Reg=R257, Offs=0x14
DW_CFA_def_cfa Reg=R14, Offs=0x10

FDE 0x0002DA: Len=23, CIE=0x288, InitLoc=0x1000C02, Range=0x12
DW_CFA_def_cfa Reg=R257, Offs=0x28
DW_CFA_def_cfa Reg=R14, Offs=0x24
DW_CFA_def_cfa Reg=R256, Offs=0x14

FDE 0x0002F5: Len=19, CIE=0x288, InitLoc=0x1000C14, Range=0xB4
DW_CFA_def_cfa Reg=R257, Offs=0x14
DW_CFA_def_cfa Reg=R14, Offs=0x10

FDE 0x00030C: Len=23, CIE=0x288, InitLoc=0x1000C62, Range=0x12
DW_CFA_def_cfa Reg=R257, Offs=0x28
DW_CFA_def_cfa Reg=R14, Offs=0x24
DW_CFA_def_cfa Reg=R256, Offs=0x14

FDE 0x000327: Len=19, CIE=0x288, InitLoc=0x1000C74, Range=0x54
DW_CFA_def_cfa Reg=R257, Offs=0x14
DW_CFA_def_cfa Reg=R14, Offs=0x10

FDE 0x00033E: Len=19, CIE=0x288, InitLoc=0x1000CC8, Range=0x2
DW_CFA_def_cfa Reg=R257, Offs=0x4
DW_CFA_def_cfa Reg=R14, Offs=0x0

The same information can be represented in a more compact form and yields the following information:

Beg=0x1000ADC, End=0x1000CCA, SP=R13, Depth:=0 LR:=R14                       // SP:=R13,   RA in R14(LR)
Beg=0x1000CC8, End=0x1000CCA, SP=R13, Depth:=4 LR:=[SP+0]                    //            RA @SP+#0
Beg=0x1000C74, End=0x1000CC8, SP=R13, Depth:=20 LR:=[SP+16]                  // depth=20,  RA @SP+#16
Beg=0x1000C62, End=0x1000C74, SP=R13, Depth:=40 LR:=[SP+36] Locals:=[SP+20]  // depth=40,  RA @SP+#36, LFS @SP+#20
Beg=0x1000C14, End=0x1000CC8, SP=R13, Depth:=20 LR:=[SP+16]                  // ...
Beg=0x1000C02, End=0x1000C14, SP=R13, Depth:=40 LR:=[SP+36] Locals:=[SP+20] 
Beg=0x1000AE0, End=0x1000CC8, SP=R13, Depth:=20 LR:=[SP+16] 
Beg=0x1000ADE, End=0x1000CC8, SP=R13, Depth:=4 LR:=[SP+0]

The frame base register 'SP' is actually specified via '.debug_loc' per function.  The start of the local frame can also be obtained via '.debug_loc'.

Note:

Contact Details

If you experience any problems or have any questions about this Application Note, contact one of our distributors or offices for assistance.

In the USA...

Keil Software, Inc.
1501 10th Street, Suite 110
Plano, TX  75074
USA

800-348-8051 - Sales
972-312-1107 - Support
972-312-1159 - Fax

sales.us@keil.com
- Sales E-Mail
support.us@keil.com
- Support E-mail 
  

In Europe...

Keil Elektronik GmbH
Bretonischer Ring 15
D-85630 Grasbrunn
Germany

+49 89 456040-0 - Sales
+49 89 456040-24 - Support
+49 89 468162 - Fax

sales.intl@keil.com
- Sales E-Mail
support.intl@keil.com
- Support E-Mail
  


Copyright © Keil Software, Inc. and Keil Elektronik GmbH.
All rights reserved.
Visit our web site at www.keil.com.