CS31 Weekly Lab: Week 6

writing and compiling IA32, IA32 debugging

Week 6 lab topics:

  1. Writing IA32 assembly code in a .s file, compiling and running it.
  2. Tools for examining binary files
  3. gdb (and ddd) for examining binary executables

Get the files:
$ cd ~/cs31/inclass
$ cp -r ~mauskop/public/cs31/week06/ .
$ cd week06
$ ls
Makefile  mainprog.c  simplefuncs.c  variables.c

Compiling Phases and Assembly

First, let's open up simplefuncs.c in an editor.

We are going to look at how to use gcc to create an assembly version of this file and how to create an object (.o) file.

If you open up the Makefile you will see rules for building .s, .o and executable files from simplefuncs.c. We will be compiling to the 32-bit instruction set (the 32 in IA32), so we will use the -m32 flag to gcc:

gcc -m32 -S simplefuncs.c   # runs the assembler to create a .s text file
gcc -m32 -c simplefuncs.c   # compiles to a relocatable object binary file (.o) 
gcc -m32 -o simplefuncs simplefuncs.o  # creates a 32-bit executable file
To see the machine code and assembly code mappings in the .o file:
$ objdump -d simplefuncs.o
You can compare this to the assembly file:
$ cat simplefuncs.s

Writing IA32 code

variables.c contains a simple function that has some local variables and returns a value. mainprog.c contains code that makes a call to this function.

$ cat variables.c
$ cat mainprog.c
If you run make it will build an assembly code version of variables.c and then link it into the executable, mainprog. Look for this command:
$ gcc -m32 -S variables.c
Let's cat out the .s file an look at some of the instructions:
$ cat variables.s
One thing to note is where the local variables are on the stack (see that they are at addresses relative to %ebp). Notice that the return value is copied into register %eax.

Let's try running mainprog.

Next, modify variables.s so that the function will return a different value. Run make again to link in the new version of variables.s. Be careful not to modify variables.c or make will rebuild variables.s from it.


Debugging at the assembly code level

With gdb you can debug and trace through a program's execution at the assembly code level. This includes executing individual IA32 instructions, examining register values, and disassembling functions.

$ gdb simplefuncs
(gdb) break main
(gdb) break func1
(gdb) run
In gdb you can disassemble code using the disass command:
(gdb) disass main
You can set a breakpoint at a specific instruction:
(gdb) break *0x08048477   # set breakpoint at specified address 
And you can step or next through assembly instructions using ni or si (si steps into function calls, ni skips over them):
(gdb) ni	  # execute the next instruction then gdb gets control again 
(gdb) ni
(gdb) ni
(gdb) ni
(gdb) ni
(gdb) disass
(gdb) cont   # continue to next break point
Now we are at the call to func1, let's step into this function.
(gdb) si	  # step into instructions in the called function (func1)
(gdb) disass
(gdb) ni
(gdb) where
(gdb) disass
(gdb) cont

You can print out the values of individual registers like this:

(gdb) print $eax
Or the memory contents at a given address:
(gdb) p *(int *)($ebp + 8)
(gdb) x $ebp + 8
(gdb) x/d $ebp + 8   # x/d display as decimal value
You can view all register values:
(gdb) info registers
You can use the display command to automatically display values each time a breakpoint is reached:
(gdb) display $eax
(gdb) display $edx
You can use the examine command (x) to display the contents of a memory location. The memory address operand to (x) can be specified as the name of the register storing the address value or as an absolute memory address value. Here are some examples (x is shorthand for examine, and p is shorthand for the print command):
(gdb) x $esp-0x8  # see what p and x display for the same value
(gdb) p $esp-0x8

(gdb) p *(int *)($ebp-0x8)    # here is how to print value at memory location

(gdb) x $ebp-0x8              # or a much easier way using x
Here is an example of examining the contents at a memory location specifying the address in two different ways (the exact address value in the second depends on what $esp - 0x1c is, it can vary run to run):
(gdb) x $esp + 0x1c
(gdb) x 0xffffd2fc

ddd

We are going to try running this in ddd instead of gdb, because ddd has a nicer interface for viewing assembly, registers, and stepping through program execution:
$ ddd simplefuncs
The gdb prompt is in the bottom window.

Choose View->Machine Code Window to view the IA32 assembly code.

You can view the register values as the program runs (choose Status->Registers to open the register window).

More Info

  • Quick summary of some useful gdb commands for debugging at the assembly code level:
    $ gdb a.out
    (gdb) break main          
    (gdb) run  6              # run with the command line argument 6
    (gdb) disass main         # disassemble the main function
    (gdb) break sum           # set a break point at the beginning of a function
    (gdb) cont                # continue execution of the program
    (gdb) break *0x0804851a   # set a break point at memory address 0x0804851a
    (gdb) ni                  # execute the next instruction
    (gdb) si                  # step into a function call (step instruction)
    (gdb) info registers      # list the register contents
    (gdb) p $eax              # print the value stored in register %eax
    (gdb) p  *(int *)($ebp+8) # print out value of an int at addr (%ebp+8)
    (gdb) x/d $ebp+8          # examine the contents of memory at the given 
                              # address (/d: prints the value as an int)
                              # display type in x is sticky: subsequent x commands
                              # will display values in decimal until another type
                              # is specified (e.g. x/x $ebp+8   # in hex)
    
  • IA32 resources
  • GDB Guide (see the "using gdb to debug assembly code and examine memory and register values" section).
  • Tools for examining .o, .so, and a.out files