JTAG·SWD·CoreSight 분석 — ARM 디버그 인터페이스 비교
RSP는 위쪽 프로토콜. 아래에서는 실제로 전선 다섯 가닥(JTAG) 또는 두 가닥(SWD)이 칩 안의 디버그 회로를 두드립니다. 이 장은 그 전기·논리 계층을 다룹니다 — TAP 상태 머신, SWD 패킷 형식, 그리고 칩 안에 무엇이 들어 있나의 표준인 ARM CoreSight.
한 번 이걸 이해하고 나면 (gdb) break main이 어떤 비트 시퀀스로 풀려 칩의 어떤 레지스터를 건드리는지 전부 추적할 수 있습니다.
#왜 알아야 하나
- 프로브가 안 붙을 때(
init mode failed,target voltage 0.000V) 어디가 끊겼는지 추적. - 새 칩이 어떤 디버그 자원을 가졌는지(BP 개수, watchpoint 개수, ETM 유무) 파악.
- 자체 보드 설계 시 SWD/JTAG 핀 배치 결정.
- 칩 회사의 디버그 매뉴얼을 읽을 수 있게 됩니다.
#JTAG — IEEE 1149.1
1990년 표준화. 원래 목적은 PCB 제조 후 핀 솔더링 검증을 위한 boundary scan — 칩의 입력 핀에 패턴을 넣고 출력에서 받는 식. 디버그·플래시 프로그래밍은 나중에 흡수된 부가 기능.
#핀
| 핀 | 방향 | 의미 |
|---|---|---|
| TCK | 입력 | 테스트 클럭 (디버거가 공급) |
| TMS | 입력 | 모드 선택 — TAP 상태 천이 결정 |
| TDI | 입력 | 데이터 입력 (시프트인) |
| TDO | 출력 | 데이터 출력 (시프트아웃) |
| TRST | 입력 | 비동기 리셋 (옵션) |
5핀 중 TRST는 옵션. 그래서 표 헤더에서는 종종 4핀이라고도 합니다 (TRST 생략 시 TAP-Reset state는 TMS=1을 5클럭 연속으로 두면 진입).
#TAP — Test Access Port
핵심은 16-상태 유한 상태 머신. TMS 값에 따라 천이.
이 상태 머신에서 *명령 레지스터(IR)*와 *데이터 레지스터(DR)*를 시프트해서 칩의 디버그 자원을 조작합니다.
흐름 한 예 — IR에 EXTEST 명령(0x00)을 넣기.
복잡해 보이지만 디버거 펌웨어가 알아서 합니다. 사용자는 신경 안 써도 됩니다 — 다만 왜 5핀이 필요하고 어떤 신호가 어떤 역할인지를 아는 것이 트러블슈팅에 결정적.
#IR 명령
| IR 코드 (예) | 명령 | 의미 |
|---|---|---|
0x00 | EXTEST | boundary scan: 외부 핀 제어 |
0x01 | SAMPLE/PRELOAD | 핀 상태 캡처 |
0x02 | INTEST | 내부 로직 테스트 |
0x0e | IDCODE | 32-bit 칩 식별자 |
0x0f | BYPASS | 1-bit 통과 (체인 우회) |
| 칩별 | DEBUG | 디버그 자원 접근 (ARM의 SCAN_N+INTEST 등) |
IDCODE가 매우 유용. 디버거가 어떤 칩인지 처음으로 알아내는 명령.
JTAG ID code (32-bit):[31:28] Version[27:12] Part Number[11:1] Manufacturer ID (JEDEC)[0] Always 1
예: 0x4ba00477 = ARM Cortex-M4 (다양한 베리언트) 0x06433041 = STM32F4 series$ openocd -f openocd.cfgInfo : JTAG tap: stm32f4x.cpu tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd), part: 0xba00, ver: 0x4)mfg: 0x23b이 ARM, part: 0xba00이 Cortex-M의 표준 ID. 다른 IDCODE면 prober가 다른 칩에 연결된 것.
#Daisy chain
여러 칩을 한 JTAG 체인에 묶을 수 있습니다.
TDI → [Chip A] → [Chip B] → [Chip C] → TDOA와 C가 BYPASS면 A·C는 1-bit, B가 실제 명령. 디버거가 IR 명령 시퀀스를 체인 전체 길이로 보내야 하고, 내가 조작하는 칩이 몇 번째인지 알아야 합니다.
(openocd)jtag newtap stm32f4x cpu -irlen 4 -expected-id 0x4ba00477-irlen 4가 그 칩의 IR 길이. 잘못 적으면 시프트가 어긋나 모든 명령이 깨집니다.
체인 디버깅에서 자주 만나는 Invalid ACK 오류는 보통 IR 길이가 틀린 경우.
#SWD — Serial Wire Debug
ARM이 핀 수를 줄이기 위해 만든 2핀 대체. JTAG 5핀 → SWD 2핀(SWCLK/SWDIO) + 옵션 1핀(SWO).
| 핀 | 의미 |
|---|---|
| SWCLK | 클럭 (디버거 공급) |
| SWDIO | 양방향 데이터 (반이중) |
| SWO | (옵션) 1핀 trace 출력 |
같은 핀(SWCLK/SWDIO)이 JTAG mode/SWD mode로 양립합니다 — STM32 같은 칩은 처음에 JTAG 모드로 시작했다가 어떤 패턴을 받으면 SWD로 전환.
JTAG → SWD 전환 시퀀스 (16-bit on SWDIO):
- 0x79E7
SWD → JTAG도 비슷한 매직 시퀀스 존재.
#SWD 패킷 형식
JTAG의 상태 머신과 달리 패킷 기반.
APnDP— Access Port(0)인가 Debug Port(1)인가?RnW— read(1) or write(0)?A2..A3— 4바이트 정렬 주소 비트.Parity— request의 APnDP+RnW+A2+A3 패리티.
#DP — Debug Port 레지스터
A2 A3:
- 00 IDCODE (read) / ABORT (write)
- 01 CTRL/STAT (control + status)
- 10 SELECT (어느 AP의 어느 뱅크?)
- 11 RDBUFF (read) / 미사용 (write)
SELECT로 어느 AP에 접근할지 + 그 AP의 어느 256바이트 뱅크인지 지정. AP 안의 4바이트 레지스터 16개 = 64바이트 단위로 잡지만 ARM의 표준 AP는 4개 뱅크 = 256바이트.
#AP — Access Port 레지스터
AP 종류:
- MEM-AP(AHB-AP, AXI-AP, APB-AP) — 칩 내부 메모리 버스 접근. CPU와 같은 주소 공간으로 메모리·MMIO를 읽고 씁니다.
- JTAG-AP — JTAG 명령을 SWD로 터널링 (드물게 사용).
MEM-AP의 주요 레지스터:
| 오프셋 | 이름 | 역할 |
|---|---|---|
| 0x00 | CSW (Control/Status Word) | 접근 크기, 자동 증분 |
| 0x04 | TAR (Transfer Address) | 다음 접근 주소 |
| 0x0c | DRW (Data R/W) | 데이터 |
| 0xfc | IDR | AP 식별자 |
SWD 패킷 한 단위로 32비트만 전송. 그러므로 임의 주소의 한 워드를 읽으려면:
- DP-SELECT로 AP와 뱅크 선택.
- AP-CSW로 32-bit access 설정 (보통 디버거가 이미 해 둠).
- AP-TAR에 주소 쓰기.
- AP-DRW 읽기 → 그 주소의 데이터.
총 3-4 패킷. 한 워드 읽기가 SWD 약 30-40 클럭. 4 MHz SWCLK에서 ~10µs.
연속 읽기는 CSW의 AddrInc 비트로 가속 — TAR을 한 번 쓰고 DRW를 연속 읽기, AP가 자동으로 주소 증분.
#SWO — Trace 출력
SWD의 3핀 (옵션). MCU에서 UART처럼 trace 패킷을 송출. 디버거가 받아 PC로 전달.
baud는 코어 클럭 / divider. 일반적으로 2 MBaud. 디버거 펌웨어가 코어 클럭을 알아야 정확한 디코딩 가능 — 그래서 tpiu config <core_clock_hz> <swo_baud> 같은 명령으로 코어 클럭을 통보합니다.
(openocd telnet) tpiu config internal /tmp/swo.log uart off 168000000 2000000코어 클럭이 실제와 다르면 trace가 garbled. 흔한 함정.
#ARM CoreSight — 디버그 IP 묶음
JTAG/SWD가 물리 인터페이스라면 그 위에서 실제로 일하는 게 칩 안의 CoreSight입니다. ARM이 표준화한 디버그·trace IP 블록 카탈로그.
| 블록 | 메모리 위치 (Cortex-M) | 역할 |
|---|---|---|
| DAP | (전용 SWD/JTAG 인터페이스) | 외부 ↔ 내부 버스 게이트웨이 |
| SCS/SCB | 0xE000ED00 시작 | System Control Block — DHCSR 등 |
| DWT | 0xE0001000 | Data Watchpoint and Trace — 4 워치포인트, 사이클 카운터 |
| FPB | 0xE0002000 | Flash Patch and Breakpoint — 6 hardware BP |
| ITM | 0xE0000000 | Instrumentation Trace Macrocell — software trace |
| TPIU | 0xE0040000 | Trace Port Interface Unit — SWO 출력 |
| ETM | 0xE0041000 | Embedded Trace Macrocell — 명령 trace (옵션) |
모두 메모리 매핑. 즉 CPU도 이 레지스터를 직접 쓸 수 있고, 디버거도 SWD를 통해 같은 주소를 두드릴 수 있습니다. 디버거 ↔ CPU의 경쟁 가능 — 보통은 디버거가 CPU를 halt시킨 뒤 접근.
#SCB / DHCSR — halt 메커니즘
break main이 어떻게 일어나나? 답은 DHCSR(Debug Halting Control and Status Register, 주소 0xE000EDF0).
DHCSR:
- [31] DBGKEY (= 0xA05F to write)
- [25] S_RESET_ST (reset since last read)
- [24] S_RETIRE_ST (instruction retired)
- [19] S_LOCKUP
- [18] S_SLEEP
- [17] S_HALT ← 1이면 정지
- [16] S_REGRDY
- [5] reserved
- [3] C_MASKINTS (mask interrupts in halt)
- [2] C_STEP
- [1] C_HALT ← 1로 쓰면 정지
- [0] C_DEBUGEN ← 1이면 디버그 가능
break main의 흐름.
- 디버거가 FPB에
main의 주소를 기록 + 활성화. - CPU가 fetch 시 PC가 FPB와 일치 → core가 debug state로 천이.
- 디버거가 DHCSR.S_HALT를 폴링하다 1이 되면 GDB에 stop 패킷 전송.
- GDB가
bt/print요청 → 디버거가 DCRSR(register select)로 레지스터를 한 개씩 끌어옴. continue→ DHCSR.C_HALT = 0 → CPU 재개.
monitor halt는 외부에서 DHCSR.C_HALT = 1을 강제로 써서 CPU를 멈추는 것.
DHCSR을 직접 들여다보면 왜 CPU가 안 멈추는지를 디버깅할 수 있습니다.
(openocd) mdw 0xe000edf00xe000edf0: 00010003 ← S_HALT=1, C_DEBUGEN=1, C_HALT=1#DCRSR / DCRDR — 레지스터 읽기
DCRSR (0xE000EDF4): [16] REGWnR — 1=write, 0=read [6:0] REGSEL — 어느 레지스터 (0=R0, ..., 15=PC, 16=xPSR, ...)
DCRDR (0xE000EDF8): 데이터 워드디버거가 R0 읽기:
- DCRSR에
0x00000000(REGWnR=0, REGSEL=0) 쓰기. - DHCSR.S_REGRDY 폴링 (작업 완료).
- DCRDR 읽기 → R0 값.
레지스터 16개를 모두 받으면 17 패킷 왕복. SWD에서 그래도 ~1ms 안에 끝.
#FPB — 브레이크포인트
Flash Patch and Breakpoint Unit. 6개의 주소 비교기(comparator) + 2개의 literal. 비교기에 주소를 넣고 활성화하면 CPU fetch가 그 주소에 도달했을 때 halt.
FP_COMP0 (0xE0002008):
- [31] REPLACE — 00=BP, 01/10/11=instruction patch
- [28] COMP — 비교 주소 (4바이트 정렬, 비트 0 무시)
- [0] ENABLE
GDB의 break main → 디버거가 FPB에 &main을 기록 + ENABLE.
(openocd) mdw 0xe0002000 80xe0002000: 00000010 ... ← FP_CTRL: NUM_CODE=2, NUM_LIT=00xe0002008: 08000125 ... ← FP_COMP0: BP at 0x08000124+1 (Thumb)Cortex-M0/M0+는 보통 4개, M3/M4/M7은 6개, M33+은 8개. flash patch는 별도 라이센스 + 칩 옵션.
#Software vs Hardware BP
Z0 (sw): 명령어 한 바이트를 BKPT #0 (Thumb: 0xBExx)로 갈아 끼우고 원래 명령은 별도 저장. 개수 무제한이지만 flash엔 못 씀.
Z1 (hw): FPB 비교기 1개 소모. flash·ROM에서 동작하지만 유한.
GDB는 보통 Z0을 먼저 시도하고, flash 영역이면 Z1로 폴백.
J-Link의 Unlimited Flash Breakpoints는 7번째 BP부터 flash 페이지 패치로 흉내냅니다.
#DWT — 워치포인트와 카운터
DWT_CTRL (0xE0001000): [31:28] NUMCOMP — 비교기 개수 (보통 4) [24] CYCEVTENA ... [0] CYCCNTENA ← 사이클 카운터 활성화
DWT_CYCCNT (0xE0001004): 32-bit 사이클 카운터DWT_COMP0 (0xE0001020): 비교 주소DWT_MASK0 (0xE0001024): 비트 마스크DWT_FUNCTION0 (0xE0001028): 동작 (read/write/access)GDB의 watch *my_var → DWT 비교기 한 개 소모. 4개 한도.
사이클 카운터는 µs 측정에 매우 유용.
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;uint32_t start = DWT->CYCCNT;do_work();uint32_t cycles = DWT->CYCCNT - start;// us = cycles / (CoreClock_MHz)168 MHz 코어에서 168 cycles = 1 µs.
DWT는 exception trace도 지원 — 어떤 ISR이 언제 들어오고 나갔는지 SWO로 송출. OS-aware 디버깅의 핵심.
#ITM — Software Trace
ITM은 32개의 stim port. 워드를 쓰면 SWO/TRACE 핀으로 trace 패킷 송출.
#define ITM_PORT_U32(n) (*((volatile uint32_t *)(0xE0000000 + 4*(n))))#define ITM_PORT_U8(n) (*((volatile uint8_t *)(0xE0000000 + 4*(n))))#define ITM_TER (*((volatile uint32_t *)(0xE0000E00))) // Trace Enable#define ITM_TCR (*((volatile uint32_t *)(0xE0000E80))) // Trace Control
void itm_putchar(char c) { if (!(ITM_TCR & 1)) return; // ITM disabled if (!(ITM_TER & 1)) return; // port 0 disabled while ((ITM_PORT_U32(0) & 1) == 0); // wait FIFO ITM_PORT_U8(0) = c;}OpenOCD/J-Link가 SWO trace를 받아 stim port 0을 stdout처럼 디코딩.
(openocd) tpiu config internal /tmp/swo.log uart off 168000000 2000000(openocd) itm port 0 on168 MHz 코어 / 2 MBaud SWO. 코어 클럭이 안 맞으면 깨진 글자.
#ETM — 명령 단위 Trace
ETM(Embedded Trace Macrocell)이 있으면 CPU의 모든 명령어 실행이 trace됩니다. 일반 4-5 핀 parallel TRACE 인터페이스 또는 ETB(Embedded Trace Buffer, 칩 안 RAM에 저장).
SEGGER J-Trace, Lauterbach TRACE32 같은 고가 도구가 받습니다.
쓰임:
- 비결정적 버그의 완전한 이전 시퀀스 재구성 (rr의 베어메탈 버전).
- 인터럽트 latency 측정.
- 캐시 hit/miss 통계.
Cortex-M7/M33은 ETM-M4 옵션 탑재. 모든 칩에 있는 건 아닙니다. opdata: M0/M0+는 ETM 없음, M3/M4는 옵션, M7은 보통 탑재.
#CTI — Cross Trigger Interface
멀티 코어 칩(예: Cortex-A + Cortex-M)에서 한 코어의 halt를 다른 코어에 신호로 전달. 동기 멈춤에 사용. 자체 보드 디버깅 시 직접 만질 일은 거의 없지만 SoC 디버깅에서 가끔 등장.
#신호 무결성 — 실전
이론과 별개로 전기적 문제가 디버깅을 망치는 일이 잦습니다.
#길이
SWD/JTAG 케이블이 너무 길면 (>15cm) capacitance가 증가해 고속 SWCLK가 망가집니다. adapter speed 4000 (4 MHz)이 잘 안 되면 1000 (1 MHz)부터 시도.
#풀업
대부분의 칩은 SWDIO에 내부 풀업이 있지만 일부는 외부 풀업(~10kΩ)이 필요. 이게 없으면 reset 직후 floating.
#Vref
JTAG 커넥터에 Vref 핀이 있어 디버거가 타깃 전압을 감지합니다. 1.65V~3.3V 자동 적응. Vref가 0V면 (Target voltage: 0.000V) 다음 중 하나:
- 타깃 전원 안 들어옴.
- Vref 핀 미연결.
- 타깃 ↔ 디버거 GND 미연결.
#RESET
JTAG 커넥터의 nRESET (SRST)이 적극적으로 토글되어야 원치 않게 펌웨어가 디버거 통신을 무력화한 경우에도 정상 진입 가능. OpenOCD의 reset_config srst_only srst_nogate가 자주 도움.
(openocd config)reset_config srst_only srst_nogate connect_assert_srst#디커플링
칩 가까이 100nF 디커플링 캡이 없으면 디버그 자체 트랜잭션이 일으키는 순간 전류가 칩에 노이즈를 줘 SWD가 자꾸 끊깁니다. 자체 보드면 항상 0.1µF 디커플링 + 10µF 벌크.
#프로브 종류
| 프로브 | 인터페이스 | 가격 | 비고 |
|---|---|---|---|
| ST-Link/V2 | SWD/JTAG | 매우 저렴 (보드 내장) | STM32에 묶임 |
| ST-Link/V3 | SWD/JTAG, VCOM, MSC | 저렴 | 더 빠름, VCP 유용 |
| CMSIS-DAP / DAPLink | SWD/JTAG (CMSIS-DAP) | 저렴 | ARM 표준 |
| J-Link EDU mini | SWD/JTAG | 저렴 (비상업용) | 가성비 |
| J-Link BASE/PLUS | SWD/JTAG | 비쌈 | 상용·산업 |
| J-Trace | SWD/JTAG + ETM | 매우 비쌈 | 명령 trace |
| Black Magic Probe | SWD/JTAG | 중간 | 내장 gdbserver (호스트 OS 없이 직접) |
| FT2232 + breakout | SWD/JTAG | 매우 저렴 | 범용 |
CMSIS-DAP가 표준 — 같은 펌웨어가 거의 모든 보드의 디버그 인터페이스에 호환. 새 칩 → CMSIS-DAP 검증 → 그 다음 다른 프로브 시도.
#정리
- JTAG = 5핀 + TAP 16-상태 FSM. boundary scan에서 디버그까지.
- SWD = 2핀 패킷 기반 (ARM 전용).
- DP·AP·MEM-AP가 SWD/JTAG → 내부 버스 게이트웨이.
- CoreSight = DAP + FPB(BP) + DWT(WP/cycle) + ITM(printf trace) + ETM(명령 trace) + TPIU(출력).
- DHCSR이 halt 메커니즘의 핵심 레지스터.
- FPB가 hardware BP를 제공 (M3/M4=6, M0=4).
- DWT가 watchpoint + cycle counter.
- ITM/SWO가 가벼운 trace, ETM이 완전 trace.
- 신호 무결성(케이블 길이·디커플링·Vref·SRST)이 트러블슈팅의 80%.
#다음 장 예고
Ch 3 — OpenOCD 깊이. TCL 인터프리터, flash driver, target 정의 파일, custom 명령 작성.
#관련 항목
- Ch 1: RSP 프로토콜 — 위쪽 계층
- Ch 3: OpenOCD 깊이
- ARMv7-M Architecture Reference Manual — CoreSight 정의
- ARM Debug Interface Architecture v5 — DAP 명세
- CMSIS-DAP
- Black Magic Probe
Embedded Debugging · 2 of 9
- 1GDB Remote Serial Protocol 분석 — 디버거-타겟 통신 메커니즘
- 2JTAG·SWD·CoreSight 분석 — ARM 디버그 인터페이스 비교
- 3OpenOCD 심화 분석 — Configuration·Adapter·Target 통합
- 4J-Link 도구 체인 분석 — JLinkExe·RTT·GDB Server 활용
- 5ELF와 MAP 파일 분석 — 베어메탈 메모리 레이아웃 추적
- 6임베디드 Trace 비교 — RTT·ITM·SWO·ETM·Semihosting 선택
- 7RTOS-aware 디버깅과 트러블슈팅 — Task·Queue·Stack 분석
- 8CXL Link Training 디버깅 — LTSSM 상태와 Protocol Analyzer 활용
- 9CXL 디바이스 트러블슈팅 — RAS 이벤트·Poison List·Media Error 추적
관련 글
RTOS-aware 디버깅과 트러블슈팅 — Task·Queue·Stack 분석
FreeRTOS/Zephyr task 콜스택, Hardfault 분석, MPU, 신호 무결성, 보안 lock 해제.
임베디드 Trace 비교 — RTT·ITM·SWO·ETM·Semihosting 선택
printf 없이 펌웨어 로그·trace 빼내기. 다섯 가지 방법 비교 + 코드 예제.
ELF와 MAP 파일 분석 — 베어메탈 메모리 레이아웃 추적
ELF의 LMA/VMA, 링커 스크립트, MAP 파일로 메모리 진단, startup 코드의 정체.