Minimal Linux IA-32 example that illustrates its usage
main.S
.section .text
.global _start
_start:
    /* Dummy access so that after will be referenced and kept. */
    mov after, %eax
    /*mov keep, %eax*/
    /* Exit system call. */
    mov $1, %eax
    /* Take the exit status 4 bytes after before. */
    mov $4, %ebx
    mov before(%ebx), %ebx
    int $0x80
.section .before
    before: .long 0
/* TODO why is the `"a"` required? */
.section .keep, "a"
    keep: .long 1
.section .after
    after: .long 2
link.ld
ENTRY(_start)
SECTIONS
{
    . = 0x400000;
    .text :
    {
        *(.text)
        *(.before)
        KEEP(*(.keep));
        *(.keep)
        *(.after)
    }
}
Compile and run:
as --32 -o main.o main.S
ld --gc-sections -m elf_i386 -o main.out -T link.ld main.o
./main.out
echo $?
Output:
1
If we comment out the KEEP line the output is:
2
If we either:
- add a dummy mov keep, %eax
- remove --gc-sections
The output goes back to 1.
Tested on Ubuntu 14.04, Binutils 2.25.
Explanation
There is no reference to the symbol keep, and therefore its containing section .keep.
Therefore if garbage collection is enabled and we don't use KEEP to make an exception, that section will not be put in the executable.
Since we are adding 4 to the address of before, if the keep section is not present, then the exit status will be 2, which is present on the next .after section.
TODO: nothing happens if we remove the "a" from .keep, which makes it allocatable. I don't understand why that is so: that section will be put inside the .text segment, which because of it's magic name will be allocatable.