Search code examples
assemblygnu-assemblerbinutils

How to compile an assembly file to a raw binary (like DOS .com) format with GNU assembler (as)?


I want to compile this source code in Windows (It just an example):

start:
NOP
NOP

When I compile it with NASM or FASM, output file length is 2 bytes. But when I compile it with GNU assembler (as) the output file length is 292 bytes!

How to compile an assembly file to a raw binary (like DOS .com) format with GNU assembler (as)?


Why I do this?

I want to write my own simple OS, I write my codes with C (without using any C standard libraries even stdio.h or math.h) and convert it to assembly:

gcc -S my_os.c -o my_os.asm -masm=intel

Then, I compile assembly file to a raw binary:

as my_os.asm

Then I rename a.out (output of assembler) to my_os.flp and finally start my OS with VMWare :)


Solution

  • ld --oformat binary

    For quick and dirty tests you can do:

    as -o a.o a.S
    ld --oformat binary -o a.out a.o
    hd a.out
    

    Gives:

    00000000  90 90                                             |..|
    00000002
    

    Unfortunately this gives a warning:

    ld: warning: cannot find entry symbol _start; defaulting to 0000000000400000
    

    which does not make much sense with binary. It could be silenced with:

    .section .text
    .globl start
    start:
    nop
    nop
    

    and:

    ld -e start --oformat binary -o a.out a.o
    

    or simply with:

    ld -e 0 --oformat binary -o a.out a.o
    

    which tells ld that the entry point is not _start but the code at address 0.

    It is a shame that neither as nor ld can take input / ouptut from stdin / stdout, so no piping.

    Proper boot sector

    If you are going to to something more serious, the best method is to generate a clean minimal linker script. linker.ld:

    SECTIONS
    {
        . = 0x7c00;
        .text :
        {
            *(.*)
            . = 0x1FE;
            SHORT(0xAA55)
        }
    }
    

    Here we also place the magic bytes with the linker script.

    The linker script is important above all to control the output addresses after relocation. Learn more about relocation at: https://stackoverflow.com/a/30507725/895245

    Use it as:

    as -o a.o a.S
    ld --oformat binary -o a.img -T linker.ld a.o
    

    And then you can boot as:

    qemu-system-i386 -hda a.img
    

    Working examples on this repository: https://github.com/cirosantilli/x86-bare-metal-examples/blob/d217b180be4220a0b4a453f31275d38e697a99e0/Makefile

    Tested on Binutils 2.24, Ubuntu 14.04.