Debugging using Segger J-Link JTAG

Published on November 18, 2016

Archived Notice

This article has been archived and may contain broken links, photos and out-of-date information. If you have any questions, please Contact Us.

JTAG is a useful tool that allows customers additional debugging options.  Segger was kind enough to send us a J-Link Plus probe for us to test.  This blog post will describe how to setup your environment and use the J-Link to debug during both U-Boot and Kernel development.

Note that all of the instructions below would work for all other J-Link probes.

Environment setup

Basic software setup

This blog post will focus on Linux installation but Windows setup should be very similar.

First, you need to install the J-Link tools from Segger website:

From there you need to download the J-Link Software and Documentation Pack for your OS.

The version v6.10m was used for this blog post, which is the latest version at the time of this writing.

If you use an Ubuntu/Debian distro, the installation is very simple:

$ sudo dpkg -i JLink_Linux_V610m_x86_64.deb

Once installed, several tools and documentation will be installed, here are the important ones:

  • UM08001_JLink.pdf: User Manual (located under /opt/SEGGER/JLink_V610m/Doc/)
  • JLinkExe: J-Link Commander tool that can be used for
    • verifying proper installation of the USB driver
    • verifying the connection to the target CPU
    • updating the firmware (automatically)
  • JLinkGDBServer: GDB remote server
    • for GDB to connect to and communicate with the target

The latter tool is the most important since it will be used for every GDB debugging session. In our case, we want to use debug on ARM targets, so we need a cross ARM toolchain and GDB.

For Ubuntu/Debian, you can simply install the following packages:

$ sudo apt-get install gcc-arm-linux-gnueabihf gdb-multiarch

Another solution is to use a toolchain from Linaro which includes the cross GDB binary:

The tools above are sufficient to start debugging using GDB manually.

Graphical IDE setup

If you wish to use a graphical IDE instead of entering GDB commands manually, there are some open-source solutions.

For this blog post, we chose the most commonly used IDE: Eclipse. Although it was primarly made for Java developement, it now exists in different flavors.

The version we are interested for U-Boot/Kernel development is the C/C++ one:

Once the tarball downloaded, you can extract to the folder of your choice.

$ tar xzf ~/Downloads/eclipse-cpp-neon-1a-linux-gtk-x86_64.tar.gz $ ./eclipse/eclipse

You then need to install the GNU ARM Eclipse plug-in which contains J-Link debugging features. The plugin website explains the installation procedure in details:

Make sure to select J-Link Debugging when installing the plugin.

ARM Plugin Install ARM Plugin Install

The J-Link part of the plugin is also well explained here.

Hardware setup

As you might have noticed, our boards have a tiny JTAG connector which is (much) smaller than the standard 20-pin connector.

In order to connect the J-Link probe you threrefore need the following adaptation board:

This board comes with several jumpers. Make sure to place them on J3, J4 and J6 so the JTAG can properly communicate with the CPU.

Also, make sure that the pin 1 (white dot) of the adaptation board matches the pin 1 of the board connector.

Here is what it should look like for the BD-SL-iMX6.

BDSL + J-Link BDSL + J-Link

At this point, if the board is powered, the JLinkExe tool should detect a CPU is connected.

$ JLinkExe -device MCIMX6Q6 -if JTAG -speed 1000 -JTAGConf -1,-1 SEGGER J-Link Commander V6.10m (Compiled Nov 10 2016 18:38:45) DLL version V6.10m, compiled Nov 10 2016 18:38:36 Connecting to J-Link via USB...O.K. Firmware: J-Link V10 compiled Sep 1 2016 18:29:58 Hardware version: V10.10 S/N: 600102841 License(s): RDI, FlashBP, FlashDL, JFlash, GDB VTref = 3.251V Type "connect" to establish a target connection, '?' for help J-Link>connect Device "MCIMX6Q6" selected. ... Debug architecture ARMv7.0 Data endian: little Main ID register: 0x412FC09A I-Cache L1: 32 KB, 256 Sets, 32 Bytes/Line, 4-Way D-Cache L1: 32 KB, 256 Sets, 32 Bytes/Line, 4-Way System control register: Instruction endian: little Level-1 instruction cache enabled Level-1 data cache enabled MMU enabled Branch prediction enabled Found 3 JTAG devices, Total IRLen = 13: #0 Id: 0x4BA00477, IRLen: 04, IRPrint: 0x1, CoreSight JTAG-DP (ARM) #1 Id: 0x00000001 #2 Id: 0x2191C01D Cortex-A9 identified.

U-Boot debugging

In this section, it is assumed your board doesn't boot to U-Boot automatically. That means that the procedure below will explain how to initialize the DDR using the JTAG.

In order to make sure that the board won't boot on its own, you can configure the boot DIP switch like for USB recovery. You can look at our unbricking blog post in order to see how to position the switch.

Before debugging, you need to build your U-Boot binary. It is assumed that you've already read our article on that subject.

Here is an example for the Nitrogen6x/BD-SL-i.MX6 (SabreLite):

~$ git clone https://github.com/boundarydevices/u-boot-imx6 -b boundary-v2016.03 ~$ cd u-boot-imx6 ~/u-boot-imx6$ export ARCH=arm ~/u-boot-imx6$ export CROSS_COMPILE=arm-linux-gnueabihf- ~/u-boot-imx6$ make nitrogen6q_defconfig ~/u-boot-imx6$ make all

Note that the output ELF binary ready to be debugged is named u-boot.

Using GDB manually

First the GDB server needs to be started.

~$ JLinkGDBServer -device MCIMX6Q6 -if JTAG -speed 1000

The i.MX6SoloX requires a specific script for the server to be able to communicate with the CPU. It can be found here:

Then a GDB session can attach to our local server (to the J-Link) in order to load the ELF binary.

As said previously, the RAM needs to be initialized first before. In order to do so, a gdb init script must be provided to set the clocks and DDR registers like the DCD table would do.

Such GDB init scripts have been created for all our boards:

For BD-SL-i.MX6 (Sabre-Lite), the nitrogen6x one must be used. The GDB command therefore looks like:

~/u-boot-imx6$ wget http://linode.boundarydevices.com/jlink/gdbinit_nitrogen6x ~/u-boot-imx6$ gdb-multiarch u-boot --nx -ix gdbinit_nitrogen6x

At this stage, the DDR and clocks are properly initialized. You now can load the ELF binary and start debugging. Here is an example.

(gdb) load Loading section .text, size 0x51b34 lma 0x17800000 Loading section .rodata, size 0x130a4 lma 0x17851b38 ... (gdb) b print_cpuinfo Breakpoint 1 at 0x17801bd4: file arch/arm/imx-common/cpu.c, line 184. (gdb) c Continuing. Breakpoint 1, print_cpuinfo () at arch/arm/imx-common/cpu.c:184 184 { (gdb) bt #0 print_cpuinfo () at arch/arm/imx-common/cpu.c:184 #1 0x17848e64 in initcall_run_list (init_sequence=init_sequence@entry=0x17866950 ) at lib/initcall.c:31 #2 0x178140a4 in board_init_f (boot_flags=) at common/board_f.c:1059 #3 0x17803ac0 in _main () at arch/arm/lib/crt0.S:93

If you are not familiar with GDB, we highly recommend this article.

Using Eclipse IDE

Disclaimer: this section will only detail how to debug U-Boot from the IDE. If you wish to build U-Boot from eclipse, we recommend you to read this pdf.

First, go to File > Import and select C/C++ Executable. Then enter the u-boot binary path into the Select Executable field.

Then the project is ready, the debug configuration now needs to be created. Go to Run > Debug Configurations and create a new GDB Segger J-Link Debugging profile.

uboot_setup1

In the Debugger tab, adjust the settings to match the picture below (Device, Endianness, Connection etc..).

uboot_setup2

Finally, in the Startup tab, copy the memU32 values from the GDB init script mentioned above to the box below.

uboot_setup3

That's it, you can hit Debug and start debugging your U-Boot source code.

uboot_debugging

Linux kernel debugging

Unlike the U-Boot setup above, the assumption of this section is that the kernel (and its device tree) is loaded from U-Boot. This eases the setup quite a lot, the JTAG just needs to connect to the target and is ready to debug.

For debugging purposes, we recommend using TFTP/NFS setup to load the kernel/device tree/OS.

Before debugging, the kernel must be built. Here are the latest instructions to do so:

~$ git clone https://github.com/boundarydevices/linux-imx6.git -b boundary-imx_4.1.15_2.0.0_ga ~$ cd linux-imx6 ~/linux-imx6$ export ARCH=arm ~/linux-imx6$ export CROSS_COMPILE=arm-linux-gnueabihf- ~/linux-imx6$ make boundary_defconfig ~/linux-imx6$ make zImage dtbs modules

If you use TFTP, don't forget to copy the zImage and dtb file to the root directory of your TFTP server.

The ELF binary that will be used for debugging is called vmlinux

Using GDB manually

Same as before, the GDB server needs to be started.

~$ JLinkGDBServer -device MCIMX6Q6 -if JTAG -speed 1000

Note that 1 JLinkGDBServer can only connect to 1 core, so if you want to debug several cores, you need to start as many GDB servers as cores. Each GDB server instance must use different ports than the other instances. Here is the command to connect to the Core1 for instance:

~$ wget http://linode.boundarydevices.com/jlink/MCIMX6-core-1.JLinkScript ~$ JLinkGDBServer -device MCIMX6Q6 -if JTAG -speed 1000 -scriptfile MCIMX6-core-1.JLinkScript -port 2334 -swoport 2335 -telnetport 2336

Then a GDB session can attach to our local server (to the J-Link) in order to load the ELF binary.

 

~/linux-imx6$ gdb-multiarch vmlinux

At this stage, the DDR and clocks are properly initialized. You now can load the ELF binary and start debugging. Here is an example.

(gdb) target remote localhost:2331 (gdb) b init/main.c:start_kernel Breakpoint 1 at 0x80c0099c: file init/main.c, line 493. (gdb) b arch/arm/mm/proc-v7.S:cpu_v7_do_idle Breakpoint 2 at 0x80119d60: file arch/arm/mm/proc-v7.S, line 72. (gdb) c Continuing.

Now that we have set our breakpoints, we can load the kernel from U-Boot. Here is an example using TFTP.

=> dhcp $fdt_addr $tftpserverip:$fdt_file => fdt addr $fdt_addr => run cmd_hdmi cmd_lcd cmd_lvds => tftp $loadaddr $tftpserverip:zImage => bootz $loadaddr - $fdt_addr

Then you should see the breakpoints on the GDB console.

Breakpoint 1, start_kernel () at init/main.c:493 493 { (gdb) c Continuing. Breakpoint 2, cpu_v7_do_idle () at arch/arm/mm/proc-v7.S:72 72 dsb @ WFI may enter a low-power mode

Using Eclipse IDE

Just like the U-Boot procedure, go to File > Import and select C/C++ Executable. Then enter the vmlinux binary path into the Select Executable field.

Then the project is ready, the debug configuration now needs to be created. Go to Run > Debug Configurations and create a new GDB Segger J-Link Debugging profile.

As said before, if you want to debug multiple cores, you need to create several debug configurations. Below is the core0 setup.

kernel_setup1

Note the difference with the U-Boot setup, here the Connect to running target is checked.

kernel_setup2

Same here, note that the Initial Reset and Halt is unchecked.

For other cores, the only differences are in the Debugger tab where you need to specify a scriptfile that you can find here. Also the ports must be different, see the Core1 setup below.

kernel_setup3

Then you can create a Launch Group that would start the debugging session on all the cores at once.

kernel_setup4

Finally you can start debugging all the cores.

kernel_debug