C Linkerhacks + Modularisierung

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Hier sind viele GCC Spezifische hacks, die jederzeit aufhören könnten zu funktionieren.

Optionale Funktionen mittels weak Symbols

Weak symbols haben unter GCC folgende Eigenschaften:

  • Wenn ein externes Symbol als Weak deklariert wurde, und es existiert weder ein strong noch ein weak Symbol, wird die Adresse des Symbols als null aufgelöst und es gibt keinen Linkerfehler.
  • Wenn etwas als weak definiert wird, wird ein weak Symbol generiert. Existiert ein identisches Symbol das nicht weak, also strong ist, überschreibt dieses das weak symbol.
  • Existieren mehrere weak symbols, kann der Compiler jedes davon nehmen, und es gibt keinen Linkerfehler.
  • Wenn etwas als weak deklariert wurde, und eine Definition ohne weak folgt, wird das Symbol dennoch ein weak symbol.
  • Beim dynamische linken werden existierende weak symbos nicht neu aufgelöst.

Dies kann man nun ausnutzen um festzustellen, ob ein Symbol existiert. Beispiel:

// utils.h
#ifndef UTILS_H
#define UTILS_H

#define weak __attribute__ ((weak))

#endif

// modul.h
#ifndef MODUL_H
#define MODUL_H

#include <utils.h>

struct some_struct {
  int test;
};

#ifdef MODUL_C
#define W
#else
#define W weak
#endif

void W do_stuff();
extern W struct some_struct something;

#endif

// modul.c
#define MODULE_C
#include <stdio.h>
#include <modul.h>

void do_stuff(){
  printf("Modul\n");
}

struct some_struct something = {
  .test = 10
};

// main.c
#include <stdio.h>
#include <modul.h>

int main( void ){
  if( do_stuff )
    do_stuff();
  if( &something )
    printf("Something exists, test is %d\n", something.test);
  return 0;
}
$ gcc modul.c -c -o modul.o
$ gcc main.c -c -o main.o
$ gcc modul.o main.o -o prog1
$ gcc main.o -o prog2
$ ./prog1
Modul
Something exists, test is 10
$ ./prog2
$

Zur Runtime generierte linked Lists mittels weak symbols und __attribute__((constructor))

Es ist möglich die linked Lists bereits vor main zusammenzusetzen, ohne die einzelnen compilation units welche die einzelnen Elemente beinhalten referenzieren zu müssen. Ich nenne diese variation von linked lists loose lists. Diese methode braucht etwas mehr Speicher als der section list hack, ist jedoch zuverlässiger und diesem immer zu bevorzugen. Die funktionsweise ist wiefolgt; es wird ein struct für die Einträge erstellt, welches einen member für das zu speichernde Element und einen Pointer zum nächsten Eintrag enthält. Es wird ein weak pointer auf den ersten Eintrag erstellt. Bei mehreren identischen weak Symbols wird nach dem Compilieren nur ein Symbol übrig bleiben. Globale symbole ohne zuweisung werden automatisch mit 0 initialisiert. Ein Eintrag wird danach in einer Funktion mit attribut constructor beim start der Anwendung zur liste hinzugefügt.

Beispielimplementierung:

// loose_list.h
#ifndef LOOSE_LIST_H
#define LOOSE_LIST_H

#ifndef CONCAT_EVAL
#define CONCAT_EVAL(A,B) A ## B
#endif
#ifndef CONCAT
#define CONCAT(A,B) CONCAT_EVAL(A,B)
#endif

#define LOOSE_LIST_DECLARE(TYPE,NAME) \
  struct NAME; \
  struct NAME { \
    TYPE entry; \
    struct NAME* next; \
  }; \
  extern struct NAME* NAME; \
  __attribute__((weak)) struct NAME* NAME;

#define LOOSE_LIST_ADD(LIST,ENTRY) \
  static struct LIST CONCAT( list_item_, __LINE__ ) = {(ENTRY),0}; \
  static void CONCAT( list_append_, __LINE__ )() __attribute__((constructor)); \
  static void CONCAT( list_append_, __LINE__ )(){ \
    CONCAT( list_item_, __LINE__ ).next = (LIST); \
    LIST = &CONCAT( list_item_, __LINE__ ); \
  }

#endif

// example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H

#include "loose_list.h"

LOOSE_LIST_DECLARE(const char*, example_list)

#endif

// a.c
#include "example.h"

LOOSE_LIST_ADD(example_list, "fox")
LOOSE_LIST_ADD(example_list, "cat")

// b.c
#include "example.h"

LOOSE_LIST_ADD(example_list, "wulf")
LOOSE_LIST_ADD(example_list, "dog")

// main.c
#include <stdio.h>
#include "example.h"

int main(){
  for( struct example_list* it = example_list; it; it = it->next ){
    puts(it->entry);
  }
}
$ gcc main.c -o main; ./main
$ gcc a.c main.c -o main; ./main
cat
fox
$ gcc b.c main.c -o main; ./main
dog
wulf
$ gcc a.c b.c main.c -o main; ./main
dog
wulf
cat
fox

Bei Verwendung von Dynamischen Libraries hat mit dem oberen Beispiel das Hauptprogramm und die Libraries eigene Instanzen des Pointers auf das erste Element der Liste, und somit eigene Listen. Damit es auch bei DLLs richtig funktioniert, müssten die Makros angepasst werden, so das eine Kompilationsunit Funktionen zur Registrierung der Einträge und einen Pointer auf den Listen Anfang als strong symbol definiert, und so wieder alle die selbe Liste nutzen.

Vom Linker zusammengestellte Arrays mittels gcc-Attribut „section“

Wenn in gcc eine Variable mit __attribute__((section("name"))) in eine Section gepackt wird, wird ein Symbol __start_name und __stop_name angelegt. Wenn alle Variablen in einer Sektion vom selben Typ sind, sollten diese lückenlos hintereinander im Speicher zwischen __start_name und __stop_name liegen. Dafür gibt es jedoch keine Garantie, der Compiler tut dies momentan praktischerweise. Versuchen Sie nicht mehrere Entitäten auf einmal mit Arrays verschiedener Größen hinzuzufügen, sonst baut gcc manchmal Füllbytes anstelle eines Eintrags ein. Wenn alle Einträge der Section const sind, wird gcc die Section nicht als writable markieren. Ich empfehle, keine Annahmen zur Reihenfolge zu machen. Außerdem funktioniert dieser Hack auf einigen Plattformen, wie z.B. auf einem AVR, nicht, und sollte nicht verwendet werden.

Dies Methode kann verwendet werden, wenn man ein Interface hat, und mehrere Implementationen ermöglichen will. Man kann dann durch alle Implementationen des Arrays durch iterieren:

Beispiel:

// test.h
#pragma once

#define FOR_SECTION_LIST_HACK(NAME,ITERATOR) \
  extern __attribute__ ((weak)) const struct test __start_ ## NAME ## _section_list_hack[]; \
  extern __attribute__ ((weak)) const struct test __stop_ ## NAME ## _section_list_hack[]; \
  for( const struct NAME* ITERATOR = __start_ ## NAME ## _section_list_hack; \
       ITERATOR < __stop_ ## NAME ## _section_list_hack; ITERATOR++ )

#define SECTION_LIST_ENTRY_HACK(TYPE,NAME) \
  const struct TYPE NAME \
   __attribute__ ((section ( DPA_STRINGIFY( TYPE ## _section_list_hack ) ),used)) =

#define DPA_STRINGIFY(X) #X

struct test {
  const char* name;
};

// main.c
#include <stdio.h>
#include <stddef.h>
#include "test.h"

int main(){
  FOR_SECTION_LIST_HACK( test, it ){
    puts(it->name);
  }
}

// a.c
#include "test.h"

SECTION_LIST_ENTRY_HACK(test,a){
  "Entry 1"
};

// b.c
#include "test.h"

SECTION_LIST_ENTRY_HACK(test,b){
  "Entry 2"
};

SECTION_LIST_ENTRY_HACK(test,c){
  "Entry 3"
};
$ gcc main.c -o main; ./main
$ gcc a.c main.c -o main; ./main
Entry 1
$ gcc b.c main.c -o main; ./main
Entry 2
Entry 3
$ gcc a.c b.c main.c -o main; ./main
Entry 1
Entry 2
Entry 3

Man beachte, dass main.c keinen direkten Bezug zu a.c oder b.c aufweist. Die Anzahl Elemente im Array kann erst zur Runtime ermittelt werden. Die Section muss bei verwendung von -flto als /used/ markiert sein, weil der Linker diese sonst unter gewissen Umständen entfernt.

Linker fallen, worauf man aufpassen muss

Allgemeines

Beim linken ist es leichter, eine Datei zu vergessen. Falls Sie Symbole verwenden, um Abhängigkeiten darzustellen, beachten Sie, dass z.B. mit -Wl,-gc-sections bei fehlenden Symbolen in nicht verwendeten Funktionen teilweise keine undefined reference linker errors mehr generiert werden. Linken Sie das Projekt einmal mit und einmal ohne die Option, wenn sie dies verhindern wollen.

Libraries

Wenn Sie eine statische Library erstellen ist zu beachten, dass der Linker die Symbole statischer Libraries anders auflöst. Statt das alle Symbole aller object files betrachtet werden, werden bei jedem object file nur die Symbole vorhergehender object files betrachtet, und selbst strong Symbols können überschrieben werden. Dies kann zu unvorhergesehenen Problemen führen, bei welcher ein weak Symbol nicht aufgelöst, oder nicht durch ein strong symbol überschrieben wurde, etc. Die Lösung bei gcc ist beim Linken vor den betroffenen Libraries -Wl,--whole-archive und nach den Libraries -Wl,--no-whole-archive anzugeben. Dies erzeugt das gewohnte Verhalten für die Library und setzt es danach wieder zurück.

Bei dynamischen Libraries ist noch beachten, dass weak Symbols mit gleichem Namen im Hauptprogramm und Libraries jeweils nicht gemerged werden, und effektiv unterschiedliche Symbole mit eigenem Speicher sind.