Нет, ASLR стека происходит один раз при запуске программы. Относительные корректировки RSP между функциями фиксируются во время компиляции и представляют собой лишь небольшие константы, освобождающие место для локальных переменных функции. (Массивы переменной длины C99 и alloca
выполняют корректировку переменных времени выполнения для RSP, но не случайным образом.)
Ваша программа содержит неопределенное поведение и фактически не печатает RSP; вместо этого некоторый адрес стека, оставленный в регистре предыдущим вызовом printf
(который выглядит как адрес стека, поэтому его старшие биты меняются в зависимости от ASLR). Он ничего не говорит вам о различиях указателей стека между функциями, только о том, как не использовать GNU C inline asm.
Первое значение правильно печатает текущий ESP, но это только младшие 32 бита из 64 -бит RSP.
Падение конца функции, отличной от void
, небезопасно , и использование возвращаемого значения - Undefined Behavior. Любой вызывающий, который использует возвращаемое значение esp_func()
, обязательно вызовет UB, поэтому компилятор может оставить все, что захочет, в RAX.
Если вы хотите написать mov %rsp, %rax
/ ret
, тогда напишите эту функцию в чистом asm или переместитесь в локальную переменную "=r"(tmp)
. Использование встроенного asm GNU C для изменения RAX, не сообщая об этом компилятору, ничего не меняет; компилятор по-прежнему видит это как функцию без возвращаемого значения.
MSV C встроенный asm отличается: очевидно, поддерживается использование _asm{ mov eax, 123 }
или чего-то еще, а затем выпадение конца непустого функция, и MSV C будет рассматривать это как возвращаемое значение функции даже при встраивании. Встроенный asm GNU C не нуждается в таких глупых хитростях: если вы хотите, чтобы ваш asm взаимодействовал со значениями C, используйте расширенный asm с ограничением вывода, как в main
. Помните, что GNU C inline asm - это не , анализируемый компилятором, просто отправьте строку шаблона как часть вывода asm компилятора для сборки.
Я не знаю точно, почему clang перезагружает возвращаемое значение из стека, но это всего лишь артефакт внутренних компонентов clang и как он выполняет генерацию кода с отключенной оптимизацией. Но это разрешено из-за неопределенного поведения. Это непустая функция, поэтому она должна иметь возвращаемое значение. Самым простым было бы просто выдать ret
, и это то, что некоторые компиляторы делают при включенной оптимизации, но даже это не решает проблему из-за межпроцедурной оптимизации.
На самом деле это Undefined Behavior от C до , используйте возвращаемое значение функции, которая не вернула единицу . Это применимо на уровне C; использование встроенного asm, который изменяет регистр, не сообщая об этом компилятору, ничего не меняет в том, что касается компилятора. Поэтому ваша программа в целом содержит UB, потому что она передает результат в printf
. Вот почему компилятору разрешено компилировать таким образом: ваш код уже сломан. На практике он просто возвращает некоторый мусор из памяти стека.
TL: DR: это недопустимый способ передать mov %rsp, %rax
/ ret
в качестве определения asm для функции.
(C ++ усиливает это тем, что UB в первую очередь падает с конца, но в C это законно, пока вызывающий не использует возвращаемое значение. Если вы компилируете тот же источник, что и C ++, с оптимизацией, g ++ даже не выдает инструкцию ret
после вашего встроенного шаблона asm. Вероятно, это сделано для поддержки C типа возврата по умолчанию - int
, если вы объявляете функцию без типа возврата.)
Этот UB также является причиной того, почему ваша измененная версия из комментариев (с фиксированными строками формата printf), скомпилированная с включенной оптимизацией (https://godbolt.org/z/sE7e84), печатает «удивительно» разные значения «RSP»: 2-е. не использует RSP вообще.
#include <inttypes.h>
#include <stdio.h>
uint64_t __attribute__((noinline)) rsp_func(void)
{
__asm__("movq %rsp, %rax");
} // UB if return value used
int main()
{
uint64_t rsp = 0;
__asm__("\t movq %%rsp,%0" : "=r"(rsp));
printf("rsp: 0x%08lx\n", rsp);
printf("rsp: 0x%08lx\n", rsp_func()); // UB here
return 0;
}
Пример вывода:
Compiler stderr
<source>:7:1: warning: non-void function does not return a value [-Wreturn-type]
}
^
1 warning generated.
Program returned: 0
Program stdout
rsp: 0x7fff5c472f30
rsp: 0x7f4b811b7170
clang -O3 asm
вывод показывает, что видимый компилятором UB был проблемой. Даже если вы использовали noinline
, компилятор все равно может видеть тело функции и пытаться выполнить межпроцедурную оптимизацию. В этом случае UB заставил его просто сдаться и не выдавать mov %rsp, %rsi
между call rsp_func
и call printf
, поэтому он печатает любое значение, которое предыдущий printf оставил в RSI
# from the Godbolt link
rsp_func: # @rsp_func
mov rax, rsp
ret
main: # @main
push rax
mov rsi, rsp
mov edi, offset .L.str
xor eax, eax
call printf
call rsp_func # return value ignored because of UB.
mov edi, offset .L.str
xor eax, eax
call printf # printf("0x%08lx\n", garbage in RSI left from last printf)
xor eax, eax
pop rcx
ret
.L.str:
.asciz "rsp: 0x%08lx\n"
GNU C Basi c asm (без ограничений) ни для чего не используется (кроме тела функции __attribute__((naked))
).
Не предполагайте, что компилятор будет делать то, что вы ожидаете, когда UB виден ему во время компиляции. (Когда UB не отображается во время компиляции, компилятор должен создать код, который будет работать для некоторых вызывающих или вызываемых абонентов, и вы получите ожидаемый asm. Но UB, видимый во время компиляции, означает, что все ставки отключены.)