Search code examples
gccassemblyx86gnu-assembler

GNU assembler did not produce a program that I can execute


I tried assembling some intermediate code generated by gcc. I used the command as -o hello hello.s, which, as far as I can tell, is the correct syntax. When I tried to run the program, it said bash: ./hello: cannot execute binary file. It doesn't seem like there's a problem with the assembly code, since it was the code generated by gcc, and it doesn't seem like there's anything wrong with how I invoked the assembler, since that seems to be the right syntax according to this manual. Can anyone help me with this?


Solution

  • Working with GNU Assembler

    Assume that your assembly file is called hello.s and looks something like (assuming a 32-Bit Linux target):

    .data
    msg:     .asciz "Hello World\n"
    msglen = .-msg
    .text
    .global _start
    _start:
        /* Use int $0x80/eax=4 to write to STDOUT */
        /* Output Hello World */
        mov $4, %eax       /* write system call */
        mov $0, %ebx       /* File descriptor 0 = STDOUT */
        mov $msg, %ecx     /* The message to output */
        mov $msglen, %edx  /* length of message */
        int $0x80          /* make the system call */
    
        /* Exit the program with int $0x80/eax=1 */
        mov $1, %eax       /* 1 = exit system call */
        mov $0, %ebx       /* value to exit with */
        int $0x80          /* make the system call */
    

    This is a 32-bit Linux assembler program in AT&T syntax that displays Hello World to standard output using 32-bit system calls via int $0x80. It doesn't use any C functions so can be assembled with the GNU assembler as and linked with the GNU linker ld to produce a final executable.

    as --32 hello.s -o hello.o
    ld -melf_i386 hello.o -o hello
    

    The first line assembles hello.s into a 32-bit ELF object called hello.o . hello.o is then linked to a 32-bit ELF executable called hello with the second command. The GNU linker assumes by default that your program starts execution at the label _start .

    Alternatively you can use GCC to assemble and link this program with this command:

    gcc -nostdlib -m32 hello.s -o hello
    

    This will produce a 32-bit ELF executable called hello . The -nostdlib tells GCC not to link in the C runtime library and allows us to use _start as our program's entry point.

    If your assembler program is intended to be linked to the C runtime and library so that it can utilize functions like C's printf then things are a bit different. Assume you have this program that needs printf (or any of the C library functions):

    .data
    msg: .asciz "Hello World\n"
    .text
    .global main
    main:
        push %ebp          /* Setup the stack frame */
        mov %esp, %ebp     /* Stack frames make GDB debugging easier */
    
        push  $msg         /* Message to print */
        call  printf
        add   $4,%esp      /* cleanup the stack */
    
        xor %eax, %eax     /* Return 0 when exiting */
        mov %ebp, %esp     /* destroy our stack frame */
        pop %ebp
        ret                /* Return to C runtime that called us
                              and allow it to do program termination */
    

    Your entry point now must be mainon most *nix type systems. The reason is that the C runtime will have an entry point called _start that does C runtime initialization and then makes a call to the function called main which we supply in our assembler code. To compile/assemble and link this we can use:

    gcc -m32 hello.s -o hello
    

    Note: on Windows the entry point called by the C runtime is _WinMain, not main.

    Working with NASM

    In the comments you also asked about NASM so I'll provide some information when assembling with it. Assume that your assembly file is called hello.asm and looks something like (It doesn't require the C runtime libraries):

    SECTION .data       ; data section
    msg     db "Hello World", 13, 10
    len     equ $-msg
    
    SECTION .text       ; code section
        global _start     ; make label available to linker
    _start:               ; standard  gcc  entry point
    
    mov edx,len     ; length of string to print
    mov ecx,msg     ; pointer to string
    mov ebx,1       ; write to STDOUT (file descriptor 0)
    mov eax,4       ; write command
    int 0x80        ; interrupt 80 hex, call kernel
    
    mov ebx,0       ; exit code, 0=normal
    mov eax,1       ; exit command to kernel
    int 0x80        ; interrupt 80 hex, call kernel
    

    Then to build it into an executable you can use commands like these:

    nasm -f elf32 hello.asm -o hello.o 
    gcc -nostdlib -m32 hello.o -o hello
    

    The first command assembles hello.asm to the ELF object file hello.o . The second line does the linking. -nostdlib excludes the C runtime from be linked in (functions like _printf etc wouldn't be available). The second line links hello.o to the executable hello .

    Alternatively you can skip using GCC and use the linker directly like this:

    nasm -f elf32 hello.asm -o hello.o 
    ld -melf_i386 hello.o -o hello
    

    If you need the C runtime and library for calling things like printf then it is a bit different. Assume you have this NASM code that needs printf:

        extern  printf
    
    SECTION .data           ; Data section, initialized variables
    
        msg:      db   "Hello World", 13, 10, 0
    
    SECTION .text           ; Code section.
    
        global main         ; the standard gcc entry point
    
    main:                   ; the program label for the entry point
        push    ebp         ; Setup the stack frame
        mov     ebp, esp    ; Stack frames make GDB debugging easier
    
        push    msg         ; Message to print
        call    printf
        add     esp, 4      ; Cleanup the stack
    
        mov     eax, 0      ; Return value of 0
        mov     esp, ebp    ; Destroy our stack frame
        pop     ebp
    endit:
        ret                 ; Return to C runtime that called us
                            ; and allow it to do program termination
    

    Then to build it into an executable you can use commands like these:

    nasm -f elf32 hello.asm -o hello.o
    gcc -m32 hello.o -o hello