지금까지 다뤘던 모든 내용 (Chapter 1~7)을 기반으로 디버깅을 하는 방법에 대해서 배우는 챕터입니다.
디버깅하는 대상 시스템은
• High exception vector를 사용합니다. EVT와 bootloader는 `0xFFFF_0000`번지에 있습니다.
• 쉬운 시스템 예제를 위해
• ISR을 IRQ mode가 아닌 SYS mode에서 수행합니다.
• 사용자 애플리케이션도 SVC mode에서 수행합니다.
• R13_SYS는 `0x00FE_E730`에서 시작합니다.
1. Reset exception
1.1. 예제 1
Reset exception이 발생했습니다. 당시의 context를 확인해 보니 다음과 같았습니다.
무엇이 원인일지 천천히 살펴봅시다.
- 먼저 PC가 `0xFFFF_0000`, High exception vector를 가리키고 있습니다. (당연하지만요)
- 다음으로 CPSR을 보니 하위 2바이트가 `1101_0011` 입니다. Mode bits가 `10011`이므로 SVC mode이고, I-bit가 활성화 돼있네요. 인터럽트가 막혀있습니다. (이것도 당연하네요)
- SPSR은 사용할 수 없습니다. Reset exception은 SW의 상황을 봐가면서 나는 예외가 아니라 갑작스럽게 일어나기 때문에 SPSR에 뭔가 값을 저장하고 들어오는 게 아니기 때문에 신뢰할 수 없습니다.
- 마지막 희망은 R14 뿐입니다.
R14 `0x00ED_CF19` (Thumb 모드가 아닌데 왜 홀수인지는 모르겠네요)로 이동하니 자기 자신으로 branch하는 `b 0x00ED_CD18` 명령어가 있고 C코드로는 `while (1)`로 무한 loop가 있는 것을 확인할 수 있습니다.
'아, 여기서 계속 무한루프가 돌았구나, watchdog timer를 reset 시키지 못해서 hard reset 당했구나?'를 알 수 있습니다.
사실 마지막 희망을 R14라고 했지만, R14 조차 원래는 믿을 수 없습니다. 따라서 위 예제는 운이 좋았던 케이스입니다. 심지어
MCU reset pin에 신호를 걸어주도록 설계된 시스템의 경우에는 HW reset이 났을 때 모든 레지스터와 메모리 정보가 싹 사라지기 때문에 이런 backtracing도 불가능할 수도 있습니다.
1.2. 예제 2
이번에도 Reset exception이 발생했습니다. 신뢰할 수는 없지만, 그래도 볼 수 있는 게 이것밖에 없으니 R14를 따라가 보도록 하죠.
스택에서 R7, PC를 pop 하는 코드가 들어있네요. 일단 그 윗줄 `bl` 명령어 때문에 돌아오는 주소를 저장한 것으로 보입니다. 스택에서 값을 pop 해서 PC에 넣는다는 건 한 번 스택을 들여다봐야겠네요. R13을 보니 `0x0116_FD3C`입니다. 그 인근 스택을 덤프 해보겠습니다.
Full descending 스택이므로 R7, PC 순으로 push 됐을 겁니다.
따라서 pop 되면서 `R7 = 0x00FC_4584`, `PC = 0x00ED_CEB1`이 들어갔을 겁니다.
아하, 무한루프가 있었네요!
예제 1과 마찬가지로 watchdog timer에 의한 reset이었습니다.
2. Abort exception
Abort는 backtracing을 이용해서 문제원인을 찾기 쉽습니다.
이전에 배웠던 복귀주소 LR 보정에 대해 다시 떠올리면서 예제를 통해 디버깅하는 방법을 알아봅시다.
2.1. Data abort
Exception vector table에서 Abort handler 부분에서 stop이 걸렸습니다. 먼저 R14를 보고 생각합시다.
Data abort는 복귀주소가 LR - 8인거 기억하시죠? 8바이트를 빼준 `0x00ED_CF1E`를 보니 `STR R0, [R1]`이 있습니다.
- R0값을 R1이 가리키는 곳에 넣다가 abort가 났음을 짐작할 수 있습니다.
- 다시 context를 보니 R1이 `0xFFFF_FFFF`네요?
- 우리의 memory map에는 `0xFFFF_FFFF`가 없습니다. 여기에 접근하려고 하니 data abort가 발생한 겁니다.
- 왜그런지 C코드를 확인해봤더니 `*HWIO_ADDR = 0x8`이 있었고, `HWIO_ADDR = (volatile int) 0xFFFFFFFFF;`로 선언돼있었던게 문제였습니다.
2.2. Prefetch abort
Context를 봅시다. CPSR을 보니 ABT mode이며 PC는 0x0C로 prefetch handler를 가리키고 있으므로 prefetch abort가 발생했음을 알 수 있습니다. 따라서 복귀주소를 보정할 때는 LR - 0x4 주소로 이동하면 됩니다.
그런데 R14가 값이 이상하네요? `0x2`라니. 모종의 이유로 LR이 오염됐음을 추측할 수 있습니다.
다음 단서로 사용할 수 있는 R13을 봅시다.
- 이때 중요한 건 현재 context의 R13이 아니라 이전 mode의 R13을 봐야 합니다.
- 이전 context를 통해서 무엇이 문제인지 탐색해야겠죠?
- SPSR을 보니 이전 mode는 SVC였고, R13은 `0x0116_FD24` 였습니다.
- 여기도 R14가 이상하게 돼있네요. 뭔가 큰 영역에 값이 덮어쓰여 많은 레지스터들이 오염된 것 같음을 지레짐작 할 수 있습니다.
- 확인했더니 역시 많은 바이트가 `0xFFFF_FFFF`로 쓰여져 값이 오염돼 있음을 확인할 수 있습니다.
- Full descending 스택이기 때문에 push 한 값들 중 가장 유효한 데이터는 `0x00ED_D0FD`네요.
- 믿져야본전이니 한 번 이게 뭔지 가봅시다.
이동해 봤더니 `bl 0x00ED_CE52` 명령어가 나왔습니다.
아하, `bl` 명령어를 만나서 복귀할 주소와 context들을 스택에 남겨놓은 흔적이었네요. 이번에는 `0x00ED_CE52`로 가봅시다.
void chaos(void) {
word localBuffer[10];
memset((void *)(localBuffer - 10), 0xFF, 40);
}
이동했더니 `void chaos(void)`라는 함수가 호출되는 부분이었습니다. 아하, 저 `memeset()` 때문이네요.
Prefetch abort가 난 이유를 설명한 그림입니다. 결국 `memset()`함수에 잘못된 시작 위치, 잘못된 크기 정보를 줬기 때문에 잘못된 값이 PC 레지스터에 들어가서 예외가 발생한 것입니다. 이 코드를 수정해야겠네요.
'Embedded' 카테고리의 다른 글
Aarch64 Memory Management 요약 정리 (0) | 2025.02.25 |
---|---|
[정리/요약] ARM Cortex-M 프로그래밍 (0) | 2025.02.11 |
임베디드 레시피 Chapter 7. Device control (0) | 2025.02.01 |
임베디드 레시피 Chapter 6. RTOS & Kernel (0) | 2025.01.31 |
임베디드 레시피 Chapter 5. SW ② 스택 (0) | 2025.01.29 |