You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
9.2 KiB
Markdown
266 lines
9.2 KiB
Markdown
# Tutorial 0B - Hardware Debugging using JTAG
|
|
|
|
In the upcoming tutorials, we are going to touch sensitive areas of the RPi's
|
|
SoC that can make our debugging life very hard. For example, changing the
|
|
processor's `Exception Level` or introducing `virtual memory`.
|
|
|
|
A hardware based debugger can sometimes be the last resort when searching for a
|
|
tricky bug. Especially for debugging intricate, architecture-specific HW issues,
|
|
it will be handy, because in this area `QEMU` sometimes can not help, since it
|
|
abstracts certain features of our RPi's HW and doesn't simulate down to the very
|
|
last bit.
|
|
|
|
So lets introduce `JTAG` debugging. Once set up, it will allow us to single-step
|
|
through our kernel on the real HW. How cool is that?!
|
|
|
|
![JTAG live demo](../doc/jtag_demo.gif)
|
|
|
|
## Outline
|
|
|
|
Functionally, this tutorial is the same as the previous one, where we reset or
|
|
power down the RPi. Around that, we add infrastructure for JTAG debugging.
|
|
|
|
## Hardware
|
|
|
|
Unlike microcontroller boards like the `STM32F3DISCOVERY`, which is used in our
|
|
WG's [Embedded Rust Book](https://rust-embedded.github.io/book/start/hardware.html),
|
|
the RPi does not have an embedded debugger on it's board. Hence, you need to buy one.
|
|
|
|
For this tutorial, we will use the
|
|
[ARM-USB-TINY-H](https://www.olimex.com/Products/ARM/JTAG/ARM-USB-TINY-H/) by
|
|
OLIMEX. It has a standard
|
|
[ARM JTAG 20 connector](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0499dj/BEHEIHCE.html).
|
|
Unfortunately, the RPi does not, so we have to connect it via jumper wires.
|
|
|
|
### Wiring
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>GPIO #</th>
|
|
<th>Name</th>
|
|
<th>JTAG #</th>
|
|
<th>Note</th>
|
|
<th width="60%">Diagram</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td></td>
|
|
<td>VTREF</td>
|
|
<td>1</td>
|
|
<td>to 3.3V</td>
|
|
<td rowspan="8"><img src="../doc/wiring_jtag.png"></td>
|
|
</tr>
|
|
<tr>
|
|
<td></td>
|
|
<td>GND</td>
|
|
<td>4</td>
|
|
<td>to GND</td>
|
|
</tr>
|
|
<tr>
|
|
<td>22</td>
|
|
<td>TRST</td>
|
|
<td>3</td>
|
|
<td></td>
|
|
</tr>
|
|
<tr>
|
|
<td>26</td>
|
|
<td>TDI</td>
|
|
<td>5</td>
|
|
<td></td>
|
|
</tr>
|
|
<tr>
|
|
<td>27</td>
|
|
<td>TMS</td>
|
|
<td>7</td>
|
|
<td></td>
|
|
</tr>
|
|
<tr>
|
|
<td>25</td>
|
|
<td>TCK</td>
|
|
<td>9</td>
|
|
<td></td>
|
|
</tr>
|
|
<tr>
|
|
<td>23</td>
|
|
<td>RTCK</td>
|
|
<td>11</td>
|
|
<td></td>
|
|
</tr>
|
|
<tr>
|
|
<td>24</td>
|
|
<td>TDO</td>
|
|
<td>13</td>
|
|
<td></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<p align="center"><img src="../doc/image_jtag_connected.jpg" width="50%"></p>
|
|
|
|
## Configuring GPIO for JTAG
|
|
|
|
Before it is possible to connect, we additionally have to configure the
|
|
respective GPIO pins for `JTAG` functionality from software. Our approach is as
|
|
allows:
|
|
|
|
Via `raspboot`, we load a tiny helper binary onto the RPi which configures the
|
|
pins respectively and then parks the executing core in an endless loop, waiting
|
|
for the `JTAG` debugger to connect. The helper binary is maintained separately
|
|
in this repository's [X1_JTAG_boot](../X1_JTAG_boot) folder, and is a
|
|
stripped-down version of the code we use in our tutorials.
|
|
|
|
This functionality is provided by the new `Makefile` target `make jtagboot`.
|
|
|
|
```console
|
|
ferris@box:~$ make jtagboot
|
|
Raspbootcom V1.0
|
|
### Listening on /dev/ttyUSB0
|
|
RBIN64
|
|
### sending kernel /jtag/jtag_boot.img [759 byte]
|
|
### finished sending
|
|
|
|
|
|
[i] JTAG is live. Please connect.
|
|
```
|
|
|
|
It is important to keep the USB serial connected and the terminal open with
|
|
`raspboot` running. When we load the actual kernel later, `UART` output will
|
|
appear here.
|
|
|
|
## OpenOCD
|
|
|
|
Next, we need to launch the [Open On-Chip Debugger](http://openocd.org/), aka
|
|
`OpenOCD` to actually connect the `JTAG`.
|
|
|
|
As always, our tutorials try to be as painless as possible regarding dev-tools,
|
|
which is why we have packaged everything into a [dedicated Docker container](../docker/raspi3-openocd) that will be provisioned automagically on the first run.
|
|
|
|
|
|
|
|
Now connect the Olimex USB JTAG debugger, open a new terminal and in the same
|
|
folder, type `make openocd` (in that order!). You will see some initial output:
|
|
|
|
```console
|
|
ferris@box:~$ make openocd
|
|
Open On-Chip Debugger 0.10.0+dev-ge243075 (2019-03-07-19:07)
|
|
Licensed under GNU GPL v2
|
|
For bug reports, read
|
|
http://openocd.org/doc/doxygen/bugs.html
|
|
trst_and_srst separate srst_gates_jtag trst_push_pull srst_open_drain connect_deassert_srst
|
|
adapter speed: 1000 kHz
|
|
jtag_ntrst_delay: 500
|
|
Info : Listening on port 6666 for tcl connections
|
|
Info : Listening on port 4444 for telnet connections
|
|
Info : clock speed 1000 kHz
|
|
Info : JTAG tap: rpi3.tap tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd.), part: 0xba00, ver: 0x4)
|
|
Info : rpi3.core0: hardware has 6 breakpoints, 4 watchpoints
|
|
Info : rpi3.core1: hardware has 6 breakpoints, 4 watchpoints
|
|
Info : rpi3.core2: hardware has 6 breakpoints, 4 watchpoints
|
|
Info : rpi3.core3: hardware has 6 breakpoints, 4 watchpoints
|
|
Info : Listening on port 3333 for gdb connections
|
|
Info : Listening on port 3334 for gdb connections
|
|
Info : Listening on port 3335 for gdb connections
|
|
Info : Listening on port 3336 for gdb connections
|
|
```
|
|
|
|
`OpenOCD` has detected the four cores of the RPi, and opened four network ports
|
|
to which `gdb` can now connect to debug the respective core.
|
|
|
|
## GDB
|
|
|
|
Finally, we need an `AArch64`-capable version of `gdb`. You guessed right, we
|
|
packaged [another container for you](../docker/raspi3-gdb). It can be launched
|
|
via `make gdb`.
|
|
|
|
This Makefile target actually does a little more. It builds a special version of
|
|
our kernel with debug information included. This enables `gdb` to show the `Rust`
|
|
source code line we are currently debugging. It also launches `gdb` such
|
|
that it already loads this debug build (`kernel8_for_jtag`).
|
|
|
|
We can now use the `gdb` commandline to
|
|
1. Set breakpoints in our kernel
|
|
2. Load the kernel via JTAG in memory (remember that currently, the RPi is still executing
|
|
the minimal JTAG pin enablement binary).
|
|
3. Manipulate the program counter of the RPi to start execution at our kernel's entry point.
|
|
4. Single-step through its execution.
|
|
|
|
```shell
|
|
>>> break _boot_cores
|
|
Breakpoint 1 at 0x80000
|
|
>>> target remote :3333 # Connect to OpenOCD, raspi3.core0
|
|
>>> load # Load the kernel into the Raspi's DRAM over JTAG.
|
|
Loading section .text, size 0x6cc lma 0x80000
|
|
Loading section .rodata, size 0x9a lma 0x806cc
|
|
Start address 0x80000, load size 1894
|
|
Transfer rate: 66 KB/sec, 947 bytes/write.
|
|
>>> set $pc = 0x80000 # Set RPI's program counter to the start of the kernel binary.
|
|
>>> cont
|
|
Breakpoint 1, 0x0000000000080000 in _boot_cores ()
|
|
>>> step
|
|
>>> step # Single-step through the kernel
|
|
>>> ...
|
|
```
|
|
|
|
### Remarks
|
|
|
|
#### Optimization
|
|
|
|
When debugging an OS binary, you have to make a trade-off between the
|
|
granularity at which you can step through your Rust source-code and the
|
|
optimization level of the generated binary. The `make` and `make gdb` targets
|
|
produce a `--release` binary, which includes an optimization level of three
|
|
(`-opt-level=3`). However, in this case, the compiler will inline very
|
|
aggressively and pack together reads and writes where possible. As a result, it
|
|
will not always be possible to hit breakpoints exactly where you want to
|
|
regarding the line of source code file.
|
|
|
|
For this reason, the Makefile also provides the `make gdb-opt0` target, which
|
|
uses `-opt-level=0`. Hence, it will allow you to have finer debugging
|
|
granularity. However, please keep in mind that when debugging code that closely
|
|
deals with HW, a compiler optimization that squashes reads or writes to volatile
|
|
registers can make all the difference in execution. FYI, the demo gif above has
|
|
been recorded with `gdb-opt0`.
|
|
|
|
#### GDB control
|
|
|
|
At some point, you may reach delay loops or code that waits on user input from
|
|
the serial. Here, single stepping might not be feasible or work anymore. You can
|
|
jump over these roadblocks by setting other breakpoints beyond these areas, and
|
|
reach them using the `cont` command.
|
|
|
|
Pressing `ctrl+c` in `gdb` will stop execution of the RPi again in case you
|
|
continued it without further breakpoints.
|
|
|
|
## Notes on USB connection constraints
|
|
|
|
If you followed the tutorial from top to bottom, everything should be fine
|
|
regarding USB connections.
|
|
|
|
Still, please note that in its current form, our `Makefile` makes implicit
|
|
assumptions about the naming of the connected USB devices. It expects
|
|
`/dev/ttyUSB0` to be the `UART` device.
|
|
|
|
Hence, please ensure the following order of connecting the devices to your box:
|
|
1. Connect the USB serial.
|
|
2. Afterwards, the Olimex debugger.
|
|
|
|
This way, Linux enumerates the devices accordingly. This has to be done only
|
|
once. It is fine to disconnect and connect the serial multiple times, e.g. for
|
|
kicking off different `make jtagboot` runs, while keeping the debugger
|
|
connected.
|
|
|
|
## In summary
|
|
|
|
1. `make jtagboot` and keep terminal open.
|
|
2. Connect USB serial device.
|
|
3. Connect `JTAG` debugger USB device.
|
|
4. In new terminal, `make openocd`.
|
|
5. In new terminal, `make gdb` or make `make gdb-opt0`.
|
|
|
|
## Acknowledgments
|
|
|
|
Thanks to [@naotaco](https://github.com/naotaco) for laying the groundwork for
|
|
this tutorial.
|