Понимание того, как язык ассемблера передает аргументы от одного метода к другому - PullRequest
0 голосов
/ 26 мая 2020

В течение нескольких часов я пытался расширить свое понимание языка ассемблера, пытаясь прочитать и понять инструкции очень простой программы, которую я написал на C, чтобы понять, как обрабатываются аргументы в КАК М.

#include <stdio.h>

int say_hello();

int main(void) {

    printf("say_hello() -> %d\n", say_hello(10, 20, 30, 40, 50, 60, 70, 80, 90, 100));

}

int say_hello(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) {

    printf("a:b:c:d:e:f:g:h:i:j -> %d:%d:%d:%d:%d:%d:%d:%d:%d:%d\n", a, b, c, d, e, f, g, h, i, j);

    return 1000;

}

Программа, как я уже сказал, очень базовая c и содержит две функции, основную и еще одну, называемую say_hello, которая принимает 10 аргументов, от a до j и печатает каждый из них в вызове printf. Я пробовал проделать тот же процесс (так что пытаюсь понять инструкции и то, что происходит), с той же программой и меньшим количеством аргументов, я думаю, что смог понять большую часть этого, но потом мне было интересно, "хорошо но что происходит, если у меня так много аргументов, что больше нет доступного регистра для хранения значения в "

" Итак, я пошел посмотреть, сколько регистров было доступно и применимо в моем случае, и я узнал из этого веб-сайта , что "только" (не уверен, поправьте меня, если я ошибаюсь) в моем случае могут использоваться следующие регистры для хранения в них значений аргументов edi, esi, r8d, r9d, r10d, r11d, edx, ecx, то есть 8, поэтому я пошел модифицировать свою программу C и добавил еще несколько аргументов, чтобы достичь предела 8, я даже добавил еще один, я действительно не знаю почему, скажем так, на всякий случай. 1014 но я ничего не ожидал от метода say_hello(), поэтому в первую очередь я попробовал его.

Итак, я собрал свою программу, затем разобрал ее с помощью команды objdump (точнее, это полная команда, которую я использовал: objdump -d -M intel helloworld), и я начал искать свой метод main, который делал примерно так, как я ожидал. от одного метода к другому. Но потом я пошел искать метод say_hello, и это меня очень запутало.

000000000000069e <say_hello>:
 69e:   55                      push   rbp
 69f:   48 89 e5                mov    rbp,rsp
 6a2:   48 83 ec 20             sub    rsp,0x20
 6a6:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
 6a9:   89 75 f8                mov    DWORD PTR [rbp-0x8],esi
 6ac:   89 55 f4                mov    DWORD PTR [rbp-0xc],edx
 6af:   89 4d f0                mov    DWORD PTR [rbp-0x10],ecx
 6b2:   44 89 45 ec             mov    DWORD PTR [rbp-0x14],r8d
 6b6:   44 89 4d e8             mov    DWORD PTR [rbp-0x18],r9d
 6ba:   44 8b 45 ec             mov    r8d,DWORD PTR [rbp-0x14]
 6be:   8b 7d f0                mov    edi,DWORD PTR [rbp-0x10]
 6c1:   8b 4d f4                mov    ecx,DWORD PTR [rbp-0xc]
 6c4:   8b 55 f8                mov    edx,DWORD PTR [rbp-0x8]
 6c7:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
 6ca:   48 83 ec 08             sub    rsp,0x8
 6ce:   8b 75 28                mov    esi,DWORD PTR [rbp+0x28]
 6d1:   56                      push   rsi
 6d2:   8b 75 20                mov    esi,DWORD PTR [rbp+0x20]
 6d5:   56                      push   rsi
 6d6:   8b 75 18                mov    esi,DWORD PTR [rbp+0x18]
 6d9:   56                      push   rsi
 6da:   8b 75 10                mov    esi,DWORD PTR [rbp+0x10]
 6dd:   56                      push   rsi
 6de:   8b 75 e8                mov    esi,DWORD PTR [rbp-0x18]
 6e1:   56                      push   rsi
 6e2:   45 89 c1                mov    r9d,r8d
 6e5:   41 89 f8                mov    r8d,edi
 6e8:   89 c6                   mov    esi,eax
 6ea:   48 8d 3d bf 00 00 00    lea    rdi,[rip+0xbf]        # 7b0 <_IO_stdin_used+0x20>
 6f1:   b8 00 00 00 00          mov    eax,0x0
 6f6:   e8 25 fe ff ff          call   520 <printf@plt>
 6fb:   48 83 c4 30             add    rsp,0x30
 6ff:   b8 e8 03 00 00          mov    eax,0x3e8
 704:   c9                      leave
 705:   c3                      ret
 706:   66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
 70d:   00 00 00

Мне заранее очень жаль, я не совсем уверен, что хорошо понимаю, что делают квадратные скобки, но из того, что я прочитал и понял, это способ «указать» на адрес, содержащий нужное мне значение (пожалуйста, поправьте меня, если я ошибаюсь), например, mov DWORD PTR [rbp-0x4],edi перемещает значение в edi на значение по адресу rsp-0x4, верно?

Я также не совсем понимаю, зачем нужен этот процесс, не может ли метод say_hello просто читать edi, например, и все? Почему программе нужно переместить его в [rbp-0x4], а затем перечитать обратно из [rbp-0x4] в eax?

Итак, программа просто продолжает читать все необходимые ей значения и помещает их в доступный регистр, а когда достигает точки, когда не остается ни одного регистра, она просто начинает перемещать их все в esi и затем помещаем их в стек, а затем повторяем процесс, пока все 10 аргументов не будут где-то сохранены.

В этом есть смысл, я был удовлетворен, а затем просто пошел, чтобы дважды проверить, действительно ли у меня все хорошо, поэтому я начал читать снизу вверх, начиная с 0x6ea до 0x6e2, поэтому образец Я работаю над

 6e2:   45 89 c1                mov    r9d,r8d
 6e5:   41 89 f8                mov    r8d,edi
 6e8:   89 c6                   mov    esi,eax
 6ea:   48 8d 3d bf 00 00 00    lea    rdi,[rip+0xbf]        # 7b0 <_IO_stdin_used+0x20>

Итак, как и во всех моих предыдущих тестах, я ожидал, что аргументы go будут "обратными", например, первый аргумент - это последняя выполненная инструкция, а последняя одна была выполнена первая инструкция, поэтому я начал дважды проверять каждое поле.

Итак, первый, rdi, был [rip+0x10b], который, как я думал, точно указывает на мою строку.

Затем я перешел к 0x6e8, который перемещает eax, который в настоящее время равно значению, хранящемуся в [rbp-0x4], которое равно edi, как указано в 0x6a6, а edi равно 0xa (10), как указано в 0x671, поэтому мой первый аргумент это моя строка, а вторая - 10, это именно то, что я ожидал.

Но потом, когда я перешел на инструкцию, выполненную прямо перед 0x6e8, поэтому 0x6e5 я ожидал, что она будет 20, поэтому я проделал тот же процесс. edi перемещается в r8d и в настоящее время равно значению, хранящемуся в [rbp-0x10], которое равно ecx, которое равно, как указано в 0x662 .. 40? Какого черта? Я запуталась, с чего бы это было 40? Затем я попытался найти инструкцию прямо над этой, и нашел 50, проделал то же самое со следующей, и снова я нашел 60 !! Зачем? Я неправильно понимаю эти значения? Что-то не хватает в инструкции? Или я просто предположил что-то, глядя на свои предыдущие программы (все они имели намного меньше аргументов и все были «в обратном порядке», как я сказал ранее), чего мне не следовало бы иметь?

* 1074 - тупой пост, я новичок в ASM (несколько часов опыта!) и просто пытаюсь прояснить это, потому что я действительно не могу понять это в одиночку. Мне также жаль, если этот пост слишком длинный, я пытался включить много информации, чтобы то, что я пытаюсь сделать, было ясным, результат, который я получаю, ясен, и в чем моя проблема также ясно. Большое спасибо за чтение и еще большее спасибо всем, кто поможет!
...