Android NDK中实现多架构SYSCALL_INLINE式内联系统调用方案咨询
实现Android多架构直接系统调用(绕过libc包装函数)
我之前刚好做过类似需求——为了避开libc里系统调用包装函数被Hook的风险,直接通过汇编触发系统调用。不同CPU架构的系统调用约定差异很大,下面是我整理的适配arm、arm64、x86、x86_64的完整实现方案,你可以直接集成到NDK项目中:
1. 核心思路
每个架构都有专属的系统调用参数传递规则和触发指令,我们需要针对每个架构编写内联汇编代码:把系统调用号和参数传入对应寄存器,触发系统调用指令后,直接返回结果,完全绕开libc的包装函数。
2. 分架构实现
我们可以用预编译指令区分不同架构,把实现封装成一个通用的syscall函数,支持最多6个参数(覆盖绝大多数系统调用的需求)。
2.1 ARM 32位(armeabi-v7a)
ARM 32位系统调用约定:
- 前4个参数依次放入
r0、r1、r2、r3寄存器 - 系统调用号存入
r7寄存器 - 用
swi #0指令触发系统调用 - 返回值存在
r0中
#include <stdint.h> #include <stdarg.h> static inline long syscall(int num, ...) { va_list args; long ret; uint32_t arg1, arg2, arg3, arg4; va_start(args, num); arg1 = va_arg(args, uint32_t); arg2 = va_arg(args, uint32_t); arg3 = va_arg(args, uint32_t); arg4 = va_arg(args, uint32_t); va_end(args); __asm__ volatile ( "mov r7, %1\n" "mov r0, %2\n" "mov r1, %3\n" "mov r2, %4\n" "mov r3, %5\n" "swi #0\n" "mov %0, r0\n" : "=r"(ret) : "r"(num), "r"(arg1), "r"(arg2), "r"(arg3), "r"(arg4) : "r0", "r1", "r2", "r3", "r7", "memory" ); return ret; }
2.2 ARM 64位(arm64-v8a)
ARM 64位系统调用约定:
- 前6个参数依次放入
x0-x5寄存器 - 系统调用号存入
x8寄存器 - 用
svc #0指令触发系统调用 - 返回值存在
x0中
#include <stdint.h> #include <stdarg.h> static inline long syscall(int num, ...) { va_list args; long ret; uint64_t arg1, arg2, arg3, arg4, arg5, arg6; va_start(args, num); arg1 = va_arg(args, uint64_t); arg2 = va_arg(args, uint64_t); arg3 = va_arg(args, uint64_t); arg4 = va_arg(args, uint64_t); arg5 = va_arg(args, uint64_t); arg6 = va_arg(args, uint64_t); va_end(args); __asm__ volatile ( "mov x8, %1\n" "mov x0, %2\n" "mov x1, %3\n" "mov x2, %4\n" "mov x3, %5\n" "mov x4, %6\n" "mov x5, %7\n" "svc #0\n" "mov %0, x0\n" : "=r"(ret) : "r"(num), "r"(arg1), "r"(arg2), "r"(arg3), "r"(arg4), "r"(arg5), "r"(arg6) : "x0", "x1", "x2", "x3", "x4", "x5", "x8", "memory" ); return ret; }
2.3 x86架构(x86)
x86系统调用约定:
- 系统调用号存入
eax寄存器 - 前6个参数依次放入
ebx、ecx、edx、esi、edi、ebp寄存器 - 用
int 0x80指令触发系统调用 - 返回值存在
eax中
#include <stdint.h> #include <stdarg.h> static inline long syscall(int num, ...) { va_list args; long ret; uint32_t arg1, arg2, arg3, arg4, arg5, arg6; va_start(args, num); arg1 = va_arg(args, uint32_t); arg2 = va_arg(args, uint32_t); arg3 = va_arg(args, uint32_t); arg4 = va_arg(args, uint32_t); arg5 = va_arg(args, uint32_t); arg6 = va_arg(args, uint32_t); va_end(args); __asm__ volatile ( "mov eax, %1\n" "mov ebx, %2\n" "mov ecx, %3\n" "mov edx, %4\n" "mov esi, %5\n" "mov edi, %6\n" "mov ebp, %7\n" "int $0x80\n" "mov %0, eax\n" : "=r"(ret) : "r"(num), "r"(arg1), "r"(arg2), "r"(arg3), "r"(arg4), "r"(arg5), "r"(arg6) : "eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "memory" ); return ret; }
2.4 x86_64架构(x86_64)
x86_64系统调用约定:
- 系统调用号存入
rax寄存器 - 前6个参数依次放入
rdi、rsi、rdx、r10、r8、r9寄存器 - 用
syscall指令触发系统调用 - 返回值存在
rax中
#include <stdint.h> #include <stdarg.h> static inline long syscall(int num, ...) { va_list args; long ret; uint64_t arg1, arg2, arg3, arg4, arg5, arg6; va_start(args, num); arg1 = va_arg(args, uint64_t); arg2 = va_arg(args, uint64_t); arg3 = va_arg(args, uint64_t); arg4 = va_arg(args, uint64_t); arg5 = va_arg(args, uint64_t); arg6 = va_arg(args, uint64_t); va_end(args); __asm__ volatile ( "mov rax, %1\n" "mov rdi, %2\n" "mov rsi, %3\n" "mov rdx, %4\n" "mov r10, %5\n" "mov r8, %6\n" "mov r9, %7\n" "syscall\n" "mov %0, rax\n" : "=r"(ret) : "r"(num), "r"(arg1), "r"(arg2), "r"(arg3), "r"(arg4), "r"(arg5), "r"(arg6) : "rax", "rdi", "rsi", "rdx", "r10", "r8", "r9", "memory" ); return ret; }
3. 封装成通用头文件
把上面的代码用预编译指令整合到一个头文件中,NDK编译时会自动根据目标架构选择对应的实现:
#ifndef DIRECT_SYSCALL_H #define DIRECT_SYSCALL_H #include <stdarg.h> #include <stdint.h> #if defined(__arm__) static inline long syscall(int num, ...) { va_list args; long ret; uint32_t arg1, arg2, arg3, arg4; va_start(args, num); arg1 = va_arg(args, uint32_t); arg2 = va_arg(args, uint32_t); arg3 = va_arg(args, uint32_t); arg4 = va_arg(args, uint32_t); va_end(args); __asm__ volatile ( "mov r7, %1\n" "mov r0, %2\n" "mov r1, %3\n" "mov r2, %4\n" "mov r3, %5\n" "swi #0\n" "mov %0, r0\n" : "=r"(ret) : "r"(num), "r"(arg1), "r"(arg2), "r"(arg3), "r"(arg4) : "r0", "r1", "r2", "r3", "r7", "memory" ); return ret; } #elif defined(__aarch64__) static inline long syscall(int num, ...) { va_list args; long ret; uint64_t arg1, arg2, arg3, arg4, arg5, arg6; va_start(args, num); arg1 = va_arg(args, uint64_t); arg2 = va_arg(args, uint64_t); arg3 = va_arg(args, uint64_t); arg4 = va_arg(args, uint64_t); arg5 = va_arg(args, uint64_t); arg6 = va_arg(args, uint64_t); va_end(args); __asm__ volatile ( "mov x8, %1\n" "mov x0, %2\n" "mov x1, %3\n" "mov x2, %4\n" "mov x3, %5\n" "mov x4, %6\n" "mov x5, %7\n" "svc #0\n" "mov %0, x0\n" : "=r"(ret) : "r"(num), "r"(arg1), "r"(arg2), "r"(arg3), "r"(arg4), "r"(arg5), "r"(arg6) : "x0", "x1", "x2", "x3", "x4", "x5", "x8", "memory" ); return ret; } #elif defined(__i386__) static inline long syscall(int num, ...) { va_list args; long ret; uint32_t arg1, arg2, arg3, arg4, arg5, arg6; va_start(args, num); arg1 = va_arg(args, uint32_t); arg2 = va_arg(args, uint32_t); arg3 = va_arg(args, uint32_t); arg4 = va_arg(args, uint32_t); arg5 = va_arg(args, uint32_t); arg6 = va_arg(args, uint32_t); va_end(args); __asm__ volatile ( "mov eax, %1\n" "mov ebx, %2\n" "mov ecx, %3\n" "mov edx, %4\n" "mov esi, %5\n" "mov edi, %6\n" "mov ebp, %7\n" "int $0x80\n" "mov %0, eax\n" : "=r"(ret) : "r"(num), "r"(arg1), "r"(arg2), "r"(arg3), "r"(arg4), "r"(arg5), "r"(arg6) : "eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "memory" ); return ret; } #elif defined(__x86_64__) static inline long syscall(int num, ...) { va_list args; long ret; uint64_t arg1, arg2, arg3, arg4, arg5, arg6; va_start(args, num); arg1 = va_arg(args, uint64_t); arg2 = va_arg(args, uint64_t); arg3 = va_arg(args, uint64_t); arg4 = va_arg(args, uint64_t); arg5 = va_arg(args, uint64_t); arg6 = va_arg(args, uint64_t); va_end(args); __asm__ volatile ( "mov rax, %1\n" "mov rdi, %2\n" "mov rsi, %3\n" "mov rdx, %4\n" "mov r10, %5\n" "mov r8, %6\n" "mov r9, %7\n" "syscall\n" "mov %0, rax\n" : "=r"(ret) : "r"(num), "r"(arg1), "r"(arg2), "r"(arg3), "r"(arg4), "r"(arg5), "r"(arg6) : "rax", "rdi", "rsi", "rdx", "r10", "r8", "r9", "memory" ); return ret; } #endif #endif // DIRECT_SYSCALL_H
4. 使用示例
比如调用open系统调用,先获取对应的系统调用号(Android和Linux基本一致,可参考NDK的<asm/unistd.h>),然后直接调用我们的syscall函数:
#include "direct_syscall.h" #include <fcntl.h> // 从NDK的asm/unistd.h中获取对应架构的系统调用号 #ifdef __arm__ #define __NR_open 5 #elif __aarch64__ #define __NR_open 2 #elif __i386__ #define __NR_open 5 #elif __x86_64__ #define __NR_open 2 #endif int main() { int fd = syscall(__NR_open, "/sdcard/test.txt", O_RDONLY); if (fd >= 0) { // 操作文件 syscall(__NR_close, fd); } return 0; }
注意事项
- 系统调用号可能因Android版本或内核版本略有差异,建议直接从NDK的
<asm/unistd.h>中引用,或自行确认目标版本的正确编号。 - 对于需要超过6个参数的系统调用,需要额外处理栈传递参数的逻辑(这类系统调用极少)。
- 编译时要确保NDK的目标架构和代码中的预编译指令匹配,比如armeabi-v7a对应
__arm__,arm64-v8a对应__aarch64__等。
内容的提问来源于stack exchange,提问作者Phillip
相关产品推荐
相关产品推荐

