Debugging embedded code Using GDB and the Skyeye simulator

Fri, 02 Jan 2009 08:55:48 +0000
tech article gdb

This post is basically a crash course in using gdb to run and debug low-level code. Learning to us a debugger effectively can be much more powerful than ghetto printf() and putc() debugging. I should point out that I am far from a power-gdb user, and am usually much more comfortable with printf() and putc(), so this is very much a beginners guide, written by a newbie. With those caveats in mind, lets get started.

So the first thing to do is to get our target up and running. For this our target will be a virtual device running with Skyeye. When you start up Skyeye and pass it the -d flag, e.g: $ skeye -c config.cfg -d. This will halt the virtual processor and provide an opportunity to attach the debugger. The debugger will be available on a UNIX socket. It defaults to port 12345. Of course a decent JTAG adapter should be able to give you the same type of thing with real hardware.

Now, you run GDB: $ arm-elf-gdb. Once gdb is running you need to attach to the target. To do this we use: (gdb) target remote :12345. Now you can start the code running with (gdb) continue.

Now, just running the code isn’t very useful, you can do that already. If you are debugging you probably want to step through the code. You do this with the step command. You can step through code line at-a-time, or instruction at-a-time. At the earliest stages you probably want to use the si command to step through instruction at-a-time.

To see what you code is doing you probably want to be able to display information. For low-level start up code, being able to inspect the register and memory state is import. You can look at the register using the info registers command, which prints out all the general-purpose registers as well as the program counter and status registers.

For examing memory the x command is invaluable. The examine command takes a memory address as an argument (actually, it can be a general expression that quates to a memory address). The command has some optional arguments. You can choose the number of units to display, the format to display memory in (hex (x), decimal (d), binary (t), character (c), instruction (i), string(s), etc), and also the unit size (byte (b), halfword (h), word (w)). So, for example to display the first five words in memory as hex we can do: (gdb) x /5x 0x0. If we want to see the values of individual bytes as decimal we could do: (gdb) x /20bd 0x0. Another common example is to display the next 5 instructions, which can be done with (gdb) x /5i $pc. The $pc expression returns the value in the pc register.

Poking at bits and bytes and stepping instruction at a time is great for low-level code, but gdb can end up being a lot more useful if it knows a little bit more about the source code you are debugging. If you have compiled the source code with the -g option, your ELF file should have the debugging information you need embedded in it. You can let gdb know about this file by using the (gdb) symbol program.elf. Now that you actually have symbols and debugging information, you can do things like normal then step command, and it will step through lines of source code (rather than instructions).

The other nice thing you have is that you can easily set breakpoint and watchpoints. (You don’t have to have source debugging enabled for this, but it makes things a lot easier!). Seting a breakpoint is easy, you can set it on a line e.g: (gdb) break file.c:37, or on a particular function e.g: (gdb) break schedule. Breakpoints are neat, but watchpoints are even cooler, since you can test for a specific conditions e.g: (gdb) watch mask < 2000.

Now that you have these nice watchpoints and breakpoints, you probably find that most of the time, you just end up printing out some variables each time you hit the point. To avoid this repetitive typing you can use the display command. Each expression you install with the display command will be printed each time program execution stops (e.g: you hit a break-point or watch-point). This avoids a lot of tedious typing!

So, this is of course just scratching the surface. One final thing to consider that will likely make your time using gdb more useful and less painful (i.e: less repetitive typing), is the define command which lets you create simple little command scripts. The other is that when you start gdb you can pass a command script with the -x. So, you might want to consider, instead of littering your code with printf() statements everywhere you might want to write some gdb commands that enable a breakpoint and display some the relevant data.

Good luck, and happy debugging!

blog comments powered by Disqus