问题描述
问题来源于我在实现 lab5 的 cat
指令时,实现的 fs_read
方法使用 memcpy
从 TMPFS_READ_BUF_VADDR
复制数据。问题的主要原因是 GCC 会对 memcpy 函数进行优化,使用 SIMD 指令来以 16 字节为单位复制(如图)。但其使用的 Q0 寄存器似乎在中断处理时没有被备份至 PCB,因此若产生缺页中断则 Q0 的值会发生改变,导致程序行为出现错误。在本例中就是第一次运行 cat tar/cat_test.txt
(此时正好是映射 pmo 后第一次读取 TMPFS_READ_BUF_VADDR
)会缺少前 16 字节打印 s is a test file.
,后面几次都是正常的。
![](https://p.sda1.dev/5/dcb21da4be4c00d1ddec7502119bcee0/Screenshot_20220421_223913.png)
触发条件
memcpy
传入的 len > 16
src & 0xf == 0
(如果未对齐则会先使用 w*
寄存器)
- 对
src ... src+16
的读取触发中断(如缺页或系统调度)
复现
编译用的 docker 镜像 ipads/chcore_builder
hash 为 d5e5d58e
$ qemu-system-aarch64 --version
QEMU emulator version 6.2.0
Copyright (c) 2003-2021 Fabrice Bellard and the QEMU Project developers
由于 qemu 新版变动,因此在 Makefile
里将平台更改为 raspi3b
。
过程可以看这里,可以看到 $q0
被清零了:https://asciinema.org/a/Hjl4cNFwIZB5SvW5deApBou4d
其中所用的 init_test.bin。为了便于调试,在故障发生处使用的函数为 memcpy2
,内容一致。
Workaround
暂时屏蔽 GCC 的编译优化即可
#pragma GCC push_options
#pragma GCC optimize("O0")
void memcpy(void *dst, const void *src, u64 len) {
// ...
}
#pragma GCC pop_options
正式修复可能需要考虑在 PCB 中备份更多寄存器。不知道分析的是否正确,还望指正!
吐槽:研究了一晚上,没想到是 GCC 的问题,呜呜,可恶的 GCC