| CompOrg Fall 2004 Lab |
|   CompOrg Home   |   p1   |   p2   |   p3   |   gdb |
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:
minimum that
returns the minimum of 2 integers. Start with the existing assembly
code for maximum and modify it!minimum subroutine and print the results (copy and paste
to start out!).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:
jle will jump only if the result of the
previous instruction is <= 0.
jl will jump only if the result of the
previous instruction is < 0.
jge will jump only if the result of the
previous instruction is >= 0.
jl will jump only if the result of the
previous instruction is > 0.
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:
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
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).
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.
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).
Tell gdb to execute one instruction with the "si" command. Now examine the loop counter and verify that it has been initialized.
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.
Continue stepping the code through instructions with the "si" command, try to keep track of what is happening by looking at register values, etc.
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:
Start up the program under gdb and step through the code until you reach a point where the loop count has the value 5 (inside the loop). Change the value of the loop counter so that the loop terminates. Use the "set" command to change the value in a register, for example you can change the value in register %eax to 18 with the command "set $eax = 18".
Issue the "continue" instruction to verify that the loop terminates (based on the new value you provide for the loop counter).
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.
Printing a register (%edx): print $edx (note that you need to use '$', not '%').
Display a register (automatically prints out the value of the register after every instruction):
display $edx
Step one instruction: stepi or si
Tell GDB to display the next instruction each time it executes an instruction:
display/i $eip (very useful!)
eip is the register that holds the address of the next instruction to be executed.
Examine memory at an asolute memory address: x/4x
0xbfbff0d4c will display 4 words (as hex) starting at address
0xbfbff0d4c, x/s $edx will display the string (ASCII chars
terminated by a null) starting at the address in register %edx.
Disassemble an entire subroutine: disas subname. For example you
could disassemble main like this: disas main.
print information about the current stack frame: info frame
Set a breakpoint at an absolute address: break
*0x8048344c. Note that you need to put a '*' in front of the
address (otherwise gdb think you mean line number 0x0848344c or
whatever address you use). Type "help breakpoints" for more
information about commands that deal with breakpoints.
Print information about all currently active stack frames: backtrace
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).