【问题标题】:syscall from within GCC inline assembly [duplicate]来自 GCC 内联程序集的系统调用 [重复]
【发布时间】:2011-02-26 20:18:49
【问题描述】:

是否可以使用系统调用从内联汇编块中写入单个字符?如果是这样,如何?它应该看起来像这样:

__asm__ __volatile__
                    (
                     " movl $1,  %%edx \n\t"
                     " movl $80, %%ecx \n\t"
                     " movl $0,  %%ebx \n\t"
                     " movl $4,  %%eax \n\t"
                     " int $0x80       \n\t"
                     ::: "%eax", "%ebx", "%ecx", "%edx"
                    );

$80 在 ascii 中是 'P',但什么也不返回。

任何建议都非常感谢!

【问题讨论】:

标签: c linux gcc inline-assembly system-calls


【解决方案1】:

IIRC,您的示例中有两件事是错误的。 首先,您正在使用 mov $0, %ebx 写入标准输入 其次, write 将指针作为第二个参数,因此要写入单个字符,您需要将该字符存储在内存中的某个位置,您不能将值直接写入 %ecx

例如:

.data
char: .byte 80
.text
mov $char, %ecx

我只在 Linux 中做过纯 asm,从不使用 gcc 内联,你不能将数据放到程序集的中间,所以我不确定你如何使用内联程序集获取指针。

编辑:我想我只是记得怎么做。您可以将 'p' 压入堆栈并使用 %esp

pushw $80
movl %%esp, %%ecx
... int $0x80 ...
addl $2, %%esp

【讨论】:

  • 在 C 中我与char *p = 'P'; write(1, &p, 1); 做“相同”,所以标准输出为 1
  • char *p = 'P' 我想应该是 char p = 'P'。
  • write()const void *buf 作为第二个参数
  • 刚刚想到,你当然是对的!应该是char p
  • 谢谢!我只是想知道这将如何与push 一起工作。使用pushpushb 有什么区别?完成后我必须弹出它们吗?
【解决方案2】:

类似


char p = 'P';

int main()
{
__asm__ __volatile__
                    (
                     " movl $1,  %%edx \n\t"
                     " leal p , %%ecx \n\t"
                     " movl $0,  %%ebx \n\t"
                     " movl $4,  %%eax \n\t"
                     " int $0x80       \n\t"
                     ::: "%eax", "%ebx", "%ecx", "%edx"
                    );
}

添加:注意我使用lea将char的有效地址加载到ecx寄存器中;对于ebx 的价值,我尝试了 $0 和 $1,但它似乎仍然可以工作......

避免使用外部字符

int main()
{
__asm__ __volatile__
                    (
                     " movl $1,  %%edx \n\t"
                     " subl $4, %%esp \n\t"
                     " movl $80, (%%esp)\n\t"
                     " movl %%esp, %%ecx \n\t"
                     " movl $1,  %%ebx \n\t"
                     " movl $4,  %%eax \n\t"
                     " int $0x80       \n\t"
                     " addl $4, %%esp\n\t"
                     ::: "%eax", "%ebx", "%ecx", "%edx"
                    );
}

注意:它的工作原理是英特尔处理器的字节序! :D

【讨论】:

  • 啊。我还不太擅长组装,所以我基本上不知道 lea 之类的东西。另外,0 和 1 是标准错误,而标准输出分别是。 iirc
  • 当然可以使用栈; gcc 生成的代码使用之前准备好的 ebp;我们可以暂时只使用 esp;我修改了答案,看看吧。
  • 太棒了!!也可以使用push 和堆栈指针来完成吗?
  • 由于使用 push 的建议,您可以更好地编写我的代码(但 pushb 不存在,无论如何它会“取消对齐”堆栈......):删除 subl $4,%esp 而是的movl $80, (%esp)pushl $80;其余的都是一样的。
  • 呃,现在看到你的评论了;是的,如上所述,您可以使用 pushl 并避免手动减少堆栈; esp 是堆栈指针;要使用基/帧指针 ebp 或您如何调用它,您应该保留 C 用于局部变量的先前值;...所以毕竟它不是那么有用,在这种情况下 esp 更容易跨度>
【解决方案3】:

您可以使用特定于体系结构的约束将参数直接放置在特定的寄存器中,而无需在内联汇编中使用movl 指令。此外,您还可以使用& 运算符来获取字符的地址:

#include <sys/syscall.h>

void sys_putc(char c) {
    // write(int fd, const void *buf, size_t count); 
    int ret;
    asm volatile("int $0x80" 
            : "=a"(ret)                    // outputs
            : "a"(SYS_write), "b"(1), "c"(&c), "d"(1)  // inputs
            : "memory");                   // clobbers
}

int main(void) {
    sys_putc('P');
    sys_putc('\n');
}

(编者注:需要"memory" clobber,或以其他方式告诉编译器&amp;c 指向的内存已被读取。How can I indicate that the memory *pointed* to by an inline ASM argument may be used?


(在这种情况下,需要=a(ret) 来指示系统调用破坏了EAX。我们不能将EAX 列为破坏器,因为我们需要一个输入操作数来使用该寄存器。@ 987654330@ 约束类似于"r" 但只能选择 AL/AX/EAX/RAX。)

$ cc -m32 sys_putc.c && ./a.out
P

您还可以返回系统调用返回的写入字节数,并使用"0" 作为约束再次指示EAX

int sys_putc(char c) {
    int ret;
    asm volatile("int $0x80" : "=a"(ret) : "0"(SYS_write), "b"(1), "c"(&c), "d"(1) : "memory");
    return ret;
}

请注意,出错时,系统调用返回值将是-errno 代码,如-EBADF(错误文件描述符)或-EFAULT(错误指针)。

普通的 libc 系统调用包装函数检查返回值是否为无符号 eax &gt; -4096UL 并设置 errno + return -1


还请注意,需要使用-m32 进行编译:64 位 syscall ABI 使用不同的调用号(和寄存器),但此 asm 对调用 32 位 ABI 的慢速方式进行了硬编码,@ 987654342@.

在 64 位模式下编译会得到 sys/syscall.h 用 64 位呼叫号码定义 SYS_write,这会破坏此代码。即使您使用了正确的数字,64 位堆栈地址也是如此。 What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? - 不要那样做。

【讨论】:

  • 在第一个示例中,我认为它是一个错误。一个问题是int $0x80 改变了EAX,因为它包含一个返回值。您需要确保您有某种输出约束,否则优化器可能只是假设 EAX 包含扩展程序集模板中的 SYS_write 的值。您的第二个代码示例正确完成。
  • 好点,谢谢!我认为在第一个版本的clobber 列表中添加“%eax”就足够了? (我确实想包含一个没有输出的版本。)
  • 不要认为 EAX clobber 会在那里工作,因为编译器可能会假设它与使用 "a" 的仅输入约束冲突。我怀疑会引发某种错误。
  • 你是对的。不久前有一个针对 gcc 的错误,他们的建议是使用虚拟输出变量。 (有人建议在输入操作数中允许&amp;,但没有得到满足。)gcc.gnu.org/bugzilla/show_bug.cgi?id=43998
  • 在第一个版本中处理 EAX clobber 问题的最佳方法是创建一个虚拟输出变量。如果您使用优化进行编译,如果它的值从未使用过,优化器将能够将其删除。只需记住将修改后的版本标记为 volatile,因为优化器会认为没有已知的副作用,它可能会完全优化输出)。这会起作用:int retval; asm volatile("int $0x80" : "=a"(retval): "0"(SYS_write), "b"(1), "c"(&amp;c), "d"(1));。 retval 的输出将保持未使用状态。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-15
  • 1970-01-01
  • 2012-04-30
  • 1970-01-01
  • 2011-05-27
  • 2016-01-13
  • 2021-10-13
相关资源
最近更新 更多