x86-64 System V ABI гарантирует 16-байтовое выравнивание стека перед call
, поэтому libc system
может использовать это преимущество для 16-байтовых выровненных загрузок / хранилищ .Если вы нарушаете ABI, это ваша проблема, если что-то пойдет не так.
При входе в функцию, после того, как call
выдвинул адрес возврата, RSP + -8 выровнен на 16 байт, и еще один push
настроит вас на вызов другой функции.
Конечно, GCC обычно не имеет проблем с этим, используя либо нечетное число push
es, либо sub rsp, 16*n + 8
, чтобы зарезервировать пространство стека.Использование локальной переменной register-asm с asm("rsp")
не нарушает этого, если только вы читаете переменную, а не присваиваете ей.
Вы говорите, что используете GCC7.3. Я поместил ваш код в проводник компилятора Godbolt и скомпилировал его с помощью -O3
, -O2
, -O1
и -O0
.Он следует ABI на всех уровнях оптимизации, создавая main
, который начинается с sub rsp, 8
и не изменяет RSP внутри функции (за исключением call
), до конца функции.
Так же как и все остальные версии и уровни оптимизации clang и gcc, которые я проверял.
Это код ggen7.3 -O3: обратите внимание, что он не делает что-либо с RSP, кроме чтения еговнутри тела функции, поэтому если main
вызывается с действительным RSP (16-байтовым выравниванием - 8), все вызовы функций main
также будут выполняться с 16-байтовым RSP.( И он никогда не найдет sp & 8
истину, поэтому он никогда не позвонит system
, во-первых. )
# gcc7.3 -O3
main:
sub rsp, 8
xor eax, eax
mov edi, OFFSET FLAT:.LC0
mov rsi, rsp # read RSP.
call printf
test spl, 8 # low 8 bits of RSP
je .L2
mov edi, OFFSET FLAT:.LC1
call puts
mov edi, OFFSET FLAT:.LC2
call system
.L2:
xor eax, eax
add rsp, 8
ret
Если вы звоните main
каким-то нестандартным образом вы нарушаете ABI .И вы не объясняете это в этом вопросе, так что это не MCVE .
Как я объяснил в Допускает ли стандарт C ++ неинициализированный bool для сбоя программы? , компиляторам разрешено выдавать код, который использует любые гарантии, которые дает ABI целевой платформы.Это включает использование movaps
для 16-байтовых загрузок / хранилищ для копирования содержимого в стеке с использованием гарантии входного выравнивания.
Это пропущенная оптимизация, которую gcc не оптимизируетif()
полностью, как clang
.
Но Clang действительно рассматривает его как неинициализированную переменную;без использования его в операторе asm
, поэтому локальный регистр asm("rsp")
, по-моему, никак не влияет на clang.Clang оставляет RSI неизмененным перед первым printf
вызовом, поэтому main
clang фактически печатает argv
, вообще не читая RSP.
Clang разрешено делать это: поддерживается только использование локальных переменных register-asm заставляет "r"(var)
расширенных ограничений asm выбрать нужный регистр.(https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html).
Руководство не подразумевает, что простое использование такой переменной в других случаях может быть проблематичным, поэтому я думаю, что этот код должен быть безопасным в целом в соответствии с написанными правилами, а также с работойна практике.
В руководстве сказано, что использование закрытого для вызова регистра (например, "rcx"
на x86) приведет к тому, что переменная будет перекрыта вызовами функций, поэтому, возможно, будет затронута переменная, использующая rsp
сгенерированным компилятором push / pop?
Это интересный тестовый пример: посмотрите его по ссылке Godbolt.
// gcc won't compile this: "error: unable to find a register to spill"
// clang simply copies the value back out of RDX before idiv
int sink;
int divide(int a, int b) {
register long long int dx asm ("rdx") = b;
asm("" : "+r"(dx)); // actually make the compiler put the value in RDX
sink = a/b; // IDIV uses EDX as an input
return dx;
}
Без asm("" : "+r"(dx));
, gcc компилирует это просто отлично,никогда не помещать b
в RDX вообще.