c++ - 如何将 C++ 子例程链接到 x86 汇编程序?

标签 c++ gcc assembly x86 nasm

我正在尝试制作一个打印“Hello!”的简单汇编程序。一次,等待一秒钟,然后再次打印。由于 sleep 函数在汇编中相对复杂,而且我不太擅长,所以我决定使用 C++ 来制作 Sleep 子例程。这是 C++ 程序:

// Sleep.cpp
#include <thread>
#include <chrono>

void Sleep(int TimeMs) {
    std::this_thread::sleep_for(std::chrono::milliseconds(TimeMs));
}

然后我使用“gcc -S Sleep.cpp”将这个 sleep 函数编译成一个汇编程序,然后使用“gcc -c Sleep.s”将它编译成一个目标文件

我正在尝试从程序集调用此 C++ 子例程。我听说您通过将参数压入堆栈来为 C++ 子例程提供参数,这是我到目前为止的汇编代码:

        global    _main
        extern    _puts
        extern    Sleep
        section   .text
_main:    
        push    rbp
        mov     rbp,    rsp
        sub     rsp,    32


        ;Prompt user:
        lea     rdi,    [rel prompt]        ; First argument is address of message
        call    _puts                       ; puts(message)

        push    1000 ; Wait 1 second (Sleep time is in milliseconds)
        call    Sleep

        lea     rdi,    [rel prompt] ; Print hello again
        call    _puts

        xor     rax,    rax                 ; Return 0
        leave
        ret

        section   .data

prompt:
    db      "Hello!", 0

这两个文件都保存到桌面/程序。我正在尝试使用 NASM 和 GCC 编译它,我的编译器调用是:

nasm -f macho64 Program.asm && gcc Program.o Sleep.s -o Program && ./Program

但是我得到了错误:

"Sleep", referenced from:
      _main in Program.o
     (maybe you meant: __Z5Sleepi)
  "std::__1::this_thread::sleep_for(std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000000l> > const&)", referenced from:
      void std::__1::this_thread::sleep_for<long long, std::__1::ratio<1l, 1000l> >(std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000l> > const&) in Sleep-7749e0.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

将代码更改为“extern __Z5Sleepi”并调用“__Z5Sleepi”而不是 Sleep 似乎无法解决问题。 (我收到相同的错误消息,只是没有“也许你的意思是 __Z5Sleepi”位。我也尝试使用 _Sleep 而不是 Sleep 但没有成功。)我做错了什么?如何正确使用此 C++ 子例程并将其与我的汇编程序链接?到目前为止,我使用的方法是否从头开始就错了?

非常感谢任何帮助,浏览堆栈溢出,似乎有很多关于此的问题,但实际上没有一个进入链接过程。 (而且他们似乎在询问将汇编与 C++ 链接,而不是将 C++ 与汇编链接。)我正在使用 NASM 和 GCC 进行编译,我的平台是 Mac OSX。

最佳答案

正如 Jester 所指出的,问题源于两件事。一是我需要更改 Sleep.cpp 程序以使用 extern "C",如下所示:

#include <thread>
#include <chrono>

extern "C" void Sleep(int TimeMS);
extern "C"
{
   void Sleep(int TimeMs) {
    std::this_thread::sleep_for(std::chrono::milliseconds(TimeMs));
   }
}

这可以防止编译器对函数进行“名称修改”。这样做将 Sleep() 的编译函数名称从“__Z5Sleepi”更改为“_Sleep”并减轻了我的链接器错误。

然后我更改了我的编译器调用以链接 g++ 而不是 gcc,以链接 C++ 标准库以实现 std::__1::this_thread 等函数::sleep_for,以及 C 标准库。

nasm -f macho64 Program.asm && g++ Program.o Sleep.o -o Program && ./Program

在此之后,编译器告诉我需要将 extern Sleep 更改为 extern _Sleep 并且与 call _Sleep 大致相同,而不是 call Sleep,因为 OS X 使用前导 _ 修饰 C 符号名称。

在我完成所有这些之后,程序正确链接但产生了段错误。 Jester 指出这是因为 x86-64 调用约定不会在堆栈上传递整数/指针函数参数。您使用寄存器的方式与调用 _printf 或 _puts 的方式相同,因为这些库函数也遵循相同的标准调用约定。

在 x86-64 System V 调用约定中(用于 OS X、Linux 和 Windows 以外的所有系统),rdi is parameter 1 .

所以我将 push 1000 更改为 mov rdi, 1000

完成所有这些更改后,程序可以正确编译并执行它应该执行的操作:打印 Hello!,等待 1 秒,然后再次打印。

关于c++ - 如何将 C++ 子例程链接到 x86 汇编程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52481831/

相关文章:

c++ - 在应用程序中嵌入 C++ 编译器

c++ - 如何使用 Win32 API 对单选框按钮进行分组

c++ - 使用 std::vector::erase 和 const_iterators

c++ - std::exchange 与 VC++ 和 gcc 的工作方式不同

c++ - 检查 __m128i 是否为零?

assembly - 为什么从AT&T切换到Intel语法会使本教程使用GAS出现段错误?

assembly - VGA 文本模式下 Intel GMA950 专用缓冲区中字节的含义

c++ - 归并排序算法辅助

c++ - Imshow 视频的特定部分

linux - gcc优化级别之间有什么区别?