#c #assembly #visual-c #x86-64 #masm
#c #сборка #visual-c #x86-64 #masm
Вопрос:
Я попытался посмотреть, как MSVC выделяет свои 32 байта теневого пространства, но, похоже, он выделяет только 8 байт теневого пространства.
// Test.c
int main() {int var1 = 1;}
Приведенная выше программа приводит к следующему файлу .asm:
var1$ = 0
main PROC
; Test.c
sub rsp, 24 ; allocates 24 bytes
mov DWORD PTR var1$[rsp], 1
xor eax, eax
add rsp, 24
ret 0
main ENDP
Он выделяет только 24 байта. Он выделяет столько же, когда я объявляю 4 переменные, и поскольку каждая переменная равна 4 байтам, это должно означать, что 16 байт из 24 байт используются для объявленных переменных, оставляя 8 байт для теневого пространства.
Только при объявлении 5 переменных он выделяет 40 байт теневого пространства. Почему он выделяет только 8 байт теневого пространства?
Я скомпилировал программу с помощью команды CL Test.c /Fa
Комментарии:
1. Я думаю, вы забыли посчитать обратный адрес, передаваемый вызывающим кодом
main
, что означает, что указатель стека смещен на 8 байт при входе в main .2. Это все объясняет!
Ответ №1:
Вычитание 24 из RSP здесь не имеет никакого отношения к теневому пространству. Теневое пространство применяется только в том случае, если main
требуется вызвать какую-либо другую 64-разрядную функцию, совместимую с Microsoft ABI. Ваша main
функция является конечной функцией (она больше ничего не вызывает), поэтому не требуется выделять дополнительное пространство для теневого пространства. Если вы изменили main
вызов чего-либо в библиотеке C / C или WinAPI, вы обнаружите, что для теневого пространства будет добавлено дополнительное пространство для выполнения такого вызова.
Учитывая, что ваша функция имеет дело с 32-разрядными значениями (и без массивов) и не вызывает ничего другого, я не вижу причин, по которым ей нужно выравнивать по 16-байтовой границе или добавлять дополнительные отступы, но это то, что она, похоже, делает. Адрес возврата в стеке смещает стек на 8. Вычитание 24 выравнивает его по 16-байтовой границе с дополнением после переменных.
Вероятно, это результат неэффективности генерации кода при компиляции без каких-либо оптимизаций (например /O1
, /O2
и т.д.) Или компилятора, заполняющего пространство локальных переменных до предпочтительного размера. Теоретически в этом случае не нужно было выделять какое-либо пространство стека. Он мог бы повторно использовать теневое пространство над обратным адресом, которое было бы выделено для main
функции кодом запуска C / C .
Примечание: При оптимизации код будет полностью удален, если вы не создадите var1
volatile
переменную. Компилятор должен распознать, что написанный вами код ничего не делает, кроме возврата обратно вызывающему.
В следующем примере добавлены вызовы ExitProcess
для отображения теневого пространства; повторное использование теневого пространства, выделенного кодом запуска C / C , который вызывал main
локальные переменные; и использование некоторого стекового пространства для переменной, которая не может поместиться в теневом пространстве. Поскольку вызываемый WinAPI ExitProcess
должен иметь 32 байта теневого пространства, выделенного перед вызовом к нему. Если вы удалите его из этого примера, компилятор не выделит для него дополнительное пространство.
test.c
// Test.c
// Get prototype for ExitProcess
#include <windows.h>
int main()
{
volatile int var1 = 1;
volatile int var2 = 2;
volatile int var3 = 3;
volatile int var4 = 4;
volatile int var5 = 5;
// Since this is a WinAPI call it needs shadow space allocated
ExitProcess(var1 var2 var3 var4 var5);
// We won't get this far
return 0;
}
Если вы скомпилируете это с /O2
оптимизацией для максимальной скорости, используя CL Test.c /Fa /O2
, вы, вероятно, увидите что-то похожее на:
var1$ = 32
var5$ = 64
var4$ = 72
var3$ = 80
var2$ = 88
main PROC
sub rsp, 56 ; 00000038H
mov DWORD PTR var1$[rsp], 1
mov DWORD PTR var2$[rsp], 2
mov DWORD PTR var3$[rsp], 3
mov DWORD PTR var4$[rsp], 4
mov DWORD PTR var5$[rsp], 5
mov edx, DWORD PTR var5$[rsp]
mov eax, DWORD PTR var4$[rsp]
add edx, eax
mov ecx, DWORD PTR var3$[rsp]
add ecx, edx
mov edx, DWORD PTR var2$[rsp]
add edx, ecx
mov ecx, DWORD PTR var1$[rsp]
add ecx, edx
call QWORD PTR __imp_ExitProcess
int 3
main ENDP
var1
имеет смещение 32 от RSP, потому что теневое пространство — это первые 32 байта, начинающиеся с RSP для вызова ExitProcess
. Другие переменные var2
, var3
, var4
и var5
все начинаются со смещения> = 64. Компилятор сгенерировал корректировку 56 для RSP. Адрес возврата находится в RSP 56 и main
имеет теневое пространство от RSP 64 до RSP 96, таким образом var2
, to var5
были помещены в теневое пространство, выделенное для main
.