Gujin

From OSDev Wiki
Jump to: navigation, search

Gujin is a GPLed bootloader for the PC.

Contents

What can Gujin do?

It can read FAT12, FAT16, FAT32, ext2, ext3, ext4 (with constant inode size) and ISO 9660 filesystems.

It has a graphical user interface with mouse support, and can be installed on any media: floppy, hard/USB disk partitions, hard/USB disk MBR, CD/DVDROM, DVD-ram (i.e. FAT with 2048 bytes/sectors). It can also use a serial port as input/ouput instead of the screen and keyboard.

Gujin can chain-load other bootloader, load Linux kernel, has an unfinished loader of multiboot specification, but more importantly for our current interest it can load standard ELF files (more exactly GZIP compressed ELF files).

There is two major ELF variant on the PC: ELF32 (with 32 bits load address, sizes and entry point) and ELF64 (with 64 bits fields), gujin loads any of them and switch the processor in protected mode to jump to the ELF entry point.

Gujin will not try to set-up memory paging at all, that is the job of the kernel to select which kind of paging it wants - so even the 64 bits ELF files will have to handle the transition from 32 bits to 64 bits themselves (because you need paging to go to 64 bits mode).

Same for interrupts, Gujin switches to protected mode but does not re-enable interrupts, that is the job of the kernel being booted to set-up the Interrupt Decriptor Table and handle each interrupts, because BIOS will not help the kernel any more.

Gujin can also relocate the ELF file if it contains relocation information, see option --emit-relocs of the "ld" linker of the "binutils" toolchain.

Because most applications will want to collect BIOS informations before the switch to protected mode, Gujin can call in real-mode a function of the ELF file, and if this function returns zero - continue the loading process - else display an error message (for instance: "trying to execute a 64 bits application on a processor without this feature!").

Moreover, in most cases the kernel can decide to return to the Gujin bootloader (if it did not erased it from memory), without forcing a reboot.

The Gujin bootloader is built using standard Linux tools, it does not need Linux to run (only a not-too-buggy BIOS) - but we have to assume you are running Linux to use the GNU toolchain: GCC, binutils, GZIP... to produce the ELF file for the kernel. Also, Gujin installer needs either Linux 32 bits or Linux 64 bits to run and install the Gujin bootloader on a device.

Because Windows uses another executable format, you cannot install cygwin/MinGW on windows and use the compilation toolchain directly, in this case you would have to generate a cross compiler toolchain to produce ELF files, and that is out of scope for this description (but not that difficult).


So enough text description, let's try it!

The floppies being out-dated, let's say we want to use a USB stick as our test media. In some cases, we may want to use a SD card, basically the process is the same.

We first need to check that the target PC will be able to boot that USB stick or SD card, when Gujin is installed on it.

To have increased chances of success, we will ask the Gujin installer to reformat completely this USB disk or SD card, so first backup any interresting file you have in some other place.

Then, download this gujin executable if you are using a 32 bits Linux, and that gujin64 executable if you are using a 64 bits Linux:

If you do not trust anybody, download the source file gujin-*.tar.gz, extract it in a directory and type "make" - that will produce an executable named "gujin" (even on a 64 bits Linux).

Remember to visit http://gujin.org to check if there is a newer version of Gujin, and to increase Gujin author counters and keep him happy.

Then, we reformat that dedicated USB stick as a bootable FAT filesystem, erasing all its content: first go in "root" by typing "su" or "sudo" (distribution dependant), then get the device name of you USB stick (let's say it is /dev/sdg) (sometimes /dev/mmcblk0 for SD cards), and type:

 ./gujin /dev/sdg --disk=BIOS:0x00,auto

Depending on the size of the USB stick, that will have created either a FAT16 or a FAT32 (or even a FAT12) filesystem, but that point is not really important.

You then unplug this device, and replug it: most distribution will automatically mount the filesystem and display a window of its content: only a single file which is the bootloader itself.

To check that this filesystem is correctly created, you can type:

 /sbin/fsck.vfat /dev/sdg

With the Gujin installer parameters we used, that would have created a "superfloppy" format on our USB stick, that is currently the format most PC will be able to understand and boot from.

That does not mean your own PC will 100% boot it, due to BIOS bugs - so you need to test now that this USB stick is bootable by your PC or not: umount the USB stick, plug it in the test PC and power it on, see if Gujin is started (you will notice easily).

If it is not started try to check:

 - that the boot order in the BIOS is set to boot USB devices first
 - try the different USB devices if your BIOS has switchable items.
 - try to tell the Gujin installer to use the Extended BIOS instead by typing (--disk=EBIOS:0x00,auto is the default):
 ./gujin /dev/sdg
 - try to tell the Gujin installer to generate a real disk and not a superfloppy by:
 ./gujin --mbr /dev/sdg --disk=BIOS:0x00,auto
 - try the two previous options together:
 ./gujin --mbr /dev/sdg --disk=EBIOS:0x00,auto
 - try to use another (smaller) USB stick, some BIOS will only accept to boot from a FAT16 superfloppy

By now you should know a lot more about your BIOS, and have a bootable USB stick.


Then, we want to generate this ELF kernel - let's try to generate a "hello world": Create a file with that content:

 const char msg1[] = "Hello protected-mode text world! please reboot ..."; 
 #define STACKSIZE 64 * 1024 
 static unsigned stack[STACKSIZE / 4] __attribute__ ((aligned(32))); 
 
 void _start (void) 
 { 
 	/* We are flat non-paged memory and interrupt disabled */ 
 	asm (" mov %0,%%esp " : : "i" (&stack[STACKSIZE / 4])); 
 	volatile unsigned short *video_array = (volatile unsigned short *)0xB8000; 
 	unsigned cpt1; 
 
 	video_array += 10 * 80; /* few empty lines */ 
 	/* We want blue background color and lightgray foreground, so 0x1700: */ 
 	for (cpt1 = 0; cpt1 < sizeof(msg1) - 1; cpt1++) 
 		video_array[cpt1] = 0x1700 + msg1[cpt1]; 
 
 	while (1) 
 		continue; 
 } 

Then compile it like (you may need to add "-fno-stack-protector" too, distribution dependant):

 $ gcc -m32 -Wall -O2 -s -static -nostartfiles -nodefaultlibs -Wl,-Ttext=0x110000 hello.c -o hello.elf 

and compress it by:

 $ gzip -9 hello.elf -c > hello.kgz 

You just need to copy that hello.kgz file into the USB stick, and reboot with this USB stick, you will get a menu with "hello.kgz" displayed.

If you click on this filename, you will have "Hello protected-mode text world! please reboot ..." displayed (Because this hello-world do not manage graphic modes, you have to "force start kernel in text mode" in Gujin configuration).

If you want a bit more complex "hello world" applications, you should download Gujin install*.tar.gz pack .

you will find there few KGZ files, like previous example but with some addresses displayed to show the exact position of the application in memory.

You will see hello_32bits.kgz and hello_32bits_r.kgz to show the effect of relocation, and hello_64bits.kgz and hello_64bits_r.kgz to show 64 bits applications.

To get the source code, just download the file gujin*.tar.gz, and extract the corresponding c files.

To see the command used to compile them, just search the string "hello_32bits.kgz:" (with semicolon) in the file Makefile.

If you just want a real mode "hello world", you can do that too with an ELF file where the load address is null for the code segment.

There is a limit of 64 Kbytes for real mode ELF file.

Just create a file which contains:

 /* hello_bios.c - public domain - no support whatsoever */
 
 /* Constant entry block, max 64 KB code+data+stack, do not touch: */
 asm (
 "	.code16gcc				\n"
 "	.section .init,\"ax\",@progbits		\n"
 "	.global _start				\n"
 "_start:					\n"
 //  "int $3 # if debugging with borland td.exe, started by tiny.exe	\n"
 "	pushw	%ds				\n"
 "	pushw	%es				\n"
 "	pushw	%fs				\n"
 "	pushw	%gs				\n"
 "	pushfl					\n"
 "	pushal					\n"
 "	movw	%ss,%cs:1f+1			\n"
 "	movw	%sp,%cs:2f+2			\n"
 "	movw	%cs,%ax				\n"
 "	movw	%ax,%ds				\n"
 "	movw	%ax,%es				\n"
 "	movw	%ax,%fs				\n"
 "	movw	%ax,%gs				\n"
 "	movw	%ax,%ss				\n"
 "	movl	$0,%esp				\n"
 "	cld					\n"
 "	calll	main				\n"
 "	movl	%eax,%cs:3f+2			\n"
 "1:	movw	$0,%ax				\n"
 "	movw	%ax,%ss				\n"
 "2:	movl	$0,%esp				\n"
 "	popal					\n"
 "	popfl					\n"
 "	popw	%gs				\n"
 "	popw	%fs				\n"
 "	popw	%es				\n"
 "	popw	%ds				\n"
 "3:	movl	$0,%eax				\n"
 "	lretw					\n"
 "	.previous				\n"
 );
 
 /* For information about asm() construct, see GCC manual:
 http://gcc.gnu.org/onlinedocs/gcc-4.5.0/gcc/Extended-Asm.html
   For information about BIOS services, see
 http://stanislavs.org/helppc/
 */
 enum standard_color {
 	black,		blue,		green,		cyan,
 	red,		magenta,	brown,		lightgray,
 	darkgray,	lightblue,	lightgreen,	lightcyan,
 	lightred,	lightmagenta,	yellow,		white
 	};
 
 /* This assumes 16 colors modes, text or graphic - i.e. not 256 colors: */
 static inline void
 VGA_writestring (const char *string, unsigned char row, unsigned char col)
 {
 	unsigned short page_attribute = 16 * blue + lightgray, strlen_string;
 	const char *endstring = string;
 
 	/* We cannot rely on libc to provide strlen(): */
 	while (*endstring)
 		endstring++;
 	strlen_string = endstring - string;
 
 	asm (
 "	xchgl	%%ebp,%4		\n"
 "	int	$0x10 # writestring	\n"
 "	xchgl	%%ebp,%4		\n"
 		: : "a" (0x1301), "b" (page_attribute),
 		"c" (strlen_string), "d" (((unsigned short)row << 8) | col),
 		"r" (string)	/* in fact %es:%bp, but "B" (string) doesn't work */
 		);
 }
 
 static inline void BIOS_wait (unsigned nb_microsecond)
 {
 	unsigned short status;
 
 	/* Use volatile when there is at least one result, but even if the result
 		is not used, the code has to be inserted (status is the result): */
 	asm volatile ("	int	$0x15	"
 		: "=a" (status)
 		:  "a" (0x8600), "d" (nb_microsecond & 0xFFFF), "c" (nb_microsecond >> 16)
 		);
 }
 
 int main (void)
 {
 	VGA_writestring ("Hello BIOS world! Please wait...\r\n", 0, 0);
 	BIOS_wait (3 * 1000 * 1000); /* 3 seconds before returning to Gujin*/
 //	return 0x80000000; /* to return to Gujin menu */
 	return 0xA0000000; /* to return to Gujin menu, do not display "press a key to continue" */
 }


And then compile it by:

 $ gcc -m32 -Wall -Os -s -static -nostartfiles -nodefaultlibs -Wa,-R -Wl,--section-start,.init=0 hello_bios.c -o hello_bios.elf

and compress it:

 $ gzip -9 hello_bios.elf -c > hello_bios.kgz

you can copy this hello_bios.kgz to the USB stick and run it, I hope the different functions of the source file are obvious.

The Gujin bootloader is willing to let the real mode and protected mode kernel to access the bootloader internal data (like what is the current video mode, how to display strings...) as long as the kernel is licensed under the GPL license - i.e. when the GZIP kernel contains a GZIP comment describing its license. That is illustrated by cleandisk.kgz compiled form this cleandisk.c source code and linked with this linker file.

The main point about the GPL license is that it is not possible to draw a line in between Gujin possible bug and a closed source application: it is no allowed to modify Gujin data while in real mode - but there isn't any enforcement by hardware.

Obviously, Gujin will enable you to run an ELF file with a real mode part AND a protected mode part, just have a look at the example hello_gpl.kgz and its source code hello_gpl.c.

See Also

Articles

Threads

External Links

  • Gujin at sourceforge.net
Personal tools
Namespaces
Variants
Actions
Navigation
About
Toolbox