分享一个git diff在mac上bug

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_SHAREDMAP_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学弟 能不能帮我破个案。