我一直在研究如何通过反汇编C代码在x86架构中处理浮点运算。使用的操作系统是64位Linux,而代码是为32位机器编译的。
这是C源代码:
#include <stdio.h>
#include <float.h>
int main(int argc, char *argv[])
{
float a, b;
float c, d;
printf("%u\n",sizeof(float));
a = FLT_MAX;
b = 5;
c = a / b;
d = (float) a / (float) b;
printf("%f %f \n",c,d);
return 0;
}
这是32位exe的主要功能的反汇编版本:
804841c: 55 push ebp
804841d: 89 e5 mov ebp,esp
804841f: 83 e4 f0 and esp,0xfffffff0
8048422: 83 ec 30 sub esp,0x30
8048425: c7 44 24 04 04 00 00 mov DWORD PTR [esp+0x4],0x4
804842c: 00
804842d: c7 04 24 20 85 04 08 mov DWORD PTR [esp],0x8048520
8048434: e8 b7 fe ff ff call 80482f0 <printf@plt>
8048439: a1 2c 85 04 08 mov eax,ds:0x804852c
804843e: 89 44 24 2c mov DWORD PTR [esp+0x2c],eax
8048442: a1 30 85 04 08 mov eax,ds:0x8048530
8048447: 89 44 24 28 mov DWORD PTR [esp+0x28],eax
804844b: d9 44 24 2c fld DWORD PTR [esp+0x2c]
804844f: d8 74 24 28 fdiv DWORD PTR [esp+0x28]
8048453: d9 5c 24 24 fstp DWORD PTR [esp+0x24]
8048457: d9 44 24 2c fld DWORD PTR [esp+0x2c]
804845b: d8 74 24 28 fdiv DWORD PTR [esp+0x28]
804845f: d9 5c 24 20 fstp DWORD PTR [esp+0x20]
8048463: d9 44 24 20 fld DWORD PTR [esp+0x20]
8048467: d9 44 24 24 fld DWORD PTR [esp+0x24]
804846b: d9 c9 fxch st(1)
804846d: dd 5c 24 0c fstp QWORD PTR [esp+0xc]
8048471: dd 5c 24 04 fstp QWORD PTR [esp+0x4]
8048475: c7 04 24 24 85 04 08 mov DWORD PTR [esp],0x8048524
804847c: e8 6f fe ff ff call 80482f0 <printf@plt>
8048481: b8 00 00 00 00 mov eax,0x0
8048486: c9 leave
8048487: c3 ret
8048488: 66 90 xchg ax,ax
804848a: 66 90 xchg ax,ax
804848c: 66 90 xchg ax,ax
804848e: 66 90 xchg ax,ax
我理解的是浮点值传输到寄存器的行。具体做法是:
mov eax,ds:0x804852c
mov eax,ds:0x8048530
根据我的理解,指令应分别等于mov eax,[0x804852c]和mov eax,[0x8048530],因为在32位模式下,ds寄存器通常指向整个32位空间,通常为0.但是当我检查ds不是0的寄存器值。它的值为
ds 0x2b
鉴于该值,不应计算
0x2b *0x10 + 0x8048520
然而浮点数存储在0x8048520和0x8048530中,就像DS中的值为0一样。任何人都可以向我解释为什么会这样?
答案 0 :(得分:5)
处于保护模式的DS完全不同。它不是线性地址的移位部分,就像在实模式中一样,它是包含段的基地址的段表的索引。 OS内核维护段表,userland代码不能。
也就是说,忽略ds:前缀。反汇编程序明确地拼写出默认行为,就是这样。默认情况下,此命令使用DS作为选择器;所以反汇编者认为它会提到。操作系统会将DS初始化为对过程有意义的东西,并且在整个过程中将使用相同的DS值。
答案 1 :(得分:0)
由于代码是32位保护模式,DS寄存器用作表的索引,如Seva所述。这称为GDT或LDT,取决于它是全局的还是本地的。全球描述表&amp;本地描述表。
每个条目指定许多不同的参数。这些包括所描述的内存区域的基本,限制和粒度,访问类型和权限级别。
完全有可能有两个在各方面都相同的描述符 - 这些显然会在表中有不同的索引,并会导致DS的值不同。
-
它还允许您访问位于地址空间中任何位置的内存,就好像它位于内存的最底层一样。例如,用于卡的线性帧缓冲器的视频存储器。不同的卡实现会将其定位在不同的地址,但由于描述符中的基本字段,您仍然可以以完全透明的方式访问这些不同的区域。
我有一张卡在0xE0000000处找到内存,而另一张卡在0xC0000000处找到它。现在我可以在查询卡后将此地址保存到全局变量,然后在任何绘图操作中加载此var并将其添加到区域中的计算偏移量。幸运的是,描述符机制使我们能够做得更好。
当我设置GDT时,我使用卡返回的值来指定将由表中特定位置的描述符引用的内存区域的基础,从而使得绘图代码不知道或关注在哪里在物理内存中,帧缓冲区驻留。
访问它就像
一样简单 push es
mov ax, LinearFrameBufferSel
mov es, ax
在指定内存的位置时,我可以将要加载的数据硬编码为GDT,如下所示:
; point to memory r/w at E000 0000 - this should not be hard-coded! we should get the value from the video card, using VBE extension functions
; accessed with ds=40
LinearFrameBufferSel equ $ - gdt
dw 0xffff ; limit low ; [0-15] - index 40
dw 0x0000 ; base low ; [0-15]
db 0x00 ; base middle ; [16-23]
db 0x92 ; access ;
db 0xCF ; granularity ; flags(4) - limit(4) [16-19]
db 0xE0 ; base hi
; ; point to memory r/w at 000A 0000 ; index 48
; ; accessed with ds=48
BankedVidMemSel equ $ - gdt
dw 0xffff ; limit low ; [0-15]
dw 0x0000 ; base low ; [0-15]
db 0x0A ; base middle ; [16-23]
db 0x92 ; access ;
db 0xCF ; granularity ; flags(4) - limit(4) [16-19]
db 0x00 ; base hi
; ; point to memory r/w at 000B 8000 ; index 56
; ; accessed with ds=56
TextVidMemSel equ $ - gdt
dw 0xffff ; limit low ; [0-15]
dw 0x8000 ; base low ; [0-15]
db 0x0B ; base middle ; [16-23]
db 0x92 ; access ;
db 0xCF ; granularity ; flags(4) - limit(4) [16-19]
db 0x00 ; base hi
VideoBackBufferSel equ $ - gdt ; point to memory 0x800000 lower than 0xE0000000 ( = 8meg lower than 3 gig )
dw 0xffff ; limit low ; [0-15]
dw 0x0000 ; base low ; [0-15]
db 0x20 ; base middle ; [16-23]
db 0x92 ; access ;
db 0xCF ; granularity ; flags(4) - limit(4) [16-19]
db 0x00 ; base hi
快速而肮脏,但不满意。一种更好的方法是声明一个表,然后使用辅助函数来设置任何特定条目的值:
static void init_gdt()
{
gdt_ptr.limit = (sizeof(gdt_entry_t) * 5) - 1;
gdt_ptr.base = (u32int)&gdt_entries;
gdt_set_gate(0, 0, 0, 0, 0); // Null segment
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // Code segment
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // Data segment
gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // User mode code segment
gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // User mode data segment
gdt_flush((u32int)&gdt_ptr);
}
所有这些描述符都指向相同的内存区域,但需要DS值为8,16,24和32(第一个条目未使用 - 每个条目的大小为8个字节)