gpt4 book ai didi

c - 在内联 C 程序集中执行系统调用会导致段错误

转载 作者:行者123 更新时间:2023-12-04 18:35:54 25 4
gpt4 key购买 nike

我最近涉足低级编程,想做一个函数somesyscall接受 (CType rax, CType rbx, CType rcx, CType rdx) .结构 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;
}
当我运行时:
#include "syscall_unix.h"

int main() {
CType rax;
rax.val = 39;
rax.typev = 0;

CType rbx;
rbx.val = 0;
rbx.typev = 0;

CType rcx;
rcx.val = 0;
rcx.typev = 0;

CType rdx;
rdx.val = 0;
rdx.typev = 0;

printf("%ld", somesyscall(rax, rbx, rcx, rdx));
}
并编译(并运行二进制文件)
clang test.c
./a.out
我得到一个段错误。然而,一切似乎都是正确的。我在这里做错什么了吗?

最佳答案

宏扩展后,您将拥有类似

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));
这不起作用,因为您没有告诉编译器在 rax 语句的序列中不允许将 rbxrcxrdxasm 用于其他内容。例如,寄存器分配器可能决定将 a2 从堆栈复制到 rax,然后使用 rax 作为 mov %0, %%rcx 指令的输入操作数——破坏您放入 rax 的值。
(没有输出的asm语句是 implicitly volatile ,所以前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 中,并用 explicit register variables 表达对什么寄存器的要求:
     register long rax asm("rax") = a0;
    register long rbx asm("rbx") = a1;
    register long rcx asm("rcx") = a2;
    register long rdx asm("rdx") = r3;

    // Note that int $0x80 only looks at the low 32 bits of input regs
    // so `uint32_t` would be more appropriate than long
    // but really you should just use "syscall" in 64-bit code.
    asm volatile ("int $0x80"
    : "+r" (rax) // read-write: in=call num, out=retval
    : "r" (rbx), "r" (rcx), "r" (rdx) // read-only inputs
    : "memory", "r8", "r9", "r10", "r11"
    );

    return rax;
    无论您需要使用哪些寄存器,这都将起作用。它也可能与您尝试用来删除类型的宏更兼容。

  • 顺便说一句,如果这是 64 位 x86/Linux 则 you should be using syscall rather than int $0x80 ,并且参数属于 ABI 标准传入参数寄存器(按顺序为 rdi、rsi、rdx、rcx、r8、r9),而不是 rbx、rcx、 rdx 等。不过,系统调用号仍然在 rax 中。 (使用来自 #include <asm/unistd.h><sys/syscall.h> 的调用号码,这将适用于您正在编译的模式的 native ABI,另一个在 64 位模式下不使用 int $0x80 的原因。)
    此外,系统调用指令的 asm 语句应该有一个“内存”clobber 并声明为 volatile ;几乎所有系统调用都以某种方式访问​​内存。
    (作为一个微优化,我想你可以有一个不读取内存、写入内存或修改虚拟地址空间的系统调用列表,并避免它们的内存破坏。这将是一个非常短的列表,并且我不确定这是否值得麻烦。或者使用 How can I indicate that the memory *pointed* to by an inline ASM argument may be used? 中显示的语法告诉 GCC 可以读取或写入哪些内存,而不是 "memory" 如果您为特定的系统调用编写包装器。
    一些无指针情况包括 getpid ,其中 call into the VDSO 会更快,以避免往返内核模式并返回,就像 glibc 对适当的系统调用所做的那样。这也适用于带有指针的 clock_gettime。)

    顺便提一下,注意实际的内核接口(interface)与 C 库包装器提供的接口(interface)不匹配。这通常记录在手册页的 NOTES 部分,例如对于 brk(2) getpriority(2)

    关于c - 在内联 C 程序集中执行系统调用会导致段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64083032/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com