慢慢欣赏linux 使用qemu调试startup_64

本文介绍如何调试QEMU启动过程中的startup_64函数。由于该函数执行时使用物理地址映射,传统的断点设置方法无法生效。文章详细解释了如何通过配置找到正确的物理地址并设置断点。

很多文章写调试qemu都是从start_kernel开始的,那么start_kernel之前的函数如何调试呢。尤其是 startup_64
即使打上了断点也无法跟踪。
原因是 startup_64 在 vmlinux 里面对应的是链接地址。

7262499 ffffffff81000000 <_text>:
7262500 ffffffff81000000:       48 8d 2d f9 ff ff ff    lea    -0x7(%rip),%rbp        # ffffffff81000000 <_text>

而实际上 startup_64 执行的时候, 页表还没有建立, 使用的虚拟地址跟物理地址是1:1对应的, 也就是其执行的时候, 虚拟地址和物理地址是相等的。
那么怎么找到其开始运行的时候的物理地址呢?

rlk@ubuntu:runninglinuxkernel_4.0_x86$ cat .config | grep CONFIG_RELOCATABLE
CONFIG_RELOCATABLE=y

rlk@ubuntu:runninglinuxkernel_4.0_x86$ cat .config | grep CONFIG_PHYSICAL_START
CONFIG_PHYSICAL_START=0x1000000
rlk@ubuntu:runninglinuxkernel_4.0_x86$ 

rlk@ubuntu:kernel$ vi vmlinux.lds
SECTIONS
{
         . = (0xffffffff80000000 + ALIGN(0x1000000, 0x200000));
         phys_startup_64 = startup_64 - 0xffffffff80000000;

}

调试代码对应的汇编语言如下

 7262497 Disassembly of section .text:
 7262498 
 7262499 ffffffff81000000 <_text>:
 7262500 ffffffff81000000:       48 8d 2d f9 ff ff ff    lea    -0x7(%rip),%rbp        # ffffffff81000000 <_text>
 7262501 ffffffff81000007:       48 81 ed 00 00 00 01    sub    $0x1000000,%rbp
 7262502 ffffffff8100000e:       48 89 e8                mov    %rbp,%rax
 7262503 ffffffff81000011:       25 ff ff 1f 00          and    $0x1fffff,%eax
 7262504 ffffffff81000016:       85 c0                   test   %eax,%eax
 7262505 ffffffff81000018:       0f 85 a7 01 00 00       jne    ffffffff810001c5 <bad_address>
 7262506 ffffffff8100001e:       48 8d 05 db ff ff ff    lea    -0x25(%rip),%rax        # ffffffff81000000 <_text>
 7262507 ffffffff81000025:       48 c1 e8 2e             shr    $0x2e,%rax
 7262508 ffffffff81000029:       0f 85 96 01 00 00       jne    ffffffff810001c5 <bad_address>
 7262509 ffffffff8100002f:       48 01 2d c2 7f f4 00    add    %rbp,0xf47fc2(%rip)        # ffffffff81f47ff8 <early_level4_pgt+0xff8>
 7262510 ffffffff81000036:       48 01 2d b3 ff e0 00    add    %rbp,0xe0ffb3(%rip)        # ffffffff81e0fff0 <level3_kernel_pgt+0xff0>
 7262511 ffffffff8100003d:       48 01 2d b4 ff e0 00    add    %rbp,0xe0ffb4(%rip)        # ffffffff81e0fff8 <level3_kernel_pgt+0xff8>
 7262512 ffffffff81000044:       48 01 2d 85 1f e1 00    add    %rbp,0xe11f85(%rip)        # ffffffff81e11fd0 <level2_fixmap_pgt+0xfd0>
 7262513 ffffffff8100004b:       48 8d 3d ae ff ff ff    lea    -0x52(%rip),%rdi        # ffffffff81000000 <_text>

所以打断点要基于物理地址打断点:

(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x000000000000fff0 in scsi_format_log ()
(gdb) b *0x1000000	// 其中, 0x1000000 是 startup_64 运行时的虚拟地址。给地址打断点, 前面需要加上 *
Breakpoint 1 at 0x1000000
(gdb) c
Continuing.

Breakpoint 1, 0x0000000001000000 in ?? ()

(gdb) info reg	// 打印寄存器
rax            0x1000000        16777216
rbx            0x1f19000        32608256
rcx            0x780    1920
rdx            0x3d5    981
rsi            0x140a0  82080
rdi            0x3d5    981
rbp            0x1000000        0x1000000
rsp            0x23fa540        0x23fa540
r8             0x4e1580 5117312
r9             0x14d9000        21860352
r10            0xd      13
r11            0xa      10
r12            0x0      0
r13            0x0      0
r14            0x0      0
r15            0x0      0
rip            0x1000000        0x1000000
eflags         0x2      [ IOPL=0 ]
cs             0x10     16
ss             0x0      0
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
fs_base        0x0      0
gs_base        0x0      0
k_gs_base      0x0      0
cr0            0x80000011       [ PG ET PE ]
cr2            0x0      0
cr3            0x23fb000        [ PDBR=1 PCID=0 ]
cr4            0x20     [ PAE ]
cr8            0x0      0
efer           0x500    [ LMA LME ]
mxcsr          0x1f80   [ IM DM ZM OM UM PM ]

(gdb) n       
Cannot find bounds of current function
(gdb) s
Cannot find bounds of current function

(gdb) b *0x1000007
Breakpoint 2 at 0x1000007

(gdb) c
Continuing.

Breakpoint 2, 0x0000000001000007 in ?? ()
(gdb) info reg
rax            0x1000000        16777216
rbx            0x1f19000        32608256
rcx            0x780    1920
rdx            0x3d5    981
rsi            0x140a0  82080
rdi            0x3d5    981
rbp            0x1000000        0x1000000
rsp            0x23fa540        0x23fa540
r8             0x4e1580 5117312
r9             0x14d9000        21860352
r10            0xd      13
r11            0xa      10
r12            0x0      0
r13            0x0      0
r14            0x0      0
r15            0x0      0
rip            0x1000007        0x1000007
eflags         0x2      [ IOPL=0 ]
cs             0x10     16
ss             0x0      0
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
fs_base        0x0      0
gs_base        0x0      0
k_gs_base      0x0      0

(gdb) b *0x100001e
Breakpoint 3 at 0x100001e

(gdb) c
Continuing.

Breakpoint 3, 0x000000000100001e in ?? ()
(gdb) info reg
rax            0x0      0	// 这条语句讲rax清零 ffffffff81000011:       25 ff ff 1f 00          and    $0x1fffff,%eax
rbx            0x1f19000        32608256
rcx            0x780    1920
rdx            0x3d5    981
rsi            0x140a0  82080
rdi            0x3d5    981
rbp            0x0      0x0 <irq_stack_union>
rsp            0x23fa540        0x23fa540
r8             0x4e1580 5117312
r9             0x14d9000        21860352
r10            0xd      13
r11            0xa      10
r12            0x0      0
r13            0x0      0
r14            0x0      0
r15            0x0      0
rip            0x100001e        0x100001e
eflags         0x46     [ IOPL=0 ZF PF ]
cs             0x10     16
ss             0x0      0
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
fs_base        0x0      0
gs_base        0x0      0
k_gs_base      0x0      0
cr0            0x80000011       [ PG ET PE ]
cr2            0x0      0
cr3            0x23fb000        [ PDBR=1 PCID=0 ]
cr4            0x20     [ PAE ]
cr8            0x0      0
efer           0x500    [ LMA LME ]

(gdb) b *0x1000025
Breakpoint 4 at 0x1000025

(gdb) c       
Continuing.

Breakpoint 4, 0x0000000001000025 in ?? ()

(gdb) info reg
rax            0x1000000        16777216	// ffffffff8100001e:       48 8d 05 db ff ff ff    lea    -0x25(%rip),%rax        # ffffffff81000000 <_text>
rbx            0x1f19000        32608256
rcx            0x780    1920
rdx            0x3d5    981
rsi            0x140a0  82080
rdi            0x3d5    981
rbp            0x0      0x0 <irq_stack_union>
rsp            0x23fa540        0x23fa540
r8             0x4e1580 5117312
r9             0x14d9000        21860352
r10            0xd      13
r11            0xa      10
r12            0x0      0
r13            0x0      0
r14            0x0      0
r15            0x0      0
rip            0x1000025        0x1000025
eflags         0x46     [ IOPL=0 ZF PF ]

 

 

<think>我们正在讨论如何在Linux 0.12内核中进行调试。由于Linux 0.12是一个古老的内核版本,现代调试工具可能无法直接支持,因此我们需要使用一些传统的方法。 主要方法包括: 1. 使用Bochs或QEMU等模拟器运行内核,并利用模拟器的调试功能。 2. 使用gdb配合模拟器进行源码级调试。 3. 使用内核内置的printk函数输出调试信息。 由于Linux 0.12运行在实模式和保护模式下(0.12已经进入保护模式),我们需要注意调试器对保护模式的支持。 下面将详细介绍使用Bochs和QEMU进行调试的步骤。 ### 使用Bochs调试Linux 0.12 Bochs是一个x86模拟器,内置调试功能,非常适合调试旧内核。 步骤: 1. **安装Bochs**:确保安装的Bochs版本支持旧内核(一般都可以)。在Ubuntu上,可以使用`sudo apt-get install bochs bochs-sdl`安装。 2. **准备Bochs配置文件**:创建一个配置文件(如`bochsrc.txt`),配置模拟的硬件环境和启动镜像。以下是一个示例配置: ```ini megs: 16 romimage: file=/usr/share/bochs/BIOS-bochs-latest vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest floppya: 1_44=Image, status=inserted boot: a log: bochslog.txt mouse: enabled=0 keyboard: keymap=/usr/share/bochs/keymaps/x11-pc-us.map ``` 3. **编译Linux 0.12**:确保你有一个可启动的Image文件(即编译好的内核镜像)。编译时,请确保在Makefile中启用调试信息(例如,使用`-g`编译选项)。 4. **运行Bochs**:使用`bochs -f bochsrc.txt -q`启动,并选择进入调试模式(启动时按`c`进入命令行调试界面)。 5. **调试命令**:在Bochs的调试命令行中,可以设置断点(如`b 0x1234`)、单步执行(`s`)、查看寄存器(`r`)、查看内存(`x /nuf addr`)等。 ### 使用QEMU和GDB调试 QEMU同样支持调试,并且可以与GDB配合进行源码级调试。 步骤: 1. **安装QEMU**:使用包管理器安装,例如`sudo apt-get install qemu-system-x86`。 2. **编译内核**:在Linux 0.12的源代码目录中,运行`make`生成Image文件。确保在编译时加入调试信息(在Makefile中CFLAGS添加`-g`)。 3. **启动QEMU**:使用以下命令启动内核并等待GDB连接: ```bash qemu-system-i386 -s -S -fda Image -boot a ``` 其中,`-s`表示在1234端口等待GDB连接,`-S`表示启动时暂停CPU。 4. **使用GDB连接**:在另一个终端中,启动GDB(需要安装gdb),然后: ```bash gdb (gdb) target remote localhost:1234 (gdb) file tools/system # 加载符号文件(注意:在Linux 0.12中,内核的入口是head.s,然后跳转到main.c,但符号文件是整个内核的elf文件,通常编译后生成的是system文件,位于tools目录下) (gdb) break main # 在main函数处设置断点 (gdb) continue # 继续执行 ``` 注意:由于内核启动时首先运行的是汇编代码(head.s),所以你可能需要先在`startup_32`处设置断点,然后再到`main`函数。 5. **调试**:此时就可以使用GDB命令进行调试了,如`step`, `next`, `print`等。 ### 使用printk 在内核代码中插入printk语句是最简单的调试方法。例如,在需要的地方添加: ```c printk("Debug: variable=%d\n", variable); ``` 然后重新编译内核并运行,在控制台或日志文件中查看输出。 注意:Linux 0.12的printk函数输出到控制台,如果控制台不可见,可能需要检查控制台配置。 ### 注意事项 1. **调试信息**:编译时确保使用`-g`选项生成调试信息。 2. **符号文件**:在GDB调试时,加载正确的符号文件(通常是编译后的system文件)。 3. **保护模式**:当内核进入保护模式后,GDB可能无法正确解析地址,但QEMU的GDB存根会处理这些转换。 以上方法中,使用QEMU+GDB的组合可能更为方便,因为GDB的功能更强大。 §§相关问题§§ 1. 如何编译Linux 0.12内核? 2. 在调试Linux 0.12内核时,如何设置断点以捕获特定事件? 3. 使用printk调试时,如何查看内核的输出信息? 4. 在QEMU中运行Linux 0.12时,如何配置网络支持?</think>### 如何调试 Linux 0.12 内核 调试 Linux 0.12 内核需要特殊工具和方法,因为它是运行在实模式/保护模式下的早期内核。以下是详细的调试方案: #### 1. **使用 Bochs 模拟器调试** Bochs 是调试早期内核的首选工具,内置调试器支持实模式和保护模式。 ```ini # bochsrc 配置文件示例 megs: 16 romimage: file=$BXSHARE/BIOS-bochs-latest vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest floppya: 1_44=Image, status=inserted # 内核镜像文件 boot: a log: bochslog.txt mouse: enabled=0 display_library: sdl debug: action=report ``` **调试步骤:** 1. 启动 Bochs:`bochs -f bochsrc -q` 2. 进入调试命令行:启动时按 `c` 进入控制台 3. 常用命令: - `b 0x7C00`:在引导扇区设断点 - `c`:继续执行 - `s`:单步执行 - `r`:查看寄存器 - `x /10i 0x10000`:反汇编内存 #### 2. **QEMU + GDB 组合调试** ```bash # 启动 QEMU(暂停 CPU) qemu-system-i386 -S -s -fda Image -boot a # GDB 连接调试 gdb -ex "target remote localhost:1234" \ -ex "symbol-file tools/system" \ -ex "b main" \ -ex "c" ``` **关键配置:** - 编译内核时在 `Makefile` 添加 `-g` 标志 - GDB 需加载符号文件 `tools/system`(编译生成) - 保护模式调试使用 GDB 的 `set architecture i386` 命令 #### 3. **内核级调试技巧** - **printk 输出调试** 在关键代码位置添加打印语句(需重新编译内核): ```c // kernel/printk.c printk("Debug: cs=%x, eip=%x\n", regs->cs, regs->eip); ``` - **硬编码断点** 在 C 代码中插入汇编指令: ```c asm volatile("int $0x3"); // 触发断点异常 ``` - **异常处理跟踪** 修改 `kernel/traps.c` 的 `die` 函数打印寄存器状态 #### 4. **调试工具链准备 | 工具 | 用途 | 安装方式 | |------------|-------------------------------|------------------------------| | Binutils 2.9.1 | 匹配的汇编器/链接器 | 源码编译 | | GCC 1.40 | 原始编译环境 | [OldLinux 提供](http://oldlinux.org) | | GDB 4.17 | 兼容早期 ELF 格式 | 源码编译 | #### 5. **调试场景示例 **问题:内核在 `main()` 后崩溃** 1. 在 GDB 中:`b init/main.c:main` 2. 单步执行 `s` 直到崩溃点 3. 查看崩溃时的堆栈:`bt` 4. 检查关键变量:`p init_task` 5. 反汇编当前位置:`disassemble /r $eip-10, +20` ### 常见问题解决 1. **断点不生效** - 确认内核加载地址:`nm system | grep " _start"` - 检查 GDB 的架构设置:`set architecture i8086`(实模式) 2. **符号缺失** - 重新编译:`make clean && make CFLAGS=-g` - 检查符号表:`objdump -t system > symtab.txt` 3. **保护模式地址转换** 使用 Bochs 的 `page` 命令转换线性地址: ``` (bochs:1) page 0x0000 ``` ### 注意事项 1. 使用原始环境编译(GCC 1.x + Binutils 2.9.x) 2. 调试保护模式代码时需关注 GDT/LDT 状态 3. 建议备份磁盘镜像:`cp Image Image.bak`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值