AArch64 64비트 아키텍쳐에서 설정해야 할 Special Register에 대해서 잠깐 살펴볼께요.
우선 커널이 구동될 때의 Exception Level이 1이니까 EL1 모드만 점검하죠.
 
 
TTBR1_EL1
약자는 Translation Table Base Register인데, 가상주소와 물리주소를 변환할 때 쓰이죠.
 
Trace32로 이 레지스터를 설정하고 싶으면 아래 커맨드를 써야 하지오.
Data.Set SPR:0x30201 %Quad 0x82ba1000  // TTBR1_EL1
 
TCR_EL1
Translation Control Register의 약자인데 Stage 1 가상 주소 Translation 시 필요한 Translation 베이스 레지스터 정보를
포함하고 있어요.
Trace32로 이 레지스터를 설정하고 싶으면 아래 커맨드를 쓰면 되죠.
Data.Set SPR:0x30202 %Quad 0x00000032B5193519 // TCR_EL1
 
MAIR_EL1
Memory Attribute Indirection Register이거든요. Trace32로 아래 명령어로 설정 가능해요.
Data.Set SPR:0x30A20 %Quad 0x000000FF440C0400 // MAIR_EL1
 
SCTLR_EL1
가장 중요한 레지스터인데요.
 
ARM53 문서께서 System Control Register라고 하시네요.
 
Trace32 설정 커맨드는 아래와 같아요.
Data.Set SPR:0x30100 %Quad 0x0000000004C5D93D // SCTLR_EL1
 
그럼 커널 패닉이 일어났을 때 이 레지스터 정보를 찍고 싶으면 어떻게 해야 할까요?
아래 패치를 적용하면 커널 로그로 확인이 가능하죠. panic reboot notifier call을 하나 설정하고 콜백함수에 해당 루틴을 추가하면 되죠.
diff --git a/drivers/soc/pompeii/crash_handle_panic.c b/drivers/soc/pompeii/crash_handle_panic.c
index 27b9b60..431fd85 100644
--- a/drivers/soc/pompeii/crash_handle_panic.c
+++ b/drivers/soc/pompeii/crash_handle_panic.c
@@ -70,6 +70,41 @@ static int gen_key_panic = 0;
 static int key_crash_cnt = 0;
 static unsigned long key_crash_last_time = 0;
 
+static DEFINE_SPINLOCK(panic_lock);
+
+static u64 ttbr1_el1_reg = 0;
+static u64 tcr_el1_reg = 0;
+static u64 mair_el1_reg = 0;
+static u64 amair_el1_reg = 0;
+static u64 sctlr_el1_reg = 0;
+
+void get_mmu_sys_ctrl_register(void)
+{
+       ttbr1_el1_reg = read_sysreg(ttbr1_el1);
+       tcr_el1_reg = read_sysreg(tcr_el1);
+       mair_el1_reg = read_sysreg(mair_el1);
+       amair_el1_reg = read_sysreg(amair_el1);
+       sctlr_el1_reg = read_sysreg(sctlr_el1);
+
+       printk("ttbr1: %llx  tcr: %llx sctlr: %llx mair: %llx amair: %llx \n",
+                                       ttbr1_el1_reg, sctlr_el1_reg, sctlr_el1_reg, mair_el1_reg, amair_el1_reg);
+}
+
+static int crash_handler_panic(struct notifier_block *this,
+                 unsigned long event,
+                 void *ptr)
+{
+    unsigned long flags;
+    spin_lock_irqsave(&panic_lock, flags);
+
+    printk(KERN_CRIT "%s called\n", __func__);
+    get_mmu_sys_ctrl_register();
+
+    spin_unlock_irqrestore(&panic_lock, flags);
+    return NOTIFY_DONE;
+}
+
+
+static struct notifier_block panic_blk = {
+    .notifier_call  = crash_handler_panic,
+    .priority    = 1004,
+};
+
 static int __init crash_panic_handler_early_init(void)
 {
        struct device_node *np;
@@ -477,6 +517,8 @@ static int __init lge_panic_handler_early_init(void)
                return -ENODEV;
        }
 
+       atomic_notifier_chain_register(&panic_notifier_list, &panic_blk);
+
        of_property_read_u32(np, "mem-addr", (u32*)&panic_handler->fb_addr);
        of_property_read_u32(np, "mem-size", (u32*)&panic_handler->fb_size);
 
번외로 한 가지 주의할 점은 u64로 설정한 변수는 %llx, %lld 로 printk 아규먼트를 줘야 해요.
 
리눅스 드라이버 및 리눅스 커널 코드가 실제 실행되는 공간은 어디일까요? 
 
커널에서 어떤 코드던 프로세스가 실행되는 운동장은 스택이에요. 
프로그램이 실행되면서 스택에 Push/Pop 동작을 엄청나게 자주하는데요.
 
예전 실리콘 벨리에서 어떤 스타트업 회사가 기획했던 아이디어라고 하는데요.
리눅스 커널 API를 하드웨어로 처리하는 아이디어를 구현하려고 했다고 하네요. 
그 아이디어의 근거는 리눅스 커널에서 자주 사용하는 API는 몇 개로 한정되어 있다는 거에요.
 
그럼 너무 자주 실행되는 함수면 어떤 동작을 자주 할까요?
스택 Push/Pop이 겠죠. 그래서 inline을 함수를 쓰는 거에요. inline 함수는 컴파일러가 호출되는 함수 내에 inline 함수를
추가하기 때문에 스택 Push/Pop 동작을 줄 일 수 있어요. 대신 inline 함수의 심볼 정보는 찾을 수 없죠.
 
대부분의 Generic Linux Kernel API는 inline 함수 타입이랍니다.
 
그럼 함수가 호출될 때 파라미터와 지역 변수를 스택에 Push/Pop하는 동작(유식한 말로 Calling Convention이라고 하죠)은 다른 글에서 다루기로 하고요.
쉽게 Stack Push을 소개하려고 해요.
 
User 공간에서 Kernel 공간으로 이동하고 싶을 때, 시스템 콜을 호출해야 한다고 하죠. 
(와이파이 콜과 시스템 콜을 혼돈해서는 안되요. 시스템 콜을 와이파이 콜로 이해하고 있는 돌대가리 관리자가 갑자기 생각나네요.)
 
아래 콜 스택은 바인더 "Binder:410_1:790" 프로세스가 ioctl 시스템 콜을 하는 동작이에요.
crash> bt 790
PID: 790    TASK: da175040  CPU: 0   COMMAND: "Binder:410_1"
bt: WARNING:  stack address:0xda233da0, program counter:0xc0fd96a0
 #0 [<c0fd96a0>] (__schedule) from [<c09d31d8>]
 #1 [<c09d31d8>] (binder_thread_read) from [<c09d8460>]
 #2 [<c09d8460>] (binder_ioctl) from [<c0247cc4>]
 #3 [<c0247cc4>] (do_vfs_ioctl) from [<c0247dfc>]
 #4 [<c0247dfc>] (sys_ioctl) from [<c0106b00>]
    pc : [<b4551998>]    lr : [<b452515f>]    psr: 200d0010
    sp : b382e888  ip : 00000000  fp : 00000000
    r10: 00000008  r9 : b40b126c  r8 : b40b12a0
    r7 : 00000036  r6 : b40b1240  r5 : c0186201  r4 : b382e8a0
    r3 : 00000000  r2 : b382e8a0  r1 : c0186201  r0 : 00000020
    Flags: nzCv  IRQs on  FIQs on  Mode USER_32  ISA ARM
 
오라 그런데, Crash Tool에서 래지스터를 찍어주네요? 이게 뭘까요?
crash> task 790
PID: 790    TASK: da175040  CPU: 0   COMMAND: "Binder:410_1"
struct task_struct {
  state = 1,
  stack = 0xda232000,
 
"Binder:410_1" 프로세스의 스택이 자라는 주소(0xda232000+0x2000)를 가보면, 그 비밀을 알 수 있어요.
참고로 ARM32 비트 아키텍쳐에서는 스택 사이즈가 0x2000, ARM64 비트는 0x4000으로 고정되어 있어요.
 
자, 0xda234000 주소로 가 볼까요?
crash> eval 0xda232000+0x2000
hexadecimal: da234000  (3573968KB)
 
crash> rd 0xda233f80 -e 0xda234000
da233f80:  00000000 c0247dfc 00000000 b382e8a0   .....}$.........
da233f90:  c0186201 b40b1240 00000036 c0106cc4   .b..@...6....l..
da233fa0:  da232000 c0106b00 b382e8a0 c0186201   . #..k.......b..
da233fb0:  00000020(R0) c0186201(R1) b382e8a0(R2) 00000000(R3)    ....b..........
da233fc0:  b382e8a0(R4) c0186201(R5) b40b1240(R6) 00000036(R7)   .....b..@...6...
da233fd0:  b40b12a0(R8) b40b126c(R9) 00000008(R10) 00000000(R11)   ....l...........
da233fe0:  00000000(R12) b382e888(R13) b452515f(R14) b4551998(PC)   ........_QR...U.
da233ff0:  200d0010 00000020 00000000 00000000   ...  ...........
 
유저 공간에서 커널 공간으로 이동하기 직전에 실행되었던 레지스터를 커널 스택 공간에 Push를 하는 거죠?
Crash Tool로 스택 덤프를 보니 눈에 잘 안들어온다고요? 그럼 T32로 볼까요?
 
아래 명령어를 써 봅시다. 아래 T32 명령어는 메모리 공간의 덤프를 심볼 정보와 함께 표현해주죠. 참 유용한 명령어죠.
d.v %y.l 0xda233f80
 
위 Crash Tool 보다 좀 눈에 잘 들어오죠?
________address|_data________|value_____________|symbol
NSD:DA233F80| 00 00 00 00  0x0
NSD:DA233F84| FC 7D 24 C0  0xC0247DFC         \\vmlinux\fs/ioctl\sys_ioctl+0x4C
NSD:DA233F88| 00 00 00 00  0x0
NSD:DA233F8C| A0 E8 82 B3  0xB382E8A0
NSD:DA233F90| 01 62 18 C0  0xC0186201         \\vmlinux\time/timer\perf_trace_hrtimer_init+0x75
NSD:DA233F94| 40 12 0B B4  0xB40B1240
NSD:DA233F98| 36 00 00 00  0x36               
NSD:DA233F9C| C4 6C 10 C0  0xC0106CC4         \\vmlinux\Global\sys_call_table
NSD:DA233FA0| 00 20 23 DA  0xDA232000
NSD:DA233FA4| 00 6B 10 C0  0xC0106B00         \\vmlinux\Global\ret_fast_syscall
NSD:DA233FA8| A0 E8 82 B3  0xB382E8A0
NSD:DA233FAC| 01 62 18 C0  0xC0186201         \\vmlinux\time/timer\perf_trace_hrtimer_init+0x75
NSD:DA233FB0| 20 00 00 00  0x20               //<<-- R0
NSD:DA233FB4| 01 62 18 C0  0xC0186201    //<<-- R1
NSD:DA233FB8| A0 E8 82 B3  0xB382E8A0    //<<-- R2
NSD:DA233FBC| 00 00 00 00  0x0               //<<-- R3
NSD:DA233FC0| A0 E8 82 B3  0xB382E8A0   //<<-- R4
NSD:DA233FC4| 01 62 18 C0  0xC0186201   //<<-- R5
NSD:DA233FC8| 40 12 0B B4  0xB40B1240   //<<-- R6
NSD:DA233FCC| 36 00 00 00  0x36             //<<-- R7
NSD:DA233FD0| A0 12 0B B4  0xB40B12A0  //<<-- R8
NSD:DA233FD4| 6C 12 0B B4  0xB40B126C  //<<-- R9
NSD:DA233FD8| 08 00 00 00  0x8              //<<-- R10
NSD:DA233FDC| 00 00 00 00  0x0        //<<-- R11
NSD:DA233FE0| 00 00 00 00  0x0        //<<-- R12
NSD:DA233FE4| 88 E8 82 B3  0xB382E888   //<<-- R13(SP)
NSD:DA233FE8| 5F 51 52 B4  0xB452515F   //<<-- R14
NSD:DA233FEC| 98 19 55 B4  0xB4551998  //<<-- PC
NSD:DA233FF0| 10 00 0D 20  0x200D0010
NSD:DA233FF4| 20 00 00 00  0x20               \\vmlinux\Global\up_b_offset+0x18
NSD:DA233FF8| 00 00 00 00  0x0
NSD:DA233FFC| 00 00 00 00  0x0
NSD:DA234000| 00 00 00 00  0x0   //<<-- stack bottom 주소
 
Userspace 레지스터 중에서 주목해야 할 놈이 있어요. 바로, R7이죠.
R7는 시스템 콜 번호를 저장하고 있어요.
 
Crash Tool로 시스템 콜 번호를 확인할 수 있거든요. 아래 "sys -c"  명령어로 시스템 콜 번호와 시스템 콜 함수 위치를 함께 확인할 수 있어요.
crash> sys -c
NUM  SYSTEM CALL                FILE AND LINE NUMBER
  0  sys_restart_syscall        ../home001/AustinKim/kernel/signal.c: 2503
  1  sys_exit
  2  sys_fork                   ../home001/AustinKim/kernel/msm-3.18/kernel/fork.c: 1729
  3  sys_read
  4  sys_write
  5  sys_open                   ../home001/AustinKim/fs/open.c: 1064
  6  sys_close                  ../home001/AustinKim/fs/open.c: 1125
  7  sys_ni_syscall
  8  sys_creat                  ../home001/AustinKim/fs/open.c: 1089
  9  sys_link
  a  sys_unlink
  b  sys_execve  
// 생략
 34  sys_umount
 35  sys_ni_syscall
 36  sys_ioctl
 37  sys_fcntl
 
역시 sys_ioctl의 시스템 콜 번호는 0x36이네요. 물론 R7도 0x36이지만요.
 
Reference: ARM 프로세서의 주요 기능
 
ARM 프로세서는 왜 배워야 할까
ARM 프로세서 학습하는 방법의 문제점
ARM 프로세서 소개  
ARM 아키텍처를 구성하는 주요 기능
   ● 어셈블리 명령어란  
   ● ARM의 동작 모드와 익셉션 레벨   
 
Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자
 
 

Quite interesting patch was released under the following url.
https://source.codeaurora.org/quic/la/kernel/msm-3.18/commit/?h=rel/msm-3.18&id=a0039b1e721b7b3ee1cbe7f7f9d44d451ac74543

The detailed patch is to initialize the stack array with a different way as below.
usb : dwc3: Initialize kernel stack variables properly
If kernel stack variables are not initialized properly,
there is a chance of kernel information disclosure.
So, initialize kernel stack variables with null characters.

-rw-r--r-- drivers/usb/dwc3/debugfs.c 10 
1 files changed, 4 insertions, 6 deletions
diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c
index ce7cd96..fb252ec 100644
--- a/drivers/usb/dwc3/debugfs.c
+++ b/drivers/usb/dwc3/debugfs.c
@@ -402,7 +402,7 @@ static ssize_t dwc3_mode_write(struct file *file,
struct dwc3 *dwc = s->private;
unsigned long flags;
u32 mode = 0;
- char buf[32];
+ char buf[32] = {0};

if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
@@ -482,7 +482,7 @@ static ssize_t dwc3_testmode_write(struct file *file,
struct dwc3 *dwc = s->private;
unsigned long flags;
u32 testmode = 0;
- char buf[32];
+ char buf[32] = {0};

if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
@@ -589,7 +589,7 @@ static ssize_t dwc3_link_state_write(struct file *file,
struct dwc3 *dwc = s->private;
unsigned long flags;
enum dwc3_link_state state = 0;
- char buf[32];
+ char buf[32] = {0};

if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
@@ -630,12 +630,10 @@ static ssize_t dwc3_store_ep_num(struct file *file, const char __user *ubuf,
{
struct seq_file *s = file->private_data;
struct dwc3 *dwc = s->private;
- char kbuf[10];
+ char kbuf[10] = {0};
unsigned int num, dir, temp;
unsigned long flags;

- memset(kbuf, 0, 10);
-
if (copy_from_user(kbuf, ubuf, count > 10 ? 10 : count))
return -EFAULT;

In order to find out how the assemble code is updated after compling the patch-set, let me walk through more within dwc3_mode_write() .
The interesting debug information inside updated assemble code after "char buf[32] = {0};" is declared is that memset is appended by ARM-GCC compiler
[1]: R1 is updated as 0x0.
[2]: R2 is updated as 32 in decimal format.
[3]: R0 is belonging to stack memory area.
[4]: Call to memset is made.
memset(R0: buf, R1: 0x0, R2: 32)

[After]
static ssize_t dwc3_mode_write(struct file *file,
const char __user *ubuf, size_t count, loff_t *ppos)
{
c0629d9c: e1a0c00d mov ip, sp
c0629da0: e92ddbf0 push {r4, r5, r6, r7, r8, r9, fp, ip, lr, pc}
c0629da4: e24cb004 sub fp, ip, #4
c0629da8: e24dd028 sub sp, sp, #40 ; 0x28
c0629dac: e52de004 push {lr} ; (str lr, [sp, #-4]!)
c0629db0: ebe7958c bl c000f3e8 <__gnu_mcount_nc>
c0629db4: e59f7130 ldr r7, [pc, #304] ; c0629eec <dwc3_mode_write+0x150>
c0629db8: e1a04001 mov r4, r1
c0629dbc: e1a05002 mov r5, r2
c0629dc0: e3a01000 mov r1, #0 //<<--[1]
c0629dc4: e3a02020 mov r2, #32 //<<--[2]
c0629dc8: e5973000 ldr r3, [r7]
c0629dcc: e50b3028 str r3, [fp, #-40] ; 0xffffffd8
c0629dd0: e59030bc ldr r3, [r0, #188] ; 0xbc
c0629dd4: e24b0048 sub r0, fp, #72 ; 0x48 //<<--[3]
c0629dd8: e5936060 ldr r6, [r3, #96] ; 0x60 
c0629ddc: ebf2ec6f bl c02e4fa0 <memset> //<<--[4] 
c0629de0: e1a0200d mov r2, sp 
c0629de4: e3c21d7f bic r1, r2, #8128 ; 0x1fc0

[Before]
[1]: Assemble code before the patch is applied, the stack memory location mapped to buf[32] is saved into R0 without initilization. 
static ssize_t dwc3_mode_write(struct file *file,
const char __user *ubuf, size_t count, loff_t *ppos)
{
c0629d9c: e1a0c00d mov ip, sp
c0629da0: e92ddbf0 push {r4, r5, r6, r7, r8, r9, fp, ip, lr, pc}
c0629da4: e24cb004 sub fp, ip, #4
c0629da8: e24dd028 sub sp, sp, #40 ; 0x28
c0629dac: e52de004 push {lr} ; (str lr, [sp, #-4]!)
c0629db0: ebe7958c bl c000f3e8 <__gnu_mcount_nc>
c0629db4: e1a05002 mov r5, r2
//.. skip ,,
c0629dfc: 1a000005 bne c0629e18 <dwc3_mode_write+0x7c>
c0629e00: e24b0048 sub r0, fp, #72 ; 0x48 //<<--[1]
c0629e04: e1a02003 mov r2, r3
c0629e08: ebf2e301 bl c02e2a14 <__copy_from_user>
c0629e0c: e3500000 cmp r0, #0

Let me make it a rule to initialize the stack array like char kbuf[10] = {0}; instead of char kbuf[10];

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

+ Recent posts