【Win32汇编】学习Win32汇编

学习Win32汇编(Windows下的32位汇编)

第一个程序Hello world
控制台输出(新建Console App工程)
Debug输出(新建Win32 App工程)
后面代码的头文件在这里
伪指令DUP与数组
运算符
子过程(函数)的传参与调用
获取数组长度和字节数等
数据对齐
获取变量地址以及伪指令this的使用
loop的使用
堆栈以及相关指令
二进制相关函数
标志寄存器
数据传送指令
逻辑运算指令
位测试与位扫描指令
移位指令
符号扩展指令
加减指令
乘除指令
跳转指令
串指令
条件及循环伪指令
结构体

书籍推荐

基础篇

第一个程序Hello world

;模式定义
.386        ;汇编伪指令,指明了程序使用80386指令集
.model flat, stdcall        ;汇编伪指令,指明了程序工作模式,Win32程序只有一种内存模式,即flat。stdcall指明了编译器参数传递的约定,即函数调用时,实参入栈从右往左。
option casemap:none     ;指明标识符区分大小写
;引入头文件和库文件,inc文件主要包含函数或常量的声明,lib文件包含了动态库函数的地址信息和静态库的函数代码
include windows.inc     ;windows.inc包含着Win32程序用到的常量、结构的声明,下面用到的MB_OK常量就是在其中声明
include user32.inc
include kernel32.inc
includelib user32.lib       ;user32.dll是用户服务接口, 负责消息管理等,MessageBox函数来自user32.dll
includelib kernel32.lib     ;kernel32.dll是系统服务接口, 负责内存管理等,ExitProcess函数来自kernel32.dll
;数据段(.data表示已初始化的数据)
.data
    szCaption db 'Hi', 0
    szMsg     db 'Hello World!', 0      ;定义字符串变量,0表示字符串结尾
;代码段
.code
start:
    invoke MessageBox, NULL, addr szMsg, addr szCaption, MB_OK      
    invoke ExitProcess, NULL        ;invoke是调用函数的伪指令,MessageBox(显示消息框)和ExitProcess(退出程序)为API函数
end start

控制台输出(新建Console App工程)

.386
.model flat, stdcall
option casemap:none

include msvcrt.inc
includelib msvcrt.lib

.data
    szFmt db 'EAX=%d; ECX=%d; EDX=%d', 0

.code
start:
    mov eax, 11
    mov ecx, 22
    mov edx, 33
    invoke crt_printf, addr szFmt, eax, ecx, edx        ;crt_printf就是C语言里面的printf函数
    ret     ;ret是用于子程序返回的指令。在没有生成Win32窗口时可以使用ret代替ExitProcess
end start

Debug输出(新建Win32 App工程)

.386
.model flat, stdcall
option casemap:none

include    windows.inc
include    kernel32.inc
include    masm32.inc
include    debug.inc
includelib kernel32.lib
includelib masm32.lib
includelib debug.lib

.data
    szText db 'Hello World!', 0
    
.code
start:
    PrintLine           ;----------------------------------------
    PrintString szText  ;Hello World!
    PrintLine           ;----------------------------------------
    ret
end start

后面代码的头文件在这里

.386
.model flat, stdcall
option casemap :none

include    windows.inc
include    kernel32.inc
include    user32.inc
include    masm32.inc
include    debug.inc
includelib kernel32.lib
includelib user32.lib
includelib masm32.lib
includelib debug.lib

伪指令DUP与数组

;数组的实现
.data
    ;初始化数组,每个数组元素大小为2个字节
    val dw 11,22,33
.code
start:
    lea ebx, val        ;等价于mov ebx,offset val
    mov esi, type val   ;每个元素大小为2个字节
    xor ax, ax
    mov ax, word ptr [ebx+esi*0]
    PrintDec ax     ;11
    ;也可以使用movzx eax,word ptr [ebx+esi*1]
    ;也可以使用mov ax, word ptr [ebx][esi*1]或者mov ax, word ptr val[esi*1]
    mov ax, word ptr [ebx+esi*1]
    PrintDec ax     ;22
    mov ax, word ptr [ebx+esi*2]
    PrintDec ax     ;33
    ret
end start
;伪指令DUP的使用
.data?
    v1 dd 4096 dup(?) ;未初始化变量应该放在.data?段,如果放在.data段,在生成exe文件时会多占4096*4字节的内存
.data
    v2 dd 2 dup(1,2,3)
.code
start:
    DumpMem offset v2, 24 ;01000000-02000000-03000000-01000000-02000000-03000000共24字节
    ret
end start

运算符

.code
start:
	;注意:下面这些运算符都是伪指令,在80386中由编译器执行。而在Dos中它们有对应字节码,由CPU执行。
	;算数运算符
    PrintDec 7 / 3   ;2
    ;关系操作符:eq(=),ne(!=),lt(<),le(<=),gt(>),ge(>=),满足条件输出-1,否则输出0
    PrintDec 2 eq 1  ;0
    PrintDec 2 eq 2  ;-1
    ;逻辑操作符
    PrintHex 0FFFFFFFFh and 0FFFF0000h  ;FFFF0000
    ;高低分离符
    PrintHex high     11223344h  ;00000033
    PrintHex highword 11223344h  ;00001122
    ;移位运算符
    PrintHex 12345678h shl 4  ;23456780
    ret
end start

子过程(函数)的传参与调用

;局部变量定义与过程函数调用
.code
proc1 proc
    PrintDec 1
    ret
proc1 endp

proc2 proc
    PrintDec 2
    ret     ;过程返回,如果不写这个的话,会继续执行下面语句
proc2 endp

main proc
    ;局部变量中的类型不能使用缩写
    LOCAL v1: dword,v2: dword
    ;数组
    LOCAL v3[3]: dword
    PrintDec v1
    mov eax, v3[0]
    PrintDec eax
    call proc2      ;调用过程proc2
    call proc1
    ret
main endp
end main
;输出结果 0 0 2 1
;求和函数传参与调用
;sum proto :dword, :dword, :dword ;函数声明的主要是参数类型, 一般省略参数名
.code
sum proc v1:dword, v2:dword, v3:dword
    mov eax, v1
    add eax, v2
    add eax, v3
    ret
sum endp

main proc
    invoke sum, 11, 22, 33  ;invoke是调用函数的伪指令。调用函数sum并传参。
    PrintDec eax  ;66
    ret
main endp
end main

获取数组长度和字节数等

.data?
    v1 dw 10 dup(0)		
    v1size=$-v1     	;获取变量v1占的字节数
    v1len=($-v1)/2		;获取变量v1长度
    vaddr=$		;$用于获取当前语句的地址。在.data中,只有v1,v2这种变量会产生字节开销

    v2 dw 11,22,33,44
    v2Size = $ - v2

.code
main proc
    ;获取类型占几个字节,对于数组是获取每个元素占几个字节
    PrintDec (type v1)  ;2
    ;测试$的作用
    PrintDec v1size		;20
    PrintDec v1len		;10
    ;可以得出v1size到v2之间的语句的$都等于v2地址
    PrintDec vaddr		;4206796
    mov eax,offset v2
	PrintDec eax		;4206796
	;获取数组元素个数以及总字节数
	PrintDec (lengthof v1)	;10
	PrintDec (sizeof v1)  ;20
main endp
end main

数据对齐

.data
    v1 db 0
    align 4;让下一个变量的起始地址保证是4的倍数
    ;even表示偶对齐,等价于align 2
    ;org 100表示跨越100个字节存储下一个变量
    v2 db 0
    v3 db 0
.code
main proc
    PrintDec offset v1  ;4206592
    PrintDec offset v2  ;4206596 
    PrintDec offset v3  ;4206597
    ret
main endp
end main

获取变量地址以及伪指令this的使用

OFFSET和ADDR的异同:
1、offset不能获取局部变量的地址;
2、addr只能用于调用函数(invoke)中, 不能用于赋值操作;
3、addr面对局部变量时会转换为lea等指令, addr面对全局变量时则直接调用offset;
例如:lea ebx,dwVal等价于mov ebx, offset dwVal。Lea是专门获取地址的指令
4、在invoke中应尽量使用addr, 其他只用offset.
;this伪指令的使用
.data
    TextAddr equ this byte   ;伪指令this可让当前变量和下一个变量同址 
    szText db 'Asm', 0
.code
main proc
    PrintHex offset szText   ;00403000
    PrintHex offset TextAddr ;00403000
    
    PrintString szText       ;Asm
    mov [TextAddr], 'a'      ;给TextAddr所在地址单元赋值
    PrintString szText       ;asm
    ret
main endp
end main

loop的使用

;数组求和
.data
    dwArr dd 1,2,3,4,5
.code
main proc
    lea edi, dwArr           
    mov ecx, lengthof dwArr 
    xor eax, eax
L1:
    add  eax, [edi]          
    add  edi, type dwArr     ;获取下一个元素的地址
    loop L1
    
    PrintDec eax  ;15
    ret
main endp
end main
;复制字符串
.data
    szSource db 'Hello World!', 0      
    szDest   db sizeof szSource dup(0) 
.code
main proc
    mov  esi, 0               
    mov  ecx, sizeof szSource  
L1:
    mov  al, szSource[esi]     
    mov  szDest[esi], al       
    add esi, type szSource      ;调整索引
    loop L1
    ;也可以使用API完成字符串复制:invoke szCopy, addr szSource, addr szDest
    PrintString szDest         
    ret
main endp
end main

堆栈以及相关指令

  • 程序把内存划分区域
    • 全局数据区
      • 全局变量在堆里
      • 堆中数据由上向下排列,内存增大方向
    • 局部数据区
      • 局部变量,局部常量,子程序参数在栈里
      • 栈中数据由下向上排列,内存减小方向
    • 其它
  • 栈顶指针ESP
    • Win32的PUSH只可以压入32位(默认)或16位的数据,因此ESP只能±2或者±4
  • push的应用
    • 函数调用(invoke)的本质

      • 本质是从右往左依次push参数,最后call函数,然后pop出栈(因为push和pop必须成对出现)
    • 保护数据

      • 调用函数前最需要保护的是EIP,因为它保存着函数调用结束后下一条指令的地址
      1 执行call指令,CPU把返回地址(EIP)压入堆栈(4个字节)
      2 esp在程序执行中随时可能用到,不可能使用它存取局部变量。ebp也是以堆栈段作为默认数据段的。所以先push ebp,再mov ebp, esp
      3 正常操作
      4 先mov esp,bsp,再pop ebp(leave指令可以起到这两个作用)
      5 执行ret指令,CPU将返回地址(EIP)出栈
      
;交换变量值
.data
    val1 dd 111
    val2 dd 999
;方案一:使用堆栈
.code
main proc
    push val1
    push val2
    pop val1
    pop val2
    PrintDec val1  ;999
    PrintDec val2  ;111
    ret
main endp
end main
;方案二:使用XCHG指令
.code
main proc
    mov  eax, val1
    xchg eax, val2      ;eax存储val2的值,val2存储val1的值
    mov  val1, eax
    PrintDec val1   ;999
    PrintDec val2   ;111
    ret
main endp
end main
;翻转字符串
.data
    szText db 'Hello World!', 0

.code
main proc
    ;压栈
    mov ecx, sizeof szText - 1  
    xor esi, esi                
@@: movzx eax, szText[esi]      ;懒得给标号取名,可以使用@@,@B表示前面最近的一个标号、@F表示后面最近的一个标号 
    push eax
    inc esi
    loop @B
    ;出栈
    mov ecx, sizeof szText - 1
    xor esi, esi
@@: pop eax
    mov szText[esi], al
    inc esi
    loop @B
    
    PrintString szText  ;!dlroW olleH
    ret
main endp
end main

二进制相关函数

;学习查看二进制是为了看到EFLAGS寄存器中的二进制位的变化
.data
    szBin db 8 dup(?), 0
.code
main proc
    lahf    ;lahf指令是把EFLAGS寄存器的低8位字节读入ah
    invoke byt2bin_ex, ah, addr szBin       ;从byte数字转为二进制字符串
    PrintString szBin       ;01000110
    ret
main endp
end main

标志寄存器

在这里插入图片描述

;置位 stc
stc     ;CF=1
;复位 clc
clc     ;CF=0
;取反 cmc
cmc     ;CF=not CF
;如果要观察整个EFLAGS的32位, 可用PUSHFD和POPFD指令让EFLAGS进栈、出栈

数据传送指令

//以下指令均不影响标志寄存器EFlags
;mov 数值传送
;lea 地址传送
;xchg 交换指令
;xlat 换码指令
.data
    szText db 'ABCDEFG', 0
.code
main proc
    lea ebx, szText     ;先将源地址放入ebx
    mov al, 1       ;将要访问的字节序号放入al
    xlat        ;xlat无参数,操作和ebx、al相关
    PrintHex al ;42,可以看出指定字节被读入到al
    ret
main endp
end main
;movzx 零扩展传送
;movsx 符号扩展传送
MOVZX 和 MOVSX 的区别是:
1、MOVZX会将目标寄存器中高出的位补0
2、如果源操作数的最高位是1, MOVSX会将目标寄存器中高出的位补1; 反之补0
.data
    bVal   db 90h
    dwVal1 dw 7FFFh
    dwVal2 dw 8000h
.code
main proc
    movzx eax, dwVal1
    movsx edx, dwVal1
    PrintHex eax ;00007FFF
    PrintHex edx ;00007FFF
    
    movzx eax, dwVal2
    movsx edx, dwVal2
    PrintHex eax ;00008000
    PrintHex edx ;FFFF8000
    
    mov cl, bVal
    movzx ax, cl
    movsx dx, cl
    PrintHex ax  ;0090
    PrintHex dx  ;FF90
    ret
main endp
end main

逻辑运算指令

;and 逻辑与,or 逻辑或,xor 逻辑异或
指令会影响EFlags,置CF=OF=0,结果影响SF、ZF、PF
可以使用xor或者not用于加密与解密字符串
;not 逻辑取反
指令不影响EFlags
;test 测试逻辑与
指令会影响EFlags,置CF=OF=0,结果影响SF、ZF、PF
test同and,但它不修改运算数,只改变标志寄存器。即它只尝试and的结果
常用于影响ZF(当test结果为0时,ZF=1),test其后往往跟着条件转移指令
【举个栗子春暖花开】
;判断字符串中每个字符的二进制位的最后一位是1还是0,统计为0为1个数
.data
    szText db 'Delphi', 0
.code
main proc
    ;清空两个寄存器用于计数
    xor eax, eax	;存储末位为1字符个数
    xor edx, edx	;存储末位为0字符个数
    
    lea esi, szText                 
    mov ecx, lengthof szText - 1   
L1: test byte ptr [esi], 00000001b  ;循环测试每个字符的最后一位是1还是0
    jz L2    ;如果是0则跳转到L2,edx+1
    inc eax  ;反之给eax+1
    jmp L3
L2: inc edx
L3: inc esi
    loop L1
    
    PrintDec eax ;2 
    PrintDec edx ;4 
    ret
main endp
end main

位测试与位扫描指令

;位测试指令  结果影响CF
BT:位测试 Bit Test
BTS:位测试并置位 Bit Test and Set
BTR:位测试并复位 Bit Test and Reset
BTC:位测试并取反 Bit Test and Complement
【举个栗子春暖花开】
;BT把10000001的第7位复制到CF,可以看出是1
mov dx,10000001b
bt dx,7
lahf
PrintHex ah ;47,即01000111b(CF=1)
;BTS:BT+置1,BTR:BT+置0,BTC:BT+取反
;位扫描指令  结果影响ZF
BSF:位扫描,由低->高
BSR:位扫描,由高->低
1 扫描的是参数二,找到是1的位后,把位置数给参数一,并置ZF=0
2 找不到是1的位,参数一的值不变,置ZF=1
【举个栗子春暖花开】
mov dx, 0000111100001100b
bsf cx, dx
PrintDec cx     ;2,即从右往左数第2位

移位指令

;移位指令   结果影响OF、SF、ZF、PF、CF
SHL、SHR:逻辑左移、逻辑右移
SAL、SAR:算数左移、算数右移
SHL和SAL:左移,低位补0,高位进CF
SHR:右移,低位进CF,高位补0
SAR:右移,低位进CF,高位不变
【举个栗子春暖花开】
mov al, 11100111b
sar al, 2   ;3表示右移两位
PrintHex al     ;F9,即11111001b
;循环移位指令   结果影响OF、CF
ROL:循环左移,高位到低位并送CF
ROR:循环右移, 低位到高位并送CF
RCL:循环左移, 进位值(原CF)到低位, 高位进位到CF
RCR:循环右移, 进位值(原CF)到高位, 低位进位到CF
【举个栗子春暖花开】
clc     ;CF=0
mov al, 11101011b
rcr al, 2       ;循环右移一位,al=01110101,CF=1;再右移一位,al=10111010,CF=1
PrintHex al ;BA - 10111010b
;双精度移位     结果影响OF、SF、ZF、PF、CF
三个操作数:操作数一是目的操作数,操作数二一直不变且须是寄存器,操作数三是移位数目
SHLD:双精度左移,左边被移出的位由操作数二相同数目的高位填充
SHRD:双精度右移,右边被移出的位由操作数二相同数目的低位填充
【举个栗子春暖花开】
.code
main proc
    ;SHLD
    mov ax, 1100110011110000b
    mov dx, 1111111100000000b
    shld ax, dx, 2
    PrintHex ax ;33C3 - 0011001111000011b
    
    ;SHRD
    mov ax, 0000111100110011b
    mov dx, 0000000011111111b
    shrd ax, dx, 2
    PrintHex ax ;C3CC - 1100001111001100b
    ret
main endp
end main

符号扩展指令

CBW:将AL扩展为AX,等价于movsx ax, al
CWDE:将AX扩展为EAX,等价于movsx eax, ax
CBW和CWDE对EFLAGS无影响
movsx的特征:如果源操作数的最高位是1, movsx会将目标寄存器中高出的位补1; 反之补0
符号扩展指令的本质:一个正数(无符号)或负数(有符号)在扩展储存空间时, 使用这些指令可保证原值不变
【举个栗子春暖花开】
.code
main proc
    mov al, 68
    cbw
    PrintHex ax ;0044
    PrintDec ax ;68
    
    mov al, -68
    cbw
    PrintHex ax ;FFBC,BC为-68的补码
    PrintDec ax ;-68
    ret
main endp
end main

加减指令

inc,dec,neg(求补或者求反),add,adc,sub,sbb,cmp   结果影响OF、SF、ZF、AF、PF、CF
【辨析NEG与NOT】
;neg就相当于取反(not)+1
mov val,44
not val
inc val
等价于
mov val,44
neg val
【举个栗子春暖花开】
cmp隐含执行sub,但并不改写操作数,只是影响标志位ZF和SF
.code
main proc
    mov eax, 3
    cmp eax, 3
    lahf
    PrintHex ah ;46,即01000110b(ZF=1 说明两个数相等)
    
    mov eax, 3
    cmp eax, 2
    lahf
    PrintHex ah ;02,即00000010b(SF=0、ZF=0 说明前者>后者)
    
    mov eax, 3
    cmp eax, 4
    lahf
    PrintHex ah ;76,即10010111b(SF=1、ZF=0 说明前者<后者)    
    ret
main endp
end main

乘除指令

【只有一个参数】
格式:[mul 参数]  无符号乘    影响OF、CF
如果参数是8位,则将al做乘数,结果放在ax
如果参数是16位,则将ax做乘数,结果放在dx:ax中
如果参数是32位,则将eax做乘数,结果放在edx:eax中
【只有一个参数】
格式:[imul 参数]  有符号乘    影响OF、CF
如果参数是8位,则将al做乘数,结果放在ax
如果参数是16位,则将ax做乘数,结果放在dx:ax中
如果参数是32位,则将eax做乘数,结果放在edx:eax中
【有符号乘和无符号乘结果的一致性】
如果操作数(比如7Fh和7Fh)都没有符号位, 结果一致
如果操作数的其中之一(比如7Fh和80h)有符号位, 结果不一致
如果操作数(比如80h和80h)都有符号位, 结果也一致
【有多个参数】
imul r16/r32, r16/r32/m16/m32/i  ;双操作数, (1)*(2) -> (1)
imul r16/r32, r16/r32/m16/m32, i ;三操作数, (2)*(3) -> (1)
其中常数 i 的位数可以 <= 但不能 > 其他操作数
【举个栗子春暖花开】
.data
    val dd 8
.code
main proc
    ;IMUL 两个操作数
    mov eax, 7
    imul eax, val
    PrintDec eax ;56
    
    ;IMUL 三个操作数
    imul eax, val, 9
    PrintDec eax ;72
    ret
main endp
end main
【只有一个参数】
格式:[div/idiv 参数]  无符号除、有符号除  对EFLAGS无影响
如果参数是8位, 将把 AX做被除数;商->AL,余数->AH
如果参数是16位, 将把DX:AX做被除数;商->AX,余数->DX
如果参数是32位, 将把EDX:EAX做被除数;商->EAX,余数->EDX
【有符号除和无符号除结果的一致性】
与乘法相同

跳转指令

  • 无条件跳转:jmp
  • 根据cx,ecx寄存器的值跳转:jcxz(cx=0则跳转),jecxz(ecx为0则跳转)
  • 根据EFLAGS标志位跳转:je,jne,jz等等
请参考汇编语言 王爽

串指令

  • 什么是"串"?
    • 不单指字符串,包括所有连续的数据(比如,数组)。
    • 串指令只用于内存操作
;移动字符串:从esi到edi,执行后根据df的值,esi和edi同方向变化
.data
    szSource db 'Delphi 2010', 0 
    len      equ $ - szSource - 1       ;计算szSource字符串长度
    szDest   db len dup(?), 0
.code
main proc
    lea esi, szSource
    lea edi, szDest
    mov ecx, len
    cld     ;设置df=0,以让串地址由低到高
    rep movsb   
    PrintString szDest  ;Delphi 2010
    ret
main endp
end main
;比较数组相不相等:比较esi和edi,执行后根据df的值,esi和edi同方向变化
.data
    dwArr1 dw 1,2,3,4,5
    dwArr2 dw 1,2,3,4,5,6
.code
main proc
    lea esi, dwArr1
    lea edi, dwArr2
    mov ecx, lengthof dwArr1
    cmp ecx, lengthof dwArr2
    jne L1
    cld
    repe cmpsw
    jne L1
    PrintText '两数组相等'
    jmp L2
L1: PrintText '两数组不等'
L2: ret
main endp
end main
;扫描某数据是否存在:依据al/ax/eax中的数据扫描edi指向的数据,执行后根据df的值,edi变化
.data
    szText db 'ABCDEFGH', 0
.code
main proc
    lea edi, szText
    mov al, 'F'
    mov ecx, lengthof szText - 1
    cld
    repne scasb
    je L1
    PrintText '没找到'
    jmp L2
L1: sub ecx, lengthof szText - 1
    neg ecx
    PrintDec ecx    ;如果找得到, 这里显示是第几个字符; 本例结果是 6
L2: ret
main endp
end main
;储存数据:将al/ax/eax中的数据储存到edi指向的数据,执行后根据df的值,edi变化
.data
    len = 31
    szText db len dup(0), 0
.code
main proc
    lea edi, szText
    mov al, 'x'
    mov ecx, len
    cld
    rep stosb
    PrintString szText ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ret
main endp
end main
;载入数据之数组求和:将esi指向的数据载入到al/ax/eax中,执行后根据df的值,edi变化
.data
    dwArr dw 1,2,3,4,5,6,7,8,9,10
.code
main proc
    lea esi, dwArr
    mov ecx, lengthof dwArr
    xor edx, edx
    xor eax, eax
    cld
@@: lodsw
    add edx, eax
    loop @B
    PrintDec edx ;55
    ret
main endp
end main

条件及循环伪指令

;和C语言类似
--------------
.if 条件
    //语句
.elseif
    //语句
.else
    //语句
.endif
--------------
.while 条件
    //语句
.endw
--------------
.repeat
    //语句
.until 条件
--------------
【举个栗子春暖花开】
.code
main proc
    mov eax, 9
    .while TRUE
        PrintDec eax
        dec eax
    .break .if eax == 5
    .endw    
    ret
main endp
end main
【举个栗子春暖花开】
.code
main proc
    mov eax, 0
    .repeat
        inc eax
    .continue .if eax == 2
        PrintDec eax
    .until eax > 3
    ret
main endp
end main

结构体

;结构体的使用
MyPoint struct
    X dd ?
    Y dd ?
MyPoint ends

.data
    pt1 MyPoint <11,22>
.code
main proc
    lea ebx, pt1
    PrintDec (MyPoint ptr [ebx]).X ;11
    PrintDec (MyPoint ptr [ebx]).Y ;22
    ret
main endp
end main
;使用SYSTEMTIME结构获取系统时间
.data
    sysTime SYSTEMTIME <> 
.code
main proc
    ;SYSTEMTIME结构定义在windows.inc,GetLocalTime函数声明在kernel32.inc
    invoke GetLocalTime, addr sysTime
    PrintDec sysTime.wYear  ;2021
    PrintDec sysTime.wMonth ;2
    PrintDec sysTime.wDay   ;8
    ret
main endp
end main
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寂寞烟火~

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值