Abfrage der Optionen -ffunction-section und -fdata-sections von gcc

Lesezeit: 8 Minuten

Benutzeravatar von Jay
Jay

Die unten auf der GCC-Seite für die Funktionsabschnitte und Datenabschnitte aufgeführten Optionen:

-ffunction-sections
-fdata-sections

Platzieren Sie jede Funktion oder jedes Datenelement in einem eigenen Abschnitt in der Ausgabedatei, wenn das Ziel beliebige Abschnitte unterstützt. Der Name der Funktion oder der Name des Datenelements bestimmt den Namen des Abschnitts in der Ausgabedatei. Verwenden Sie diese Optionen auf Systemen, auf denen der Linker Optimierungen durchführen kann, um die Referenzlokalität im Anweisungsbereich zu verbessern. Die meisten Systeme, die das ELF-Objektformat verwenden, und SPARC-Prozessoren, auf denen Solaris 2 ausgeführt wird, verfügen über Linker mit solchen Optimierungen. AIX kann diese Optimierungen in der Zukunft haben.

Verwenden Sie diese Optionen nur, wenn Sie erhebliche Vorteile daraus ziehen. Wenn Sie diese Optionen angeben, erstellen der Assembler und der Linker größere Objekt- und ausführbare Dateien und sind außerdem langsamer. Sie können gprof nicht auf allen Systemen verwenden, wenn Sie diese Option angeben, und Sie könnten Probleme beim Debuggen haben, wenn Sie sowohl diese Option als auch -g angeben.

Ich hatte den Eindruck, dass diese Optionen helfen, die Größe der ausführbaren Datei zu reduzieren. Warum sagt diese Seite, dass sie größere ausführbare Dateien erstellen wird? Übersehe ich etwas?

Benutzeravatar von Anton Staaf
Anton Staf

Interessanterweise mit -fdata-sections kann die wörtlichen Pools Ihrer Funktionen und damit Ihre Funktionen selbst größer machen. Ich habe dies insbesondere bei ARM bemerkt, aber es ist wahrscheinlich anderswo wahr. Die Binärdatei, die ich getestet habe, wuchs nur um ein Viertel Prozent, aber sie wuchs. Mit Blick auf die Demontage der geänderten Funktionen war klar warum.

Wenn alle BSS- (oder DATA-) Einträge in Ihrer Objektdatei einem einzelnen Abschnitt zugeordnet sind, kann der Compiler die Adresse dieses Abschnitts im Funktionsliteralpool speichern und Ladevorgänge mit bekannten Offsets von dieser Adresse in der Funktion generieren, um auf Ihre zuzugreifen Daten. Aber wenn Sie aktivieren -fdata-sections Es fügt jedes Stück BSS- (oder DATA-) Daten in einen eigenen Abschnitt ein, und da es nicht weiß, welche dieser Abschnitte später möglicherweise von der Garbage Collection erfasst werden oder in welcher Reihenfolge der Linker alle diese Abschnitte in das endgültige ausführbare Bild einfügt. Es kann keine Daten mehr laden, die Offsets von einer einzelnen Adresse verwenden. Stattdessen muss es einen Eintrag im Literal-Pool pro verwendeten Daten zuweisen, und sobald der Linker herausgefunden hat, was in das endgültige Bild hineingeht und wo, kann er diese Literal-Pool-Einträge mit der tatsächlichen Adresse von reparieren die Daten.

Also ja, sogar mit -Wl,--gc-sections das resultierende Bild kann größer sein, da der eigentliche Funktionstext größer ist.

Unten habe ich ein Minimalbeispiel hinzugefügt

Der folgende Code reicht aus, um das Verhalten zu sehen, von dem ich spreche. Bitte lassen Sie sich nicht von der flüchtigen Deklaration und Verwendung globaler Variablen abschrecken, die beide in echtem Code fragwürdig sind. Hier sorgen sie für die Erstellung von zwei Datenabschnitten, wenn -fdata-sections verwendet wird.

static volatile int head;
static volatile int tail;

int queue_empty(void)
{
    return head == tail;
}

Die für diesen Test verwendete Version von GCC ist:

gcc version 6.1.1 20160526 (Arch Repository)

Zunächst erhalten wir ohne -fdata-sections Folgendes.

> arm-none-eabi-gcc -march=armv6-m \
                    -mcpu=cortex-m0 \
                    -mthumb \
                    -Os \
                    -c \
                    -o test.o \
                    test.c

> arm-none-eabi-objdump -dr test.o

00000000 <queue_empty>:
 0: 4b03     ldr   r3, [pc, #12]   ; (10 <queue_empty+0x10>)
 2: 6818     ldr   r0, [r3, #0]
 4: 685b     ldr   r3, [r3, #4]
 6: 1ac0     subs  r0, r0, r3
 8: 4243     negs  r3, r0
 a: 4158     adcs  r0, r3
 c: 4770     bx    lr
 e: 46c0     nop                   ; (mov r8, r8)
10: 00000000 .word 0x00000000
             10: R_ARM_ABS32 .bss

> arm-none-eabi-nm -S test.o

00000000 00000004 b head
00000000 00000014 T queue_empty
00000004 00000004 b tail

Aus arm-none-eabi-nm wir sehen, dass queue_empty 20 Bytes lang ist (14 hex), und die arm-none-eabi-objdump Die Ausgabe zeigt, dass es am Ende der Funktion ein einzelnes Verschiebungswort gibt, es ist die Adresse des BSS-Abschnitts (der Abschnitt für nicht initialisierte Daten). Die erste Anweisung in der Funktion lädt diesen Wert (die Adresse des BSS) in r3. Die nächsten beiden Befehle werden relativ zu r3 geladen, jeweils um 0 und 4 Bytes versetzt. Diese beiden Lasten sind die Lasten der Werte von Head und Tail. Wir können diese Offsets in der ersten Spalte der Ausgabe von sehen arm-none-eabi-nm. Das nop am Ende der Funktion ist die Wortausrichtung der Adresse des Literalpools.

Als nächstes werden wir sehen, was passiert, wenn -fdata-sections hinzugefügt wird.

arm-none-eabi-gcc -march=armv6-m \
                  -mcpu=cortex-m0 \
                  -mthumb \
                  -Os \
                  -fdata-sections \
                  -c \
                  -o test.o \
                  test.c

arm-none-eabi-objdump -dr test.o

00000000 <queue_empty>:
 0: 4b03     ldr   r3, [pc, #12]    ; (10 <queue_empty+0x10>)
 2: 6818     ldr   r0, [r3, #0]
 4: 4b03     ldr   r3, [pc, #12]    ; (14 <queue_empty+0x14>)
 6: 681b     ldr   r3, [r3, #0]
 8: 1ac0     subs  r0, r0, r3
 a: 4243     negs  r3, r0
 c: 4158     adcs  r0, r3
 e: 4770     bx    lr
    ...
             10: R_ARM_ABS32 .bss.head
             14: R_ARM_ABS32 .bss.tail

arm-none-eabi-nm -S test.o

00000000 00000004 b head
00000000 00000018 T queue_empty
00000000 00000004 b tail

Wir sehen sofort, dass sich die Länge von queue_empty um vier Bytes auf 24 Bytes (18 Hex) erhöht hat und dass nun zwei Verschiebungen im Literalpool von queue_empty durchgeführt werden müssen. Diese Verschiebungen entsprechen den Adressen der zwei BSS-Abschnitte, die erstellt wurden, einer für jede globale Variable. Hier müssen zwei Adressen vorhanden sein, da der Compiler die relative Position nicht kennen kann, an der der Linker die beiden Abschnitte letztendlich einfügt. Wenn wir uns die Anweisungen am Anfang von queue_empty ansehen, sehen wir, dass es eine zusätzliche Last gibt, den Compiler muss separate Ladepaare generieren, um die Adresse des Abschnitts und dann den Wert der Variablen in diesem Abschnitt zu erhalten. Die zusätzliche Anweisung in dieser Version von queue_empty macht den Hauptteil der Funktion nicht länger, es nimmt nur die Stelle ein, die zuvor ein Nop war, aber das wird im Allgemeinen nicht der Fall sein.

  • Warum ist der Funktionstext größer, nachdem der Linker die Adressen korrigiert hat? Können Sie eine Assembly-Ausgabe hinzufügen, die den Unterschied erklärt?

    – Jakow Galka

    1. Juni 2016 um 14:35 Uhr

  • Der Compiler muss mehrere Ladevorgänge generieren, um die Adressen der mehreren Datenabschnitte zu erhalten, anstelle eines einzigen Ladevorgangs für den Beginn des monolithischen Datenabschnitts im Fall von Nicht-Fdata-Abschnitten. Ich werde ein Beispiel hinzufügen, wie Sie vorschlagen.

    – Anton Staaf

    24. August 2016 um 21:56 Uhr

  • Danke für die Antwort. Ich bin neugierig, wissen Sie, ob Sie verwenden -ffunction-sections -fdata-sections ist praktisch gleichbedeutend mit der Verwendung einer Quelldatei pro Symbol (Funktion oder Daten) oder gibt es technische Unterschiede?

    – PSkočik

    29. September 2017 um 12:39 Uhr

  • @PSkocik, ich vermute, dass es einige geringfügige Unterschiede geben würde, wie Symbole aus Bibliotheken exportiert werden oder wie statischer oder gemeinsamer Speicher gehandhabt wird. Aber es ist eine ziemlich gute Annäherung erster Ordnung.

    – Anton Staaf

    2. Oktober 2017 um 21:49 Uhr

  • Gilt alles Beschriebene auch für statisch gelinkte ausführbare Dateien, die mit diesen Optionen verwendet werden, oder ändert sich dann etwas?

    – nh2

    4. April 2019 um 3:23 Uhr

leppies Benutzeravatar
Leppie

Wenn Sie diese Compileroptionen verwenden, können Sie die Linkeroption hinzufügen -Wl,--gc-sections Dadurch wird der gesamte unbenutzte Code entfernt.

  • Wenn wir die -gc-Abschnitte nicht übergeben, hat dies überhaupt keine Auswirkungen. Rechts? Gibt es auch andere Optionen, die den gleichen Effekt erzielen können?

    – Jay

    26. November 2010 um 3:58 Uhr

  • @Jay: Richtig, Sie werden am Ende eine Binärdatei mit viel unbenutztem Code haben. Zur Profilierung kann es entfernt werden. Funktioniert immer noch gut zum Debuggen.

    – lepi

    26. November 2010 um 4:50 Uhr

Benutzeravatar von fwhacking
verdammt

Sie können verwenden -ffunction-sections und -fdata-sections auf statische Bibliotheken, wodurch die Größe der statischen Bibliothek erhöht wird, da jede Funktion und globale Datenvariable in einen separaten Abschnitt gestellt wird.

Und dann verwenden -Wl,--gc-sections auf das Programm, das mit dieser statischen Bibliothek verknüpft wird, wodurch nicht verwendete Abschnitte entfernt werden.

Daher ist die endgültige Binärdatei kleiner als ohne diese Flags.

Seien Sie jedoch vorsichtig, da -Wl,--gc-sections kann Dinge kaputt machen.

  • Wie können -Wl, –gc-Abschnitte Dinge kaputt machen? irgendein beispiel?

    – Kumpel

    19. Januar 2017 um 4:48 Uhr

  • @buddy “Die Dinge werden kaputt gehen, wenn das Programm spezielle Abschnitte verwendet, die in das Programm gezogen werden sollen, wenn andere Teile von .o-Dateien hineingezogen werden, selbst wenn diese “magischen” Abschnitte selbst nicht referenziert werden”: elinux.org/images/2/2d/ELC2010-gc-sections_Denys_Vlasenko.pdf

    –Andreas

    29. September 2020 um 12:19 Uhr

Benutzeravatar von Rei Vilo
Rei Vilos

Ich erhalte bessere Ergebnisse, wenn ich einen zusätzlichen Schritt hinzufüge und eine erstelle .a Archiv:

  1. Zuerst werden gcc und g++ verwendet -ffunction-sections -fdata-sections Flaggen
  2. dann alle .o Objekte werden in ein gelegt .a Archiv mit ar rcs file.a *.o
  3. schließlich wird der Linker mit aufgerufen -Wl,-gc-sections,-u,main Optionen
  4. für alle ist die optimierung auf eingestellt -Os.

Ich habe es vor einiger Zeit ausprobiert und wenn ich mir die Ergebnisse ansehe, scheint es, dass die Größenzunahme von der Reihenfolge der Objekte mit unterschiedlicher Ausrichtung herrührt. Normalerweise sortiert der Linker Objekte, um die Auffüllung zwischen ihnen klein zu halten, aber es sieht so aus, als ob das nur innerhalb eines Abschnitts funktioniert, nicht über die einzelnen Abschnitte hinweg. Daher erhalten Sie häufig zusätzliche Polsterung zwischen den Datenabschnitten für jede Funktion, wodurch der Gesamtraum vergrößert wird.

Bei einer statischen Bibliothek mit -Wl, -gc-Abschnitten wird das Entfernen unbenutzter Abschnitte die kleine Erhöhung jedoch höchstwahrscheinlich mehr als wettmachen.

1402480cookie-checkAbfrage der Optionen -ffunction-section und -fdata-sections von gcc

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy