lkml.org 
[lkml]   [2006]   [Jun]   [4]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patches in this message
    /
    Date
    From
    Subject[PATCH] Use ld's garbage collection feature

    Hi,

    Usage of ld's -gc-sections with gcc's -ffunction-sections has
    been discussed several times in the past, with a few proposals being
    submitted but none being merged (don't know why since it is really
    useful).

    So, the following patch adds a CONFIG_GCSECTIONS option to make use of
    it, allowing the linker to discard unreferenced sections.

    There are two main differences from previous attempts:

    - Makes use of "-print-gc-sections" option (patch to binutils attached).
    - Do not discard symbols referenced by modules (via KEEP directive from
    linker script).
    - Splits ksymtab* section into one section per member, allowing the
    linker to do the sweeping job when CONFIG_MODULES is set (otherwise
    exported unused symbols have a reference to them from ksymtab* sections,
    preventing garbage collection).


    Current implementation requires "make modules" to be run before "make
    vmlinux" (need to know what functions used by modules must be kept), but
    thats just silly and should be fixed inside the Makefile.

    TODO:
    - Do the same as done for ksymtab* to kcrctab* (CONFIG_MODVERSIONS)
    - Fixup end_rodata breakage (either move it outside generic
    vmlinux.lds.h or find a way to get "#include" expansion working
    recursively).
    - Very likely that KEEP() has been missed from a few unusual
    sections.
    - Recursive make invocation from Makefile is sort of ugly.

    Results:

    This config
    http://hera.kernel.org/~marcelo/gcsections/config-test-gcsections
    results in
    http://hera.kernel.org/~marcelo/gcsections/dropped_sections.txt
    sections being dropped.

    vmlinux shrinks from 1090389 to 983933 bytes, or 106k (~= 10%).

    I would like to see some equivalent of this merged, since its
    _very_ useful. There are _many_ helper functions used by specific
    configurations which can just be wasted completly from most builds.

    For example

    mm/built-in.o:.text.free_pages_and_swap_cache (only used by x86_64)
    fs/built-in.o:.text.ilookup5_nowait
    fs/built-in.o:.text.ilookup5 (only used by OCFS2 and VFAT)
    kernel/built-in.o:.text.kallsyms_lookup_name (only used by PPC)
    mm/built-in.o:.text.free_cold_page (unused!)

    (check dropped_sections.txt for more fun)

    Comments are welcome!

    diff --git a/Makefile b/Makefile
    index 3494c17..45c3f4e 100644
    --- a/Makefile
    +++ b/Makefile
    @@ -571,11 +571,20 @@ vmlinux-lds := arch/$(ARCH)/kernel/vmli

    # Rule to link vmlinux - also used during CONFIG_KALLSYMS
    # May be overridden by arch/$(ARCH)/Makefile
    +ifndef CONFIG_GCSECTIONS
    quiet_cmd_vmlinux__ ?= LD $@
    cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ \
    -T $(vmlinux-lds) $(vmlinux-init) \
    --start-group $(vmlinux-main) --end-group \
    $(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) FORCE ,$^)
    +else
    +quiet_cmd_vmlinux__ ?= LD $@
    + cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ \
    + -T $(vmlinux-lds) $(vmlinux-init) \
    + --start-group $(vmlinux-main) --end-group \
    + > arch/$(ARCH)/dropped_sections.txt \
    + $(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) FORCE ,$^)
    +endif

    # Generate new vmlinux version
    quiet_cmd_vmlinux_version = GEN .version
    @@ -593,6 +602,27 @@ # Generate System.map
    quiet_cmd_sysmap = SYSMAP
    cmd_sysmap = $(CONFIG_SHELL) $(srctree)/scripts/mksysmap

    +ifdef CONFIG_GCSECTIONS
    +# Find undefined symbols inside modules, and add them to the linker
    +# script with KEEP directive. Then rebuild the kernel.
    +
    +define update_used_symbols
    + $(Q)if [ $(MAKELEVEL) -eq "0" ]; then \
    + find . -name "*.ko" -exec nm {} \; |grep "U "|cut -f2 -dU | \
    + sed -e "s/ //g" > arch/$(ARCH)/undef_module_syms.txt; \
    + grep .text arch/$(ARCH)/dropped_sections.txt | sed -e s/.*://g \
    + > arch/$(ARCH)/drop_strip.txt; \
    + rm -f arch/$(ARCH)/kernel/vmlinux.ldskeep.h; \
    + cat arch/$(ARCH)/undef_module_syms.txt | while read line ; do \
    + egrep "$$line$$" arch/$(ARCH)/drop_strip.txt | \
    + while read L ; do echo KEEP\(*\($$L\)\) ; done \
    + >> arch/$(ARCH)/kernel/vmlinux.ldskeep.h ; true ; done; \
    + sh scripts/ksymbols-gen-keep.sh $(ARCH); \
    + GC_REBUILD_PASS=true $(MAKE) vmlinux \
    + ;fi
    +endef
    +endif
    +
    # Link of vmlinux
    # If CONFIG_KALLSYMS is set .version is already updated
    # Generate System.map and verify that the content is consistent
    @@ -605,6 +635,8 @@ define rule_vmlinux__
    $(call cmd,vmlinux__)
    $(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd

    + $(update_used_symbols)
    +
    $(Q)$(if $($(quiet)cmd_sysmap), \
    echo ' $($(quiet)cmd_sysmap) System.map' &&) \
    $(cmd_sysmap) $@ System.map; \
    @@ -803,8 +835,21 @@ archprepare: prepare1 scripts_basic
    prepare0: archprepare FORCE
    $(Q)$(MAKE) $(build)=.

    +
    +create_keep_files := keep.ksymstrings.txt keep.ksymtab.txt keep.ksymtabgpl.txt \
    + vmlinux.ldskeep.h
    +
    +create_keep_lists:
    +ifndef GC_REBUILD_PASS
    + $(Q)$(foreach f, $(create_keep_files), \
    + rm -f arch/$(ARCH)/kernel/$(f); \
    + touch arch/$(ARCH)/kernel/$(f); \
    + )
    +endif
    +
    # All the preparing..
    -prepare prepare-all: prepare0
    +prepare prepare-all: prepare0 create_keep_lists
    +

    # Leave this as default for preprocessing vmlinux.lds.S, which is now
    # done in arch/$(ARCH)/kernel/Makefile
    diff --git a/arch/i386/Kconfig b/arch/i386/Kconfig
    index 8dfa305..4a60635 100644
    --- a/arch/i386/Kconfig
    +++ b/arch/i386/Kconfig
    @@ -691,6 +691,12 @@ config REGPARM

    If unsure, say Y.

    +config GCSECTIONS
    + bool "Garbage collect unused sections"
    + default n
    + help
    + Use ld's --gc-sections option to garbage collect unused sections.
    +
    config SECCOMP
    bool "Enable seccomp to safely compute untrusted bytecode"
    depends on PROC_FS
    diff --git a/arch/i386/Makefile b/arch/i386/Makefile
    index 3e4adb1..1a2da0d 100644
    --- a/arch/i386/Makefile
    +++ b/arch/i386/Makefile
    @@ -24,9 +24,11 @@ LD := $(LD) -m elf_i386
    CC := $(CC) -m32
    endif

    +ldflags-$(CONFIG_GCSECTIONS) += --gc-sections --print-gc-sections
    +
    LDFLAGS := -m elf_i386
    OBJCOPYFLAGS := -O binary -R .note -R .comment -S
    -LDFLAGS_vmlinux :=
    +LDFLAGS_vmlinux := $(ldflags-y)
    CHECKFLAGS += -D__i386__

    CFLAGS += -pipe -msoft-float
    @@ -39,6 +41,8 @@ include $(srctree)/arch/i386/Makefile.cp

    cflags-$(CONFIG_REGPARM) += -mregparm=3

    +cflags-$(CONFIG_GCSECTIONS) += -ffunction-sections
    +
    # temporary until string.h is fixed
    cflags-y += -ffreestanding

    diff --git a/arch/i386/kernel/vmlinux.lds.S b/arch/i386/kernel/vmlinux.lds.S
    index 8831303..8c2cb26 100644
    --- a/arch/i386/kernel/vmlinux.lds.S
    +++ b/arch/i386/kernel/vmlinux.lds.S
    @@ -21,6 +21,7 @@ SECTIONS
    _text = .; /* Text and read-only data */
    .text : AT(ADDR(.text) - LOAD_OFFSET) {
    *(.text)
    + #include "vmlinux.ldskeep.h"
    SCHED_TEXT
    LOCK_TEXT
    KPROBES_TEXT
    @@ -32,11 +33,30 @@ SECTIONS

    . = ALIGN(16); /* Exception table */
    __start___ex_table = .;
    - __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) { *(__ex_table) }
    + __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) { KEEP(*(__ex_table)) }
    __stop___ex_table = .;

    RODATA

    + /* Kernel symbol table: Normal symbols */
    + __ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) {
    + VMLINUX_SYMBOL(__start___ksymtab) = .;
    + #include "keep.ksymtab.txt"
    + VMLINUX_SYMBOL(__stop___ksymtab) = .;
    + }
    +
    + /* Kernel symbol table: GPL-only symbols */
    + __ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - LOAD_OFFSET) {
    + VMLINUX_SYMBOL(__start___ksymtab_gpl) = .;
    + #include "keep.ksymtabgpl.txt"
    + VMLINUX_SYMBOL(__stop___ksymtab_gpl) = .;
    + }
    +
    + /* Kernel symbol table: strings */
    + __ksymtab_strings : AT(ADDR(__ksymtab_strings) - LOAD_OFFSET) {
    + #include "keep.ksymstrings.txt"
    + }
    +
    /* writeable */
    .data : AT(ADDR(.data) - LOAD_OFFSET) { /* Data */
    *(.data)
    @@ -74,7 +94,7 @@ SECTIONS
    __smp_alt_begin = .;
    __smp_alt_instructions = .;
    .smp_altinstructions : AT(ADDR(.smp_altinstructions) - LOAD_OFFSET) {
    - *(.smp_altinstructions)
    + KEEP(*(.smp_altinstructions))
    }
    __smp_alt_instructions_end = .;
    . = ALIGN(4);
    @@ -84,7 +104,7 @@ SECTIONS
    }
    __smp_locks_end = .;
    .smp_altinstr_replacement : AT(ADDR(.smp_altinstr_replacement) - LOAD_OFFSET) {
    - *(.smp_altinstr_replacement)
    + KEEP(*(.smp_altinstr_replacement))
    }
    . = ALIGN(4096);
    __smp_alt_end = .;
    @@ -100,33 +120,33 @@ SECTIONS
    .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { *(.init.data) }
    . = ALIGN(16);
    __setup_start = .;
    - .init.setup : AT(ADDR(.init.setup) - LOAD_OFFSET) { *(.init.setup) }
    + .init.setup : AT(ADDR(.init.setup) - LOAD_OFFSET) { KEEP(*(.init.setup)) }
    __setup_end = .;
    __initcall_start = .;
    .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
    - *(.initcall1.init)
    - *(.initcall2.init)
    - *(.initcall3.init)
    - *(.initcall4.init)
    - *(.initcall5.init)
    - *(.initcall6.init)
    - *(.initcall7.init)
    + KEEP(*(.initcall1.init))
    + KEEP(*(.initcall2.init))
    + KEEP(*(.initcall3.init))
    + KEEP(*(.initcall4.init))
    + KEEP(*(.initcall5.init))
    + KEEP(*(.initcall6.init))
    + KEEP(*(.initcall7.init))
    }
    __initcall_end = .;
    __con_initcall_start = .;
    .con_initcall.init : AT(ADDR(.con_initcall.init) - LOAD_OFFSET) {
    - *(.con_initcall.init)
    + KEEP(*(.con_initcall.init))
    }
    __con_initcall_end = .;
    SECURITY_INIT
    . = ALIGN(4);
    __alt_instructions = .;
    .altinstructions : AT(ADDR(.altinstructions) - LOAD_OFFSET) {
    - *(.altinstructions)
    + KEEP(*(.altinstructions))
    }
    __alt_instructions_end = .;
    .altinstr_replacement : AT(ADDR(.altinstr_replacement) - LOAD_OFFSET) {
    - *(.altinstr_replacement)
    + KEEP(*(.altinstr_replacement))
    }
    /* .exit.text is discard at runtime, not link time, to deal with references
    from .altinstructions and .eh_frame */
    @@ -134,7 +154,7 @@ SECTIONS
    .exit.data : AT(ADDR(.exit.data) - LOAD_OFFSET) { *(.exit.data) }
    . = ALIGN(4096);
    __initramfs_start = .;
    - .init.ramfs : AT(ADDR(.init.ramfs) - LOAD_OFFSET) { *(.init.ramfs) }
    + .init.ramfs : AT(ADDR(.init.ramfs) - LOAD_OFFSET) { KEEP(*(.init.ramfs)) }
    __initramfs_end = .;
    . = ALIGN(L1_CACHE_BYTES);
    __per_cpu_start = .;
    diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
    index 9d11550..a967c04 100644
    --- a/include/asm-generic/vmlinux.lds.h
    +++ b/include/asm-generic/vmlinux.lds.h
    @@ -24,16 +24,16 @@ #define RODATA \
    /* PCI quirks */ \
    .pci_fixup : AT(ADDR(.pci_fixup) - LOAD_OFFSET) { \
    VMLINUX_SYMBOL(__start_pci_fixups_early) = .; \
    - *(.pci_fixup_early) \
    + KEEP(*(.pci_fixup_early)) \
    VMLINUX_SYMBOL(__end_pci_fixups_early) = .; \
    VMLINUX_SYMBOL(__start_pci_fixups_header) = .; \
    - *(.pci_fixup_header) \
    + KEEP(*(.pci_fixup_header)) \
    VMLINUX_SYMBOL(__end_pci_fixups_header) = .; \
    VMLINUX_SYMBOL(__start_pci_fixups_final) = .; \
    - *(.pci_fixup_final) \
    + KEEP(*(.pci_fixup_final)) \
    VMLINUX_SYMBOL(__end_pci_fixups_final) = .; \
    VMLINUX_SYMBOL(__start_pci_fixups_enable) = .; \
    - *(.pci_fixup_enable) \
    + KEEP(*(.pci_fixup_enable)) \
    VMLINUX_SYMBOL(__end_pci_fixups_enable) = .; \
    } \
    \
    @@ -96,7 +96,7 @@ #define RODATA \
    /* Built-in module parameters. */ \
    __param : AT(ADDR(__param) - LOAD_OFFSET) { \
    VMLINUX_SYMBOL(__start___param) = .; \
    - *(__param) \
    + KEEP(*(__param)) \
    VMLINUX_SYMBOL(__stop___param) = .; \
    }

    diff --git a/include/linux/module.h b/include/linux/module.h
    index eaec13d..4675cc0 100644
    --- a/include/linux/module.h
    +++ b/include/linux/module.h
    @@ -181,6 +181,7 @@ #else
    #define __CRC_SYMBOL(sym, sec)
    #endif

    +#ifndef CONFIG_GCSECTIONS
    /* For every exported symbol, place a struct in the __ksymtab section */
    #define __EXPORT_SYMBOL(sym, sec) \
    extern typeof(sym) sym; \
    @@ -193,6 +194,24 @@ #define __EXPORT_SYMBOL(sym, sec) \
    __attribute__((section("__ksymtab" sec), unused)) \
    = { (unsigned long)&sym, __kstrtab_##sym }

    +#else
    +/* For garbage collection, each symbol has its own
    + __ksymtab and __kstrtab section, which are later merged into
    + single ones.
    + */
    +#define __EXPORT_SYMBOL(sym, sec) \
    + extern typeof(sym) sym; \
    + __CRC_SYMBOL(sym, sec) \
    + static const char __kstrtab_##sym[] \
    + __attribute__((section("__ksymtab_strings_" #sym))) \
    + = MODULE_SYMBOL_PREFIX #sym; \
    + static const struct kernel_symbol __ksymtab_##sym \
    + __attribute_used__ \
    + __attribute__((section("___ksymtab_" #sym sec), unused))\
    + = { (unsigned long)&sym, __kstrtab_##sym }
    +
    +#endif
    +
    #define EXPORT_SYMBOL(sym) \
    __EXPORT_SYMBOL(sym, "")

    diff --git a/scripts/ksymbols-gen-keep.sh b/scripts/ksymbols-gen-keep.sh
    new file mode 100755
    index 0000000..3f9fb9b
    --- /dev/null
    +++ b/scripts/ksymbols-gen-keep.sh
    @@ -0,0 +1,59 @@
    +#!/bin/sh
    +#
    +# Add symbols from special sections ksymtab,ksymtab_strings,ksymtab_gpl
    +# which we do not want to be discarded into one linker script per
    +# special section.
    +#
    +
    +ARCH=$1
    +
    +rm -f vmlinux.nm
    +nm vmlinux | grep "T " | cut -f 2 -d T | while read line
    + do echo "$line" >> vmlinux.nm
    +done
    +
    +nm vmlinux | grep "D " | cut -f 2 -d D | while read line
    + do echo "$line" >> vmlinux.nm
    +done
    +
    +nm vmlinux | grep "B " | cut -f 2 -d B | while read line
    + do echo "$line" >> vmlinux.nm
    +done
    +
    +cat arch/$ARCH/kernel/vmlinux.ldskeep.h | while read line
    + do sed -e s/"KEEP(\*(.text."//g -e s/"))$"//g
    +done >> vmlinux.nm
    +
    +# ksymtab_strings
    +cat arch/$ARCH/dropped_sections.txt | sed -e "s/.*:__ksymtab_strings_//g" | \
    +grep -v : | while read line
    + do grep $line$ vmlinux.nm
    +done > keep.ksymstrings
    +
    +cat keep.ksymstrings | while read line ; do
    + echo KEEP\(*\("__ksymtab_strings_$line"\)\)
    +done > keep.ksymstrings.txt
    +
    +# ksymtab
    +egrep -v "ksymtab_strings|ksymtab_.*_gpl" arch/$ARCH/dropped_sections.txt | \
    +sed -e "s/.*:___ksymtab_//g" | grep -v ":" | while read line
    + do grep $line$ vmlinux.nm
    +done > keep.ksymtab
    +
    +cat keep.ksymtab | while read line
    + do echo "KEEP(*(___ksymtab_$line))"
    +done > keep.ksymtab.txt
    +
    +# ksymtab gpl
    +egrep "ksymtab_.*_gpl" arch/$ARCH/dropped_sections.txt | \
    +sed -e "s/.*://g" -e s/___ksymtab_//g -e s/_gpl$//g | while read line
    + do grep $line$ vmlinux.nm
    +done > keep.ksymtabgpl
    +
    +cat keep.ksymtabgpl | while read line
    + do echo KEEP\(*\(___ksymtab_"$line"_gpl\)\)
    +done > keep.ksymtabgpl.txt
    +
    +rm -f keep.ksymstrings keep.ksymtab keep.ksymtabgpl
    +
    +mv keep.*.txt arch/$ARCH/kernel/


    --- ./ld/ldmain.c.orig 2006-05-21 17:18:46.000000000 -0300
    +++ ./ld/ldmain.c 2006-05-21 17:18:55.000000000 -0300
    @@ -316,6 +316,7 @@ main (int argc, char **argv)
    link_info.relax_pass = 1;
    link_info.warn_shared_textrel = FALSE;
    link_info.gc_sections = FALSE;
    + link_info.print_gc_sections = FALSE;

    ldfile_add_arch ("");

    --- ./ld/lexsup.c.orig 2006-05-21 17:06:21.000000000 -0300
    +++ ./ld/lexsup.c 2006-05-21 17:18:27.000000000 -0300
    @@ -124,6 +124,7 @@ enum option_values
    OPTION_FORCE_EXE_SUFFIX,
    OPTION_GC_SECTIONS,
    OPTION_NO_GC_SECTIONS,
    + OPTION_PRINT_GC_SECTIONS,
    OPTION_HASH_SIZE,
    OPTION_CHECK_SECTIONS,
    OPTION_NO_CHECK_SECTIONS,
    @@ -370,6 +371,9 @@ static const struct ld_option ld_options
    { {"no-gc-sections", no_argument, NULL, OPTION_NO_GC_SECTIONS},
    '\0', NULL, N_("Don't remove unused sections (default)"),
    TWO_DASHES },
    + { {"print-gc-sections", no_argument, NULL, OPTION_PRINT_GC_SECTIONS},
    + '\0', NULL, N_("Print removed unused sections"),
    + TWO_DASHES },
    { {"hash-size=<NUMBER>", required_argument, NULL, OPTION_HASH_SIZE},
    '\0', NULL, N_("Set default hash table size close to <NUMBER>"),
    TWO_DASHES },
    @@ -812,6 +816,9 @@ parse_args (unsigned argc, char **argv)
    case OPTION_GC_SECTIONS:
    link_info.gc_sections = TRUE;
    break;
    + case OPTION_PRINT_GC_SECTIONS:
    + link_info.print_gc_sections = TRUE;
    + break;
    case OPTION_HELP:
    help ();
    xexit (0);
    --- ./include/bfdlink.h.orig 2006-05-21 17:16:54.000000000 -0300
    +++ ./include/bfdlink.h 2006-05-21 17:17:21.000000000 -0300
    @@ -324,6 +324,9 @@ struct bfd_link_info
    /* TRUE if unreferenced sections should be removed. */
    unsigned int gc_sections: 1;

    + /* TRUE if should print removed unreferenced sections. */
    + unsigned int print_gc_sections: 1;
    +
    /* What to do with unresolved symbols in an object file.
    When producing executables the default is GENERATE_ERROR.
    When producing shared libraries the default is IGNORE. The
    --- ./bfd/elflink.c.orig 2006-05-21 17:20:57.000000000 -0300
    +++ ./bfd/elflink.c 2006-05-21 18:41:19.000000000 -0300
    @@ -8978,6 +8978,9 @@ elf_gc_sweep (bfd *abfd, struct bfd_link
    to remove a section from the output. */
    o->flags |= SEC_EXCLUDE;

    + if (info->print_gc_sections == TRUE)
    + printf("%s:%s\n", sub->filename, o->name);
    +
    /* But we also have to update some of the relocation
    info we collected before. */
    if (gc_sweep_hook
    \
     
     \ /
      Last update: 2006-06-05 02:34    [W:0.055 / U:0.396 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site