CS31 Weekly Lab: Week 6

Week 6 lab topics:

  1. Writing IA32 code in a .s file and compiling and running it. Compiling to Assembly
  2. tools for examining binary files
  3. gdb (and ddd) for examining binary executable run state

Create a week06 subdirectory in your weeklylab subdirectory and copy over some files:
    cd cs31/weeklylab		
    pwd
    mkdir week06
    ls
    cd week06
    pwd
    cp ~newhall/public/cs31/week06/* .
    ls

Compiling Phases and Assembly
First, let's open up simplefuncs.c in vim.

We are going to look again at how to use gcc to create an assembly version of this file, and how to create a object .o file, and how to examine its contents.

If you open up the Makefile you can see the rules for building .s, .o and executable files from simplefuncs.c. We will be compiling the 32-bit version of instructions, so we will use the -m32 flag to gcc:

gcc -m32 -S simplefuncs.c   # just 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
In variables.c is a simple function that has some local variables and returns a value, and in mainprog.c is a main function 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). Another is to notice that the return value is copied into register %eax.

Let's try running the mainprog and see what happens.

Next, modify code in variables.s in a way that the function will return a different value. Run make again to link in your new version of variables.s into mainprog executable. Then run and see what happens. Be careful not to modify variables.c or make will rebuild variables.s from it.

Tools for examining binary files
Some tools for examining binary files:
  1. strings dumps all the strings in a binary file:
      strings simplefuncs 
    
  2. nm (or objdump -t) to list symbol table contents:
      objdump -t  simplefuncs   # list symbol table in the executable (a.out) file
      nm --format sysv  simplefuncs  # list symbol table in the executable file
    
    The symbol table includes the names of all functions and global variables in the program. There is a lot of information in the symbol table that looks odd, but you should be able to see an entry for the two funcitons main and func1, and see where their start addresses are in memory.

  3. gdb and ddd and disass

gdb and ddd to debug at the assembly code level
With gdb you can debug and trace through a program execution at the assembly code level. This includes executing individual IA32 instructions, examine register values, and disassembling functions. Let's try it out again with the simplefuncs program, but first do 'make clean' then a 'make' to rebuild an IA32 version of the simpleops executable file.

First, let's open up simplefuncs.c in vim. Then, let's try some things out in gdb:

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 break point at a specific instruction:
(gdb) break *0x08048477   # set breakpoint at specified address 
And you can step or next at the instruction level 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 using si (we also have a breakpoint at this function, let's see when it is hit):
(gdb) si	  # step into instructions in the called function (func1)
(gdb) disass
(gdb) ni 
(gdb) where
(gdb) disass
(gdb) cont
The difference between si and ni is shows up in what each does on a call instruction. si gives gdb control again at instructions at the begining of the called function. ni gives gdb control again at the instruction immediately after the call instruction (the instruction at the return address). si "steps into" the called function, "ni" lets the called function code continue, and only after the function returns does gdb get control again.

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

(gdb) print $eax
Or the memory contents at a given address, providing either the absolute numeric address or its value stored in registers:
(gdb) p *(int *)($ebp + 8)
(gdb) x     $ebp + 8
(gdb) x/d     $ebp + 8   # x/d display as decimal value
You can also view all register values:
(gdb) info registers
You can also 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):
x $esp-0x8  # see what p and x display for the same value
p $esp-0x8    

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

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)
x $esp + 0x1c    
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. There are also menu options and buttons for gdb commands, but I find using the gdb prompt at the bottom easier to use.

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