我一直在尝试在 Ubuntu 16.04 下使用 C 中的内联程序集访问系统时间。我的代码如下所示:
struct timeval *date_time;
asm(
"movl $116, %%eax;"
"push $0;"
"push %0;"
"push $0;"
"int $0x80;"
:
:"b"(date_time)
);
假设我上面有几行存储在特定函数中。每当我调用该函数时,它都会触发错误代码:
分段故障(核心转储)
由于我是内联汇编的新手,我猜代码可能有问题。因此,如果您能指出我做错了什么,我将非常高兴。感谢您的所有建议。
你在这里有很多错误,其中最重要的是:
-
您将东西推到堆栈上,但在离开内联装配块之前没有再次弹出它们。 编译器不知道你这样做了,所以它会在错误的地方查找堆栈上的所有内容(例如返回地址)。 这很可能是导致崩溃的原因。
-
更一般地说,使用这种内联程序集样式的编译器根本不解释程序集指令。 他们相信您已经正确使用了输入、输出、标记注释。 如果您忽略提及一个已修改的寄存器或内存区域,编译器将在程序集插入周围生成不正确的代码,并且程序将无法工作。
-
"Ubuntu 16.04"是Linux的发行版,因此您使用了错误的调用约定。 Linux 在寄存器中获取系统调用参数,而不是在堆栈上,如此处所述,
gettimeofday
在 x86-32/Linux 上不是系统调用编号 116。 (始终使用SYS_foo
常量,从sys/syscall.h
开始,用于系统呼叫号码。
此外,最好在实际插入的程序集中尽可能少地执行操作。 在这种情况下,这意味着int
指令本身。 改为使用输入和输出约束设置参数。 这为编译器提供了最大的优化余地。 (如果你因为编译器未能做好优化而手动编写汇编,你应该编写一个纯汇编的整个".s"文件,而不是一个带有巨大程序集插入的.c文件;这样更易于维护。
此任务的正确代码如下所示
#include <assert.h>
#include <sys/time.h>
#include <sys/syscall.h>
struct timeval
call_gettimeofday()
{
struct timeval ret;
int dummy;
asm("int $0x80"
: "=m" (ret), "=a" (dummy)
: "1" (SYS_gettimeofday), "b" (&ret), "c" (0));
assert(!dummy); // gettimeofday should never fail
return ret;
}
最后要注意的是,使用内联程序集进行系统调用几乎总是一个错误。 C 库的包装器函数可能正在做比你看得更多的工作,并且它们知道如何在可能的情况下使用更有效的陷阱序列(使用sysenter
或syscall
而不是int
)。 在gettimeofday
的情况下,区别更加深刻:C 库知道如何在不捕获内核的情况下执行gettimeofday
操作!(阅读vDSO
以了解这是如何实现的。