c:gdb:覆盖并释放过多的内存

时间:2018-10-18 16:49:15

标签: c memory gdb free

使用一个非常简单的示例,该示例使用2个指针。 指针1(s1_buffer)是malloc,然后是memset。 指针2(s2_buffer)是malloc,然后是memset,因此该位置有望接近指针1(即指针)。

当然,这不是使用指针和结构的首选方法,但是它是在模仿其他代码的。目的是了解幕后发生的情况以及gdb所显示的内容。

这是代码:

#include <stdio.h>
#include <stdlib.h>

typedef struct
    {
    struct
        {
        int a;
        int b;
        int c;
        int d;
        } n_s;
    int    e;
    } struct1;


typedef struct
    {
    char  aa[5];
    int   bb;
    } struct2;

int main () {

   struct1      s1;
   struct2      s2;
   int *        s1_buffer = (int*) 0;
   int *        s2_buffer = (int*) 0;

   printf("Size...\n");
   printf("     s1 : %d\n", sizeof (struct1));
   printf("     s2 : %d\n", sizeof (struct2));

   int numOfElements  = 1;

   s1_buffer = ( int*) malloc((numOfElements * sizeof (struct1)));
   memset((int *)s1_buffer, 0, (numOfElements * sizeof (struct1)));

   s2_buffer = ( int*) malloc((numOfElements * sizeof (struct2)));
   memset((int *)s2_buffer, 0, (numOfElements * sizeof (struct2)));


   //The following shows that the memory locations are close to each other. 
   printf("\nMemory Location... \n");
   printf("     s1_buffer    : %p\n", s1_buffer);
   printf("     s2_buffer    : %p\n", s2_buffer);

   numOfElements  = 2;

   //Here a memset is done WITHOUT doing a malloc and since the size is 
   //now twice as large Pointer 1's area should overwrite Pointer 2's memory. 
   memset((int *)s1_buffer, 0, (numOfElements * sizeof (struct1)));

   printf("\nFreeing Memory... \n");
   printf("   s1_buffer\n");
   free(s1_buffer);
   printf("   s2_buffer\n");
   free(s2_buffer);

   return(0);
}

运行程序将产生以下输出。

> ./memFreeTest
Size...
     s1 : 20
     s2 : 12

Memory Location...
     s1_buffer    : 0x16a6010
     s2_buffer    : 0x16a6030

Freeing Memory...
   s1_buffer
*** Error in `./memFreeTest': free(): invalid next size (fast): 0x00000000016a6010 ***
======= Backtrace: =========
/usr/lib64/libc.so.6(+0x7c503)[0x2af569904503]
./memFreeTest[0x4007b1]
/usr/lib64/libc.so.6(__libc_start_main+0xf5)[0x2af5698a9b35]
./memFreeTest[0x400589]
======= Memory map: ========

基于s1_buffer和s2_buffer的地址,它们彼此之间在32个字节之内。完成s1_buffer的第二个内存集后,它应该踩到s2_buffer区域。

通过gdb分析核心文件,在“反汇编主程序”调用并查看寄存器后,可以看到以下内容。

   0x0000000000400779 <+300>:   shl    $0x2,%rax
   0x000000000040077d <+304>:   mov    %rax,%rdx
   0x0000000000400780 <+307>:   mov    -0x8(%rbp),%rax
   0x0000000000400784 <+311>:   mov    $0x0,%esi
   0x0000000000400789 <+316>:   mov    %rax,%rdi

以下是第二个内存集。

   0x000000000040078c <+319>:   callq  0x400520 <memset@plt>

为什么将$ 0x4008c9的值移动到edi?

   0x0000000000400791 <+324>:   mov    $0x4008c9,%edi
   0x0000000000400796 <+329>:   callq  0x400500 <puts@plt>
   0x000000000040079b <+334>:   mov    $0x4008dd,%edi
   0x00000000004007a0 <+339>:   callq  0x400500 <puts@plt>
   0x00000000004007a5 <+344>:   mov    -0x8(%rbp),%rax
   0x00000000004007a9 <+348>:   mov    %rax,%rdi
   0x00000000004007ac <+351>:   callq  0x4004f0 <free@plt>

下一行是失败的地方。 为什么将$ 0x4008ea的值移动到edi?

我指的是edi,因为它似乎是发生故障的地方。

=> 0x00000000004007b1 <+356>:   mov    $0x4008ea,%edi

正在检查edi,它似乎是有效的:

 (gdb) info reg edi
 edi            0x19569  103785

dgb输出在这里继续:

   0x00000000004007b6 <+361>:   callq  0x400500 <puts@plt>
   0x00000000004007bb <+366>:   mov    -0x10(%rbp),%rax
   0x00000000004007bf <+370>:   mov    %rax,%rdi
   0x00000000004007c2 <+373>:   callq  0x4004f0 <free@plt>
   0x00000000004007c7 <+378>:   mov    $0x0,%eax
   0x00000000004007cc <+383>:   leaveq
   0x00000000004007cd <+384>:   retq
End of assembler dump.
(gdb) info reg
rax            0x0      0
rbx            0x0      0
rcx            0xffffffffffffffff       -1
rdx            0x6      6
rsi            0x19569  103785
rdi            0x19569  103785
rbp            0x7ffdc0b23440   0x7ffdc0b23440
rsp            0x7ffdc0b23400   0x7ffdc0b23400
r8             0x2af5699fe840   47233527441472
r9             0x2af569886000   47233525899264
r10            0x8      8
r11            0x246    582
r12            0x400560 4195680
r13            0x7ffdc0b23520   140727836357920
r14            0x0      0
r15            0x0      0
rip            0x4007b1 0x4007b1 <main+356>
eflags         0x246    [ PF ZF IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) info reg edi
edi            0x19569  103785

核心文件第一次出现。如何知道此内存不属于s1_buffer?

如果知道它不“拥有”它以释放它,为什么它不知道它不拥有该内存集呢?似乎应该在此时出现错误或警告。

1 个答案:

答案 0 :(得分:2)

首先,根据C标准,一旦通过memset指针s1_buffer花了太多时间,就调用了Undefined Behavior。但是无视...

s1_buffer长20个字节。 s2_buffer开始后0x16a6030 - 0x16a6010 = 0x20 = 32个字节开始s1_buffer个字节,这之间留有12个字节的间隔。您的“坏”内存集正在写入40个字节,它是s1_buffer的全部,12个字节的间隙,然后是s2_buffer的前8个字节。当您尝试free时,是中止12字节的间隙(您的libc可能将其用于自己的记帐目的)导致中止。由于C通常不会尝试发现这种问题,因此不会更早发现该错误。幸运的是,free能够发现它的内部数据不一致时能够找到它。