I use PE too (maybe I'm just an idiot or something, but I was already familiar with PE, and knew nothing about ELF), and as others have said, your sections are off alignment.
Your .text (code), .data, .rdata, .rodata, .bss, etc. sections are going to each be given virtual addresses by the linker. But GRUB knows nothing about this -- it uses the location of the Multiboot header to orient the initial load address -- that is, to load that section (.text) where the linker prepared it to be loaded. But it will load the entire file as-is relative to this point. It's not going to parse the PE header and section directory to figure out where the linker instructed it to put the .data sections and so on.
So, to use PE, you have two choices:
(1) Write some sort of middle-stage loader (using no non-local variables) that will read the PE section directory when your kernel boots and move all the sections to the real addresses the linker expected them to end up at. It will have to move them in such a way that you won't overwrite bits you still need (tricky).
(2) Arrange your PE file so that the physical and/or virtual addresses of all the sections relative to the one containing the Multiboot header are exactly the same as the offsets of the bytes of the file. Then it can just be shoved into memory at one location (like the way GRUB will load it) and it will all work.
The second solution is the one I use. Unfortunately, it not nearly that simple. Not only you do need to be able to control the padding between the load addresses of the sections, you need to control the padding of how the sections are arranged in the file. I use only GCC, so unfortunately I have no idea if this is possible with Visual C. (Does it even support the concept of linker scripts? I have no idea. Sorry.) I do know that even with GCC's linker, using its --file-alignment and --section-alignment options, I could not get it to stop putting some padding between the PE sections in the file.
Therefore, with GCC, building a PE file, I resorted to writing a PHP script that would read in the resulting exe, extract each section, and dump it into a new raw file arranged exactly as I wanted it. (Using objcopy in the GNU toolchain should have been good enough, but I use a
higher-half kernel at 0xF0000000, loaded initially at 0x00100000 physical, and the stupid program kept trying to produce a 4-gigabyte file spanning the entire address space).
I really don't recommend doing what I do. I stick with it because it already works, so there's no point fiddling with it, and I'd already done it before I understood exactly what I was doing. But since you have your heart set on doing this via PE, and since I don't know whether VC might offer a better way, here is my GNU LD linker script and [the relevant parts of] the PHP script I use to arrange things properly. Maybe you'll find it useful (or at least informative). (Though honestly, these guys are probably right about ELF making things easier. Perhaps there's a different linker that will let you build an ELF file from VC's object files?)
linker.ld:
(Note: I omitted the list of input files for brevity; ".text.boot" contains the asm code that initialises paging so I run the rest of it at a high virtual address. )
Code: Select all
ENTRY( _multiboot_entry )
SECTIONS
{
. = 0x00100000;
.text.boot . : {
*(.text.boot)
}
. = 0xF0000000 + SIZEOF(.text.boot);
.text . : {
*(.text .rdata .rodata .data)
*(.text.hot)
*(.text.unlikely)
}
.bss . : {
*(.bss)
}
_bss_section_end = . - 0xF0000000 + 0x00100000;
}
convertexe.php:
Code: Select all
<?php
$input_file = 'kernel.sys';
$output_file = 'kernel.sys';
// convertexe.php extracts the program sections from the PE header and places
// them with no padding into the output file.
error_reporting(E_ALL);
$data = file_get_contents($input_file);
$fout = fopen($output_file, 'wb') or die('Cannot open output file');
if (substr($data, 0, 2) != 'MZ') {
die("Convertexe: Not a PE file or already converted.\n");
}
$out_size = 0;
$bss_size = null;
$sections = GetPESections($data);
//print_r($sections);
foreach ($sections as $section) {
if ($section['Name'] == '.comment') continue; // skip
if ($section['Name'] == '.bss') {
$bss_size = $section['VirtualSize'];
continue;
}
$section_data = substr($data, $section['PointerToRawData'], $section['VirtualSize']);
$out_size += strlen($section_data);
fwrite($fout, $section_data);
}
fclose($fout);
echo 'Convertexe: Output file: ' . number_format($out_size) . ' bytes';
if ($bss_size !== null) {
echo '; BSS section: ' . number_format($bss_size) . ' bytes';
}
echo "\n";
exit(0);
function GetPESections($data) {
$x = unpack('VPEOffset', substr($data, 0x3C, 4));
$PEOffset = $x['PEOffset'];
$PEHeader = unpack(
'VSignature/vMachine/vNumberOfSections/VTimeDateStamp/' .
'VPointerToSymbolTable/VNumberOfSymbols/vSizeOfOptionalHeader/' .
'vCharacteristics',
substr($data, $PEOffset, 24)
);
$sections_offset = $PEOffset + 24 + $PEHeader['SizeOfOptionalHeader'];
$sections = array();
for ($i = 0; $i < $PEHeader['NumberOfSections']; $i++) {
$sections[] = unpack(
'a8Name/VVirtualSize/VVirtualAddress/VSizeOfRawData/' .
'VPointerToRawData/VPointerToRelocations/VPointerToLineNumbers/' .
'vNumberOfRelocations/vNumberOfLineNumbers/VCharacteristics',
substr($data, $sections_offset + $i * 40, 40)
);
}
return $sections;
}
*EDIT*: Maybe there's a simpler way; I just thought of this. For all the primary sections of my kernel I use the linker script to actually combine them into one big fat .text section to eliminate most of the padding annoyances (and make the file tighter). (This was something I figured out much later.) So nowadays, my PHP script's main purpose is to remove only a single piece of padding between two sections. If you can generate the output exe like this, the only remaining issue is if you want to use a higher-half kernel or load/run things at sparse virtual addresses. Otherwise, the resulting PE file should be fine -- with everything packed in one section, padding disappears.