E. Debugging Tools#

您可以使用许多工具来调试 Pintos。 本附录向您介绍其中的一些。

E.1 printf()#

不要低估 printf() 的价值。 在Pintos中已经实现了printf(),您几乎可以从内核中的任何位置调用它,无论是在内核线程中还是在中断处理程序中,而且几乎不用考虑持有什么锁。

printf()不仅仅用于检查数据。它还可以帮助确定出现问题的时间和地点,即使内核崩溃或panic而没有有用的错误消息。策略是在您怀疑失效的代码片段中使用不同的字符串(例如 "<1>""<2>"、...)来调用 printf()。 如果你甚至没有看到<1>打印,那么在那之前发生了一些不好的事情,如果你看到<1>但没有看到<2>,那么这两个点之间发生了一些不好的事情,依此类推. 根据调试结果,您可以在您怀疑的新的、较小的代码区域中插入更多的“printf()”调用。 最终,您可以将问题缩小到单个语句。相关技术,请参见 E.6 Triple Faults部分。

E.2 ASSERT#

断言很有用,因为它们可以在问题被注意到之前及早发现问题。理想情况下,每个函数都应该以一组断言开始,以检查其参数的有效性。(函数局部变量的初始化程序在检查断言之前进行求值,因此请注意不要假设参数在初始化程序中有效。)您还可以在函数体中您怀疑可能出错的地方放置断言.这在检查循环不变式(loop invariant)时特别有用。

Pintos 提供了在 中定义的 ASSERT 宏,用于检查断言。

Macro: ASSERT (expression)

测试表达式的值。如果它的计算结果为零(false),内核panic。panic消息包括失败的表达式、它的文件和行号,以及一个回溯,它应该可以帮助您找到问题。有关详细信息,请参阅 E.4 回溯

E.3 函数和参数属性#

这些在中定义的宏告诉编译器函数或函数参数的特殊属性。它们的扩展符合GCC规定。

Macro: UNUSED

附加到函数参数以告诉编译器该参数可能不会在函数中使用。否则编译器会给出没有使用的警告。

Macro: NO_RETURN

附加到函数原型以告诉编译器该函数永远不会返回。 它允许编译器微调其警告和代码生成。

Macro: NO_INLINE

附加到函数原型以告诉编译器永远不要生成内联函数。有时候这有助于提高回溯的质量(见下文)。

Macro: PRINTF_FORMAT (format, first)

附加到函数原型以告诉编译器该函数采用类似“printf()”的格式字符串作为参数编号format(从1开始),并且相应的值参数从first编号的参数开始。这让编译器告诉你是否传递了错误的参数类型。

E.4 回溯#

当内核崩溃时,它会打印一个“回溯”,即程序如何到达它的位置的摘要,以及崩溃时正在运行的函数内的地址列表。您还可以插入对在中原型化的 debug_backtrace() 的调用,以在代码中的任何位置打印回溯。 debug_backtrace_all(),也在 中声明,打印所有线程的回溯。

回溯中的地址被列为原始十六进制数字,这很难理解。我们提供了一个名为backtrace的工具来将这些地址转换为函数名和源文件行号。将您的kernel.o的名称作为第一个参数,将组成回溯的十六进制数字(包括0x前缀)作为其余参数,它将输出每个地址对应的函数名和源文件行号。

如果回溯的翻译形式是乱码,或者没有意义(例如,函数A列在函数B之上,但B没有调用A),那么这是一个好迹象,表明您正在破坏内核线程的堆栈,因为回溯是从堆栈中提取的。或者,可能是您传递给“回溯”的kernel.o与产生回溯的内核不同。

有时回溯可能看上去很正常但结果令人困惑。编译器优化可能会导致令人惊讶的行为。当一个函数调用另一个函数作为其最终操作(tail call)时,调用函数可能根本不会出现在回溯中。类似地,当函数A调用另一个永远不会返回的函数B时,编译器可能会进行优化,使得不相关的函数C出现在回溯中而不是A。函数C只是恰好在A之后的内存中的函数。在threads项目中,这在测试失败的回溯中很常见;有关详细信息,请参阅 pass() Fails

E.4.1 例子#

这是一个例子。 假设Pintos打印出以下调用堆栈,该调用堆栈取自文件系统项目的实际 Pintos 提交:

Call stack: 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8.

然后,您将调用如下所示的backtrace实用程序,将回溯信息剪切并粘贴到命令行中。假设kernel.o在当前目录中。您当然会在单个shell命令行中输入以下所有内容,即使这会超出边界:

backtrace kernel.o 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8

回溯输出将如下所示:

0xc0106eff: debug\_panic (lib/debug.c:86)
0xc01102fb: file\_seek (filesys/file.c:405)
0xc010dc22: seek (userprog/syscall.c:744)
0xc010cf67: syscall\_handler (userprog/syscall.c:444)
0xc0102319: intr\_handler (threads/interrupt.c:334)
0xc010325a: intr\_entry (threads/intr-stubs.S:38)
0x0804812c: (unknown)
0x08048a96: (unknown)
0x08048ac8: (unknown)

(如果您在自己的内核二进制文件上运行上述命令,您可能不会看到完全相同的地址,因为您编译的源代码和您使用的编译器可能不同。)

回溯中的第一行是 debug_panic(),实现内核panic的函数。 因为回溯通常是由内核panic导致的,所以debug_panic()经常是回溯中显示的第一个函数。

第二行显示file_seek()作为发生panic的函数,在这里是断言失败的结果。在本示例使用的源代码树中,filesys/file.c 的第405行是断言

ASSERT (file_ofs >= 0);

(断言失败消息中也引用了这一行。)因此,file_seek()发生panic,因为它传递了一个负的文件偏移参数。

第三行表明 seek() 调用了 file_seek(),大概没有验证 offset 参数。 在本次提交中,seek() 实现了seek 系统调用。

第四行显示系统调用处理程序syscall_handler()调用了seek()

第五行和第六行是中断处理程序入口路径。

其余行用于 PHYS_BASE 下面的地址。 这意味着它们指的是用户程序中的地址,而不是内核中的地址。 如果您知道内核崩溃时正在运行什么用户程序,您可以在用户程序上重新运行 backtrace,如下所示:(当然,在一行中键入命令):

backtrace tests/filesys/extended/grow-too-big 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8

结果如下所示:

0xc0106eff: (unknown)
0xc01102fb: (unknown)
0xc010dc22: (unknown)
0xc010cf67: (unknown)
0xc0102319: (unknown)
0xc010325a: (unknown)
0x0804812c: test\_main (...xtended/grow-too-big.c:20)
0x08048a96: main (tests/main.c:10)
0x08048ac8: \_start (lib/user/entry.c:9)

您甚至可以在命令行上同时指定内核和用户程序名称,如下所示:

backtrace kernel.o tests/filesys/extended/grow-too-big 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8

结果是一个组合的回溯:

In kernel.o:
0xc0106eff: debug\_panic (lib/debug.c:86)
0xc01102fb: file\_seek (filesys/file.c:405)
0xc010dc22: seek (userprog/syscall.c:744)
0xc010cf67: syscall\_handler (userprog/syscall.c:444)
0xc0102319: intr\_handler (threads/interrupt.c:334)
0xc010325a: intr\_entry (threads/intr-stubs.S:38)
In tests/filesys/extended/grow-too-big:
0x0804812c: test\_main (...xtended/grow-too-big.c:20)
0x08048a96: main (tests/main.c:10)
0x08048ac8: \_start (lib/user/entry.c:9)

这里有一个额外的提示给读到这里的读者:backtrace足够聪明,它会剥离Call stack:标头和末尾的. (如果您的命令包含这些)。这可以为您节省一些剪切和粘贴的麻烦。因此,以下命令打印的输出与我们使用的第一个命令相同:

backtrace kernel.o Call stack: 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8.

E.5 GDB#

您可以在 GDB 调试器的监督下运行 Pintos。 首先,使用 --gdb 选项启动 Pintos,例如 pintos --gdb -- run mytest。其次,在同一台机器上打开第二个终端并使用pintos-gdb调用kernel.o上的GDB:(8)

pintos-gdb kernel.o

并发出以下 GDB 命令: 

target remote localhost:1234

现在GDB通过本地网络连接到模拟器。您现在可以发出任何普通的GDB命令。如果您发出c命令,模拟的 BIOS 将获得控制权,加载 Pintos,然后 Pintos 将以通常的方式运行。您可以随时使用 Ctrl+C 暂停该进程。

E.5.1 使用GDB#

您可以通过在终端命令提示符下键入“info gdb”来阅读GDB手册。下面是一些常用的GDB命令:

GDB Command: c

继续执行直到 Ctrl+C 或下一个断点.

GDB Command: si

执行一条机器指令.

GDB Command: break function

GDB Command: break file:line

GDB Command: break *address

在函数、文件中的行或地址处设置断点。(使用"0x"前缀以十六进制指定地址。)

当Pintos开始运行时,使用 break main 使 GDB 停止。

GDB Command: p expression

评估给定的表达式并打印它的值。如果表达式包含函数调用,则该函数将实际执行。

GDB Command: l *address

列出地址周围的几行代码。 (使用 0x 前缀以十六进制指定地址。)

GDB Command: bt

打印类似于上述 backtrace 程序输出的堆栈回溯。

GDB Command: p/a address

打印占用地址的函数或变量的名称。 (使用0x前缀以十六进制指定地址。)

GDB Command: diassemble function

反汇编函数。

我们还提供了一组专门用于调试Pintos的宏,由Godmar Back编写。 您可以键入“帮助用户定义”以获取有关宏的基本帮助。以下是基于Godmar文档的功能概述:

GDB Macro: debugpintos

将调试器附加到同一台机器上等待的pintos进程。 target remote localhost:1234 的简写。

GDB Macro: dumplist &list type element

打印 list 的元素,它应该是一个 struct 列表,其中包含给定类型的元素(没有单词 struct),其中 element 是链接元素的 struct list_elem 成员。

示例:dumplist &all_list thread allelem 使用struct list_elem allelem 打印在struct list all_list 中链接的struct thread 的所有元素,struct list_elem allelemstruct thread 的一部分。

GDB Macro: btthread thread

显示线程的回溯,它是指向应该显示回溯的线程的struct thread的指针。 对于当前线程,这与 bt(回溯)命令相同。 它也适用于任何挂起在 schedule() 中的线程,前提是您知道其内核堆栈页面的位置。

GDB Macro: btthreadlist list element

显示列表中所有线程的回溯,即保存线程的“结构列表”。 将 element 指定为 struct thread 中使用的 struct list_elem 字段以将线程链接在一起。

示例:btthreadlist all_list allelem 显示了包含在 struct list all_list 中的所有线程的回溯,通过 allelem 链接在一起。 此命令可用于确定发生死锁时线程卡在的位置。 请参阅下面的示例场景。

GDB Macro: btthreadall

btthreadlist all_list allelem 的简写。

GDB Macro: btpagefault

在页面错误异常之后打印当前线程的回溯。 通常,当发生页面错误异常时,GDB 将停止并显示一条消息:(9)

Program received signal 0, Signal 0. 0xc0102320 in intr0e_stub ()

在这种情况下,bt 命令可能不会提供有用的回溯。 请改用 btpagefault

您也可以使用 btpagefault 来处理用户进程中发生的页面错误。 在这种情况下,您可能还希望使用 loadusersymbols 宏加载用户程序的符号表,如上所述。

GDB Macro: hook-stop

每次模拟停止时,GDB都会调用此宏,Bochs将针对每个处理器异常以及其他原因执行此操作。 如果模拟由于页面错误而停止,“hook-stop”将打印一条消息,说明并进一步解释页面错误是发生在内核还是用户代码中。

如果用户代码发生异常,hook-stop 会说:

pintos-debug: a page fault exception occurred in user mode pintos-debug: hit 'c' to continue, or 's' to step to intr_handler

在项目2中,用户进程中的页面错误导致进程终止。 您应该期望这些页面错误发生在我们测试您的内核是否正确终止尝试访问无效地址的进程的健壮性测试中。 要调试它们,请在 exception.c 中的 page_fault() 中设置一个断点,您需要相应地对其进行修改。

在项目3中,用户进程中的页面错误不再自动导致进程终止。 相反,它可能需要读取进程试图访问的页面的数据,或者因为它被换出或者因为这是它第一次被访问。 在任何一种情况下,您都会到达 page_fault() 并需要在那里采取适当的措施。

如果页面错误在执行用户进程时没有发生在用户模式下,那么它在执行内核代码时发生在内核模式下。 在这种情况下,hook-stop 将打印以下消息:

pintos-debug: a page fault occurred in kernel mode

然后是 btpagefault 命令的输出。

在项目3之前,内核代码中的页面错误异常始终是内核中的错误,因为您的内核永远不会崩溃。 从项目 3 开始,如果您使用 get_user()put_user() 策略来验证用户内存访问,情况将会改变(请参阅第 3.1.5 访问用户内存)。

E.5.2 GDB会话实例#

本节讲述一个示例GDB会话,由Godmar Back提供。这个例子说明了如何调试Project 1解决方案,其中偶尔调用timer_sleep()的线程没有被唤醒。有了这个错误,诸如“mlfqs_load_1”之类的测试就会卡住。

该会话是使用稍旧版本的 Bochs 和 Pintos 的 GDB 宏捕获的,因此它看起来与现在略有不同。 程序输出以普通类型显示,用户输入以黑体显示。

首先,启动Pintos:

$ pintos -v --gdb -- -q -mlfqs run mlfqs-load-1
Writing command line to /tmp/gDAlqTB5Uf.dsk...
bochs -q
========================================================================
Bochs x86 Emulator 2.2.5
Build from CVS snapshot on December 30, 2005
========================================================================
00000000000i[ ] reading configuration from bochsrc.txt
00000000000i[ ] Enabled gdbstub
00000000000i[ ] installing nogui module as the Bochs GUI
00000000000i[ ] using log file bochsout.txt
Waiting for gdb connection on localhost:1234

然后,在同一台机器上打开第二个窗口并启动 GDB:

$ pintos-gdb kernel.o
GNU gdb Red Hat Linux (6.3.0.0-1.84rh)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
Using host libthread_db library "/lib/libthread_db.so.1".

然后,告诉GDB附加到等待的Pintos模拟器:

(gdb) debugpintos
Remote debugging using localhost:1234
0x0000fff0 in ?? ()
Reply contains invalid hex digit 78

现在通过执行 ccontinue 的缩写)两次来告诉 Pintos 运行:

(gdb) c
Continuing.
Reply contains invalid hex digit 78
(gdb) c
Continuing.

现在 Pintos 将继续并输出:

Pintos booting with 4,096 kB RAM...
Kernel command line: -q -mlfqs run mlfqs-load-1
374 pages available in kernel pool.
373 pages available in user pool.
Calibrating timer... 102,400 loops/s.
Boot complete.
Executing 'mlfqs-load-1':
(mlfqs-load-1) begin
(mlfqs-load-1) spinning for up to 45 seconds, please wait...
(mlfqs-load-1) load average rose to 0.5 after 42 seconds
(mlfqs-load-1) sleeping for another 10 seconds, please wait...

...直到它因为遇到错误而卡住。在调试器窗口中按 Ctrl+C:

Program received signal 0, Signal 0.
0xc010168c in next_thread_to_run () at ../../threads/thread.c:649
649 while (i <= PRI_MAX && list_empty (&ready_list[i]))
(gdb)

当中断Pintos时正在运行的线程是空闲线程。如果运行“回溯”,它会显示这个回溯:

(gdb) bt

#0  0xc010168c in next\_thread\_to\_run () at ../../threads/thread.c:649  
#1  0xc0101778 in schedule () at ../../threads/thread.c:714  
#2  0xc0100f8f in thread\_block () at ../../threads/thread.c:324  
#3  0xc0101419 in idle (aux=0x0) at ../../threads/thread.c:551  
#4  0xc010145a in kernel\_thread (function=0xc01013ff , aux=0x0)  
    at ../../threads/thread.c:575  
#5  0x00000000 in ?? ()  

不是非常有用。 我真正想知道的是其他线程(或线程)的情况。 由于我将所有线程保存在一个名为 all_list 的链表中,并由一个名为 allelemstruct list_elem 成员链接在一起,因此我可以使用我编写的宏库中的 btthreadlist 宏。 btthreadlist 遍历线程列表并打印每个线程的回溯:

(gdb) btthreadlist all_list allelem

pintos-debug: dumping backtrace of thread 'main' @0xc002f000  
#0  0xc0101820 in schedule () at ../../threads/thread.c:722  
#1  0xc0100f8f in thread\_block () at ../../threads/thread.c:324  
#2  0xc0104755 in timer\_sleep (ticks=1000) at ../../devices/timer.c:141  
#3  0xc010bf7c in test\_mlfqs\_load\_1 () at ../../tests/threads/mlfqs-load-1.c:49  
#4  0xc010aabb in run\_test (name=0xc0007d8c "mlfqs-load-1")  
    at ../../tests/threads/tests.c:50  
#5  0xc0100647 in run\_task (argv=0xc0110d28) at ../../threads/init.c:281  
#6  0xc0100721 in run\_actions (argv=0xc0110d28) at ../../threads/init.c:331  
#7  0xc01000c7 in main () at ../../threads/init.c:140  

pintos-debug: dumping backtrace of thread 'idle' @0xc0116000  
#0  0xc010168c in next\_thread\_to\_run () at ../../threads/thread.c:649  
#1  0xc0101778 in schedule () at ../../threads/thread.c:714  
#2  0xc0100f8f in thread\_block () at ../../threads/thread.c:324  
#3  0xc0101419 in idle (aux=0x0) at ../../threads/thread.c:551  
#4  0xc010145a in kernel\_thread (function=0xc01013ff , aux=0x0)  
    at ../../threads/thread.c:575  
#5  0x00000000 in ?? ()  

在这种情况下,只有两个线程,空闲线程和主线程。 内核堆栈页面(struct thread 指向的)分别位于 0xc0116000 和 0xc002f000。 主线程卡在从 test_mlfqs_load_1 调用的timer_sleep() 中。

知道线程卡在哪里非常有用,例如在诊断死锁或无法解释的挂起时。

GDB Macro: loadusersymbols

您还可以使用 GDB 调试在 Pintos 下运行的用户程序。 为此,请使用 loadusersymbols 宏来加载程序的符号表:

loadusersymbols program

其中 program 是程序可执行文件的名称(在主机文件系统中,而不是在 Pintos 文件系统中)。 例如,您可能会发出:

(gdb) loadusersymbols tests/userprog/exec-multiple
add symbol table from file "tests/userprog/exec-multiple" at
.text_addr = 0x80480a0
(gdb)

在此之后,您应该能够像调试内核一样调试用户程序,通过放置断点、检查数据等。您的操作适用于在 Pintos 中运行的每个用户程序,而不仅仅是您要调试的用户程序, 所以在解释结果时要小心:GDB 不知道哪个进程当前处于活动状态(因为这是 Pintos 内核创建的抽象)。 此外,出现在内核和用户程序中的名称实际上是指内核名称。 (后一个问题可以通过在 GDB 命令行中给出用户可执行文件名而不是 kernel.o,然后使用 loadusersymbols 加载 kernel.o 来避免。)loadusersymbols 是通过 GDB 的 add-symbol- 文件命令。

E.5.3 FAQ#

GDB can't connect to Bochs.

If the target remote command fails, then make sure that both GDB and pintos are running on the same machine by running hostname in each terminal. If the names printed differ, then you need to open a new terminal for GDB on the machine running pintos.

GDB doesn't recognize any of the macros.

If you start GDB with pintos-gdb, it should load the Pintos macros automatically. If you start GDB some other way, then you must issue the command source pintosdir/src/misc/gdb-macros, where pintosdir is the root of your Pintos directory, before you can use them.

Can I debug Pintos with DDD?

Yes, you can. DDD invokes GDB as a subprocess, so you'll need to tell it to invokes pintos-gdb instead:

ddd --gdb --debugger pintos-gdb

Can I use GDB inside Emacs?

Yes, you can. Emacs has special support for running GDB as a subprocess. Type M-x gdb and enter your pintos-gdb command at the prompt. The Emacs manual has information on how to use its debugging features in a section titled "Debuggers."

GDB is doing something weird.

如果您在使用 GDB 时发现奇怪的行为,有以下三种可能性:修改后的 Pintos 中的错误、Bochs 与 GDB 的接口或 GDB 本身中的错误,或原始 Pintos 代码中的错误。 第一个和第二个很有可能,你应该认真考虑两者。 我们希望第三种可能性较小,但也有可能。

E.6 Triple Faults#

当 CPU 异常处理程序(例如页面错误处理程序)由于缺失(missing)或缺陷(defective)而无法调用时,CPU 将尝试调用“双重错误”处理程序。如果双重故障处理程序本身缺失或有缺陷,则称为“三重故障”。三重故障会导致CPU立即复位。

因此,如果您碰到机器循环重启的情况,那可能是“三重故障”。在三重故障情况下,您可能无法使用 printf() 进行调试,因为甚至在 printf() 所需的所有内容都初始化之前可能会重新启动。

至少有两种方法可以调试三重故障。首先,您可以在 GDB 下的 Bochs 中运行 Pintos(参见 E.5 GDB 部分)。如果已经为 Pintos 正确构建了 Bochs,则 GDB 下的三重故障将导致它在控制台上打印消息“三重故障:为 gdb 停止”并闯入调试器。 (如果 Bochs 没有在 GDB 下运行,三重故障仍会导致它重新启动。)然后您可以检查 Pintos 停止的位置,即三重故障发生的位置。

另一种选择是我所说的“无限循环调试”。在 Pintos 代码中选择一个位置,插入无限循环 for (;;);在那里,然后重新编译并运行。有两种可能的可能性:

机器挂起而不重新启动。如果发生这种情况,您就知道无限循环正在运行。这意味着导致重新启动的任何原因都必须在您插入无限循环的位置之后。现在在代码序列中稍后移动无限循环。

机器循环重启。如果发生这种情况,您就知道机器没有进入无限循环。因此,导致重新启动的任何原因都必须在您插入无限循环的位置之前。现在将无限循环移到代码序列的前面。

如果你以“二分搜索”的方式在无限循环中移动,你可以使用这种技术来确定一切出错的确切位置。最多只需要几分钟。

E.7 Modifying Bochs#

一种高级调试技术是修改和重新编译模拟器。 当模拟硬件的信息多于操作系统可用的信息时,这证明是有用的。 例如,页面错误有很长的潜在原因列表,但硬件不会向操作系统准确报告哪一个是特定原因。 再者,内核处理缺页错误的bug很容易导致递归错误,但“三重错误”会导致CPU自行复位,不利于调试。

在这种情况下,您可能会喜欢让 Bochs 打印出更多的调试信息,例如发生的故障的确切类型。 这不是很难。 首先从 http://bochs.sourceforge.net 检索 Bochs 2.2.6 的源代码并将文件 bochs-2.2.6.tar.gz 保存到 目录。 脚本 pintos/src/misc/bochs-2.2.6-build.sh 将 pintos/src/misc 中包含的许多补丁应用到 Bochs 树,然后构建 Bochs 并将其安装在您选择的目录中。 不带参数运行此脚本以了解使用说明。 要将 bochs 二进制文件与 pintos 一起使用,请将其放在 PATH 中,并确保它早于 /usr/class/cs140/`uname -m`/bin/bochs。

当然,要从中获得任何好处,您必须实际修改 Bochs。 执行此操作的说明完全超出了本文档的范围。 但是,如果您想按照上面的建议调试页面错误,开始添加 printf() 的一个好地方是 cpu/paging.cc 中的 BX_CPU_C::dtranslate_linear()

E.8 Tips#

“threads/palloc.c”中的页面分配器和“threads/malloc.c”中的块分配器在空闲时将内存中的所有字节清除为0xcc。 因此,如果您看到尝试解引用 0xcccccccc 之类的指针,或对 0xcc 的其他引用,则很有可能您正在尝试重用已被释放的页面。此外,字节0xcc是“调用中断 3”的 CPU 操作码,因此如果您看到类似中断 0x03(#BP 断点异常)的错误,那么 Pintos 会尝试在已释放的页面或块中执行代码。

表达式 sec_no < d->capacity 的断言失败表明 Pintos 试图通过已关闭和释放的 inode 访问文件。 释放一个 inode 会将其起始扇区号清除为 0xcccccccc,对于小于约 1.6 TB 的磁盘,这不是一个有效的扇区号。