3
votes

I'm working with an embedded OS like environment, and so size of binaries needs to be as small as possible. I had been using simple flat binary files as an executable, but now I'm trying to use ELF. I'm just using assembly code, but linking with ld. Assembly code:

CPU i386
BITS 32
SECTION .text progbits alloc exec nowrite
GLOBAL start


start:
mov eax, 0 
add eax, 1
inc eax
mov eax, 0x12345678 
mov dword [0x100000], eax
mov ebx, dword [0x100000]
mov eax, ebx

out 0xF3, al ;dump memory API call

out 0xF0, ax
cli
hlt

Build commands:

yasm -o testbench/test.o testbench/test.asm -f elf32
i386-elf-gcc -T testbench/linker.ld -o test.elf -ffreestanding -nostdlib testbench/test.o -Wl,--gc-sections -dead_strip -fdata-sections -ffunction-sections -Os -nostartfiles -nodefaultlibs
strip --strip-all test.elf

And finally, linker script:

OUTPUT_FORMAT("elf32-i386")
ENTRY(start)
phys = 0x1000;
scratch = 0x100000;
MEMORY
{
  coderom (rx) : ORIGIN = phys, LENGTH = 128K
  scratchram (!rx) : ORIGIN = scratch, LENGTH = 1024K
}
SECTIONS
{
  .text phys : AT(phys) {
    code = .;
    *(.text.start);
    *(.text*)
    *(.rodata)
    . = ALIGN(4);
  } > coderom
  __text_end=.;
  .data : AT(scratch)
  {
    data = .;
    *(.data)
    . = ALIGN(4);
  } > scratchram
  __data_end=.;
  __binary_end = .;
  .bss : AT(scratch + (bss - data))
  {
    bss = .;
    *(.bss)
    . = ALIGN(4);
  } > scratchram
}

Which results in this readelf listing:

earlz@earlz-ubdev:~/x86LibSC$ readelf -a test.elf
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x1000
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4160 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         5
  Section header string table index: 4

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00001000 001000 000024 00  AX  0   0 16
  [ 2] .data             PROGBITS        00100000 001024 000000 00  WA  0   0  1
  [ 3] .bss              NOBITS          00100000 000000 000000 00  WA  0   0  1
  [ 4] .shstrtab         STRTAB          00000000 001024 00001c 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0x00001000 0x00001000 0x00024 0x00024 R E 0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text 

There is no dynamic section in this file.

There are no relocations in this file.

The decoding of unwind sections for machine type Intel 80386 is not currently supported.

No version information found in this file.

As you can see, this should compile to 100 or 200 bytes at the maximum (and did when using flat binary format).. but for some reason this takes up over 4Kb.

How can I reduce the ELF program size as much as possible short of building the ELF file manually in the assembler?

For reference, in this OS environment there are no relocations or dynamic code. It just loads flat program sections.

1
Looks like .text alignment set to 4k. - Jester
I noticed that for the program header bit, it's aligned to 4K in the file itself... but I don't see any way to tell LD to not do that @Jester - Earlz
The 4kb alignment is potentially coming from the input sections of the object files and propagated to the output sections in the final executable. You may be able to verride that by using the SUBALIGN directive on the .text and .data lines. So this might override it .text phys : AT(phys) SUBALIGN(64) and .data : AT(scratch) SUBALIGN(64) the 64 can be the value of alignment you wish to use (zero is supported as well). - Michael Petch
The terse docs for the SUBALIGN option can be found here: sourceware.org/binutils/docs/ld/Forced-Input-Alignment.html - Michael Petch

1 Answers

3
votes

I ended up solving this by using the "-n" (also known as "-nmagic") linker option. This basically tells ld to not worry about aligning program sections on page boundaries. This got me from 4K to about 700 (400 after strip). And then I used sstrip to reduce that further to just about 150 bytes. This is the type of size I was looking to achieve for this use case.