【问题标题】:Scan from stdin and print to stdout using inline assembly in gcc使用 gcc 中的内联汇编从标准输入扫描并打印到标准输出
【发布时间】:2014-10-10 06:54:36
【问题描述】:

如何在内联汇编 gcc 中从标准输入读取并写入标准输出,就像我们在 NASM 中那样:

_start:
mov ecx, buffer ;buffer is a data word initialised 0h in section .data
mov edx, 03
mov eax, 03 ;read
mov ebx, 00 ;stdin
int 0x80
;Output the number entered
mov eax, 04 ;write
mov ebx, 01 ;stdout
int 0x80

我尝试在内联汇编中从标准输入读取,然后将输入分配给 x:

#include<stdio.h>
int x;
int main()
{
    asm(" movl $5,  %%edx \n\t" " 
    movl $0,  %%ebx \n\t" " 
    movl $3,  %%eax \n\t" " 
    int $0x80 \n\t "
    mov %%ecx,x" 
    ::: "%eax", "%ebx", "%ecx", "%edx");

    printf("%d",x);  
    return 0;
}

但是它没有这样做。

syscall from within GCC inline assembly

此链接包含一个只能将单个字符打印到标准输出的代码。

【问题讨论】:

  • 我不在linux上,所以无法测试。但是,我想到了一些想法:1)ebx 是一个文件句柄。这向我表明,如果在调用时没有准备好输入,它可能会返回零字节而不是等待输入。您是否使用a.out &lt; foo.txt 测试可执行文件? 2)我相信 ecx 应该是一个缓冲区,而不是一个返回单个字节的地方。 3)看起来 edx 应该是 ecx 缓冲区的大小(不确定你从哪里得到 5)。 4)这确实是低效编写的asm。我愿意重写它,但是 cmets 只能这么长而且我已经不在了
  • 您的 C 内联 asm 版本没有缓冲区,也没有在 int $0x80 之前设置 %ecx。而int $0x80 返回值在%eax,而不是%ecx...stackoverflow.com/questions/2535989/…
  • 请参阅 What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?,了解为什么当缓冲区是堆栈上的本地缓冲区(不是 static 或全球)。

标签: c gcc nasm inline-assembly


【解决方案1】:

此代码完全基于我对 linux 参考资料的阅读。我不在linux上,所以我无法测试它,但它应该非常接近。我会使用重定向对其进行测试:a.out

#include <stdio.h>

#define SYS_READ 3

int main()
{
   char buff[10]; /* Declare a buff to hold the returned string. */
   ssize_t charsread; /* The number of characters returned. */

   /* Use constraints to move buffer size into edx, stdin handle number
      into ebx, address of buff into ecx.  Also, "0" means this value
      goes into the same place as parameter 0 (charsread).  So eax will
      hold SYS_READ (3) on input, and charsread on output.  Lastly, you
      MUST use the "memory" clobber since you are changing the contents
      of buff without any of the constraints saying that you are.

      This is a much better approach than doing the "mov" statements
      inside the asm.  For one thing, since gcc will be moving the 
      values into the registers, it can RE-USE them if you make a 
      second call to read more chars. */

   asm volatile("int $0x80" /* Call the syscall interrupt. */
      : "=a" (charsread) 
      : "0" (SYS_READ), "b" (STDIN_FILENO), "c" (buff), "d" (sizeof(buff))
      : "memory", "cc");

    printf("%d: %s", (int)charsread, buff);

    return 0;
}

在下面回应 Aanchal Dalmia 的 cmets:

1) 正如 Timothy 下面所说,即使您不使用返回值,也必须让 gcc 知道 ax 寄存器正在被修改。换句话说,删除“=a”(字符读取)是不安全的,即使它看起来有效。

2) 我对你的观察感到非常困惑,除非 buff 是全局的,否则这段代码将无法工作。现在我已经安装了 linux,我能够重现错误并且我怀疑我知道问题所在。我敢打赌你在 x64 系统上使用int 0x80。这不是你应该在 64 位中调用内核的方式。

这里有一些替代代码,展示了如何在 x64 中执行此调用。请注意,函数编号和寄存器与上面的示例不同(参见http://blog.rchapman.org/post/36801038863/linux-system-call-table-for-x86-64):

#include <stdio.h>

#define SYS_READ 0
#define STDIN_FILENO 0

int main()
{
   char buff[10]; /* Declare a buff to hold the returned string. */
   ssize_t charsread; /* The number of characters returned. */

   /* Use constraints to move buffer size into rdx, stdin handle number
      into rdi, address of buff into rsi.  Also, "0" means this value
      goes into the same place as parameter 0 (charsread).  So eax will
      hold SYS_READ on input, and charsread on output.  Lastly, I
      use the "memory" clobber since I am changing the contents
      of buff without any of the constraints saying that I am.

      This is a much better approach than doing the "mov" statements
      inside the asm.  For one thing, since gcc will be moving the 
      values into the registers, it can RE-USE them if you make a 
      second call to read more chars. */

   asm volatile("syscall" /* Make the syscall. */
      : "=a" (charsread) 
      : "0" (SYS_READ), "D" (STDIN_FILENO), "S" (buff), "d" (sizeof(buff))
      : "rcx", "r11", "memory", "cc");

    printf("%d: %s", (int)charsread, buff);

    return 0;
}

需要一个比我更好的 linux 专家来解释为什么 x64 上的 int 0x80 不能与堆栈变量一起使用。但是使用 syscall 确实有效,而且在 x64 上 syscall 比 int 更快。

编辑:有人向我指出内核clobbers rcx 和 r11 在系统调用期间。不考虑这一点可能会导致各种问题,所以我将它们添加到了clobber列表中。

【讨论】:

  • 非常感谢。它工作正常。然而,只需要一个小小的改变。 char 数组需要全局化。 ideone.com/LNonCE 再次感谢您。
  • 很高兴你能成功。由于我自己无法运行此程序,因此您的回复让我感到惊讶。 ideone 上的代码删除了"=a" (charsread)。这没有像我预期的那样返回读取的字符数吗?或者你只是不需要它?你是说如果没有 buff 是全局的,中断就不起作用(当然看起来应该如此)?或者只是因为其他原因你需要它全局?关于使用 int 0x80 的信息似乎很少,未来的 SO 读者可能想知道。
  • 是的,如果数组不是全局的,代码就不会打印任何东西。不知道原因。至于“=a”(charsread),我不需要它,因此将其删除。感谢您的帮助。
  • 有趣的事实:syscall 不会破坏"cc"。 Linux 保存/恢复 RFLAGS。 int 0x80 也一样。因此,这是 x86 / x86-64 的隐式 "cc" 破坏者在理论上可能导致错过优化的罕见情况之一:P
  • @PeterCordes 我认为syscall 指令本身修改了RFLAGS。如果在调用操作系统之前更改了值,那么让 linux 保存/恢复无关紧要。
猜你喜欢
  • 1970-01-01
  • 2011-09-14
  • 1970-01-01
  • 2013-05-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-19
  • 2021-09-09
  • 1970-01-01
相关资源
最近更新 更多