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 und objcopy , mit denen man definierte Sektionen kopieren oder "strippen" kann. Man kann mit gcc 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.c

    extern 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 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.



  • 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 ich gcc auf und übergebe die Linker Parameter ohne Linker-File und da scheint es zu funktionieren, es wird tatsächlich die eine Funktion func_a() mitgelinkt.
    Vielleicht hat es was mit dem Linker-File und dem Format binary zu tun 😕 Ich 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
    

Anmelden zum Antworten