前言
最近一段时间终于有了连续的空闲,重新拿起xv6
,复习os内容,同时记录一下一个bug的调试经历。
问题
在看过xv6和类似os的相关文章后,我开始自己动手写一个minios
。发现,在笔记本电脑上,qemu
运行minios
很顺利,但在我另一个台式机上,qemu
运行minios
出现问题,弹出的黑框一直在闪烁,同时无法运行到main()
的位置。
分析
首先排除开发环境的问题。笔记本和台式机均在ubuntu18.04环境下,qemu版本相同,不应该是开发环境有问题。
确定是代码的问题,开始调试。
调试
查看kernel.asm
文件,确定entry()
在0x7d87
位置,于是将xv6(minios)
在qemu-gdb
模式下运行,在该位置打个断点。
(gdb) b *0x7d87
Breakpoint 1 at 0x7d87
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x7d87: call *0x10018
Breakpoint 1, 0x00007d87 in ?? ()
(gdb) si
=> 0x10000c: mov %cr4,%eax
0x0010000c in ?? ()
(gdb)
=> 0x10000f: or $0x10,%eax
0x0010000f in ?? ()
(gdb)
=> 0x100012: mov %eax,%cr4
0x00100012 in ?? ()
(gdb)
=> 0x100015: mov $0x103000,%eax
0x00100015 in ?? ()
(gdb) x/i 0x00100028
0x100028: mov $0x80105790,%esp
(gdb) si
=> 0x10001a: mov %eax,%cr3
0x0010001a in ?? ()
(gdb)
=> 0x10001d: mov %cr0,%eax
0x0010001d in ?? ()
(gdb)
=> 0x100020: or $0x80010000,%eax
0x00100020 in ?? ()
(gdb)
=> 0x100025: mov %eax,%cr0
0x00100025 in ?? ()
(gdb)
=> 0x100028: Error while running hook_stop:
Cannot access memory at address 0x100028
0x00100028 in ?? ()
在单步运行的情况下,发现qemu
报错Cannot access memory at address 0x100028
。
报错的指令为mov $0x80105790,%esp
,更新stack
基址,不设计访问内存的相关问题,那就是说可能是无法读取指令。
下一步,通过objdunp -h kernel
,分析指令为何无法读取。
kernel: 文件格式 elf32-i386
节:
Idx Name Size VMA LMA File off Algn
0 .text 00001f55 80100000 00100000 00001000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 00000311 80101f60 00101f60 00002f60 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 00000001 80102271 80102271 00003271 2**0
CONTENTS, ALLOC, LOAD, DATA
3 .data 00001770 80103000 80103000 00004000 2**12
CONTENTS, ALLOC, LOAD, DATA
4 .bss 00004260 80104780 80104780 00005770 2**5
ALLOC
5 .debug_line 00000edc 00000000 00000000 00005770 2**0
CONTENTS, READONLY, DEBUGGING
6 .debug_info 00005b16 00000000 00000000 0000664c 2**0
CONTENTS, READONLY, DEBUGGING
7 .debug_abbrev 0000171c 00000000 00000000 0000c162 2**0
CONTENTS, READONLY, DEBUGGING
8 .debug_aranges 00000220 00000000 00000000 0000d880 2**3
CONTENTS, READONLY, DEBUGGING
9 .debug_str 00000698 00000000 00000000 0000daa0 2**0
CONTENTS, READONLY, DEBUGGING
10 .debug_ranges 00000248 00000000 00000000 0000e138 2**0
CONTENTS, READONLY, DEBUGGING
11 .comment 00000029 00000000 00000000 0000e380 2**0
CONTENTS, READONLY
12 .debug_loc 00001d48 00000000 00000000 0000e3a9 2**0
CONTENTS, READONLY, DEBUGGING
从上述中发现,kernel
中,从.stab
段开始,LMA
地址就发生了巨大变化,相互之间不再是连续的地址。
这样就明确了,是kernel.ld
链接脚本有一点问题,导致链接器运行时,没有将各个段之间连续排列。
/* Include debugging information in kernel memory */
.stab : {
PROVIDE(__STAB_BEGIN__ = .);
*(.stab);
PROVIDE(__STAB_END__ = .);
BYTE(0) /* Force the linker to allocate space
for this section */
}
.stabstr : {
PROVIDE(__STABSTR_BEGIN__ = .);
*(.stabstr);
PROVIDE(__STABSTR_END__ = .);
BYTE(0) /* Force the linker to allocate space
for this section */
}
与xv6
相比,我的kernel.ld
脚本多了BYTE(0)
内容–我不知道是从哪个博客/知乎文章里复制粘贴的。
将这两个BYTE(0)
删除后,kernel的各个段加载地址变为连续,qmeu
成功运行,不再闪烁。
解释
花时间学习ld脚本语言后,总算对这个bug有了解释。
BYTE(0)
命令表示,在位置计数器处存储一个单字节数据0。然而,此时的位置计数器.
已经在第一行表明,是0x80100000
,因此,从.stab
后续的几个段,位置发生变化,与.text
段不再连续,导致了后续的错误。
为了验证这个想法,对kernel.ld
进行如下改变。
. = ALIGN(0x1000);
.stab : {
PROVIDE(__STAB_BEGIN__ = .);
*(.stab);
PROVIDE(__STAB_END__ = .);
BYTE(0)
}
在kernel.ld
后面,有再次对位置寄存器.
进行赋值,将.stab
剪贴到该赋值命令后,编译运行,发现qemu
成功启动,没有闪烁。
表明上述解释是正确的。
总结
遇到一个不同平台上运行效果不一致的问题,着实伤脑筋。但调试解决后,收获的成长和喜悦也是巨大的。