C语言中的栈上变量与函数调用
01 栈上变量
C 语言中,栈上变量的位置是这样的:
Intel 的 x86 系列 CPU——小端存储(低位放到低地址)
比如 0x000000C8 存储在计算机中:
低位 --> 高位
C8 00 00 00
02 static
先看 C 语言代码:
int func(int i)
{
static int a = i;
static int b = i+1;
// other code
}
下面是汇编代码:
xor eax, eax
mov al, byte_4255D8 ; 这个存储 static 是否被赋值
and eax, 1 ; 只保留最低位
test eax, eax
jnz short loc_40105E
; 如果没有初始化过这个 static
mov cl, byte_4255D8
or cl, 1 ; 将最低位赋值为 1
mov byte_4255D8, cl
mov edx, [ebp+arg_0]
mov dword_4255DC, edx ; 初始化static为函数第一个参数
loc_40105E:
xor eax, eax
mov al, byte_4255D8 ; 这个存储 static 是否被赋值
and eax, 2 ; 只保留倒数第二位
test eax, eax
jnz short loc_401087
; 如果没有初始化过这个 static
mov cl, byte_4255D8
or cl, 2 ; 将倒数第二位赋值为 1
mov byte_4255D8, cl
mov edx, [ebp+arg_0]
add edx, 1
mov dword_4255E0, edx ; 初始化static为函数第一个参数+1
03 switch
先看 C 语言:
switch(a)
{
case 1:
// xxx
break;
case 2:
// xxx
break;
case 3:
// xxx
break;
case 4:
// xxx
break;
default:
// xxx
}
再看汇编:
mov edx, [ebp+var_4]
jmp ds:off_4010D2[edx*4]
.text
off_4010D2:
dd offset loc_401047
dd offset loc_4010xx
dd offset loc_4010xx
dd offset loc_4010xx
dd offset loc_4010xx
注意:
当 case 数量减少时,会变成和 if-elif-else 类似
但是最后会有一个单入单出的 jmp 指令

第二种情况,case不是单增且线性的。先看 C 语言:
switch(a)
{
case 1:
// xxx
break;
case 2:
// xxx
break;
case 3:
// xxx
break;
case 4:
// xxx
break;
case 255:
// xxx
break;
default:
// xxx
}
再看汇编:
mov edx, [ebp+var_4]
xor edx, edx
mov dl, ds:byte_4010F7[eax]
jmp ds:off_4010D2[edx*4]
byte_4010F7:
db 0, 1, 2, 3, 5, 5,..., 5, 4
.text
off_4010D2:
dd offset loc_401047
dd offset loc_4010xx
dd offset loc_4010xx
dd offset loc_4010xx
dd offset loc_4010xx
dd offset loc_4010xx
04 循环
while
int i=100;
while(i--)
{
// xxx
}
loc_40102F:
mov eax, [ebp+var_4] ; 因为我们是 i--,先判断再减
mov ecx, [ebp+var_4]
sub ecx, 1
mov [ebp+var_4], ecx
test eax, eax
jz short loc_401052 ; 跳出循环
; 循环内部
jmp short loc_40102F ; 跳回循环开头
loc_401052:
; 后续代码
for
int i=100;
for(;i;i--)
{
// xxx
}
loc_401031: ; 这里不是循环入口点
mov eax, [ebp+var_4]
sub eax, 1
mov [ebp+var_4], eax ; i--
loc_40103A: ; 这里才是循环入口点
cmp [ebp+var_4], 0
jz short loc_401053 ; 跳出循环
; 循环内部
jmp short loc_401031 ; 跳回开头
loc_401053:
; 后续代码
do-while
int i=100;
do
{
i--;
// xxx
} while(i);
loc_40102F: ; 循环入口
mov eax, [ebp+var_4]
sub eax, 1
mov [ebp+var_4], eax ; i--
; 循环内部
cmp [ebp+var_4], 0
jnz short loc_40102F ; 跳回开头
; 后续代码
05 函数
mov eax, [ebp+var_4]
push eax
call foo
add esp, 4 ; 手动清栈,去除掉多的esp
foo:
; 计算栈顶栈底
push ebp
mov ebp, esp
sub esp, 44h
; 开始保存母函数环境
push ebx
push esi
push edi
; 函数内部逻辑
; ...
; 恢复母函数环境
pop edi
pop esi
pop ebx
; 计算栈顶栈底
add esp, 44h
cmp ebp, esp
call __chkesp
mov esp, ebp
pop ebp
retn
函数调用流程:
使用 push 将参数压入栈
在 call 的时候,将返回地址(call 下一行的地址)压入栈,然后进入函数内部
函数执行流程:
使用 push 将母函数栈底压入栈
使用 mov 将子函数的栈底设为母函数的栈顶
通过 sub 计算子函数栈顶
使用 push 保存母函数的寄存器
<程序执行>
使用 pop 恢复母函数的寄存器
通过 add 恢复母函数栈顶
使用 cmp 比较堆栈是否平衡
通过 mov 恢复母函数栈顶
通过 pop 恢复母函数栈底
通过 ret 返回母函数返回地址
函数调用约定——Cdecl
参数从右向左依次压入栈
函数调用约定——Stdcall
略
函数调用约定——Fastcall
略
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 LinJHS
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果

