Debugging#
This page describes the debug information EmbCC produces: the -g
options, the DWARF it emits and what that DWARF describes, which targets
and object formats support it, how -g and optimization interact, how
EmbLD carries debug information into a linked image, and how to debug a
program with gdb, lldb, a QEMU gdb stub or EmbDBG. It is written for
people who compile code with EmbCC and want to debug it.
Producing debug information#
-g#
Emit DWARF version 4 debug information into the object file. Off by default.
The output is deterministic. The compilation directory is recorded as
., not as the directory the compiler ran in, and the source file name
exactly as it was given on the command line. Compiling the same file
under the same relative name, with the same options, gives the same
bytes in any directory; an absolute file name is recorded as such.
-g can be combined with any -O level, and it never changes the
generated code (see -g does not change the generated
code). See
Optimized code for what a debugger can rely on above
--opt=none and --opt=debug.
-ggdb, -g1, -g2, -g3, -gdwarf, -gdwarf-2, -gdwarf-3, -gdwarf-4#
Each of these is the same as -g. EmbCC emits one kind of debug
information, DWARF 4, at one level of detail, so a level or a version
that asks for no more than that is accepted. In particular -g1 does
not limit the output to line tables, and -g3 does not add macro
information.
-gdwarf-5, -gsplit-dwarf, -gz#
Refused, because EmbCC cannot produce what they ask for:
embcc: error: -gdwarf-5 is not supported; EmbCC emits DWARF 4, uncompressed and in one piece
Any other -gdwarf-N gets the same message.
Options that are not accepted#
-g0, -gline-tables-only, -gcolumn-info, -gno-..., -gz=...
and -fdebug-prefix-map=OLD=NEW are not recognized:
embcc: error: unknown argument '-g0'
To turn debug information off, leave out -g. No prefix map is needed
for reproducible builds, because no directory is recorded.
What the debug information describes#
Sections#
An object compiled with -g has these DWARF sections, each with its
relocation section:
| Section | Contents |
|---|---|
.debug_abbrev |
Abbreviations for .debug_info |
.debug_info |
One compile unit: functions, parameters, local variables, types |
.debug_line |
The line-number table |
.debug_frame |
Call frame information, on Thumb and RISC-V (see Call frames and unwinding) |
.debug_ranges is added when the unit's code is in several sections.
They are not allocated: they occupy no memory in the running program.
EmbCC emits no .debug_str, .debug_aranges, .debug_loc or
.debug_macro.
The 32-bit DWARF format is used. The address size is the target's
pointer size: 8 on x86-64, AArch64 and RV64, 4 on Thumb and RV32. AVR's
is 4, as avr-gcc's is, although its pointers are 2 bytes: a code address
there is a byte address in flash, and a data address is 0x800000 plus
the SRAM address (see Targets).
Compile unit#
| Attribute | Value |
|---|---|
DW_AT_producer |
EmbCC |
DW_AT_language |
DW_LANG_C99, for C and for C++ (C++ is compiled through C) |
DW_AT_name |
The input file name as given on the command line |
DW_AT_comp_dir |
. |
DW_AT_low_pc, DW_AT_high_pc |
The unit's code |
DW_AT_stmt_list |
The line table |
Line table#
The line table maps each instruction address to a source line. Each
function's first row is at its entry address, with the line the
function is declared on; the first row of its body carries
prologue_end, which is where a debugger puts break FUNCTION. A new
row starts wherever the source line changes. Every row is a statement
boundary (is_stmt). Columns are not recorded.
The table has one file entry, the main source file. Code that comes from
a header, such as a static inline function, is attributed to the main
file with the header's line numbers, so a debugger shows the wrong
source text for it.
Functions#
Each function defined in the unit is a DW_TAG_subprogram with its
name, return type and address range. There is no
DW_AT_external, DW_AT_decl_file or DW_AT_decl_line, and no
description of inlined calls (DW_TAG_inlined_subroutine): code inlined
at --opt=speed is part of its caller.
The frame base, against which every variable's location is given:
| Target | DW_AT_frame_base |
|---|---|
| x86-64 | DW_OP_reg6 (rbp) |
| AArch64 | DW_OP_reg29 (x29) |
| Thumb | DW_OP_breg13 0 (sp); DW_OP_breg7 0 (r7) in a function with alloca, a variable-length array or an over-aligned local, which addresses its frame from r7 |
| RISC-V | DW_OP_breg2 0 (sp); DW_OP_breg8 0 (s0) in such a function |
| AVR | DW_OP_breg28 0 (Y, the pair r28:r29), which the prologue points one byte below the frame: a variable at DW_OP_fbreg 5 is the one the code addresses as Y+5 |
Variables#
Every parameter and every local variable of a function is described,
with its name, its type and a location. The location is a single offset
from the frame base (DW_OP_fbreg) when the variable lives in its
stack slot, and empty (<optimized out>) when the optimized code keeps
it somewhere else (see Optimized code). There are no
location lists and no register locations yet.
An array or structure aligned beyond what the stack guarantees (for
example char buf[64] __attribute__((aligned(64)))) is stored in a
block carved from the stack at function entry, and its slot holds the
block's address. Its DWARF entry describes that slot, so its type is a
pointer to the declared type (unsigned char (*)[64]), and the
debugger shows the address; dereference it to see the contents.
Variables declared in inner blocks are listed directly under the
function, with no DW_TAG_lexical_block: the debugger sees all of a
function's variables at once, including ones whose block has not been
entered or has ended.
Each global variable the unit defines is a DW_TAG_variable child of
the compile unit, with its name, its type and a DW_OP_addr location
relocated against its own symbol. Thread-local variables, static local
variables and variables only declared (extern) are not described; a
static local is in the symbol table as FUNCTION.NAME (f.cnt), so
a debugger can find its address but not its type.
Types#
| Type | Described as |
|---|---|
Integer and character types, float, double, long double |
Base type with name, size and encoding |
_Bool |
Unsigned base type of size 1 |
_Complex types |
Complex-float base type |
| Pointers | Pointer to the described type |
| Arrays | Array with its upper bound; an array of unknown size or a variable-length array has none |
struct, union |
Name, size, and each member with its offset; bit-fields with bit offset and width |
enum |
Its underlying type: int, or the wider integer type an enumeration takes when a value does not fit in int; enumerators are not described |
typedef names |
The type they name; the typedef name itself is not described |
const, volatile |
Not described; the unqualified type is used |
| Pointers to functions | Pointer to void |
Support by target and object format#
| Target | -g |
Notes |
|---|---|---|
| x86-64 (ELF) | Yes | Tested with gdb under QEMU. |
| AArch64 (ELF) | Yes | Tested with gdb under QEMU. |
| Thumb (Cortex-M) | Yes | |
| RISC-V, RV32 and RV64 | Yes | Tested with gdb under QEMU (RV32). See Known problems. |
| AVR | Yes | Tested with gdb and with EmbDBG under QEMU (-M uno). |
| Any Darwin target (Mach-O) | No | Compiled without debug information, with a warning. |
| Any Windows target (COFF) | Refused |
For a Darwin target, -g gives a warning and the object has no debug
information; the code is the same either way. Its DWARF belongs in a
__DWARF segment that EmbCC does not write yet:
embcc: warning: -g: no debug information for aarch64-apple-darwin yet; d.c is compiled without it
For a Windows target the compilation stops:
embcc: d.c: error: -g is not supported for a Windows target yet: its debug information goes in CodeView records this does not write, and emitting DWARF under a COFF name would be worse than refusing
A unit whose code is in more than one section, by
--sections=functions or __attribute__((section("NAME"))), gives its
compile unit a DW_AT_ranges list (in .debug_ranges) instead of one
DW_AT_low_pc..DW_AT_high_pc span, and each function's addresses are
relocated in its own section, so the debug information stays right
wherever the linker puts each one -- and when --gc-sections drops one,
its addresses resolve to 0, as with GNU ld.
With -S, the assembly output contains no debug information, with or
without -g.
Call frames and unwinding#
A debugger finds a function's caller in one of these ways, depending on the target:
- Thumb and RISC-V:
.debug_frame. With-g, every function has call frame information in.debug_frame: where the caller's frame is, and where the return address and each saved register went, step by step through the prologue. On Thumb that ispush,vpush, the frame'ssub sp, andr7as the frame base in a function withallocaor a variable-length array (r5on ARMv6-M). On RISC-V it is theaddi sp, the stores ofraand the savedsandfsregisters, ands0as the frame base. A debugger unwinds by it at every level, through floating-point frames too, and reads a caller's saved registers back, float registers included.tests/golden/debug-frame.shbacktraces through such frames with gdb. A RISC-V interrupt handler has no entry. - x86-64 and AArch64: frame records. At
--opt=noneand--opt=debugevery function keeps a frame record (rbp, orx29andx30), so the chain of frame pointers leads from each frame to its caller. Optimized, a leaf may have no frame record; the debugger then analyzes the function's prologue. .eh_frame. When unwind tables are enabled, each function has an.eh_frameentry describing its frame, which a debugger also uses. Unwind tables are on for C++, with--unwind-tables,-fasynchronous-unwind-tablesor--exceptions, and by default for a Linux target.--no-unwind-tablesand-fno-asynchronous-unwind-tablesturn them off. They describe frames correctly only on x86-64 and AArch64 (see Known problems).- The other embedded targets. No
.debug_frameyet; the debugger analyzes the function's prologue. - AVR.
Yis a frame pointer, but nothing records where the return address is above it; gdb analyzes the prologue, which follows avr-gcc's.
Frame pointers#
--no-frame-pointer and --frame-pointer are accepted and
have no effect.
| Target | Without -g |
With -g |
|---|---|---|
| x86-64 | rbp frame in every function at --opt=none; from --opt=basic, frameless leaf functions and push-only frames have none |
rbp frame in every function |
| AArch64 | x29/x30 frame record at --opt=none; from --opt=basic, frameless leaf functions have none |
Frame record in every function |
| Thumb | None (r7 only in a function with alloca, a variable-length array, or a local aligned beyond 8 bytes) |
Same |
| RISC-V | None (s0 only in a function with alloca, a variable-length array, or a local aligned beyond 16 bytes) |
Same |
See Optimization for AVR.
-g does not change the generated code#
The code, data, symbols and relocations of an object are the same with
and without -g, at every optimization level and on every target. -g
only adds the debug sections. So the image you debug is the image you
ship: build once with -g, flash the stripped copy, and debug it with
the unstripped ELF.
tests/golden/g-same-code.sh checks this. It compiles programs for
every target at --opt=none, --opt=basic, --opt=speed, --opt=size and --opt=debug, with and without
-g, and requires the two objects to be identical once their debug
sections are stripped.
Optimized code#
At --opt=none and --opt=debug, every variable lives in its stack slot for its whole
lifetime, and the debug information names that slot. --opt=debug is --opt=basic
with this one difference: the optimizer still removes work, but every
source variable stays in memory, where a debugger reads it.
At --opt=basic and above, the optimizer keeps many variables in registers, or
turns them into temporaries that no longer exist as one value. Such a
variable's location is empty, which the debugger prints as
<optimized out>. Variables that remain in their slot have that slot's
location, which is always right:
- A local the code still reads or writes in memory is in its slot.
- A parameter that has a slot is stored there on entry, so the slot holds the value it arrived with. Once the function assigns it after the optimizer has taken it out of memory, its location is empty instead.
- A
volatilelocal is the exception at every level: it is never promoted, forwarded or given a register, so its slot always holds its current value.
There are no location lists yet: a variable in a register is described as optimized out, rather than as being in that register.
Also, at --opt=basic and above:
- Inlined functions have no frame of their own: a backtrace shows the caller, and the line table moves between the caller's lines and the inlined function's lines.
- Tail calls replace the caller's frame with the callee's, so the caller is missing from a backtrace.
- Loops may be rotated, unrolled or vectorized, so stepping visits the loop's lines in an order that does not match the source.
Line-table breakpoints and backtraces remain usable in optimized code.
For variable values, debug at --opt=none or --opt=debug, or use
embcc inspect ir
to see what became of a variable.
Debug information in a linked image#
EmbLD merges the debug sections of its input objects into the output:
Each
.debug_*section of every input object, including archive members, is appended to the output section of the same name, and the relocations into it are resolved. The result is one.debug_info, one.debug_abbrevand one.debug_line, containing one compile unit per object that had debug information.The debug sections are not allocated and lie outside every loadable segment, so they take no flash and no RAM. The symbol table is kept in the image in the same way.
EmbLD also writes a native debug file,
OUTPUT.embdbg, built from the objects named on its command line that have a line table (archive members are not included). It reports this as:embld: wrote fw.elf.embdbg (debug info from 1 object)No
.embdbgfile is written when no input has debug information. This is the split recorded in design decision D-010: the compiler emits relocatable DWARF, and the linker, which knows the final addresses, produces the absolute-addressed native form that EmbDBG reads.
EmbLD has no option to strip debug information (-s, -S and
--strip-debug are refused as unknown options). Given to the driver as
-Wl,-s or -Wl,--strip-debug, they are accepted and change nothing:
the debug sections stay in the image. Strip a finished image
with a separate tool if needed, for example
llvm-objcopy --strip-debug fw.elf. An image converted to a raw binary
for flashing does not contain the debug sections in any case.
Using a debugger#
The DWARF EmbCC emits is read by gdb and lldb. The test suite checks it
with llvm-dwarfdump --verify and with gdb sessions against QEMU on
x86-64, AArch64, RISC-V and AVR.
A program on the host#
For a Linux target, compile and link with -g and run the debugger on
the executable:
embcc --target=x86_64-linux-gnu -g --opt=none prog.c -o prog
gdb ./prog
Darwin and Windows targets do not support -g.
A board under QEMU#
QEMU's gdb stub lets a debugger control a bare-metal image. Start QEMU
halted (-S) with the stub listening on a TCP port (-gdb tcp::PORT),
then connect:
embcc --target=riscv32-unknown-elf -g --opt=none -c prog.c -o prog.o
# link prog.o with startup code into prog.elf (see Embedded programming)
qemu-system-riscv32 -M virt -bios none -nographic -kernel prog.elf -S -gdb tcp::3333 &
gdb prog.elf -ex 'set architecture riscv:rv32' -ex 'target remote :3333'
Then use gdb as usual: break scale, continue, info args, next,
info locals, bt.
| Target | QEMU used by the test suite | gdb set architecture |
|---|---|---|
| x86-64 | qemu-system-x86_64 |
i386:x86-64 |
| AArch64 | qemu-system-aarch64 -M virt -cpu cortex-a72 |
aarch64 |
| Thumb, Cortex-M3 | qemu-system-arm -M lm3s6965evb -cpu cortex-m3 |
arm |
| Thumb, Cortex-M4F | qemu-system-arm -M mps2-an386 |
arm |
| Thumb, Cortex-M33 | qemu-system-arm -M mps2-an505 |
arm |
| RISC-V | qemu-system-riscv32 / qemu-system-riscv64 -M virt -bios none |
riscv:rv32 / riscv:rv64 |
| AVR | qemu-system-avr -M uno -bios IMAGE |
avr |
On x86-64, the test suite uses hardware breakpoints (hbreak), because
the boot loader writes the code after the debugger has attached and
would overwrite a software breakpoint.
For a real board, an OpenOCD or probe gdb server takes QEMU's place:
connect to its port with target remote.
How to build and run images for each board is described in Embedded programming.
EmbDBG#
EmbDBG is EmbCC's own debugger. It reads the DWARF in an image, or the
.embdbg file EmbLD writes, without gdb: it lists functions and line
tables, symbolizes addresses, prints backtraces and source context with
the variables in scope, analyzes crash dumps, and converts DWARF to the
.embdbg form. Its remote command speaks the gdb remote protocol, so
it can drive QEMU's gdb stub or OpenOCD directly:
qemu-system-riscv64 -M virt -bios none -nographic -m 8 -kernel fw.elf -S -gdb tcp::3333 &
embdbg fw.elf remote :3333
remote supports x86-64, AArch64, Thumb, RV32, RV64 and AVR. See
EmbDBG for its commands.
Known problems#
These are defects in the current implementation, not intended behavior.
- Unwind tables on Thumb, RISC-V and AVR.
--unwind-tables(and C++ at RV64, the one of these targets that compiles C++) produce an.eh_framein the x86-64 layout on these targets, which does not describe their frames. Do not rely on it for unwinding. On MIPS32 the options are refused by name instead. - Header files. Code from a header is attributed to the main source file, as described in Line table.