инструкция повторяется дважды при декодировании на машинный язык, - PullRequest
0 голосов
/ 24 марта 2020

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

На примере простого mov инструкция и использование нотации .byte Я хотел бы детально понять, как декодируются инструкции,

Мой простой код выглядит следующим образом:

#include <stdio.h>
#include <iostream>



int main(int argc, char const *argv[])
{
    int x{5};
    int y{0};

    // mov %%eax, %0

asm (".byte 0x8b,0x45,0xf8\n\t" //mov %1, eax
    ".byte 0x89, 0xC0\n\t"
    : "=r" (y)
    : "r" (x)

   );



   printf ("dst value : %d\n", y);

    return 0;
} 

и когда я использую objdump чтобы проанализировать, как он разбит на машинный язык, я получаю следующий вывод:

000000000000078a <main>:
 78a:    55                       push   %ebp
 78b:    48                       dec    %eax
 78c:    89 e5                    mov    %esp,%ebp
 78e:    48                       dec    %eax
 78f:    83 ec 20                 sub    $0x20,%esp
 792:    89 7d ec                 mov    %edi,-0x14(%ebp)
 795:    48                       dec    %eax
 796:    89 75 e0                 mov    %esi,-0x20(%ebp)
 799:    c7 45 f8 05 00 00 00     movl   $0x5,-0x8(%ebp)
 7a0:    c7 45 fc 00 00 00 00     movl   $0x0,-0x4(%ebp)
 7a7:    8b 45 f8                 mov    -0x8(%ebp),%eax
 7aa:    8b 45 f8                 mov    -0x8(%ebp),%eax
 7ad:    89 c0                    mov    %eax,%eax
 7af:    89 45 fc                 mov    %eax,-0x4(%ebp)
 7b2:    8b 45 fc                 mov    -0x4(%ebp),%eax
 7b5:    89 c6                    mov    %eax,%esi
 7b7:    48                       dec    %eax
 7b8:    8d 3d f7 00 00 00        lea    0xf7,%edi
 7be:    b8 00 00 00 00           mov    $0x0,%eax
 7c3:    e8 78 fe ff ff           call   640 <printf@plt>
 7c8:    b8 00 00 00 00           mov    $0x0,%eax
 7cd:    c9                       leave  
 7ce:    c3                       ret    

Что касается этого вывода objdump, почему инструкция 7aa: 8b 45 f8 mov -0x8(%ebp),%eax повторяется дважды, по любой причине за этим или я делаю что-то не так при использовании обозначения .byte?

1 Ответ

1 голос
/ 24 марта 2020

Один из них генерируется компилятором, потому что вы попросили G CC предоставить данные для выбора регистра для вас. Вот что означает "r"(x). И вы скомпилировали с отключенной оптимизацией (по умолчанию -O0), так что она фактически сохранила x в памяти, а затем перезагрузила ее перед вашим оператором asm.

В вашем коде нет никакого дела, предполагая что-либо о содержимом памяти или где EBP указывает.

Поскольку вы используете 89 c0 mov %eax,%eax, , единственными безопасными ограничениями для вашего оператора asm являются "a" ограничения явного регистра для ввода и вывода, заставляет компилятор выбрать это. Если вы компилируете с включенной оптимизацией, ваш код полностью сломается, потому что вы соврали компилятору о том, что на самом деле делает ваш код.

// constraints that match your manually-encoded instruction
asm (".byte 0x89, 0xC0\n\t"
    : "=a" (y)
    : "a" (x)
   );

Там нет ограничение заставляет G CC выбирать определенный режим адресации для источника "m" или операнда "=m" dest, поэтому вам нужно запрашивать входы / выходы в указанных c регистрах.

Если вы хотите чтобы кодировать ваши собственные mov инструкции в отличие от от стандартного mov, см. , какие инструкции MOV в x86 не используются или используются реже всего, a nd может использоваться для пользовательского расширения MOV - вы можете использовать префикс перед обычными опкодами mov, чтобы вы могли позволить ассемблеру кодировать регистры и режимы адресации, например .byte something; mov %1, %0.


Посмотрите на вывод asm, сгенерированный компилятором (gcc -S, а не дизассемблирование .o или исполняемого файла). Затем вы можете увидеть, какие инструкции взяты из оператора asm, а какие - G CC.

Если вы явно не ссылаетесь на некоторые операнды в шаблоне asm, но по-прежнему хотите увидеть, что выбрал компилятор, вы можете использовать их в asm комментариях, таких как:

asm (".byte 0x8b,0x45,0xf8    # 0 = %0   1 = %1  \n\t"
    ".byte 0x89, 0xC0\n\t"
    : "=r" (y)
    : "r" (x)
   );

и g cc заполнит его для вас, чтобы вы могли видеть, какие операнды он ожидает Вы должны читать и писать. ( Годболт с g++ -m32 -O3). Я поместил ваш код в void foo(){} вместо main, потому что G CC -m32 считает, что нужно перестроить стек в верхней части main. Это усложняет выполнение кода.

# gcc-9.2 -O3 -m32 -fverbose-asm
.LC0:
        .string "dst value : %d\n"
foo():
        subl    $20, %esp       #,
        movl    $5, %eax        #, tmp84
 ## Notice that GCC hasn't set up EBP at all before it runs your asm,
 ## and hasn't stored x in memory.
 ## It only put it in a register like you asked it to.
        .byte 0x8b,0x45,0xf8   # 0 = %eax   1 = %eax    # y, tmp84
        .byte 0x89, 0xC0

        pushl   %eax  # y
        pushl   $.LC0 #
        call    printf  #
        addl    $28, %esp       #,
        ret

Также обратите внимание, что если вы компилируете как 64-битный код, он, вероятно, выберет %esi в качестве регистра, потому что printf будет нуждаться во втором аргументе. Таким образом, ограничение "a" вместо "r" действительно имеет значение.

Вы можете получить 32-битный G CC, чтобы использовать другой регистр, если вы присваиваете переменную, которая должна сохраняться в функции вызов; тогда G CC выберет регистр с сохранением вызовов, такой как EBX, вместо EAX.

...