【问题标题】:Calling Assembly code from C++从 C++ 调用汇编代码
【发布时间】:2016-06-01 13:37:00
【问题描述】:

我正在尝试编写自己的 I/O 库,所以第一步就是能够输出一个字符。到目前为止,这只需要在 Linux 上工作。我在 x86(使用 Intel 语法)中编写了我认为是可靠的_putc: 标签,并将它放在“io.h”中,然后从我的主文件“test.cpp”中调用它。下面是 test.cpp 的内容:

#include "io.h"
extern void _putc(char) __asm__("_putc");
int main(int argc, char** argv){
    _putc('c');
    return 0;
}

这是我的 io.h:

asm(
"\n.intel_syntax noprefix"
"\n.globl _putc"
"\n_putc:"
"\n\tpush [ebp]"
"\n\tmov ebp,esp"
"\n\tmov eax,4"
"\n\tmov ebx,0"
"\n\tmov ecx,[ebp+8]"
"\n\tmov edx,1"
"\n\tint 0x80"
"\n\tret"
"\n.att_syntax" ;return to g++'s at&t syntax
);

我正在使用 G++

在 x86_64 Linux 操作系统上开发它

我用这个来编译:

 g++ -o test test.cpp -Wall

运行时会立即出现段错误。我认为我的问题是调用约定,但没有办法绝对确定。我尝试将程序集包装在一个函数 (void _putc(char c)) 中并删除全局标签,但这没有帮助。我还尝试推送 ebp 的内容而不是它指向的内容,但它无法编译。

注意:这只能与主文件中的行 #include io.h 一起工作(我可以在此工作后移动 extern void _putc(char) __asm__("_putc");),并且它必须与 C++ 一起工作,而不仅仅是C。另外,请不要跟我讲头文件中的函数定义,这正是我正在做的。

【问题讨论】:

  • 有一种方法可以绝对确定:使用调试器。
  • 我做到了。它在push [ebp] 出现段错误。
  • 这是一个好点,但它一开始就出现了段错误,没有处理中断。所以肯定还有另一个问题。另外,我不知道什么是“clobbering”或者如何告诉汇编程序我正在这样做,你能解释一下吗?
  • 当然push [ebp] 获取ebp 指向的内存地址的值并将其推送。但是,我认为您的意思是用push ebp 推动ebp 的值。如果您在push ebp 上遇到错误,那么我怀疑您是编译为 64 位还是 32 位代码?
  • 这样的事情也是等待 GCC 内联汇编器发生的灾难:mov ecx,[ebp+8]。您假设putc 的参数位于堆栈上的特定位置。但是,如果您的代码得到优化,那么您感兴趣的值很可能被放入寄存器中。

标签: c++ linux g++ x86-64 inline-assembly


【解决方案1】:

对于 64 位代码,您确实需要考虑使用 syscall 而不是 int 0x80。系统调用使用 64 位约定,系统调用号与 32 位代码中的 int 0x80 不同。您可以在此table 中了解 64 位系统调用。 sys_writesys_read 调用如下所示:

%rax  System call %rdi              %rsi             %rdx         %r10      %r8   %r9
0     sys_read    unsigned int fd   char *buf        size_t count         
1     sys_write   unsigned int fd   const char *buf  size_t count

如果您想了解如何在 64 位 Linux 代码中传递参数,则需要熟悉 System V 64-Linux ABI。除其他事项外,如果您打算调用其他函数或系统调用,则通常必须将堆栈对齐到 16 字节边界。

您的代码还存在将字符传递给sys_write 的缺陷。 sys_write 需要一个指向包含字符的缓冲区的地址,而不是字符本身。

这是一个可行的代码转储:

asm(
"\n.intel_syntax noprefix"
"\n.globl _putc"
"\n_putc:"
"\n\tadd rsp, -8"   // Allocate space on the stack to store the character passed
                    // Also aligns stack back to 16-bye boundary 
                    // with return address already on stack
"\n\tmov [rsp],rdi" // Move the character passed in RDI to memory(stack) for sys_write
"\n\tmov eax,1"     // With 64-bit syscalls RAX = 1 for sys_write
"\n\txor edi,edi"   // 1st parameter to syscall is in RDX. FD = 0
"\n\tlea rsi,[rsp]" // We need to pass pointer to memory containing our char
"\n\tmov edx,1"     // RDX = 1 character to print. Could reuse EAX instead of 1 for src
"\n\tsyscall"       // Make syscall (using syscall instead of int 0x80 for 64-bit)

"\n\tadd rsp, 8"    // Restore stack to state right after function entry
"\n\tret"
"\n.att_syntax prefix" //return to g++'s at&t syntax
);

【讨论】:

  • @RobertHarvey:感谢您的更新(包括语法)。这是一个快速的答案,我急于打字。不过,我不认为这是一个高质量的答案,而且我觉得按照我通常的标准,它可能值得被否决。对我来说,这是一个加了糖的代码转储。诚然,如果您想触及所有问题领域,这个问题有点宽泛。
猜你喜欢
  • 2013-04-16
  • 2011-09-04
  • 1970-01-01
  • 1970-01-01
  • 2017-01-19
  • 2012-03-04
  • 2016-09-07
  • 2017-02-18
  • 1970-01-01
相关资源
最近更新 更多