[QEMU + Yocto + Raspberry Pi 4 + ARM64] 리눅스 커널 디버깅하기

문연수·2021년 8월 16일
0

Embedded Linux

목록 보기
3/4

0. 개요

이번 글에서는

  1. 리눅스 커널을 ARM64 아키텍쳐로 크로스 컴파일하여 빌드하고,

  2. Yocto Projectrootfs 를 구축한 뒤,

  3. 여기에서 생성된 파일을 Raspberry Pi 4 (AArch64) 로 보내

  4. QEMU 로 에뮬레이트하여

  5. head.S, System Call 그리고 Module Programming 각각에 대해 디버깅

할 수 있는 환경 구축 방법에 대해 서술한다.

1. 예상 질문

ARM64 리눅스 커널 디버깅을 위해 왜 이런 짓을 해야 하는지 의문을 가질 수 있기 때문에 예상 질문을 만들어 보았다.

Q. 무지성(non-Intelligence) 호스트 실행

아니 그냥 호스트 컴퓨터에서 커널을 올려서 바로 확인하면 되는거 아닌가?

호스트 컴퓨터에서 작업하던 중 커널 패닉을 일으키면 모든 자료를 날릴 수도 있다. 필자처럼 리눅스(Fedora) 를 메인 OS 로 사용하는 사용자는 반드시 가상 환경에서 작업을 진행해야 한다.

Q. 가상 머신으로 디버깅 (Not Using Raspberry Pi)

그럼 호스트 머신에서 QEMUEmulate 하면 되는거 아닌가? 굳이 Raspberry Pi 에서?

틀린 말은 아니지만 필자가 디버깅하고자 하는 아키텍쳐는 ARM64 이다. 일반적인 데스크탑과 랩탑은 ARM 코어를 사용하지 않는다. ARM 은 보통 임베디드 장치에 탑재되는 코어이기 때문에 x86AMD 에서는 이를 네이티브로 실행할 수 없다.
호스트 머신이 ARM64 라면 (그럴 일은 거의 없겠지만) Raspberry Pi 까지 갈 필요없다.

Q. Linux KernelrootfsRaspberry Pi 에 직접 올린다

이제 Raspberry Pi 보드에 바로 올리면 되는거 아냐?

아쉽게도 좋은 생각이 아니다. 이 역시 무지성 호스트 실행 과 마찬가지로 커널 패닉이나 기타 문제가 발생했을 경우 다시 싹다 갈아야 하는데 이 작업은 그리 즐겁지 않다.
또한 head.S 부터 디버깅하기 위해서는 QEMU 를 통해 원격 디버깅을 진행해야 하는데 이 작업은 보드에 직접 Kernel 을 올려서 부팅하는 방식으론 확인하기 어렵다. 적어도 필자는 아직까지 그러한 디버깅 방법을 모른다.

2. 사전 지식과 필요 장비

이 글은 독자가 아래의 사항들을 이해하고 있으며, 또한 이러한 요구사항을 만족시키는 방법을 알고 있다는 것을 가정하고 글을 작성하였다:

  1. Yocto Project 로 완성한 리눅스 커널을 QEMU 로 부팅할 수 있다.

  2. Raspberry Pi 가 무엇인지 알고 있으며, 보드 부팅에 필요한 기본적인 장비를 갖추고 있다.

  3. Raspberry PiHeadless Mode 로 사용할 수 있다.

  4. Raspberry Pi OS 64-bit 를 설치할 수 있다.

  5. 특정 버전에 Linux Kernel 을 크로스 컴파일하여 빌드할 수 있다.

잘 모르겠다면 하이퍼링크해둔 필자의 글들을 읽기 바란다. 영어에 자신있고 장문에 대한 두려움이 없다면 공식 문서를 찾아서 읽는 것을 추천한다.

+ 주의 사항

5번 의 경우 필자가 리눅스 커널을 Raspberry Pi 내부에서 빌드하는 바보같은 짓을 했는데 이럴 필요 없다. 호스트 머신에서 아키텍쳐는 arm64 로, defconfig 을 주고 크로스 컴파일하면 된다.

3. 리눅스 커널 빌드하기

가장 먼저 해야 하는 작업은 리눅스 커널을 빌드하는 것이다. 필자는 호스트 머신에서 Linux Kernel 5.10 버전의 소스를 ARM64 아키텍쳐에 defconfig 설정을 주어 크로스 컴파일했다.

그럼 build 경로에 다음의 두 가지 파일이 생성되는데 이를 Raspberry Pi 에 복사한다:

  • build/vmlinux 파일 (head.S 디버깅 시 필요)
  • build/arch/arm64/boot/Image 파일 (QEMU 실행 시 필요)

필자는 커널 빌드 후 scp 를 이용해서 파일을 Raspberry Pi 의 홈 디렉터리로 전송했다.

4. Yocto Projectrootfs 만들기

Yocto Project 를 통해 rootfs 를 구축한다. 필자는 core-image-kernel-devbitbake 하였고, gdb, strace 패키지를 추가했다. 자세한 사항은 다음의 글을 참조하길 바란다. 한치의 오차도 없이 똑같이 진행했다.

성공적으로 빌드가 되었다면 다음 경로에 rootfs.ext4 파일이 생성된다:

build/tmp/deploy/images/qemuarm64/core-image-kernel-dev-qemuarm64-날짜.rootfs.ext4

이 파일을 마찬가지로 Raspberry Pi 보드로 옮긴다. 역시 scp 를 이용했다.

5. QEMU 로 부팅하기

파일이 성공적으로 전송되었다면 sshRaspberry 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 로 만들어서 실행시킨다:

rootfsinsmod, rmmod, strace, gdb 등의 프로그램이 존재하므로 쉽게 모듈 프로그래밍이 가능하다. 또한 wget, scp 등의 프로그램도 사용 가능하므로 쉽게 파일을 주고 받을 수 있다.

6. gdb 로 디버깅

gdbhead.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 디버깅을 위한 사전 작업은 끝난 것이다.

출처

[사이트] https://blog.naver.com/rlawlxo1064/221621552737

profile
2000.11.30

2개의 댓글

comment-user-thumbnail
2021년 9월 26일

I love it

1개의 답글