Skip to content

디버깅

  • 커널 디버깅에는 세 가지 요소가 필요하다.
    • 버그가 처음 등장한 커널 버전을 파악할 수 있는가?
    • 버그를 재현할 수 있는가?
    • 커널 코드에 관한 지식을 갖추고 있는가?
  • 버그를 명확하게 정의하고 안정적으로 재현할 수 있다면 성공적인 디버깅에 절반 이상 달성한 것이다. ​

출력을 이용한 디버깅

printk()와 웁스(oops)

  • 커널 출력 함수인 printk()는 C 라이브러리의 printf() 함수와 거의 동일하다.
    • 주요 차이점은 로그수준(Loglevel)을 지정할 수 있다는 점이다.
    • 가장 낮은 수준인 KERN_DEBUG 부터 가장 높은 수준인 KERN_EMERG 까지 7단계로 설정할 수 있다.
  • printk()의 장점은 커널의 어느 곳에서도 언제든지 호출할 수 있다는 점이다.
    • 인터럽트 컨텍스트, 프로세스 컨텍스트 모두 호출 가능하다.
    • 락을 소유하든 소유하지 않든 호출 가능하다.
    • 어느 프로세서에서도 사용할 수 있다.
  • printk()의 단점은 커널 부팅 과정에서 콘솔이 초기화되기 전에는 사용할 수 없다는 점이다.
    • 이식성을 포기하고 printk() 함수 변종인 early_printk() 함수를 사용하는 해결책이 있다.
  • printk()를 사용할 때 주의할 점은, 너무 빈번하게 호출되는 커널 함수에 사용하면 시스템이 뻗어버린다는 것이다.
    • 출력 폭주를 막기 위해 두 가지 방법을 사용할 수 있다.
    • 첫 번째 방법: jiffies를 이용해서 수초마다 한 번씩만 출력한다.
    • 두 번째 방법: printk_ratelimit() 함수를 이용해서 n초에 한 번씩만 출력한다.
  • 커널 메시지는 크기가 LOG_BUF_LEN인 원형 큐(버퍼)에 저장된다.
    • 크기는 CONFIG_LOG_BUF_SHIFT 옵션을 통해 설정할 수 있으며 기본값은 16KB다.
    • 원형 큐이므로 가득찼을 때 가장 오래된 메시지를 덮어쓴다.
    • 표준 리눅스 시스템은 사용자 공간의 ‘klogd’ 데몬이 로그 버퍼에서 커널 메시지를 꺼내고 ‘syslogd’ 데몬을 거쳐서 시스템 로그 파일(기본: /var/log/messages)에 기록한다.
  • 웁스(oops)는 커널이 사용자에게 무언가 나쁜 일이 일어났다는 것을 알려주는 방법이다.
    • 커널은 심각한 오류가 났을 때 사용자 프로세스처럼 책임감 없이 프로세스를 종료할 수 없다.
      • 커널은 웁스가 발생했을 때 최대한 실행을 계속 하려고 시도한다.
      • 더 이상 커널 실행이 불가능하다고 판단할 경우 ‘패닉 상태’가 된다.
    • 커널은 콘솔 오류 메시지 + 레지스터 내용물 + 콜스택 역추적 정보 등을 출력한다.

버그 확인과 정보 추출

  • BUG(), BUG_ON(): 웁스를 발생한다.
    • 의도적으로 웁스를 발생시키는 시스템콜이다.
    • 이 함수는 발생해서는 안 되는 상황에 대한 조건문을 확인할 때 사용한다.
    • if (...) BUG()BUG_ON(...) 는 동일한 구문이다. 그래서 대부분 커널 개발자는 BUG_ON()을 사용하는 것을 더 좋아하며 조건문에 unlikely()를 함께 사용(BUG_ON(unlikely());)한다.
  • panic() : 패닉을 발생한다.
    • 좀 더 치명적인 오류의 경우에는 웁스가 아닌 패닉을 발생시킨다.
    • 이 함수를 호출하면 오류 메시지를 출력하고 커널을 중지시킨다.
  • dump_stack() : 디버깅을 위한 스택 역추적 정보를 출력한다.

참고