Overview
 
커널 크래시가 발생해 덤프를 TRACE32로 분석할 때 유저 공간에서 실행 중인 함수들의 콜스택을 보고 싶을 때가 있습니다. 그 이유는 아래와 같은 디버깅 정보를 얻기 위해서 입니다.;
 
   * 유저 공간에서 어떤 함수가 실행한 후 시스템 콜을 통해 커널 공간으로 실행 흐름이 바뀌었나?
   * 유저 공간에서 어떤 파라미터를 전달했을까?
 
그런데 대부분 리눅스 시스템 개발자들은 TRACE32로 커널 공간의 콜스택만을 보면서 디버깅을 합니다. 
 
이번 포스팅에서는 커널 패닉이 발생했을 때 유저 공간의 콜스택을 TRACE32으로 보는 방법을 소개합니다. 패치 코드를 활용하면 커널의 특정 함수에 브레이크 포인트를 걸었을 때도 유저 공간의 콜스택을 볼 수 있습니다.
 
조건
 
이번에 소개하는 패치 코드와 TRACE32 설정 방법은 Aarch64 ARMv8 아키텍처 기반 환경입니다. 즉 64비트 ARM 아키텍처입니다.
 
 
추가할 코드
 
TRACE32를 설정하기 앞서 먼저 간단히 코드를 수정합시다. 다음 패치 코드는 MMU co-register를 출력하는 기능입니다.
 
+void get_mmu_sys_ctrl_register(void)
+{
+       u64 sctlr_el1, tcr_el1, ttbr0_el1, ttbr1_el1, sp_el0, sp_el1;
+       u64 esr_el1, far_el1, contextidr_el1, tpidr_el0, tpidr_el1, tpidrro_el0;
+
+       asm volatile ("mrs %0, sctlr_el1\n\t"
+                               "mrs %1, tcr_el1\n\t"
+                               "mrs %2, ttbr0_el1\n\t"
+                               "mrs %3, ttbr1_el1\n\t"
+                               "mrs %4, sp_el0\n\t"
+                               "mov %5, sp\n\t"
+                               "mrs %6, tcr_el1\n\t"
+                               "mrs %7, esr_el1\n\t"
+                               "mrs %8, far_el1\n\t"
+                               "mrs %9, contextidr_el1\n\t"
+                               "mrs %10, tpidr_el0\n\t"
+                               "mrs %11, tpidr_el1\n\t"
+                               "mrs %12, tpidrro_el0\n\t"
+                               : "=r"(sctlr_el1), "=r"(tcr_el1),
+                               "=r"(ttbr0_el1), "=r"(ttbr1_el1),
+                               "=r"(sp_el0), "=r"(sp_el1),
+                               "=r"(tcr_el1), "=r"(esr_el1),
+                               "=r"(far_el1), "=r"(contextidr_el1),
+                               "=r"(tpidr_el0), "=r"(tpidr_el1), "=r"(tpidrro_el0)
+                               : : "memory");
+
+       printk("[+]sctlr_el1: %016llx  tcr_el1: %016llx\n", sctlr_el1, tcr_el1);
+       printk("[+]ttbr0_el1: %016llx  ttbr1_el1: %016llx\n", ttbr0_el1, ttbr1_el1);
+       printk("[+]sp_el0: %016llx  sp_el1: %016llx\n", sp_el0, sp_el1);
+       printk("[+]tcr_el1: %016llx  esr_el1: %016llx\n", tcr_el1, esr_el1);
+       printk("[+]far_el1: %016llx  contextidr_el1: %016llx\n", far_el1, contextidr_el1);
+       printk("[+]tpidr_el0: %016llx  tpidr_el1: %016llx\n", tpidr_el0, tpidr_el1);
+       printk("[+]tpidrro_el0: %016llx\n", tpidrro_el0);
+}
 
 
커널 패닉이 발생한 후 레지스터를 뿌려주는 함수에 get_mmu_sys_ctrl_register() 함수가 호출되도록 추가합니다.
 
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index dd1e817..b26656f 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -59,6 +59,9 @@
 #include <asm/processor.h>
 #include <asm/stacktrace.h>
 
 
 #ifdef CONFIG_STACKPROTECTOR
 #include <linux/stackprotector.h>
 unsigned long __stack_chk_guard __read_mostly;
@@ -306,6 +309,8 @@ void __show_regs(struct pt_regs *regs)
 
        printk("sp : %016llx\n", sp);
 
+       get_mmu_sys_ctrl_register();
        i = top_reg;
 
get_mmu_sys_ctrl_register() 함수 호출 위치는 상황에 맞게 수정하면 됩니다. 
 
테스트 방법
 
위 패치 코드를 반영 후 "echo c > /proc/sysrq-trigger" 명령어로 강제 커널 패닉을 유발합니다. 덤프를 받고 기존 방식으로 TRACE32를 로딩합니다.
 
TRACE32 세팅 방법
 
1. 먼저 다음과 같이 커널 패닉이 발생한 순간의 콜스택을 복원합니다.
 
-000|el1_da(asm)
 -->|exception
-001|sysrq_handle_crash(?)
-002|rcu_read_unlock(inline)
-002|__handle_sysrq(?, ?)
-003|write_sysrq_trigger(?, buf = 0x00000071EEC0B288, ?, ?)
-004|use_pde(inline)
-004|proc_reg_write(file = 0xFFFFFFC5399CDBC0, buf = 0x00000071EEC0B288, count = 2, ppos = 0xFFFFFF8016C6
-005|__vfs_write(file = 0xFFFFFFC5399CDBC0, p = 0x00000071EEC0B288, count = 2, pos = 0xFFFFFF8016C6BE00)
-006|vfs_write(file = 0xFFFFFFC5399CDBC0, buf = 0x00000071EEC0B288, count = 2, pos = 0xFFFFFF8016C6BE00)
-007|ksys_write(?, buf = 0x00000071EEC0B288, count = 2)
-008|__se_sys_write(inline)
-008|__arm64_sys_write(regs = 0xFFFFFF8016C6BEC0)
-009|el0_svc_common(regs = 0xFFFFFF8016C6BEC0, scno = 64, sc_nr = 294, syscall_table = 0xFFFFFFAF18600790
-010|el0_svc_handler(regs = 0xFFFFFF8016C6BEC0)
-011|el0_svc(asm)
 -->|exception
-012|NUX:0x71EF25EAF8(asm)
 ---|end of frame
 
콜스택과 같이 유저 공간에는 주소가 1줄 'NUX:0x71EF25EAF8(asm)' 밖에 보이지 않습니다.
아마 유저 공간에서 시스템 콜을 발생하는 함수의 주소로 판단이 됩니다.
 
이제 커널 로그를 보겠습니다.
커널 로그를 보면 다음과 같이 MMU Co-Processor 레지스터 정보를 볼 수 있습니다.
 
[  235.751791] <c6>CPU: 5 PID: 11014 Comm: sh Tainted: G S      W  OE     4.19.30+ #7
[  235.751812] <c2>pstate: 60400005 (nZCv daif +PAN -UAO)
[  235.751835] <c2>pc : sysrq_handle_crash+0x70/0x80
[  235.751846] <c2>lr : sysrq_handle_crash+0x5c/0x80
[  235.751856] <c2>sp : ffffff801a853c70
[  235.751879] <c2>[+]sctlr_el1: 000000003475591d  tcr_el1: 000001b2b5593519
[  235.751889] <c2>[+]ttbr0_el1: 000000012c924000  ttbr1_el1: 17280000a3ff0000
[  235.751899] <c2>[+]sp_el0: fffffff270350040  sp_el1: ffffff801a8537f0
[  235.751908] <c2>[+]tcr_el1: 000001b2b5593519  esr_el1: 0000000056000000
[  235.751918] <c2>[+]far_el1: 0000000000000000  contextidr_el1: 0000000000002b06
[  235.751928] <c2>[+]tpidr_el0: 0000007da4881020  tpidr_el1: 000000533e00d000
[  235.751937] <c2>[+]tpidrro_el0: 0000000000000000
[  235.751946] <c2>x29: ffffff801a853c80 x28: fffffff270350040 
[  235.751962] <c2>x27: 0000000000000000 x26: 0000000000000000 
[  235.751977] <c2>x25: 0000000056000000 x24: fffffff270350040 
 
2. TRACE32 명령어로 MMU Co-Register를 복원하기
 
다음과 TRACE32 cmm script를 복사한 후 restore_user.cmm으로 저장합니다.
"do restore_user.cmm" 명령어를 실행해 MMU Co-Register를 로딩합니다. 
 
&sctlr_el1_reg=0x0 
&tcr_el1_reg=0x0 
&ttbr0_el1_reg=0x0 
&ttbr1_el1_reg=0x0 
&esr_el1_reg=0x0 
&far_el1_reg=0x0 
&contextidr_el1_reg=0x0 
&tpidr_el0_reg=0x0 
&tpidr_el1_reg=0x0 
&tpidrro_el0_reg=0x0 
 
&sctlr_el1_reg=V.VALUE(sctlr_el1)
&tcr_el1_reg=V.VALUE(tcr_el1)
&ttbr0_el1_reg=V.VALUE(ttbr0_el1)
&ttbr1_el1_reg=V.VALUE(ttbr1_el1) 
&esr_el1_reg=V.VALUE(esr_el1) 
&far_el1_reg=V.VALUE(far_el1)
&contextidr_el1_reg=V.VALUE(contextidr_el1) 
&tpidr_el0_reg=V.VALUE(tpidr_el0) 
&tpidr_el1_reg=V.VALUE(tpidr_el1)
&tpidrro_el0_reg=V.VALUE(tpidrro_el0) 
 
&sctlr_el1_reg_t=FORMAT.DECIMAL(0., &sctlr_el1_reg)
 
PER.Set SPR:0x30100 %QUAD &sctlr_el1_reg   // SCTLR_EL1
PER.Set SPR:0x30200 %QUAD &ttbr0_el1_reg   // TTBR0_EL1
PER.Set SPR:0x30201 %QUAD &ttbr1_el1_reg  // TTBR1_EL1
PER.Set SPR:0x30202 %QUAD &tcr_el1_reg  // TCR_EL1
PER.Set SPR:0x30520 %QUAD &esr_el1_reg // ESR_EL1
PER.Set SPR:0x30600 %QUAD &far_el1_reg // FAR_EL1 
PER.Set SPR:0x30D01 %QUAD &contextidr_el1_reg
// CONTEXTIDR_EL1
PER.Set SPR:0x33D02 %QUAD &tpidr_el0_reg // TPIDR_EL0
PER.Set SPR:0x33D03 %QUAD &tpidrro_el0_reg  // TPIDRRO_EL0 
PER.Set SPR:0x33D04 %QUAD &tpidr_el1_reg // TPIDR_EL1
 
mmu.delete
mmu.scan
mmu.on
 
enddo
 
보시다시피 위에서 보이는 TRACE32 스크립트 코드는 MMU의 메모리 페이지 테이블을 설정하는 기능입니다.
다시 콜스택을 보겠습니다. 이제 콜스택 정보가 다릅니다.
 
-000|el1_da(asm)
 -->|exception
-001|sysrq_handle_crash(?)
-002|rcu_read_unlock(inline)
-002|__handle_sysrq(?, ?)
-003|write_sysrq_trigger(?, buf = 0x00000071EEC0B288, ?, ?)
-004|use_pde(inline)
-004|proc_reg_write(file = 0xFFFFFFC5399CDBC0, buf = 0x00000071EEC0B288, count = 2, ppos = 0xFFFFFF8016C6
-005|__vfs_write(file = 0xFFFFFFC5399CDBC0, p = 0x00000071EEC0B288, count = 2, pos = 0xFFFFFF8016C6BE00)
-006|vfs_write(file = 0xFFFFFFC5399CDBC0, buf = 0x00000071EEC0B288, count = 2, pos = 0xFFFFFF8016C6BE00)
-007|ksys_write(?, buf = 0x00000071EEC0B288, count = 2)
-008|__se_sys_write(inline)
-008|__arm64_sys_write(regs = 0xFFFFFF8016C6BEC0)
-009|el0_svc_common(regs = 0xFFFFFF8016C6BEC0, scno = 64, sc_nr = 294, syscall_table = 0xFFFFFFAF18600790
-010|el0_svc_handler(regs = 0xFFFFFF8016C6BEC0)
-011|el0_svc(asm)
 -->|exception
-012|NUX:0x71EF25EAF8(asm)
-013|NUX:0x55686F8384(asm)
-014|NUX:0x55686F3764(asm)
-015|NUX:0x55686F2150(asm)
-016|NUX:0x5568708294(asm)
-017|NUX:0x5568707D58(asm)
-018|NUX:0x71EF20A89C(asm)
 ---|end of frame
 
다음과 같이 유저 공간에서 실행 중인 함수의 주소가 더 보입니다.
 
-012|NUX:0x71EF25EAF8(asm)
-013|NUX:0x55686F8384(asm)
-014|NUX:0x55686F3764(asm)
-015|NUX:0x55686F2150(asm)
-016|NUX:0x5568708294(asm)
-017|NUX:0x5568707D58(asm)
-018|NUX:0x71EF20A89C(asm)
 ---|end of frame
 
3. 유저 공간의 라이브러리를 로딩해 유저 공간의 함수 콜스택 복원하기
 
자, 이제 본격적으로 유저 공간 콜스택을 복원 합시다.
먼저 위에서 확인한 바와 같이 유저 공간에서 가장 마지막에 실행된 코드 주소는 콜스택과 같이 0x71EF25EAF8 입니다.
 
이 주소의 정체를 확인하기 위해 커널 패닉이 발생한 프로세스의 virtual 메모리 공간을 덤프합니다.
 
crash> vm 5533
PID: 5533   TASK: ffffffc4da6d4340  CPU: 5   COMMAND: "sh"
       MM               PGD          RSS    TOTAL_VM
ffffffc50cdbbc40  ffffffc4e2efc000  3036k    33588k
      VMA           START       END     FLAGS FILE
ffffffc4dde36528 55686d3000 55686de000    871 /system/bin/sh
ffffffc4dde36f78 55686de000 556871b000    874 /system/bin/sh
ffffffc4e42f6948 556871b000 556871c000 100873 /system/bin/sh
ffffffc4dde377b8 556871c000 556871e000 100871 /system/bin/sh
...
ffffffc4e409b398 71ef18c000 71ef1cd000     71 /apex/com.android.runtime/system/apex/com.android.runtime.debug/lib64/bionic/libc.so
ffffffc4e409b188 "71ef1cd000" 71ef279000     74 /apex/com.android.runtime/system/apex/com.android.runtime.debug/lib64/bionic/libc.so
ffffffc4e409bde8 71ef279000 71ef27c000 100073 /apex/com.android.runtime/system/apex/com.android.runtime.debug/lib64/bionic/libc.so
ffffffc4e409b5a8 71ef27c000 71ef283000 100071 /apex/com.android.runtime/system/apex/com.android.runtime.debug/lib64/bionic/libc.so
 
 
Flags 100073 윗 부분에 있는 0x71ef1cd000 주소가  libc.so 파일을 로딩할 주소입니다.
 
참고로 심볼 정보가 있는 libc.so 라이브러리 위치는 다음 경로입니다.
out/target/product/rpi/symbols/recovery/root/system/lib64/libc.so
 
이번에 다음 명령어로 libc.so를 로딩합니다. TRACE32로 libc.so 라이브러리를 로딩할 때 오프셋 커맨드를 주의하세요.
 
Data.LOAD.elf libc.so 0x71EF18C000 /pack /nocode /noclear /strippart 5
 
 
명령어 포멧
Data.LOAD.elf [라이브러리 이름] [오프셋] /pack /nocode /noclear /strippart 5
 
위 명령어를 입력하니 유저 공간 콜스택에서 write 심볼이 보입니다.
 
-000|el1_da(asm)
 -->|exception
-001|sysrq_handle_crash(?)
-002|rcu_read_unlock(inline)
-002|__handle_sysrq(?, ?)
-003|write_sysrq_trigger(?, buf = 0x00000071EEC0B288, ?, ?)
-004|use_pde(inline)
-004|proc_reg_write(file = 0xFFFFFFC5399CDBC0, buf = 0x00000071EEC0B288, count = 2, ppos = 0xFFFFFF8016C6
-005|__vfs_write(file = 0xFFFFFFC5399CDBC0, p = 0x00000071EEC0B288, count = 2, pos = 0xFFFFFF8016C6BE00)
-006|vfs_write(file = 0xFFFFFFC5399CDBC0, buf = 0x00000071EEC0B288, count = 2, pos = 0xFFFFFF8016C6BE00)
-007|ksys_write(?, buf = 0x00000071EEC0B288, count = 2)
-008|__se_sys_write(inline)
-008|__arm64_sys_write(regs = 0xFFFFFF8016C6BEC0)
-009|el0_svc_common(regs = 0xFFFFFF8016C6BEC0, scno = 64, sc_nr = 294, syscall_table = 0xFFFFFFAF18600790
-010|el0_svc_handler(regs = 0xFFFFFF8016C6BEC0)
-011|el0_svc(asm)
 -->|exception
-012|write(asm)
-013|NUX:0x55686F8384(asm)
-014|NUX:0x55686F3764(asm)
-015|NUX:0x55686F2150(asm)
-016|NUX:0x5568708294(asm)
-017|NUX:0x5568707D58(asm)
-018|__libc_init(?, ?, ?, ?)
 ---|end of frame
 
위에서 볼드체로 된 부분을 눈여겨봅시다.
 
다음에 파싱할 주소는 아래와 같이 NUX:0x55686F8384(asm)이며 이에 매핑하는 라이브러리 파일을 찾아야 할 차례입니다.
 
-013|NUX:0x55686F8384(asm)
 
커널 패닉이 발생한 프로세스의 virtual 메모리 공간을 덤프합니다.
 
crash> vm 5533
PID: 5533   TASK: ffffffc4da6d4340  CPU: 5   COMMAND: "sh"
       MM               PGD          RSS    TOTAL_VM
ffffffc50cdbbc40  ffffffc4e2efc000  3036k    33588k
      VMA           START       END     FLAGS FILE
ffffffc4dde36528 55686d3000 55686de000    871 /system/bin/sh
ffffffc4dde36f78 55686de000 556871b000    874 /system/bin/sh
ffffffc4e42f6948 556871b000 556871c000 100873 /system/bin/sh
ffffffc4dde377b8 556871c000 556871e000 100871 /system/bin/sh
 
 
Flags 871 에 있는 0x55686d3000 주소가 sh 파일을 로딩할 주소입니다.
 
참고로, 심볼 정보가 포함된 sh 파일 위치는 다음과 같습니다.
out/target/product/rpi/symbols/vendor/bin/sh
 
이어서 sh 파일을 다음 오프셋으로 로딩합니다.
Data.LOAD.elf sh 0x55686D3000  /pack /noclear /strippart 5
 
 
복원된 유저 공간 콜스택의 정보
 
이제 유저 공간에 있는 콜스택을 모두 볼 수 있습니다.
 
-000|el1_da(asm)
 -->|exception
-001|sysrq_handle_crash(?)
-002|rcu_read_unlock(inline)
-002|__handle_sysrq(?, ?)
-003|write_sysrq_trigger(?, buf = 0x00000071EEC0B288, ?, ?)
-004|use_pde(inline)
-004|proc_reg_write(file = 0xFFFFFFC5399CDBC0, buf = 0x00000071EEC0B288, count = 2, ppos = 0xFFFFFF8016C6
-005|__vfs_write(file = 0xFFFFFFC5399CDBC0, p = 0x00000071EEC0B288, count = 2, pos = 0xFFFFFF8016C6BE00)
-006|vfs_write(file = 0xFFFFFFC5399CDBC0, buf = 0x00000071EEC0B288, count = 2, pos = 0xFFFFFF8016C6BE00)
-007|ksys_write(?, buf = 0x00000071EEC0B288, count = 2)
-008|__se_sys_write(inline)
-008|__arm64_sys_write(regs = 0xFFFFFF8016C6BEC0)
-009|el0_svc_common(regs = 0xFFFFFF8016C6BEC0, scno = 64, sc_nr = 294, syscall_table = 0xFFFFFFAF18600790
-010|el0_svc_handler(regs = 0xFFFFFF8016C6BEC0)
-011|el0_svc(asm)
 -->|exception
-012|write(asm)
-013|c_print(?)
-014|call_builtin(inline)
-014|comexec()
-015|execute(t = 0x00000071EEC4F808, flags = 0, xerrok = 0x0000007FE1E8BEA4)
-016|shell(s = 0x00000071EEC38008, level = 0)
-017|main(?, ?)
-018|__libc_init(?, ?, ?, ?)
 ---|end of frame
 
유저 공간에서 호출된 함수의 정보 확인해보기
 
'y.l.line r(pc)' 명령어로 유저 공간에 있는 각각 함수 위치를 확인해봤습니다.
 
-012|write(asm)
__________________address________________|module_________|source__________________________________|line_______________|offset____|
     P:00000071EF25EAF8--00000071EF25EAFB|\\libc\write   |bionic\libc\arch-arm64\syscalls\write.S |\9--0              |   prelim.| \\libc\Global\write+0x8
     P:00000071EF25EAFC--00000071EF25EAFF|\\libc\write   |bionic\libc\arch-arm64\syscalls\write.S |\10--0             |   prelim.| \\libc\Global\write+0x0C
 
 
-013|c_print(?)
__________________address________________|module____|source______________________|line_______|offset____|
     P:00000055686F8384--00000055686F8387|\\sh\funcs|external\mksh\src\funcs.c   |\567--569  |          | \\sh\funcs\c_print+0x770
     P:00000055686F8388--00000055686F838B|\\sh\funcs|external\mksh\src\funcs.c   |\578--583  |          | \\sh\funcs\c_print+0x774
 
 
-014|call_builtin(inline)
-014|comexec()
__________________address________________|module_______|source_____________________|line_______________|offset____|
     P:00000055686F3758--00000055686F3767|\\sh\exec    |external\mksh\src\exec.c   |\1398--1398        |          | \\sh\exec\comexec+0x974
     P:00000055686F3768--00000055686F376F|\\sh\exec    |external\mksh\src\exec.c   |\1399--1399        |          | \\sh\exec\comexec+0x984
 
 
-015|execute(t = 0x00000071EEC4F808, flags = 0, xerrok = 0x0000007FE1E8BEA4)
__________________address________________|module_______|source______________________|line_______________|offset____|
     P:00000055686F2138--00000055686F2153|\\sh\exec    |external\mksh\src\exec.c    |\161--162          |          | \\sh\exec\execute+0xB44
     P:00000055686F2154--00000055686F216B|\\sh\exec    |external\mksh\src\exec.c    |\163--166          |          | \\sh\exec\execute+0xB60
 
 
-016|shell(s = 0x00000071EEC38008, level = 0)
__________________address________________|module________|source_____________________|line_______________|offset____|
     P:000000556870828C--000000556870829F|\\sh\main     |external\mksh\src\main.c   |\910--910          |          | \\sh\main\shell+0x2D8
     P:00000055687082A0--00000055687082BB|\\sh\main     |external\mksh\src\main.c   |\911--911          |          | \\sh\main\shell+0x2EC
 
 
-017|main(?, ?)
__________________address________________|module______|source______________________|line_______________|offset____|
     P:0000005568707D54--0000005568707D5B|\\sh\main   |external\mksh\src\main.c    |\699--704          |          | \\sh\main\main+0x9F8
     P:0000005568707D5C--0000005568707D67|\\sh\main   |external\mksh\src\main.c    |\705--705          |          | \\sh\main\main+0xA00
 
 
-018|__libc_init(?, ?, ?, ?)
 ---|end of frame
__________________address________________|module_____________________|source__________________________________|line_______________|offset____|
     P:00000071EF20A898--00000071EF20A89F|\\libc\libc_init_dynamic   |bionic\libc\bionic\libc_init_dynamic.cpp|\134--136          |          | \\libc\libc_init_dynamic\__libc_init+0x6C
     P:00000071EF20A8A0--00000071EF20A8A3|\\libc\libc_init_dynamic   |bionic\libc\bionic\libc_init_dynamic.cpp|\138--144          |          | \\libc\libc_init_dynamic\__libc_shared_globals
 
이 포스팅에 올린 내용을 참고해 일찍 퇴근합시다.
 
궁금한 점이 있으면 댓글로 질문을 남겨주세요. 아는한 성실히 답신을 드리겠습니다.
by AustinKim 2019/12/07 15:29
 
 
 
가끔 인터럽트 핸들러의 처리 시간이 조금 더 정확히 보고 싶을 때가 있습니다.
이때, 다음과 같은 패치 코드를 적용하면 인터럽트 핸들러의 실행 시간을 측정할 수 있습니다.
 

 

패치 코드는 다음과 같습니다.
 
diff --git a/drivers/mailbox/bcm2835-mailbox.c b/drivers/mailbox/bcm2835-mailbox.c
index a03aeed..e353beb 100644
--- a/drivers/mailbox/bcm2835-mailbox.c
+++ b/drivers/mailbox/bcm2835-mailbox.c
@@ -72,17 +72,29 @@ static struct bcm2835_mbox *bcm2835_link_mbox(struct mbox_chan *link)
        return container_of(link->mbox, struct bcm2835_mbox, controller);
 }
 
+#define HANDLE_TIME 100
+
 static irqreturn_t bcm2835_mbox_irq(int irq, void *dev_id)
 {
+       u64 diff, start_time, curr_time;
        struct bcm2835_mbox *mbox = dev_id;
        struct device *dev = mbox->controller.dev;
        struct mbox_chan *link = &mbox->controller.chans[0];
 
+       start_time = sched_clock();
        while (!(readl(mbox->regs + MAIL0_STA) & ARM_MS_EMPTY)) {
                u32 msg = readl(mbox->regs + MAIL0_RD);
                dev_dbg(dev, "Reply 0x%08X\n", msg);
                mbox_chan_received_data(link, &msg);
        }
+
+       curr_time = sched_clock();
+       diff = curr_time - start_time;
+
+       if (diff > HANDLE_TIME)
+               trace_printk("interrupt: elapsed: %llu, start: %llu \n",
+                               diff, start_time);
+
        return IRQ_HANDLED;
 }
 
코드 내용은 너무 간단합니다. 나노초 단위의 시간 정보를 반환하는 sched_clock() 함수를 사용해서 
실행 시각을 계산하는 코드입니다. 인터럽트 함수의 실행 시간 정보를 저장하는 diff 지역 변수를 ftrace로출력합니다.
 
참고로 인터럽트 핸들러가 실행될 때는 인터럽트 컨텍스트입니다. 빨리 코드가 실행돼야 하니 printk() 함수를 써서 커널 로그를 출력하는 것은 자제해야 합니다.
 
Austin Kim, 궁금한 점이 있으면 댓글로 질문을 남겨주세요.
 
 
 



이번에는 조금 더 재미있는 패치 코드를 같이 볼까요? 다음은 패치 코드의 내용입니다.
 
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index cd8b61b..128c998 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -49,7 +49,7 @@
 #include <linux/uaccess.h>
 #include <linux/sched/isolation.h>
 #include <linux/nmi.h>
-
+#include <linux/sched_clock.h>
 #include "workqueue_internal.h"
 
 enum {
@@ -2029,6 +2029,18 @@ static bool manage_workers(struct worker *worker)
        return true;
 }
 
+static int work_timer_debug = 1;
+static unsigned long long max_work_handler_time_stamp = 0;
+
+static void work_handler_elapsed_timer_func(struct timer_list *unused)
+{
+       unsigned long long curr_time = sched_clock();
+
+       pr_err("work_handler max elapsed time : %lld, cur time: %lld\n",
+                       max_work_handler_time_stamp, curr_time);
+}
+static DEFINE_TIMER(work_handler_elapsed_timer, work_handler_elapsed_timer_func);
+
 /**
  * process_one_work - process single work
  * @worker: self
@@ -2052,6 +2064,7 @@ __acquires(&pool->lock)
        bool cpu_intensive = pwq->wq->flags & WQ_CPU_INTENSIVE;
        int work_color;
        struct worker *collision;
+       u64 start_time, end_time, elapse_time;
 #ifdef CONFIG_LOCKDEP
        /*
         * It is permissible to free the struct work_struct from
@@ -2149,6 +2162,12 @@ __acquires(&pool->lock)
         * workqueues), so hiding them isn't a problem.
         */
        lockdep_invariant_state(true);
+
+       if (work_timer_debug) {
+               start_time = sched_clock();
+               mod_timer(&work_handler_elapsed_timer, msecs_to_jiffies(500) + jiffies);
+       }
+
        trace_workqueue_execute_start(work);
        worker->current_func(work);
        /*
@@ -2156,6 +2175,19 @@ __acquires(&pool->lock)
         * point will only record its address.
         */
        trace_workqueue_execute_end(work);
+
+       if (work_timer_debug) {
+               end_time = sched_clock();
+               del_timer_sync(&work_handler_elapsed_timer);
+               elapse_time = end_time - start_time;
+
+               if (elapse_time > max_work_handler_time_stamp) {
+                       max_work_handler_time_stamp = elapse_time;
+                       pr_err("work_handler elapsed time : %lld, handler: %pS\n",
+                                       max_work_handler_time_stamp, worker->current_func);
+               }
+       }
+
        lock_map_release(&lockdep_map);
        lock_map_release(&pwq->wq->lockdep_map);
 
@@ -2235,6 +2267,9 @@ static void set_pf_worker(bool val)
  *
  * Return: 0
  */
 
 
위 패치 코드는 2가지 기능을 지원하는데 세부 동작은 두 가지 시나리오에서 생각해볼 수 있습니다.
 
< 첫 번째 시나리오 >
1. 타이머 설정(500ms)
2.         worker->current_func(work); 함수 실행
3. 'worker->current_func(work)' 코드의 실행 시각이 500ms 이내이면 
   타이머 해제
4. 최대 실행 시각을 저장
 
< 두 번째 시나리오 >
1. 타이머 설정(500ms)
2.         worker->current_func(work); 함수 실행
3. 'worker->current_func(work)' 코드의 실행 시각이 500ms 이상이 걸리면  
   타이머 핸들러 함수가 호출됨
4. work_handler_elapsed_timer_func() 타이머 핸들러에서 에러 정보 출력
   : 관련 자료 구조
   : 프로세스의 콜스택
 
임베디드 리눅스 개발자분들은 이 포스팅에 올린 자료를 참고해서 더 빨리 퇴근하셨으면 좋겠습니다.





크래시 유틸리티는 다양한 메모리 및 파일 시스템 디버깅 기능을 제공합니다.
그 중에 유용한 기능 중 하나를 소개합니다.
 
   * files -p '아이노드 주소'
 
먼저 다음 명령어를 입력해 오픈된 파일에 대한 파일 디스크립터, 아이노드 그리고 덴트리를 확인 합니다.
 
   * files <pid>
 
crash> files 1664
PID: 1664   TASK: dc270000  CPU: 1   COMMAND: "Chrome-proc.anim"
ROOT: /    CWD: /
 FD    FILE     DENTRY    INODE    TYPE  PATH
  0  de4d8200  e1a33ab0  e0ce8dd8  CHR   /dev/null
  1  de4d8200  e1a33ab0  e0ce8dd8  CHR   /dev/null
...
 32  dd0cb000  dd4c0000  e02a6af8  REG   /system/framework/framework-res.apk
 33  dd0cbf00  dd470558  e038daf8  REG   /data/media/0/Pictures/Screenshots/Screenshot_20191217-134455.png
 
이어서 아이노드 주소와 함께 아래 명령어를 입력합니다.
 
crash> files -p e038daf8
 INODE    NRPAGES
e038daf8      489
 
  PAGE    PHYSICAL   MAPPING    INDEX CNT FLAGS
e731eba0  7925d000  e038dbf4       269  1 40010228 uptodate,lru,arch_1,mappedtodisk
e7602300  90418000  e038dbf4       517  1 4001026c referenced,uptodate,lru,active,arch_1,mappedtodisk
e71e4980  6f54c000  e038dbf4       803  6 4021026c referenced,uptodate,lru,active,arch_1,mappedtodisk,r_readahead
e71ffdc0  702ee000  e038dbf4       804  4 4021022c referenced,uptodate,lru,arch_1,mappedtodisk,r_readahead
e7b6f900  bbac8000  e038dbf4       805  2 40210268 uptodate,lru,active,arch_1,mappedtodisk,r_readahead
e721e040  71202000  e038dbf4       806  2 40010268 uptodate,lru,active,arch_1,mappedtodisk
 
 
무려 489개의 페이지가 해당 Screenshot_20191217-134455.png 파일의 아이노드에 액세스하고 있습니다.
 
이번에는 다른 명령어를 입력해 볼까?
 
   * files -d <덴트리 주소>
 
crash> files -d dd470558
 DENTRY    INODE    SUPERBLK  TYPE  PATH
dd470558  e038daf8  e1cad800  REG  /data/media/0/Pictures/Screenshots/Screenshot_20191217-134455.png
 
덴트리에 대한 경로 파일을 확인할 수 있다.
 
해당 파일에 대한 슈퍼 블럭을 확인한 ex4 파일 시스템의 슈퍼 블럭임을 확인할 수 있다.
 
crash> struct super_block e1cad800
struct super_block {
  s_list = {
    next = 0xe1cae000,
    prev = 0xe4bc4000
  },
  s_dev = 265289728,
  s_blocksize_bits = 12 '\f',
  s_blocksize = 4096,
  s_maxbytes = 17592186040320,
  s_type = 0xc1733a2c <ext4_fs_type>,
  s_op = 0xc1017f00 <ext4_sops>,
  dq_op = 0xc1017f98 <ext4_quota_operations>,
  s_qcop = 0xc1017fc0 <ext4_qctl_operations>,
  s_export_op = 0xc1017f74 <ext4_export_ops>,
이번 포스팅에서는 크래시 유틸리티의 extensions인 gcore를 빌드하는 방법과 이 기능을 활용해 유저 프로세스의 스택을 추출하는 방법을 소개한다.
 
* gcore 소스 코드를 내려받기
 
'http://people.redhat.com/~anderson/extensions.html' url에 액세스한 다음에, 
crash-gcore-command-1.5.1.tar.gz 파일을 내려받는다.
 
crash-gcore-command-1.5.1.tar.gz
 
crash-gcore-command-1.5.1.tar.gz 파일을 받은 다음에 압축을 푼다.
 
* gcore 소스 코드를 빌드하기
 
먼저 크래시 유틸리티의 소스 코드를 빌드한다. (ARM64 아키텍처를 기준)
$ make target=ARM64
 
크래시 유틸리티의 소스에서 extensions 디렉토리로 이동한다.
 
austindh.kim:~/bin/crash_src/crash/extensions$ ls
COPYING  defs.h  dminfo.c  echo.c  eppic  eppic.c  eppic.mk  libgcore  Makefile  snap.c  snap.mk  trace.c
 
gcore 유틸리티의 소스 코드를 extensions 폴더로 복사한다.
 
austindh.kim:~/bin/crash_src/crash/extensions$ cp -r ~austindh.kim/bin/gcore-command-1.5.1/crash-gcore-command-1.5.1/* .
austindh.kim:~/bin/crash_src/crash/extensions$ ls
COPYING  defs.h  dminfo.c  echo.c  eppic  eppic.c  eppic.mk  gcore.c  gcore.mk  libgcore  Makefile  snap.c  snap.mk  trace.c
 
gcore의 소스 코드를 복사한 후 gcore.c와 gcore.mk 파일이 보일 것이다. 그럼 제대로 복사가 된 것이다.
 
크래시 유틸리티의 최상단 디렉토리로 이동한 후 'make target=ARM64 extensions' 명령어를 입력해 extensions 기능을 빌드한다. 
 
austindh.kim:~/bin/crash_src/crash$ make target=ARM64 extensions
gcc -Wall -g -shared -rdynamic -o echo.so echo.c -fPIC -DARM64  -DGDB_7_6
cd eppic/libeppic && make
 
extensions 디렉터리로 이동하면 gcore.so이란 라이브러리 파일이 제대로 생성됐음을 확인할 수 있다.
 
austindh.kim:~/bin/crash_src/crash/extensions$ ls
COPYING  dminfo.c   echo.c   eppic    eppic.mk  gcore.c   gcore.so  Makefile  snap.mk  trace.so
defs.h   dminfo.so  echo.so  eppic.c  eppic.so  gcore.mk  libgcore  snap.c    trace.c
 
크래시 유틸리티를 실행한 다음에 gcore.so 라이브러리 파일을 extension 명령어로 로딩한다.
 
crash64> extend /home007/austindh.kim/bin/Crash64Tool/extensions/gcore.so
/home007/austindh.kim/bin/Crash64Tool/extensions/gcore.so: shared object loaded
 
'shared object loaded' 메시지가 보이니 제대로 gcore.so 라이브러리 파일이 로딩됐음을 알 수 있다.
 
* 크래시 유틸리티에서 gcore.so 파일을 로딩하기
 
이제 PID가 1인 init 프로세스의 유저 공간을 덤프해보자.
이를 위해 'gcore [pid]' 명령어를 입력해야 한다.
 
crash64> gcore 1
gcore: WARNING: page fault at 64a000
gcore: WARNING: page fault at 64b000
gcore: WARNING: page fault at 64e000
gcore: WARNING: page fault at 651000
gcore: WARNING: page fault at 652000
gcore: WARNING: page fault at 659000
gcore: WARNING: page fault at 7f78601000
gcore: WARNING: page fault at 7f78602000
...
gcore: WARNING: page fault at 7fc2740000
gcore: WARNING: page fault at 7fc2741000
Saved core.1.init
 
'WARNING: page fault' 메시지는 False Positive 이니 너무 겁먹지 말자.
 
* TRACE32로 gcore 덤프 파일을 로딩하기
 
이제 gcore로 추출한 덤프 파일을 TRACE32 프로그램으로 로딩하자.
 
Data.LOAD.binary core.1.init 0x7FC20FD000
Data.LOAD.elf init 
 
참고로 0x7FC20FD000
 오프셋은 gcore 메타 해더 오프셋을 고려해 계산했다.
0x7FC20FD000 = 0x7FC2742000 - 0x645000
 
커널 스택 주소의 스택 최하단에 위치한 '유저 프로세스의 레지스터 세트'정보를 참고해 레지스터 세트를 설정한다.
TRACE32 명령어는 다음과 같다.
 
$ r.s X29   0x7FC2744110
$ r.s X30                  0
$ r.s PC            0x419344
$ r.s SP    0x7FC27440D0
$ v.f
 
이제 유저 공간에서 실행 중인 init 프로세스의 콜스택을 볼 수 있다.
 
-000|std::__1::__compressed_pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allo
-000|std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(
-000|android::init::SocketConnection::source_context(?)
-001|std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(
-001|android::init::handle_property_set_fd()
-002|android::init::main(?, ?)
-003|main(?, ?)
-004|__libc_init(?, ?, slingshot = 0x00403698, structors = 0x0000007FC2744600)
 ---|end of frame
 
보통 프로덕션 빌드로 이미지를 생성하려고 할 때 커널에서 ftrace를 아예 꺼버리고 싶을 때가 있다. 
이 때 다음 패치를 적용해보자.
 
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 40817e4..2cf0d86 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -273,6 +273,7 @@ unsigned long long ns2usecs(u64 nsec)
  */
 static struct trace_array global_trace = {
        .trace_flags = TRACE_DEFAULT_FLAGS,
+       .buffer_disabled = 1,
 };
 
 LIST_HEAD(ftrace_trace_arrays);
 
* 유튜브 강의 동영상도 있으니 같이 들으시면 더 많은 걸 배울 수 있습니다. 



 
아래와 같이 t32 파일을 수정하면 폰트 사이즈를 LARGE로 변경할 수 있습니다.
 
index d07418b..db9c6c9 100644
--- a/config-sim.t32
+++ b/config-sim.t32
@@ -16,7 +16,7 @@ ID=T32
 SYS=${2}                       ; make system directory the same as the executable directory
 
 SCREEN=
-FONT=SMALL
+FONT=LARGE
 HEADER=iTSP - HANCOM MDS GUI ${4}
 
 ; Ethernet on Host information
@@ -26,4 +26,4 @@ CORE=${4}                     ; CORE=1 indicates the primary core that has the trace windowsen
 
 ; Printer settings
 ;PRINTER=WINDOWS
-;-------------------------------------------------------------------------------------------------------------
\ No newline at end of file
+;-------------------------------------------------------------------------------------------------------------
 
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
 
크래시 유틸리티 프로그램을 실행할 때 페이지 디스크립터가 위치한 vmemmap의 범위는
'ffffffbc00000000 - ffffffbdffffffff'와 같습니다.
 
crash64: kernel image: ffffff96ff280000 - ffffff9702265000
crash64:      vmemmap: ffffffbc00000000 - ffffffbdffffffff
 
kmalloc 슬랩 캐시를 확인해보겠습니다.
 
crash64> p kmalloc_caches
kmalloc_caches = $1 =
 {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffc6008fdc80, 0xffffffc6008fdb00, 0xffffffc6008fd980, 0xffffffc6008fd800, 0xffffffc6008fd680, 0xffffffc6008fd500, 0xffffffc6008fd380}
 
0xffffffc6008fdb00 주소에 해당하는 슬랩 캐시의 정체는 다음과 같습니다.
 
crash64> struct kmem_cache.name 0xffffffc6008fdb00
  name = 0xffffff9700f6e049 "kmalloc-256"
 
페이지 디스크립터 구조체를 보면 포인터 형인 slab_cache 필드가 있습니다.
crash64> struct page
struct page {
    unsigned long flags;
...
        struct {
            union {
                struct list_head slab_list;
                struct {
                    struct page *next;
                    int pages;
                    int pobjects;
                };
            };
            struct kmem_cache *slab_cache;
            void *freelist;
...
 
crash64_kaslr> struct page.slab_cache
struct page {
  [0x18]         struct kmem_cache *slab_cache;
}
 
그래서 슬랩 캐시의 주소 검색을 통해 해당 슬랩 캐시의 페이지 갯수를 확인할 수 있습니다.
 
crash64_kaslr> search 0xffffffc6008fdb00
ffffff80156ab8a0: ffffffc6008fdb00
ffffff97012c4a00: ffffffc6008fdb00
ffffffbf18023e98: ffffffc6008fdb00
...
 
vmemmap의 시작 주소가 ffffffbc00000000 이므로 ffffff80156ab8a0와 ffffff97012c4a00 주소에 위치한 슬랩 캐시의 주소는 별 의미가 없습니다.
 
vmemmap 메모리 공간에서 "kmalloc-256" 슬랩 캐시를 포함하는 페이지 디스크립터의 갯수는 880개입니다.
 
crash64> search 0xffffffc6008fdb00 | wc -l
882
 
// 880 = 882 - 2
 
이번에는 "kmalloc-128" 슬랩 캐시의 페이지 디스크립터의 갯수를 확인하겠습니다.
 
crash64_kaslr> struct kmem_cache.name 0xffffffc6008fdb00 
  name = 0xffffff9700f6e03d "kmalloc-128"
 
엄청난 갯수의 페이지 디스크립터를 사용하고 있습니다.
 
crash64> search  0xffffffc6008fdc80 | wc -l
891606
 
vmemmap의 범위('ffffffbc00000000 - ffffffbdffffffff') 밖의 주소가 포함하는 "kmalloc-128" 슬랩 캐시의 갯수는
16이므로, 891590(891606 - 16) 개의 페이지를 소진하고 있습니다.
 
crash64> search  0xffffffc6008fdc80
ffffff80083ab670: ffffffc6008fdc80
ffffff80083ab6f8: ffffffc6008fdc80
ffffff800951bc20: ffffffc6008fdc80
ffffff800951bc30: ffffffc6008fdc80
ffffff800951bc50: ffffffc6008fdc80
ffffff800958b8b0: ffffffc6008fdc80
ffffff800958b8c0: ffffffc6008fdc80
ffffff80145bbba0: ffffffc6008fdc80
ffffff8015a0bab0: ffffffc6008fdc80
ffffff8015e9bb30: ffffffc6008fdc80
ffffff801647b6d0: ffffffc6008fdc80
ffffff801772b830: ffffffc6008fdc80
ffffff8017a5b830: ffffffc6008fdc80
ffffff8017a5b840: ffffffc6008fdc80
ffffff8021223ab0: ffffffc6008fdc80
ffffff97012c49f8: ffffffc6008fdc80
 
"kmalloc-128" 슬랩 오브젝트의 Leak을 확인할 필요가 있습니다.
 
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
 
 
 
 
 
 
 
 
 
 
 
 
 
소스 코드를 분석 하다보면 구조체와 enum의 정체를 알고 싶을 때가 있습니다.
이 때 TRACE32를 활용하면 바로 이 정보를 확인할 수 있습니다.
 
구조체 확인하기
 
먼저 구조체의 세부 필드는 다음과 같은 명령어를 입력하면 확인할 수 있습니다.
 
$ v.type % %m %l %hi %sp  struct '구조체 이름'
 
자, 그럼 리눅스 커널에서 프로세스 정보를 나타내는 struct task_struct 구조체의 정체를 확인해봅시다.
 
$ v.type % %m %l %hi %sp  struct task_struct
(struct task_struct) struct task_struct struct (4096 bytes,
                [0] struct thread_info thread_info,
                [24] long int state,
                [32] void * stack,
                [40] atomic_t usage,
                [44] unsigned int flags,
                [48] unsigned int ptrace,
                [56] struct llist_node wake_entry,
                [64] int on_cpu,
 
위와 같이 task_struct 구조체의 세부 필드 정보를 확인할 수 있습니다.
 
그럼, 실제 리눅스 커널에서 struct task_struct 구조체의 선언부는 어느 코드에서 확인이 가능할까요?
struct task_struct 구조체의 정의문은 다음과 같습니다.
 
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
 * For reasons of header soup (see current_thread_info()), this
 * must be the first element of task_struct.
 */
struct thread_info thread_info;
#endif
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;
 
/*
 * This begins the randomizable portion of task_struct. Only
 * scheduling-critical items should be added above here.
 */
randomized_struct_fields_start
 
void *stack;
refcount_t usage;
/* Per task flags (PF_*), defined further below: */
unsigned int flags;
unsigned int ptrace;
 
#ifdef CONFIG_SMP
struct llist_node wake_entry;
int on_cpu;
 
enum 타입 확인하기
 
이번에는 enum의 선언부를 확인해봅시다.
먼저 enum 세부 필드는 다음과 같은 명령어를 입력하면 확인할 수 있습니다.
 
$ v.type % %m %l %hi %sp  enum 'enum 선언부'
 
자, 그럼 TRACE32를 활용해 리눅스 커널에서 파워 Supply 정보를 enum power_supply_property 정체를 확인해봅시다.
 
$ v.type % %m %l %hi %sp  enum power_supply_property
 
(enum power_supply_property) enum power_supply_property enum (32 bits, unsigned,
                POWER_SUPPLY_PROP_STATUS = 0,
                POWER_SUPPLY_PROP_CHARGE_TYPE = 1,
                POWER_SUPPLY_PROP_HEALTH = 2,
                POWER_SUPPLY_PROP_PRESENT = 3,
                POWER_SUPPLY_PROP_ONLINE = 4,
                POWER_SUPPLY_PROP_AUTHENTIC = 5,
                POWER_SUPPLY_PROP_TECHNOLOGY = 6,
                POWER_SUPPLY_PROP_CYCLE_COUNT = 7,
                POWER_SUPPLY_PROP_VOLTAGE_MAX = 8,
                POWER_SUPPLY_PROP_VOLTAGE_MIN = 9,
                POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN = 10,
                POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN = 11,
                POWER_SUPPLY_PROP_VOLTAGE_NOW = 12,
                POWER_SUPPLY_PROP_VOLTAGE_AVG = 13,
                POWER_SUPPLY_PROP_VOLTAGE_OCV = 14,
                POWER_SUPPLY_PROP_VOLTAGE_BOOT = 15,
                POWER_SUPPLY_PROP_CURRENT_MAX = 16,
                POWER_SUPPLY_PROP_CURRENT_NOW = 17,
                POWER_SUPPLY_PROP_CURRENT_AVG = 18,
                POWER_SUPPLY_PROP_CURRENT_BOOT = 19,
...
 
그럼, 실제 리눅스 커널에서 power_supply_property enum의 선언부는 어느 코드에서 확인이 가능할까요?
 
power_supply_property enum의 정의문은 다음과 같습니다.
 
enum power_supply_property {
/* Properties of type `int' */
POWER_SUPPLY_PROP_STATUS = 0,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_AUTHENTIC,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
POWER_SUPPLY_PROP_VOLTAGE_MIN,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_VOLTAGE_BOOT,
 
정리하면 TRACE32를 활용하면 struct 구조체와 enum 타입의 정체를 바로 확인할 수 있습니다.
 
---
"이 포스팅이 유익하다고 생각되시면 공감 혹은 댓글로 응원해주시면 감사하겠습니다. 
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!"
 
​Thanks,
Guillermo Austin Kim(austindh.kim@gmail.com)
---
리눅스 프로젝트를 개발하면 코어덤프(coredump)를 열어서 크래시가 발생한 원인을 분석할 때가 많습니다.
많은 개발자 분들이 코어덤프를 열어서 크래시가 발생한 원인을 분석하죠.
 
코어덤프를 열어 gdb를 사용해 디버깅할 때 가장 짜증나는 것 중 하나는 공유 라이브러리를 제대로 로딩하지 못해
콜 스택이 보이지 않을 때 입니다.
 
이번에는 코어덤프를 로딩할 때 필요한 정보 중 하나인 공유 라이브러리의 정보(패스/이름)을 확인하는 방법을 소개합니다.
 
깨진 콜 스택 확인하기
 
먼저 콜 스택을 보겠습니다.
 
(gdb) bt
#0  0x0000007f7def1808 in __glibc_raise (sig=sig@entry=6) at /usr/glibc/raise.c:1354
#1  0x0000007f7def2d80 in __glibc_abort () at /usr/glibc/abort.c:489
#2  0x0000007f7deeadbc in __assert_fail_base()     at /usr/glibc/assert.c:912
#3  0x0000007f7deeae6c in __glibc___assert_fail () at  /usr/glibc/assert.c:1014
#4  0x0000007f7e4f13ec in power_railway_delay () at /usr/src/powerrail/powerrail.c:9489
#5  0x0000007f7efa5af4 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
 
위 메시지를 보면 #5 줄에서 콜 스택이 깨진 걸 볼 수 있습니다.
 
#5  0x0000007f7efa5af4 in ?? ()
 
0x0000007f7efa5af4 주소가 뭔지 모르겠다는 소리죠.
 
공유 라이브러리 이름 확인하기
 
이번에는 'info share' 명령어를 입력해 공용 라이브러리의 경로를 확인합시다.
 
(gdb) info share
From                To                  Syms Read   Shared Object Library
                                        No          /usr/lib/libPowerRailwayManagement.so
0x0000007f7ee1cf30  0x0000007f7eec9c9c  Yes          /usr/lib/librsi.so.0.1
 
가장 첫 번째 줄을 보면 libPowerRailwayManagement.so 라이브러리가 제대로 로딩이 안 됐다고 확인됩니다.
 
No          /usr/lib/libPowerRailwayManagement.so
 
 
공유 라이브러리 오프셋 확인하기
 
리눅스 터미널을 하나 더 열고, './readelf -n coredump' 명령어를 입력해 코어덤프에 있는 NT_FILE 노트를 확인합시다. 참고로 NT_FILE 노트에는 코어덤프에서 공유 라이브러리 정보가 포함돼 있습니다.
 
baldcandy.kim# ./readelf -n coredump 
 
Displaying notes found at file offset 0x00005168 with length 0x00010c58:
  Owner                 Data size       Description
  CORE                 0x00000188       NT_PRSTATUS (prstatus structure)
  CORE                 0x00000088       NT_PRPSINFO (prpsinfo structure)
  CORE                 0x00000080       NT_SIGINFO (siginfo_t data)
  CORE                 0x00000130       NT_AUXV (auxiliary vector)
  CORE                 0x00002b9d       NT_FILE (mapped files)
    Page size: 4096
                 Start                 End         Page Offset
...
    0x0000007f7ef21000  0x0000007f7f18a000  0x0000000000000000
        /usr/lib/libPowerRailwayManagement.so
    0x0000007f7f18a000  0x0000007f7f19a000  0x0000000000000269
        /usr/lib/libPowerRailwayManagement.so
    0x0000007f7f19a000  0x0000007f7f1a3000  0x0000000000000269
        /usr/lib/libPowerRailwayManagement.so
    0x0000007f7f1a3000  0x0000007f7f1a5000  0x0000000000000272
        /usr/lib/libPowerRailwayManagement.so
 
libPowerRailwayManagement.so 라이브러리의 실행 오프셋은 0x0000007f7ef21000임을 알 수 있습니다.
 
이번에는 libPowerRailwayManagement.so 라이브러리 파일을 찾아서 헤더 정보를 확인합시다.
이를 위해 'readelf -S libPowerRailwayManagement.so' 명령어를 입력해야 합니다.
 
baldcandy.kim# readelf -S libPowerRailwayManagement.so
There are 27 section headers, starting at offset 0x273728:
 
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.build-i NOTE             00000000000001c8  000001c8
       0000000000000024  0000000000000000   A       0     0     4
...
  [10] .plt              PROGBITS         0000000000074b60  00074b60
       000000000000a110  0000000000000010  AX       0     0     16
  [11] .text             PROGBITS         000000000007ec70  0007ec70
       0000000000160ebc  0000000000000000  AX       0     0     16
 
'[11]' 번째 줄을 보면 .text 섹션 헤더의 오프셋은 0x0007ec70으로 확인됩니다.
 
결국 libPowerRailwayManagement.so 라이브러리의 오프셋은 다음 계산식으로 0x7F7EF9FC70입니다.
 
0x7F7EF9FC70 = 0x0000007f7ef21000 + 0x0007ec70
 
공유 라이브러리 로딩하기
 
다시 GDB 프로그램 화면으로 되돌아와, 다음 명령어를 입력해 라이브러리 패스를 추가합시다.
 
'add-symbol-file ./libPowerRailwayManagement.so 0x7F7EF9FC70'
 
(gdb) add-symbol-file ./libPowerRailwayManagement.so 0x7F7EF9FC70
add symbol table from file "./libPowerRailwayManagement.so" at
        .text_addr = 0x7f7ef9fc70
(y or n) y
 
명령어를 입력하면 '(y or n)' 텍스트가 출력되면서 .text_addr를 0x7f7ef9fc70로 지정하도 되는지 묻습니다.
바로 'y'를 선택합니다. 그러면 다음과 같은 화면이 보일 것입니다.
 
(y or n) y
Reading symbols from ./libPowerRailwayManagement.so...(no debugging symbols found)...done.
 
공유 라이브러리를 로딩한 다음 콜 스택을 확인하니 깨진 콜 스택이 복구해 볼 수 있습니다.
 
(gdb) bt
#0  0x0000007f7def1808 in __glibc_raise (sig=sig@entry=6) at /usr/glibc/raise.c:1354
#1  0x0000007f7def2d80 in __glibc_abort () at /usr/glibc/abort.c:489
#2  0x0000007f7deeadbc in __assert_fail_base()     at /usr/glibc/assert.c:912
#3  0x0000007f7deeae6c in __glibc___assert_fail () at  /usr/glibc/assert.c:1014
#4  0x0000007f7e4f13ec in power_railway_delay () at /usr/src/powerrail/powerrail.c:9489
#5  0x0000007f7efa5af4 in PowerManager::set_powerrail_delay() 
#6  0x0000007f7efaf774 in PowerManager::set_powerrail_run() 
#7  0x0000007f7efb17e8 in PowerManager::powerrail_set_railway() 
#8  0x0000007f7efb3018 in PowerManager::powerrail_init(void*) ()
#9  0x0000007f7efb3c60 in ?? ()
#10 0x0000007f7e26b020 in start_thread (arg=0x147f7e28f010 <__pthread_keys+15752>) at /usr/src/pthread_create.c:11335
#11 0x0000007f7df88970 in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:89
 
아래는 공유 라이브러리를 로딩하기 전의 깨진 콜 스택 정보입니다.
 
(gdb) bt
#0  0x0000007f7def1808 in __glibc_raise (sig=sig@entry=6) at /usr/glibc/raise.c:1354
#1  0x0000007f7def2d80 in __glibc_abort () at /usr/glibc/abort.c:489
#2  0x0000007f7deeadbc in __assert_fail_base()     at /usr/glibc/assert.c:912
#3  0x0000007f7deeae6c in __glibc___assert_fail () at  /usr/glibc/assert.c:1014
#4  0x0000007f7e4f13ec in power_railway_delay () at /usr/src/powerrail/powerrail.c:9489
#5  0x0000007f7efa5af4 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
 
 
---
"이 포스팅이 유익하다고 생각되시면 공감 혹은 댓글로 응원해주시면 감사하겠습니다. 
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!"
 
​Thanks,
Guillermo Austin Kim(austindh.kim@gmail.com)
---

+ Recent posts