ld: Sektionen aus Map-Datei entfernen
-
Hi,
Da ich zuletzt gemerkt habe, dass meine Binaries eine Menge ungenutzer Symbole enthalten, habe ich versucht, das mit der Angabe von
-ffunction-sections -fdata-sections
bei der Kompilation und
-gc-sections
beim Linken zu lösen. Ich verwende GCC (4.4) oder Clang (3.0) mit GNU ld (2.21).
Mein Ausgangsproblem hat das zwar gelöst, hat aber auch eine unschöne Nebenwirkung verursacht: Pro Symbol wird eine Sektion angelegt, da ld angeblich nur ganze Sektionen wegoptimieren kann. Die map-Datei, die ich generieren lasse, hat daher ca. die dreifache (bei Clang sogar die 20fache, wenn mit -fdata-sections) Größe und ist durch die vielen Sektionen auch deutlich schwerer lesbar.
Hier mal ein Beispiel:
Vorher:.text 0x00109f3c 0x3b4 object_files/kernel/netprotocol/udp.o 0x00109f75 udp_send 0x0010a004 udp_usend 0x0010a050 udp_receive 0x0010a188 udp_cleanup 0x0010a207 udp_unbind 0x0010a289 udp_bind
Nachher:
.text 0x0010b53c 0x0 object_files/kernel/netprotocol/udp.o *fill* 0x0010b53c 0x4 00 .text.udp_bind 0x0010b540 0x88 object_files/kernel/netprotocol/udp.o 0x0010b540 udp_bind *fill* 0x0010b5c8 0x8 00 .text.udp_unbind 0x0010b5d0 0x7c object_files/kernel/netprotocol/udp.o 0x0010b5d0 udp_unbind *fill* 0x0010b64c 0x4 00 .text.udp_cleanup 0x0010b650 0x79 object_files/kernel/netprotocol/udp.o 0x0010b650 udp_cleanup *fill* 0x0010b6c9 0x7 00 .text.udp_receive 0x0010b6d0 0x125 object_files/kernel/netprotocol/udp.o 0x0010b6d0 udp_receive *fill* 0x0010b7f5 0xb 00 .text.udp_send 0x0010b800 0x9c object_files/kernel/netprotocol/udp.o 0x0010b800 udp_send *fill* 0x0010b89c 0x4 00 .text.udp_usend 0x0010b8a0 0xc5 object_files/kernel/netprotocol/udp.o 0x0010b8a0 udp_usend *fill* 0x0010b965 0x3 00
Das verwendete Linker\1:
OUTPUT_FORMAT(binary) OUTPUT_ARCH(i386) STARTUP(object_files/kernel/kernel.o) SECTIONS { . = 0x00100000; _kernel_beg = .; .text : { *(.text*) } .data : { *(.data*) } .rodata : { *(.rodata*) } .bss : { _bss_start = .; *(.bss*); _bss_end = .; } .cdi : { _cdi_start = .; *(.cdi*); _cdi_end = .; } _kernel_end = .; }
Gibt es eine Möglichkeit, diese Nebenwirkung einzudämmen?
Ich habe schon in den Dokumentationen zu ld, Linkerscripts, gcc, und via google gesucht, aber nichts derartiges gefunden. Vielleicht könnte man diese Subsektionen wieder zu einer Sektion verschmelzen? Oder die Ausgabe von Subsektionen unterbinden? Oder vielleicht ein ganz anderer Weg, um das Ausgangsproblem zu lösen, d.h. ungenutzte Symbole (insbesondere Funktionen) zu entfernen?Hat da jemand eine gute Idee?
mfg
Mr X
-
Mr X schrieb:
Da ich zuletzt gemerkt habe, dass meine Binaries eine Menge ungenutzer Symbole enthalten, habe ich versucht ...
Verstehe deinen Versuch nicht... es gibt Programme wie
strip
undobjcopy
, mit denen man definierte Sektionen kopieren oder "strippen" kann. Man kann mitgcc
auch gleich "gestrippte" Binaries bauen, mit-s
:gcc -c bla.c -o bla.o -s
-
Strip entfernt Debugsymbole, aber keine ungenutzen Funktionen.
-
Der Linker müsste eigentlich intelligent genug sein, die unbenutzten Funktionen nicht mitzulinken. Vielleicht werden die Funktionen doch noch irgendwo im Quellcode benutzt, irgendwie indirekt, in einem schlauen Makro z.B.
-
Das habe ich auch gedacht. Aber ich habe folgendes festgestellt:
Mit jeder Funktion, die ich in meiner Library ergänze (statisch gelinkt), wird die Executable, die gegen die Lib linkt, größer. Ohne dass ich die neu eingebauten Funktionen irgendwo nutze. Wenn ich den Trick mit -ffunction-sections anwende, schrumpft die Executable um ca. 70%.
LD ist offenbar nicht schlau genug, diese Funktionen zu erkennen, wenn sie nicht in einer eigenen Sektion liegen. Dann allerdings wird - wie gesagt - die Map unleserlich.
-
Kann man das Problem irgendwie vereinfacht reproduzieren?
Ich habe grade folgendes gemacht:
main.cextern int func_a(void); int main(int argc, char* argv[]) { return func_a(); }
func.c
int func(void) { return 0x55555555; } int func_a(void) { return 0x66666666; } int func_b(void) { return 0xAAAAAAAA; }
Makefile
all: @echo Select test_a or test_b test_a: gcc -c func.c -o func.o -s -O2 -fomit-frame-pointer -g0 gcc -c main.c -o main.o -s -O2 -fomit-frame-pointer -g0 ld -T linker.txt -Map mapfile -o main main.o func.o ls -l main test_b: gcc -c func.c -o func.o -s -O2 -fomit-frame-pointer -g0 -ffunction-sections -fdata-sections gcc -c main.c -o main.o -s -O2 -fomit-frame-pointer -g0 -ffunction-sections -fdata-sections ld -T linker.txt -Map mapfile -gc-sections -o main main.o func.o ls -l main
Linkerfile linker.txt
OUTPUT_FORMAT(binary) OUTPUT_ARCH(i386) SECTIONS { . = 0x00100000; _kernel_beg = .; .text : { *(.text*) } .data : { *(.data*) } .rodata : { *(.rodata*) } .bss : { _bss_start = .; *(.bss*); _bss_end = .; } .cdi : { _cdi_start = .; *(.cdi*); _cdi_end = .; } _kernel_end = .; }
Das Ergebnis ist immer eine 54 Bytes große
main
, die alle drei Funktionen aus func.c enthält. Ich habe die main mit objdump disassembliert:$ objdump -D --target=binary --architecture=i386 main main: file format binary Disassembly of section .data: 00000000 <.data>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 e4 f0 and $0xfffffff0,%esp 6: e8 15 00 00 00 call 0x20 b: 89 ec mov %ebp,%esp d: 5d pop %ebp e: c3 ret f: 00 b8 55 55 55 55 add %bh,0x55555555(%eax) 15: c3 ret ... 1e: 00 00 add %al,(%eax) 20: b8 66 66 66 66 mov $0x66666666,%eax 25: c3 ret ... 2e: 00 00 add %al,(%eax) 30: b8 aa aa aa aa mov $0xaaaaaaaa,%eax 35: c3 ret
d.h. ich bekomme es nicht hin, dass nur die Funktion func_a mitgelinkt wird. Komisch
-
Bist Du sicher, dass dein Makefile so funktioniert? Dieser Auswahlmechanismus kommt mir suspekt vor.
Wie sieht denn deine Map-Datei aus (Die mit -ffunction-sections)?
-
Das Makefile funktioniert, ich habe es nur geschrieben, weil ich faul war, die ganzen Kommandos immer wieder einzugeben und der Reproduzierbarkeit wegen. Man tippt einfach ein:
make test_a
oder
make test_b
und betrachtet in Ruhe das Ergebnis.
Ich wollte nur schauen, was der Linker in beiden Fällen macht und bei mir sieht es so aus, dass alle Funktionen aus func.c mitgelinkt werden, egal, ob sie in dermain.c
benutzt werden oder nicht, und egal ob-ffunction-sections -fdata-sections
mit dabei ist oder nicht.
Deswegen meine Frage, ob du dein Problem irgendwie vereinfachen könntest, so wie ich es gemacht habe, damit ich und die anderen es bei sich reproduzieren könnten.
-
abc.w schrieb:
Das Makefile funktioniert, ich habe es nur geschrieben, weil ich faul war, die ganzen Kommandos immer wieder einzugeben und der Reproduzierbarkeit wegen. Man tippt einfach ein:
make test_a
oder
make test_b
und betrachtet in Ruhe das Ergebnis.
Ok. Ich war nur wegen des all-Targets etwas verwirrt.
Ich wollte nur schauen, was der Linker in beiden Fällen macht und bei mir sieht es so aus, dass alle Funktionen aus func.c mitgelinkt werden, egal, ob sie in der
main.c
benutzt werden oder nicht, und egal ob-ffunction-sections -fdata-sections
mit dabei ist oder nicht.
Deswegen meine Frage, ob du dein Problem irgendwie vereinfachen könntest, so wie ich es gemacht habe, damit ich und die anderen es bei sich reproduzieren könnten.Ich würde eigentlich sagen, Du hast es recht gut vereinfacht. Warum es nicht das gewünschte Resultat liefert, weiß ich nicht. Ich probiere es gleich mal aus und gucke, was dort "falsch" ist. Es wäre vielleicht aufschlussreich, wenn Du mal die Map-Datei posten könntest, die er für test_b ausgibt. Dann sieht man, ob der erste Schritt, alles in separate Sektionen auszulagern, funktioniert hat.
-
Ja, die separaten Sektionen sind da:
Memory Configuration Name Origin Length Attributes *default* 0x0000000000000000 0xffffffffffffffff Linker script and memory map 0x0000000000100000 . = 0x100000 0x0000000000100000 _kernel_beg = . .text 0x0000000000100000 0x36 *(.text*) .text 0x0000000000100000 0x0 main.o .text.main 0x0000000000100000 0xf main.o 0x0000000000100000 main *fill* 0x000000000010000f 0x1 00 .text 0x0000000000100010 0x0 func.o .text.func 0x0000000000100010 0x6 func.o 0x0000000000100010 func *fill* 0x0000000000100016 0xa 00 .text.func_a 0x0000000000100020 0x6 func.o 0x0000000000100020 func_a *fill* 0x0000000000100026 0xa 00 .text.func_b 0x0000000000100030 0x6 func.o 0x0000000000100030 func_b .data 0x0000000000100038 0x0 load address 0x0000000000100036 *(.data*) .data 0x0000000000100038 0x0 main.o .data 0x0000000000100038 0x0 func.o .rodata *(.rodata*) .bss 0x0000000000100038 0x0 0x0000000000100038 _bss_start = . *(.bss*) .bss 0x0000000000100038 0x0 main.o .bss 0x0000000000100038 0x0 func.o 0x0000000000100038 _bss_end = . .cdi 0x0000000000100038 0x0 0x0000000000100038 _cdi_start = . *(.cdi*) 0x0000000000100038 _cdi_end = . 0x0000000000100038 _kernel_end = . LOAD main.o LOAD func.o OUTPUT(main binary) .comment 0x0000000000000000 0x5a .comment 0x0000000000000000 0x2d main.o .comment 0x000000000000002d 0x2d func.o .note.GNU-stack 0x0000000000000000 0x0 .note.GNU-stack 0x0000000000000000 0x0 main.o .note.GNU-stack 0x0000000000000000 0x0 func.o
Ich habe noch was ausprobiert, erweitertes Makefile:
all: @echo Select test_a or test_b or test_c or test_d test_a: gcc -c func.c -o func.o -s -O2 -fomit-frame-pointer -g0 gcc -c main.c -o main.o -s -O2 -fomit-frame-pointer -g0 ld -T linker.txt -Map mapfile -o main main.o func.o ls -l main test_b: gcc -c func.c -o func.o -s -O2 -fomit-frame-pointer -g0 -ffunction-sections -fdata-sections gcc -c main.c -o main.o -s -O2 -fomit-frame-pointer -g0 -ffunction-sections -fdata-sections ld -T linker.txt -Map mapfile -gc-sections -o main main.o func.o ls -l main test_c: gcc -c func.c -o func.o -s -O2 -fomit-frame-pointer -g0 gcc -c main.c -o main.o -s -O2 -fomit-frame-pointer -g0 gcc -Wl,-Map mapfile -o main main.o func.o ls -l main test_d: gcc -c func.c -o func.o -s -O2 -fomit-frame-pointer -g0 -ffunction-sections -fdata-sections gcc -c main.c -o main.o -s -O2 -fomit-frame-pointer -g0 -ffunction-sections -fdata-sections gcc -Wl,-Map mapfile -Wl,-gc-sections -o main main.o func.o ls -l main
Im Fall von
test_d
rufe ichgcc
auf und übergebe die Linker Parameter ohne Linker-File und da scheint es zu funktionieren, es wird tatsächlich die eine Funktionfunc_a()
mitgelinkt.
Vielleicht hat es was mit dem Linker-File und dem Format binary zu tunIch weiss nicht, aber vielleicht hilft es weiter.
-
Ich habs gerade selbst ausprobiert: Du hast Recht, mit binary funktionierts tatsächlich nicht. Bei meinen Tests hatte ich auch damals schon nur bei den Userprogrammen (elf32-i386) nennenswerte Größenänderungen festgestellt.
Linker\1:
OUTPUT_FORMAT(elf32-i386) OUTPUT_ARCH(i386) SECTIONS { . = 0x1400000; .text : { *(.text*) } .data : { *(.data*) } .rodata : { *(.rodata*) } .bss : { *(.bss*) } }
Makefile:
test_a: i586-elf-gcc -c func.c -o func.o -O -ffreestanding -nostdinc -fno-pic -fno-stack-protector -fomit-frame-pointer i586-elf-gcc -c main.c -o main.o -O -ffreestanding -nostdinc -fno-pic -fno-stack-protector -fomit-frame-pointer i586-elf-ld -T linker.ld -Map map.map -nostdlib -s -o main.bin main.o func.o test_b: i586-elf-gcc -c func.c -o func.o -O -ffreestanding -nostdinc -fno-pic -fno-stack-protector -fomit-frame-pointer -ffunction-sections -fdata-sections i586-elf-gcc -c main.c -o main.o -O -ffreestanding -nostdinc -fno-pic -fno-stack-protector -fomit-frame-pointer -ffunction-sections -fdata-sections i586-elf-ld -T linker.ld -Map map.map -nostdlib -gc-sections -s -o main.bin main.o func.o