汇编指令学习总结

时间:2024.4.20

看过破解教程,都知道test,cmp是比较关键,可是我一直不清楚它们究竟是怎么比较的,最后下决心找了很多资料,和大家一起把它们弄清楚.

首先看看:状态寄存器(即标志寄存器)

PSW(Program Flag)程序状态字(即标志)寄存器,是一个16位寄存器,由条件码标志(flag)和控制标志构成,

如下所示:

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

OF DF IF TF SF ZF AF PF CF

条件码:

①OF(Overflow Flag)溢出标志,溢出时为1,否则置0.标明一个溢出了的计算,如:结构和目标不匹配.

②SF(Sign Flag)符号标志,结果为负时置1,否则置0.

③ZF(Zero Flag)零标志,运算结果为0时置1,否则置0.

④CF(Carry Flag)进位标志,进位时置1,否则置0.注意:Carry标志中存放计算后最右的位.

⑤AF(Auxiliary carry Flag)辅助进位标志,记录运算时第3位(半个字节)产生的进位置。

有进位时1,否则置0.

⑥PF(Parity Flag)奇偶标志.结果操作数中1的个数为偶数时置1,否则置0. 控制标志位:

⑦DF(Direction Flag)方向标志,在串处理指令中控制信息的方向。

⑧IF(Interrupt Flag)中断标志。

⑨TF(Trap Flag)陷井标志。

为举例方便说一下jnz和jz

测试条件

JZ ZF=1

JNZ ZF=0

即Jz=jump if zero (结果为0则设置ZF零标志为1,跳转)

Jnz=jump if not zero

好,接着来看test和cmp

*******************************************************************************

test属于逻辑运算指令

功能: 执行BIT与BIT之间的逻辑运算

测试(两操作数作与运算,仅修改标志位,不回送结果).

Test对两个参数(目标,源)执行AND逻辑操作,并根据结果设置标志寄存器,结果本身不会保存。EST AX,BX 与 AND AX,BX 命令有相同效果

语法: TEST r/m,r/m/data

影响标志: C,O,P,Z,S(其中C与O两个标志会被设为0)

运用举例:

1.Test用来测试一个位,例如寄存器:

test eax, 100b; b后缀意为二进制

jnz ******; 如果eax右数第三个位为1,jnz将会跳转

我是这样想的,jnz跳转的条件是ZF=0,ZF=0意味着ZF(零标志)没被置位,即逻辑与结果为1.

2.Test的一个非常普遍的用法是用来测试一方寄存器是否为空:

test ecx, ecx

jz somewhere

如果ecx为零,设置ZF零标志为1,Jz跳转

*******************************************************************************

CMP属于算术运算指令

功能: 比较两个值(寄存器,内存,直接数值)

语法: CMP r/m,r/m/data

标志位: C,P,A,Z,O

CMP比较.(两操作数作减法,仅修改标志位,不回送结果).

cmp实际上是只设置标志不保存结构的减法,并设置Z-flag(零标志).

零标志很像carry,也是内部标志寄存器的一位.

例如:

Cmp eax, 2; 如果eax-2=0即eax=2就设置零标志为1

Jz ****; 如果设置了零标志就跳转

*******************************************************************************

我得出的结论

test逻辑与运算结果为零,就把ZF(零标志)置1;

cmp 算术减法运算结果为零,就把ZF(零标志)置1.

TEST - Test For Bit Pattern

//位测试指令

Usage: TEST dest,src

//用法:TEST dest,src

Modifies flags: CF OF PF SF ZF (AF undefined)

//受影响的标志位有:

//你要注意的是这一部位才对。你上面的指令test eax,eax后面跟着的是jnz指令,jnz指令作用于零标志位的,若零标志位不为零则跳,所以test eax,eax是测试eax是否为零的指令,若eax有数据,不管是什么数据(111也好,101也好),jnz都会跳。

//更新一下,若eax有数据,不管是111也好,222也好,只要他们两个操作数作逻辑与运算之后,结果不为零,则jnz都会跳。而jz则不跳。

CF //进位标志,主要作用于JC/JNC指令。

OF //溢出标志,主要作用于JO/JNO指令

PF //奇偶标志,主要作用于JP/JPE/JNP/JPO指令

SF //符号标志,主要作用于JS/JNS指令

ZF //零标志,主要作用于JZ/JE/JNZ/JNE指令

AF //辅助进位标志,不过未定义。

Performs a logical AND of the two operands updating the flags

//执行两个操作数的逻辑与运算,并更新标志位。

register without saving the result.

//该操作只修改标志位,不保留运算结果值。

已经有经验的知道TEST指令是对寄存器或者说对两个操作数进行逻辑与,然后影响标志寄存器。

最常见的指令是TEST EAX,EAX, 但是如何逆向出TEST指令呢? 对我们新手来说正向经验不足是个难度。

开始的时候 我也不知道从何下手得到这 TEST指令, 我就随便看Object Pascal官方中文版的书,

然后反出来看, 于是渐渐的TEST指令就出来了。

而实际要逆出的指令是这一组, 不妨我们都逆逆看。

0040899C |. 8B1D 4CD24000 mov ebx, dword ptr [40D24C] ; NoPacker.0040D164

004089A2 |. 8B5B 18 mov ebx, dword ptr [ebx+18]

004089A5 |. 85DB test ebx, ebx

004089A7 |. 74 12 je short 004089BB

004089A9 |. A1 4CD24000 mov eax, dword ptr [40D24C]

004089AE |. E8 DDB9FFFF call <jmp.&kernel32.GetACP> ; [GetACP 004089B3 |. 3BD8 cmp ebx, eax 004089B5 |. 0F85 D2030000 jnz 00408D8D

004089BB |> BA 9CE94000 mov edx, 0040E99C

一、

(1)函数调用

00407CDC |. B8 FC7C4000 mov eax, 00407CFC ; ASCII "0 :" 00407CE1 |. E8 8AFFFFFF call 00407C70 00407CE6 |. B8 087D4000 mov eax, 00407D08 ; ASCII "Hello World"

00407CEB |. E8 90FFFFFF call 00407C80

(2)另外一种形式的 如 MessageBox(0, PChar(varb), PChar(varb2), 0);

00407CBF |. 6A 00 push 0

00407CC1 |. 8B45 F8 mov eax, dword ptr [ebp-8]

00407CC4 |. E8 77BCFFFF call 00403940

00407CC9 |. 50 push eax

00407CCA |. 8B45 FC mov eax, dword ptr [ebp-4]

00407CCD |. E8 6EBCFFFF call 00403940

00407CD2 |. 50 push eax ; |Text

00407CD3 |. 6A 00 push 0 ; |hOwner = NULL

00407CD5 |. E8 8EC8FFFF call <jmp.&user32.MessageBoxA> ; \MessageBoxA

(3)两个整型参数 func(90, 90);

00407D3A |. BA 5A000000 mov edx, 5A

00407D3F |. B8 5A000000 mov eax, 5A

00407D44 |. E8 47FFFFFF call 00407C90

(4)将两个值相加再转为string tmpStr:=IntToStr(a + b);

00407CB8 |. 8D55 FC lea edx, dword ptr [ebp-4] 00407CBB |. 8D041E lea eax, dword ptr [esi+ebx]

00407CBE |. E8 01D6FFFF call 004052C4

(5)值相加tBuf := Integer('A');

tmpStr:=IntToStr(a + b + tBuf);

00407CB9 |. BB 41000000 mov ebx, 41

00407CBE |. 8D55 FC lea edx, dword ptr [ebp-4]

00407CC1 |. 8D0437 lea eax, dword ptr [edi+esi]

00407CC4 |. 03C3 add eax, ebx 00407CC6 |. E8 F9D5FFFF call 004052C4

(6)获取一个函数指针

00407CB9 |. A1 A0974000 mov eax, dword ptr [4097A0]

00407CBE |. A3 9C974000 mov dword ptr [40979C], eax

(7)来回传递 F := Func(P);

Func(P) := F;

00407CB9 |. A1 A0974000 mov eax, dword ptr [4097A0]

00407CBE |. A3 9C974000 mov dword ptr [40979C], eax

00407CC3 |. A1 9C974000 mov eax, dword ptr [40979C] 00407CC8 |. A3 A0974000 mov dword ptr [4097A0], eax

(8) 相同的反汇编结果

@F := P; 和 F := Func(P);

00407CB9 |. A1 A0974000 mov eax, dword ptr [4097A0]

00407CBE |. A3 9C974000 mov dword ptr [40979C], eax

00407CCD |. A1 A0974000 mov eax, dword ptr [4097A0]

00407CD2 |. A3 9C974000 mov dword ptr [40979C], eax

P := @F; 和 Func(P) := F;也是相同的

(9)调用 N := F(N);

00407CDC |. A1 A4974000 mov eax, dword ptr [4097A4]

00407CE1 |. FF15 9C974000 call dword ptr [40979C] 00407CE7 |. A3 A4974000 mov dword ptr [4097A4], eax

(10) N := Func(P)(N);

00407CEC |. A1 A4974000 mov eax, dword ptr [4097A4]

00407CF1 |. FF15 A0974000 call dword ptr [4097A0]

00407CF7 |. A3 A4974000 mov dword ptr [4097A4], eax

(11)

00407CB9 |. BB 03000000 mov ebx, 3 ; I := 3

00407CBE |. 8D55 FC lea edx, dword ptr [ebp-4] ;得到tmpStr地址 00407CC1 |. 8D0437 lea eax, dword ptr [edi+esi] ;edi+esi就是90+90 00407CC4 |. 03C3 add eax, ebx ; I + edi + esi值

00407CC6 |. E8 F9D5FFFF call 004052C4 ; 存储在tmpStr内

(12)

00407CB9 |. BB 03000000 mov ebx, 3 ;I := 3 00407CBE |. 43 inc ebx ;I := I+1;

(13)当不自动为变量初始化时Delphi会自己给变量初始化, 形态是这样的。 //一般不是自己手动的临时变量要么是作为输入参数要么是作为中转存储。 00407C9B |. 894D F8 mov dword ptr [ebp-8], ecx

00407C9E |. 894D F4 mov dword ptr [ebp-C], ecx

00407CA1 |. 8955 FC mov dword ptr [ebp-4], edx

(14)Delphi的临时栈空间是在每个函数的后面。

00407D1F 00 db 00

00407D20 . FFFFFFFF dd FFFFFFFF

00407D24 . 14000000 dd 00000014

00407D28 . 4D 69 63 72 6F 73 >ascii "Microsoft vs Bor"

00407D38 . 6C 61 6E 64 00 ascii "land",0 00407D3D 00 db 00

00407D3E 00 db 00

00407D3F 00 db 00

如这段, 它在00407D1F之前存在一个函数将引用此段内某临时变量。

(15) 一个逻辑表达式 Done := (I>=1) and (I<100);

00407CB6 |. 83FB 01 cmp ebx, 1 (1)

00407CB9 |. 7C 05 jl short 00407CC0

00407CBB |. 83FB 64 cmp ebx, 64 (100)

00407CBE |. 7C 04 jl short 00407CC4

00407CC0 |> 33C0 xor eax, eax //清零

00407CC2 |. EB 02 jmp short 00407CC6

00407CC4 |> B0 01 mov al, 1 //清1

00407CC6 |> 8BD8 mov ebx, eax

(16) sqr求平方, sqrt开平方

I:= Sqr(J);

00407CB3 |. B9 64000000 mov ecx, 64

00407CB8 |. 8BC1 mov eax, ecx

00407CBA |. F7E8 imul eax

00407CBC |. 8BD8 mov ebx, eax

(17)关键字 test终于被我再次搞出来了, 且是存在于循环之中。

00407CB3 |. BB 64000000 mov ebx, 64

00407CB8 |> 4B /dec ebx

00407CB9 |. 85DB |test ebx, ebx //这里的test ebx ebx正好合乎我的需求。

00407CBB |.^ 7F FB \jg short 00407CB8

当然肯定还有各种test的情形, 不一定非要是循环。不过我们到是可以检测下各种

条件表达式下的循环。

[源]

I := 100;

while I > 0 do

begin

I:= I-1;

end;

关于条件表达式中进行总结。

根据上面的循环由于 I 和 0 作比较, 所以一般用test指令作为检测指令。然后根据

标志寄存器状态作跳转。我根据此推测又写了一个循环,同样还是让另外一个新变量J和

0作比较。

[反]

00407CB3 |. BB 64000000 mov ebx, 64 ;i = 100

00407CB8 |. B8 04000000 mov eax, 4 ;j = 4;

00407CBD |> 48 /dec eax ;循环四次,没次减1

00407CBE |. 85C0 |test eax, eax

00407CC0 |.^ 75 FB \jnz short 00407CBD

00407CC2 |. 85DB test ebx, ebx

00407CC4 |. 7E 05 jle short 00407CCB ;(test后若ZF!=OF或ZF=1)转 ;否则循环100次,每次减1

00407CC6 |> 4B /dec ebx

00407CC7 |. 85DB |test ebx, ebx

00407CC9 |.^ 7F FB \jg short 00407CC6

00407CCB |> \8D55 FC lea edx, dword ptr [ebp-4]

这是根据循环,我目的是想产生类似这样的代码。

0040899C |. 8B1D 4CD24000 mov ebx, dword ptr [40D24C] ; NoPacker.0040D164

004089A2 |. 8B5B 18 mov ebx, dword ptr [ebx+18]

004089A5 |. 85DB test ebx, ebx

004089A7 |. 74 12 je short 004089BB

004089A9 |. A1 4CD24000 mov eax, dword ptr [40D24C]

004089AE |. E8 DDB9FFFF call <jmp.&kernel32.GetACP> ; [GetACP 004089B3 |. 3BD8 cmp ebx, eax

004089B5 |. 0F85 D2030000 jnz 00408D8D

004089BB |> BA 9CE94000 mov edx, 0040E99C

现在知道了如何产生 test指令, 而cmp指令则很容易产生,一般 逻辑或条件表达式

内又大于0的数就会使用cmp作减法, 然后比较, test 和 cmp都是 将两个操作数进行

逻辑和算术运算,但是不修改寄存器值。 test负责逻辑判断影响标志寄存器。 cmp进行算术寄存器,也影响标志寄存器。

现在再次推理, 如果被比较的操作数是小于0, 或者 是个存储器操作数,或是个函数,

或是其他形式 该如何产生test指令呢? 如果 被比较的数是1或 -1会不会也产生 test指令呢。

下面这段是前面两个小循环

I := 100;

J := 4;

while j <> 0 do

begin;

J := J - 1;

end;

while I > 0 do

begin

I:= I-1;

end;

//先面开始测试是否 跟 1 -1等有关, 也可产生test指令。

第一次试验失败,看来跟1比较是不行的

00407CB3 |. B8 64000000 mov eax, 64

00407CB8 |> 48 /dec eax

00407CB9 |. 83F8 01 |cmp eax, 1

00407CBC |.^ 75 FA \jnz short 00407CB8

[源]

J := 100;

while j <> 1 do

begin;

J := J - 1;

end;

还有-1

同样也失败

00407CB3 |. B8 64000000 mov eax, 64

00407CB8 |> 48 /dec eax 00407CB9 |. 83F8 FF |cmp eax, -1

00407CBC |.^ 75 FA \jnz short 00407CB8

[源]

J := 100;

while j <> 1 do

begin;

J := J - 1;

end;

看来都不行, 不过现在知道和0比较可以了就可以了。那么再次推测,和常量0 或者 存储器0, 或者其他形式的, 如返回值0比较是否也要产生test指令呢?

先不管循环的形式, 如果将条件转移为if then else ...类的条件内和0比较是否也可以呢

00407CB3 |. B8 64000000 mov eax, 64

00407CB8 |. 85C0 test eax, eax

00407CBA |. 7E 13 jle short 00407CCF

00407CBC |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL

00407CBE |. 68 3C7D4000 push 00407D3C ; |Title = "Hello World"

00407CC3 |. 68 3C7D4000 push 00407D3C ; |Text = "Hello World"

00407CC8 |. 6A 00 push 0 ; |hOwner = NULL

00407CCA |. E8 99C8FFFF call <jmp.&user32.MessageBoxA> ; \MessageBoxA

00407CCF |> 8D55 FC lea edx, dword ptr [ebp-4]

看来是可以的,

[源]

J := 100;

if J > 0 then

begin

MessageBox(0, 'Hello World', 'Hello World', 0);

end;

小结: test是两两个寄存器作与运算,然后影响标志寄存器,然后后面的转移指令根据标志寄存器

来转。

我稍微改变了个符号后

00407CB3 |. B8 64000000 mov eax, 64

00407CB8 |. 85C0 test eax, eax

00407CBA |. 7D 13 jge short 00407CCF

00407CBC |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL

00407CBE |. 68 3C7D4000 push 00407D3C ; |Title = "Hello World"

00407CC3 |. 68 3C7D4000 push 00407D3C ; |Text = "Hello World"

00407CC8 |. 6A 00 push 0 ; |hOwner = NULL

00407CCA |. E8 99C8FFFF call <jmp.&user32.MessageBoxA> ; \MessageBoxA

[源]

J := 100;

if J < 0 then

begin

MessageBox(0, 'Hello World', 'Hello World', 0);

end;

则jle 被改为了jge, jge要求 SF=OF, 由于test指令对eax进行了与运算,将全部标志寄存器清零了。

所以SF=OF,所以要转。好下面完成后面的测试, 当被比较的不是0, 是常量,是变量,是函数时

会产生什么代码。

(1).1常量

[源]

const ZeroN = 0;

...

J := 100;

if J < ZeroN then

begin

MessageBox(0, 'Hello World', 'Hello World', 0);

end;

[反]

00407CB3 |. B8 64000000 mov eax, 64

00407CB8 |. 85C0 test eax, eax

00407CBA |. 7D 13 jge short 00407CCF

00407CBC |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL

00407CBE |. 68 3C7D4000 push 00407D3C ; |Title = "Hello World"

00407CC3 |. 68 3C7D4000 push 00407D3C ; |Text = "Hello World"

00407CC8 |. 6A 00 push 0 ; |hOwner = NULL

00407CCA |. E8 99C8FFFF call <jmp.&user32.MessageBoxA> ; \MessageBoxA

00407CCF |> 8D55 FC lea edx, dword ptr [ebp-4]

看来常量是可以的。那么存储器的呢。

存储器的就不行了 00407CB3 |. B8 64000000 mov eax, 64 ;J := 100;

00407CB8 |. 33DB xor ebx, ebx ;I := 0;

00407CBA |. 3BD8 cmp ebx, eax

00407CBC |. 7E 13 jle short 00407CD1

00407CBE |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL

00407CC0 |. 68 407D4000 push 00407D40 ; |Title = "Hello World"

00407CC5 |. 68 407D4000 push 00407D40 ; |Text = "Hello World"

00407CCA |. 6A 00 push 0 ; |hOwner = NULL

00407CCC |. E8 97C8FFFF call <jmp.&user32.MessageBoxA> ; \MessageBoxA

00407CD1 |> 8D55 FC lea edx, dword ptr [ebp-4]

存储通过xor 进行了清零,然后用的cmp 做减法 然后判断跳。

还有一种就是函数的返回值了。当函数返回Boolean值或 指针 或 0 值时。 函数的返回值得也是不行的

00407CE0 |. BB 64000000 mov ebx, 64

00407CE5 |. B8 5A000000 mov eax, 5A

00407CEA |. E8 A1FFFFFF call 00407C90

00407CEF |. 3BD8 cmp ebx, eax

00407CF1 |. 7D 13 jge short 00407D06

00407CF3 |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL

00407CF5 |. 68 747D4000 push 00407D74 ; |Title = "Hello Wor

ld"

00407CFA |. 68 747D4000 push 00407D74 ; |Text = "Hello World"

00407CFF |. 6A 00 push 0 ; |hOwner = NULL

00407D01 |. E8 62C8FFFF call <jmp.&user32.MessageBoxA> ; \MessageBoxA

00407D06 |> 8D55 FC lea edx, dword ptr [ebp-4]

但是我们随便Google一下test指令, 找到的破解的test的形式有如下几种等:

1)TEST EAX,EAX

2)TEST EBX,EBX

3)TEST ESI,ESI

4)TEST EDI,EDI 这种直接逻辑与寄存器的比较多。 含存储器的呢?

5)test al,1 含一个立即数

6)test ebp,esi 总之还有其他形式的test , 但是以test eax,eax最多, 因为Windows API多对eax做了优化, 当使用 if 检测是否返回0

时, 就要使用test指令, 更多有待总结。

浮点数的几种表示方法

浮点二进制表示

浮点数是由三部分组成的:符号、尾数、指数。以数字 -1.23456X10^5为例,其中“-”是符号,表示该浮点数是负数,1.23456是尾数,5是指数。

IEEE二进制浮点数的表示

Intel微处理器使用IEEE发布的Standard754-1985 for Binary Floating-Point Arithmetic中规定的三种浮点二进制存储格式。见下表。

格式 说明

单精度 32位:符号占1位、指数占8位、尾数中的小数部分占23位。

可表示的大致范围2^-126~2^127,也称为短实数

双精度 64位:符号占1位、指数占11位、尾数中的小数部分占52位。

可表示的大致范围2^-1022~2^1023,也称为长实数

扩展精度 80位:符号占1位、指数占16位、尾数中的小数部分占63位。

可表示的大致范围是2^-16382~2^16383,也称为扩展实数

三种格式非常相似,因此这里重点讲述单精度格式。见下图。图中最高有效位

(MSB)在左边:

1 8 23

符号 指数 尾数中的小数部分

符号

二进制浮点数的符号由一个符号位便是,如果该位为1表示负数,为0表示

正数。浮点数的0是正数。

尾数

在形如mXb*e的表达式表示的浮点数中,m称为尾数,b是基数,e是指数。

尾数部分m是一个十进制小数。大家知道的二进制,十进制和十六进制的加权

进位表示法。现在可以把同样的概念扩展到数字的小数部分。例如十进制数

123.154可以表示为表达式的和:

123.154=(1X10^2)+(2X10^1)+(3X10^-0)+(1X10^-1)+(5X10^-2)+(4X10^-3)

二进制浮点数的表示方法类似,只要用2为基数计算位权值即可。例如浮点数二

进制值11.1011可表示为:

11.1011=(1X2^1)+(1X2^0)+(1X2^-1)+(0X2^-2)+(1X2^-3)+(1X2^-4)

另一种表示小数点后面的值的方法是用以2的幕为分母的分数之和表示,例如

.1011=1/2+0/4+1/8+1/16=11/16

得到分数表示的方法是相当直观的。二进制数1011实际上是十进制数11,可用

1011(十进制数11)做分数的分子。假设e是二进制小数点后的有效数字的位

数,那么分母就是2^e。在该例子中e=4,因此分母2^e=16。下表中给出了另外一

些二进制浮点表示转换成十进制分数的例子。表1中的最后一项是23位规格化

尾数所以能存储的最小分数。表2中列出了另一些例子,例子中分别给出了二进

制浮点数以及与其等价的分数和十进制数。

表1 二进制浮点数转换成分数

二进制浮点数 十进制分数值

11.11 3 3/4

101.0011 5 3/16

1101.100101

13 37/64

0.00101 5/32

1.011 1 3/8

0.00000000000000000000001

1/8388608

表2 二进制和十进制分数

二进制浮点数 十进制分数值 十

进制值

.1

1/2 .5

.01

1/4 .25

.001

1/8 .125

.0001

1/16 .0625

.00001

1/32 .03125

尾数的精度

连续实数的整体不可能以有限个数据位的浮点格式表示。例如,假设使用

一种简化的格式表示浮点数,其中尾数占5个数据位,那么就没有办法表示二进

制数1.1111和二进制数10.0000之间的实数了。因此表示二进制数1.11111就需

要更高精度的尾数。类似的结论可以推广到IEEE双精度格式,53位的尾数不能

表示需要54或更多个数据位的二进制数字。

指数

单精度浮点数的指数是以8位无符号正数的格式存储的,实际存储的是指数

值与127相加的和。以数字1.101X2^5为例,指数(5)与127相加的和(十进

制132,二进制10100010)存储在指数部分。下表给出了一些指数的例子。其中

第一列是有符号十进制整数,第二列是调整后的值,最后一列是调整后的整数对

应的二进制值。调整后的指数部分总是正数(在1~254之间),

实际指数范围是-126~+127。之所以选择这个范围,是为了避免最小可能的指

数的倒数发生溢出(这里由于如果最小的指数选择了-127,那么-127+127=0)

指数(E) 调整后(E+127) 二进制值

+5 132 10000100

0 127 01111111

-10 117 01110101

+127 254 11111110

-126 1 00000001

-1 126 01111110

二进制浮点数的正规化

大多数浮点二进制数都是以正规化的格式存储,以便能够使得尾数的精度

最大化。对于任何给定的浮点二进制数,可通过移动小数点,使小数点前仅有一

个数字“1”从而使其正规化,指数表示小数点向左(正数)或向右移动(负数)

的位数。看下面例子

未正规化格式 正规化格式

1110.1

1.1101X2^3

.000101 1.01X2^-4

101001. 1.010001X2^6

非正规化值:二进制浮点正规化操作的逆过程称为逆正规化,可通过移动二进制小数点直到指数为零。如果指数部分使正数n,那么向右移动小数点n位;如果指数部分是负数n,那么向左移动小数点n位。如果需要,开头的空位应以0填充。

IEEE表示法

实数编码

一旦符号位、指数和尾数域进行了正规化和编码,得到一个完整的二进制IEEE短实数就非常容易了。例如,二进制值1.101X2^0可表示如下:

符号位:0 (因为是正数)

指数 :01111111 (因为指数是0 0+127=127=01111111)

小数 :10100000000000000000000 (因为单精度短实数尾数中小数部分占23位)

调整后的指数01111111是十进制数127,所有正规化后的二进制尾数在小数点前面都有一个1,因此对该位就无需显式编码了。下表中给出了几个例子: 单精度浮点数位编码的例子

二进制 调整后的指数值 符号、指数和尾数

-1.11 127 1 01111111 11000000000000000000000

+1101.101 130 0 10000010 10110100000000000000000

-.00101 124 1 01111100 01000000000000000000000

+100111.0 132 0 10000100 00111000000000000000000

+0000001101011 120 0 01111000 10101100000000000000000

IEEE规范中还规定了几类实数和非实数的编码:

正数0和负数0

反向正规化的有限数

正规化的有限数

正无穷和负无穷

非数字值(NaN)

不确定数

Intel浮点单元使用不确定数响应一些无效的浮点操作。

正规化和反向正规化:正规化的有限数是0到无穷大之间的所有可以编码为正规化实数的非0有限值。尽管乍看起来所有非零浮点数都可以正规化,不过事实上在值非常接近于0时这是不可能的。由于指数范围的限制,FPU有可能不能把二进制的小数点移动到正规化的位置。假设FPU的计算结果是1.0101111X2^-129,其指数太小无法在单精度实数中存储,这时将产生一个下溢出异常,可通过向左移动小数点(每次一位)使其正规化,直到指数落在有效的范围内为止:例 1.01011110000000000001111 X 2^-129

0.10101111000000000000111 X 2^-128

0.01010111100000000000011 X 2^-127

0.00101011110000000000001 X 2^-126

在这个例子中,由于移动了小数点,精度会有一些损失。

正无穷和负无穷:正无穷(+∞)表示最大的正实数,负无穷(-∞)表示最小的负实数。可以比较无穷值和其他值:-∞小于+∞,-∞小于任何有限数,+∞大于任何有限数。正无穷和负无穷都能用来表示溢出。

非数字(NaN):NaN是不表示任何有效实数的位序列。IA-32体系结构包含两种类型的NaN:quiet NaN可通过大多数算术运算而不会导致任何异常;signalling NaN可用于产生一个无效操作异常。编译器可以用Signalling NaN值填充未初始化的数组,对该数组进行任何计算的企图都会引发一个异常。quiet NaN可用于存放调试会话产生的诊断信息。浮点单元不会试图对NaN执行操作。IA-32手册详细描述了一套规则,用于确定以这两种类型的NaN组合作为操作数时指令执行的结果。

特殊值的编码:下表列出了浮点操作中经常遇到的几个特殊值的编码。标记位的位置既可以是0,也可以试1。QNaN表示quiet NaN , SNaN表示 signalling NaN。 特殊的单精度浮点值的编码

值 符号、指数、尾数

正数0 0 00000000 00000000000000000000000

负数0 1 00000000 00000000000000000000000

正无穷 0 11111111 00000000000000000000000

负无穷 1 11111111 00000000000000000000000

QNaN x 11111111 1xxxxxxxxxxxxxxxxxxxxxxxxx

SNaN x 11111111 1xxxxxxxxxxxxxxxxxxxxxxxxx

*SNaN的尾数域以0开始,但是其余x表示的域中至少要有一个为1。

把十进制分数转换为二进制实数

如果十进制分数可以很容易地表示为1/2+1/4+1/8+......的格式,那么就很容易得到其对应的二进制实数。下表中的左边一列中的大部分分数一眼上去都不容易转换成二进制,不过如果写成第二列的格式就容易多了。

十进制分数和二进制实数的例子

十进制分数 分解为 二进制实数 1/2

1/2 .1

1/4

1/4 .01

3/4 1/2+1/4 .11 1/8

1/8 .001

7/8 1/2+1/4+1/8 .111

3/8 1/4+1/8 .011 1/16

1/16 .0001

3/16 1/8+1/16 .0011 5/16 1/4+1/16 .0101 很多实数,如1/10(0.1)或1/100(.01),不能用有限个二进制数字位表示,这样的分数只能近似表示为若干以2的幕为分母的分数之和。想想货币值如$39.95会受什么影响。

另一种方法:使用二进制长除法。把十进制分数转换成二进制数时如果涉及到的十进制值比较小,可以使用一种很方便的方法:首先把分子和分母都转换成二进制值,然后再进行除法操作。例如,十进制值0.5可表示成分数5/10,十进制5是二进制值0101,十进制值10是二进制值1010,执行下面的除法,我们发现商是二进制值0.1:

_____.1__________

1010 | 0101.0

- 101 0

___________

在被除数减去1010后余数为0时,除法终止。因此十进制分数5/10等于二进制值0.1,我们称上面这种方法为二进制长除法。PS来自于DePaul大学的Harvey Nice。

例子:以二进制数表示0.2。下面把十进制的0.2(2/10)转换成二进制值,使用长除法。首先,把二进制值10除以1010(十进制值10):

___.00110011(等等)_________________ 1010 | 10.00000000

1 010

__________

1100

1010

___________

10000

1010

____________

1100

1010

_____________

等等

第一个足够大的余数是10000,在除以1010之后,余数是110,在末尾添加一个0,新的被除数是1100,除以1010后,余数是10,再添加3个0,新的被除数是10000,这等于进行第一次除法操作时被除数的值。从这点开始,商中重复出现0011,因此我们得知准确的商是无法得到的。0.2不能用有限个二进制位表示。0.2的单精度编码的尾数部分是00110xxxxxxxxxxxx11001

把单精度值转换成十进制数

下面是在把IEEE单精度值转换成十进制数时的推荐步骤:

1。如果MSB(最高有效位)是1,该值为负数,否则为正数。

2。接下来的8位表示指数,减去二进制值01111111(十进制值127),得到未调整的值数值,把未调整的指数值转换成十进制数。

3。接下来的23位表示尾数,注意尾数小数点前面有一个被省略的“1.”,尾数尾部的0可以忽略。这时就可以使用第一步中得到的符号位、第二步中得到的指数以及本步骤中得到的尾数创建一个浮点二进制数了。

4。对第三步得到的浮点数进行逆规格化操作(根据指数相应地移动小数点,指数为正则右移动,指数为负则左移动)。

5。从左到右,使用位权表示方法得到浮点数的2的次幕之和的表示,进而得到相应的十进制数。

例子:把IEEE浮点数(0 10000010 01011000000000000000000) 转换成十进制数

1. 该数是正数。

2. 未调整的指数值是00000011,也就是十进制数3。

3. 组合符号位、指数和尾数得到二进制数+1.01011X2^3

4. 逆正规化后的二进制数是+1010.11

5. 对应的十进制值是+10 3/4或+10.75

提到编码,只要学过一点汇编的人都应该知道一些常用的汇编指令的编码,比如:B8 78 56 34 12,一看到B8就知道对应的汇编指令是MOV EAX,0X12345678 占用5字节,一看到E8就知道是E8后面跟的是JMP的4字节偏移,一见90就知道是NOP,因为这些指令都很常用,编码也都很简单,想必大家对这些指令编码都熟记于心了。如果提到 MOV EBX,XXXXXXXX MOV ECX,XXXXXXX这些指令也许大家对指令编码就不怎么记得了,因为X86的编码太多了,要把他全记住那可不是件容易的事。其实要把X86的编码指令记住并不是件难事,因为X86编码指令看似复杂庞大,其实这大部分编码有是有规律可寻的。

在这里先说组寄存器:

0 1 2 3 4 5 6 7

EAX ECX EDX EBX ESP EBP ESI EDI

0 1 2 3 4 5 6 7

AL CL DL BL AH CH DH BH

不知道各位同学当年学汇编的时候寄存器是不是按这个顺序记的,如果是按这个顺序记住的话,接下来讲的编码你可能一看就记住了

B8是MOV EAX 大家都很清楚的记得,那么B9呢?B9就是MOV ECX ,BA MOV EDX 聪明的同学应该很快的看出规律出来了吧!BB 是MOV EBX ,BC是MOV ESP 一直到BF 是MOV EDI

B0是MOV AL,XX 2字节立即数,对照上面的表格,大家应该很容易的说出B1是MOV CL,XX,一直到B7是MOV BH,XX

90是NOP大家都知道,其实他的真正编码指令是XCHG EAX,EAX,91 XCHG EAX,ECX 一直到97 XCHG EAX,EDI

40到47是INC EAX 到 INC EDI ,48到4F是DEC EAX到DEC EDI

50到57是PUSH EAX 到 PUSH EDI ,58到 5F是 POP EAX 到 POP EDI 现在对于这一类的指令编码,大家是不是感觉记起来轻松了

对内存访问的指令在汇编中也经常出现,现在在说说这些指令的编码格式 ADD OR ADC SBB AND SUB XOR CMP

ES CS SS DS

DAA DAS AAA AAS

就跟上面我说的寄存器一样先按顺序记下这些东西再说

要讲对内存访问的编码就不得不先说说X86通用的编码指令格式

上图参考INTEL开发手册卷二,想具体了解的可以去参考下,看不懂英文的,论坛的翻译版块有部分章节的翻译。大家可以去找找

Opcode(操作码),之前所说的B8 XX XX XX XX指令B8就代表Opcode(操作码),之后跟的是4字节的Immediate (立即数),一看到B8开头的编码,就要知道B8代表Opcode,后面带有4字节Immediate的属性

在举个列子,B0 XX,B0代表 Opcode,后面带有1字节Immediate的属性 00 是ADD EB,GB (EB代表1字节内存,GB代表8位寄存器)

碰到00编码,00代表着Opcode(操作码),后面的第2个1字节编码就代表着ModR/M

编码 对应的汇编指令

ModR/M字节的二进制6 7位MOD为0

00 00 ADD BYTE PTR DS:[EAX],AL

00 01 ADD BYTE PTR DS:[ECX],AL

。。。。。

00 07 ADD BYTE PTR DS:[EDI],AL

00 08 ADD BYTE PTR DS:[EAX],CL

。。。。

00 0F ADD BYTE PTR DS:[EDI],CL

00 10 ADD BYTE PTR DS:[EAX],DL

。。。。

00 3F ADD BYTE PTR DS:[EDI],BH

ModR/M字节的二进制6 7位MOD为1,第三个编码为1字节Displacement 00 40 XX ADD BYTE PTR DS:[EAX+XX],AL

00 41 XX ADD BYTE PTR DS:[ECX+XX],AL

。。。。

00 7F XX ADD BYTE PTR DS:[EDI+XX],BH

ModR/M字节的二进制6 7位MOD为2, 从第三个编码开始为4字节Displacement 00 80 XX XX XX XX ADD BYTE PTR DS:[EAX+XXXXXXXX],AL

00 81 XX XX XX XX ADD BYTE PTR DS:[ECX+XXXXXXXX],AL

00 BF XX XX XX XX ADD BYTE PTR DS:[EDI+XXXXXXXX],BH

ModR/M字节的二进制6 7位MOD为3

00 C0 ADD AL,AL

00 C1 ADD CL,AL

。。。。

00 FF ADD BH,BH

相信大家很轻易的就在上面的列子中找到规律了吧

现在如果写条编码 00 04 大家肯定都会脱口而出ADD BYTE PTR DS:[ESP],AL 00 05 ADD BYTE PTR DS:[EBP],AL,如果是这样那就不对了,现在我就在来讲下这种特殊情况

当 ModR/M字段的MOD等于 0 1 2的时候,出现[ESP]就接着ModR/M字段后扩展出SIB字段,

这时[ESP]就扩展成SIB的[Base+Index*2^Scale+Displacement] ,Base和Index的值也是代表着EAX到EDI,2^Scale代表比例因子1 2 4 8,当Base等于 ESP EBP的时候就会以SS 为段前辍,其他情况默认为DS

当MOD等于0时,出先[EBP]从第三个字节开始为4字节的Displacement

[XXXXXXXX],一般全局变量的访问就是采用这种寻址方式

当MOD等于0时,出现[ESP],SIB的BASE等于5,即EBP,

[Base+Index*2^Scale+Displacement]就会变成[Index*2^Scale+Displacement] Displacement这时为4字节

如果还有不明白的,大家可以在调式器直接写编码试试各种情况

00 ADD EB,GB (EB代表1字节内存,GB代表8位寄存器)

01 ADD EV,GV (EV代表4字节内存,GV代表32或8位寄存器) 02 ADD GB,EB (内存到8位寄存器)

03 ADD GV,EV (内存到32位寄存器)

04 XX ADD AL,IB (IB代表1字节立即数)

05 XX XX XX XX ADD EAX,ID (ID代表4字节立即数)

操作码08到0C对应着00到05的格式,只不过ADD指令换成OR指令

相同的10到15为ADC,18到1C为SBB,20到25为AND,28到2C为SUB,30到35为XOR,

38到3C为CMP

到此00到40的编码除了个位为6 7 E F的之外都讲了

06 PUSH ES

07 POP ES

0E PUSH CS

0F大家一定会脱口而出是POP CS,这样大家又错了,0F这条编码被废弃做为扩展编码,为什么要废弃0F呢?我想因为X86指令提供了RET的指令,而单独的修改CS没啥作用,就废弃掉做为扩展指令,扩展指令这里就不细谈了,因为那又是一片漫长的编码。

16 PUSH SS

17 POP SS

1E PUSH DS

1F POP DS

26表示前缀为ES,编码指令格式中好象就剩下Instruction Prefixes这个字节没讲了

如果从26开始解释一个编码,编码的第一个字节为26,第二个字节才为Opcode(操作码)

举个列子 编码00 00 本来表示指令ADD BYTE PTR DS:[EAX],AL

但如果编码为26 00 00 指令就表示 ADD BYTE PTR ES:[EAX],AL

Instruction Prefixes的作用就是用来修改段寄存器的

2E CS

26 SS

3E DS

27 DAA

37 AAA

2F DAS

3F AAS

这组4个指令关于BCD码的

更多相关推荐:
汇编语言学习心得

汇编语言程序设计学习总结与心得班级09计本3班学号0姓名刘得分本学期学习了汇编语言汇编语言是面向机器的程序设计语言在汇编语言中用助记符代替操作码用地址符号或标号代替地址码这样用符号代替机器语言的二进制码就把机器...

汇编语言学习心得

汇编学习心得08网工一班李锐0804031002在大三接触汇编语言之前我们在计算机组成原理课程中就已经有所了解了但也只是略微明白一些如jmpmov这样的指令极度缺乏系统性的学习在接触这门课程后感到汇编语言并不是...

汇编语言个人学习总结

DOS的提示符下可如下键入Debug启动调试程序DEBUG路径文件名参数1参数2Debug后可以不带文件名仅运行Debug程序需要时再用N和L命令调入被调试程序命令中可以带有被调试程序的文件名则运行Debug的...

《汇编语言程序设计》学习心得自主学习报告

自主学习报告书题目学习汇编语言程序设计报告学习课程汇编语言程序设计姓名专业学号福建工程学院国脉信息学院教务处制二一二年六月学习汇编语言程序设计报告书由于实际工作中对汇编语言程序设计应用较多在业余时间我自主学习了...

汇编语言总结

汇编语言学习体会大一大二期间学写了一些高级语言,如C语言和C++。在对一些实际问题的编程处理上使用这些高级语言显得很是方便。于是在刚接触这门课的时候就对其实用性产生了怀疑和一些的抵触情绪。再学习了一段时间后,虽…

汇编语言学习心得

汇编语言学习体会前段时间刚从计算机系统组成与体系结构的稍微学习了汇编语言只学习了简单的一些指令通过自学80868088汇编语言程序设计虽然有了一定的了解但是整体上还是不懂的还是不能写出一段完整的程序这次的实验还...

汇编语言学习活动

比较汇编语言与高级语言说明其优缺点答高级语言是高度封装了的编程语言与低级语言又称汇编语言相对由于汇编语言依赖于硬件体系且助记符量大难记于是人们又发明了更加易用的所谓高级语言在这种语言下其语法和结构更类似普通英文...

汇编语言总结

转汇编语言指令系统整理总结数据传送指令一通用数据传送指令1传送指令MOVmove指令的汇编格式MOVDSTSRC指令的基本功能DSTltSRC将原操作数字节或字传送到目的地址指令支持的寻址方式目的操作数和源操作...

汇编语言期末复习总结(完整版)

习题124说明下列指令中源操作数的寻址方式如果BX20xxHDI40H给出DX的值或有效地址EA的值1movdx1234h直接寻址EA1234H3movdxbx寄存器寻址DX20xxH6movdxbxdi基址变...

汇编语言--指令系统整理总结

一通用数据传送指令1传送指令MOVmove指令的汇编格式MOVDSTSRC指令的基本功能DSTltSRC将原操作数字节或字传送到目的地址指令支持的寻址方式目的操作数和源操作数不能同时用存储器寻址方式这个限制适用...

期末复习-汇编语言各章总结

汇编语言学习笔记汇编语言王爽前言学习汇编目的充分获得底层编程体验深刻理解机器运行程序的机理原则没有通过监测点不要向下学习没有完成当前实验不要向下学习第一章基础知识有三类指令组成汇编语言汇编指令伪指令其他符号8b...

c语言学习心得体会

c语言学习心得体会c语言学习心得体会一c语言学习心得体会c语言作为一种计算机的语言我们学习它有助于我们更好的了解计算机与计算机进行交流因此c语言的学习对我们尤其重要在这个星期里我们专业的学生在专业老师的带领下进...

汇编语言学习总结(14篇)