把 GDB 降级到 8.0.1

在 macOS 上使用 GDB 需要 codesigning 。但是在 GDB 升级到 8.1 后这种方法不知道为何失效了。所以我安装回了 GDB 8.0.1 并且重新 codesigning ,现在又可以正常升级了。 对 Formula 进行 patch: diff --git a/Formula/gdb.rb b/Formula/gdb.rb index 29a1c590..25360893 100644 --- a/Formula/gdb.rb +++ b/Formula/gdb.rb @@ -1,14 +1,15 @@ class Gdb < Formula desc "GNU debugger" homepage "https://www.gnu.org/software/gdb/" - url "https://ftp.gnu.org/gnu/gdb/gdb-8.1.tar.xz" - mirror "https://ftpmirror.gnu.org/gdb/gdb-8.1.tar.xz" - sha256 "af61a0263858e69c5dce51eab26662ff3d2ad9aa68da9583e8143b5426be4b34" + url "https://ftp.gnu.org/gnu/gdb/gdb-8.0.1.tar.xz" + mirror "https://ftpmirror.gnu.org/gdb/gdb-8.0.1.tar.xz" + sha256 "3dbd5f93e36ba2815ad0efab030dcd0c7b211d7b353a40a53f4c02d7d56295e3" bottle do - sha256 "43a6d6cca157ef70d13848f35c04e11d832dc0c96f5bcf53a43330f524b3ac40" => :high_sierra - sha256 "fe7c6261f9164e7a744c9c512ba7e5afff0e74e373ece9b5aa19d5da6443bfc2" => :sierra - sha256 "cd89001bcf8c93b5d6425ab91a400aeffe0cd5bbb0eccd8ab38c719ab5ca34ba" => :el_capitan + sha256 "e98ad847402592bd48a9b1468fefb2fac32aff1fa19c2681c3cea7fb457baaa0" => :high_sierra + sha256 "0fdd20562170c520cfb16e63d902c13a01ec468cb39a85851412e7515b6241e9" => :sierra + sha256 "f51136c70cff44167dfb8c76b679292d911bd134c2de3fef40777da5f1f308a0" => :el_capitan + sha256 "2b32a51703f6e254572c55575f08f1e0c7bc2f4e96778cb1fa6582eddfb1d113" => :yosemite end deprecated_option "with-brewed-python" => "with-python@2"

Read More

近来做 Stanford CS140e 的一些进展和思考(8)

在上一篇文章之后,我其实还是很忙,但是一直心理惦记着这件事,毕竟只剩最后的一点点就可以做完了,不做完总是觉得心痒。 今天做的部分是调度。我们目前只在 EL0 运行了一个 shell ,每当触发 exception 时回到 kernel 进行处理,再回到原来的地方。但现在,我要实现一个 preemtive round-robin scheduler ,就需要管理当前的所有进程,并且维护当前的进程状态,当时钟中断到来的时候,决定下一个 time slice 要执行的进程,再切换过去。这个过程当然会遇到不少的坑。 首先,我们需要判断一个进程是否可以执行了。考虑到阻塞的 IO ,作者提供了一个优雅的方法:如果这个进程阻塞在 IO 上,那么,提供一个函数,在 scheduler 中调用,判断所需要的数据是否到达。这样,我们就可以一个循环把下一个 time slice 要执行的线程找到。如果找不到,就等待 interrupt 再尝试。 困难的地方在于,在启动的时候,切换到一个起始线程。并且在上下文切换的时候,在 process 1 -> kernel -> process 2 这两步过程中,有许多寄存器都需要仔细考虑如何实现。并且在这个过程中,我也发现了之前写的代码中的问题,最终修复了(目前来看是 working 了)。 我的代码实现在 这里 。下一步就要写 syscall 了。希望能在期中前抽时间赶紧把这个做完。 18:54 PM Update: 刚实现完了 sleep 的 syscall 。比预想中要简单。果然找到了自己实现的调度器的 BUG 。此系列大概是完结了。 2019-02-12 Update: 下一篇文章。

Read More

近来做 Stanford CS140e 的一些进展和思考(7)

在上一篇文章之后,我很长时间都没有在继续我这个项目,清明节刚好闲下来了我就回来继续啃它。Stanford那边已经结课,最后的 3-spawn 也只有一部分,剩下的部分不知道什么时候作者才会填上去了。 这次主要要写的代码就是,对异常的处理。这里的异常并不是我们编程语言中的 catch/throw ,而是硬件的异常。AArch64 和 x86 一样,也有不同的特权级别的区分,前者是 EL0~EL3 ,后者则是 RING0 和 RING3 。特权级别高可以往特权级别低转换,但是反过来,只能通过异常的方式提高特权等级,并且切换特权等级后只有固定的一些代码可能会跳转,这就是 exception handler/vectors 。这些函数可以知道是什么原因调用了他们,根据硬件规定好的文档,我们可以知道发生了什么事情,是对齐出错了呢,还是用户调用了 syscall 呢,等等。根据不同的情况,我们需要进行不同的处理。当处理完之后,我们需要考虑,跳转回用户代码的时候,回到哪里,提供什么值,不提供什么。 实现的话,需要很多步骤。首先是构造好 exception vector ,这里作者已经写好了一个宏(这里 @BenYip 遇到了一个 assembler 的 BUG ),直接用宏就可以把它写出来。然后,我们需要把它加载到当前 EL 的 VBAR_ELx 寄存器中,当 CPU 抛出异常的时候,就会找到这里相应的处理器进行处理。进到这里以后,我们首先先不考虑太多上下文保存的事情–我们先保证能处理异常,恢复也是个有很多坑的步骤,作者也是在这里分成了两个 Subphase 。首先还是从 ESR_ELx 中解析到错误的来源的具体内容,如果是我们在 shell 中自己调用的 brk 2 指令,我们就自己新开一个 shell ,修改了提示符以示区别。这样,我们就成功地捕捉到了这个异常。由于我们还无法恢复回去,所以我们直接死循环。 接下来我们要做的是,从异常中恢复出来。由于用户代码可能在各种地方抛出异常,异常也分同步和异步两种情况,这里有许多需要考虑的问题。为了简化,我们目前只考虑同步的 brk 2 导致的 Brk 异常。为了能恢复之后能够正常运行,我们需要把所有的寄存器都保存下来,即 TrapFrame 。保存的时候需要讲究 AArch64 平台下 SP 寄存器的对齐问题。我们也要把一些特殊的寄存器保存下来。还有一点,就是,因为 exception handler 中调用了 context_save 函数,所以此时的 lr 本身也需要进行保存,这个地方也卡了我很久。最后,再把这些一个一个地恢复到原来的样子,调整 ELR_EL1 使得退回到原来的状态时,会跳过当前的 brk 2 指令,调用它的下一调指令。这样,我们就成功地在遇到异常时,弹出一个 shell ,而且还可以回退回来。

Read More

偶遇清华吴文虎教授

今天百团大战,正准备收摊的时候,天空工场那边来了一位长者,在和他们聊着什么。我很感兴趣,就上去听。老人大概已有八十高龄(后来查,是1936年生),但依然精神矍铄,首先和我们讲,作为工科的学生,一定在理解原理的基础上,多多去实践。他举了他自己的例子,他首先在电机系学习,后来,计算机系成立(当时还是自动控制系),他转到了计算机系,重新学起了计算机,说计算机编程学起来并没有什么难的。当年,苹果公司送过来了中国第一台 Apple-2 ,他们就把电脑拆了下来研究原理,又装上去继续工作。后来,他就在计算机系任教,教的正是《程序设计基础》这门课程。他十分重视实践,在第一年开课的时候就说,最关键的就是实践,安排了一些编程实验课,期中期末就是大作业。一开始有一些同学不重视实践,结果期末就挂科了。后来同学们就明白了实践的重要性,实践起来发现并没有那么难,最后就说,“吴老师,你说得对”。他又谈到了他的体育,他当年是北京长跑代表队的集训队选手,擅长一千五百米项目,他三千米只需要九分钟就能跑完。我们都感到自愧不如。我们说,现在的《程序设计基础》是徐明星老师在教,他说徐明星是他的博士生,邬晓钧也是他的博士生,他另外还有一个高徒我记不清楚了。他还是IOI中国队的前教练,听到我们有过一些OI基础,表示了赞许和鼓励。还有一些细节记不清楚了,记起来了再补充吧。

Read More

〖新手向〗绕过 C++ 类的访问限制

这是一篇很水的文章,面向萌新,已经知道了的可以自觉绕道。 昨天上课,有同学问,如果用户偷偷把 private 改成 public 再和原有的库链接,是不是就可以在用户代码里更改了。这个答案是肯定的。下面我们就做个实验: 首先,创建 good_class.h 和 good_class.cpp: class SomeClass { private: int data; public: int getData(); }; #include "good_class.h" int SomeClass::getData() { return data; } 然后,首先编译, clang++ -c good_class.cpp -o good_class.o 然后,修改 good_class.cpp 并写一个 evil_user.cpp class SomeClass { public: int data; public: int getData(); }; #include <stdio.h> #include "good_class.h" int main() { SomeClass a; a.data = 37; printf("%d\n", a.getData()); return 0; } 编译:

Read More

近来做 Stanford CS140e 的一些进展和思考(6)

在上一篇文章之后,作者终于更新了测试的用例,我的程序终于可以成功跑过所有测试,也成功在树莓派跑起来。不过,我的代码中很多地方的错误处理比较偷懒,往往直接 panic ,显然并不友好。同时,我想到了使用 cargo-fuzz 来进行自动化测试,果然,使用这个很快就修复了不少我没想到的会出错的地方,比如乘法溢出,目录项没有正确结束等等。目前还发现一个 timeout 的问题,研究发现大概是文件的 cluster chain 中出现了环,导致一直读取文件而没有停止。要解决这个问题,我目前想到的是 Floyd 的判圈算法,但还没上实现。等过几天,新的 Assignment 3 出了以后,再继续更新。希望作者少点跳票,多点勤奋,哈哈哈哈哈 更新:下一篇在这里。

Read More

近来做 Stanford CS140e 的一些进展和思考(5)

在上一篇文章之后,作者多次延期跳票之后(again),终于放出了 Assignment 2 Phase 3: Saddle Up 。这次,我们要做的变成了把已经写好的(错漏百出)的 fat32 的驱动搬到树莓派里面去,然后实现一些基本的 shell 命令: ls cat cd 等等。作者首先更新了老版本的新的测试样例,放了一些映像然后提供了预期的结果,结果发现,这里的 fat32 有一些不同,主要的就是 bytes_per_sector 不是 512 了,意味着物理的扇区和逻辑扇区并不一致。同时, sectors_per_cluster 也不是 1 了,需要考虑多个扇区的情况。同时, read_cluster 传入的 offset 也可能不再是第一个 sector 中的,所以需要做一个处理。对于物理和逻辑扇区的问题,作者推荐的方案是,把 fat32 之外的扇区保持不变,把其内的扇区视为逻辑扇区。这样,其它代码都可以透明地工作,而不用到处更改,这就体现了封装的威力。接着,作者提供了一个写好了的 libsd 和一些导出的函数,使用这些函数即可。不过,在错误处理和 timeout 上也遇到了一些坑。后面,把东西搬到树莓派上运行,问题就出现了:读取了第一个扇区(即 MBR 所在的扇区)之后,直接就死掉了。想了半天都没找到方案,突然想起可以利用 panic! 对错误语句进行二分查找。查找了大概有七八个小时之后,终于发现,问题出现在读取一个 u32 类型的变量上。我起初怀疑是栈出了问题,所以放到堆上分配,然而还是不行。忽然想起以前遇到的对齐问题,在 AArch64 架构上,可能为了简化,读取的 u32 必须对齐到四个字节上。于是找了找 Rust 中的对齐方面的文档,找到了 #[repr(align=4)] 这种表示方法,代替了原来的 #[repr(packed)] ,并且把数据先拷贝到对齐后的栈上的对应数据结构,然后再读取对应的项。果然,这个问题就解决了。然后又发现我的盘中会出现 lfn 项并不是从后往前的情况,于是我又修改了一下相关的代码。现在,终于可以成功地 ls cat cd 。 不过还是要吐槽一下,作者的测试用的映像文件中,会出现 0xE5 表示这个项已经被删除的情况,但是似乎作者的代码并没有处理这个,所以在预期的输出中出现了一些明显不正确的结果,导致我的代码跑测试并不能通过。而且,作者的代码在一些情况下会把文件的后缀漏掉。作者后来更新了几次测试的文件,不过这个问题只解决了一部分,并没有完全解决。坐等作者继续放出新的测试文件吧。 更新:下一篇在这里。

Read More

近来做 Stanford CS140e 的一些进展和思考(4)

在上一篇文章之后,作者多次延期跳票之后,终于放出了 Assignment 2 Phase 2:32-bit Lipids ,这两天就把只读 FAT32 写完了(不过封装得并不好,许多地方利用了 pub(super) 把变量可以访问的范围控制到 vfat 中,然后直接读,只有少数需要特殊处理的进行了函数的封装)。首先当然是研究了半天 MBR 和 FAT32 的结构,拿了不同来源的 FAT 结构说明进行对比和验证,最后终于把格式搞清楚了,先实现了 MasterBootRecord ,这个其实很好实现,以前也有接触过 MBR ,本身也很简单。然后就是根据 MBR 找到第一个 FAT32 的分区,根据偏移找到分区的开头,开头的第一个扇区就是 EBPB 数据结构,里面保存了 FAT32 分区的各种信息。根据里面的信息,可以找到 FAT 表的位置和数量,还有数据部分的 Cluster 的位置和数量。接着,解析一下 FAT 表,实际上是一个与 Cluster 一一对应的链表结构,用特殊的数据代表链表的尾和空、坏扇区。利用这些,和 EBPB 中根目录所在的第一个 Cluster ,先在 VFat 里面实现了读取一个 Cluster 链的内容的函数,利用这个函数读取一个一个的目录项,解析目录项,把长文件名的项合并到一个之中,然后对应地丢到 Entry 对象中,目录则可以枚举子目录项,根据名字比较去找子目录或者子文件夹,文件则实现了 io::Read和 io::Seek 使得可以读取文件的内容。实现好了这些以后,就拿了 raspbian-strech-lite.img 作为硬盘映像,从文件里读取文件信息,成功地把 config.txt 读取出来。 其中还是遇到许多困难,如各种偏移的计算,如何处理跨 Cluster 和跨 Sector 的读写,等等,有不少的坑在其中,花了两天的空余时间才差不多完善了这个功能。还有就是利用 Rust 现有的功能完成 C 里面很轻易就可以实现的指针操作,也花了不少时间。 更新:下一篇在这里。

Read More

近来做 Stanford CS140e 的一些进展和思考(3)

由于 Assignment 2: File System 延期发布,所以中间那段时间转向 MIT 6.828 稍微研究了一下。前几天放出了新的任务,在上一篇文章之后,我又有了一些进展: 实现了从内存中读取 ATAGS(ARM Tags) 信息的代码,从而可以获得内存大小的信息,根据这个信息,实现了 bump 和 bin 两种内存分配器,并且把二者之一注册为全局内存分配器,利用上更新了的 std 就可以使用需要动态分配内存的相关工具了。利用这个,我实现了 shell 输入历史的回溯,把输入历史保存在一个动态增长的数组中,再特殊处理上下键,把当前的行替换为历史。 这个过程也不是没有踩坑。一开始代码放出来了,但是题目说明还没出,我就自己按照代码做了 ATAGS 和 bump 分配器,后来做完了,看到说明出了以后,发现理解还是有偏差,把代码更改了并修复了分配器的 BUG 。看到 bin 分配器的时候,我按照网上的 buddy memory allocation 实现了一个内存分配器,原理看起来简单实现起来还是有很多细节问题,后来按照新放出的单元测试,修修补补才写得差不多可用了。同时,原来的 bootloader 因为用了新的 std 而缺失了 alloc 不能编译,我就把 kernel 下的相关文件软连接过去,调了数次后把问题解决。此时, kernel 文件大小已经有 40K ,按照 115200 Baudrate 发送需要几秒才能传输过去,我就调到了 230400 Baudrate ,果然现在的传输速度就有所提升,可以接受了。等之后写了 EMMC(SD card) 的驱动和 FAT32 的文件系统后,就可以实现更多的 shell 的功能了。中间还遇到一个问题,就是如果给 kernel 开启了 bin 分配器,使用 exit 回到 bootloader 就无法传新的 kernel 上去了,结果发现是因为 bin 中用到的侵入式 LinkedList 实现覆盖了部分 bootloader 的代码,换回不能回收内存的 bump 分配器即可,反正目前远远还用不了那么多内存。

Read More