在内联C程序集中执行系统调用会导致segfault
我最近涉猎了低级编程,想做一个接受在内联C程序集中执行系统调用会导致segfault,c,ubuntu,assembly,system-calls,C,Ubuntu,Assembly,System Calls,我最近涉猎了低级编程,想做一个接受(CType-rax、CType-rbx、CType-rcx、CType-rdx)的函数somesyscall。struct CType看起来像: /* TYPES: 0 int 1 string 2 bool */ typedef struct { void* val; int typev; } CType; 函数有点混乱,但理论上应该可以工作: #include <errno.h
(CType-rax、CType-rbx、CType-rcx、CType-rdx)的函数somesyscall
。struct CType看起来像:
/*
TYPES:
0 int
1 string
2 bool
*/
typedef struct {
void* val;
int typev;
} CType;
函数有点混乱,但理论上应该可以工作:
#include <errno.h>
#include <stdbool.h>
#include "ctypes.h"
//define functions to set registers
#define seteax(val) asm("mov %0, %%rax" :: "g" (val) : "%rax")
#define setebx(val) asm("mov %0, %%rbx" :: "g" (val) : "%rbx")
#define setecx(val) asm("mov %0, %%rcx" :: "g" (val) : "%rcx")
#define setedx(val) asm("mov %0, %%rdx" :: "g" (val) : "%rdx")
///////////////////////////////////
#define setregister(value, register) \
switch (value.typev) { \
case 0: { \
register(*((double*)value.val)); \
break; \
} \
case 1: { \
register(*((char**)value.val)); \
break; \
} \
case 2: { \
register(*((bool*)value.val)); \
break; \
} \
}
static inline long int somesyscall(CType a0, CType a1, CType a2, CType a3) {
//set the registers
setregister(a0, seteax);
setregister(a1, setebx);
setregister(a2, setecx);
setregister(a3, setedx);
///////////////////
asm("int $0x80"); //interrupt
//fetch back the rax
long int raxret;
asm("mov %%rax, %0" : "=r" (raxret));
return raxret;
}
并使用
我犯了一个错误。然而,一切看起来都是正确的。我在这里做错了什么吗?在宏扩展之后,您将看到类似
long int raxret;
asm("mov %0, %%rax" :: "g" (a0) : "%rax");
asm("mov %0, %%rbx" :: "g" (a1) : "%rbx");
asm("mov %0, %%rcx" :: "g" (a2) : "%rcx");
asm("mov %0, %%rdx" :: "g" (a3) : "%rdx");
asm("int $0x80");
asm("mov %%rax, %0" : "=r" (raxret));
这不起作用,因为您没有告诉编译器,在asm
语句序列期间,不允许将rax
、rbx
、rcx
和rdx
用于其他内容。例如,寄存器分配器可能会决定将a2
从堆栈复制到rax
,然后使用rax
作为mov%0、%%rcx
指令的输入操作数——对放入rax
的值进行压缩
(没有输出的asm语句是无效的,因此前5个语句不能相对彼此重新排序,但最后一个语句可以移动到任何位置。例如,在以后的代码之后,可以移动到编译器可以方便地在其选择的寄存器中生成raxret
。此时RAX可能不再具有系统调用返回值-您需要告诉编译器输出来自实际生成它的asm语句,而不假设asm语句之间存在任何寄存器。)
有两种不同的方法告诉编译器不要这样做:
仅将int
指令放在asm中,并用约束字母表示在哪个寄存器中的所有要求:
asm volatile ("int $0x80"
: "=a" (raxret) // outputs
: "a" (a0), "b" (a1), "c" (a2), "d" (a3) // pure inputs
: "memory", "r8", "r9", "r10", "r11" // clobbers
// 32-bit int 0x80 system calls in 64-bit code zero R8..R11
// for native "syscall", clobber "rcx", "r11".
);
对于这个简单的示例,这是可能的,但一般来说并不总是可能的,因为每个寄存器都没有约束字母,特别是在除x86之外的CPU上
// use the native 64-bit syscall ABI
// remove the r8..r11 clobbers for 32-bit mode
仅将int
指令放在asm中,并用以下内容表达进入寄存器的要求:
无论您需要使用哪个寄存器,这都会起作用。它可能与您试图用来擦除类型的宏更兼容
顺便说一句,如果这是64位x86/Linux,那么参数属于ABI标准传入参数寄存器(按该顺序为rdi、rsi、rdx、rcx、r8、r9),而不是rbx、rcx、rdx等。不过,系统调用号仍然是rax。(如果您为特定的系统调用编写包装,请使用#include中的调用号码告诉GCC哪些内存可能被读取或写入,而不是“内存”
clobber)
一些无指针的情况包括getpid
,在这种情况下,避免往返内核模式会快得多,就像glibc对适当的系统调用所做的那样。这也适用于使用指针的clock\u gettime
)
顺便说一句,请注意实际的内核接口与C库包装器提供的接口不匹配。这通常记录在手册页的“注释”部分中,例如,对于和,在宏扩展之后,您将有如下内容
long int raxret;
asm("mov %0, %%rax" :: "g" (a0) : "%rax");
asm("mov %0, %%rbx" :: "g" (a1) : "%rbx");
asm("mov %0, %%rcx" :: "g" (a2) : "%rcx");
asm("mov %0, %%rdx" :: "g" (a3) : "%rdx");
asm("int $0x80");
asm("mov %%rax, %0" : "=r" (raxret));
这不起作用,因为您没有告诉编译器,在asm
语句序列期间,不允许将rax
、rbx
、rcx
和rdx
用于其他内容。例如,寄存器分配器可能决定将a2
从堆栈复制到rax
,并且然后使用rax
作为mov%0、%%rcx
指令的输入操作数--删除输入rax
的值
(没有输出的asm语句是无效的,因此前5个语句不能相对彼此重新排序,但最后一个语句可以移动到任何位置。例如,在以后的代码之后,可以移动到编译器可以方便地在其选择的寄存器中生成raxret
。此时RAX可能不再具有系统调用返回值-您需要告诉编译器输出来自实际生成它的asm语句,而不假设asm语句之间存在任何寄存器。)
有两种不同的方法告诉编译器不要这样做:
仅将int
指令放在asm中,并用约束字母表示在哪个寄存器中的所有要求:
asm volatile ("int $0x80"
: "=a" (raxret) // outputs
: "a" (a0), "b" (a1), "c" (a2), "d" (a3) // pure inputs
: "memory", "r8", "r9", "r10", "r11" // clobbers
// 32-bit int 0x80 system calls in 64-bit code zero R8..R11
// for native "syscall", clobber "rcx", "r11".
);
对于这个简单的示例,这是可能的,但一般来说并不总是可能的,因为每个寄存器都没有约束字母,特别是在除x86之外的CPU上
// use the native 64-bit syscall ABI
// remove the r8..r11 clobbers for 32-bit mode
仅将int
指令放在asm中,并用以下内容表达进入寄存器的要求:
无论您需要使用哪个寄存器,这都会起作用。它可能与您试图用来擦除类型的宏更兼容
顺便说一句,如果这是64位x86/Linux,那么参数属于ABI标准传入参数寄存器(按该顺序为rdi、rsi、rdx、rcx、r8、r9),而不是rbx、rcx、rdx等。不过,系统调用号仍然是rax。(如果您为特定的系统调用编写包装,请使用#include中的调用号码告诉GCC哪些内存可能被读取或写入,而不是“内存”
clobber)
一些无指针的情况包括getpid
,在这种情况下,避免往返内核模式会快得多,就像glibc对适当的系统调用所做的那样。这也适用于使用指针的clock\u gettime
)
顺便说一句,请注意实际的内核接口与C库包装器提供的接口不匹配。这通常记录在手册页的注释部分,例如for和请注意,32位int$0x80
API(实际上是64位代码)使用了它的参数