이번 글에서는
리눅스 커널을 ARM64
아키텍쳐로 크로스 컴파일하여 빌드하고,
Yocto Project
로 rootfs
를 구축한 뒤,
여기에서 생성된 파일을 Raspberry Pi 4
(AArch64
) 로 보내
QEMU
로 에뮬레이트하여
head.S
, System Call
그리고 Module Programming
각각에 대해 디버깅
할 수 있는 환경 구축 방법에 대해 서술한다.
ARM64
리눅스 커널 디버깅을 위해 왜 이런 짓을 해야 하는지 의문을 가질 수 있기 때문에 예상 질문을 만들어 보았다.
non-Intelligence
) 호스트 실행아니 그냥 호스트 컴퓨터에서 커널을 올려서 바로 확인하면 되는거 아닌가?
호스트 컴퓨터에서 작업하던 중 커널 패닉을 일으키면 모든 자료를 날릴 수도 있다. 필자처럼 리눅스(Fedora
) 를 메인 OS 로 사용하는 사용자는 반드시 가상 환경에서 작업을 진행해야 한다.
Not Using Raspberry Pi
)그럼 호스트 머신에서
QEMU
로Emulate
하면 되는거 아닌가? 굳이Raspberry Pi
에서?
틀린 말은 아니지만 필자가 디버깅하고자 하는 아키텍쳐는 ARM64
이다. 일반적인 데스크탑과 랩탑은 ARM
코어를 사용하지 않는다. ARM
은 보통 임베디드 장치에 탑재되는 코어이기 때문에 x86
과 AMD
에서는 이를 네이티브로 실행할 수 없다.
호스트 머신이 ARM64
라면 (그럴 일은 거의 없겠지만) Raspberry Pi
까지 갈 필요없다.
Linux Kernel
과 rootfs
를 Raspberry Pi
에 직접 올린다이제
Raspberry Pi
보드에 바로 올리면 되는거 아냐?
아쉽게도 좋은 생각이 아니다. 이 역시 무지성 호스트 실행
과 마찬가지로 커널 패닉이나 기타 문제가 발생했을 경우 다시 싹다 갈아야 하는데 이 작업은 그리 즐겁지 않다.
또한 head.S
부터 디버깅하기 위해서는 QEMU
를 통해 원격 디버깅을 진행해야 하는데 이 작업은 보드에 직접 Kernel
을 올려서 부팅하는 방식으론 확인하기 어렵다. 적어도 필자는 아직까지 그러한 디버깅 방법을 모른다.
이 글은 독자가 아래의 사항들을 이해하고 있으며, 또한 이러한 요구사항을 만족시키는 방법을 알고 있다는 것을 가정하고 글을 작성하였다:
잘 모르겠다면 하이퍼링크해둔 필자의 글들을 읽기 바란다. 영어에 자신있고 장문에 대한 두려움이 없다면 공식 문서를 찾아서 읽는 것을 추천한다.
5번
의 경우 필자가 리눅스 커널을 Raspberry Pi
내부에서 빌드하는 바보같은 짓을 했는데 이럴 필요 없다. 호스트 머신에서 아키텍쳐는 arm64
로, defconfig
을 주고 크로스 컴파일하면 된다.
가장 먼저 해야 하는 작업은 리눅스 커널을 빌드하는 것이다. 필자는 호스트 머신에서 Linux Kernel 5.10
버전의 소스를 ARM64
아키텍쳐에 defconfig
설정을 주어 크로스 컴파일했다.
그럼 build
경로에 다음의 두 가지 파일이 생성되는데 이를 Raspberry Pi
에 복사한다:
build/vmlinux
파일 (head.S
디버깅 시 필요)build/arch/arm64/boot/Image
파일 (QEMU
실행 시 필요)필자는 커널 빌드 후 scp
를 이용해서 파일을 Raspberry Pi
의 홈 디렉터리로 전송했다.
Yocto Project
로 rootfs
만들기Yocto Project
를 통해 rootfs
를 구축한다. 필자는 core-image-kernel-dev
로 bitbake
하였고, gdb
, strace
패키지를 추가했다. 자세한 사항은 다음의 글을 참조하길 바란다. 한치의 오차도 없이 똑같이 진행했다.
성공적으로 빌드가 되었다면 다음 경로에 rootfs.ext4
파일이 생성된다:
build/tmp/deploy/images/qemuarm64/core-image-kernel-dev-qemuarm64-날짜.rootfs.ext4
이 파일을 마찬가지로 Raspberry Pi
보드로 옮긴다. 역시 scp
를 이용했다.
QEMU
로 부팅하기 파일이 성공적으로 전송되었다면 ssh
로 Raspberry Pi
접속 시 다음과 같은 파일이 나타날 것이다:
Yocto
로 생성한 rootfs
는 파일은 파일명이 너무 길어서 rootfs.ext4
로 변경했다.
이제 다음의 명령어를 실행시키면 QEMU
로 부팅이 가능하다:
qemu-system-aarch64 \ -device virtio-net-device,netdev=eth0 \ -netdev user,id=eth0 \ -object rng-random,filename=/dev/urandom,id=rng0 \ -device virtio-rng-pci,rng=rng0 \ -drive id=disk0,file=rootfs.ext4,if=none,format=raw \ -device virtio-blk-device,drive=disk0 \ -device qemu-xhci -device usb-tablet -device usb-kbd \ -machine virt \ -cpu host \ -enable-kvm \ -m 256 \ -serial mon:stdio \ -serial null \ -nographic \ -kernel Image \ -append 'root=/dev/vda rw mem=256M console=ttyAMA0 console=hvc0' \
필자는 보통 이를 shell script
로 만들어서 실행시킨다:
rootfs
에 insmod
, rmmod
, strace
, gdb
등의 프로그램이 존재하므로 쉽게 모듈 프로그래밍이 가능하다. 또한 wget
, scp
등의 프로그램도 사용 가능하므로 쉽게 파일을 주고 받을 수 있다.
gdb
로 디버깅gdb
로 head.S
를 디버깅하기 위해선 두 개의 창을 띄워야 하는데 ssh
로 접속하면 되므로 그리 어려운 일은 아니다.
아래의 명령어를 실행하여 gdb
에서 접속 가능한 상태로 qemu
를 실행한다:
qemu-system-aarch64 \ -device virtio-net-device,netdev=eth0 \ -netdev user,id=eth0 \ -object rng-random,filename=/dev/urandom,id=rng0 \ -device virtio-rng-pci,rng=rng0 \ -drive id=disk0,file=rootfs.ext4,if=none,format=raw \ -device virtio-blk-device,drive=disk0 \ -device qemu-xhci -device usb-tablet -device usb-kbd \ -machine virt \ -cpu host \ -enable-kvm \ -m 256 \ -serial mon:stdio \ -serial null \ -nographic \ -kernel Image \ -append 'root=/dev/vda rw mem=256M console=ttyAMA0 console=hvc0 nokaslr' \ -s -S
이제 다른 터미널을 열어서 gdb
를 실행시킨다.
target remote localhost:1234
명령어를 실행하면 원격으로 디버깅이 가능하다.
보는 것처럼 원격으로 연결된 것을 확인할 수 있다.
어셈블리 코드까지 제대로 보인다. 위 코드는 실제 head.S
코드는 아니지만 head.S
디버깅을 위한 사전 작업은 끝난 것이다.
I love it