Using GDB and GDB Server with the Beaglebone Black

This post is a “quick and dirty” for using GDB and GDB server on the command line with the Beaglebone Black (BBB) Single Board Computer (SBC).

Host and Target Environments

ItemHostTarget
MachineCustom-built PCBeaglebone Black (BBB) Single-board Computer (SBC)
Operating SystemLinux Mint 21.1 Vera, Linux 5.15.0-58-generic #64-Ubuntu SMPDebian Linux with TI BSP, Linux 5.10.145-ti-rt-r55 #1bullseye SMP PREEMPT_RT
System Memory32 GB512 MB
Storage3 TB4 GB
ProcessorIntel© Core™ i7-8700K CPU @ 3.70GHz × 6TI Sitara AM3358BZCZ100 ARM Cortex-8 @ 1GHz
# of cores6 (12 threads)1

Required Software Tools

For the host PC, the GDB program required is packaged with the ARM GCC toolchain. Download the toolchain and install it to a directory of your choosing. The writer of this document is using the 10.2 toolchain (gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf) installed in the ‘/opt’ folder. For the target, use SSH to tunnel into the system and verify that GDB server is installed. If the program is not installed, install it onto the system using the package manager (or similar command with your package manager):

sudo apt install gdbserver

Building the Needed Binary for the Target

Using GDB server on the embedded target, a program containing debugging symbols is not required. One can (and should) fully strip the program to save space. What should be done, is to pass in the right flags to the compiler during the build process. Here is a snippet from my own top-level CMakeLists.txt file:

...
option( XMBED_SMALLEST "Aggressive optimizations for smallest size binary applied" OFF )
option( XMBED_OPTIMIZE "Some optimizations applied" OFF )

set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra" )
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra" )

set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -g" )
set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g" )

if( XMBED_OPTIMIZE )
    message( "Release build: Some optimizations applied" )
    set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2" )
    set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2" )
elseif( XMBED_SMALLEST )
    message( "Release build: aggressive optimizations for smallest size binary applied" )
    set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Os" )
    set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os" )
else()
    message( "Release build: no optimizations applied" )
    set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O0" )
    set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0" )
endif()
...

The option variables are settings that can be passed to the CMake build system via the command line. The release build targets the BBB and there are three flavors. The first is choice uses the ‘XMBED_OPTIMIZE’ flag and passes the very common ‘-O2’ optimization heuristic to the compiler. The second flavor is specified with ‘XMBED_SMALLEST’ flag, this will pass the ‘-Os’ flag to the compiler, which asks the compiler to apply an optimization heuristic that aggressively produces small size programs. The third release build flavor passes the ‘-O0’ to the compiler with disables any optimization heuristics during the build process. It is this binary we need for the best debugging experience on the BBB.

The following Bash shell script is used to invoke the CMake build process given specific arguments:

#!/bin/bash
# print options and exit
if [ "$1" == "help" ]; then
    echo usage: ./build.sh \[help -OR- \<target\>\] \<option\>
    echo help : show options and exit
    echo \<target\>=arm : specify ARM embedded target, with no options binary is built with no optimizations
    echo \<option\>=debug : debug information in binary, only valid for \<target\>=arm
    echo \<option\>=optimize : Some optimizations applied to binary, only valid for \<target\>=arm
    echo \<option\>=smallest : Aggressive optimizations for smallest size applied to binary, only valid for \<target\>=arm
    echo *running script without specifying a valid target builds the binary for desktop \(amd64\)*
    exit 0
fi
# the build directory
BUILD_DIR=_Build
# create build directory
mkdir -p $BUILD_DIR
# go into the build directory
cd $BUILD_DIR
# configure for ARM embedded target -OR- build for desktop
if [ "$1" == "arm" ]; then
    if [ "$2" == "debug" ]; then
        # build binary with debug information
        cmake -DCMAKE_TOOLCHAIN_FILE:PATH="..\toolchain.cmake" -DCMAKE_BUILD_TYPE=Debug ..
    elif [ "$2" == "optimize" ]; then
        # build binary with some optimizations applied
        cmake -DCMAKE_TOOLCHAIN_FILE:PATH="..\toolchain.cmake" -DCMAKE_BUILD_TYPE=Release -DXMBED_OPTIMIZE=ON ..
    elif [ "$2" == "smallest" ]; then
        # build binary with smallest size optimizations applied
        cmake -DCMAKE_TOOLCHAIN_FILE:PATH="..\toolchain.cmake" -DCMAKE_BUILD_TYPE=Release -DXMBED_SMALLEST=ON ..
    else
        # build binary with NO optimizations applied
        cmake -DCMAKE_TOOLCHAIN_FILE:PATH="..\toolchain.cmake" -DCMAKE_BUILD_TYPE=Release ..
    fi
else
    # build for desktop
    cmake -DCMAKE_BUILD_TYPE=Debug ..
fi
# build the software products
cmake --build . -j $(nproc)

To build the program needed for the target, we invoke the script in the following manner:

./build.sh arm

We see the ‘XMBED_OPTIMIZE’ and ‘XMBED_SMALLEST’ flags discussed earlier are passed to the CMake build system using the ‘-D’ specifier concatenated with the names. The toolchain.cmake file in the project’s root folder provides setup for the CMake build system to use the GCC ARM toolchain to cross-compile programs targeting the BBB. CMake toolchain files will not be covered here and one is encouraged search online for the ample examples.

Once compilation is complete, copy the program to the target (here we use ‘rsync’):

rsync -av -e ssh path/to/program username@ip-address:/path/to/directory

For the writer’s project setup the command looks like this:

rsync -av -e ssh _Deployment/app/release/xmbedApp debian@192.168.0.6:/home/debian

Type in the required password if prompted and the transfer should complete shortly.

Compiling the program for Host-side GDB Consumption

We need a version of our custom program with debugging symbols for consumption by GDB on the host side. To get this program, we build using the following command:

./build.sh arm debug

Earlier, a snippet of the top-level CMakeLists.txt file was provided. In there, one can see that debug flags ‘-O0 -g’ are passed to the compiler. Again, the ‘-O0’ flag tells the compiler to disable any optimizations, but the additional ‘-g’ flag tells the compiler to generate debug symbols in the system’s native format and compile them into the program. Since we are cross-compiling, the debug symbols will be in the target’s native format (whatever that is).

Connecting GDB Host-side with Target-side GDB Server

Open a second terminal window and tunnel into the target via ‘SSH’. Navigate to the directory containing our program. The GDB server program is invoked in the following manner:

gdbserver ip-address:port path/to/program

Since we are running the server on the target, we can omit the ip-address and just start the server with a port number and pass in the name of our custom program as an argument:

gdbserver :2345 program

The Server will wait and listen for a connection and commands from the host PC (example view from BBB in terminal):

debian@BeagleBone:~$ gdbserver :2345 xmbedApp 
Process /home/debian/xmbedApp created; pid = 1237
Listening on port 2345

NOTE: Word to the wise, one cannot ‘Ctrl+C’ out of gdbserver while waiting for the host connection, one will have to power-cycle the target to break out.

On the host side, use the GDB program packaged with the ARM GCC toolchain passing in the path to the previously compiled program as an argument:

/path/to/ARM-GCC-toolchain-GDB /path/to/debug-binary

The writer of this document has created a symbolic link to the required GDB program and named it ‘gdb-arm’. So for our example here GDB on the host is invoked this way:

gdb-arm _Deployment/app/debug/xmbedApp 

Connect to the target with the following command within GDB:

(gdb) target remote ip-address:port

where ‘ip-address’ is the network address of the target and ‘port’ is the GDB Server listening port given earlier. For the setup used by the writer, the command would look like this:

(gdb) target remote 192.168.0.6:2345

Once GDB on the host side connects to the target side, the terminal window for the target should indicate the host is connected:

Remote debugging from host ::ffff:192.168.0.2, port 46328

Setup with GDB

GDB needs to know where to find the code files associated with our program, direct GDB to the root location by issuing the following command:

(gdb) dir /path/to/code-root-folder

For example, if GDB was invoked from within the project’s root folder as is the case for the writer of this document, one can simply type this:

(gdb) dir ./

Next, we need to give GDB the system root location on the target, do so by typing the following:

(gdb) set sysroot target:/

Time to Debug

Our example program is very simple containing the following lines in main.cpp:

Example program

Now, let us set one break point using the ‘break’ (‘b’) command:

(gdb) break main.cpp:16

GDB does not support the ‘run’ (‘r’) command for remote debugging sessions, we must start debug session on the target by issuing the ‘continue’ (‘c’) command:

(gdb) c

GDB on the target will run the program and halt at the first set break point. For this example, the writer chose to step through until the end by issuing the ‘next’ (‘n’) command:

Breakpoint 1, main (argc=1, argv=0xbefff714) at /home/bdn/Programming/Beaglebone/app/main.cpp:16
16	    std::cout << "Hello world!" << std::endl;
(gdb) n
17	    std::cout << "I feel useful" << std::endl;
(gdb) n
18	    std::cout << "Good bye" << std::endl;
(gdb) n
19	    return 0;
(gdb) n
20	}
(gdb) n
0xb6ce45a0 in __libc_start_main () from target:/lib/arm-linux-gnueabihf/libc.so.6
(gdb) n
Single stepping until exit from function __libc_start_main,
which has no line number information.
[Inferior 1 (process 1785) exited normally]

We display the print out from the target side terminal window for the debugging session:

debian@BeagleBone:~$ gdbserver :2345 xmbedApp 
Process /home/debian/xmbedApp created; pid = 1237
Listening on port 2345
Remote debugging from host ::ffff:192.168.0.2, port 46328
Hello world!
I feel useful
Good bye

Child exited with status 0

This walk-through is far from perfect and the reader is encouraged to conduct more research on using GDB and GDB server on his or hers own time.

Appendix

The following section contains the entire host-side terminal output for the GDB remote debug session for completeness.

bdn@Sonora2:~/Programming/Beaglebone(develop)$ gdb-arm _Deployment/app/debug/xmbedApp 
GNU gdb (GNU Toolchain for the A-profile Architecture 10.2-2020.11 (arm-10.16)) 10.1.90.20201028-git
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=arm-none-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://bugs.linaro.org/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from _Deployment/app/debug/xmbedApp...
(gdb) target remote 192.168.0.6:2345
Remote debugging using 192.168.0.6:2345
Reading symbols from /opt/gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/lib/ld-linux-armhf.so.3...
0xb6fd5a80 in _dl_start_user () from /opt/gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/lib/ld-linux-armhf.so.3
(gdb) dir ./
Source directories searched: /home/bdn/Programming/Beaglebone:$cdir:$cwd
(gdb) set sysroot target:/
Reading /lib/ld-linux-armhf.so.3 from remote target...
warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.
Reading /lib/ld-linux-armhf.so.3 from remote target...
warning: .dynamic section for "target:/lib/ld-linux-armhf.so.3" is not at the expected address (wrong library or version mismatch?)
Reading symbols from target:/lib/ld-linux-armhf.so.3...
Reading /lib/17f0bca2ae53f327681aa4c81f6d849c5d5fe2.debug from remote target...
Reading /lib/.debug/17f0bca2ae53f327681aa4c81f6d849c5d5fe2.debug from remote target...
Reading /usr/lib/debug//lib/17f0bca2ae53f327681aa4c81f6d849c5d5fe2.debug from remote target...
Reading /usr/lib/debug/lib//17f0bca2ae53f327681aa4c81f6d849c5d5fe2.debug from remote target...
Reading target://usr/lib/debug/lib//17f0bca2ae53f327681aa4c81f6d849c5d5fe2.debug from remote target...
(No debugging symbols found in target:/lib/ld-linux-armhf.so.3)
Reading /lib/ld-linux-armhf.so.3 from remote target...
Reading /lib/ld-linux-armhf.so.3 from remote target...
Reading symbols from target:/lib/ld-linux-armhf.so.3...
Reading /lib/17f0bca2ae53f327681aa4c81f6d849c5d5fe2.debug from remote target...
Reading /lib/.debug/17f0bca2ae53f327681aa4c81f6d849c5d5fe2.debug from remote target...
Reading /usr/lib/debug//lib/17f0bca2ae53f327681aa4c81f6d849c5d5fe2.debug from remote target...
Reading /usr/lib/debug/lib//17f0bca2ae53f327681aa4c81f6d849c5d5fe2.debug from remote target...
Reading target://usr/lib/debug/lib//17f0bca2ae53f327681aa4c81f6d849c5d5fe2.debug from remote target...
(No debugging symbols found in target:/lib/ld-linux-armhf.so.3)
(gdb) break main.cpp:16
Breakpoint 1 at 0x1071e: file /home/bdn/Programming/Beaglebone/app/main.cpp, line 16.
(gdb) c
Continuing.
Reading /usr/lib/arm-linux-gnueabihf/libatomic.so.1 from remote target...
Reading /usr/lib/arm-linux-gnueabihf/libstdc++.so.6 from remote target...
Reading /lib/arm-linux-gnueabihf/libm.so.6 from remote target...
Reading /lib/arm-linux-gnueabihf/libgcc_s.so.1 from remote target...
Reading /lib/arm-linux-gnueabihf/libpthread.so.0 from remote target...
Reading /lib/arm-linux-gnueabihf/libc.so.6 from remote target...
Reading /usr/lib/arm-linux-gnueabihf/792ecf1710c7a0a6819a5afaadc9477511aa5b.debug from remote target...
Reading /usr/lib/arm-linux-gnueabihf/.debug/792ecf1710c7a0a6819a5afaadc9477511aa5b.debug from remote target...
Reading /usr/lib/debug//usr/lib/arm-linux-gnueabihf/792ecf1710c7a0a6819a5afaadc9477511aa5b.debug from remote target...
Reading /usr/lib/debug/usr/lib/arm-linux-gnueabihf//792ecf1710c7a0a6819a5afaadc9477511aa5b.debug from remote target...
Reading target://usr/lib/debug/usr/lib/arm-linux-gnueabihf//792ecf1710c7a0a6819a5afaadc9477511aa5b.debug from remote target...
Reading /usr/lib/arm-linux-gnueabihf/e6afe7412e5771fcd3e4bc2ac075e0021b2d80.debug from remote target...
Reading /usr/lib/arm-linux-gnueabihf/.debug/e6afe7412e5771fcd3e4bc2ac075e0021b2d80.debug from remote target...
Reading /usr/lib/debug//usr/lib/arm-linux-gnueabihf/e6afe7412e5771fcd3e4bc2ac075e0021b2d80.debug from remote target...
Reading /usr/lib/debug/usr/lib/arm-linux-gnueabihf//e6afe7412e5771fcd3e4bc2ac075e0021b2d80.debug from remote target...
Reading target://usr/lib/debug/usr/lib/arm-linux-gnueabihf//e6afe7412e5771fcd3e4bc2ac075e0021b2d80.debug from remote target...
Reading /lib/arm-linux-gnueabihf/ad7a330dfc613be2732c15f4b9e439e0742b50.debug from remote target...
Reading /lib/arm-linux-gnueabihf/.debug/ad7a330dfc613be2732c15f4b9e439e0742b50.debug from remote target...
Reading /usr/lib/debug//lib/arm-linux-gnueabihf/ad7a330dfc613be2732c15f4b9e439e0742b50.debug from remote target...
Reading /usr/lib/debug/lib/arm-linux-gnueabihf//ad7a330dfc613be2732c15f4b9e439e0742b50.debug from remote target...
Reading target://usr/lib/debug/lib/arm-linux-gnueabihf//ad7a330dfc613be2732c15f4b9e439e0742b50.debug from remote target...
Reading /lib/arm-linux-gnueabihf/b55e299e787b32133eaea00cf280dc84215770.debug from remote target...
Reading /lib/arm-linux-gnueabihf/.debug/b55e299e787b32133eaea00cf280dc84215770.debug from remote target...
Reading /usr/lib/debug//lib/arm-linux-gnueabihf/b55e299e787b32133eaea00cf280dc84215770.debug from remote target...
Reading /usr/lib/debug/lib/arm-linux-gnueabihf//b55e299e787b32133eaea00cf280dc84215770.debug from remote target...
Reading target://usr/lib/debug/lib/arm-linux-gnueabihf//b55e299e787b32133eaea00cf280dc84215770.debug from remote target...
Reading /lib/arm-linux-gnueabihf/f5f6b192d7c804b0acd47bfb1c617d6ec3849a.debug from remote target...
Reading /lib/arm-linux-gnueabihf/.debug/f5f6b192d7c804b0acd47bfb1c617d6ec3849a.debug from remote target...
Reading /usr/lib/debug//lib/arm-linux-gnueabihf/f5f6b192d7c804b0acd47bfb1c617d6ec3849a.debug from remote target...
Reading /usr/lib/debug/lib/arm-linux-gnueabihf//f5f6b192d7c804b0acd47bfb1c617d6ec3849a.debug from remote target...
Reading target://usr/lib/debug/lib/arm-linux-gnueabihf//f5f6b192d7c804b0acd47bfb1c617d6ec3849a.debug from remote target...
Reading /lib/arm-linux-gnueabihf/7e3cad4070da5965d7c8ba10334e9058d0eb60.debug from remote target...
Reading /lib/arm-linux-gnueabihf/.debug/7e3cad4070da5965d7c8ba10334e9058d0eb60.debug from remote target...
Reading /usr/lib/debug//lib/arm-linux-gnueabihf/7e3cad4070da5965d7c8ba10334e9058d0eb60.debug from remote target...
Reading /usr/lib/debug/lib/arm-linux-gnueabihf//7e3cad4070da5965d7c8ba10334e9058d0eb60.debug from remote target...
Reading target://usr/lib/debug/lib/arm-linux-gnueabihf//7e3cad4070da5965d7c8ba10334e9058d0eb60.debug from remote target...

Breakpoint 1, main (argc=1, argv=0xbefff714) at /home/bdn/Programming/Beaglebone/app/main.cpp:16
16	    std::cout << "Hello world!" << std::endl;
(gdb) n
17	    std::cout << "I feel useful" << std::endl;
(gdb) n
18	    std::cout << "Good bye" << std::endl;
(gdb) n
19	    return 0;
(gdb) n
20	}
(gdb) n
0xb6ce45a0 in __libc_start_main () from target:/lib/arm-linux-gnueabihf/libc.so.6
(gdb) n
Single stepping until exit from function __libc_start_main,
which has no line number information.
[Inferior 1 (process 1237) exited normally]