常用指令系统
x64 汇编网 https://www.felixcloutier.com/x86/index.html
伪指令
伪指令在编译阶段起作用
没有对应的机器码
\ 换行符 连接上一句
如
invoke MessageBox, \
NULL, \
offset A_Text, \
offset A_Title, \
MB_OK
invoke ExitProcess,NULL
\
它的作用是连接上一句,使之成为一个整体
等效于
invoke MessageBox, NULL, offset A_Text, offset A_Title, MB_OK
段指令
.code .data .data? .const .code这些事分段的伪指令
.386
.model flat ,stdcall
option casemap :none
<一些include指令>
.stack
<堆栈的大小>
.data
<一些初始化过的变量定义>
.data?
<一些没有被初始化过的变量定义>
.const
<一些常量的定义>
.code
<代码>
<开始标号>
其它语句
end<开始标号>
就书上的东西而言
x86下
.data .data? .const
.data?
<一些没有被初始化过的变量定义>
在内存分配时,他们不占空间,编译器只是会记录这个位置区域的大小,而真正的使用它会在另外一个空间空间开辟
.const
<一些常量,好比字符串,整形数据>
可读不可写的常量区,如果写到这里来就会报错然后退出
x64下
.bss
存放一些没有没有初始化的变量,不包含任何数值,
在编译阶段不会占据内存,在执行时像操作系统请求所需的内存,并初始化为0
如果执行时,没有足够的内存分配,程序会崩溃
一个例子,不是很理解
section .bss
flist resb 11
于是就表示了filst是一个11字节的字符串
有一些这些类型
resb 8位
resw 16位
resd 32位
resq 64位
声明一个数组
arr resd 20
还有一个例子就是
scanf的缓冲区与是放在.bss区域
代码段
放在了text节区
一般代码段时不可读不可写的,但是修改一些属性就可以
栈段
书上说斩断时可读可写可执行的
,woc???所以黑客攻击栈段?????
equ 相等
EQU可以与C语言的define做一个比较
EQU相关的数据,内存不会给他分配空间
凡是遇到EQU定义的数据,系统就给你自动替换为了相应的字面量
3种情况
左值 | EQU | 右值 | 说明 |
dqx | EQU | <“I am Dqx_Gh0st”,0>或者<1024*2> | 凡是遇到Dqx就用相应的字符串或者数值替代 |
xx | EQU | symble | |
xxx | EQU | 表达式 |
既然与Define做了一个比较,那么的话define定义的都是常量,是不可以再次赋值的
;------------------------------------------------------------------------
Just_A_Ejd EQU <"Pess and key to continue...",0>
Just_A_Var EQU <100*100>
.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
.data
Arr db 1,2,3,4,5,6,7,8,9,10
len dd $-Arr
.code
main PROC
mov eax,Just_A_Var
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
main ENDP
END main
在IDA里面
sub_402028 proc near
mov eax, 10000
push 0 ; uExitCode
call j_ExitProcess
sub_402028 endp
Just_A_Var被替换为了100*100
其实也不是这样
Just_A_Var会被替换位100*100,而不是100000
毕竟是一个字符串的替换,而不是u直接算出结果
textequ 文本相等
与EQU类似
name | textEQU | <指令表达式或者带“”的字符串> |
name | textEQU | 已经被创建的EQU宏 |
name | textEQU | %数值 |
例子
;------------------------------------------------------------------------
x=20
num TEXTEQU %(x*10)
move TEXTEQU <mov>
init_ax TEXTEQU <move ax,num>
(略)
.code
main PROC
init_ax
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
main ENDP
END main
在IDA里面
sub_402028 proc near
mov ax, 200
push 0 ; uExitCode
call j_ExitProcess
sub_402028 endp
如果我用EQU
x=20
num TEXTEQU %(x*10)
move EQU <mov>
init_ax EQU <move ax,num>
在IDA里面可以得到一样的结果
@@ 地址标号
@@ 是当前的地址
@F会指向下一个最近的@@
@B会指向下上一个最近的@@
遵循就近原则
jmp @F
@@:
invoke MessageBox, NULL, offset A_Text, offset A_Title, MB_OK
jmp @B
实现一个死循环
type 获取数据类型的大小
include Dqx.inc
.data
var dword ?
.code
start:
xor eax,eax
mov eax,type var
mov eax,type start
mov eax,type near
mov eax,type far
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
end start
IDA
public start
start proc near
xor eax, eax
mov eax, 0FF04h
mov eax, 0FF04h
mov eax, 0FF06h
mov eax, 4
push 0 ; uExitCode
call ExitProcess
start endp
关于,start与near,far的标号值是固定的
lengthof 计算数组元素的个数
相比于C语言,
如果是整数数组,它的功能就是sizeof(arr)/sizeof(arr[0])
如果是字符串数组,它的功能就是strlen(str)
它会返回一个32位的数值
关于细节
;------------------------------------------------------------------------
.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
.data
arr1 db 1,2,3,4
db 5,6,7,8
ptr_arr dd arr1
arr2 dw 1,2,3,4,
5,6,7,8
ptr_arr2 dd arr2
.code
main PROC
xor eax,eax
mov eax,lengthof arr1;我感觉offset的就像C语言的取地址运算符&
xor eax,eax
mov eax,lengthof arr2
;mov eax,lengthof ptr_arr2
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
main ENDP
END main
IDA源码
sub_402028 proc near
xor eax, eax
mov eax, 4
xor eax, eax
mov eax, 8
push 0 ; uExitCode
call j_ExitProcess
sub_402028 endp
①.
arr1 db 1,2,3,4
db 5,6,7,8
ptr_arr dd arr1
第2行与第1行没有关系
lengthof=4
arr1 db 1,2,3,4,
db 5,6,7,8
ptr_arr dd arr1
这样写会报错
,
逗号的意思…..你可以看作第一行与第二行之间有很多的空格,那个逗号就是连接的
报错的原有?
这么看
arr1 db 1,2,3,4 ,db 5,6,7,8
这样当然会报错
arr1 db 1,2,3,4 db 5,6,7,8
这样就不会报错
但是第二个db和第一个db就不不再有联系了(对于lengthof)的话
sizeof/size 返回数组的总字节数
可以形象的比喻它为
type x lengthof
数组单元个数x每个单元的字节数
include Dqx.inc
.data
var dword ?
arr word 4 dup(0)
.code
start:
xor eax,eax
mov eax,size var
mov eax,sizeof var
mov eax,size arr
mov eax,sizeof arr
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
end start
IDA
public start
start proc near
xor eax, eax
mov eax, 4
mov eax, 4
mov eax, 8
mov eax, 8
push 0 ; uExitCode
call ExitProcess
start endp
使用情况
arr1 db "hello,I am dqx_Gh0st",0
arr2 db sizeof arr1 dup(0)
返回的结果是21,字符串长度+1,那个1是0的占位
typedef 定义新的类型
可以与C语言联想
但是不同于C语言
C语言
typedef+原来的类型+新的类型
typedef char *ptr B_ptr
x86汇编
新的类型+typedef+老的类型
B_ptr typedef ptr byte
title Dqx_Gh0st
.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
db_ptr typedef ptr byte
dw_ptr typedef ptr word
.data
arr1 db 9,8,7,6,5,4,3,2
arr2 dw 9,8,7,6,5,4,3,2,1
ptr1 dd arr1
ptr2 dd arr2
.code
main PROC
......................
$ 地址计数器
;------------------------------------------------------------------------
x=100
.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
.data
temp dd $ ;偏移地址的长度data:00405000,可见它是一个4字节的长度,我们要用dd来获取
.code
main PROC
mov eax,temp;eax就获取了temp的偏移地址
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
main ENDP
END main
于是你在IDA里面看到的
sub_402028 proc near
mov eax, off_405000
push 0 ; uExitCode
call j_ExitProcess
sub_402028 endp
temp直接就转化为了一个地址的变量名称
以前的变量名称是temp
写在变为了off_405000
40500就是偏移地址
源代码的data
.data
temp dd $
IDA里面是
.data
off_405000 dd offset off_405000
获取当前位置的偏移地址
.data
Arr db 1,2,3,4,5,6,7,8,9,10
Arr_end dd $
Arr_end就是它的当前的偏移地址
就是获取一个偏移地址,至于后面怎么样再说
计算数组长度
简要介绍
计算数组的长度
数组的第一个元素会有一个偏移量
数组的最后一个元素也会有一个偏移量
.data
Arr db 1,2,3,4,5,6,7,8,9,10
Arr_end dd $
.data:00405000 unk_405000 db 1 ; Arr
.data:00405001 db 2
.data:00405002 db 3
.data:00405003 db 4
.data:00405004 db 5
.data:00405005 db 6
.data:00405006 db 7
.data:00405007 db 8
.data:00405008 db 9
.data:00405009 db 14h
.data:0040500A dd offset unk_405000;Arr_end不太理解IDA里面为什是offset unk_405000,而不是offset unk_40500A
Arr ->xxxx5000
Arr_end->xxxx500A
0xA-0x0=10
于是就有10个数据
你可能会想到10个元素
最后一个元素index=9,
第一个元素 index=0
9-0=9
得到数字9
你也知道这样得到的数据是不对的,9只是[0]和[9]的差量
len=10是[10]和[0]的差量
源码
;------------------------------------------------------------------------
x=100
.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
.data
Arr db 1,2,3,4,5,6,7,8,9,10
Arr_end dd $
.code
main PROC
mov ax,Arr_end-Arr
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
main ENDP
END main
IDA
sub_402028 proc near
mov ax, 10
push 0 ; uExitCode
call j_ExitProcess
sub_402028 endp
一般情况
更加常见的的计算方式
;------------------------------------------------------------------------
x=100
.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
.data
Arr db 1,2,3,4,5,6,7,8,9,10
len dd $-Arr
.code
main PROC
mov eax,len
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
main ENDP
END main
IDA
data
.data:00405000 db 1
.data:00405001 db 2
.data:00405002 db 3
.data:00405003 db 4
.data:00405004 db 5
.data:00405005 db 6
.data:00405006 db 7
.data:00405007 db 8
.data:00405008 db 9
.data:00405009 db 0Ah
.data:0040500A dword_40500A dd 10
code
sub_402028 proc near
mov eax, dword_40500A;也就是10
push 0 ; uExitCode
call j_ExitProcess
sub_402028 endp
其它类型数组
db | len=($-Arr) |
dw | len=($-Arr)/2 |
dd | len=($-Arr)/4 |
….
= 等号指令
=指令的对象是一个32位长度的数据
它的优点….利于代码的维护与修改
在我对C语言或者C++不明白的情况下
我觉得=指令
给我的感觉就是一个全局的变量
无论你在后面赋值还是前面赋值,系统都可以为你找到数据然后赋值
;------------------------------------------------------------------------
.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
.code
main PROC
mov eax,x
INVOKE ExitProcess,0;调用结束的函数,
x=100
;---------------------------------------------------------------------------
main ENDP
END main
或者
;------------------------------------------------------------------------
x=100
.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
.code
main PROC
mov eax,x
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
main ENDP
END main
无论怎么样.在IDA里面都是
proc near ; CODE XREF: start↑j
mov eax, 100
push 0 ; uExitCode
call j_ExitProcess
endp
加深一下理解
;------------------------------------------------------------------------
x=1
.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
.code
main PROC
xor eax,eax
x=100
mov eax,x
x=1000
mov eax,x
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
main ENDP
END main
在DIA里面
proc near ; CODE XREF: start↑j
xor eax, eax
mov eax, 100
mov eax, 1000
push 0 ; uExitCode
call j_ExitProcess
sub_402028 endp
high/low/highword/lowword
他可以取出高8位,低8位,高16位,低16位的数据
他的操作数是立即数,不是变量,不是寄存器
include Dqx.inc
.data
.code
start:
xor eax,eax
xor ebx,ebx
mov ah,high 1234h
mov al,low 1234h
mov ax,highword 12345678h
mov ax,lowword 12345678h
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
end start
IDA
public start
start proc near
xor eax, eax
xor ebx, ebx
mov ah, 12h
mov al, 34h ; '4'
mov ax, 1234h
mov ax, 5678h
push 0 ; uExitCode
call ExitProcess
start endp
运算符优先级
(1)LENGTH,SIZE,WIDTH,MASK. (),[],(结构体),<>。 (2)PTR,OFFSET,TYPE,SEG,THIS,冒号。冒号用于表示段超越前缀。 (3)*,/,MOD,SHL,SHR (4)HIGH,LOW (5)+,- (6)EQ,NE,LT,LE,GT,GE (7)NOT (8)AND (9)OR,XOR (10)SHORT
非伪指令
字节对齐
在C语言下,字节对齐可以自己规定
#pragma pack(1)
struct str
{
}
#pragma pack()
每个成员所占空间是字节对齐的倍数,不足就补0
align
可以联想一下nop
,这里的algin
会填充空字节,也就是填充0
然后algin的原理是个啥
最后的结果就是让下一个数据的地址是1/2/4/8/16的倍数
好比
db1 byte 1
db2 byte 2
align 4 ;让dw1的地址是4的倍数
dw1 word 3
align 2 ;让dd1的地址是2的倍数
dd1 dword 4
dd2 dword 5
align 4 ;让db3的地址是4的倍数
db3 byte 6
为了得到这些倍数,他就通过在前面补充0来填充字节
另外之一下
align 4 不是说真的填充4字节
align 16 也不是说填充16字节
可能还是填充2字节,4字节,,看地址情况而定
algin的主要功能之一
16/32位 字节对齐
这对寻址很有帮助
alignb
BCD码指令->fld,fbstp
见<<汇编语言基于x86处理器>>的Page-60
inc/dec/add/sub
自己百度
dup 数据重复
db 128 dup(0)表示重复128个1,单位字节
db 128 dup('1')表示重复128个字符'1',单位字节
db 128 dup(?) 表示没有初始化
offset 就取处偏移地址
他的对象是一个常数地址,不会发生变化,编译器遇到offset就会寻找那个偏移地址,然后ofset xxx = 地址常量
为什么?
offset 用于全局变量
全局变量是不会消失的
所以offset的操作数是一个全局的常量
在编译阶段直接由编译器赋值
在之前我们有这样的写法
8086中,只能把数组写到代码段寻址
code segment
Arr1 db 1,2,3,4
start:
....
mov si,Arr1
于是我们就可以获取偏移地址
在x86中,可以把数组写进data段
.data
Arr1 db 1,2,3,4
.code
mov ax,[Arr1+1]
于是我,们就可以直接把数组名当中地址来用
为了更加的严谨.利于代码的阅读性与维护性
我们用offset
.data
Arr1 db 1,2,3,4
ptr_arr dd Arr1
.code
main PROC
mov esi,offset Arr1;我感觉offset的就像C语言的取地址运算符&
mov edi,ptr_arr;一个指针,
mov al,[esi]
mov ah,[edi]
offset返回的数据是一个对应位数的偏移地址
8086的offset返回2字节
x86的offset返回4字节
lea 取地址
不同于offset,offset是编译是直接获取的常量地址
lea是运行时动态的算出每一次地址
lea Des,Src
lea eax,dword ptr ds:[ebx]
其中eax=ebx
ptr 重写操作数大小类型
8686下
mov ax,arr[1*2]
ax就决定了数组的数据长度
x86下就离谱
mov ax, byte ptr ptr1[1*(type arr1)]
mov ax, word ptr ptr2[1*(type arr2)]
非要这样写,否者就报错
对于一个好比4字节的数据
你可以对它的2个字节2个字节的访问
或者8个字节8个字节的访问,这就会访问4字节后面的4字节
;------------------------------------------------------------------------
.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
.data
x dw 4321h
y dw 8765h
.code
main PROC
xor eax,eax
mov eax,dword ptr x
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
main ENDP
END main
IDA
; Attributes: noreturn
sub_652028 proc near
xor eax, eax
mov eax, dword_654000
push 0 ; uExitCode
call j_ExitProcess
sub_652028 endp
最后EAX=87654321h
xchg 交换指令
xchg x,y
那么x,y的数据会交换
如何交换2个数?还是要用到第3者
对于操作数,必须2个必须是可读可写的变量
way1-我自己想的
xchg x,eax
xchg y,eax
xchg x,eax
way-2
mov eax,x
xchg y,eax
mov x,eax
way-3
mov temp,x
mov x,y
mov y,temp
call
本质是
push next_IP
jmp call_func_location
回归到CPU执行指令的原理
读取当前指令到BUFF
IP=下一个IP
CPU执行BUFF
那么的话,当我们执行了Call会,IP=下一个IP
那么的话call会就会push下一个指令的ip
call s
然后会push ip
ret
然后会
pop ip
call far ptr
然后会
push cs
push ip
retf
然后会
pop ip, pop cs
对一般的call
它就是push一些东西
然后再 jmp 到 call 的那个地址
再次说一遍,我们是push 地址,不是push一个指令
对far ptr
push cs
push ip
对于数据,肯定还要pop
就是
pop ip
pop cs
call word ptr ds:[xx] 会 push ip
call dword ptr ds:[xx] 会push cs, push ip
loop
loop的代码执行效率会比jmp慢,所以在实际的调试时,很少出现loop,都是一些条件跳转
loop也有很多的形式
他们对计数器的要求会不同
他们对程序的指令的兼容性也会不同
遇到就查吧
add会偷偷修改标志寄存器
所以在使用寄存器的时候你要注意那些寄存器的变化
判断是否循环的原则
先对ecx/cx作运算
然后看是否为0
assume cs:code
code segment
mov ax,0
mov cx,4
func: add ax,2
loop func
mov ax,4c00h
int 21h
code ends
end
对于嵌套的LOOP循环,要记住push cx和pop cx,并且注意中途少用cx
对于这种机制,要注意的是
高级语言往往是先检验是否为0,再执行cx-1
Loop是先执行cx-1,再去判断
mov ecx,0
add_sum:
add eax,arr[esi]
inc esi
loop add_sum
它会是一个很大的循环
一直到ecx发生溢出或者进位,他就为0了
cmpxchg
他有2个操作数
cmpxchg x,y
用高级语言解释一下
if(eax==x)
{
x=y;
ZF=1;
}
else
{
eax=x;
ZF=0;
}
在我的电脑上无法运行该指令
bsr/bsf 位扫描
数值的下标是这样安排的
最右边是index=0
最左边是index=xx
字符串的下标
做左边是index=0
最右边是index=xx
bsf :Bit Scan Forward
bsr :Bit Scanf Reverse
反汇编会被翻译为函数
_BitScanReverse()
bsf指令的功能: 从右往左扫描,正向寻取 二进制值的最低位的1的index,返回index,然后初始化ZF的值
bsr指令的功能: 从左往右扫描 逆向寻取 寻取二进制值的最低位的1的index,返回index,然后初始化ZF的值
如果找到1,说明value!=0,那么ZF=0
如果找不到1,,说明value==0,那么ZF=1
好比1101,
用bsr寻取到index=3,最左边是[3]=1
用bsf寻取到index=0,最右边是[0]=1
这不同于字符串的index
bsr ecx ,vale
那么他就把value的1值最高位返回给ecx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
bswap 字节反向存储
好比
mov eax,12345678h
bswap eax
eax=78563412h
return返回
虽然本质是pop ip
其实我们可以在栈里面修改它那个ip,然后在pop出去
这就很妙
ret近转移
本质
是pop ip
他是近转移
ret 后面也可以带参数
ret 8
masm32下表示
pop ip
add esp+8
retn 近转移
意思是ret near
retn操作:pop eip
retn N操作:先pop eip,然后add esp,N
retn 其实等价于 ret
retf 远转移
他是段间转移
可以修改CS:IP
先pop IP
后pop CS
因为之前有push cs,push ip
把栈顶的word数据给IP
又把下一个栈顶word数据给CS
iret 中断返回
中断过程中的iret
他会实现
pop ip
pop cs
popf
二进制第i位操作
从右往左寻找二进制的1
bt
bt eax,2
把eax的第[2]位传递给CF,然后,[2]位不变
_bittest()
btc
btc eax,2
将eax的第[2]传递给CF,把[2]取反
btr
btr eax,2
把eax的第[2]位给CF,然后置[2]位为0
bts
bts eax,2
把eax的第[2]位给CF,然后置[2]位为1
CPU控制指令
nop
该指令的执行不会影响任何标志寄存器
wait
让CPU处于等待状态,直到协处理器完成运算
问题来了,什么叫协处理器
hlt 暂停指令
执行了hlt后,CPU处于暂停的工作状态,
EIP指向HLT的下一条指令
把EIP入栈
当产生了reset信号或者中断请求信号时,才会eip出栈,CPU继续执行
关于中断的产生,CPU强制去执行中断
CPU转去处理中断程序后,中断结束,iret弹出EIP,并唤醒HLT的下一条指令,这样CPU退出了暂停
所以这个IRET还是强大呀
lock 封锁数据
它是一个前缀指令
就像rep movsb指令套
lock会跟一个操作指令
lock可以保证在其后指令的执行过程中,禁止协处理器修改数据总线
起到单独占线的占用,该指令不会影响寄存器
cpuid 获取CPU信息
这应该与端口相关吧....
x86模式下
初始化eax,然后调用cpuid在最后在ebx/edx/ecx检查该值
x64模式下
初始化eax,然后调用cpuid在最后在ecx/edx中检查该值
#include<stdio.h>
#include<windows.h>
unsigned long getcpu(char*);
int main()
{
char b_str[128]={0};
unsigned long ret=getcpu(b_str);
printf("ID:%u %s",ret,b_str);
system("pause");
return 0;
}
unsigned long getcpu(char* b_str)
{
__asm
{
mov eax,0
cpuid
mov eax,b_str
mov [eax],ebx
mov [eax+4],edx
mov [eax+8],ecx
mov eax,1
cpuid
mov eax,edx
}
}
条件跳转
一般汇编语言的跳转判断是根据指定条件的相反条件
若满足相反条件就就指向else
否则顺序执行后面的语句
浮点数的比较
fcomp会影响FPU的寄存器
fcomi指令字节影响CPU的eflag
jmp
原理
CPU如何指令命令?
- CPU从CS:IP读取指令到buff
- IP=IP+刚才读取的指令长度
于是IP指向了下一条指令 - CPU执行buff的指令
CPU又开始读取IP,循环操作上面的操作
机器码剖析
那么jmp的话
就是 EB xx
那个xx
就是一个偏移地址,这个偏移地址的基地址是jmp后面那个指令的开始字节,从字节数0开始数
然后,偏移到jmp的地方
xx
快速计算的方法就是
跳到那里的地址-jmp后的那个IP=xx
其它jmp
jmp far ptr 段间转移 跳转范围 -32768~32767
jmp short ptr 段转移 跳转的范围小于128字节
jmp word ptr ds:[0] 会取ds: [0] [1] 位ip地址
jmp dword ptr ds:[1] 取[0] [1]为ip,[2] [3]为cs
jmp byte ptr ds:[1]
cmp-有符号/无符号
对于数值的cmp
你若看作是无符号比较,就只看ZF
你若看作有符号的比较,就看SF/OF,或者ZF
CPU将cmp指令得到的结果记录在flag的相关标志位中,我们可以根据指令执行后,相关标志位的值来判断比较的结果。单纯的考察SF的值不可能知道结果的正负
cmp al,bl
SF=1,OF=0 al<bl
SF=1,OF=1 al>bl SF=OF
如果因为溢出导致的实际结果为负数,那么真正的结果为正数
SF=0,OF=1 al<bl
SF=0,OF=0 al>nl SF==OF
如果因为溢出导致的实际结果为正数,那么真正的结果为负数
OF==SF 才会有大于
je 相等就跳转
jne 不相等就跳转
jb jmp blow 小于就跳转
jnb jmp not blow 大于等于就跳转
ja jmp above 大于就跳转
jna jmp not above 小于等于就跳转
eflag跳转
助记符 | 说明 | 标志位/寄存器 |
JZ | 为零跳转 | ZF=1 |
JNZ | 非零跳转 | ZF=0 |
JC | 进位跳转 | CF=1 |
JNC | 无进位跳转 | CF=0 |
JO | 溢出跳转 | OF=1 |
JNO | 无溢出跳转 | OF=0 |
JS | 有符号跳转 | SF=1 |
JNS | 无符号跳转 | SF=0 |
JP | 偶校验跳转 | PF=1 |
JNP | 奇校验跳转 | PF=0 |
相等跳转
用cmp判断
助记符 | 说明 |
JE/JZ | 相等跳转 (leftOp=rightOp) |
JNE/JNZ | 不相等跳转 (leftOp M rightOp) |
JCXZ | CX=0 跳转 |
JECXZ | ECX=0 跳转,你在8086用的比较多 |
JRCXZ | RCX=0 跳转(64 位模式) |
无符号跳转
above/below
助记符 | 说明 | ||
JA=JNBE | > | CF|ZF=0 | |
JB=JNAE | > | ||
JAE=JNB | >= | ||
JNA=JBE | <= | CF|ZF=1 |
有符号的跳转
greate/little
助记符 | 说明 | |
JG=JNLE | > | (SF ^OF)|ZF=0 |
JL=JNGE | < | SF^OF=1,意思SF = OF |
JGE=JNL | >= | SF ^ OF=0,意思是SF! = OF |
JLE=JNG | <= | (SF^OF)|ZF=1 |
测试条件转存指令
条件跳转的应用
举个例子al是8位
把8位当作开关,于是我们来检测
某一位是否开启
test al,10000000b
jnz xxx
//开启了就ZF!=0,
//没开启,ZF==1
对于10001000b
0位数据不管,检测那个1位,如果他也为1,那么最后ZF=0.否则为1
某几位当中是否有任意一位开启
test al,10010001b
jnz xxx
只要开启了一位,最后的ZF=0,都没有开启就会ZF=1
某几位都开启
要用减法
and al,10010001b
cmp al,10010001b
jz xxx
and把无关的位数都屏蔽了,留下了要检测的
然后一个cmp,如果都开启,最后结果就是0,ZF==1
求3个数的最小值
假设x,y,z
mov eax,x
cmp eax,y
jbe next
mov eax,y
next:
cmp eax,z
jbe end
mov result,z
end:
mov result,eax
数的比较
char x=0xff char y=0x1 比较x/y x<y
unsigned char x=0xff unsigned char y=0x1 比较x/y x>y
在数的比较中,我们会对其进行扩展
好比8位的比较变化位32位了
无符号的比较也可能用来有符号的跳转,可能涉及了符号的扩展
条件测试+与循环语句
条件测试
.if/.elseif/.endif/.else
一个基本的条件循环是
.if
.endif
2面性判断
.if
.else
.endif
多面性判断
.if
.elseif
.elseif
.endif
这些if语句会与跳转指令相挂钩
简介
条件测试语句?
就是逻辑条件的判断...
masm条件测试的几个限制
表达式左边只能是常量/寄存器
表达式2边不能同时是变量,可以是寄存器
另外表达式中不能是算术表达式,好比x*y>0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
一些标志寄存器的伪指令
.if carry
.if overflow
.if parity
.if sign
.if zero
条件的测试语句的伪指令见后面的目录
.if eax && (ebx>=var) || !(var!=ecx);
这句话实现有很多个跳转
这句判断的判断是一个顺序的结构
eax=0,跑去判断var!=ecx,
eax=1,就去判断ebx>=var,如果是,就直接跳过后面的判断,直接执行
eax=1,就去判断ebx>=var,如果不是,就去判断var!=ecx,然后看是否要执行
所以这个判断还是有很多的jmp/jnz/jz的
很头疼
天坑
1️⃣
关于那些条件条件跳转
他们也有sign与unsigned之分,
好比无符号的jb/jnb
mov eax,1
jb eax,-1
那么的话-1是一个很大的值
对于无符号的比较指令好像书上写了
见cmp指令的跳转指令\
怎么在源代码里面避免这些情况???
办法一是把数据类型改为有符号的数,好比sword
办法二,强制类型转化
.if sword ptr eax >0
.if eax > sword ptr 0
2️⃣
条件指令就那几个
注意在前面加上点.
,否则他就成为了宏指令
代码示意
可以调试一下它!!!!巨离谱
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Hello.asm
; Link /subsystem:windows Hello.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;对于上面的lib,inc还不是很理解
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
.data
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
var dd 20
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
xor eax,eax
.if eax && (ebx>=var) || !(var!=ecx);把前面看作一个部分,如果前面2个返回1,那么后面就不在判断
mov esi,1
.elseif edx
mov esi,2
.elseif esi & 1
mov esi,3
.elseif ZERO? && CARRY?
mov esi,4
.endif
push 0
call ExitProcess
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
循环语句
while/endw/repeat/until/untilcxz/continue/break
搭配
while-endw
repeat-until
repeat-untilcxz
while+表达式
表示一个循环执行的条件
一次循环结束就jmp到while那里再次判断是否执行
break/continue就类别C语言就可以了,用于中途的跳出...
break到哪里去?while循环外的第一条语句
continue不会执行后面的代码,会jmp到循环开始的while
until+一个表达式
这个表达式是结束的表达式,也就是循环到什么时候结束
untilcxz是一个loop的盗版,他会ecx-=1,判断ecx是否为0,然后执行loop一样的操作
它也可以接上一个表达式,那么的话,循环🔚的条件就变为了2个
loop指令不会影响标志寄存器的,是一个很好的选择
关于循环语句的伪指令见指令那一章节
例子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Hello.asm
; Link /subsystem:windows Hello.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;对于上面的lib,inc还不是很理解
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
.data
szCaption db 'Dqx-Gh0st',0
szText1 db 'I',0
szText2 db 'Love',0
szText3 db 'You',0
szText4 db 'Deeply',0
fuck db 'fuck',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
var dd 20
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
xor eax,eax
mov eax,5
.while eax>0
.if eax==4
dec eax
.continue
;continue后面的代码是不会执行的,可以对比一下C语言
push eax
invoke MessageBox,NULL,offset fuck,offset szCaption,MB_OK
pop eax
;前面好像说过,函数的返回值一般在eax,所以这里的eax我们要保持不变
.elseif eax==3
push eax
invoke MessageBox,NULL,offset szText1,offset szCaption,MB_OK
pop eax
dec eax
.elseif eax==2
.break
.else
dec eax
.endif
.endw
;do {}while(eax>0)
mov eax,2
.repeat
push eax
invoke MessageBox,NULL,offset szText2,offset szCaption,MB_OK
pop eax
dec eax
.until eax == 0
mov ecx,2
.repeat
pushad
invoke MessageBox,NULL,offset szText3,offset szCaption,MB_OK
popad
.untilcxz
push 0
call ExitProcess
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
把上面的代码转化一下
mov eax,2
.repeat
push eax
invoke MessageBox,NULL,offset szText2,offset szCaption,MB_OK
pop eax
dec eax
.until eax == 0
i=0;
do
{
printf("xxx");
}
while(i!=0)
;>>>>>>>>>>>>>>>>>>>>>>汇编
mov eax,5
.while eax>0
.if eax==4
dec eax
.continue
;continue后面的代码是不会执行的,可以对比一下C语言
push eax
invoke MessageBox,NULL,offset fuck,offset szCaption,MB_OK
pop eax
;前面好像说过,函数的返回值一般在eax,所以这里的eax我们要保持不变
.elseif eax==3
push eax
invoke MessageBox,NULL,offset szText1,offset szCaption,MB_OK
pop eax
dec eax
.elseif eax==2
.break
.else
dec eax
.endif
.endw
;>>>>>>>>>>>>>>>>>>>>>>>C语言
i=5;
for(i=5;i>0;)
{
if(i==4)
{
i--;
continue;
sub();
}
else if(i==3)
{
printf("xxx");
i--;
}
elseif(i==2)
breakl;
else
i--;
}
movsx/movzx 符号扩展
mov cl,66
mov ax,0
mov al,cl
这样就实现了cl到ax的变化
但是有个问题,如果cl是负数呢?我们
mov cl,0xf0
mov ax,0xffff
mov al,cl
这会很麻烦
movsx eax,cl
无论cl是正是负.eax都会跟着cl的符号
movzx eax,cl 就不会有符号的跟随
零扩展/符号扩展
如果你是正数,就把你的二进制高位全部置为0
如果你是负数,就把你的二进制高位全部置为1
,
这样扩展不会改变原来的值
它不会要求你的长度怎么怎么样
只要长度左边>=右边
就可以,它会把右边的数据自动的长度扩展
movzx只用于无符号的数据,但是这句话不太明白
书上的例子mov bx,0A69B
,也没有指明后者是正数还是负数
整数运算
AND/OR/NOT/XOR/TEST
在移位的时候,你的左移和右移是和有无符号相关的
所以这回是一个细节
简介
AND 对1没影响,对0会得0
所以AND可以
用1去AND,那个数,可用于检测,如果对方是1那么结果是1,如果不是就为0
用0去AND,结果只会是0,
用0去or那个数,那个数不会发生任何改变,用于保存某些位的数
用1去or,结果只能是1
NOT 是取反
XOR 异或
如果2个数相同就不相异,结果为0
如果2个数不相同就相异,结果为1
TEST
不会修改操作数,相当于AND
会影响ZF/SF/PF
映射集
补集用NOT
交集用AND,对于2个操作数,如果有都有1,那就1,否则就是0
并集用OR,只要有一个人有就可以了
neg
求补码
neg求的是有符号的数据
如果结果无法放入寄存器,就补码无效,也就是OF=1,就neg无效
好比数据-128
xor eax,eax
mov al,-128
neg al
mov al,-60
neg al
第一次neg
al=128,of=1,neg无效
,al不发生变化,al还是原来的-128
第二次neg
al=-60,neg后al=60,of=0,neg成功
neg可以用来取相反数
shl/shr
mov ax,0
mov al,11001100b
mov cl,1
shl al,cl//
如果左移1位, 可以shl al,1
如果左移>1位,必须用cl保存左移的位数,cl也可以是1[^这是8086的说法]
左移会存在数据的进位,进位的保存会在CF中
mov ax,0
mov al,11000001b
mov cl,1
shr al,cl
如果右移1位, 可以shl al,1
如果右移>1位,必须用cl保存左移的位数,cl也可以是,[^8086的说法]
右移会存在数据的移除,消失的最后一位会在CF中-
计算100*36
mov eax,100
mov ebx,eax
shl eax,5
shl ebx,2
add eax,ebx
它的原理分配法
100x36=100x(32+4)
sal/sar
就算数左移的话,与逻辑左移没有什么区别,都是填充0
就是右移就有些不同
10001000b是负数,逻辑右移后01000100b是正数,符号的性质发生了改变
于是我们就要用到算数右移了
10001000b用sar后11000100b,结果还是负数
ROL/ROR 后进位循环移动
什么意思?
好比10101111b用指令
mov al,1010 1111b
rol al,4
al= 1111 1010b
左移动4位,每移动一位,CF就记录那个值,然后在右边补上
它的用处?
①.位组交换
它可以交换一个8bit的前4位和后4位
②.坑
我们都知道一位的16进制对应了4位的二进制
于是左移一位16进制就是左移4位二进制
于是把数据
mov ax,6A4Bh
rol ax,4
rol ax,4
rol ax,4
rol ax,4
于是到了最后ax的值没有发生变化
RCL/RCR 先进位循环移动
与上面的原理一样
不一样的地方在于
它有CF的初始化值,移动后,先用已经初始化的CF取填充,再去记录那个移动的值
STC ;CF=1
mov cl,4
mov al,10010010b
rcl al,4
最后AL=00101100
为了做一个对比
STC
mov cl,4
mov al,10010010b
rol al,4
于是最后
al= 00101001
SHLD/SHRD
第一个操作数是16位/32位的寄存器或者内存单元
第二个操作数只能是寄存器
第三个操作数可以使CL或者立即数
SHLD D,S,count
SHRD D,S,count
count啥意思? 就是移多少位
SHLD D,S,count
把D左移coutn位,用S的高coutn位填充D的低count位
好比
shld 0x9BA6,0xAC36,4
4位就是16进制的一个数字
最后的0x9BA6->0xBA60->0xBA6A
SHRD D,S,count
把D右移count位
用S的低count为去填充D的高count为
好比
shrd 0x234B,0x7654,4
0x2345->0x0234->0x4234
mul 乘法指令
由于目的操作数是被乘数和乘数大小的两倍,因此不会发生溢岀。
8bit类型
AL*1Byte
因数1是AL
因数2是一个8位的数据
其结果默认存放在AX中
16bit类型
AX*1word
因数1是AX
因数2是一个16位的数据
其结果默认存放
低16位 AX
高16位 DX
上面是8086机学到的,可以兼容32位
32bit
数据长度 | 因数1 | 因数2 | 结果 |
8位 | al | bl/byte ptr ds:[si] | AX |
16位 | ax | bx/word ptr ds:[si] | DX:AX |
32位 | eax | EDX:EAX |
什么时候高位会产生数据? 对于无符号的数据来说,数据太大就进位
所以才会有高位的寄存器,当高位寄存器!=0,那么CF=1,表示进位了
好比这一段代码
data segment
db 5,4,3,2,1,0
data ends
mov ax,0
mov bx,0
mov al,2
mov bl,3
mul bl
mul byte ptr ds:[1]
mov ax,2
mov bx,10
mul bx
mul word ptr ds:[0]
IMUL 指令 有符号的乘法
IMUL有3种语法
1个操作数/2个操作数/3个操作数
对于无符号的数据,好比
mov al,48
mov bl,4
imul bl
48*4=192
192>127溢出
192<255不会进位,但是就离谱的CF=1
其实我还是没有分清IMUL与MUL到底有什么区别....
难道说他会有一个mul的有符号填充???
一个操作数
mov al,10
mov bl,20
IMUL bl
ax=al*bl
两个操作数
mov ax,10
IMUL ax,20
ax=ax*20
三个操作数
imul cx,ax,bx
//cx=ax*bx
DIV 除法指令
24/9=2...6
被除数24
除数9
商2
余数6
被除数 | 被除数位置 | 除数 | 除数位置 | 商 | 余数 |
16bti | AX | 8位 | 8bit的内存单元/寄存器 | AL | AH |
32bit | DX:AX | 16位 | 16bit的内存单元/寄存器 | AX | DX |
64bit | EDX:EAX | 32位 | 32bit的内存单元/寄存器 | EAX | EDX |
IDIV 有符号除法
与DIV的区别在于,IDIV要进行符号的扩张
也就是DX/EDX会根据AX/EAX的正负进行有无符号的填写
其余无区别了
.386
.model flat,stdcall
option casemap:none
.data
x sword -101
.code
start:
xor edx,edx
xor eax,eax
mov dx,0
mov ax,x
mov bx,2
idiv bx
;用DIV是一样的
;=-51...1
xor edx,edx
xor eax,eax
mov dx,0
mov ax,x
cwd
mov bx,2
idiv bx
;=-50...-1
END start
很奇怪
cbw/cwd/cdq/cdqe/cwde符号扩展
cbw ;扩展到了AH,用AL的符号填充AH,AH的值会被覆盖
若al=78h,cbw后ax=78h
若al=87h,cbw后ax=ff87h
cwd ;扩展到了DX,用AX的符号填充DX, DX的值会被覆盖
cwde AX->EAX
cdq ;扩展到了EDX,用EAX的符号填充EDX,EDX的值会被覆盖
cdqe EAX->RAX
为什么要符号扩展????
做8位除法,被除数必须16位AX
做16位除法,被除数必须32位DX|AX
做32位除法,被除数必须64位EDX|EAX
.386
.model flat,stdcall
option casemap:none
.data
.code
start:
xor edx,edx
xor eax,eax
mov ah,0
mov al,-48
mov bl,5
idiv bl
;-48(208)/5=40..3
xor edx,edx
xor eax,eax
mov ah,0
mov al,-48
cbw ;让ah=FF
mov bl,5
idiv bl
;-48/5=-9..-3;
END start
不用就会产生错误的解决方案
.386
.model flat,stdcall
option casemap:none
.data
x sword -101 ;=0x9b,-101一定是负数,但是0x9b却不是
.code
start:
xor edx,edx
xor eax,eax
;mov al,9bh,al一定是负数
mov al,-101
cbw ;扩展到了AH
;mov ax,9bh,ax不是负数,cwd失效
mov ax,-101
cwd ;扩展到了dx
;mov eax,9bh,eax不是是负数,cdq失效
mov eax,-101
cdq ;扩展到了edx
END start
adc 进位加法
adc实现了带有符号位的加法,建立在二进制位的那个符号
adc eax,dword ptr [esi]
等价于
eax=eax+dword ptr [esi]+CF
adc的优势体现在哪????
好比1234+4567
我们可以这样算
34+67=01,本来是101,但是进位了1
然后
12+45=57,于是加上进位,就是58
最后的结果就是5801
通过这种分割的形式我们可以算很长的数据
下面是一个8字节程度的数据,实现加法
好比0x1234567890102345+0x1234567890102345
这个代码你还还是多看一下,很妙
.386
.model flat,stdcall
option casemap:none
.data
arr1 dw 12h,34h,56h,78h,9h
arr2 dw 9h,87h,65h,43h,21h
.code
start:
mov esi,offset arr1
mov edi,offset arr2
mov ecx,5
xor eax,eax
clc
;clc指令把CF寄存器初始化为0,给首位adc使用
ok:
mov ax,[esi]
adc [edi],ax
pushfd
add esi,2
add edi,2
popfd
;为什么把寄存器入栈?因为add会修改CF寄存器的值
loop ok
END start
sbb 借位减法
就是一个借位的过程,做一些记录
xor eax,eax
mov edx,7
mov eax,1
sub eax,2
sbb edx,0
xadd 很sb的加法
z=x+y
xadd的效果就是
z=y+x
在我的电脑上,这个指令无法用
非压缩10进制算法原理
aaa 加法
会影响AF/CF
通俗介绍
aaa 操作的对象16进制的个位数,也就是4个bit位
好比0x2F,他会把F拿出来,然后把F分解为1和5,因为F是15
好比0x39,ASCII是9,然后把9分解为0和9,也就是9
于是下面这些例子
3+'6'=0x39,然后对个位数可以转化为0和9,依次放在ah,al
'3'+'6'=0x69,然后对个位数可以转化为0和9
因为它只看个位数,同时请保证AH=0,再进行操作
16进制个位数满足0-15,对于10进制绰绰有余,
AH代表了进位,会传递给一个进位者
书上说
如果AL的低4位>9,也就数10进制进位了AL+=6,AH+=1.....是吗?????/
会有CF=1,AF=1
否则CF=0.AF=0.
难道会有置空?
关于计算过程
好比0x37=0x39
也就是7+9=0x70
你可以看到低4字节为0
但是他有一个进位
CF=1,AF=1
就相当于0x10=16了
例子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
printf proto c:dword,:vararg
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data
x db "96543"
y db "53279"
sum db (sizeof x + 1) dup('0'),0
format db "%s",0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
xor ecx,ecx
xor eax,eax
;为什么esi-1
;因为从0开始计数,所以最后一位的index=len-1
;为什么edi不-1?
;edi本来就比esi长一个字节,好比x是4字节,那么sum就5字节,会有进位
mov esi,sizeof x -1
mov edi,sizeof x
mov ecx,sizeof x
;数值的填充由字符串最高位开始==数值最低位开始
;让进位值由bh来装,最先的进位还是0
mov bh,0
L1: ;aaa指令要求ah=0
mov ah,0
mov al,x[esi]
;al每一次先加上进位值,再加上数值
add al,bh
aaa
add al,y[esi]
aaa
;+了后,bh还要保持新的进位
mov bh,ah
or al,30h
;转ascii然后放入sum
mov sum[edi],al
dec esi
dec edi
loop L1
;最后一个也可能有进位
or bh,30h
mov sum[edi],bh
invoke printf,addr format,addr sum
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
又写了一遍
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
printf proto c:dword,:vararg
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data
x db "96543",0
y db "53279",0
sum db sizeof x dup(0),0
format db "%s",13,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
xor ecx,ecx
xor eax,eax
;为什么esi-1
;因为从0开始计数,所以最后一位的index=len-1
;为什么edi不-1?
;edi本来就比esi长一个字节,好比x是4字节,那么sum就5字节,可能会有进位
mov esi,sizeof x - 2
mov edi,sizeof x - 1
mov ecx,sizeof x - 1
;数值的填充由字符串最高位开始==数值最低位开始
clc
L1:
mov ah,0
mov al,x[esi]
adc al,y[esi]
aaa
pushfd
or al,30h
popfd
mov sum[edi],al
dec esi
dec edi
loop L1
cmp ah,1
jnz over
or ah,30h
mov sum[edi],ah
over:
invoke printf,addr format,addr sum
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
or,add,会影响CF
inc/dex不会
daa 加法
影响AF/CF/PF/SF/ZF
书上说
其调整规则
若AL的低4位有进位,则AF=1,AL+=6,
若AL的高4位有进位,则CF=1,AL+=60h,
都不成立就AF=CF=0
他把16进制的0x12
可以转化为10进制的0x12
xor eax,eax
mov al,12h
add al,28h
daa
12+28=40,
最后的结果是16进制的40
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data
Num1 WORD 4536h
Num2 WORD 7207h
sum DWORD ?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
xor eax,eax
mov sum,0
mov esi,0
mov ecx,type Num1
CLC
ok:
mov al,byte ptr Num1[esi]
add al,byte ptr Num2[esi]
daa
mov byte ptr sum[esi],al
inc esi
loop ok
adc byte ptr sum[esi],0
mov eax,sum
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
关于进位的情况
mov al,56h
add al,92h
daa
56+92=148
al装不下148,只能装48
所以就进位了
aas 减法
影响AC/CF
它操作的对象也是16进制
好比操作0xF,就是操作15
然后得到10进制意义的数值
可以类比aaa
书上说
如果AL的低4位>9,也就数10进制借位了AL-=6,AH-=1.....是吗?????/
会有CF=1,AF=1
否则CF=0.AF=0.
难道会有置空?
例子
mov ah,0
mov al,5
sub al,10
pushf
add al,30h
popf
;or会影响CF
aas
0-5=FB
然后aas后
AH=0xFF,AL=5
可能AH代表了符号,AL代表了值
其中产生的借位留在了CF中
das 减法
影响AF/CF/PF/SF/ZF\
书上说
其调整规则
若AL的低4位有进位,则AF=1,AL+=6,
若AL的高4位有进位,则CF=1,AL+=60h,
都不成立就AF=CF=0
例子
对16进制形式操作
结果也是10进制意义的16进制
start:
xor eax,eax
xor ebx,ebx
mov al,85h
mov bl,48h
sub al,bl
das
结果是10进制意义16进制的85-48=37
对于负数的处理
mov al,50h
sub al,90h
das
其实这里的借位我算是见过了
诸如的结果60h
怎么来的
150-90=60
所以就60h
aam 乘法
会影响PF/SF/ZF
ascii adjust after mul
操作的对象是16进制
得到结果是10进制,分别放在AH,AL
AH=AL/10
AL=AL%10
话不多说
mov ah,0
mov al,5
mov bl,6
mul bl
aam
然后结果就是30
ah=03,al=00
如果结果是98.AH=09h.AL=08h
如果结果是125.AH=12h.AL=05h
就像aaa的那个程序一样
aad 除法
会影响PF/SF/ZF
它很不一样
操作的是10进制,从AH,Al当中取数据
AL=AH*10+AL
AH=0
好比0x0207
就是操作10进制的27
在Al中生成商,在AH中生存余数
,可是数据太多怎么办?????
mov ax,0307h
aad
;合成了数值37=0x某某
mov bl,5
div bl
他可以识别ax=0x307位10进制的的37
于是想要使用aad的话,你得是10进制的形式然后被识别为16进制
好比你把一串ASCII-0x30,然后放在AH,AL中
"1234"
识别为,1,2,3,4然后除法?????woc????/貌似不行的
字符串基本指令
指令 | 说明 |
MOVSB、MOVSW、MOVSD | 传送字符串数据: ESI的内存数据---> EDI 的内存位置 |
CMPSB、CMPSW、CMPSD | 比较字符串:比较分别由 ESI 和 EDI 寻址的内存数据 |
SCASB、SCASW、SCASD | 扫描字符串:比较累加器 (AL、AX 或 EAX) 与 EDI 寻址的内存数据 |
STOSB、STOSW、STOSD | 保存字符串数据:将累加器内容保存到 EDI 寻址的内存位置 |
LODSB、LODSW、LODSD | 从字符串加载到累加器:将 ESI 寻址的内存数据加载到累加器 |
方向标志位指令
STD 让DF=1 表示方向
CLD 让DF=0 表示正向
DF | 对ESI和EDI的影响 | esi/edi位置 | 地址顺序 |
0 | 每一次增加type 个大小 | 指向首位 | 小->大,正序 |
1 | 每一次减少type 个大小 | 指向末尾 | 大->小,逆序 |
字符串循环的退出条件之一
指令 | 说明 |
rep | ECX > 0 时循环 |
repz,repe | ZF= 1 且 ECX > 0 时循环 |
repnz,repne | ZF=0 且 ECX > 0 时循环 |
movs b/w/d 数据复制
MoVS BYTE PTR ES:[EDI], BYTE PTR DS: [ESI] 简写为:MOVSB Movs WORD PTR ES:[EDI], WORD PTR DS:[ESI] 简写为:MOvsw MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] 简写为:MOVSD
指令 | 数据类型 | ESI 和 EDI 增加或减少的数值 |
MOVSB | 传送(复制)byte | 1 |
MOVSW | 传送(复制)word | 2 |
MOVSD | 传送(复制)dword | 4 |
从esi到edi
一个从尾巴到头部的复制制
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A_str db "I love you deeply",0
len = lengthof A_str
B_str db len dup ('C')
;--------------------------------------------------------
;function
;--------------------------------------------------------
;--------------------------------------------------------
.code
;--------------------------------------------------------
start:
mov esi,offset A_str +len - 1 ;+len指向了0后面
mov edi,offset B_str +len - 1 ;指向了最后一个C
mov ecx,len ;表示循环次数0也复制进去
std
rep movsb
invoke ExitProcess,NULL
;--------------------------------------------------------
;function
;--------------------------------------------------------
end start
;--------------------------------------------------------
cmpsb/sw/sd 数据比较
CMPSB | 比较字节 |
CMPSW | 比较字 |
CMPSD | 比较双字 |
通过循环可以比较字符串,与strcmp可以相当
比较esi与edi指向的数据串,循环ecx次
cmpsb 是字节比较
cmpsw 是字比较
cmpsd 是双值比较
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A1 db "I lOve You",0
B1 db "I l0ve You",0
;--------------------------------------------------------
;function
;--------------------------------------------------------
;--------------------------------------------------------
.code
;--------------------------------------------------------
start:
mov esi,offset A1
mov edi,offset B1
mov ecx,lengthof A1 - 1
cld
repe cmpsb
;repe 如果zf=1,ecx>0就继续循环
;故循环继续的条件是ecx>0&&ZF==1
jnz show1
jz show2
show1: mov eax,1
jmp over
show2: mov eax,2
jmp over
over:
invoke ExitProcess,NULL
;--------------------------------------------------------
;function
;--------------------------------------------------------
end start
;--------------------------------------------------------
对于cmpsw/sd的比较,我就不多说了
scasb/sw/sd 数据遍历
scasb | 扫描字节 |
scasw | 扫描字 |
scasd | 扫描双字 |
通过循环,可以逐个遍历字符串然后获取其中的index
这个指令是拿al/ax/eax与edi指向的数据串进行遍历比较
scan-xx指令有什么要注意的
1️⃣.它是拿al/ax/eax与[edi]作比较,而不是[esi]
2️⃣.每比较一个就dec edi,这是一个连在一起的过程
3️⃣.他的用处到底是啥?寻找一个数据串当中有没有对应的数据
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A1 db "I lOve You",0
;--------------------------------------------------------
;function
;--------------------------------------------------------
;--------------------------------------------------------
.code
;--------------------------------------------------------
start:
mov edi,offset A1
mov ecx,lengthof A1 - 1
mov al,'Y'
cld
repne scasb
;相等就退出循环
;不相等就一直遍历,一直到ecx=0||ZF=1才退出
jnz show1 ;最后都还是没有找到相等的值
jz show2 ;最后找到了相等的值
show1: mov eax,0
jmp over
show2:
dec edi ;把edi再一次指向'Y'
mov ah,byte ptr [edi]
jmp over
over:
invoke ExitProcess,NULL
;--------------------------------------------------------
;function
;--------------------------------------------------------
end start
;--------------------------------------------------------
stos b/w/d 数据存入
把Al/AX/EAX的值存储到[EDI]指定的内存单元
stosb | 存储字节 |
stosw | 存储字 |
stosd | 存储双字 |
STOS BYTE PTR ES:[EDI] 简写为STOSB STos WORD PTR ES:[EDI] 简写为STOSw STOS DWORD PTR ES:[EDI] 简写为STOSD
循环ecx次
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A1 db "I lOve You",0
len = lengthof A1 - 1
;--------------------------------------------------------
;function
;--------------------------------------------------------
;--------------------------------------------------------
.code
;--------------------------------------------------------
start:
mov edi,offset A1
mov ecx,len
mov al,'D'
cld
rep stosb
;他会把'D'依次填满字符串A1
over:
invoke ExitProcess,NULL
;--------------------------------------------------------
;function
;--------------------------------------------------------
end start
;--------------------------------------------------------
losb/sw/sd 数据拿出
把[esi]指向的数据串依次给了al/ax/eax
这个代码的功能
将数据串[esi]的每一位拿出来放在al
然后乘以10,
然后把al放进[edi]
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A1 db 1,2,3,4,5
len dd lengthof A1
;--------------------------------------------------------
;function
;--------------------------------------------------------
;--------------------------------------------------------
.code
;--------------------------------------------------------
start:
mov esi,offset A1
mov edi,esi
mov ecx,len
mov bh,10
cld
ok:
lodsb
mul bh
stosb
loop ok
over:
invoke ExitProcess,NULL
;--------------------------------------------------------
;function
;--------------------------------------------------------
end start
;--------------------------------------------------------
xlat/xlatb
他会用2个寄存器
ebx存放加密表的table的地址
al存放待加密的数据,和已经加密好的数据
mov al, byte ptr [esi]
sub al,'+'
xlat table
mov byte ptr [edi],al
然后一个例子
加密字符串"Dqx_Gh0st"
include Dqx.inc
printf proto c:dword,:vararg
.data
table byte "+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",0
ming byte "Dqx_Gh0st",0
len = sizeof ming
mi byte len-1 dup('@'),0
format byte "%c %c",10,0
.code
start:
xor eax,eax
mov ebx,offset table
mov esi,offset ming
mov edi,offset mi
mov ecx,len
flag:
mov al, byte ptr [esi]
sub al,'+'
xlat table
mov byte ptr [edi],al
pushad
movzx eax,byte ptr [esi]
movzx ebx,byte ptr [edi]
invoke printf ,addr format,eax,ebx
popad
inc esi
inc edi
loop flag
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
end start
IDA的分析
void __fastcall __noreturn start(int a1, int a2)
{
unsigned __int8 *_esi; // esi
unsigned __int8 *_edi; // edi
int len; // ecx
int v7; // [esp-20h] [ebp-20h]
int v8; // [esp-1Ch] [ebp-1Ch]
int count; // [esp-18h] [ebp-18h]
_EAX = 0;
_EBX = aAbcdefghijklmn;
_esi = (unsigned __int8 *)aDqxGh0st;
_edi = (unsigned __int8 *)asc_40304B;
len = 10;
do
{
LOBYTE(_EAX) = *_esi - 43;
__asm { xlat }
*_edi = _EAX;
count = len;
v8 = a2;
v7 = _EAX;
printf("%c %c\n", *_esi, *_edi);
_EAX = v7;
a2 = v8;
++_esi;
++_edi;
len = count - 1;
}
while ( count != 1 );
ExitProcess(0);
}
上面的v7/v8应该就是源码的一个pushad和popad操作
insb/w/d 输入串指令
输入串操作从DX指定端口接受一个byte/word/dword
并存入以EDI为起始地址的的存储单元,DF决定了写入的方向,然后EDI根据单位值进行自增与自减
书上没有例子,这可能与终端有关
outsb/w/d输出串指令
从EDI指向内存单元输出到指定的端口
x64
遇到的指令
没有对齐的加法
movupd xmm0, dpvector1
movupd xmm1, dpvector2
addpd xmm0, xmm1
movupd ds:dpvector_res, xmm0
对齐的加法
movapd xmm0, dpvector1
addpd xmm0, dpvector2
movapd ds:dpvector_res, xmm0
movups
移动未对齐的打包单精度
会得到4个精度值的向量
movaps
movaps
MOVAPS xmm1, xmm2 / m128
把对齐的xmm2/8字节mem
给xmm1
MOVAPS xmm2 / m128.xmm1
把对齐的xmm1给xmm2/8字节mem
movapd
MOVAPD xmm1, xmm2/m128
把对齐的xmm2或者8字节mem
的double类型给xmm1
MOVAPD xmm2/m128, xmm1
把对齐的xmm1给xmm2/8字节mem
movss
把float送到目的地
MOVSS xmm1, xmm2
MOVSS xmm1, m32
movdqa
将对齐的压缩整数值从 16字节长度 移动到 16字节
movd
4字节的mov
MOVD xmm , r/m32
MOVD r/m32 , xmm
movq
8字节的mov
MOVQ xmm , r/m64
MOVQ r/m64 , xmm
movsd
在8字节基础上
MOVSD xmm1, xmm2
MOVSD xmm1, m64
MOVSD xmm1/m64, xmm2
处理的数据类型是double
addps
将压缩的单精度浮点值从 16字节 数据长度 添加到 xmm寄存器 并将结果存储在 xmm寄存器 中。
打包单精度数据相加
也就是每4字节相加....
.text:0000000000401166 movaps xmm0, spvector1
.text:000000000040116E addps xmm0, spvector2
appad
将压缩双精度浮点值从128位的数据长度 添加到 xmm寄存器 并将结果存储在 xmm寄存器中。
每8字节8字节的相加
cvtss2sd
CVTSS2SD xmm1, xmm2/m32
double<--flaot
cvtsd2ss
CVTSD2SS xmm1, xmm2/m64
float<--double
paddb/w/d/q
.text:0000000000401166 movdqa xmm0, pdivector1
.text:000000000040116F paddd xmm0, pdivector2
//xmm0= pdivector1+ pdivector2
加法运算...
pextrb/w/d/q
.text:00000000004011A3 pextrd eax, xmm3, 0
.text:00000000004011A9 pextrd ebx, xmm3, 1
.text:00000000004011AF pextrd ecx, xmm3, 2
.text:00000000004011B5 pextrd edx, xmm3, 3
.text:00000000004011BB pinsrd xmm0, eax, 3
.text:00000000004011C1 pinsrd xmm0, ebx, 2
.text:00000000004011C7 pinsrd xmm0, ecx, 1
.text:00000000004011CD pinsrd xmm0, edx, 0
pextrb
参数1=byte 参数2[参数3]
pextrw
参数1=word 参数2[参数3]
pextrd
参数1=dword 参数2[参数3]
pextrq
参数1=qword 参数2[参数3]
pinsrb/w/d/q
.text:00000000004011A3 pextrd eax, xmm3, 0
.text:00000000004011A9 pextrd ebx, xmm3, 1
.text:00000000004011AF pextrd ecx, xmm3, 2
.text:00000000004011B5 pextrd edx, xmm3, 3
.text:00000000004011BB pinsrd xmm0, eax, 3
.text:00000000004011C1 pinsrd xmm0, ebx, 2
.text:00000000004011C7 pinsrd xmm0, ecx, 1
.text:00000000004011CD pinsrd xmm0, edx, 0
pinsrb
参数1[参数3]=参数2
pinsrw
pinsrd
参数1[参数3]=参数2
参数2是4字节
参数1是xmm寄存器
参数3是index
pinsrq
参数1[参数3]=参数2
参数2是8字节
参数1是xmm寄存器
参数3是index
pcmpistri
打包比较隐式的字符串,返回索引
pcmpistrm
打包比较隐式的字符串,返回索引,返回掩码
pcmpestri
打包比较显式的字符串,返回索引
pcmpestrm
打包比较显
式的字符串,返回掩码
pxor
16字节与16字节异或
mulsd
MULSD xmm1,xmm2/m64
double低位之间的乘法
FPU指令系统
里面的基础知识很多很杂,用到的地方也很少
遇到一个有用的指令就记录一下
FPU 不使用通用寄存器 (EAX、EBX 等等)。
反之,它有自己的一组寄存器,称为寄存器栈 (register stack)。
数值从内存加载到寄存器栈,然后执行计算,再将堆栈数值保存到内存。
他的没有esp,但是有top指针
IEE 规范 存储小数
化为二进制科学计算法
用IEE规范转8.25
(1), 8转二进制0000 1000
(2). 0.25转二进制
0.25*2=0.5 个位是0
0.5*2 =1.0 个位是1
因为小数点第一位是0,所以结束
0.25=0b01,不是0b10
这里0b10只有2位,有的可能是无限不循环,或者说是无限循环的,于是就有一个精确位的取舍
科学计数法
8.25是1000.01,转科学计算法,1.00001*2的3次方
分解二进制位
(1)
小数点后,数23位放到结果的二进制[9,31]位
00001->00001000000000000000000
没有就补0
(2)
之前的科学计算法,
小数点左移n位,n是正的, n=n-1,取n的后7位填充结果二进制的[2,8] ,[1]=1
小数点右移n位,n是负的, n=n-1,取n的后7位填充结果二进制的[2,8] ,[1]=0
或者直接127+n,结果后8位,填充[1,8]
左移n为正,右移,n位负
(3)
[0]位是符号位
如果是8.25,[0]=0
如果是-8.25,[0]=1
最后整合
01000001000001000000000000000000
转16进制,然后存储
0.25转
0转二进制->0b0
0.25转二进制->0.01
0.25->0b0.01->1.0*2的负二次方
符号位[0]=0
[1]=0 右移
-2-1=-3=1111 1101
取111 1101放入[2,8]
[1,8]=0111 1101
数1.0*2的负二次方小数点后23位00000000000000000000000
整合
0 01111101 00000000000000000000000
最后再转16进制
FPU 栈
入栈是fld
出栈是fstp
他有栈有8个空间,st0~st7
这8个空间循环使用
栈顶指针始终指向st0
st只是一个标号
具体细节遇到后再完善
舍入原则
四舍五入
对于0.5这种类型
如果整数部分是偶数,就保留整数,舍去小数部分
如果整数部分是奇数,就整数+1,舍去小数部分
对于0.1,0.2这种,直接舍去,然后整数不变
对于0.6,0.8这种,就直接舍去,整数+1
2.5=2
3.5=4
2.6=3
2.1=2
向下舍入
就是小于它的最大整数
102.3=102
-103.2=-104
向上舍入
就是大于它的最小整数
100.101=101
-100.101=-100
向0舍入
数据向0靠齐,舍去小数部分
100,101=100
-103.89=-103
指令
浮点指令名用字母 F 开头,以区别于 CPU 指令。
指令助记符的第二个字母(通常为 B 或 I)指明如何解释内存操作数:B 表示 BCD 操作数,I 表示二进制整数操作数。
末尾带P,表示要出栈
实数传输指令
FLD 浮点数入栈
fld x
可以把x入FPU栈
.data
array REAL8 10 DUP (?)
.code
fld array ;直接寻址
fld [array+16 ] ;直接偏移
fld REAL8 PTR[esi] ;间接寻址
fld array[esi] ;变址寻址
fld array[esi*8] ;带比例因子的变址
fld array[esi*TYPE array] ;带比例因子的变址
fld REAL8 PTR[ebx+esi] ;基址-变址
fld array[ebx+esi] ;基址-变址-偏移量
fld aray[ebx+esi*TYPE array] ;带比例因子的基址-变址-偏移量
关于栈中的数据
.data
dblOne REAL8 234.56
dblTwo REAL8 10.1
.code
fld dblOne ; ST(0) = dblOne
fld dblTwo ; ST(0) = dblTwo, ST(1) = dblOne
st(0)=10.1
st(1)=234.56
FLD-xxx加载常数
下面的指令将特定常数加载到堆栈。这些指令没有操作数:
- FLD1 指令将 1.0 压入寄存器堆栈。
- FLDL2T 指令将 log210 压入寄存器堆栈。
- FLDL2E 指令将 log2e 压入寄存器堆栈。
- FLDPI 指令将 π 压入寄存器堆栈。
- FLDLG2 指令将 log102 压入寄存器堆栈。
- FLDLN2 指令将 loge2压入寄存器堆栈。
- FLDZ(加载零)指令将 0.0 压入 FPU 堆栈。
FILD 将整数入栈
FILD(加载整数)指令将 16 位、32 位或 64 位有符号整数源操作数转换为双精度浮点数,并加载到 ST(0)。源操作数符号保留。
FILD 支持的内存操作数类型与 MOV 指令一致(间接、变址、基址-变址等)
FBLD bcd码入栈
FST 栈顶数据导出
FST(保存浮点数值)指令将浮点操作数从 FPU 栈顶复制到内存。
fstp x
把栈顶的数据给了x
不涉及TOP的变化
为什么说不涉及top变化
FST 不是弹出堆栈。下面的指令将 ST(0) 保存到内存。
假设
ST(0) 等于 10.1,
ST(1) 等于 234.56:
我们的栈顶指针一直是st(0)
fst dblThree ; fst取出了栈顶的数据10.1
fst dblFour ; fst还是取出了栈顶的数据10.1
直观地说,代码段期望 dblFour 等于 234.56。
但是第一条 FST 指令把 10.1 留在 ST(0) 中。
如果代码段的意图是把 ST(1) 复制到 dblFour,那么就要用 FSTP 指令。
FSTP 栈顶数据弹出栈
FSTP(保存浮点值到Dest并将st0出栈)
假设执行下述指令前
ST(0) 等于 10.1,
ST(1) 等于 234.56:
fstp dblThree ; 10.1
fstp dblFour ; 234.56
指令执行后,这两个数值会从堆栈中逻辑移除。从物理上看,每次执行 FSTP,TOP 指针都会减 1,修改 ST(0) 的位置。
FIST 栈顶数据四舍五入后导出
首先栈顶得有数据
然后把栈顶的数据四舍五入后导出
fitp x
出栈的数据给了x
不涉及top的变化
FISTP 栈顶数据四舍五入后出栈
原理和上面一样,不过会设计top的变化
FBSTP 栈顶数据四舍五入后以bcd码出栈
它不仅会对栈顶数据四舍五入还会对原地址的数据四舍五入
真的吗?
fxch 实数交换
fxch
fxch st(i)
fxch实现了str[0]和st[1]的交换
fxch st[i]实现了st[i]与st[0]的交换
实数比较
浮点数不能使用 CMP 指令进行比较,因为后者是通过整数减法来执行比较的。取而代之,必须使用 FCOM 指令。
执行 FCOM 指令后,还需要采取特殊步骤,然后再使用逻辑 IF 语句中的条件跳转指令(JA、JB、JE 等)。
由于所有的浮点数都为隐含的有符号数,因此,FCOM 执行的是有符号比较。
指令
他们都是和st0比较
fcom/fcomp/fcompp
FCOM(比较浮点数)指令将其源操作数与 ST(0) 进行比较,我们要把比较的数据入栈。
源操作数可以为内存操作数或 FPU 寄存器。其语法如下表所示:
指令 | 说明 |
FCOM | 比较 ST(0) 与 ST(1) |
FCOM m32fp | 比较 ST(0) 与 m32fp |
FCOM m64fp | 比较 ST(0) 与 m64fp |
FCOM ST(i) | 比较 ST(0) 与 ST(i) |
FCOMP 指令的操作数类型和执行的操作与 FCOM 指令相同,但是它要将 ST(0) 弹岀堆栈。
FCOMPP 指令与 FCOMP 相同,但是它有两次出栈操作。
fucom/fucomp/fucompp
他和前面讲的fcom是一样的货色
fcomi/FCOMIP/FUCOMI/FUCOMIP
它是.686
的指令
在下面我们会讲一下条件码,于是会说到
把条件码怎么和eflag联系在一起
要用到fnstsw和sahf指令,这里的开销比较大
于是就进入了fcomi
跳过"把条件码怎么和eflag联系在一起",直接把状态位给了eflag
FCOMI 指令代替了之前代码段中的三条指令,但是增加了一条 FLD 指令。也就是把要比较的数据都压入栈中
FCOMI 指令不使用内存操作数。
关于后面的3条指令也不多说
FICOM/FICOMP
就是把st0与操作数比较,初始化c3c2c0
ficomph会出栈
FTST
将实数与0作比较,初始化c3c2c0
条件码
FPU 条件码标识有 3 个,C3、C2 和 C0,用以说明浮点数比较的结果,
如下表所示。由于 C3、C2 和 C0 的功能分别与零标志位 (ZF)、奇偶标志位 (PF) 和进位标志位 (CF) 相同,
因此表中列标题给出了与之等价的 CPU 状态标识
条件 st0-操作数 | C3==ZF | C2==PF | C0==CF | 使用的条件跳转指令 |
ST(0) > SPC | 0 | 0 | 0 | JA. JNBE |
ST(0) < SPC | 0 | 0 | 1 | JB. JNAE |
ST(0) = SPC | 1 | 0 | 0 | JE. JZ |
无序 | 1 | 1 | 1 | (无) |
0 | JAE | |||
1 | JBE | |||
1 | JBE | |||
0 | JNE |
如果出现无效算术运算操作数异常(无效操作数,无穷大,无穷小,格式不正常,非实数),且该异常被屏蔽,则 C3、C2 和 C0 按照标记为“无序”的行来设置。
在比较了两个数值并设置了 FPU 条件码之后,遇到的主要挑战就是怎样根据条件分支到相应标号。这包括了两个步骤:
- 用 FNSTSW 指令把 FPU 状态字送入 AX。
- 用 SAHF 指令把 AH 复制到 EFLAGS 寄存器。
条件码送入 EFLAGS 之后,就可以根据 ZF、PF 和 CF 进行条件跳转。
问题是:为什么要送入eflag?
因为你的用的是FPU系统的条件码,和EFLAG无法直接联系在一起,毕竟你的跳转指令是和eflag联系的
所以我们才用fnstsw和sahf指令吧=把条件码送入ax,再把ah送入eflag
四则运算
所有算术运算指令支持的内存操作数类型与 FLD (加载)和 FST(保存)一致,'
因此,操作数可以是间接操作数、变址操作数和基址-变址操作数等等
FCHS | 修改符号 |
FADD | 源操作数与目的操作数相加 |
FSUB | 从目的操作数中减去源操作数 |
FSUBR | 从源操作数中减去目的操作数 |
FMUL | 源操作数与目的操作数相乘 |
FDIV | 目的操作数除以源操作数 |
FDIVR | 源操作数除以目的操作数 |
FADD/FADDP/FIADD
fadd
FADD(加法)指令格式如下,
FADD
FADD m32fp ;st0+=m32fp
FADD m64fp ;st0+=m64fp
FADD ST(0), ST(i) ;st0+=sti
FADD ST(i) , ST(0) ;sti+=st0
1️⃣.如果 FADD 没有操作数,则 ST(0)与 ST(1)相加,结果暂存在 ST(1)。
然后 ST(0) 弹出堆栈,把加法结果保留在栈顶。假设堆栈已经包含了两个数值,
执行前
st0=10.5
st1=2.4
执行后
st0=12.9
st1=2.4
2️⃣如果参数是寄存器操作数
FADD ST(0), ST(1)
同上
3️⃣如果使用的是内存操作数,FADD 将操作数与 ST(0) 相加。示例如下:
fadd mySingle ; ST(0) += mySingle
fadd REAL8 PTR [esi] ; ST(0) += [esi]
faddp
FADDP(相加并出栈)指令先执行加法操作,再将 ST(0) 弹出堆栈。
FADDP ST(i),ST(0)
原理同上
FIADD(整数加法)
FIADD(整数加法)指令先将源操作数转换为扩展双精度浮点数,再与 ST(0) 相加。指令语法如下:
.data
myInteger DWORD 1
.code
fiadd myInteger ; ST(0) += myInteger
FSUB /FSUBP/FISUB/FSUBR
fsub
FSUB 指令从Dest中减去Source,并把result保存在Dest中。
Dest总是一个 FPU 寄存器,Source可以是 FPU 寄存器或者内存操作数。
该指令操作数类型与 FADD 指令一致:
FSUB ;st0=st1-st0 ;这里的顺序别错的
FSUB m32fp ;st0=st0-m32fp
FSUB m64fp ;st0=sto-m64fp
FSUB ST(0), ST(i) ;st0=st0-sti
FSUB ST(i), ST(0) ;sti=sti-st0
1️⃣无参数 FSUB 实现 ST(1) - ST(0),结果暂存于 ST(1)。然后 ST(0) 弹出堆栈,将减法结果留在栈顶。
2️⃣若 FSUB 使用内存操作数,则从 ST(0) 中减去内存操作数,且不再弹出堆栈。
fsub mySingle ; ST(0) -= mySingle
fsub array[edi*8] ; ST(0) -= array[edi*8]
fsubp
FSUBP(相减并出栈)指令先执行减法,再将 ST(0) 弹出堆栈。MASM 支持如下格式:
FSUBP ST(i),ST(0)
FISUB(整数减法)
FISUB(整数减法)指令先把源操作数转换为扩展双精度浮点数,再从 ST(0) 中减去该操作数:
FSUBR
方向相减
之前的减法顺序
FSUB ;st0=st1-st0 ;这里的顺序别错的
FSUB m32fp ;st0=st0-m32fp
FSUB m64fp ;st0=sto-m64fp
FSUB ST(0), ST(i) ;st0=st0-sti
FSUB ST(i), ST(0) ;sti=sti-st0
用一下fubr,减法的顺序就反了
FSUBR ;st0=st1-st0
FSUBR m32fp ;st0=m32fp-st0
FSUBR m64fp ;st0=m64fp-st0
FSUBR ST(0), ST(i) ;st0=sti-st0
FSUBR ST(i), ST(0) ;sti=st0-sti
FMUL /FMULP/FIMUl
FMUL
FMUL 指令将Source与Dest相乘,乘积保存在Dest中。
Dest总是一个 FPU 寄存器,Source可以为寄存器或者内存操作数。
其语法与 FADD 和 FSUB 相同:
FMUL ;st0*=st1
FMUL m32fp ;st0*=m32fp
FMUL m64fp ;st0*=m64fp
FMUL ST(0), ST(i) ;st0*=sti
FMUL ST(i), ST(0) ;sti*=st0
1️⃣无参数 FMUL 将 ST(O) 与 ST(1) 相乘,乘积暂存于 ST(1)。然后 ST(0) 弹出堆栈,将乘积留在栈顶
2️⃣使用内存操作数的 FMUL 则将内存操作数与 ST(0) 相乘:
fmul mySingle ; ST(0) *= mySingle
fmulp
FMULP(相乘并出栈)指令先执行乘法,再将 ST(0) 弹出堆栈。MASM 支持如下格式:
FMULP ST(i),ST(O)
FIMUL 整数相乘
FDIV /FDIVP/FIDIV/FDIVR
FDIV
FDIV 指令执行Dest除以Source,被除数保存在Dest中。
Dest总是一个寄存器,Sourc可以为寄存器或者内存操作数。
其语法与 FADD 和 FSUB 相同:
FDIV ;st0=st1/st0
FDIV m32fp ;st0/=m32fp
FDIV m64fp ;st0/=m64fp
FDIV ST(O), ST(i) ;st0/=sti
FDIV ST(i), ST(O) ;sti/=st0
1️⃣无参数 FDIV 执行 ST(1) 除以 ST(0)。然后 ST(0) 弹出堆栈,将被除数留在栈顶
2️⃣使用内存操作数的 FDIV 将 ST(0) 除以内存操作数。
.data
dblOne REAL8 1234.56
dblTwo REAL8 10.0
dblQuot REAL8 ?
.code
fid dblOne ; 加载到 ST (0)
fdiv dblTwo ; ST(0) 除以 dblTwo
fstp dblQuot ; 将 ST(0) 保存到 dblQuot
若源操作数为 0,则产生除零异常。若源操作数等于正、负无穷,零或 NaN,则使用一些特殊情况。
fdivp
多了一个出栈的操作
fdivp
FIDIV 指令先将整数源操作数转换为扩展双精度浮点数,再执行与 ST(0) 的除法
FIDIV ml6int
FIDIV m32int
FDIVR
把原来的除数当做被除数,被除数当做除数
但是结果还是放在Dest中
算术指令
FCHS/FABS 求绝对值|st0|,求相反数
FCHS( 修改符号 ) 指令将 ST(0) 中浮点数值的符号取反
FABS ( 绝对值 ) 指令清除 ST(0) 中数值的符号,以得到它的绝对值。
这两条指令都没有操作数:
FCHS
FABS
fsqrt 求 %22%20aria-hidden%3D%22true%22%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMAIN-221A%22%20x%3D%220%22%20y%3D%22-2%22%3E%3C%2Fuse%3E%0A%3Crect%20stroke%3D%22none%22%20width%3D%221331%22%20height%3D%2260%22%20x%3D%22833%22%20y%3D%22739%22%3E%3C%2Frect%3E%0A%3Cg%20transform%3D%22translate(833%2C0)%22%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMATHI-73%22%20x%3D%220%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMATHI-74%22%20x%3D%22469%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMAIN-30%22%20x%3D%22831%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%3C%2Fg%3E%0A%3C%2Fg%3E%0A%3C%2Fsvg%3E)
用于求st0的平方根
fxtract 求阶码与尾数
阶码不多说
尾数就是小数+整数吧
(10进制)12=1.1x2^3(二进制)
于是
阶码3
尾数1.1(二进制)=1.5(10进制)
flad xx
fxtract
fstp weishu
fstp jeima
使用fxtrcat,他会把阶码压入栈st1,尾数压入栈st0
然后获取的话就是尾数出栈,阶码出栈
fprem/fprem1 取余数
取余数的原理是什么?
对于fprem
就是x/y=z
余数=x-y*z
就是这个道理
对于fprem1
他的x/y=z会有一个四舍五入,这样的求余数当然会有误差
frndint 四舍五入
fld x
frndint
fstp y
x=2.5
那么y=2
fscale 求st0x2st1
fscale是求st0x2st1
st0乘以2的st1次方,结果返回st0
比如计数x*y^2
fld y ;入栈
fld x ;入栈
fscale
fstp result ;出栈
fstp buff ;出栈
f2xm1 求2st0 -1
f2xm1 是求2st0 -1
计算2x-1
fld x
f2xm1
fstp result
fyl2x 求 st1%22%20aria-hidden%3D%22true%22%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMAIN-D7%22%20x%3D%220%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%3C%2Fg%3E%0A%3C%2Fsvg%3E)
st0
fld x
fld y
fyl2x
fstp result
结果存st1,st0出栈
fyl2xp1 求 st1%22%20aria-hidden%3D%22true%22%3E%0A%20%3Cuse%20xlink%3Ahref%3D%22%23E1-MJMAIN-D7%22%20x%3D%220%22%20y%3D%220%22%3E%3C%2Fuse%3E%0A%3C%2Fg%3E%0A%3C%2Fsvg%3E)
(st0+1)
fld x
fld y
fyl2xp1
fstp result
结果存st1,st0出栈
三角函数 用弧度代入
fsin 求
(st0)
fld jiaodu
fmul PI
fidiv Num_180
fsin
fstp result
你也可以直接算出弧度,再直接入栈
fcos 求
(st0)
st0弧度已经转化
fld hudu'
fcos
fstp result
fsincos 求sin
cos(st0)
先计算sin入栈
再计算cos,入栈
于是
st1是sin
st0是cos
fptan 求
(st0)
st0还是弧度
不知道为什么.
st0会自动压入1
st1才是计算的结果
于是出栈的时候
先把st0弹出
再把结果弹出
fld hudu
fptan
fstp buff
fstp result
fpatan 坐标轴下的有符号角度
好比
(1,1)=450
(-1,0)=1800
st0是x坐标
st1是y坐标
返回的是一个弧度
fld y
fld x
fpatan
fimul 180
fstp result
余数结果就是度数
FPU 控制指令
finit/fninit 初始化FPU
控制字是什么?就那几个判断异常的东西....
状态字是什么?就是C3C2C0
标记字是什么>?
上面可能说错的......
反正他们会初始化一下FPU里的东西
然后
finit初始化前会检测没有处理的异常
fuinit初始化前并不会取检测
fstsw/fntstw 导出状态字
针对于状态字
ftstw ax ;检测并且处理异常,再状态字传递给ax,
ftstw m16 ;检测并且处理异常,再状态字传递给内存m16,
fnstsw ax ;不检测并且处理异常,再状态字传递给ax,
fnstsw m16 ;不检测并且处理异常,再状态字传递给内存m16,
fstcw/fnstcw 导出控制字
针对于控制字
fstcw m16 ;检测并且处理异常,再控制字传递给m16,
fnstcw m16 ;不检测并且处理异常,再控制字传递给m16,
fldcw 导入控制字
把16位变量的值加载到控制字
fldcw m16
fldenv 导入环境
将src的数据加载到FPU
fstenv/fnstenv 导出小环境
保存FPU当前操作环境到Dest内存变量指定的14或者28字节
fstenv dest ;检测并且处理异常后保存到dext,再屏蔽异常
fnstenv Dest :不检测并且处理异常,直接保存到dext,再屏蔽异常
fsave/fnsave 导出大环境
把fpu当前状态导出到94或者108字节的内存,然后自动初始化FPU
fsave dest ;检测/处理异常,然后导出到dext,然后初始化FPU
fnsave dest ;不检测/处理异常,然后导出到dest,然后出释怀fpu
fclex/fncles 清除异常
清除浮点异常标志
fclex ;检测并且处理异常,然后清除异常标志位
fnclex ;不检测并且处理异常,直接清除异常标志位
除了像fstenv那样,他还会保存浮点数数据寄存器,按照st0-st7的顺序紧随其后
frstor导入环境
把src的94或者108个字节导入到fpu
frstor src
fincstp /fdecstp top的自增自减
Inc top 会让st0的数据转移到st7,(st0去部st7的位)
dec top会让st7的数据转移到st0,(st7去补st0的位)
可以对应一下栈图,因为他只有8个空间,要循环使用,所以的话才会有这种奇奇怪怪的转移
inc之前/dec之前
4
5
6
-1.#IND
-1.#IND
-1.#IND
-1.#IND
inc之后
5
6
-1.#IND
-1.#IND
-1.#IND
4
dec之后
.#IND
4
5
6
-1.#IND
-1.#IND
-1.#IND
ffree sti
free后sti就是-1/#IND
ffree st(0)
fnop
同CPU的nop
fwait/wait
他们是同一个东西
功能是检查并且处理没有屏蔽的浮点异常
在浮点指令后加一条wait指令确保任何未被处理的异常在下一条IP执行前被处理
异常同步fwait/wait
整数 (CPU) 和 FPU 是相互独立的单元,因此,在执行整数和系统指令的同时可以执行浮点指令。这个功能被称为并行性 (concurrency),
我认为这是一个很好的隐藏手段,IDA都调试不了中间过程
当发生未屏蔽的浮点异常时,它可能是个潜在的问题。反之,已屏蔽异常则不成问题,因为,FPU 总是可以完成当前操作并保存结果。
FXAM
检测st0是否是0,正无穷,负无穷,非实数,或者正常数,然后会初始化c3c2c0
具体遇到再说
异常的屏蔽与未屏蔽简述
指令
fstcw
;获取控制字
fldcw
;结果加载到 FPU
导读
默认情况下,异常是被屏蔽的,因此,当出现浮点异常时,处理器分配一个默认值为结果,并继续平稳地工作。
例如,一个浮点数除以 0 生成结果为无穷,但不会中断程序,是吗?????哦????
.data
val1 DWORD 1
val2 REAL8 0.0
.code
fild val1 ;整数加载到ST(0)
fdiv val2 ;ST(0) =正无穷
如果 FPU 控制字没有屏蔽异常,那么处理器就会试着执行合适的异常处理程序。
清除 FPU 控制字中的相应位就可以实现异常的未屏蔽操作,如下表所示。
位 | 说明 | 位 | 说明 |
0 | 无效操作异常屏蔽位 | 5 | 精度异常屏蔽位 |
1 | 非规格化操作数异常屏蔽位 | 8〜9 | 精度控制位 |
2 | 除零异常屏蔽位 | 10〜11 | 舍入控制位 |
3 | 上溢异常屏蔽位 | 12 | 无穷控制位 |
4 | 下溢异常屏蔽位 |
于是对于除0异常,我们就去看2位,把它置0
- 将 FPU 控制字保存到 16 位变量。
- 清除位 2(除零标志位)。
- 将变量加载回控制字。
一个自动被屏蔽的异常
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
printf proto C :ptr sbyte,:vararg
scanf proto C :ptr sbyte,:vararg
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
val1 DWORD 1
val2 REAL8 0.0
hello byte "Dqx_Gh0st",0
fmt byte "%s",0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
fild val1 ;整数加载到ST(0)
fdiv val2 ;ST(0) =正无穷
invoke printf,offset fmt,offset hello
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
然后我们
.data
ctrlWord WORD ?
.code
fstcw ctrlWord ;获取控制字
and ctrlWord, 1111111111111011b ;不屏蔽除零异常
fldcw ctrlWord ;结果加载回 FPU
然后我们也没发生啥呀....woc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
printf proto C :ptr sbyte,:vararg
scanf proto C :ptr sbyte,:vararg
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
val1 DWORD 1
val2 REAL8 0.0
ctrlWord WORD ?
hello byte "Dqx_Gh0st",0
fmt byte "%s",0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
fstcw ctrlWord
and ctrlWord, 1111111111111011b
fldcw ctrlWord
fild val1 ;整数加载到ST(0)
fdiv val2 ;ST(0) =正无穷
invoke printf,offset fmt,offset hello
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
x64的浮点数四则运算
假设我们用的双精度浮点运算
addsd 加法
movsd xmm2, [number1] ; double precision float into xmm
addsd xmm2, [number2] ; add into to xmm
subsd 减法
movsd xmm2, [number1] ; double precision float into xmm
subsd xmm2, [number2] ; subtract from xmm
mulsd 乘法
movsd xmm2, [number1] ; double precision float into xmm
mulsd xmm2, [number2] ; multiply with xmm
divsd 除法
movsd xmm2, [number1] ; double precision float into xmm
divsd xmm2, [number2] ; divide xmm0
sqrtsd 开方
sqrtsd xmm1, [number1]
//结果保持在xmm1中
应用例子
混合运算
valD=-valA+(valB*valC)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
printf proto C :ptr sbyte,:vararg
scanf proto C :ptr sbyte,:vararg
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data
valA REAL8 1.5
valB REAL8 2.5
valC REAL8 3.0
valD REAL8 ? ; +6.0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
fld valA ; ST(0) = valA
fchs ;修改 ST(0) 的符号
fld valB ; 将 valB 加载到 ST(0)
fmul valC ; ST(0) *= valC
fadd ; ST(0) += ST(1)
fstp valD ; 将 ST(0) 保存到 valD
;invoke printf,addr out_fmt,edi
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
数组求和
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
printf proto C :ptr sbyte,:vararg
scanf proto C :ptr sbyte,:vararg
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
ARRAY_SIZE = 20
sngArray REAL8 1.2,2.3,3.4
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
mov esi, 0 ;数组索引
fldz ; 0.0 入栈
mov ecx,ARRAY_SIZE
L1: fld sngArray[esi] ;将内存操作数加载到ST(0)
fadd ; ST(0) 加 ST(1),出栈
add esi,TYPE REAL8 ;移至!I 下一个元素
loop L1
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
开方求和
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
printf proto C :ptr sbyte,:vararg
scanf proto C :ptr sbyte,:vararg
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
valA REAL8 25.0
valB REAL8 36.0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
fld valA ; valA 入栈
fsqrt ; ST(0) = sqrt(valA)
fld valB ; valB 入栈
fsqrt ; ST(0) = sqrt(valB)
fadd ; ST (0)+ST(1)
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
C语言的强制类型转换
//c
int N = 20;
double X = 3.5;
double Z = N + X;
//asm
.data
N SDWORD 20
X REAL8 3.5
Z REAL8 ?
.code
fild n ;整数加载到ST(0)
fadd X ;将内存操作数与ST(0)相加
fstp z ;将ST(0)保存到内存操作数
//c
int N = 20;
double X = 3.5;
int Z = (int)(N + X);
//Asm
fild N ;整数加载到ST(0)
fadd X ;将内存操作数与ST(0)相加
fist Z ;将ST(0)保存为整型内存操作数,这里就是一个转化
修改舍入模式,什么鬼
FPU 控制字的 RC 字段指定使用的舍入类型。可以先用 FSTCW 把控制字保存为一个变量,再修改 RC 字段(位 10 和 11),最后用 FLDCW 指令把这个变量加载回控制字:
fstew ctrlWord ;保存控制字
or ctrlWord, 110000000000b ;设置眈=截断
fldcw ctrlWord ;加载控制字
之后采用截断执行计算,生成结果为 Z=23:
fild N ;整数加载到ST(0)
fadd X ;将内存整数与ST(0)相加
fist Z ;将ST(0)保存为整型内存操作数
或者,把舍入模式重新设置为默认选项(舍入到最接近的偶数):
fstcw ctrlWord ;保存控制字
and ctrlWord, 001111111111b ;重置舍入模式为默认
fldcw ctrlWord ;加载控制字