CS31 Weekly Lab Week 4

IA32 assembly, gdb disassemble
Create a week04 subdirectory in your weeklylab subdirectory and copy over some files:
    cd cs31/weeklylab		
    mkdir week04
    cd week04
    cp ~lammert/public/cs31/week04/* .
    Makefile conditions.c simpleops.c while-loop-asm.s while-loop.c

Week 4 lab goals:

  1. Practice with IA32 assembly code, gcc, objdump
  2. Learn gdb disassemble, ni, si,
  3. Review condition codes, with one exercise
  4. Learn about writing loops in assembly

gcc to generate IA32 assembly

Let's try out gcc to build IA32 assembly files and .o files and look at the results.

Open up simpleops.c in a vim or emacs.

We are going to look 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. We are also going to look at how to used gdb to see the assembly code and to step through the execution of individual instructions.

If you open up the Makefile you can see the rules for building .s, .o and executable files from simpleops.c. We will be compiling the 32-bit version of instructions, so we will use the -m32 flag, and we are using version 4.4 of gcc (version 4.4 generates easier to read IA32 code):

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

gdb disassemble and instruction stepping

Next, let's try disassembling code using gdb. With gdb we can execute individual IA32 instructions, examine register values, and disassemble functions. Do a 'make clean' then a 'make' to rebuild an IA32 version of the simpleops executable file.
gdb simpleops
(gdb) break main
(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 *0x080483c1   # set breakpoint at specified address 
(gdb) cont
(gdb) disass 
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
You can print out the values of individual registers like this:
(gdb) print $eax
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
See my GDB Guide for more information about using gdb. (see the "using gdb to debug assembly code and examine memory and register values" section).

Also see figure 3.30 on p.255 of the textbook.

Condition codes

Recall the CPU condition codes we talked about in class:

ZF: If the computed result is zero.

SF: If the computed result's most significant bit is set (i.e., it's negative if interpreted as signed).

CF: If the computed result caused an overflow, when interpreted as an unsigned operation.

OF: If the computed result caused an overflow, when interpreted as a signed operation.

Assume %eax holds the value 5, and %ecx holds 7. Suppose we executed the following instructions:

  1. subl $5, %eax
  2. cmpl %ecx, %eax

For each of these instructions, which flags should be set?

Experimental verification

Now that think we know which flags would be set, let's see if we're correct. Open conditions.c in your text editor. We'll use this simple C file to generate the above instructions (or, at least some very similar ones).

Run make, and let's open gdb:

gdb ./conditions

Let's put a breakpoint at main:

break main

From here, we can step through individual instructions (via the ni command). Let's stop when we get to the second sub instruction (sub $0x5, %eax). We can see that at this point, %eax holds the value 5, as expected. Now, execute the instruction with ni. Are the condition codes (in the eflags register) what you expected them to be?

Now do the same with the cmp instruction that comes a few instructions after the sub we just examined.

The condition codes for your ALU should work the same way!

Writing a loop in assembly

Next, we'll try writing a while loop in IA32 assembly. Take a look at the while-loop.c file. You'll see that it creates an array with five buckets. It sets three of them to 5, 10, and 2, and it prompts the user to fill in the other two. It then calls a sum_function. Right now, the sum function, which should return the sum of all five buckets in the array, is empty! We need to fill it in, only rather than doing it in C, let's write it IA32 assembly.

Open the file while-loop-asm.s in a text editor. There's lots of stuff in here that was generated by gcc that isn't easily human-readable, but I've added comments in the section that you need to edit. At the beginning, the base of the array is stored in the %ecx register. You want to put your result in %eax when you're done. Right now, the constant 10 is being put into that register, which is no good. Work with your partner to write a loop that sums up the five array values.

At any time, you can run make and then execute while-loop-asm to test your implementation. Don't be surprised if you see some crazy values the first few times you execute it.

Comparison with gcc

After we've written a correct while loop in assembly, let's edit while-loop.c with an equivalent C implementation and use gcc -S to see what the compiler generated:

gcc-4.4 -m32 -S while-loop.c
gcc-4.4 -m32 -Wall -g -o while-loop while-loop.c
emacs while-loop.s &

Lab 3 Assignment

Let's continue working on Lab 3, and implementing your ALUs. Good luck!