본문 바로가기

시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리

[T32] Cortex A53: 프로세스 별 Call Stack 복원하는 방법

커널 디버깅을 하다 보면 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 <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자