In addition to debugging the low-level assembly, compilers can include debugging directives in your generated assembly code that will allow users to use gdb to step through a source program and view the values of variables from the perspective of the source code.
To enable source-level debugging, you must:
The details are below. After doing this, assemble your compiler’s output using gcc without the -g switch, and the debugging information will be placed in the executable for the debugger to use.
To get started, have a look at files in examples/codegen/debugdemo:
See the makedemo.sh
script for commands to assemble and link.
Consider:
.file "demo.dream"
.stabs "demo.dream",100,0,0,.Ltext0
.text
.Ltext0:
.stabs "int:t(0,1)=r(0,1);-2147483648;2147483647;",128,0,0,0
This “debugging header” ties the demo.s assembly file back to “demo.dream” source file. Your compiler should emit these lines at the top of your generated assembly file, substituting the name of the source .dream file in place of “demo.dream”.
Look at this declaration of the global x variable:
.comm x,4,4
.stabs "x:G(0,1)",32,0,0,0
The .comm directive is followed by a .stabs directive designating that x is a global int-type variable. By emitting this line, you give the debugger the information it needs to be able to show you the value of the variable when you hover the mouse pointer over the variable in the source code, or you add a watch on it, etc.
Note that you must not prefix your variable names with an underscore if you want nice integration with your debugger.
When you get around to implementing local variables, the debugging directive is a little different. Try compiling a C program with -gstabs -S flags and looking at the assembly output to see how to get debugger support for local variables.
For each method in your Dream program, emit a debug directive, like this:
# Line 5: start()
.global main
.stabs "main:F",36,0,0,main
main:
Insert the debug directives in between the .global
and the label for the function. This example shows how to do it for the start() method (which, in Phase 4, corresponds to the main() function).
Consider the following line in the original source file:
x := 5
Here’s the generated assembly code, including the debug directive:
# Line 7: x := 5
.stabn 68,0,7,.line7-main
.line7:
pushl $5
...
For each statement in your Dream source program, emit the following:
# Line 7: x := 5
.stabn 68,0,<line#>,.line<line#>-main
This directive tells the debugger which assembler instructions go with which source line. Example:
.stabn 68,0,7,.line7-main
Note: If you’re emitting code for some function other than main, you must use that function’s name instead of main. For example, if you’re emitting code for a method Point_setX, you would emit a line like this:
.stabn 68,0,7,.line7-Point_setX
.line7:
Now that you’ve done this work, invoke gcc without the -g switch:
gcc -m32 -g -c stdlib.c
gcc -m32 -c demo.s stdlib.o -o demo
Then start gdb:
gdb demo