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

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


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

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


文章目录

  • 汇编及C/C++汇编调用约定讲解
      • 专栏目录(文章在更新中)
      • 1. 先放一张内存模型的图
      • 2. gcc 命令参数
      • 3. 示例代码
      • 4. 生成
      • 5. gdb 调试
      • 6. 进入函数时 rsp 指针的偏移
      • 3. main函数中 rsp 指针的偏移
        • 4. 进入 fn() 函数中
        • 此后内容与在 main() 函数中的讲述基本相同


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

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

C++内存模型以及寄存器指针rsp和rbp_第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 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 调试

C++内存模型以及寄存器指针rsp和rbp_第2张图片
C++内存模型以及寄存器指针rsp和rbp_第3张图片
C++内存模型以及寄存器指针rsp和rbp_第4张图片

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
    C++内存模型以及寄存器指针rsp和rbp_第5张图片
  2. 通过x /12x $rsp 查看和访问寄存器 rsp 及 rsp 之后的变量(x 命令使用方法见下图)
    C++内存模型以及寄存器指针rsp和rbp_第6张图片
  3. 换十进制输出内存中的数据, 可见是a 和 b, 所以 rsp 指针偏移给局部变量留出了空间(开头那个图)
    在这里插入图片描述
4. 进入 fn() 函数中
  1. 再进入 rsp_m() 函数中, 观察寄存器监视器可见此时 rsp 和 sbp 地址相同, 这是因为 rsp_m() 函数中没有局部变量, 所以 rsp 指针不需要向下偏移来为变量留出空间
    C++内存模型以及寄存器指针rsp和rbp_第7张图片
此后内容与在 main() 函数中的讲述基本相同
  1. 返回到fn()函数查看此时寄存器 rsp 和 rbp
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    可见函数 rbp 后的地址的内容是, 即需要返回的地址, rsp 后也是变量的地址(开头那个图)

你可能感兴趣的:(汇编,反汇编,内存结构,指针,linux,c++)