Почему NASM в Linux меняет регистры в сборке x86_64

Я новичок в программировании сборки x86_64. Я писал простую программу "Hello World" в сборке x86_64. Ниже мой код, который прекрасно работает.

global _start

section .data

    msg: db "Hello to the world of SLAE64", 0x0a
    mlen equ $-msg

section .text
    _start:
            mov rax, 1
            mov rdi, 1
            mov rsi, msg
            mov rdx, mlen
            syscall

            mov rax, 60
            mov rdi, 4
            syscall 

Теперь, когда я разбираю в GDB, он дает ниже вывод:

(gdb) disas
Dump of assembler code for function _start:
=> 0x00000000004000b0 <+0>:     mov    eax,0x1
   0x00000000004000b5 <+5>:     mov    edi,0x1
   0x00000000004000ba <+10>:    movabs rsi,0x6000d8
   0x00000000004000c4 <+20>:    mov    edx,0x1d
   0x00000000004000c9 <+25>:    syscall
   0x00000000004000cb <+27>:    mov    eax,0x3c
   0x00000000004000d0 <+32>:    mov    edi,0x4
   0x00000000004000d5 <+37>:    syscall
End of assembler dump.

Мой вопрос, почему NASM ведет себя таким образом? Я знаю, что это меняет инструкции на основе кода операции, но я не уверен в том же поведении с регистрами.

Также это поведение влияет на функциональность исполняемого файла?

Я использую Ubuntu 16.04 (64 бит), установленную в VMware на процессоре i5.

Заранее спасибо.

2 ответа

В 64-битном режиме mov eax, 1 очистит верхнюю часть rax зарегистрироваться (см. здесь для объяснения) таким образом mov eax, 1 семантически эквивалентно mov rax, 1,

Бывший однако запасной REX.W (48h численно) префикс (байт, необходимый для указания регистров, введенных с x86-64), код операции одинаков для обеих инструкций (0b8h сопровождаемый DWORD или QWORD).
Таким образом, ассемблер идет вперед и подбирает самую короткую форму.

Это типичное поведение NASM, см. Раздел 3.3 руководства NASM, где приведен пример [eax*2] собран как [eax+eax] пощадить disp32 поле после байта SIB 1 ([eax*2] кодируется только как [eax*2+disp32] где установлен ассемблер disp32 до 0).

Я не смог заставить NASM излучать настоящий mov rax, 1 инструкция (т.е. 48 B8 01 00 00 00 00 00 00 00) даже с префиксом инструкции o64,
Если реальный mov rax, 1 необходимо (это не ваш случай), нужно прибегнуть к сборке вручную с db и тому подобное.

РЕДАКТИРОВАТЬ: ответ Питера Кордеса показывает, что, на самом деле, есть способ сказать NASM не оптимизировать инструкцию с strictмодификатор.
mov rax, STRICT 1 выдает 10-байтовую версию инструкции (mov r64, imm64) в то время какmov rax, STRICT DWORD 1 выдает 7-байтовую версию (mov r64, imm32где imm32 продлен знак перед использованием).


Примечание: лучше использовать относительную RIP-адресацию, это позволяет избежать непосредственных 64-битных констант (тем самым уменьшая размер кода) и является обязательным в MacOS(на случай, если вам это интересно).
Изменитьmov esi, msg вlea esi, [REL msg](Относительный RIP - это режим адресации, поэтому ему нужна квадратная скобка для адресации, чтобы избежать чтения с того адреса, который мы используем leaэто только вычисляет эффективный адрес, но не имеет доступа).
Вы можете использовать директиву DEFAULT REL чтобы не печататьRELв каждом доступе к памяти.

У меня сложилось впечатление, что формат файла Mach-O требует кода PIC, но это может быть не так.


1Базовый байт индекса шкалы, используемый для кодирования нового режима адресации, введенного тогда в 32-битном режиме.

Это совершенно безопасная и полезная оптимизация, очень похожая на использование 8-битного немедленного вместо 32-битного немедленного при написании add eax, 1,

NASM оптимизирует только тогда, когда более короткая форма инструкции имеет идентичный архитектурный эффект, потому что mov eax,1 неявно обнуляет верхние 32 бита RAX.

Но обратите внимание, что YASM этого не делает, поэтому неплохо бы провести оптимизацию самостоятельно в исходном коде asm, если вы заботитесь о размере кода (даже косвенно по соображениям производительности).

Для инструкций, в которых 32- и 64-битный размер операнда не будет эквивалентен, если у вас очень большие (или отрицательные) числа, вам нужно явно использовать 32-битный размер операнда, даже если вы собираете с NASM вместо YASM, если вам нужно преимущество размера / производительности 32-битного размера операнда. Преимущества использования 32-битных регистров / инструкций в x86-64


Для 32-битных констант, для которых не установлен высокий бит, нулевое значение или знак, расширяющие их до 64 бит, дают идентичный результат. Таким образом, это чистая оптимизация для сборки mov rax, 1 до 5 байт mov r32, imm32 (с неявным расширением нуля до 64 бит) вместо 7 байт mov r/m64, sign_extended_imm32,

На всех современных процессорах x86 единственной разницей в производительности между этим и 7-байтовым кодированием является размер кода, поэтому фактором являются только косвенные эффекты, такие как выравнивание и давление L1I$. Внутренне это просто mov-немедленное, так что эта оптимизация также не меняет микроархитектурного эффекта вашего кода (за исключением, конечно, размера кода / выравнивания / того, как он упаковывается в кэш uop).

10-байтовый mov r64, imm64 Кодировка еще хуже для размера кода. Если константе фактически задан какой-либо из ее старших битов, то она имеет дополнительную неэффективность в кэше UOP на процессорах семейства Intel Sandybridge (используя 2 записи в кэше UOP и, возможно, дополнительный цикл для чтения из кэша UOP). Но если константа находится в диапазоне -2^31 .. +2^31 (32-разрядный со знаком), она сохраняется так же эффективно внутри, используя только одну запись uop-cache, даже если она была закодирована на компьютере x86 код с использованием 64-битной немедленной. (См. Документ микроархива Agner Fog, таблица 9.1. Размер различных инструкций в кэше μop в разделе Sandybridge)

От сколько способов установить регистр на ноль? Вы можете принудительно использовать любую из трех кодировок с помощью NASM:

mov    eax, 1                ; 5 bytes to encode (B8 imm32)
mov    rax, strict dword 1   ; 7 bytes: REX mov r/m64, sign-extended-imm32.    NASM optimizes mov rax,1 to the 5B version, but dword or strict dword stops it for some reason
mov    rax, strict qword 1   ; 10 bytes to encode (REX B8 imm64).  movabs mnemonic for AT&T.  Normally assemblers choose smaller encodings if the operand fits, but strict qword forces the imm64.

Обратите внимание, что NASM использовал 10-байтовую кодировку (которую вызывает синтаксис AT&T movabs и так же objdump в режиме Intel-синтаксиса) для адреса, который является постоянной времени соединения, но неизвестен во время сборки.

YASM выбирает mov r64, imm32 т.е. он предполагает модель кода, где адреса меток 32-битные, если вы не используете mov rsi, strict qword msg

Поведение YASM обычно хорошее (хотя mov r32, imm32 для статических абсолютных адресов, таких как компиляторы C, было бы еще лучше). Модель кода по умолчанию, отличная от PIC, помещает весь статический код / ​​данные в нижние 2 ГБ виртуального адресного пространства, поэтому 32-разрядные константы с нулевым или знаковым расширением могут содержать адреса.

Если вы хотите 64-битные адреса меток, вы должны обычно использовать lea r64, [rel address] сделать RIP-родственник LEA. (В Linux, по крайней мере, позиционно-зависимый код может идти ниже 32, поэтому, если вы не используете модели большого / огромного кода, в любое время, когда вам нужно заботиться о 64-битных адресах меток, вы также создаете код PIC где вы должны использовать REA-относительную LEA, чтобы избежать необходимости перемещения текста абсолютных адресных констант).

т.е. gcc и другие компиляторы использовали бы mov esi, msg , или же lea rsi, [rel msg] , никогда mov rsi, msg,

Другие вопросы по тегам