繁体   English   中英

为什么 ptrace 显示一个 32 位 execve 系统调用,其 EAX = 59,即 64 位调用号? 32 位系统调用如何在 x86-64 上工作?

[英]Why does ptrace show a 32-bit execve system call having EAX = 59, the 64-bit call number? How do 32-bit system calls work on x86-64?

我正在用下面的代码玩弄ptrace 我发现execve的系统调用号是 59,即使我使用-m32选项编译。 由于我在 64 位机器上使用 Ubuntu,因此可以理解。

很快,问题就出现了:“libc32 在 32 位机器和 64 位机器上的行为是否不同?它们不同吗?” 所以我检查了 libc32 在 64 位中的内容。 但是,libc 的execve系统调用号是 11,这与 32 位系统的execv系统调用号相同。 那么魔法发生在哪里呢? 先感谢您。

这是代码。 它起源于https://www.linuxjournal.com/article/6100

#include <sys/ptrace.h>                                                                                                           
#include <sys/types.h>                                                                                                            
#include <sys/wait.h>                                                                                                             
#include <unistd.h>                                                                                                               
#include <sys/user.h>                                                                                                             
#include <stdio.h>                                                                                                                
                                                                                                                                  
int main()                                                                                                                        
{                                                                                                                                 
        pid_t child;                                                                                                              
        long  orig_eax;                                                                                                           
        child = fork();                                                                                                           
        if (child == 0) {                                                                                                         
                ptrace(PTRACE_TRACEME, 0, NULL, NULL);                                                                            
                execl("/bin/ls", "ls", NULL);                                                                                     
        } else {                                                                                                                  
                wait(NULL);                                                                                                       
                orig_eax = ptrace(PTRACE_PEEKUSER,                                                                                
#ifdef __x86_64__                                                                                                                 
                                child, &((struct user_regs_struct *)0)->orig_rax,                                                 
#else                                                                                                                             
                                child, &((struct user_regs_struct *)0)->orig_eax,                                                 
#endif                                                                                                                            
                                NULL);                                                                                            
                printf("The child made a "                                                                                       
                        "system call %ld\n", orig_eax);                                                                           
                ptrace (PTRACE_CONT, child, NULL, NULL);                                                                          
        }                                                                                                                         
        return 0;                                                                                                                 
}

这是代码的结果

~/my-sandbox/ptrace$ file s1 && ./s1
s1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f84894c2f5373051682858937bf54a66f21cbeb4, for GNU/Linux 3.2.0, not stripped
The child made a system call 59

~/my-sandbox/ptrace$ file s2 && ./s2
s2: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=cac6a2bbeee164e27c11764c1b68f4ddd06405cf, for GNU/Linux 3.2.0, with debug_info, not stripped
The child made a system call 59

这是我使用 gdb 从 32 位可执行文件中得到的。 如您所见,它使用的是 /lib/i386-linux-gnu/libc.so.6,而 execve 的系统调用号是 11。

>>> bt
#0  0xf7e875a0 in execve () from /lib/i386-linux-gnu/libc.so.6
#1  0xf7e8799f in execl () from /lib/i386-linux-gnu/libc.so.6
#2  0x565562a4 in main () at simple1.c:15
>>> disassemble
Dump of assembler code for function execve:
=> 0xf7e875a0 <+0>: endbr32 
   0xf7e875a4 <+4>: push   %ebx
   0xf7e875a5 <+5>: mov    0x10(%esp),%edx
   0xf7e875a9 <+9>: mov    0xc(%esp),%ecx
   0xf7e875ad <+13>:    mov    0x8(%esp),%ebx
   0xf7e875b1 <+17>:    mov    $0xb,%eax
   0xf7e875b6 <+22>:    call   *%gs:0x10
   0xf7e875bd <+29>:    pop    %ebx
   0xf7e875be <+30>:    cmp    $0xfffff001,%eax
   0xf7e875c3 <+35>:    jae    0xf7dd9000
   0xf7e875c9 <+41>:    ret    
End of assembler dump.

execve是特殊的; 它是唯一一个与PTRACE_TRACEME有特殊交互的。 strace的工作方式,其他系统调用确实显示 32 位调用号。 (现代 strace 需要特殊帮助才能知道这是int 0x80 / sysenter的 32 位调用号还是 64 位调用号,因为 64 位进程仍然可以调用int 0x80 ,尽管它们通常不应该调用。这个仅在 2019 年通过PTRACE_GET_SYSCALL_INFO添加了支持)


没错,当实际调用 kernel 时,EAX 持有11 ,来自__NR_execveunistd_32.h 它由mov $0xb,%eax在 glibc 的 execve 包装器跳转到 VDSO 页面以通过此硬件支持的任何有效方法(通常为sysenter )进入 kernel 之前设置。

但是执行实际上并没有停止,直到它到达主execve实现中检查PTRACE_TRACEME并引发SIGTRAP的一些代码。

显然在此之前的某个时间,它在 arch/x86/kernel/process_64.c 中调用void set_personality_64bit(void) ,其中包括

    /* Pretend that this comes from a 64bit execve */
    task_pt_regs(current)->orig_ax = __NR_execve;

我发现通过在 kernel 源浏览器中搜索__NR_execve并查看 arch/x86 中最可能的文件。 我没有继续交叉引用来查找调用的位置; 它存在的事实(以及理智的非混淆设计的假设)非常强烈地表明这是您的谜团的答案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM