Debugging formats DWARF and STAB (original) (raw)

Comments

Overview

The debugging information stored within a binary helps a programmer or debugger locate problem areas in a program, and find the stack trace when an error happens in an application. This information is also used by popular debugger tools, such as gdb and wdb. A debugging data format is a means of storing information about a compiled program for use by high-level debuggers. Modern debugging data formats store enough information to allow source-level debugging.

The debugger and debug information is available or present in all software components, however, not all users are aware of what the debug data consists of and how the data is stored.

If you work with debugging formats, are a C/C++ programmer with relevant experience on any UNIX platform and have basic knowledge of compilers and debuggers, or are in the process of choosing formats then this article is for you.

We cover extracting debugging information from two popular debugging formats, STAB and DWARF. We discuss the advantages of one format over the other. Additionally, we provide an example application to extract information from a binary in a DWARF format to assist in experimentation and understanding.

The DWARF and STAB formats are the most widely used executable and linking format (ELF).

Programmers can make use of this document in following situations:

Debugging formats

A debugging data format is a means of storing information about a compiled computer program for use by high-level debuggers. Modern debugging data formats store enough information to allow source-level debugging.

There are many debugging formats available. STAB, COFF, PECOFF, OMF, IEEE695, and three versions of DWARF, are a few common choices. Let's compare STAB and DWARF, and discuss extracting debug information from both formats.

STAB

The traditional format for debugging information is called STAB (symbol table). STAB information is saved in the .stab and.stabstr. sections of an ELF file.

The STAB debug format is a poorly documented, semi-standard format for debugging information in COFF and ELF object files. The debugging information is stored as part of the object file's symbol table and thus is limited in complexity and scope. Despite this, STAB is a common debugging format on older UNIX and compatible systems.

For some object file formats, the debugging information is encapsulated in assembler directives known collectively as STAB directives, which are interspersed with the generated code. STABs are the native format for debugging information in the a.out and XCOFF object file formats. The GNU tools can also emit STABs in the COFF and ECOFF object file formats.

The assembler creates two custom sections:

The byte order of the STABs binary data depends on the object file format.

Structure of a program

The elements of the program structure that STABs encode include the name of the main function, the names of the source and include files, the line numbers, procedure names and types, and the beginnings and ends of blocks of code.

Most languages allow the main program to have any name. TheN_MAIN STAB type tells the debugger the name that is used in this program. Only the string field is significant; it is the name of a function that is the main program. Most C compilers do not use this STAB (they expect the debugger to assume that the name is main), but some C compilers emit an N_MAIN STAB for the main function.

Before any other STABs occur, there must be a STAB specifying the source file. This information is contained in a symbol of STAB typeN_SO. The string field contains the name of the file. The value of the symbol is the start address of the portion of the text section corresponding to that file.

An N_SLINE symbol represents the start of a source line. Thedesc field contains the line number and thevalue contains the code address for the start of that source line. On most machines the address is absolute; for STABs in sections it is relative to the function in which the N_SLINE symbol occurs.

All of the following STABs normally use the N_FUN symbol type for functions.

Other common sections include:

DWARF

DWARF (debug with arbitrary record format) is a more recent format for ELF files. It was created to overcome shortcomings in STAB, allowing for more detailed and compact descriptions of data structures, data variable movement, and complex language structures, such as in C. The debugging information is stored in sections in the object file. It is a compact representation of the relationship between the executable program and the source in a way that is reasonably efficient for a debugger to process.

The ownership relation of debugging information entries is achieved naturally because the debugging information is represented as a tree. The nodes of the tree are the debugging information entries themselves. The child entries of any node are exactly those debugging information entries owned by that node. The tree itself is represented by flattening it in prefix order. Each debugging information entry is defined either to have child entries or not to have child entries. If an entry is defined not to have children, the next physically succeeding entry is the sibling of the prior entry. If an entry is defined to have children, the next physically succeeding entry is the first child of the prior entry. Additional children of the parent entry are represented as siblings of the first child. A chain of sibling entries is terminated by a null entry.

Debugging information entry

The basic descriptive entity in DWARF is the debugging information entry (DIE). A DIE has a tag that specifies what the DIE describes and a list of attributes that fills in details, and further describes the entity. A DIE (except for the top most) is contained in, or owned by, a parent DIE and may have sibling DIEs or children DIEs. Attributes may contain a variety of values: constants (such as a function name), variables (such as the start address for a function), or references to another DIE (such as for the type of a function's return value).

Compilation unit

A compilation unit (CU) is a type of DIE. Most interesting programs consists of more than a single file. Each source file that makes up a program is compiled independently and then linked together with system libraries to make up the program. DWARF calls each separately compiled source file a compilation unit. The DWARF data for each compilation unit starts with a CU DIE. This DIE contains general information about the compilation, including: the directory and name of the source file, the programming language used, and a string that identifies the producer of the DWARF data, and offsets into the DWARF data sections to help locate the line number and macro information. If the CU is contiguous (that is, it is loaded into memory in one piece) then there are values for the low and high memory addresses for the unit. This makes it easier for a debugger to identify which compilation unit created the code at a particular memory address. The CU DIE is the parent of all of the DIEs that describe the compilation unit.

A CU typically represents the text and data contributed to an executable by a single relocatable object file. It may be derived from several source files, including pre-processed "include files.’"

The CU entry may have the following attributes:

Other types of DIE include:

The subprogram DIE owns DIEs describing the subprogram. The parameters that may be passed to a function are represented by variable DIEs that have the variable parameter attribute.

Line number table

The DWARF line table contains the mapping between the source lines (for the executable parts of a program) and the memory containing the code corresponding to the source. In the simplest form, this can be looked at as a matrix with one column containing the memory addresses and another column containing the source triplet (file, line, and column). If you want to set a breakpoint at a particular line, the table gives you the memory address to store the breakpoint instruction. Conversely, if your program has a fault (for example, using a bad pointer) at some location in memory, you can look for the source line that is closest to the memory address.

A program is described as a tree with nodes representing the various functions, data, and types in the source in a compact language and machine independent fashion. The line table provides the mapping between the executable instructions and the source that generated them.

Getting information from STAB and DWARF formats

STAB and DWARF are two different ways of representing debug information for binaries. The following section describes how the debug information can be captured from STAB and DWARF sections.

The approach followed is to scan each binary file for debug information and determine which file names contribute to that binary. For each file, all function names and line numbers (first line number and last line number) for each function is captured. A linked list can be used to store this information.

Information from STAB format

The binary file is scanned through and the stab sections in the file are checked. When N_SOL is found, it represents the name of the file that is included in this binary and from this symbol the file name can be stored. After the file name is obtained, the next step should be to get all the functions that are present in this file. N_FUN represents a function in the stab section. If we get this symbol after anN_SOL symbol (file name), it means a function is encountered for the above file name. From N_FUN, the function name and details can be stored.

After this, when an N_LBRAC symbol is returned, it represents a left brace and the start of the function. Now we can conclude that the above function starts here. Following this symbol would beN_SLINE or N_XLINE, which denote lines in the function. The first and last line numbers, or if required, all line numbers can be stored from this symbol.

After a few entries for line numbers (N_SLINE orN_XLINE ) the N_RBRAC symbol would be captured. When this symbol is encountered, it denotes the end of the function. At this point, one set of information is captured, that is, for a binary, one of the included file names and one function and the details of that function. Similarly, we can obtain all the functions in that included file and the details of all the included files for that binary. The above steps need to be carried out for retrieving the full information about a binary file.

The symbol table stores the details of all the symbols (functions) in the binary. Listing 1 is a diagrammatic representation of gathering information from STAB format for a simple function.

Listing 1. Simple program showing STAB types

abcd.c ----> Symbol is N_SOL int function_name(int a, int b) ------> Symbol is N_FUN { ----> Symbol is N_LBRAC int i; ----->Symbol is N_SLINE i = a+b; return i; } -----> Symbol is N_RBRAC

A flow chart representing the algorithm for getting the information from the STAB format follows in Figures 1 and 2.

Start by getting the next stab section, then:

Figure 1. Flow chart to extract debug information from STAB

Flowchart showing the algorithm for extracting debug information from STAB

View image at full size

Flowchart showing the algorithm for extracting debug information from STAB

Figure 2. Continuation of flow chart to extract debug information from STAB

Continuation of flowchart showing the algorithm for extracting debug information from STAB

View image at full size

Continuation of flowchart showing the algorithm for extracting debug information from STAB

Information from DWARF format

In DWARF format all information is in a tree format, as shown in Listing 2.

Listing 2. DWARF Tree Format

COMPILE_UNIT

: <0>< 11> dw_tag_compile_unit 0 DW_AT_stmt_list 0 DW_AT_HP_actuals_stmt_list 0 DW_AT_macro_info 0 DW_AT_name /usres/mainsoft-v52-orig/mw/init.C DW_AT_producer DW_AT_lanugage DW_LANG_C_plus_plus DW_AT_HP_proc_per_section yes(1) DW_AT_comp_dir /home/yadvecha/ISLHPIA02/RTWIN/730/Source

LOCAL_SYMBOLS: <1>< 109> DW_TAG_module DW_AT_name /users/mainsoft-v52-orig/mw/init.C DW_AT_sibling <365> <2>< 149> DW_TAG_class_type DW_AT_name setInFunc DW_AT_declaration yes(1) DW_AT_sibling <333> <3>< 166> DW_TAG_subprogram DW_AT_name ~setInFunc DW_AT_HP_linkage_name _ZN9setInFuncD1Ev DW_AT_declaration no DW_AT_external yes (1) DW_AT_accessibility DW_ACCESS_public DW_AT_sibling <229> <4>< 203> DW_TAG_formal_parameter DW_AT_name this DW_AT_artificial yes (1) DW_AT_location DW_OP_regx 34 DW_AT_type <365>

The symbol table section in DWARF is the same as in STAB. Extracting the debug information from DWARF works as follows:

Each DIE in the binary is parsed and when a CU DIE is found, the name of the CU is stored. This represents the name of the files that are included in this binary. The name of the CU is also stored to find the line number table for locating the line numbers for all the functions in this CU. When DW_TAG_subprogram DIE is captured after a CU DIE, this denotes that this subprogram belongs to the file that is captured under the CU DIE. When DW_TAG_subprogram is encountered, the details of the function are stored. From the attributes of the subprogram, get the low address and high address corresponding to this subprogram. This represents the addresses corresponding to the start and end of that subprogram. For calculating the line numbers corresponding to these addresses, get the line buffer from the debug_line section (line buffer) for the CU DIE. For each line in the line buffer for a CU, check for the low address match to find the first line number for the function. The last line number for the function is obtained from the debug_line section where the line number address is less than or equal to the high address of the function.

With this captured information, we can create a list of source file names, function names and the line numbers for the functions. The debugger uses these basic pieces of information for debugging purposes.

Listing 3 shows the DIE for a subprogram and how the line number is obtained from the line table.

Listing 3. DWARF line number mapping

DW_TAG_subprogram DW_AT_sibling = 10 DW_AT_external = 1 DW_AT_name = strndup DW_AT_prototyped = 1 DW_AT_type = 10 DW_AT_low_pc = 0 DW_AT_high_pc = 0x7b Address File Line Col

0x0 0 42 0
0x9 0 44 0 0x1a 0 44 0 0x24 0 46 0 0x2c 0 47 0 0x32 0 49 0 0x41 0 50 0 0x47 0 51 0 0x50 0 53 0 0x59 0 54 0
0x6a 0 54 0 0x73 0 55 0 0x7b 0 56 0 File 0: strndup.c

The start and end line numbers for this subprogram are 42 and 56, which correspond to addresses 0x0(DW_AT_low_pc) and0x7b(DW_AT_high_pc).

Figure 3 shows a flow chart representing the algorithm for getting the information from DWARF:

Start by initializing the DWARF to read information from it. If a DIE is not available then end. If a DIE is available:

Figure 3. Flow chart to extract debug information from DWARF

Flowchart showing the algorithm for extracting debug information from DWARF

View image at full size

Flowchart showing the algorithm for extracting debug information from DWARF

Which one should I use?

DWARF is a block-structured and extensible description of a program's source, and how it is translated into executable code. It is easy to add new descriptions or extend descriptions in DWARF. Additionally, DWARF is a more advanced format and has facilities for describing a more complex execution environment, such as discontiguous scopes, stack structures, and stack unwinding, which STAB cannot. STAB, on the other hand, is more traditional, and limited in it's expressive abilities. It depends on predefined symbol and type definitions and is not easily modified or extended.

Some of the major advantages of using DWARF are:

New applications using debug information are better served by DWARF. As mentioned, it provides more information and is easily extendible. If you are working on an application where the debug information is already stored in STAB, however, it is better to continue with STAB unless you have sufficient time to change the design and fully switch over to DWARF. As a general rule, DWARF is more desirable.

Going forward

The download example has an application (dwarfexample) that reads the important debug information in a DWARF format from a binary. A binary file (stacktrace) along with the source code (stack_tracing.c) is also provided so that the user can run the "dwarfexample" application using the binary and match the output of the application with the actual data in the source code (as in stack_tracing.c). Debug information displayed using this application includes the source files, which are included in the binary, and the functions details (function names and the line numbers) in each of the source files. You can edit the source code of the sample program (stack_tracing.c) to either add or delete functions or change the line numbers and observe that the output of the sample application when run using the new binary reflects the changes done in the sample program.

In the sample application (see Download) the output as of now displays files, functions, and line numbers present in the binary. You can extend this application to get more information such as inline subprograms, and call frame information, from the binary.

Downloadable resources

Subscribe me to comment notifications