C++内存模型以及寄存器指针rsp和rbp

汇编及C/C++汇编调用约定讲解


专栏目录(文章在更新中)

> 汇编及C/C++汇编调用约定(汇总帖)
> 汇编编译和gdb调试命令列表
> gdb TUI使用方法
> 汇编C语言调用约定(标准函数)
> 汇编C语言调用约定(递归函数)
> C++内存模型以及寄存器指针rsp和rbp



学C++的时候跨过来的, 这个也磕磕绊绊拖了好长时间, 上手比较费劲, 大概整理了一下用到的东西.

1. 先放一张内存模型的图

在这里插入图片描述

2. gcc 命令参数

  1. 控制何时停止从C语言文件产生可执行文件的过程
    • -E Preprocess only; do not compile, assemble or link.
    • -S Compile only; do not assemble or link.
    • -c Compile and assemble, but do not link.
    • -o <file> Place the output into file.
  2. 优化级别
    • -O0: 不做任何优化,默认的编译选项。
    • -O1:优化会消耗更多的编译时间,它主要对代码的分支,常量以及表达式等进行优化。
    • -O2:会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。
    • -O3: 在O2的基础上进行更多的优化。
    • -Os:相当于-O2.5。是使用了所有-O2的优化选项,但又不缩减代码尺寸的方法。

3. 示例代码

	int fn(int a, int b){
		int rsp_move = 4;
		int c = a + b;
		return c;
	}

	int main() {
		int a = 1;
		int b = 2;
		int c = fn(a, b);
		return 0;
	}

4. 生成

  1. gcc t.c -o t.s -m32 -S -O0
    在此从gcc得到未经编译优化的汇编文件
    汇编代码(为了简洁删去了一些行):
    	.file	"t.c"
    	.text
    	.globl	fn
    	.type	fn, @function
    fn:
    	endbr32
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$16, %esp
    	movl	$4, -8(%ebp)
    	movl	8(%ebp), %edx
    	movl	12(%ebp), %eax
    	addl	%edx, %eax
    	movl	%eax, -4(%ebp)
    	movl	-4(%ebp), %eax
    	leave
    	ret
    
    	.globl	main
    	.type	main, @function
    main:
    	endbr32
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$16, %esp
    	movl	$1, -12(%ebp)
    	movl	$2, -8(%ebp)
    	pushl	-8(%ebp)
    	pushl	-12(%ebp)
    	call	fn
    	addl	$8, %esp
    	movl	%eax, -4(%ebp)
    	movl	$0, %eax
    	leave
    	ret
    
  2. 因为这是依赖于gcc的汇编代码,所以这里对其进行一些修改,让其可以通过汇编器编译
    函数结束时,通常在发出ret指令前通过以下指令清理栈:
    movl %ebp, %esp
    popl %ebp
    # ret
    
    但是,gcc输出只包括指令leave,这条指令只是上面两条指令的组合
  .section .data
  .section .text
  .globl _start	

_start:
  pushl $2
  pushl $1
  call fn
  movl %eax, %ebx
  movl $1, %eax
  int  $0x80

  .type	fn, @function
fn:
  pushl	%ebp
  movl	%esp, %ebp
  subl	$16, %esp
  movl	$4, -8(%ebp)
  movl	8(%ebp), %edx
  movl	12(%ebp), %eax
  addl	%edx, %eax
  movl	%eax, -4(%ebp)
  movl	-4(%ebp), %eax

  movl %ebp, %esp
  popl %ebp
  ret

  1. as t.s -o t.o --32 --gstabs
  2. ld t.o -o t -m elf_i386

5. gdb 调试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6. 进入函数时 rsp 指针的偏移

  1. 在此之前先提一下push和mov的区别:
    • push指令可以分解为两条更基本的汇编指令:
      sub $指针移动长度 %rsp # 栈指下移
      mov 源数据 (%rsp) # 推入数据至栈顶
      使用push时,栈指针同时会移动到此入栈数据的地址上,数据不会被覆盖掉
    • 当使用mov将数据移入栈中时,如果栈指针在此地址之上,那么这个数据是无意义的,随时可能被覆盖
  2. 栈指针的偏移并不一定是局部变量,例如在进行文件读取时,提前为读取出的两个文件描述符预留了位置
    .equ ST_SIZE_RESERVE, 8
    .equ ST_FD_IN, -4
    .equ ST_FD_OUT, -8
    
    之后使用mov就可以保存这两个文件描述符
    # 保存返回的文件描述符
    movl %eax, ST_FD_IN(%ebp)
    movl %eax, ST_FD_OUT(%ebp)
    

# 后边还没改完

3. main函数中 rsp 指针的偏移

  1. 在 AT&T 汇编风格中sub $0x10, %rsp表示 rsp 寄存器指针向下偏移 16
    在这里插入图片描述
  2. 通过x /12x $rsp 查看和访问寄存器 rsp 及 rsp 之后的变量(x 命令使用方法见下图)
    在这里插入图片描述
  3. 换十进制输出内存中的数据, 可见是a 和 b, 所以 rsp 指针偏移给局部变量留出了空间(开头那个图)
    在这里插入图片描述
4. 进入 fn() 函数中
  1. 再进入 rsp_m() 函数中, 观察寄存器监视器可见此时 rsp 和 sbp 地址相同, 这是因为 rsp_m() 函数中没有局部变量, 所以 rsp 指针不需要向下偏移来为变量留出空间
    在这里插入图片描述
此后内容与在 main() 函数中的讲述基本相同
  1. 返回到fn()函数查看此时寄存器 rsp 和 rbp
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    可见函数 rbp 后的地址的内容是<main() + 40>, 即需要返回的地址, rsp 后也是变量的地址(开头那个图)
### 关于 `sub rsp, 0E0h` 指令的作用 在 x86_64 架构中,栈指针寄存器 RSP 负责指向当前栈顶位置[^3]。当执行 `sub rsp, 0E0h` 指令时,这条指令会将 RSP 的值减去十六进制数值 E0 (即十进制的 224),从而调整栈指针的位置。 #### 使用场景分析 此操作常见于函数前导部分(prologue),用于为局部变量分配空间或创建所谓的“阴影空间”。具体来说: - **局部变量存储**:如果一个函数需要大量局部变量,则可以通过一次性减少 RSP 值来预留足够的连续内存区域供这些变量使用。 - **调用约定遵循**:某些平台上的 ABI(应用程序二进制接口)规定,在每次进入新一层子程序之前都应留出固定大小的空间作为参数传递缓冲区或其他用途;这被称为“阴影空间”。 ```assembly ; 函数入口处设置堆栈帧并保留额外空间给后续可能发生的函数调用 push rbp ; 保存旧基底指针 mov rbp, rsp ; 设置新的基底指针等于现在的rsp sub rsp, 0E0h ; 预留至少128字节以上的影子空间以及任何必要的本地变量所需空间 ... call some_function ; 可能在此之后发生其他函数调用 add rsp, 0E0h ; 清理之前开辟出来的临时空间 pop rbp ; 恢复原来的rbp ret ; 返回到上层调用者 ``` 上述代码片段展示了如何在一个典型的 C/C++ 编译后的目标文件里处理这类情况的方式之一。通过预先分配好足够多的空间可以简化实际运行期间动态调整的需求,并有助于提高性能安全性。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值