英格兰世界杯预选赛_世界杯卡塔尔 - sctzjx.com

【Win32汇编】学习Win32汇编

  • Home
  • 世界杯卫冕
  • 【Win32汇编】学习Win32汇编
  • 2025-09-07 20:14:59
  • admin

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

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

书籍推荐

Windows环境下32位汇编语言程序设计(典藏版)

链接:https://pan.baidu.com/s/1XLkMW177ySxt9YUUnqQ9tw提取码:k10i 环境配置

方案一

自己体会 方案二(推荐)

链接:https://pan.baidu.com/s/170AUzz4yyGWGit9f_vs3eA提取码:u3u3 参考博客

万一的Delphi博客

基础篇

第一个程序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

Previus Post
西宁城东精油spa-足疗保健上门养生会馆

Copyright © 2088 英格兰世界杯预选赛_世界杯卡塔尔 - sctzjx.com All Rights Reserved.
友情链接