Понимание неоптимизированного asm для C, который разыменовывает неинициализированный указатель, вызывая ошибку сегментации - PullRequest
0 голосов
/ 25 мая 2018

Я поставил эксперимент, чтобы посмотреть, работает ли он или нет

int *p;
p[0] = 3;

Моя идея состоит в том, что компилятор дает случайное значение p, и я могу рассматривать его как массив.

Но получается ошибка сегментации, и я не понимаю ассемблерный код.

   0x0000000000401530 <+0>: push   %rbp
   0x0000000000401531 <+1>: mov    %rsp,%rbp
   0x0000000000401534 <+4>: sub    $0x30,%rsp
   0x0000000000401538 <+8>: mov    %ecx,0x10(%rbp)
   0x000000000040153b <+11>:    mov    %rdx,0x18(%rbp)
   0x000000000040153f <+15>:    callq  0x402170 <__main>
   0x0000000000401544 <+20>:    mov    -0x8(%rbp),%rax
=> 0x0000000000401548 <+24>:    movl   $0x3,(%rax)
   0x000000000040154e <+30>:    mov    $0x0,%eax
   0x0000000000401553 <+35>:    add    $0x30,%rsp
   0x0000000000401557 <+39>:    pop    %rbp
   0x0000000000401558 <+40>:    retq    

Я искал в Google, mov - это стиль Intel, а movl - стиль AT & T.Как эти два стиля объединяются?

В этой строке:

mov    -0x8(%rbp),%rax

Кажется, что нужно переместить значение адреса rbp-0x8, чтобы зарегистрировать rax, верно?Является ли это «-0x8 (% rbp)» случайным значением, равным p?

Я думаю, что% rax - это не p, потому что на следующей строке CPU выдает $ 0x3% rax.Кажется,% rax является первой памятью массива.

Как мне интерпретировать этот ассемблерный код?Спасибо.

1 Ответ

0 голосов
/ 25 мая 2018

Я не специалист по сборке, но код выглядит достаточно ясно, поэтому я попытаюсь объяснить это.


   0x0000000000401530 <+0>: push   %rbp
   0x0000000000401531 <+1>: mov    %rsp,%rbp

(выше) Это некая "процедурная процедура""код, который генерирует компилятор, когда оптимизация не включена.Это сохраняет состояние RSP (64-битный указатель стека).

   0x0000000000401534 <+4>: sub    $0x30,%rsp

Это резервирует дополнительное место в стеке для локальных переменных.

   0x0000000000401538 <+8>: mov    %ecx,0x10(%rbp)
   0x000000000040153b <+11>:    mov    %rdx,0x18(%rbp)

Это сохраняет argc и argv настек, потому что вы сделали отладочную сборку, поэтому все переменные должны быть в памяти.(Используемое пространство находится над обратным адресом, где зарезервированное пространство для вызывающего абонента main. Это называется теневым пространством и является функцией соглашения о вызовах Windows x64.)

   0x000000000040153f <+15>:    callq  0x402170 <__main>

Это вызываеткакая-то ранняя функция инициализации.Он может использовать argc и argv (все еще в регистрах) или не может;мы не можем сказать по коду.

   0x0000000000401544 <+20>:    mov    -0x8(%rbp),%rax

Это загружает неинициализированную стековую память как значение int *p.Автоматические переменные помещаются в стек.Вы читаете p, не написав этого сначала, и компилятор просто читает любой мусор или нули, которые уже были в слоте стека, который он выбрал для int *p;

=> 0x0000000000401548 <+24>:    movl   $0x3,(%rax)

В этой строке задается адрес, который raxуказывает на непосредственное значение 3.

Значение p находится в rax, так что это ваш p[0] = 3;, разыменовывающий любой мусор, который содержит p.Вы терпите крах, потому что это не указывает на доступную для записи память.(Перезапись некоторого случайного слова в памяти вряд ли будет лучше, но, по крайней мере, ваш код не потерпит крах здесь , возможно, в какой-то момент позже, если значение мусора p окажется действительным указателем.)

   0x000000000040154e <+30>:    mov    $0x0,%eax

Устанавливает регистр eax в ноль, а эффективно устанавливает rax в ноль, тоже .Windows x64 (как и любое стандартное соглашение о вызовах) использует RAX для возвращаемых значений, поэтому он реализует неявный return 0; в нижней части main.

   0x0000000000401553 <+35>:    add    $0x30,%rsp
   0x0000000000401557 <+39>:    pop    %rbp
   0x0000000000401558 <+40>:    retq

Восстанавливает состояния указателя до вызова функции.

...