在从函数 A() 调用函数 B() 期间,B() 分配一个 100 字符的数组并多次填充,包括一次使用 101 字符的字符串和一次使用 110 字符的字符串。这是一个明显的错误。
后来,函数 A() 尝试访问完全不相关的 int 变量 i,并发生分段错误。
我明白为什么会发生缓冲区溢出,但是为什么我在访问这个整数时会出现分段错误?为什么我不简单地获取垃圾数据?
在从函数 A() 调用函数 B() 期间,B() 分配一个 100 字符的数组并多次填充,包括一次使用 101 字符的字符串和一次使用 110 字符的字符串。这是一个明显的错误。
后来,函数 A() 尝试访问完全不相关的 int 变量 i,并发生分段错误。
我明白为什么会发生缓冲区溢出,但是为什么我在访问这个整数时会出现分段错误?为什么我不简单地获取垃圾数据?
缓冲区溢出可能会破坏堆栈上先前保存的帧指针版本。当函数返回时,这个损坏的版本被加载到帧指针寄存器中,导致你描述的行为。
维基百科的页面包含一个图和定义。
在A()
调用时B()
,B 的前导指令会保存 A 的帧指针——A 保存局部变量的堆栈位置,然后用 B 自己的帧指针替换它。它看起来像这样:
当 B 超出其局部变量时,它会弄乱将重新加载到帧指针中的值。这是作为帧指针值的垃圾,因此 A 的所有局部变量都被丢弃。更糟糕的是,未来对局部变量的写入会扰乱属于其他人的内存。
根据您的描述,最可能的解释是 B 中的溢出破坏了 A 堆栈上保存的帧指针。因此,在 B 返回后,A 的帧指针中有垃圾,并在尝试访问局部变量时崩溃。
如果您通过指针访问 i ,那么问题是指针是垃圾。
重要的是要记住,您为 nul 终止字符分配了足够的内存加上一个(精明的读者会指出这个 nul,这主要是有原因的 - 带有一个“l”的 nul 是'\0'
[感谢 Software Monkey 指出错误!],带有两个 'l' 的 null 是指向空的指针)。
这是一个如何发生段错误的示例
int main(int argc, char **argv){ int *x = NULL; *x = 5; // 繁荣 }
由于 x 是一个指针并设置为 null,我们尝试取消引用该指针并为其分配一个值。一种产生分段错误的有保证的方法。
有一个老技巧可以通过设置信号处理程序来捕获 SIGSEGV,并在信号处理程序中调用如下进程,从而实际捕获 seg 错误并获取堆栈跟踪,这在 unix 环境中更常见:
字符缓冲区[250]; buf[0] = '\0'; sprintf(buf, "gdb -a %d | where > mysegfault.txt", getpid()); 系统(缓冲区);
这会将当前执行的 C 程序和 shell 附加到调试器并将其自身附加到它,它的where
一部分显示导致 seg 错误的违规行的堆栈跟踪,并将输出重定向到当前目录中的文件。
注意:这是实现定义的,取决于安装,在 AIX 下,存在 gnu 调试器,因此这将起作用,您的里程可能会有所不同。
希望这会有所帮助,最好的问候,汤姆。