ARM64 Linux下syscall拦截替换导致程序崩溃问题排查
问题描述
尝试拦截程序中所有syscall,能正常接收调用请求,但启用替换实现后程序会崩溃,未启用时运行正常。目的是了解程序及其依赖调用的syscall,暂不想用ptrace等工具,需排查当前实现的问题。运行环境为ARM64 Linux。
拦截实现代码
long syscall(long number, ...){ printf("Intercepted syscall: %lu\n", number); va_list original_args; va_start(original_args, number); long (*original_syscall)(long number, ...) = NULL; original_syscall = (long (*)(long, ...))dlsym(RTLD_NEXT, "syscall"); long result = original_syscall(number, original_args); va_end(original_args); return result; }
崩溃日志
Intercepted syscall: 178 Intercepted syscall: 178 Intercepted syscall: 178 Intercepted syscall: 178 Intercepted syscall: 178 Intercepted syscall: 57 Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 3606 (Thread-2)
问题分析与修复
核心错误原因
直接将va_list类型变量传递给原始syscall函数是完全错误的。syscall是可变参数函数(声明为...),它期望接收栈上的原始参数序列,而非va_list指针。这种错误的参数传递会彻底破坏ARM64的寄存器/栈帧布局,导致后续调用(比如日志中触发崩溃的syscall 57——clone创建线程)时出现致命错误,最终触发SIGABRT。
修复方案
ARM64 Linux的syscall最多支持6个参数(通过x0-x5寄存器传递),我们可以从va_list中逐个取出参数后再调用原始syscall,同时优化dlsym的调用时机避免重复开销:
#define _GNU_SOURCE #include <stdio.h> #include <stdarg.h> #include <dlfcn.h> // 提前初始化原始syscall函数指针,避免每次调用重复dlsym static long (*original_syscall)(long, long, long, long, long, long, ...) = NULL; __attribute__((constructor)) void init_original_syscall() { original_syscall = (long (*)(long, long, long, long, long, long, ...))dlsym(RTLD_NEXT, "syscall"); } long syscall(long number, ...) { printf("Intercepted syscall: %lu\n", number); va_list args; va_start(args, number); // 按ARM64 syscall参数规则取出最多6个参数 long arg1 = va_arg(args, long); long arg2 = va_arg(args, long); long arg3 = va_arg(args, long); long arg4 = va_arg(args, long); long arg5 = va_arg(args, long); long arg6 = va_arg(args, long); // 传递展开后的参数调用原始syscall long result = original_syscall(number, arg1, arg2, arg3, arg4, arg5, arg6); va_end(args); return result; }
补充说明
日志中的syscall 57对应ARM64的clone系统调用,用于创建线程。线程创建对栈帧和参数的正确性要求极高,参数传递错误会直接导致线程初始化失败,触发SIGABRT信号。修复参数传递逻辑后,该问题即可解决。
内容的提问来源于stack exchange,提问作者Papers.ch
相关产品推荐
相关产品推荐

