汇编基础-----通过x64dbg了解什么是堆栈
什么是堆栈
在汇编语言中,堆栈(stack)是一种用于存储临时数据和执行函数调用的内存结构。堆栈是一种后进先出(Last-In-First-Out, LIFO)的数据结构,通常用于保存函数调用的返回地址、局部变量和参数等。
以下是堆栈的一些重要概念和操作:
- 堆栈指针(Stack Pointer): 堆栈指针是一个特殊的寄存器,用于指向堆栈顶部的位置。堆栈指针在不同的体系结构中可能具有不同的名称,例如在x86架构中,堆栈指针常用
ESP
或RSP
表示。 - 压栈(Push): 将数据压入堆栈的操作称为压栈。压栈操作会先将堆栈指针减小,然后将数据写入指针所指向的位置。
- 出栈(Pop): 从堆栈中取出数据的操作称为出栈。出栈操作会先将堆栈指针增加,然后将指针所指向的数据读取出来。
- 堆栈帧(Stack Frame): 函数在执行时会创建一个堆栈帧,用于存储函数调用时需要的信息,如返回地址、参数和局部变量等。当函数调用结束时,堆栈帧会被销毁。
- 调用约定(Calling Convention): 调用约定定义了函数调用时参数传递、返回值处理和堆栈管理等规则。常见的调用约定包括stdcall、cdecl和fastcall等。
- 堆栈溢出(Stack Overflow): 当压入堆栈的数据超过了堆栈的容量时,会发生堆栈溢出,导致程序崩溃或者安全漏洞。
- 堆栈保护(Stack Guard): 一些编译器和操作系统提供堆栈保护机制,用于检测和防止堆栈溢出攻击。
- 递归调用(Recursive Call): 函数可以直接或间接地调用自身,这种调用方式称为递归调用。递归函数使用堆栈来保存每一次调用的状态。
- 栈帧布局(Stack Frame Layout): 每个函数在堆栈上创建的堆栈帧具有特定的布局,包括参数、局部变量和返回地址等。栈帧布局在不同的编程语言和编译器中可能有所不同。
- 堆栈指令(Stack Instructions): 汇编语言提供了一些用于操作堆栈的特定指令,如PUSH、POP、CALL和RET等。
理解堆栈在汇编语言中的作用和原理对于理解函数调用、内存管理和程序执行流程非常重要。通过合理地使用堆栈,可以实现高效的函数调用和数据传递,同时避免堆栈溢出等问题。
x64dbg
布局基本了解
了解RBP和RSP是什么?
RBP:基址指针寄存器。通常用于建立堆栈帧。也就是我们所说的栈底.
RSP:堆栈指针寄存器。指向当前栈顶,用于管理函数调用和局部变量。
x64调用约定
x86应用程序的函数调用有stdcall、_cdecl、Fastcall等方式,但x64应用程序只有1种寄存器快速调用约定。前4个参数使用寄存器传递,如果参数超过4个,多余的参数就放在栈里,人栈顺序为从右到左,由函数调用方平衡栈空间。前4个参数存放的寄存器是固定的,分别是第1个参数RCX、第2个参数 RDX、第3个参数R8、第4个参数R9,其他参数从右往左依次入栈。任何大于8字节或者不是1字节、2字节、4字节、8字节的参数必须由引用来传递(地址传递)。所有浮点参数的传递都是使用XMM寄存器完成的,它们在XMMO、XMM1、XMM2和 XMM3中传递
函数的前4个参数虽然使用寄存器来传递,但是栈仍然为这4个参数预留了空间( 32字节),为方便描述,这里称之为预留栈空间。在x64环境里,前4个参数使用寄存器传递,因此在函数内部这4个寄存器就不能使用了
,相当于函数少了4个可用的通用寄存器。当函数功能比较复杂时,这可能导致寄存器不够用。为了避免这个问题,可以使用预留栈空间,方法是函数调用者多申请32字节的栈空间,当函数寄存器不够用时,可以把寄存器的值保存到刚才申请的栈空间中。
预留栈空间由函数调用者提前申请。由函数调用者负责平衡栈空间。