调用sys_brk与printf()出现段错误的原因问询
注意:
sys_brk系统调用返回程序断点(program break),而C库的brk()函数返回0或-1。(依据brk(2)手册的NOTES部分)
正常运行的代码
以下代码可以正常执行:
#include <unistd.h> #include <stdio.h> #include <sys/syscall.h> #include <string.h> #include <stdint.h> int main (void) { long beg_break = syscall (SYS_brk, 0);//获取当前程序中断点 long mid_break = syscall (SYS_brk, beg_break + 100); long end_break = syscall (SYS_brk, beg_break);//将程序中断点恢复 }
加入printf后出现段错误
但如果在sys_brk调用之间插入printf()打印值,会在syscall (SYS_brk, beg_break)这一行触发段错误:
long beg_break = syscall (SYS_brk, 0); printf ("%#lX\n", beg_break); long mid_break = syscall (SYS_brk, beg_break + 100); printf ("%#lX\n", mid_break); long end_break = syscall (SYS_brk, beg_break); printf ("%#lX\n", end_break);
输出结果:
0X557E46CDE000 0X557E46CDE064 Segmentation fault
该问题可稳定复现,在多台设备多次测试均出现相同现象。
验证实验
针对该问题做了以下三组验证实验:
实验1:改用stderr输出
使用stderr替代stdout后,程序可正常运行:
long beg_break = syscall (SYS_brk, 0); fprintf (stderr, "%#lX\n", beg_break); long new_break = syscall (SYS_brk, beg_break + 100); fprintf (stderr, "%#lX\n", new_break); long end_break = syscall (SYS_brk, beg_break); fprintf (stderr, "%#lX\n", end_break);
实验2:将printf放在所有sys_brk调用之后
不在sys_brk调用之间使用printf(),程序可正常运行:
long beg_break = syscall (SYS_brk, 0); long mid_break = syscall (SYS_brk, beg_break + 100); long end_break = syscall (SYS_brk, beg_break); printf ("%#lX\n", beg_break); printf ("%#lX\n", mid_break); printf ("%#lX\n", end_break);
实验3:关闭stdout缓冲
关闭stdout的缓冲后,程序可正常运行:
setvbuf (stdout, NULL, _IONBF, 0); long beg_break = syscall (SYS_brk, 0); printf ("%#lX\n", beg_break); long mid_break = syscall (SYS_brk, beg_break + 100); printf ("%#lX\n", mid_break); long end_break = syscall (SYS_brk, beg_break); printf ("%#lX\n", end_break);
原因推测
从实验结果可以推测,问题根源在于printf()调用了基于brk()实现的malloc()。以下代码可以证明这一点:
long beg = syscall (SYS_brk, 0); printf ("hello world %d\n", 123); long end = syscall (SYS_brk, 0); printf ("%#lX\n", beg); printf ("%#lX\n", end);
输出结果:
hello world 123 0X56501B6F0000 0X56501B711000
可见printf()执行后,程序断点明显后移,说明它确实调用了malloc分配堆内存。
疑问解答
既然brk()本身并没有读写堆内存,为什么段错误会触发在brk()调用,而非printf()?
这是因为:当你调用printf()时,C库的malloc()会通过brk()扩展堆空间,并且在堆中分配了内部缓冲区(比如stdout的行缓冲)。此时程序断点已经被malloc()推到了更高的地址。当你手动调用sys_brk(beg_break)把断点拉回初始位置时,相当于直接回收了malloc()刚分配的内存——但C库的内存管理系统还不知道这块内存已经被你手动收回了。
Linux内核在处理sys_brk调用时,会检查你要设置的断点是否低于当前进程已分配的内存区域的关键边界(比如C库malloc()已经标记为“已使用”的内存起始地址)。如果内核发现你要把断点设置到一个已经被分配给堆使用的区域内部,就会触发段错误来阻止这种破坏内存管理的操作。
简单来说:你手动移动程序断点的操作,破坏了C库内存管理系统维护的堆结构,内核检测到这种非法操作,在执行sys_brk时直接抛出段错误,而不是等到后续内存访问时才报错。
内容的提问来源于stack exchange,提问作者barbyQAQ

