汇编及C/C++汇编调用约定讲解
专栏目录(文章在更新中)
> 汇编及C/C++汇编调用约定(汇总帖)
> 汇编编译和gdb调试命令列表
> gdb TUI使用方法
> 汇编C语言调用约定(标准函数)
> 汇编C语言调用约定(递归函数)
> C++内存模型以及寄存器指针rsp和rbp
文章目录
学C++的时候跨过来的, 这个也磕磕绊绊拖了好长时间, 上手比较费劲, 大概整理了一下用到的东西.
1. 先放一张内存模型的图
2. gcc 命令参数
- 控制何时停止从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.
- 优化级别
-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. 生成
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
- 因为这是依赖于gcc的汇编代码,所以这里对其进行一些修改,让其可以通过汇编器编译
函数结束时,通常在发出ret指令前通过以下指令清理栈:
但是,gcc输出只包括指令leave,这条指令只是上面两条指令的组合movl %ebp, %esp popl %ebp # ret
.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
as t.s -o t.o --32 --gstabs
ld t.o -o t -m elf_i386
5. gdb 调试
6. 进入函数时 rsp 指针的偏移
- 在此之前先提一下push和mov的区别:
- push指令可以分解为两条更基本的汇编指令:
sub $指针移动长度 %rsp
# 栈指下移
mov 源数据 (%rsp)
# 推入数据至栈顶
使用push时,栈指针同时会移动到此入栈数据的地址上,数据不会被覆盖掉 - 当使用mov将数据移入栈中时,如果栈指针在此地址之上,那么这个数据是无意义的,随时可能被覆盖
- push指令可以分解为两条更基本的汇编指令:
- 栈指针的偏移并不一定是局部变量,例如在进行文件读取时,提前为读取出的两个文件描述符预留了位置
之后使用mov就可以保存这两个文件描述符.equ ST_SIZE_RESERVE, 8 .equ ST_FD_IN, -4 .equ ST_FD_OUT, -8
# 保存返回的文件描述符 movl %eax, ST_FD_IN(%ebp) movl %eax, ST_FD_OUT(%ebp)
# 后边还没改完
3. main函数中 rsp 指针的偏移
- 在 AT&T 汇编风格中
sub $0x10, %rsp
表示 rsp 寄存器指针向下偏移 16
- 通过x /12x $rsp 查看和访问寄存器 rsp 及 rsp 之后的变量(x 命令使用方法见下图)
- 换十进制输出内存中的数据, 可见是a 和 b, 所以 rsp 指针偏移给局部变量留出了空间(开头那个图)
4. 进入 fn() 函数中
- 再进入 rsp_m() 函数中, 观察寄存器监视器可见此时 rsp 和 sbp 地址相同, 这是因为 rsp_m() 函数中没有局部变量, 所以 rsp 指针不需要向下偏移来为变量留出空间
此后内容与在 main() 函数中的讲述基本相同
- 返回到fn()函数查看此时寄存器 rsp 和 rbp
可见函数 rbp 后的地址的内容是<main() + 40>, 即需要返回的地址, rsp 后也是变量的地址(开头那个图)