在 MASM 中调用标准库函数
Calling a standard-library-function in MASM
我想以混合 C++/汇编的方式开始使用 MASM。
我目前正在尝试从汇编中的 PROC 调用标准库函数(例如 printf),然后我在 C++ 中调用它。
在我的 cpp 文件中声明 printf 的签名后,我的代码就可以工作了。但是我不明白为什么我必须这样做,以及我是否可以避免那样做。
我的 cpp 文件:
#include <stdio.h>
extern "C" {
extern int __stdcall foo(int, int);
}
extern int __stdcall printf(const char*, ...); // When I remove this line I get Linker-Error "LNK2019: unresolved external symbol"
int main()
{
foo(5, 5);
}
我的 asm 文件:
.model flat, stdcall
EXTERN printf :PROC ; declare printf
.data
tstStr db "Mult: %i",0Ah,"Add: %i",0 ; 0Ah is the backslash - escapes are not supported
.code
foo PROC x:DWORD, y:DWORD
mov eax, x
mov ebx, y
add eax, ebx
push eax
mov eax, x
mul ebx
push eax
push OFFSET tstStr
call printf
ret
foo ENDP
END
一些更新
为了回应评论,我尝试重新编写代码以符合 cdecl 调用约定。不幸的是,这并没有解决问题(代码在 extern
声明下运行良好,但没有抛出错误)。
但通过反复试验我发现,extern
似乎强制外部链接,即使不需要关键字,因为外部链接应该是函数声明的 default .
我可以通过在我的 cpp 代码中使用该函数来省略声明(即,如果在源文件中的某处添加一个 printf("[=16=]");
,则链接器可以正常工作并且一切正常。
新的(但不是真的更好)cpp 文件:
#include <stdio.h>
extern "C" {
extern int __cdecl foo(int, int);
}
extern int __cdecl printf(const char*, ...); // omiting the extern results in a linker error
int main()
{
//printf("[=12=]"); // this would replace the declaration
foo(5, 5);
return 0;
}
asm 文件:
.model flat, c
EXTERN printf :PROC
.data
tstStr db "Mult: %i",0Ah,"Add: %i",0Ah,0 ; 0Ah is the backslash - escapes are not supported
.code
foo PROC
push ebp
mov ebp, esp
mov eax, [ebp+8]
mov ebx, [ebp+12]
add eax, ebx
push eax
mov eax, [ebp+8]
mul ebx
push eax
push OFFSET tstStr
call printf
add esp, 12
pop ebp
ret
foo ENDP
END
这确实有点没有意义,不是吗?
链接器通常是非常愚蠢的东西。他们需要被告知目标文件需要 printf
。链接器无法从缺少的 printf
符号中找出这一点,这已经够愚蠢了。
当你写extern int __stdcall printf(const char*, ...);
时,C++编译器会告诉链接器它需要printf
。或者,这是正常的方式,编译器会在您实际调用 printf
时告诉链接器。但是你的 C++ 代码没有调用它!
汇编程序也很笨。您的汇编器显然无法告诉链接器它需要来自 C++ 的 printf
。
一般的解决方案是不要在汇编中做复杂的事情。这不是汇编的好处。从 C 到程序集的调用通常运行良好,从其他方式调用是有问题的。
我最好的猜测是,这与 Microsoft 从 VS 2015 开始重构 C 库和一些 C 库现在内联(包括 printf
)并且实际上不在默认 .lib
个文件。
我的猜测是在这个声明中:
extern int __cdecl printf(const char*, ...);
extern
强制将旧的遗留库包含在 link 过程中。这些库包含非内联函数 printf
。如果 C++ 代码不强制 MS linker 包含遗留 C 库,那么 MASM 代码对 printf
的使用将无法解决。
我相信这与 2015 年的这个 Whosebug and 有关。如果您想从 C++ 代码中删除 extern int __cdecl printf(const char*, ...);
,您不妨考虑将此行添加到您的 MASM 代码中:
includelib legacy_stdio_definitions.lib
如果您使用 CDECL 调用约定并将 C/C++ 与程序集混合使用,您的 MASM 代码将如下所示:
.model flat, C ; Default to C language
includelib legacy_stdio_definitions.lib
EXTERN printf :PROC ; declare printf
.data
tstStr db "Mult: %i",0Ah,"Add: %i",0 ; 0Ah is the backslash - escapes are not supported
.code
foo PROC x:DWORD, y:DWORD
mov eax, x
mov ebx, y
add eax, ebx
push eax
mov eax, x
mul ebx
push eax
push OFFSET tstStr
call printf
ret
foo ENDP
END
您的 C++ 代码将是:
#include <stdio.h>
extern "C" {
extern int foo(int, int); /* __cdecl removed since it is the default */
}
int main()
{
//printf("[=13=]"); // this would replace the declaration
foo(5, 5);
return 0;
}
在汇编代码中传递 includelib
行的替代方法是将 legacy_stdio_definitions.lib
添加到 Visual Studio 项目的 linker 选项的依赖项列表中,或者命令行选项,如果您手动调用 linker。
在您的 MASM 代码中调用约定错误
您可以阅读 CDECL calling convention for 32-bit Windows code in the Microsoft documentation as well as this Wiki article。 Microsoft 将 CDECL 调用约定总结为:
On x86 platforms, all arguments are widened to 32 bits when they are passed. Return values are also widened to 32 bits and returned in the EAX register, except for 8-byte structures, which are returned in the EDX:EAX register pair. Larger structures are returned in the EAX register as pointers to hidden return structures. Parameters are pushed onto the stack from right to left. Structures that are not PODs will not be returned in registers.
The compiler generates prologue and epilogue code to save and restore the ESI, EDI, EBX, and EBP registers, if they are used in the function.
最后一段对您的代码很重要。 ESI、EDI、EBX 和 EBP 寄存器是非易失性的,如果它们被修改,必须由被调用函数保存和恢复。您的代码损坏 EBX,您必须保存并恢复它。您可以通过在 PROC
语句中使用 USES
指令让 MASM 做到这一点:
foo PROC uses EBX x:DWORD, y:DWORD
mov eax, x
mov ebx, y
add eax, ebx
push eax
mov eax, x
mul ebx
push eax
push OFFSET tstStr
call printf
add esp, 12 ; Remove the parameters pushed on the stack for
; the printf call. The stack needs to be
; properly restored. If not done, the function
; prologue can't properly restore EBX
; (and any registers listed by USES)
ret
foo ENDP
uses EBX
告诉 MASM 生成额外的序言和结尾代码以在开始时保存 EBX 并在开始时恢复 EBX函数执行 ret
指令。生成的指令类似于:
0000 _foo:
0000 55 push ebp
0001 8B EC mov ebp,esp
0003 53 push ebx
0004 8B 45 08 mov eax,0x8[ebp]
0007 8B 5D 0C mov ebx,0xc[ebp]
000A 03 C3 add eax,ebx
000C 50 push eax
000D 8B 45 08 mov eax,0x8[ebp]
0010 F7 E3 mul ebx
0012 50 push eax
0013 68 00 00 00 00 push tstStr
0018 E8 00 00 00 00 call _printf
001D 83 C4 0C add esp,0x0000000c
0020 5B pop ebx
0021 C9 leave
0022 C3 ret
我想以混合 C++/汇编的方式开始使用 MASM。 我目前正在尝试从汇编中的 PROC 调用标准库函数(例如 printf),然后我在 C++ 中调用它。
在我的 cpp 文件中声明 printf 的签名后,我的代码就可以工作了。但是我不明白为什么我必须这样做,以及我是否可以避免那样做。
我的 cpp 文件:
#include <stdio.h>
extern "C" {
extern int __stdcall foo(int, int);
}
extern int __stdcall printf(const char*, ...); // When I remove this line I get Linker-Error "LNK2019: unresolved external symbol"
int main()
{
foo(5, 5);
}
我的 asm 文件:
.model flat, stdcall
EXTERN printf :PROC ; declare printf
.data
tstStr db "Mult: %i",0Ah,"Add: %i",0 ; 0Ah is the backslash - escapes are not supported
.code
foo PROC x:DWORD, y:DWORD
mov eax, x
mov ebx, y
add eax, ebx
push eax
mov eax, x
mul ebx
push eax
push OFFSET tstStr
call printf
ret
foo ENDP
END
一些更新
为了回应评论,我尝试重新编写代码以符合 cdecl 调用约定。不幸的是,这并没有解决问题(代码在 extern
声明下运行良好,但没有抛出错误)。
但通过反复试验我发现,extern
似乎强制外部链接,即使不需要关键字,因为外部链接应该是函数声明的 default .
我可以通过在我的 cpp 代码中使用该函数来省略声明(即,如果在源文件中的某处添加一个 printf("[=16=]");
,则链接器可以正常工作并且一切正常。
新的(但不是真的更好)cpp 文件:
#include <stdio.h>
extern "C" {
extern int __cdecl foo(int, int);
}
extern int __cdecl printf(const char*, ...); // omiting the extern results in a linker error
int main()
{
//printf("[=12=]"); // this would replace the declaration
foo(5, 5);
return 0;
}
asm 文件:
.model flat, c
EXTERN printf :PROC
.data
tstStr db "Mult: %i",0Ah,"Add: %i",0Ah,0 ; 0Ah is the backslash - escapes are not supported
.code
foo PROC
push ebp
mov ebp, esp
mov eax, [ebp+8]
mov ebx, [ebp+12]
add eax, ebx
push eax
mov eax, [ebp+8]
mul ebx
push eax
push OFFSET tstStr
call printf
add esp, 12
pop ebp
ret
foo ENDP
END
这确实有点没有意义,不是吗?
链接器通常是非常愚蠢的东西。他们需要被告知目标文件需要 printf
。链接器无法从缺少的 printf
符号中找出这一点,这已经够愚蠢了。
当你写extern int __stdcall printf(const char*, ...);
时,C++编译器会告诉链接器它需要printf
。或者,这是正常的方式,编译器会在您实际调用 printf
时告诉链接器。但是你的 C++ 代码没有调用它!
汇编程序也很笨。您的汇编器显然无法告诉链接器它需要来自 C++ 的 printf
。
一般的解决方案是不要在汇编中做复杂的事情。这不是汇编的好处。从 C 到程序集的调用通常运行良好,从其他方式调用是有问题的。
我最好的猜测是,这与 Microsoft 从 VS 2015 开始重构 C 库和一些 C 库现在内联(包括 printf
)并且实际上不在默认 .lib
个文件。
我的猜测是在这个声明中:
extern int __cdecl printf(const char*, ...);
extern
强制将旧的遗留库包含在 link 过程中。这些库包含非内联函数 printf
。如果 C++ 代码不强制 MS linker 包含遗留 C 库,那么 MASM 代码对 printf
的使用将无法解决。
我相信这与 2015 年的这个 Whosebug extern int __cdecl printf(const char*, ...);
,您不妨考虑将此行添加到您的 MASM 代码中:
includelib legacy_stdio_definitions.lib
如果您使用 CDECL 调用约定并将 C/C++ 与程序集混合使用,您的 MASM 代码将如下所示:
.model flat, C ; Default to C language
includelib legacy_stdio_definitions.lib
EXTERN printf :PROC ; declare printf
.data
tstStr db "Mult: %i",0Ah,"Add: %i",0 ; 0Ah is the backslash - escapes are not supported
.code
foo PROC x:DWORD, y:DWORD
mov eax, x
mov ebx, y
add eax, ebx
push eax
mov eax, x
mul ebx
push eax
push OFFSET tstStr
call printf
ret
foo ENDP
END
您的 C++ 代码将是:
#include <stdio.h>
extern "C" {
extern int foo(int, int); /* __cdecl removed since it is the default */
}
int main()
{
//printf("[=13=]"); // this would replace the declaration
foo(5, 5);
return 0;
}
在汇编代码中传递 includelib
行的替代方法是将 legacy_stdio_definitions.lib
添加到 Visual Studio 项目的 linker 选项的依赖项列表中,或者命令行选项,如果您手动调用 linker。
在您的 MASM 代码中调用约定错误
您可以阅读 CDECL calling convention for 32-bit Windows code in the Microsoft documentation as well as this Wiki article。 Microsoft 将 CDECL 调用约定总结为:
On x86 platforms, all arguments are widened to 32 bits when they are passed. Return values are also widened to 32 bits and returned in the EAX register, except for 8-byte structures, which are returned in the EDX:EAX register pair. Larger structures are returned in the EAX register as pointers to hidden return structures. Parameters are pushed onto the stack from right to left. Structures that are not PODs will not be returned in registers.
The compiler generates prologue and epilogue code to save and restore the ESI, EDI, EBX, and EBP registers, if they are used in the function.
最后一段对您的代码很重要。 ESI、EDI、EBX 和 EBP 寄存器是非易失性的,如果它们被修改,必须由被调用函数保存和恢复。您的代码损坏 EBX,您必须保存并恢复它。您可以通过在 PROC
语句中使用 USES
指令让 MASM 做到这一点:
foo PROC uses EBX x:DWORD, y:DWORD
mov eax, x
mov ebx, y
add eax, ebx
push eax
mov eax, x
mul ebx
push eax
push OFFSET tstStr
call printf
add esp, 12 ; Remove the parameters pushed on the stack for
; the printf call. The stack needs to be
; properly restored. If not done, the function
; prologue can't properly restore EBX
; (and any registers listed by USES)
ret
foo ENDP
uses EBX
告诉 MASM 生成额外的序言和结尾代码以在开始时保存 EBX 并在开始时恢复 EBX函数执行 ret
指令。生成的指令类似于:
0000 _foo: 0000 55 push ebp 0001 8B EC mov ebp,esp 0003 53 push ebx 0004 8B 45 08 mov eax,0x8[ebp] 0007 8B 5D 0C mov ebx,0xc[ebp] 000A 03 C3 add eax,ebx 000C 50 push eax 000D 8B 45 08 mov eax,0x8[ebp] 0010 F7 E3 mul ebx 0012 50 push eax 0013 68 00 00 00 00 push tstStr 0018 E8 00 00 00 00 call _printf 001D 83 C4 0C add esp,0x0000000c 0020 5B pop ebx 0021 C9 leave 0022 C3 ret