movb $0xF, (%ebx)
прекрасно собирается (с префиксом размера адреса 0x67
) и выполняется правильно, если адрес в ebx
действителен.
Это может быть ошибка (и, например, приводящая к сбою в результате усечения указателя), или неоптимальная, но если ваша книга делает более сильное утверждение, чем эта (например, она не будет собираться), то ваша книга содержит ошибка.
Единственная причина, по которой вы когда-либо использовали это вместо movb $0xF, (%rbx)
, заключается в том, что старшие байты %rbx
потенциально содержали мусор, например в x32 ABI (ILP32 в длинном режиме) , или если вы тупой компилятор, который всегда использует префиксы размера адреса при нацеливании на режим 32-битного указателя, даже когда известно, что адреса безопасно расширен ноль .
32-битный размер адреса на самом деле полезен для ABI x32 для более распространенного случая, когда регистр индекса содержит большое количество мусора, например, movl $0x12345, (%edi, %esi,4)
.
gcc -mx32
может легко выдать movb $0xF, (%ebx)
инструкцию в реальной жизни. (Обратите внимание, что -mx32
(32-разрядные указатели в длинном режиме) отличается от -m32
(i386 ABI))
int ext(); // can't inline
void foo(char *p) {
ext(); // clobbers arg-passing registers
*p = 0xf; // so gcc needs to save the arg for after the call
}
Компилируется с gcc7.3 -mx32 -O3
в проводнике компилятора Godbolt в
foo(char*):
pushq %rbx # rbx is gcc's first choice of call-preserved reg.
movq %rdi, %rbx # stupid gcc copies the whole 64 bits when only the low 32 are useful
call ext()
movb $15, (%ebx) # $15 = $0xF
popq %rbx
ret
mov $edi, %ebx
было бы лучше; IDK, почему gcc хочет скопировать весь 64-битный регистр, когда он обрабатывает указатели как 32-битные значения. К сожалению, x32 ABI так и не завоевал популярность на x86, поэтому я полагаю, что никто не потратил время на то, чтобы gcc сгенерировал для него отличный код.
AArch64 также имеет ABI ILP32 для сохранения объема памяти / кэша на данных указателя, поэтому, возможно, gcc будет лучше работать с 32-разрядными указателями в 64-разрядном режиме в целом (также с преимуществами x86-64), если какая-либо работа для AArch64 ILP32 улучшает общие кросс-архитектурные части этого.
так что если бы строка кода была movl% eax, вместо% edx, ошибка бы не произошла?
Правильно, , который будет ноль расширять EAX до RDX . Если вы хотите подписать - расширить EAX в RDX, используйте movslq %eax, %rdx
(он же Intel-синтаксис movsxd
)
(Почти) все инструкции x86 требуют, чтобы все их операнды были одинакового размера. (С точки зрения размера операнда; многие инструкции имеют форму с 8-битным или 32-битным непосредственным значением, знак которого расширен до 64-битного или независимо от размера операнда инструкции. Например, add $1, %eax
будет использовать 3-байтовый add imm8, r/m32
форма .)
Исключения включают shl %cl, %eax
и movzx / movsx.
В синтаксисе AT & T размеры регистров должны соответствовать суффиксу размера операнда, если вы его используете. Если вы этого не сделаете, регистры подразумевают размер операнда. например mov %eax, %edx
совпадает с movl
.
Память + немедленные инструкции без источника или назначения регистра требуют явного размера: add $1, (%rdx)
не собирается, потому что размер операнда неоднозначен, но add %eax, (%rdx)
- это addl
(32-битный размер операнда) .
movew %si, 8(%rbp)
Нет, movw %si, 8(%rbp)
будет работать, хотя: P Но учтите, что если вы сделали традиционный фрейм стека с push %rbp
/ mov %rsp, %rbp
при вводе функции, то это сохранение в 8(%rbp)
перезапишет младшие 16 битов Ваш обратный адрес в стеке.
Но в коде x86-64 для Windows или Linux не требуется, чтобы вы указывали %rbp
или содержали правильный указатель вообще. Это просто регистр с сохранением вызовов, такой как %rbx
, который вы можете использовать для чего угодно, если только вы восстановите значение вызывающей стороны перед возвратом.