我正在尝试制作一个打印“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/