ARMAssembly:RegistersOnly C

Copyright 2018, Clinton A Staley

Concepts

  1. Debugging assembly code
  2. Assembler errors
  3. Linker errors
  4. Runtime errors
  5. gdb

Debugging Prg2Bug.s

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.

Assembler Errors

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.

*** Question 1*** See if you can figure out what it's complaining about on those lines.

Answer 1

Just like compilers, assemblers often give unclear error messages, like the one for line 7. 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.

Question 2 Please fix those, using a vi session on the Pi, and try again. This requires several steps listed below, with hints provided in some cases. Use a vi helpsheet if you don't already know the editor:

  1. Open vi on the file
  2. Navigate to the text to change. (arrows, hjkl keys, / key to search for patterns)
  3. Edit the text (x, cw, i, a)
  4. Save the changes (ZZ or :wq, but NOT ctl-Z!)

Answer 2

vi , use hjkl for left/down/up/right, or just use arrow keys, or use / to search, e.g. /R7 to find pattern R7, edit with either a or i, or cw to change an entire word. Save with ZZ

With those two fixes, you should be able to successfully assemble the program, but what happens when we link the result?

Linking Errors

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

Label start should have been _start.

Fixing that error, and you should now be able to link. But, what happens when we run the program?

Runtime Errors

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.

GDB 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.

Gdb help

There are many more gdb commands, and gdb's help command offers documentation on them.

Question 4

Use gdb help to find out what command will list all the current breakpoints.

Answer 4

info breakpoints will do this, per the help status command.

Gdb summary

  1. Assemble with -g flag
  2. Run via gdb
  3. Use gdb commands: list, run, cont, b, info r, and step to figure out what's happening.
  4. End gdb with quit