进入本次正题:
(九)[ C++ 之 浮点数运算 ] 今天来学学浮点数的反汇编吧.
因为这几天电信宽带到期了, 暂时不想联网, 所以对于浮点数这里, 我没有查什么资料, 关于浮点数原理方面的,我也没做过多的阐述, 有兴趣的可以去看看特定的CPU手册,应该都差不多了,80X86模式的. 或者也可以看看汇编的书籍,都会有介绍的, 今次我们的目的是为了在反汇编一个用到了浮点数的程序时不至于理解不了其中的指令.
浮点数分为单精度和双精度, 单精度的C关键字为float, 双精度的为double. 在IA-32体系结构下, 一个float占32位,也即一个机器字(跟字WORD意义不一样) . 一个double 占64位, 也即2个机器字, 其进度更高.
OK,废话少说.
来cpp:
#include <stdio.h>
float floatcalc(float x,float y)
{
float f;
__asm
{
nop
}
f = x + y;
f = x - y;
f = x * y;
f = x / y;
f++;
return f;
}
double doublecalc(double x,double y)
{
double d;
__asm
{
nop
}
d = x + y;
d = x - y;
d = x * y;
d = x / y;
d++;
return d;
}
void main(void)
{
double x;
double y;
__asm int 3
x = 123.111;
y = 456.888;
doublecalc(x,y);
floatcalc((float)x,(float)y);
}
////Debug版:
00401110 > 55 push ebp
00401111 8BEC mov ebp, esp
00401113 83EC 58 sub esp, 58
00401116 53 push ebx
00401117 56 push esi
00401118 57 push edi
00401119 8D7D A8 lea edi, [ebp-58]
0040111C B9 16000000 mov ecx, 16
00401121 B8 CCCCCCCC mov eax, CCCCCCCC
00401126 F3:AB rep stos dword ptr es:[edi]
00401128 90 nop
00401129 C745 F8 C976BE>mov dword ptr [ebp-8], 9FBE76C9 ; /这里的数据是编译器根据我们
00401130 C745 FC 1AC75E>mov dword ptr [ebp-4], 405EC71A ; |给出的浮点数转换而来的.
00401137 C745 F0 91ED7C>mov dword ptr [ebp-10], 3F7CED91 ; |这四句是在为我们的两个double
0040113E C745 F4 358E7C>mov dword ptr [ebp-C], 407C8E35 ; \变量x和y进行赋值.
00401145 8B45 F4 mov eax, [ebp-C] ; /先压y的高32位,再压y的
00401148 50 push eax ; |低32位,这四句完成
00401149 8B4D F0 mov ecx, [ebp-10] ; |对y这个double型参数的
0040114C 51 push ecx ; \压栈过程.
0040114D 8B55 FC mov edx, [ebp-4] ; /
00401150 52 push edx ; |这四句完成对x的压栈过程.
00401151 8B45 F8 mov eax, [ebp-8] ; |
00401154 50 push eax ; \
00401155 E8 B5FEFFFF call Float.0040100F ; 进去看看.
0040115A DDD8 fstp st ;对st寄存器执行一次pop.
0040115C 83C4 10 add esp, 10 ; 恢复堆栈.
0040115F DD45 F0 fld qword ptr [ebp-10] ; /这两句是在做double型的y到
00401162 D955 EC fst dword ptr [ebp-14] ; \float的强制转换. 这里它另外使用了内存.
00401165 51 push ecx ; 开辟一个float所需的空间.
00401166 D91C24 fstp dword ptr [esp] ; 存储到指定地址,并清空ST寄存器.
00401169 DD45 F8 fld qword ptr [ebp-8] ; /
0040116C D955 E8 fst dword ptr [ebp-18] ; |这四句和上面四句是一样的 ,
0040116F 51 push ecx ; |把float型的x压栈.
00401170 D91C24 fstp dword ptr [esp] ; \
00401173 E8 92FEFFFF call Float.0040100A ; 进去看看.
00401178 DDD8 fstp st ; 执行一次pop操作, 因为ST0寄存器用不到了,所以pop之.
0040117A 83C4 08 add esp, 8 ; 两个float的参数丢弃.
0040117D 5F pop edi
0040117E 5E pop esi
0040117F 5B pop ebx
00401180 83C4 58 add esp, 58
00401183 3BEC cmp ebp, esp
00401185 E8 D6000000 call Float._chkesp
0040118A 8BE5 mov esp, ebp
0040118C 5D pop ebp
0040118D C3 retn ////doublecalc内部:
004010A0 >/> \55 push ebp
004010A1 |. 8BEC mov ebp, esp
004010A3 |. 83EC 48 sub esp, 48 //Debug默认的40h和我们的一个double型的8.
004010A6 |. 53 push ebx
004010A7 |. 56 push esi
004010A8 |. 57 push edi
004010A9 |. 8D7D B8 lea edi, [ebp-48]
004010AC |. B9 12000000 mov ecx, 12
004010B1 |. B8 CCCCCCCC mov eax, CCCCCCCC
004010B6 |. F3:AB rep stos dword ptr es:[edi]
004010B8 |. 90 nop ; 我们的标志.
//加法
004010B9 |. DD45 08 fld qword ptr [ebp+8] //装载浮点数,精度看其指定的内存大小.qword为双,dword为单.(相应ST置valid,意正在使用中.)
004010BC |. DC45 10 fadd qword ptr [ebp+10] //加上另一个浮点数, 这里面应该有一个隐含的装载过程.
004010BF |. DD5D F8 fstp qword ptr [ebp-8] // store and pop : 也即存储至指定位置, 且将ST0清空(置empty).
//减法
004010C2 |. DD45 08 fld qword ptr [ebp+8]
004010C5 |. DC65 10 fsub qword ptr [ebp+10]
004010C8 |. DD5D F8 fstp qword ptr [ebp-8]
//乘法
004010CB |. DD45 08 fld qword ptr [ebp+8]
004010CE |. DC4D 10 fmul qword ptr [ebp+10]
004010D1 |. DD5D F8 fstp qword ptr [ebp-8]
//除法
004010D4 |. DD45 08 fld qword ptr [ebp+8]
004010D7 |. DC75 10 fdiv qword ptr [ebp+10]
004010DA |. DD55 F8 fst qword ptr [ebp-8] //仅仅store进指定位置. 没有清零ST0.
004010DD |. DC05 2060420>fadd qword ptr [_real] //加上1, _real是个内部符号, 此值其实是1的双精度表示, 在.rdata段.
004010E3 |. DD55 F8 fst qword ptr [ebp-8] //再来store到指定位置.(我们的double型局部变量) 到这里其实ST0还是valid的, 还没有清空. 004010E6 |. 5F pop edi
004010E7 |. 5E pop esi
004010E8 |. 5B pop ebx
004010E9 |. 83C4 48 add esp, 48
004010EC |. 3BEC cmp ebp, esp
004010EE |. E8 6D010000 call Float._chkesp
004010F3 |. 8BE5 mov esp, ebp
004010F5 |. 5D pop ebp
004010F6 \. C3 retn
////float : 都差不多, 只不过double是qword. Float是dword. 就不分析了.
00401030 >/> \55 push ebp
00401031 |. 8BEC mov ebp, esp
00401033 |. 83EC 44 sub esp, 44 //40h+4.
00401036 |. 53 push ebx
00401037 |. 56 push esi
00401038 |. 57 push edi
00401039 |. 8D7D BC lea edi, [ebp-44]
0040103C |. B9 11000000 mov ecx, 11
00401041 |. B8 CCCCCCCC mov eax, CCCCCCCC
00401046 |. F3:AB rep stos dword ptr es:[edi]
00401048 |. 90 nop
00401049 |. D945 08 fld dword ptr [ebp+8]
0040104C |. D845 0C fadd dword ptr [ebp+C]
0040104F |. D95D FC fstp dword ptr [ebp-4]
00401052 |. D945 08 fld dword ptr [ebp+8]
00401055 |. D865 0C fsub dword ptr [ebp+C]
00401058 |. D95D FC fstp dword ptr [ebp-4]
0040105B |. D945 08 fld dword ptr [ebp+8]
0040105E |. D84D 0C fmul dword ptr [ebp+C]
00401061 |. D95D FC fstp dword ptr [ebp-4]
00401064 |. D945 08 fld dword ptr [ebp+8]
00401067 |. D875 0C fdiv dword ptr [ebp+C]
0040106A |. D955 FC fst dword ptr [ebp-4]
0040106D |. D805 1C60420>fadd dword ptr [_real]
00401073 |. D955 FC fst dword ptr [ebp-4]
00401076 |. 5F pop edi
00401077 |. 5E pop esi
00401078 |. 5B pop ebx
00401079 |. 83C4 44 add esp, 44
0040107C |. 3BEC cmp ebp, esp
0040107E |. E8 DD010000 call Float._chkesp
00401083 |. 8BE5 mov esp, ebp
00401085 |. 5D pop ebp
00401086 \. C3 retn ////Release版本的.
00401040 90 nop
00401041 68 358E7C40 push 407C8E35
00401046 68 91ED7C3F push 3F7CED91
0040104B 68 1AC75E40 push 405EC71A
00401050 68 C976BE9F push 9FBE76C9 ; 干脆直接.4个push搞定两个double的压参.
00401055 E8 C6FFFFFF call Float.00401020
0040105A 68 AA71E443 push 43E471AA
0040105F 68 D538F642 push 42F638D5 ; 干脆直接, 2个push搞定两个float的压参.
00401064 DDD8 fstp st ; ST0中的值"剪切"到ST7中.
00401066 E8 95FFFFFF call Float.00401000
0040106B DDD8 fstp st ;
0040106D 83C4 18 add esp, 18
00401070 C3 retn
////double test.
00401020 55 push ebp
00401021 8BEC mov ebp, esp
00401023 90 nop
00401024 DD45 08 fld qword ptr [ebp+8] ; load第一个double.
00401027 DC75 10 fdiv qword ptr [ebp+10] ; 除第二个double.对第二个double隐含load.我是这样理解的.
0040102A DC05 A8604000 fadd qword ptr [4060A8] ; 加1
00401030 5D pop ebp
00401031 C3 retn
////这里只有加法的原因是因为编译器优化的结果, 我对Release设置的是最小大小的优化.
////float test.
00401000 55 push ebp
00401001 8BEC mov ebp, esp
00401003 90 nop
00401004 D945 08 fld dword ptr [ebp+8]
00401007 D875 0C fdiv dword ptr [ebp+C]
0040100A D805 A0604000 fadd dword ptr [4060A0]
00401010 5D pop ebp
00401011 C3 retn
////几乎一样了, 只是qword 和 dword的区别. 小结:
浮点寄存器(FPU,Float Process Register, 存在与CPU的FPU中)在IA-32体系下有8个.
汇编指令用ST0~ST7表示. ST0一般简写作ST.
其状态标记我自己测试发现有4种:
Empty: 就是空状态 , 没有使用.
Valid: 就是正在使用. 这两种最常用.
Zero: 当ST寄存器的值为0时,即置此状态.
Bad: 脏数据的意思, 当我多次fld的时候, 超过了7次就会覆盖以前.
浮点寄存器貌似是一种环形的堆栈(或者认为是环形链表). 可以这样来理解:传统的堆栈看作是移动esp指针, 这里的堆栈可看成是指针固定,即ST0~ST7这8个寄存器始终在我们OD窗口中固定, push和pop看成是将环形栈的数据推动或拉动.
常用指令:
Fld :用于将一个指定的数据块装载到STx寄存器中.(一般为一个dword或一个qword),这里其实是一个push的动作.
格式:后跟指定内存地址和大小. 如:
Push XXXXXXXX
Push XXXXXXXX
Fld qword ptr [esp] . //XX … XXX 为编译器对我们的123.345 …等转化后的数据. Fadd : 浮点加法(同类的有fsub,fmul,fdiv.)
如:
Push XXXXXXXX
Push XXXXXXXX
Fld dword ptr[esp]
Fadd dword ptr [esp+4] (这样就默认目的地址为ST0, 也可以指明为其他FPR)
Fst: 浮点数储存(store) .: 将ST寄存器中的数据储存到内存地址中.(默认为ST0,也可指明为其他)
Push XXXXXXXX
Push XXXXXXXX
Fld qword ptr[esp]
Push ecx
Push ecx // make space.
Fst qword ptr [esp]
Fstp: 这个后面多了一个p, 也即pop的意思.除了执行fst的功能外, 另执行一次pop操作. 1:__ftol函数.
Double d = 123.456;
Long l = (long)d;
编译器在处理此句的时候会默认调用CRT库的__ftol来将double型的d转换为long型.
这个函数__ftol并未被CRT库导出.我们在反汇编中看到的__ftol都是由于程序中使用了如上格式的类型强制转换.
这个函数的功能: 将STx寄存器中的浮点数转换为相应的long型数据然后送入EAX,EDX寄存器.
2:floor函数,(math.h头文件有声明)
这个是被CRT库导出的函数, 其作用和数学中的高斯函数类似. 原型如下:
Double floor(double); (可能会存在别的形式.)
作用:对给定的一浮点数,求出不超过该浮点数的最大”整形”浮点数.
(比如:floor(123.111… 111)=123.0000…000 , floor(99.123123…) = 99.000 … 000等)
规则:floor函数相当于先fld,再对STx寄存器取高斯值.
其他的以后再补充吧.
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)