Zen Hacker
Zen Hacker

Reputation: 883

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?

Upvotes: 2

Views: 2052

Answers (2)

Michael Petch
Michael Petch

Reputation: 47593

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

Upvotes: 3

Keith Thompson
Keith Thompson

Reputation: 263387

Neither a compiler nor an assembler generates an executable file. Both generate an object file, which can then be linked with other object and/or library files to generate an executable.

The command gcc -c, for example, invokes just the compiler; it can take a source file like hello.c as input and generate an object file like hello.o as output.

Likewise, as can take an assembly language source file like hello.s and generate an object file like hello.o.

The linker is a separate tool that generates executables from object files.

It just happens that compiling and linking in one step is so convenient that that's what the gcc command does by default; gcc hello.c -o hello invokes the compiler and the linker to generate an executable file.

Note that the gcc command isn't just a compiler. It's a driver program that invokes the preprocessor, the compiler proper, the assembler, and/or the linker. (The preprocessor and assembler, can be thought of as components of the compiler, and in some cases they aren't even separate programs, or a compiler can generate machine object code instead of assembly code.)

In fact, you can perform the same multi-step process in one command for assembly language as well:

gcc hello.s -o hello

will invoke the assembler and linker and generate an executable file.

This is specific to gcc (and probably to most other compilers for Unix-like systems). Other implementations might be organized differently.

Upvotes: 3

Related Questions