Folgendes Problem:
Ich habe eine Sammlung Quellmodule, die per #define gesteuert Funktionen
ein- oder ausschließen.
Namen und Adressen dieser Funktionen sollen in eine Namenstabelle
eingetragen werden, wenn die jeweilige Funktion im Objektcode enthalten
ist. Die Tabelle soll im Flasspeicher angelegt werden. Das soll
automatisch zur Generierungszeit passieren.
Die Tabelle wird zur Laufzeit von einer Routine durchsucht, die die
Funktionsadressen in Namensstrings umsetzt. Es muss also auch die
variable Länge der Tabelle zur Laufzeit feststellbar sein.
Wir bekommt man das hin?
Alternativ: überall da, wo via #define irgendwas hinzu kommt, kommt auch
gleich der Tabelleneintrag mit dazu (bspw. in Form einer eigenen memory
section).
Das spart es, den ganzen Salat via Postprocessing nachzuverarbeiten, was
ja am Ende auf zweimaliges Linken hinaus läuft (einmal, um zu wissen,
was alles "an Bord" ist, das zweite Mal, um die mit diesem Wissen
gebaute Tabelle dazu zu linken).
Jörg W. schrieb:> Alternativ: überall da, wo via #define irgendwas hinzu kommt, kommt auch> gleich der Tabelleneintrag mit dazu (bspw. in Form einer eigenen memory> section).
Genau sowas schwebt mir vor. Wie fange ich das an, ohne zusätzlich ein
Linkerscript bauen zu müssen??
Moriz schrieb:> Wie fange ich das an, ohne zusätzlich ein Linkerscript bauen zu müssen??
Man kann halt nicht alles haben. Ein eigenes Linkerscript brauchst du
dafür dann schon.
Ist aber m.E. immer noch einfacher als irgendein Postprocessing.
Ich würde das über #defines machen, und mir den ganzen Tabellen-Zirkus
sparen.
Der Linker macht das ja für dich.
Wenn du tatsächlich unterschiedliche Memory-Regions brauchst -was bei
den 8bit AVR eher die Ausnahme ist- kannst du ja einfach alle denkbaren
Memory-Regions im Linkerscript eintragen, und via #define kontrollieren,
welche tatsächlich genutzt werden.
Ungenutzte Zuweisungen im Linkerscript, tun ja nicht weh.
Ist zwar kein AVR, aber in diesem Projekt kannst du sehen, wie man in
einer config.h mit nem Haufen #define kontrolliert, was da gerade gebaut
wird.:
Beitrag "Re: [STM32/HAL] simples U(S)ART-Library"
Harry L. schrieb:> Ich würde das über #defines machen, und mir den ganzen Tabellen-Zirkus> sparen.> Der Linker macht das ja für dich.
Mir scheint, ich habe mich nicht klar genug ausgedrückt…
Ich habe einen Scheduler als Quelltextbibliothek.
Für die Task-Tabelle des Schedulers gibt es eine Dumpfunktion, die im
Moment eine Adresse-nach-Namensumsetzungsroutine in der Anwendung
aufruft. Dafür muss eine Tabelle gepflegt werden, die den Task-Adressen
den Klarnamen zuordnet. Die Pflege der Tabelle will ich umgehen, indem
ich einfach in jeder Task-Routine einen Macro aufrufe, der den
zugehörigen Umsaetzungstabelleneintrag generiert.
Die Tabelleneinträge müssen hintereinander in einer Flash-Section landen
– das ist Linkers Job.
Damit die Suchroutine weiß, wo die Tabelle endet, muss hinter dieser
Section eine weitere leere liegen, die die Adresse hinter der Tabelle
liefert.
Ich habe etwas herum experimentiert: der Compiler nimmt Deklarationen
wie diese an:
1
#define N(name) { name, FSTR(#name) }
2
staticconstName__flash1NameTable[]={
3
N(powerBankRefreshed),
4
N(powerBankRefresher),
5
};
6
#undef N
wobei __flash1 der der allgemeinen Form __flashN entspricht.
Wenn ich dahinter einen leeren Array in __flash2 anlege, könnte das
funktionieren, wenn die Sections hintereinander liegen.
Nur leider finde ich im .map-File dazu überhaupt nichts, weder
Sectionnamen, noch Variablennamen. (Dieser .map-File scheint mir
überhaupt nicht zu allzuviel nutze zu sein…)
wieso eigenes Linker-Script? Du brauchst doch nur eine weitere Memory
section für Deine Tabelle. Und der Linker liefert Dir nachher Adresse
und Länge der Section. Also nur ein paar Zeilen im Script.
Wieso brauchst Du überhaupt getrennte Quellen? Wenn Du in ein Sammelfile
alle Header einbinden kannst, kann das der Precompiler für Dich
erledigen.
Wenn es nur eine Handvoll Teilprojekte sind, kannst du auch eine Liste
von (Teilprojekt-)Tabellen anlegen.
Wenn es Dir nur um das Listen-/Tabellenende geht, mache sie
"Nullterminiert" (natürlich nicht wenn der Linker die anordnet).
Bruno V. schrieb:> wieso eigenes Linker-Script? Du brauchst doch nur eine weitere Memory> section für Deine Tabelle. Und der Linker liefert Dir nachher Adresse> und Länge der Section. Also nur ein paar Zeilen im Script.
In welchem Skript?
> Wieso brauchst Du überhaupt getrennte Quellen? Wenn Du in ein Sammelfile> alle Header einbinden kannst, kann das der Precompiler für Dich> erledigen.
Ganz simpel: ich will den Scheduler einfach in ein Projekt aufnehmen,
also scheduler.c und scheduler.h
In der Anwendung sind dann die Task-Funktionen verstreut. Wenn jede
Taskfunktion es schafft, den zugehörigen Umsetzungstabellen-Eintrag in
eine spezielle Section zu bugsieren und der Name des Section-Anfangs und
-Endes dem Scheduler bekannt ist, dann muss man sich um die Tabelle
nicht mehr weiter kümmern.
> Wenn es nur eine Handvoll Teilprojekte sind, kannst du auch eine Liste> von (Teilprojekt-)Tabellen anlegen.
Nein, solches Zeug will ich tunlichst vermeiden – das wird schnell
furchtbar unübersichtlich und ist bald unpflegbar…
Moriz schrieb:> Nur leider finde ich im .map-File dazu überhaupt nichts, weder> Sectionnamen, noch Variablennamen. (Dieser .map-File scheint mir> überhaupt nicht zu allzuviel nutze zu sein…)
Das Map-File ist nützlich zum Debuggen, wenn man Probleme mit dem Linker
hat. Das ist ganz sicher nicht dafür da, irgendwie maschinenlesbar zu
sein.
Die Symboltabelle wäre vielleicht interessanter für dich.
Moriz schrieb:> Wie findet man das heraus?
Indem du den GCC mit -v startest, dann sagt er dir, welche Backends er
so aufruft.
Moriz schrieb:> Ich habe eine Sammlung Quellmodule, die per #define gesteuert Funktionen> ein- oder ausschließen.> Die Tabelle wird zur Laufzeit von einer Routine durchsucht, die die> Funktionsadressen in Namensstrings umsetzt.
Wozu diese Umstände?
Ein #define läßt sich doch prima schon zur Compilezeit auswerten.
1
#define MODULE_CMD_GETCALIB "GetCalib" // Diagnostics only. Returns the calbiration signature
2
#define MODULE_CMD_READ_ADC "ReadAdc" // Diagnostics only, doesnt work for muxed ADC.
Moriz schrieb:> Nein, solches Zeug will ich tunlichst vermeiden – das wird schnell> furchtbar unübersichtlich und ist bald unpflegbar…
Ich hab nen Kollegen, der ist Fan von überall im Code verteilten
sscanf() zum Parsen. Mit dem Erfolg, daß gerne mal Schreibfehler
passieren oder sogar Namen doppelt vergeben werden bzw. fehlen. Für
einen anderen Leser ist es sehr unübersichtlich, wenn die Strings und
Aufrufe überall wahllos verteilt sind. Außerdem erzeugt jedes weitere
sscanf() auch massig Codeoverhead.
In einer Tabelle kann man viel besser auf einheitliche Syntaxregeln
achten und Fehler erkennen.
Peter D. schrieb:> Außerdem erzeugt jedes weitere> sscanf() auch massig Codeoverhead.
Wieso das? sscanf wird, wie jede Library-Funktion, einmal zum Code
dazugelinkt. Ob man es an drei, zehn oder fünfzig Stellen im Code
verwendet, ändert an dessen Größe nichts. Ist doch keine inline-Funktion
...
Harald K. schrieb:> Ob man es an drei, zehn oder fünfzig Stellen im Code> verwendet, ändert an dessen Größe nichts.
sscanf() Aufrufe bekommen oft sehr viele Parameter übergeben, die auf
dem Stack abgelegt werden müssen. Man kann sich mal im Listing
anschauen, was für ein Bohei vor jedem Aufruf veranstaltet wird.
Peter D. schrieb:> Mit dem Erfolg, daß gerne mal Schreibfehler> passieren oder sogar Namen doppelt vergeben werden bzw. fehlen.
Im Prinzip hast du Recht, aber hier liegt ein spezieller Fall vor, für
den das eben gerade nicht gilt, während es für die global zu pflegende
Tabelle zutrifft.
In schedule.h gibt es bereits einen Macro SCHEDULER_TASK(name), der
eigentlich nur syntaktischer Zucker ist und die Lesbarkeit des Codes
verbessert:
1
#define SCHEDULER_TASK(name) void name()
Wenn es gelingt, diesen Macro so umzubauen, dass er gleich den
entsprechenden Namenstabelleneintrag generiert, dann braucht man keine
globale Tabelle mehr, der Code bleibt lesbar und Namensänderungen
schlagen sofort auf die Tabelle durch.
Zusätzlich kann man auch zwei Versionen davon bauen, eine mit
eingebautem Tabellen-Dump und einen ohne und die ganze Chose über ein in
der Kommandozeile des Compilers angebbares Symbol steuern.
Dann wäre das alles völlig automatisiert und trotzdem transparent.
Die Tabelleneintragsgenerierung kann man mit dem Trick mit der
verzeigerten Liste aus
https://www.mikrocontroller.net/articles/C_Linkerhacks_%2B_Modularisierung
dezentralisieren. (Das mit den Sections funktioniert beim AVR nach dem
Artikel nicht, weil die Reihenfolge der Sections nicht definiert ist.)
Moriz schrieb:> Jörg W. schrieb:>> Die Symboltabelle wäre vielleicht interessanter für dich.>> Danach habe ich gesucht… Wie krieg ich die?
avr-nm (mit verschiedenen Optionen)
Oder programmatisch mit einer ELF-Bibliothek.
Moriz schrieb:> Wenn es gelingt, diesen Macro so umzubauen, dass er gleich den> entsprechenden Namenstabelleneintrag generiert
Das Problem an C-Macros ist, daß es nur eine Textersetzung durchführt.
Ein Macro kann also nicht weitere Macros erzeugen und es sind auch keine
nested Macros möglich.
Sowas kann nur der AVR Assembler.
Moriz schrieb:> Das mit den Sections funktioniert beim AVR nach dem Artikel nicht, weil> die Reihenfolge der Sections nicht definiert ist
Das eigentliche Problem beim AVR ist das linker script. Die custom
sections kommen nicht in .text oder .data rein, die Objekte landen dann
nicht auf dem AVR.
Und wenn man seine Sections z.B. .data.bla nennt, dann sollte es zwar in
.data rein kommen, aber dann generiert der Linker die _start und
_stop symbole nicht mehr, die gibts nur wenn der Sektionsname ein
gültiger c identifier wäre, und "." kann man da halt nicht haben. Eine
echt blöde Situation...
Peter D. schrieb:> Ein Macro kann also nicht weitere Macros erzeugen und es sind auch keine> nested Macros möglich.
Muss es auch nicht. Sieh dir den Artikel an, den Daniel A. hier
Beitrag "Re: avrgcc: verteilte Array-Initialisierung" verlinkt hat…
Ein Problem könnte es geben, wenn in zwei Quellmodulewn der Macro
zufällig auf gleichen Zeilennummern aufgerufen wird. Das könnte man aber
leicht beheben, z.B. mit einer für jeden Quellfile individuellen
Modul-ID – das habe ich in meinem Projekt auch bereits eingebaut, um
fatale Fehler mit einer LED blinkend zu kommunizieren.
Moriz schrieb:> Wieso gibt es eigentlich nur leere man-Eintrage für das avr-* Zeug?
Das musst du denjenigen fragen, der das bei dir zusammen gepackt hat.
Ich habe sowohl auf einem Ubuntu als auch auf dem FreeBSD dort reale
Einträge.
Aber: was die Symboltabelle anbelangt, da kannst du auch einfach "man
nm" machen.
Peter D. schrieb:> Das Problem an C-Macros ist, daß es nur eine Textersetzung durchführt.
Das wird in dem zitierten Artikel mit dem alten CPP CONCAT-Trick
umgangen. Womöglich kannst du da noch was lernen ;-)
Neue Macros in einem Macro erzeugen geht nicht, aber das braucht man
hier auch nicht. Für solche Schweinereien muss dann der olle m4 her…
Dessen Syntax ist allerdings gewöhnungsbedürftig.
Moriz schrieb:> Jörg W. schrieb:>> Aber: was die Symboltabelle anbelangt, da kannst du auch einfach "man>> nm" machen.>> Unter Mint 21 kommt da ein leerer man-Eintag.
Da musst du dich wohl bei den Mint-lern bedanken gehen.
Hast du mal
Moriz schrieb:> Jörg W. schrieb:>> Hast du malenv LANG=C man ld>> probiert?>> Damit geht es, Teufel…
Normalerweise sollte das man-Kommando natürlich auf die englische
Version zurück fallen, wenn keine in der Regionalsprache installiert
ist. Da scheint also generell was bei dem System kaputt zu sein.
> Für das avr-*-Zeug hilft das nichts.
Das wiederum dürfte an demjenigen liegen, der die avr-* Pakete gebaut
hat. Aber von avr-libc abgesehen (die via "avr-man" bei Bedarf einen
Sack voller eigener man pages installieren kann), sind die man pages von
GCC und binutils natürlich eh die gleichen wie die ohne "avr-".
Peter D. schrieb:> sscanf() Aufrufe bekommen oft sehr viele Parameter übergeben, die auf> dem Stack abgelegt werden müssen. Man kann sich mal im Listing> anschauen, was für ein Bohei vor jedem Aufruf veranstaltet wird.
Mit ziemlicher Sicherheit gibts noch viel mehr Bohei, wenn man das, was
ein sscanf-Aufruf macht, zu Fuß in einzelne Funktionsaufrufe auflöst.
Aber wegen einem einzigen sscanf den ganzen Modul zu laden, sollte man
sich im Zweifelsfall schon gut überlegen.
Das sieht schon mal ganz gut aus… nur finde ich die Funktion
initReadSensors_Schd_NameTableEntry_ctor() weder in der Symboltabelle,
noch im .map-File. Irgend was scheint da mit der Behandlung von
Konstruktoren nicht zu stimmen.
Ursache für die fehlende initReadSensors_Schd_NameTableEntry_ctor() ist
Linker-Optimierung. Wenn ich die Option -flto entferne, sind sie im
Objekt enthalten.
Gibt es eine Möglichkeit, dem Linker mitzuteilen, dass er eine Funktion
von der Optimierung ausnehmen soll?