2

我正在尝试使用 sigaction() 和 setitimer() 系统调用在 OS X Lion 上的 32 位汇编程序中实现计时器例程。这个想法是使用 setitimer() 设置一个计时器,然后让生成的警报信号调用之前通过 sigaction() 设置的处理函数。我有这样的机制在 Linux 上运行,但似乎无法让它在 OS X 上运行。我知道 OS X 和 Linux 之间的系统调用约定不同,并且 OS X 有 16 字节对齐要求。尽管对这些进行了补偿,但我仍然无法使其正常工作(通常是“总线错误:10”错误)。认为我在对齐方面做错了,我编写了一个简单的 C 程序来执行我想要的操作,然后使用 clang 3.2 生成汇编代码。然后我通过替换对 sigaction() & 的调用来修改机器生成的程序集 setitimer() 与适当的系统 & int $0x80 调用,以及堆栈对齐指令。结果程序仍然不起作用。

这是我用来生成程序集的 C 程序 sigaction.c。请注意,我注释掉了 printf 和 sleep 的东西,因此生成的汇编代码更容易阅读:

//#include <stdio.h>
#include <signal.h>
#include <sys/time.h>

struct sigaction action;

void handler(int arg) {
//    printf("HERE!\n");
}

int main() {

    action.__sigaction_u.__sa_handler = handler;
    action.sa_mask = 0;
    action.sa_flags = 0;

//    printf("sigaction size: %d\n", sizeof(action));

    int fd = sigaction(14, &action, 0);

    struct itimerval timer;
    timer.it_interval.tv_sec = 1;
    timer.it_interval.tv_usec = 0;
    timer.it_value.tv_sec = 1;
    timer.it_value.tv_usec = 0;

//    printf("itimerval size: %d\n", sizeof(timer));

    fd = setitimer(0, &timer, 0);

    while (1) {
//        sleep(60);
    }

    return 0;

}

这是在上述文件中使用“clang -arch i386 -S sigaction.c”生成的汇编代码:

        .section        __TEXT,__text,regular,pure_instructions
        .globl  _handler
        .align  4, 0x90
_handler:                               ## @handler
## BB#0:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %eax
        movl    8(%ebp), %eax
        movl    %eax, -4(%ebp)
        addl    $4, %esp
        popl    %ebp
        ret

        .globl  _main
        .align  4, 0x90
_main:                                  ## @main
## BB#0:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %esi
        subl    $52, %esp
        calll   L1$pb
L1$pb:
        popl    %eax
        movl    $14, %ecx
        movl    L_action$non_lazy_ptr-L1$pb(%eax), %edx
        movl    $0, %esi
        leal    _handler-L1$pb(%eax), %eax
        movl    $0, -8(%ebp)
        movl    %eax, (%edx)
        movl    $0, 4(%edx)
        movl    $0, 8(%edx)
        movl    $14, (%esp)
        movl    %edx, 4(%esp)
        movl    $0, 8(%esp)
        movl    %esi, -36(%ebp)         ## 4-byte Spill
        movl    %ecx, -40(%ebp)         ## 4-byte Spill
        calll   _sigaction
        movl    $0, %ecx
        leal    -32(%ebp), %edx
        movl    %eax, -12(%ebp)
        movl    $1, -32(%ebp)
        movl    $0, -28(%ebp)
        movl    $1, -24(%ebp)
        movl    $0, -20(%ebp)
        movl    $0, (%esp)
        movl    %edx, 4(%esp)
        movl    $0, 8(%esp)
        movl    %ecx, -44(%ebp)         ## 4-byte Spill
        calll   _setitimer
        movl    %eax, -12(%ebp)
LBB1_1:                                 ## =>This Inner Loop Header: Depth=1
        jmp     LBB1_1

        .comm   _action,12,2            ## @action

        .section        __IMPORT,__pointers,non_lazy_symbol_pointers
L_action$non_lazy_ptr:
        .indirect_symbol        _action
        .long   0

.subsections_via_symbols

如果我使用“clang -arch i386 sigaction.s -o sigaction”编译汇编代码,使用 lldb 调试它并在处理函数中放置一个断点,处理函数确实每秒调用一次。所以我知道汇编代码是正确的(C 代码也是如此)。

现在,如果我将对 sigaction() 的调用替换为:

#       calll   _sigaction
movl    $0x2e, %eax
subl    $0x04, %esp
int     $0x80
addl    $0x04, %esp

以及对 setitimer() 的调用:

#       calll   _setitimer
movl    $0x53, %eax
subl    $0x04, %esp
int     $0x80
addl    $0x04, %esp

汇编代码不再起作用,并生成与我的手工编码汇编代码相同的“总线错误:10”。

我尝试删除用于对齐堆栈的 subl/addl 指令以及更改值以确保堆栈在 16 字节边界上对齐,但似乎没有任何效果。我要么得到总线错误、分段错误,要么代码在没有调用处理函数的情况下挂起。

我在调试期间确实注意到的一件事是 sigaction 调用似乎对底层系统调用有一个冗长的包装。如果您从 lldb 中反汇编这两个函数,您会看到 sigaction() 有一个冗长的包装器,但 setitimer 没有。不确定这意味着什么,但也许 sigaction() 包装器在传递数据之前正在处理数据。我尝试调试该代码,但还没有找到任何确定的东西。

如果有人知道如何通过用适当的系统调用替换 sigaction() 和 setitimer() 函数来使上述汇编代码工作,将不胜感激。然后我可以进行这些更改并将它们应用到我的手工编码例程中。

谢谢。

更新:我将我的手写汇编代码剥离到可管理的大小,并且能够使用 sigaction() 和 setitimer() 库调用使其工作,但仍然没有弄清楚为什么系统调用不起作用。这是代码(timer.s):

.globl _main

.data

.set    ITIMER_REAL, 0x00
.set    SIGALRM, 0x0e
.set    SYS_SIGACTION, 0x2e
.set    SYS_SETITIMER, 0x53
.set    TRAP, 0x80

itimerval:
    interval_tv_sec:
        .long 0
    interval_tv_usec:
        .long 0
    value_tv_sec:
        .long 0
    value_tv_usec:
        .long 0

sigaction:
    sa_handler:
        .long handler
    sa_mask:
        .long 0
    sa_flags:
        .long 0

.text

handler:
    pushl   %ebp
    movl    %esp, %ebp

    movl    %ebp, %esp
    popl    %ebp

    ret

_main:

    pushl   %ebp
    movl    %esp, %ebp

    subl    $0x0c, %esp

    movl    $SIGALRM, %ebx
    movl    $sigaction, %ecx
    movl    $0x00, %edx

    pushl   %edx
    pushl   %ecx
    pushl   %ebx
#    subl    $0x04, %esp
    call    _sigaction
#    movl    $SYS_SIGACTION, %eax
#    int    $0x80
    addl    $0x0c, %esp
#    addl    $0x10, %esp
    movl    $ITIMER_REAL, %ebx
    movl    $0x01, interval_tv_sec          # Successive calls every 1 second
    movl    $0x00, interval_tv_usec
    movl    $0x01, value_tv_sec             # Initial call in 1 second
    movl    $0x00, value_tv_usec
    movl    $itimerval, %ecx
    movl    $0x00, %edx

    pushl   %edx
    pushl   %ecx
    pushl   %ebx
#    subl    $0x04, %esp
    call    _setitimer
#    movl    $SYS_SETITIMER, %eax
#    int    $0x80
    addl    $0x0c, %esp
#    addl    $0x10, %esp

loop:
     jmp     loop

当使用“clang -arch i386 timer.s -o timer”编译并使用 lldb 调试时,处理程序例程每秒调用一次。我放弃了使代码与代码中的系统调用一起工作的努力——它们在 sigaction() 和 setitimer() 调用周围被注释掉了。如果除了教育自己(和其他人)之外没有其他原因,我仍然希望尽可能让系统调用版本正常工作,如果没有,请了解它不起作用的原因。

再次感谢。

更新 2:我让 setitimer 系统调用正常工作。这是修改后的代码:

pushl   %edx
pushl   %ecx
pushl   %ebx
subl    $0x04, %esp
movl    $SYS_SETITIMER, %eax
int    $0x80
addl    $0x10, %esp

但是相同的编辑不适用于 sigaction sys 调用,这使我回到了最初的结论 - sigaction() 库函数在进行实际的 syscall 之前做了一些额外的事情。来自 dtruss 的这个片段似乎暗示了同样的事情:

使用 sigaction() 系统调用(不工作):

sigaction(0xE, 0x2030, 0x0)      = 0 0
setitimer(0x0, 0x2020, 0x0)      = 0 0

使用 sigaction() 库调用(工作):

sigaction(0xE, 0xBFFFFC40, 0x0)  = 0 0
setitimer(0x0, 0x2028, 0x0)      = 0 0

如您所见,两个版本之间的第二个参数是不同的。使用系统调用时,似乎直接传递了 sigaction 结构的地址(0x2030),但使用库调用时传递了其他东西。我猜“其他东西”是在 sigaction() 库函数中生成的。

更新 3:我发现 FreeBSD 9.1 上存在同样的问题。setitimer 系统调用有效,但 sigaction 系统调用无效。与 OS X 一样,sigaction() 库调用确实有效。

BSD 有一些 sigaction 系统调用 - 到目前为止,我只尝试过我在 OS X 中使用的同一个 - 0x2e。也许其他 sigaction 系统调用之一会起作用。知道 BSD 具有相同的行为将使这更容易追踪,因为我可以提取 C 源代码。此外,这向可能已经知道问题所在的更广泛的人群开放了问题。

基于我对系统调用如何工作的理解以及 sigaction 在 Linux 上工作的事实,我不禁认为我的代码做错了什么。然而,用 sigaction() 库函数替换 int $0x80 调用导致我的代码工作的事实似乎与此相矛盾。FreeBSD 开发人员手册中有一整章是关于汇编语言编程的,还有一节是关于进行系统调用的,所以我正在做的应该是可能的:

https://www.freebsd.org/doc/en_US.ISO8859-1/books/developers-handbook/x86-system-calls.html

除非有人能指出我做错了什么(如果有的话),否则我认为下一步是让我查看 sigaction() 的 BSD 源代码。正如我之前提到的,我查看了 OS X 上 sigaction 的反汇编版本,发现它与其他系统调用相比相当长,并且充满了幻数。希望通过查看 C 代码可以清楚地了解它在做什么才能使其正常工作。最后,它可能很简单,例如传入错误的 sigaction 结构(其中有几个)或未能在某处设置一些位。

4

0 回答 0