内联汇编访问系统时间后C段错误
C segmentation fault after accessing the system time by inline assembly
我一直在尝试在 Ubuntu 16.04 下使用 C 中的内联汇编来访问系统时间。我的代码如下所示:
struct timeval *date_time;
asm(
"movl 6, %%eax;"
"push [=10=];"
"push %0;"
"push [=10=];"
"int [=10=]x80;"
:
:"b"(date_time)
);
假设我在特定函数中存储了上面的几行。每当我调用该函数时,它都会触发错误代码:
Segmentation fault (core dumped)
由于我是内联汇编的新手,我猜代码可能有问题。因此,如果您能指出我做错了什么,我将非常高兴。感谢您的所有建议。
您这里有 很多 个错误,其中最重要的是:
您将东西压入堆栈,但在离开内联汇编块之前没有再次弹出它们。编译器不知道你这样做了,所以它会在错误的地方查找堆栈上的所有内容(例如 return 地址)。这很可能是导致崩溃的原因。
更一般地说,使用这种内联汇编风格的编译器根本不解释汇编指令。他们相信你正确地使用了输入、输出、破坏注解。如果您甚至忘记提及 一个 已修改的寄存器或内存区域,编译器将围绕汇编插入生成不正确的代码,程序将无法运行。
"Ubuntu 16.04" 是 Linux 的分布,因此您使用了错误的调用约定。 Linux 在寄存器中获取系统调用参数,而不是在堆栈中,as documented here,并且 gettimeofday
不是 x86-32/Linux 上的系统调用编号 116。 (始终使用 sys/syscall.h
中的 SYS_foo
常量作为系统调用编号。)
另外,在实际插入的程序集中,最好尽量少。在这种情况下,这意味着 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 [=10=]x80"
: "=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
以了解这是如何实现的。)
我一直在尝试在 Ubuntu 16.04 下使用 C 中的内联汇编来访问系统时间。我的代码如下所示:
struct timeval *date_time;
asm(
"movl 6, %%eax;"
"push [=10=];"
"push %0;"
"push [=10=];"
"int [=10=]x80;"
:
:"b"(date_time)
);
假设我在特定函数中存储了上面的几行。每当我调用该函数时,它都会触发错误代码:
Segmentation fault (core dumped)
由于我是内联汇编的新手,我猜代码可能有问题。因此,如果您能指出我做错了什么,我将非常高兴。感谢您的所有建议。
您这里有 很多 个错误,其中最重要的是:
您将东西压入堆栈,但在离开内联汇编块之前没有再次弹出它们。编译器不知道你这样做了,所以它会在错误的地方查找堆栈上的所有内容(例如 return 地址)。这很可能是导致崩溃的原因。
更一般地说,使用这种内联汇编风格的编译器根本不解释汇编指令。他们相信你正确地使用了输入、输出、破坏注解。如果您甚至忘记提及 一个 已修改的寄存器或内存区域,编译器将围绕汇编插入生成不正确的代码,程序将无法运行。
"Ubuntu 16.04" 是 Linux 的分布,因此您使用了错误的调用约定。 Linux 在寄存器中获取系统调用参数,而不是在堆栈中,as documented here,并且
gettimeofday
不是 x86-32/Linux 上的系统调用编号 116。 (始终使用sys/syscall.h
中的SYS_foo
常量作为系统调用编号。)
另外,在实际插入的程序集中,最好尽量少。在这种情况下,这意味着 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 [=10=]x80"
: "=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
以了解这是如何实现的。)