整理复习汇编语言的知识点,以前在学习《Intel汇编语言程序设计 - 第五版》时没有很认真的整理笔记,主要因为当时是以学习理解为目的没有整理的很详细,这次是我第三次阅读此书,每一次阅读都会有新的收获,这次复习,我想把书中的重点,再一次做一个归纳与总结(注:16位汇编部分跳过),并且继续尝试写一些有趣的案例,这些案例中所涉及的指令都是逆向中的重点,一些不重要的我就直接省略了,一来提高自己,二来分享知识,转载请加出处,敲代码备注挺难受的。

该笔记重点复习字符串操作指令的一些使用技巧,以及浮点数运算相关内容,浮点数运算也是非常重要的知识点,在分析大型游戏时经常会碰到针对浮点数的运算指令,例如枪械换弹动作,人物跳跃时的状态,都属于浮点数运算范围,也就一定会用到浮点数寄存器栈,浮点指令集主要可分为,传送指令,算数指令,比较指令,超越指令,常量加载指令等。

字符串操作指令

移动串指令: MOVSB、MOVSW、MOVSD ;从 ESI -> EDI; 执行后, ESI 与 EDI 的地址移动相应的单位
比较串指令: CMPSB、CMPSW、CMPSD ;比较 ESI、EDI; 执行后, ESI 与 EDI 的地址移动相应的单位
扫描串指令: SCASB、SCASW、SCASD ;依据 AL/AX/EAX 中的数据扫描 EDI 指向的数据, 执行后 EDI 自动变化
储存串指令: STOSB、STOSW、STOSD ;将 AL/AX/EAX 中的数据储存到 EDI 给出的地址, 执行后 EDI 自动变化
载入串指令: LODSB、LODSW、LODSD ;将 ESI 指向的数据载入到 AL/AX/EAX, 执行后 ESI 自动变化

移动串指令: 移动串指令包括MOVSB、MOVSW、MOVSD原理为从ESI到EDI中,执行后将ESI地址里面的内容移动到EDI指向的内存空间中,该指令常用于对特定字符串的复制操作.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.data; 逐字节拷贝SrcString    BYTE "hello lyshark",0h      ; 源字符串SrcStringLen EQU $ - SrcString - 1        ; 计算出原始字符串长度DstString    BYTE SrcStringLen dup(?),0h  ; 目标内存地址szFmt BYTE '字符串: %s 长度: %d ',0dh,0ah,0; 四字节拷贝ddSource DWORD 10h,20h,30h               ; 定义三个四字节数据ddDest   DWORD lengthof ddSource dup(?)  ; 得到目标地址.codemain PROC; 第一种情况: 实现逐字节拷贝cld                         ; 清除方向标志mov esi,offset SrcString    ; 取源字符串内存地址mov edi,offset DstString    ; 取目标字符串内存地址mov ecx,SrcStringLen        ; 指定循环次数,为原字符串长度rep movsb                   ; 逐字节复制,直到ecx=0为止lea eax,dword ptr ds:[DstString]mov ebx,sizeof DstStringinvoke crt_printf,addr szFmt,eax,ebx; 第二种情况: 实现4字节拷贝lea esi,dword ptr ds:[ddSource]lea edi,dword ptr ds:[ddDest]cldrep movsd; 使用loop循环逐字节复制lea esi,dword ptr ds:[SrcString]lea edi,dword ptr ds:[DstString]mov ecx,SrcStringLencld                               ; 设置方向为正向复制@@: movsb                             ; 每次复制一个字节dec ecx                           ; 循环递减jnz @B                            ; 如果ecx不为0则循环lea eax,dword ptr ds:[DstString]mov ebx,sizeof DstStringinvoke crt_printf,addr szFmt,eax,ebxinvoke ExitProcess,0main ENDP
END main

比较串指令: 比较串指令包括CMPSB、CMPSW、CMPSD比较ESI、EDI执行后将ESI指向的内存操作数同EDI指向的内存操作数相比较,其主要从ESI指向内容减去EDI的内容来影响标志位.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.data; 逐字节比较SrcString    BYTE "hello lyshark",0hDstStringA   BYTE "hello world",0h
.constszFmt BYTE '字符串: %s',0dh,0ah,0YES BYTE "相等",0NO  BYTE "不相等",0.codemain PROC; 实现字符串对比,相等/不相等输出lea esi,dword ptr ds:[SrcString]lea edi,dword ptr ds:[DstStringA]mov ecx,lengthof SrcStringcldrepe cmpsbje L1jmp L2L1: lea eax,YESinvoke crt_printf,addr szFmt,eaxjmp lop_endL2:   lea eax,NOinvoke crt_printf,addr szFmt,eaxjmp lop_endlop_end:int 3invoke ExitProcess,0main ENDP
END main

CMPSW 是对比一个字类型的数组,只有当数组中的数据完全一致的情况下才会返回真,否则为假.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.dataArray1 WORD 1,2,3,4,5      ; 必须全部相等才会清空ebxArray2 WORD 1,3,5,7,9
.constszFmt BYTE '数组: %s',0dh,0ah,0YES BYTE "相等",0NO  BYTE "不相等",0.codemain PROClea esi,Array1lea edi,Array2mov ecx,lengthof Array1cldrepe cmpswje L1lea eax,NOinvoke crt_printf,addr szFmt,eaxjmp lop_endL1: lea eax,YESinvoke crt_printf,addr szFmt,eaxjmp lop_endlop_end:int 3invoke ExitProcess,0main ENDP
END main

CMPSD则是比较双字数据,同样可用于比较数组,这里就演示一下比较单数的情况.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.datavar1 DWORD 1234hvar2 DWORD 5678h
.constszFmt BYTE '两者: %s',0dh,0ah,0YES BYTE "相等",0NO  BYTE "不相等",0.codemain PROClea esi,dword ptr ds:[var1]lea edi,dword ptr ds:[var2]cmpsdje L1lea eax,dword ptr ds:[YES]invoke crt_printf,addr szFmt,eaxjmp lop_endL1:  lea eax,dword ptr ds:[NO]invoke crt_printf,addr szFmt,eaxjmp lop_endlop_end:int 3invoke ExitProcess,0main ENDP
END main

扫描串指令: 扫描串指令包括SCASB、SCASW、SCASD其作用是把AL/AX/EAX中的值同EDI寻址的目标内存中的数据相比较,这些指令在一个长字符串或者数组中查找一个值的时候特别有用.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.dataszText BYTE "ABCDEFGHIJK",0
.constszFmt BYTE '字符F所在位置: %d',0dh,0ah,0.codemain PROC; 寻找单一字符找到会返回第几个字符lea edi,dword ptr ds:[szText]mov al,"F"mov ecx,lengthof szText -1cldrepne scasb                 ; 如果不相等则重复扫描je L1xor eax,eax                 ; 如果没找到F则清空eaxjmp lop_endL1:   sub ecx,lengthof szText -1neg ecx                     ; 如果找到输出第几个字符invoke crt_printf,addr szFmt,ecxlop_end:int 3main ENDP
END main

如果我们想要对数组中某个值是否存在做判断可以使用SCASD指令,对数组进行扫描.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.dataMyArray DWORD 65,88,93,45,67,89,34,67,89,22
.constszFmt BYTE '数值: %d 存在',0dh,0ah,0
.codemain PROClea edi,dword ptr ds:[MyArray]mov eax,34mov ecx,lengthof MyArray - 1cldrepne scasdje L1xor eax,eaxjmp lop_endL1:  sub ecx,lengthof MyArray - 1neg ecxinvoke crt_printf,addr szFmt,ecx,eaxlop_end:int 3main ENDP
END main

储存串指令: 存储指令主要包括STOSB、STOSW、STOSD起作用是把AL/AX/EAX中的数据储存到EDI给出的地址中,执行后EDI的值根据方向标志的增加或减少,该指令常用于初始化内存或堆栈.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.dataCount  DWORD 100String BYTE 100 DUP(?),0.codemain PROC; 利用该指令初始化字符串mov al,0ffh                   ; 初始化填充数据lea di,byte ptr ds:[String]   ; 待初始化地址mov ecx,Count                 ; 初始化字节数cld                           ; 初始化:方向=前方rep stosb                     ; 循环填充; 存储字符串: 使用A填充内存lea edi,dword ptr ds:[String]mov al,"A"mov ecx,Countcldrep stosbint 3main ENDP
END main

载入串指令: 载入指令主要包括LODSB、LODSW、LODSD起作用是将ESI指向的内存位置向AL/AX/EAX中装载一个值,同时ESI的值根据方向标志值增加或减少,如下分别完成加法与乘法计算,并回写到内存中.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.dataArrayW      WORD 1,2,3,4,5,6,7,8,9,10ArrayDW     DWORD 1,2,3,4,5ArrayMulti  DWORD 10szFmt BYTE '计算结果: %d ',0dh,0ah,0.codemain PROC; 利用载入命令计算数组加法lea esi,dword ptr ds:[ArrayW]mov ecx,lengthof ArrayWxor edx,edxxor eax,eax@@: lodsw          ; 将输入加载到EAXadd edx,eaxloop @Bmov eax,edx    ; 最后将相加结果放入eaxinvoke crt_printf,addr szFmt,eax; 利用载入命令(LODSD)与存储命令(STOSD)完成乘法运算mov esi,offset ArrayDW   ; 源指针mov edi,esi              ; 目的指针cld                      ; 方向=向前mov ecx,lengthof ArrayDW ; 循环计数器L1:    lodsd                    ; 加载[esi]至EAXmul ArrayMulti           ; 将EAX乘以10stosd                    ; 将结果从EAX存储至[EDI]loop L1; 循环读取数据(存在问题)mov esi,offset ArrayDW     ; 获取基地址mov ecx,lengthof ArrayDW   ; 获取长度xor eax,eax@@: lodsdinvoke crt_printf,addr szFmt,eaxdec ecxloop @B    int 3main ENDP
END main

统计字符串: 过程StrLength()通过循环方式判断字符串结尾的0标志,来统计字符串的长度,最后将结果存储在EAX中.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataString BYTE "hello lyshark",0szFmt  BYTE '计算结果: %d ',0dh,0ah,0.code; 计算字符串长度StrLength PROC USES edi,pString:PTR BYTEmov edi,offset String    ; 取出字符串的基地址xor eax,eax              ; 清空eax用作计数器L1:  cmp byte ptr [edi],0     ; 分别那[edi]的值和0作比较je L2                    ; 上一步为零则跳转得到retinc edi                  ; 否则继续执行inc eaxjmp L1L2: retStrLength endpmain PROCinvoke StrLength, addr Stringinvoke crt_printf,addr szFmt,eaxint 3main ENDP
END main

字符串转换: 字符串转换是将小写转为大写,或者将大写转为小写,其原理是将二进制位第五位置1或0则可实现.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataMyString BYTE "hello lyshark",0szFmt  BYTE '结果: %s ',0dh,0ah,0.codemain PROCmov esi,offset MyString        ; 取出字符串的偏移地址L1:    cmp byte ptr [esi],0           ; 分别拿出每一个字节,与0比较je L2                          ; 如果相等则跳转到L2and byte ptr [esi],11011111b   ; 执行按位与操作inc esi                        ; 每次esi指针递增1jmp L1                         ; 重复循环L2:   lea eax,dword ptr ds:[MyString]invoke crt_printf,addr szFmt,eaxretmain ENDP
END main

字符串拷贝: 使用两个指针分别指向两处区域,然后通过变址寻址的方式实现对特定字符串的拷贝.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.datasource BYTE "hello lyshark welcome",0htarget BYTE SIZEOF source DUP(0),0h       ; 取源地址数据大小szFmt BYTE '结果: %s ',0dh,0ah,0
.codemain PROC; 实现正向拷贝字符串mov esi,0                                ; 使用变址寄存器mov ecx,sizeof source                    ; 循环计数器L1:mov al,byte ptr ds:[source + esi]         ; 从源地址中取一个字符mov byte ptr ds:[target + esi],al         ; 将该字符存储在目标地址中inc esi                                   ; 递增,将指针移动到下一个字符loop L1lea eax,dword ptr ds:[target]invoke crt_printf,addr szFmt,eax; 实现反向拷贝字符串mov esi,sizeof sourcemov ecx,sizeof sourcemov ebx,0L2:mov al,byte ptr ds:[source + esi]mov byte ptr ds:[target + esi],aldec esiinc ebxloop L2lea eax,dword ptr ds:[target]invoke crt_printf,addr szFmt,eaxpush 0call ExitProcessmain ENDP
END main

浮点数操作指令集(重点)

浮点数的计算是不依赖于CPU的,运算单元是从80486处理器开始才被集成到CPU中的,该运算单元被称为FPU浮点运算模块,FPU不使用CPU中的通用寄存器,其有自己的一套寄存器,被称为浮点数寄存器栈,FPU将浮点数从内存中加载到寄存器栈中,完成计算后在回写到内存中.

FPU有8个可独立寻址的80位寄存器,分别名为R0-R7他们以堆栈的形式组织在一起,栈顶由FPU状态字中的一个名为TOP的域组成,对寄存器的引用都是相对于栈顶而言的,栈顶通常也被叫做ST(0),最后一个栈底则被记作ST(7)其实用方式与堆栈完全一致.

浮点数运算通常会使用一些更长的数据类型,如下就是MASM汇编器定义的常用数据类型.

.datavar1 QWORD  10.1    ; 64位整数var2 TBYTE  10.1    ; 80位(10字节)整数var3 REAL4  10.2    ; 32位(4字节)短实数var4 REAL8  10.8    ; 64位(8字节)长实数var5 REAL10 10.10   ; 80位(10字节)扩展实数

此外浮点数对于指令的命名规范也遵循一定的格式,浮点数指令总是以F开头,而指令的第二个字母则表示操作位数,例如:B表示二十进制操作数,I表示二进制整数操作,如果没有指定则默认则是针对实数的操作fld等.

FLD/FSTP 操作指令: 这两个指令是最基本的浮点操作指令,其中的FLD入栈指令,后面的FSTP则是将浮点数弹出堆栈.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.datavar1 QWORD 10.0var2 QWORD 20.0var3 QWORD 30.0var4 QWORD 40.0result QWORD ?
.codemain PROC; 初始化浮点单元finit; 依次将数据入栈fld qword ptr ds:[var1]fld qword ptr ds:[var2]fld qword ptr ds:[var3]fld qword ptr ds:[var4]; 获取当前ST(0)栈帧元素fst qword ptr ds:[result]; 从栈中弹出元素fstp qword ptr ds:[result]fstp qword ptr ds:[result]fstp qword ptr ds:[result]fstp qword ptr ds:[result]int 3main ENDP
END main

压栈时会自动向下填充,而出栈时则相反,不但要出栈,还会将地址回绕到底部,覆盖掉底部的数据。

当压栈参数超出了最大承载范围,就会覆盖掉正常的数据,导致错误。

压栈同样支持变址寻址的方式,如下我们可以通过循环将一个数组压入浮点数寄存器,其中使用FLD指令时压入一个浮点实数,而FILD则是将实数转换为双精度浮点数后压入堆栈.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataArray QWORD 10.0,20.0,30.0,40.0,50.0Count DWORD ?Result QWORD ?
.codemain PROC; 初始化浮点单元finitmov dword ptr ds:[Count],0jmp L1L2: mov eax,dword ptr ds:[Count]add eax,1mov dword ptr ds:[Count],eaxL1:    mov eax,dword ptr ds:[Count]cmp eax,5jge lop_end; 使用此方式压栈fld qword ptr ds:[Array + eax * 8]   ; 压入浮点实数fild qword ptr ds:[Array + eax * 8]  ; 压入双精度浮点数jmp L2lop_end:int 3main ENDP
END main

浮点交换指令: 浮点交换有两个指令需要特别注意,第一个是FCHS该指令把ST(0)中的值的符号变反,FABS指令则是取ST(0)中值的绝对值,这两条指令无传递操作数.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataArray QWORD 10.0,20.0,30.0,40.0,50.0Result QWORD ?szFmt BYTE 'ST寄存器: %f ',0dh,0ah,0
.codemain PROC; 初始化压栈finitfld qword ptr ds:[Array]fld qword ptr ds:[Array + 8]fld qword ptr ds:[Array + 16]fld qword ptr ds:[Array + 24]fld qword ptr ds:[Array + 32]; 对ST(0)数据取反 (不影响浮点堆栈)fchs                                               ; 对ST(0)取反fchs                                               ; 再次取反fst qword ptr ds:[Result]                          ; 取ST(0)赋值到Resultinvoke crt_printf,addr szFmt,qword ptr ds:[Result]; 循环将数组取反后回写如Array中mov ecx,5S1:fchsfstp qword ptr ds:[Array + ecx * 8]loop S1; 读入Array中的数据到ST寄存器mov ecx,5S2:fld qword ptr ds:[Array + ecx * 8]loop S2; 通过FABS取绝对值,并反写会Array中mov ecx,5S3:fabs                                  ; 取ST(0)的绝对值fstp qword ptr ds:[Array + ecx * 8]   ; 反写loop S3int 3main ENDP
END main

浮点加法指令: 浮点数加法,该加法分为FADD/FADDP/FIADD分别针对不同的场景,此外还会区分无操作数模式,寄存器操作数,内存操作数,整数相加等.

第一种无操作数模式,执行FADD时,ST(0)寄存器和ST(1)寄存器相加后,结果临时存储在ST(1)中,然后将ST(0)弹出堆栈,最终结果就会存储在栈顶部,使用FST指令即可取出来.

第二种则是两个浮点寄存器相加,最后的结果会存储在源操作数ST(0)中.

第三种则是内存操作数,就是ST寄存器与内存相加.

第四种是与整数相加,默认会将整数扩展为双精度,然后在于ST(0)相加.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataArray  QWORD 10.0,20.0,30.0,40.0,50.0IntA   DWORD 10Result QWORD ?szFmt BYTE 'ST寄存器: %f ',0dh,0ah,0
.codemain PROCfinitfld qword ptr ds:[Array]fld qword ptr ds:[Array + 8]fld qword ptr ds:[Array + 16]fld qword ptr ds:[Array + 24]fld qword ptr ds:[Array + 32]; 第一种:无操作数 fadd = faddp;fadd;faddp; 第二种:两个浮点寄存器相加fadd st(0),st(1)          ; st(0) = st(0) + st(1)fst qword ptr ds:[Result] ; 取出结果invoke crt_printf,addr szFmt,qword ptr ds:[Result]fadd st(0),st(2)          ; st(0) = st(0) + st(2)fst qword ptr ds:[Result] ; 取出结果invoke crt_printf,addr szFmt,qword ptr ds:[Result]; 第三种:寄存器与内存相加fadd qword ptr ds:[Array] ; st(0) = st(0) + Arrayfst qword ptr ds:[Result] ; 取出结果invoke crt_printf,addr szFmt,qword ptr ds:[Result]fadd real8 ptr ds:[Array + 8]fst qword ptr ds:[Result] ; 取出结果invoke crt_printf,addr szFmt,qword ptr ds:[Result]; 第四种:与整数相加fiadd dword ptr ds:[IntA]fst qword ptr ds:[Result] ; 取出结果invoke crt_printf,addr szFmt,qword ptr ds:[Result]int 3main ENDP
END main

浮点减法指令: 浮点数减法,该加法分为FSUB/FSUBP/FISUB该指令从目的操作数中减去原操作数,把差存储在目的操作数中,目的操作数必须是ST寄存器,源操作数可以是寄存器或内存,运算的过程与加法指令完全一致.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataArray      QWORD 10.0,20.0,30.0,40.0,50.0IntQWORD   QWORD 20Result QWORD ?szFmt BYTE 'ST寄存器: %f ',0dh,0ah,0
.codemain PROCfinitfld qword ptr ds:[Array]fld qword ptr ds:[Array + 8]fld qword ptr ds:[Array + 16]fld qword ptr ds:[Array + 24]fld qword ptr ds:[Array + 32]; 第一种:无操作数减法;fsub;fsubp                         ; st(0) = st(0) - st(1); 第二种:两个浮点数寄存器相减fsub st(0),st(1)               ; st(0) = st(0) - st(1)fst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]; 第三种:寄存器与内存相减fsub qword ptr ds:[Array]      ; st(0) = st(0) - Arrayfst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]; 第四种:与整数相减fisub dword ptr ds:[IntQWORD]  ; st(0) = st(0) - IntQWORDfst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]int 3main ENDP
END main

浮点乘除法指令: 浮点数乘法指令有FMUL/FMULP/FIMUL,浮点数除法则包括FDIV/FDIVP/FIDIV这三种,其主要的使用手法与前面的加减法保持一致,下面是乘除法的总结.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataArray      QWORD 10.0,20.0,30.0,40.0,50.0IntQWORD   QWORD 20Result     QWORD ?szFmt BYTE 'ST寄存器: %f ',0dh,0ah,0
.code
InitFLD PROCfinitfld qword ptr ds:[Array]fld qword ptr ds:[Array + 8]fld qword ptr ds:[Array + 16]fld qword ptr ds:[Array + 24]fld qword ptr ds:[Array + 32]ret
InitFLD endpmain PROCinvoke InitFLD; 第一种:无操作数乘法与除法fmulfmulp              ; st(0) = st(0) * st(1)fst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]fdivfdivp              ; st(0) = st(0) / st(1)fst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]; 第二种:两个浮点数寄存器之间的乘法与除法invoke InitFLDfmul st(0),st(4)    ; st(0) = st(0) * st(4)fst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]fdiv st(0),st(2)    ; st(0) = st(0) / st(2)fst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]; 第三种:寄存器与内存之间的乘法与除法invoke InitFLDfmul qword ptr ds:[Array + 8]     ; st(0) = st(0) * [Array + 8]fst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]fdiv qword ptr ds:[Array + 16]    ; st(0) = st(0) / [Array + 16]fst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]; 第四种:与整数之间的乘法与除法invoke InitFLDfimul dword ptr ds:[IntQWORD]     ; st(0) = st(0) * IntQWORDfst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]fidiv dword ptr ds:[IntQWORD]     ; st(0) = st(0) / IntQWORDfst qword ptr ds:[Result]invoke crt_printf,addr szFmt,qword ptr ds:[Result]int 3main ENDP
END main

浮点数比较指令: 浮点数比较指令包括FCOM/FCOMP/FCOMPP这三个指令都是比较ST(0)和源操作数,源操作数可以是内存操作数或FPU寄存器,FCOM和FCOMP格式基本一致,唯一区别在于FCOMP在执行对比后还要从堆栈中弹出元素,FCOMP和FCOMPP也基本一致,最后都是要从堆栈中弹出元素.

比较指令的重点就是比较条件码的状态,FPU中包括三个条件状态,分别是C3(零标志),C2(奇偶标志),C0(进位标志),我们可以使用FNSTSW指令将这些状态字送入AX寄存器中,然后通过SAHF指令把AH赋值到EFLAGS标志中,一旦标志状态被送入EFLAGS寄存器,那么就可以使用标准的标志位对跳转指令进行影响了,例如以下代码的汇编案例.

double x = 1.2; double y = 3.0; int n = 0;
if(x<y)
{n=1;
}; ----------------------------------------------------
; C语言伪代码的汇编指令如下
; ----------------------------------------------------
.datax REAL8 1.2y REAL8 3.0n DWORD 0
.codemain PROCfld x        ; st(0) = xfcomp y      ; cmp x,y ; pop xfnstsw ax    ; 取出状态值送入AXsahf         ; 将状态字送入EFLAGSjnb L1       ; x < y 小于mov n,1      ; 满足则将n置1L1: xor eax,eax  ; 否则清空寄存器int 3main ENDP
END main

对于前面的案例来说,由于浮点数运算比整数运算在开销上会更大一些,因此Intel新版处理器新增加了FCOMI指令,专门用于比较两个浮点数的值,并自动设置零标志,基偶标志,和进位标志,唯一的缺点是其不支持内存操作数,针对上方案例的修改如下.

.datax REAL8 1.2y REAL8 3.0n DWORD 0
.codemain PROCfld yfld xfcomi st(0),st(1)jnb L1            ; st(0) not st(1) ?mov n,1L1:    xor eax,eaxint 3main ENDP
END main

对于浮点数的比较来说,例如比较X与Y是否相等,如果比较X==y?则可能会出现近似值的情况,导致无法计算出正确结果,正确的做法是取其差值的绝对值,并和用户自定义的小的正数相比较,小的正整数作为两个值相等时其差值的临界值.

.dataepsilon REAL8 1.0E-12var2    REAL8 0.0var3    REAL8 1.001E-13
.codemain PROCfld epsilonfld var2fsub var3fabsfcomi st(0),st(1) ; cmp epsilon,var2ja skipxor ebx,ebx       ; 相等则清空ebxskip:int 3             ; 不相等则结束main ENDP
END main

浮点表达式: 通过浮点数计算表达式valD = -valA + (valB * valC)其计算过程,首先加载ValA并取反,加载valB至ST(0),这时-ValA保存在ST(1)中,valC和ST(0)相乘,乘基保存在ST(0)中,最后ST(0)与ST(1)相加后存入ValD中.

.datavalA REAL8 1.5valB REAL8 2.5valC REAL8 3.0valD REAL8 ?
.codemain PROCfld valA         ; 加载valAfchs             ; 取反-valAfld valB         ; 加载valB = st(0)fmul valC        ; st(0) = st(0) * valCfadd             ; st(0) = st(0) + st(1)fstp valD        ; valD = st(0)main ENDP
END main

通过循环计算一个双精度数组中所有元素的总和.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataMyArray REAL8 10.0,20.0,30.0,40.0,50.0
.codemain PROCmov esi,0           ; 设置因子fldz                ; st(0)清空mov ecx,5           ; 设置数组数L1: fld MyArray[esi]    ; 压入栈fadd                ; st(0) = st(0) + MyArray[esi]add esi,TYPE REAL8  ; esi += 8loop L1main ENDP
END main

求ValA与ValB两数的平方根,FSQRT指令计算ST(0)的平方根并把结果存储在ST(0)中,如下是计算平方根方法.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.datavalA REAL8 25.0valB REAL8 39.0
.codemain PROCfld valAfsqrt         ; st(0) = sqrt(valA)fld valB      ; push valBfsqrt         ; st(0) = sqrt(valB)fadd          ; add st(0),st(1)main ENDP
END main

接着看一下计算数组的点积面,例如(Array[0] * Array[1]) + (Array[2] * Array[3])这种计算就叫做点积面计算.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataArray REAL4 6.0,3.0,5.0,7.0
.codemain PROCfld Arrayfmul [Array + 4]fld [Array + 8]fmul [Array + 12]faddmain ENDP
END main

有时候我们需要混合计算,也就是整数与双精度浮点数进行运算,此时在执行运算前会将整数自动提升为浮点数,例如下面的两个案例,第一个是整数与浮点数相加时,整数自动提升为浮点数,第二个则需要调用FIST指令对Z向上裁剪保留整数部分.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataN DWORD 20X REAL8 3.5Z REAL8 ?
.codemain PROC; 计算 int N = 20; double X = 3.5; double Z = N + X;fild N      ; 加载整数到ST(0)fadd X      ; ST(0) = ST(0) + X fstp Z      ; 存储到Z中; 计算 int N = 20; double X = 3.5; int Z=(int)(N+X)fild Nfadd Xfist E      ; 将浮点数裁剪,只保留整数部分main ENDP
END main

过程与结构体(扩展知识点)

过程的实现离不开堆栈的应用,堆栈是一种后进先出(LIFO)的数据结构,最后压入栈的值总是最先被弹出,而新数值在执行压栈时总是被压入到栈的最顶端,栈主要功能是暂时存放数据和地址,通常用来保护断点和现场.

栈是由CPU管理的线性内存数组,它使用两个寄存器(SS和ESP)来保存栈的状态.SS寄存器存放段选择符,而ESP寄存器的值通常是指向特定位置的一个32位偏移值,我们很少需要直接操作ESP寄存器,相反的ESP寄存器总是由CALL,RET,PUSH,POP等这类指令间接性的修改.

CPU系统提供了两个特殊的寄存器用于标识位于系统栈顶端的栈帧.
ESP 栈指针寄存器: 栈指针寄存器,其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶.
EBP 基址指针寄存器: 基址指针寄存器,其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部.

在通常情况下ESP是可变的,随着栈的生成而逐渐变小,而EBP寄存器是固定的,只有当函数的调用后,发生入栈操作而改变.

执行PUSH压栈时,堆栈指针自动减4,再将压栈的值复制到堆栈指针所指向的内存地址.
执行POP出栈时,从栈顶移走一个值并将其复制给内存或寄存器,然后再将堆栈指针自动加4.
执行CALL调用时,CPU会用堆栈保存当前被调用过程的返回地址,直到遇到RET指令再将其弹出.

PUSH/POP 入栈出栈: 执行PUSH指令时,首先减小ESP的值,然后把源操作数复制到堆栈上,执行POP指令则是先将数据弹出到目的操作数中,然后在执行ESP值增加4,如下案例,分别将数组中的元素压入栈,并且通过POP将元素反弹出来.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataArray DWORD 1,2,3,4,5,6,7,8,9,10szFmt BYTE '%d ',0dh,0ah,0
.codemain PROC; 使用Push指令将数组正向入栈mov eax,0mov ecx,10S1:push dword ptr ds:[Array + eax * 4]inc eaxloop S1; 使用pop指令将数组反向弹出mov ecx,10S2:push ecx                         ; 保护ecxpop ebx                          ; 将Array数组元素弹出到ebxinvoke crt_printf,addr szFmt,ebxpop ecx                          ; 弹出ecxloop S2int 3main ENDP
END main

由于堆栈是先进后出的结构,所以我们可以利用这一特性,首先循环将字符串压入堆栈,然后再从堆栈中反向弹出来,这样就可以实现字符串的反转操作了,实现代码如下:

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataMyString BYTE "hello lyshark",0NameSize DWORD ($ - MyString) - 1szFmt BYTE '%s',0dh,0ah,0
.codemain PROC; 正向压入字符串mov ecx,dword ptr ds:[NameSize]mov esi,0S1:  movzx eax,byte ptr ds:[MyString + esi]push eaxinc esiloop S1; 反向弹出字符串mov ecx,dword ptr ds:[NameSize]mov esi,0S2:   pop eaxmov byte ptr ds:[MyString + esi],alinc esiloop S2invoke crt_printf,addr szFmt,addr MyStringint 3main ENDP
END main

PROC/ENDP 伪指令: 该指令可用于创建过程化流程,过程使用PROC和ENDP伪指令来声明,下面我们通过使用过程创建ArraySum方法,实现对整数数组求和操作,默认规范将返回值存储在EAX中,直接打印出来就好.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataMyArray  DWORD 1,2,3,4,5,6,7,8,9,10Sum      DWORD ?szFmt    BYTE '%d',0dh,0ah,0
.code; 数组求和过程ArraySum PROCpush esi                     ; 保存ESI,ECXpush ecxxor eax,eaxS1:    add eax,dword ptr ds:[esi]   ; 取值并相加add esi,4                    ; 递增数组指针loop S1pop ecx                      ; 恢复ESI,ECXpop esiretArraySum endpmain PROClea esi,dword ptr ds:[MyArray]   ; 取出数组基址mov ecx,lengthof MyArray         ; 取出元素数目call ArraySum                    ; 调用方法mov dword ptr ds:[Sum],eax       ; 得到结果invoke crt_printf,addr szFmt,Sumint 3main ENDP
END main

接着来实现一个获取随机数的案例,具体原理就是获取随机种子,使用除法运算取出溢出数据作为随机数使用,特殊常量地址343FDh每次访问也会产出一个随机的数据.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataseed DWORD 1szFmt    BYTE '随机数: %d',0dh,0ah,0
.code; 生成 0 - FFFFFFFFh 的随机种子Random32 PROCpush  edxmov   eax, 343FDhimul  seedadd   eax, 269EC3hmov   seed, eaxror   eax,8pop   edxretRandom32 endp; 生成随机数RandomRange PROCpush  ebxpush  edxmov   ebx,eaxcall  Random32mov   edx,0div   ebxmov   eax,edxpop   edxpop   ebxretRandomRange endpmain PROC; 调用后取出随机数call RandomRangeinvoke crt_printf,addr szFmt,eaxint 3main ENDP
END main

局部变量与堆栈传参: 局部变量是在程序运行时,由系统动态的在栈上开辟的,在内存中通常在基址指针(EBP)之下,尽管在汇编时不能给定默认值,但可以在运行时初始化,如下一段伪代码:

void MySub()
{int var1 = 10;int var2 = 20;
}

上面的一段代码经过C编译后,会变成如下,其中EBP-4必须是4的倍数,因为默认就是4字节存储,如果去掉了mov esp,ebp,那么当执行pop ebp时将会得到EBP等于10,执行RET指令会导致控制转移到内存地址10处执行,从而程序会崩溃.

MySub PROCpush ebp                  ; 将EBP存储在栈中mov ebp,esp               ; 堆栈框架的基址sub esp,8                 ; 创建局部变量空间(分配2个局部变量)mov DWORD PTR [ebp-8],10  ; var1 = 10mov DWORD PTR [ebp-4],20  ; var2 = 20mov esp,ebp               ; 从堆栈上删除局部变量pop ebp                   ; 恢复EBP指针ret 8                     ; 返回,清理堆栈
MySub ENDP

为了使代码更容易阅读,可以在上面的代码的基础上给每个变量的引用地址都定义一个符号,并在代码中使用这些符号.

var1_local EQU DWORD PTR [ebp-8]   ; 添加符号1
var2_local EQU DWORD PTR [ebp-4]   ; 添加符号2MySub PROCpush ebpmov ebp,espsub esp,8mov var1_local,10mov var2_local,20mov esp,ebppop ebpret 8
MySub ENDP

接着我们来写一个案例,首先C语言伪代码如下,其中的MakeArray()函数内部是动态生成的一个MyString数组,然后通过循环填充为星号,最后使用POP弹出,并输出结果,观察后尝试用汇编实现.

void makeArray()
{char MyString[30];for(int i=0;i<30;i++){myString[i] = "*";}
}call makeArray()

汇编代码如下,唯一需要注意的地方就是出栈是平栈参数,例如我们使用了影响堆栈操作的指令,则平栈要手动校验并修复.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataszFmt BYTE '出栈数据: %x ',0dh,0ah,0
.codemakeArray PROCpush ebpmov ebp,esp; 开辟局部数组sub esp,32                    ; MyString基地址位于 [ebp - 30]lea esi,[ebp - 30]            ; 加载MyString的地址; 填充数据mov ecx,30                    ; 循环计数S1:    mov byte ptr ds:[esi],'*'     ; 填充为*inc esi                       ; 每次递增一个字节loop S1; 弹出2个元素并输出,出栈数据pop eaxinvoke crt_printf,addr szFmt,eaxpop eaxinvoke crt_printf,addr szFmt,eax  ; 以下平栈,由于我们手动弹出了2个数据; 则平栈 32 - (2 * 4) = 24 add esp,24                    ; 平栈mov esp,ebppop ebp                       ; 恢复EBPretmakeArray endpmain PROCcall makeArrayinvoke ExitProcess,0main ENDP
END main

接着来看一下堆栈传参中平栈方的区别,平栈方可以是调用者平栈也可以由被调用者平,如下案例分别演示了两种平栈方式.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataszFmt BYTE '数据: %d ',0dh,0ah,0
.code; 第一种方式:被调用者平栈MyProcA PROCpush ebpmov ebp,espxor eax,eaxmov eax,dword ptr ss:[ebp + 16]   ; 获取第一个参数mov ebx,dword ptr ss:[ebp + 12]   ; 获取第二个参数mov ecx,dword ptr ss:[ebp + 8]    ; 获取第三个参数add eax,ebxadd eax,ebxadd eax,ecxmov esp,ebppop ebpret 12       ; 此处ret12可平栈,也可使用 add ebp,12MyProcA endp; 第二种方式:调用者平栈MyProcB PROCpush ebpmov ebp,espmov eax,dword ptr ss:[ebp + 8]add eax,10mov esp,ebppop ebpretMyProcB endpmain PROC; 第一种被调用者MyProcA平栈 3*4 = 12push 1push 2push 3call MyProcAinvoke crt_printf,addr szFmt,eax; 第二种方式:调用者平栈push 10call MyProcBadd esp,4invoke crt_printf,addr szFmt,eaxint 3main ENDP
END main

如果使用PROC定义过程,则传递参数是可以使用push的方式实现堆栈传参,如下所示.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataszFmt BYTE '计算参数: %d ',0dh,0ah,0.codemy_proc PROC x:DWORD,y:DWORD,z:DWORD   ; 定义过程局部参数LOCAL @sum:DWORD               ; 定义局部变量存放总和mov eax,dword ptr ds:[x]mov ebx,dword ptr ds:[y]       ; 分别获取到局部参数mov ecx,dword ptr ds:[z]add eax,ebxadd eax,ecx                    ; 相加后放入eaxmov @sum,eaxretmy_proc endpmain PROCLOCAL @ret_sum:DWORDpush 10push 20push 30          ; 传递参数call my_procmov @ret_sum,eax ; 获取结果并打印invoke crt_printf,addr szFmt,@ret_sumint 3main ENDP
END main

局部变量操作符: 上方的代码中我们在申请局部变量时都是通过手动计算的,在汇编中可以使用LOCAL伪指令来实现自动计算局部变量空间,以及最后的平栈,极大的提高了开发效率.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.codemain PROC; 定义局部变量,自动压栈/平栈LOCAL var_byte:BYTE,var_word:WORD,var_dword:DWORDLOCAL var_array[3]:DWORD; 填充局部变量mov byte ptr ds:[var_byte],1mov word ptr ds:[var_word],2mov dword ptr ds:[var_dword],3; 填充数组方式1lea esi,dword ptr ds:[var_array]mov dword ptr ds:[esi],10mov dword ptr ds:[esi + 4],20mov dword ptr ds:[esi + 8],30; 填充数组方式2mov var_array[0],100mov var_array[1],200mov var_array[2],300invoke ExitProcess,0main ENDP
END main

USES/ENTER 伪指令: 指令USES的作用是当我们需要压栈保存指定寄存器时,可以使用此关键字,汇编器会自动为我们保存寄存器中参数,ENTER指令则是预定义保留局部变量的指令.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.code; USES 自动压入 eax,ebx,ecx,edxmy_proc PROC USES eax ebx ecx edx x:DWORD,y:DWORDenter 8,0          ; 自动保留8字节堆栈空间add eax,ebxleavemy_proc endpmain PROCmov eax,10mov ebx,20call my_procint 3main ENDP
END main

堆栈传参(递归阶乘): 通过EAX寄存器传递一个数值,然后使用Factorial过程递归调用自身,实现对该数阶乘的计算.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib.dataszFmt BYTE '数据: %d ',0dh,0ah,0
.codeFactorial PROCpush ebpmov ebp,espmov eax,dword ptr ss:[ebp + 8]   ; 取出参数cmp eax,0                        ; eax > 0 ?ja L1mov eax,1                        ; 否则返回1jmp L2L1: dec eaxpush eaxcall Factorial                   ; 调用自身mov ebx,dword ptr ss:[ebp + 8]mul ebx                          ; 取参数/相乘L2:   mov esp,ebppop ebpret 4Factorial endpmain PROC; 第一组push 3call Factorialinvoke crt_printf,addr szFmt,eax; 第二组push 5call Factorialinvoke crt_printf,addr szFmt,eaxint 3main ENDP
END main

Struct/Union 结构与联合体: 结构体就是将一组不同内存属性的变量封装成为统一的整体,结构常用于定义组合的数据类型,结构在内存中的分布也是线性的,其存储形式与数组非常相似,我们同样可以使用数组的规范化排列实现一个结构体.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib; 定义坐标结构
MyPoint Structpos_x DWORD ?pos_y DWORD ?pos_z DWORD ?
MyPoint ends; 定义人物结构
MyPerson StructFname db 20 dup(0)fAge  db 100fSex  db 20
MyPerson ends.data; 声明结构: 使用 <>,{}符号均可PtrA MyPoint <10,20,30>PtrB MyPoint {100,200,300}; 声明结构: 使用MyPerson声明结构UserA MyPerson <'lyshark',24,1>.codemain PROC; 获取结构中的数据lea esi,dword ptr ds:[PtrA]mov eax,(MyPoint ptr ds:[esi]).pos_xmov ebx,(MyPoint ptr ds:[esi]).pos_ymov ecx,(MyPoint ptr ds:[esi]).pos_z; 向结构中写入数据lea esi,dword ptr ds:[PtrB]mov (MyPoint ptr ds:[esi]).pos_x,10mov (MyPoint ptr ds:[esi]).pos_y,20mov (MyPoint ptr ds:[esi]).pos_z,30; 直接获取结构中的数据mov eax,dword ptr ds:[UserA.Fname]mov ebx,dword ptr ds:[UserA.fAge]int 3main ENDP
END main

结构数组的构造与寻址,第一次总结,存在问题的,寻址是否可以这样 mov eax,dword ptr ds:[PtrA + esi + ecx * 4]

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib; 定义坐标结构
MyPoint Structpos_x DWORD ?pos_y DWORD ?pos_z DWORD ?
MyPoint ends.data; 声明结构: 使用 <>,{}符号均可PtrA MyPoint <10,20,30>,<40,50,60>,<70,80,90>,<100,110,120>szFmt BYTE '结构数据: %d',0dh,0ah,0
.codemain PROC; 获取结构中的数据lea esi,dword ptr ds:[PtrA]mov eax,(MyPoint ptr ds:[esi]).pos_x          ; 获取第一个结构Xmov eax,(MyPoint ptr ds:[esi + 12]).pos_x     ; 获取第二个结构X; 循环遍历结构中的所有值mov esi,0                ; 遍历每个结构mov ecx,4                ; 循环4个大结构S1:push ecxmov ecx,3S2:mov eax,dword ptr ds:[PtrA + esi + ecx * 4]invoke crt_printf,addr szFmt,eaxpop ecxloop S2add esi,12loop S1int 3main ENDP
END main

输出数组的第二种方式

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib; 定义坐标结构
MyPoint Structpos_x DWORD ?pos_y DWORD ?pos_z DWORD ?
MyPoint ends; 定义循环结构
MyCount Structcount_x DWORD ?count_y DWORD ?
MyCount ends.data; 声明结构: 使用 <>,{}符号均可PtrA  MyPoint <10,20,30>,<40,50,60>,<70,80,90>,<100,110,120>Count MyCount <0,0>szFmt BYTE '结构数据: %d',0dh,0ah,0.codemain PROC; 获取结构中的数据lea esi,dword ptr ds:[PtrA]mov eax,(MyPoint ptr ds:[esi]).pos_x          ; 获取第一个结构Xmov eax,(MyPoint ptr ds:[esi + 12]).pos_x     ; 获取第二个结构X; while 循环输出结构的每个首元素元素mov (MyCount ptr ds:[Count]).count_x,0S1:    cmp (MyCount ptr ds:[Count]).count_x,48        ; 12 * 4 = 48jge lop_endmov ecx,(MyCount ptr ds:[Count]).count_xmov eax,dword ptr ds:[PtrA + ecx]              ; 寻找首元素invoke crt_printf,addr szFmt,eaxmov eax,(MyCount ptr ds:[Count]).count_xadd eax,12                                     ; 每次递增12mov (MyCount ptr ds:[Count]).count_x,eaxjmp S1; while 煦暖输出整个PtrA结构中的成员mov (MyCount ptr ds:[Count]).count_x,0         ; 初始化 count_xS2: cmp (MyCount ptr ds:[Count]).count_x,48        ; 设置循环次数 12 * 4 = 48jge lop_end;    mov ecx,(MyCount ptr ds:[Count]).count_x;   mov eax,dword ptr ds:[PtrA + ecx]              ; 寻找首元素;    invoke crt_printf,addr szFmt,eaxmov (MyCount ptr ds:[Count]).count_y,0S4:   cmp (MyCount ptr ds:[Count]).count_y,12        ; 内层循环 3 * 4 = 12jge S3mov ebx,(MyCount ptr ds:[Count]).count_xadd ecx,(MyCount ptr ds:[Count]).count_ymov eax,dword ptr ds:[PtrA + ecx]invoke crt_printf,addr szFmt,eaxmov eax,(MyCount ptr ds:[Count]).count_yadd eax,4                                       ; 每次递增4字节mov (MyCount ptr ds:[Count]).count_y,eaxjmp S4S3: mov eax,(MyCount ptr ds:[Count]).count_xadd eax,12                                     ; 每次递增12mov (MyCount ptr ds:[Count]).count_x,eaxjmp S1lop_end:int 3main ENDP
END main

在上面的基础上继续递增,每次递增将两者的偏移相加,获得比例因子,嵌套双层循环实现寻址打印.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib; 定义坐标结构
MyPoint Structpos_x DWORD ?pos_y DWORD ?pos_z DWORD ?
MyPoint ends; 定义循环结构
MyCount Structcount_x DWORD ?count_y DWORD ?
MyCount ends.data; 声明结构: 使用 <>,{}符号均可PtrA  MyPoint <10,20,30>,<40,50,60>,<70,80,90>,<100,110,120>Count MyCount <0,0>szFmt BYTE '结构数据: %d',0dh,0ah,0.codemain PROC; 获取结构中的数据lea esi,dword ptr ds:[PtrA]mov eax,(MyPoint ptr ds:[esi]).pos_x          ; 获取第一个结构Xmov eax,(MyPoint ptr ds:[esi + 12]).pos_x     ; 获取第二个结构X; while 循环输出结构的每个首元素元素mov (MyCount ptr ds:[Count]).count_x,0S1:    cmp (MyCount ptr ds:[Count]).count_x,48        ; 12 * 4 = 48jge lop_endmov (MyCount ptr ds:[Count]).count_y,0S3:   cmp (MyCount ptr ds:[Count]).count_y,12jge S2mov eax,(MyCount ptr ds:[Count]).count_xadd eax,(MyCount ptr ds:[Count]).count_yinvoke crt_printf,addr szFmt,eaxmov eax,(MyCount ptr ds:[Count]).count_yadd eax,4mov (MyCount ptr ds:[Count]).count_y,eaxjmp S3 S2:    mov eax,(MyCount ptr ds:[Count]).count_xadd eax,12                                     ; 每次递增12mov (MyCount ptr ds:[Count]).count_x,eaxjmp S1lop_end:int 3main ENDP
END main

最终可以完成寻址,输出这个结构数组中的所有数据了

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib; 定义坐标结构
MyPoint Structpos_x DWORD ?pos_y DWORD ?pos_z DWORD ?
MyPoint ends; 定义循环结构
MyCount Structcount_x DWORD ?count_y DWORD ?
MyCount ends.data; 声明结构: 使用 <>,{}符号均可PtrA  MyPoint <10,20,30>,<40,50,60>,<70,80,90>,<100,110,120>Count MyCount <0,0>szFmt BYTE '结构数据: %d',0dh,0ah,0.codemain PROC; 获取结构中的数据lea esi,dword ptr ds:[PtrA]mov eax,(MyPoint ptr ds:[esi]).pos_x          ; 获取第一个结构Xmov eax,(MyPoint ptr ds:[esi + 12]).pos_x     ; 获取第二个结构X; while 循环输出结构的每个首元素元素mov (MyCount ptr ds:[Count]).count_x,0S1:    cmp (MyCount ptr ds:[Count]).count_x,48        ; 12 * 4 = 48jge lop_endmov (MyCount ptr ds:[Count]).count_y,0S3:   cmp (MyCount ptr ds:[Count]).count_y,12        ; 3 * 4 = 12jge S2mov eax,(MyCount ptr ds:[Count]).count_xadd eax,(MyCount ptr ds:[Count]).count_y       ; 相加得到比例因子mov eax,dword ptr ds:[PtrA + eax]              ; 使用相对变址寻址invoke crt_printf,addr szFmt,eaxmov eax,(MyCount ptr ds:[Count]).count_yadd eax,4                                      ; 每次递增4mov (MyCount ptr ds:[Count]).count_y,eaxjmp S3 S2:   mov eax,(MyCount ptr ds:[Count]).count_xadd eax,12                                     ; 每次递增12mov (MyCount ptr ds:[Count]).count_x,eaxjmp S1lop_end:int 3main ENDP
END main

结构体同样支持内嵌的方式,如下Rect指针中内嵌两个MyPoint分别指向左子域和右子域,这里顺便定义一个MyUnion联合体把,其使用规范与结构体完全一致,只不过联合体只能存储一个数据.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib; 定义坐标结构
MyPoint Structpos_x DWORD ?pos_y DWORD ?pos_z DWORD ?
MyPoint ends; 定义左右结构
Rect StructLeft MyPoint <>Right MyPoint <>
Rect ends; 定义联合体
MyUnion Unionmy_dword DWORD ?my_word WORD ?my_byte BYTE ?
MyUnion ends.dataPointA Rect <>PointB Rect {<10,20,30>,<100,200,300>}test_union MyUnion {1122h}szFmt BYTE '结构数据: %d',0dh,0ah,0
.codemain PROC; 嵌套结构的赋值mov dword ptr ds:[PointA.Left.pos_x],100mov dword ptr ds:[PointA.Left.pos_y],200mov dword ptr ds:[PointA.Right.pos_x],100mov dword ptr ds:[PointA.Right.pos_y],200; 通过地址定位lea esi,dword ptr ds:[PointB]mov eax,dword ptr ds:[PointB]        ; 定位第一个MyPointmov eax,dword ptr ds:[PointB + 12]   ; 定位第二个内嵌MyPoint; 联合体的使用mov eax,dword ptr ds:[test_union.my_dword]mov ax,word ptr ds:[test_union.my_word]mov al,byte ptr ds:[test_union.my_byte]main ENDP
END main

结构体定义链表: 首先定义一个ListNode用于存储链表结构的数据域与指针域,接着使用TotalNodeCount定义链表节点数量,最后使用REPEAT伪指令开辟ListNode对象的多个实例,其中的NodeData域包含一个1-15的数据,后面的($ + Counter * sizeof ListNode)则是指向下一个链表的头指针,先来看一下其内存分布.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.libListNode StructNodeData DWORD ?NextPtr  DWORD ?
ListNode endsTotalNodeCount = 15
NULL = 0
Counter = 0.dataLinkList LABEL PTR ListNodeREPEAT TotalNodeCountCounter = Counter + 1ListNode <Counter,($ + Counter * sizeof ListNode)>ENDMListNode<0,0>.codemain PROCmov esi,offset LinkListmain ENDP
END main

接着来完善实现对链表结构的遍历。结构体定义链表: 首先定义一个ListNode用于存储链表结构的数据域与指针域,接着使用TotalNodeCount定义链表节点数量,最后使用REPEAT伪指令开辟ListNode对象的多个实例,其中的NodeData域包含一个1-15的数据,后面的($ + Counter * sizeof ListNode)则是指向下一个链表的头指针,先来看一下其内存分布.

.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.libListNode StructNodeData DWORD ?NextPtr  DWORD ?
ListNode endsTotalNodeCount = 15
Counter = 0.dataLinkList LABEL PTR ListNodeREPEAT TotalNodeCountCounter = Counter + 1ListNode <Counter,($ + Counter * sizeof ListNode)>ENDMListNode<0,0>szFmt BYTE '结构地址: %x 结构数据: %d',0dh,0ah,0
.codemain PROCmov esi,offset LinkList; 判断下一个节点是否为<0,0>L1: mov eax,(ListNode PTR [esi]).NextPtrcmp eax,0je lop_end; 显示节点数据mov eax,(ListNode PTR [esi]).NodeDatainvoke crt_printf,addr szFmt,esi,eax; 获取到下一个节点的指针mov esi,(ListNode PTR [esi]).NextPtrjmp L1lop_end:int 3main ENDP
END main

Win32汇编:字符串浮点数运算过程相关推荐

  1. 图解Win32汇编字符串和Debug输出

    1 使用PrintString宏输出字符串 .386 .model flat,stdcalloption casemap:noneinclude W:\masm32\include\windows.i ...

  2. JAVA字符串数学公式运算-辅助类-支持浮点数错误纠正-低消耗-高可用性-小数点后面保留16位小数

    //计算辅助类·基础计算 public class ArithHelper {// 默认除法运算精度private static final int DEF_DIV_SCALE = 16;// 这个类 ...

  3. 利用c#实现远程注入非托管WIN32程序,并利用嵌入汇编调用非托管WIN32程序中的内部过程...

    c#通过调用windows API函数,可以很轻松的完成非托管WIN32程序的注入.内存读写等操作,以下为c#实现远程注入非托管WIN32程序,并利用嵌入汇编调用非托管WIN32程序中的内部过程的源码 ...

  4. 浮点数的加减法运算过程详解(面向小白的)

    浮点数的加减法运算过程详解(面向小白的) 一. 浮点数在计算机内的表示 二. 浮点数的加减运算步骤 第一次写博客,难免有疏漏之处,如果有错误请批评指正,感谢! 对于浮点数的加减运算,书上写的名词太多, ...

  5. float php 运算_PHP浮点数(float)运算过程中出现的错误问题解决方案

    PHP开发中经常会涉及到金额的计算,而大多数线上项目的金额会精确到单位分,而在PHP运算中经常会遇到浮点数运算的偏差,导致金额统计错误和运算判断上的偏差. 案例1(浮点数相加):$x = 0.1; $ ...

  6. 计算机浮点数乘法过程,计算机中单精度浮点数运算详解

    写在前面 在PA_2019fall中有一项任务是完成CPU中的浮点数运算,这也是我第一次认真的思考了一下真实的计算机中CPU是如何进行的浮点数运算 在写PA的过程中一头雾水,从迷茫,到困惑,到弄懂,到 ...

  7. win32 汇编基础概念整理

    一.关于寄存器 寄存器有EAX,EBX,ECX,EDX,EDI,ESI,ESP,EBP等,似乎IP也是寄存器,但只有在CALL/RET在中会默认使用它,其它情况很少使用到,暂时可以不用理会. EAX是 ...

  8. win32汇编·指令

    win32汇编·指令 常用伪指令 数字常量 字符串常量 预留空间 复制重复 符号定义伪指令 等号伪指令 操作符伪指令 算术运算符 逻辑操作符 关系操作符 框架定义 样例 函数声明语句 include ...

  9. C指针原理(23)-win32汇编及.NET调试

    2018-12-28 20:36:07 在WINDOWS系统能用到汇编的机会不多,基本都可以用C或C++代劳,更何况现在MICROSOFT的Visual Studio 系列工具非常强大,WINDOWS ...

最新文章

  1. java管程 实现,Java中的管程模型
  2. java1.8之supplier
  3. flash _currentframe+指定帧步 控制线程
  4. 函数式接口作为方法的返回值
  5. Django视图简介
  6. 前端向后端发送请求,后端返回的一个值的请求的ajax.get();方法
  7. Java基础-异常处理机制
  8. 02277微型计算机原理及应用,微型计算机原理及应用(课程代码:02277).doc
  9. javaShop JAVA版多用户B2B2C商城源码(PC+H5+小程序+APP)
  10. 七、树莓派做Aria2下载机
  11. 什么是Apache APR
  12. bundle adjustment 详解
  13. 简单xss接收cookie平台的搭建以及xss拿cookie的一些总结
  14. 推荐google浏览器插件(为专注工作使用)
  15. 【TCP】Recv-Q和Send-Q 不要在有什么误解了
  16. leetcode动态规划之零钱兑换问题
  17. windows下cppcheck的使用
  18. 图灵机,又称图灵计算、图灵计算机
  19. HTML在有序列表方中填充序号,HTML中的有序列表,使用奇数编号
  20. XigmaNas系统(六):VirtualBox安装xp,迅雷,百度云安装,文件共享,远程桌面

热门文章

  1. 关于我发表了TalentOrg的面试文章而被官方的人找上门
  2. jeecgboot--根据模板自定义导出
  3. 火星惊现「神秘入口」?好奇号发现的这道门,玉兔二号看着挺眼熟
  4. .NET实现工资管理系统
  5. 设计模式日常学习(四)
  6. 机器学习算法分析汇总
  7. 数据结构 第七章 图的作作业
  8. 哈佛商学院 MBA 课程介绍
  9. python一键抠图
  10. 迪桑特宣布彭于晏成为品牌代言人