练习2

练习2:使用qemu执行并调试lab1中的软件

练习2文档

相关阅读:启动后第一条执行的指令

为了熟悉使用qemu和gdb进行的调试工作,我们进行如下的小练习:

  1. 从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。

  2. 在初始化位置0x7c00设置实地址断点,测试断点正常。

  3. 从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。

  4. 自己找一个bootloader或内核中的代码位置,设置断点并进行测试。

相关文件

从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。

背景知识

  1. 什么是gdb

    在这里,gdb指的是GNU Debuggergdb即其缩写。

    GDB是GNU软件系统中的标准调试器,此外GDB也是个具有移携性的调试器,经过移携需求的调修与重新编译,如今许多的类UNIX操作系统上都可以使用GDB,而现有GDB所能支持调试的编程语言有C、C++、Pascal以及FORTRAN。

  2. 如何使用gdb

    如直接使用,在命令行调用即可。但ucoreMakefile中已写好了调用gdb的命令,可直接使用。在本练习中,我们只需修改tools/gdbinitGDB的配置文件即可。

    附:Makefile中调用gdb的命令及参数含义

    调用gdb的命令:

    # File:   Makefile
    # Line:   220-223
    
    debug: $(UCOREIMG)
        $(V)$(QEMU) -S -s -parallel stdio -hda $< -serial null &
        $(V)sleep 2
        $(V)$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit"

    由以上代码可以知道,在执行make debug以启动debug这一target后,会先完成UCOREIMG这一target,然后相继启动qemugdb

    qemu的参数不再赘述,本节重点讨论gdb的相关参数。

    首先,make调用$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit"。这里使用Linux内置的终端开启了一个新窗口然后在其中调用gdb

    • -q,即--quiet--silent,作用为:Do not print version number on startup.

      • 在正常启动gdb时,gdb会输出相关的版本信息,如笔者的会输出:

        GNU gdb (GDB) 9.1

        Copyright (C) 2020 Free Software Foundation, Inc.

        License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html

        This is free software: you are free to change and redistribute it.

        There is NO WARRANTY, to the extent permitted by law.

        Type "show copying" and "show warranty" for details.

        This GDB was configured as "x86_64-pc-linux-gnu".

        Type "show configuration" for configuration details.

        For bug reporting instructions, please see:

        http://www.gnu.org/software/gdb/bugs/.

        Find the GDB manual and other documentation resources online at:

        http://www.gnu.org/software/gdb/documentation/.

        For help, type "help".

        Type "apropos word" to search for commands related to "word".

        (gdb)

        然后即可在(gdb)后面输入命令。 而添加-q参数后启动gdb,则会输出:

        (gdb)

        仅此而已。

    • -tui,作用为:Use a terminal user interface. 即在终端中使用UI,读者可亲自尝试,本处不再赘述。

      另外,如果不需要该选项,可以将make debug替换为make debug-nox

    • -x file,作用为:Execute GDB commands from file file. 即从文件中执行GDB命令。

    以上就是Makefilegdb所使用的参数含义。

问题分析

  1. CPU加电后第一条指令从哪里开始执行?

    0xffff0开始执行。

    为验证这一结论,我们可以修改tools/gdbinit文件,然后在lab1目录下输入$ make debug启动调试。

    要修改的gdbinit的内容:

    set architecture i8086
    target remote :1234

    启动debug后,我们可以看到GDB停在了0xfff0位置,这里的0xfff0即IP寄存器内的值。如果我们想要知道此时指令所在位置,还需要有CS寄存器的值。

    我们在gdb中键入:info registers cs eip,可以看到下面返回了

    (gdb) info registers cs eip

    cs 0xf000 61440

    eip 0xfff0 0xfff0

那么此时BIOS第一条指令所在位置就是CS:IP所指向的位置,即0xffff0了。

那么,应该如何查看这条指令是什么汇编指令呢?我们尝试在gdb的命令行中输入x/i 0xffff0,就可以看到这条指令的内容了。

(gdb) x/i 0xffff0

0xffff0: ljmp $0xf000,$e05b

显然这是一条跳转指令,将指令跳转到了0xfe05b。我们在gdb中键入stepi,或者其缩写si,可以跳转到下一步指令的位置。

然后我们可以继续输入x/10i 0xfe05b,可以查看接下来的10条指令,将此处的10改为更大的值,可以查看更多指令,此处请读者自行尝试。

(gdb) x/i 0xfe05b

0xfe05b: cmpw $0xffc8,%cs:(%esi)

0xfe060: bound %eax,(%eax)

0xfe062: jne 0xd241d0b6

0xfe068: mov %edx,%ss

0xfe06a: mov $0x7000,%sp

0xfe06e: add %al,(%eax)

0xfe070: mov $0x8a4,%dx

0xfe074: verw %cx

0xfe077: mov $0xee,%dl

0xfe079: push %bp

参考文献

在初始化位置0x7c00设置实地址断点,测试断点正常。

背景知识

  1. 如何在gdb中设置断点?

    gdb中设置断点需使用指令berak或者其缩写b。设置断点有多种方法,在此我们只讲解两种:函数断点和内存断点。

    • 函数断点:break function,直接在break后加函数名即可。注意此方法需提前加载被执行程序的符号表,否则GDB无法识别该函数所在具体位置。函数断点在ucore中经常使用,我们在调试内核时就在kern_init函数处设置了断点,然后就可以从此处进行进一步的调试。

    • 内存断点:break *address,比如我们要在0x7c00处设置断点,就可键入break *0x7c00,当程序执行到断点时程序就会自动停止,然后就可以开始调试了。

问题分析

  1. 在初始化位置0x7c00设置实地址断点,测试断点正常。

    我们在gdbinit文件中设置断点break *0x7c00,然后启动调试,发现启动后程序停在了0x7c00处,说明断点设置正常。

参考文献

  • 练习2文档

从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和bootblock.asm进行比较。

背景知识

  1. 如何单步跟踪反汇编代码?

    我们可以修改gdbinit文件,在其中添加如下命令:

    define hook-stop
    x/i $pc
    end

    该命令在gdb中定义了一个钩子,每执行一步操作都会输出当前命令所在位置的汇编代码。随后我们就可以键入stepi观察每一步命令了。

  2. gdb中的单步命令

    在gdb中,有next, nexti, step, stepi等指令来单步调试程序,他们功能各不相同,区别在于单步的“跨度”上。

    next 单步到程序源代码的下一行,不进入函数。

    nexti 单步一条机器指令,不进入函数。

    step 单步到下一个不同的源代码行(包括进入函数)。

    stepi 单步一条机器指令。

问题分析

  1. 从0x7c00开始跟踪代码运行并将单步跟踪反汇编得到的代码与bootasm.S和bootblock.asm进行比较

    单步跟踪得到的反汇编代码(bootasm.S部分):

    0x7c02: cli

    0x7c01: cld

    0x7c02: xor %eax,%eax

    0x7c04: mov %eax,%ds

    0x7c06: mov %eax,%es

    0x7c08: mov %eax,%ss

    0x7c0a: in $0x64,%al

    0x7c0c: test $0x2,%al

    0x7c0e: jne 0x7c0a

    0x7c10: mov $0xd1,%al

    0x7c12: out %al,$0x64

    0x7c14: in $0x64,%al

    0x7c16: test $0x2,%al

    0x7c18: jne 0x7c14

    0x7c1a: mov $0xdf,%al

    0x7c1c: out %al,$0x60

    0x7c1e: lgdtl (%esi)

    0x7c23: mov %cr0,%eax

    0x7c26: or $0x1,%ax

    0x7c2a: mov %eax,%cr0

    0x7c2d: ljmp $0xb866,$0x87c32

    0x7c32: mov $0x10,%ax

    0x7c36: mov %eax,%ds

    0x7c38: mov %eax,%es

    0x7c3a: mov %eax,%fs

    0x7c3c: mov %eax,%gs

    0x7c3e: mov %eax,%ss

    0x7c40: mov $0x0,%ebp

    0x7c45: mov $0x7c00,%esp

    0x7c4a: call 0x7d0f

    受篇幅限制,后续请读者自行分析与bootasm.Sbootblock.asm的区别。

    提示bootblock.asm文件位于lab1/obj/bootblock.asm

参考文献

  • 练习2文档

自己找一个bootloader或内核中的代码位置,设置断点并进行测试。

Last updated