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

时间:2018-09-24 14:42:23

标签: c++ gcc assembly x86 nasm

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

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

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

然后我使用“ 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

这两个文件都保存到桌面/程序中。我正在尝试使用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。

1 个答案:

答案 0 :(得分:7)

正如杰斯特(Jester)指出的那样,问题出在两点。一种是我需要将Sleep.cpp程序更改为使用外部“ 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链接,以链接诸如std::__1::this_thread::sleep_for之类的C ++标准库以及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符号名称前导_

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

在x86-64 System V调用约定(在OS X,Linux和Windows以外的所有其他版本上使用)中,rdi is parameter 1

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

进行所有这些更改之后,程序将正确编译并完全执行应做的操作:打印Hello !,等待1秒钟,然后再次打印。