6.S081 Lab00 xv6启动过程
0. 说明与简介
说明:这里为了和lab 0区分用了lab 00来代替,以后还可能会有Lab 000 (xv6的启动代码分析 – bios)
主要内容:简单的介绍XV6是如何从0开始直到第一个Shell程序运行起来
如果时间不够,可以只看3.关键知识点总结。
文章目录
1. 调试命令
(1) gdb开启qemu的调试
首先在xv6-riscv目录下,运行命令make CPUS=1 qemu-gdb:
注意:这里为什么是 CPUS = 1?–为了方便调试,我们把CPU设置为1,而不是默认的4(正如前面6.S081-2内核的隔离性(isolation)所讲,qemu模拟的riscv是4核的)。 在单核或者单线程场景下,单个断点就可以停止整个程序的运行。
wc@r740:~/OS_experiment/xv6-riscv-fall19$ make CPUS=1 qemu-gdb
sed "s/:1234/:26017/" < .gdbinit.tmpl-riscv > .gdbinit
*** Now run 'gdb' in another window.
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 1 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -S -gdb tcp::26017
(2) 开启gdb
然后根据提示,Now run ‘gdb’ in another window. – 在新的terminal下,打开gdb进行调试,调试命令如下
(注意最后一行的tcp::26017这点后面要用到(可能你的端口数字未必和我一样,后面用自己的端口号就行))
$ riscv64-unknown-elf-gdb kernel/kernel
运行上面命令后可以看到一大堆输出,如果此时最后一行变成了(gdb)说明我们成功进入了gdb。但是我们还没有成功进入调试环境!!!– 这里还是主机环境上的gdb,但是想要调试xv6的kernel,需要在riscv的机器上进行。
(3) 将gdb远程连接到qemu上
在新打开的gdb环境下,远程链接qemu (这里的26017是tcp的端口号,来源于make CPUS=1 qemu-gdb命令的最后一行输出,因为自己电脑上的某些端口可能被占用,因此这个端口号每个人或许不同,请注意修改)
(gdb) target remote localhost:26017
结果为
Remote debugging using localhost:26017
0x0000000000001000 in ?? ()
以上结果表示成功进入gdb,接下来可以对kernel进行调试。这部分可以参考 HITSZ_OS手册
# b //设置断点(breakpoint)
# c //继续运行(Continue)--到断点处就会停止
# n //单步调试
# p //打印变量内容
# si //断点定位
# display/i $pc //每次停止时,都可以显示下一条指令的反汇编
# layout asm //可以显示当前的汇编指令
# display /3i $pc //如果您希望在单步执行程序时自动显示下3条指令,可以使用display命令
# p *path@6 //这里是打印path指向的内容,打印长度为6(如果不写@6那么只能打印一个char)
2. 设置断点,进行调试
(0) 第一条指令的执行 + 第一个断点 (运行在machine mode)
首先是在_entry处设置断点(因为_entry是kernel的第一条指令 – 可以通过查看kernel.asm查看)
kernel.asm (内核的汇编代码)部分代码如下:
kernel/kernel: file format elf64-littleriscv
Disassembly of section .text:
0000000080000000 <_entry>:
80000000: 0000b117 auipc sp,0xb
80000004: 80010113 addi sp,sp,-2048 # 8000a800 <stack0>
80000008: 6505 lui a0,0x1
8000000a: f14025f3 csrr a1,mhartid
8000000e: 0585 addi a1,a1,1
80000010: 02b50533 mul a0,a0,a1
80000014: 912a add sp,sp,a0
80000016: 070000ef jal ra,80000086 <start>
000000008000001a <junk>:
8000001a: a001 j 8000001a <junk>
000000008000001c <timerinit>:
// which arrive at timervec in kernelvec.S,
// which turns them into software interrupts for
// devintr() in trap.c.
void
timerinit()
{
8000001c: 1141 addi sp,sp,-16
8000001e: e422 sd s0,8(sp)
80000020: 0800 addi s0,sp,16
// which hart (core) is this?
static inline uint64
- 设置断点,并继续运行代码到断点处,如下:
(gdb) b _entry
Breakpoint 1 at 0x8000000a
(gdb) c
Continuing.
Breakpoint 1, 0x000000008000000a in _entry ()
可以看到,我们并没有停在0x80000000 (即_entry处),而是停在了0x8000000a处
问题:为什么第一条指令的地址是0x80000000???
– 0x80000000是一个被qemu认可的地址,如果想要使用qemu,第一条指令就必须在0x80000000处
这一点可以打开kernel.ld查看(关于kernel.ld的分析,可以看好文链接)
OUTPUT_ARCH( "riscv" )
ENTRY( _entry )
SECTIONS
{
/*
* ensure that entry.S / _entry is at 0x80000000,
* where qemu's -kernel jumps.
*/
. = 0x80000000;
.text :
{
*(.text)
. = ALIGN(0x1000);
*(trampsec)
}
. = ALIGN(0x1000);
PROVIDE(etext = .);
/*
* make sure end is after data and bss.
*/
.data : {
*(.data)
}
.bss : {
*(.bss)
*(.sbss*)
PROVIDE(end = .);
}
}
- 回到gdb中,可以看到已经在_entry里面了,
0x8000000a处的汇编代码为:csrr a1,mhartid它的长度是:2B。因此它的下一条指令地址为:0x000000008000000e,对应的汇编是:addi a1,a1,1.
(1) main() 的执行 (进入kernel mode)
- 接下来继续打一个断点在
main()处,可以得到如下结果 (XV6从entry.s开始启动,这个时候没有内存分页,没有隔离性,并且运行在M-mode(machine mode)。XV6会尽可能快的跳转到kernel mode或者说是supervisor mode。我们在main函数设置一个断点,main函数已经运行在supervisor mode了。接下来我运行程序,代码会在断点,也就是main函数的第一条指令停住。)
26017: No such file or directory.
(gdb) target remote localhost:26017
Remote debugging using localhost:26017
0x0000000000001000 in ?? ()
(gdb) b _entry
Breakpoint 1 at 0x8000000a
(gdb) display/i $pc
1: x/i $pc
=> 0x1000: auipc t0,0x0
(gdb) c
Continuing.
Breakpoint 1, 0x000000008000000a in _entry

1901

被折叠的 条评论
为什么被折叠?



