Post

[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개의 파일이 필요합니다.

0%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 명령어를 실행하면 정상적으로 부팅이되고 커널 디버깅이 가능합니다.

This post is licensed under CC BY 4.0 by the author.