Mein Blog    Projekte    Archiv    Impressum

libopencm3 mit C++ verwenden

Jetzt, wo ich ein schönes Open-Source-Debugging-Setup für STM32-Mikrocontroller habe, ist der letzte Schritt zu vollständiger Open-Source-Entwicklung, die von ST mitgelieferten Bibliotheken durch die Open-Source-Bibliothek libopencm3 zu ersetzen.

Checkout und erste Anpassungen

Als Ausgangspunkt für meine Experimente verwende ich das offizielle „Hello-World“ von libopencm3: Das libopencm3-miniblink!

Submakefiles

Das Projekt baut alle Targets auf einmal, das brauche ich nicht. Ich will nur genau ein Target. Zunächst lösche ich alle Submakefiles bis auf boards.stm32.mk. In boards.stm32.mk lösche ich alles, was nicht zum STM32F103C8T6 passt (Cortex M3, Familie STM32F1). Und dort lasse ich spezifisch den Teil für die „Bluepill“ (GPIOC, GIPIO13) stehen.

Dann sieht das boards.stm32.mk-Makefile nur noch so aus:

LFLAGS_STM32=$(LFLAGS) template_stm32.c -T ld.stm32.basic

# STM32F1 starts up with HSI at 8Mhz
STM32F1_CFLAGS=$(M3_FLAGS) -DSTM32F1 -DLITTLE_BIT=200000 $(LFLAGS_STM32) -lopencm3_stm32f1

define RAWMakeBoard
	mkdir -p $(OD)/$(7)
	$(CC) -DRCC_LED1=RCC_$(1) -DPORT_LED1=$(1) -DPIN_LED1=$(2) \
		$(if $(5),-DRCC_LED2=RCC_$(5) -DPORT_LED2=$(5) -DPIN_LED2=$(6),) \
		$(3) -o $(OD)/$(7)/$(4)
endef

define MakeBoard
BOARDS_ELF+=$(OD)/$(5)/$(1).elf
BOARDS_BIN+=$(OD)/$(5)/$(1).bin
BOARDS_HEX+=$(OD)/$(5)/$(1).hex
$(OD)/$(5)/$(1).elf: template_stm32.c libopencm3/lib/libopencm3_$(5).a
	@echo "  $(5) -> Creating $(OD)/$(5)/$(1).elf"
	$(call RAWMakeBoard,$(2),$(3),$(4),$(1).elf,$(6),$(7),$(5))
endef

define stm32f1board
	$(call MakeBoard,$(1),$(2),$(3),$(STM32F1_CFLAGS),stm32f1,$(4),$(5))
endef

# STM32F1 boards
$(eval $(call stm32f1board,bluepill,GPIOC,GPIO13))

Damit funktioniert der Build-Prozess, und am Ende erhalte ich genau das eine, erwartete Binary. Im ersten Durchlauf wurde zunächst das libopencm3-Repository (im Miniblink-Repository als Git-Submodul integriert) geclont und libopencm3 vollständig gebaut. Das dauerte einige Zeit. Mit einem schnellen make clean und anschließendem erneuten make zeigte sich aber: Das passiert nur beim ersten Mal. Die folgenden Build-Vorgänge beschränken sich auf das eigentliche Projekt und sind entsprechend schnell.

Einzelmakefile.

Die ganzen Methoden in dem gekürzten Makefile kann ich jetzt noch direkt auflösen. (Und dabei direkt das Schreiben des stm32f1-Unterordners unterlassen, weil es nur noch das eine Target gibt.) Dann lande ich bei:

LFLAGS_STM32=$(LFLAGS) template_stm32.c -T ld.stm32.basic

# STM32F1 starts up with HSI at 8Mhz
STM32F1_CFLAGS=$(M3_FLAGS) -DSTM32F1 -DLITTLE_BIT=200000 $(LFLAGS_STM32) -lopencm3_stm32f1

BOARD = bluepill
FAMILY = stm32f1

BOARDS_ELF+=$(OD)/$(BOARD).elf
BOARDS_BIN+=$(OD)/$(BOARD).bin
BOARDS_HEX+=$(OD)/$(BOARD).hex

$(OD)/$(BOARD).elf: template_stm32.c libopencm3/lib/libopencm3_$(FAMILY).a
	@echo "  $(FAMILY) -> Creating $(OD)/$(BOARD).elf"
	mkdir -p $(OD)
	$(CC) -DRCC_LED1=RCC_GPIOC -DPORT_LED1=GPIOC -DPIN_LED1=GPIO13 \
		$(STM32F1_CFLAGS) -o $(OD)/$(BOARD).elf

IntelliSense (Syntax-Highlighting in VSCode)

Das Syntax-Highlighting funktioniert nicht komplett „out of the box“. Die Defines im Makefile werden nicht erkannt. Lösung: Makefile doch wieder per bear ausführen (tasks.json anpassen) und dann bei IntelliSense hinterlegen, wo die compile_commands.json liegt:

.vscode/c_cpp_properties.json:

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [],
            "cStandard": "c17",
            "cppStandard": "gnu++17",
            "intelliSenseMode": "linux-gcc-x64",
            "configurationProvider": "ms-vscode.makefile-tools",
            "compileCommands": [
                "${workspaceFolder}/compile_commands.json"
            ]
        }
    ],
    "version": 4
}

C-Datei anpassen

Hier sind noch ein paar Makros, die in meinem Single-Board-Setup nicht mehr gebraucht werden. Die lösche ich raus:

  • #if defined(RCC_LED2) kommt bei meinem Board nicht vor.
  • #if defined(STM32F1) ist dauerhaft an, also kann die #if/#else-Abzweigung weg.

Und damit habe ich dann das sehr überschaubare C-File:

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>

int main(void) {
    rcc_periph_clock_enable(RCC_LED1);
	gpio_set_mode(PORT_LED1, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, PIN_LED1);
	gpio_set(PORT_LED1, PIN_LED1);
	while(1) {
		/* wait a little bit */
		for (int i = 0; i < LITTLE_BIT; i++) {
			__asm__("nop");
		}
		gpio_toggle(PORT_LED1, PIN_LED1);
	}
}

Erster Test

Über make lässt sich dieser Code bauen, und auch das Debuggen funktioniert. Die LED blinkt wie erwartet.

Umbau auf C++

Makefile

Um das Ganze dann als C++ zu kompilieren, habe ich das Makefile um den folgenden Block ergänzt:

## C++ Settings / overrides:
SFLAGS_CPP= --static -nostartfiles -g3 -Os
SFLAGS_CPP+= -fno-common -ffunction-sections -fdata-sections
SFLAGS_CPP+= -I./libopencm3/include -L./libopencm3/lib
LFLAGS_CPP+=-Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group

CPP_FLAGS_ADDITIONAL= -fno-exceptions

# Actual overrides (comment out, to switch back to C):
M3_FLAGS = $(SFLAGS_CPP) -mcpu=cortex-m3 -mthumb -msoft-float
CC=$(PREFIX)g++
LFLAGS_STM32=$(LFLAGS_CPP) template_stm32.c -T ld.stm32.basic $(CPP_FLAGS_ADDITIONAL)
## End of C++ overrides

An tatsächlicher Änderung bedeutet das:

  • Die -std=c11-Einstellung fällt weg.
  • Das -fno-exceptions-Flag kommt hinzu.
  • Der Compiler wird von gcc auf g++ umgestellt.

Mit diesen Änderungen ist das Projekt direkt wieder kompilierbar. Sowohl mit C als auch mit C++ erhalte ich:

arm-none-eabi-size bin/bluepill.elf
   text    data     bss     dec     hex filename
   1028       0       0    1028     404 bin/bluepill.elf

Bzw.:

wombat@T470s:~/VSCode/libopencm3-miniblink$ du -b bin/bluepill.hex
2952    bin/bluepill.hex

C- bzw. CPP-Datei

Dann habe ich mein Minimalset an C++-exklusiven Sprachelementen in die template_stm32.c integriert:

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <vector>

int main(void) {
    rcc_periph_clock_enable(RCC_LED1);
	gpio_set_mode(PORT_LED1, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, PIN_LED1);
	gpio_set(PORT_LED1, PIN_LED1);
	std::vector<bool> ledmuster {true, false, true, false, true, false, false, false, false};
	while(1) {
		
		for(auto ledstate : ledmuster){
      		if(!ledstate) {
				// SET -> LED ist aus; RESET -> LED ist an.
				gpio_set(PORT_LED1, PIN_LED1);
			} else {
				gpio_clear(PORT_LED1, PIN_LED1);
			}
      		/* wait a little bit */
			for (int i = 0; i < LITTLE_BIT; i++) {
				__asm__("nop");
			}
    	}
	}
}

Beim Bauen bekomme ich dann den Fehler:

/usr/lib/gcc/arm-none-eabi/14.2.1/../../../arm-none-eabi/bin/ld: /usr/lib/gcc/arm-none-eabi/14.2.1/../../../arm-none-eabi/lib/thumb/v7-m/nofp/libc.a(libc_a-signalr.o): in function `_getpid_r':
/build/reproducible-path/newlib-4.5.0.20241231/build/arm-none-eabi/thumb/v7-m/nofp/newlib/../../../../../../newlib/libc/reent/signalr.c:83:(.text+0x28): undefined reference to `_getpid'
/usr/lib/gcc/arm-none-eabi/14.2.1/../../../arm-none-eabi/bin/ld: (_getpid): Unknown destination type (ARM/Thumb) in /usr/lib/gcc/arm-none-eabi/14.2.1/../../../arm-none-eabi/lib/thumb/v7-m/nofp/libc.a(libc_a-signalr.o)
/build/reproducible-path/newlib-4.5.0.20241231/build/arm-none-eabi/thumb/v7-m/nofp/newlib/../../../../../../newlib/libc/reent/signalr.c:83:(.text+0x28): dangerous relocation: unsupported relocation
collect2: error: ld returned 1 exit status

Und einige weitere Fehler dieser Art zu Methoden wie _exit, _sbrk_r, _write, _close etc. Dies ließ sich, durch das zusätzliche Linker-Setting --specs=nosys.specs verbessern. (Ich hatte auch --specs=nano.specs versucht, so wie ich es in meinem CubeMX-C++-Projekt verwendet hatte. Das funktionierte hier nicht, also ging ich auf nosys.specs. Ein Fehler, wie sich unten zeigen wird.) Dadurch wurden aus den Fehlern Warnungen:

/usr/lib/gcc/arm-none-eabi/14.2.1/../../../arm-none-eabi/bin/ld: /usr/lib/gcc/arm-none-eabi/14.2.1/../../../arm-none-eabi/lib/thumb/v7-m/nofp/libc.a(libc_a-signalr.o): in function `_getpid_r':
/build/reproducible-path/newlib-4.5.0.20241231/build/arm-none-eabi/thumb/v7-m/nofp/newlib/../../../../../../newlib/libc/reent/signalr.c:83:(.text+0x28): warning: _getpid is not implemented and will always fail

Das sollte ich mir später noch einmal genauer anschauen.

Außerdem hatte ich am Anfang des Logs den Hinweis:

/usr/lib/gcc/arm-none-eabi/14.2.1/../../../arm-none-eabi/bin/ld: bin/bluepill.elf section `.text' will not fit in region `rom'
/usr/lib/gcc/arm-none-eabi/14.2.1/../../../arm-none-eabi/bin/ld: region `rom' overflowed by 10824 bytes

Um das zu vermeiden, passe ich die Datei „ld.stm32.basic“ an.

MEMORY
{
	rom (rx) : ORIGIN = 0x08000000, LENGTH = 8K
	ram (rwx) : ORIGIN = 0x20000000, LENGTH = 2K
}

Hier ändere ich den Minimalkonsens, der für alle STM32 passt und für eine blinkende LED in C ausreicht, auf die tatsächlichen Werte des STM32F103C8T6: 64kB ROM und 20kB RAM. Damit lässt sich das Programm dann vollständig bauen. Es lässt sich auch auf einen echten Mikrocontroller übertragen und funktioniert erwartungsgemäß.

Optimierungen

Nun gilt es einerseits, die oben genannten Warnungen loszuwerden. Außerdem ist das Binary (für eine einfache, blinkende LED) extrem groß:

arm-none-eabi-size bin/bluepill.elf
   text    data     bss     dec     hex filename
  17744    1400     412   19556    4c64 bin/bluepill.elf

du -b bin/bluepill.hex
53926   bin/bluepill.hex

Bevor ich anfange, da herumzustochern, vereinfache ich das Makefile deutlich und teile Kompilieren und Linken in zwei Schritte auf:

DEFINES= -DRCC_LED1=RCC_GPIOC -DPORT_LED1=GPIOC -DPIN_LED1=GPIO13 -DLITTLE_BIT=200000 -DSTM32F1
COMMON= -mcpu=cortex-m3 -mthumb -msoft-float

CC_FLAGS=  -g3 -Os
CC_FLAGS+= -fno-common -ffunction-sections -fdata-sections
CC_FLAGS+= -fno-exceptions
CC_FLAGS+= -I./libopencm3/include

LD_FLAGS=  --static -nostartfiles
LD_FLAGS+= -T ld.stm32.basic -L./libopencm3/lib
LD_FLAGS+= -Wl,--start-group -lc -lstdc++ -lgcc -lnosys -Wl,--end-group
LD_FLAGS+= --specs=nosys.specs

bin/bluepill.elf: bin/main.o
	arm-none-eabi-g++ $(COMMON) $(LD_FLAGS) bin/main.o -lopencm3_stm32f1 -o bin/bluepill.elf
	arm-none-eabi-size bin/bluepill.elf

# Anscheinend muss -lopencm3_stm32f1 zwischen bin/main.o und -o ... stehen, sonst funktioniert es nicht. Aber warum?!?

bin/main.o: main.cpp
	arm-none-eabi-g++ $(COMMON) $(DEFINES) $(CC_FLAGS) -c main.cpp -o bin/main.o

clean:
	rm -f bin/main.o
	rm -f bin/bluepill.elf

Erste Erkenntnisse

  • Die Reihenfolge, in der ich die Objekte/Bibliotheken an den Linker übergebe, spielt eine Rolle!
  • Anstatt -lopencm3_stm32f1 kann ich auch direkt libopencm3/lib/libopencm3_stm32f1.a anbieten, dann brauche ich auch -L./libopencm3/lib nicht mehr.

libopencm3 Template Project

Als mögliche Abkürzung, versuche ich mal, meinen Code über das Template-Projekt zu bauen. Änderungen, die ich vornehme:

  • my-common-code Verzeichnis löschen
  • my-project.c in my-project.cxx umbenennen und meinen Code pasten
  • Die Defines direkt im Code einsetzen, um nicht zu viel am Makefile ändern zu müssen
  • Im Makefile die CFILES- und AFILES-Zeilen löschen und eine CXXFILES = my-project.cxx-Zeile einfügen
  • Im Makefile den Chipnamen anpassen (OOCD_FILE ist egal, weil ich kein OpenOCD verwende.)

Ergebniss: Diverse Fehler, dass C++-Methoden fehlen (z. B. undefined reference to `operator new(unsigned int)’).

Weitere Änderung: In rules.mk die Zeile

LDLIBS += -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group

zu

LDLIBS += -Wl,--start-group -lc -lstdc++ -lgcc -lnosys -Wl,--end-group

umbauen. Also genau so, wie oben in meinem Minimal-Makefile auch. Aber hier funktioniert es mit den nano.specs während es oben nur mit den nosys.specs funktioniert. Und entsprechend erhalte ich auch:

wombat@T470s:~/VSCode/blinky2/my-project$ arm-none-eabi-size awesomesauce.elf 
   text    data     bss     dec     hex filename
  10092      88      32   10212    27e4 awesomesauce.elf

Also ein deutlich kleineres Binary. Und hier ist sogar das Exception-Handling noch mit drin.

Weitere Erkenntnisse

  1. nosys.specs ist umfangreicher als nano.specs. Das führt zu größeren Binaries und kann über Fehlkonfigurationen beim Linken hinwegtäuschen. Konkret hatte ich hier nosys.specs genommen, weil es damit funktionierte, mit nano.specs aber nicht. Ich hätte anstatt dieser Try-and-Error-Methodik lieber direkt untersuchen sollen, warum die nano.specs (die in meiner Variation des CubeMX-Codes funktionieren!) hier nicht funktionierten.
  2. Bei statischem Linken ist die Reihenfolge, in der dem Linker die Bibliotheken und Objekte mitgeteilt werden, relevant! Die Liste wird von links nach rechts durchgegangen, und enthaltene, aber unbenutzte Methoden und Symbole werden verworfen. Falls diese in weiter rechts gelisteten Bibliotheken oder Objekten doch noch benötigt werden, kommt es zu „undefined reference“-Fehlern.
  3. Dieses Verhalten kann umgangen werden, wenn --start-group und --end-group verwendet wird (Siehe die ld-Doku, Strg+F: --start-group archives --end-group). Da hier iterativ alle Bibliotheken immer wieder durchlaufen werden, bis alle Referenzen erfolgreich aufgelöst wurden, führt das zu einem erhöhten Aufwand für den Linker. Zumindest bei meinen Versuchen ergab sich aber kein spürbarer Unterschied in der Geschwindigkeit des Linkers.

Mit diesen Erkenntnissen umgebaut, sieht mein Minimal-Makefile nun so aus:

DEFINES= -DRCC_LED1=RCC_GPIOC -DPORT_LED1=GPIOC -DPIN_LED1=GPIO13 -DLITTLE_BIT=200000 -DSTM32F1
COMMON= -mcpu=cortex-m3 -mthumb -msoft-float

CC_FLAGS=  -g3 -Os
CC_FLAGS+= -fno-common -ffunction-sections -fdata-sections
CC_FLAGS+= -fno-exceptions
CC_FLAGS+= -I./libopencm3/include

LD_FLAGS=  --static -nostartfiles
LD_FLAGS+= -T ld.stm32.basic -L./libopencm3/lib
LD_FLAGS+= -Wl,--start-group -lc -lstdc++ -lgcc -lnosys bin/main.o -lopencm3_stm32f1 -Wl,--end-group
LD_FLAGS+= --specs=nano.specs

bin/bluepill.elf: bin/main.o
	arm-none-eabi-gcc $(COMMON) $(LD_FLAGS) -o bin/bluepill.elf
	arm-none-eabi-size bin/bluepill.elf

bin/main.o: main.cpp
	arm-none-eabi-g++ $(COMMON) $(DEFINES) $(CC_FLAGS) -c main.cpp -o bin/main.o

clean:
	rm -f bin/main.o
	rm -f bin/bluepill.elf

Und damit bekomme ich dann ein Binary in einer von mir erwarteten Größenordnung:

arm-none-eabi-size bin/bluepill.elf
   text    data     bss     dec     hex filename
   2776      80      20    2876     b3c bin/bluepill.elf

Jetzt schalte ich zusätzlich noch die Linker-Garbage-Collection an. Dazu werden die LD_FLAGS um -Wl,--gc-sections ergänzt. Das Ergebnis ist:

arm-none-eabi-size bin/bluepill.elf
   text    data     bss     dec     hex filename
   2200      80      20    2300     8fc bin/bluepill.elf

Also noch einmal merklich kleiner.

Fazit

Ich habe jetzt einiges über die Interaktion zwischen gcc/g++ und ld gelernt und bin in der Lage, auch mit libopencm3 C++-Projekte umzusetzen. Als Ausgangspunkt dafür, kann ich sowohl mein Minimal-Makefile, als auch das Makefile aus dem libopencm3-Template (mit den oben genannten Ergänzungen) verwenden.

Wäre das auch einfacher gegangen?

Vielleicht schon, wenn ich direkt das Template verwendet hätte, anstatt zu versuchen, das miniblink-Projekt umzubiegen. Aber dann hätte ich dabei auch deutlich weniger gelernt.

Notiz

Ein Ansatz für mögliche weitere Optimierungen ist es, sich einen Überblick über die Methoden im entstehenden Binary zu verschaffen. Mit einem einfachen Kommandozeilenaufruf (entnommen aus diesem Stackoverflow-Post) lassen sich sämtliche Funktionen in der ELF-Datei auflisten:

readelf -sW bin/bluepill.elf | awk '$4 == "FUNC"' | c++filt

Hier passieren drei Dinge:

  1. readelf öffnet die ELF-Datei, -s zeigt die Symbole an, und -W sorgt dafür, dass lange Namen nicht abgeschnitten werden.
  2. awk $4 == "FUNC" filtert, sodass nur Zeilen angezeigt werden, bei denen in der vierten Spalte "FUNC" steht.
  3. c++filt sorgt dafür, dass die Namen ordentlich angezeigt werden. (z. B. operator new(unsigned int) statt _Znwj oder std::get_new_handler() statt _ZSt15get_new_handlerv)

Die Ausgabe sieht dann, bei meinem finalen Stand, so aus:

wombat@T470s:~/VSCode/libopencm3-miniblink$ readelf -sW bin/bluepill.elf | awk '$4 == "FUNC"' | c++filt
    60: 080005ed    76 FUNC    LOCAL  DEFAULT    1 sbrk_aligned
    95: 0800084d    16 FUNC    GLOBAL DEFAULT    1 _getpid
    96: 08000291   148 FUNC    WEAK   DEFAULT    1 reset_handler
    97: 080007fd    40 FUNC    GLOBAL DEFAULT    1 _kill_r
    98: 0800028b     2 FUNC    WEAK   DEFAULT    1 usart3_isr
    99: 0800028b     2 FUNC    WEAK   DEFAULT    1 rtc_isr
   100: 080003b5    74 FUNC    GLOBAL DEFAULT    1 _signal_r
   101: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim7_isr
   102: 080007f9     2 FUNC    GLOBAL DEFAULT    1 __malloc_unlock
   103: 0800028b     2 FUNC    WEAK   DEFAULT    1 adc1_2_isr
   104: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim1_trg_com_isr
   106: 0800028b     2 FUNC    WEAK   DEFAULT    1 usb_hp_can_tx_isr
   108: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim6_isr
   110: 080001fd   106 FUNC    GLOBAL DEFAULT    1 gpio_set_mode
   111: 0800028b     2 FUNC    WEAK   DEFAULT    1 usb_wakeup_isr
   112: 0800028b     2 FUNC    GLOBAL DEFAULT    1 blocking_handler
   113: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim5_isr
   114: 0800028b     2 FUNC    WEAK   DEFAULT    1 otg_fs_isr
   115: 0800028b     2 FUNC    WEAK   DEFAULT    1 spi1_isr
   116: 0800028b     2 FUNC    WEAK   DEFAULT    1 exti2_isr
   117: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma1_channel6_isr
   118: 0800028d     2 FUNC    GLOBAL DEFAULT    1 null_handler
   119: 0800028b     2 FUNC    WEAK   DEFAULT    1 can_rx1_isr
   121: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma1_channel5_isr
   122: 08000369    16 FUNC    GLOBAL DEFAULT    1 malloc
   123: 08000401    80 FUNC    GLOBAL DEFAULT    1 _raise_r
   124: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma2_channel5_isr
   125: 0800028b     2 FUNC    WEAK   DEFAULT    1 usart1_isr
   126: 08000825     4 FUNC    GLOBAL DEFAULT    1 _getpid_r
   127: 08000451    98 FUNC    GLOBAL DEFAULT    1 __sigtramp_r
   128: 08000829    36 FUNC    GLOBAL DEFAULT    1 _sbrk_r
   129: 0800028b     2 FUNC    WEAK   DEFAULT    1 usage_fault_handler
   130: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim8_trg_com_isr
   131: 0800028b     2 FUNC    WEAK   DEFAULT    1 can2_rx0_isr
   132: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim1_brk_isr
   134: 0800028b     2 FUNC    WEAK   DEFAULT    1 can2_rx1_isr
   135: 08000349    16 FUNC    GLOBAL DEFAULT    1 std::get_new_handler()
   136: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim1_cc_isr
   137: 08000271    26 FUNC    GLOBAL DEFAULT    1 rcc_periph_clock_enable
   138: 08000359    14 FUNC    GLOBAL DEFAULT    1 abort
   139: 0800086d    28 FUNC    GLOBAL DEFAULT    1 _sbrk
   140: 0800028b     2 FUNC    WEAK   DEFAULT    1 sdio_isr
   141: 0800028b     2 FUNC    WEAK   DEFAULT    1 eth_isr
   142: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma1_channel4_isr
   143: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim8_brk_isr
   144: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma2_channel4_5_isr
   146: 0800028b     2 FUNC    WEAK   DEFAULT    1 pvd_isr
   147: 0800028d     2 FUNC    WEAK   DEFAULT    1 sv_call_handler
   148: 0800028b     2 FUNC    WEAK   DEFAULT    1 rcc_isr
   150: 0800028b     2 FUNC    WEAK   DEFAULT    1 flash_isr
   153: 0800028b     2 FUNC    WEAK   DEFAULT    1 uart4_isr
   154: 0800028b     2 FUNC    WEAK   DEFAULT    1 rtc_alarm_isr
   155: 0800028b     2 FUNC    WEAK   DEFAULT    1 exti15_10_isr
   156: 0800028b     2 FUNC    WEAK   DEFAULT    1 hard_fault_handler
   157: 0800028b     2 FUNC    WEAK   DEFAULT    1 exti1_isr
   158: 08000739   188 FUNC    GLOBAL DEFAULT    1 _free_r
   159: 0800028b     2 FUNC    WEAK   DEFAULT    1 i2c1_ev_isr
   160: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma2_channel1_isr
   161: 0800028d     2 FUNC    WEAK   DEFAULT    1 pend_sv_handler
   162: 0800028b     2 FUNC    WEAK   DEFAULT    1 spi2_isr
   163: 08000509    76 FUNC    GLOBAL DEFAULT    1 signal
   164: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim8_up_isr
   165: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma2_channel2_isr
   166: 0800028d     2 FUNC    WEAK   DEFAULT    1 debug_monitor_handler
   167: 0800028b     2 FUNC    WEAK   DEFAULT    1 exti3_isr
   168: 0800028b     2 FUNC    WEAK   DEFAULT    1 adc3_isr
   169: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim3_isr
   170: 0800028b     2 FUNC    WEAK   DEFAULT    1 usart2_isr
   171: 0800028b     2 FUNC    WEAK   DEFAULT    1 usb_lp_can_rx0_isr
   172: 080007f5     2 FUNC    GLOBAL DEFAULT    1 __malloc_lock
   174: 0800028b     2 FUNC    WEAK   DEFAULT    1 i2c2_er_isr
   175: 08000151   172 FUNC    GLOBAL DEFAULT    1 main
   176: 0800028b     2 FUNC    WEAK   DEFAULT    1 i2c2_ev_isr
   177: 0800028b     2 FUNC    WEAK   DEFAULT    1 uart5_isr
   178: 0800028d     2 FUNC    WEAK   DEFAULT    1 sys_tick_handler
   180: 0800028b     2 FUNC    WEAK   DEFAULT    1 fsmc_isr
   181: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma1_channel1_isr
   182: 0800028b     2 FUNC    WEAK   DEFAULT    1 exti4_isr
   183: 08000639   256 FUNC    GLOBAL DEFAULT    1 _malloc_r
   184: 08000555    52 FUNC    GLOBAL DEFAULT    1 _init_signal
   185: 0800028b     2 FUNC    WEAK   DEFAULT    1 mem_manage_handler
   186: 0800028b     2 FUNC    WEAK   DEFAULT    1 can2_tx_isr
   187: 0800028b     2 FUNC    WEAK   DEFAULT    1 exti9_5_isr
   188: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma2_channel3_isr
   191: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma1_channel7_isr
   193: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim1_up_isr
   194: 0800028b     2 FUNC    WEAK   DEFAULT    1 can2_sce_isr
   195: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim4_isr
   197: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma1_channel2_isr
   198: 0800028b     2 FUNC    WEAK   DEFAULT    1 i2c1_er_isr
   199: 0800028b     2 FUNC    WEAK   DEFAULT    1 can_sce_isr
   200: 0800028d     2 FUNC    WEAK   DEFAULT    1 nmi_handler
   201: 08000325    34 FUNC    GLOBAL DEFAULT    1 operator new(unsigned int)
   202: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim8_cc_isr
   203: 0800085d    16 FUNC    GLOBAL DEFAULT    1 _kill
   205: 0800028b     2 FUNC    WEAK   DEFAULT    1 tamper_isr
   207: 0800026b     6 FUNC    GLOBAL DEFAULT    1 gpio_clear
   208: 0800028b     2 FUNC    WEAK   DEFAULT    1 eth_wkup_isr
   209: 08000889     2 FUNC    GLOBAL DEFAULT    1 _exit
   210: 08000389    44 FUNC    GLOBAL DEFAULT    1 _init_signal_r
   213: 08000267     4 FUNC    GLOBAL DEFAULT    1 gpio_set
   214: 0800028b     2 FUNC    WEAK   DEFAULT    1 bus_fault_handler
   215: 0800028b     2 FUNC    WEAK   DEFAULT    1 wwdg_isr
   216: 0800028b     2 FUNC    WEAK   DEFAULT    1 dma1_channel3_isr
   219: 0800028b     2 FUNC    WEAK   DEFAULT    1 spi3_isr
   220: 08000589   100 FUNC    GLOBAL DEFAULT    1 __sigtramp
   221: 0800028b     2 FUNC    WEAK   DEFAULT    1 tim2_isr
   222: 080004b5    84 FUNC    GLOBAL DEFAULT    1 raise
   223: 08000379    16 FUNC    GLOBAL DEFAULT    1 free
   224: 0800028b     2 FUNC    WEAK   DEFAULT    1 exti0_isr

Da sind einige Methoden zu Peripherals, die ich gar nicht verwende. Vielleicht ergibt sich da noch weiteres Optimierungspotenzial.