当前位置: 首页 > news >正文

nasm - BasicWindow_64

文章目录

    • nasm - BasicWindow_64
    • 概述
    • 笔记
    • my_build.bat
    • nasm_main.asm
    • END

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反汇编的代码基本一致,搬过来改,要方便的多。

END

相关文章:

  • 关于重启Pod后,CNI网络插件问题报错
  • AI 内容检测工具全解析,助力内容创作无忧
  • 蓝桥杯备考策略
  • Linux常用操作
  • java每日精进 2.20 MQ相关复健
  • 【量化策略】均值回归策略
  • 蓝桥杯 2.基础算法
  • gen_gauss_filter用于检测带方向的线条
  • 运维脚本——6.资源优化
  • 斐波那契数列模型:在动态规划的丝绸之路上追寻斐波那契的足迹(下)
  • 系统思考—价格策略
  • 【CSS】---- CSS 变量,实现样式和动画函数复用
  • MyBatis框架七:缓存
  • 【Golang 面试题】每日 3 题(六十一)
  • 前后端分离的Netty + WebSocket实现聊天室
  • 【Linux】多线程 -> 线程同步与基于BlockingQueue的生产者消费者模型
  • 【C++第二十章】红黑树
  • python-leetcode 38.翻转二叉树
  • 自然语言处理NLP 03案例——提取小说红楼梦各卷关键词TOP10
  • 快速入门——Vue框架快速上手
  • 国家发改委答澎湃:将指导限购城市针对长期摇号家庭和无车家庭等重点群体定向增发购车指标
  • 银川市长信箱被指乱回复:问诗词大会、答工程欠款,官方称工作失误
  • 图像编辑新增一款开源模型,阶跃星辰发布Step1X-Edit
  • “一对一讨论诸多事宜”,泽连斯基披露此次特泽会更多细节
  • 如何做大中国拳击产业的蛋糕?这项赛事给出办赛新思考
  • 四川甘孜州白玉县发生4.9级地震,震源深度10千米