Ошибка сегментации в GDB показывает физический или виртуальный адрес? - PullRequest
0 голосов
/ 29 января 2020

Я пытался sma sh стек:

int main (void) {

    int ar[5] = {1,2,3,4,5};
    for(int i =0; i<255 ; i++)
        ar[i] = 10;

    return 0;
}

с gcc -fno-stack-protector somefile.c. Первый вопрос: почему существует разница с ошибкой с (SIGABRT) и без (SIGSEGV) протектора, когда оба обращаются к нелегальной памяти (поэтому, я думаю, должна быть одна и та же ошибка). Секунда, когда objdump:

0000000000001125 <main>:
    1125:   55                      push   %rbp
    1126:   48 89 e5                mov    %rsp,%rbp
    1129:   c7 45 e0 01 00 00 00    movl   $0x1,-0x20(%rbp)
...

Начало основного адреса является виртуальным 0000000000001125

НО при компиляции без защиты:

Program received signal SIGSEGV, Segmentation fault.
0x0000000af7be5b6b in ?? ()

Адрес (0x0000000af7be5b6b) это что? виртуальный, физический? Я не вижу его нигде в дизассемблированном (как показано выше) файле, так откуда этот адрес?

РЕДАКТИРОВАТЬ: с защитой (и неисправность для этого SIGABRT), вывод, который я тоже не понимаю :

Program received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50

Что такое __GI_raise макрос? и что в скобках sig=sig@entry=6, добавляет ли gcc таблицу ошибок или что это за отметки от компоновщика?

1 Ответ

2 голосов
/ 30 января 2020

почему существует разница с ошибкой с (SIGABRT) и без (SIGSEGV) защитного устройства, когда оба обращаются к нелегальной памяти (поэтому, я думаю, должно быть то же самое).

Когда вы компилируете без стекового протектора, вы просто перезаписываете часть памяти, к которой у вас нет прав, и программа убивается операционной системой через SIGSEGV сигнал.

Однако, когда вы компилируете с помощью стекового протектора, ошибка может быть обнаружена библиотекой lib c, и в этом случае вызывается функция __stack_chk_fail(). Это можно увидеть с помощью objdump:

 72d:   b8 00 00 00 00          mov    eax,0x0
 732:   48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
 736:   64 48 33 14 25 28 00    xor    rdx,QWORD PTR fs:0x28
 73d:   00 00
 73f:   74 05                   je     746 <main+0x76>
 741:   e8 3a fe ff ff          call   580 <__stack_chk_fail@plt>
 746:   c9                      leave
 747:   c3                      ret

Затем функция __stack_chk_fail() вызывает __fortify_fail_abort(), что вызывает __libc_message() и вывод сообщения об ошибке. и, наконец, выполнение abort(), которое посылает процессу SIGABRT сигнал через raise(), убивая его.

Начало основного адрес виртуальный 0000000000001125

неверный . Это , а не виртуальный адрес, то есть просто смещение внутри двоичного файла, что означает, что код, который вы видите, начинается с байта 0x1125 в самом двоичном файле. Когда двоичный файл затем выполняется, для программы создается область виртуальной памяти, начинающаяся с некоторого (обычно рандомизированного) базового виртуального адреса. Базовый виртуальный адрес будет определять положение main() и все остальное. Например, main() будет на base_virtual_addr + 0x1125. У вас нет способа определить реальный физический адрес, по которому ваша программа загружена в ОЗУ, это знает только операционная система (то есть ядро), и программе пространства пользователя это не нужно.

Вы можете взглянуть на карту виртуальной памяти вашей программы, когда она работает под GDB, с помощью команды info proc mappings, результат будет примерно таким:

(gdb) info proc mappings
process 11084
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
      0x555555554000     0x555555555000     0x1000        0x0 /home/marco/Desktop/a.out
      0x555555754000     0x555555755000     0x1000        0x0 /home/marco/Desktop/a.out
      0x555555755000     0x555555756000     0x1000     0x1000 /home/marco/Desktop/a.out
      0x7ffff7a3a000     0x7ffff7bcf000   0x195000        0x0 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7bcf000     0x7ffff7dcf000   0x200000   0x195000 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7dcf000     0x7ffff7dd3000     0x4000   0x195000 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7dd3000     0x7ffff7dd5000     0x2000   0x199000 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7dd5000     0x7ffff7dd9000     0x4000        0x0
      0x7ffff7dd9000     0x7ffff7dfc000    0x23000        0x0 /lib/x86_64-linux-gnu/ld-2.24.so
      0x7ffff7fcf000     0x7ffff7fd1000     0x2000        0x0
      0x7ffff7ff8000     0x7ffff7ffa000     0x2000        0x0 [vvar]
      0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
      0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x23000 /lib/x86_64-linux-gnu/ld-2.24.so
      0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x24000 /lib/x86_64-linux-gnu/ld-2.24.so
      0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

В этом случае Вы можете видеть, что моя программа (/home/marco/Desktop/a.out) запускается с виртуального базового адреса 0x555555554000.

Что такое макрос __GI_raise? и что в скобках sig=sig@entry=6, добавляет ли g cc таблицу ошибок или что это за отметки от компоновщика?

__GI_raise() - это реализация raise() функция. Помните? Я только что упомянул об этом ранее, когда говорил о защитнике стека. В вашем случае gdb останавливается после того, как программа была убита raise(SIGABRT), и показывает точную точку, в которой программа умерла, что находится внутри внутренней функции __GI_raise(), используемой lib c для доставки сигнала.

Строка sig=sig@entry=6 - это просто хороший способ для GDB сообщить вам, что функция была вызвана с единственным аргументом, установленным в 6, который является номером сигнала для SIGABRT.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...