MSVC Grub - A new way

This forums is for OS project announcements including project openings, new releases, update notices, test requests, and job openings (both paying and volunteer).
Post Reply
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

MSVC Grub - A new way

Post by neon »

Hello everyone,

Ive been working on experimenting with booting GRUB using Microsoft Visual C++ 2008 and developed an easy method of allowing one to boot a PE kernel built with MSVC with GRUB. The method seems too easy to warrant a tutorial on it, so I decided to create a post describing the method in hopes that it will be helpful for other developers using MSVC.

To get it booting, all we need to do is define a multiboot structure. Here is the declaration:

Code: Select all

#pragma pack (push, 1)

/**
*	Multiboot structure
*/
typedef struct _MULTIBOOT_INFO {

	uint32_t magic;
	uint32_t flags;
	uint32_t checksum;
	uint32_t headerAddr;
	uint32_t loadAddr;
	uint32_t loadEndAddr;
	uint32_t bssEndAddr;
	uint32_t entryPoint;
	uint32_t modType;
	uint32_t width;
	uint32_t height;
	uint32_t depth;

}MULTIBOOT_INFO, *PMULTIBOOT_INFO;

#pragma pack(pop,1)
Easy enough :) With our structure declared, we just define the multiboot structure in the beginning of the .text section. The .text section is always the first section of the image after the PE header structures. PE data (such as resources and export/import tables etc) are always at the end of the binary so the PE headers are always the same size (0x400). The multiboot spec says that this structure must be defined within the first 8k in the image, aligned on a 32-bit boundary. By placing this structure at the beginning of a segment in the file, it will always be at 0x400 aligned to the set section alignment, which are always 32 bit aligned:

Code: Select all

#pragma section(".text")
__declspec(allocate(".text"))
MULTIBOOT_INFO _MultibootInfo = {

	MULTIBOOT_HEADER_MAGIC,
	MULTIBOOT_HEADER_FLAGS,
	CHECKSUM,
	HEADER_ADDRESS,
	LOADBASE,
	0, //load end address
	0, //bss end address
	KeStartup
};
Thats all that there is to it. KeStartup is your entry point function, LOADBASE is the base address of your kernel (like 1MB for example), HEADER_ADDRESS is the address of the multiboot header (which happens to be LOADBASE+0x400 do to .text always starting at 0x400), magic is 0x1BADB002, tested flags of 0x00010003 and the checksum being -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS).

Here is the complete example:

Code: Select all

#pragma pack (push, 1)

/**
*	Multiboot structure
*/
typedef struct _MULTIBOOT_INFO {

	uint32_t magic;
	uint32_t flags;
	uint32_t checksum;
	uint32_t headerAddr;
	uint32_t loadAddr;
	uint32_t loadEndAddr;
	uint32_t bssEndAddr;
	uint32_t entryPoint;
	uint32_t modType;
	uint32_t width;
	uint32_t height;
	uint32_t depth;

}MULTIBOOT_INFO, *PMULTIBOOT_INFO;

#pragma pack(pop,1)

/**
*	Kernel entry
*/
void KeStartup ( PMULTIBOOT_INFO* loaderBlock ) {

	__halt ();
}

//! loading address
#define LOADBASE                     0x100000

//! header offset will always be this
#define   ALIGN                           0x400
#define   HEADER_ADDRESS         LOADBASE+ALIGN

#define MULTIBOOT_HEADER_MAGIC        0x1BADB002
#define MULTIBOOT_HEADER_FLAGS        0x00010003
#define STACK_SIZE                    0x4000    
#define CHECKSUM                      -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)

#pragma section(".text")
__declspec(allocate(".text"))
MULTIBOOT_INFO _MultibootInfo = {

	MULTIBOOT_HEADER_MAGIC,
	MULTIBOOT_HEADER_FLAGS,
	CHECKSUM,
	HEADER_ADDRESS,
	LOADBASE,
	0, //load end address
	0, //bss end address
	KeStartup
};
Thats all that there is to it. Assuming GRUB is configured to boot your kernel, this should make your kernel bootable by GRUB. Tested and works with two separate projects. Please post comments if there are any :D

*Wiki: I never used the Wiki so dont know how to add pages. Please feel free to use this content if you like.
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
ru2aqare
Member
Member
Posts: 342
Joined: Fri Jul 11, 2008 5:15 am
Location: Hungary

Re: MSVC Grub - A new way

Post by ru2aqare »

Nice work there. There is only one small problem though. Putting the multiboot header in the .text section does not prevent the linker from arbitrarily moving it within the section (especially if you turn on "smart linking" - when every function gets compiled into a separate .text section, and the linker merges them together). A better way would be using linker scripts ... wait, those are not available with msvc :)

But have no fear, here is another way. You may need to tweak this a little bit.

Code: Select all

#pragma section(push, ".text$1")
MULTIBOOT_INFO _MultibootInfo =
{
   ...
};
#pragma section(pop)
The linker always groups together these $1 ($2 etc.) sections with the main section (text in this case), but puts these ordered sections before it. This is how the MS C/C++ runtime implements static constructors/destructors (by putting them into these ordered sections).

Disclaimer: I am writing this entirely from memory. It may be incorrect in some places.
User avatar
Firestryke31
Member
Member
Posts: 550
Joined: Sat Nov 29, 2008 1:07 pm
Location: Throw a dart at central Texas
Contact:

Re: MSVC Grub - A new way

Post by Firestryke31 »

You could do what I was thinking about doing and replace the DOS stub with a tiny multiboot program, which contains the DOS MZ header, then the multiboot header, then some p-mode code to look through the PE header to find the entry point and run that. Since the DOS stub is guaranteed to be at the very beginning of the file, and the linker won't reorganize it, the multiboot structure will be within the first 8k of the file. If you want to, you could do: DOS header, RMode-code that goes to PMode and relocates the exe to 1MB and jumps to the multiboot bit, then the multiboot header, then the multiboot code which finds and runs the entry point of the PE part of the exe. Just be sure that the RM-code sets ebx (I think it was) to a different code than GRUB so your program doesn't get confused.

If you really wanted to, you could have the multiboot stub do all of the relocations and whatnot first, then run the entry point.
Owner of Fawkes Software.
Wierd Al wrote: You think your Commodore 64 is really neato,
What kind of chip you got in there, a Dorito?
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Re: MSVC Grub - A new way

Post by Creature »

Looks like your experiments brought you to an interesting discovery. It's always nice to discover something new just by experimenting, certainly in the OSDev world. Great job!
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
clwillingham
Posts: 8
Joined: Fri Feb 26, 2010 10:44 am

Re: MSVC Grub - A new way

Post by clwillingham »

This is great now i can make the boot process in my project make sense :-). just one question, my current method of booting through grub has an annoying limit on the number of characters stored in memory. i have been able to increase it significantly, but its still there and it bothers me. is there a limit in this method as well?
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: MSVC Grub - A new way

Post by neon »

Hello,

It seems this method may not work in large projects. While defining the structure in its own section will guarantee alignment, there is no way of knowing if the section will be in the first 8k of the binary needed by the multiboot standard.

ru2aqare's method may not work - while .text$0 should be placed first in .text MSVC doesnt treat it that way, it seems to place it in its own section possibly away from .text. I personally cannot find a way to create a section before .text. Thus MSVC may very well define the data outside the 8k mark.

Firestryke31's method, however, would provide a solution here. If we use a stub program that defines the structure in it and uses its entry point to be called by the bootloader, which in turn, calls the kernels real entry point, should work very well. - Great idea :D

*edit: hm... Actually Firestryke31's method wont work. The stub program will only link if its a 16 bit program. Multiboot spec says that, when execution is transfered, the machine must already be running in protected mode.

clwillingham- Sorry, I am not sure what it is that you mean. Please rephrase.

Creature- Thanks :D Its always fun experimenting with different things and creating your own solutions :D

*edit2: This seems to fix it:

Code: Select all

#pragma code_seg(".a$0")
__declspec(allocate(".a$0"))
MULTIBOOT_INFO _MultibootInfo = {

   MULTIBOOT_HEADER_MAGIC,
   MULTIBOOT_HEADER_FLAGS,
   CHECKSUM,
   HEADER_ADDRESS,
   LOADBASE,
   0, //load end address
   0, //bss end address
   KeStartup
};

#pragma comment(linker, "/merge:.text=.a")
The above modification has been tested and boots with Grub. On both machines with two different MSVC kernels it creates the structure always at the beginning of the file, linking the section before the .text section in the program binary.

Please feel free to make comments if there are any :)
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
technik3k
Member
Member
Posts: 31
Joined: Thu Jan 14, 2010 5:35 pm

Re: MSVC Grub - A new way

Post by technik3k »

Don't forget that you need to link with an "/align:512" or similar option that guaranties that section alignment in the file matches virtual addresses that linker assumes.
User avatar
Firestryke31
Member
Member
Posts: 550
Joined: Sat Nov 29, 2008 1:07 pm
Location: Throw a dart at central Texas
Contact:

Re: MSVC Grub - A new way

Post by Firestryke31 »

neon wrote: *edit: hm... Actually Firestryke31's method wont work. The stub program will only link if its a 16 bit program. Multiboot spec says that, when execution is transfered, the machine must already be running in protected mode.
MSVC has the /stub option, where the only thing it does is update the field in the MZ header that points to the PE header. There is nothing saying you can't have the stub divided into 3 parts: a 16 bit part that sets up protected mode and goes to the common p-mode part (for if the user for some reason started the program in DOS or Dex's bootloader or something like that), the GRUB p-mode part that contains the multiboot header and the multiboot entry point which jumps to the next part, and finally the common p-mode part which looks in the PE header and jumps to the entry point there. There's no reason why the multiboot entry point has to be the same as the MZ entry point or the PE entry point.

Basically the EXE would look something like this:

Code: Select all

MZ Stub:
- 16-bit R-mode code (run by MZ loader like DOS)
-- Sets up basic protected mode
-- Go to PMcommon

- 32-bit Multiboot code
-- Multiboot header with entry point set to MBentry
MBentry:
-- does some data massaging
-- Go to PMcommon

- Common P-mode code
PMcommon:
-- Sets up desired environment (paging, GDT, etc.)
-- Looks through PE header to find real entry point
-- Optional: Performs base relocations
-- Runs PE entry point

PE data and whatnot:
- Contains the bulk of the program
Owner of Fawkes Software.
Wierd Al wrote: You think your Commodore 64 is really neato,
What kind of chip you got in there, a Dorito?
User avatar
astrocrep
Member
Member
Posts: 127
Joined: Sat Apr 21, 2007 7:21 pm

Re: MSVC Grub - A new way

Post by astrocrep »

Awesome!! Thanks so much for this!

I am going to add this to the Visual c++ wiki entry

Edit: Nvm, I don't have access...
Mouse Pad - Coming in the distant future...
Kernel: Indigo Kernel - v0.0.1

Thanks to JamesM and BrokenThorn for there tutorials!
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Re: MSVC Grub - A new way

Post by Creature »

astrocrep wrote:Awesome!! Thanks so much for this!

I am going to add this to the Visual c++ wiki entry

Edit: Nvm, I don't have access...
You do.
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
Post Reply