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

Sec*_*Sam 5 c++ x86 assembly gcc nasm

我正在尝试制作一个简单的汇编程序,打印出"Hello!" 一次,等待一秒钟,然后再打印出来.由于睡眠功能在组装时相对复杂,而且我不擅长它,所以我决定使用C++来制作Sleep子程序.这是C++程序:

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

void Sleep(int TimeMs) {
    std::this_thread::sleep_for(std::chrono::milliseconds(TimeMs));
}
Run Code Online (Sandbox Code Playgroud)

然后我使用"gcc -S Sleep.cpp"将这个睡眠函数编译成汇编程序,然后使用"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
Run Code Online (Sandbox Code Playgroud)

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

nasm -f macho64 Program.asm && gcc Program.o Sleep.s -o Program && ./Program
Run Code Online (Sandbox Code Playgroud)

但我得到错误:

"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)
Run Code Online (Sandbox Code Playgroud)

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

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

Sec*_*Sam 7

正如杰斯特所指出的那样,问题产生于两件事.一个是我需要更改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));
   }
}
Run Code Online (Sandbox Code Playgroud)

这可以防止编译器"命名修改"该函数.这样做会将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
Run Code Online (Sandbox Code Playgroud)

在此之后,编译器告诉我,我需要更改extern Sleepextern _Sleep更多,call _Sleep而不是call Sleep,因为OS X用前导来装饰C符号名称_.

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

在x86-64 System V调用约定(用于OS X,Linux和Windows以外的所有内容)中,rdi参数为1.

所以我改变了push 1000mov rdi, 1000

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