我正在尝试使用 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 结构(其中有几个)或未能在某处设置一些位。