【问题标题】:Writing a Linux int 80h system-call wrapper in GNU C inline assembly [duplicate]在 GNU C 内联汇编中编写 Linux int 80h 系统调用包装器 [重复]
【发布时间】:2011-07-05 03:10:05
【问题描述】:

我正在尝试使用内联汇编... 我阅读了此页面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 */
    );
}

【问题讨论】:

  • 你想传递一个输出参数,连同 str,len ?,或者你的意思是你想发送一个你想写入 str 的文件描述符?
  • 我只想传递我的字符串指针和我的字符串长度......就像它在汇编代码中看起来一样......所以,只使用系统调用,我可以将我的字符串打印到标准输出.

标签: c linux assembly x86 inline-assembly


【解决方案1】:

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

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

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

您还需要告诉编译器内联 asm 将或可能读取或写入输入操作数指向的内存;这不是暗示的。

因此,您的 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 和 % 在寄存器名称之前。

现在这可以工作了,但效率低下,因为它会包含许多额外的 mov。一般来说,你永远不应该在 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,但是当真正的目的不是计算某些东西时,让它显式并没有什么坏处;它是为了像系统调用这样的副作用。)

编辑

最后一点——这个函数正在执行一个 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-1(含)的值是负的errno 代码,其他值是非错误的。 (这适用于所有 Linux 系统调用)。

如果您正在编写通用系统调用包装器,您可能需要"memory" clobber,因为不同的系统调用具有不同的指针操作数,并且可能是输入或输出。请参阅https://godbolt.org/z/GOXBue 了解如果您忽略它会中断的示例,this answer 了解有关虚拟内存输入/输出的更多详细信息。

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

【讨论】:

  • 您在转换为 AT&T 时错过了一件事情:常量前面需要一个 $。否则它们是内存引用,我很确定你不想执行地址 0x80 发生的任何中断。
  • 您可以将 intel 语法与“.intel_syntax”指令一起使用。
  • 此答案中的第三个示例不正确,因为未通知编译器 eax 已更改,因此将假定它没有更改。
  • @Zboson:是的,但你需要以某种方式告诉编译器int 0x80 修改了 EAX。因此,如果您想避免输出,您会遇到一个clobber 和一个"mov $" __NR_write ", %%eax" 而不是输入约束。对于复制用户空间内存的系统调用(比如read(2),而不是write(2)),你需要一个虚拟内存输出操作数来告诉编译器它. 这需要一个虚拟输入,或者一个"memory" clobber,否则在调用之前存储到缓冲区是可以优化掉的死存储。
  • @Alexander:不要在 asm 模板的顶部/底部使用 .intel_syntax noprefix / .att_syntax;而是使用-masm=intel 编译。尽管在这种情况下,您没有像 %0 这样的操作数替换模板(您希望编译器使用 eax4,而不是 %eax$4),它可能没关系。 How to set gcc to use intel syntax permanently?
猜你喜欢
  • 2016-01-16
  • 1970-01-01
  • 1970-01-01
  • 2015-04-30
  • 1970-01-01
  • 2014-06-19
  • 2018-11-08
  • 2022-08-04
  • 2020-06-07
相关资源
最近更新 更多