Linux下常用的函数调用栈规范

本文深入解析了函数调用栈规范中的C规范、FastCall与Pascal规范,详细解释了参数传递、返回值及栈清理的过程。以printf函数为例,展示了实际的汇编代码,帮助开发者理解栈操作背后的细节。

我们都应该知道,高级语言的函数调用过程中,有“栈”这么一个概念,被调用函数的局部变量是存放在栈中的,函数调用的参数也是通过栈传递的。那么,调用函数是怎么把各种数据压入栈中,被调用函数又是怎么对栈进行操作以获取必要的数据呢?函数调用发生完毕之后,谁又负责清理这个栈?这就用到了函数调用栈规范

函数调用栈规范是指编译器的一中“约定”,他规定了调用者如何传递参数,被调用者如何获取参数,调用完成后怎么清理栈,怎么传递返回值等。编译器在编译程序的时候遵循这种规范,从而使程序正确的执行。对于不同的编译器,不同的高级语言,这种规范是不尽相同的。

在X86平台下的Linux内核中,常用的函数调用规范有:C,FastCall,Pascal等,下面简单介绍下这几种规范:

1、C规范

规定调用者将参数从右至左压栈,返回值通过EAX寄存器传递,如果返回值超过32位,则使用EDX:EAX传递,最后由调用者负责清理栈。这个规范被大多数C编译器遵守。

我们需要注意的是,由调用者负责清理堆栈,可以支持变参函数,比如我们所熟悉的printf函数。让我们看个例子:

printf("%d, %d, %d\n", i, j, k);

这个语句的目的是调用一个printf函数。被调用的函数在编译的时候并不知道自己将要被传递多少个函数,但是调用者知道。这个语句返汇编后大概的样子如下:

pushl   $k  //伪汇编,将k入栈
pushl   $j
pushl   $i
push    $addr //addr为第一个参数的语句的地址
call     printf
addl    $0x10, %esp

或者翻译成:

sub    $0x10, %esp //先申请栈空间
mov   $addr, (%esp)
mov   $i, 0x4(%esp)
mov   $j, 0x8(%esp)
mov   $k, 0xc(%esp)
call    printf
addl   $0x10, %esp

实际上,第二种翻译和第一种完成的功能基本一致,都是越右边的参数越靠近栈底,因为栈是从高地址往低地址增长的,所以地址也就越高。printf函数被调用后,先读栈顶的参数,也就是"%d, %d, %d\n",然后根据这个参数确定其他参数的个数,并向高地址寻找即可。

从上面的汇编代码可以看出,

addl   $0x10, %esp

这条指令是负责清理栈的,他位于调用者种,他知道自己应该清理多大的栈。而在被调用者中,往往通过

ret   $n
这条指令来清理栈的,因为printf在编译时并不知道自己会被传递多少个参数,因此这个n也就不能确定,所以没办法在被调用者中清理栈。

2、FastCall

顾名思义,fastcall就是快速调用的意思。因为压栈操作必须要访存,对于一些经常被调用的参数简单的小函数来说会有一定的开销,因此可以通过寄存器传参。以gcc为例,gcc使用fastcall的时候,默认从左到右的前两个参数通过ECX和EDX两个寄存器来传递,其他参数使用栈传递,但是可以通过__attribute__((regparm(n)))来控制可使用寄存器的数目,例如regparm(3)就表示前三个参数使用寄存器来传递,默认寄存器是EAX,ECX,EDX。返回值传递和栈清理同C规范相同。

这种规范应该是对C规范的一种补充和优化,在linux内核中经常用到,比如系统调用。

3、Pascal

参数从左到右入栈,被调用者负责清理栈,返回值通过EAX或EDX:EAX传递。

<think>我们正在讨论的是strace是否能够获取系统调用的调用信息。根据用户的问题,他之前已经询问了strace是否可以抓取进程下所有线程的系统调用,现在他想知道strace是否支持获取系统调用的调用信息。回顾引用: 引用[1]提到strace可以查看程序调用了哪些系统调用API,包括系统调用的参数和返回值。 引用[3]提到strace被设计用来跟踪进程执行时产生的系统调用及参数、信号传递、系统调用返回结果等信息。引用[4]详细说明了strace如何通过ptrace获取系统调用号,然后通过系统调用号找到对应的sysent结构体,从而打印系统调用的信息。但是,从这些引用中并没有提到strace可以获取系统调用的调用(即函数调用)。实际上,strace的主要功能是跟踪系统调用和信号,它并不提供系统调用的调用(用户空间的函数调用)信息。然而,我们可以考虑strace的-k选项(在较新版本中支持),该选项可以打印每个系统调用的堆跟踪(即内核?或者用户?)。实际上,strace的-k选项用于显示每个系统调用发生时的用户空间调用(即用户空间的函数调用链)。但需要注意的是,这个功能需要strace在编译时启用了堆跟踪支持(通常是通过libunwind库),并且可能会对性能产生较大影响。 因此,我们可以这样回答: strace在较新的版本中(从4.11版本开始)支持使用`-k`选项来打印系统调用发生时的用户空间调用。但是,这个功能默认可能不开启,需要strace在编译时启用了堆跟踪支持(依赖于libunwind库)。 使用方法: ```bashstrace-k./your_program```或者结合其他选项,例如: ```bashstrace-f-k-ooutput.txt ./your_program``` 注意:1. `-k`选项会显示每个系统调用发生时的调用(用户空间函数调用链)。2.由于获取调用信息需要额外的开销,因此使用`-k`选项会显著降低程序的运行速度。3.输出中,每个系统调用之后会显示调用,通常以`---`开始,然后是一系列函数地址和符号(如果程序有调试信息,则符号名会被解析出来)。 示例输出片段: ```openat(AT_FDCWD,"file.txt",O_RDONLY)=3--- backtrace:---0:openat1: main2:__libc_start_main3: _start```如果程序没有调试信息,则可能只能看到地址。另外,我们还可以提到其他工具,比如使用gdb在系统调用处设置断点并打印调用,或者使用perf工具记录调用。但是,根据引用[4]中strace的实现原理,它主要是通过ptrace来捕获系统调用事件,然后通过读取进程的寄存器来获取系统调用号,再根据系统调用号打印对应的系统调用名。而获取调用并不是strace的默认功能,需要额外的支持。 因此,总结回答:strace支持通过`-k`选项获取系统调用的调用(用户空间),但需要版本支持且编译时启用了堆跟踪功能。 同时,我们也可以生成相关问题:§§相关问题§§1.如何安装支持堆跟踪的strace版本?2.除了strace,还有哪些工具可以获取系统调用的调用?3.如何解读strace-k输出的调用信息? 4.使用strace -k选项对性能有多大影响?</think>### 是的,strace 支持获取系统调用的调用信息,但需要特定选项 strace **默认不显示调用**,但可以通过 `-k` 或 `--stack-trace` 选项启用此功能。以下是具体实现方式和使用注意事项: --- ### 🔧 获取调用的方法 1. **使用 `-k` 选项**(推荐): ```bash strace -k ./your_program ``` 每个系统调用后会显示完整的用户空间调用函数调用链) 2. **结合线程跟踪**: ```bash strace -f -k -p <PID> # 跟踪进程所有线程的调用 ``` 3. **示例输出**: ``` openat(AT_FDCWD, "test.txt", O_RDONLY) = 3 --- backtrace: --- /lib/x86_64-linux-gnu/libc.so.6(open64+0x5b) [0x7f8e5a3b45bb] ./your_program(main+0x2a) [0x401152] ./your_program(_start+0x2a) [0x40107a] --- ``` 显示从 `_start` → `main` → `open64` 的完整调用路径 --- ### ⚠️ 实现原理与限制 1. **依赖调试信息**: - 需要目标程序编译时包含调试信息(`gcc -g`) - 否则只能显示函数地址而非符号名[^4] 2. **内核级捕获机制**: ```mermaid graph LR A[系统调用入口] --> B[ptrace捕获] B --> C[读取寄存器状态] C --> D[解析内存映射] D --> E[回溯调用] ``` strace 通过 `ptrace_getregset` 获取寄存器值,结合进程内存映射解析调用链[^4] 3. **性能影响**: - 获取调用会使程序运行速度降低 100-1000 倍 - 生产环境慎用,建议在测试环境使用 --- ### 🛠️ 增强调试技巧 1. **符号解析优化**: ```bash strace -k -y ./your_program # 显示文件描述符对应文件名 ``` 2. **过滤特定调用**: ```bash strace -k -e open,read ./your_program # 只跟踪文件操作调用 ``` 3. **时间戳分析**: ```bash strace -k -ttt ./your_program # 显示微秒级时间戳 ``` --- ### 🔄 替代方案 当需要更详细的调用分析时,可结合其他工具: 1. **GDB 调试器**: ```bash gdb -ex "catch syscall open" -ex "command 1 bt" ./your_program ``` 在特定系统调用触发时打印调用 2. **perf 工具**: ```bash perf trace --call-graph dwarf ./your_program ``` 低开销采集系统调用链 3. **BPF 工具集**: ```bash sudo bpftrace -e 'tracepoint:syscalls:sys_enter_open* { @[ustack] = count(); }' ``` 统计所有 `open` 系统调用的调用频率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值