如何判断系统调用是否返回错误?基于ptrace模拟strace的疑问
如何用ptrace判断Linux系统调用的错误返回及错误类型
核心问题解析
Linux内核的系统调用错误返回机制和GLIBC封装的syscall()函数逻辑一致,但不能直接用返回值和-1对比——内核会把错误码以负数偏移的方式存在返回值里,而非直接返回-1。
一、判断系统调用是否出错
Linux内核规定:当系统调用出错时,返回值(rax寄存器的值)会落在[-4096, -1]这个区间里(对应无符号数的0xFFFFFFFFFFFFF000到0xFFFFFFFFFFFFFFFF)。这就是你找到的GLIBC内部宏INTERNAL_SYSCALL_ERROR_P的核心逻辑,这个规则是x86_64 Linux的通用内核行为,不是GLIBC专属,你可以直接复用:
#define SYSCALL_ERROR_P(val) ((unsigned long)(val) > -4096UL)
注:x32架构的判断逻辑是>= -4095LL,但x86_64下用上面的宏足够覆盖所有场景。
二、获取具体错误类型
当通过上面的宏判断为错误返回时,直接对rax的值取反(-rax)就能得到对应的errno错误码,再通过strerror()函数或者错误常量(如EBADF)即可转换成可读的错误信息,和strace的输出格式对应。
三、代码实现片段
结合ptrace获取寄存器的逻辑,核心处理部分如下:
#include <sys/ptrace.h> #include <sys/user.h> #include <sys/wait.h> #include <stdio.h> #include <string.h> #include <errno.h> #define SYSCALL_ERROR_P(val) ((unsigned long)(val) > -4096UL) // 示例:处理系统调用返回值 void handle_syscall_ret(struct user_regs_struct *regs) { unsigned long ret_val = regs->rax; if (SYSCALL_ERROR_P(ret_val)) { int err_num = -ret_val; // 模拟strace的输出格式:= -1 EBADF (Bad file descriptor) printf("= -1 %s (%s)\n", strerror(err_num), strerror(err_num)); } else { printf("= %ld\n", ret_val); } } int main() { pid_t child = fork(); if (child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execvp("./exit", (char*[]){"exit", NULL}); } else { int status; int in_syscall = 0; wait(&status); while (WIFSTOPPED(status)) { struct user_regs_struct regs; ptrace(PTRACE_GETREGS, child, NULL, ®s); // 区分系统调用入口和出口:第一次停在入口,第二次停在出口 if (in_syscall) { handle_syscall_ret(®s); // 特殊处理不会返回的系统调用(如exit、execve) long syscall_num = regs.orig_rax; if (syscall_num == 60 /* exit */ || syscall_num == 59 /* execve */) { printf("= ?\n"); } } in_syscall = !in_syscall; ptrace(PTRACE_SYSCALL, child, NULL, NULL); wait(&status); } printf("+++ exited with %d +++\n", WEXITSTATUS(status)); } return 0; }
四、关键注意事项
- 区分系统调用的入口/出口:
PTRACE_SYSCALL会在系统调用的进入和退出两个节点触发停止,需要用状态标记(如in_syscall变量)只在出口时处理返回值。 - 特殊系统调用处理:像
exit、execve这类调用成功后不会返回用户态,需要单独判断并输出= ?,和strace的行为对齐。
内容的提问来源于stack exchange,提问作者Clement-Z4RM
相关产品推荐
相关产品推荐

