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 構造体 (いくつかあります) を渡すか、どこかにビットを設定するのに失敗するなど、単純なことである可能性があります。