[x86] GDT (Global Descriptor Table) Verständnisproblem (Base und Limit) - QEMU reset

Hallo Coummunity,

ich habe ein neues Projekt für mich persönlich gestartet. Und zwar möchte ich gerne einen eigenen Mikrokernel haben.
Die Hauptzielplattform ist eigentlich ARM, habe das Projekt aber mal auf x86 gestartet.

Problem habe ich aber mit der GDT. Wenn ich die Werte für Base auf 0x0 und Limit auf 0xFFFFF setze, dann klappt alles wunderbar.

Auszug:
C:
static void gdt_init(void)
{
    /* NULL entry */
    gdt_set_entry(&gdt[0], 0, 0, 0, 0);
    
    /* Kernel Code */
    gdt_set_entry(&gdt[1], 0, 0xFFFFF, GDT_ACCESS_EXEC |
                                       GDT_ACCESS_SEGMENT |
                                       GDT_ACCESS_RING0 |
                                       GDT_ACCESS_PRESENT,
                                       GDT_FLAG_SIZE |
                                       GDT_FLAG_GRAN);
    
    /* Kernel Data */
    gdt_set_entry(&gdt[2], 0, 0xFFFFF, GDT_ACCESS_RW |
                                       GDT_ACCESS_SEGMENT |
                                       GDT_ACCESS_RING0 |
                                       GDT_ACCESS_PRESENT,
                                       GDT_FLAG_SIZE |
                                       GDT_FLAG_GRAN);
    
    /* User Code */
    gdt_set_entry(&gdt[3], 0, 0xFFFFF, GDT_ACCESS_EXEC |
                                        GDT_ACCESS_SEGMENT |
                                        GDT_ACCESS_RING3 |
                                        GDT_ACCESS_PRESENT,
                                        GDT_FLAG_SIZE |
                                        GDT_FLAG_GRAN);
    
    /* User Data */
    gdt_set_entry(&gdt[4], 0, 0xFFFFF, GDT_ACCESS_RW |
                                       GDT_ACCESS_SEGMENT |
                                       GDT_ACCESS_RING3 |
                                       GDT_ACCESS_PRESENT,
                                       GDT_FLAG_SIZE |
                                       GDT_FLAG_GRAN);
    
    gdt_p.ptr = (uint32_t) &gdt[0];
    gdt_p.limit = ((sizeof(struct gdt_entry) * GDT_ENTRIES) - 1);
    _cpu_gdt_flush((uint32_t) &gdt_p, ((uint32_t )&gdt[2].lo - (uint32_t)&gdt[0].lo));
}

Sobald ich aber aber die Adresse vom .text Segment für Code und .rodata, .data usw. für Daten angebe, dann resetet QEMU.
Hab das ganze vorerst noch nicht auf echter Hardware ausprobiert. Weiß also nicht, ob sich echte Hardware gleich verhält.

Die Adressen von den Segmenten entnehme ich aus dem Linkerscript. Was aber leider nicht klappt:
Code:
ENTRY(_start)

SECTIONS {
    . = 1M;
    
    _kern_start = .;
    
    .text  ALIGN(4096) :  {
        _kern_text_start = .;
        *(.multiboot)
        *(.text)
        _kern_text_end = .;
        
    }
    
    .rodata ALIGN(4096) : {
    _kern_rodata_start = .;
        *(.rodata)
        _kern_rodata_end = .;
    }
    
    


    .data  ALIGN(4096):  {
    _kern_data_start = .;
        *(.data)
        _kern_data_end = .;
    }
    
    

    .bss ALIGN(4096) :  {
    _kern_bss_start = .;
        *(.bss)
        _kern_bss_end = .;
    }
    
    

    
    .stack ALIGN(4096) :  {
    _kern_stack_start = .;
        *(.stack)
        _kern_stack_end = .;
    }
    
    
    
    .heap  ALIGN(4096) :  {
        _kern_heap_start = .;
        *(.heap)
        _kern_heap_end = .;
    }
    
    _kern_end = .;
}

Die Symbole lese ich mit Funktionen in Assembler ein, z.B.:
C-ähnlich:
FUNCTION(_kern_text_get_addr)
    pushl %ebp
    movl %esp, %ebp
    movl $_kern_text_start, %eax
    leave
    ret
    
FUNCTION(_kern_text_get_size)
    pushl %ebp
    movl %esp, %ebp
    movl $_kern_text_start, %ebx
    movl $_kern_text_end, %eax
    sub %ebx, %eax
    leave
    ret

Hier ist die Funktion gdt_set_entry():
C:
#define GDT_ACCESS_RW           0x02
#define GDT_ACCESS_DIRECTION    0x04
#define GDT_ACCESS_EXEC         0x08
#define GDT_ACCESS_SEGMENT      0x10
#define GDT_ACCESS_RING0        0x00
#define GDT_ACCESS_RING1        0x20
#define GDT_ACCESS_RING2        0x40
#define GDT_ACCESS_RING3        0x60
#define GDT_ACCESS_PRESENT      0x80
#define GDT_FLAG_SIZE           0x04
#define GDT_FLAG_GRAN           0x08

static void gdt_set_entry(struct gdt_entry *entry,
                          uint32_t base,
                          uint32_t limit,
                          uint8_t access,
                          uint8_t flags)
{
    if (!entry)
        return;
    
    entry->lo = (limit & 0xFFFF);
    entry->lo |= ((base & 0xFFFF) << 16);
    entry->hi = ((base & 0xFF0000) >> 16);
    entry->hi |= (access << 8);
    entry->hi |= (limit & 0xF0000);
    entry->hi |= ((flags & 0xF) << 20);
    entry->hi |= (base & 0xFF000000);
}

Ist hier eventuell ein Fehler?
 
Vorweg, ich konnte es nicht abwarten und habe bei einem OS Development Forum nachgefragt. Möchte aber aus bestimmten Gründen nicht darauf verweisen.

Aber sollte noch jemand auf das gleiche Problem stoßen, hier die Erklärung:

Erstens wird bei diesem Vorhaben genauer genommen ein General Protection Fault (#GP) ausgelöst. Da eine andere GDT geladen wurde, funktionieren die Interrupthandler nicht mehr, die dieses Problem abfangen könnten. Was dazu führt das ein Triple Fault ausgelöst wird und das System resetet.

Hier die Fehlerquelle (Disassembliert):
Code:
0010002e <_cpu_gdt_flush>:
  10002e:       55                      push   %ebp
  10002f:       89 e5                   mov    %esp,%ebp
  100031:       8b 44 24 08             mov    0x8(%esp),%eax
  100035:       0f 01 10                lgdtl  (%eax)
  100038:       8b 44 24 0c             mov    0xc(%esp),%eax
  10003c:       8e c0                   mov    %eax,%es
  10003e:       8e e0                   mov    %eax,%fs
  100040:       8e e8                   mov    %eax,%gs
  100042:       8e d0                   mov    %eax,%ss
  100044:       ea 4b 00 10 00 08 00    ljmp   $0x8,$0x10004b

0010004b <.1>:
  10004b:       c9                      leave 
  10004c:       c3                      ret

Der Fehler passiert hier an der Adresse 0x100044. Also bei der ljmp Instruktion. Mein Vorhaben war eigentlich das er auf das Symbol/Label .1 (0x10004b) springt. Was er auch macht, wenn die Basis in der GDT 0 ist. Allerdings wollte ich, in diesem Fall, eine Basis von 0x100000 haben und zwar wo der Kernel Code beginnt (Sektion .text). Jetzt stimmt aber die Zieladresse nicht mehr von der ljmp Instruktion, weil der Compiler nicht weiß, dass ich Basis geändert habe. Korrekterweise müsste die Zieladresse von der ljmp Instruktion 0x4B sein. Dann würde das Vorhaben funktionieren.

Hoffe ich konnte das so einigermaßen kurz erklären.
 
Zurück
Oben