fritzler-avr.de
Spaceage 2

Software Startup

Hier ist der allgemeine Startup des Spaceage 2 beschrieben vom Makefile über Linkerscript/Speicherlayout zum eigentlichen Assemblerstartup.

Makefile

  1. #Compilerprefix and Flags
  2. PRC = mips-elf-
  3.  
  4. #standard flags for compiling
  5. CFLAGS = -g -O2 -Wall -Wextra -march=mips1
  6. #the div0 check uses alway branch delay slots
  7. CFLAGS += -mno-check-zero-division
  8. #there is nothing but emptyness around our code
  9. CFLAGS += -ffreestanding
  10. CFLAGS += -msoft-float
  11. CFLAGS += -nostartfiles
  12. CFLAGS += -T linkerscript.lds
  13.  
  14. #Binaryname and Sourcefiles
  15. BIN = test
  16. OBJS = start.o div_u.o mult.o multu.o sp2io.o statemachine.o statemachine_helper.o
  17.  
  18. #compile Objects, Link Objects, Objectdump for Slotcheck,
  19. #create Binary, measure Codesize
  20. all:$(OBJS)
  21.    $(PRC)gcc $(CFLAGS) $(OBJS) -o $(BIN) -lm -lc -specs=nosys.specs -Wl,-Map -Wl,$(BIN)_map.map
  22.    $(PRC)objdump -D $(BIN) > $(BIN)_dissassembly.s
  23.    $(PRC)objcopy -S -O binary $(BIN) $(BIN).bin
  24.    $(PRC)size $(BIN)
  25.    
  26. %.o:  %.S
  27.    $(PRC)gcc $(CFLAGS) -c  $< -o $@
  28.    
  29. %.o:  %.s
  30.    $(PRC)gcc $(CFLAGS) -c  $< -o $@
  31.  
  32. %.o:  %.c
  33.    $(PRC)gcc $(CFLAGS) -c  $< -o $@
  34.  
  35. clean:
  36.    rm -f $(OBJS)
  37.    rm -f $(BIN)
  38.    rm -f $(BIN).bin
  39.    rm -f $(BIN)_dissassembly.s
  40.    rm -f $(BIN)_map.map
  41.  

Als erstes der Compilerpräfix, dieser wird extra gespeichert um später z.B. auf einen anderen MIPS Compiler wechseln zu können. Aus diesem Präfix werden die Kommandozeilenaufrufe des Compilers generiert. Also Beispielhaft mips-elf-gcc oder mips-elf-objdump.

Zu den Compilerflags gehören erstmal die Standardmäßigen, wie -02 für die Optimierungsstufe sowie –Wall und –Wextra um möglichst viele Fehlermeldungen zu erhalten, zuletzt in der ersten Zeile noch die Angabe, dass er für den MIPS1 Befehlssatz compilieren soll. Das –g sorgt für Debug Informationen im Compilat, somit liefert der Objectdump lesbarere Ergebnisse. Weiterhin soll die Division nicht gegen div0 getestet werden, da dafür eine Systemexception bereitstehen müsste und zudem fängt das bereits die Punktrechnung Software Emulation ab. Weiterhin ist das Programm freestanding, läuft also nicht auf einem Betriebsystem. Eine FPU ist nicht vorhanden, also muss soft-float genutzt werden. Weiterhin soll kein C Compiler startup Code mit eingebunden werden, das wird manuell in der start.s erledigt. Schlussendlich wird noch das Linkerscript bekannt gegeben.

Hinter OBJS müssen alle Quelldateien angegeben werden, egal ob mit der Endung .s/.S/.c es muss mit .o hingeschrieben werden. Der Name nach BIN gibt an wie die Ausgabedateien heißen sollen.

Der Rest des Makefiles besteht aus den make Regeln. Wird "make all" eingetippt so wird die Regel all: ausgeführt. Dort werden zuerst alle Quelldaten (OBJS) compiliert und als Objectfiles abgelegt (.o) dabei sind die Regeln %.o behilflich, welche weitere Flags für den Vorgang angeben können. Danach werden alle Objectfiles gelinkt in der Zeile mit gcc. Mit der Angabe weiterer zu linkenden Objekten wie der lib math und der libc (lm und lc). Das nosys.specs dient der Angabe, dass kein Betriebssystem vorhanden ist und somit auch keine Systemcalls vorhanden. So würde z.B. ein malloc() das Betriebsystem nach Speicher fragen, hier beim Spaceage2 müssen dies eingebettete libs übernehmen, die mit nosys.specs eingebunden werden. Weiterhin wird in dieser Zeile auch das map File erzeugt um zu überprüfen ob auch alles wie im Linkerscript angegeben gelinkt wurde. Danach wird der Objectdump erstellt um überprüfen zu können was der Compiler erzeugt hat. Danach wird durch Objcopy das Binary erzeugt, welches auf den Spaceage2 hochgeladen werden kann, um die Größe des Programms zu wissen wird noch size aufgerufen.

Speicherlayout

Bevor das Linkerscript erklärt werden kann, muss erst einmal definiert werden wie das Speicherlayout vom Spaceage2 aussieht. Dieses gilt nur für Programme die direkt also ohne Betriebsystem auf dem Spaceage2 laufen. Der Spaceage2 hat 2 Speicherbereiche:
- 1MB Batteriegepufferter SRAM von 0x00000000 bis 0x000FFFFC, als ROM bezeichnet
- 4MB SRAM von 0x00400000 bis 0x007FFFFC, als RAM bezeichnet

Am Anfang des ROM Bereichs befindet sich hardgecodete Vektoren des Prozessorkerns für Exception, Interrupts und dem Reset.
Diese sind wie folgt definiert:

ExceptionVektor
Reset0x00000000
Divison Unsigned Emulation0x00000080
Division Emulation0x000000A0
Multiplikation Unsigned Emulation0x000000C0
Multiplikation Emulation0x000000E0
Interrupt0x00000100
Integer Overflow0x00000120
Undefined Instruction0x00000140
Syscall0x00000160
Break0x00000180

Weiterhin erzeugt der Compiler für verschiedene Speicherbereiche Code und Daten, den sogenannten Sections.
.text
Enthält den eigentlichen Programmcode.
.rodata
Die Section für read only Daten wie Konstanten und Strings.
.data
Hier befinden sich mit ungleich Null vorinitialisierte globale oder static Variablen.
.bss
Hier befinden sich auch globale oder static Variablen wieder, aber diejenigen die mit Null initialisiert sind.
.common
Schlussendlich die globalen oder static Variablen die deklariert, aber nicht initialisiert sind. Diese werden beim Linkerscript zu .bss gehören und daher auch mit Null initialisiert sein zur Laufzeit.

Weiterhin besitzt der MIPS noch small data und small bss auf diese mit Hilfe des Global Pointer Register zugegriffen wird. Hier finden Variablen mit jeweils unter 8Byte Größe ihren Platz für den schnelleren Zugriff. Diese Sections heißen .sdata und .bss Weiterhin wird noch ein Heap benötigt für dynamische Daten (malloc) sowie ein Stack zum Register sichern bei dem Aufruf von Unterfunktionen.

Die Exceptionvektoren, die Sections sowie Heap und Stack müssen nun in den beiden Speicherbereichen untergebracht werden.
Damit ergibt sich folgendes Speicherlayout:

Im ROM Bereich werden alle Read Only Sections vorgehalten, hier befinden sich aber auch .data / .sdata Diese Sections werden allerdings für den RAM Bereich gelinkt, aber im ROM Bereich gespeichert und beim Startup kopiert. Dies hat den Hintergrund, dass in .data zwar bereits die Initialisierungen gespeichert sind, aber zur Programmlaufzeit ändern diese sich mit Sicherheit. Bei einem Neustart / Reset würde das Programm also nicht mehr die vorgesehenen Startbedingungen vorfinden. Nach dem kopieren von dem ROM in den RAM Bereich nach dem Startup findet das Programm immer seine definierten Startbedingungen vor.

Linkerscript

Damit der Linker des Compilers auch das gewünschte Binary für das Speicherlayout erzeugt, muss ihm das per Linkerscript mitgeteilt werden. Dieses Script besteht aus 2 Hauptkomponenten, ENTRY und SECTIONS. Die ENTRY verweist nur darauf wo der Startpunkt des Programms ist, in unserem Falle 0x00000000, also der Reset Vektor und dort ist dann auch das ENTRY Symbol _start in der start.s deklariert. Der SECTIONS Teil legt die Compilersections fest. Wie die Vektoren und die Datenbereiche.

Die Exceptionvektoren müssen an bestimmten Speicheradressen liegen, daher muss jeder einzelner Vektor an seine Adresse gelegt werden und bekommt seine eigene .text Untersection wie z.B. .text.reset. Diese kann in der start.s angegeben werden und so landet der Codeblock an dieser Adresse.

  1. SECTIONS
  2. {
  3.    /*resetvector*/
  4.    . = 0x00000000;
  5.    reset : {
  6.       *(.text.reset)
  7.    }
  8.    
  9.    /*div unsigned*/
  10.    . = 0x00000080;
  11.    divu : {
  12.       *(.text.divu)
  13.    }
  14.    
  15.    /*div signed*/
  16.    . = 0x000000A0;
  17.    div : {
  18.       *(.text.div)
  19.    }
  20.    
  21.    /*mul unsigned*/
  22.    . = 0x000000C0;
  23.    multu : {
  24.       *(.text.multu)
  25.    }
  26.    
  27.    /*mult signed*/
  28.    . = 0x000000E0;
  29.    mult : {
  30.       *(.text.mult)
  31.    }
  32.    
  33.    /*ext interrupt*/
  34.    . = 0x00000100;
  35.    eirq : {
  36.       *(.text.eirq)
  37.    }
  38.    
  39.    /*overflow*/
  40.    . = 0x00000120;
  41.    ovw : {
  42.       *(.text.ovw)
  43.    }
  44.    
  45.    /*undefined instruction*/
  46.    . = 0x00000140;
  47.    undef : {
  48.       *(.text.undef)
  49.    }
  50.    
  51.    /*syscall exception*/
  52.    . = 0x00000160;
  53.    sysc : {
  54.       *(.text.sysc)
  55.    }
  56.    
  57.    /*break exception*/
  58.    . = 0x00000180;
  59.    break : {
  60.       *(.text.break)
  61.    }
  62. }
  63.  

Ab der Adresse 0x00000200 liegt der Programmbereich. Dabei sind .text und .rodata noch verständlich und offensichtlich angelegt. Die Sections .data und .sdata sind jedoch nur als Labels zu sehen aber einer Adresse hinter .rodata. Wobei .sdata hinter .data liegt (erkennbar an dem + _data_size). Ab hier beginnt die Linkerscript Magic. Durch einen , in dem Unterkapitel RAM beriech erklärten, Linkerscriptbefehl werden zwar diese Sections im RAM Bereich gelinkt, aber im Binary an die Labels _rom_data_start und _rom_sdata_start gespeichert.

  1. SECTIONS
  2. {
  3.    /*main*/
  4.    . = 0x00000200;
  5.    .text : {
  6.       *(.text.startup)
  7.       statemachine.o (.text)
  8.       *(.text)
  9.       *(.text.phandling)
  10.    }
  11.    
  12.    . = ALIGN(4);
  13.    .rodata : {
  14.       *(.rodata)
  15.       *(.rodata*)
  16.       *(.MIPS.abiflags)
  17.       *(.eh_frame)
  18.    }
  19.    
  20.    /*Hier liegen data und sdata zum Kopieren*/
  21.    _rom_data_start = .;
  22.    _rom_sdata_start = . + _data_size;
  23. }
  24.  

Zuerst wird die Adresse des Linkers in diesem Bereich auf 0x00400000 gesetzt, denn ab jetzt sollen alle Sections ab dort anfangen. Im RAM Bereich werden die Sections von .data und .sdata angelegt und mit dem AT(Label) wird dem Linker mitgeteilt, dass diese Sections zwar für die Adressen des RAM gelinkt werden sollen. Also das Programm wird auf diese Adressen zugreifen, aber der Inhalt liegt erstmal im ROM Bereich des Binarys. Weiterhin werden Labels erstellt, welche Start, Ende und Größe der Sections markieren, damit die start.s darauf zugreifen kann.

Nach dem .sdata wird der Global Pointer festegelegt, dieser befindet sich zwischen .sdata und .sbss, damit der signed Offset des Pointers (MIPS Ladebefehl) maximal ausgenutzt werden kann. Die .sbss und .bss Section muss beim Startup mit Null überschrieben werden, daher auch hier wieder Labels für Start, Ende und Größe damit die start.s darauf zugreifen kann. Schlussendlich wird noch das Label „end“ gesetzt, ab hier beginnt dann der Heap.

  1. SECTIONS
  2. {
  3.    /*Hier liegen data und sdata zum Kopieren*/
  4.    _rom_data_start = .;
  5.    _rom_sdata_start = . + _data_size;
  6.    
  7.    /*Ab hier liegt alles im RAM
  8.    Also Initialisierte Daten werden kopiert damit der Initwert erhalten bleibt
  9.    bss wird genullt zum startup*/
  10.    . = 0x00400000;
  11.    
  12.    .data : AT( _rom_data_start ) {
  13.       _data_start = .;
  14.       *(.data)
  15.       *(.data*)
  16.    }
  17.    _data_end = .;
  18.    _data_size = _data_end - _data_start;
  19.    
  20.    . = ALIGN(4);
  21.    .bss : {
  22.       _bss_start = .;
  23.       *(.bss)
  24.       *(common)
  25.       *(.common)
  26.    }
  27.    _bss_end = .;
  28.    _bss_size = _bss_end - _bss_start;
  29.    
  30.    . = ALIGN(4)
  31.    .sdata : AT( _rom_sdata_start ) {
  32.       _sdata_start = .;
  33.       *(.sdata)
  34.       *(.sdata*)
  35.    }
  36.    _sdata_end = .;
  37.    _sdata_size = _sdata_end - _sdata_start;
  38.    
  39.    /*.sbss und .sdata benutzt das Global Pointer Register
  40.    daher passendes Symbol hier in die Mitte setzen fuer start.s*/
  41.    . = ALIGN(4);
  42.    _gp = .;
  43.    
  44.    . = ALIGN(4);
  45.    .sbss : {
  46.       _sbss_start = .;
  47.       *(.sbss)
  48.       *(scommon)
  49.       *(.scommon)
  50.    }
  51.    _sbss_end = .;
  52.    _sbss_size = _sbss_end - _sbss_start;
  53.    
  54.    /*Nach dem Programm und seinen statischen Daten
  55.    kommt der Heap mit den dynamischen Daten*/
  56.    . = ALIGN(4);
  57.    end = .;
  58. }
  59.  

Startup

In der start.s befindet sich der Startup Bereich ab dem Reset Vektor und später die Verteilung der Exception Vektoren.

In dem Startup werden zuerst Stack und Global Pointer gesetzt. Danach werden die .sbss und .bss Section mit Nullen überschrieben. Anschließend werden mit Hilfe der Labels aus dem Linkerscript die .data und .sdata aus dem ROM in den RAM kopiert. Dafür werden die Labels in die Argument Register geladen und dann eine Funktion der libc aufgerufen. Schlussendlich wird in das Hauptprogramm gesprungen.

Bei den Exceptionvektoren der Punktrechnungsemulation wird dann die entsprechnde Emulationsroutine angesprungen. Alle anderen Exceptionvektoren soringen bisher nur ins Hauptprogramm zurück, können später dann auch zu den passenden Handlern springen.

  1. .section .text.reset
  2. .extern main
  3. .globl _start
  4. _start:
  5.    la $sp, 0x800000 /*Stack an 8MB -> Ende von den 4MB RAM*/
  6.    la $gp, _gp
  7.    
  8.    j meminit
  9.    
  10. .section .text
  11. meminit:
  12.    /* data vom ROM in den RAM kopieren*/
  13.    la $a0, _data_start
  14.    la $a1, _rom_data_start
  15.    la $a2, _data_size
  16.    jal memcpy
  17.    
  18.    /* sdata vom ROM in den RAM kopieren*/
  19.    la $a0, _sdata_start
  20.    la $a1, _rom_sdata_start
  21.    la $a2, _sdata_size
  22.    jal memcpy
  23.    
  24.    /*.sbss Section mit 0 initilisieren*/
  25.    la $a0, _sbss_start
  26.    li $a1, 0
  27.    la $a2, _sbss_size
  28.    jal memset
  29.    
  30.    /*.bss Section mit 0 initilisieren*/
  31.    la $a0, _bss_start
  32.    li $a1, 0
  33.    la $a2, _bss_size
  34.    jal memset
  35.    
  36.    /*endlich gehts zum Hauptprogramm*/
  37.    j main
  38.    
  39.    
  40. .section .text.div
  41. div_vec:
  42.    j divsim_sign
  43.    
  44. .section .text.divu
  45. divu_vec:
  46.    j divsim_unsign
  47.    
  48. .section .text.mult
  49. mult_vec:
  50.    j mulsim_signed
  51.    
  52. .section .text.multu
  53. multu_vec:
  54.    j mulsim_unsigned
  55.  
  56. .section .text.eirq
  57. eirq_veq:
  58.    rfe
  59.    
  60. .section .text.ovw
  61. ovw_veq:
  62.    rfe
  63.    
  64. .section .text.undef
  65. undef_vec:
  66.    rfe
  67.    
  68. .section .text.sysc
  69. sysc_veq:
  70.    rfe
  71.    
  72. .section .text.break
  73. break_veq:
  74.    rfe
  75.  

Kontakt - Haftungsausschluss - Impressum