This is the next installment in my series covering the uses of the venerable UART in Virtual Platform simulation. Use the links below to review the previous articles:
This article covers using gdb to debug a program running on the simulated system. The communication between gdb, running on the host machine, and the program being debugged, running on the target system, is done through the UART.
To clarify, this use case is primarily for application debugging, not for operating systems or other bare metal software. There is a feature called kgdb that enables Linux kernel debug via a UART, and some CPU models support direct gdb connections, but those are topics for another day.
Sometimes a system boots fine, but when a particular application is run, problems arise. To focus on the operation of a user space application, the GNU debugger (gdb) can be used. For embedded systems it's common to run the debugger on the host machine and connect to the program running in the target system using the gdb remote protocol. This eliminates the need to put a full gdb, the program source code, and the program executable compiled with -g into the target machine. It also requires less CPU consumption and is somewhat less intrusive. To make this connection possible, a handy program called gdbserver that is smaller than the full gdb is run on the target system. The gdbserver executable is part of gdb. Install the gdbserver executable into the target filesystem (for example in /bin) using the methods described in my previous articles.
Connecting the target gdb on the host to the gdbserver running in the target requires a communication channel. There are two types of connections that are commonly used in embedded systems; Ethernet and UART. Since this series is about the UART, this is the communication channel that will be used. This also demonstrates why multiple UARTs are included in a system. It's common for some UARTs to serve as terminals for logging in (as covered in the previous articles) and some UARTs to be used for debugging.
Unfortunately, some changes are required to configure the system to be able to use a UART for remote application debugging instead of as a terminal to type commands.
Connecting a serial port to a network socket
The gdb remote protocol uses the "target remote" command to establish the connection between the gdb running on the host and gdbserver running in the target. The target remote command can use either a serial port (UART) or a network connection (Ethernet). Since the gdb on the host needs to connect to the Virtual Platform it's clear a serial port as the argument is no good because this would mean to connect to a serial port on the host machine. The host machine serial ports have no connection to the target virtual platform. This leaves the possibility to use a network connection as the way to connect to the virtual platform. It also means some users will be confused because the "target remote" command issued to gdb looks like it's connecting to a network socket (which it is), but we are using the UART as the communication channel in the simulated system (which we are). Let's see if we can sort this out and demonstrate how it works.
SystemC model to connect to the UART
This can be done by building a SystemC model that interacts with a simulated UART and a network socket. The idea is that a network socket is opened to send and receive characters from the outside world and connect this to a UART send and receive function.
This provides the necessary bridge to connect gdb to a socket using the gdb remote protocol and bridges this socket to the actual UART in the simulated system.
The good news is the same SystemC model that was demonstrated for the telnet connection to provide an interactive terminal can be used again for this application! The same 2 source files tterm.cpp and tterm.h will provide the gdb connection. You will see a boolean constructor parameter on the tterm model called telnet_ that can be set to true for the telnet connection and false for the gdb connection. All that is needed is to set this parameter to false and we can use it for the gdb connection.
For this solution to work it's important to make sure no getty is started on the serial interface that will be used for gdbserver. We don't want a login prompt to start sending characters to the UART after the system boots. This will interfere with the gdb connection.
To turn off the getty for the UART to be used for gdb, edit the file /etc/inittab in the target file system. This is just an example of an inittab file, but most systems have something similar. In this case the getty for the second UART in the design /dev/ttyAMA1 is commented out. The result is that the getty will not be started for this UART.
# Copyright (C) 2001 Erik Andersen <firstname.lastname@example.org>
# Note: BusyBox init doesn't support runlevels. The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use
# Format for each entry: <id>:<runlevels>:<action>:<process>
# id == tty to run on, or empty for /dev/console
# runlevels == ignored
# action == one of sysinit, respawn, askfirst, wait, and once
# process == program to run
# Startup the system
# Set up a couple of getty's
tty1::respawn:/sbin/getty 38400 tty1
tty2::respawn:/sbin/getty 38400 tty2
::respawn:/sbin/getty -L ttyAMA0 38400 vt100
#::respawn:/sbin/getty -L ttyAMA1 38400 vt100
# Logging junk
null::respawn:/sbin/syslogd -n -m 0
tty3::respawn:/usr/bin/tail -f /var/log/messages
# Stuff to do for the 3-finger salute
# Stuff to do before rebooting
null::shutdown:/bin/umount -a -r
The key line to comment out is the one that start getty on ttyAMA1:
#::respawn:/sbin/getty -L ttyAMA1 38400 vt100
It's good to know how inittab works. I get a lot of questions about simulations where an xterm opens but no login prompt is there. Just because there is no login prompt it doesn't mean anything is wrong; it could be there is no getty started on that particular UART.
Connect gdb and start debugging
To run gdbserver on a virtual platform running Linux and debug the linpack application use:
# /bin/gdbserver /dev/ttyAMA1 linpack.exe
This is using the second UART in the system and matches the one we disabled the getty for, and is the one that the tterm SystemC model is connected to.
On the host machine you need to have the same executable that that has been placed into the target file system along with the source code for the program. Also make sure you compiled the application with -g so gdb can work.
On the host machine start gdb and give the target remote command. Notice the target remote command gives :1234 as the argument. This is for the form machine:port, but in this case the machine name is omitted to indicate you should use the same machine and the port is 1234 which corresponds to the default port number in the tterm SystemC model. If needed, we can run gdb on any other machine on the network and connect to the simulation. As with the telnet connection it could even be a Windows machine connecting to a simulation on Linux.
$ arm-none-eabi-gdb linpack.exe
(gdb) b main
(gdb) target remote :1234
Now you can debug the application with process id 67 on the target with a cross-gdb running on the host.
The output of the gdbserver process is shown in the screenshot below.
Debugging applications on a virtual platform running Linux can be done using a gdb connection to a gdbserver process running in the target machine. Doing this requires no special support from the CPU model so it works the same way regardless of the type of CPU model being used. I always grin when a CPU model vendor says they support a gdb connection for debugging applications (as I have described today) because it has nothing to do with the CPU models.
Next time I will cover the final usage of the UART; using it to log operating system activity of an RTOS.