汇编调用C函数及汇编逻辑执行问题(含64位适配)
我正在开展一个汇编代码与编程语言集成的项目,目的是深入理解机器代码抽象并应用于大型项目。使用VSCode编辑代码,通过MSVC工具及开发者命令行将各文件编译为.obj文件。
最初的16位汇编尝试问题
最初编写的汇编代码应在执行时向控制台打印Select a menu option 1 - 4:$及4个菜单项,但链接后仅执行C文件中的测试字符串。
编译与链接命令
- 编译main.asm:
ml /c main.asm - 编译procedures.c:
cl /c procedures.c - 链接命令:
link main.obj procedures.obj /OUT:spectra.exe /SUBSYSTEM:CONSOLE /ENTRY:central /LIBPATH:"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.42.34433\lib\x86" ucrt.lib libcmtd.lib /NODEFAULTLIB:LIBCMT
代码
main.asm
.model flat, stdcall EXTERN sayHello: PROC .data ; Menu strings menu_msg DB "Select a menu option 1 - 4:$" .code central PROC near stdcall ; Print menu options lea dx, menu_msg ; Load address of menu message mov ah, 09h ; DOS interrupt for printing strings int 21h call sayHello central ENDP END
procedures.c
#include <stdio.h> #include <math.h> /* Testing & Debugging Functions */ __declspec(dllexport) void sayHello() { printf("Hello from C!\n"); }
尝试了10种不同的链接方式,包括指定/SUBSYSTEM、/ENTRY及MSVC文件夹中的具体库,但始终仅执行C文件中的代码,汇编文件逻辑从未运行。如何才能让汇编代码执行并向控制台打印消息?
64位汇编更新后的问题
意识到之前是在64位Windows终端中运行16位汇编,于是更新了代码,链接后程序终于能正常运行,但仍无法实现控制台打印。C文件保持不变,汇编代码改为调用消息框显示文本:
编译与链接命令
- 汇编编译命令:
ml64 /c /Fo main2.obj main2.asm - C代码编译命令:
cl /c procedures.c /Fo:procedures.obj - 链接命令:
link /OUT:spectra.exe main2.obj procedures.obj /SUBSYSTEM:CONSOLE /ENTRY:main /LIBPATH:"C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\lib\x64" user32.lib libucrt.lib libvcruntime.lib
main2.asm代码
; main2.asm (for 64-bit) option casemap:none EXTERN MessageBoxA: PROC EXTERN ExitProcess: PROC EXTERN sayHello: PROC .data menu_msg db "Select a menu option:", 0 ; Title of the message box option1 db "1. Add two numbers", 0 option2 db "2. Subtract two numbers", 0 option3 db "3. Multiply two numbers", 0 option4 db "4. Divide two numbers", 0 ; Combine the menu options in the message body combined_msg db "1. Add two numbers", 0, "2. Subtract two numbers", 0 msg_line2 db "3. Multiply two numbers", 0, "4. Divide two numbers", 0 .code main PROC ; Align the stack to 16-byte boundary sub rsp, 8 ; Adjust stack for 16-byte alignment ; Call MessageBoxA with the new title and combined message mov rcx, 0 ; uType (0 = OK button) lea rdx, option1 ; lpCaption (title of the message box) lea r8, menu_msg ; lpText (message with menu options) mov r9, 0 ; hWnd (no parent window) call MessageBoxA ; Call MessageBoxA API ; Add another message box with second part if needed lea r8, msg_line2 ; Update text to the next part call MessageBoxA ; Call MessageBoxA API again ; Call sayHello procedure (assuming sayHello is implemented in C) call sayHello ; Exit the program mov rcx, 0 ; Exit code (0) call ExitProcess ; Call ExitProcess API ; Clean up the stack add rsp, 8 ; Restore stack main ENDP END
解决方法
1. 最初16位汇编问题的根源
int 21h是16位DOS中断,无法在32位/64位Windows的PE可执行文件中运行。即使指定/SUBSYSTEM:CONSOLE,现代Windows也不会模拟DOS环境执行这类中断,因此汇编中的打印代码不会生效,程序直接跳过该逻辑进入sayHello调用。
2. 64位汇编的问题修正
(1)消息框参数顺序错误
64位Windows调用约定(fastcall)下,MessageBoxA的参数顺序为:
rcx:父窗口句柄hWndrdx:消息文本lpTextr8:窗口标题lpCaptionr9:消息框类型uType
原代码参数顺序完全颠倒,导致消息框无法正确显示。修正后的调用代码:
; 正确调用MessageBoxA mov rcx, 0 ; hWnd = NULL lea rdx, combined_msg ; lpText = 组合后的菜单内容 lea r8, menu_msg ; lpCaption = 窗口标题 mov r9, 0 ; uType = MB_OK call MessageBoxA
同时,combined_msg的定义需调整:字符串用0结尾后无法拼接,应使用换行符0Ah连接成完整字符串,示例:
combined_msg db "1. Add two numbers", 0Ah, "2. Subtract two numbers", 0Ah, db "3. Multiply two numbers", 0Ah, "4. Divide two numbers", 0
(2)实现控制台打印
若需要控制台打印而非消息框,可采用两种方式:
- 调用C语言的
printf:在汇编中声明EXTERN printf: PROC,按64位调用约定传递参数,示例:
EXTERN printf: PROC .data menu_fmt db "Select a menu option 1 - 4:", 0Ah, db "1. Add two numbers", 0Ah, db "2. Subtract two numbers", 0Ah, db "3. Multiply two numbers", 0Ah, db "4. Divide two numbers", 0Ah, 0 .code main PROC sub rsp, 32 ; 预留32字节阴影空间并保证栈对齐 lea rcx, menu_fmt ; 第一个参数:格式化字符串 call printf add rsp, 32 call sayHello mov rcx, 0 call ExitProcess main ENDP
- 调用Windows API
WriteConsoleA:直接使用系统API打印到控制台,无需依赖C标准库。
(3)栈对齐问题
64位Windows要求调用API前栈必须是16字节对齐。原代码中sub rsp,8仅调整了8字节,无法满足要求,正确做法是预留32字节阴影空间(即使只传4个参数),同时保证栈对齐:
sub rsp, 32 + 8 ; 32字节阴影空间 + 8字节对齐调整
内容的提问来源于stack exchange,提问作者Ragnarok1337

