Win64 NASM调用CommandLineToArgvW出现段错误问题求助
在Win64环境下,使用NASM按照标准Windows调用约定调用CommandLineToArgvW函数时触发段错误,但功能完全等效的C程序运行正常。本人是NASM新手,怀疑问题出在调用约定的理解上,遵循的教程中定义的调用约定(栈上分配32字节shadow space,RCX为第一个参数,RDX为第二个,返回结果存于RAX等)对GetCommandLineW和printf均有效,尝试过栈传参的写法也无法解决问题。
NASM源代码
extern GetCommandLineW extern CommandLineToArgvW extern LocalFree extern printf section .rodata argc_format: db '%llu', 0 section .bss argc: resq 1 section .text global main main: sub rsp, 32 call GetCommandLineW mov rcx, rax lea rdx, [rel argc] call CommandLineToArgvW; mov rcx, rax call LocalFree lea rcx, [rel argc_format] mov rdx, [rel argc] call printf add rsp, 32 xor rax, rax ret
NASM程序运行结果
...\main_nasm>main_nasm ...\main_nasm>echo %ERRORLEVEL% -1073741819
GDB调试过程
Reading symbols from main_nasm... (gdb) break main Breakpoint 1 at 0x140001540 (gdb) run Starting program: ...\main_nasm.exe [New Thread 10868.0x4ed0] [New Thread 10868.0x3adc] [New Thread 10868.0x479c] Thread 1 hit Breakpoint 1, 0x00007ff6a6b71540 in main () (gdb) next Single stepping until exit from function main, which has no line number information. 0x00007ff6a6b7157c in GetCommandLineW () (gdb) next Single stepping until exit from function GetCommandLineW, which has no line number information. 0x00007ffac901f6d0 in KERNEL32!GetCommandLineW () from C:\Windows\System32\kernel32.dll (gdb) next Single stepping until exit from function KERNEL32!GetCommandLineW, which has no line number information. 0x00007ffac8d00fd0 in KERNELBASE!GetCommandLineW () from C:\Windows\System32\KernelBase.dll (gdb) next Single stepping until exit from function KERNELBASE!GetCommandLineW, which has no line number information. 0x00007ff6a6b71549 in main () (gdb) next Single stepping until exit from function main, which has no line number information. Thread 1 received signal SIGSEGV, Segmentation fault. 0x00007ffac9da4220 in StrStrW () from C:\Windows\System32\shell32.dll (gdb) next Single stepping until exit from function StrStrW, which has no line number information. Thread 1 received signal SIGSEGV, Segmentation fault. 0x00007ffac9da4220 in StrStrW () from C:\Windows\System32\shell32.dll (gdb) next Single stepping until exit from function StrStrW, which has no line number information. [Thread 10868.0x479c exited with code 3221225477] [Thread 10868.0x4ed0 exited with code 3221225477] [Thread 10868.0x3adc exited with code 3221225477] Program terminated with signal SIGSEGV, Segmentation fault. The program no longer exists.
功能等效的C源代码
#include <stdio.h> #include <Windows.h> int main() { int argc = 0; LocalFree(CommandLineToArgvW(GetCommandLineW(), &argc)); printf("%llu", argc); return 0; }
C程序运行结果
...\main_c>main_c 1 ...\main_c>echo %ERRORLEVEL% 0
尝试过的其他写法(无效)
call GetCommandLineW lea rbx, [rel argc] push rbx push rax call CommandLineToArgvW
问题原因与解决方法
核心问题:栈对齐不符合Windows x64调用约定
Windows x64调用约定强制要求:调用任何函数前,RSP必须保持16字节对齐。
当main函数被启动代码调用时,call指令会将8字节的返回地址压入栈,此时RSP的地址是16n + 8(即8字节偏移,未对齐)。你的代码中仅执行sub rsp, 32分配shadow space,此时RSP的偏移为8 + 32 = 40,并非16的倍数,导致调用CommandLineToArgvW时栈未对齐,进而触发段错误。
修正方案
将main函数中的栈调整指令从sub rsp, 32改为sub rsp, 40,从add rsp, 32改为add rsp, 40。这样既分配了32字节的shadow space,又将RSP调整回16字节对齐状态(8 + 40 = 48,48是16的倍数,减去后RSP为16n +8 -48 =16(n-2),符合对齐要求)。
同时,CommandLineToArgvW的第二个参数是int*类型,你可以将.bss段中的argc: resq 1改为argc: resd 1(占用4字节,符合int类型大小),并将printf的格式符改为%u(匹配unsigned int),这样更严谨。
修正后的完整代码:
extern GetCommandLineW extern CommandLineToArgvW extern LocalFree extern printf section .rodata argc_format: db '%u', 0 section .bss argc: resd 1 section .text global main main: sub rsp, 40 call GetCommandLineW mov rcx, rax lea rdx, [rel argc] call CommandLineToArgvW mov rcx, rax call LocalFree lea rcx, [rel argc_format] mov edx, [rel argc] call printf add rsp, 40 xor rax, rax ret
运行修正后的代码即可正常输出argc值,无段错误。
内容的提问来源于stack exchange,提问作者LoC

