В чем разница между инструкциями AT & T для x86-64 movq и movabsq? - PullRequest
0 голосов
/ 21 сентября 2018

После прочтения этого ответа о переполнении стека и этого документа я все еще не понимаю разницу между movq и movabsq.

Myв настоящее время понимается, что в movabsq первый операнд является 64-разрядным непосредственным операндом, тогда как movq расширяет знак 32-разрядного непосредственного операнда.Из второго документа, упомянутого выше:

Перемещение непосредственных данных в 64-битный регистр может быть выполнено либо с помощью инструкции movq, которая подпишет расширение 32-битного непосредственного значения, либо с помощьюmovabsq инструкция, когда требуется полная немедленная 64-разрядная версия.

В первом обращении Питер утверждает:

Интересный эксперимент: movq $0xFFFFFFFF, %rax, вероятно, неткодируемый, потому что он не может быть представлен 32-битным немедленным расширением знака и нуждается в кодировке imm64 или кодировке назначения %eax.

Однако, когда я собираю / запускаю это, кажется,отлично работают:

        .section .rodata
str:
        .string "0x%lx\n"
        .text
        .globl  main
main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $str, %edi
        movq    $0xFFFFFFFF, %rsi
        xorl    %eax, %eax
        call    printf
        xorl    %eax, %eax
        popq    %rbp
        ret

$ clang file.s -o file && ./file

отпечатки 0xffffffff.(Это работает аналогично для больших значений, например, если вы добавите несколько дополнительных «F»).movabsq генерирует идентичный вывод.

Clang выводит то, что я хочу?Если это так, есть ли еще преимущество в movabsq над movq?

Я что-то пропустил?

1 Ответ

0 голосов
/ 21 сентября 2018

Существует три вида ходов для заполнения 64-битного регистра:

  1. Переход к младшей 32-битной части : B8 +rd id, 5 байтов
    Пример: mov eax, 241 / mov[l] $241, %eax
    Переход к нижней 32-битной части обнулит верхнюю часть.

  2. Переход с 64-битнойнемедленный : 48 B8 +rd io, 10 байтов
    Пример: mov rax, 0xf1f1f1f1f1f1f1f1 / mov[abs][q] $0xf1f1f1f1f1f1f1f1, %rax
    Перемещение полного 64-битного немедленного.

  3. Перемещение с 32-разрядным немедленным расширением знака : 48 C7 /0 id, 7 байтов
    Пример: mov rax, 0xffffffffffffffff / mov[q] $0xffffffffffffffff, %rax Перемещение со знаком32-битный регистр с немедленным заполнением до 64-битного.

Обратите внимание, что на уровне сборки есть место для неопределенности , movq используется для второго и третьего случая.

Для каждого непосредственного значения мы имеем:

  • (a) Значения в [0, 0x7fff_ffff] могут быть закодированы с помощью (1), (2) и(3).
  • (b) Значения в [0x8000_0000, 0xffff_ffff] можно кодировать с помощью (1) и (2).
  • (c) Значения в [0x1_0000_0000, 0xffff_ffff_7fff_ffff] может быть закодировано с помощью (2)
  • (d) значений в [0xffff_ffff_8000_0000, 0xffff_ffff_ffff_ffff] ) и может быть закодировано 3 1058 *) и может быть закодировано 3 1058 *) (3) и может быть закодировано 3 1058 *) и может быть закодировано 3 1058 *) и может быть закодировано 3 1058 *) (3) и (можно) кодировать с помощью 3 1058 *) (3).

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

Для ГАЗА:
movabs[q] всегда соответствует (2).
mov[q] соответствует (3) для случаев (a) и (d), (2) длядругие случаи.
Он никогда не генерирует (1) для перехода в 64-битный регистр.

Чтобы его забрать (1), мы должны использовать mov[l] $0xffffffff, %edi, что эквивалентно (я думаю, что GAS не будет преобразовывать переход в 64-битный регистр в один в его более низкий 32-битный регистр, дажекогда это эквивалентно).


В 16/32-битную эру, различие между (1) и (3) не считалось действительно важным (все же в GAS можно выбрать один конкретныйform ), поскольку это была не операция расширения знака, а артефакт оригинальной кодировки в 8086.

Инструкция mov никогда не разделялась на две формы для учета (1) и (3) вместо этого один mov использовался с ассемблером, почти всегда выбирающим (1) вместо (3).

С новыми 64-битными регистрами, имеющими 64-битные непосредственные значения, код тоже был бы слишком далекоразреженный (и может легко нарушить текущую максимальную длину команды в 16 байтов), поэтому не стоит расширять (1), чтобы всегда принимать 64-битное непосредственное значение.
Вместо этого (1) все еще имеют 32-битное непосредственное значение и ноль-расширяется (чтобы сломать любые ложные данныеЗависимость) и (2) были введены для редкого случая, когда на самом деле нужен 64-битный непосредственный операнд.
Если воспользоваться возможностью, (3) также был изменен на все еще с 32-битным немедленнымно также подписать и расширить его.
(1) и (3) должно быть достаточно для наиболее распространенных непосредственных (например, 1 или -1).

Однако разница между (1) / (3) и (2) глубже, чем разница в прошлом между (1) и (3), поскольку в то время как (1) и (3) оба имеют операндтот же размер, 32-разрядный, (3) имеет 64-разрядный непосредственный операнд.

Зачем нужна искусственно удлиненная инструкция?
Одним из вариантов использования может быть заполнение, так что следующий цикл будет кратен 16/32 байтов.
Этопожертвуйте ресурсы во внешнем интерфейсе (больше места в кеше команд) для ресурсов во внутреннем интерфейсе (меньше uOP, чем при заполнении без операционных инструкций).

Другой, более частый, случай использования - это когда нужно всего лишь создать шаблон машинного кода.
Например, в JIT может потребоваться подготовить последовательность инструкций для использования и заполнить значения немедленных значенийтолько во время выполнения.
В этом случае использование (2) значительно упростит обработку, поскольку всегда есть место для всех возможных значений.

Другой случай касается некоторой функциональности исправления, в отладочной версии программного обеспечения определенные вызовы могут быть сделаны косвенно с адресом в регистре, который только что был загружен с (2), так что отладчик может легко перехватить вызов на любойновая цель.

...