Page 1 of 2

C Bootloader Experiment

Posted: Thu Jul 24, 2008 9:50 pm
by 01000101
I got bored today and decided to try peicing together a C bootloader using GCC in the DJGPP environment. I don't plan on using this for anything too useful, but maybe it (once fixed) can help some others. I have the core of the code finished, but I just can't seem to either get the GDT installed correctly or I'm making a bad far jump. I haven't spent too much time on this so i appologize for its rough edges =). If you can fix it so that it jumps into PMode correctly, it's all yours. It was a fun experiment. I would like to get this solved just to know what the issue is. I'm not the greatest with AT&T syntax, as this makes this my 2-3 time using it. I have a feeling that the error has something to do with a label address not being correct.

This code takes up almost an entire sector of a floppy. The global 'start' label is only used for the linker I used.

Code: Select all


	asm(".code16gcc\n");
	asm(".globl start\n");
	asm("start:\n");
	asm("movw $0, %ax; movw %ax, %bx; movw %ax, %cx; movw %ax, %dx;");
	asm(".extern _main\n");
	asm("jmp _main;\n");

	asm("gdt_start: \
		.quad 0x0000000000000000; \
		.quad 0x00cf9a000000ffff; \
		.quad 0x00cf92000000ffff; \
		gdt_48: \
		.word (gdt_48 - gdt_start - 1); \
		.long gdt_start;");

void reset_floppy()
{
	unsigned char status = 0;

	__asm__ __volatile__("movb %1, %%ah;\
						  int $0x13;\
						  movb %%ah, %0;"
						  :"=r"(status)
						  :"r"(status)
						  :"%ax");
	if(status){reset_floppy();}
}

void load_floppy_sector(unsigned short segment, unsigned short offset,
						unsigned char nSectors, unsigned char Cylinder, unsigned char Start_Sector, unsigned char Head)
{
	unsigned char Command = 2, Status = 0;
    unsigned short ax, bx, cx, dx;
	
	ax = ((((Command << 8) & 0xFF00)) + (nSectors & 0x00FF));;
	bx = offset;
	cx = ((((Cylinder << 8) & 0xFF00))  + (Start_Sector & 0x00FF));
	dx = ((Head << 8) & 0xFF00);

	__asm__ __volatile__("movw %1, %%ax;\
						  movw %%ax, %%es;\
						  movw %2, %%ax;\
						  movw %3, %%bx;\
						  movw %4, %%cx;\
						  movw %5, %%dx;\
						  int $0x13;\
						  movb %%ah, %0;"
						  :"=m"(Status)
						  :"m"(segment), "m"(ax), "m"(bx), "m"(cx), "m"(dx)
						  :"%ax","%bx","%cx","%dx");
}

void main()
{

	reset_floppy();
	load_floppy_sector(0x0000, 0x1000, 18, 0, 1, 0); /* place 18 sectors starting at 0x1000:0x0000 */

	asm("inb $0x64, %al; \
		 movb $0xDF, %al; \
		 outb %al, $0x64;");

	asm("lgdt gdt_48; \
		 movl %cr0, %eax; \
		 or $1, %eax; \
		 movl %eax, %cr0; \
		 ljmp $0x08, $enter_pmode; \
		 hlt;");
}   
	asm(".code32 \
	     .globl enter_pmode; \
	      enter_pmode: \
	      hlt;");

asm(".org (0x200 - 2); .word 0xAA55;\n");
/* anything placed past here is in sector 2 of the floppy */


to compile just run a simple batch script

Code: Select all

gcc -ffreestanding -c "boot.c" -o boot.o
ld -T link.ld -o kernel.bin boot.o

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 10:35 am
by suthers
Nice experiment... :D
Jules

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 12:01 pm
by 01000101
ok, I meshed some of my own code with the GDT setup code from Brans tutorial at OSDever.net. Now it works fine and enters pmode.

Code: Select all

   asm(".code16gcc\n");
   asm(".globl start\n");
   asm("start:\n");
   asm("cli; movw $0, %ax; movw %ax, %bx; movw %ax, %cx; movw %ax, %dx;");
   asm(".extern _main\n");
   asm("jmp _main;\n");

struct gdt_entry
{
    unsigned short limit_low;
    unsigned short base_low;
    unsigned char base_middle;
    unsigned char access;
    unsigned char granularity;
    unsigned char base_high;
} __attribute__((packed));

struct gdt_ptr
{
    unsigned short limit;
    unsigned int base;
} __attribute__((packed));

struct gdt_entry gdt[3];
struct gdt_ptr gp;

void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran)
{
    gdt[num].base_low = (base & 0xFFFF);
    gdt[num].base_middle = (base >> 16) & 0xFF;
    gdt[num].base_high = (base >> 24) & 0xFF;
    gdt[num].limit_low = (limit & 0xFFFF);
    gdt[num].granularity = ((limit >> 16) & 0x0F);
    gdt[num].granularity |= (gran & 0xF0);
    gdt[num].access = access;
}

void gdt_install()
{
    gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
    gp.base = (unsigned int)&gdt;
    gdt_set_gate(0, 0, 0, 0, 0);
    gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
    gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
    asm("lgdt (_gp); \
         movl %cr0, %eax; \
         or $1, %eax; \
         movl %eax, %cr0; \
         movw $0x10, %ax; \
         movw %ax, %ds; \
         movw %ax, %es; \
         movw %ax, %fs; \
         movw %ax, %gs; \
         movw %ax, %ss; \
         jmp $0x08, $(0x7C00 + _pmode_entry_point);");
}

void main()
{
   gdt_install();
}

void pmode_entry_point()
{
     asm(".code32;");
     asm("cli;hlt;");
}

asm(".org (0x200 - 2); .word 0xAA55;\n");
/* anything placed past here is in sector 2 of the floppy */

compile with the same batch script from the first post.
have fun.

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 12:08 pm
by suthers
even better, I don't have time to compile it now, but I'll do it later, where does it place es:sp though?
Jules

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 12:33 pm
by 01000101
I'm not 100% sure. I'm not at that computer at the moment, but Ill check a little later.

I didn't set a stack start position, but it can be easily added. This is also because I didn't put alot of effort into this, I just wanted to get a somewhat working model out there so that all the people that ask about C bootloaders can not be discouraged from the start.

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 12:39 pm
by suthers
Well it's very good for boredom code (You should see mine :lol:, even I don't know what I was thinking after... :lol: )
Jules

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 3:21 pm
by 01000101
ok, here is a fully working bootloader that reads 18 floppy disk sectors into memory, loads PMode, and jumps to the loaded kernel.

Very basic and very rough looking, but it is a working C bootloader for people to experiment with.
due to size restraints from the high amount of code generated from the compiler, I was unable to include an extensible read_sectors function as it overflowed the 512 byte restriction. If anyone can squeeze one in that would be great. Also, set the 'phys' or similar variables in your linker script to 0x00000000. If it is set too high, it breaks the 16bit limit and errors.

@suthers: fixed the issue you mentioned before.

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 3:44 pm
by cg123
I'm not sure if it'll help all that much, but have you tried compiling with -Os?

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 3:51 pm
by 01000101
yes, and it seems to place 90% of what is supposed to be in sector 1, in sector 2.
I strongly suggest staying away from bootloader optimizations as any sort of deviation from the intended will result in chaos.

It is known that C functions are very code heavy just to make the function declaration. If you want to slim down the bootloader, id suggest cramming all of the C code into one big function instead of my many functions.

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 7:18 pm
by Alboin
It is known that C functions are very code heavy just to make the function declaration. If you want to slim down the bootloader, id suggest cramming all of the C code into one big function instead of my many functions.
Couldn't you just inline them, like: (From the GCC manual.)

Code: Select all

inline void foo (const char) __attribute__((always_inline));

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 7:29 pm
by 01000101
Alboin wrote:
It is known that C functions are very code heavy just to make the function declaration. If you want to slim down the bootloader, id suggest cramming all of the C code into one big function instead of my many functions.
Couldn't you just inline them, like: (From the GCC manual.)

Code: Select all

inline void foo (const char) __attribute__((always_inline));
indeed. should I post a wiki article about making a C bootloader? if I did, you could just edit it yourself, and maybe some other people could edit it as well until it has more room to add more functionality.

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 8:08 pm
by Alboin
01000101 wrote:indeed. should I post a wiki article about making a C bootloader? if I did, you could just edit it yourself, and maybe some other people could edit it as well until it has more room to add more functionality.
We're all about being 'complete', so I suppose that it couldn't hurt. I would think that a 'disclaimer' or sorts would be needed, however, to advise that this might not work on all compilers, versions, etc.

Re: C Bootloader Experiment

Posted: Fri Jul 25, 2008 8:55 pm
by 01000101
actually, I will probably end up re-writing the gdt stuff that way it has none of brans code in it (even though I think it is PD as well). I would end up releasing a revised version under public domain and without any liabilities expressed.

Re: C Bootloader Experiment

Posted: Sun Aug 24, 2008 4:23 am
by FlashBurn
Ok, I also tried to write my loader with gcc and the example code which was given at the start.

It works, but there is only one problem. GCC produces 32bit assembly code :(

Code: Select all

	.file	"loader.c"
/APP
	.code16gcc
	.globl _start
	_start:
	cli
	push %cs
	xorw %bx,%bx
	pop %ax
	movw %bx,%ss
	movw %ax,%ds
	movw %ax,%es
	movw %bx,%fs
	movw %bx,%gs
	sti
	jmp loader
/NO_APP
	.text
.globl loader
	.type	loader, @function
loader:
	pushl	%ebp
	movl	%esp, %ebp
.L2:
/APP
/ 18 "loader.c" 1
	hlt
/ 0 "" 2
/NO_APP
	jmp	.L2
	.size	loader, .-loader
	.ident	"GCC: (GNU) 4.3.1"
The problem there is the function prologue and the epilogue will be the same. Is there some switch so that gcc produces 16bit code?

I dissambled the code and it was really 16bit code, except of the function prologue ("pushl %ebp; movl %esp, %ebp")!

Re: C Bootloader Experiment

Posted: Sun Aug 24, 2008 8:23 am
by Brendan
Hi,
FlashBurn wrote:I dissambled the code and it was really 16bit code, except of the function prologue ("pushl %ebp; movl %esp, %ebp")!
Are you sure it's 32-bit code? It looks like 16-bit code using 32-bit registers to me.... ;)


Cheers,

Brendan