In this segment we'll do a bit of assembly debugging and demonstrate a debugger. You should follow along with a Pi of your own, using the example Prg2Bug.s code. This is also a chance to learn/reinforce your Unix/vi skills. All the work we'll do on the Pi will use commandline Unix and vi editing. They're pretty old technology (1970s/80s) but still widely used professionally, so it's worth your investment to learn them.
Using "ls" to display the directory containing my files, I see I have the Prg2Bug.s file ready to use. You should do the same, probably by copying the file from the website onto another machine and uploading it from that machine to your Pi. (Accessing the web directly from the Pi may be another option.)
Let's start by looking at a few assembler errors. If your assembly code doesn't obey syntax and punctuation rules, the assembler reports errors, just as a compiler would for high level languages.
Try assembling Prg2Bug.s:
as Prg2Bug.s -o Prg2Bug.o Prg2Bug.s: Assembler messages: Prg2Bug.s:7: Error: bad instruction `looptop' Prg2Bug.s:18: Error: immediate expression requires a # prefix -- `mov R7,1'
The assembler reports errors on lines 7 and 18.
Answer 1
loopTop isn't an instruction, bad or otherwise; it's a label. But the absence of a : after it makes the assembler think it's the start of an assembly instruction. The line 18 bug is clearer, but "immediate expression" is unfamiliar jargon. "Immediate" is assembler-ese for "constant". Clearly we missed the # before 1.
Answer 2
With those two fixes, you should be able to successfully assemble the program, but what happens when we link the result?
ld Prg2Bug.o -0 Prg2Bug ld: warning: cannot find entry symbol _start; defaulting to 00010054
This is a linker error. Such errors generally have to do with misnamed or undefined labels, since the linker's job is to link together different pieces of code (e.g. your program and the libraries it uses) and labels have a lot to do with that.
Question 3
Can you see the label-related bug?
Answer 3
start should have been _start.
Fixing that error, and you should now be able to link. But, what happens when we run the program?
Looks like an endless loop. If this were a C program you might insert a printf, but the best we can do in the way of output so far is an exit code, and that's not going to help find out why the program is looping. Instead, we'll use a debugger.
The gdb debugger helps you debug assembly programs. You run the program under the debugger's control. Debugger commands let you stop the program, inspect the contents of registers, advance the program instruction-by-instruction, etc.
To use gdb, first assemble your program with a debugging flag:
as -g Prg2Bug.s -o Prg2Bug.o
The -g flag tells as to include the assembly code and its labels as additional information in the .o file, along with the machine language translation. The ld command copies this information into the final executable, and gdb reads it from the executable when running your program, in order to give you an assembly-language listing of the code as part of the gdb session.
Run gdb with the debug-assembled program as an argument:
gdb Prg2Bug
You'll get a block of initial output, which you can usually ignore. At the gdb prompt, type list
(gdb) list
This gives you a partial list of the assembly, 10 lines or so, using the information inserted by -g. If you want more lines, give the start and ending lines as list arguments:
(gdb) list 1, 15
As we'll see, you can pause the program at any point during its execution, and you can list the assembly code any time the program is paused. The list command will show about 10 lines surrounding the instruction you're paused at.
The command run, runs the program under gdb control.
(gdb) run
The program will behave exactly as if you'd run it normally, but if you stop it via control-C, gdb resumes control, and shows you what line was executing at that point. Let's try that for our program.
We can see it was executing a line within the loop. A list command will show the surrounding lines if you like. Let's see what's in the registers. Run
(gdb) info r
to display all the register content.
(Note that r13-r15 have special names. We already know that r15 is the PC, so that name is no surprise. We'll see why r13 and r14 are "sp" and "lr" in a later segment. And there's a 17th register cpsr. This is the PSR register we briefly discussed earlier. More on that later, too.)
Each register's value is given both as a decimal value to the right, and as a hexadecimal value in the middle column. Read the decimal column if you care about the register's numerical value, and the hex column if you care about the particular pattern of bits in the register.
The registers we're most interested in are r0 and r1. And here something strange is happening. r0 has a huge value, and r1 is still at 1 -- it hasn't increased. At this point you've probably noticed that line 13 is the problem. It adds 0 to r1, so the loop goes forever, and r0 gets increased by a lot.
But, if we were still baffled, we could examine the program's behavior more carefully. Let's insert a breakpoint. A breakpoint is a marked instruction at which you want gdb to automatically stop the program, so you can see what's going on. Maybe we'd like to stop at line 11, the top of the loop body. Do this with the b command:
(gdb) b 11
Gdb tells you there is now a breakpoint on line 11. Then restart the program via a run command:
(gdb) run
You'll be asked if you want to start from scratch. Do so.
The program stops automatically at line 11, our breakpoint. An info r command will show register content. R0 and r1 are 0 and 1 at this point.
We can resume execution via cont (for "continue")
(gdb) cont
We stop again at line 11, on the second loop iteration. Now r0 and r1 are both 1. Let's try advancing the program one instruction at a time, via step:
(gdb) step
Each step command moves the program forward one instruction, and you can inspect register content after each instruction. By this time, we'd surely notice that r1 is not changing. Assuming we're done, we exit gdb via quit:
(gdb) quit
It'll complain that you have a currently running program. It's safe to quit anyway.
There are many more gdb commands, and gdb's help command offers documentation on them.
Use gdb help to find out what command will list all the current breakpoints.
Answer 4