Now that you have gdbserver installed on the target and a cross GDB on the host you can start a debug session.
The connection between GDB and gdbserver can be through a network or a serial interface. In the case of a network connection, you launch gdbserver with the TCP port number to listen on and, optionally, an IP address to accept connections from. In most cases you don't care which IP address is going to connect, so you can just give the port number. In this example gdbserver waits for a connection on port 10000
from any host:
# gdbserver :10000 ./hello-world Process hello-world created; pid = 103 Listening on port 10000
Next, start the copy of GDB from your toolchain, giving the same program as an argument so that GDB can load the symbol table:
$ arm-poky-linux-gnueabi-gdb hello-world
In GDB, you use the command target remote
to make the connection, giving the IP address or host name of the target and the port it is waiting on:
(gdb) target remote 192.168.1.101:10000
When gdbserver sees the connection from the host it prints the following:
Remote debugging from host 192.168.1.1
The procedure is similar for a serial connection. On the target, you tell gdbserver which serial port to use:
# gdbserver /dev/ttyO0 ./hello-world
You may need to configure the port baud rate beforehand using stty
or a similar program. A simple example would be as follows:
# stty -F /dev/ttyO1 115200
There are many other options to stty
, please read the man page for more details. It is worthwhile noting that the port must not be used for anything else, for example, you can't use a port that is being used as the system console. On the host, you make the connection to gdbserver using target remote
plus the serial device at the host end of the cable. In most cases you will want to set the baud rate of the host serial port using the GDB command set remotebaud
:
(gdb) set remotebaud 115200 (gdb) target remote /dev/ttyUSB0
GDB needs to know where to find debug symbols and source code for shared libraries. When debugging natively the paths are well known and built in to GDB, but when using a cross toolchain, GDB has no way to guess where the root of the target filesystem is. You do so by setting the sysroot. The Yocto Project and Buildroot have different ways of handling library symbols so the location of the sysroot is quite different.
The Yocto Project includes debug information in the target filesystem image, so you need to unpack the target image tar file that is generated in build/tmp/deploy/images
, for which you would need to do something like this:
$ mkdir ~/rootfs $ cd ~/rootfs $ sudo tar xf ~/poky/build/tmp/deploy/images/beaglebone/core-image-minimal-beaglebone.tar.bz2Then you can point sysroot to the root of the unpacked files: (gdb) set sysroot /home/chris/MELP/rootfs
Buildroot compiles libraries with minimal or full debug symbols, depending on BR2_ENABLE_DEBUG
, puts them into the staging directory, then strips them as they are copied into target image. So, for Buildroot, the sysroot is always the staging area regardless of where the root filesystem is extracted.
There are some things that you need to do each time you run GDB, for example, setting the sysroot. It is convenient to put such commands into a command file and run them each time GDB is started. GDB reads commands from $HOME/.gdbinit
, then from .gdbinit
in the current directory and then from files specified on the command line with the -x
parameter. However, recent versions of GDB will refuse to load .gdbinit
from the current directory for security reasons. You can override that behavior for a single directory by adding a line like this to your $HOME/.gdbinit
:
add-auto-load-safe-path /home/chris/myprog/.gdbinit
You can also disable the check globally by adding:
set auto-load safe-path /
My personal preference is use the -x
parameter to point to the command file, which exposes the location of the file so I don't forget about it.
To help you set up GDB, Buildroot creates a GDB command file containing the correct sysroot command in output/staging/usr/share/buildroot/gdbinit
. It will contain a command similar to this one:
set sysroot /home/chris/buildroot/output/host/usr/arm-buildroot-linux-gnueabi/sysroot
GDB has a great many commands, which are described in the online manual and in the resources mentioned in the Further Reading section. To help you get going as quickly as possible, here is a list of the most commonly used commands. In most cases there is a short-hand for the command, which is listed underneath the full command.
The following table shows the commands for breakpoints:
Commands |
Use |
---|---|
|
Set a breakpoint on a function name, line number or line. Examples are: |
|
List breakpoints |
|
Delete breakpoint |
The following table shows the commands for running and stepping:
Commands |
Use |
---|---|
|
Load a fresh copy of the program into memory and start it running. This does not work for remote debug using gdbserver |
c |
Continue execution from a breakpoint |
|
Stop the program being debugged |
|
Step one line of code, stepping into any function that is called |
|
Step one line of code, stepping over a function call |
|
Run until the current function returns |
The following table shows the commands for getting information:
Commands |
Use |
---|---|
|
List the call stack |
|
Continue execution from a breakpoint |
|
Stop the program |
|
Print the value of a variable, e.g. |
|
List lines of code around the current program counter |
Gdbserver loads the program into memory and sets a breakpoint at the first instruction, then waits for a connection from GDB. When the connection is made you enter into a debug session. However, you will find that if you try to single step immediately you will get this message:
Cannot find bounds of current function
This is because the program is halted in code written in assembly that creates the run time environment for C and C++ programs. The first line of C or C++ code is the main()
function. Supposing that you want to stop at main()
, you would set a breakpoint there and then use the continue
command (abbreviation c
) to tell gdbserver to continue from the breakpoint at the start of the program and stop at main:
(gdb) break main Breakpoint 1, main (argc=1, argv=0xbefffe24) at helloworld.c:8 8 printf("Hello, world!\n");
If at this point you see the following:
warning: Could not load shared library symbols for 2 libraries, e.g. /lib/libc.so.6.
That means that you have forgotten the set sysroot!
This is all very different to starting a program natively, where you just type run
. In fact, if you try typing run
in a remote debug session, you will either see a message saying that the remote target does not support run
, or in older versions of GDB it will just hang without any explanation.