Сборка: Цель загрузки действующего адреса перед вызовом функции? - PullRequest
3 голосов
/ 22 января 2020

Источник C Код:

 int main()
    {
      int i;
      for(i=0, i < 10; i++)
      {
        printf("Hello World!\n");
      }
    }

Дамп кода ассемблера Intel x86 для функции main:

  1.  0x000055555555463a <+0>:     push   rbp
  2.  0x000055555555463b <+1>:     mov    rbp,rsp 
  3.  0x000055555555463e <+4>:     sub    rsp,0x10
  4.  0x0000555555554642 <+8>:     mov    DWORD PTR [rbp-0x4],0x0
  5.  0x0000555555554649 <+15>:    jmp    0x55555555465b <main+33>
  6.  0x000055555555464b <+17>:    lea    rdi,[rip+0xa2]    # 0x5555555546f4
  7.  0x0000555555554652 <+24>:    call   0x555555554510 <puts@plt>
  8.  0x0000555555554657 <+29>:    add    DWORD PTR [rbp-0x4],0x1
  9.  0x000055555555465b <+33>:    cmp    DWORD PTR [rbp-0x4],0x9
  10. 0x000055555555465f <+37>:    jle    0x55555555464b <main+17>
  11. 0x0000555555554661 <+39>:    mov    eax,0x0
  12. 0x0000555555554666 <+44>:    leave  
  13. 0x0000555555554667 <+45>:    ret    

Я сейчас работаю через "Взлом, The «Искусство эксплуатации 2-е издание Джона Эриксона», и я только начинаю заниматься сборкой.

У меня есть несколько вопросов о переводе предоставленного C кода в Assembly, но я в основном задаюсь вопросом о своем первом вопросе.

1-й вопрос: Какова цель строки 6? (lea rdi,[rip+0xa2]).

Моя текущая рабочая теория заключается в том, что это используется для сохранения того, куда будут переходить следующие инструкции, чтобы отследить, что происходит. Я считаю, что эта строка соотносится с функцией printf в исходном коде C.

По сути, он загружает эффективный адрес rip+0xa2 (0x5555555546f4) в регистр rdi, чтобы просто отследить, куда он перейдет для функции printf?

2-й вопрос: какова цель строки 11? (mov eax,0x0?) Я не вижу предварительного использования регистра, EAX и не уверен, почему его нужно установить на 0.

Ответы [ 3 ]

7 голосов
/ 22 января 2020

LEA помещает указатель на строковый литерал в регистр в качестве первого аргумента для put. Поисковый термин, который вы ищете, это «соглашение о вызовах» и / или ABI. (А также RIP-относительная адресация). Почему адрес переменных stati c относительно указателя инструкций?

Небольшое смещение между кодом и данными (только +0xa2) связано с тем, что секция .rodata становится связанной в тот же сегмент ELF, что и .text, и ваша программа крошечная. (Более новые версии g cc + ld помещают его на отдельную страницу, поэтому он может быть неисполняемым.)

Компилятор не может использовать более короткий более эффективный mov edi, address в позиционно-независимом коде в ваш Linux P IE исполняемый файл. Это будет сделано с gcc -fno-pie -no-pie

mov eax,0, реализующим неявный return 0 в конце main, который гарантируют C99 и C ++. EAX - это регистр возвращаемого значения во всех соглашениях о вызовах.

Если вы не используете gcc -O2 или выше, вы не получите оптимизацию глазка, например, обнуление по xor (xor eax,eax).

3 голосов
/ 22 января 2020

Это:

lea    rdi,[rip+0xa2]

Типично не зависит от позиции LEA, помещая строковый адрес в регистр (вместо загрузки с этого адреса памяти).

Ваш исполняемый файл не зависит от позиции , что означает, что он может быть загружен во время выполнения по любому адресу. Поэтому реальный адрес аргумента, передаваемого в puts(), необходимо вычислять во время выполнения каждый раз, поскольку базовый адрес программы может каждый раз отличаться. Кроме того, puts() используется вместо printf(), потому что компилятор оптимизировал вызов, поскольку не нужно ничего форматировать.

В этом случае двоичный файл, скорее всего, был загружен с базовым адресом 0x555555554000 , Используемая строка хранится в вашем двоичном файле со смещением 0x6f4. Поскольку следующая инструкция находится со смещением 0x652, вы знаете, что независимо от того, где двоичный файл загружен в память, вам нужен адрес rip + (0x6f4 - 0x652) = rip + 0xa2, который вы видите выше. См. мой ответ моего другого примера.

Цель:

mov eax,0x0

- установить возвращаемое значение main(). В Intel x86 соглашение о вызовах должно возвращать значения в регистр rax (eax, если значение равно 32 бита, что в данном случае верно, поскольку main возвращает int). См. Запись в таблице для x86-64 в конце этой страницы .

Даже если вы не добавите явный оператор return, main() - это специальная функция, и компилятор добавит для вас значение по умолчанию return 0.

0 голосов
/ 22 января 2020

Если вы добавите отладочные данные и символы в сборку, все будет проще. Также легче читать код, если вы добавите некоторые оптимизации.

Существует очень полезный инструмент godbolt и ваш пример https://godbolt.org/z/9sRFmU

В списке asm вы можете ясно видеть, что эти строки загружают адрес строкового литерала который затем будет напечатан функцией.

EAX считается энергозависимым, а main по умолчанию возвращает ноль, и именно поэтому он обнуляется.

Соглашение о вызовах объясняется здесь: https://en.wikipedia.org/wiki/X86_calling_conventions

Здесь у вас есть более интересные случаи https://godbolt.org/z/M4MeGk

...