DS segment register in 32 bit architecture

2019-09-12 01:51发布

I have been studying how floating point operations are handled in an x86 architecture by disassembling C code. The OS used is a 64 bit linux, while the code was compiled for a 32 bit machine.

Here is the C source code:

#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;
}

And here is the disassembled version of the main function of the 32 bit 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

What I have trouble understanding is the lines where the floating point values are transferred to the registers. Specifically:

mov    eax,ds:0x804852c  
mov    eax,ds:0x8048530

In my understanding, the instructions should be equal to mov eax,[0x804852c] and mov eax,[0x8048530] respectively since in 32 bit mode the ds register usually points to the whole 32 bit space and is usually 0. However when I check register values the ds is not 0. It has the value

ds             0x2b

Given that value, shouldn't the calculation be

0x2b *0x10 + 0x8048520

However the floats are stored in 0x8048520 and 0x8048530 which is like having a value of 0 in DS. Can anyone explain to me why is this?

2条回答
时光不老,我们不散
2楼-- · 2019-09-12 02:24

DS in protected mode works completely differently. It's not a shifted part of the linear address, like in real mode, it's an index into a segment table which contains the base address of the segment. The OS kernel maintains the segment table, userland code can't.

That said, ignore the ds: prefix. The disassembler is explicitly spelling out the default behavior, that's it. This command uses DS as the selector by default; so the disassembler thought it'd mention. The OS would initialize DS to something that makes sense for the process, and the same value of DS will be used throughout the whole process.

查看更多
倾城 Initia
3楼-- · 2019-09-12 02:25

Since the code is 32bit protected mode, the DS register is used as an index into a table, as Seva mentioned. This is called the GDT or LDT, depending on whether it's global or local to the process. Global Descriptor Table & Local Descriptor Table.

Each entry specifies a number of different parameters. These include the base, limit and granularity, access-type and privilege level of the memory region described.

It's entirely possible to have two descriptors which are identical in every way - these would obviously have different indexes in the table and would result in a different value for DS.

--

It also allows you to access memory located anywhere in address space as though it was at the very bottom of memory. Take for instance video memory for the card's linear frame-buffer. Different card implementations will locate this at different addresses, yet you can still access these different areas in a totally transparent manner, thanks to the base field in the descriptor.

One card I have locates memory at 0xE0000000, while another locates it at 0xC0000000. Now I could save this address to a global variable after querying the card for it, then in any drawing operations load this var and add it to the calculated offset in the region. Luckily, the descriptors mechanism allows us to do even better than this.

When I setup the GDT, I use the value returned from the card to specify the base for the memory region that will be referenced by a descriptor in a specific position in the table, thus making the drawing code not know or care where in physical memory the frame-buffer resides.

Accessing this is as simple as

 push   es
 mov    ax, LinearFrameBufferSel
 mov    es, ax

While specifying where the memory is located, I can hard-code the data to be loaded as the GDT like so:

    ;   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

Quick and dirty, but unsatisfying. A far better approach is to declare a table and then use a helper function to set the values for any particular entry:

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);
 }

All of these descriptors point to the same region of memory, but will need DS values of 8, 16, 24 and 32 (the first entry is unused - each entry is 8 bytes in size)

查看更多
登录 后发表回答