IT/Linux Kernel

디버깅을 통해 배우는 리눅스 커널의 구조와 원리 1, 도서 공부하기 5 - 디버깅과 ftrace 예습

변화의 물결1 2025. 2. 26. 00:05

 

 

안녕하세요.

 

 이번 장에서는 디버깅하는 방법 예습차원에서 중요성, ftrace 내용을 알려주고 있습니다.

내용 잘 학습해 보아요.

 


1. 디버깅의 중요성

 

1) 임베디드 및 BSP 개발 도중 만나는 대표적인 문제

 

 - 부팅 도중 커널 크래시 발생

 - 인터럽트 핸들러를 설정했는데 인터럽트 핸들러가 호출되지 않음

 - 시스템 응답 속도가 매우 느려짐

 - 파일 복사가 안됨

 - 다른 개발자가 작성한 커널 코드를 만날 가능성이 높음

   (디바이스 드라이버는 커널함수로 구성되어 있으며 각 서브 시스템을 담당한 개발자가 작성한 코드이기 때문입니다.)

 

 그렇기 때문에 정상적인 동작할 때 몇 가지 내용 파악이 필요

 

 - 함수가 실행될 때 변경되는 자료구조

 - 함수가 실행되는 빈도와 실행 시간

 - 실행 중인 코드를 어떤 프로세서가 실행하는지 확인

 

2) 커널 로그 분석

 

첫째, 오류 메시지를 커널 어느 코드에서 출력했는지 확인

둘째, 소스코드에서 에러 메시지를 출력한 이유를 살펴봐야 함

셋째, 필요에 따라 디버깅 코드를 작성해 다시 문제가 발생했을 때 추가 커널 로그 확보 시도

 

디버깅 능력은 문제해결능력 그 자체이므로 디버깅을 꾸준히 해서 리눅스 커널 익힘 필요

 

3) 디버깅과 코드학습 능력

 

 "필자는 디버깅과 함께 커널 소스를 분석하면 소스만 분석할 때보다 5배 정도 학습효과가 있다고 생각합니다."

  -> 필자가 적은 이 구절은 영어공부와 비슷하다고 생각이 듭니다. 영어만 읽기만 열심히 공부하는 것이 아니라 문장의 구조와, 어떤 상황에서 상황에서 이 문장을 사용하는지 파악하는 것과 유사하다는 느낌이 들었습니다.

 

 

2. 디버깅 위한 코드 분석

 

 뒷부분에서 ftrace와 관련된 내용이 자세히 나오겠지만, 간단하게 이렇게 할 거야 정도로 내용을 보여주고 있습니다.

 5장의 인터럽트를 학습 중이며 아래 명령을 입력하면 인터럽트 세부 속성을 알 수 있다는 것을 확인한 상태라고 가정합니다.

 

# cat /proc/interrupts

 

 

 

 인터럽트 속성을 보고가 커널의 어느 코드에서 출력하는 궁금증 발생 -> 코드 검색 -> show_interrupt() 알게 됨 -> 내용이 궁금 해짐

  

 기존 원본 커널 소스 수정오류로 다시 다운로드하는 것을 방지하기 위해서 branch를 만들어 놓고 시작합니다.

 

 

 

 test할 브랜치를 생성하고 생성한 branch로 이동합니다.

그러나 git version이 낮아서 switch(이동) 명령어 사용되지 않습니다. 그래서 chechkout 명령으로 이동합니다.

 

# git branch rpi-4.19.y_test
# git switch rpi-4.19.y_test
#git version

# git checkout rpi-4.19.y_test
# git branch

 

 

 

원본 소스코드와 비교하기 위해서 branch 쪽에서 작업하도록 하겠습니다.

에디터 프로그램(vim, gedit 등)을 이용해서 proc.c 파일에 패치 코드를 입력합니다.

rpi_kernel_src/linux# vim kernel/irq/proc.c

 

 

원본 커널 소스와 branch에 수정한 proc.c 파일을 비교해 봅니다. 아래의 비교가 되려면 branch 쪽에서 commit이 완료한 후에 origin(rpi-4.19.y) 쪽과 branch(rpi-4.19.y_test) 쪽 비교가 가능합니다. 사실 commit 파일과 이전 파일의 내용을 쉽게 비교도 가능합니다.

linux# git diff rpi-4.19.y..rpi-4.19.y_test -- kernel/irq/proc.c

 

 

--- 코드 추가 1

void rpi_get_interrupt_info(struct irqaction *action_p)
{
         unsigned int irq_num = action_p->irq;
         void *irq_handler = NULL;

         if (action_p->handler) {
                  irq_handler = (void*)action_p->handler;
         }

         if (irq_handler) {
                  trace_printk("[%s] %d: %s, irq_handler: %pS \n",
                                                     current->comm, irq_num, action_p->name, irq_handler);
         }
}

 

 

--- 코드 추가 2

 

         if (action)
                  rpi_get_interrupt_info(action);

 

 

 위의 코드 추가 1 부분의 rpi_get_interrupt_info() 함수 구현 내용은 trace_printk로 프로세서 이름, 인터럽트 번호, 인터럽트 이름, 인터럽트 핸들러 함수 이름을 출력합니다.

 

 코드 추가 2의 내용은 show_interrupts() 함수가 실행될 때 irqaction 구조체가 NULL이 아닐 경우 rpi_get_interrupt_info() 호출합니다.

 

 위와 같이 작성한 이유는 인터럽트 속성 정보를 담고 있는 구조체 찾기 위해서입니다. desc(디스크립트)는 irq_desc 구조체이고 다시 구조체 안에 *action 포인터는 irqaction 구조체를 가리키고 있습니다.

 

 

 

 irqaction 구조체에 인터럽트 정보 형태가 있다는 것을 알 수 있습니다. 111번 줄 인터럽트 핸들러 함수, 118줄 인터럽트 번호, 122줄 문자열 타입의 인터럽트 이름으로 구성되어 있습니다.

 

  

 

3. 패치 코드 빌드하기

 

 이전 빌드 글에서 사용했던 빌드 스크립트를 그대로 사용합니다. git과 같이 사용하다 보니, 빌드할 때 경로와 명령어 디렉터리를 확인하기 바랍니다.

  

/linux# ../build_rpi_kernel.sh

 

  

빌드가 끝났다면, 동일하게 설치 스크립트도 실행합니다.

 

/linux# ../install_rpi_kernel_img.sh

 

 

4. ftrace 설정

 

 ftrace 사용하기 위해 tracking_on, event enable 등 매번 설정을 해야 하기 때문에 설정 스크립트를 만들어 사용합니다.

 책에 나온 내용을 그대로 이용합니다.

 참고로, wikibook 사이트에서 예제파일을 다운로드해서 사용하면 됩니다. 그러나 irq_trace_ftrace.sh 파일이 책 뒷부분 내용으로 저장되어 현재 부분을 사용할 때는 파일을 조금 수정해야 합니다.

 

rpi_kernel_src# vim irq_trace_ftrace.sh
rpi_kernel_src# chmod +x irq_trace_ftrace.sh
rpi_kernel_src# ./irq_trace_ftrace.sh

  

--- irq_trace_ftrace.sh

#!/bin/bash

echo 0 > /sys/kernel/debug/tracing/tracing_on
sleep 1
echo "tracing_off"

echo 0 > /sys/kernel/debug/tracing/events/enable
sleep 1
echo "events disabled"

echo  secondary_start_kernel  > /sys/kernel/debug/tracing/set_ftrace_filter
sleep 1
echo "set_ftrace_filter init"

echo function > /sys/kernel/debug/tracing/current_tracer
sleep 1
echo "function tracer enabled"

echo rpi_get_interrupt_info > /sys/kernel/debug/tracing/set_ftrace_filter
sleep 1
echo "set_ftrace_filter enabled"

echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable
echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_exit/enable
sleep 1
echo "event enabled"

echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
echo 1 > /sys/kernel/debug/tracing/options/sym-offset
echo "function stack trace enabled"

echo 1 > /sys/kernel/debug/tracing/tracing_on
echo "tracing_on"

 

 

 ftrace에서 중요한 부분은 rpi_get_interrupt_info() 함수명을 /sys/kernel/debug/tracing/set_ftrace_filter 파일에 지정하는 명령어입니다. 이렇게 설정하면 지정한 함수를 누가 호출했는지 알 수 있습니다.

 

 스크립트를 실행하고, cat 명령어를 실행해서 rpi_get_interrupt_info() 함수가 호출되도록 합니다.

 

 

 

5. ftrace 로그 추출

 

 ftrace 로그를 라즈베리 파이에서 안전하게 추출하고, 반복 작업을 줄이기 위해서 스크립트를 작성합니다.

 스크립트 내용은 ftrace를 비활성화해서 출력데이터를 더 이상 기록하지 않도록 합니다.

 그 후에 trace 파일을 분석할 수 있게 복사한 후 이름을 변경합니다.

 

 chmod 명령으로 실행 권한을 부여하는 대신 # sh ./get_ftrace.sh 바로 스크립트를 실행할 수도 있습니다.

 

rpi_kernel_src# vim get_ftrace.sh
rpi_kernel_src# chmod +x ./get_ftrace.sh
rpi_kernel_src# ./get_ftrace.sh

 

 

--- get_ftrace.sh

#!/bin/bash

echo 0 > /sys/kernel/debug/tracing/tracing_on
echo "ftrace off"

sleep 3

cp /sys/kernel/debug/tracing/trace .
mv trace ftrace_log.c

 

 

6. ftrace_log.c 로그 확인

 

 ftrace_log.c 파일을 열어 확인하면 여러 가지 정보들이 나옵니다. 여기서 rpi_get_interrupt_info로 검색해서 위치를 확인합니다.

 

 

 

 ftrace를 이용한 실제 분석하는 방법은 이후 설명에서 나옵니다.

 

 간단하게 보면, PID 1795 cat 명령어에서 rpi_get_interrupt_info를 호출한 것과 seq_read() 함수에서 show_interrrupts() 함수를 호출한 것을 알 수 있습니다. sys_read함수가 호출되었으므로 user공간에서 시스템 콜을 실행했다는 것을 알 수 있습니다.

 

 함수 호출 흐름(Call Trace)은 스택(stack) 구조이므로 실제 실행 순서는 반대로 해석해야 합니다. sys_read() → ksys_read() → vfs_read() ... rpi_get_interrupt_info() 순서로 호출됩니다.

 

 

 

 책에서는 bcm2835_mmc_irq 도 따라가는 내용이 나옵니다.  

 인터럽트 번호는 35, 인터럽트 이름은 mmc1, 인터럽트 핸들러 이름 : bcm2835_mmc_irq

 그러나 여기서는 이정도에서 확인하고 마무리하겠습니다.

  

 

 

 bcm2835_mmc_irq 호출을 추적하고 싶다면 irq_trace_ftrace.sh 스크립트 파일을 한 부분을 수정하고 위의 실습한 절차대로 다시 수행하면 됩니다.

 

 echo bcm2835_mmc_irq > /sys/kernel/debug/tracing/set_ftrace_filter

  

 본격적으로 코드 분석을 시작하지도 않았는데, 알아야 할 것이 많이 있음을 느낍니다.

 

 

 

감사합니다.

 

 

[유용한 TIP]

 

 root 계정에서 git을 사용할 때 git 명령어가 tab키를 눌렀을 때 자동완성 기능이 되지 않을 때 root 계정의 .bashrc 파일에 git-completion.bash 파일이 로드되지 않았을 수 있습니다.

 pi계정 .bashrc 파일에는 아래내용이 있는 root계정에 .bashrc 파일에 없다면 내용을 추가해 주면 tab 자동완성 기능을 사용할 수 있습니다.

 

~# cat .bashrc

 

# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
if ! shopt -oq posix; then
  if [ -f /usr/share/bash-completion/bash_completion ]; then
    . /usr/share/bash-completion/bash_completion
  elif [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
  fi
fi

 

 

 

<참고 자료>

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

 

 

반응형