Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

81112
549
416221


[T32] Cortex A53: 프로세스 별 Call Stack 복원하는 방법 #이제는 Arm의 시대

커널 디버깅을 하다 보면 T32 simulator를 많이 쓰게 된다. 디버깅 과정에서 콜 스택을 보고 싶을 경우가 많다.
이번에 ARM Cortex A53 Call Stack 복원 방법을 정리하도록 하자.

T32 시뮬레이터의 가장 큰 장점은 각 프로세스 별로 Call Stack을 이쁘게 볼 수 있다는 점이다.
그런데 current process는 Call Stack을 제대로 볼 수 없다. 그 이유는 실행 도중 프로세스이기 때문에 Context(Register)정보를 Task Descriptor에
제대로 저장을 할 수 없기 때문이다.

magic___________|command_________|state_____|uid___|pid___|spaceid|tty_|flags___|cpu|
FFFFFFC001624860|swapper/0       |running   |    0.|    0.| 0000  | 0  |00200000| 0.|
FFFFFFC00E690000|init            |sleeping  |    0.|    1.| 0001  | 0  |40400100| 3.|
FFFFFFC00E690AC0|kthreadd        |sleeping  |    0.|    2.| 0000  | 0  |00208040| 1.|
<생략>
FFFFFFC072900AC0|migration/7     |current(7)|    0.|   28.| 0000  | 0  |04208040| 7.|
FFFFFFC072901580|ksoftirqd/7     |sleeping  |    0.|   29.| 0000  | 0  |04208040| 7.|
FFFFFFC072902B00|kworker/7:0H    |sleeping  |    0.|   31.| 0000  | 0  |04208060| 7.|
FFFFFFC0729035C0|khelper         |sleeping  |    0.|   32.| 0000  | 0  |04208060| 4.|
FFFFFFC072904080|netns           |sleeping  |    0.|   33.| 0000  | 0  |04208060| 4.|
FFFFFFC072904B40|perf            |sleeping  |    0.|   34.| 0000  | 0  |04208060| 4.|


"migration/7" 이놈 프로세스를 선택 후 우클릭을 해서 Call Stack을 보면 아래 화면과 같이 흐름이 깨져 나온다.
-000|__switch_to()
-001|degrade_zero_ticks(asm)
 ---|end of frame

그럼 이제 좀 시작해 볼까.
"migration/7" 이놈 프로세스의 Task Descriptor를 보자. 아래 stack 주소가 보인다.
  (struct task_struct)*0xFFFFFFC072900AC0 = (
    state = 0,
    stack = 0xFFFFFFC07290C000,
    usage = (counter = 3),
    flags = 69238848,
    ptrace = 0,
    wake_entry = (next = 0x0),
    on_cpu = 1,

아래 명령어로 스택 Base 주소로 접근하자. 64비트 ARM CortexA53 아키텍처의 스택 사이즈는 0x4000이다.
(참고로 ARM32 비트 아키텍처에서는 d.v %y.l [주소] 를 입력해야 함)
d.v %y.q 0xFFFFFFC07290C000+0x4000
________________address|_data____________________|value_____________|symbol
   NSD:FFFFFFC07290BD78| A4 2A 29 00 C0 FF FF FF  0xFFFFFFC000292AA4 \vmlinuxwatchdog__touch_watchdog+0x1C
   NSD:FFFFFFC07290BD80| 90 BD 90 72 C0 FF FF FF  0xFFFFFFC07290BD90
   NSD:FFFFFFC07290BD88| 6C 0B 25 00 C0 FF FF FF  0xFFFFFFC000250B6C \vmlinuxsched/clocksched_clock_cpu+0x1C
   NSD:FFFFFFC07290BD90| A0 BD 90 72 C0 FF FF FF  0xFFFFFFC07290BDA0
   NSD:FFFFFFC07290BD98| 98 0B 25 00 C0 FF FF FF  0xFFFFFFC000250B98 \vmlinuxsched/clocklocal_clock+0x10
   NSD:FFFFFFC07290BDA0| B0 BD 90 72 C0 FF FF FF  0xFFFFFFC07290BDB0
   NSD:FFFFFFC07290BDA8| A4 2A 29 00 C0 FF FF FF  0xFFFFFFC000292AA4 \vmlinuxwatchdog__touch_watchdog+0x1C
   NSD:FFFFFFC07290BDB0| E0 BD 90 72 C0 FF FF FF  0xFFFFFFC07290BDE0
   NSD:FFFFFFC07290BDB8| BC 3D 24 00 C0 FF FF FF  0xFFFFFFC000243DBC \vmlinuxsmpbootsmpboot_thread_fn+0x118
   NSD:FFFFFFC07290BDC0| C0 DE 8B 72 C0 FF FF FF  0xFFFFFFC0728BDEC0
   NSD:FFFFFFC07290BDC8| 00 80 90 72 C0 FF FF FF  0xFFFFFFC072908000
   NSD:FFFFFFC07290BDD0| E0 BD 90 72 C0 FF FF FF  0xFFFFFFC07290BDE0
   NSD:FFFFFFC07290BDD8| 50 3E 24 00 C0 FF FF FF  0xFFFFFFC000243E50 \vmlinuxsmpbootsmpboot_thread_fn+0x1AC
   NSD:FFFFFFC07290BDE0| 30 BE 90 72 C0 FF FF FF  0xFFFFFFC07290BE30
   NSD:FFFFFFC07290BDE8| 40 D3 23 00 C0 FF FF FF  0xFFFFFFC00023D340 \vmlinuxkthreadkthread+0xB0
   NSD:FFFFFFC07290BDF0| 70 BC 69 0E C0 FF FF FF  0xFFFFFFC00E69BC70
   NSD:FFFFFFC07290BDF8| 38 78 7F 01 C0 FF FF FF  0xFFFFFFC0017F7838 \vmlinuxkthreadkthread_create_lock
   NSD:FFFFFFC07290BE00| 93 07 17 01 C0 FF FF FF  0xFFFFFFC001170793 \vmlinuxGlobalkallsyms_token_index+0x4A93
   NSD:FFFFFFC07290BE08| C0 DE 8B 72 C0 FF FF FF  0xFFFFFFC0728BDEC0
   NSD:FFFFFFC07290BE10| A4 3C 24 00 C0 FF FF FF  0xFFFFFFC000243CA4 \vmlinuxsmpbootsmpboot_thread_fn
   NSD:FFFFFFC07290BE18| 00 00 00 00 00 00 00 00  0x0
   NSD:FFFFFFC07290BE20| 00 00 00 00 00 00 00 00  0x0
   NSD:FFFFFFC07290BE28| 24 D3 23 00 C0 FF FF FF  0xFFFFFFC00023D324 \vmlinuxkthreadkthread+0x94


위 스택 덤프에서 볼드체로 되어 있는 부분을 참고하자. 함수를 호출한 주소가 위치해있는 주소이다.
ARM CortexA53은 정말 함수 흐름을 잘 확인할 수 있는 Calling Convention인 것 같다. (Cortex A7은 좀 거시기 하지.)
그럼 가장 많은 정보를 볼 수 있는 Call Stack을 복원하기 위해 Stack을 0xFFFFFFC07290BD90, Program Counter를 0xFFFFFFC000250B6C로 지정해보자

________________address|_data____________________|value_____________|symbol
   NSD:FFFFFFC07290BD78| A4 2A 29 00 C0 FF FF FF  0xFFFFFFC000292AA4 \vmlinuxwatchdog__touch_watchdog+0x1C
   NSD:FFFFFFC07290BD80| 90 BD 90 72 C0 FF FF FF  0xFFFFFFC07290BD90
   NSD:FFFFFFC07290BD88| 6C 0B 25 00 C0 FF FF FF  0xFFFFFFC000250B6C \vmlinuxsched/clocksched_clock_cpu+0x1C
   NSD:FFFFFFC07290BD90| A0 BD 90 72 C0 FF FF FF  0xFFFFFFC07290BDA0
   NSD:FFFFFFC07290BD98| 98 0B 25 00 C0 FF FF FF  0xFFFFFFC000250B98 \vmlinuxsched/clocklocal_clock+0x10
   NSD:FFFFFFC07290BDA0| B0 BD 90 72 C0 FF FF FF  0xFFFFFFC07290BDB0


아래 명령어를 사용하자.
r.s sp 0xFFFFFFC07290BD90
r.s pc 0xFFFFFFC000250B6C

이후 v.f 명령어를 입력하여 콜 스택을 보면 아래와 같이 이쁘게 복원이 된다.
-000|sched_clock_cpu(cpu = ?)
-001|local_clock()
-002|get_timestamp(inline)
-002|__touch_watchdog()
-003|smpboot_thread_fn(data = 0xFFFFFFC0728BDEC0)
-004|smpboot_thread_fn(data = 0xFFFFFFC07290BE30)
-005|test_bit(inline)
-005|kthread(_create = 0x0)
 ---|end of frame


Reference: ARM 프로세서의 주요 기능

ARM 프로세서는 왜 배워야 할까
ARM 프로세서 학습하는 방법의 문제점
ARM 프로세서 소개  
ARM 아키텍처를 구성하는 주요 기능
   ● 어셈블리 명령어란  
   ● ARM의 동작 모드와 익셉션 레벨   

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


    핑백

    덧글

    • ym0914 2020/11/30 12:59 # 삭제 답글

      안녕하세요. blog 잘 보고 있습니다. 이렇게 고생하시면서 터득하신걸 올려주셔서 너무 감사합니다.

      다름이 아니라 이번 게시글에 언급하신 아래 내용에 잘 이해가 가지 않아 문의 드려 봅니다.

      "아래 명령어로 스택 Base 주소로 접근하자. 64비트 ARM CortexA53 아키텍처의 스택 사이즈는 0x4000이다.
      (참고로 ARM32 비트 아키텍처에서는 d.v %y.l [주소] 를 입력해야 함)
      d.v %y.q 0xFFFFFFC07290C000+0x4000"
      요렇게 하면 task stack 의 base 를 참조한다고 하셨는데 현재 sp 에 따라 base 를 넘어서 참조할 수 있지도 않을런지요?

      그리고 아래 내용은 오타가 아닐지 조심스럽게 문의 드려봅니다
      "그럼 가장 많은 정보를 볼 수 있는 Call Stack을 복원하기 위해 Stack을 0xFFFFFFC072E07D90, Program Counter를 0xFFFFFFC00023D로 지정해보자."
      sp 와 pc 가 본 게시글에 있는 것에서 찾을 수가 없는지 혹시 오타가 아닌지요?

    • AustinKim 2020/11/30 13:58 #

      댓글로 질문 주신 내용에 대해 답신드립니다.

      1. 프로세스의 스택 사용 범위

      프로세스는 자신에게 할당된 스택 공간을 사용해, 함수의 지역 변수와 자신을 호출한 코드의 주소(링크 레지스터)를 저장합니다. 그런데 64비트 기반 리눅스 커널의 경우 프로세스 스택의 사이즈는 0x4000이며, 스택의 높은 주소에서 낮은 주소 방향으로 자랍니다.
      즉, 0xFFFFFFC07290C000+0x4000 주소에서 0xFFFFFFC07290C000 주소 방향으로 스택을 사용한다고 볼 수 있습니다.

      그런데 특정 함수에서 지역 변수를 큰 사이즈인 배열로 잡거나 Recursive Call 로 함수를 호출하면 (0xFFFFFFC07290C000+0x4000 주소에서 자라나는) 스택 포인터(Stack Pointer)가 0xFFFFFFC07290C000 주소를 넘어설 수 있습니다. 이를 보통 스택 오버 플로우라고 하죠.

      2. 오타 관련

      확인해보니, 오타가 맞습니다. 다음과 같이 블로그의 글을 수정합니다.

      "그럼 가장 많은 정보를 볼 수 있는 Call Stack을 복원하기 위해 Stack을 0xFFFFFFC072E07D90, Program Counter를 0xFFFFFFC00023D로 지정해보자."
      ->
      "그럼 가장 많은 정보를 볼 수 있는 Call Stack을 복원하기 위해 Stack을 0xFFFFFFC07290BD90, Program Counter를 0xFFFFFFC000250B6C로 지정해보자."

      즐거운 한 주 되세요. 감사합니다.
    • ym0914 2020/11/30 21:54 # 삭제 답글

      답변 감사합니다!
    • AustinKim 2020/12/02 09:59 #

      개발하시는데 도움이 됐으면 좋겠습니다. 즐거운 한 주 보내세요.
    댓글 입력 영역