跟踪write()时为什么会得到与其他人不同的结果?

时间:2011-10-16 00:58:34

标签: c linux assembly x86 system-calls

我正在做一些x86练习;我的任务让我在调试器中逐步调试write()库调用的汇编代码,直到我们达到SYSENTER指令,但我得到的结果与我的一些同学不同。我在SYSENTER前看到的是:

   │0xf7fdf421 <__kernel_vsyscall+1>        push   %edx                                                                                            
   │0xf7fdf422 <__kernel_vsyscall+2>        push   %ebp                                                                                            
   │0xf7fdf423 <__kernel_vsyscall+3>        mov    %esp,%ebp                                                                                       
   │0xf7fdf425 <__kernel_vsyscall+5>        sysenter    

这是我应该看到的吗?如果是这样,为什么它与我的一些同学看到的不一样? 在执行sysenter指令之前,%edx和%ebp寄存器也保存在堆栈中吗? (根据我得到的答案,或者我错了,它似乎不是这样吗?)

这是我作业的原始说明:

汇编代码:

.file    "A3Program2.c"
    .section    .rodata
.LC0:
    .string    "hello\n"
.LC1:
    .string    "xxxx\n"
    .text
.globl secondCall
    .type    secondCall, @function
secondCall:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $40, %esp
    movl    $6, 8(%esp)
    movl    $.LC0, 4(%esp)
    movl    $1, (%esp)
    call    write
    movl    %eax, -12(%ebp)
    movl    $8, 8(%esp)
    movl    $.LC1, 4(%esp)
    movl    $1, (%esp)
    call    write
    addl    %eax, -12(%ebp)
    movl    12(%ebp), %eax
    movl    8(%ebp), %edx
    leal    (%edx,%eax), %eax
    addl    %eax, -12(%ebp)
    movl    -12(%ebp), %eax
    leave
    ret
    .size    secondCall, .-secondCall
.globl firstCall
    .type    firstCall, @function
firstCall:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $40, %esp
    movl    $2, 4(%esp)
    movl    $4, (%esp)
    call    secondCall
    movl    %eax, -12(%ebp)
    movl    -12(%ebp), %eax
    leave
    ret
    .size    firstCall, .-firstCall
.globl main
    .type    main, @function
main:
    pushl    %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    call    firstCall
    movl    %eax, 12(%esp)
    movl    $0, %eax
    leave
    ret
    .size    main, .-main
    .ident    "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5"
    .section    .note.GNU-stack,"",@progbits

以下说明适用于Linux:

在secondCall中找到第二次写入调用的行号“call write” 功能。在此行设置断点。根据我的说法,这是22。

在此行设置断点。

 break 22

在调试器中运行程序。

 run

程序将在您设置的断点处停止。 进入没有调试信息的代码。

  si 

您将在源布局中看到“[No Source Available]”。所以你需要查看 拆解的指示。

 layout asm

重复步入(si然后返回/输入将执行si命令 反复)直到你看到“sysenter”出现在屏幕的asm布局部分。 我试图从asm布局部分的顶部复制指令(包括它们的地址),直到并包括sysenter指令。

提示:您可以通过键入Ctrl-x o将键盘的焦点更改为命令区域。这个       方式箭头键可以用来带回早期的命令(它只是节省一些       打字)。

1 个答案:

答案 0 :(得分:10)

您正在追踪所谓的'virtual dynamic shared object' (VDSO) - 其内容是Linux内核的实现细节。有许多条件可能导致VDSO的内容发生变化;因此这里没有单一的正确答案

特别是,在32位x86系统上,至少有三种不同的机制可用于进行系统调用:

  • INT $0x80
  • SYSCALL(最近的AMD CPU)
  • SYSENTER(最近的英特尔CPU)

你会注意到只有INT $0x80适用于所有CPU(事实上,即使有更现代的替代方案,内核也可以用于遗留应用程序);然而,它也很慢。内核将探测引导时支持哪些内容,并选择使用最有效机制的VDSO版本。

因此,根据您的CPU型号,您可能会在VDSO中看到不同的代码 - 特别是,如果您有AMD CPU,您可能会看到SYSCALL路径,如果您有真的很老的CPU甚至可能会看到INT $0x80路径。如果你对其他人感到好奇,这里是源代码:

最有可能的是,你实验室中得到不同结果的其他人有一个AMD CPU并正在查看SYSCALL路径(或者他们有一台古董PC,正在查看INT $0x80路径)。

另请注意,在64位进程中,SYSCALL将直接使用,而无需通过VDSO。