본문 바로가기

Linux Kernel

Linux kernel debugging with qemu


x86-64 리눅스 커널을 디버깅 하는 방법입니다.

커널을 디버깅하기 위해 host-machine, target-machine이 필요한데 host-machine은 우분투 x86-64라고 가정하고 target-machine은 커널을 qemu에 올려서 vm으로 진행하겠습니다.

먼저 디버깅할 버전의 커널을 컴파일 해야합니다.

커널을 다운로드 받고 (http://kernel.org) 압축을 풀어서 커널 디렉토리로 이동한 후

make defconfig
make kvmconfig
make menuconfig

를 순서대로 실행해줍니다.

make menuconfig를 하면 옵션을 선택할 수 있는 창들이 뜰 텐데 여기서 

Kernel Hacking
    compile-time checks and compiler options
        compile the kernel with debug info 체크

를 해준 후 exit하면 make를 하기 위한 설정이 다 되었습니다.

이제 make를 해서 빌드를 시작하고 기다리면 됩니다.

빌드가 다 되었다면 arch/x86/boot/bzImage에 커널 파일이 생기고 최상위 디렉토리에 vmlinux파일이 정상적으로 생겼을 것입니다. 이 파일을 vm을 구성할 디렉토리로 옮겨줍니다.

다음은 파일시스템을 구성해야합니다.

sudo apt-get install debootstrap를 해서 다운로드 해주고 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/bin/bash
# Copyright 2016 syzkaller project authors. All rights reserved.
# Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
 
# create-image.sh creates a minimal Debian-wheezy Linux image suitable for syzkaller.
 
set -eux
 
# Create a minimal Debian-wheezy distributive as a directory.
RELEASE=wheezy
DIR=wheezy
sudo rm -rf $DIR
mkdir -p $DIR
sudo debootstrap --include=openssh-server,curl,tar,gcc,libc6-dev,time,strace,ltrace,sudo,less,psmisc,selinux-utils,policycoreutils,checkpolicy,selinux-policy-default,gdb $RELEASE $DIR
 
# Set some defaults and enable promtless ssh to the machine for root.
sudo sed -'/^root/ { s/:x:/::/ }' $DIR/etc/passwd
echo 'T0:23:respawn:/sbin/getty -L ttyS0 115200 vt100' | sudo tee -a $DIR/etc/inittab
printf '\nauto eth0\niface eth0 inet dhcp\n' | sudo tee -a $DIR/etc/network/interfaces
echo 'debugfs /sys/kernel/debug debugfs defaults 0 0' | sudo tee -a $DIR/etc/fstab
echo 'SELINUX=disabled' | sudo tee $DIR/etc/selinux/config
echo "kernel.printk = 7 4 1 3" | sudo tee -a $DIR/etc/sysctl.conf
echo 'debug.exception-trace = 0' | sudo tee -a $DIR/etc/sysctl.conf
echo "net.core.bpf_jit_enable = 1" | sudo tee -a $DIR/etc/sysctl.conf
echo "net.core.bpf_jit_harden = 2" | sudo tee -a $DIR/etc/sysctl.conf
echo "net.ipv4.ping_group_range = 0 65535" | sudo tee -a $DIR/etc/sysctl.conf
echo -en "127.0.0.1\tlocalhost\n" | sudo tee $DIR/etc/hosts
echo "nameserver 8.8.8.8" | sudo tee -a $DIR/etc/resolve.conf
echo "debugging-machine" | sudo tee $DIR/etc/hostname
ssh-keygen -f $RELEASE.id_rsa -t rsa -''
sudo mkdir -p $DIR/root/.ssh/
cat $RELEASE.id_rsa.pub | sudo tee $DIR/root/.ssh/authorized_keys
 
# Build a disk image
dd if=/dev/zero of=$RELEASE.img bs=1M seek=2047 count=1
sudo mkfs.ext4 -F $RELEASE.img
sudo mkdir -/mnt/$DIR
sudo mount -o loop $RELEASE.img /mnt/$DIR
sudo cp -a $DIR//mnt/$DIR/.
sudo umount /mnt/$DIR
cs

위 코드를 create-image.sh로 붙여넣고 실행해주면 됩니다.
(google syzkaller에서 가져옴 (https://github.com/google/syzkaller/blob/master/tools/create-image.sh))

wheezy.img까지 정상적으로 생성되었다면 이제 부팅을 할 시간입니다.

qemu가 없다면 sudo apt-get install qemu kvm qemu-kvm을 해서 설치해주고

qemu-system-x86_64 -kernel bzImage -append "console=ttyS0 root=/dev/sda debug" -hda wheezy.img -net user,hostfwd=tcp::10021-:22 -net nic -nographic -m 4G -smp 2 -s

을 이용해서 부팅할 수 있습니다.

kernel은 아까 컴파일한 bzImage파일을 사용하겠다는 의미이고 이미지는 wheezy.img, 22번 포트를 10021포트로 리다이렉팅 했기 때문에 ssh -p 10021 -i wheezy.id_rsa root@localhost를 이용해서 ssh접속을 할 수 있습니다.

그리고 메모리 4G, cpu코어 두개를 할당하고 1234번 포트로 gdb 디버깅이 들어오도록 listen하고 있겠다는 의미입니다.

만약 본인의 cpu가 가상화를 지원한다면 -enable-kvm 옵션을 추가해서 좀 더 빠르게 qemu를 돌릴 수 있습니다.

이제 정상적으로 부팅이 됐다면 디버깅을 해볼 차례입니다. qemu를 종료하고 gdb -q ./vmlinux를 해서 심볼을 로드합니다.

그 후 b*start_kernel을 해서 커널의 첫 시작 함수인 start_kernel에 브레이크 포인트를 걸고 target remote localhost:1234를 해서 1234번 포트로 디버기가 붙기를 기다립니다.

그 후 다시 qemu를 부팅한다면 gdb로 커널이 붙게됩니다. 그 상태에서 c를 입력해주면 start_kernel에 브레이크 포인트가 걸립니다.

심볼까지 로드했기 때문에 현재 어셈의 위치와 소스를 비교하면서 디버깅 할 수 있습니다.

이제 원하는 함수 / 드라이버에 브레이크 포인트를 걸고 디버깅하면 됩니다.