内存违规的奇怪行为

时间:2015-10-25 22:22:54

标签: c out-of-memory access-violation buffer-overflow

在以下代码中,pos为负值时存在内存违规可能性。 为什么仅在pos=-2时出现分段错误;为什么仅在pos=-5,-6,-7等时发生总线错误? (这些结果是一致的)。

#include <stdio.h>


#define TABLE_SIZE (800)
int table[TABLE_SIZE] = {0};

    int insert_in_table(int val, int pos){
        int int_size = sizeof(int);
        if(pos > TABLE_SIZE / int_size){
            printf("pos is too big\n");
            return -1;
        }


        table[pos] = val;
        printf("table[%u]=%d\n", pos, table[pos]);

        return 0;
    }

    int main(void){

        int pos, val;
        printf("Position:\n");
        scanf("%d", &pos);
        printf("Value:\n");
        scanf("%d", &val);

        return insert_in_table(val, pos);
    }

这是我的机器Mac OSX上的一个运行示例:

My $./prog
Position:
-1
Value:
3
table[4294967295]=3
My $./prog
Position:
-2
Value:
3
Segmentation fault: 11
My $./prog
Position:
-3
Value:
5
table[4294967293]=5
My $./prog
Position:
-4
Value:
3
table[4294967292]=3
My $./prog
Position:
-5
Value:
3
Bus error: 10
My $./prog
Position:
-6
Value:
3
Bus error: 10
My $./prog
Position:
-7
Value:
3
Bus error: 10

2 个答案:

答案 0 :(得分:2)

无所谓。这是未定义的行为。

C标准本身没有指定进程的任何信号或终止原因。它是定义它们的OS(或POSIX)。因此,从纯粹的C角度来看,问题本身就存在缺陷,因为未定义的行为是未定义的行为。任何事都可能发生。

我们可以更深入一级,低于C标准和操作系统。 Wikipedia定义了这样的总线错误:

  

[...]总线错误是硬件引发的错误,通知操作系统(OS)进程正在尝试访问CPU无法物理寻址的内存[...]

虽然分段错误通常仅表示无效的内存访问(包括缺少访问某些内存区域的权限)。因此,显然发生了总线错误,因为您尝试寻址CPU根本无法寻址的内存位置,而分段错误仅显示无效内存位置的访问权。

答案 1 :(得分:1)

根据要求,我做了一些gdb:ing。

tmp$ gcc -O2 -g -Wno-unused-result tmp.c -o bushmem
tmp$ gdb bushmem 
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from bushmem...done.
(gdb) break tmp.c:15
Breakpoint 1 at 0x40066c: file tmp.c, line 15.
(gdb) run
Starting program: /tmp/bushmem 
Position:
-2
Value:
3

Breakpoint 1, insert_in_table (val=3, pos=-2) at tmp.c:15
15          table[pos] = val;
(gdb) info symbol table
table in section .bss of /tmp/bushmem
(gdb) info symbol &table[2]
table + 8 in section .bss of /tmp/bushmem
(gdb) info symbol &table[-2]
No symbol matches &table[-2].
(gdb) info proc mappings
process 20372
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
            0x400000           0x401000     0x1000        0x0 /tmp/bushmem
            0x600000           0x601000     0x1000        0x0 /tmp/bushmem
            0x601000           0x602000     0x1000     0x1000 /tmp/bushmem
      0x7ffff7a15000     0x7ffff7bd0000   0x1bb000        0x0 /lib/x86_64-linux-gnu/libc-2.19.so
      0x7ffff7bd0000     0x7ffff7dcf000   0x1ff000   0x1bb000 /lib/x86_64-linux-gnu/libc-2.19.so
      0x7ffff7dcf000     0x7ffff7dd3000     0x4000   0x1ba000 /lib/x86_64-linux-gnu/libc-2.19.so
      0x7ffff7dd3000     0x7ffff7dd5000     0x2000   0x1be000 /lib/x86_64-linux-gnu/libc-2.19.so
      0x7ffff7dd5000     0x7ffff7dda000     0x5000        0x0 
      0x7ffff7dda000     0x7ffff7dfd000    0x23000        0x0 /lib/x86_64-linux-gnu/ld-2.19.so
      0x7ffff7fcd000     0x7ffff7fd0000     0x3000        0x0 
      0x7ffff7ff6000     0x7ffff7ffa000     0x4000        0x0 
      0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
      0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x22000 /lib/x86_64-linux-gnu/ld-2.19.so
      0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x23000 /lib/x86_64-linux-gnu/ld-2.19.so
      0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0 
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]
(gdb) p/x &table[0]
$2 = 0x601080
(gdb) info symbol 0x601080
table in section .bss of /tmp/bushmem
(gdb) info symbol 0x601084
table + 4 in section .bss of /tmp/bushmem
(gdb) x/32 &table[-16]
0x601040:                   0x00000000  0x00000000  0x00000000  0x00000000
0x601050:                   0x00000000  0x00000000  0x00000000  0x00000000
0x601060 <completed.6973>:  0x00000000  0x00000000  0x00000000  0x00000000
0x601070:                   0x00000000  0x00000000  0x00000000  0x00000000
0x601080 <table>:           0x00000000  0x00000000  0x00000000  0x00000000
0x601090 <table+16>:        0x00000000  0x00000000  0x00000000  0x00000000
0x6010a0 <table+32>:        0x00000000  0x00000000  0x00000000  0x00000000
0x6010b0 <table+48>:        0x00000000  0x00000000  0x00000000  0x00000000

您可以在gdb中使用help <cmd>来了解有关每个命令的更多信息。

好的,所以我做了,按顺序:

  • 在表格写入行设置断点。每当我们到达这条线时,GDB都会停止该程序。
  • 当我们到达那里时,我展示了info symbol <addr>
  • 的使用
  • 在我的情况下,&amp; table [-2]没有符号匹配 - 但请记住,我无法复制,所以你的情况可能会有所不同。
  • info proc mappings显示程序的虚拟内存映射。
  • p/x simple以十六进制打印一个值 - 在这里,我们打印table[0]的地址,结果为0x601080。请注意,这是在第三个内存区域,从0x601000开始。
  • 正如您所见,info symbol 0x601080告诉我们此地址属于table符号。正如预期的那样,后面的4个字节为table+4
  • 最后,我们打印一些内存内容,从table之前开始。就我而言,这些都是零 - 在您的情况下,它们可能具有不同的值(或者它们可能为零,但该值对于某些内容而言重要)。

在此之后,您应该能够运行continue命令,并且gdb应该捕获崩溃发生的位置。使用bt命令查看它发生的位置。但崩溃的位置可能与您修改内存的读取位置不同(因此事情真的开始出错)。

如果您愿意,可以尝试the rwatch command in gdb。在&table[-2]上设置它应该会在有人读取内存时使gdb停止,这可能会帮助您找出导致该代码的代码。也许它用于什么。