Page 1 of 1

GNU linker bootcode from 16bit up to 64bit is a no-no?

Posted: Tue Feb 05, 2013 2:08 pm
by gusc
Hello!

I've been surfing this site for about 3 months now, and this is the first time I've stumbled on something I can not find answer for anywhere. I'm writing a bootcode for x86 that starts in real mode (16bit), then does protected mode (32bit) jump, prepares interrupts and stuff, then does a jump to long mode (64bit).
  • The source
  • I'm working on windows with MinGW
  • I've build myself x86_64 GNU toolchain (binutils 2.23, gcc 4.72)
  • I'm using NASM for assembly
  • Intermediate object formats: elf and elf64
  • Output format: binary - to be written in BIOS Boot partition
So what's happening. Everything up until Protected mode compiles fine, it runs in Qemu, Bochs and VirtualBox. I've compiled everythin using the x86_64 toolchain, but specified 32bit output. Now that I've added some 64bit code and compile it with:

Code: Select all

x86_64-pc-elf-gcc -nostdlib -fno-builtin -nostartfiles -nodefaultlibs
and link it:

Code: Select all

x86_64-pc-elf-ld -melf_i386 -T bbp.ld $(OBJECTS) -o ../Release/bbp.img
Where $(OBJECTS) are mixed 16bit, 32bit and 64bit code objects and in bbp.ld I've specified to output binary file. The linker says:

Code: Select all

i386:x86-64 architecture of input file `boot64/main64.c.o' is incompatible with i386 output
Now if I change linker attributes to:

Code: Select all

x86_64-pc-elf-ld -melf_x86_64 -T bbp.ld $(OBJECTS) -o ../Release/bbp.img
The linker says:

Code: Select all

i386 architecture of input file `boot32/main32.c.o' is incompatible with i386:x86-64 output
Simply put, it does not allow me to mix i386 with x86_64 code in a single binary file. I've only found one small paragraph in gnu toolchain documents, that you can't (and I've lost the URL for now).

Is there any other solution, or maybe somebody could point me in the right direction? My idea is to keep this 16bit, 32bit and 64bit bootcode in a single build, so that I can refer to some data structures defined earlier and not to worry about memory locations other than my entry point.

Thank you in advance!

PS. I've also tried to embed binary stabs in NASM, but I run in to relocation problems.

Re: GNU linker bootcode from 16bit up to 64bit is a no-no?

Posted: Tue Feb 05, 2013 2:48 pm
by beyondsociety
Quick guess, but i dont see you passing -m32 to the gcc command for compiling the kernel, I believe you need both the -m32 and -melf_386 in order for it to work for compiling 32-bit code with a 64-bit cross-compiler.

Re: GNU linker bootcode from 16bit up to 64bit is a no-no?

Posted: Tue Feb 05, 2013 3:29 pm
by scippie
I have done it with FASM without any problems. But now I don't do it like that anymore, I prefer the 3 modes being split up. But is't possible:

Code: Select all

org	   7C00h
USE16

start:
	jmp short boot_code
	nop

bBootDrive	db 'X'

boot_code:
	cli
	push cs
	pop ds

	; BIOS puts boot drive code in dl
	mov [bBootDrive], dl

	; Set ss to 9000h
	mov ax, 09000h
	mov ss, ax
	mov sp, start
	mov es, ax
	
	mov si, sp
	mov di, sp
	; Copy 256 words (512 bytes)
	mov cx, 0100h
	cld
	rep movsw

	; ds:si->int 1Eh "handler"
	lds si, [1Eh * 4]
	; es:di->boot_code + 4
	mov di, boot_code + 4
	; Save the address of the old int 1Eh "handler"
	mov [cs:di - 4], si
	mov [cs:di - 2], ds
	; Store the offset and segment of new int 1Eh "handler"
	mov [cs:1Eh * 4], di
	mov [cs:1Eh * 4 + 2], ax
	; Copy 11 bytes
	mov cx, 11
	rep movsb

	mov ax, 0900h
	mov ds, ax
	; set head settle time to 15ms
	mov byte [di-2], 15
	; al = sectors per track (last sector)
	;mov al, byte [wSecsPerTrack]
	; Set last sector on track
	;mov byte [di-7], al
	; Set last sector on track <-- hmm, ok?
	mov byte [di-7], 36
	; Jump to the new code
	jmp far 9000h:jump_here
jump_here:
	mov ax, 0160h
	mov es, ax
	; Read 1 sector starting on head 0, track 0, sector 1
	mov ax, 020Eh
	mov cx, 0002h
	mov dh, 0
	mov dl, [bBootDrive]
	; Read into es:bx = 0160h:0000h
	xor bx, bx
	int 13h
	; TODO: check cf, ah, al
	jnc read_ok

	mov bx, 0b800h
	mov es, bx
	xor di, di
	mov bx, 01f45h
	mov [es:di], bx
	mov [es:di + 2], ax
	hlt

read_ok:
	; Enable A20
	in al, 0x92
	or al, 2
	out 0x92, al

	jmp far 0000h:1600h

	rb 7C00h + 512 - 2 - $ ; fill boot sector with emptyness except for signature
	db 055h, 0aah	       ; boot signature

org	1600h

kernel_start:
	lgdt [cs:GDTR]
	; Switch to protected mode
	mov eax, cr0
	or al, 1
	mov cr0, eax

	jmp CODE_SELECTOR:protectedmode_start

NULL_SELECTOR = 0
DATA_SELECTOR = 1 shl 3 		; flat data selector (ring 0)
CODE_SELECTOR = 2 shl 3 		; 32-bit code selector (ring 0)
LONG_SELECTOR = 3 shl 3 		; 64-bit code selector (ring 0)

GDTR:					; Global Descriptors Table Register
	dw 4*8-1				; limit of GDT (size minus one)
	dq GDT				; linear address of GDT

GDT rw 4				; null desciptor
	dw 0FFFFh,0,9200h,08Fh		; flat data desciptor
	dw 0FFFFh,0,9A00h,0CFh		; 32-bit code desciptor
	dw 0FFFFh,0,9A00h,0AFh		; 64-bit code desciptor

USE32

protectedmode_start:
	; Load 4 GB data descriptor to all data segment registers
	mov eax, DATA_SELECTOR
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax
	
	mov edi, [0x80000 + 40]
	
	mov eax, 0xFFFF0000
	mov ecx, 65536 / 4
	rep stosd

	; Enable physical-address extensions
	mov eax, cr4
	or eax, 1 shl 5
	mov cr4, eax

	; Clear the page tables
	mov edi, 70000h
	mov ecx, 4000h shr 2
	xor eax, eax
	rep stosd

; Address of first PDE table, 4GB of pages to map
	mov edi, 72000h
	mov eax, 0 + 10000111b
	mov ecx, 2048
make_page_entries:
	stosd
	add edi, 4
	add eax, 200000h
	loop make_page_entries

	mov dword [70000h], 71000h + 111b ; PML4 index 0 = PDPE at 71000h
	mov dword [71000h +  0], 72000h + 111b ; PDPE index 0 = PDE at 72000h
	mov dword [71000h +  8], 73000h + 111b ; PDPE index 1 = PDE at 73000h
	mov dword [71000h + 16], 74000h + 111b ; PDPE index 2 = PDE at 74000h
	mov dword [71000h + 24], 75000h + 111b ; PDPE index 3 = PDE at 75000h
	mov dword [71000h + 32], 76000h + 111b ; PDPE index 4 = PDE at 76000h
	mov dword [71000h + 40], 77000h + 111b ; PDPE index 5 = PDE at 77000h
	mov dword [71000h + 48], 78000h + 111b ; PDPE index 6 = PDE at 78000h
	mov dword [71000h + 56], 79000h + 111b ; PDPE index 7 = PDE at 79000h

	; Load page-map level-4 base
	mov eax, 70000h
	mov cr3, eax

	; EFER MSR
	mov ecx, 0C0000080h
	rdmsr
	; Enable long mode
	or eax, 1 shl 8
	wrmsr

	; Enable paging
	mov eax, cr0
	or eax, 1 shl 31
	mov cr0, eax

	jmp LONG_SELECTOR:longmode_start

USE64
longmode_start:
	mov al, 10001b		     ; begin PIC 1 initialization
	out 20h, al
	mov al, 10001b		     ; begin PIC 2 initialization
	out 0A0h, al
	mov al, 80h		     ; IRQ 0-7: interrupts 80h-87h
	out 21h, al
	mov al, 88h		     ; IRQ 8-15: interrupts 88h-8Fh
	out 0A1h, al
	mov al, 100b		     ; slave connected to IRQ2
	out 21h, al
	mov al, 2
	out 0A1h, al
	mov al, 1		     ; Intel environment, manual EOI
	out 21h, al
	out 0A1h, al
	in al, 21h
	mov al, 0;11111100b	     ; enable only clock and keyboard IRQ
	out 21h, al
	in al, 0A1h
	mov al, 0;11101111b      ; enable only ps2-mouse IRQ
	out 0A1h, al

	xor edi, edi		     ; create IDT (at linear address 0)
	mov ecx, 21
make_exception_gates: 		; make gates for exception handlers
	mov esi, exception_gate
	movsq
	movsq
	loop make_exception_gates
	mov ecx, 256 - 21
make_interrupt_gates: 		; make gates for the other interrupts
	mov esi, interrupt_gate
	movsq
	movsq
	loop make_interrupt_gates

	mov word [80h*16], clock     ; set IRQ 0 handler
	mov word [81h*16], keyboard  ; set IRQ 1 handler
	mov word [82h*16], irq2
	mov word [83h*16], irq3
	mov word [84h*16], irq4
	mov word [85h*16], irq5
	mov word [86h*16], irq6
	mov word [87h*16], irq7
	mov word [88h*16], irq8
	mov word [89h*16], irq9
	mov word [8Ah*16], irqA
	mov word [8Bh*16], irqB
	mov word [8Ch*16], mouse     ; set IRQ 12 handler
	mov word [8Dh*16], irqD
	mov word [8Eh*16], irqE
	mov word [8Fh*16], irqF
	lidt [IDTR]
...
This code is quick&dirty and I have omitted some of it because it's not necessary to show that it's possible.

That's FASM of course.

Re: GNU linker bootcode from 16bit up to 64bit is a no-no?

Posted: Wed Feb 06, 2013 12:13 am
by gusc
beyondsociety wrote:Quick guess, but i dont see you passing -m32 to the gcc command for compiling the kernel, I believe you need both the -m32 and -melf_386 in order for it to work for compiling 32-bit code with a 64-bit cross-compiler.
I've 3 separate makefiles:
  • for boot16 - 16bit code that sets up protected mode (single asm file) and calls boot32 main() function
  • for boot32 - 32bit code that sets up interrupts etc.
  • for boot64 - 64bit code that will be my long mode environment
I compile them accordingly:

Code: Select all

Boot16 is just a single ASM bootstrap code:
nasm -felf -o boot16.s.o boot.asm

Boot32:
every ASM file:
nasm -felf -o some_file.s.o some_file.asm
every C file:
x86_64-pc-elf-gcc -m32 -march=i686 -nostartfiles -nostdlib -nodefaultlibs -fno-builtin -Wno-attributes -c some_file.c -o some_file.c.o

Boot64 (currently only C, to see if it compiles at all):
x86_64-pc-elf-gcc -nostdlib -fno-builtin -nostartfiles -nodefaultlibs -c some_file.c -o some_file.c.o
So as you can see i'm using -m32 for 32bit code. The problem seems to be that elf64 (i think) does not allow 16bit or 32bit code to be included. :(

Re: GNU linker bootcode from 16bit up to 64bit is a no-no?

Posted: Wed Feb 06, 2013 12:21 am
by gusc
scippie wrote:I have done it with FASM without any problems. But now I don't do it like that anymore, I prefer the 3 modes being split up.
...
That's FASM of course.
Yes it's no problem for NASM too - as I said - I tried to embed binary blob output from GCC, but then I had problems with variable/data locations as I had to specify the location for each portion my self or they'll end up being located relative to 0x0000, not 0x7C00.

NASM is fine with mixing [bits 16] and [bits 64] in a single file, the problem is with the linker that I use to mix C and ASM together - as I prefer C for maintainability.

Also I'm not using a file system (only GPT), so I can't create boot16.bin, boot32.bin and boot64.bin and chain load them, I'm trying to mix them in a single boot.bin that's properly linked together and boot16 is the main controller here.

Re: GNU linker bootcode from 16bit up to 64bit is a no-no?

Posted: Wed Feb 06, 2013 4:53 am
by Velko
I had a similar problem while trying to link some 32-bit C code into 64-bit kernel.

I solved this by compiling it using -m32 and then using objcopy to convert it to elf64-x86-64 format.

Re: GNU linker bootcode from 16bit up to 64bit is a no-no?

Posted: Wed Feb 06, 2013 5:57 am
by gusc
Velko wrote:I had a similar problem while trying to link some 32-bit C code into 64-bit kernel.

I solved this by compiling it using -m32 and then using objcopy to convert it to elf64-x86-64 format.
Hey, thanks, this worked .. kind of .. it compiles well, but all the memory locations are resolved relative to 0x0000 instead of 0x7C00 (which is where I load my stage 2).

What did I do:
  • I complied all the 16bit and 32bit C and ASM code in elf32 objects
  • I linked them together in a single elf32 object file
  • I objcopy-ed it into elf64 object file
  • I compiled 64bit C code and linked everything togeter
There were no linker errors, but all the memory locations point to a location relative to ORG 0x0000. Does the linker even tries to do some address resolutions?

My final linker script goes like this:

Code: Select all

OUTPUT_FORMAT(binary)
ENTRY(start16)
SECTIONS {
	.text 0x7C00 : AT( 0x7C00 ) {
		boot16_64.o(.text); 
		*(.text);
	}
	.rodata : {
		*(.rodata);
	}
	.data : {
		*(.data);
	}
	/DISCARD/ : {
		*(.eh_frame);
		*(.bss);
	}
	. = ALIGN(512);
}
As you can see, I've pointed .text section to be located at 0x7C00, and any subsequent sections that follow should be located right after. What could be the problem here?

Re: GNU linker bootcode from 16bit up to 64bit is a no-no?

Posted: Wed Feb 06, 2013 8:43 am
by gusc
Aha! Gotcha - I better start learning to use my tools properly. I forgot (and actually didn't know about) to add -i switch to linker, that creates incremental object files (which are still available for later relocation). And objcopy does the trick pretty well.

Thank you guys!

My main makefile now looks like this:

Code: Select all

AS = nasm -felf -O0
LD = i786-elf-ld -i
LD64 = x86_64-pc-elf-ld -melf_x86_64
OC = x86_64-pc-elf-objcopy -I elf32-i386 -O elf64-x86-64
OBJECTS = boot16_64.o boot64.o

all: $(OBJECTS)
	$(LD64) -T bbp.ld $(OBJECTS) -o ../Release/bbp.img

boot16_64.o: boot16.s.o
	make -C boot32/
	$(LD) -T boot16_32.ld boot16.s.o boot32.o -o boot16_32.o
	$(OC) boot16_32.o boot16_64.o

boot64.o:
	make -C boot64/	

%.s.o: %.asm
	$(AS) -o $@ $<

clean:
	rm -f *.o boot32/*.o boot64/*.o ../Release/bbp.img ../Release/disk.img
and boot32 makefile:

Code: Select all

AS = nasm -felf -O0
CC = i786-elf-gcc -march=i686 -nostartfiles -nostdlib -nodefaultlibs -fno-builtin -Wno-attributes
LD = i786-elf-ld -i
OBJECTS = gdt.s.o idt.s.o interrupts.s.o screen.c.o memory.c.o string.c.o acpi.c.o main32.c.o

#all: $(OBJECTS)

all: boot32.o

boot32.o: $(OBJECTS)
	$(LD) -T boot32.ld $(OBJECTS) -o ../boot32.o

%.s.o: %.asm
	$(AS) -o $@ $<

%.c.o: %.c
	$(CC) -c $< -o $@

clean:
	rm -f *.o