At the moment I have a very simple boot loader that follows the example of x86 bare metal (https://github.com/cirosantilli/x86-bare-metal-examples) and OSDev's Bare Bones to load the rest of the code, enter protected mode, and call the C function that is the kernel entry point. I also have a few functions that can write using VGA text mode.
The C file with the kernel entry point has two initialized global variables. I enter the kernel (function dd_test) and immediately test the global variables. They have the wrong values. More interestingly, the values that are in the variables are part of a string embedded at the end of the MBR. Specifically, even though the global variable is placed at memory address 0x9818, the value printed is the data loaded at address 0x7ce4. (I doubt the problem is the VGA code because I check their values before running the VGA code.)
I've used hexdump to examine the boot image, and the global variables have the expected addresses. (Printing &d1 displays 0x9818, which is what I would expect given that the d1's initial value appears at location 0x1c18 in the hexdump and 0x1c18 + 0x7c00 = 0x9818)
My guess is that I'm overlooking some subtlety of either linking or switching to protected mode. Any ideas?
Code: Select all
// kernel entry point
unsigned d1 = 0x4b4b4b4b;
unsigned d2 = 0x4c4c4c4c;
void dd_test() {
int correct_value;
if (d1 == 0x4b4b4b4b) {
correct_value = 1;
} else {
correct_value = 0;
}
vga_text_section_t head, body;
vgat_initialize_head_body(&head, &body, 7);
if (correct_value == 1) {
vgat_write_string(&head, "\nCorrect\n");
} else {
vgat_write_string(&head, "\nIncorrect \n");
vgat_write_unsigned_hex(&head, d1, " <= d1 value; ");
vgat_write_unsigned_hex(&head, (unsigned) &d1, " <= &d1\n");
vgat_write_unsigned_hex(&head, d2, " <= d2 value; ");
vgat_write_unsigned_hex(&head, (unsigned) &d2, " <= &d2;\n");
}
Code: Select all
Incorrect
0x20657265 <= d1 value; 0x9818 <= &d1
0x7473756a <= d2 value; 0x981c <= &d2
Code: Select all
000000b0 7c 00 00 66 b8 10 00 8e d8 8e c0 8e e0 8e e8 8e ||..f............|
000000c0 d0 bd 00 70 00 00 89 ec e9 33 01 00 00 45 6e 64 |...p.....3...End|
000000d0 20 6f 66 20 4d 42 52 2e 20 20 4d 6f 72 65 20 74 | of MBR. More t|
000000e0 65 78 74 20 68 65 72 65 20 6a 75 73 74 20 74 6f |ext here just to|
000000f0 20 74 61 6b 65 20 75 70 20 72 6f 6f 6d 2e 00 66 | take up room..f|
00000100 90 66 90 66 90 66 90 66 90 66 90 66 90 66 90 66 |.f.f.f.f.f.f.f.f|
...
00001b10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001c00 47 47 47 47 70 70 70 70 53 52 51 54 57 57 57 57 |GGGGppppSRQTWWWW|
00001c10 7a 7a 00 00 79 79 79 79 4b 4b 4b 4b 4c 4c 4c 4c |zz..yyyyKKKKLLLL|
00001c20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
Code: Select all
SECTIONS
{
. = 0x7c00; __start = .;
/* First place the code (aka "text") in the image */
.text :
{
*(.boot) // the MBR in boot.S
. = 0x1FE;
SHORT(0xAA55)
/* The code in the ".boot" section (the first 512 bytes) is
responsible for issuing the BIOS calls to load the next
sections of code. If you look in boot.S, you will see that
the code for this second stage of booting is placed in
".section .stage2". The line below places this startup code
immediately following the boot sector. "stage2" contains code
that moves the processor from "real" mode to "protected" mode,
then calls the function that represents the entry point of the
OS. */
*(.stage2)
/* All other code (i.e., code that is not part of the boot loader)
is placed in ".section .text". The line below instructs the
linker to place this remaining code next in the image. */
*(.text)
}
/* Read-only data. When generating assembly code, gcc places
hard-coded strings (among other things) in .section .rodata */
.rodata ALIGN(1024) :
{
LONG(0x35343332); /* Mark the beginning of rodata. */
*(.rodata*)
LONG(0x32444E45); /* Mark the end of the disk rodata. */
}
/* Read-write data. When generating assembly code, gcc places
global and static data in .section .data. */
.data : ALIGN(1024)
{
LONG(0x47474747) // mark the beginning of .data
*(.data)
}
// Not used yet. Just filled with some data so I can see how the linker works.
.data2 : ALIGN(1024)
{
*(.data2)
}
/* Calculate the total number of sectors used by the code and data.
Normally, the "." gives the value of the linker's internal
pointer relative to the current section. Surrounding the
calculation with ABSOLUTE assures that the values used are
relative to the entire image. This value is used in boot.S to
specify the number of sectors that must be loaded
*/
__stage2_nsectors = ABSOLUTE((. - __start) / 512);
/* Ensure that the generated image is a multiple of 512 bytes
long. (Some machines and VMs freak out if the boot image is not a
multiple of 512 bytes. */
__end = .;
__end_align_4k = ALIGN(4k);
// Make sure image is a multiple of 512 bytes
.data3 : ALIGN(1024)
{
. = 512 -4; /* Make sure that the disk image is a multiple of 512 bytes. */
LONG(0x444E4500); /* Mark the end of the disk image. */
}