www.mikrocontroller.net

Forum: Compiler & IDEs winavr linkerscript .bss vor .data und an konst. Adresse


Autor: Holger M. (qibono)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich habe ein Problem mit dem Linker (winavr-2010010). Kommt mir 
eigentlich ganz einfach vor, ich bin aber wohl zu blöd die richtige 
google Formel zu finden bzw. die ld-Doku zu verstehen.


Das .bss Segment (00 initialisierte Variablen) muss an eine konstante 
Adresse (den Anfang vom RAM).

Das .data Segment soll dahinter liegen. Im ursprüngliche Script von 
ATMEL war das anders herum.

Der Grund fuer den Aufstand: Das Programm soll Programmteile des 
Bootloaders verwenden (d.h. Codeteile aus zwei verschiedenen Hex-Files 
arbeiten zusammen). Beide Programmteile müssen Wissen über gemeinsam 
genutzte Variablen teilen. Ich muss also für die Variablen des 
Bootloaders im eigentlichen Programm Platz reservieren (ausserdem sollen 
sie beim Programmstart auf 00 initialisiert werden). Diese Variable habe 
ich in einem Assemblerfile in einem Segment .absasmdata angelegt, das 
ich an den Anfang der .bss Section und die wiederum an den Anfang des 
data-Bereichs    linken möchte (also Adresse 0x100 bzw. 0x8000100 für 
den Linker).
  .section .absasmdata, "", @nobits

  .global FAST_TICKER_L
FAST_TICKER_L: 
  .space 1

  ... 

Die .data-Variablen (mit Konstanten initialisierte Variablen) sollen 
dann hinter dem .bss Segment landen.
// main.c
int const_4321 = 0x4321;


Unten folgt ein Ausschnitt aus meinem Linkerscript. Die Kommentare vor 
.data zeigen einen winzigen Teil meiner kläglich gescheiterten Versuche.
Entweder bricht der Linker mit einem Fehler ab oder aber er linkt .bss 
und .data beide auf Adresse 0x100. Letzteres führt dann dazu, dass 
const_4321 an der selben Speicherstelle (0x100 bzw. 0x800100) landet, 
wie meine FAST_TICKER_L Variable aus dem .bss-Segment.

Wer kennt das "Linker oeffne Dich" zu diesem Problem?

MEMORY
{
  text    (rx)  : ORIGIN = 0x000000, LENGTH = 0xEC00
  data    (rw!x)  : ORIGIN = 0x800100, LENGTH = 4k
  eeprom    (rw!x)  : ORIGIN = 0x810000, LENGTH = 2K
  fuse    (rw!x)  : ORIGIN = 0x820000, LENGTH = 3
  lock    (rw!x)  : ORIGIN = 0x830000, LENGTH = 1
  signature  (rw!x)  : ORIGIN = 0x840000, LENGTH = 1K
}
...

  bss :
  {
    PROVIDE (__bss_start = .);
    *(.absasmdata)
    *(.bss)
    *(.bss*)
    *(COMMON)
    PROVIDE (__bss_end = .);
  }> data
  __bss_data_end = (ADDR(.bss) + SIZEOF(.bss));

  /* .data wird mit Konstanten initialisiert */
  /* .data ADDR (.bss) + SIZEOF (.bss) : AT (ADDR (.text) + SIZEOF (.text))   */
  /* .data (ADDR(.bss) + SIZEOF(.bss)) : AT (ADDR (.text) + SIZEOF (.text))   */
  .data AT( __bss_data_end ) : AT (ADDR (.text) + SIZEOF (.text))
  {
    PROVIDE (__data_start = .);
    *(.data)
    *(.data*)
    *(.rodata) /* We need to include .rodata here if gcc is used */
    *(.rodata*) /* with -fdata-sections.  */
    *(.gnu.linkonce.d*)
    . = ALIGN(2);
    _edata = .;
    PROVIDE (__data_end = .);
  }> data

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Holger M. schrieb:
> Der Grund fuer den Aufstand: Das Programm soll Programmteile des
> Bootloaders verwenden (d.h. Codeteile aus zwei verschiedenen Hex-Files
> arbeiten zusammen). Beide Programmteile müssen Wissen über gemeinsam
> genutzte Variablen teilen.

Dann lass dieses Bootloader-Programmteil (ja wohl eine oder mehrere 
Funktionen, oder?) nicht einfach auf irgendwelche globalen Variablen 
zugreifen, sondern gib der Funktion einen (oder mehrere) Pointer mit.

Autor: Holger M. (qibono)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Stefan Ernst
Ist keine Option. Sinn der Veranstaltung ist Code-Speicher zu sparen. 
Das Pointer laden und -Lookup würde mehr Code brauchen als ich sparen 
kann.

Holger

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Holger M. schrieb:
> Das Pointer laden und -Lookup würde mehr Code brauchen als ich sparen
> kann.

Glaube ich dir nicht ungesehen. Wenn es bei den gemeinsamen Daten um 
einen größeren Datenblock geht (z.B. ein Array) wird in der aufgerufenen 
Funktion ja sowieso ein Pointer darauf erzeugt werden. Und wenn es um 
mehrere unterschiedliche Variablen geht, könnte man sie zu einer struct 
zusammenfassen, und einen Pointer darauf übergeben. Das könnte eventuell 
sogar kürzeren Code ergeben, als das Laden der ganzen Variablen von 
festen Adressen.

Autor: Holger M. (qibono)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich glaube ich habe eine Antwort auf mein Problem gefunden; vieleicht 
hilft sie ja irgendwann einmal jemandem.

Das ursprüngliche Linkerscript geht wohl auf ein Beispiel von ATMEL 
zurück. Ich vermute deswegen, dass es bei vielen Leuten ähnlich aussehen 
wird.

Meine Vermutung ist, dass der Name ".data" etwas überstrapaziert wurde. 
Einmal ausserhalb der geschweiften Klammenr als Unterbereich von "data" 
und dann innerhalb der geschweiften Klammern als Segmentname (vom gcc 
vorgegeben). Wenn beide .data-Bereiche auf Offset 0000 innerhalb ihres 
jeweiligen Bereichs liegen, fällt diese Mehrdeutigkeit nicht auf - genau 
das wollte ich aber ja ändern. Ob das vielleicht sogar eine 
Schwachstelle vom Linker ist, interessiert mich hier im Moment nicht. 
Ich finde es ohnehin eklig, verschiedene Dinge identisch zu bezeichnen.

Zur Lösung habe ich also die Subsection .data in .initializedram 
umbenannt und weil ich gerade dabei war und es mit .bss die gleiche 
Mehrdeutigkeit gab, habe ich auch .bss in .zeroedram umbenannt. Danach 
sieht mein MAP-File so aus, wie ich es erwartet habe.

Die Labels __data_load_start, __data_load_end müssen auch noch 
entsprechend angepasst werden damit die Konstanteninitialisierung 
funktioniert.
MEMORY
{
  text    (rx)  : ORIGIN = 0x000000, LENGTH = 0xEC00
  data    (rw!x)  : ORIGIN = 0x800100, LENGTH = 4k
  eeprom    (rw!x)  : ORIGIN = 0x810000, LENGTH = 2K
  fuse    (rw!x)  : ORIGIN = 0x820000, LENGTH = 3
  lock    (rw!x)  : ORIGIN = 0x830000, LENGTH = 1
  signature  (rw!x)  : ORIGIN = 0x840000, LENGTH = 1K
}

  .zeroedram :
  {
    PROVIDE (__bss_start = .);
    *(.absasmdata)
    *(.bss)
    *(.bss*)
    *(COMMON)
    PROVIDE (__bss_end = .);
  }> data

  .initializedram : AT (ADDR (.text) + SIZEOF (.text)) 
  {
    PROVIDE (__data_start = .);
    *(.data)
    *(.data*)
    *(.rodata) /* We need to include .rodata here if gcc is used */
    *(.rodata*) /* with -fdata-sections.  */
    *(.gnu.linkonce.d*)
    . = ALIGN(2); 
    _edata = .;  
    PROVIDE (__data_end = .);
  }> data
  __data_load_start = LOADADDR(.initializedram);
  __data_load_end = __data_load_start + SIZEOF(.initializedram);

Ich habe diese Änderungen bisher nicht auf Seiteneffekte auf Simulator, 
Programmiertools etc. überprüft. Wenn jemand dazu Informationen (oder 
eine fundierte Meinung) hat, nehme ich diese gern zur Kentniss

Holger

Autor: Flo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Holger!

Ich glaube ich habe eine Antwort auf mein Problem gefunden; vieleicht 
hilft sie ja irgendwann einmal jemandem. 

Und ob! ;)
Ich habe auch eine etwas unübliche Bootloaderkonstruktion gebaut, die 
kein UART implementiert, sondern nur als Ausführ-Ort für das SPM 
Kommando dient. Die Anwendungen selbst rufen den Bootloader auf. Um 
Bootloader und Images trotzdem die Möglichkeit zu geben, schreiben zu 
können (und der Ram sehr begrenzt ist >.<) , habe ich nach einer 
sauberen Lösung für gemeinsam bekannten Puffer im Ram gesucht.
Nach studenlangem Einlesen ins GNU-LD Handbuch, hab ich meine 
Wunschkonstellation wie du ewig nicht hingekriegt. Und exakt der Tip mit 
den eigenartig identischen Symbol/Section-Namen hat die gewünschte 
Symbolverteilung gebracht!

Ich hätte bei
.bss   : AT ( ADDR (.bss) ) { .. } 
aber auch schon stutzig werden müssen ;).

Jedoch wird ein Hex-File erzeugt, bei dem explizit ein Adress-Sprung auf 
0x0080 drin ist (Intel-Hex Zeile vom Typ 04). Der avr-dude sagt mir da, 
dass er die Adresse nicht gefunden hat (so einen großen Flash hat der 
avr nun mal nicht ;). Im Avr wird die Adresse 0x0080.xxxx offenbar nicht 
verwertet und an den SRAM weitergeleitet (wäre ja auch eine 32bit 
Adresse).
Das was wir wollen, ist ja, dass der Linker zur Compile-Zeit allen 
Variablen die richtige Adresse zurordnet.

Avr-Dude kann soweit ich das verstanden habe, keinen Ram direkt 
schreiben. Der wird ja sowieso beim Reset mit den initialisierten 
Datenwerten geladen (__data_load_start .. __data_load_end).
Ich hab gerade nochmal probiert, eeprom-Werte mit reinzukompilieren. Da 
passiert dasselbe...
Avr-Dude erkennt folgende Intel-Hex Zeile als ungültig an:
:02000004008179
:04000000FEA4AFFEAD
 --> also vom Typ 04, und Adresse 0x0081 als obere 16bit für die 
folgenden Zeilen. Das korreliert mit dieser Zeile aus dem Linkerscript:
eeprom (rw!x) : ORIGIN = 0x810000, LENGTH = 64K
(zugehöriger Code: uint8_t eeFooByteArray4[] EEMEM = { 0xFE, 0xA4, 
0xAF,0xFE }; )

Wenn man mit Avr-Dude den eeprom schreiben will, braucht man eine andere 
Kommandozeile (eeprom:w:e.hex) ... Offenbar scheint das Schreiben in 
verschiedene Speicher mit verschiedenen SPI-Schreib-Befehlen codiert zu 
sein.

Die Lösung des Problems war im Endeffekt, dass man eine globale Variable 
anlegt (Position egal) und sie mit einer eigenen Section-Anweisung 
versieht.
uint8_t globalBuf[128] __attribute__ (( section(".globalBufSec")));
Im Linkerscript wird eine Zeile eingfügt, dass vor den .data Sections 
aller Dateien, die eigenen Sections zu stehen haben.
  
  .data : AT( ADDR (.text) + SIZEOF(.text) ) 
  {
  /* hier werden SYMBOLE definiert! */  
     PROVIDE (__data_start = .) ; /* The PROVIDE keyword may be used to define a symbol, such as `etext', only if it is referenced but not defined. */
  
  *(.globalBufSec) /*eigene sections*/
  
    
  *(.data)
      *(.data*)
      *(.rodata)  /* We need to include .rodata here if gcc is used */
      *(.rodata*) /* with -fdata-sections.  */
      *(.gnu.linkonce.d*)
    
      . = ALIGN(2); /* Linker location counter auf relativen Offset von 2 ggü Start der Section,
          dieser Wert gilt *MINDESTENS* --> wenn data-variablen: "_edata" direkt danach*/
  _edata = . ; /* SYMBOL: end data */    
  PROVIDE (__data_end = .) ;     
  }  > data
  

Wenn man die .data und .bss Blöcke umbenennt, gibt es mit hoher 
Wahrscheinlichkeit einen Fehler!! Zwar meckert weder avr-gcc noch 
avr-dude, aaaber:
Aus dem Datenblatt:
Data Memory Map for ATmega164P/324P/644P.
32 Registers    ...  0x0000 - 0x001F
64 I/O Registers  ...  0x0020 - 0x005F
160 Ext I/O Reg.  ...  0x0060 - 0x00FF
Internal SRAM    ...  0x0100 - 0x04FF/0x08FF/0x10FF
Wenn man .data in .dataOut ändert (also den Block innerhalb von 
SECTIONS{..}), starten die zugehörigen Daten bei 0x0080.0060 
(entsprechend der Definition im Linkerscript von "data" in MEMORY. siehe 
avr-objdump [..].elf -x). Und genau das ist tödlich beim atmega164!
Somit werden nämlich die "Externen I/O Register" überschrieben und nicht 
der SRAM benutzt!

Zum Schluss habe ich noch kurz gesucht, was die ganze Toolchain dazu 
bewogen hat ".data" anders als ".dataOut" (ausgedachter Name) zu 
behandeln und den Inhalt einfach so auf 0x0080.0100 zu schieben:
In /usr/lib/avr/include/avr gibt es eine Menge config-Dateien, die wohl 
vom avr-gcc je nach Auswahl des Controllers (-mmcu=..) geladen werden.
Dort steht zb "#define RAMSTART (0x100)" drin, was intern mit der 
Ausgabe-Section ".data" des Linker-Scripts verknüpft sein muss. 
(gefunden mit: grep "x100" -r /usr/lib/avr/* )

Soviel dazu ... ;)

Viele Grüße
Flo

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.