Mittwoch, 19. Dezember 2012

SDCC: crt0 ersetzen

Wenn man mit SDCC eine Datei kompiliert, nach dem Motto
sdcc -mz80 test.c
so wird die vom Compiler mitgelieferte Datei crt0.rel mit reingelinkt. Diese bestimmt die Link-Reihenfolge, setzt Standardinterruptvektoren, sorgt für die Initialisierung globaler Variablen und so weiter.
Die so erzeugte ausführbare Datei beginnt daher an Adresse 0x0. Der selbstgeschriebene Code, der in der .c-Datei abgelegt ist, beginnt ab 0x100.
Nun möchte man nicht unbedingt immer die Interruptvektoren neu definieren, oder man möchte die Startadresse des eigenen Codes ändern.

Mit
sdcc -mz80 --code-loc 0x2010 test.c
verschiebt man den selbstgeschriebenen Code auf die Adresse 0x2010. Aber man hat immer noch die Interruptvektoren usw. ab Adresse 0x0. Dann lässt er ca. 0x2000 Byte Leerraum und dann kommt der eigene Code.
Man muss die crt0.rel mit einer eigenen ersetzen. Die von SDCC mitgelieferte crt0.s sieht (abgekürzt) so aus:


   .module crt0
 .globl _main
.area _HEADER (ABS)
;; Reset vector
.org 0
jp init
.org 0x08
reti
.org 0x10
reti
.org 0x18
reti
.org 0x20
reti
.org 0x28
reti
.org 0x30
reti
.org 0x38
reti
.org 0x100
init:
;; Stack at the top of memory.
ld sp,#0xffff
 ;; Initialise global variables
 call    gsinit
call _main
jp _exit
Die .org 0x8 bis .org 0x38 Einträge sind die Interruptvektoren. Ich möchte, dass jeglicher Code ab Adresse 0x2000 beginnt. Im Bereich von 0x0 bis 0x1FFF befindet sich nämlich mein Monitorprogramm, welches Programme, die an höheren Speicheradressen abgelegt wurden, ausführen kann.
Das .org 0 bestimmt, dass die crt0.s ab Adresse 0 beginnt. Das gilt es zu ändern.

Ich verwende daher eine modifizierte crt0.s:   

.module crt0
.globl _main
.area _HEADER (ABS)
;; Reset vector
.org 0x2000
jp init
init:
;; Initialise global variables
call gsinit
call _main
ret

;; Ordering of segments for the linker.
.area _HOME
.area _CODE
.area _GSINIT
.area _GSFINAL
.area _GSINIT
gsinit::
.area _GSFINAL
ret
.area _DATA
.area _BSEG
.area _BSS
.area _HEAP
Diese assembliere ich so:
sdasz80 -o crt0_$2000.rel crt0_$2000.s
Meine .c-Datei kompiliere ich nun so:
sdcc.exe -mz80 --code-loc 0x2010 --data-loc 0 --no-std-crt0 crt0_$2000.rel test.c
Die Angabe von --data-loc 0 weist den Linker an, das Datensegment direkt hinter das Codesegment zu platzieren. So weit, so gut.

So sieht die test.c aus:
void main(void) {
}
SDCC erzeugt daraus test.ihx. Dies ist die ausführbare Datei im Intel Hex Format. Die wandle ich mit hex2bin in eine Binärdatei um:
hex2bin.exe test.ihx
hex2bin v1.0.8, Copyright (C) 1998 Jacques Pelletier
checksum extensions Copyright (C) 2004 Rockwell Automation
improved P.G. 2007
Lowest address = 00002000
Highest address = 00002011
Pad Byte = FF
8-bit Checksum = 36
Die niedrigste Adresse ist 0x2000, die höchste 0x2011. In den ersten paar Bytes liegt die crt0.s, ab 0x2010 liegt die test.c. In diesem simpelsten Beispiel belegt test.c nur ein Byte (nämlich das return aus der main()).

Nun füge ich ein großes globales Array hinzu: 
char huge_array[4096];
void main(void) {
}
Und kompiliere neu und erzeuge wieder eine Binärdatei.

hex2bin.exe test.ihx
hex2bin v1.0.8, Copyright (C) 1998 Jacques Pelletier
checksum extensions Copyright (C) 2004 Rockwell Automation
improved P.G. 2007
Lowest address  = 00002000
Highest address = 00003011
Pad Byte        = FF
8-bit Checksum = 46
Die höchste Adresse ist nun 0x3011. Und das, obwohl der Array im RAM liegen sollte und nicht im Codesegment! Dies hat zufolge, dass alle globalen Variablen die mit SDCC kompilierten Dateien unnötig aufblähen.
Bei näherer Betrachtung scheint SDCC die _GSFINAL Sektion hinter das Datensegment zu hängen, obwohl es anders in crt0.s angegeben ist. Das wird zu Problemen führen, wenn man das Programm in einem EEPROM ausführen will.

Diesen SDCC-Fehler kann man umgehen. Man kompiliert test.c, linkt aber erst im zweiten Schritt:
sdcc -mz80 test.c -c
sdcc -mz80 --code-loc 0x2010 --data-loc 0 --no-std-crt0 crt0_$2000.rel test.rel -o test.ihx
hex2bin sagt nun:
hex2bin.exe test.ihx
hex2bin v1.0.8, Copyright (C) 1998 Jacques Pelletier
checksum extensions Copyright (C) 2004 Rockwell Automation
improved P.G. 2007
Lowest address = 00002000
Highest address = 00002011
Pad Byte = FF
8-bit Checksum = 36

Die höchste Adresse liegt nun wie gewünscht bei 0x2011.

2 Kommentare:

  1. Hi,

    das ist GENAU das was ich gesucht habe"!!!!! Danke !

    Gruss,
    Christian

    AntwortenLöschen
  2. Hallo Dirk,

    perfekt! Nach der Info habe ich eine ganze Weile gesucht :)

    Vielen Dank,
    Tobias

    AntwortenLöschen