Linux 汇编-函数调用
GNU 汇编函数
定义函数
- 格式
.type func_name, @function func_name: #content ret
- 注解
.type 指令指定 func_name 为汇编程序调用此函数的地址
fun_name 为函数名
@function 表示函数内容开始
ret 表示函数结束,返回到父函数调用子函数处
调用函数
- 格式
call fun_name
- 注解
call 是调用子函数的汇编命令
fun_name 是定义子函数时,.type 指定的函数地址
编写汇编函数
使用寄存器和全局变量传递函数参数
即在主函数中先将数据保存到某寄存器,然后在函数中直接使用这个寄存器的指。
# ASM function use register give the inut data .section .data str:.ascii "ASM THIRD DAY\n" sl=.-str .section .text .global _start _start: movl $0, %eax # Init eax movl $2, %ebx # Prepare data in ebx first call add_fun # Then call child function # System call: write() movl $1, %ebx movl $str, %ecx movl $sl, %edx int $0x80 movl $1, %eax # System call: exit movl $0, %ebx int $0x80 # The method of GNU define a function .type add_fun, @function add_func: add %ebx, %ebx movl %ebx, %eax ret
root@root:~# as fun_register.s -o fun.o root@root:~# ld fun.o -o fun root@root:~# .fun
- 注解
主程序中,movl $2, %ebx 将常量 2 赋值给寄存器 ebx, 为子函数 add_func 准备好数据。
接下来调用子函数,使用 add 指令对寄存器 ebx 数据进行处理。
通过全局变量给子函数传递数据,只需在数据段(.data 或者 .bss)定义变量,在主函数中初始化后在调用子函数来使用变量。
在父函数调用子函数时,一定要符合子函数的需求:准备好要用到的寄存器和变量,准备好子函数输出结果的保存地址,同时主函数中也要直到函数输出保存的地址。
使用 C 风格传参方式
C 风格传参方式使用栈来给子函数准备数据,适合复杂的汇编程序
- 栈
只在栈顶发生操作,入栈的新栈顶,出栈移除当前栈顶元素,入栈 push,出栈 pop。
堆栈寄存器 esp 需要始终指向栈顶。
ebp 用于保存栈基址。
高地址内存叫栈底,低地址内存叫栈顶。
入栈后,esp 值减小,即指向更小的地址。
除使用 push 和 pop 指令外,还可操作栈地址来实现。通常将 esp 值拷贝到 ebp 来操作。
- 通过栈为函数准备数据
将子函数需要的数据存到栈里,需要保证存在栈里的数据与子函数使用的顺序相对应。
注意: 当调用子函数时,系统会将当前地址压入栈中,当子函数执行完之后返回之前地址,以便程序继续执行。
# Given data to child function by stack .section .data str:.ascii "ASM FOURTH DAY\n" sl=.-str .section .text .global _start _start: push $4 # Be ready data for child func by stack call give_value_to_eax movl $1, %ebx # System call: write() movl $str, %ecx movl $sl, %edx int $0x80 movl $1, %eax # System call exit movl $0, %ebx int $0x80 # Child func .type give_value_to_eax, @function give_value_to_eax: pushl %ebp # Save ebp value movl %esp, %ebp # Get stack top address for ebp movl 8(%ebp), %eax # eax's value is the bottom value of the stack pop %ebp # Get the old ebp value again ret
root@root:~# as fun_stack.s -o fun.o root@root:~# ld fun.o -o fun root@root:~# .fun
- 注解
主函数中,是两个系统调用代码。
在 write 系统调用代码中,eax 的值通过子函数 give_value_to_eax 来赋予。
在 give_value_to_eax 子函数中,使用在主函数中在栈中准备的数据来给 eax 赋值。因为压入栈中的还有当前调用函数的地址,因此使用 ebp 来访问原先压入栈中的数据。
子函数中,第一行为备份 ebp 值,第二行将栈顶的 esp 值赋给 ebp,第三行通过 ebp 找到主函数为子函数准备的数据,并赋给 eax。
使用 ebp 来获取 esp 值,然后通过 ebp 来访问栈中数据是为了保证 esp 一直指向栈顶。
Program Stack +-------------------------+ | | | | Indirect addresing |-------------------------| | Function parameter 3 | 16(%ebp) |-------------------------| | Function parameter 2 | 1(%ebp) |-------------------------| | Function parameter 1 | 8(%ebp) |-------------------------| | Return Address | 4(%ebp) |-------------------------| | Old EBP Value | (%ebp) <----- ESP +-------------------------+
函数末尾 pop %ebp 之后,栈顶就是 Return Address 了。
当要用栈来存储函数内的局部变量时,只需要将 esp 继续开辟新的栈顶增加即可:
Program Stack +-------------------------+ | | | | Indirect addresing |-------------------------| | Function parameter 3 | 16(%ebp) |-------------------------| | Function parameter 2 | 1(%ebp) |-------------------------| | Function parameter 1 | 8(%ebp) |-------------------------| | Return Address | 4(%ebp) |-------------------------| | Old EBP Value | (%ebp) |-------------------------| | Local Variable 1 | -4(%ebp) |-------------------------| | Local Variable 2 | -8(%ebp) |-------------------------| | Local Variable 3 | -12(%ebp) <---- ESP |-------------------------| | | |-------------------------| | | +-------------------------+