在 GNU C 内联汇编中编写 Linux int 80h 系统调用包装器 [重复]

2024-03-19

我正在尝试使用内联汇编... 我读过这一页http://www.codeproject.com/KB/cpp/edujini_inline_asm.aspx http://www.codeproject.com/KB/cpp/edujini_inline_asm.aspx但我无法理解传递给我的函数的参数。

我正在写一个 C 编写示例..这是我的函数头:

write2(char *str, int len){
}

这是我的汇编代码:

global write2
write2:
    push ebp
    mov ebp, esp
    mov eax, 4      ;sys_write
    mov ebx, 1      ;stdout
    mov ecx, [ebp+8]    ;string pointer
    mov edx, [ebp+12]   ;string size
    int 0x80        ;syscall
    leave
    ret

我必须做什么才能将该代码传递给 C 函数...我正在做这样的事情:

write2(char *str, int len){
    asm ( "movl 4, %%eax;"
          "movl 1, %%ebx;"
          "mov %1, %%ecx;"
          //"mov %2, %%edx;"
          "int 0x80;"
           :
           : "a" (str), "b" (len)
    );
}

那是因为我没有输出变量,那么我该如何处理呢? 另外,使用这段代码:

global main
main:
    mov ebx, 5866       ;PID
    mov ecx, 9      ;SIGKILL
    mov eax, 37     ;sys_kill
    int 0x80        ;interruption
    ret 

我怎样才能将该代码内联到我的代码中..这样我就可以向用户询问pid..像这样.. 这是我的预编码

void killp(int pid){
    asm ( "mov %1, %%ebx;"
          "mov 9, %%ecx;"
          "mov 37, %%eax;"
           :
           : "a" (pid)         /* optional */
    );
}

好吧,你没有具体说,但从你的帖子来看,你似乎正在使用 gcc 及其带有约束语法的内联汇编(其他 C 编译器具有非常不同的内联语法)。也就是说,您可能需要使用 AT&T 汇编器语法而不是 Intel,因为这就是 gcc 所使用的语法。

综上所述,让我们看看您的 write2 函数。首先,您不想创建堆栈帧,因为 gcc 将创建一个堆栈帧,因此如果您在 asm 代码中创建一个堆栈帧,最终将得到两个帧,事情可能会变得非常混乱。其次,由于 gcc 正在布置堆栈帧,因此您无法使用“[ebp + offset]”访问变量,因为您不知道它是如何布置的。

这就是约束的目的——你说你希望 gcc 将值放在什么地方(任何寄存器、内存、特定寄存器)并在 asm 代码中使用“%X”。最后,如果您在 asm 代码中使用显式寄存器,则需要在第三部分(在输入约束之后)列出它们,以便 gcc 知道您正在使用它们。否则,它可能会在其中一个寄存器中放入一些重要的值,而您会破坏该值。

您还需要告诉编译器内联汇编将或可能从输入操作数指向的内存中读取或写入;那是not默示。

综上所述,您的 write2 函数如下所示:

void write2(char *str, int len) {
    __asm__ volatile (
        "movl $4, %%eax;"      // SYS_write
        "movl $1, %%ebx;"      // file descriptor = stdout_fd
        "movl %0, %%ecx;"
        "movl %1, %%edx;"
        "int $0x80"
        :: "g" (str), "g" (len)       // input values we MOV from
        : "eax", "ebx", "ecx", "edx", // registers we destroy
          "memory"                    // memory has to be in sync so we can read it
     );
}

注意 AT&T 语法 — src, dest 而不是 dest, src and%寄存器名称之前。

现在这可以工作了,但是效率很低,因为它会包含很多额外的 movs。一般来说,您不应该在 asm 代码中使用 mov 指令或显式寄存器,因为您最好使用约束来说明您想要的东西并让编译器确保它们在那里。这样,优化器可能可以摆脱大部分 mov,特别是如果它内联函数(如果您指定 -O3,它将执行此操作)。方便的是,i386 机器模型对特定寄存器有限制,因此您可以这样做:

void write2(char *str, int len) {
    __asm__ volatile (
        "movl $4, %%eax;"
        "movl $1, %%ebx;"
        "int $0x80"
        :: "c" (str), /* c constraint tells the compiler to put str in ecx */
           "d" (len)  /* d constraint tells the compiler to put len in edx */
        : "eax", "ebx", "memory");
}

甚至更好

// UNSAFE: destroys EAX (with return value) without telling the compiler
void write2(char *str, int len) {
    __asm__ volatile ("int $0x80"
        :: "a" (4), "b" (1), "c" (str), "d" (len)
        : "memory");
}

还要注意使用volatile这是需要告诉编译器即使它的输出(没有)没有被使用,它也不能被消除。 (asm没有输出操作数已经是隐式的volatile,但是当真正的目的不是计算某些东西时,将其明确化并没有什么坏处;这是为了像系统调用这样的副作用。)

edit

最后一点要注意的是——这个函数正在执行一个 write 系统调用,它确实在 eax 中返回一个值——要么是写入的字节数,要么是错误代码。所以你可以通过输出约束得到它:

int write2(const char *str, int len) {
    __asm__ volatile ("int $0x80" 
     : "=a" (len)
     : "a" (4), "b" (1), "c" (str), "d" (len),
       "m"( *(const char (*)[])str )       // "dummy" input instead of memory clobber
     );
    return len;
}

所有系统调用都在 EAX 中返回。值来自-4095 to -1(含)为负数errno代码,其他值均无错误。 (这适用于全局的所有 Linux 系统调用)。

如果您正在编写通用系统调用包装器,您可能需要一个"memory"破坏,因为不同的系统调用有不同的指针操作数,并且可能是输入或输出。看https://godbolt.org/z/GOXBue https://godbolt.org/z/GOXBue举个例子,如果你把它省略了,它就会崩溃,并且这个答案 https://stackoverflow.com/questions/34244185/looping-over-arrays-with-inline-assembly有关虚拟内存输入/输出的更多详细信息。

对于此输出操作数,您需要显式volatile-- 正好一个write每次系统调用asm语句在源中“运行”。否则,编译器可以假设它的存在只是为了计算其返回值,并且可以消除使用相同输入的重复调用,而不是编写多行。 (或者如果您没有检查返回值,则将其完全删除。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 GNU C 内联汇编中编写 Linux int 80h 系统调用包装器 [重复] 的相关文章

随机推荐