Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

37120
1703
402252


[Linux][Kernel] BUG 매크로 부록

BUG 매크로는 보통 소프트웨어적으로 심각한 오류 상태라 더는 실행할 수 없다고 판단할 때 호출 합니다. 혹시 소프트웨어 공학에서 ASSERT란 단어 들어 보신 적 있나요? 보통 포인터가 NULL일 때 ASSERT를 호출하죠. 예제 코드는 다음과 같습니다.
void trace_kernel_process_name(void *param)
{
   if( !param )
      ASSERT(1);

  printk(“ process name: %s pid: %d \n”, current->comm, current->pid)
}
 
리눅스 커널에서는 ASSERT 대신 BUG()를 씁니다. 그리고 panic이란 함수도 비슷한 역할을 수행합니다.
BUG나 panic이란 함수를 호출하면 커널 크래시가 발생하는 것이지요. 커널 크래시가 발생한 시스템 정보를 출력하고 리셋되거나 그 화면에서 멈춰있죠.
void trace_kernel_process_name(void *param)
{
   if( !param )
      BUG();

  printk(“ process name: %s pid: %d \n”, current->comm, current->pid)
}
 
BUG와 더불어 BUG_ON매크로도 많이 쓰는데요, BUG_ON 매크로 내의 조건이 1이면 BUG를 호출하는 것입니다. BUG_ON(조건) 이렇게 표현할 수도 있겠네요.

리눅스 커널 로그를 검색하면 BUG()와 BUG_ON(1)과 같은 코드가 지뢰같이 엄청 많이 깔려 있어요. 다른 용어로 "버그 혹은 패닉 맞았다" 이렇게 표현을 합니다. 

그럼 커널 크래시가 발생했을 때 검은색 배경에 흰색 메시지를 출력하는데요. 이 때 출력하는 에러 메시지를 하나 소개할게요. 다음은 BUG가 실행됐을 때 출력되는 에러 메시지입니다.
[22.423007][1] ------------[ cut here ]------------
[22.423027][1] kernel BUG at /home001/austindh.kim/src/kernel/mm/slub.c:3485!
[22.423047][1] Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM
[22.423064][0] Modules linked in: texfat(PO)
[22.423093][1] CPU: 1 PID: 1 Comm: init  
[22.423110][1] task: e3688040 ti: e3682000 task.ti: e3682000
[22.423133][1] PC is at kfree+0x13c/0x280
[22.423152][1] LR is at gs_free_req+0x14/0x2c
[22.423171][1] pc : [<c0220a10>]    lr : [<c06bb1dc>]    psr: 40070093
[22.423171][1] sp : e3683e38  ip : 00000000  fp : ae30b638
[22.423193][1] r10: 00000000  r9 : df615540  r8 : 00000200
[22.423209][1] r7 : 00000100  r6 : 6b6b6b6b  r5 : df63d840  r4 : e7953968
[22.423224][1] r3 : 40000000  r2 : e7953968  r1 : 40000000  r0 : e3bb9000
[22.423240][1] Flags: nZcv  IRQs off  FIQs on  Mode SVC_32  ISA ARM  Segment none
[22.423255][1] Control: 10c0383d  Table: 2e0cc06a  DAC: 00000051
[22.423270][1] Process init (pid: 1, stack limit = 0xe3682210)
[22.423285][1] Stack: (0xe3683e38 to 0xe3684000)

그럼 각 에러 메시지의 의미를 간단히 알아볼까요?

[1]: 커널 크래시가 발생한 코드 정보입니다. 친절하게 slub.c 파일 3485라인에서 커널 크래시가 발생했다고 알려주네요.
[22.423027][1] kernel BUG at /home001/austindh.kim/src/kernel/mm/slub.c:3485!

[2]: 모듈 타입으로 로딩한 디바이스 드라이버를 출력합니다.
[22.423064][0] Modules linked in: texfat(PO)
 
[3]: 커널 크래시가 발생했을 때 CPU 번호는 1이고 프로세스 이름은 init pid는 1입니다. 그리고 해당 프로세스의 태스크 디스크립터는 e3688040라고 알려줍니다.
[22.423093][1] CPU: 1 PID: 1 Comm: init  
[22.423110][1] task: e3688040 ti: e3682000 task.ti: e3682000
 
[4]: 커널 크래시가 발생했을 때 레지스터 정보를 출력합니다.
[22.423133][1] PC is at kfree+0x13c/0x280
[22.423152][1] LR is at gs_free_req+0x14/0x2c
[22.423171][1] pc : [<c0220a10>]    lr : [<c06bb1dc>]    psr: 40070093
[22.423171][1] sp : e3683e38  ip : 00000000  fp : ae30b638
[22.423193][1] r10: 00000000  r9 : df615540  r8 : 00000200
[22.423209][1] r7 : 00000100  r6 : 6b6b6b6b  r5 : df63d840  r4 : e7953968
[22.423224][1] r3 : 40000000  r2 : e7953968  r1 : 40000000  r0 : e3bb9000
 
위 정보를 바탕으로 커널 slub.c 파일의 3485라인 코드를 보니 역시 BUG_ON이 실행됐습니다. 코드를 잠깐 보면 해제하려는 메모리를 페이지 디스크립터로 변환했는데 페이지 디스크립터 정보에 문제가 있어 발생한 커널 크래시임을 알 수 있습니다.
3473 void kfree(const void *x)
3474{
3475 struct page *page;
3476 void *object = (void *)x;
3477
3478 trace_kfree(_RET_IP_, x);
3479
3480 if (unlikely(ZERO_OR_NULL_PTR(x)))
3481 return;
3482
3483 page = virt_to_head_page(x);
3484 if (unlikely(!PageSlab(page))) {
3485 BUG_ON(!PageCompound(page));
 
그럼 BUG 매크로 코드를 분석할 시간입니다. 다음 코드를 보시면 아시겠지만 BUG란 매크로는 리눅스 커널보다는 ARM 프로세서를 제어하는 어셈블리 코드로 구성돼 있습니다. 
[arch/arm/include/asm/bug.h]
#define BUG() _BUG(__FILE__, __LINE__, BUG_INSTR_VALUE)

#define __BUG(__file, __line, __value) \
do { \
asm volatile("1:\t" BUG_INSTR(__value) "\n"  \
".pushsection .rodata.str, \"aMS\", %progbits, 1\n" \
"2:\t.asciz " #__file "\n"  \
".popsection\n"  \
".pushsection __bug_table,\"a\"\n" \
".align 2\n" \
"\t.hword " #__line ", 0\n" \
".popsection"); \
unreachable(); \
} while (0)

#define BUG_INSTR_VALUE 0xe7f001f2

BUG 매크로는 __BUG 매크로로 치환되며 __BUG 매크로에서는 0xe7f001f2란 기계어를 실행합니다. 0xe7f001f2란 ARM 프로세스가 식별할 수 없는 코드를 실행하여 ARM 프로세스의 undefined instruction 익셉션을 유발하는 동작이죠.

리눅스 커널 코드에서 BUG, BUG_ON을 보면 심각한 시스템 오류가 있는 상태일 때 실행하는 코드이고 위와 같은 코드 흐름으로 undefined instruction 익셉션으로 커널 크래시를 유발한다는 점을 기억하세요.

여기서 BUG 매크로를 호출하는 코드를 지우면 커널 크래시가 발생 안 하고 계속 커널은 실행이 될 텐데 굳이 BUG 매크로를 추가한 이유가 뭔지 모르겠다고 생각할 수도 있습니다. 하지만 BUG 매크로가 없으면 커널은 이후 다양한 증상으로 오동작(락업, 랜덤 커널 크래시)할 가능성이 매우 높기 때문에 오히려 문제 원인을 알아내기가 더 힘듭니다. 그러니 BUG 매크로가 실행되어 커널 크래시가 발생하면 어떤 조건으로 BUG가 실행됐는지 차근차근 분석하며 문제 원인을 잡는 것이 중요합니다.

"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
그리고 혹시 궁금점이 있으면 댓글로 질문 남겨주세요. 상세한 답글 올려드리겠습니다!"


# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2





핑백

덧글

댓글 입력 영역