Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

67112
549
416207


[리눅스커널] 커널 디버깅과 코드 학습: 커널 디버깅용 Debugfs 드라이버 코드 3. 커널 디버깅과 코드 학습

편하게 커널 코드를 수정할 수 있는 디버깅용 Debugfs 드라이버 코드 소개

이전 절까지 리눅스 커널 코드를 수정해서 ftrace나 커널 로그를 출력하는 방법을 소개했습니다.

리눅스 드라이버를 처음 접하거나 커널 코드에 익숙하지 않은 분은 커널 로그를 수정해 커널 빌드를 하는 것은 굉장히 낯선 일입니다. 왜나면 코드를 잘못 입력해 커널이 오동작하면 어떨지 걱정이 앞설 수 있기 때문입니다. 그런데 실수로 커널 코드를 잘못 입력하면 라즈베리파이가 부팅을 못할 수도 있습니다.

필자도 실수로 커널 코드를 잘못 수정한 후 컴파일한 적이 있습니다. 결과 라즈베리파이가 부팅이 안됐습니다. 처음 이런 상황을 겪으니 조금 당황스러웠습니다.

임베디드 리눅스 실전 개발자는 코드를 수정한 다음 커널 부팅이 안 되는 상황을 종종 겪습니다. 하지만 많이 당황하지는 않습니다. 실전 개발에서는 각자 개발 환경에 맞게 응급 다운로드로 백업한 커널 이미지를 내려 받을 수 있기 때문입니다. 하지만 라즈베리파이로 리눅스를 처음 접하는 분 입장에선 다릅니다. 커널 코드를 수정한 라즈베리파이가 부팅을 못하면 많이 당황하게 됩니다. 물론 라즈베리파이를 다시 설치하면 되지만 시간이 오래 걸릴 수 있습니다.

그래서 이번 소절에 수정한 커널 코드가 특정 조건에서만 동작할 수 있는 간단한 debugfs 드라이버 코드를 소개합니다. 이번 소절에 소개하는 코드를 활용하면 훨씬 편한 마음으로 커널 로그를 수정 후 빌드할 수 있습니다.

rpi_debugfs 소스 코드 소개
우선 코드 구현부는 다음과 같습니다.
/* (C) 2019 Austin Kim <austindh.kim@gmail>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Author:
 * Austin Kim <austindh.kim@gmail>
 */ 
 
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/memblock.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/cpu.h>
#include <linux/delay.h>
#include <asm/setup.h>
#include <linux/input.h>
#include <linux/debugfs.h>

uint32_t raspbian_debug_state = 0x1000;
static struct dentry *raspbian_kernel_debug_debugfs_iface_root;

static int raspbian_kernel_debug_iface_debugfs_get(void *data, u64 *val)
{
    printk("===[%s][L:%d][val:%d]===\n", __func__, __LINE__, raspbian_debug_state);
    *val = raspbian_debug_state;
    
    printk("[KERNEL]===[%s][L:%d]==\n", __func__, __LINE__);

    return 0;
}

static int raspbian_kernel_debug_iface_debugfs_set(void *data, u64 val)
{
raspbian_debug_state = (uint32_t)val;

printk("[raspbian] [%s][L:%d], raspbian_debug_state[%lu],value[%lu]===\n", 
__func__, __LINE__, (long unsigned int)raspbian_debug_state, (long unsigned int)val);
     return 0;
}


DEFINE_SIMPLE_ATTRIBUTE(raspbian_kernel_debug_iface_debugfs_fops, raspbian_kernel_debug_iface_debugfs_get, raspbian_kernel_debug_iface_debugfs_set, "%llu\n");

static struct platform_driver kernel_bsp_debug_driver = {
    .driver     = {
        .owner  = THIS_MODULE,
        .name   = "raspbian_debug_iface",
    },
};

static int __init raspbian_kernel_debug_init(void)
{
    printk("===[%s][L:%d]===\n", __func__, __LINE__);

    raspbian_kernel_debug_debugfs_iface_root = debugfs_create_dir("raspbian_debug_iface", NULL);
    debugfs_create_file("val", S_IRUGO, raspbian_kernel_debug_debugfs_iface_root, NULL, &raspbian_kernel_debug_iface_debugfs_fops);
     
    return platform_driver_register(&kernel_bsp_debug_driver);
}

late_initcall(raspbian_kernel_debug_init);

MODULE_DESCRIPTION("raspberrypi debug interface driver");
MODULE_AUTHOR("Austin Kim <austindh.kim@gmail.com>");
MODULE_LICENSE("GPL");

위 파일을 입력한 다음 drivers/soc/bcm 경로로 이동해서 drivers/soc/bcm/rpi_debugfs.c 이란 파일이름으로 저장합니다.

이 파일이 컴파일이 되야 하니 다음과 같이 Make 파일을 수정합니다.
diff --git a/drivers/soc/bcm/Makefile b/drivers/soc/bcm/Makefile
index dc4fced..4e951cc 100644
--- a/drivers/soc/bcm/Makefile
+++ b/drivers/soc/bcm/Makefile
@@ -1,2 +1,2 @@
-obj-$(CONFIG_RASPBERRYPI_POWER)        += raspberrypi-power.o
+obj-$(CONFIG_RASPBERRYPI_POWER)        += raspberrypi-power.o rpi_debugfs.o
 obj-$(CONFIG_SOC_BRCMSTB)      += brcmstb/

위와 같이 커널 빌드를 하면 drivers/soc/bcm/raspbian_kernel_debugfs.c 파일이 컴파일되어 커널 이미지에 포함됩니다.

라즈베리파이에 리눅스 커널 이미지를 설치한 다음에 리부팅을 합니다.

RPi_debugfs 동작 확인
이번에 위 코드에 제대로 커널 이미지에 포함됐는지 확인합시다. 이를 위해 다음 명령어를 입력합시다.
austin:/ $ ls /sys/kernel/debug/raspbian_debug_iface/
val

/sys/kernel/debug 폴더에 raspbian_debug_iface 하위 폴더가 생겼습니다.

이번에는 /sys/kernel/debug/raspbian_debug_iface/val 값을 확인합시다.

소스 코드에서 raspbian_debug_state 이란 전역 변수를 0x1000으로 초기화했으니, val 값은 4096입니다.
austin:/ $ cat /sys/kernel/debug/raspbian_debug_iface/val
4096

위와 같이 cat 명령어를 입력해서 "/sys/kernel/debug/raspbian_debug_iface/val" 파일을 읽으면, raspbian_kernel_debug_iface_debugfs_get() 함수가 호출됩니다.

이번에 raspbian_kernel_debug_iface_debugfs_get() 함수가 어떻게 실행되는지 알아봅시다.
1 uint32_t raspbian_debug_state = 0x1000;
2 static struct dentry *raspbian_kernel_debug_debugfs_iface_root;
3
4 static int raspbian_kernel_debug_iface_debugfs_get(void *data, u64 *val)
5 {
6    printk("===[%s][L:%d][val:%d]===\n", __func__, __LINE__, raspbian_debug_state);
7    *val = raspbian_debug_state;
8    
9    printk("[KERNEL]===[%s][L:%d]==\n", __func__, __LINE__);
10
11    return 0;
12 }

"/sys/kernel/debug/rpi_debug/val" 파일 메모리 주소는 raspbian_kernel_debug_iface_debugfs_get() 함수 *val 이란 두 번째 포인터 인자로 전달됩니다. 

이 포인터 인자에 raspbian_debug_state 변수 값을 저장하는 코드입니다.
7    *val = raspbian_debug_state;

RPi_debugfs 로 전역 변수 바꿔보기
이번에는 /sys/kernel/debug/raspbian_debug_iface/val 을 825로 변경합시다.
root@raspberrypi:/proc # echo 825 > /sys/kernel/debug/rpi_debug/val
root@raspberrypi:/proc # cat /sys/kernel/debug/rpi_debug/val
825

위와 같이 명령어를 입력하면 다음 raspbian_kernel_debug_iface_debugfs_set() 함수가 호출됩니다.
1 static int raspbian_kernel_debug_iface_debugfs_set(void *data, u64 val)
2 {
3 raspbian_debug_state = (uint32_t)val;
4
5 printk("[raspbian] [%s][L:%d], raspbian_debug_state[%lu],value[%lu]===\n", 
__func__, __LINE__, (long unsigned int)raspbian_debug_state, (long unsigned int)val);
6     return 0;
7 }

raspbian_kernel_debug_iface_debugfs_set() 함수 두 번째 인자인 val로 /sys/kernel/debug/raspbian_debug_iface/val 파일이 저장한 값이 전달됩니다.

echo 명령어로 /sys/kernel/debug/raspbian_debug_iface/val 파일을 825로 변경하면 raspbian_debug_state 전역 변수가 825로 변경됩니다.

이렇게 특정 디바이스 노드를 변경하면 이에 대응하는 raspbian_debug_state 이란 전역 변수를 바꿀 수 있습니다.

RPi_debugfs 를 활용해 커널 코드 변경해보기
이번에는 위에서 소개한 debugfs를 활용해 리눅스 커널 코드를 고쳐 보겠습니다.
다음은 리눅스 커널에서 인터럽트 핸들러 함수를 호출하는 코드를 수정한 예시입니다.
1 diff --git a/kernel/irq/handle.c b/kernel/irq/handle.c
2 index 79f987b..de6e535 100644
3 --- a/kernel/irq/handle.c
4 +++ b/kernel/irq/handle.c
5 @@ -132,6 +132,8 @@ void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
6        wake_up_process(action->thread);
7 }
8
9  +extern uint32_t raspbian_debug_state;
10 +
11 irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
12 {
13        irqreturn_t retval = IRQ_NONE;
14 @@ -143,6 +145,12 @@ irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags
15        for_each_action_of_desc(desc, action) {
16                irqreturn_t res;
17
18 +               if ( 825 == raspbian_debug_state ) {
19 +                       void *irq_handler = (void*)action->handler;
20 +                       trace_printk("[+] irq_num:[%d] handler: %pS caller:(%pS) \n",
21 +                                               irq, irq_handler, (void *)__builtin_return_address(0));
22 +               }
23 +
24                trace_irq_handler_entry(irq, action);
25                res = action->handler(irq, action->dev_id);
26                trace_irq_handler_exit(irq, action, res);


먼저 코드 입력 방법부터 소개합니다. 위 코드에서 왼쪽에 + 기호로 된 줄이 새롭게 추가된 코드입니다.

입력한 패치와 함수에 대해 살펴보겠습니다. 패치 코드를 입력한 __handle_irq_event_percpu() 함수는 인터럽트 핸들러를 호출합니다.

인터럽트는 대부분 리눅스 시스템에서 1초에 수 백 번 이상 발생하며 인터럽트 핸들러도 매우 자주 호출됩니다. 인터럽트 핸들러를 호출하기 전 인터럽트 핸들러 함수 정보를 보고 싶은 상황입니다.

인터럽트 핸들링 함수 흐름을 잘 이해하고 있는 분들에겐 당연히 동작하는 코드일 수 있으나 처음 코드를 작성할 때는 겁이 날 수 있습니다. 리눅스 커널 코드를 입력할 때 대부분 걱정이 앞섭니다. 
고친 코드가 잘못 동작해서 부팅은 제대로 하려나?
혹시 커널 패닉으로 시스템이 리셋을 치지는 않을까?

다음 19~21 번째 줄 코드는 raspbian_debug_state 전역변수가 825일 때만 실행합니다.
18 +               if ( 825 == raspbian_debug_state ) {
19 +                       void *irq_handler = (void*)action->handler;
20 +                       trace_printk("[+] irq_num:[%d] handler: %pS caller:(%pS) \n",
21 +                                               irq, irq_handler, (void *)__builtin_return_address(0));
22 +               }
23 +

raspbian_debug_state 변수는 기본으로 4096으로 설정돼 있습니다.
다음 명령어를 입력해 raspbian_debug_state 전역 변수를 825로 변경하기 전 까지 18~21번 째 줄 코드는 실행하지 않습니다. 
echo 825 > /sys/kernel/debug/raspbian_debug_iface/val


다음 if 문에서 상수와 전역 변수 위치가 바뀌어 있는 것을 볼 수 있습니다.
if ( 825 == raspbian_debug_state ) {

이렇게 코드를 입력한 이유는 실수로 다음과 같이 코드를 작성할 수 있기 때문입니다. 
if ( raspbian_debug_state = 825 ) {


이 방식을 응용하면 겁먹지 않고 리눅스 커널 코드를 수정할 수 있습니다.

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)

커널 디버깅과 코드 학습

디버깅이란
ftrace
   * ftrace란     


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

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

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


 



핑백

덧글

  • cat 2020/07/14 22:08 # 삭제 답글

    rpi_debugfs.c 파일을 / home / pi / rpi_kernel_src / linux / drivers / soc / bcm 경로에 넣었습니다. 그리고 커널build and 이미지를 만들었습니다. 그러나 재부팅 후에도 여전히 메시지가 나타납니다.
    root@raspberrypi:/home/pi# ls /sys/kernel/debug/rpi_debug/
    ls: cannot access '/sys/kernel/debug/rpi_debug/': 그런 파일이나 디렉터리가 없습니다

    WHy??
  • AustinKim 2020/07/15 07:53 #

    아래 포스팅의 내용을 참고하세요.

    『3장』 질문: rpi_debugfs가 제대로 빌드가 되지 않습니다
    http://rousalome.egloos.com/10017313

    감사합니다.
  • 2020/07/15 07:53 # 답글 비공개

    비공개 덧글입니다.
  • 리눅스뿌셔! 2020/10/20 02:00 # 삭제 답글

    안녕하세요. 쓰신 책으로 잘 공부하고 있습니다.

    그런데 이 부분에 있어서 책에 나온 방법대로는 아무리 해도 안되서 블로그를 보게 되었는데 Makefile의 내용과 val파일이 있는 위치가 책과는 다르네요? 물론 블로그대로 해보니 되기는 했습니다만 ... 책이 이 블로그 글보다 이후에 출판될걸로 알고있는데 책의 방법은 왜 안될까요..?
  • AustinKim 2020/10/20 07:06 #

    먼저, 책을 진지하게 읽고 질문 주셔서 감사합니다.

    책의 3.6절에 소개된 소스 코드와 Makefile을 말씀하시는 것 같은데요.
    관련 코드를 확인해 봤는데, 오타나 문제가 될만한 부분이 보이지 않습니다.

    책의 내용 중에 잘 안되는 부분을 "구체적"으로 알려주시면 좋겠습니다.
    참고로, 블로그에 있는 코드는 처음에 작성한 건데, 이 내용을 정리한 것이 책에 있는 코드입니다.

    감사합니다.
  • 2020/10/20 07:06 # 답글 비공개

    비공개 덧글입니다.
  • 질문 2022/10/07 02:55 # 삭제 답글

    rpi_debugfs 소스 코드 추가 후 Makefile을 변경한 후 build_rpi_kernel.sh로 빌드를 하였습니다.
    install_rpi_kernel_img.sh로 설치를 하는데 "cannot stat '/home/pi/rpi_kernel_src/out/arch/arm/boot/dts/overlays/README': No such file or directory라는 메세지가 나오고 설치가 종료 되었습니다.

    어떻게 해야 설치를 바르게 할 수 있을까요?
  • AustinKim 2022/10/08 06:21 #

    알려주신 에러는 $OUTPUT/arch/arm/boot/dts/overlays/README 파일이 없어서 출력되는 메시지입니다. 그런데 라즈비안 이미지를 설치하실 때 큰 영향을 미치진 않습니다.

    install_rpi_kernel_img.sh 셸 스크립트 파일은 아래와 같은데요.

    01 KERNEL_TOP_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
    02 OUTPUT="$KERNEL_TOP_PATH/out"
    03 echo "$OUTPUT"
    04
    05 cd linux
    06
    07 make O=$OUTPUT modules_install
    08 cp $OUTPUT/arch/arm/boot/dts/*.dtb /boot/
    09 cp $OUTPUT/arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
    10 cp $OUTPUT/arch/arm/boot/dts/overlays/README /boot/overlays/
    11 cp $OUTPUT/arch/arm/boot/zImage /boot/kernel7.img

    10번째 줄에 있는 코드를 아예 삭제하시고 install_rpi_kernel_img.sh 스크립트를 실행하시면 됩니다.
  • 질문 2022/10/11 08:49 # 삭제 답글

    답변 감사드립니다. 말씀하신대로 10번째 줄을 지우고 설치를 하니 성공적으로 설치가 되어서 리붓을 하였습니다.
    val 값을 호출할때 rpi_kernel_debug_stat_get 함수가 실행 되는데 함수는 어디서 찾을 수 있을까요?
  • AustinKim 2022/10/14 17:03 #

    drivers/soc/bcm/rpi_debugfs.c 파일입니다.
  • 엔지니어 우미 2022/10/24 02:57 # 답글

    아이고,, ㅎㅎ 커널 빌드 & 설치가 저처럼 처음이라 터무니 없는 실수를 하는 분이 계실까싶어 적어봅니다.

    out/drivers/soc/bcm/ 경로에 계속 왜 rpi_debugfs.o 파일이 없지했는데... 빌드를 잊어버린 채 계속 설치만 다시 하고 있었군요. ㅎㅎ
    빌드하고 설치하니 잘 됩니다! 감사합니다~!
  • AustinKim 2022/10/26 09:45 #

    빌드와 설치가 잘 된다고 하시니 다행입니다. 유익한 정보를 알려주셔서 감사합니다.
댓글 입력 영역