Asm-指令篇

吴陆奇

关注

阅读 100

2022-07-18

常用指令系统

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如何指令命令?

  1. CPU从CS:IP读取指令到buff
  2. IP=IP+刚才读取的指令长度
    于是IP指向了下一条指令
  3. 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 求

用于求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 求 st1st0

fld x
fld y
fyl2x
fstp result

结果存st1,st0出栈

fyl2xp1 求 st1(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 求sincos(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

  1. 将 FPU 控制字保存到 16 位变量。
  2. 清除位 2(除零标志位)。
  3. 将变量加载回控制字。

一个自动被屏蔽的异常

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
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 ;加载控制字

精彩评论(0)

0 0 举报