https://velog.io/@embeddedjune/%EC%9E%84%EB%B2%A0%EB%94%94%EB%93%9C-%EB%A0%88%EC%8B%9C%ED%94%BC-%EC%9A%94%EC%95%BD-%EB%B0%8F-%EC%A0%95%EB%A6%AC-Chapter-8.-Debug

 

[임베디드 레시피 요약 및 정리] Chapter 8. Debug

지난 chapter 1~7의 모든 내용이 머릿속에 있다는 가정하에 이야기를 전개한다.이 chapter는 가상의 시스템을 가정하고 특정 문제상황에서 어떻게 디버깅을 하면 좋을지 설명한다.따라서 시스템의

velog.io

0. 들어가기 전에

  • 지난 chapter 1~7의 모든 내용이 머릿속에 있다는 가정하에 이야기를 전개한다.
  • 이 chapter는 가상의 시스템을 가정하고 특정 문제상황에서 어떻게 디버깅을 하면 좋을지 설명한다.
  • 따라서 시스템의 특징에 대해 먼저 정의를 내리고 이야기를 진행해야 한다.
  • 이 시스템은
    1. High vector를 사용한다. 따라서 EVT와 bootloader는 0xFFFF_0000번지에 존재한다.
    2. ISR을 SYS mode에서 처리한다. (∵ Nesting interrupt & mode change를 용이하게 하기 위해)
    3. SVC mode에서 user application과 kernel을 모두 실행한다.
    4. R13_SYS의 시작주소는 0x00FE_E730이다.

1. Reset 디버깅

1.1. Interrupt lock

  • 우선 PC값이 High EVT의 reset handler를 가리키고 있기 때문에 HW reset이 발생했음을 눈치챌 수 있다.
  • Reset이 발생했으니 당연히 SVC mode다.
  • 안타깝게도 reset은 일반적인 상황이 아니다보니 제대로 context가 남아있지 않을 때도 많기 때문에 SPSR의 정보를 신뢰할 수 없다. 현재 우리는 지푸라기 잡는 심정으로 LR을 믿어보는 수 밖에 없다.
  • LR이 가리키는 0x00EDCF19로 이동해보자. 만일 LR이 제대로 정보를 가지고 있다면, 이 근방에서 reset이 발생했을 것이다.

  • 해당 주소로 이동해보니 0x00ED_CF18번지에서 b명령어로 자기자신으로 branch 하는 line을 발견할 수 있다.
  • 원인은 다름아니라 while(1); 무한 loop를 발생시키는 코드 때문이었다.
  • 그래서 task가 끝나지 못하고 watch dog task에게 report해서 timer를 reset 시키지 못해 HW reset이 발생한 것이다.
  • 사실 굉장히 운이 좋은 경우다. Reset은 LR의 유효성을 보장하지 않는다. 그 위의 Interrupt_Lock() 덕분에 blx명령어가 실행돼 LR에 복귀주소가 기록되는 바람에 LR에 유용한 정보가 남아있었던 것이다.
  • 게다가 MCU reset pin에 신호를 걸어주도록 설계된 시스템의 경우에는 HW reset이 났을 때 모든 레지스터와 메모리 정보가 싹 사라지기 때문에 디버깅이 매우 힘들어진다.

1.2. Task lock

  • 이번에는 조금 더 어려운 상황을 생각해보자.

  • 이번에도 PC는 EVT의 reset handler를 가리키고 있으므로 HW reset이 발생했음을 눈치챌 수 있다.
  • 이번에도 LR을 믿어보자. 0x0000_1323번지를 덤프해보면 다음과 같다.

  • LR은 bl명령어를 만나 Interrupt_Free()를 수행한 뒤 복귀주소로 0x0000_1323번지를 담고 있었다.
  • 그리고 pop명령어는 R7, PC값을 반환했다. 저 PC값이 무엇이었는지 확인하기 위해 R13이 가리키는 주소 0x0116_FD3C로 이동해보자.

  • R13은 0을 가리키고 있다. Stack은 full descending stack이니까 pop을 2회 하면 주소가 높아졌을 것이다.
  • 즉, SP를 두 칸 아래로 옮기면, 0x00FC_4584는 R7, 0x00ED_CEB1은 PC에 저장됐을 것이다.
  • 이제 원래 PC값을 알게 됐으니 0x00ED_CEB1번지를 덤핑해보자.
  • 덤핑해보니 방금 다뤘던 시나리오처럼 무한 loop를 발생하는 while(1)문이 들어있었음을 확인할 수 있다. 따라서 task가 끝나지 못하고 watch dog에 의한 HW reset이 발생했음을 알 수 있다.
  • 이번에는 LR과 SP를 활용해서 stack backtracing을 통해 문제 원인을 밝힐 수 있었다.

1.3. 0x0 branch

  • 이번에도 reset이 발생했는데 특이하게 PC가 EVT를 가리키지 않고 low vector 일 때의 EVT를 가리키고 있다.
  • 즉, HW reset이 아니라 SW적인 원인으로 발생한 reset임을 눈치챌 수 있다.
  • 우선 이번에도 우리가 할 수 있는건 LR을 따라가는 것 뿐이니 0x00ED_CF3B로 이동해보자.

  • 당장 눈에 보이는건 blx명령어인데, 이전까지 Thumb mode로 실행되고 있다가 이때부터 ARM mode로 실행됐다.
  • blx명령어를 만나고 복귀주소로 LR에 집어넣은 것 같으니 운이 좋다.
  • 위를 보니 memset()함수가 보인다. 범인을 찾은 느낌이 든다.
  • memset()함수는 함수의 시작주소로부터 60-bytes를 0으로 초기화한다.
  • 따라서 R0에는 0x0이 들어가고, 그 주소로 branch를 해버렸으니 reset이 발생한 것이다.

2. Abort exception 디버깅

  • Abort는 reset보다 상대적으로 문제 원인을 찾기 매우 쉽다.

2.1. Data abort

  • 우선 context를 보면 ABT mode이며 PC가 0x10로 abort handler를 가리키고 있으므로 data abort가 발생했음을 알 수있다. 따라서 LR - 0x8 주소로 이동하면 된다.
  • LR이 0x00ED_CF26이므로 해당 주소로 이동한 뒤 0x8을 뺀 line을 보면, STR R0,[R1] 명령어가 있다.
  • R1이 0xFFFF_FFFF값을 가지고 있는데, 현재 시스템의 memory map에는 해당 주소가 정의돼있지 않다.
  • 즉, 접근할 수 없는 주소에 write하려는 시도 때문에 data abortion이 발생했음을 알 수 있다.
  • C 소스코드를 보니 HWIO_ADDR이라는 전역변수가 0xFFFF_FFFF를 가리키는데 여기에 0x8을 쓰려다가 abortion이 발생했음을 알 수 있다. 잘못된 주소를 가리키고 있으니 초기화 값을 수정해주면 오류를 해결할 수 있다.

2.2. Prefetch abort

  • Context를 보면 ABT mode이며 PC가 0x0C로 prefetch handler를 가리키고 있으므로 prefetch abort가 발생했음을 알 수 있다. 따라서 LR - 0x4 주소로 이동하면 된다.
  • 그런데 LR을 보니 0x0000_0002라는 이상한 값을 갖고 있다. 무슨 이유에선지 LR이 corrupt 됐음을 직감할 수 있다.
  • 단서로 사용할 수 있는 다음 후보 R13을 바라보자. 물론 ABT mode의 R13을 봐서는 안 된다.
  • SPSR을 보니 10011로 이전 mode가 SVC임을 알 수 있다.
    • R14_SVC는 0xFFFF_FFFF를 가리키고 있다. 이쪽도 LR이 corrupt 됐다.
    • R13_SVC는 0x0116_FD24를 가리키고 있으니 그쪽으로 가보자.

  • 일단 SP를 포함해 인근 영역이 전부 0xFF로 가득찬 것을 보아하니 심상치 않은 일이 발생했음을 알 수 있다.
  • Stack을 backtracing 해보면, 가장 최근에 push한 정보는 0x00ED_D0FD이므로 우선 이 주소로 이동해보자.
  • 이동해보니 bl 0xEDCE52 명령어가 보인다. bl 명령어를 만나 복귀할 주소를 LR에 남겨놓은 흔적을 볼 수 있다. ([※] 근데 왜 LR이 stack에 저장돼있었는지는 모르겠네요.)
  • 이번에는 0xEDCE52로 이동해보자. void chaos(void)라는 함수가 시작되는 부분이다.
  • 아하, 이 함수에서 memset((void *)(LocalBuffer - 10), 0xFF, 40)을 호출하는 것을 알 수 있다.
    • 지역변수로 word형 LocalBuffer[10]을 선언하고
    • LocalBuffer 시작주소로부터 20-bytes 밑(void * type이 2-byte 크기라고 가정하자)에서부터 40-bytes만큼을 0xFF로 초기화한다.

  • 정리하면 위 그림의 설명과 같다.

2.3. Interrupt 처리 중 abort

  • 이번에도 PC가 abort handler를 가리키는 것으로 보아 data abort가 발생했음을 알 수 있다.
  • SPSR값을 보니 11111로 이전 mode가 SYS였다. 즉, 이번 abortion은 ISR을 처리하다가 발생했음을 눈치챌 수 있다.
  • R14값 0x476A로 이동해보니 역시 clock_tick_ISR()이라는 ISR을 처리하다가 abortion이 발생했다.

  • Data abort이므로 LR - 0x8인 0x0000_4762부터 바라보니 심상치 않은 어셈블리를 볼 수 있다.
  • ldmia명령어가 계속 반복되며 딱 봐도 C 코드와 일치하지 않는 mnemonic이다.
  • 높은 확률로 이쪽 주소 영역의 코드가 corruption이 발생한 것이므로 0x0000_4762번지에 write breakpoint를 걸어 누군가 이쪽 주소에 데이터를 쓰려고 시도하면 break가 걸리도록 해보자.
  • 확인해보니 memset((void *)(functionsMarcel.HWIO_init), 0xABCD, 10);이라는 함수가 원인임을 발견했다.
    • functionsMarcel.HWIO_init이 어떻게 초기화 됐는지 확인해보니
    • functionsMarcel = {clock_io, clock_tick_isr};로 clock_tick_isr이 HWIO_init함수 포인터가 가리키는 함수로 초기화 돼있음을 확인할 수 있다.
  • 따라서, memset()에 의해 clock_tick_isr() ISR의 시작주소로부터 10-bytes의 영역이 0xABCD로 초기화 됐고,
  • Little endian이므로 해당 영역의 opcode가 CDAB로 채워져있었음을 알 수 있다.

3. Memory 불량

  • 지금까지 우리는 정말 다양한 exception 시나리오와 대처방법을 배웠다.
  • 이번에는 정말 웃기지도 않는 황당한 경우를 보면서 ‘정말 다양한 원인으로 문제가 발생하는구나’라고 느껴보자.

  • 계속되는 reset 및 abort로 인해 메모리를 덤프해보니 위와 같았다.
  • 뭔가 규칙성이 느껴지지 않는가? 특정 열만 0xFD인 규칙성이 보인다.
    • 0xFF이면 1111_1111인데, 0xFD는 1111_1101이다.
  • 즉, 메모리의 특정 bit가 고장이 난 SDRAM 불량인 경우다.
  • 이런 경우는 진짜 오랜 개발경험이 없는 한 전혀 감이 안 오는 상황이다. Stack이 문제인가? 실행 binary를 잘

'경로 및 정보' 카테고리의 다른 글

exception vector table 구성  (0) 2023.08.01
Linux Memory Protection  (0) 2023.07.19
Content-Type 이란  (0) 2023.06.27
CAN TP Protocol  (0) 2023.06.22
source insight 메뉴얼 등  (0) 2023.06.20
블로그 이미지

wtdsoul

,