Hi, ich such mir in der GCC Doku grad n Wolf, ich versuche herauszufinden, woher die Segmentnamen ".text", ".bss" und ".data" eigentlich kommen? Sind das fest eingebaute Namen und der Linker weist Funktion ohne 'attribute' automatisch .text zu (und analog dazu Variablen .data bzw. .bss), oder ist das einfach eine Konvention, die einfach in allen Linker-Scripts Einzug gehalten hat? Ralf
Ralf schrieb: > ich such mir in der GCC Doku grad n Wolf, ich versuche herauszufinden, > woher die Segmentnamen ".text", ".bss" und ".data" eigentlich kommen? UNIX-Historie. "bss" steht der Überlieferung nach für "block starting symbol", was wiederum auf irgendeiner alten Maschine die Mnemonik eines entsprechenden Befehls war. > Sind das fest eingebaute Namen und der Linker weist Funktion ohne > 'attribute' automatisch .text zu (und analog dazu Variablen .data bzw. > .bss), oder ist das einfach eine Konvention, die einfach in allen > Linker-Scripts Einzug gehalten hat? Sowohl als auch. Es dürfte der Assembler sein, der Default-Sections einträgt, für ausführbaren Code eben .text, für Datenreservierungen .data. .bss bzw. COMMON muss explizit belegt werden.
@Jörg Wunsch: > UNIX-Historie. "bss" steht der Überlieferung nach für "block > starting symbol", was wiederum auf irgendeiner alten Maschine die > Mnemonik eines entsprechenden Befehls war. Ah, okay. Also "so ist's halt gewesen, so ist's auch geblieben". :) > Sowohl als auch. Es dürfte der Assembler sein, der Default-Sections > einträgt, für ausführbaren Code eben .text, für Datenreservierungen > .data. .bss bzw. COMMON muss explizit belegt werden. Hmmmm... Okay, ich wollt es mir so einrichten, dass .text mit "Code" angegeben wird. Aber wenn der Assembler das defaultmäßig vergibt, dann lass ich's so. Wenn ich das richtig verstanden habe, dann ist .text + .data die Größe, die ein Firmware-Image belegt, ist das korrekt? .text ist reiner Code, .data sind die initialisierten Variablen. .bss ist informell wieviel nicht initialisierter RAM verbraucht wurde oder der komplette RAM? Ralf
Jörg Wunsch schrieb: > UNIX-Historie. "bss" steht der Überlieferung nach für "block > starting symbol", was wiederum auf irgendeiner alten Maschine die > Mnemonik eines entsprechenden Befehls war. Interessant. Ich hab damals noch gelernt, es hieße "Block Storage Segment": http://www.wachtler.de/ck/9_Speichermodell_Prozesses.html
Ralf schrieb: > Wenn ich das richtig verstanden habe, dann ist .text + .data die Größe, > die ein Firmware-Image belegt, ist das korrekt? Ja, bzw. der ladbare Teil einer ELF-Datei, wenn man sie direkt lädt (bspw. in einem unixartigen System). > .text ist reiner Code, > .data sind die initialisierten Variablen. Yep. > .bss ist informell wieviel > nicht initialisierter RAM verbraucht wurde oder der komplette RAM? .bss sind die nicht initialisierten Daten, die durch den Startup-Code ausgenullt werden. .bss + .data ist der gesamte statische RAM- Verbrauch. Zur Laufzeit kommen dann noch Stack und ggf. Heap hinzu. Manfred Freise schrieb: > Interessant. Ich hab damals noch gelernt, es hieße "Block Storage > Segment": Scheint ein gängiger Irrtum zu sein. Meine Erinnerung war auch nicht völlig korrekt: "block started by symbol", siehe Wikipedia: http://en.wikipedia.org/wiki/.bss
Um die Verwirrung noch zu vergrößern, unterscheider der Linker zwischen Input- und Output-Sections. Zu .text gehört daher auch .progmem.data, in die zB Lookup-Tabellen per progmem gelegt werden. Und Sprungtabellen aus switch/case werdenn auch dort abgelegt. Weiers gehören zur text-Sections: .vectors, .lowtext, .jumptables, .trampolines, .ctors, .dtors, ... Zu .bss wiederum gehört auch .noinit, das vom Startupcode nicht gelöscht wird.
@Jörg Wunsch: >> Wenn ich das richtig verstanden habe, dann ist .text + .data die Größe, >> die ein Firmware-Image belegt, ist das korrekt? > Ja, bzw. der ladbare Teil einer ELF-Datei, wenn man sie direkt > lädt (bspw. in einem unixartigen System). Okay... Verwirrung :) Was heisst in dem Zusammenhang "ladbarer Teil"? Das ist doch das gesamte Programm, oder? Die .bss entsteht zur Laufzeit, ohne Stack oder nicht-statische lokale Variablen, dachte ich. > Zur Laufzeit kommen dann noch Stack und ggf. Heap hinzu. Heap ist soweit ich das verstanden habe (siehe auch oben) der freie RAM-Bereich, in dem lokale Variablen erzeugt werden, korrekt? @Johann L.: > Um die Verwirrung noch zu vergrößern, unterscheider der Linker zwischen > Input- und Output-Sections. > ... Ja, das hatte ich in der GCC Doku schon gelesen (aber noch nicht ganz verinnerlicht, ich arbeite dran grins ). Ist für einen Newbie etwas viel. Hab bisher nur C & ASM für 8051er programmiert, da ist das ein klitze bitze einfacher ;) Gibt's eigentlich schon von jemandem, der da richtig tief drin steckt einen Artikel dazu? ;) Wie lernt man das denn alles? Immer dann wenn man's braucht, oder wie? Ich find die Doku in der Hinsicht nicht geeignet. Ralf
Ralf schrieb: > Was heisst in dem Zusammenhang "ladbarer Teil"? Alles das, was in der ELF-Datei das Flag PT_LOAD gesetzt hat. ;-) > Die .bss entsteht zur Laufzeit, ohne Stack oder > nicht-statische lokale Variablen, dachte ich. bss ist statisch bereits vorbelegt, während der Initialisierung wird lediglich das Ausnullen vorgenommen. Im Prinzip könnte man bss auch wie data behandeln, dann müsste man allerdings die Nullen mit in der Datei (bzw. dann im Flash) speichern. Früher (vor Version 4.x) hat GCC auch zwischen diesen beiden Varianten unterschieden (auf global scope natürlich bezogen):
1 | int foo; /* .bss, wird implizit mit 0 initialisiert */ |
2 | int foo = 0; /* .data, 0 wurde explizit als Initialwert gespeichert */ |
Aktuell optimiert er das zweite jedoch auch als bss. >> Zur Laufzeit kommen dann noch Stack und ggf. Heap hinzu. > Heap ist soweit ich das verstanden habe (siehe auch oben) der freie > RAM-Bereich, in dem lokale Variablen erzeugt werden, korrekt? Nein, lokale Variablen werden auf dem Stack angelegt. Der Heap ist für die dynamische Speicherverwaltung da (malloc() bei C, new bei C++). > Wie lernt man das denn alles? Du lernst es doch gerade. :-)
Jörg Wunsch schrieb: > Früher (vor Version 4.x) hat GCC auch zwischen diesen beiden > Varianten unterschieden (auf global scope natürlich bezogen): > > int foo; /* .bss, wird implizit mit 0 initialisiert */ > int foo = 0; /* .data, 0 wurde explizit als Initialwert gespeichert */ Macht das einen Unterschied? Dafür ist doch -f[no-]zero-initilized-in-bss (ab 3.3.1).
@Jörg Wunsch: >> Was heisst in dem Zusammenhang "ladbarer Teil"? > Alles das, was in der ELF-Datei das Flag PT_LOAD gesetzt hat. ;-) grins Okay :) > bss ist statisch bereits vorbelegt, während der Initialisierung > wird lediglich das Ausnullen vorgenommen. Damit die nicht initialisierten Daten C-konform den Wert 0 haben. > Im Prinzip könnte man bss auch wie data behandeln, dann müsste man > allerdings die Nullen mit in der Datei (bzw. dann im Flash) speichern. Klar, eine Schleife, die den kompletten RAM erstmal mit Null füllt ist kürzer ;) > Nein, lokale Variablen werden auf dem Stack angelegt. Der Heap ist > für die dynamische Speicherverwaltung da (malloc() bei C, new bei > C++). Dann ist das eine der Denkfallen, in die ich getappt bin. Beim 8051 ist der Stack für Rücksprungadressen und Sicherung von Registern während Funktionsaufrufen bzw. Interrupt-Service-Funktionen. Die Bezeichnung Stack hat hier wohl eine ganz andere Bedeutung. Dann muss ich mir das nämlich auch noch genauer angucken, denn ich versteh gerade nicht, wo denn dann die Rücksprungadressen etc. landen und wie dann Parameter gehandhabt werden, die nicht in die Register R0-R12 passen. Zu dem Kapitel muss ich dann nämlich auch noch schauen, wie lokale Variablen gehandhabt werden verglichen mit globalen etc. >> Wie lernt man das denn alles? > Du lernst es doch gerade. :-) Ja, das stimmt, aber a) kann ich dich ja nicht die ganze Zeit alles fragen, was ich grad wissen will, damit ich ne gescheite Basis hab, und b) wohnst du wahrscheinlich nicht um die Ecke, so dass ich dir wenigstens n Bier ausgeben könnte ;) Mir bleibt momentan einzig mich zwischendurch mal zu bedanken -> Danke! Ralf
Johann L. schrieb: >> int foo; /* .bss, wird implizit mit 0 initialisiert */ >> int foo = 0; /* .data, 0 wurde explizit als Initialwert gespeichert */ > > Macht das einen Unterschied? Ja, im Flash-Verbrauch bzw. der Größe der ELF-Datei schon. > Dafür ist doch > -f[no-]zero-initilized-in-bss (ab 3.3.1). OK, dann ist es schon ab GCC 3.3.1 so, dass er explizit mit 0 initialisierte Variablen freiwillig in den BSS legt. Davor (bzw. mit -fno-zero-initialized-in-bss) ist es einfach eine Pessimierung, wenn ein Programmierer denkt, er müsste dem C-Standard nicht übern Weg trauen, und globale oder statische Variablen explizit mit 0 initialisieren, "für den Fall der Fälle". Ralf schrieb: >> bss ist statisch bereits vorbelegt, während der Initialisierung >> wird lediglich das Ausnullen vorgenommen. > Damit die nicht initialisierten Daten C-konform den Wert 0 haben. Es gibt noch weitere Gründe, warum man diese Bereiche ausnullt: Sicherheit beispielsweise. Es sollte ja nicht so sein, dass du die Passwörter eines anderen Nutzers plötzlich in deinem nicht initiali- sierten Speicher vorfindest … insofern muss BSS bei aktuellen Betriebssystemen dann gar nicht unbedingt explizit genullt werden, wenn man sich drauf verlassen kann, dass das OS es sowieso macht. > Beim 8051 ist > der Stack für Rücksprungadressen und Sicherung von Registern während > Funktionsaufrufen bzw. Interrupt-Service-Funktionen. Stichwort Parameter- und Returnstack. IAR zum Beispiel trennt auch beide. Vorteil: man kann die Returnadressen besser nachvollziehen. Nachteil: man kann nicht beide Stacks "automatisch" so anlegen, dass sie stets nur so viel verbrauchen, wie tatsächlich nötig ist. Einer von beiden muss statisch vorgegeben werden (typisch der Returnstack), und wehe, seine Tiefe wird zur Laufzeit überschritten. Unix-Systeme haben daher typisch beide in einen vereinigt. Das macht das Modell etwas komplizierter, aber dafür wird nur so viel Platz benötigt, wie tatsächlich erforderlich ist. Je nach Architektur gibt es einen sogenannten frame pointer, über den man die Aufruf- hierarchie dann verfolgen kann.
Hallo, sehr interessant das Thema hier :-) Deswegen reicht es wahrscheinlich auch, wenn nur die Sektionen .data und .text ins Hex-File übernommen werden, richtig? (Bei objcopy)
Guten Morgen zusammen, wie ist das dann eigentlich, wenn ich nun beispielsweise einen RAM-Bereich mit einem anderen Namen anlege, sagen wir mal einfach .RAM2. RAM2 liegt innerhalb vom normalen RAM, also .data. ist RAM2 dann automatisch auch .data? Ralf
Das hängt davon am ob .RAM2 eine Input-Section ist oder eine Output-Section, der ein eigener Spreicherbereich und Input-Sections zugeordnet werden per Linker-Script. Wird die Input-Section nicht im Linker-Script behandelt, dann handelt es sich um eine Orphan-Section, die der Linker anhand ihrer Flags zuordnet bzw. zuzuordnen versucht. Legt man Daten ich eine nicht-Standard Input-Section, so werden diese u.U. nicht initialisier vom Startup-Code, denn avr-gcc berücksichtigt aus Effizienzgründen lediglich .bss* und .data*, siehe PR18145.
Wie würde ich denn nun eine eigene Section anlegen und Compiler mitteilen, er solle mir genau da mein Zeugs reinlegen? Sagen wir, ich würde gerne 3 8-Bit Werte an immer der-selben Adresse liegen haben? Geht das?
le x. schrieb: > Deswegen reicht es wahrscheinlich auch, wenn nur die Sektionen .data und > .text ins Hex-File übernommen werden, richtig? > (Bei objcopy) ojbcopy übernimmt ohnehin nur Sektionen mit Inhalt. Hat man allerdings auch Sektionen für EEPROM und/oder Fuses, dann würden diese (mit ihren fiktiven Adressen) auch mit kopiert. Daher kann man das auch entsprechend einschränken. Hat man Sektionen mit Nicht-Standard-Namen, muss man diese natürlich ggf. ebenfalls mit kopieren lassen. Masl schrieb: > Wie würde ich denn nun eine eigene Section anlegen und Compiler > mitteilen, er solle mir genau da mein Zeugs reinlegen? > Sagen wir, ich würde gerne 3 8-Bit Werte an immer der-selben Adresse > liegen haben? Geht das? Zum Beispiel (Offsets beziehen sich jetzt auf AVR, geht aber im Prinzip mit jeder Architektur ungefähr so):
1 | $ cat test.c |
2 | #include <stdint.h> |
3 | |
4 | #define ALTSECTION __attribute__((section(".alt")))
|
5 | |
6 | volatile uint8_t var1 ALTSECTION; |
7 | volatile uint8_t var2; |
8 | |
9 | int
|
10 | main(void) |
11 | {
|
12 | return var1 + var2; |
13 | }
|
1 | $ avr-gcc -Os -mmcu=atmega1281 -o test.elf test.c -Wl,--section-start=.alt=0x805000 |
2 | $ avr-objdump -dh test.elf |
3 | |
4 | test.elf: file format elf32-avr |
5 | |
6 | Sections: |
7 | Idx Name Size VMA LMA File off Algn |
8 | 0 .text 0000010a 00000000 00000000 00000094 2**1 |
9 | CONTENTS, ALLOC, LOAD, READONLY, CODE |
10 | 1 .alt 00000001 00805000 00805000 0000019e 2**0 |
11 | CONTENTS, ALLOC, LOAD, DATA |
12 | 2 .bss 00000001 00800200 00800200 0000019e 2**0 |
13 | ALLOC |
14 | 3 .stab 000006cc 00000000 00000000 000001a0 2**2 |
15 | CONTENTS, READONLY, DEBUGGING |
16 | 4 .stabstr 00000054 00000000 00000000 0000086c 2**0 |
17 | CONTENTS, READONLY, DEBUGGING |
18 | |
19 | Disassembly of section .text: |
20 | |
21 | 00000000 <__vectors>: |
22 | 0: 0c 94 66 00 jmp 0xcc ; 0xcc <__ctors_end> |
23 | 4: 0c 94 78 00 jmp 0xf0 ; 0xf0 <__bad_interrupt> |
24 | [...] |
25 | |
26 | 000000f4 <main>: |
27 | f4: 20 91 00 50 lds r18, 0x5000 |
28 | f8: 80 91 00 02 lds r24, 0x0200 |
29 | fc: 30 e0 ldi r19, 0x00 ; 0 |
30 | fe: 28 0f add r18, r24 |
31 | 100: 31 1d adc r19, r1 |
32 | 102: c9 01 movw r24, r18 |
33 | 104: 08 95 ret |
34 | |
35 | [...] |
Allerdings bekommt man ohne einen modifizierten Linkerscript wohl offenbar nur ladbare Sektionen auf diese Weise hin, also solche mit Initialisierungsdaten, wobei die Initialisierungsdaten jedoch nicht vom Startup-Code berücksichtigt werden (der kennt die ja nicht). Ein Äquivalent zu einer BSS-Sektion auf diese einfache Weise zu generieren, ist mir nicht gelungen. Wenn man nur wenige Daten auf festen Adressen hat und diese Adressen mit der Hand verwalten kann/möchte, geht auch diese Variante:
1 | $ cat test.c |
2 | #include <stdint.h> |
3 | |
4 | extern volatile uint8_t var1; |
5 | volatile uint8_t var2; |
6 | |
7 | int
|
8 | main(void) |
9 | {
|
10 | return var1 + var2; |
11 | }
|
1 | $ avr-gcc -Os -mmcu=atmega1281 -o test.elf test.c -Wl,--defsym=var1=0x805000 |
2 | $ avr-objdump -dh test.elf |
3 | |
4 | test.elf: file format elf32-avr |
5 | |
6 | Sections: |
7 | Idx Name Size VMA LMA File off Algn |
8 | 0 .text 0000010a 00000000 00000000 00000074 2**1 |
9 | CONTENTS, ALLOC, LOAD, READONLY, CODE |
10 | 1 .bss 00000001 00800200 00800200 0000017e 2**0 |
11 | ALLOC |
12 | 2 .stab 000006cc 00000000 00000000 00000180 2**2 |
13 | CONTENTS, READONLY, DEBUGGING |
14 | 3 .stabstr 00000054 00000000 00000000 0000084c 2**0 |
15 | CONTENTS, READONLY, DEBUGGING |
16 | |
17 | Disassembly of section .text: |
18 | |
19 | 00000000 <__vectors>: |
20 | 0: 0c 94 66 00 jmp 0xcc ; 0xcc <__ctors_end> |
21 | 4: 0c 94 78 00 jmp 0xf0 ; 0xf0 <__bad_interrupt> |
22 | |
23 | [...] |
24 | |
25 | 000000f4 <main>: |
26 | f4: 20 91 00 50 lds r18, 0x5000 |
27 | f8: 80 91 00 02 lds r24, 0x0200 |
28 | fc: 30 e0 ldi r19, 0x00 ; 0 |
29 | fe: 28 0f add r18, r24 |
30 | 100: 31 1d adc r19, r1 |
31 | 102: c9 01 movw r24, r18 |
32 | 104: 08 95 ret |
33 | |
34 | [...] |
Danke dir, das mit __attribute__((section(".alt"))) war mir aus der avr-libc Doku bekannt. Nur dachte ich bisher, ich müsste diese Sektion noch irgendwo "erstellen", z.B. im Linkerscript. Aber das wird ja anscheinend "automatisch" gemacht. Das mit den Initialisierungsdaten ist mir noch bischen unklar. Bedeutet das, die Daten die ich in meiner eigenen Sektion liegen habe werden "aufwendig" initialisiert, d.h. so wie auch bei .data der Fall? Wenns nur ein paar Variablen sind sollte man ja damit leben können. Nochwas: wie bekomme ich beim Linkeraufruf die erste freie Adresse? Sagen wir, ich möchte die Daten direkt zwischen .bss und dem Heap haben. Mein Makefile kennt ja die Größe von .data + .bss nicht. Viele Grüße, Masl
Ok, vergiss meine letzte Frage. Die Daten immer direkt nach .bss zu platzieren ist natürlich Schwachsinn, dadurch liegen sie ja auch nicht "fest". D.h. ich würde mir irgendeine Adresse ausdenken müssen, wo ich die Daten haben will. Sagen wir mal, die Adresse liegt zufällig im Bereich .data. Würde nun der Linker initialisierte Daten in .data legen, dann kommt irgendwann meine Sektion, und dann wird .data fortgesetzt?
Masl schrieb: > Das mit den Initialisierungsdaten ist mir noch bischen unklar. > Bedeutet das, die Daten die ich in meiner eigenen Sektion liegen habe > werden "aufwendig" initialisiert, d.h. so wie auch bei .data der Fall? Nein, sie landen zwar in der Objektdatei ("dem ELF-File"), aber da der Startup-Code nichts davon weiß (das würde nur über entsprechende Hilfsmarken im Linkerscript funktionieren), kann er den RAM damit nicht vorladen. Bleibt also der Platzverbrauch in der Objektdatei übrig. Masl schrieb: > Würde nun der Linker initialisierte Daten in .data legen, dann kommt > irgendwann meine Sektion, und dann wird .data fortgesetzt? Nein, der Linker kann nichts teilen. Er würde dir einen section conflict anzeigen.
Dann bleibt ja eigentlich für so ein Vorhaben nur der Bereich vor .data, also direkt die erste verfügbare RAM-Adresse nach den memory-mapped registern?
Masl schrieb: > Dann bleibt ja eigentlich für so ein Vorhaben nur der Bereich vor .data, > also direkt die erste verfügbare RAM-Adresse nach den memory-mapped > registern? Ist zumindest die sinnvollste Variante. .data kannst du ja mit -Wl,--section-start=.data=0x80xxxx problemlos nach hinten schieben. Alternativ könntest du einen Bereich hinter dem Stack dafür anlegen, dann musst du nur die Stackpointerinitialisierung ändern. Geht bei der avr-libc über das Symbol __stack, welches standardmäßig auf RAMEND gesetzt wird.
Jörg Wunsch schrieb: > Ist zumindest die sinnvollste Variante. .data kannst du ja mit > -Wl,--section-start=.data=0x80xxxx problemlos nach hinten schieben. Muss ich das extra machen, oder reicht es wenn ich einfach meine eigene Sektion davor einfüge? Alles Nachfolgende sollte ja automatisch verschoben werden, wenn nötig (zumindest wenn .data in den externen RAM verschoben wird werden .bss und der Heap mitverschoben).
Masl schrieb: > Muss ich das extra machen, oder reicht es wenn ich einfach meine eigene > Sektion davor einfüge? "davor einfügen" impliziert einen eigenen Linkerscript. Ja, das geht. Die andere Variante hat den Vorteil, sowas nicht zu brauchen, sondern komplett mit Kommandozeilenargumenten auszukommen.
Hi, da hab ich wohl ein echt interessantes Thema angerissen :) Ihr habt's jetzt von der AVR-Implementierung des GCC, ich brauch's konkret für Cortex-Controller. Ich hoffe, die bisherigen Beiträge lassen sich auf beides anwenden? Kann man sich eigentlich ein "virtuelles" Segment anlegen? Also bestimmte Startadresse und Länge, Inhalt wird nicht initialisiert oder in irgendeiner anderen Weise durch den Startup-Code angefasst. Ralf
Ralf schrieb: > Ich hoffe, die bisherigen Beiträge lassen > sich auf beides anwenden? Ja, im Großen und Ganzen schon. > Kann man sich eigentlich ein "virtuelles" Segment anlegen? Also > bestimmte Startadresse und Länge, Inhalt wird nicht initialisiert oder > in irgendeiner anderen Weise durch den Startup-Code angefasst. Per Linkerscript auf jeden Fall. Da die ARM-Leute sowieso dazu tendieren, dass jeder seinen eigenen (mehr oder minder buggigen :-) Linkerscript in seiner Applikation mit herumschleppt, dürfte das aber für dich kein Thema sein.
@Jörg Wunsch: > Per Linkerscript auf jeden Fall. Da die ARM-Leute sowieso dazu > tendieren, dass jeder seinen eigenen (mehr oder minder buggigen :-) > Linkerscript in seiner Applikation mit herumschleppt, dürfte das > aber für dich kein Thema sein. Okay, dann muss ich mir das automatisch erzeugte Linker-Script mal angucken und entsprechend modifizieren. So, dann kommt als nächstes auf die Prüfliste, wie denn Zugriffe über Pointer eigentlich im GCC realisiert werden :) Da kommt mittlerweile eine schöne Stichwort-Sammlung raus, aber ist ja bald Wochenende, dann kann ich mal "nachforschen". Ralf
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.