2

sigaction() & setitimer() システム コールを使用して、OS X Lion の 32 ビット アセンブラでタイマー ルーチンを実装しようとしています。アイデアは、setitimer() を使用してタイマーを設定し、生成されたアラーム信号が、sigaction() によって以前に設定されたハンドラー関数を呼び出すようにすることです。私は Linux で動作するこのようなメカニズムを持っていますが、OS X で動作するようには見えません。OS X と Linux ではシステム コールの規則が異なり、OS X には 16 バイトのアラインメント要件があることを知っています。これらを補っているにもかかわらず、私はまだそれを機能させることができません (通常は "Bus error: 10" エラー)。アラインメントに何か問題があったと考えて、私が望むことを実行する単純な C プログラムを作成し、clang 3.2 を使用してアセンブリ コードを生成しました。次に、sigaction() & への呼び出しを置き換えることで、機械で生成されたアセンブリを変更しました。setitimer() に適切な system & 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

アセンブリ コードが機能しなくなり、手動でコーディングしたアセンブリ コードと同じ "Bus error: 10" が生成されます。

スタックをアラインするために使用している subl/addl 命令を削除し、値を変更してスタックが 16 バイト境界にアラインされるようにしましたが、何も機能していないようです。バス エラー、セグメンテーション フォールトが発生するか、ハンドラー関数を呼び出さずにコードがハングします。

デバッグ中に気づいたことの 1 つは、sigaction 呼び出しが、基になるシステム コールの周りに長いラッパーを持っているように見えることです。lldb 内から両方の関数を逆アセンブルすると、sigaction() には長いラッパーがありますが、setitimer にはありません。これが何かを意味するかどうかはわかりませんが、sigaction() ラッパーがデータを渡す前にデータを処理している可能性があります。そのコードをデバッグしようとしましたが、まだ決定的なものは見つかりませんでした。

sigaction() および setitimer() 関数を適切なシステム コールに置き換えることで、上記のアセンブリ コードを機能させる方法を知っている人がいれば、大歓迎です。次に、これらの変更を取得して、手作業でコーディングしたルーチンに適用できます。

ありがとう。

更新: 手書きのアセンブリ コードを扱いやすいサイズに縮小し、sigaction() & setitimer() ライブラリ呼び出しを使用して動作させることができましたが、syscall が動作しない理由はまだわかりません。コードは次のとおりです(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 でデバッグすると、ハンドラー ルーチンが毎秒呼び出されます。コード内の syscall を使用してコードを機能させる努力をやめました。それらは sigaction() および setitimer() 呼び出しの周りでコメントアウトされています。自分自身 (および他の人) を教育する以外の理由がない場合でも、可能であればシステム コールのバージョンを機能させたいと考えています。

再度、感謝します。

更新 2: setitimer syscall が機能するようになりました。変更されたコードは次のとおりです。

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

しかし、同じ編集は sigaction sys コールでは機能しません。これにより、最初の結論に戻ります。sigaction() ライブラリ関数は、実際の syscall を行う前に特別なことを行っています。dtruss からのこのスニペットは、同じことを示唆しているようです:

sigaction() syscall を使用 (動作していません):

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

ご覧のとおり、2 つのバージョンで 2 番目の引数が異なります。syscallではsigaction構造体(0x2030)のアドレスがそのまま渡されているようですが、ライブラリコールでは別のものが渡されています。「何か他のもの」が sigaction() ライブラリ関数で生成されていると推測しています。

更新 3: FreeBSD 9.1 にもまったく同じ問題が存在することがわかりました。setitimer システムコールは機能しますが、sigaction システムコールは機能しません。OS X と同様に、sigaction() ライブラリ呼び出しは機能します。

BSD にはいくつかの sigaction syscall があります。これまでのところ、OS X で使用していたのと同じ 0x2e のみを試しました。おそらく、他の sigaction システムコールの 1 つが機能するでしょう。BSD にも同じ動作があることを知っていれば、C ソース コードを引っ張ることができるので、追跡が容易になります。さらに、これにより、問題が何であるかをすでに知っている可能性のある、より幅広い人々のグループに問題が開かれます。

syscall がどのように機能するかについての私の理解と、Linux で sigaction が機能するという事実に基づいて考えると、自分のコードで何か間違ったことをしていると思わざるを得ません。ただし、 int $0x80 呼び出しを sigaction() ライブラリ関数に置き換えるとコードが機能するという事実は、これと矛盾しているようです。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