유저 공간에서 zygote가 강제 종료되면서 부팅을 못하는 상황입니다. 커널 로그로 아래 메시지를 볼 수 있습니다.
아래 로그는 init 프로세스가 zygote에 SIGABRT(6) 시그널을 전달해서 zygote를 종료시키고 있습니다.
[ 46.116831 / 01-02 01:20:24.859][0] init: Service 'zygote' (pid 1777) killed by signal 6
[ 46.124107 / 01-02 01:20:24.869][1] init: Service 'zygote' (pid 1777) killing any children in process group
그럼 이 동작을 할 때 커널 관점으로 어떤 코드가 수행되는지 살펴보겠습니다.
zygote는 커널 공간에서 "main" 이란 쓰레드로 수행됩니다. 그래서 아래 코드를 눈여겨 보면 __send_signal()/send_sigqueue 함수에서 전달하는 struct task_struct *t 가 시그널 전달 받는 프로세스입니다. 이 프로세스의 comm이란 멤버가 프로세스 이름을 담고 있습니다.
참고로 아래와 같이 ftrace log를 설정하고, 강제로 zygote를 종료하는 테스트를 수행했습니다.
adb shell "echo 1 > /d/tracing/events/sched/sched_process_exit/enable"
adb shell "echo 1 > /d/tracing/events/sched/sched_process_fork/enable"
adb shell "echo 1 > /d/tracing/events/signal/enable"
kill -6 918, 즉 kill -6 [pid]로 자이고트에 SIGABRT 시그널을 전달하는 것입니다.
root 918 1 1111816 81936 poll_sched b2ae1ec4 S zygote
austin:/ # kill -6 918
그럼 다음과 같은 ftrace 로그를 볼 수 있습니다. main이란 프로세스가 sig 6 SIGABT를 맞고 결국 종료됩니다.
<...>-456 [005] d..2 1186.470605: signal_generate: sig=6 errno=0 code=-6 comm=main pid=918 grp=0 res=0
<...>-918 [005] d..2 1186.470786: signal_deliver: sig=6 errno=0 code=-6 sa_handler=0 sa_flags=14000000
<...>-6202 [007] d..2 1186.470881: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6205 [005] d..2 1186.470895: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6203 [007] d..2 1186.470919: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6204 [005] d..2 1186.470969: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6203 [007] ...1 1186.471086: sched_process_exit: comm=FinalizerDaemon pid=6203 prio=120
<...>-918 [005] ...1 1186.471092: sched_process_exit: comm=main pid=918 prio=120
<...>-6204 [005] ...1 1186.471780: sched_process_exit: comm=FinalizerWatchd pid=6204 prio=120
<...>-6202 [007] ...1 1186.471798: sched_process_exit: comm=ReferenceQueueD pid=6202 prio=120
그래서 프로세스 이름이 main일 때 강제 커널 패닉을 유발하는 코드를 작성했습니다.
diff --git a/kernel/signal.c b/kernel/signal.c
index 7408330..3157014 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1031,6 +1031,12 @@ static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
assert_spin_locked(&t->sighand->siglock);
result = TRACE_SIGNAL_IGNORED;
+ if (!strcmp(t->comm, "main")) {
+ show_stack(NULL, NULL);
+ printk("[+][Pompeii] proc %s is ignored \n", t->comm);
+ BUG();
+ }
+
if (!prepare_signal(sig, t,
from_ancestor_ns || (info == SEND_SIG_FORCED)))
goto ret;
@@ -1120,6 +1126,7 @@ out_set:
sigaddset(&pending->signal, sig);
complete_signal(sig, t, group);
ret:
+ printk("[+][Pompeii] proc %s sig: %d \n", t->comm, sig);
trace_signal_generate(sig, info, t, group, result);
return ret;
}
@@ -1568,6 +1575,15 @@ int send_sigqueue(struct sigqueue *q, struct task_struct *t, int group)
unsigned long flags;
int ret, result;
+
+ if (!strcmp(t->comm, "main")) {
+ show_stack(NULL, NULL);
+ printk("[+][Pompeii] proc %s is ignored \n", t->comm);
+ show_stack(NULL, NULL);
+
+ BUG();
+ }
+
위 코드를 반영하고 타겟 디바이스에 다운로드를 했습니다. 역시 예상대로 부팅 후 20초 정도 시간이 지나자 커널 패닉이 발생했습니다.
유저 영역 콜스택까지 확인하니 아래 함수 흐름으로 __send_signal 함수가 호출됩니다.
참고로 심볼 정보가 포함된 libc.so 파일은 symbols\system\lib64에 위치한 libc.so로 로딩해야 한다는 점을 잊지 마세요.
-000|do_undefinstr(regs = 0xFFFFFFC001714630)
-001|__send_signal.constprop.27(sig = 1865235760, info = 0xFFFFFFC06F2D3E40, t = 0xFFFFFFC0000AE488, grou
-002|send_signal(inline)
-002|do_send_sig_info(sig = 6, info = 0xFFFFFFC06F2D3E40, p = 0xFFFFFFC06F2C8000, ?)
-003|do_send_specific(tgid = 748, ?, sig = 6, info = 0xFFFFFFC06F2D3E40)
-004|do_tkill(tgid = 748, pid = 748, sig = 6)
-005|SYSC_tgkill(inline)
-005|sys_tgkill(?, ?, ?)
-006|el0_svc_naked(asm)
-->|exception
-007|tgkill(asm)
-008|pthread_kill(?, sig = 6)
-009|raise(sig = 748)
-010|abort()
-011|android_log_write_string8_len(?, ?, ?)
유저 공간에서 어떤 일이 있었는지 확인해보니 아래 흐름으로 abort가 발생했습니다.
abort -> raise -> pthread_kill -> tgkill
정리하면, 유저 영역에서 abort가 발생하면 커널 영역에 signal 6를 전달함을 알 수 있죠.
그런데 dump state 로그를 확인하면 아래 콜스택을 볼 수 있습니다.
/system/lib64/libc.so (tgkill+8)
/system/lib64/libc.so (pthread_kill+64)
/system/lib64/libc.so (raise+24)
/system/lib64/libc.so (abort+52)
/system/lib64/libart.so art::Runtime::Abort() /system/lib64/libart.so art::LogMessage::~LogMessage() /system/lib64/libart.so art::Thread::AssertNoPendingException()
/system/lib64/libart.so art::ClassLinker::FindClass(art::Thread*, char const*, art::Handle<art::mirror::ClassLoader>) /system/lib64/libart.so art::ClassLinker::FindArrayClass(art::Thread*, art::mirror::Class**) /system/lib64/libart.so art::JNI::NewObjectArray(_JNIEnv*, int, _jclass*, _jobject*)
/system/lib64/libandroid_runtime.so /system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.nativeInitialize+124)
/system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.<init>+300)
/system/framework/oat/arm64/services.odex (offset 0xd1b000)
C++ 코드에서 클래스 메쏘드로 구현된 함수들은 심볼들이 이상하게 보입니다.
아래 폴더에 있는 바이너리 유틸리티 프로그램을 쓰면 원래 심볼로 보여줍니다. c++filt 옵션을 기억합시다.
android/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-androidkernel-nm ./aarch64-linux-androidkernel-nm libart.so | c++filt > sym_libart.c
참고로 dump state 원래 로그는 아래와 같습니다. 알 수 없는 _ZN3art7Runtime5AbortEv 심볼이 보이죠.
01-03 00:37:24.599 1721 1721 F DEBUG : backtrace:
01-03 00:37:24.599 1721 1721 F DEBUG : #00 pc 000000000006b8c8 /system/lib64/libc.so (tgkill+8)
01-03 00:37:24.599 1721 1721 F DEBUG : #01 pc 0000000000068d4c /system/lib64/libc.so (pthread_kill+64)
01-03 00:37:24.599 1721 1721 F DEBUG : #02 pc 00000000000242b8 /system/lib64/libc.so (raise+24)
01-03 00:37:24.599 1721 1721 F DEBUG : #03 pc 000000000001ccd4 /system/lib64/libc.so (abort+52)
01-03 00:37:24.599 1721 1721 F DEBUG : #04 pc 000000000042c5d0 /system/lib64/libart.so (_ZN3art7Runtime5AbortEv+352)
01-03 00:37:24.599 1721 1721 F DEBUG : #05 pc 00000000000e4b24 /system/lib64/libart.so (_ZN3art10LogMessageD2Ev+1204)
01-03 00:37:24.599 1721 1721 F DEBUG : #06 pc 000000000044eaf4 /system/lib64/libart.so (_ZNK3art6Thread24AssertNoPendingExceptionEv+836)
01-03 00:37:24.599 1721 1721 F DEBUG : #07 pc 0000000000123ec8 /system/lib64/libart.so (_ZN3art11ClassLinker9FindClassEPNS_6ThreadEPKcNS_6HandleINS_6mirror11ClassLoaderEEE+68)
01-03 00:37:24.599 1721 1721 F DEBUG : #08 pc 000000000011f170 /system/lib64/libart.so (_ZN3art11ClassLinker14FindArrayClassEPNS_6ThreadEPPNS_6mirror5ClassE+744)
01-03 00:37:24.600 1721 1721 F DEBUG : #09 pc 0000000000348c3c /system/lib64/libart.so (_ZN3art3JNI14NewObjectArrayEP7_JNIEnviP7_jclassP8_jobject+652)
01-03 00:37:24.600 1721 1721 F DEBUG : #10 pc 0000000000142254 /system/lib64/libandroid_runtime.so
01-03 00:37:24.600 1721 1721 F DEBUG : #11 pc 0000000001baa5d0 /system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.nativeInitialize+124)
01-03 00:37:24.600 1721 1721 F DEBUG : #12 pc 0000000001ba9e80 /system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.<init>+300)
01-03 00:37:24.600 1721 1721 F DEBUG : #13 pc 0000000000dc8474 /system/framework/oat/arm64/services.odex (offset 0xd1b000)
정리하면 유저 공간에서 Abort가 떨어지면 abort() -> raise() -> pthread_kill() -> tgkill() 흐름으로 sys_tgkill란 시스템 콜이 호출되어 커널 공간에서는 __send_signal 함수가 호출됩니다.
위 동작은 모두 Aarch64 아키텍처 환경에서 설명을 드린 건데요. Aarch32 아키텍처는 약간 동작이 다릅니다.
-000|__do_user_fault(tsk = 0x46, addr = 0, fsr = 5, sig = 11, code = 0, regs = 0x0)
-001|__do_kernel_fault(inline)
-001|do_page_fault(addr = 3883189760, fsr = 5, regs = 0xE5BEFFB0)
-002|do_translation_fault(?, fsr = 3854499232, ?)
-003|do_DataAbort(addr = 0, fsr = 5, regs = 0xE5BEFFB0)
-004|__dabt_usr(asm)
arm32 비트 아키텍처에선 유저 영역에서 어보트가 떨어지면 __dabt_usr란 익셉션 벡터가 실행되고, (물론 유저 공간에서 수행 중인 레지스터를 스택에 푸시합니다.)
NSR:C0F733E0|E24DD048 __dabt_usr: sub r13,r13,#0x48 ; r13,r13,#72
NSR:C0F733E4|E98D1FFE stmib r13,{r1-r12}
NSR:C0F733E8|EE117F10 mrc p15,0x0,r7,c1,c0,0x0 ; p15,0,r7,c1,c0,0 (system control)
NSR:C0F733EC|E51F80B4 ldr r8,0xC0F73340
NSR:C0F733F0|E8900038 ldm r0,{r3-r5}
NSR:C0F733F4|E28D003C add r0,r13,#0x3C ; r0,r13,#60
NSR:C0F733F8|E3E06000 mvn r6,#0x0 ; r6,#0
NSR:C0F733FC|E58D3000 str r3,[r13]
NSR:C0F73400|E5988000 ldr r8,[r8]
NSR:C0F73404|E8800070 stm r0,{r4-r6}
NSR:C0F73408|E9406000 stmdb r0,{r13-r14}^
NSR:C0F7340C|E1380007 teq r8,r7
NSR:C0F73410|1E018F10 mcrne p15,0x0,r8,c1,c0,0x0 ; p15,0,r8,c1,c0,0 (system control)
NSR:C0F73414|E3A0B000 mov r11,#0x0 ; r11,#0
NSR:C0F73418|EBC5BDA8 bl 0xC00E2AC0 ; trace_hardirqs_off
NSR:C0F7341C|E1A0200D cpy r2,r13
NSR:C0F73420|EBC2BC6E bl 0xC00225E0 ; v7_early_abort
v7_early_abort란 레이블에서 MMU의 fault register를 r1 레지스터로 읽습니다.
NSR:C00225E0|EE151F10 v7_early_abort: mrc p15,0x0,r1,c5,c0,0x0 ; p15,0,r1,c5,c0,0 (data fault status)
NSR:C00225E4|EE160F10 mrc p15,0x0,r0,c6,c0,0x0 ; p15,0,r0,c6,c0,0 (data fault address)
NSR:C00225E8|EAFF97CF b 0xC000852C ; do_DataAbort
위 코드에서 "mrc p15,0x0,r1,c5,c0,0x0" 명령어를 잘 기억하세요.
결국 MMU fault register에서 읽은 값에 따라 fsr_info[5] 전역변수에 이미 정의된 do_translation_fault 함수를 호출하는군요.
(static struct fsr_info [32]) fsr_info = (
[0] = (
(int (*)()) fn = 0xC001F58C = do_bad,
(int) sig = 11,
(int) code = 0,
(char *) name = 0xC13F1AD6 = kallsyms_token_index+0x4FC6),
[1] = (
(int (*)()) fn = 0xC0021628 = do_alignment,
(int) sig = 7,
(int) code = 196609,
(char *) name = 0xC13F1AE7 = kallsyms_token_index+0x4FD7),
[2] = (
(int (*)()) fn = 0xC00184B4 = hw_breakpoint_pending,
(int) sig = 5,
(int) code = 196612,
(char *) name = 0xC13F0451 = kallsyms_token_index+0x3941),
[3] = (
(int (*)()) fn = 0xC001F58C = do_bad,
(int) sig = 11,
(int) code = 196609,
(char *) name = 0xC13F17F3 = kallsyms_token_index+0x4CE3),
[4] = (
(int (*)()) fn = 0xC0F7528C = do_translation_fault,
(int) sig = 11,
(int) code = 196609,
(char *) name = 0xC13F17D9 = kallsyms_token_index+0x4CC9),
[5] = (
(int (*)()) fn = 0xC0F7528C = do_translation_fault,
(int) sig = 11,
(int) code = 196609,
(char *) name = 0xC13F193F = kallsyms_token_index+0x4E2F),
[6] = ((int (*)()) fn = 0xC001F58C = do_bad, (int) sig = 11, (int) code = 196609, (char *) name = 0xC13F17F3 = kallsyms_token_index+0x4CE3),
[7] = ((int (*)()) fn = 0xC0F74E4C = do_page_fault, (int) sig = 11, (int) code = 196609, (char *) name = 0xC13F1970 = kallsyms_token_index+0x4E60),
# Reference: For more information on 'Linux Kernel';
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
'[Debugging] Tips' 카테고리의 다른 글
[Trace32] 폰트 색상 변경 - setup.color (0) | 2023.05.04 |
---|---|
[리눅스커널][Trace32] wakelock 디버깅 - container_of (0) | 2023.05.04 |
[Crash-Utility] Radix Tree 디버깅: 'tree -t radix -N (struct radix_tree_node *) 구조체 주소' (0) | 2023.05.04 |
[리눅스커널] 안드로이드/라즈베리 파이 부팅 실패 100% 해결하는 디버깅 패치 (0) | 2023.05.04 |
[TRACE32] 유저 공간 콜스택 복원하기 - ARMv8(Aarch64) (0) | 2023.05.04 |