Hallo,
wie setzt der GCC positionsunabhängigen Code auf verschiedenen
Plattformen um? Beim AVR erscheint das relativ leicht, wenn sich die
Sprunglänge in Grenzen hält, denn es gibt ja eine rjmp-, bzw.
rcall-Anweisung.
Wie verhält es sich aber zum Beispiel beim MSP430? Wie ich sehe, ist
sein R0-Register gleichzeitig der Program Counter. Außerdem hat er einen
Adressierungsmodus der sich auf dieses R0-Register bezieht (AS=01).
Heißt das, es gibt eine Instruktion, die als Operanden ein Offset zum PC
akzeptiert und dann dort hin springt? Dann wäre das ja im Prinzip
ähnlich zu rjmp beim AVR und die Erzeugung von positionsunabhängigen
Code für den GCC ganz einfach.
Ich verstehe dann aber nicht, warum zum Beispiel dann in [1] bei der
call-Anweisung gesagt wird, dass es keinen einfachen Weg für pc-relative
Sprünge gibt.
Wie sieht es bei anderen Mikrocontrollerarchitekturen aus? Gibt es in
jedem Fall einen Befehl, den man irgendwie für pc-relative Sprünge
gebrauchen kann?
Mir geht es darum zu verstehen, wie einfach die Erzeugung von
positionsunabhängigem Code ist und wieviel Overhead dadurch entsteht.
Vielen Dank
Richard
[1] http://mspgcc.sourceforge.net/manual/x223.html
Der genaue Terminus ist "relokatibel" bzw. "verschieblich". Auf Deutsch
klingts dann ein bischen holprig. Das Fremdwort ist mir lieber.
Aber gut.
Ob ein Code relokatibel ist oder nicht, hängt davon ab, ob es
Anweisungen gibt, die den Programmfluss so beeinflussen, das der Program
Counter "relativ zum momentanen Stand" verändert wird. Das Gegenteil
wäre, das der PC mit absoluten Adressen geladen wird. (Das die mit einer
MMU dann wieder irgendwie relativ sind, ist eine andere Sache).
>Wie sieht es bei anderen Mikrocontrollerarchitekturen aus? Gibt es in>jedem Fall einen Befehl, den man irgendwie für pc-relative Sprünge>gebrauchen kann?
Da gibt es keine Regel. Manche haben es, manche nicht. Einen gewissen
Vorzug hat das, wenn vornehmlich Tabellen abgearbeitet werden. Z.B.
Basic-Token vergleiche oder Netzwerkadressen. Da kann man sicher
Beispiele finden.
>Mir geht es darum zu verstehen, wie einfach die Erzeugung von>positionsunabhängigem Code ist und wieviel Overhead dadurch entsteht.
Da wäre es gut zu wissen, zu welchem Zweck Du das verstehen willst.
Für einen Compilerbauer ist, der Unterschied: Oft gibt es Begrenzungen
in der relativen Sprungweite. Das muss man berücksichtigen.
Als Benutzer hat man oft nicht die Wahl, ob ein Compiler
ausschliesslich, bevorzugt, garnicht oder nur an Feiertagen keine,
immer, wenn es geht usw. relative Sprünge benutzt.
Was Deinen Link betrifft, finde ich die Beschreibung widersprüchlich.
Ich kenne mich mit MSP aber auch nicht aus.
Da steht was von "PC-relative" Adressierungsart: "the PC-relative
addressing mode fetches a word and uses it as an absolute address. "
Das ist eben keine PC-relative Adressierung. Eben deswegen kann damit
nicht relative Sprünge ausführen.
>Außerdem hat er einen>Adressierungsmodus der sich auf dieses R0-Register bezieht (AS=01)
Der Call Befehl um den es Dir geht, gehört in die erste Zeile der ersten
Tabelle. Daher ist das AS-Flag hier garnicht gefragt.
rw schrieb:
> Hallo,>> wie setzt der GCC positionsunabhängigen Code auf verschiedenen> Plattformen um?
Das ist plattformabhängig und im jeweiligen Backend implementiert --
oder auch nicht. Der Schalter zur Aktivierung ist -fpic bzw. -fPIC-
> Beim AVR erscheint das relativ leicht, wenn sich die> Sprunglänge in Grenzen hält, denn es gibt ja eine rjmp-, bzw.> rcall-Anweisung.
avr-gcc unterstützt weder positionsunabhängigen Code noch
positionsunabhängige Daten. Beispiel:
1
voidbar(void);
2
void*foo(void)
3
{
4
returnbar;
5
}
Der Returnwert von foo soll für einen indirekten Funktionsaufruf
weiterverwendet werden. In foo wird aber die absolute Adresse von bar
genommen, so daß der Code nicht relokatibel ist.
> Mir geht es darum zu verstehen, wie einfach die Erzeugung von> positionsunabhängigem Code ist und wieviel Overhead dadurch entsteht.
Am einfachsten schaust du dir einen foo-gcc an, in dessen Toolchain es
auch einen dynamischen Linker gibt. Das sind typischerweise solche, die
für Controller auf welchen auch Linux läuft Code erzeugen (i386, ARM,
AVR32, IA64, ...).
Die Adressierung ist aufwändiger und erfolgt zB über die GOT (global
offset table)
Such einfach in den GCC-Quellen nach folgenden Makros:
http://gcc.gnu.org/onlinedocs/gccint/PIC.html#PIC
bzw. nach flag_pic unterhalb von ./gcc/config
Dann hast du gleich die Targets die PIC implementieren und den Code dazu
:-)
Johann
Hallo,
danke für die Informationen und für den Link. Ich bin allerdings ein
wenig ob der Begriffsverwendungen in den vorangegangenen Beiträgen
verwirrt.
@wechselbalg:r
Ernn ich mich nicht irre, sind doch alle Objektdateien relozierbar wenn
sie frisch kompiliert wurden. Erst der Linker löst die in der
Relokationstabellen (bei ELF) angegebenen Adressen in der .text-Sektion
der Objektdateien auf. So habe ich zumindest die Dokumentation von ELF
verstanden. Mir geht es hier wirklich um positionsunabhängigen Code,
also Code, der so gebunden wird, dass er von beliebiger Adresse im ROM
ausgeführt werden kann. Dieser braucht nicht mehr reloziert zu werden.
@Johann:
Danke für den Link. Ich werde in der gcc-Doku wohl mehrmals lesen
müssen, um die Fülle von Informationen zu erfassen. Dynamisches Linken
für 8Bit-Mikrocontroller wäre wohl etwas hoch gestochen, auch wenn es
anscheinend verrückte Menschen gibt, die so etwas mehr oder weniger
realisiert zu haben scheinen (Adam Dunkels bei Contiki).
Wenn ich das Konzept der Global Offset Tables richtig verstanden habe,
finden sie nur Anwendung in Shared Objects, also Code-Teilen die
gleichzeitig von mehreren "Prozessen" benutzt werden. Während des
Programmflusses ist lediglich der Abstand vom Modulbeginn zur gerade
verwendeten GOT (für jeden Ausführungskontext existiert eine GOT) in
irgend einem Register hinterlegt.
Für Funktionsaufrufe wird dann im Weiteren eine Procedure Lookup Table
verwendet (PLT), die auch die Adressauflösung zur Laufzeit vornimmt.
Habe ich das so richtig verstanden?
Wenn ja, scheint es mir, als wären hardwareunterstütze PC-relative
Sprünge gar nicht nötig, weil die Sprungadresse immer zur Laufzeit
berechnet werden kann, nämlich folgendermaßen:
Das PIC-Modul kennt zur Laufzeit seine eigene Startadresse im
Programmspeicher und den Abstand zur gerade gültigen GOT. Außerdem wurde
durch den Linker festgelegt, unter welchem Index in der PLT die
gewünschte Funktion eingetragen ist. Damit ließe sich die absolute
Adresse dieses Eintrags auch zur Laufzeit berechnen, dort hin springen
und dann von dort aus weiterspringen (oder die dort gespeicherte Adresse
laden und dann springen). Das würde jetzt aber bedeuten, dass auch
modulinterne Sprünge immer mindestens eine zusätzliche Addition
erfordern, bevor gesprungen werden kann. Das wäre also ein erheblicher
Aufwand, aber würde erklären, warum im Zusammenhang mit ELF Shared
Objects immer von einem signifikanten Laufzeitverlust die Rede ist.
Wie ich mir das jetzt ausgemalt habe, steigt der Aufwand also mindestens
um eine Addition und eventuell ein paar Load&Store-Instruktionen. Das
klingt mir irgendwie zu einfach. Möglicherweise muss ich noch einmal ELF
Shared Objects studieren.
Ich würde mich freuen, wenn jemand einschätzen könnte, wie nah der eben
geschriebene Erguss der Realität steht.
Herzlichen Dank
Richard
Hallo rw,
rw schrieb:
> Wie verhält es sich aber zum Beispiel beim MSP430? Wie ich sehe, ist> sein R0-Register gleichzeitig der Program Counter. Außerdem hat er einen> Adressierungsmodus der sich auf dieses R0-Register bezieht (AS=01).> Heißt das, es gibt eine Instruktion, die als Operanden ein Offset zum PC> akzeptiert und dann dort hin springt?
Zumindest das User Guide nennt solch einen Adressierungsmodus:
"Symbolic mode: ADDR (PC + X) points to the operand. X is stored in the
next word. Indexed mode X(PC) is used." Beim Symbolic Mode handelt es
sich also um einen Spezialfall des Indexed Mode. Weiter aus der
Beschreibung zu bedingten Sprüngen: "Conditional jumps support program
branching relative to the PC and do not affect the status bits. The
possible jump range is from −511 to +512 words relative to the PC value
at the jump instruction. The 10-bit program-counter offset is treated as
a signed 10-bit value that is doubled and added to the program counter".
> Dann wäre das ja im Prinzip ähnlich zu rjmp beim AVR und die Erzeugung> von positionsunabhängigen Code für den GCC ganz einfach.
Dazu kann ich nichts sagen, da ich weder die Architektur AVR noch die
Internas des GCCs kenne.
> Ich verstehe dann aber nicht, warum zum Beispiel dann in [1] bei der> call-Anweisung gesagt wird, dass es keinen einfachen Weg für pc-relative> Sprünge gibt.
Steht doch dort: "Call: [...]the PC-relative addressing mode fetches a
word and uses it as an absolute address". Was der 430er also beim
Springen kann, kann er in Verbindung mit CALL einfach nicht.
Call funktioniert beim MSP430 nämlich so:
CALL dst: SP − 2 → SP, PC+2 → @SP, dst → PC
JMP hingegen so: PC + 2 × offset −> PC
> Wie sieht es bei anderen Mikrocontrollerarchitekturen aus?
Von Architektur zu Architektur verschieden.
> Gibt es in jedem Fall einen Befehl, den man irgendwie für pc-relative> Sprünge gebrauchen kann?
Nein.
hth, Iwan