git diff 在 mac 上有个奇怪现象,有时候我修改了文件,但 git diff 认为它没变;稍作研究后,发现是 mac 和 linux 的 mmap 表现不一致,也不好说是 bug 还是 feature。水一篇文章记录一下。
一、最初的表现
环境:macos 10.15,任意版本的 git 命令。
平时逆向时候会涉及到 section headers 的修复,自己用 C 写代码来修,但发现我修复后的文件无法被 commit,但文件的 hash 确实发生了变化,而且内容也是修改后的内容。
二、测试样例
先使用 c 编写一个简单的使用 mmap 修改文件内容的可执行文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <string.h> #include <fcntl.h> #include <zconf.h> #include <sys/stat.h> #include <sys/mman.h> int main(int argc, char **argv) { int fd = open(argv[1], O_RDWR); struct stat file_state; fstat(fd, &file_state); char *buffer = (char *) mmap(NULL, file_state.st_size, PROT_READ | PROT_WRITE, MAP_SHARED|MAP_NOCACHE, fd, 0); memset(buffer, 0x31, 100); munmap(buffer, file_state.st_size); close(fd); } |
然后使用下列命令进行测试
1 2 3 4 5 6 7 |
git init dd count=1 if=/dev/random of=1.bin git add 1.bin git commit -m 'init commit' gcc main.c ./a.out 1.bin git diff |
在 macos 上运行,发现 git diff 输出为空;在 linux 上运行,发现 git diff 认为1.bin 被修改了
三、初步结论
偶然使用 ls -la 发现,mac 使用这种方式,文件的修改日期时间戳是不会被刷新的,但 linux 使用这种方式,修改日期时间戳会被刷新。
使用 touch -t 刷新文件的修改日期,就可以让 git diff 感知到文件被修改了。
稍加阅读 git 的代码,大概有类似的行为。因此,初步结论是mac 的锅,mmap 不刷新时间戳,导致 git 抽风。
四、可能性猜测
linux mmap man page 有这句话
The st_ctime and st_mtime field for a file mapped with PROT_WRITE and MAP_SHARED will be updated after a write to the mapped region, and before a subsequent msync(2) with the MS_SYNC or MS_ASYNC flag, if one occurs.
但 mac 没有明确说自己会刷时间,因此这可能是个 feature,也可能是个 bug。
希望有生之年能够看看内核代码实现上有什么区别,以及希望 Lan学弟 能不能帮我破个案。