objdump를 이용한 oops 메세지 분석을 통한 디버깅.

쉽게 쉽게... 고의로 커널 패닉을 낸후에 그 위치를 찾는 방법을 설명한다.

커널 패닉의 종류는 꽈꽉 꼬여있는 라이브러리에서 발생하기도 하고 도저히 찾을수 없는
위치에 존재하기도 한다.(그런 경우는 알아서 하자)

다만, printk 를 한줄씩 넣어가면 하는 디버깅보다는 운이 좋다면 한번에 정확한 위치를 찾을수도 있다.

커널의 init/main.c 함수에는 start_kernel  함수가 있다.
여기에서 고의로 널포인터를 넣고 웁스 패닉을 낸후에 그 위치를 찾아보자.


문제의 소스이다.
init/main.c 의 start_kernel 함수의 맨 마지막 위치이다.

    /* Do the rest non-__init'ed, we're now alive */
    trap_yyc();
    swmem = VMALLOC_START;
    printk("virt to phys 0x%x\n", virt_to_phys(swmem));
    *swmem = 0x12345678;
    printk("--------------------------- 0x%x\n", *swmem);
    printk("--------------------------- 0x%p\n", swmem);
    rest_init();

VMALLOC_START 위치는 당연히 물리적으로 갖고 있는 메모리의 위치 바로 위에 존재한다.
따라서 그런 곳에 포인터를 두고 access 를 시도하면 100% 웁스가 발생한다.

발생한 커널 웁스 메세지이다.

Unable to handle kernel paging request at virtual address c2800000
pgd = c0004000
[c2800000] *pgd=21c14011, *pte=00000000, *ppte=00000000
Internal error: Oops: 807 [#1]
Modules linked in:
CPU: 0    Not tainted  (2.6.24 #135)
PC is at start_kernel+0x2b0/0x350
LR is at 0xc033a3a4
pc : [<c0008ae4>]    lr : [<c033a3a4>]    psr: 60000053
sp : c0335fd4  ip : c033a3a4  fp : c0335ff4
r10: 2002132c  r9 : 41069265  r8 : 20021360
r7 : c0337cd4  r6 : c0022f28  r5 : c035626c  r4 : c0355e24
r3 : 12345678  r2 : c2800000  r1 : 00000001  r0 : c02ebf70
Flags: nZCv  IRQs on  FIQs off  Mode SVC_32  ISA ARM  Segment kernel
Control: 0005317f  Table: 20004000  DAC: 00000017
Process swapper (pid: 0, stack limit = 0xc0334258)
Stack: (0xc0335fd4 to 0xc0336000)
5fc0:                                              c0008470 c0022f28 00053175 
5fe0: c0356728 c0022f24 00000000 c0335ff8 20008034 c0008844 00000000 00000000 
Backtrace: 
[<c0008834>] (start_kernel+0x0/0x350) from [<20008034>] (0x20008034)
 r6:c0022f24 r5:c0356728 r4:00053175
Code: eb01240a e5942000 e59f3094 e59f0094 (e5823000) 
---[ end trace ca143223eefdc828 ]---
Kernel panic - not syncing: Attempted to kill the idle task!

흠... 일단 익숙한 함수가 보인다 start_kernel 이다.  그 함수에서 실행한 무언가에 문제가 있는건 확실하다.
한줄씩 printk 를 넣어서 어디까지 실행하는지 확인해 볼까?

한가지만 해보고 그런 디버깅은 시도하자.
위에서 보면 매우 중요한 정보가 하나 있다.

PC is at start_kernel+0x2b0/0x350

프로그램 카운터가 바로 start_kernel + 0x2b0 에 있다는 것이다. 
그리고 0x350  ===>>> 요 녀석은 바로 start_kernel 의 함수의 크기이다.
오호.... 헥사로 0x2b0 면 함수의 뒤쪽에 있나 보군...
자 첫번째 유추를 할수 있다. 하지만 이것으로는 좀 부족하다.

위의 유추는 정확한가?  System.map 파일을 보고 검증해보자.
vi System.map 으로 본 내용이다.

   31 c00087b8 T parse_early_param
   32 c0008824 W smp_setup_processor_id
   33 c0008834 T start_kernel
   34 c0008b84 t initcall_debug_setup
   35 c0008ba4 t nosoftlockup_setup
   36 c0008bc4 t kernel_init

웁스의 밑에 보면 
[<c0008834>] (start_kernel+0x0/0x350) from [<20008034>] (0x20008034)

kernel_start 함수의 위치와 어디에서 호출되었는지 정보가 있다.
역시 System.map 파일을 보면 __enable_mmu 이다.  이건 그냥 넘어가자.


다시 본론으로 돌아와서 우리가 알고 있는 정보는 문제가 발생한곳이
start_kernel + 0x2b0 위치에 있다는 것이다.

이제 objdump 를 이용해 보자.
내가 사용하려고 하는 옵션은 두가지 이다.

첫번째로 -D 옵션이다.
이것은 disassemble 을 하는데 모조리 표시해 달라고 한다.
arm-linux-objdump -D init/main.o 

이중에 내가 원하는 함수의 역어셈블 화면을 보자.
00000474 <start_kernel>:
 474: e1a0c00d mov ip, sp
 478: e92dd870 stmdb sp!, {r4, r5, r6, fp, ip, lr, pc}
 47c: e24cb004 sub fp, ip, #4 ; 0x4
 480: e24dd008 sub sp, sp, #8 ; 0x8

...... 중간 생략

 700: e2833502 add r3, r3, #8388608 ; 0x800000
 704: e1a03ba3 mov r3, r3, lsr #23
 708: e1a03b83 mov r3, r3, lsl #23
 70c: e2831206 add r1, r3, #1610612736 ; 0x60000000
 710: e5843000 str r3, [r4]
 714: ebfffffe bl 714 <start_kernel+0x2a0>
 718: e5942000 ldr r2, [r4]
 71c: e59f3094 ldr r3, [pc, #148] ; 7b8 <.init.text+0x7b8>
 720: e59f0094 ldr r0, [pc, #148] ; 7bc <.init.text+0x7bc>
 724: e5823000 str r3, [r2]
 728: e5943000 ldr r3, [r4]
 72c: e5931000 ldr r1, [r3]
 730: ebfffffe bl 730 <start_kernel+0x2bc>
 734: e5941000 ldr r1, [r4]
 738: e59f0080 ldr r0, [pc, #128] ; 7c0 <.init.text+0x7c0>
 73c: ebfffffe bl 73c <start_kernel+0x2c8>
 740: ebfffffe bl 740 <start_kernel+0x2cc>
 744: e24bd018 sub sp, fp, #24 ; 0x18
 748: e89da870 ldmia sp, {r4, r5, r6, fp, sp, pc}
...
 75c: 00000378 andeq r0, r0, r8, ror r3
...
 76c: 00000010 andeq r0, r0, r0, lsl r0
 770: 0000037c andeq r0, r0, ip, ror r3
 
 ... 나머지 생략


위의 정보에서 start_kernel 함수의 시작이 0x474 이다.
이유는 잘 아시다시피 링크되기 전의 오브젝트 파일이니까.
그러면 0x474 + 0x2b0 = 0x724 이다.
그 위치를 확인하자.

 714: ebfffffe bl 714 <start_kernel+0x2a0>
 718: e5942000 ldr r2, [r4]
 71c: e59f3094 ldr r3, [pc, #148] ; 7b8 <.init.text+0x7b8>
 720: e59f0094 ldr r0, [pc, #148] ; 7bc <.init.text+0x7bc>
 724: e5823000 str r3, [r2]
 728: e5943000 ldr r3, [r4]
 72c: e5931000 ldr r1, [r3]
 730: ebfffffe bl 730 <start_kernel+0x2bc>
 734: e5941000 ldr r1, [r4]
 738: e59f0080 ldr r0, [pc, #128] ; 7c0 <.init.text+0x7c0>
 73c: ebfffffe bl 73c <start_kernel+0x2c8>
 740: ebfffffe bl 740 <start_kernel+0x2cc>
 744: e24bd018 sub sp, fp, #24 ; 0x18
 748: e89da870 ldmia sp, {r4, r5, r6, fp, sp, pc}
 

문제가 되는 실행 라인은 찾았다
 724: e5823000 str r3, [r2]
히자만 여기가 정확히 어디인지 아직 봐야 할 것이 남아있다.

이번에는 -x 옵션을 써보자
arm-linux-objdump -x init/main.o

내가 원하는 것만 표시하겠다.
000006e0 R_ARM_PC24        page_writeback_init
000006e4 R_ARM_PC24        proc_root_init
000006e8 R_ARM_PC24        check_writebuffer_bugs
000006ec R_ARM_PC24        trap_yyc
00000714 R_ARM_PC24        printk
00000730 R_ARM_PC24        printk
0000073c R_ARM_PC24        printk
00000740 R_ARM_PC24        .text.init.refok

오호... 내가 원하는 724 의 위치 이전에 trap_yyc 가 있고, 
그 뒤에 printk 가 하나 있고 그 다음 printk 가 나오는 사이로군...
그렇다면 첫번째 printk 거나 그 사이에 어딘가 문제를 일으킨다.

다시 소스를 보자. 
    /* Do the rest non-__init'ed, we're now alive */
    trap_yyc();
    swmem = VMALLOC_START;
    printk("virt to phys 0x%x\n", virt_to_phys(swmem));
    *swmem = 0x12345678;
    printk("--------------------------- 0x%x\n", *swmem);
    printk("--------------------------- 0x%p\n", swmem);
    rest_init();

trap_yyc 소스뒤에 printk 가 하나 있고 두번째 printk 사이에 
잘못된 포인터에 데이타를 넣는 것이 보인다.

724: e5823000 str r3, [r2]
를 이전에 찾았었다.
알다시피 의미는 [r2] 에 r3를 넣으라는 것이었다.

이제 oops 메세지 분석으로 문제가 되는 위치를 찾았다.


참고... 절대 실제로  항상 이렇게 찾을 거라 생각하지 말자.
이건 상당히 운이 좋은 케이스에 불과하기 때문이다.
그럼에도 불구하고 생각보다 oops 메세지는 우리에게 많은 정보를 주는 것도 사실이다.