1

I have compiled these programs:

  BITS 16
extern _main
start:
      mov ax, 07C0h 
      add ax, 288
      mov ss, ax 
      mov sp, 4096

      mov ax, 07C0h 
      mov ds, ax 

      mov si, text_string 
      call print_string 

      jmp $

      text_string db 'Calling Main Script'
      call _main

print_string:
      mov ah, 0Eh 

.repeat:
      lodsb 
      cmp al, 0
      je .done 
      int 10h
      jmp .repeat 

.done:
      ret 

      times 510-($-$$) db 0
      dw 0xAA55

and this as a test just to try linking them

int main()
{
  return 0;
}

both compile completely fine on their own using: gcc -Wall -m32 main.c nasm -f elf bootloader.asm however I cannot link them using: ld bootloader.o main.o -lc -I /lib/Id-linux.so.2 and I get this error:

ld: i386 architecture of input file `bootloader.o' is incompatible with i386:x86-64 output
ld: i386 architecture of input file `main.o' is incompatible with i386:x86-64 output
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
ld: bootloader.o: file class ELFCLASS32 incompatible with ELFCLASS64
ld: final link failed: file in wrong format

Any help would be great thanks

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • See https://stackoverflow.com/questions/30184929/use-ld-on-64-bit-platform-to-generate-32-bit-executable – Michael Feb 06 '20 at 13:08
  • Your assembly code is for 16bits configuration. Actual OS supports only 32-64bits code. And I don't see any C code here. – Frankie_C Feb 06 '20 at 13:39
  • You also need to compile your `main.c` with `-c`, otherwise `gcc` will generate an executable instead of a relocatable object (`.o`). – Erlkoenig Feb 06 '20 at 13:41
  • 2
    What you are doing is difficult to get right. You need to create code that runs in real mode, the segment registers should be the same (CS=DS=ES=SS), you will run out of space fast in 512 byte boot sector with C code so you'll want to read more sectors. You have to build freestanding, and you can't link to the Linux loader, your linker wants to default to 64-bit so you need a `-melf_i386`. That is just to name a few problems (there are many more). This answer may be of some use: https://stackoverflow.com/questions/53714458/getting-int-16h-key-scancode-instead-of-character/53716486#53716486 – Michael Petch Feb 06 '20 at 13:42
  • You also won't be able to use the C library and you'll be forced to likely use inline assembly in C or create assembly functions to do low level work (like calling the BIOS). I really don't recommend what you are doing without at least changing into protected mode in your bootloader assembly code before calling into C code. – Michael Petch Feb 06 '20 at 13:54

2 Answers2

1

GCC by default already dynamically linking with libc, so if you want linking manually using ld, be sure make your ELF executable static, you can passing with -static flag.

gcc -o <filename> <filename>.c -static -Wall -m32 then link with ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o <filename> -lc <filename>.o

I guess, since assembler like NASM has statically (stand-alone without libc) you can make ELF dynamic executable directly with libc, you can passing with -dynamic-linker flag.

For example :

x86

nasm -f elf32 -o <filename>.o <filename>.asm
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o <filename> -lc <filename>.o

x86_64

nasm -f elf64 -o <filename>.o <filename>.asm
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o <filename> -lc <filename>.o
  • You don't want `BITS 16` code inside a 32-bit or 64-bit ELF executable. Look at the code, it's an MBR boot sector. But yes, those commands would work if the `.asm` had code for a 32-bit or 64-bit function. You forgot to say that `_main` is the wrong name, though; GNU/Linux ELF doesn't prepend a leading underscore. – Peter Cordes Feb 07 '20 at 05:21
0

In case you just want to do some simple assembly programming on your PC, don't actually need 16bit code, and don't want to dive into bootloaders and OS development, you can get started much more easily by writing 32bit (IA32) or 64bit (AMD64) application code. Instead of BIOS interrupts, you'd use (Linux) system calls.

An example "hello world" for i386 would be:

.section .text._start
.global _start
.type  _start, %function

_start:
    mov $4, %eax
    mov $1, %ebx
    mov $message, %ecx
    mov $14, %edx

    int $0x80

    mov $1, %eax
    xor %ebx, %ebx
    int $0x80

.section .rodata.message
.type message, %object
message:
    .ascii "Hello, World!\n"

Assemble, link and execute via

as --32 test32.S -o test32.o && ld -m elf_i386 test32.o -o test32 && ./test32

The same thing for AMD64:

.section .text._start
.global _start
.type  _start, %function

_start:
    mov $1, %rax
    mov $1, %rdi
    mov $message, %rsi
    mov $14, %rdx

    syscall

    mov $0x3c, %rax
    xor %rdi, %rdi
    syscall

.section .rodata.message
.type message, %object
message:
    .ascii "Hello, World!\n"

Assemble, link and execute via

as --64 test64.S -o test64.o && ld -m elf_x86_64 test64.o -o test64 && ./test64

Just for fun, the same thing for ARM (32bit):

.syntax unified
.arch armv6
.arm

.section .text._start
.global _start
.type  _start, %function

_start:
    movs r7, #4
    movs r0, #1
    ldr r1, =#message
    movs r2, #14
    svc #0

    movs r7, #1
    movs r0, #0
    svc #0

    .ltorg

.section .rodata.message
.type message, %object
message:
    .ascii "Hello, World!\n"

Assemble, link and execute via (e.g. on a Raspberry PI or Beaglebone):

as testarm.S -o testarm.o && ld testarm.o -o testarm && ./testarm
Erlkoenig
  • 2,664
  • 1
  • 9
  • 18