ptrace 能否判断 x86 系统调用使用的是 64 位还是 32 位 ABI?

Can ptrace tell if an x86 system call used the 64-bit or 32-bit ABI?

我正在尝试使用 ptrace 来跟踪由单独进程进行的所有系统调用,无论是 32 位 (IA-32) 还是 64 位 (x86-64)。我的跟踪器将 运行 在启用 IA-32 仿真的 64 位 x86 安装上,但理想情况下能够跟踪 64 位和 32 位应用程序,包括 64 位应用程序是否分叉并执行32 位进程。

问题是,由于 32 位和 64 位系统调用编号不同,我需要知道进程是 32 位还是 64 位以确定它使用了哪个系统调用,即使我有系统调用数字。似乎有 imperfect methods,比如检查 /proc/<pid>/exec 或(如 strace 那样)寄存器结构的大小,但没有可靠的东西。

64 位进程可以 to execute 32-bit code directly. They can also ,这当然会使用 32 位系统调用编号,这使问题变得复杂。我不 "trust" 我跟踪的进程没有使用这些技巧,所以我想正确地检测它们。而且我已经独立验证了至少在后一种情况下,ptrace 看到的是 32 位系统调用编号和参数寄存器分配,而不是 64 位的。

我在内核源代码中四处寻找,每当 64 位进程进行 32 位系统调用时,都会在 arch/x86/include/asm/processor.h, which appears to be set 中遇到 TS_COMPAT 标志。唯一的问题是我不知道如何从用户区访问这个标志,或者是否可能。

我也考虑过阅读 %cs 并将其与 [=15=]x23[=16=]x33 进行比较,灵感来自 在 运行ning 中切换位数过程。但这只检测 32 位 processes,不一定是来自 64 位的 32 位 syscalls(那些用 int [=11=]x80 制作的)过程。它也很脆弱,因为它依赖于未记录的内核行为。

最后,我注意到 x86 架构在扩展功能启用寄存器 MSR 中有一个用于长模式的位。但是 ptrace 无法从被跟踪者中读取 MSR,而且我觉得从我的跟踪器中读取它是不够的,因为我的跟踪器总是 运行ning 处于长模式。

我很茫然。或许我可以尝试使用其中一种技巧——目前我倾向于 %cs/proc/<pid>/exec 方法——但我想要一些能够真正区分 32 位和 64 位的耐用方法系统调用。 在 x86-64 下使用 ptrace 的进程检测到它的跟踪对象进行了系统调用,如何才能可靠地确定该系统调用是使用 32 位 (int [=11=]x80) 还是 64 位 ( syscall) ABI? 用户进程是否可以通过其他方式获取有关它被授权进行 ptrace 的另一个进程的信息?

有趣的是,我没有意识到 strace 没有明显更聪明的方法可以用来从 64 位进程正确解码 int 0x80。 (这正在进行中,请参阅 this answer 以获取指向建议的内核补丁的链接,以将 PTRACE_GET_SYSCALL_INFO 添加到 ptrace API。strace 4.26 已经在补丁内核上支持它。)

更新:现在支持per-syscall检测 IDK哪个主线内核版本添加了这个功能。我在 Arch Linux 上测试了内核版本 5.5 和 strace 版本 5.5.

例如此 NASM 源代码组装成静态可执行文件:

mov eax, 4
int 0x80
mov eax, 60
syscall

给出此痕迹:nasm -felf64 foo.asm && ld foo.o && strace ./a.out

execve("./foo", ["./foo"], 0x7ffcdc233180 /* 51 vars */) = 0
strace: [ Process PID=1262249 runs in 32 bit mode. ]
write(0, NULL, 0)                       = 0
strace: [ Process PID=1262249 runs in 64 bit mode. ]
exit(0)                                 = ?
+++ exited with 0 +++
每次系统调用使用与以前不同的 ABI 位数时,

strace 都会打印一条消息。请注意,关于 runs in 32 bit mode 的消息是完全错误的;它只是使用 64 位模式下的 32 位 ABI。 "Mode"有a specific technical meaning for x86-64,这不是


使用旧内核

作为解决方法,我认为您可以在 RIP 处反汇编代码并检查它是否是 syscall 指令(0F 05),因为 ptrace 确实让您阅读目标进程的内存。

但是对于像 disallowing some system calls 这样的安全用例,这很容易受到竞争条件的影响:系统调用进程中的另一个线程可以在之后将 syscall 字节重写为 int 0x80它们会执行,但在您可以使用 ptrace.

查看它们之前

如果进程是 运行 64 位模式,您只需要这样做,否则只有 32 位 ABI 可用。如果不是,则无需检查。 (vdso 页面可能会在支持它但不支持 sysenter 的 AMD CPU 上使用 32 位模式 syscall。不首先检查 32 位进程可以避免这种极端情况。)我认为你我是说你至少有一个可靠的方法来检测 那个

(我没有直接使用 ptrace API,只是像 strace 这样的工具使用它。所以我希望这个答案有意义。)