I'm betting this is a stack or segment pointer problem, because if I move my stack the problem goes away. (It it possible for my code to corrupt data and/or stack used by BIOS?)
Big picture:
My very simple OS has it's own very simple boot loader. Switches to protected mode and runs 32 bit C code, then switches back to real mode and uses BIOS to dump a data buffer back to the boot disk.
The Real mode code sets the stack pointer to 0x7c00 (this is safe, provided I don't overflow, right? http://wiki.osdev.org/Memory_Map_(x86)).
If I dump memory immediately after setting up the stack, everything looks as expected. If I dump memory immediately after calling the int 13, I can see the memory corruption. (A few bytes in the data buffer I intend to dump are modified.)
Moving the stack from 0x7c00 to 0x6000 appears to fix the problem; but, I'm curious what I'm doing wrong. (And I want to make sure I've really fixed the problem, as opposed to pushed the corrupted memory somewhere where it hasn't caused problems --- yet.)
I wonder if I'm somehow trashing memory that is used by BIOS.
Here is the final real mode code
Code: Select all
/*******************************************************************
*
* Return to real mode and dump the output data
*
* Code for return to real mode obtained here:
*http://www.rohitab.com/discuss/topic/35103-switch-between-real-mode-and-protected-mode/
******************************************************************/
move_to_real:
cli
lgdt gdt16_descriptor
ljmp $CODE_SEG_16, $p_mode16
.code16
p_mode16:
mov $DATA_SEG_16, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
mov %ax, %di
mov %cr0, %eax
and $~1, %al
mov %eax, %cr0
ljmp $0, $real
real:
/* Sets %ax to 0. (I'm not sure why we're not using mov.) */
xor %ax, %ax
/* Set all the segment registers to 0. */
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
// mov $0x7bfc, %sp // $0x7c00 - $4
mov $0x7000, %sp // 0x6000 works. 0x7000 and 0x7c00 don't
mov %sp, %bp
// Print debugging message indicating we are back in real mode.
mov $0x0E, %ah
mov $0x52, %al // 'R'
int $0x10
mov $0x4D, %al // 'M'
int $0x10
mov $0x20, %al // ' '
int $0x10
/*******************************************************************
*
* Dump the data buffer to the boot device.
*
******************************************************************/
.set drive_type, -4
.set max_sector_num, -8
.set max_cylinder_num, -12
.set max_head_num, -16
.set num_drives, -20
.set deubg_test, -24
.set total_sectors_written, -28
// Make room on the stack for some local variables.
add total_sectors_written, %sp
movw $0, total_sectors_written(%bp)
// If I dump data here, everything looks normal: data buffer is untouched.
/////////////////////////////////////
//
// First, gather data about the drive
//
/////////////////////////////////////
mov $0x08, %ah
mov initial_dl, %dl
int $0x13
// If I dump data here, data buffer has about 80 bytes changed. I can't tell what the data here represents.
// Its not obviously output from the interrupt.
// Save the returned values before we mess them up. (These
// values probably need only be 16-bit; but, better safe than
// sorry.)
mov %bx, drive_type(%bp) // Drive type
mov %cx, max_sector_num(%bp) // max sector number
mov %cx, max_cylinder_num(%bp) // max cylinder number
mov %dx, max_head_num(%bp) // max head number
mov %dx, num_drives(%bp) // num drives
movl $0x41424344, debug_test(%bp)
// continue on writing sectors to disk.