-
반응형
1. 디버깅이란?
디버깅은 'debug'와 '-ing'의 합성어로 버그를 잡는 과정을 뜻한다. 누군가 '디버깅하고 있다'라고 말하면 '버그를 수정하고 있다'라고 볼 수 있다.
디버깅은 문제 해결 능력의 지름길
- 커널 디버깅을 잘해야 문제 해결 능력을 키울 수 있는 이유는 무엇일까?
대부분의 임베디드 및 BSP 개발자들은 커널 디버깅 능력을 키우기 위해 노력한다. 그렇다면 커널 디버깅 능력을 업그레이드하려는 이유는 무엇일까, 커널 디버깅은 문제 해결 능력 그 자체이기 때문이다. 임베디드 리눅스는 활용 분야가 다양하지만 대표적인 문제를 추리면 다음과 같다.
- 부팅 도중 커널 크래시 발생
- 인터럽트 핸들러를 설정했는데 인터럽트 핸들러가 호출되지 않음
- 시스템 응답 속도가 매우 느려짐
- 파일 복사가 안 됨
문제를 해결하기 위해서는 어떤 과정을 거칠까, 실전 개발에서는 문제가 발생했을 때 확보한 커널 로그와 메모리 덤프로 문제 원인을 분석할 수 있다. 커널 로그와 메모리 덤프를 정확히 문제 원인 분석으로 이어지고, 이를 통해 ' 문제를 해결' 하게 된다. 그래서 커널 디버깅은 문제 해결 능력 그 자체라고 할 수 있다. 커널 로그나 메모리 덤프로 함수나 자료구조를 분석할 때는 자신이 작성한 코드보다 다른 개발자가 작성한 커널 코드를 만날 가능성이 매우 높다. 디바이스 드라이버는 커널 함수로 구성돼 있고, 커널 함수는 각 서브시스템을 담당한 개발자가 작성한 코드이기 때문이다. 임베디드 BSP 개발 과정에서 디버깅할 때 우리가 작성한 코드만 분석하면 좋겠지만 난이도가 높은 문제일수록 커널 내부 함수를 분석할 가능성이 높아진다. 리눅스 커널을 구성하는 서브시스템이 정상적으로 동작할 때는 다음 내용을 파악할 필요가 있다.
- 함수가 실행될 때 변경되는 자료구조
- 함수가 실행되는 빈도와 실행 시각
- 실행 중인 코드를 어떤 프로세스가 실행하는지 확인
프로그램이 정상적으로 동작할 때의 함수 호출 흐름과 자료구조를 알고 있어야 오류나 버그가 발생했을 때 무엇이 문제인지 식별할 수 있기 때문이다. 사실 개발 도중에 커널 로그에서 에러 메시지를 만날 경우가 많다. 그런데 커널 로그는 커널이나 디바이스 드라이버가 정상 동작을 한다는 정보를 출력하기도 하고 시스템에 심각한 오류가 있다는 사실을 알려주기도 한다. 커널 로그에서 오류 정보를 바로 식별할 수 있으면 좋겠지만 '정상 동작'을 할 때의 메시지와 '오류 정보' 메시지가 뒤섞여 있는 경우도 많다. 그래서 로그에서 오류 정보를 잘 알아채려면 ' 정상 동작'시 출력되는 로그의 패턴을 미리 잘 숙지해야 합니다.
커널 디버깅 따라해보기: 커널 로그 분석
임베디드 리눅스 개발을 하다가 문제가 생기면 대부분 커널 로그를 본다. 그런데 디바이스 드라이버 코드에 오류가 있으면 우리가 작성한 드라이버 코드에서 오류 메시지를 출력하지 않는다. 대부분의 경우 디바이스 드라이버가 호출한 커널 함수 내부에서 에러 메시지를 출력한다.
WARNING: at kenel/irq/manage.c:225 __enable_irq+0x3b/0x57()
__enable_irq() 함수에서 뭔가 오류 조건을 검출한 것으로 보인다. 에러 메시지를 보면서 어느 코드에서 오류를 유발했는지 분석해 보자.
첫째, 오류 메시지를 커널 어느 코드에서 출력했는지 확인해야 한다.
먼저 콜 스택을 어느 코드에서 출력했는지 살펴봐야 한다. 인터럽트 디스크립터의 자료구조인 irq_desc 구조체의 depth 필드는 인터럽트를 활성화하면 0, 비활성화하면 1을 설정한다. 만약 4번째 인터럽트를 활성화하면 4번째 인터럽트 디스크립터의 depth 필드를 0으로 설정한다. 그런데 6번째 줄 코드는 인터럽트를 2번 활성화했을 때 실행된다. 이미 인터럽트를 활성화했으면 depth 필드가 0인데 다시 활성화했으니 경고 메시지와 함께 콜 스택을 출력한다.
둘째, 소스코드에서 에러 메시지를 출력한 이유를 살펴봐야한다.
"IRQ 4"라는 메시지가 보인다. 정리하면 4번 인터럽트를 두 번 활성화했으므로 해당 오류 메시지를 출력한 것이다. 위와 같은 에러 메시지는 enable_irq() 함수에서 출력한다. 그렇다면 enable_irq() 함수 내부에 논리적 오류가 있어서 위와 같은 에러메시지를 출력하는 것이 아니다. enable_irq() 함수 내 코드에 오류가 있다기보다는 enable_irq() 함수를 호출한 드라이버 코드에 무엇인가 오류가 있을 가능성이 높다. 그래서 어떤 함수에서 4번 인터럽트를 2번 연속으로 활성화하는지 점검해야 한다. 만약 에러 메시지가 커널 내부 함수인 __enable_irq() 함수에서 출력한다면? 만약 커널이 정상 동작할 때 irq_desc 구조체의 depth 필드를 알고 있으면 이제까지 분석한 에러 메시지의 의미를 바로 알 수 있다. 하지만 그게 아니라면, 관련 드라이버 코드를 상세히 분석해 힌트를 얻을 수 있다. 커널 에러 메시지를 보면 scsknfdrvr 드라이버 모듈이 보인다. 만약 enable_irq() 함수를 호출한 코드가 scsknfdrvr 드라이버 코드에 있다면 관련 코드를 분석한 수 논리적인 오류가 있는지 점검해야 한다.
셋째, 필요에 따라 디버깅 코드를 작성해 다시 문제가 발생했을 때 추가 커널 로그 확보를 시도한다.
패치 코드를 입력해 문제 현상을 재현한 후 커널 로그를 받아보면 커널 로그가 누가 4번 인터럽트를 2번 활성화했는지 알려줄 것이다. 패치코드를 입력한 다음 커널을 빌드해서 커널 이미지를 설치한 후 다시 재현해야 한다. 패치코드를 설명하자면, __disable_irq() 함수와 __enable_irq() 함수의 2번째 인자는 인터럽트 번호를 나타내는 irq이다. 인터럽트 번호가 4일 때 콜 스택을 호출한다. 패치 코드를 빌드해서 시스템에 반영한 후 문제를 다시 재현하면 4번 인터럽트를 활성화 혹은 비활성화할 때 콜 스택을 커널 로그로 출력한다. 따라서 어떤 코드에서 4번 인터럽트를 연속으로 활성화하는지 알 수 있다. 리눅스 커널의 고수 중 디버깅을 잘 못하는 개발자는 한 번도 만나본 적이 없다. 디버깅 능력은 문제 해결 능력 그 자체이므로 평소에 디버깅을 꾸준히 해서 리눅스 커널을 익힐 필요가 있다.
반응형