Prerequisite: OpenOCD development environment

Hardware requirements:

SD card set up with Raspberry Pi OS image

We have selected Raspberry Pi OS which is a Debian based Linux distribution for the purpose of this tutorial. Raspberry Pi foundation has developed a GUI based tool called Raspberry Pi Imager for downloading and writing various OS images to SD cards. At the time of this writing, 64-bit Raspberry Pi OS image was not available for download via Imager utility, however we can manually download OS images and Imager utility will be able prepare a SD card using those custom images as well.

This blog post has details on how to use Raspberry Pi Imager utility. Also if your desired OS image is not available via Imager utility click here to download an appropriate image. For writing SD cards manually please follow the link for instructions.

Once the SD card is prepared we need to insert it into relevant Raspberry Pi board and boot it up once where it will resize partition on startup and we will be able to verify that OS installation was successful.

Build and install custom Linux kernel for debugging

Once we have successfully prepared a working SD card for our Raspberry Pi, the next step is to build Linux kernel with debug information and install custom kernel image on to SD card for JTAG debugging via OpenOCD.

Install dependencies

sudo apt install gcc g++ gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu git gdb-multiarch bison flex libncurses-dev libssl-dev

Download Raspberry Pi Linux kernel sources

git clone --depth=1  https://github.com/raspberrypi/linux.git

We are going to build the default Linux kernel version however you may select other versions by switching to the appropriate GIT branch in the source tree downloaded above.

# Change to linux source directory
cd linux

# Clean up source directory before build
make mrproper

# Create following directories inside Linux source directory
mkdir mnt
mkdir mnt/fat32
mkdir mnt/ext4

Insert SD card containing Raspberry Pi OS image installed above into card reader  on the host machine. Run lsblk before mounting to check sdcard partition names.

sudo mount /dev/mmcblk0p1 mnt/fat32
sudo mount /dev/mmcblk0p2 mnt/ext4

Linux Kernel build for Raspberry Pi 2 B (Arm v7) or Raspberry Pi 3 B (AArch32)

# Import bcm2709_defconfig default kernel configuration
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig

# Initiate menu config to enable debug symbols
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

From menuconfig tool select > Kernel hacking > Compile-time checks and compiler options

# Save config, exit menuconfig tool and initiate kernel build
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

# Install the modules into Raspberry Pi OS root file system mounted from SD card
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=mnt/ext4 modules_install

# Copy newly built 32-bit custom Linux kernel and device tree blobs onto the SD card
KERNEL=custom_kernel7l
sudo cp arch/arm/boot/zImage mnt/fat32/$KERNEL.img
sudo cp arch/arm/boot/dts/*.dtb mnt/fat32/
sudo cp arch/arm/boot/dts/overlays/*.dtb* mnt/fat32/overlays/
sudo cp arch/arm/boot/dts/overlays/README mnt/fat32/overlays/

Linux Kernel build for Raspberry Pi 3 B 64-bit (AArch64)

# Import bcmrpi3_defconfig default kernel configuration
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcmrpi3_defconfig

# Initiate menu config to enable debug symbols
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

From menuconfig tool select > Kernel hacking > Compile-time checks and compiler options

# Save config, exit menuconfig tool and initiate kernel build
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

# Install the modules into Raspberry Pi OS root file system mounted from SD card
sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=mnt/ext4 modules_install

# Copy newly built 64-bit custom Linux kernel and device tree blobs onto the SD card
KERNEL=custom_kernel8
sudo cp arch/arm64/boot/Image mnt/fat32/$KERNEL.img
sudo cp arch/arm64/boot/dts/broadcom/*.dtb mnt/fat32/
sudo cp arch/arm64/boot/dts/overlays/*.dtb* mnt/fat32/overlays/
sudo cp arch/arm64/boot/dts/overlays/README mnt/fat32/overlays/

Enable JTAG debugging on Raspberry Pi

Edit mnt/fat32/config.txt and add following lines:

# For 32-bit kernel image
kernel=custom_kernel7l.img

# For 64-bit kernel image
kernel=custom_kernel8.img

# Enable JTAG GPIO configuration
enable_jtag_gpio=1

Kernel command line parameter nokaslr disabled KASLR. If the architecture that you are using enables KASLR by default then virtual addresses where the kernel image is mapped will be randomized and GDB may not be able to resolve kernel symbol addresses from the debug information of vmlinux. Also software breakpoints and writes to read only sections of kernel address space may not work unless we specifically turn off the read only mode. Kernel command line parameter rodata=off allows for this.

# Edit mnt/fat32/cmdline.txt and append following to kernel command line:
nokaslr rodata=off

# Unmount and remove SD card, you all set to boot custom Linux kernel on Raspberry Pi
sudo umount mnt/fat32
sudo umount mnt/ext4

Raspberry Pi Hardware set up for JTAG debugging:

In this article we are going to use the Bus Blaster v3 JTAG debugger which is based on FT2232H USB to UART/FIFO IC. However connection instructions described will work for any JTAG debugger which has a ARM 20 pin JTAG connector. In the previous section we have already configured Raspberry Pi for JTAG debug by selecting GPIO alternate function ALT4 on GPIO22 to GPIO27. We first need to figure out which pins on the Raspberry Pi GPIO header correspond to GPIO22 to GPIO27. Please keep in mind that there is no one to one mapping between GPIO numbers and GPIO header pinouts. 

ARM Standard 20-pin JTAG Connector

Raspberry Pi 40 Pin GPIO Header


From the pictures above we can figure out the following pin mappings between 40 pin Raspberry Pi GPIO header and Arm 20 pin JTAG connector.

Pin NoArm 20 Pin JTAG HeaderRaspberry Pi GPIO Header
VCCPIN 1PIN 1
GPIO22 ARM_TRSTPIN 3PIN 15
GPIO23 ARM_RTCKPIN 11PIN 16
GPIO24 ARM_TDOPIN 13PIN 18
GPIO25 ARM_TCKPIN 9PIN 22
GPIO26 ARM_TDIPIN 5PIN 37
GPIO27 ARM_TMSPIN 7PIN 13
GNDPIN 4PIN 9

Now that we have figured out pin mappings we can connect Bus Blaster v3 with Raspberry Pi JTAG pins using jumper cables. Please be cautious and double check every jumper connection.


OpenOCD set up for Raspberry Pi JTAG debugging

To start Linux kernel JTAG debugging on Raspberry Pi using OpenOCD we need to spawn OpenOCD session with configuration file for the specific board. First check JTAG connections jumper, insert SD card into Raspberry Pi, power on the board and wait for a minute or so for the kernel to boot. Connect Bus Blaster v3 JTAG probe to host computer via USB mini cable.

Download OpenOCD configuration file for Raspberry Pi 2 or Raspberry Pi 3

Following line at the end of both configuration files enables SMP mode where single GDB will control all cores simultaneously. In case your use-case requires multiple GDB connections to each individual core just comment this line before running OpenOCD.

target smp $_TARGETNAME.0 $_TARGETNAME.1 $_TARGETNAME.2 $_TARGETNAME.3

# Spawn OpenOCD session with Raspberry Pi 3 configuration
./openocd-src/src/openocd -f openocd-src/tcl/interface/ftdi/dp_busblaster.cfg -f ./raspberry-pi3.cfg

# Spawn OpenOCD session with Raspberry Pi 2 configuration
./openocd-src/src/openocd -f openocd-src/tcl/interface/ftdi/dp_busblaster.cfg -f ./raspberry-pi2.cfg

GDB set up for debugging Linux kernel via OpenOCD

Now we can start gdb and connect to OpenOCD GDB stub and debug Linux kernel running on Raspberry Pi.

gdb-multiarch <path-to-raspberrypi-linux-sources>/vmlinux
(gdb) tar remote :3333
(gdb) b do_sys_open

Set a breakpoint on do_sys_open routine, when any process will try to open a file and the do_sys_open() breakpoint will trigger.