浮点运算指令

前言

出某道招新题因为对浮点寄存器及其指令不了解导致自己也不会做了,同时复现qwb的restaurant也遇到了浮点数就补了补习…
参考了以下文章:
SSE学习笔记
汇编语言学习笔记(十二)-浮点指令
浮点寄存器概述
《深入理解计算机系统》

图片我也是直接截图了…

反汇编发现32位和64使用的浮点数指令不相同,64位使用SSE指令,32位使用FPU进行浮点运算

FPU

FPU:(Float Point Unit,浮点运算单元)FPU是专用于浮点运算的处理器,以前的FPU是一种单独芯片,在486之后,英特尔把FPU集成在CPU之内。

常用指令:

1
2
3
fld: 将数据压入寄存器堆栈
fst: 将数据从寄存器堆栈弹出
fstp: 取栈顶的值,但是不弹出

指令的最前面f表示FPU,ld表示load,st表示store

FPU有8个独立寻址的80位寄存器 st0 - st7 ,他们形成寄存器堆栈.新放入的浮点数先存放在st0中,原本st0中的浮点数放入到st1中,以此类推。

测试:

实验代码:

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
#include<stdlib.h>
int main()
{
float a;
scanf("%f",&a);
if (a==3.15){
system("/bin/sh");
}
}

1
0x80484eb <main+48>    fld    dword ptr [ebp - 0x10]

执行这条指令之前st0寄存器为:

1
2
pwndbg> p $st0
$1 = 0

执行后:

1
2
pwndbg> p $st0
$2 = 1

1
0x80484ee <main+51>    fld    qword ptr [0x80485d0]

执行这条指令后,st1寄存器变为1,st0寄存器变为3.15:

1
2
3
4
pwndbg> p $st0
$3 = 3.1499999999999999111821580299874768
pwndbg> p $st1
$4 = 1

SSE指令

SSE有八个个128位计算器,xmm0 - xmm7。 每个128位的寄存器,可以存放四个32位的单精度浮点数。
图源

1
2
3
4
5
6
7
8
9
10
pwndbg> p $xmm0
$1 = {
v4_float = {0, 0, 0, 0},
v2_double = {0, 0},
v16_int8 = {0 <repeats 16 times>},
v8_int16 = {0, 0, 0, 0, 0, 0, 0, 0},
v4_int32 = {0, 0, 0, 0},
v2_int64 = {0, 0},
uint128 = 0
}

SSE的浮点运算指令有两个类型:packed 和 scalar。
packed指令是一次对xmm寄存器中的四个浮点数均进行计算,而scalar类型的则会根据指令的最后一位来对xmm寄存器中的低位进行计算。如下图所示:
图源

SSE指令的构成:

第一部分表示指令的作用,比如加法add,mov,com等;
中间的即是上面说到的两种类型:p或者s,分别表示为packed或者scalar;
第三部分为s或者d,s(single)表示单精度浮点数, d(double)表示双精度浮点数

例如指令movss value , xmm0 ,是将value这个单精度浮点数放入xmm0寄存器低位的第一个浮点数槽中.

常用的指令:

值转移指令:

浮点数和整形之间的转换:

比较指令:

遇到的问题…

原本出的题的源码如下:

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
#include<stdlib.h>
int main()
{
float a;
char b[10];
read(0,b,0x20);
if (a==3.15){
system("/bin/sh");
}
}

想通过溢出改变a的值,然后执行system(“/bin/sh”),但是实际操作时,将a覆盖为3.15对应的float底层表示,但是仍然不能比较通过.
原因是 3.15因为float精度不够,编译器将他编译成double类型的3.15
对应的汇编代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0x804845f <main+36>    fld    dword ptr [ebp - 0xc]
0x8048462 <main+39> fld qword ptr [0x8048528]
0x8048468 <main+45> fucomip st(1)
0x804846a <main+47> jp main+79 <0x804848a>

0x804846c <main+49> fld qword ptr [0x8048528]
0x8048472 <main+55> fucomip st(1)
0x8048474 <main+57> fstp st(0)
0x8048476 <main+59> jne main+81 <0x804848c>

.rodata:08048528 byte_8048528 db 33h ; DATA XREF: main+27↑r
.rodata:08048528 ; main+31↑r
.rodata:08048529 db 33h ; 3
.rodata:0804852A db 33h ; 3
.rodata:0804852B db 33h ; 3
.rodata:0804852C db 33h ; 3
.rodata:0804852D db 33h ; 3
.rodata:0804852E db 9
.rodata:0804852F db 40h ; @

将32位的float和64位的double比较肯定不会通过比较…

解决办法是强制类型转化下:

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
#include<stdlib.h>
int main()
{
float a;
char b[10];
read(0,b,0x20);
if (a==(float)3.15){
system("/bin/sh");
}
}