objdump
유틸리티 책에서는 Raspberry Pi
내에서 objdump
를 찍지만 필자는 호스트 머신에서 찍기 때문에 다른 이름의 프로그램을 사용해야 한다:
aarch64-linux-gnu-objdump -x vmlinux | grep
aarch64-linux-gnu-objdump \
--start-address=0xffffffc080000000 \
--stop-address=0xffffffc080000020 \
-d vmlinux
주소 범위로 어셈블리 코드를 찾는 것도 가능하다. 책에서는 다른 주소를 썼는데 필자는 64-bit 운영체제라서 하위 주소에서 아무 것도 나오는게 없다.
ftrace
를 통한 디버깅 책을 따라서 코드를 작성하고 리빌드 한 뒤에 다시 실행시켜 봤는데 ftrace
로그에 결과가 찍히지 않는다. 그래서 이 문제 자체를 디버깅하는 과정을 적어보려 한다.
우선 빌드가 제대로 되었는지 확인하기 위해서 vmlinux
파일의 symbol
을 dump
하여 rpi_get_interrupt_info
함수가 제대로 있는지 확인해보았다:
rpi_get_interrupt_info
함수는 확실히 존재한다. 그렇다면 컴파일에는 문제가 없었다는 뜻이다. 그렇다면 무엇이 문제였을까? 필자는 커널이 업데이트(변경)되지 않았다고 생각했다. 따라서 dmesg
로 raspberry pi
의 커널 로그를 확인해보았다:
맨 윗 줄만 보고 커널은 반드시 업데이트 되었다고 생각했다. 그렇게 생각한 이유는 우선 빌드 환경이 크로스 빌드임이 확인되었고 또 커널 업데이트 날짜가 완전히 최신이다. 그리고 다음의 로그를 보고 확신했다:
위에서 작성한 패치 코드는 분명히 적용되었다. 그렇다면 문제는 두 가지로 좁혀진다:
rpi_get_interrupt_info
함수를 부르지 못했다.ftrace
의 설정이 잘못 되어 함수는 호출되었으나 이를 캐치하지 못했다.rpi_get_interrupt_info
는 호출 되었는가?필자는 이를 확인하기 위해 코드를 새롭게 한 줄 더 작성했다:
printk
함수는 dmesg
에 바로 붙잡히기 때문에 cat /proc/interrupts
이후에 찍어보면 바로 확인 가능하다:
자, 이제 커널은 변경 되었고 결과만 확인하면 된다...
rpi_get_interrupt_info()
was called!
하지만 아직 한 가지 코너 케이스가 남아있다. 이것까지 로그로 찍어 보았으면 좋았을텐데 나의 판단에 아쉬움이 남는다. 그건 바로 irq_handler
가 NULL
인 경우이다. 이때에는 if
문이 실행되지 않아 trace_printk
함수도 호출되지 않을 것이다. 변경 전에 코드를 한번 더 살펴 보았다:
rpi_get_interrupt_info()
를 부르는 함수는 show_interrupts()
이고, 그 상위 함수를 찾아서 handler
가 어떻게 전달되는지 확인하려 했으나 위와 같은 구조로 맵핑되어 있어서 일단 찾는건 포기하고 다시 ftrace
쪽으로 넘어가기로 했다.
ftrace
설정 문제? ftrace
를 설정하는 과정에서 11번째 줄이 에러를 발생 시킨다. 이건 secondary_start_kernel
부분인데 처음에는 이곳이 문제인가? 했다가 바로 생각을 고쳐 먹었는데 그 이유는 다음과 같다:
우선 show_interrupts
함수가 캐칭이 되고 호출된 횟수가 printk
를 호출한 횟수와 같다. 그래서 한번 ftracer
의 filter function
을 show_interrupt
로 변경해보았다:
진짜 띠용하다. 코드로 보아서는 확실히 rpi_get_interrupt_info
도 호출되었고, trace_printk
도 호출되었다.
궁금해서 커널 변경 후, 전체 과정을 bash script
로 재작성하여 다시 수행해 보았다.
제대로 출력된다!
최종적인 테스트 스크립트는 위와 같다. 아무래도 재부팅 이전의 event
, function
설정에 문제가 있었는데, 재부팅 이후에 설정이 다 날아간 것 같다. 그리고 하나 더, 전체 과정을 bash script
로 옮기는 과정에서 cat /proc/interrupts
명령을 수행하는 주체가 root
로 바뀌었다는 점도 있다. 책에서도 root
권한으로 위 명령을 수행하는데 이전 과정에서 필자는 그냥 일반 유저 권한으로 명령을 실행했다.
궁금해서 다시 테스트 해 본 결과 아무래도 ftrace
설정 문제였던 것 같다. 일반 유저 권한으로도 문제가 없다.
#!/bin/bash
BUILD_DIR=../../build/raspberrypi
TARGET=mythos@192.168.0.128
DEST_DIR=/home/mythos/build
KERNEL=kernel8 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs O=$BUILD_DIR -j$(nproc)
KERNEL=kernel8 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=$BUILD_DIR/ INSTALL_MOD_PATH=$BUILD_DIR modules_install -j$(nproc)
rm $BUILD_DIR/lib/modules/6.6.78-v8+/build
echo "Start send file"
sshpass -p $1 scp $BUILD_DIR/arch/arm64/boot/Image $TARGET:$DEST_DIR/Image
sshpass -p $1 scp -r $BUILD_DIR/lib/modules/6.6.78-v8+/ $TARGET:$DEST_DIR/modules/
sshpass -p $1 scp $BUILD_DIR/arch/arm64/boot/dts/broadcom/*.dtb $TARGET:$DEST_DIR/dtb/
sshpass -p $1 scp $BUILD_DIR/arch/arm64/boot/dts/overlays/*.dtb* $TARGET:$DEST_DIR/dtb/overlays/
echo "move sending files"
sshpass -p $1 ssh -t $TARGET "echo $1 | sudo -S cp $DEST_DIR/Image /boot/firmware/kernel8.img"
sshpass -p $1 ssh -t $TARGET "echo $1 | sudo -S cp -r $DEST_DIR/dtb/ /boot/firmware/"
sshpass -p $1 ssh -t $TARGET "echo $1 | sudo -S cp -r $DEST_DIR/modules/ /lib/modules/6.6.74+rpt-rpi-v8/"
sshpass -p $1 ssh -t $TARGET "sync"
sshpass -p $1 ssh -t $TARGET "echo $1 | sudo -S reboot -h now"
echo "done!"
./build_and_run.sh <password>
이렇게 입력하면 커널을 빌드한 뒤에 빌드 결과물을 라즈베리 파이로 복사하고, 복사한 파일로 커널을 교체한 뒤 자동으로 재부팅한다.
ftrace
래핑 스크립트#!/bin/bash
FTRACE=/sys/kernel/debug/tracing
if (( $# <= 0 )); then
echo "usage $0 <command>"
echo -e "command list:
on [timeout]
off
list-target
set-target <target>
get-target
list-filter
set-filter <target>
get-filter
list-option
set-option <option> <value>
get-option <option>
result
"
fi
case $1 in
"on")
echo 1 > $FTRACE/tracing_on
if (( $# == 2 )); then
sleep $2
echo "tracing off"
echo 0 > $FTRACE/tracing_on
fi
;;
"off")
echo 0 > $FTRACE/tracing_on
;;
"list-target")
cat $FTRACE/available_tracers
;;
"set-target")
echo $2 > $FTRACE/current_tracer
;;
"get-target")
cat $FTRACE/current_tracer
;;
"list-filter")
TARGET=$(cat $FTRACE/current_tracer)
case $TARGET in
"function" | "function_graph") cat $FTRACE/available_filter_functions ;;
"event") cat $FTRACE/available_filter_events ;;
*) echo "no filter provided for $TARGET" ;;
esac
;;
"set-filter")
echo $2 > $FTRACE/set_ftrace_filter
;;
"get-filter")
cat $FTRACE/set_ftrace_filter
;;
"list-option")
ls $FTRACE/options/
;;
"set-option")
echo $3 > $FTRACE/options/$2
;;
"get-option")
cat $FTRACE/options/$2
;;
"result")
cat $FTRACE/trace
;;
esac
ftrace
를 일일이 입력하는 것이 불편해서 만든 래핑 스크립트이다. 아래의 명령어를 순서대로 입력하면 위에서 수행했던 과정을 그대로 따라할 수 있다:
sudo ftrace set-target function
>
sudo ftrace set-filter rpi_get_interrupt_info
>
sudo ftrace set-option func_stack_trace 1
sudo ftrace set-option sym-offset 1
>
sudo ftrace on 5 &
>
cat /proc/interrupts
# 5 초간 대기...
>
cat ftrace result