一般的调试流程其实很简单:发现问题,稳定复现,确定临界条件,定位问题,修复问题,核查结果。迭代这个过程,形成一个闭环

老实说,OS的实验代码,开箱体验极差,程序跳来跳去,进了Lab4后还要考虑内核态切换,很难靠肉眼完成上述闭环。debug愉悦指数为负。

所以在几周的探索后,我大概总结整理了一些调试经验,主要是如何在当前体系下利用或构建调试工具,改善调试体验。

我们的口号是:没有蛀牙

抛砖引玉。

从0开始:现在我们有什么

现在我们手里有:

  • 一坨新鲜的系统代码。
  • 上学期积累的老练的MIPS 32汇编开发与理论基础,虽然这个系统用的是MIPS 2
  • 对高级语言特别是面向对象语言的美好眷恋
  • 全宇宙最牛逼的编程语言:C,以及全宇宙最牛逼的C tool chain:GCC

除此之外我们还有:

  • 凑活能用的printf
  • 助教为我们封装好的panicassert,她们是海滩上最抢眼的宝贝儿
  • 用户态的一些函数,但说真的它们不太好用
  • 一颗勇敢的心

Level 0:静态调试

也就是所谓的“瞪眼法”。能瞪出来的Bug请直接瞪出来。

瞪眼法的升级版是小黄鸭调试法,进一步升级版是面向室友调试,终极形态是面向男朋友/女朋友/老妈调试

别漏了这一步,有时候瞪是最快的,但这也要求你对OS实验到底要干什么有完全清晰的把控。

记得调VIM配色,记得买珍视明。

Level 1: 断言与防御性编程

assert这个东西可能我们已经在各种check里面见过了。它的实现在include/mm.h中(很奇怪的位置),在我们的代码中是以宏的形式实现的:

#define assert(x) do {if(!(x)) panic("...%s", #x); } while(0)

作者十分老练,这里的trick是用拼接运算将参数x作为一个字符串传入panic,这样输出时我们就可以看到断言内的具体内容。

现在assert的功能就是这样的:如果传入的语句x布尔值为假,就陷入panic(顺带一提,panic其实就是在输出信息后陷入死循环)。熟悉其他语言的断言机制,比如python,的同学应该会感到十分亲切

这个东西有什么用呢?我们可以在代码中合适的位置加入断言,来显示地检查某段运行逻辑是否如我们的预期。

举个例子,page_insert后,插入的页面所对应的物理地址应该等于虚拟地址va所映射的物理地址(这正是这个函数的功能),因此在函数的末尾我们可以添加一句断言:

assert(va2pa(pgdir, va) == page2pa(pp));

当程序运行到这里时,如果没有出现错误,那么这句assert不会产生任何作用,如果出现了各种情况导致这两个物理地址不一致,那么程序就会自陷终止并输出信息。当我们调试时,如果看到了它自陷,我们就能知道这里的代码一定存在逻辑漏洞(或者,大灾难),在它引起更隐晦的错误前将其修复。

当然assert也不是完全没有副作用:它依旧会占用指令运行周期数,这个问题我们之后再讨论。

Level 2:把printf撒的满屋子都是

然后因为忘了删调试语句,喜提评测0/100

Level 3:更好用的输出调试信息:封装一个debug()

printf的问题在于,开关并不方便。很难做到只做少量的修改将所有调试信息全部关闭。因此这里开始我们需要自己造轮子

稍微优雅一些的做法是,声明一个调试宏或一个全局变量,来控制全局的调试开关

宏的写法类似于:

# ifndef __DEBUG__
# define __DEBUG__
# endif ... # ifdef __DEBUG__
printf("SOME DEBUG INFO\n");
# endif

全局变量的写法则是

extern int is_debug_mode = 1;

...

if (is_debug_mode) {
printf("SOME DEBUG INFO\n");
}

宏的一个优势在于,如果我们关掉了调试,整个调试代码块不会被编译,执行的语句更少

直接用变量则当然更可读,更美观,并且可以更轻松地实现调试信息分级

无论哪一种,每次输出的时候敲三行代码太复杂了,因此终极形式是包装一个debug函数


void _debug(char * file, int line, char *fmt, ...){
if (is_debug_mode) printf("SAY SOMETHING I'M GIVING UPON YOU.\n");
} # define debug(...) _debug(__FILE__, __LINE__, __VA__ARGS__)

这样我们就能像使用printf一样使用debug(支持可变参数),同时还能输出更多调试信息,比如这一句在哪个文件的哪一行

然后在init中控制debug的全局开闭就好

Level 3.5 汇编调试:相信聪明的MIPS一定帮我们搞定了

上面的各种方法都是针对C语言程序的,汇编则不太好办。大体上有两个思路:

  1. 为某一部分特定信息的输出单独封装一个函数,并在汇编中JAL跳转链接调用,比如上机时我们写过的output_ov_info(我怎么可能记得它叫什么,反正差不多就这个东西)。这个东西使用场景太杂乱,不展开了
  2. 加断点,断点是好文明。

MIPS为我们提供了BREAK和一系列Trap指令,这里先只讨论Break的使用,Trap是类似的。

BREAK的作用是,抛出一个bp断点异常并使CPU切入内核态处理异常。bp的cause编码是9,为了让我们的小系统可以处理这个异常我们需要仿照课上的ov为其添加一个handler

  1. 修改lib/trap.c,声明外链接extern handle_bp();,然后将其绑定在9号异常上
  2. lib/genex.S中添加handle_bp的处理程序。推荐使用BUILD_HANDLER bp break_handler cli将异常处理移交给一个C语言函数break_handler(struct Trapframe *)完成

然后我们在异常处理中输出各种需要的信息,末尾死循环即可(也就是,panic)。最重要的信息或许是pc。

如果想要更好的体验,添加一个读入字符syscall(参考lab1某次课上),让我们可以向系统输入字符,然后把break的死循环换成等待读入。这么做的时候记得处理中断屏蔽

回过头看C程序的调试,用类似的技巧也可以实现断点调试。如果需要可以再封装一个bp()配合debug()使用。


再回过头看Trap(TEQ、TNE等一系列条件内陷语句),我们可以用类似的方法为其添加异常处理程序,这些指令抛出的异常都是TRAP(13),因此我们只需要把处理程序挂在13号异常上就可以令其运作。

Trap很像MIPS版的assert,当满足某个条件时让内核自陷。因此在汇编代码中善用这些指令也能起到尽早发现并规避错误的作用。


或许我们可以进一步挖掘MIPS的片上调试,但目前我还没有遇到这种粒度的需求。

Level 4

更多奇技淫巧,我自己还在探索与试用,如果体验不错的话之后再更新。

stay tuned

注意事项

  • 自己实现的函数,声明尽量放在单独的头文件中,定义尽量放在单独的源文件中,所有东西尽可能封装,做到可插拔。万一课上把自己实现的某个关键文件替换掉导致CE(目前我还没遇到),沉着冷静处理,比如更换函数声明的位置
  • 少造轮子,多看代码
  • 舒服的才是好的,按自己的使用习惯改造自己的调试工具与调试流程
  • 不要尝试学我的代码风格,我已经没有救了,你还有

BUAA OS实验调试指南:从看懂到看开的更多相关文章

  1. 4张图看懂delphi 10生成ipa和在iPhone虚拟器上调试(教程)

    4张图看懂delphi 10生成ipa和在iPhone虚拟器上调试(教程) (2016-02-01 03:21:06) 转载▼ 标签: delphi ios delphi10 教程 编程 分类: 编程 ...

  2. 新手也能看懂的 SpringBoot 异步编程指南

    本文已经收录自 springboot-guide : https://github.com/Snailclimb/springboot-guide (Spring Boot 核心知识点整理. 基于 S ...

  3. linux内核调试指南

    linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 ...

  4. Linux Kernel - Debug Guide (Linux内核调试指南 )

    http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级 ...

  5. 小刻也能看懂的Unraid系统使用手册:基础篇

    小刻也能看懂的Unraid系统使用手册 基础篇 Unraid系统简介 Unraid 的本体其实是 Linux,它主要安装在 NAS 和 All in One 服务器上,经常可以在 Linus 的视频里 ...

  6. PS2鼠标+LCD12864实验(调试未成功)

    此试验我一人调试许久都未成功,但发送ff时,读出来的数据确是对的,一开始让我窃喜,但发送f4时,读出来的数据确是错的,哎让苦恼啊,能力有限,只能先暂时就这样吧,那位什么还要贴出来呢,有两个原因: 1. ...

  7. 看懂SqlServer查询计划【转】

    原文链接:http://www.cnblogs.com/fish-li/archive/2011/06/06/2073626.html 开始 SQL Server 查找记录的方法 SQL Server ...

  8. 【转载】看懂SqlServer查询计划

    看懂SqlServer查询计划 阅读目录 开始 SQL Server 查找记录的方法 SQL Server Join 方式 更具体执行过程 索引统计信息:查询计划的选择依据 优化视图查询 推荐阅读-M ...

  9. 看懂SqlServer查询计划

    看懂SqlServer查询计划 阅读目录 开始 SQL Server 查找记录的方法 SQL Server Join 方式 更具体执行过程 索引统计信息:查询计划的选择依据 优化视图查询 推荐阅读-M ...

随机推荐

  1. PTA 数组循环右移

    6-2 数组循环右移 (20 分)   本题要求实现一个对数组进行循环右移的简单函数:一个数组a中存有n(>)个整数,将每个整数循环向右移m(≥)个位置,即将a中的数据由(a​0​​a​1​​⋯ ...

  2. java例题_43 求0—7所能组成的奇数个数

    1 /*43 [程序 43 求奇数个数] 2 题目:求 0-7 所能组成的奇数个数. 3 */ 4 5 /*分析 6 * 1.0不能作最高位且最低位只能是1,3,5,7; 7 * 2.没有限定是几位数 ...

  3. 最短路径(Floyd算法)

    声明:图片及内容基于https://www.bilibili.com/video/BV1oa4y1e7Qt?from=articleDetail 多源最短路径的引入 Floyd算法 原理 加入a: 加 ...

  4. 解决send-mail: fatal: parameter inet_interfaces: no local interface found for ::1

    1:检查sendmail服务的状态 service sendmail status 2:开启sendmail服务 service sendmail start3:关闭sendmail服务 servic ...

  5. Windows下C++/Fortran调用.exe可执行文件

    目录 软件环境 Windows下CMake编译配置 设置项目的generator Command Line CMake GUI PreLoad.cmake 设置make 示例程序 CMake 设置Fo ...

  6. thinkphp各版本常用漏洞总结

    0x01 漏洞分析及复现 1.漏洞分析 漏洞影响范围: Thinkphp 5.1.0 - 5.1.31Thinkphp 5.0.5 - 5.0.23 漏洞产生原因: Thinkphp5.x版本(5.0 ...

  7. Mediapipe 在RK3399PRO上的初探(一)(编译、运行CPU和GPU Demo, RK OpenglES 填坑,编译bazel)

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...

  8. Day11_49_HashTable

    HashTable * HashTable是较早期的使用Hash算法的一种容器结构,现在基本已被淘汰,单线程多使用HashMap,多线程使用ConcurrentHashMap. * HashTable ...

  9. 基于MATLAB的手写公式识别(3)

    基于MATLAB的手写公式识别 图像的膨胀化,获取边缘(思考是否需要做这种处理,初始参考样本相对简单) %膨胀 imdilate(dilate=膨胀/扩大) clc clear A1=imread(' ...

  10. 1.4.18- HTML之特殊字符标签

    上面图片内容简单了解一下,下面我们看一段代码: <!DOCTYPE html> <html lang="en"> <head> <meta ...