There are a lot of things that can go wrong in programming, and assembly language is no different. This lab is about common problems, and using a debugger to help.
First, we will look at a few possible problems that you might run into.
Download the code at several_errors.asm.
Try to assemble it with the command
nasm -f elf64 several_errors.asm
,
then edit it to correct the errors noted by the assembler.
int1 dd 10
int2 dd 5
The code looks OK, and is even correct with some assemblers.
But the assembler that we use, NASM, gives an error.
The following is the solution.
int1: dd 10
int2: dd 5
move rax, 0
The problem is that "move" is not a mnemonic. Obviously, the programmer
means "mov" here, but that is not obvious to the assembler.
mov rsi, ebx ; Value to print
The problem is that rsi is 64 bits, while ebx is 32.
The solution is to use this line instead:
mov rsi, rbx ; Value to print
mov rdi, newline
This happens because "newline" has no meaning to the assembler, and
it is therefore expecting "newline" to be defined somewhere, such as
the data section. The solution is simple, to add the following
line in section .data
. Once it is defined, the error goes
away.
newline: dq 10
This is just like using a variable without declaring and initializing
it first.
gcc several_errors.o -o several_errors
to link it.
The error is having a stray
push rax
command. By itself, this is not a problem. But when the program runs,
it generates the error:
"Segmentation fault (core dumped)".
Since this error occurs when the program runs, the assembler
does not indicate that there will be a problem.
If we had put a corresponding pop rax
, then
this would not be a problem.
mov rax, 3 ; rax gets the value 3
mov rbx, [rax]
This will assemble and link. Running it gives a "segmentation fault" error.
The reason is that we use 3 as an address, and attempt to load the
value there. The program does not have access to that memory, though.
In the above, you should have altered the "several_errors.asm" program to fix all of the errors noted by the assembler. Make sure to show the new version of the program, and that it assembles and links. Now we will look at how you might debug the final error that occurs at run-time.
The "gdb" program is the GNU debugger. You can run it from the command line by typing
gdb several_errors
From the "(gdb)" prompt, you can type "quit" to exit the program.
Another good command to know is "help", such as "help running".
This provides documentation on commands related to running a program
in the debugger.
Type the following commands at the "(gdb)" prompt, pressing return between them.
set disassembly-flavor intel
disassemble main
The first command informs the debugger of what type of code to show.
If you forget it, it will show code that looks familiar, but different.
The default is "att" style code, from AT&T, and yes: it means
American Telephone and Telegraph Company,
the telephone/telecommunications company that may be your internet
provider.
For example, instead of "mov eax,0x0
",
you would see "mov $0x0,%eax
".
The second one shows the assembly language for the "main" function.
Notice that the output is different from the source file. This is because
it is showing a disassembly: it
says what commands are there, but it bases this on the executable program
instead of the original source code.
We will set a breakpoint. This is a place where the execution will stop, allowing us to inspect the state of the computer. Enter the command
break main
to set a break-point at the main label.
You could set more break-points, based on any other defined labels.
Let's see what break-points are set with the following command.
info breakpoints
Now run the program.
run
You should see something like this in the output.
(You do not type this; this is something that the computer says in the response.)
Breakpoint 1, 0x0000000000400580 in main ()
Now you can step through the code by entering the command
nexti
This executes the next instruction.
You can repeat "nexti" to step through as many instructions as you want.
From here, you could enter "continue" to keep going until the next break-point.
Or "step" to keep going until it reaches the main function's exit.
The problem with entering "nexti" again and again is that you will lose track of what it is doing. Enter the following command to show the instructions that will execute with the "nexti" command.
display/i $pc
You should inspect the contents of the registers. This is done with the following command.
info registers
If you just want the contents of one register, you can specify it/
Try this:
info registers rax
Likewise, you can inspect the stack with
info stack
Inspect the variables with the following command.
info variables
Finally, inspect the flags with the following command.
info registers eflags
The gdb program will respond with a hexadecimal value of the eflags
register, along with a list of flags that are set.
Now that you know about the gdb program, use it to debug the segmentation fault error. Walk us through the program, along with the information that you discover with gdb, explaining it. Talk about how what each instruction does, and how you can observe its effect.
QuestionsRemember to use "quit" to quit the gdb program.
In this lab, we have learned:
gdb command | effect |
break main | set a break-point at main |
continue | run until the next break-point |
disassemble main | disassemble the code starting at main |
display/i $pc | show the next instruction |
help | get help information |
help running | get help information about running the program |
info breakpoints | show the set break-points |
info registers | show contents of the registers |
info registers eflags | show contents of the eflags register (flags) |
info registers rax | show contents of the rax register |
info stack | show contents of the stack |
info variables | show information about variable locations |
nexti | step through the next instruction |
run | begin running the program |
quit | quit gdb |
set disassembly-flavor intel | switch the instruction format |
step | keep going until the exit of the main function |