c: gdb: перезаписать и освободить слишком много памяти - PullRequest
0 голосов
/ 18 октября 2018

Используя очень простой пример, который использует 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, после вызова «disassemble main» и просмотра регистров видно следующее.

   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?

Если он знает, что не «владеет» им, чтобы освободить его, почему он не знал, что ему не принадлежит область, являющаяся memset?Похоже, ошибка или предупреждение должны были появиться в то время.

1 Ответ

0 голосов
/ 19 октября 2018

Прежде всего, в соответствии со стандартом C, вы вызывали неопределенное поведение, как только вы слишком много memset указали с помощью указателя s1_buffer.Но игнорируя это ...

s1_buffer имеет длину 20 байтов.s2_buffer запускается 0x16a6030 - 0x16a6010 = 0x20 = 32 байтов после запуска s1_buffer, что оставляет промежуток в 12 байтов между ними.Ваш «плохой» набор записей записывает 40 байтов, что составляет s1_buffer, 12-байтовый пробел, затем первые 8 байтов s2_buffer.Именно сокращение 12-байтного разрыва (который, по-видимому, ваш libc использовал для своих собственных учетных целей) вызывает прерывание, когда вы пытаетесь free его.Ошибка не обнаружена ранее, потому что C обычно не пытается найти такую ​​проблему.На самом деле, только по счастливой случайности free может найти его, когда обнаруживает несогласованность внутренних данных.

...