Page 1 of 2

[SOLVED] Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 12:05 pm
by Tinei
Hello,
I'm writing my own bootloader (with two stages), everything seems ok, it gets the low and high memory size, it switches to protected mode, it runs the kernel...
But I would like to pass my datas collected in real mode in my "boot1.asm" file (the low and high memory size) to a function in C that will fill the multiboot structure fields, so that the kernel can use it.

Here is what my bootloader do (with "low_memory" and "high_memory" labels for words):

Code: Select all

;Ask for low memory size
printString msg_low_mem
int 0x12
mov [low_memory], ax
	
;Ask for high memory size
printString msg_high_mem
mov ah, 0x88
int 0x15
mov [high_memory], ax
jc error_high_mem

;Switching to protected mode...
;...

; Jump to multiboot loader
jmp dword 0x8:0x8000
My "multiboot.c" file:

Code: Select all

#include "../../kernel/include/multiboot.h"
_start()
{
	multiboot_info mbi;
	
	mbi.low_mem = ???;
	mbi.high_mem = ???;
	
	// Put the address of the multiboot structure in EBX, boot.asm will use it
	asm("movl %0, %%ebx"
		:
		: "a" (&mbi)
		: "ebx"
	);
	
	// Jump to the kernel
	__asm__("jmp $0x8, $0x9000");
}

__asm__(". = 449");
I've tried with the stack but it didn't work (push [low_memory] and push [high_memory]).
Can you help me?
Thank you in advance.

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 12:19 pm
by BrightLight
If you want it to be multiboot-compliant, EAX has to be 0x2BADB002 when the kernel is executed and EBX has to be a physical pointer to the multiboot structure. Set up a multiboot structure in memory following the multiboot standard.

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 12:36 pm
by iocoder
In C you can declare variables with "extern" modifier to inform the compiler that these variables are allocated in other object files.

So your code would be like this:

Code: Select all

#include "../../kernel/include/multiboot.h"
_start()
{
   multiboot_info mbi;
   extern multiboot_uint32_t low_memory, high_memory;
   
   mbi.low_mem = low_memory;
   mbi.high_mem = high_memory;
   
   // Put the address of the multiboot structure in EBX, boot.asm will use it
   asm("movl %0, %%ebx"
      :
      : "a" (&mbi)
      : "ebx"
   );
   
   // Jump to the kernel
   __asm__("jmp $0x8, $0x9000");
}

__asm__(". = 449");

Make sure low_memory and high_memory are set as global symbols by the assembler.

EDIT: I just noticed that you may not be linking the two stages together. In this case one nasty solution is to copy low_memory and high_memory to well known memory addresses, then read these addresses in _start(). However, doing the whole thing in real mode assembly is better as others pointed out.

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 12:40 pm
by Brendan
Hi,
Tinei wrote:I'm writing my own bootloader (with two stages), everything seems ok, it gets the low and high memory size, it switches to protected mode, it runs the kernel...
But I would like to pass my datas collected in real mode in my "boot1.asm" file (the low and high memory size) to a function in C that will fill the multiboot structure fields, so that the kernel can use it.
Why? Just put your multiboot structure somewhere that real mode assembly can access and store the data directly in it (in assembly); instead of storing it in the wrong place and then shifting it to the right place later in C.

Note that your C code will need to be 32-bit protected mode (as that's what multiboot requires when passing control to the kernel, and real mode can't even access the memory that the kernel and modules need to be loaded into). This means you're going to be switching between real mode and protected mode and passing a lot of information (the full memory map from "int 0x15, eax=0xE820", a whole bunch of stuff from VBE, data read from disk, etc). For this reason you're going to want to plan how you use the "real mode accessible" memory to minimise the hassle/overhead; and you're probably going to want to implement generic code to switch between real mode and protected mode (e.g. some sort of "switch to real mode, call routine and then return to protected mode" function like "void callRealMode(void)", where real mode registers are stored in a structure beforehand and retrieved from that structure after).

Also, don't forget that the low and high memory size fields in multiboot are almost entirely worthless, and that if you're writing a boot loader specifically for your OS there's no point bothering with the crippling limitations of multiboot.


Cheers,

Brendan

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 1:05 pm
by Tinei
Thank you iocoder, I had not thought about that.
But the problem now is that I end up with weird values:
I get 21488 kB for low memory and 255 kB for high memory it's not normal.

Thanks Brendan, I'm going to see that.

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 1:56 pm
by iocoder
Tinei wrote:Thank you iocoder, I had not thought about that.
But the problem now is that I end up with weird values:
I get 21488 kB for low memory and 255 kB for high memory it's not normal.

Thanks Brendan, I'm going to see that.
Hi, I think this is because the variables are allocated as "words" in assembly code, while they should be allocated as double words, initialized with zero in the beginning.

Or you can just replace "multiboot_uint32_t" in your C code with "multiboot_uint16_t". However don't forget that the low_mem and high_mem members of mbi are 32-bit.

Did you consider checking the AX value after calling int 0x12 and int 0x15? what do they contain?

I have just looked up at the wiki and it seems that int 0x12 and int 0x15 return sizes in KBs... maybe you ar reading the wrong memory location in your C code? can we see your new code?

Best.

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 3:14 pm
by Tinei
Here is a part of my boot1.asm:

Code: Select all

[BITS 16]

global low_memory
global high_memory

jmp start
%include "boot1_defines.inc"
%include "screen.inc"
%include "floppy.inc"
%include "a20_activation.inc"

start:
	; Segments initialization
    mov ax, 0x0100
    mov ds, ax
    mov es, ax
	; Stack from 0x8F000 to 0x80000
    mov ax, 0x8000
    mov ss, ax
    mov sp, 0xf000
	
	; ----------------------------------
	; Enable A20 Gate for compatibility
	; ----------------------------------
	call a20_activation
	
	; ---------------------------------------------
	; Get informations for the multiboot structure
	; ---------------------------------------------
	
	;Ask for low memory size
	printString msg_low_mem
	int 0x12
	mov [low_memory], ax
	
	;Ask for high memory size
	printString msg_high_mem
	mov ah, 0x88
	int 0x15
	mov [high_memory], ax
	jc error_high_mem
	
	; -------------------------------------------------------
	; Copy the multiboot loader and the kernel in the memory
	; -------------------------------------------------------
	
	; Load the multiboot loader from the floppy to the memory
	readSectors 0x800, 1, 5

        ; Some code...

        ; ------------------------------------
	; Switch to protected mode
	; ------------------------------------
	
	cli					; Disable interrupts because they won't be valid anymore after switching to protected mode
        lgdt [gdt_struct]	                ; Load the GDT in GDTR register, to indicate to the CPU where is located the GDT 
    
	mov eax, cr0
        or  ax, 1
        mov cr0, eax        ; Set the "PE" (Protection Enable) bit in CR0 register
	
	jmp run_kernel
	
run_kernel:
	mov ax, 0x10		; Initialize data segments to data selector (0x10)
        mov ds, ax
	mov cx, ax
        mov fs, ax
        mov gs, ax
        mov es, ax
        mov ss, ax
        mov esp, 0x9F000
	
	; Jump to multiboot loader
	jmp dword 0x8:0x8000
And multiboot.c:

Code: Select all

#include "../../kernel/include/multiboot.h"

_start()
{
	multiboot_info mbi;
	extern uint32 low_memory, high_memory;
	
	mbi.low_mem = low_memory;
	mbi.high_mem = high_memory;
	
	// Put the address of the multiboot structure in EBX, boot.asm will use it
	asm("movl %0, %%ebx"
		:
		: "a" (&mbi)
		: "ebx"
	);
	
	// Jump to the kernel
	__asm__("jmp $0x8, $0x9000");
}

__asm__(". = 449");
I don't know why, now my kernel print "16733168" for low and high memory :(

The commands I use in my makefile:
nasm -f elf32 boot1.asm -o boot1.elf
i686-elf-gcc -c multiboot.c -o multiboot.o
i686-elf-ld --oformat=binary -Ttext 0x0 $(BUILD_DIR)/boot1.elf $(BUILD_DIR)/multiboot.o -o $(BUILD_DIR)/multiboot.bin
cat boot0.bin multiboot.bin > bootloader.img

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 3:25 pm
by Brendan
Hi,
Tinei wrote:I don't know why, now my kernel print "16733168" for low and high memory :(
How does a 16-bit variable contain a value that's larger than 65536 (e.g. 16733168)?


Cheers,

Brendan

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 3:38 pm
by Tinei
I don't know, I think I made the things good, I don't understand what happens :(

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 3:39 pm
by iocoder
Tinei wrote:Here is a part of my boot1.asm:

Code: Select all

[BITS 16]

global low_memory
global high_memory

jmp start
%include "boot1_defines.inc"
%include "screen.inc"
%include "floppy.inc"
%include "a20_activation.inc"

start:
	; Segments initialization
    mov ax, 0x0100
    mov ds, ax
    mov es, ax
	; Stack from 0x8F000 to 0x80000
    mov ax, 0x8000
    mov ss, ax
    mov sp, 0xf000
	
	; ----------------------------------
	; Enable A20 Gate for compatibility
	; ----------------------------------
	call a20_activation
	
	; ---------------------------------------------
	; Get informations for the multiboot structure
	; ---------------------------------------------
	
	;Ask for low memory size
	printString msg_low_mem
	int 0x12
	mov [low_memory], ax
	
	;Ask for high memory size
	printString msg_high_mem
	mov ah, 0x88
	int 0x15
	mov [high_memory], ax
	jc error_high_mem
	
	; -------------------------------------------------------
	; Copy the multiboot loader and the kernel in the memory
	; -------------------------------------------------------
	
	; Load the multiboot loader from the floppy to the memory
	readSectors 0x800, 1, 5

        ; Some code...

        ; ------------------------------------
	; Switch to protected mode
	; ------------------------------------
	
	cli					; Disable interrupts because they won't be valid anymore after switching to protected mode
        lgdt [gdt_struct]	                ; Load the GDT in GDTR register, to indicate to the CPU where is located the GDT 
    
	mov eax, cr0
        or  ax, 1
        mov cr0, eax        ; Set the "PE" (Protection Enable) bit in CR0 register
	
	jmp run_kernel
	
run_kernel:
	mov ax, 0x10		; Initialize data segments to data selector (0x10)
        mov ds, ax
	mov cx, ax
        mov fs, ax
        mov gs, ax
        mov es, ax
        mov ss, ax
        mov esp, 0x9F000
	
	; Jump to multiboot loader
	jmp dword 0x8:0x8000
And multiboot.c:

Code: Select all

#include "../../kernel/include/multiboot.h"

_start()
{
	multiboot_info mbi;
	extern uint32 low_memory, high_memory;
	
	mbi.low_mem = low_memory;
	mbi.high_mem = high_memory;
	
	// Put the address of the multiboot structure in EBX, boot.asm will use it
	asm("movl %0, %%ebx"
		:
		: "a" (&mbi)
		: "ebx"
	);
	
	// Jump to the kernel
	__asm__("jmp $0x8, $0x9000");
}

__asm__(". = 449");
I don't know why, now my kernel print "16733168" for low and high memory :(

The commands I use in my makefile:
nasm -f elf32 boot1.asm -o boot1.elf
i686-elf-gcc -c multiboot.c -o multiboot.o
i686-elf-ld --oformat=binary -Ttext 0x0 $(BUILD_DIR)/boot1.elf $(BUILD_DIR)/multiboot.o -o $(BUILD_DIR)/multiboot.bin
cat boot0.bin multiboot.bin > bootloader.img
Does boot0.bin load the whole multiboot.bin to memory, or just the first sector of multiboot.bin? What does the line "readSectors 0x800, 1, 5" do? It seems to me you have linkage inconsistency.

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 3:53 pm
by Tinei
Boot0 (located in the first sector) loads Boot1 (that is 3 sectors sized) to 0x100:0, then boot1 loads the multiboot.c to 0x800:0 ("readSectors 0x800, 1, 5" one sector to load from the 5th sector). Then boot1 loads the kernel at 0x900:0.
I'm going to check one more time all that (the sectors, the linkage..)

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 4:06 pm
by iocoder
Tinei wrote:Boot0 (located in the first sector) loads Boot1 (that is 3 sectors sized) to 0x100:0, then boot1 loads the multiboot.c to 0x800:0 ("readSectors 0x800, 1, 5" one sector to load from the 5th sector). Then boot1 loads the kernel at 0x900:0.
I'm going to check one more time all that (the sectors, the linkage..)
Obviously, the linker thinks that multiboot.c is located in memory just after boot1 in the same segment, which is not true. So if low_memory variable is in .text section and is located at offset x from the beginning of boot1 .text section, then the linker shall substitute the "extern low_memory" with x, although DS base might not be 0x100 while multiboot is executing [I think 0x8 and 0x10 segments in your GDT structure have base of 0, right?]

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 4:14 pm
by iocoder
Some Suggestions:
  • Instead of using 0x100:0x0000 to refer to 0x1000, use 0x0000:0x1000. I mean make your bootloader jump to 0x0000:0x1000 instead of 0x100:0x0000, and in boot1.asm, set DS and ES to 0x0000 instead of 0x100. You would also need to insert ".org 0x1000" at the beginning of boot1.asm to tell the assembler that our code starts at 0x1000 offset. Now tell your linker that .text section starts at 0x1000 [-Ttext 0x1000], now all variables will be adjusted w.r.t the base of physical memory.
  • You may also just pass the variables using explicit memory areas instead of relying on the linker [bad solution].
  • You may try again to use stack [good solution].

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 4:38 pm
by Tinei
To better see the problem, I just created 2 files in a folder with just a few commands:
boot.asm:

Code: Select all

[BITS 16]

global test_var

jmp start
%include "screen.inc"
%include "floppy.inc"

start:
	; Segments initialization at 0x07C00
    mov ax, 0x07C0
    mov ds, ax
    mov es, ax
	; Stack from 0x8F000 to 0x80000
    mov ax, 0x8000
    mov ss, ax
    mov sp, 0xf000
	
	readSectors 0x100, 5, 2
	
	; ------------------------------------
	; Prepare to switch to protected mode
	; ------------------------------------
	
	; Compute the limit (=GDT size)
	mov ax, gdtend
    mov bx, gdt
    sub ax, bx						; Compute the difference between the first data and the last data of the GDT (gdtend - gdt)
    mov word [gdt_struct], ax		; Store the result in the first field of the structure

	; Compute the base (compute the physical address)
    xor eax, eax					; Set EAX to 0
    xor ebx, ebx					; Set EBX to 0
    mov ax, ds						; Store the data segment value in AX
    mov ecx, eax					; Store it in ECX via EAX
    shl ecx, 4						; Shift the value in ECX left 4 bits (the same as multiplying it by 16)
    mov bx, gdt						; Store label "gdt" address in BX
    add ecx, ebx					; Add data segment address and label "gdt" address
    mov dword [gdt_struct+2], ecx	; Store the result in the second field of the structure
	
	; ------------------------------------
	; Switch to protected mode
	; ------------------------------------
	
	cli					; Disable interrupts because they won't be valid anymore after switching to protected mode
    lgdt [gdt_struct]	; Load the GDT in GDTR register, to indicate to the CPU where is located the GDT 
    
	mov eax, cr0
    or  ax, 1
    mov cr0, eax        ; Set the "PE" (Protection Enable) bit in CR0 register
	
	jmp run_kernel
	
run_kernel:
	mov ax, 0x10		; Initialize data segments to data selector (0x10)
    mov ds, ax
	mov cx, ax
    mov fs, ax
    mov gs, ax
    mov es, ax
    mov ss, ax
    mov esp, 0x9F000
	
	; Jump to the kernel
	jmp dword 0x8:0x1000
	
	
test_var: dd 11

msg_reading_error: 	db "Error by reading on the floppy disk.", 13, 10, 0
msg_reboot: 		db "Press a key to restart the computer", 13, 10, 0
bootdisk: db 0

; GDT Descriptors for code segment and data segment
gdt:															; NULL descriptor
    db 0, 0, 0, 0, 0, 0, 0, 0
gdt_cs:															; Code segment descriptor
    db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10011011b, 11011111b, 0x0
gdt_ds:															; Data segment descriptor
    db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10010011b, 11011111b, 0x0
gdtend:															; Indicate the end of the table (useful to compute the limit)

; Structure containing the limit and the base of the GDT, which have to be loaded in the GDTR register (via "lgdt" instruction)
gdt_struct:
    dw 0  	; Limit
    dd 0  	; Base
	
; Fill the remaining bytes with 0 (to have 512 bytes in total)
times 510-($-$$) db 0
dw 0xAA55					; Boot signature: so that the bios recognize it as bootable
kernel.c:

Code: Select all

#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25

asm("jmp _start");

typedef unsigned int size_t;

char* vidmem=(char*)0xb8000;
int cursorX, cursorY = 0;

void itoa(char *buf, unsigned long int n, int base)
{
	unsigned long int tmp;
	int i, j;

	tmp = n;
	i = 0;

	do {
		tmp = n%base;
		buf[i++] = (tmp < 10) ? (tmp + '0') : (tmp + 'a' - 10);
	} while (n /= base);
	buf[i--] = 0;

	for (j=0; j<i; j++, i--) {
		tmp = buf[j];
		buf[j] = buf[i];
		buf[i] = tmp;
	}
}

void putc(char character)
{
	size_t index = (cursorY * SCREEN_WIDTH + cursorX)*2;
	vidmem[index] = character;
	vidmem[index+1] = 0x07;
	if(++cursorX == SCREEN_WIDTH)
	{
		cursorX = 0;
		cursorY++;
	}
}

size_t strlen(const char* str)
{
	size_t ret = 0;
	while ( str[ret] != 0 )
		ret++;
	return ret;
}

void print(const char* string)
{
	size_t size = strlen(string);
	size_t i = 0;
	for (i; i < size; i++)
		putc(string[i]);
}

_start()
{
	//asm("movb $0, %ah; movb $3, %al; int $0x10");
	print("Hello");
	extern unsigned long int test_var;
	char test[20]; itoa(test, test_var, 10);
	print(test);
}
Commands:
nasm -f elf32 boot.asm -o boot.elf
i686-elf-gcc -c kernel.c -o kernel.o
i686-elf-ld --oformat=binary -Ttext 0x0 boot.elf kernel.o -o test.bin
qemu-system-i386 -boot a -fda test.bin


It doesn't work too.

Re: Pass arguments (bootloader)

Posted: Sat Jan 02, 2016 5:34 pm
by iocoder
What about this code, does this work?

boot.asm:

Code: Select all

[BITS 16]

global test_var
extern _start

jmp start

start:
   ; Segments initialization at 0x0000:0x07C00
    mov ax, 0x0000
    mov ds, ax
    mov es, ax
    jmp 0x0000:begin  ; set CS to 0x0000 and IP to 0x7Cxx
begin:
   ; Stack from 0x8F000 to 0x80000
    mov ax, 0x8000
    mov ss, ax
    mov sp, 0xf000
    nop

   ;readSectors 0x100, 5, 2
    pusha
    mov ah, 02h
    mov al, 15
    mov ch, 0
    mov cl, 2
    mov dh, 0
    mov bx, 0x0000
    mov es, bx
    mov bx, 0x7E00
    int 0x13
    popa

   ; ------------------------------------
   ; Prepare to switch to protected mode
   ; ------------------------------------

   ; Compute the limit (=GDT size)
    mov ax, gdtend
    mov bx, gdt
    sub ax, bx                  ; Compute the difference between the first data and the last data of the GDT (gdtend - gdt)
    mov word [gdt_struct], ax      ; Store the result in the first field of the structure

   ; Compute the base (compute the physical address)
    xor eax, eax               ; Set EAX to 0
    xor ebx, ebx               ; Set EBX to 0
    mov ax, ds                  ; Store the data segment value in AX
    mov ecx, eax               ; Store it in ECX via EAX
    shl ecx, 4                  ; Shift the value in ECX left 4 bits (the same as multiplying it by 16)
    mov bx, gdt                  ; Store label "gdt" address in BX
    add ecx, ebx               ; Add data segment address and label "gdt" address
    mov dword [gdt_struct+2], ecx   ; Store the result in the second field of the structure

   ; ------------------------------------
   ; Switch to protected mode
   ; ------------------------------------

    cli                ; Disable interrupts because they won't be valid anymore
                       ; after switching to protected mode
    lgdt [gdt_struct]  ; Load the GDT in GDTR register, to indicate to the CPU
                       ; where is located the GDT

    mov eax, cr0
    or  ax, 1
    mov cr0, eax        ; Set the "PE" (Protection Enable) bit in CR0 register

    jmp run_kernel

run_kernel:
    mov ax, 0x10      ; Initialize data segments to data selector (0x10)
    mov ds, ax
    mov cx, ax
    mov fs, ax
    mov gs, ax
    mov es, ax
    mov ss, ax
    mov esp, 0x9F000

   ; Jump to the kernel
    jmp dword 0x8:0x7E00

test_var: dd 11

msg_reading_error:    db "Error by reading on the floppy disk.", 13, 10, 0
msg_reboot:       db "Press a key to restart the computer", 13, 10, 0
bootdisk: db 0

; GDT Descriptors for code segment and data segment
gdt:                                             ; NULL descriptor
    db 0, 0, 0, 0, 0, 0, 0, 0
gdt_cs:                                             ; Code segment descriptor
    db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10011011b, 11011111b, 0x0
gdt_ds:                                             ; Data segment descriptor
    db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10010011b, 11011111b, 0x0
gdtend:                                             ; Indicate the end of the table (useful to compute the limit)

; Structure containing the limit and the base of the GDT, which have to be loaded in the GDTR register (via "lgdt" instruction)
gdt_struct:
    dw 0     ; Limit
    dd 0     ; Base

; Fill the remaining bytes with 0 (to have 512 bytes in total)
times 510-($-$$) db 0
dw 0xAA55               ; Boot signature: so that the bios recognize it as bootable
kernel.c:

Code: Select all

#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25

asm("jmp _start");

typedef unsigned int size_t;

char* vidmem=(char*)0xb8000;
int cursorX, cursorY = 0;

void itoa(char *buf, unsigned long int n, int base)
{
   unsigned long int tmp;
   int i, j;

   tmp = n;
   i = 0;

   do {
      tmp = n%base;
      buf[i++] = (tmp < 10) ? (tmp + '0') : (tmp + 'a' - 10);
   } while (n /= base);
   buf[i--] = 0;

   for (j=0; j<i; j++, i--) {
      tmp = buf[j];
      buf[j] = buf[i];
      buf[i] = tmp;
   }
}

void putchr(char character)
{
   size_t index = (cursorY * SCREEN_WIDTH + cursorX)*2;
   vidmem[index] = character;
   vidmem[index+1] = 0x07;
   if(++cursorX == SCREEN_WIDTH)
   {
      cursorX = 0;
      cursorY++;
   }
}

size_t strlen(const char* str)
{
   size_t ret = 0;
   while ( str[ret] != 0 )
      ret++;
   return ret;
}

void print(const char* string)
{
   size_t size = strlen(string);
   size_t i = 0;
   for (i; i < size; i++)
      putchr(string[i]);
}

int _start()
{
   //asm("movb $0, %ah; movb $3, %al; int $0x10");
   print("Hello");
   extern unsigned long int test_var;
   char test[20];
   itoa(test, test_var, 10);
   print(test);
   while(1);
}
Makefile:

Code: Select all

MM=-m32 -march=i686 -mtune=generic
all:
	nasm -f elf32 boot.asm -o boot.elf
	gcc $(MM) -c kernel.c -o kernel.o
	gcc $(MM) -nostartfiles -nostdlib -nodefaultlibs -Xlinker \
	--oformat=binary -Xlinker -Ttext -Xlinker 0x7C00 \
	-o test.bin boot.elf kernel.o
	dd if=/dev/zero of=dumpsect.bin count=1 bs=512
	cat test.bin dumpsect.bin > test2.bin
	qemu-system-i386 -boot a -fda test2.bin