函数之间的参数传递在汇编程序中如何工作?
How does transfer of parameters between functions work in Assembler?
Soo 我正在尝试了解 Assembler 如何处理堆栈帧等。
我做了一些练习,并使用英特尔符号用 GDB 反汇编了一些 C 代码。现在的任务是找出变量 'r' 是如何通过在 'main' 和 'f' 函数之间传递参数来计算的。我刚开始学习,有点迷失在这个例子的实际作用上。关于从哪里开始的任何想法或提示?
这是一个与教员合作的递归程序:
#include <stdio.h>
unsigned int f(unsigned int i) {
if (i>1) {
return i * f(i-1);
} else {
return 1;
}
}
int main() {
unsigned int i=5, r=0;
r = f(i);
printf("i = %d, f(i) = %d\n", i, r);
}
汇编代码如下所示:
#include <stdio.h>
unsigned int f(unsigned int i) {
1149: f3 0f 1e fa endbr64
114d: 55 push rbp
114e: 48 89 e5 mov rbp,rsp
1151: 48 83 ec 10 sub rsp,0x10
1155: 89 7d fc mov DWORD PTR [rbp-0x4],edi
if (i>1) {
1158: 83 7d fc 01 cmp DWORD PTR [rbp-0x4],0x1
115c: 76 13 jbe 1171 <f+0x28>
return i * f(i-1);
115e: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
1161: 83 e8 01 sub eax,0x1
1164: 89 c7 mov edi,eax
1166: e8 de ff ff ff call 1149 <f>
116b: 0f af 45 fc imul eax,DWORD PTR [rbp-0x4]
116f: eb 05 jmp 1176 <f+0x2d>
} else {
return 1;
1171: b8 01 00 00 00 mov eax,0x1
}
}
1176: c9 leave
1177: c3 ret
int main() {
1178: f3 0f 1e fa endbr64
117c: 55 push rbp
117d: 48 89 e5 mov rbp,rsp
1180: 48 83 ec 10 sub rsp,0x10
unsigned int i=5, r=0;
1184: c7 45 f8 05 00 00 00 mov DWORD PTR [rbp-0x8],0x5
118b: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
r = f(i);
1192: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
1195: 89 c7 mov edi,eax
1197: e8 ad ff ff ff call 1149 <f>
119c: 89 45 fc mov DWORD PTR [rbp-0x4],eax
研究您的环境的调用约定。许多架构的许多调用约定的概述:https://www.dyncall.org/docs/manual/manualse11.html
调用约定指定:
其中参数和 return 值必须出现在指令流控制从调用方到被调用方的单个转移点。对于传递的参数,该单点是在调用之后和被调用者的第一条指令之前(对于 return 值,在被调用者完成的点和调用者恢复执行之前)。
许多约定将传入 CPU 寄存器的参数与堆栈内存相结合,以处理不适合 CPU 寄存器的参数。甚至一些不使用 CPU 寄存器作为参数的一些仍然使用 CPU 寄存器作为 return 值。
函数允许破坏的寄存器与必须保留的寄存器。 Call-clobbered 寄存器可以被赋予新值而无需担心 Call-preserved 寄存器可以使用,但必须恢复到它们在 return 调用方之前输入时的值。 call-preserved 寄存器的优点在于,由于它们在一次调用中得以保留,因此您可以将它们用于需要在另一次调用中存活的变量。
堆栈指针的含义和处理方式,关于当前指针下方和上方的内存,以及堆栈分配的对齐要求。
如果函数以某种方式分配堆栈 space,那么内存参数看起来会远离堆栈顶部(当然,它们实际上并没有移动,但会变得更大当前堆栈或帧指针)。编译器知道这一点并相应地调整他们对堆栈内存的访问。
一些编译器设置帧指针来引用堆栈内存。帧指针是在序言中某个时刻创建的堆栈指针的副本。帧指针并不总是必需的,但有助于异常处理和堆栈展开,以及动态堆栈分配。
Soo 我正在尝试了解 Assembler 如何处理堆栈帧等。 我做了一些练习,并使用英特尔符号用 GDB 反汇编了一些 C 代码。现在的任务是找出变量 'r' 是如何通过在 'main' 和 'f' 函数之间传递参数来计算的。我刚开始学习,有点迷失在这个例子的实际作用上。关于从哪里开始的任何想法或提示?
这是一个与教员合作的递归程序:
#include <stdio.h>
unsigned int f(unsigned int i) {
if (i>1) {
return i * f(i-1);
} else {
return 1;
}
}
int main() {
unsigned int i=5, r=0;
r = f(i);
printf("i = %d, f(i) = %d\n", i, r);
}
汇编代码如下所示:
#include <stdio.h>
unsigned int f(unsigned int i) {
1149: f3 0f 1e fa endbr64
114d: 55 push rbp
114e: 48 89 e5 mov rbp,rsp
1151: 48 83 ec 10 sub rsp,0x10
1155: 89 7d fc mov DWORD PTR [rbp-0x4],edi
if (i>1) {
1158: 83 7d fc 01 cmp DWORD PTR [rbp-0x4],0x1
115c: 76 13 jbe 1171 <f+0x28>
return i * f(i-1);
115e: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
1161: 83 e8 01 sub eax,0x1
1164: 89 c7 mov edi,eax
1166: e8 de ff ff ff call 1149 <f>
116b: 0f af 45 fc imul eax,DWORD PTR [rbp-0x4]
116f: eb 05 jmp 1176 <f+0x2d>
} else {
return 1;
1171: b8 01 00 00 00 mov eax,0x1
}
}
1176: c9 leave
1177: c3 ret
int main() {
1178: f3 0f 1e fa endbr64
117c: 55 push rbp
117d: 48 89 e5 mov rbp,rsp
1180: 48 83 ec 10 sub rsp,0x10
unsigned int i=5, r=0;
1184: c7 45 f8 05 00 00 00 mov DWORD PTR [rbp-0x8],0x5
118b: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
r = f(i);
1192: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
1195: 89 c7 mov edi,eax
1197: e8 ad ff ff ff call 1149 <f>
119c: 89 45 fc mov DWORD PTR [rbp-0x4],eax
研究您的环境的调用约定。许多架构的许多调用约定的概述:https://www.dyncall.org/docs/manual/manualse11.html
调用约定指定:
其中参数和 return 值必须出现在指令流控制从调用方到被调用方的单个转移点。对于传递的参数,该单点是在调用之后和被调用者的第一条指令之前(对于 return 值,在被调用者完成的点和调用者恢复执行之前)。
许多约定将传入 CPU 寄存器的参数与堆栈内存相结合,以处理不适合 CPU 寄存器的参数。甚至一些不使用 CPU 寄存器作为参数的一些仍然使用 CPU 寄存器作为 return 值。
函数允许破坏的寄存器与必须保留的寄存器。 Call-clobbered 寄存器可以被赋予新值而无需担心 Call-preserved 寄存器可以使用,但必须恢复到它们在 return 调用方之前输入时的值。 call-preserved 寄存器的优点在于,由于它们在一次调用中得以保留,因此您可以将它们用于需要在另一次调用中存活的变量。
堆栈指针的含义和处理方式,关于当前指针下方和上方的内存,以及堆栈分配的对齐要求。
如果函数以某种方式分配堆栈 space,那么内存参数看起来会远离堆栈顶部(当然,它们实际上并没有移动,但会变得更大当前堆栈或帧指针)。编译器知道这一点并相应地调整他们对堆栈内存的访问。
一些编译器设置帧指针来引用堆栈内存。帧指针是在序言中某个时刻创建的堆栈指针的副本。帧指针并不总是必需的,但有助于异常处理和堆栈展开,以及动态堆栈分配。