我一直在研究如何在x86体系结构中处理浮点运算,然后分解C代码。 使用的操作系统是一个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的主要function的反汇编版本:
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.然而,当我检查寄存器值不是0.它是有价值的
ds 0x2b
给定的价值,不应该计算
0x2b *0x10 + 0x8048520
然而,浮点数存储在0x8048520和0x8048530中,就像DS中的值为0。 任何人都可以向我解释这是为什么?
处于保护模式的DS完全不同。 它不是线性地址的移位部分,就像在实模式中那样,它是一个包含段基地址的段表的索引。 OS内核维护段表,用户级代码不能。
也就是说,忽略ds:前缀。 反汇编器明确地说明了默认行为,就是这样。 该命令默认使用DS作为选择器; 所以反汇编者认为它会提到。 操作系统将DS初始化为对于该过程有意义的事情,并且在整个过程中将使用相同的DS值。
由于代码是32位保护模式,因此如Seva所述,DS寄存器用作表中的索引。 这就是所谓的GDT或LDT,取决于它是全球的还是本地的。 全局描述符表和局部描述符表。
每个条目都指定了许多不同的参数。 这些包括所描述的内存区域的基础,限制和粒度,访问类型和特权级别。
完全有可能在每个方面都有两个相同的描述符 – 这些显然会在表中有不同的索引,并且会导致DS的值不同。
–
它还允许您访问位于地址空间任何位置的内存,就好像它位于内存底部一样。 以卡的线性帧缓冲区为例,视频内存。 不同的卡片实现将定位于不同的地址,但仍然可以以完全透明的方式访问这些不同的区域,这要归功于描述符中的基本字段。
我有一个卡位于内存0xE0000000,而另一个位于0xC0000000。 现在我可以把这个地址保存到一个全局变量中,然后在任何绘图操作中加载这个变量,并把它加到这个区域的计算偏移量中。 幸运的是,描述符机制使我们可以做得比这更好。
当我设置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); }
所有这些描述符指向相同的内存区域,但是需要8,16,24和32的DS值(第一个条目未被使用 – 每个条目的大小为8个字节)