Autor Thema: Long Mode - Welche Methode um Kernel zu linken?  (Gelesen 3860 mal)

tiger717

  • Beiträge: 84
    • Profil anzeigen
Gespeichert
« am: 23. October 2012, 21:58 »
Hallo,

Ich habe in den letzten Tagen die Wikieinträge im os-dev.org-Wiki zum Long Mode recht ausführlich studiert ("unsere" Seite dazu ist ja noch recht spärlich) und werde irgendwie nicht schlau, wie ich am besten Kernel und Bootstrap linke.
In diesem Eintrag werden dazu drei Methoden beschrieben:
Loading...
    5.1 With your own boot loader             Fällt schonmal weg, lieber Multiboot
    5.2 With a separate loader                             
    5.3 With a 32-bit bootstrap in your kernel

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 23. October 2012, 22:23 »
Hallo,

was willst du denn genau wissen?

Im zweiten Fall ist dein Second-Stage-Bootloader, der als Multiboot-Kernel geladen wird, ein ganz normaler 32-Bit-Kernel und der eigentliche Kernel, der als Multiboot-Modul geladen wird, ein Binary in einem von dir frei wählbaren Format (z.B. flat binary *g*).

Im dritten Fall ist dein Kernel ein 64-Bit-Kernel, an den vorne ein 32-Bit-Bootstrap rangepappt wurde. Das funktioniert ähnlich, wie ein Higher-Half-Kernel im 32-Bit-Modus, bei dem du gewisse Dateien/Sektionen an andere Adressen linkst wie den Rest. Das dort angegebene Linkerscript tut genau das.

Getrennte Dateien machen die Entwicklung einfacher, sind aber später mehr Aufwand in der Handhabung. Was du davon benutzt, ist dir überlassen.

Gruß,
Svenska

tiger717

  • Beiträge: 84
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 24. October 2012, 07:38 »
Entschuldigung, ich sehe gerade, der Editor hat da etwas "vergessen".  :-(

Ich werde meine Frage heute Abend nochmal erklären, trotzdem Danke!

tiger717

  • Beiträge: 84
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 24. October 2012, 21:48 »
Okay, ich will eigentlich Methode 3 nehmen, da sie mir am korrektesten erscheint.
1. Ist das so?
2. Ich kompilier ich dann den (64-Bit) Kernel als elf64 und den Bootstrap als elf32 und linke dann das ganze zu elf64, oder? (elf64 kann ja von GRUB2 geladen werden)
3. So wie ich das verstanden habe, besteht der einzige Unterschied zwischen elf32 und elf64 in dem unterschiedlichen Addresslimit (und ein paar unwichtigen zusätzlichen Sektionen). Richtig?

thx

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 24. October 2012, 22:19 »
Okay, ich will eigentlich Methode 3 nehmen, da sie mir am korrektesten erscheint.
1. Ist das so?
Kommt drauf an, was du mit korrekt meinst. Ich bevorzuge die zweite Methode, weil GRUB Legacy und der Multiboot-Loader in qemu keine ELF64-Dateien laden können. Aber wenn dich das nicht stört, seh ich kein Problem mit deiner Methode.

2. Ich kompilier ich dann den (64-Bit) Kernel als elf64 und den Bootstrap als elf32 und linke dann das ganze zu elf64, oder? (elf64 kann ja von GRUB2 geladen werden)
Ich glaube der Bootstrap muss auch als ELF64 kompiliert werden. Der 32-Bit Code wird durch das .code32 erzeugt, aber der Linker will vermutlich einheitliche Dateitypen haben.

3. So wie ich das verstanden habe, besteht der einzige Unterschied zwischen elf32 und elf64 in dem unterschiedlichen Addresslimit (und ein paar unwichtigen zusätzlichen Sektionen). Richtig?
Es gibt noch ein paar Unterschiede in den Datenstrukturen, aber die betreffen dich nicht solange du keinen eigenen ELF-Loader implementierst. Außerdem kennt der 64-Bit GCC "Red Zones", aber wenn du dich an den osdev.org-Artikel hälst, und die deaktivierst, solltest du kein Problem damit bekommen.
« Letzte Änderung: 24. October 2012, 22:22 von Jidder »
Dieser Text wird unter jedem Beitrag angezeigt.

tiger717

  • Beiträge: 84
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 28. October 2012, 10:03 »
Hallo nochmal,

folgendes Problem: nasm kann problemlos 32-Bit Code in elf64 stecken, gcc hat damit aber anscheinend große Probleme. Mit -m32 produziert gcc elf32.
Ich habe deshalb
objcopy -O elf64-little arch/x86/init32.o arch/x86/init.oprobiert. (init32.o ist die Outputdatei von gcc, -m32, init.o ist die Ausgabedatei)
Output von objcopy:
BFD: BFD (GNU Binutils for Ubuntu) 2.22.90.20120924 Erklärung fehlgeschlagen ../../bfd/reloc.c: 6621
make: *** [arch/x86/init.o] Speicherzugriffsfehler (Speicherauszug erstellt)
make: *** Datei »arch/x86/init.o« wird gelöscht
Öhm, ja? Speicherzugriffsfehler? Ich glaube nicht, dass das so gewollt ist...
init.o enthält ein paar Initialisierungsfunktionen, die doch recht ausführlich sind. Ich möchte die nicht unbedingt in Assembler neu schreiben...
Gibt es also eine Möglichkeit, dass gcc 32-bittigen elf64-Code erzeugt?

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 28. October 2012, 10:49 »
Hallo,

der Segfault klingt nach einem Bug in den Binutils. Bei mir tritt folgende Fehlermeldung auf:

$ objcopy -O elf64-little main64.o test32.o
BFD: BFD (GNU Binutils for Debian) 2.22 assertion fail ../../bfd/reloc.c:6054
BFD: test32.o: unsupported relocation type R_X86_64_32
objcopy:test32.o: Bad value
$

Gruß,
Svenska

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 28. October 2012, 11:17 »
tyndur macht aber auch irgendwelche Magie mit objcopy, vielleicht kannst du da was abschauen: http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/kernel2/src/arch/amd64/Makefile.all
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

tiger717

  • Beiträge: 84
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 04. November 2012, 17:59 »
Okay, anscheinend ist der tyndur-Trick doch nicht so genial:

(ld-Output)
arch/x86/init.o: In function `_binary_arch_x86_init32_o_start':
(.data+0x0): multiple definition of `_binary_arch_x86_init32_o_start'
./arch/x86/init.o:(.data+0x0): first defined here
arch/x86/init.o: In function `_binary_arch_x86_init32_o_end':
(.data+0x5cd0): multiple definition of `_binary_arch_x86_init32_o_end'
./arch/x86/init.o:(.data+0x5cd0): first defined here
arch/x86/loader.o: In function `loader':
arch/x86/loader.S:(.text+0x0): multiple definition of `loader'
./arch/x86/loader.o:arch/x86/loader.S:(.text+0x0): first defined here
arch/x86/loader.o: In function `multiboot':
arch/x86/loader.S:(.bss+0x188): multiple definition of `multiboot'
./arch/x86/loader.o:arch/x86/loader.S:(.bss+0x188): first defined here
./arch/x86/loader.o: In function `shortjmp':
arch/x86/loader.S:(.text+0x9b): undefined reference to `init_32'
arch/x86/loader.o: In function `shortjmp':
arch/x86/loader.S:(.text+0x9b): undefined reference to `init_32'
make: *** [image] Fehler 1

Kurzer Blick auf readelf vor der Umwandlung:
$ readelf -s arch/x86/init32.o

Symboltabelle ‚.symtab‛ enthält 42 Einträge:
   Num:    Wert   Size Typ     Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS init.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    5
     3: 00000000     0 SECTION LOCAL  DEFAULT    7
     4: 00000000     0 SECTION LOCAL  DEFAULT    8
     5: 00000000     0 SECTION LOCAL  DEFAULT    9
     6: 00000000     0 SECTION LOCAL  DEFAULT   11
     7: 00000000     0 SECTION LOCAL  DEFAULT   12
     8: 00000000     0 SECTION LOCAL  DEFAULT   13
     9: 00000000     0 SECTION LOCAL  DEFAULT   15
    10: 00000000     0 SECTION LOCAL  DEFAULT   25
    11: 00000000     0 SECTION LOCAL  DEFAULT   27
    12: 00000000     0 SECTION LOCAL  DEFAULT   29
    13: 00000000     0 SECTION LOCAL  DEFAULT   17
    14: 00000000     0 SECTION LOCAL  DEFAULT   19
    15: 00000000     0 SECTION LOCAL  DEFAULT   21
    16: 00000000     0 SECTION LOCAL  DEFAULT   23
    17: 00000000     0 SECTION LOCAL  DEFAULT   30
    18: 00000000     0 NOTYPE  LOCAL  DEFAULT    1 wm4.1.e9f73b8cd07e2ddf8d0
    19: 00000000     0 NOTYPE  LOCAL  DEFAULT    2 wm4.paging.h.6.4b576cfed7
    20: 00000000     0 NOTYPE  LOCAL  DEFAULT    3 wm4.stddef.h.2.6ea241740e
    21: 00000000     0 NOTYPE  LOCAL  DEFAULT    4 wm4.stdbool.h.2.2f4031346
    22: 00000000     0 SECTION LOCAL  DEFAULT   28
    23: 00000000     0 SECTION LOCAL  DEFAULT    1
    24: 00000000     0 SECTION LOCAL  DEFAULT    2
    25: 00000000     0 SECTION LOCAL  DEFAULT    3
    26: 00000000     0 SECTION LOCAL  DEFAULT    4
    27: 00000000    17 FUNC    GLOBAL DEFAULT    5 init_32
    28: 000000fb   469 FUNC    GLOBAL DEFAULT    5 init_pmm_32
    29: 0000009a    97 FUNC    GLOBAL DEFAULT    5 init_vmm_32
    30: 00000011   137 FUNC    GLOBAL DEFAULT    5 memset_32
    31: 00000436   567 FUNC    GLOBAL DEFAULT    5 pmm_alloc_32
    32: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND pml4
    33: 00000a4d    82 FUNC    GLOBAL DEFAULT    5 map_page_range_32
    34: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND mmap_count
    35: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND mmap
    36: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND multiboot
    37: 000002d0   176 FUNC    GLOBAL DEFAULT    5 find_free_page_32
    38: 00000380   182 FUNC    GLOBAL DEFAULT    5 find_contingous_free_page
    39: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND malloc
    40: 0000066d   992 FUNC    GLOBAL DEFAULT    5 map_page_32
    41: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND pmm_alloc

Dann
objcopy -B i386:x86-64 -I binary -O elf64-x86-64 arch/x86/init32.o arch/x86/init.o

Daraufhin:
tobias@MD5320:~/os-dev$ readelf arch/x86/init.o -s

Symboltabelle ‚.symtab‛ enthält 5 Einträge:
   Num:    Wert           Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     2: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    1 _binary_arch_x86_init32_o
     3: 0000000000005cd0     0 NOTYPE  GLOBAL DEFAULT    1 _binary_arch_x86_init32_o
     4: 0000000000005cd0     0 NOTYPE  GLOBAL DEFAULT  ABS _binary_arch_x86_init32_o

objcopy hat also mal eben 37 Symbole unter den Tisch fallen lassen. Hmm.
Und die multiple-definition-Fehler kann ich auch nicht verstehen.

P.S. mmap, mmap_count und pml4 sind extern ints.

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #9 am: 04. November 2012, 20:12 »
Okay, anscheinend ist der tyndur-Trick doch nicht so genial:

(ld-Output)
Sieht für mich danach aus, als würdest du init.o zweimal an ld übergeben.

objcopy hat also mal eben 37 Symbole unter den Tisch fallen lassen. Hmm.
Das ist auch die Idee in tyndur. Um mal zu erläutern wie tyndur das macht:
Der Loader ist ein 32-Bit "Kernel". In den Loader wird der 64-Bit Kernel eingebettet. Dazu wird der Kernel (der eigentlich im ELF-Format ist) von objdump genommen und als binary interpretiert. objdump erzeugt daraus eine neue ELF-Objektdatei, die nur die start/end-Symbole enthält, und darin eingebettet ist der Kernel. Diese neue Objekt-Datei wird zum Loader dazugelinkt, der nun auch die start/end-Symbole kennt. Der Loader hat einen kleinen ELF-Parser eingebaut, der den Kernel an seine korrekte Adresse kopiert. Anschließend springt der Loader an die Startadresse der Kernels. Die Startadresse steht im ELF-Header.

Wenn du hingegen den 32-Bit-Teil in den 64-Bit-Kernel einbetten willst, kannst du mal ausprobieren, ob du init32.o in eine 64-Bit-ELF umwandeln kannst. objcopy -O elf64-x86-64 init32.o oder so. (Ungetestet.)


« Letzte Änderung: 04. November 2012, 20:17 von Jidder »
Dieser Text wird unter jedem Beitrag angezeigt.

tiger717

  • Beiträge: 84
    • Profil anzeigen
Gespeichert
« Antwort #10 am: 07. November 2012, 19:37 »
Okay, anscheinend ist der tyndur-Trick doch nicht so genial:

(ld-Output)
Sieht für mich danach aus, als würdest du init.o zweimal an ld übergeben.
Habe ich auch schon gedacht und 10x gecheckt, ist aber nicht der Fall.
Hier mal der Output von $ nm x86/*.o amd64/*.o -A | grep "_32"x86/init.o:0000000000000380 T find_contingous_free_pages_32
x86/init.o:00000000000002d0 T find_free_page_32
x86/init.o:0000000000000000 T init_32
x86/init.o:00000000000000fb T init_pmm_32
x86/init.o:000000000000009a T init_vmm_32
x86/init.o:000000000000066d T map_page_32
x86/init.o:0000000000000a89 T map_page_range_32
x86/init.o:0000000000000011 T memset_32
x86/init.o:0000000000000436 T pmm_alloc_32
x86/loader.o:                 U handler_32
x86/loader.o:                 U init_32

Und zum Beweis auch noch die Makefile: (Ich weiß, ein wenig schmutzig)
CC =gcc
AS =nasm
LD =ld
EMU =qemu-system-i386 -no-kvm -monitor stdio -s -S -cdrom $(ISO)

#WFLAGS  =-Wall -Wextra -Wshadow -Wconversion -Wunreachable-code -Werror-implicit-function-declaration -Wuninitialized
WFLAGS =-Wall -Wextra -pedantic -Wshadow -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -Wno-long-long -Wuninitialized -Wconversion -Wstrict-prototypes
CFLAGS =-nostdinc -nostdlib -nostartfiles -nodefaultlibs -ffreestanding -fno-stack-protector -fno-leading-underscore -fno-strict-aliasing -fno-builtin -fomit-frame-pointer -m64 -g3 -O0 -c -Iinclude -mcmodel=kernel -mno-red-zone -mno-mmx -mno-sse -mno-sse2 -mno-sse3 -mno-3dnow
ASFLAGS =-f elf64
LDFLAGS =-m elf_x86_64 -T linker.ld -nostdlib -nodefaultlibs -Bstatic

STDLIB =lib/std/string.o lib/std/ctype.o lib/std/errno.o lib/std/stdio.o lib/std/stdlib.o lib/std/uchar.o lib/std/wchar.o #lib/std/math_exp.o lib/std/math_misc.o lib/std/math_pow.o lib/std/math_trigo.o
MEMLIB =#lib/ptmalloc3/malloc.o lib/ptmalloc3/ptmalloc3.c
KERNEL =arch/x86/loader.o arch/amd64/kernel.o arch/amd64/handler.o arch/amd64/mm.o arch/amd64/pmm.o arch/amd64/vmm.o arch/amd64/console.o arch/x86/init.o
OBJ     =$(STDLIB) $(MEMLIB) $(KERNEL)

KERNELF =kernel.sys

.PHONY: all run

all: image

image: kernel stdlib memlib
$(LD) $(LDFLAGS) $(OBJ)
-objcopy --only-keep-debug $(KERNELF) kernel.sym
-objcopy --strip-debug $(KERNELF)

kernel: $(KERNEL)

memlib: $(MEMLIB)

stdlib: $(STDLIB)

arch/x86/init.o: arch/x86/init.c
$(CC) -nostdinc -nostdlib -nostartfiles -nodefaultlibs -ffreestanding -fno-builtin -fomit-frame-pointer -m32 -g3 -O0 -c -Iinclude -o arch/x86/init32.o $^
objcopy -O elf64-x86-64 arch/x86/init32.o $@
-@rm arch/x86/init32.o

%.o: %.S
$(AS) $(ASFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -o $@ $^

mkimage: all
@cp $(KERNELF) $(BOOTDIR)
grub-mkrescue -o "$(ISO)" $(ISODIR)
-@wait

run: mkimage
$(EMU)

Ich kann es mir nicht erklären...

objcopy hat also mal eben 37 Symbole unter den Tisch fallen lassen. Hmm.
Das ist auch die Idee in tyndur. Um mal zu erläutern wie tyndur das macht:
Der Loader ist ein 32-Bit "Kernel". In den Loader wird der 64-Bit Kernel eingebettet. Dazu wird der Kernel (der eigentlich im ELF-Format ist) von objdump genommen und als binary interpretiert. objdump erzeugt daraus eine neue ELF-Objektdatei, die nur die start/end-Symbole enthält, und darin eingebettet ist der Kernel. Diese neue Objekt-Datei wird zum Loader dazugelinkt, der nun auch die start/end-Symbole kennt. Der Loader hat einen kleinen ELF-Parser eingebaut, der den Kernel an seine korrekte Adresse kopiert. Anschließend springt der Loader an die Startadresse der Kernels. Die Startadresse steht im ELF-Header.

Wenn du hingegen den 32-Bit-Teil in den 64-Bit-Kernel einbetten willst, kannst du mal ausprobieren, ob du init32.o in eine 64-Bit-ELF umwandeln kannst. objcopy -O elf64-x86-64 init32.o oder so. (Ungetestet.)
Ich will eigentlich erstmal meinen Long Mode Code testen. Ich musste ja sehr viel "blind" schreiben.
Andere Frage: Wozu braucht man das end-Symbol? Zum Auslesen braucht man doch eigentlich nur den Header irgendwo am Anfang der Datei?

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #11 am: 07. November 2012, 22:20 »
Willst du nicht wissen, wieviel Speicher du für den Kernel mappen/kopieren musst? ;-)

tiger717

  • Beiträge: 84
    • Profil anzeigen
Gespeichert
« Antwort #12 am: 07. November 2012, 23:15 »
Gibt es da nicht eine magische Sache namens... Moment mal... ELF-Header?  :-D

Also, im IRC haben Jidder und ich folgendes herausgefunden:
1. Es liegt an meinem verhunzten Linkerscript.
2. GNU ld kann (bei mir) mit EXCLUDE_FILE nicht umgehen. Er ignoriert es einfach und linkt fröhlich init.o nochmal dazu, Effekt siehe oben.
 
Ich habe jetzt testweise den Linkerscript gefixt (mit anderen Worten: Noch weiter verhunzt), sodass ich erstmal meinen Long Mode-Code reparieren kann. (Erster Testlauf: CPU im SMM, ich überhaupt nicht wie, warum oder wieso. Dann kann das Debuggen ja heiter werden...)
Ich tendiere aber dann doch zu der Version von tyndur.

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #13 am: 07. November 2012, 23:47 »
Das Ende bzw. die Länge der Datei ist nützlich, um rauszukriegen, ob die Datei vollständig geladen ist. Theoretisch solltest du für jedes Offset, das du im ELF-Header findest, prüfen, ob es noch in der Datei liegt. Praktisch mach ich das nur für die Zugriffe auf den ELF-Header.

Sicher, dass dein Code im SMM gelandet ist? Qemu wechselt noch vor dem Laden des OS einmal kurz in den SMM und verzeichnet das im Interrupt-Log. Das könnte also gar nichts mit deinem Code zu tun haben.

Eine Idee noch zu EXCLUDE_FILE: Versuch mal nur *loader.o *init.o (ggf. auch im .bootstrap-Teil des Linkerskripts.)
« Letzte Änderung: 07. November 2012, 23:50 von Jidder »
Dieser Text wird unter jedem Beitrag angezeigt.

 

Einloggen