CompOrg Fall 2004 Lab

Lab #5 - Assembly Code and gdb.

10/6/2004

This lab involves making changes to IA32 assembly language programs, compiling, testing and debugging the resulting code.

There is an IA32 instruction set reference here: IA32 Quick Reference (pdf).

Exercise 1: Simple Assembly code

The assembly program labeled p1.s (shown below) corresponds to the C program p1.c (also below). Note that p1.s is not the direct output of gcc -S p1.c, it has been simplified (but still accomplishes the same computation). Your job is to modify p1.s so that it corresponds to p1new.c, that is, add code that subtracts x-y and prints the result, also code that multiplies and prints the result. All the code you add should be to p1.s (don't write this in C, the idea is to write some assembly code!).

You can compile the code with "gcc -o p1 p1.s", then test by running "./p1".

Note that the code for main includes some instructions we have not yet looked at, specifically the call instruction. call is used to call a subroutine (a C function). The parameters to the function are pushed on the stack using the pushl instruction in reverse order (last parameter is pushed first). So the following code will push the value 4 on the stack, followed by the address of the string "%d\n", and then call printf. The corresponds to the C code: printf("%d\n",4);

.foo:
  .string  "%d\n"      ; declares the string "%d\n" with label foo

   pushl $4            ; param 2 is the number 4
   pushl .foo          ; param 1 is the string "%d\n"

   call printf         ; go call printf

p1.s p1.c
  .file	"p1.c"
  .section	.rodata
.LC0:
  .string  "x + y = %d\n"

  .text
.globl main
  .type	main,@function
main:
  pushl  %ebp
  movl  %esp, %ebp

  movl  $100, %eax    # eax is x
  movl  $200, %ecx    # ecx is y
  addl  %ecx,%eax     # eax = x + y
  pushl %eax          # pass eax to printf
  pushl $.LC0         # format string passed to printf
  call  printf        # call printf
  movl  $0, %eax      # return(0)
  leave
  ret
#include <stdio.h>

int main(){
  int x,y,z;

  x = 100;
  y = 200;
  z = x+y;
  printf("x + y = %d\n",z);
  return(0);
}
 p1new.c
 
#include <stdio.h>

int main(){
  int x,y,z;

  x = 100;
  y = 200;
  z = x+y;
  printf("x + y = %d\n",z);

  z = x-y;
  printf("x - y = %d\n",z);

  z = x * y;
  printf("x * y = %d\n",z);

  return(0);
}

Exercise 2: Conditionals (if statements)

Same idea as above - start with p2.s and modify it. Here you need to do the following:

The end results should be a new assembly program that when run outputs something like this:

The maximum of 10 and 20 is 20
The maximum of 100 and 20 is 100
The maximum of 10 and 10 is 10
The maximum of -10 and -20 is -10
The minimum of 10 and 20 is 10
The minimum of 100 and 20 is 20
The minimum of 10 and 10 is 10
The minimum of -10 and -20 is -20

Note that this requires the use of some instructions we have not yet discussed, specifically jmp, jle, jl, jge, and possibly jg. The jmp instruction jumps to a new location. The conditional jump instructions do a jump only if the result of the the previous instruction matches the jump conditions:

The conditional jump instructions use "the result of the previous instruction" to decide what to do - this could be any arithmetic instruction. The most commonly used instruction is the cmpl instruction which does a subtraction (just like subl), it just doesn't store the result of the subtraction anywhere. So, the following code would jump to foo only when the contents of %eax is greater than the contents of %edx:

    cmpl %edx,%eax          ; compute eax - edx
    jg   foo                ; jump if eax - edx > 0 (which is eax > edx)
p2.s p2.c
  .file	"p2.c"
  .text
.globl maximum
  .type	maximum,@function
maximum:
  pushl	%ebp
  movl	%esp, %ebp

  movl	8(%ebp),%eax    # eax is x				
  movl  12(%ebp),%ecx   # ecx is y

  cmpl  %ecx,%eax	# compute x-y, set CC
  jle	.L2		# jump if x-y <= 0
  movl	%eax, %eax	# return x
  jmp	.L1
.L2:
  movl	%ecx, %eax	# return y
.L1:
  leave
  ret

.Lfe1:
  .section  .rodata
  .align 32
.LC0:
  .string  "The maximum of %d and %d is %d\n"
  .text
.globl main
  .type  main,@function
main:
  pushl  %ebp
  movl   %esp, %ebp
  pushl  %ebx
	  
  movl	 $10, %ebx  # x=10
  movl   $20, %ecx  # y=20

  pushl  %ecx       # push y
  pushl  %ebx	    # push x
  call   maximum

  pushl  %eax       # push return value
  pushl  %ecx       # push y
  pushl  %ebx       # push x
  pushl  $.LC0      # push address of fmt string
  call   printf

  movl  $100, %ebx  # x=100
  movl  $20, %ecx   # y=20

  pushl  %ecx       # push y
  pushl  %ebx       # push x
  call   maximum

  pushl  %eax       # push return value
  pushl  %ecx       # push y
  pushl  %ebx       # push x
  pushl  $.LC0      # push address of fmt string
  call   printf

  movl  $10, %ebx   # x=10
  movl  $10, %ecx   # y=10

  pushl  %ecx       # push y
  pushl  %ebx       # push x
  call   maximum

  pushl  %eax       # push return value
  pushl  %ecx       # push y
  pushl  %ebx       # push x
  pushl  $.LC0      # push address of fmt string
  call   printf
    
  movl   $-10, %ebx # x=-10
  movl   $-20, %ecx # y=-20

  pushl  %ecx       # push y
  pushl  %ebx       # push x
  call   maximum

  pushl  %eax       # push return value
  pushl  %ecx       # push y
  pushl  %ebx       # push x
  pushl  $.LC0      # push address of fmt string
  call   printf
  pop    %ebx       # restore ebx
  leave
  ret
#include <stdio.h>

int maximum(int x, int y) {
  if (x > y) {
	return(x);
  } else {
	return(y);
  }
}


int main(){
  int x,y;

  x=10; y=20;
  printf("The maximum of %d and %d is %d\n",x,y,maximum(x,y));

  x=100; y=20;
  printf("The maximum of %d and %d is %d\n",x,y,maximum(x,y));

  x=10; y=10;
  printf("The maximum of %d and %d is %d\n",x,y,maximum(x,y));


  x=-10; y=-20;
  printf("The maximum of %d and %d is %d\n",x,y,maximum(x,y));

  return(0);
}

Exercise 3: For loop in Assembly

The assembly program labeled p3.s (shown below) is derived from a C program with just a main function that contains a simple for loop. Compile and run the code to see what it does.

Change the assembly program so that the code counts down from 10 to 1 (instead of up from 1 to 10).

No C code is shown here and the assembly is not commented (you need to figure this out from just the assembly code!).

p3.s
  .file  "p3.c"
  .section  .rodata
.LC0:
  .string  "%d\n"
.LC1:
  .string  "Blast Off\n"
  .text
.globl main
  .type  main,@function
main:
  pushl  %ebp
  movl   %esp, %ebp
  pushl  %ebx		
		
  movl  $0, %ebx      
.L2:
  cmpl  $10, %ebx	  
  jle  .L5
  jmp  .L3
.L5:
  pushl  %ebx
  pushl  $.LC0
  call  printf
  incl  %ebx
  jmp  .L2
.L3:
  pushl  $.LC1
  call  printf
  popl  %ebx
  leave
  ret


Exercise 4: using the GDB debugger

gdb is a debugger that can handle C code (compile C code with the "-g" option to support debugging of C programs) as well as assembly language. For this exercise you will run gdb on the executable created in exercise 3 (from p3.s) and learn to use some of the gdb commands (information about gdb follows the tasks you need to complete for this exercise).

You need to do the following:

  1. Compile p3.s (either version is fine for this exercise) and create and executable named p3. Now start up gdb and tell it you want to load the p3 program like this:

    gdb ./p3
  2. Disassemble the function main so you can see the individual assembly code instructions and the address of each one (check out the "disas" gdb command).

  3. Set a breakpoint for the instruction that initializes the loop counter (using the "break" gdb command), and then start the program running with the "run" gdb command.

  4. Use the "print" command to examine the value of the loop counter (which is in a register). Try "print/x" (to see the value displayed in hex).

  5. Tell gdb to execute one instruction with the "si" command. Now examine the loop counter and verify that it has been initialized.

  6. Tell gdb that you would now like it to always display the next instruction to be executed (every time you issue and instruction it will print out the next instruction): display/i $eip.

  7. Continue stepping the code through instructions with the "si" command, try to keep track of what is happening by looking at register values, etc.

  8. Once you get bored, you can tell gdb to continue execution (until the end of the program or until a breakpoint is hit) with the "continue" command.

Now try to do the following:

Complete documentation on the gdb debugger is available online at: http://sources.redhat.com/gdb/current/onlinedocs/, below is a brief description of some commands to get you started.

Some GDB commands:


Extra Exercise!: Figure out what an executable does

Here is a challenging exercise: given an executable program (and nothing else), use gdb to look at the assembly code, and step the program through to determine what the program is doing. Your job is to find out what input should be provided so that the program prints the string "You are a true GDB guru".

The executable program can be found at ~hollingd/public/p5 on monte.cs.rpi.edu.

You can grab your own copy like this:

cp ~hollingd/public/p5 .

HINT: All the code before the call to atoi deals with getting the value of the first command line argument (and atoi converts it to an integer).