Lab 3 - Debugging Assembly
Accept the Assignment
Accept the assignment on Github Classroom here.
Breaking the Compiler
This repository contains the hw3 stencil code, but we've introduced a bug into
the compiler's implementation of binary primitives. If you run dune test
,
you'll see that there's a failing test involving addition--take a look at the
test input, the interpreter and compiler outputs, and the relevant code in
lib/compile.ml
.
Finding the Problem
What's wrong with the compiler may not be immediately obvious, which is why we're going to use a tool called GDB to debug it. You may have already had some practice with GDB in a course like 33, but if not, you'll learn everything you need to know about it for CSCI 1260 in this lab.
First, we need to get a binary we can execute to demonstrate the bad behavior.
Create a new test called add.lisp with the program '(+ 1 (+ 2 3))'.
Compile the failing test with
dune exec ./bin/compile.exe -- examples/add.lisp out
. This will put an
assembly file add.lisp.s
and an executable add.lisp.exe
in the ./out
directory.
Working with GDB
To open the executable with GDB, run gdb out/add.lisp.exe
. Once opened, GDB
will present you with a prompt you can use to step through and introspect your
program. Before getting started, enter the command
set disassembly-flavor intel
to switch GDB to use the flavor of assembly we
use in the course.
Next, enter the command layout asm
. This will split the interface into two
horizontal panes, the bottom one displaying the prompt as before and the top one
displaying the assembly instructions making up the program. You can use the
arrow keys to navigate the program's instructions.
To tell GDB to run a program, enter the run
command. It'll output something
like the following:
Starting program: lab3/out/add.lisp.exe
4[Inferior 1 (process 1148955) exited normally]
This gives us the output of the program (4), but isn't terribly useful, since we
could have found that out by running the program normally. Instead, we'll add a
breakpoint, which tells GDB to pause execution of a program at a certain
point. For this example and for most of the course, the first breakpoint you'll
want to set is lisp_entry
, the section containing the instructions generated
by our compiler and linked into the runtime. To add this breakpoint, enter the
command b lisp_entry
(b
short for break
).
Now, enter the run
command again. You'll see a message like the following
stating that execution has been paused at the breakpoint.
Breakpoint 1, 0x0000000000401130 in lisp_entry ()
Note also that the first instruction of lisp_entry
is highlighted in the top
pane, indicating it is the next instruction that will be executed. To step
through instructions, enter si
(step instruction) and you'll see the
highlight shift down to the next instruction.
Inspecting Program State
At any point in time, you can inspect the state of the program in various ways.
One of the most useful for our purposes will be to inspect the state of
registers like rax
. To do so, enter info registers
(or i r
for short), and
GDB will show you the current values of each register.
You can also inspect values in memory using the x
command. For instance, to
inspect the value located 8 bytes below the stack pointer, enter:
x/d $rsp-8
This will output a value in decimal form, but there are other formatters you
can use like hex (x/h
). Note also the $
in front of rsp
--that tells
GDB to use the value contained in the rsp
register.
Task: Use GDB to step through the failing test and find the point at which things go wrong. Once you've found it, call a TA and show them how you worked through it. If you're having trouble check out some of the GDB tricks below.
GDB Tips and Tricks
- Pressing enter on a blank line will repeat the last command (this is
especially helpful to save you from typing
si
repeatedly). - If the graphical interface of
gdb
becomes distorted, enterctrl+l
to refresh it. - By default, the arrow keys will scroll the assembly window. To switch the
focus to the gdb prompt so you can use the arrow keys to select previous
commands, enter
focus cmd
. To return focus to the assembly window, typefocus asm
. - To continue execution after pausing at breakpoint, enter
c
orcontinue
. - To see all registers at all times, type
layout regs
.
div
To get more practice working with GDB, take a look at the division operator of HW3 (if you haven't already) and take a stab at implementing it in lab! As you work on it, try using GDB to trace through simple programs that make use of it, and keep an eye on the contents of the relevant registers.