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



我正在尝试制作一个简单的汇编程序,它打印"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"将这个睡眠函数编译成汇编程序,然后使用"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"而不是睡眠似乎并不能解决问题。(我收到相同的错误消息,只是没有"也许你的意思是__Z5Sleepi"位。我也尝试使用_Sleep而不是睡眠,但没有成功。我做错了什么?如何正确使用此C++子例程并将其与我的汇编程序链接?到目前为止,我使用的方法来从头开始做这件事是错误的吗?

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

正如杰斯特指出的那样,问题源于两件事。一个是我需要将 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参数1。

所以我把push 1000改成了mov rdi, 1000

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

相关内容

  • 没有找到相关文章