Hallo zusammen, ich erstelle derzeit ein reines Assembler Programm für den xmega und verwende dafür den gnu assembler in der avr-gcc toolchain. Jetzt habe ich viele Hilfsfunktionen geschrieben, die ich aber nie alle brauche. Er haut mir aber immer alle Symbole in die elf-Binary rein. Natürlich habe ich schon mehrmals Google gefragt, aber alle Hilfestellungen beziehen sich wohl auf C-Code, jedenfalls wird mein Code durch die Parameterkette "-ffunction-sections -fdata-sections -Wl,--gc-sections -Os" nicht kleiner. Hier der gesamte Aufruf: avr-gcc main.S -o main.elf -Wall -Wextra -std=gnu99 -mmcu=atxmega128a4u -I../include -ffunction-sections -fdata-sections -Wl,--gc-sections -Os Ich habe die Befürchtung, dass bei reinem Assembler der Compiler einfach nicht schlau genug ist um rauszufinden, welche Symbole nicht gebraucht werden. Ich könnte ja mit einem gemeinem EIJMP eine mathematisch berechnete Adresse anspringen :p Das wäre aber ein echter Nachteil der Assemblerprogrammierung, wenn ich hinterher von Hand alle Symbole rauskommentieren muss und das kann ja nicht wirklich so gedacht sein?
Dir ist aber schon klar, dass C-Compiler-Optionen keine Auswirkung auf den Assembler bzw Linker haben? Bei ein "gcc foo.S ..." wird der C-Compiler nicht gestartet - nur der Preprozessor, Assembler und Linker. Insb sind -ffunction-sections & co Optionen, die die Codegenerierung des C-Compilers steuern und habe keine Auswirkungen auf den Assembler. Damit --gc-sections (eine Linker-Option) funktioniert, musst du in deinem Assembler-Source selbst pro optionaler Funktion eine Sektion definieren. Schau dir einfach mal an, was für Unterschiede der Compiler mit und ohne -ffunction-sections erzeugt (mittels "gcc -S" Assembler-Output erzeugen).
Ach so: die übliche Methode, die überall funktioniert und nicht irgendwelche erweiterten Features der GNU-Toolchain braucht, ist die, jede optionale Funktion/Funktionenblock in eine eigene Datei zu packen, all diese kleinen Dateien in ein .a-Library packen und dieses dann beim finalen Linken anzugeben. Der Linker sucht sich dann nur die Files heraus, die benötigt werden. Geht immer, auf allen Systemen.
Solange sie in den FLASH des benutzten AVR passen (und die definierten Variablen keinen RAM-Engpaß verursachen), haben nicht benutzte "Routinen" doch keine negative Auswirkungen. (C-)Compiler können solche Biblioteksfunktionen auf Anfrage selbst in verdauliche Häpchen zerlegen, aber die erzeugen ja, so ein hier berühmter Fisch, nur Code-Bloat. Ups, bei Handarbeit, sprich ASM scheint das ja auch der Fall zu sein. Unfassbar ;-)
a) Unbenutzten Code automatisch zu entfernen ist quasi unmöglich. Deshalb gehen da Compiler, wenn überhaupt, sehr vorsichtig vor. In der Praxis wird somit auch in C, und besonders in C++ fast nie was entfernt. b) Symbole haben im Assembler nichts mit Code zu tun. Wenn Du eine Sprungmarke setzt, dann wird die im Code einfach durch die entsprechende Adresse ersetzt. c) Dein Assembler macht genau das was Du ihm sagst. Er entfernt keine Befehle sondern setzt die nur 1:1 um.
Unmögliches gibt es sogar zum kostenlosen Runterladen! Zum Glück entschließt die Realität sich nie dazu, sich an manche "Wahrheiten" zu halten ;-)
Christian Berger schrieb: > a) Unbenutzten Code automatisch zu entfernen ist quasi unmöglich. Komisch, das konnte mein Pascal-Compiler unter CP/M schon vor 30 Jahren, und das absolut zuverlässig: der Linker mit der Option "Scan" linkt nur Routinen, die tatsächlich aufgerufen werden. Wenn man sich das logisch überlegt ist das auch garkein Problem, man verzichtet nur heute aus Bequemlichkeit darauf, weil eines der Mantras heutiger Softwareentwicklung ist, dass jedweglicher Speicher immer in beliebiger Grösse verfügbar ist. Wenn ich z.B. die inch/mm-Umwandlung mit Multiplikationen ausführe und auch sonst nirgends was dividiere, verschwindet die Division automatisch aus der ausführbaren Datei. Ich habe das eigentlich auch immer für den Normalfall gehalten. Von Pascal, C oder Assembler ist das übrigens völlig unabhängig. Georg
Georg schrieb: > man verzichtet nur heute Der alte Keil Compiler machte das auch. Indirekt aufgerufene Funktionen mussten explizit markiert werden, damit sie nicht weg optimiert wurden. Vermutlich ist das auch heute noch so.
Georg schrieb: > Wenn ich z.B. die inch/mm-Umwandlung mit Multiplikationen ausführe und > auch sonst nirgends was dividiere, verschwindet die Division automatisch > aus der ausführbaren Datei. Ich habe das eigentlich auch immer für den > Normalfall gehalten. Von Pascal, C oder Assembler ist das übrigens > völlig unabhängig. Im Prinzip ja. Allerdings ist das eine Funktion des Linkers und für Assemblerprogramme wird der u.U. gar nicht aufgerufen. Aber selbst wenn - der Linker braucht hier Unterstützung. Man muß dann entweder jedes der einzeln linkbaren Häppchen als separates Objektcodefile in eine Library tun. So machen das Programmierumgebungen seit etlichen Dekaden. Oder man geht noch einen Schritt weiter und nutzt das --gc-sections Feature des GNU Linkers. Dann muß man aber auch selber dafür sorgen, daß jede Funktion und die von ihr benutzten statischen Variablen in eine eigene passend benannte Section kommen. Der C/C++ Compiler macht das auf Anfrage automatisch. In Assembler muß man es händisch tun. Im Gegenzug bekommt man die Entscheidung, was am Ende in das Binary gelinkt wird, so fein aufgelöst wie es nur überhaupt geht. Wie so oft zahlt es sich auch hier aus, wenn man mal hinter die Kulissen geschaut hat und weiß wie die Magie funktioniert. Leider wird allzu häufig das Gegenteil angepriesen. Man solle doch bitte die komfortablen IDE's benutzen, weil ohne wäre das doch alles viel zu kompliziert.
Du mußt dich schon an die Syntax halten, die man unter C für Assemblerobjekte benutzt. Nur dann kann der Linker sie erkennen und wegoptimieren. Um so ein Gerüst zu erhalten, kann man sich einfach in C eine Dummyfunktion schreiben und die nach Assembler compilieren lassen. Ansonsten gilt für den Linker, alles was an Assembler dasteht, muß auch ins Hex-File. In der Regel halten sich Assemblerprogrammierer aber nicht an Konventionen, sondern schreiben einfach drauflos und dann ist der Linker machtlos.
Georg schrieb: > Christian Berger schrieb: >> a) Unbenutzten Code automatisch zu entfernen ist quasi unmöglich. > > Komisch, das konnte mein Pascal-Compiler unter CP/M schon vor 30 Jahren, Ja richtig, das ist auch Pascal, da ist vieles sehr viel einfacher. Das ist eine wohldefinierte Sprache, da kann der Compiler das machen. In C oder C++ geht das einfach nicht, weil Du ja keine Units hast sondern so eine Art "Modul" bei dem Du nur sehr schwer sagen kannst, ob Du nicht doch irgendwo einen Zeiger auf eine Funktion bekommst. > Wenn ich z.B. die inch/mm-Umwandlung mit Multiplikationen ausführe und > auch sonst nirgends was dividiere, verschwindet die Division automatisch > aus der ausführbaren Datei. Ich habe das eigentlich auch immer für den > Normalfall gehalten. Von Pascal, C oder Assembler ist das übrigens > völlig unabhängig. a) Assembler macht so was grundsätzlich nicht. Die ganze Idee hinter Assembler ist, dass der Assembler genau das macht was da steht. b) Bestimmte Sprachen haben unterschiedliche Merkmale. In Pascal kannst Du zum Beispiel nur an die Stelle zurückspringen, von der eine Funktion oder Prozedur aufgerufen wurde. In C kannst Du an beliebige Stellen des Programms springen. Das macht das Problem deutlich komplexer. Hier sind Übrigens Vorträge von Felix von Leitner. Der hat meines Wissens nach schon mal seine eigene Libc geschrieben und versteht somit grob was davon, was er macht: http://www.fefe.de/source-code-optimization.pdf http://www.fefe.de/c++/c%2b%2b-talk.pdf
Christian Berger schrieb: > a) Assembler macht so was grundsätzlich nicht. Die ganze Idee hinter > Assembler ist, dass der Assembler genau das macht was da steht. Unsinn: der Assembler übersetzt wohl, was da steht, aber der Linker linkt nur das, was auch verwendet wird. Deine Kenntnisse müssen wohl älter sein als die 30 Jahre, die mein System alt ist - und mit Pascal hat das überhaupt nichts zu tun, du verstehst einfach nicht, wie Linken funktioniert. Es ist vollkommen wurscht, ob eine Routine in Pascal oder Assembler geschrieben ist, ich habe das immer nach Bedarf gemischt, und stell dir vor, der Linker von damals kommt damit zurecht. Irgenwelche Vorträge ändern daran nicht das geringste, sie sind höchstens falsch. Aber wahrscheinlich hast du sie einfach nicht verstanden. Im übrigen ist das völlig trivial. Ich schreibe eine Routine, nenne sie Routine1, dann übergibt der Compiler oder Assembler (!!!) das unaufgelöste Symbol Routine1 an den Linker nur, wenn die Routine irgendwo aufgerufen wird, sonst eben nicht. Und der Linker sieht dann auch keinen Grund sie dazuzulinken. Natürlich kann man absichtlich Blödsinn produzieren, z.B. in Assembler eine Routine als extern deklarieren, obwohl man sie nicht verwendet, aber warum sollte man sowas Dummes tun? Nur damit du recht behältst? Georg
Der Linker verbindet Teile. Dabei ist Grundvorausetzung für das Weglassen unbenützter Teile, daß es mehr als ein Teil gibt.
Ich glaube, wir sind uns hier alle einig, dass der Assambler (zumindest der GNU-Assembler, um den es hier geht) keine Optimierungen vornimmt, und dass der Linker prinzipiell zwar in der Lage ist, unbenutzte Codeteile wegzulassen, dies aber nur dann tut, wenn er den Code häppchenweise vorgesetzt bekommt. Diese Häppchen sind entweder Module (d.h. separate Objektdateien) oder Sections. Der Assemblerprogrammierer ist für die Aufteilung seines Codes in Module oder Sections selber verantwortlich und erhält dabei so gut wie keine Unterstützung durch den Assembler. Genau diese Unterstützung ist aber wohl das, was sich der TE erhofft hat, denn sein Code scheint aus einem einzigen Modul zu bestehen, das aus der Quellcodedatei main.S mit per #include eingebundenen weiteren Quellcodedateien gebildet wird. Da er vermutlich auch keine Aufteilung in mehrere Code-Sections vornimmt, kommt das Ganze als ein einzelner monolithischer Block beim Linker an, der deswegen nicht in der Lage ist, unbenutzte Teile wegzulassen. Auch in höheren Programmiersprachen ist die Modularisierung meist Sache des Programmierers. Allerdings bieten hier einige Compiler Unterstützung bei der Aufteilung in Sections. So stellt bspw. der GCC mit der Option -ffunction-sections jede C-Funktion in eine eigene Section, um dem Linker die Möglichkeit zu geben, unbenutzte Funktionen zu eliminieren. So etwas ist aber in Assembler nicht möglich, da hier kein Zwang zur Unterteilung des Programms in klar voneinander abgegrenzte Unterprogramme besteht. Während man (und auch der Compiler) in einer Hochsprache leicht entscheiden kann, welche Code-Teile zu einer Funktion A gehören und welche zu einer Funktion B, ist dies in Assembler nicht so ohne weiteres möglich, da dort nach Belieben kreuz und quer im Code hin- und hergesprungen werden kann. Die Aufteilung in einzelne Sections muss der Programmierer deswegen manuell vornehmen. Es gibt also zwei Lösungen des Problems: - Manuelle Modularisierung - Manuelle Sektionierung Genau diese beiden Alternativen hat der User asdfasd bereits ganz am Anfang in seinen beiden Antworten genannt. Ein optimierender Compiler hat zusätzlich noch die Möglichkeit der Dead-Code-Elimination, bei der durch Kontrollflussanalyse Codeteile ermittelt werden, die garantiert nie ausgeführt werden. Mit Dead-Code-Elimination kann auch monolithischer Code optimiert werden. Das Verfahren ist prinzipiell auch auf Assembler-Programme anwendbar. M.W. gibt bzw. gab es optimierende Assembler, die diese Optimierung implementiert haben. Der GNU-Assembler gehört aber nicht dazu.
Hmmm mit welchem Weg fährt man denn erfahrungsgemäss besser? Sections oder Module? Vom Gefühl her würde ich jetzt auf Module tippen, aber mir fehlt da die Erfahrung. Und wenn ich jetzt sagen wir den Code in 10+x Dateien aufgeteilt habe, wie gehe ich dann am besten weiter vor? Es sollte ja schon so sein, dass ich nicht für jede neue Datei das makefile ändern muss. Leider habe ich bisher keine gut lesbare "best practice" Dokumentation zu diesem doch sehr spezifischen Problem gefunden. Die meisten nutzen halt IDEs und drücken F7 :p
Der klassische Weg ist dieser: asdfasd schrieb: > Ach so: die übliche Methode, die überall funktioniert und nicht > irgendwelche erweiterten Features der GNU-Toolchain braucht, ist die, > jede optionale Funktion/Funktionenblock in eine eigene Datei zu packen, > all diese kleinen Dateien in ein .a-Library packen und dieses dann beim > finalen Linken anzugeben. Der Linker sucht sich dann nur die Files > heraus, die benötigt werden. Geht immer, auf allen Systemen. Diese Methode ist nur dann etwas ungeschickt, wenn du die verwendeten Hilfsfunktionen oft änderst, da wegen jeder kleinen Änderung oft ein Großteil der gesamten Bibliothek neu geschrieben werden muss. Aber im Zeitalter schneller Festplatten ist das kein schwerwiegendes Problem. Die Erstellung einer Bibliothek für die Hilfsfunktionen hat noch einen weiteren Vorteil: Sind die Funktionen so generisch, dass du sie in mehreren Projekten benutzen möchtest, legst für die Bibliothek ein eigenes Projekt mit einem eigenem Makefile an. Die fertig gebaute Bibliothek legst du bspw. als libauxfuncs.a an zentraler Stelle ab, wo alle anderen Projekt Zugriff darauf haben. Dann bindest du sie in jedes Projekt mit der Option -lauxfuncs ein. Damit stehen eventuelle Verbesserungen und Erweiterungen der Hilfsfunktionen sofort allen Projekten zur Verfügung. Du musst diese dann nur neu bauen, brauchst aber keine Änderungen an den jeweiligen Makefiles vorzunehmen. Du hast damit etwas Ähnliches geschaffen wie die Standardbibliothek in höheren Programmiersprachen. Da vom Linker immer nur die tatsächlich benötigten Module in die Executables eingebaut werden, darf die Bibliothek im Laufe der Zeit beliebig umfangreich werden, ohne dass dir dadurch Nachteile entstehen.
:
Bearbeitet durch Moderator
Christian S. schrieb: > Hmmm mit welchem Weg fährt man denn erfahrungsgemäss besser? Sections > oder Module? Vom Gefühl her würde ich jetzt auf Module tippen, aber mir > fehlt da die Erfahrung. Module ist halt das, was schon immer funktioniert. Sections on demand linken ist ein Zusatzfeature des GNU Linkers. Das bringt tendenziell nochmal kleinere executables. > Und wenn ich jetzt sagen wir den Code in 10+x Dateien aufgeteilt habe, > wie gehe ich dann am besten weiter vor? Es sollte ja schon so sein, dass > ich nicht für jede neue Datei das makefile ändern muss. Warum nicht? Wenn du eine neue Datei anlegen kannst (eigentlich zwei, denn du willst die Funktionen ja sicher auch in einem Header-File deklarieren) dann kannst du ja im selben Zug auch das Makefile ändern. Für ganz Faule bietet GNU make aber auch Wildcards.
> Hmmm mit welchem Weg fährt man denn erfahrungsgemäss besser? Ich würde immer Module nehmen. Die Function-Sections sind hauptsächlich für automatisch generierten Code gedacht und sind mMn eher eine Notlösung (da erstmal alles immer generiert wird und im letzten Schritt dann ein Großteil wieder weggeschmissen wird). > Und wenn ich jetzt sagen wir den Code in 10+x Dateien aufgeteilt habe, > wie gehe ich dann am besten weiter vor? Es sollte ja schon so sein, dass > ich nicht für jede neue Datei das makefile ändern muss. Na ja, eine Datei in einem Makefile eintragen ist nicht so die Welt, aber wie Axel schrieb, mit gmake's Wildcards könnte ein Makefile so aussehen:
1 | all: main |
2 | |
3 | main: main.o lib.a |
4 | cc -o main main.o lib.a |
5 | |
6 | lib_SRCS:=$(wildcard lib-*.S) |
7 | lib.a: lib.a(${lib_SRCS:.S=.o}) |
8 | |
9 | clean: |
10 | rm -f main main.o lib.a |
11 | |
12 | .PHONY: all clean |
main.S ist das Hauptprogramm und alle lib-xxx.S sind die Module. Die "all" and "clean" targets nur zur Vollständigkeit. Wenn einzelne Module allerdings selbst Build-Abhängigkeiten haben, kommst du nicht herum, das dem Makefile explizit mitzuteilen (z.B. lib-foo.S braucht ein foo-data.inc file, dann muss die Zeile "lib-foo.o: foo-data.inc" eingefügt werden). Statt die Module lib-XXX.S zu nennen, kannst du sie auch in ein Unterverzeichnis packen: lib_SRCS:=$(wildcard lib/*.S). Bei einigen Entwicklern ist dies sogar gängige Praxis, für jedes Unterverzeichnis ein Lib zu erstellen. Die Unterverzeichnisse sind dann "stand alone", mit eigenem Makefile, die einfach nur ein subdir/lib.a erstellen. Das Haupt-Makefile kümmert sich dann gar nicht mehr darum, welche .o-Files welches Unterverzeichnis bei welchen Optionen erzeugt - es linkt einfach das Hauptprogramm mit subdir1/lib.a subdir2/lib.a etc.
Vielen Dank für die sehr informativen Beiträge. Hat mir sehr geholfen. Ich werde mal ein paar der Ansätze ausprobieren und schauen, womit ich am besten fahre.
asdfasd schrieb: > Ich würde immer Module nehmen. Die Function-Sections sind hauptsächlich > für automatisch generierten Code gedacht und sind mMn eher eine > Notlösung (da erstmal alles immer generiert wird und im letzten Schritt > dann ein Großteil wieder weggeschmissen wird). Das sehe ich ganz anders. Auch bei Libraries ist es ja genauso, daß sie erstmal alles enthalten, was eventuell gebraucht werden könnte und erst beim Linken entschieden wird, was denn nun wirklich gebraucht wird. Wenn man einen Unterschied sehen will, dann liegt er eher darin, daß Libraries i.d.R. soweit unabhängig von dem sie verwendenden Programm sind, daß sie unabhängig übersetzt werden können. Sections im Programm hingegen werden bei jedem Übersetzungslauf wieder neu durch den Compiler gedreht, auch wenn sie sich nicht geändert haben. Historisch war das einer der Gründe warum man Programme in viele kleine Übersetzungseinheiten modularisiert - damit ein Compilerlauf kürzer dauert. Bei heutigen Maschinen ist das weniger problematisch. Die sind im Vergleich zu früher rattenschnell. Obwohl ich mir die Interna nicht angeschaut habe, würde ich sogar sagen, daß Sections sich genau die gleichen Mechanismen zu eigen machen wie Module. Man kann sich das vereinfacht so vorstellen, daß ein Modul (= Objektcodefile) genauso in Sections gesplittet wird wie eine Library in Module. Am Ende hat man einfach eine Menge an unabhängigen Objektcode- Schnipselchen, aus denen der Linker die benötigten heraussucht und zusammenfügt. > Na ja, eine Datei in einem Makefile eintragen ist nicht so die Welt, > aber wie Axel schrieb, mit gmake's Wildcards könnte ein Makefile so > aussehen: Yep. Ungefähr so hatte ich das gemeint.
Es fehlt dem GAS (Gnu Assembler) eben die LTO Option. Mit der können GCC/GNU Linker in meist weniger als einer Sekunde entscheiden, welche Maschinenbefehle zur Lösung eines Problems benötigt werden. Daß sie dabei manchmal anders optimieren, als das der ASM-Programmierer gemacht hätte ... was soll's, der braucht auch etwas länger ;-)
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.