Caffeinated 6.828：实验工具指南
GDB 6.828
![](/data/attachment/album/201811/25/115935paqa0dqwtg5o5o7j.jpg)


熟悉你的环境对高效率的开发和调试来说是至关重要的。本文将为你简单概述一下 JOS 环境和非常有用的 GDB 和 QEMU 命令。话虽如此，但你仍然应该去阅读 GDB 和 QEMU 手册，来理解这些强大的工具如何使用。


### 调试小贴士


#### 内核


GDB 是你的朋友。使用 `qemu-gdb target`（或它的变体 `qemu-gdb-nox`）使 QEMU 等待 GDB 去绑定。下面在调试内核时用到的一些命令，可以去查看 GDB 的资料。


如果你遭遇意外的中断、异常、或三重故障，你可以使用 `-d` 参数要求 QEMU 去产生一个详细的中断日志。


调试虚拟内存问题时，尝试 QEMU 的监视命令 `info mem`（提供内存高级概述）或 `info pg`（提供更多细节内容）。注意，这些命令仅显示**当前**页表。


（在实验 4 以后）去调试多个 CPU 时，使用 GDB 的线程相关命令，比如 `thread` 和 `info threads`。


#### 用户环境（在实验 3 以后）


GDB 也可以去调试用户环境，但是有些事情需要注意，因为 GDB 无法区分开多个用户环境或区分开用户环境与内核环境。


你可以使用 `make run-name`（或编辑 `kern/init.c` 目录）来指定 JOS 启动的用户环境，为使 QEMU 等待 GDB 去绑定，使用 `run-name-gdb` 的变体。


你可以符号化调试用户代码，就像调试内核代码一样，但是你要告诉 GDB，哪个符号表用到符号文件命令上，因为它一次仅能够使用一个符号表。提供的 `.gdbinit` 用于加载内核符号表 `obj/kern/kernel`。对于一个用户环境，这个符号表在它的 ELF 二进制文件中，因此你可以使用 `symbol-file obj/user/name` 去加载它。不要从任何 `.o` 文件中加载符号，因为它们不会被链接器迁移进去（库是静态链接进 JOS 用户二进制文件中的，因此这些符号已经包含在每个用户二进制文件中了）。确保你得到了正确的用户二进制文件；在不同的二进制文件中，库函数被链接为不同的 EIP，而 GDB 并不知道更多的内容！


（在实验 4 以后）因为 GDB 绑定了整个虚拟机，所以它可以将时钟中断看作为一种控制转移。这使得从底层上不可能实现步进用户代码，因为一个时钟中断无形中保证了片刻之后虚拟机可以再次运行。因此可以使用 `stepi` 命令，因为它阻止了中断，但它仅可以步进一个汇编指令。断点一般来说可以正常工作，但要注意，因为你可能在不同的环境（完全不同的一个二进制文件）上遇到同一个 EIP。


### 参考


#### JOS makefile


JOS 的 GNUmakefile 包含了在各种方式中运行的 JOS 的许多假目标。所有这些目标都配置 QEMU 去监听 GDB 连接（`*-gdb` 目标也等待这个连接）。要在运行中的 QEMU 上启动它，只需要在你的实验目录中简单地运行 `gdb` 即可。我们提供了一个 `.gdbinit` 文件，它可以在 QEMU 中自动指向到 GDB、加载内核符号文件、以及在 16 位和 32 位模式之间切换。退出 GDB 将关闭 QEMU。


* `make qemu`


在一个新窗口中构建所有的东西并使用 VGA 控制台和你的终端中的串行控制台启动 QEMU。想退出时，既可以关闭 VGA 窗口，也可以在你的终端中按 `Ctrl-c` 或 `Ctrl-a x`。
* `make qemu-nox`


和 `make qemu` 一样，但仅使用串行控制台来运行。想退出时，按下 `Ctrl-a x`。这种方式在通过 SSH 拨号连接到 Athena 上时非常有用，因为 VGA 窗口会占用许多带宽。
* `make qemu-gdb`


和 `make qemu` 一样，但它与任意时间被动接受 GDB 不同，而是暂停第一个机器指令并等待一个 GDB 连接。
* `make qemu-nox-gdb`


它是 `qemu-nox` 和 `qemu-gdb` 目标的组合。
* `make run-nam`


（在实验 3 以后）运行用户程序 name。例如，`make run-hello` 运行 `user/hello.c`。
* `make run-name-nox`,`run-name-gdb`, `run-name-gdb-nox`


（在实验 3 以后）与 `qemu` 目标变量对应的 `run-name` 的变体。


makefile 也接受几个非常有用的变量：


* `make V=1 …`


详细模式。输出正在运行的每个命令，包括参数。
* `make V=1 grade`


在评级测试失败后停止，并将 QEMU 的输出放入 `jos.out` 文件中以备检查。
* `make QEMUEXTRA=' _args_ ' …`


指定传递给 QEMU 的额外参数。


#### JOS obj/


在构建 JOS 时，makefile 也产生一些额外的输出文件，这些文件在调试时非常有用：


* `obj/boot/boot.asm`、`obj/kern/kernel.asm`、`obj/user/hello.asm`、等等。


引导加载器、内核、和用户程序的汇编代码列表。
* `obj/kern/kernel.sym`、`obj/user/hello.sym`、等等。


内核和用户程序的符号表。
* `obj/boot/boot.out`、`obj/kern/kernel`、`obj/user/hello`、等等。


内核和用户程序链接的 ELF 镜像。它们包含了 GDB 用到的符号信息。


#### GDB


完整的 GDB 命令指南请查看 [GDB 手册](http://sourceware.org/gdb/current/onlinedocs/gdb/)。下面是一些在 6.828 课程中非常有用的命令，它们中的一些在操作系统开发之外的领域几乎用不到。


* `Ctrl-c`


在当前指令处停止机器并打断进入到 GDB。如果 QEMU 有多个虚拟的 CPU，所有的 CPU 都会停止。
* `c`（或 `continue`）


继续运行，直到下一个断点或 `Ctrl-c`。
* `si`（或 `stepi`）


运行一个机器指令。
* `b function` 或 `b file:line`（或 `breakpoint`）


在给定的函数或行上设置一个断点。
* `b * addr`（或 `breakpoint`）


在 EIP 的 addr 处设置一个断点。
* `set print pretty`


启用数组和结构的美化输出。
* `info registers`


输出通用寄存器 `eip`、`eflags`、和段选择器。更多更全的机器寄存器状态转储，查看 QEMU 自己的 `info registers` 命令。
* `x/ N x addr`


以十六进制显示虚拟地址 addr 处开始的 N 个词的转储。如果 N 省略，默认为 1。addr 可以是任何表达式。
* `x/ N i addr`


显示从 addr 处开始的 N 个汇编指令。使用 `$eip` 作为 addr 将显示当前指令指针寄存器中的指令。
* `symbol-file file`


（在实验 3 以后）切换到符号文件 file 上。当 GDB 绑定到 QEMU 后，它并不是虚拟机中进程边界内的一部分，因此我们要去告诉它去使用哪个符号。默认情况下，我们配置 GDB 去使用内核符号文件 `obj/kern/kernel`。如果机器正在运行用户代码，比如是 `hello.c`，你就需要使用 `symbol-file obj/user/hello` 去切换到 hello 的符号文件。


QEMU 将每个虚拟 CPU 表示为 GDB 中的一个线程，因此你可以使用 GDB 中所有的线程相关的命令去查看或维护 QEMU 的虚拟 CPU。


* `thread n`


GDB 在一个时刻只关注于一个线程（即：CPU）。这个命令将关注的线程切换到 n，n 是从 0 开始编号的。
* `info threads`


列出所有的线程（即：CPU），包括它们的状态（活动还是停止）和它们在什么函数中。


#### QEMU


QEMU 包含一个内置的监视器，它能够有效地检查和修改机器状态。想进入到监视器中，在运行 QEMU 的终端中按入 `Ctrl-a c` 即可。再次按下 `Ctrl-a c` 将切换回串行控制台。


监视器命令的完整参考资料，请查看 [QEMU 手册](http://wiki.qemu.org/download/qemu-doc.html#pcsys_005fmonitor)。下面是 6.828 课程中用到的一些有用的命令：


* `xp/ N x paddr`


显示从物理地址 paddr 处开始的 N 个词的十六进制转储。如果 N 省略，默认为 1。这是 GDB 的 `x` 命令模拟的物理内存。
* `info registers`


显示机器内部寄存器状态的一个完整转储。实践中，对于段选择器，这将包含机器的 隐藏 段状态和局部、全局、和中断描述符表加任务状态寄存器。隐藏状态是在加载段选择器后，虚拟的 CPU 从 GDT/LDT 中读取的信息。下面是实验 1 中 JOS 内核处于运行中时的 CS 信息和每个字段的含义：



```
CS =0008 10000000 ffffffff 10cf9a00 DPL=0 CS32 [-R-]
```
* `CS =0008`


代码选择器可见部分。我们使用段 0x8。这也告诉我们参考全局描述符表（0x8&4=0），并且我们的 CPL（当前权限级别）是 0x8&3=0。
* `10000000`


这是段基址。线性地址 = 逻辑地址 + 0x10000000。
* `ffffffff`


这是段限制。访问线性地址 0xffffffff 以上将返回段违规异常。
* `10cf9a00`


段的原始标志，QEMU 将在接下来的几个字段中解码这些对我们有用的标志。
* `DPL=0`


段的权限级别。一旦代码以权限 0 运行，它将就能够加载这个段。
* `CS32`


这是一个 32 位代码段。对于数据段（不要与 DS 寄存器混淆了），另外的值还包括 `DS`，而对于本地描述符表是 `LDT`。
* `[-R-]`


这个段是只读的。
* `info mem`


（在实验 2 以后）显示映射的虚拟内存和权限。比如：



```
ef7c0000-ef800000 00040000 urw
efbf8000-efc00000 00008000 -rw
```

这告诉我们从 0xef7c0000 到 0xef800000 的 0x00040000 字节的内存被映射为读取/写入/用户可访问，而映射在 0xefbf8000 到 0xefc00000 之间的内存权限是读取/写入，但是仅限于内核可访问。
* `info pg`


（在实验 2 以后）显示当前页表结构。它的输出类似于 `info mem`，但与页目录条目和页表条目是有区别的，并且为每个条目给了单独的权限。重复的 PTE 和整个页表被折叠为一个单行。例如：



```
VPN range     Entry         Flags        Physical page
[00000-003ff]  PDE[000]     -------UWP
  [00200-00233]  PTE[200-233] -------U-P 00380 0037e 0037d 0037c 0037b 0037a ..
[00800-00bff]  PDE[002]     ----A--UWP
  [00800-00801]  PTE[000-001] ----A--U-P 0034b 00349
  [00802-00802]  PTE[002]     -------U-P 00348
```

这里各自显示了两个页目录条目、虚拟地址范围 0x00000000 到 0x003fffff 以及 0x00800000 到 0x00bfffff。 所有的 PDE 都存在于内存中、可写入、并且用户可访问，而第二个 PDE 也是可访问的。这些页表中的第二个映射了三个页、虚拟地址范围 0x00800000 到 0x00802fff，其中前两个页是存在于内存中的、可写入、并且用户可访问的，而第三个仅存在于内存中，并且用户可访问。这些 PTE 的第一个条目映射在物理页 0x34b 处。


QEMU 也有一些非常有用的命令行参数，使用 `QEMUEXTRA` 变量可以将参数传递给 JOS 的 makefile。


* `make QEMUEXTRA='-d int' ...`


记录所有的中断和一个完整的寄存器转储到 `qemu.log` 文件中。你可以忽略前两个日志条目、“SMM: enter” 和 “SMM: after RMS”，因为这些是在进入引导加载器之前生成的。在这之后的日志条目看起来像下面这样：



```
     4: v=30 e=0000 i=1 cpl=3 IP=001b:00800e2e pc=00800e2e SP=0023:eebfdf28 EAX=00000005
EAX=00000005 EBX=00001002 ECX=00200000 EDX=00000000
ESI=00000805 EDI=00200000 EBP=eebfdf60 ESP=eebfdf28
...
```

第一行描述了中断。`4:` 只是一个日志记录计数器。`v` 提供了十六进程的向量号。`e` 提供了错误代码。`i=1` 表示它是由一个 `int` 指令（相对一个硬件产生的中断而言）产生的。剩下的行的意思很明显。对于一个寄存器转储而言，接下来看到的就是寄存器信息。


注意：如果你运行的是一个 0.15 版本之前的 QEMU，日志将写入到 `/tmp` 目录，而不是当前目录下。




---


via: <https://pdos.csail.mit.edu/6.828/2018/labguide.html>


作者：[csail.mit](https://pdos.csail.mit.edu) 选题：[lujun9972](https://github.com/lujun9972) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
# 6.828 lab tools guide

Familiarity with your environment is crucial for productive
development and debugging. This page gives a brief overview of the
JOS environment and useful GDB and QEMU commands. Don't take our word
for it, though. Read the GDB and QEMU manuals. These are powerful
tools that are worth knowing how to use.

## Debugging tips

### Kernel

GDB is your friend. Use the `qemu-gdb` target (or its `qemu-gdb-nox` variant) to make
QEMU wait for GDB to attach. See the [GDB](#gdb) reference
below for some commands that are useful when debugging kernels.

If you're getting unexpected interrupts, exceptions, or triple
faults, you can ask QEMU to generate a detailed log of interrupts
using the [-d](#qemu--d) argument.

To debug virtual memory issues, try the QEMU monitor commands `info mem` (for a high-level
overview) or `info pg` (for lots
of detail). Note that these commands only display the *current*
page table.

(Lab 4+) To debug multiple CPUs, use GDB's thread-related commands
like `thread` and `info threads`.

### User environments (lab 3+)

GDB also lets you debug user environments, but there are a few
things you need to watch out for, since GDB doesn't know that there's
a distinction between multiple user environments, or between user and
kernel.

You can start JOS with a specific user environment using `make run-`*name* (or you can edit
`kern/init.c` directly). To make QEMU wait for GDB to attach,
use the `run-`*name*-gdb
variant.

You can symbolically debug user code, just like you can kernel
code, but you have to tell GDB which [symbol
table](#obj-elf) to use with the `symbol-file` command, since it
can only use one symbol table at a time. The provided
`.gdbinit` loads the kernel symbol table,
`obj/kern/kernel`. The symbol table for a user environment is
in its ELF binary, so you can load it using `symbol-file
obj/user/`*name*. *Don't* load symbols from any
`.o` files, as those haven't been relocated by the linker
(libraries are statically linked into JOS user binaries, so those
symbols are already included in each user binary). Make sure you get
the *right* user binary; library functions will be linked at
different EIPs in different binaries and GDB won't know any
better!

(Lab 4+) Since GDB is attached to the virtual machine as a whole,
it sees clock interrupts as just another control transfer. This makes
it basically impossible to step through user code because a clock
interrupt is virtually guaranteed the moment you let the VM run again.
The `stepi` command works because it
suppresses interrupts, but it only steps one assembly instruction. [Breakpoints](#gdb-b) generally work, but watch out because
you can hit the same EIP in a different environment (indeed, a
different binary altogether!).

## Reference

### JOS makefile

The JOS GNUmakefile includes a number of phony targets for running JOS
in various ways. All of these targets configure QEMU to listen for
GDB connections (the

`*-gdb` targets also wait for this
connection). To start once QEMU is running, simply run

`gdb`
from your lab directory. We provide a

`.gdbinit` file that
automatically points GDB at QEMU, loads the kernel symbol file, and
switches between 16-bit and 32-bit mode. Exiting GDB will shut down
QEMU.

`make qemu`
- Build everything and start QEMU with the VGA console in a new
window and the serial console in your terminal. To exit, either
close the VGA window or press
`Ctrl-c` or `Ctrl-a x`
in your terminal.
`make qemu-nox`
- Like
`make qemu`, but run with only the serial console.
To exit, press `Ctrl-a x`. This is particularly useful over
SSH connections to Athena dialups because the VGA window consumes a
lot of bandwidth.
`make qemu-gdb`
- Like
`make qemu`, but rather than passively accepting GDB
connections at any time, this pauses at the first machine
instruction and waits for a GDB connection.
`make qemu-nox-gdb`
- A combination of the
`qemu-nox` and `qemu-gdb`
targets.
`make run-`*name*
- (Lab 3+) Run user program
*name*. For example, `make
run-hello` runs `user/hello.c`.
`make run-`*name*-nox,
`run-`*name*-gdb,
`run-`*name*-gdb-nox,
- (Lab 3+) Variants of
`run-`*name* that correspond to
the variants of the `qemu` target.

The makefile also accepts a few useful variables:

`make V=1 ...`
- Verbose mode. Print out every command being executed, including
arguments.
`make V=1 grade`
- Stop after any failed grade test and leave the QEMU output in
`jos.out` for inspection.
- Specify additional arguments to pass to QEMU.

### JOS obj/

When building JOS, the makefile also produces some additional
output files that may prove useful while debugging:

`obj/boot/boot.asm`,
`obj/kern/kernel.asm`, `obj/user/hello.asm`, etc.
- Assembly code listings for the bootloader, kernel, and user
programs.
`obj/kern/kernel.sym`,
`obj/user/hello.sym`, etc.
- Symbol tables for the kernel and user programs.
`obj/boot/boot.out`, `obj/kern/kernel`,
`obj/user/hello`, etc
- Linked ELF images of the kernel and user programs. These
contain symbol information that can be used by GDB.

### GDB

See the [GDB
manual](http://sourceware.org/gdb/current/onlinedocs/gdb/) for a full guide to GDB commands. Here are some
particularly useful commands for 6.828, some of which don't typically
come up outside of OS development.

`Ctrl-c`
- Halt the machine and break in to GDB at the current
instruction. If QEMU has multiple virtual CPUs, this halts all of
them.
`c` (or `continue`)
- Continue execution until the next breakpoint or
`Ctrl-c`.
`si` (or `stepi`)
- Execute one machine instruction.
`b function` or `b file:line` (or
`breakpoint`)
- Set a breakpoint at the given function or line.
`b *`*addr* (or `breakpoint`)
- Set a breakpoint at the EIP
*addr*.
`set print pretty`
- Enable pretty-printing of arrays and structs.
`info registers`
- Print the general purpose registers,
`eip`,
`eflags`, and the segment selectors. For a much more
thorough dump of the machine register state, see QEMU's own `info
registers` command.
`x/`*N*x *addr*
- Display a hex dump of
*N* words starting at virtual address
*addr*. If *N* is omitted, it defaults to 1. *addr*
can be any expression.
`x/`*N*i *addr*
- Display the
*N* assembly instructions starting at
*addr*. Using `$eip` as *addr* will display the
instructions at the current instruction pointer.
`symbol-file `*file*
- (Lab 3+) Switch to symbol file
*file*. When GDB attaches
to QEMU, it has no notion of the process boundaries within the
virtual machine, so we have to tell it which symbols to use. By
default, we configure GDB to use the kernel symbol file,
`obj/kern/kernel`. If the machine is running user code, say
`hello.c`, you can switch to the hello symbol file using
`symbol-file obj/user/hello`.

QEMU represents each virtual CPU as a thread in GDB, so you can use
all of GDB's thread-related commands to view or manipulate QEMU's
virtual CPUs.

`thread `*n*
- GDB focuses on one thread (i.e., CPU) at a time. This command
switches that focus to thread
*n*, numbered from zero.
`info threads`
- List all threads (i.e., CPUs), including their state (active or
halted) and what function they're in.

### QEMU

QEMU includes a built-in monitor that can inspect and modify the
machine state in useful ways. To enter the monitor, press `Ctrl-a
c` in the terminal running QEMU. Press `Ctrl-a c` again
to switch back to the serial console.

For a complete reference to the monitor commands, see the [QEMU
manual](http://wiki.qemu.org/download/qemu-doc.html#pcsys_005fmonitor). Here are some particularly useful commands:

`xp/`*N*x *paddr*
- Display a hex dump of
*N* words starting at *physical*
address *paddr*. If *N* is omitted, it defaults to 1.
This is the physical memory analogue of GDB's `x`
command.
`info registers`
- Display a full dump of the machine's internal register state.
In particular, this includes the machine's
*hidden* segment
state for the segment selectors and the local, global, and interrupt
descriptor tables, plus the task register. This hidden state is the
information the virtual CPU read from the GDT/LDT when the segment
selector was loaded. Here's the CS when running in the JOS kernel
in lab 1 and the meaning of each field:
CS =0008 10000000 ffffffff 10cf9a00 DPL=0 CS32 [-R-]

`CS =0008`
- The visible part of the code selector. We're using segment
0x8. This also tells us we're referring to the global descriptor
table (0x8&4=0), and our CPL (current privilege level) is
0x8&3=0.
`10000000`
- The base of this segment. Linear address = logical address +
0x10000000.
`ffffffff`
- The limit of this segment. Linear addresses above 0xffffffff
will result in segment violation exceptions.
`10cf9a00`
- The raw flags of this segment, which QEMU helpfully decodes
for us in the next few fields.
`DPL=0`
- The privilege level of this segment. Only code running with
privilege level 0 can load this segment.
`CS32`
- This is a 32-bit code segment. Other values include
`DS` for data segments (not to be confused with the DS
register), and `LDT` for local descriptor tables.
`[-R-]`
- This segment is read-only.

`info mem`
- (Lab 2+) Display mapped virtual memory and permissions. For
example,
ef7c0000-ef800000 00040000 urw
efbf8000-efc00000 00008000 -rw

tells us that the 0x00040000 bytes of memory from 0xef7c0000 to
0xef800000 are mapped read/write and user-accessible, while the
memory from 0xefbf8000 to 0xefc00000 is mapped read/write, but only
kernel-accessible.
`info pg`
- (Lab 2+) Display the current page table structure. The output
is similar to
`info mem`, but distinguishes page directory
entries and page table entries and gives the permissions for each
separately. Repeated PTE's and entire page tables are folded up
into a single line. For example,
VPN range Entry Flags Physical page
[00000-003ff] PDE[000] -------UWP
[00200-00233] PTE[200-233] -------U-P 00380 0037e 0037d 0037c 0037b 0037a ..
[00800-00bff] PDE[002] ----A--UWP
[00800-00801] PTE[000-001] ----A--U-P 0034b 00349
[00802-00802] PTE[002] -------U-P 00348

This shows two page directory entries, spanning virtual addresses
0x00000000 to 0x003fffff and 0x00800000 to 0x00bfffff, respectively.
Both PDE's are present, writable, and user and the second PDE is also
accessed. The second of these page tables maps three pages, spanning
virtual addresses 0x00800000 through 0x00802fff, of which the first
two are present, user, and accessed and the third is only present and
user. The first of these PTE's maps physical page 0x34b.

QEMU also takes some useful command line arguments, which can be
passed into the JOS makefile using the

[QEMUEXTRA](#make-qemuextra) variable.

`make QEMUEXTRA='-d int' ...`
- Log all interrupts, along with a full register dump, to
`qemu.log`. You can ignore the first two log entries, "SMM:
enter" and "SMM: after RMS", as these are generated before entering
the boot loader. After this, log entries look like
4: v=30 e=0000 i=1 cpl=3 IP=001b:00800e2e pc=00800e2e SP=0023:eebfdf28 EAX=00000005
EAX=00000005 EBX=00001002 ECX=00200000 EDX=00000000
ESI=00000805 EDI=00200000 EBP=eebfdf60 ESP=eebfdf28
...

The first line describes the interrupt. The `4:` is just a
log record counter. `v` gives the vector number in hex.
`e` gives the error code. `i=1` indicates that this
was produced by an `int`

instruction (versus a hardware
interrupt). The rest of the line should be self-explanatory. See
[info registers](#qemu-info-registers) for a description
of the register dump that follows.
- Note: If you're running a pre-0.15 version of QEMU, the log will
be written to
`/tmp` instead of the current directory.


Questions or comments regarding 6.828? Send e-mail to the TAs at
*6828-staff@lists.csail.mit.edu*.

![Creative Commons License](https://i.creativecommons.org/l/by/3.0/us/88x31.png)

[Top](#top) //
[6.828 home](index.html) //
[Accessibility](https://accessibility.mit.edu/) //
*Last updated Wednesday, 22-Sep-2021 12:14:47 EDT*