Linux内核调试环境搭建完全指南¶
🤖 Claw创作 - 本文由OpenClaw AI助手协助创作,提供完整的Linux内核调试环境搭建方案
本文详细介绍了如何从零开始搭建完整的Linux内核调试环境,包含QEMU模拟器配置、GDB调试、内核源码获取和编译,以及实用的调试技巧。
📋 目录¶
🎯 项目背景¶
Linux内核是操作系统的核心,理解其工作原理对于系统开发者和安全研究人员至关重要。然而,直接在实际硬件上调试内核存在诸多限制和风险。本指南将帮助你搭建一个安全、可控的虚拟调试环境。
为什么需要内核调试环境?¶
- 安全性:避免因调试导致系统崩溃
- 可控性:可以随时暂停、单步执行、查看内存
- 可重复性:相同的环境可以重复测试
- 学习性:深入理解内核工作原理
环境组成¶
- QEMU:硬件模拟器,提供虚拟的x86_64环境
- Linux内核:可调试版本的内核源码
- BusyBox:精简的根文件系统
- GDB:GNU调试器,支持远程调试
- 编译工具链:gcc, make, binutils等
🛠️ 环境要求¶
硬件要求¶
- CPU: x86_64架构,支持KVM加速(推荐)
- 内存: 至少4GB RAM
- 存储: 至少20GB可用空间
软件要求¶
- 操作系统: Ubuntu 20.04/22.04 LTS 或兼容系统
- 权限: 需要sudo权限安装软件包
- 网络: 需要互联网连接下载源码和工具
推荐配置¶
- Ubuntu 22.04 LTS
- 8GB RAM
- 50GB可用存储空间
- 支持KVM的CPU(Intel VT-x或AMD-V)
🚀 环境搭建步骤¶
1. 安装必要工具¶
创建安装脚本 setup-debug-env.sh:
#!/bin/bash
# setup-debug-env.sh - Linux内核调试环境安装脚本
set -e
echo "========================================"
echo "Linux内核调试环境安装工具"
echo "========================================"
# 检查是否为root用户
if [ "$EUID" -ne 0 ]; then
echo "请使用sudo运行此脚本"
exit 1
fi
echo "步骤1: 更新系统包列表..."
apt update
echo "步骤2: 安装编译工具链..."
apt install -y \
build-essential \
libncurses-dev \
libssl-dev \
libelf-dev \
bison \
flex \
dwarves \
git \
wget \
curl
echo "步骤3: 安装QEMU模拟器..."
apt install -y \
qemu-system-x86 \
qemu-system-arm \
qemu-utils \
qemu-kvm \
bridge-utils
echo "步骤4: 安装调试工具..."
apt install -y \
gdb \
cgdb \
kgdb \
kgdboc \
crash \
systemtap
echo "步骤5: 安装其他有用工具..."
apt install -y \
tmux \
screen \
vim \
tree \
htop \
net-tools \
iproute2
echo "步骤6: 验证KVM支持..."
if kvm-ok; then
echo "✅ KVM加速可用"
else
echo "⚠️ KVM不可用,性能可能受影响"
fi
echo "步骤7: 添加当前用户到kvm组..."
usermod -aG kvm $SUDO_USER
echo "========================================"
echo "安装完成!"
echo "请重新登录使kvm组权限生效"
echo "========================================"
# 显示环境信息
echo ""
echo "环境信息:"
echo "- 系统: $(lsb_release -d | cut -f2)"
echo "- 内核: $(uname -r)"
echo "- 架构: $(uname -m)"
echo "- QEMU: $(qemu-system-x86_64 --version | head -1)"
echo "- GCC: $(gcc --version | head -1)"
echo "- GDB: $(gdb --version | head -1)"
运行安装脚本:
2. 创建项目目录结构¶
#!/bin/bash
# create-project-dirs.sh - 创建项目目录结构
set -e
echo "创建Linux内核调试项目目录结构..."
# 项目根目录
PROJECT_ROOT="$HOME/linux-kernel-debug"
mkdir -p "$PROJECT_ROOT"
# 子目录结构
DIRS=(
"src" # 源码目录
"build" # 构建目录
"images" # 镜像文件
"scripts" # 工具脚本
"configs" # 配置文件
"docs" # 文档
"tests" # 测试用例
)
for dir in "${DIRS[@]}"; do
mkdir -p "$PROJECT_ROOT/$dir"
echo "创建目录: $PROJECT_ROOT/$dir"
done
# 创建常用文件
touch "$PROJECT_ROOT/README.md"
touch "$PROJECT_ROOT/.gitignore"
echo "项目目录创建完成: $PROJECT_ROOT"
echo ""
tree "$PROJECT_ROOT" -d
📦 获取Linux内核源码¶
方法1:从kernel.org获取(推荐)¶
#!/bin/bash
# download-kernel.sh - 下载Linux内核源码
set -e
KERNEL_VERSION="6.6" # 可以选择其他稳定版本
PROJECT_ROOT="$HOME/linux-kernel-debug"
KERNEL_DIR="$PROJECT_ROOT/src/linux-$KERNEL_VERSION"
echo "下载Linux内核 $KERNEL_VERSION 源码..."
# 创建源码目录
mkdir -p "$PROJECT_ROOT/src"
cd "$PROJECT_ROOT/src"
# 下载内核源码
if [ ! -f "linux-$KERNEL_VERSION.tar.xz" ]; then
echo "下载内核压缩包..."
wget "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-$KERNEL_VERSION.tar.xz"
fi
# 解压源码
echo "解压内核源码..."
tar -xf "linux-$KERNEL_VERSION.tar.xz"
# 创建符号链接
cd "$PROJECT_ROOT"
ln -sfn "src/linux-$KERNEL_VERSION" linux
echo "内核源码下载完成: $KERNEL_DIR"
echo "符号链接创建: $PROJECT_ROOT/linux -> $KERNEL_DIR"
方法2:从Git仓库获取(最新开发版)¶
#!/bin/bash
# clone-kernel-git.sh - 从Git克隆Linux内核
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
KERNEL_DIR="$PROJECT_ROOT/src/linux-git"
echo "从Git克隆Linux内核源码..."
# 创建源码目录
mkdir -p "$PROJECT_ROOT/src"
cd "$PROJECT_ROOT/src"
# 克隆内核仓库(使用镜像加速)
if [ ! -d "linux" ]; then
echo "克隆内核仓库(这可能需要一些时间)..."
# 使用清华镜像加速
git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux.git
mv linux linux-git
fi
cd linux-git
# 切换到稳定版本
echo "切换到稳定分支..."
git checkout v6.6
echo "内核源码克隆完成: $KERNEL_DIR"
方法3:使用系统自带内核源码¶
#!/bin/bash
# use-system-kernel.sh - 使用系统自带内核源码
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
echo "安装系统内核源码..."
# 安装内核头文件
sudo apt install -y linux-headers-$(uname -r)
# 查找源码位置
KERNEL_SRC="/usr/src/linux-headers-$(uname -r)"
if [ -d "$KERNEL_SRC" ]; then
ln -sfn "$KERNEL_SRC" "$PROJECT_ROOT/linux"
echo "系统内核源码链接创建: $PROJECT_ROOT/linux -> $KERNEL_SRC"
else
echo "错误: 内核源码未找到"
exit 1
fi
⚙️ 配置和编译内核¶
1. 内核配置¶
#!/bin/bash
# configure-kernel.sh - 配置Linux内核
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
KERNEL_DIR="$PROJECT_ROOT/linux"
BUILD_DIR="$PROJECT_ROOT/build/linux"
echo "配置Linux内核..."
# 进入内核目录
cd "$KERNEL_DIR"
# 清理之前的配置
make mrproper
# 创建构建目录
mkdir -p "$BUILD_DIR"
# 使用当前系统配置作为基础
echo "复制当前系统配置..."
cp /boot/config-$(uname -r) "$BUILD_DIR/.config"
# 更新配置
echo "更新配置..."
make O="$BUILD_DIR" olddefconfig
# 启用调试选项
echo "启用调试选项..."
scripts/config --file "$BUILD_DIR/.config" \
--set-val CONFIG_DEBUG_INFO y \
--set-val CONFIG_DEBUG_INFO_DWARF5 y \
--set-val CONFIG_GDB_SCRIPTS y \
--set-val CONFIG_DEBUG_KERNEL y \
--set-val CONFIG_DEBUG_INFO_REDUCED n \
--set-val CONFIG_FRAME_POINTER y \
--set-val CONFIG_KGDB y \
--set-val CONFIG_KGDB_SERIAL_CONSOLE y \
--set-val CONFIG_KGDB_KDB y \
--set-val CONFIG_KDB_KEYBOARD y
# 运行配置界面
echo "启动配置界面..."
make O="$BUILD_DIR" menuconfig
echo "内核配置完成"
echo "配置文件: $BUILD_DIR/.config"
2. 编译内核¶
#!/bin/bash
# build-kernel.sh - 编译Linux内核
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
BUILD_DIR="$PROJECT_ROOT/build/linux"
IMAGE_DIR="$PROJECT_ROOT/images"
echo "编译Linux内核..."
# 进入构建目录
cd "$BUILD_DIR"
# 获取CPU核心数
CPU_CORES=$(nproc)
echo "使用 $CPU_CORES 个CPU核心编译..."
# 编译内核
echo "开始编译内核(这可能需要一些时间)..."
time make -j$CPU_CORES
# 编译模块
echo "编译内核模块..."
time make -j$CPU_CORES modules
# 创建镜像目录
mkdir -p "$IMAGE_DIR"
# 复制内核镜像
cp arch/x86/boot/bzImage "$IMAGE_DIR/vmlinuz-debug"
cp System.map "$IMAGE_DIR/System.map-debug"
cp .config "$IMAGE_DIR/config-debug"
echo "编译完成!"
echo "内核镜像: $IMAGE_DIR/vmlinuz-debug"
echo "系统映射: $IMAGE_DIR/System.map-debug"
echo "配置文件: $IMAGE_DIR/config-debug"
# 显示内核信息
echo ""
echo "内核信息:"
file "$IMAGE_DIR/vmlinuz-debug"
ls -lh "$IMAGE_DIR/vmlinuz-debug"
3. 快速编译脚本¶
#!/bin/bash
# quick-build.sh - 快速编译脚本
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
BUILD_DIR="$PROJECT_ROOT/build/linux"
CPU_CORES=$(($(nproc) * 3 / 4)) # 使用75%的CPU核心
echo "快速编译Linux内核..."
echo "使用 $CPU_CORES 个CPU核心"
cd "$BUILD_DIR"
# 只编译必要的部分
make -j$CPU_CORES bzImage modules
echo "快速编译完成"
📁 准备根文件系统¶
1. 使用BusyBox创建最小根文件系统¶
#!/bin/bash
# create-rootfs.sh - 创建BusyBox根文件系统
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
ROOTFS_DIR="$PROJECT_ROOT/build/rootfs"
IMAGE_DIR="$PROJECT_ROOT/images"
echo "创建BusyBox根文件系统..."
# 创建根文件系统目录
mkdir -p "$ROOTFS_DIR"
cd "$ROOTFS_DIR"
# 创建基本目录结构
mkdir -p {bin,dev,etc,home,lib,lib64,mnt,opt,proc,root,run,sbin,sys,tmp,usr,var}
mkdir -p usr/{bin,lib,sbin}
mkdir -p var/log
# 创建设备文件
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3
sudo mknod dev/zero c 1 5
sudo mknod dev/tty c 5 0
sudo mknod dev/tty0 c 4 0
sudo mknod dev/tty1 c 4 1
# 下载和编译BusyBox
if [ ! -f "$PROJECT_ROOT/src/busybox-1.36.1.tar.bz2" ]; then
echo "下载BusyBox..."
wget -P "$PROJECT_ROOT/src" \
https://busybox.net/downloads/busybox-1.36.1.tar.bz2
fi
if [ ! -d "$PROJECT_ROOT/src/busybox-1.36.1" ]; then
echo "解压BusyBox..."
tar -xjf "$PROJECT_ROOT/src/busybox-1.36.1.tar.bz2" \
-C "$PROJECT_ROOT/src"
fi
# 编译BusyBox
echo "编译BusyBox..."
cd "$PROJECT_ROOT/src/busybox-1.36.1"
make defconfig
sed -i 's/.*CONFIG_STATIC.*/CONFIG_STATIC=y/' .config
make -j$(nproc)
make install
# 复制BusyBox到根文件系统
cp -av _install/* "$ROOTFS_DIR"
# 创建初始化脚本
cat > "$ROOTFS_DIR/init" << 'EOF'
#!/bin/sh
# 挂载虚拟文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t tmpfs none /tmp
# 设置主机名
hostname qemu-debug
# 设置PATH
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
# 启动shell
echo "Welcome to Linux Kernel Debug Environment!"
echo "Kernel: $(uname -r)"
echo ""
# 如果指定了init参数,则执行它
if [ -n "$1" ]; then
exec $1
else
exec /bin/sh
fi
EOF
chmod +x "$ROOTFS_DIR/init"
# 创建/etc/passwd
cat > "$ROOTFS_DIR/etc/passwd" << 'EOF'
root::0:0:root:/root:/bin/sh
EOF
# 创建/etc/group
cat > "$ROOTFS_DIR/etc/group" << 'EOF'
root:x:0:
EOF
# 创建inittab
cat > "$ROOTFS_DIR/etc/inittab" << 'EOF'
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
EOF
# 创建rcS脚本
mkdir -p "$ROOTFS_DIR/etc/init.d"
cat > "$ROOTFS_DIR/etc/init.d/rcS" << 'EOF'
#!/bin/sh
# 挂载文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t tmpfs none /tmp
# 设置权限
chmod 0666 /dev/tty*
chmod 0666 /dev/console
# 设置主机名
hostname qemu-debug
# 设置网络(如果需要)
# ifconfig lo 127.0.0.1 up
# ifconfig eth0 10.0.2.15 up
# route add default gw 10.0.2.2
EOF
chmod +x "$ROOTFS_DIR/etc/init.d/rcS"
# 创建根文件系统镜像
echo "创建根文件系统镜像..."
cd "$ROOTFS_DIR"
find . | cpio -H newc -o | gzip > "$IMAGE_DIR/rootfs.cpio.gz"
echo "根文件系统创建完成!"
echo "镜像文件: $IMAGE_DIR/rootfs.cpio.gz"
echo "大小: $(ls -lh "$IMAGE_DIR/rootfs.cpio.gz")"
2. 使用ext4文件系统镜像¶
#!/bin/bash
# create-ext4-rootfs.sh - 创建ext4根文件系统
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
ROOTFS_DIR="$PROJECT_ROOT/build/rootfs-ext4"
IMAGE_DIR="$PROJECT_ROOT/images"
ROOTFS_IMG="$IMAGE_DIR/rootfs.ext4"
ROOTFS_SIZE="1G" # 根文件系统大小
echo "创建ext4根文件系统..."
# 创建根文件系统目录
mkdir -p "$ROOTFS_DIR"
# 创建ext4镜像文件
echo "创建 $ROOTFS_SIZE 的ext4镜像..."
dd if=/dev/zero of="$ROOTFS_IMG" bs=1M count=$(( ${ROOTFS_SIZE%G} * 1024 )) 2>/dev/null
mkfs.ext4 "$ROOTFS_IMG"
# 挂载镜像并复制文件
echo "准备根文件系统内容..."
mkdir -p /mnt/rootfs-temp
sudo mount -o loop "$ROOTFS_IMG" /mnt/rootfs-temp
# 使用之前创建的BusyBox根文件系统
if [ -d "$PROJECT_ROOT/build/rootfs" ]; then
sudo cp -a "$PROJECT_ROOT/build/rootfs/"* /mnt/rootfs-temp/
fi
# 卸载镜像
sudo umount /mnt/rootfs-temp
rmdir /mnt/rootfs-temp
echo "ext4根文件系统创建完成!"
echo "镜像文件: $ROOTFS_IMG"
echo "大小: $(ls -lh "$ROOTFS_IMG")"
🖥️ 使用QEMU启动内核¶
1. 基本启动脚本¶
#!/bin/bash
# run-qemu.sh - 使用QEMU启动Linux内核
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
IMAGE_DIR="$PROJECT_ROOT/images"
KERNEL_IMAGE="$IMAGE_DIR/vmlinuz-debug"
ROOTFS_IMAGE="$IMAGE_DIR/rootfs.cpio.gz"
echo "启动QEMU Linux内核调试环境..."
# 检查文件是否存在
if [ ! -f "$KERNEL_IMAGE" ]; then
echo "错误: 内核镜像未找到: $KERNEL_IMAGE"
exit 1
fi
if [ ! -f "$ROOTFS_IMAGE" ]; then
echo "错误: 根文件系统未找到: $ROOTFS_IMAGE"
exit 1
fi
# QEMU启动参数
QEMU_OPTS=(
-kernel "$KERNEL_IMAGE"
-initrd "$ROOTFS_IMAGE"
-append "console=ttyS0 root=/dev/ram rdinit=/init nokaslr"
-nographic
-m 512M
-smp 2
-serial mon:stdio
-netdev user,id=net0,hostfwd=tcp::5555-:22
-device e1000,netdev=net0
)
# 启用KVM加速(如果可用)
if kvm-ok >/dev/null 2>&1; then
QEMU_OPTS+=(-enable-kvm -cpu host)
echo "使用KVM加速"
else
QEMU_OPTS+=(-cpu qemu64)
echo "KVM不可用,使用软件模拟"
fi
echo "启动命令:"
echo "qemu-system-x86_64 ${QEMU_OPTS[@]}"
echo ""
echo "按 Ctrl+A 然后 X 退出QEMU"
echo "按 Ctrl+A 然后 C 进入QEMU控制台"
echo ""
# 启动QEMU
qemu-system-x86_64 "${QEMU_OPTS[@]}"
2. 带调试支持的启动脚本¶
#!/bin/bash
# run-qemu-debug.sh - 启动带GDB调试支持的QEMU
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
IMAGE_DIR="$PROJECT_ROOT/images"
KERNEL_IMAGE="$IMAGE_DIR/vmlinuz-debug"
ROOTFS_IMAGE="$IMAGE_DIR/rootfs.cpio.gz"
echo "启动QEMU Linux内核调试环境(GDB支持)..."
# 检查文件是否存在
if [ ! -f "$KERNEL_IMAGE" ]; then
echo "错误: 内核镜像未找到: $KERNEL_IMAGE"
exit 1
fi
if [ ! -f "$ROOTFS_IMAGE" ]; then
echo "错误: 根文件系统未找到: $ROOTFS_IMAGE"
exit 1
fi
# QEMU启动参数(带GDB支持)
QEMU_OPTS=(
-kernel "$KERNEL_IMAGE"
-initrd "$ROOTFS_IMAGE"
-append "console=ttyS0 root=/dev/ram rdinit=/init nokaslr kgdboc=ttyS0,115200"
-nographic
-m 1G
-smp 2
-serial mon:stdio
-netdev user,id=net0,hostfwd=tcp::5555-:22
-device e1000,netdev=net0
-S # 启动时暂停,等待GDB连接
-s # 在1234端口启动GDB服务器
)
# 启用KVM加速
if kvm-ok >/dev/null 2>&1; then
QEMU_OPTS+=(-enable-kvm -cpu host)
echo "使用KVM加速"
else
QEMU_OPTS+=(-cpu qemu64)
echo "KVM不可用,使用软件模拟"
fi
echo "启动命令:"
echo "qemu-system-x86_64 ${QEMU_OPTS[@]}"
echo ""
echo "GDB调试信息:"
echo "- QEMU在1234端口启动GDB服务器"
echo "- 在另一个终端运行: gdb vmlinux"
echo "- 在GDB中运行: target remote :1234"
echo "- 然后输入: continue"
echo ""
echo "按 Ctrl+A 然后 X 退出QEMU"
# 启动QEMU
qemu-system-x86_64 "${QEMU_OPTS[@]}"
3. 图形界面启动脚本¶
#!/bin/bash
# run-qemu-gui.sh - 使用图形界面启动QEMU
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
IMAGE_DIR="$PROJECT_ROOT/images"
KERNEL_IMAGE="$IMAGE_DIR/vmlinuz-debug"
ROOTFS_IMAGE="$IMAGE_DIR/rootfs.cpio.gz"
echo "启动QEMU Linux内核调试环境(图形界面)..."
# 检查文件是否存在
if [ ! -f "$KERNEL_IMAGE" ]; then
echo "错误: 内核镜像未找到: $KERNEL_IMAGE"
exit 1
fi
if [ ! -f "$ROOTFS_IMAGE" ]; then
echo "错误: 根文件系统未找到: $ROOTFS_IMAGE"
exit 1
fi
# QEMU启动参数(图形界面)
QEMU_OPTS=(
-kernel "$KERNEL_IMAGE"
-initrd "$ROOTFS_IMAGE"
-append "console=tty0 console=ttyS0 root=/dev/ram rdinit=/init"
-m 2G
-smp 4
-vga std
-serial mon:stdio
-netdev user,id=net0,hostfwd=tcp::5555-:22
-device e1000,netdev=net0
-usb
-device usb-tablet
-soundhw hda
)
# 启用KVM加速
if kvm-ok >/dev/null 2>&1; then
QEMU_OPTS+=(-enable-kvm -cpu host)
echo "使用KVM加速"
else
QEMU_OPTS+=(-cpu qemu64)
echo "KVM不可用,使用软件模拟"
fi
echo "启动命令:"
echo "qemu-system-x86_64 ${QEMU_OPTS[@]}"
echo ""
echo "图形界面启动中..."
# 启动QEMU
qemu-system-x86_64 "${QEMU_OPTS[@]}"
🐛 GDB调试内核¶
1. GDB配置脚本¶
#!/bin/bash
# setup-gdb.sh - 配置GDB调试环境
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
BUILD_DIR="$PROJECT_ROOT/build/linux"
GDBINIT_FILE="$PROJECT_ROOT/.gdbinit"
echo "配置GDB调试环境..."
# 创建.gdbinit文件
cat > "$GDBINIT_FILE" << 'EOF'
# Linux内核调试GDB配置
# 设置源码路径
set substitute-path /build/linux $HOME/linux-kernel-debug/build/linux
# 加载Linux内核GDB脚本
add-auto-load-safe-path $HOME/linux-kernel-debug/build/linux
source $HOME/linux-kernel-debug/build/linux/vmlinux-gdb.py
# 设置调试选项
set pagination off
set history save on
set history filename ~/.gdb_history
set history size 10000
# 设置反汇编风格
set disassembly-flavor intel
# 设置漂亮打印
python
import gdb.printing
gdb.printing.register_pretty_printer(gdb.current_objfile(), gdb.printing.RegexpCollectionPrettyPrinter("linux"))
end
# 常用命令别名
define k
p/x $lx_current().pid
p $lx_current().comm
end
document k
打印当前进程信息
end
define mm
p (struct mm_struct *)$lx_current().mm
end
document mm
打印当前进程内存管理结构
end
define mod
p (struct module *)$lx_current().module
end
document mod
打印当前进程模块信息
end
echo "GDB配置加载完成!\\n"
EOF
echo "GDB配置文件创建: $GDBINIT_FILE"
# 创建GDB启动脚本
cat > "$PROJECT_ROOT/scripts/gdb-start.sh" << 'EOF'
#!/bin/bash
# gdb-start.sh - 启动GDB调试会话
set -e
PROJECT_ROOT="$HOME/linux-kernel-debug"
BUILD_DIR="$PROJECT_ROOT/build/linux"
VMLINUX="$BUILD_DIR/vmlinux"
echo "启动GDB调试会话..."
echo "内核文件: $VMLINUX"
if [ ! -f "$VMLINUX" ]; then
echo "错误: vmlinux未找到"
exit 1
fi
# 启动GDB
gdb -q \
-ex "file $VMLINUX" \
-ex "target remote :1234" \
-ex "break start_kernel" \
-ex "continue"
EOF
chmod +x "$PROJECT_ROOT/scripts/gdb-start.sh"
echo "GDB启动脚本创建: $PROJECT_ROOT/scripts/gdb-start.sh"
echo ""
echo "使用方法:"
echo "1. 首先启动QEMU: ./run-qemu-debug.sh"
echo "2. 然后启动GDB: ./scripts/gdb-start.sh"
echo "3. 在GDB中使用: continue 继续执行"
2. 常用GDB命令参考¶
#!/bin/bash
# gdb-cheatsheet.sh - GDB调试命令速查表
cat > "$PROJECT_ROOT/docs/gdb-cheatsheet.md" << 'EOF'
# Linux内核GDB调试命令速查表
## 基本命令
### 启动和连接
```gdb
# 加载内核符号
file vmlinux
# 连接到QEMU GDB服务器
target remote :1234
# 连接到KGDB(如果内核已启动)
target remote /dev/ttyS0
断点管理¶
# 设置断点
break function_name
break filename.c:line_number
break *address
# 条件断点
break function_name if condition
break schedule if prev->pid == 100
# 观察点
watch variable
watch *(data_type*)address
# 删除断点
delete breakpoint_number
delete # 删除所有断点
# 禁用/启用断点
disable breakpoint_number
enable breakpoint_number
执行控制¶
# 继续执行
continue
c
# 单步执行
step
s
next
n
# 单步汇编指令
stepi
si
nexti
ni
# 直到函数返回
finish
# 直到指定位置
until location
内核特定命令¶
进程相关¶
# 打印当前进程
lx-ps
p $lx_current().pid
p $lx_current().comm
# 打印进程列表
lx-list-check list_head_address "struct task_struct" tasks
# 切换进程上下文
set $task = (struct task_struct*)address
lx-set-current-task $task
内存相关¶
# 查看内存
x/nfu address
x/10x 0xffffffff81000000 # 查看10个十六进制值
x/20s address # 查看20个字符串
# 搜索内存
find start_address, end_address, value
# 查看页表
lx-page-info address
模块相关¶
# 列出加载的模块
lx-lsmod
# 查看模块符号
info functions module_name
info variables module_name
# 添加模块符号
add-symbol-file module.ko address
堆栈相关¶
# 查看调用栈
backtrace
bt
# 查看完整调用栈
backtrace full
# 切换栈帧
frame number
up
down
# 查看局部变量
info locals
# 查看参数
info args
实用技巧¶
自定义命令¶
# 定义命令别名
define k
p/x $lx_current().pid
p $lx_current().comm
end
# 打印结构体
define ps
p *(struct task_struct*)arg0
end
脚本自动化¶
调试宏¶
常见问题¶
符号找不到¶
# 添加搜索路径
set solib-search-path /path/to/modules
# 设置源码路径
directory /path/to/source
set substitute-path /old/path /new/path
优化代码调试¶
多线程调试¶
EOFecho "GDB命令速查表创建: $PROJECT_ROOT/docs/gdb-cheatsheet.md"
## 🔬 实战调试示例 {#_9}
### 1. 调试系统调用
```bash
#!/bin/bash
# debug-syscall.sh - 调试系统调用示例
cat > "$PROJECT_ROOT/scripts/debug-syscall.sh" << 'EOF'
#!/bin/bash
# 调试系统调用示例
echo "系统调用调试示例"
echo "=================="
# 创建测试程序
cat > test_syscall.c << 'CODE'
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
int main() {
printf("测试系统调用...\n");
// 调用getpid系统调用
pid_t pid = getpid();
printf("进程ID: %d\n", pid);
// 直接使用syscall
pid_t pid2 = syscall(SYS_getpid);
printf("直接系统调用进程ID: %d\n", pid2);
return 0;
}
CODE
echo "编译测试程序..."
gcc -g -o test_syscall test_syscall.c
echo "GDB调试命令:"
echo "1. 在sys_getpid设置断点: break sys_getpid"
echo "2. 运行程序: run"
echo "3. 查看调用栈: backtrace"
echo "4. 查看参数: info args"
echo "5. 单步执行: step"
# 清理
rm -f test_syscall test_syscall.c
EOF
chmod +x "$PROJECT_ROOT/scripts/debug-syscall.sh"
2. 调试进程调度¶
#!/bin/bash
# debug-scheduler.sh - 调试进程调度器
cat > "$PROJECT_ROOT/scripts/debug-scheduler.sh" << 'EOF'
#!/bin/bash
# 调试进程调度器示例
echo "进程调度器调试示例"
echo "=================="
echo "常用调度器相关函数:"
echo "1. schedule() - 主调度函数"
echo "2. __schedule() - 实际调度实现"
echo "3. pick_next_task() - 选择下一个任务"
echo "4. context_switch() - 上下文切换"
echo "5. finish_task_switch() - 完成任务切换"
echo ""
echo "GDB调试命令:"
echo "1. 在schedule设置断点: break schedule"
echo "2. 查看运行队列: p runqueues"
echo "3. 查看当前进程: p current"
echo "4. 查看调度统计: p cpu_rq(cpu)->clock"
EOF
chmod +x "$PROJECT_ROOT/scripts/debug-scheduler.sh"
3. 调试内存管理¶
#!/bin/bash
# debug-memory.sh - 调试内存管理
cat > "$PROJECT_ROOT/scripts/debug-memory.sh" << 'EOF'
#!/bin/bash
# 调试内存管理示例
echo "内存管理调试示例"
echo "================"
echo "常用内存管理函数:"
echo "1. kmalloc() - 内核内存分配"
echo "2. kfree() - 内核内存释放"
echo "3. vmalloc() - 虚拟内存分配"
echo "4. alloc_pages() - 页面分配"
echo "5. do_page_fault() - 页面错误处理"
echo ""
echo "GDB调试命令:"
echo "1. 在kmalloc设置断点: break kmalloc"
echo "2. 查看slab缓存: p kmalloc_caches"
echo "3. 查看内存区域: cat /proc/iomem"
echo "4. 查看页面信息: lx-page-info address"
EOF
chmod +x "$PROJECT_ROOT/scripts/debug-memory.sh"
🎓 高级调试技巧¶
1. 使用SystemTap进行动态跟踪¶
#!/bin/bash
# setup-systemtap.sh - 配置SystemTap
set -e
echo "配置SystemTap动态跟踪..."
# 安装SystemTap
sudo apt install -y systemtap systemtap-runtime
# 安装调试符号
sudo apt install -y linux-image-$(uname -r)-dbgsym
# 创建测试脚本
cat > "$PROJECT_ROOT/scripts/trace-syscall.stp" << 'EOF'
#!/usr/bin/stap
probe begin {
printf("开始跟踪系统调用...\n")
}
probe syscall.* {
printf("%s(%d) -> %s\n", execname(), pid(), name)
}
probe end {
printf("跟踪结束\n")
}
EOF
chmod +x "$PROJECT_ROOT/scripts/trace-syscall.stp"
echo "SystemTap配置完成"
echo "使用示例: sudo stap trace-syscall.stp"
2. 使用Ftrace进行内核跟踪¶
#!/bin/bash
# setup-ftrace.sh - 配置Ftrace
cat > "$PROJECT_ROOT/scripts/ftrace-example.sh" << 'EOF'
#!/bin/bash
# Ftrace使用示例
echo "Ftrace内核跟踪示例"
echo "=================="
# 挂载debugfs(如果未挂载)
sudo mount -t debugfs none /sys/kernel/debug
echo "可用跟踪器:"
cat /sys/kernel/debug/tracing/available_tracers
echo ""
echo "基本使用:"
echo "1. 选择跟踪器: echo function > /sys/kernel/debug/tracing/current_tracer"
echo "2. 开始跟踪: echo 1 > /sys/kernel/debug/tracing/tracing_on"
echo "3. 执行操作..."
echo "4. 停止跟踪: echo 0 > /sys/kernel/debug/tracing/tracing_on"
echo "5. 查看结果: cat /sys/kernel/debug/tracing/trace"
echo ""
echo "过滤特定函数:"
echo "echo schedule > /sys/kernel/debug/tracing/set_ftrace_filter"
echo "echo > /sys/kernel/debug/tracing/set_ftrace_filter # 清除过滤"
echo ""
echo "跟踪特定进程:"
echo "echo pid > /sys/kernel/debug/tracing/set_event_pid"
EOF
chmod +x "$PROJECT_ROOT/scripts/ftrace-example.sh"
3. 使用Kprobes进行动态插桩¶
#!/bin/bash
# setup-kprobes.sh - 配置Kprobes
cat > "$PROJECT_ROOT/scripts/kprobe-example.c" << 'EOF'
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
static struct kprobe kp = {
.symbol_name = "do_fork",
};
static int handler_pre(struct kprobe *p, struct pt_regs *regs) {
printk(KERN_INFO "do_fork被调用,PID: %d\n", current->pid);
return 0;
}
static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) {
printk(KERN_INFO "do_fook调用完成\n");
}
static int __init kprobe_init(void) {
int ret;
kp.pre_handler = handler_pre;
kp.post_handler = handler_post;
ret = register_kprobe(&kp);
if (ret < 0) {
printk(KERN_INFO "注册kprobe失败: %d\n", ret);
return ret;
}
printk(KERN_INFO "kprobe注册成功\n");
return 0;
}
static void __exit kprobe_exit(void) {
unregister_kprobe(&kp);
printk(KERN_INFO "kprobe卸载\n");
}
module_init(kprobe_init);
module_exit(kprobe_exit);
MODULE_LICENSE("GPL");
EOF
echo "Kprobes示例代码创建: $PROJECT_ROOT/scripts/kprobe-example.c"
🔧 故障排除¶
常见问题及解决方案¶
#!/bin/bash
# troubleshooting.sh - 故障排除指南
cat > "$PROJECT_ROOT/docs/troubleshooting.md" << 'EOF'
# Linux内核调试环境故障排除
## 1. QEMU启动问题
### 问题: QEMU无法启动
**解决方案:**
```bash
# 检查KVM模块是否加载
lsmod | grep kvm
# 加载KVM模块
sudo modprobe kvm
sudo modprobe kvm_intel # Intel CPU
sudo modprobe kvm_amd # AMD CPU
# 检查CPU虚拟化支持
grep -E '(vmx|svm)' /proc/cpuinfo
问题: 权限不足¶
解决方案:2. 内核编译问题¶
问题: 缺少依赖¶
解决方案:问题: 配置错误¶
解决方案:3. GDB调试问题¶
问题: 符号找不到¶
解决方案:问题: 无法连接GDB¶
解决方案:# 确保QEMU以调试模式启动
qemu-system-x86_64 -S -s ...
# 检查端口是否被占用
netstat -tlnp | grep 1234
# 使用不同端口
qemu-system-x86_64 -gdb tcp::1235
4. 根文件系统问题¶
问题: 内核恐慌 (Kernel Panic)¶
解决方案:# 检查根文件系统路径
# 确保initrd参数正确
-append "root=/dev/ram rdinit=/init"
# 重新创建根文件系统
cd rootfs
find . | cpio -H newc -o | gzip > ../rootfs.cpio.gz
问题: 无法执行init¶
解决方案:5. 性能问题¶
问题: QEMU运行缓慢¶
解决方案:
问题: 编译时间过长¶
解决方案:
6. 网络问题¶
问题: 网络不可用¶
解决方案:
# QEMU网络配置
-netdev user,id=net0 -device e1000,netdev=net0
# 在客户机中配置网络
ifconfig eth0 up
dhclient eth0
7. 存储问题¶
问题: 磁盘空间不足¶
解决方案:
调试技巧¶
1. 启用早期控制台¶
2. 增加日志级别¶
3. 禁用地址随机化¶
4. 启用KGDB¶
获取帮助¶
1. 查看内核日志¶
2. 查看QEMU日志¶
3. 在线资源¶
- Linux内核文档: https://www.kernel.org/doc/
- QEMU文档: https://www.qemu.org/docs/
- GDB文档: https://sourceware.org/gdb/documentation/ EOF
echo "故障排除指南创建: $PROJECT_ROOT/docs/troubleshooting.md"
## 🎉 总结 {#_12}
### 环境搭建完成
经过以上步骤,你已经成功搭建了一个完整的Linux内核调试环境,包括:
1. **完整的工具链**:编译器、调试器、模拟器
2. **可调试的内核**:包含调试符号和GDB支持
3. **虚拟运行环境**:QEMU模拟的x86_64系统
4. **根文件系统**:基于BusyBox的最小系统
5. **调试脚本**:各种实用工具和示例
### 学习路径建议
#### 初级阶段
1. **熟悉环境**:编译内核,启动QEMU
2. **基本调试**:设置断点,单步执行,查看变量
3. **系统调用**:跟踪系统调用流程
#### 中级阶段
1. **进程调度**:调试schedule()函数
2. **内存管理**:跟踪内存分配和释放
3. **文件系统**:了解VFS和具体文件系统
#### 高级阶段
1. **网络协议栈**:调试TCP/IP实现
2. **设备驱动**:编写和调试简单驱动
3. **性能分析**:使用perf和ftrace
### 实用技巧
#### 1. 保存环境状态
```bash
# 创建环境快照
tar -czf kernel-debug-env-$(date +%Y%m%d).tar.gz \
~/linux-kernel-debug/
2. 自动化测试¶
# 创建测试脚本
cat > test-all.sh << 'EOF'
#!/bin/bash
echo "测试内核编译..."
make -j$(nproc) bzImage
echo "测试QEMU启动..."
./run-qemu.sh &
echo "测试GDB连接..."
sleep 2
gdb -ex "target remote :1234" -ex "quit"
EOF
3. 扩展环境¶
资源推荐¶
文档资源¶
- 内核文档:
Documentation/目录 - LWN.net:Linux内核新闻和教程
- 内核邮件列表:学习最新开发动态
书籍推荐¶
- 《Linux内核设计与实现》
- 《深入理解Linux内核》
- 《Linux设备驱动程序》
在线课程¶
- Linux基金会内核开发课程
- MIT 6.828: Operating System Engineering
- 各种内核研讨会和会议视频
下一步行动¶
- 开始调试:选择一个简单的内核模块开始
- 贡献代码:修复简单的内核bug
- 深入学习:阅读内核源码和文档
- 分享经验:写博客或教程帮助他人
最后建议¶
Linux内核调试是一个需要耐心和实践的过程。不要期望一开始就能理解所有细节,从简单的问题开始,逐步深入。记住:
- 实践是最好的老师:多动手调试
- 阅读源码:理解实现细节
- 参与社区:提问和分享
- 保持好奇:探索未知领域
祝你在Linux内核的世界里探索愉快! 🚀
最后更新: 2024年3月13日 作者: [你的名字] 博客: [你的博客链接]
内核调试不是魔术,而是系统性的探索。每一个崩溃都是学习的机会,每一个bug都是理解的窗口。