[Linux] Kernel Debugging
개요
Linux Kernel을 빌드하는 법과 ubuntu22.04 cloudimg
를 이용해서 qemu로 실행하는 법을 서술했습니다.
💡 ENV?
만약,mac OS
에서 빌드를 진행하게 된다면 파일 시스템이 대소문자를 구분하는지 확인해야 합니다! 또한 docker 환경에서도bzImage
까지 빌드는 가능하지만, 그 이후의 과정이 불가능합니다.
필수 패키지 설치
아래의 명령어로 빌드를 위한 패키지를 설치합니다.
1
apt-get install vim git make gcc build-essential rsync fakeroot libncurses-dev libncurses5 libncurses5-dev bin86 libssl-dev libelf-dev xz-utils curl wget bc flex bison
크로스 컴파일 설정
만약, x86_64
로 크로스 컴파일 할거라면 아래의 패키지도 설치합니다.
- 만약, 다른 아키텍쳐로 크로스 컴파일하려면 대상 아키텍쳐에 맞는 컴파일러를 설치합니다.
1
apt-get install gcc-x86-64-linux-gnu
또한 환경변수를 설정합니다. CROSS_COMPILE
의 경우 마지막 gcc를 제외하고 입력해줍니다.
1
2
export ARCH=x86_64
export CROSS_COMPILE=x86_64-linux-gnu-
Kernel Build
원하는 Kernel 소스 코드를 가져오는 방법은 2가지 방법이 존재합니다.
git 사용
첫번째 방법은 git
을 이용해서 clone
하는 방법입니다. 원하는 version에 대해 git clone
을 받아옵니다.
💡 git clone?
git clone으로 코드를 받아오지 않으면 나중에 설정할 때 필요한 파일을 빌드할 수 없기 때문에 git으로 받아와야합니다.
아래는 예시로 v6.8.0-rc2
를 받아오는 코드입니다.
1
git clone --branch v6.8-rc2 https://github.com/torvalds/linux.git
git 사용(ubuntu kernel)
두번째 방법은 apt-get을 이용해서 소스코드를 가져오는 방법입니다. 첫번째 방법은 ubuntu
와 같은 OS
에서 자체적으로 수정한 Kernel
소스 코드는 존재하지 않는데 해당방법을 사용하면 자체적으로 패치한 내용의 코드도 받아올 수 있습니다.
1
git clone -b "Ubuntu-5.15.0-27.28" git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/jammy ./linux-5.15.0-27
위의 방법으로 코드를 가져오면 됩니다.
💡 5.15.0-27.28
여기서 버전 명명 규칙은Ubuntu-<kernel version>-<revision>
와 같습니다.
홈페이지에서 다운로드
세번째 방법으로 홈페이지에서 다운로드해서 빌드하는 방법입니다. 링크에서 원하는 버전을 다운받아서 빌드할 수 있습니다.
💡 링크에서 다운로드 위치를 찾기 힘들다면 아래와 같이 url을 접근하면 됩니다.
https://launchpad.net/ubuntu/+source/linux/<version>
ex) https://launchpad.net/ubuntu/+source/linux/5.11.0-16.17
위의 링크에 접속하게 되면 아래와 같은 부분을 확인할 수 있습니다. dsc
확장자를 뺀 2개의 파일이 필요합니다.
%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2024-03-11_18.39.35.png
다운 받은 파일중 orig 파일을 아래의 명령어로 압축을 해제합니다.
1
tar -xvf linux_5.11.0.orig.tar.gz
그리고 Makefile
을 확인해서 버전 정보를 확인해둡니다.
1
2
3
4
5
# SPDX-License-Identifier: GPL-2.0
VERSION = 5
PATCHLEVEL = 11
SUBLEVEL = 0
EXTRAVERSION =
압축이 풀린 폴더에 들어가서 diff
파일을 복사해온 뒤 아래의 명령어로 패치를 진행합니다.
1
gunzip -c linux_5.11.0-16.17.diff.gz | patch -p1
이후, 다시 Makefile
을 확인하면 아래와 같이 버전이 변경된 것을 확인할 수 있습니다.
1
2
3
4
5
# SPDX-License-Identifier: GPL-2.0
VERSION = 5
PATCHLEVEL = 11
SUBLEVEL = 12
EXTRAVERSION =
💡 여기서 다운로드 받은 파일과 버전이 다른이유는 릴리즈버전이
Makefile
에 들어가기 때문입니다. 위의 버전이 헷갈린다면Makefile
의 버전정보를 수정하면 됩니다.
패치가 완료됐다면 해당 파일을 삭제하거나 다른 곳으로 옮겨야합니다. 안그러면 빌드과정에서 에러가 발생하게 됩니다.
bzImage 및 package
다음의 명령어로 .config
파일을 생성합니다.
1
2
make defconfig
make kvm_guest.config
위와 같이 .config
파일을 생성하면 디버깅을 위한 설정을 빠져있기 때문에 .config
파일에서 아래의 내용을 수정합니다.
1
2
3
4
5
6
7
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y
CONFIG_GDB_SCRIPTS=y
CONFIG_SLUB_DEBUG=y
CONFIG_KRETPROBES=y
CONFIG_KPROBES=y
CONFIG_KGDB=y
.config
파일의 수정이 끝났으면 아래의 명령어로 커널을 빌드합니다.
💡 ERROR?
make[2]: *** No rule to make target 'net/netfilter/xt_TCPMSS.o'
라는 에러를 만나게된다면net/netfilter/xt_tcpmss.c
파일의 이름을xt_TCPMSS.o
로 변경해줘야 합니다.
리눅스 파일 시스템과 다른 파일 시스템의 차이 때문에 발생하는 에러로 대소문자를 구분하냐 안하냐의 차이입니다!
💡 ERROR?
또한 libelf-dev 패키지 의존성 에러가 계속해서 발생한다면scripts/package/mkdebian
파일의 내용을 아래와 같이 변경합니다.extra_build_depends=”, $(if_enabled_echo CONFIG_UNWINDER_ORC libelf-dev:native)”
extra_build_depends=”$extra_build_depends, $(if_enabled_echo CONFIG_SYSTEM_TRUSTED_KEYRING libssl-dev:native)”
1
make deb-pkg -j$(nproc)
그리고 성공적으로 빌드가 완료되면 아래와 같이 arch/x86/boot/bzImage
경로에 bzImage
가 빌드가 된것을 확인할 수 있습니다.
1
2
3
4
Setup is 15548 bytes (padded to 15872 bytes).
System is 8137 kB
CRC 4ca4d75b
Kernel: arch/x86/boot/bzImage is ready (#1)
1
2
➜ boot file bzImage
bzImage: Linux kernel x86 boot executable bzImage, version 4.19.0 (root@c3094df7be7d) #1 SMP Sat Feb 3 00:03:41 UTC 2024, RO-rootFS, swap_dev 0x7, Normal VGA
bzImage
를 빌드 완료 했다면 원하는 폴더에 복사해둡니다!
또한 상위 폴더에 다음과 같은 패키지 파일도 빌드가 됩니다. 해당 파일도 전부 복사해둡니다.
1
2
3
4
5
6
7
8
9
10
11
12
➜ linux-5.19.0 ls -l
total 373792
drwxr-xr-x 1 root root 2208 Feb 7 20:32 linux-5.19.0
-rw-r--r-- 1 root root 8560104 Feb 7 20:33 linux-headers-5.19.0_5.19.0-1_amd64.deb
-rw-r--r-- 1 root root 143438804 Feb 7 20:36 linux-image-5.19.0-dbg_5.19.0-1_amd64.deb
-rw-r--r-- 1 root root 11927044 Feb 7 20:33 linux-image-5.19.0_5.19.0-1_amd64.deb
-rw-r--r-- 1 root root 1292778 Feb 7 20:33 linux-libc-dev_5.19.0-1_amd64.deb
-rw-r--r-- 1 root root 257549 Feb 7 20:26 linux-upstream_5.19.0-1.diff.gz
-rw-r--r-- 1 root root 1144 Feb 7 20:26 linux-upstream_5.19.0-1.dsc
-rw-r--r-- 1 root root 6613 Feb 7 20:36 linux-upstream_5.19.0-1_amd64.buildinfo
-rw-r--r-- 1 root root 3078 Feb 7 20:37 linux-upstream_5.19.0-1_amd64.changes
-rw-r--r-- 1 root root 217252222 Feb 7 20:24 linux-upstream_5.19.0.orig.tar.gz
💡 ERROR?
여기서 에러가 발생한다면 git clone으로 한것이 아닌 zip 파일을 다운 받았기 때문에 발생하는 에러입니다.
debugging
마지막으로 다음의 명령어로 gdb 설정을 해줍니다. 정상적으로 완료된다면 해당 폴더에 vmlinux-gdb.py
파일이 생성됩니다.
1
2
3
chmod 755 ./scripts/*
make olddefconfig
make scripts_gdb
qemu
init qemu
여기까지 따라하셨다면, qemu를 동작하기 위한 모든 파일을 빌드하게 됩니다. 이제 qemu를 동작하기 위한 ubuntu22.04 cloudimg
를 다운받습니다. 아래의 명령어로 다운받을 수 있습니다.
1
wget https://cloud-images.ubuntu.com/minimal/releases/jammy/release-20220420/ubuntu-22.04-minimal-cloudimg-amd64.img
그리고 용량이 부족하기 때문에 다음 명령어로 늘려줍니다.
1
qemu-img resize ubuntu-22.04-minimal-cloudimg-amd64.img +2G
그리고 ubuntu22.04 cloudimg
를 설정하기 위해 다음의 명령어로 비밀번호를 설정합니다.
1
2
3
4
5
6
7
cat > cloud_config.yaml << EOF
#cloud-config
password: 1234
ssh_pwauth: True
chpasswd:
expire: false
EOF
💡 ERROR?
만약 qemu를 부팅하고 password가 틀렸다면 위의 내용을 복사해서 발생할 수 도 있습니다! 이 경우 직접 입력을 해줘야합니다.
1
cloud-localds seed.raw cloud_config.yaml
이제 모든 설정이 완료되었기 때문에 아래의 스크립트를 이용해서 ubuntu를 부팅합니다.
1
2
3
4
5
6
7
#!/bin/sh
qemu-system-x86_64 -m 2048 -smp 2 \
-drive file=ubuntu-22.04-minimal-cloudimg-amd64.img,format=qcow2 \
-drive file=seed.raw,format=raw \
-netdev user,id=user.0,hostfwd=tcp::2222-:22 \
-device virtio-net,netdev=user.0 -nographic
부팅이 완료되면 다음의 패키지를 설치해줍니다. guest에서 실행해야 합니다!
1
2
sudo apt update
sudo apt install wireless-regdb
패키지가 설치된 다음 *.deb
파일을 scp
명령어로 guest에 옮겨줍니다.
1
scp -P 2222 ./package/*.deb ubuntu@localhost:~/
그리고 guest에서 다음의 명령어로 *.deb
파일을 설치합니다.
1
sudo dpkg -i ./*.deb
설치가 완료되면 /boot
폴더에 존재하는 vmlinuz-6.8.0-rc2
, initrd.img-6.8.0-rc2
, config-6.8.0-rc2
파일을 host로 복사해서 바꿔줍니다.
boot qemu
이제 진짜 모든 설정이 끝났기 때문에 다음과 같은 스크립트를 작성하고 실행합니다.
1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
qemu-system-x86_64 -m 2048 -smp 2,cores=2,sockets=1 \
-kernel ./boot/vmlinuz-6.8.0-rc2 \
-initrd ./boot/initrd.img-6.8.0-rc2 \
-append "root=/dev/vda1 console=tty1 console=ttyS0 nokaslr" \
-netdev id=net00,type=user,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net00 \
-drive if=virtio,format=qcow2,file=ubuntu-22.04-minimal-cloudimg-amd64.img \
-drive if=virtio,format=raw,file=seed.raw \
-nographic -S -s
이제 gdb
를 통해서 1234
포트로 연결하고 c
명령어를 실행하면 정상적으로 부팅이되고 커널 디버깅이 가능합니다.