nasm-BasicWindow_64
- 创业
- 2025-08-25 13:51:01

文章目录 nasm - BasicWindow_64概述笔记my_build.batnasm_main.asmEND nasm - BasicWindow_64 概述
学习网上找到的demo. x64和x86的汇编源码还差挺多的。 x64的汇编代码不好写,细节整不对,程序就不运行。 如果要查为啥不运行,也要看和正向生成的C工程的反汇编代码有哪些区别,才看的出来。 LEA, MOV很容易弄混。 MOV时,是否该加 地址数据类型的修饰(e.g. qword, dword), 这些也是细节。 感觉真是有需求用反汇编实现关键函数时,也是要以正向的代码生成的反汇编代码的基础上小步快跑的改,才不至于改了一坨后跑不起来。
笔记 my_build.bat @echo off cls rem ---------------------------------------- rem my_build.bat rem env rem NASM version 2.16.03 compiled on Apr 17 2024 rem GoLink.Exe Version 1.0.4.6 Copyright Jeremy Gordon 2002-2025 set path=C:\Program Files\NASM;C:\soft\Golink;%path% rem . bat默认是不支持中文的 . rem echo full path name - %~f0 rem echo full path - %~dp0 rem echo file name - %~nx0 rem echo work path - %cd% rem all param - %* rem . rem注释的第1个字符和最后1个字符,不能是中文,否则有概率会当作命令来执行 . rem . 调试.bat的方法 . rem . 如果.bat写的不对,又不容易看出来,只能在每行后面加pause, 然后执行.bat, 然后按一下空格执行一行 . set prj_src_name_1=nasm_main.asm set prj_obj_name_1=nasm_main.obj set prj_exe_name=BasicWindow_64.exe rem win32 or win64 set prj_build_type=win64 rem /console or /SUBSYSTEM:WINDOWS set prj_sub_sys=/SUBSYSTEM:WINDOWS echo [%~nx0 %*] if "%1" == "build" ( call :fn_build ) else if "%1" == "clear" ( call :fn_clear ) else ( call :fn_usage ) goto end rem ---------------------------------------- rem function rem ---------------------------------------- :fn_usage echo usage my_build.bat [option] echo build - build asm to EXE echo clear - clear trush on the project exit /b rem ---------------------------------------- :fn_build echo build ... rem find file on work path call :fn_del_file "%prj_obj_name_1%" nasm -f %prj_build_type% %prj_src_name_1% -o %prj_obj_name_1% rem 用IDA打开.obj 已经可以看到实现逻辑了 call :fn_del_file "%prj_exe_name%" rem 如果不指定要连接的dll, 会报错 golink /entry:fn_Start '%prj_sub_sys%' kernel32.dll user32.dll %prj_obj_name_1% /fo %prj_exe_name% call :fn_exec "%prj_exe_name%" exit /b rem ---------------------------------------- :fn_clear echo clear ... call :fn_del_file "%prj_obj_name_1%" call :fn_del_file "%prj_exe_name%" exit /b rem ---------------------------------------- :fn_del_file if exist "%~1" ( echo del "%~1" del "%~1" ) exit /b :fn_exec if exist "%~1" ( echo exec "%~1" %~1 ) exit /b rem ---------------------------------------- :end echo END rem pause call cmd nasm_main.asm ; @file nasm_main.asm ; @brief 用NASM实现一个64bits的基本窗口 ; nasm - BasicWindow_64 ; ---------------------------------------- ; 宏定义 ; ---------------------------------------- ; Basic Window, 64 bit. V1.01 COLOR_WINDOW EQU 5 ; Constants CS_BYTEALIGNWINDOW EQU 2000h CS_HREDRAW EQU 2 CS_VREDRAW EQU 1 CW_USEDEFAULT EQU 80000000h IDC_ARROW EQU 7F00h IDI_APPLICATION EQU 7F00h IMAGE_CURSOR EQU 2 IMAGE_ICON EQU 1 LR_SHARED EQU 8000h NULL EQU 0 SW_SHOWNORMAL EQU 1 WM_DESTROY EQU 2 WS_EX_COMPOSITED EQU 2000000h WS_OVERLAPPEDWINDOW EQU 0CF0000h WindowWidth EQU 640 WindowHeight EQU 480 ; ---------------------------------------- ; 导入函数声明 ; ---------------------------------------- extern CreateWindowExA ; Import external symbols extern DefWindowProcA ; Windows API functions, decorated extern DispatchMessageA extern ExitProcess extern GetMessageA extern GetModuleHandleA extern IsDialogMessageA extern LoadImageA extern PostQuitMessage extern RegisterClassExA extern ShowWindow extern TranslateMessage extern UpdateWindow ; ---------------------------------------- ; 区段定义 ; ---------------------------------------- ; 函数的入口名称是啥都可以, 只要link的时候指定主函数是啥 global fn_Start ; Export symbols. The entry point section .data ; Initialized data segment WindowName db "Basic Window 64", 0 ClassName db "Window", 0 section .bss ; Uninitialized data segment hInstance resq 1 section .text ; Code segment ; ---------------------------------------- ; 函数实现 ; 如果对x64汇编不熟,可以先写一个x86版的NASM工程,正常用了,再改一个x64工程出来,比较简单,不用想事 ; ---------------------------------------- fn_Start: sub RSP, 8 ; // 16对齐 SUB RSP, 32 XOR RCX, RCX call GetModuleHandleA mov [hInstance], RAX ADD RSP, 32 call fn_WinMain ; .LB_Exit: XOR RCX, RCX call ExitProcess ret ; ---------------------------------------- fn_WinMain: ; 如果函数内有局部变量,需要自己开栈空间 ; EBP - X 是局部变量 ; EBP + X 是入参 push RBP ; Set up a stack frame mov RBP, RSP sub RSP, 136 + 8 ; 136 bytes for local variables. 136 is not ; a multiple of 16 (for Windows API functions), ; the + 8 takes care of this. %define wc RBP - 136 ; WNDCLASSEX structure, 80 bytes %define wc.cbSize RBP - 136 ; 4 bytes. Start on an 8 byte boundary %define wc.style RBP - 132 ; 4 bytes %define wc.lpfnWndProc RBP - 128 ; 8 bytes %define wc.cbClsExtra RBP - 120 ; 4 bytes %define wc.cbWndExtra RBP - 116 ; 4 bytes %define wc.hInstance RBP - 112 ; 8 bytes %define wc.hIcon RBP - 104 ; 8 bytes %define wc.hCursor RBP - 96 ; 8 bytes %define wc.hbrBackground RBP - 88 ; 8 bytes %define wc.lpszMenuName RBP - 80 ; 8 bytes %define wc.lpszClassName RBP - 72 ; 8 bytes %define wc.hIconSm RBP - 64 ; 8 bytes. End on an 8 byte boundary %define msg RBP - 56 ; MSG structure, 48 bytes %define msg.hwnd RBP - 56 ; 8 bytes. Start on an 8 byte boundary %define msg.message RBP - 48 ; 4 bytes %define msg.Padding1 RBP - 44 ; 4 bytes. Natural alignment padding %define msg.wParam RBP - 40 ; 8 bytes %define msg.lParam RBP - 32 ; 8 bytes %define msg.time RBP - 24 ; 4 bytes %define msg.py.x RBP - 20 ; 4 bytes %define msg.pt.y RBP - 16 ; 4 bytes %define msg.Padding2 RBP - 12 ; 4 bytes. Structure length padding %define hWnd RBP - 8 ; 8 bytes ; 在函数内 %define x, 当使用x时,只在函数内生效 mov dword [wc.cbSize], 80 mov dword [wc.style], CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNWINDOW LEA RAX, [REL fn_WndProc] mov qword [wc.lpfnWndProc], RAX mov qword [wc.cbClsExtra], NULL mov qword [wc.cbWndExtra], NULL mov RAX, qword [REL hInstance] mov qword [wc.hInstance], RAX sub RSP, 32 + 16 ; // x64调用函数时,必须留shadow space, 这是x64函数调用约定,阴影区size = 32 = 4 x 8 = (size RCX + RDX + R8 + R9) mov qword [RSP + 32 + 1 * 8], LR_SHARED ; // param6 mov qword [RSP + 32 + 0 * 8], NULL ; // param5 XOR R9, R9 ; // param4 mov R8, IMAGE_ICON ; // param3 mov RDX, IDI_APPLICATION ; // param2 XOR RCX, RCX ; // param1 call LoadImageA mov [wc.hIcon], RAX add RSP, 32 + 16; 调用WinApi时,是被调用者平衡堆栈(阴影区 + 入参) sub RSP, 32 + 16 mov qword [RSP + 32 + 1 * 8], LR_SHARED mov qword [RSP + 32 + 0 * 8], NULL XOR R9, R9 MOV R8, IMAGE_CURSOR MOV RDX, IDC_ARROW XOR RCX, RCX call LoadImageA mov qword [wc.hCursor], RAX add RSP, 32 + 16 mov qword [wc.hbrBackground], COLOR_WINDOW + 1 mov qword [wc.lpszMenuName], NULL LEA RAX, [REL ClassName] mov qword [wc.lpszClassName], RAX ; 小图标内装的也是大图标... sub RSP, 32 + 16 mov qword [RSP + 5 * 8], LR_SHARED mov qword [RSP + 4 * 8], NULL mov R9, NULL mov R8, IMAGE_ICON mov RDX, IDI_APPLICATION XOR ECX, ECX call LoadImageA ; Small program icon mov qword [wc.hIconSm], RAX add RSP, 32 + 16 sub RSP, 32 lea RCX, [wc] call RegisterClassExA add RSP, 32 ; 假设操作数为x ; [x] 代表x的地址 ; x 代表x是一个立即数 ; [REL x] 是相对RIP的地址,支持重定位, [x]是绝对地址,不支持重定位 ; 所以要优先考虑使用 [REL x] 这样的用法 ; mov [hWnd], RAX 和 mov qword [hWnd] 的区别 ; mov [hWnd], RAX 依赖于编译器推断, 在复杂寻址或者代码优化后可能不靠谱 ; 既然已经是汇编了, 还是最好是显势指定地址类型, e.g. mov qword [hWnd] ; push qword [hInstance] 强制将hInstance的地址作为qword类型,不安全, 如果hInstance的类型size < sizeof(qword), 风险就来了 ; push [hInstance] 会自动扩展, 安全,优先使用这种来压栈 ; 但是 NASM中,push一个地址中的内容时,必须指定数据类型的size ; 所以,如果一个数据地址的类型不是qwrod, 必须先载入寄存器(进行数据扩展,高端字节扩展为0),再将寄存器入栈 ; 如果 hWnd 是定义在栈上变量的别名,MOV RCX, [REL hWnd] 就是错误的,因为REL 是相对于RIP的相对便宜。 ; 应该用 MOV RCX, [hWnd] ; LEA 地址的操作, MOV是数据的操作 ; e.g. lea RCX, [msg] 等价语句为 mov RCX, msg ; 不过最好操作地址还是用 lea, 因为可以用REL修饰改为产生相对地址。 ; x64程序,参数的压入,最好使用手工调整RSP, 避免栈中数据不对齐和其他风险. ; 入参在影子区后面(影子区结尾 = RSP + 32) ; parma1 = RSP + 32 + 0 *8 ; parma2 = RSP + 32 + 1 *8 ; parma3 = RSP + 32 + 2 *8 ; 准备调用 CreateWindowExA,入参12个, 4个在寄存器(RCX,RDX,R8,R9),其余8个需要入栈 sub RSP, 32 + (8 * 8) mov qword [RSP + 32 + (7 * 8)], NULL mov RAX, qword [REL hInstance] mov qword [RSP + 32 + (6 * 8)], RAX mov qword [RSP + 32 + (5 * 8)], NULL mov qword [RSP + 32 + (4 * 8)], NULL mov qword [RSP + 32 + (3 * 8)], WindowHeight mov qword [RSP + 32 + (2 * 8)], WindowWidth mov dword [RSP + 32 + (1 * 8)], CW_USEDEFAULT mov dword [RSP + 32 + (0 * 8)], CW_USEDEFAULT mov R9, WS_OVERLAPPEDWINDOW LEA R8, [REL WindowName] LEA RDX, [REL ClassName] mov RCX, WS_EX_COMPOSITED call CreateWindowExA mov [hWnd], RAX add RSP, 32 + (8 * 8) ; // 平衡栈指针, 阴影区 + 入参的8个参数(不算RCX, RDX, R8, R9) SUB RSP, 32 MOV RDX, SW_SHOWNORMAL MOV RCX, [hWnd] call ShowWindow ADD RSP, 32 SUB RSP, 32 mov RCX, [hWnd] call UpdateWindow ADD RSP, 32 .LB_MessageLoop: SUB RSP, 32 XOR R9, R9 XOR R8, R8 XOR RDX, RDX LEA RCX, [msg] call GetMessageA ADD RSP, 32 cmp EAX, 0 je .LB_Fn_End SUB RSP, 32 LEA RDX, [msg] MOV RCX, [hWnd] call IsDialogMessageA ADD RSP, 32 cmp EAX, 0 jne .LB_MessageLoop ; Skip TranslateMessage and DispatchMessage SUB RSP, 32 LEA RCX, [msg] call TranslateMessage ADD RSP, 32 SUB RSP, 32 lea RCX, [msg] call DispatchMessageA ADD RSP, 32 jmp .LB_MessageLoop ; .LB_X 这种在函数内的标签的,作用域只在函数内 ; 多个函数内,可以有多个同名的标签 ; 所以函数内的标签一定要以.开头 .LB_Fn_End: mov RSP, RBP ; Remove the stack frame pop RBP xor RAX, RAX ret ; ---------------------------------------- fn_WndProc: push RBP ; Set up a Stack frame mov RBP, RSP ; 用RBP来标记入参比较好 ; 入参开始栈地址为(RBP + 16)的原因, 因为进了函数栈顶是返回地址(+8), 然后又保存了RBP(+8) ; 此时RBP + 16 才是入参开始地址 %define hWnd RBP + 16 + (0 * 8) ; Location of the 4 passed parameters from %define uMsg RBP + 16 + (1 * 8) ; the calling function %define wParam RBP + 16 + (2 * 8) ; We can now access these parameters by name %define lParam RBP + 16 + (3 * 8) ; 将前4个参数从寄存器写入阴影区, 然后就可以自由使用4个寄存器了,也可以从阴影区读取前4个入参了 MOV qword [hWnd], RCX MOV qword [uMsg], RDX MOV qword [wParam], R8 MOV qword [lParam], R9 cmp qword [uMsg], WM_DESTROY ; [EBP + 12] je .LB_WMDESTROY ; .LB_DefaultMessage: SUB RSP, 32 MOV R9, qword [lParam] MOV R8, qword [wParam] MOV RDX, qword [uMsg] MOV RCX, qword [hWnd] call DefWindowProcA ADD RSP, 32 jmp .LB_fn_end .LB_WMDESTROY: SUB RSP, 32 XOR RCX, RCX call PostQuitMessage ADD RSP, 32 xor EAX, EAX .LB_fn_end: mov RSP, RBP pop RBP ret ; ---------------------------------------- ; 如果先写好一个x86版本的实现,再从x86版改出一个x64版,挺麻烦的,容易搞错(x86和x64的汇编代码,其实差的还是挺多的,改还不如重写) ; 还是用VS2019写一个工程,编译成release + x64(关掉SDL,不优化),然后用IDA看,将需要的部分摘出来整理比较方便。 ; NASM的代码实现和VS2019反汇编的代码基本一致,搬过来改,要方便的多。 ENDnasm-BasicWindow_64由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“nasm-BasicWindow_64”
上一篇
ue----git局域网内部署裸仓库,别的机器进行访问
下一篇
Linux常用操作