IT/Linux Kernel

디버깅을 통해 배우는 리눅스 커널의 구조와 원리 1, 도서 공부하기 8 - debugsfs 드라이버 코드

변화의 물결1 2025. 3. 9. 15:16

 

 

 

 안녕하세요.

 

 지금까지 디버깅방법을 알아봤습니다. 그런데 커널 소스를 수정하는 것에 낯설게 느껴지고 수정하다가 부팅이 안되면 어떡하지 약간의 불안감도 있을 것입니다.

 그래서 저도 코드를 작성할 때 한번 더 확인하고, 기존의 작업했던 내용은 SD Card 내용을 전체 백업을 하였습니다.

 

 사실 커널 코드를 잘못 입력하거나 실수하면 시스템이 오동작할 수 있습니다. 즉 부팅이 안될 수 있습니다. 그래서 책에서는 이것을 조금 방지하고자 debugfs 드라이버 코드를 알려줍니다.

 


 

1. debugfs 드라이버 원리

 

 전역 변수처럼 변수를 셀에서 설정 값을 주었을 때만 코드가 작동하고, 재부팅하거나 설정값을 초기화하면 원래코드로 동작하는 간단한 원리입니다.

 

 그러나 커널 코드다 보니, 드라이버 파일로 만들어 작동하게 해 놓았습니다.

 

 

2. rpi_debugfs 소스코드

 

 전체코드는 길이가 있어서 첨부파일로 추가해 놓았습니다.

 혹은 wikibook 사이트에서 책을 검색해 예제 파일 받아서 사용해도 됩니다.

 파일을 다음과 같은 위치에 복사 혹은 생성해서 소스코드를 입력합니다.

 

linux# vim drivers/soc/bcm/rpi_debugfs.c

 

 

3. 컴파일을 위한 Makefile

 rpi_debugfs.c를 컴파일하기 위해 drivers/soc/bcm/ 디렉터리에 있는 Makefile을 수정합니다.

 

 linux# vim drivers/soc/bcm/Makefile

 

 

 

 

아래와 같이 한 줄 추가합니다.

  obj-y += rpi_debugfs.o

 

 커널을 빌드하면 컴파일되고 커널이미지에 포함됩니다. 이미지를 설치한 다음 재부팅합니다.

 빌드와 설치 방법은 이전 글에서 만들었던 셀 스크립트를 그대로 사용하면 됩니다.

 

linux# sh ../build_rpi_kernel.sh
linux# sh ../install_rpi_kernel_img.sh
linux# reboot

 

 

 빌드 중에 아래와 같은 에러를 만날 수도 있습니다. 이것은 이전 빌드에서 생성된 파일이 남아 있으면, 새 빌드를 할 때 충돌이 발생할 수 있으며 특히, 이전 설정과 충돌하거나, 소스 코드가 변경되었는데 반영되지 않을 경우 문제가 생길 수 있는 부분입니다.

 

 make mrproper 명령어를 사용해서 커널 빌드를 처음부터 다시 할 수 있는 상태로 되돌려주는 것이 필요합니다. 그러나 만약의 상황을 대비해서 필요한 경우 .config 파일 백업 필요할 수 있습니다.

 

scripts/kconfig/conf  --syncconfig Kconfig

  GEN     ./Makefile

  Using /home/pi/rpi_kernel_src/linux as source for kernel

  /home/pi/rpi_kernel_src/linux is not clean, please run 'make mrproper'

 

 

 

4. rpi_debugfs 작동 확인 방법

 

 재부팅했다면 먼저 rpi_debugfs 드라이버가 정상적으로 설치되었는지 확인합니다. rpi_debug 디렉터리와 val 파일이 있는 것을 알 수 있습니다.

 

linux# ls -al /sys/kernel/debug/rpi_debug/

 

 

 

 소스코드가 작동하는지 값을 확인합니다. val 파일을 읽으면 rpi_kernel_debug_stat_get()이 실행됩니다.

 rpi_kernel_debug_stat 전역 변수를 0x1000으로 설정했기 때문에 4096으로 출력됩니다. 0x1000 16진수 이므로 10진수로 출력하면 val는 4096이 됩니다.

 

linux# cat /sys/kernel/debug/rpi_debug/val

 

 

 

[TIP] printk 로깅레벨 설정

 

 val 값에 printk 로그를 넣었는데, dmesg를 해보면 로그가 나오지 않을 수 있습니다. 라즈베리 파이의 printk 로깅레벨 때문입니다.

 default 레벨은 4인데 현재 설정된 것이 3단계이기 때문에 설정 변경이 필요합니다.  조금 자세한 설명은 이전 글을 참조하면 도움이 될 것입니다.

 

"공부하기 6 - printk와 dump_stack" 글에서 printk 로깅레벨 내용을 활용하면 출력한 메시지를 확인할 수 있습니다.

 

 

디버깅을 통해 배우는 리눅스 커널의 구조와 원리 1, 도서 공부하기 6 - printk와 dump_stack 함수

안녕하세요.  이번 시간은 printk()와 dump_stack()함수에 대해 학습하고 간단하게 디버깅 하는 방법을 알아 보겠습니다.  1. printk 함수  이미 C언어를 접해본 분이라면 printf 함수에 대해 잘 알고

remnant24c1.tistory.com

 

 

 linux# cat /proc/sys/kernel/printk
 linux# echo '4 4 1 4' > /proc/sys/kernel/printk
 linux# cat /proc/sys/kernel/printk
 linux# cat /sys/kernel/debug/rpi_debug/val
 linux# dmesg | grep ===

 

 

 

5. rpi_debugfs 소스코드 확인

 

static int rpi_kernel_debug_stat_get(void *data, u64 *val)
static int rpi_kernel_debug_stat_set(void *data, u64 val)

 

 

 get경우, rpi_debug/val 메모리 주소는 rpi_kernel_debug_stat_get() 함수의 *val 두 번째 포인터 인자로 전달됩니다. 이를 이용에 전역변수 raspbian_debug_state에 *val포인터가 가리키고 있는 값을 전달합니다.

 

 set경우, rpi_debug/val에 저장한 값을 rpi_kernel_debug_stat_set() 인자인 val에 전달합니다. 그래서 echo 명령어로 정수값을 넣으면 전역변수인 raspbian_debug_state 값이 변경됩니다.

 

 

6. rpi_debugfs 활용한 커널 코드 변경

 

 초기화되는 전역변수 값을 사용해서 부담 없이 커널 소스코드를 수정하고 테스트할 수 있습니다.

 irq/handle.c 파일에 가서 패치 코드를 추가해 줍니다.

 

 extern 변수를 __handle_irq_event_percpu() 함수 위에 정의해주고 있습니다. 이것은 변수가 다른 파일에 정의되어 있음을 컴파일러에게 알리고, 여러 소스 파일에서 변수를 공유할 수 있게 해주는 것입니다.

 

extern uint32_t raspbian_debug_state;

 

 

 책에서 825를 넣었는데, 쉽게 말해서 1로 생각하고 메시지 출력 활성화가 되었으면, 디버깅 메시지를 출력하라고 생각하면 어떨까 합니다.

   

 소스코드에 보면 새로운 출력함수 trace_printk가 있습니다.

 이것은 dmesg에 출력하는 것이 아닌 /sys/kernel/debug/tracing/trace에 출력합니다.

 

 상수 825를 if 조건문의 앞에 적은 것은 raspbian_debug_state = 825처럼 코딩 실수를 방지하기 위한 하나 방법이라고 생각하면 됩니다.

 

 linux# vim kernel/irq/handle.c

 

+ extern uint32_t raspbian_debug_state;

+ if ( 825 == raspbian_debug_state ) {
+                       void *irq_handler = (void*)action->handler;
+                       trace_printk("[+] irq_num:[%d] handler: %pS caller:(%pS) \n",
+                                                               irq, irq_handler, (void*)__builtin_return_address(0));
+               }

 

 

 

 커널소스를 수정했다면 빌드와 설치를 한 후 ftrace로 로그를 확인할 수 있습니다.

 ftrace를 실행하기 전에 825로 활성화하고 ftrace를 설정해서 디버깅을 하면 됩니다.

 

linux#  echo 825 > /sys/kernel/debug/rpi_debug/val

 

 

만약 코드에 문제가 있어 오류가 생겨 재부팅하거나 되면 val 값이 4096으로 바뀌면서 if문 아래의 코드는 실행되지 않습니다. 그렇기 때문에 원래 상태에서 코드를 확인 작업을 하면 됩니다.

  

 

7. rpi_debugfs 수정 가능 사항

 

 책에서 사용한 방법을 조금 수정해서 볼 수 있겠다 생각이 들어 추가해 보았습니다.

 

 비트연산처럼 만들어서 넣어서 원하는 몇 가지 레벨로 나눠 필요에 따라 출력할 수 있는 것처럼 할 수 있겠다고 생각되었습니다. 사실 상수 값 혹은 헤더 등으로도 다르게 할 수 있지만, 작은 변화를 주어 사용 가능하겠다고 생각했습니다.

 

 아래와 같이 비트 연산이 가능하도록 하고 이것은 사용자가 원하는 레벨을 정해서 로그 출력 여부를 설정할 수 있게 단계를 만듭니다.

 0b100=4(로그 종류2),  0b10=2(로그 종류1),  0b1=1(로그 종류0), 0b0 = 0(비활성화)

 

위의 handle.c 코드에서 예를 들자면 종류2를 출력하게 하는 if문을 수정해 보겠습니다. if문에서 단계에 맞게 비트연산(Shift와 &연산)으로 비교합니다.

+ if ( (raspbian_debug_state>>2) & 1 ) {
+                       void *irq_handler = (void*)action->handler;
+                       trace_printk("[+] irq_num:[%d] handler: %pS caller:(%pS) \n",
+                                                               irq, irq_handler, (void*)__builtin_return_address(0));
+               }

 

 

 로그 종류 1을 출력하는 로그를 만든다면 아래와 같이 가능할 것입니다.

 if ( (raspbian_debug_state>>1) & 1 ) {

 

 

 위와 같이 수정하면 장점은 원하는 로그 종류 메시지만 출력가능하고 필요에 따라 전체 메시지도 출력할 수 있다는 것입니다. 단점은 약간 헷갈릴 수 있다는 것과 이진수에 대해서 알고 있어야 한다는 것입니다.

 echo에 상수 1,2,4 조합으로도 val값을 변경할 수 있을 것입니다. 

linux # echo 4 > /sys/kernel/debug/rpi_debug/val

 

 

 그러나 셀(Shell)에서 2진수를 입력하려고 하려면 조금 복잡해질 수도 있어서 로그 종류2만 출력하고 싶을 때 아래와 같이 해야 합니다.

 linux # echo $((2#100)) > /sys/kernel/debug/rpi_debug/val

 

 

 

감사합니다.

 

 

 

<참고 자료>

1. [도서] 디버깅을 통해 배우는 리눅스 커널의 구조와 원리, wikibook

 

Makefile
0.00MB
rpi_debugfs.c
0.00MB

반응형