UART and JTAG Debug Access
TL;DR: UART gives you a console, JTAG gives you the CPU — find the pins with a multimeter and a logic analyser, then drive them with a USB adapter and openocd.
What it is
Most embedded devices leave debug headers on the board: UART for the boot/serial console and JTAG (or SWD on Cortex-M) for halt-mode CPU debugging. UART is the cheapest win — a working console often exposes a U-Boot prompt that lets you dump memory, boot from TFTP, or drop to a root shell. JTAG is heavier artillery: with openocd attached you can halt the CPU, read/write memory, set breakpoints, and single-step the kernel.
Preconditions / where it applies
- Physical access to the PCB and the ability to solder or hold pogo pins
- A 3.3V USB-UART adapter (FT232, CP2102, or TIGARD), and a JTAG probe (TIGARD, FT2232H, Segger J-Link, or Bus Blaster)
- A logic analyser (Saleae Logic 8 or a cheap CY7C68013A clone) and a multimeter
- Optional but huge: a JTAGulator for blind pin discovery on unknown headers
Technique
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. UART pin discovery with a multimeter
# - GND: continuity to the chassis / shield
# - VCC: steady 3.3V or 1.8V relative to GND
# - TX: idles high (~3.3V) and dips during boot output
# - RX: idles high but floats — pulled low only when host transmits
# 2. Baudrate hunt — capture TX with a logic analyser, measure the
# shortest pulse width, then baud = 1 / pulse_width_seconds
# Common values: 9600, 38400, 57600, 115200, 921600
picocom -b 115200 /dev/ttyUSB0
# If output is garbled, try: 57600, 38400, 9600 — or use sigrok-cli + uart decoder
# 3. Catch the U-Boot autoboot countdown
# Spam Enter / Ctrl-C / 'x' during the "Hit any key to stop autoboot" window
=> printenv
=> md.b 0x80000000 0x100 # memory dump
=> setenv bootargs 'init=/bin/sh console=ttyS0,115200'
=> boot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4. JTAG pin mapping with a JTAGulator
# Connect all 24 channels to the candidate header, then run BYPASS scan
# JTAGulator reports the TDI/TDO/TCK/TMS/TRST mapping and IDCODE
# 5. openocd config — example for an FT2232H probe and an ARM926EJ-S target
openocd -f interface/ftdi/tigard.cfg -f target/arm926ejs.cfg
# In another terminal:
telnet localhost 4444
> halt
> reg
> mdw 0x00000000 16
> load_image vmlinux 0x80008000
# 6. Single-step the kernel after attach
arm-none-eabi-gdb vmlinux
(gdb) target remote :3333
(gdb) hbreak start_kernel
(gdb) continue
(gdb) stepi
If JTAG is fused off but UART still works, the U-Boot console is often the path of least resistance — md and mw let you read and write physical memory before the kernel takes over.
Detection and defence
- Production builds should strip the kernel console (
CONFIG_PRINTK=norquietin bootargs) and disable the U-Boot autoboot key - JTAG can be permanently disabled by blowing the SoC’s debug-disable OTP fuse (TI
JTAG_DIS, NXPJTAG_SECURITY) - Secure boot rooted in mask ROM stops a modified kernel from running even after a JTAG memory write — the next reset re-verifies signatures
- Anti-tamper switches and conformal coating raise the cost of board access; epoxy on debug headers is a cheap deterrent
- Glitch-resistant fuse readout (Intel CSME, NXP HABv4) prevents fuse-bypass via voltage / clock glitching
References
- JTAGulator — Joe Grand’s pin-discovery tool
- openocd user guide — config and command reference
- U-Boot environment commands —
printenv,setenv,bootm - sigrok PulseView — logic-analyser UART decode
See also: firmware-extraction, executable-files-pe-elf.