Custom Linux Kernel Debugging With Ubuntu Cloud Image

· omacs's blog


Table of Contents

Introduction #

CIDY님은 우분투 클라우드 이미지를 사용하여 커널 디버깅 환경을 구축하는 방법을 설명하였다. 이 방법은 우분투 클라우드 이미지를 다운받아서 QEMU로 구동하고, 이 이미지에서 사용하는 소스와 디버깅 심볼을 GDB에 등록하여 디버깅을 하는 방식이다. 그리고 BACK ON TOP님은 우분투에서 커널 소스를 빌드하여 우분투에 설치하는 방법을 설명하였다. 본 글에서는 이 두 글을 기반으로 우분투에 빌드한 커널을 설치하고 디버깅하는 방법을 정리한다[1, 2].

Ubuntu Cloud Image Basic Setup #

먼저 ubuntu라는 이름의 디렉토리를 생성하자. 여기서는 이 디렉토리를 루트로 하여 설명할 것이다. 이제 우분투 이미지를 다운받는다.

1cd ubuntu/img/
2wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img

user-data.yaml 파일을 다음과 같이 작성하고

1#cloud-config
2ssh_pwauth: true
3chpasswd:
4  expire: false
5  users:
6    - name: ubuntu
7      password: '1234'
8      type: text

다음과 같이 이미지를 변환한다.

1cloud-localds seed.raw user-data.yaml
2qemu-img convert -f raw -O qcow2 seed.raw seed.img
3qemu-img resize jammy-server-cloudimg-amd64.img +45G

이때 45 기가 바이트나 주는 이유는 이후에 커널에 디버깅 정보를 포함하여 빌드할 때 상당한 용량을 요하기 때문이다. 그럼 다음과 같이 QEMU를 구동할 수 있다.

1sudo qemu-system-x86_64 -enable-kvm -m 4G -nographic \
2      -netdev id=net00,type=user,hostfwd=tcp::2222-:22 \
3      -device virtio-net-pci,netdev=net00 \
4      -drive if=virtio,format=qcow2,file=jammy-server-cloudimg-amd64.img \
5      -drive if=virtio,format=qcow2,file=seed.img

Linux Kernel Compilation with Debian Package in QEMU #

QEMU로 구동된 우분투에서 리눅스 커널 컴파일은 다음과 같이 할 수 있고, 여기서는 5.18 버전을 설치할 것이다[3].

 1sudo apt update
 2sudo apt install build-essential libncurses5 libncurses5-dev bin86 libssl-dev bison flex libelf-dev -y
 3wget https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.18.tar.gz
 4sudo tar zxf linux-5.18.tar.gz -C/usr/src
 5cd /usr/src/linux-5.18/
 6sudo cp /boot/config-5.15.0-179-generic ./.config
 7sudo make olddefconfig
 8sudo scripts/config --disable SYSTEM_TRUSTED_KEYS
 9sudo scripts/config --disable SYSTEM_REVOCATION_KEYS
10sudo make -j8 bindeb-pkg DEB_BUILD_OPTIONS="nostrip debug"

위 make 명령어에서 bindeb-pkg는 (여기서는) 리눅스 커널을 .deb 패키지로 만들며, DEB_BUILDOPTIONS를 설정해서 디버깅 정보를 포함하도록 만든다. 이때 컴파일 초반에 터미널에서 질문이 뜰 것인데 엔터키를 입력하여 기본값으로 넘어가면 된다[4].


컴파일이 완료되면 다음과 같이 .deb 파일이 생성된다.

 1$ ls /usr/src/
 2linux-5.18
 3linux-headers-5.15.0-179
 4linux-headers-5.15.0-179-generic
 5linux-headers-5.18.0
 6linux-headers-5.18.0_5.18.0-1_amd64.deb
 7linux-image-5.18.0-dbg_5.18.0-1_amd64.deb
 8linux-image-5.18.0_5.18.0-1_amd64.deb
 9linux-libc-dev_5.18.0-1_amd64.deb
10linux-upstream_5.18.0-1_amd64.buildinfo
11linux-upstream_5.18.0-1_amd64.changes
12$

그리고 다음과 같이 커널을 설치할 수 있다.

1cd /usr/src/
2sudo dpkg -i *.deb
3sudo update-grub
4sudo reboot

Debugging Environment Setup #

이제 디버깅 정보가 있는 파일을 사용하여 환경을 구성해보자. 먼저 QEMU 게스트에서 디버깅 관련 파일을 /tmp 디렉토리로 복사한다.

1tar cfz /tmp/boot.tar.gz /boot/*.18.0
2tar cfz /tmp/dbg.tar.gz /usr/src/linux-image-5.18.0-dbg_5.18.0-1_amd64.deb

그 다음, 호스트에서 다음과 같이 위 tarball 파일을 다운받는다.

1cd ubuntu/
2scp -P 2222 ubuntu@localhost:/tmp/boot.tar.gz ./
3scp -P 2222 ubuntu@localhost:/tmp/dbg.tar.gz ./
4tar zxf boot.tar.gz             # boot directory is generated
5tar zxf dbg.tar.gz              # usr directory is generated

앞서 우분투 게스트에서 설치한 커널이 5.18 버전이므로 호스트에도 다음과 같이 동일하게 구성한다.

 1cd ubuntu/
 2wget https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.18.tar.gz
 3tar zxf linux-5.18.tar.gz
 4cd usr/src/
 5sudo apt-get install ./linux-image-5.18.0-dbg_5.18.0-1_amd64.deb
 6cd ubuntu/linux-5.18/
 7cp /lib/debug/boot/vmlinux-5.18.0 ./vmlinux
 8cp -Rf /usr/lib/debug/lib/modules/5.18.0/* ./
 9cp ../boot/config-5.18.0 ./.config
10make olddefconfig
11make scripts_gdb
12echo -e "set auto-load safe-path /\n" >> ~/.gdbinit
13echo -e "add-auto-load-safe-path /\n" >> ~/.gdbinit
14sudo bash -c 'echo -e "set auto-load safe-path /\n" >> /root/.gdbinit'

Debugging Test #

앞서 /boot 디렉토리 내 파일을 복사한 것으로 다음과 같이 QEMU 스크립트를 작성할 수 있다.

 1#!/usr/bin/env bash
 2RAM="4G"
 3CPU_SOCKETS="1"
 4CPU_CORES="2"
 5CPU_THREADS="2"
 6args=(
 7  # kvm 켜기, RAM 크기 설정
 8  -enable-kvm -m "$RAM"
 9  # CPU 코어 설정
10  -smp "$CPU_THREADS",cores="$CPU_CORES",sockets="$CPU_SOCKETS"
11  # 사용할 kernel image
12  -kernel ./boot/vmlinuz-5.18.0
13  # 사용할 initial ramdisk
14  -initrd ./boot/initrd.img-5.18.0
15  # kernel command line, /dev/vda1을 mount하고, 시리얼 포트를 이용해 콘솔 연결, kaslr 꺼서 디버깅 편하도록
16  -append "root=/dev/vda1 console=tty1 console=ttyS0 nokaslr"
17    # host의 port 2222를 guest의 port 22로 포트 포워딩 
18  -netdev id=net00,type=user,hostfwd=tcp::2222-:22 
19  -device virtio-net-pci,netdev=net00
20  # 사용할 drive
21  -drive if=virtio,format=qcow2,file=./img/jammy-server-cloudimg-amd64.img
22  -drive if=virtio,format=qcow2,file=./img/seed.img
23    -nographic
24  # vnc로 접속 가능하도록 설정
25  # -vnc :1
26  # qemu monitor 사용 가능하도록 설정
27  # -monitor stdio
28  # gdb에서 target remote :1234로 attach할 수 있음
29  -s
30)
31qemu-system-x86_64 "${args[@]}" 2>&1

그리고 gdb를 QEMU에 연결하면 다음과 같이 소스 코드가 표시되면서 디버깅할 수 있음을 알 수 있다.

  1$ gdb -q ./vmlinux
  2Reading symbols from ./vmlinux...
  3------- tip of the day (disable with set show-tips off) -------
  4break-if-taken and break-if-not-taken commands sets breakpoints after a given jump instruction was taken or not
  5pwndbg> target remote :1234
  6Remote debugging using :1234
  70xffffffff825a91bb in native_safe_halt () at ./arch/x86/include/asm/irqflags.h:52
  852      }
  9Permission error when attempting to parse page tables with gdb-pt-dump.
 10Either change the kernel-vmmap setting, re-run GDB as root, or disable `ptrace_scope` (`echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope`)   
 11Warning: Avoided exploring possible address 0xffffffff825a9010.
 12You can explicitly explore it with `vmmap-explore 0xffffffff825a9000`                                                                            
 13Warning: Avoided exploring possible address 0xffffffff83425140.
 14You can explicitly explore it with `vmmap-explore 0xffffffff83425000`                                                                            
 15Warning: Avoided exploring possible address 0xffffffff8258ea6a.
 16You can explicitly explore it with `vmmap-explore 0xffffffff8258e000`                                                                            
 17Warning: Avoided exploring possible address 0xffffed102134857e.
 18You can explicitly explore it with `vmmap-explore 0xffffed1021348000`                                                                            
 19Warning: Avoided exploring possible address 0xffffffff82c5aaa0.
 20You can explicitly explore it with `vmmap-explore 0xffffffff82c5a000`                                                                            
 21Warning: Avoided exploring possible address 0xffff888109a42beb.
 22You can explicitly explore it with `vmmap-explore 0xffff888109a42000`                                                                            
 23Warning: Avoided exploring possible address 0xffffffff8414c1a0.
 24You can explicitly explore it with `vmmap-explore 0xffffffff8414c000`                                                                            
 25Warning: Avoided exploring possible address 0xffffffff83407db0.
 26You can explicitly explore it with `vmmap-explore 0xffffffff83407000`                                                                            
 27Warning: Avoided exploring possible address 0xffff88810465a000.
 28You can explicitly explore it with `vmmap-explore 0xffff88810465a000`                                                                            
 29Warning: Avoided exploring possible address 0xffff888109a00000.
 30You can explicitly explore it with `vmmap-explore 0xffff888109a00000`                                                                            
 31Warning: Avoided exploring possible address 0xffffffff81072c05.
 32You can explicitly explore it with `vmmap-explore 0xffffffff81072000`                                                                            
 33LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
 34─────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────
 35 RAX  0xffffffff825a9010 (default_idle) ◂— 0xffffffff825a9010
 36 RBX  0xffffffff83425140 (init_task) ◂— 0xffffffff83425140
 37 RCX  0xffffffff8258ea6a (rcu_eqs_enter.constprop+154) ◂— 0xffffffff8258ea6a
 38 RDX  0xffffed102134857e ◂— 0xffffed102134857e
 39 RDI  0xffffffff82c5aaa0 ◂— 0xffffffff82c5aaa0
 40 RSI  0xffffffff82c5aa60 ◂— 0xffffffff82c5aa60
 41 R8   1
 42 R9   0xffff888109a42beb ◂— 0xffff888109a42beb
 43 R10  0xffffed102134857d ◂— 0xffffed102134857d
 44 R11  1
 45 R12  0
 46 R13  0xffffffff8414c1a0 (__cpu_online_mask) ◂— 0xffffffff8414c1a0
 47 R14  0
 48 R15  0
 49 RBP  0xffffffff83407db0 (init_thread_union+32176) —▸ 0xffffffff83407dc0 (init_thread_union+32192) —▸ 0xffffffff83407de0 (init_thread_union+32224) —▸ 0xffffffff83407e90 (init_thread_union+32400) —▸ 0xffffffff83407ea8 (init_thread_union+32424) ◂— ...
 50 RSP  0xffffffff83407da8 (init_thread_union+32168) —▸ 0xffffffff825a901e (default_idle+14) ◂— 0xffffffff825a901e
 51 RIP  0xffffffff825a91bb (native_safe_halt+11) ◂— 0xffffffff825a91bb
 52 CR0  0x80050033 [ PE WP PG ]
 53 CR3  0x10465a000 [virtual: 0xffff88810465a000 ◂— 0xffff88810465a000]
 54 CR4  0x6f0 [ umip fsgsbase smep smap pke cet pks ]
 55 EFER 0xd01 [ NXE ]
 56 GS_BASE 0xffff888109a00000 ◂— 0xffff888109a00000
 57 FS_BASE 0
 58 CS   0x10    SS   0x18    DS   0x0    ES   0x0    FS   0x0    GS   0x0   
 59──────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────
 60 ► 0xffffffff825a91bb <native_safe_halt+11>    ret                                <default_idle+14>
 61 62   0xffffffff825a901e <default_idle+14>        nop    
 63   0xffffffff825a901f <default_idle+15>        pop    rbp
 64   0xffffffff825a9020 <default_idle+16>        ret    
 65
 66   0xffffffff825a9021 <default_idle+17>        int3   
 67   0xffffffff825a9022                          int3   
 68   0xffffffff825a9023                          int3   
 69   0xffffffff825a9024                          int3   
 70   0xffffffff825a9025                          int3   
 71   0xffffffff825a9026                          int3   
 72   0xffffffff825a9027                          int3   
 73────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────
 74In file: ubuntu/linux-5.18/arch/x86/include/asm/irqflags.h:52
 75   47 
 76   48 static inline __cpuidle void native_safe_halt(void)
 77   49 {
 78   50         mds_idle_clear_cpu_buffers();
 79   51         asm volatile("sti; hlt": : :"memory");
 80 ► 52 }
 81   53 
 82   54 static inline __cpuidle void native_halt(void)
 83   55 {
 84   56         mds_idle_clear_cpu_buffers();
 85   57         asm volatile("hlt": : :"memory");
 86────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────
 8700:0000│ rsp 0xffffffff83407da8 (init_thread_union+32168) —▸ 0xffffffff825a901e (default_idle+14) ◂— 0xffffffff825a901e
 8801:0008│ rbp 0xffffffff83407db0 (init_thread_union+32176) —▸ 0xffffffff83407dc0 (init_thread_union+32192) —▸ 0xffffffff83407de0 (init_thread_union+32224) —▸ 0xffffffff83407e90 (init_thread_union+32400) —▸ 0xffffffff83407ea8 (init_thread_union+32424) ◂— ...
 8902:0010│+008 0xffffffff83407db8 (init_thread_union+32184) —▸ 0xffffffff81072c05 (arch_cpu_idle+21) ◂— 0xffffffff81072c05
 9003:0018│+010 0xffffffff83407dc0 (init_thread_union+32192) —▸ 0xffffffff83407de0 (init_thread_union+32224) —▸ 0xffffffff83407e90 (init_thread_union+32400) —▸ 0xffffffff83407ea8 (init_thread_union+32424) —▸ 0xffffffff83407ed0 (init_thread_union+32464) ◂— ...
 9104:0020│+018 0xffffffff83407dc8 (init_thread_union+32200) —▸ 0xffffffff825a93b3 (default_idle_call+99) ◂— 0xffffffff825a93b3
 9205:0028│+020 0xffffffff83407dd0 (init_thread_union+32208) ◂— 0xffffffff83407dd0
 9306:0030│+028 0xffffffff83407dd8 (init_thread_union+32216) —▸ 0xffffffff83425140 (init_task) ◂— 0xffffffff83425140
 9407:0038│+030 0xffffffff83407de0 (init_thread_union+32224) —▸ 0xffffffff83407e90 (init_thread_union+32400) —▸ 0xffffffff83407ea8 (init_thread_union+32424) —▸ 0xffffffff83407ed0 (init_thread_union+32464) —▸ 0xffffffff83407ee0 (init_thread_union+32480) ◂— ...
 95──────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────
 96 ► 0 0xffffffff825a91bb native_safe_halt+11
 97   1 0xffffffff825a901e default_idle+14
 98   2 0xffffffff825a901e default_idle+14
 99   3 0xffffffff81072c05 arch_cpu_idle+21
100   4 0xffffffff825a93b3 default_idle_call+99
101   5 0xffffffff811b2578 do_idle+904
102   6 0xffffffff811b2578 do_idle+904
103   7 0xffffffff811b28f0 cpu_startup_entry+32
104──────────────────────────────────────────────────────────────[ THREADS (2 TOTAL) ]──────────────────────────────────────────────────────────────
105  ► 1   "" stopped: 0xffffffff825a91bb <native_safe_halt+11> 
106    2   "" stopped: 0xffffffff825a91bb <native_safe_halt+11> 
107─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
108pwndbg> 

이때 커널 모듈을 디버깅할 때는 GDB를 붙이기 전에 모듈을 로드하는 것이 좋은 것으로 보인다.

References #

  1. CIDY, "[Linux Kernel] Kernel Debugging Environment setup." orcinus-orca.tistory.com, Accessed: Jun. 01, 2026. [Online]. Available: https://orcinus-orca.tistory.com/246
  2. BACK ON TOP, "[Linux/Ubuntu] 리눅스 커널 컴파일, 시스템 호출." hjrrkd.tistory.com, Accessed: Jun. 01, 2026. [Online]. Available: https://hjrrkd.tistory.com/entry/LinuxUbuntu-%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BB%A4%EB%84%90-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%98%B8%EC%B6%9C
  3. ME74, "No rule to make target 'debian/canonical-revoked-certs.pem'." me74.tistory.com, Accessed: Jun. 01, 2026. [Online]. Available: https://me74.tistory.com/145
  4. "BuildADebianKernelPackage." wiki.debian.org, Accessed: Jun. 01, 2026. [Online]. Available: https://wiki.debian.org/BuildADebianKernelPackage
last updated: