为什么我的程序没有崩溃,当我写过一个数组的结束?

为什么我的程序没有崩溃,当我写过一个数组的结束?

为什么下面的代码在没有崩溃@运行时的情况下工作呢?

而且,大小完全取决于机器/平台/编译器!我甚至可以在一台64位的机器上捐出多达200美元。如何在操作系统中检测到主函数中的分割错误?

int main(int argc, char* argv[]){
    int arr[3];
    arr[4] = 99;}

这个缓冲空间从何而来?这是分配给进程的堆栈吗?



海绵宝宝撒
浏览 394回答 3
3回答

宝慕林4294392

我不久前为教育写的东西.考虑以下c-程序:int&nbsp;q[200];main(void)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;i; &nbsp;&nbsp;&nbsp;&nbsp;for(i=0;i<2000;i++)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q[i]=i; &nbsp;&nbsp;&nbsp;&nbsp;}}编译并执行它之后,生成一个核心转储:$&nbsp;gcc&nbsp;-ggdb3&nbsp;segfault.c $&nbsp;ulimit&nbsp;-c&nbsp;unlimited $&nbsp;./a.outSegmentation&nbsp;fault&nbsp;(core&nbsp;dumped)现在,使用gdb执行死后分析:$&nbsp;gdb&nbsp;-q&nbsp;./a.out&nbsp;coreProgram&nbsp;terminated&nbsp;with&nbsp;signal&nbsp;11,&nbsp;Segmentation&nbsp;fault.[New&nbsp;process&nbsp;7221]#0&nbsp;&nbsp;0x080483b4&nbsp;in&nbsp;main&nbsp;()&nbsp;at&nbsp;s.c:88&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; q[i]=i;(gdb)&nbsp;p&nbsp;i $1&nbsp;=&nbsp;1008(gdb)嗯,当一个人在分配的200个项目之外写东西时,程序没有分段错误,而是当我=1008时它崩溃了,为什么?输入页面。在UNIX/Linux上可以通过多种方式确定页面大小,一种方法是使用系统函数sysconf(),如下所示:#include&nbsp;<stdio.h>#include&nbsp;<unistd.h>&nbsp;//&nbsp;sysconf(3)int&nbsp;main(void)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;printf("The&nbsp;page&nbsp;size&nbsp;for&nbsp;this&nbsp;system&nbsp;is&nbsp;%ld&nbsp;bytes.\n", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sysconf(_SC_PAGESIZE)); &nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;0;}它提供了输出:此系统的页大小为4096字节。或者可以使用命令行实用程序getconf,如下所示:$&nbsp;getconf&nbsp;PAGESIZE4096死后事实证明,分段故障不是发生在i=200,而是发生在i=1008时,让我们找出原因。启动gdb进行一些死后分析:$gdb&nbsp;-q&nbsp;./a.out&nbsp;coreCore&nbsp;was&nbsp;generated&nbsp;by&nbsp;`./a.out'.Program&nbsp;terminated&nbsp;with&nbsp;signal&nbsp;11,&nbsp;Segmentation&nbsp;fault.[New&nbsp;process&nbsp;4605] #0&nbsp;&nbsp;0x080483b4&nbsp;in&nbsp;main&nbsp;()&nbsp;at&nbsp;seg.c:66&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q[i]=i;(gdb)&nbsp;p&nbsp;i $1&nbsp;=&nbsp;1008(gdb)&nbsp;p&nbsp;&q $2&nbsp;=&nbsp;(int&nbsp;(*)[200])&nbsp;0x804a040(gdb)&nbsp;p&nbsp;&q[199]$3&nbsp;=&nbsp;(int&nbsp;*)&nbsp;0x804a35cq结束于地址0x804a35c,或者更确切地说,Q[199]的最后一个字节位于该位置。页面大小是我们前面看到的4096字节,机器的32位字大小给出了一个虚拟地址分解为一个20位的页码和一个12位偏移量。q[]以虚拟页码结尾:0x804a=32842偏移量:0x35c=860,因此仍然存在:4096-864=分配q[]的内存页上剩下的3232个字节。这个空间可以容纳:3232/4=808整数,代码将其视为在位置200至1008处包含q元素。我们都知道这些元素不存在,编译器也没有抱怨,HW也没有,因为我们对那个页面有写权限。只有当i=1008时,q[]才引用我们没有写权限的另一个页面上的地址,虚拟内存HW才会检测到这一点,并触发一个分段错误。整数以4个字节存储,这意味着这个页面包含808(3236/4)额外的假元素,这意味着从q[200]、q[201]到元素199+808=1007(q[1007])访问这些元素仍然是完全合法的,而不会触发SEG故障。当访问q[1008]时,输入一个权限不同的新页面。

慕少森

关于局部变量缓冲区溢出崩溃的确切时间/位置取决于以下几个因素:调用函数时堆栈上的数据量,其中包含溢出的变量访问。总计写入溢出变量/数组的数据量。记住堆积如山向下..对象附近的堆栈指针开始进程执行。端部作为堆栈使用的内存。它不是从最后一个映射的单词开始的,这是因为系统的初始化代码可能决定在创建时将某种“启动信息”传递给进程,并且经常在堆栈上这样做。那就是平日失败模式-从包含溢出代码的函数返回时出现崩溃。如果共计写入堆栈上的缓冲区的数据量大于以前使用的堆栈空间总量(由调用方/初始化代码/其他变量使用),那么无论哪个内存访问首先运行到堆栈的顶部(开始),都会发生崩溃。崩溃的地址就会越过一页边界-SIGSEGV由于要访问堆栈顶部以外的内存,所以没有映射。如果这个总数现在小于堆栈中使用的部分的大小,那么它就会正常工作并崩溃。后来-实际上,在存储堆栈上的地址的平台上(x86/x64是这样的),当从函数返回时。那是因为CPU指令ret实际上从堆栈(返回地址)获取一个单词,并将执行重定向到那里。如果该地址不包含预期的代码位置,而是包含任何垃圾,则会发生异常并导致程序死亡。为了说明这一点:何时main()调用时,堆栈看起来如下(在32位x86 UNIX程序上):[ esp&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ] <return addr to caller> (which exits/terminates process)[ esp + 4&nbsp; &nbsp; &nbsp; ] argc[ esp + 8&nbsp; &nbsp; &nbsp; ] argv[ esp + 12&nbsp; &nbsp; &nbsp;] envp <third arg to main() on UNIX - environment variables>[ ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ][ ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ] <other things - like actual strings in argv[], envp[][ END&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ] PAGE_SIZE-aligned stack top - unmapped beyond什么时候main()开始时,它将为各种用途分配堆栈上的空间,其中包括托管待溢出数组。这将使它看起来像:[ esp&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ] <current bottom end of stack>[ ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ] <possibly local vars of main()>[ esp + X&nbsp; &nbsp; &nbsp; ] arr[0][ esp + X + 4&nbsp; ] arr[1][ esp + X + 8&nbsp; ] arr[2][ esp + X + 12 ] <possibly other local vars of main()>[ ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ] <possibly other things (saved regs)>[ old esp&nbsp; &nbsp; &nbsp; ] <return addr to caller> (which exits/terminates process)[ old esp + 4&nbsp; ] argc[ old esp + 8&nbsp; ] argv[ old esp + 12 ] envp <third arg to main() on UNIX - environment variables>[ ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ][ ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ] <other things - like actual strings in argv[], envp[][ END&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ] PAGE_SIZE-aligned stack top - unmapped beyond这意味着你可以快乐地进入arr[2].对于缓冲区溢出导致的不同崩溃的体验,请尝试如下:#include <stdlib.h>#include <stdio.h>int main(int argc, char **argv){&nbsp; &nbsp; int i, arr[3];&nbsp; &nbsp; for (i = 0; i < atoi(argv[1]); i++)&nbsp; &nbsp; &nbsp; &nbsp; arr[i] = i;&nbsp; &nbsp; do {&nbsp; &nbsp; &nbsp; &nbsp; printf("argv[%d] = %s\n", argc, argv[argc]);&nbsp; &nbsp; } while (--argc);&nbsp; &nbsp; return 0;}看看异类与堆栈结束后溢出缓冲区时相比,当缓冲区溢出一点(例如,10位)时,就会发生崩溃。用不同的优化级别和不同的编译器来尝试它。很能说明问题,因为它显示了这两种行为的不当行为(不一定会全部打印出来)argv[]以及在不同的地方崩溃,甚至没完没了的循环(例如,编译器放置i或argc到堆栈中,代码在循环期间覆盖它)。
打开App,查看更多内容
随时随地看视频慕课网APP