I spent a bit of time cleaning up some of the major issues. Your pit and kbd routines rely on
leave/iretw to leave a C function. This makes big assumptions like the fact that there are stack frames present (may to be if you want optimizations or build without them). Your interrupt routines don't properly save and restore the volatile registers. When your interrupt handlers finally return it is possible for registers to change and become values that could make the code interrupted fail to work properly. GCC 7 added a new function attribute (
__attribute__(interrupt) ). that would be helpful. I modified your code with some C macros that create entry points for the interrupt handlers. They save the general purpose registers, the segment registers, clear the direction flag (forward movement), add an interrupt number (if you wish to multiplex multiple interrupts through a single handler), and I've set the code up to make the stack variables available through a parameter. These stubs can be changed to suit you (they may do extra work you may not care about).
If you ever move to physical media for testing and opt to ditch zeroing out more than the size of your kernel, I have added a BSS section in your linker script that allows you to determine the starting point and length of that section so your code can zero out the region explicitly. It is also possible that GCC can rearrange functions so
_start isn't actually guaranteed to appear first in the output file. I modified the code to add an inline assembly stub that is placed in a special section (the linker script was modified to support it) called
.text.startup. We make sure that section appears before all other code so we know it will be palced at the beginning of the kernel.
I did other minor changes in the C code here and there. I modified the make script to split up the linking and compiling phases and use objcopy to create the final binary file. This has the advantage that you can generate symbolic debug information and use it when debugging in QEMU.
The newer version of your payload.c code was:
Code: Select all
#include <stdint.h>
typedef struct {
/* Pushed explicitly in our common stub code */
uint32_t gs, fs, es, ds;
/* Pushed in our stub code by pushad instruction */
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax;
uint16_t int_num; /* Interrupt number */
uint16_t ip, cs, flags; /* Pushed by processor */
} interrupt_frame_rm32_t;
/* This code will be placed at the beginning of the second stage
* in the special section .text.startup that the linker script is
* aware of. It zeroes the BSS section, ensures the direction flag
* is set to forward movement (CLD) and then transfers control to
* kmain */
__asm__(".pushsection .text.startup\n\t"
".global _start\n"
"_start:\n\t"
"cld\n\t"
"lea edi, __bss_start\n\t"
"lea ecx, __bss_size_l\n\t"
"xor eax, eax\n\t"
"rep stosd\n\t"
"call kmain\n\t"
".popsection");
/* Make interrupt/exception entry point that calls specified handler
* Push all the segment registers and the general purpose registers
* so that they are available to interrupt function (intfun). Pushes a unique
* interrupt number (intno) after the error code so that a handler can be multiplexed
* if needed. Restore all the registers upon exit.
*
* intentry: Is the interrupt entry point that can be used in an Interrupt
* vector table (IVT) entry.
* intfunc: Is the C interrupt function that the stub calls to do processing
* intno: Interrupt number. Can be used to multiplex multiple interrupts to one
* intfunc handler.
*/
#define MAKE_RM32_INTERRUPT(intentry, intfunc, intno) \
extern void intentry (void); \
__asm__(".pushsection .text\n\t" \
".global " #intentry "\n\t" \
#intentry ":\n\t" \
"pushw "#intno"\n\t" \
"pusha\n\t" \
"push ds\n\t" \
"push es\n\t" \
"push fs\n\t" \
"push gs\n\t" \
"cld\n\t" \
"push esp\n\t" \
"call " #intfunc "\n\t" \
"pop eax\n\t" \
"pop gs\n\t" \
"pop fs\n\t" \
"pop es\n\t" \
"pop ds\n\t" \
"popa\n\t" \
"add esp, 2\n\t" /* Skip int_num */ \
"iretw\n\t" \
".popsection\n\t");
MAKE_RM32_INTERRUPT (interrupt_pit_entry, pit, 0x00)
MAKE_RM32_INTERRUPT (interrupt_kbd_entry, kbd, 0x01)
#define port_out_b(port, value) ({ \
__asm__ __volatile__ ( "out dx, al" \
: \
: "a" (value), "d" (port) \
: ); \
})
#define port_out_w(port, value) ({ \
__asm__ __volatile__ ( "out dx, ax" \
: \
: "a" (value), "d" (port) \
: ); \
})
#define port_out_d(port, value) ({ \
__asm__ __volatile__ ( "out dx, eax" \
: \
: "a" (value), "d" (port) \
: ); \
})
#define port_in_b(port) ({ \
uint8_t value; \
__asm__ __volatile__ ( "in al, dx" \
: "=a" (value) \
: "d" (port) \
: ); \
value; \
})
#define port_in_w(port) ({ \
uint16_t value; \
__asm__ __volatile__ ( "in ax, dx" \
: "=a" (value) \
: "d" (port) \
: ); \
value; \
})
#define port_in_d(port) ({ \
uint32_t value; \
__asm__ __volatile__ ( "in eax, dx" \
: "=a" (value) \
: "d" (port) \
: ); \
value; \
})
void hook_int(int interrupt, void* handler) {
uint16_t* intptr = (uint16_t*)(interrupt * 4);
*(intptr++) = (uint16_t)((uint32_t)handler);
*intptr = 0;
return;
}
#define VGA_X_MAX 80
#define VGA_Y_MAX 25
#define VGA_MAX (VGA_X_MAX * 2 * VGA_Y_MAX)
#define VGA_LIN_POS ((vga_cur_x * 2) + (vga_cur_y * (VGA_X_MAX * 2)))
int vga_cur_x = 0;
int vga_cur_y = 0;
char vga_text_attr = 0x07;
char vga_cur_attr = 0x70;
void vga_init(void) {
char* vidmem = (char*)0xb8000;
vga_cur_x = 0;
vga_cur_y = 0;
for (int i = 0; i < VGA_MAX; i++) {
vidmem[i++] = ' ';
vidmem[i] = vga_text_attr;
}
vidmem += VGA_LIN_POS;
*(vidmem + 1) = vga_cur_attr;
}
void vga_scroll(void) {
char* vidmem = (char*)0xb8000;
for (int i = VGA_X_MAX * 2; i < VGA_MAX; i++)
vidmem[i - VGA_X_MAX * 2] = vidmem[i];
for (int i = VGA_MAX - VGA_X_MAX * 2; i < VGA_MAX; i++) {
vidmem[i++] = ' ';
vidmem[i] = vga_text_attr;
}
}
void vga_putchar(char c) {
char* vidmem = (char*)(0xb8000 + VGA_LIN_POS);
if (c == '\n') {
*(vidmem + 1) = vga_text_attr;
vga_cur_x = 0;
if (vga_cur_y == (VGA_Y_MAX - 1))
vga_scroll();
else
vga_cur_y++;
vidmem = (char*)(0xb8000 + VGA_LIN_POS);
*(vidmem + 1) = vga_cur_attr;
return;
}
if (c == '\b') {
if (!vga_cur_x && !vga_cur_y)
return;
*(vidmem + 1) = vga_text_attr;
if (!vga_cur_x)
vga_cur_x = VGA_X_MAX - 1, vga_cur_y--;
else
vga_cur_x--;
vidmem = (char*)(0xb8000 + VGA_LIN_POS);
*vidmem = ' ';
*(vidmem + 1) = vga_cur_attr;
return;
}
*(vidmem++) = c;
*(vidmem++) = vga_text_attr;
if (++vga_cur_x == VGA_X_MAX)
vga_cur_x = 0, vga_cur_y++;
if (vga_cur_y == VGA_Y_MAX) {
vga_scroll();
vga_cur_x = 0, vga_cur_y--;
vidmem = (char*)(0xb8000 + VGA_LIN_POS);
}
*(vidmem + 1) = vga_cur_attr;
}
void vga_putstr(char* str) {
while (*str)
vga_putchar(*(str++));
}
// PIT ISR stuff
void pit(interrupt_frame_rm32_t *frame) {
port_out_b(0x20, 0x20);
}
#define MAX_CODE 0x57
static const char keymap[] = {
'\0', '?', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', '\t',
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', '\0', 'a', 's',
'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', '\0', '\\', 'z', 'x', 'c', 'v',
'b', 'n', 'm', ',', '.', '/', '\0', '\0', '\0', ' '
};
int inchar = -1;
void kbd(interrupt_frame_rm32_t *frame) {
uint8_t in = port_in_b(0x60);
if (in < MAX_CODE)
inchar = (int)(keymap[in]);
port_out_b(0x20, 0x20);
}
char kbd_getchar(void) {
while (inchar == -1)
__asm__("hlt");
char ret = (char)inchar;
inchar = -1;
return ret;
}
char* kbd_getstr(char* str, int limit) {
int i = 0;
char c;
for (;;) {
switch (c = kbd_getchar()) {
case '\n':
vga_putchar(c);
break;
case '\b':
if (i) {
vga_putchar(c);
str[--i] = 0;
}
continue;
default:
if (i < (limit - 1)) {
vga_putchar(c);
str[i++] = c;
}
continue;
}
break;
}
str[i] = 0;
return str;
}
void set_PIC0_mask(uint8_t mask) {
port_out_b(0x21, mask);
return;
}
void set_PIC1_mask(uint8_t mask) {
port_out_b(0xA1, mask);
return;
}
uint8_t get_PIC0_mask(void) {
return port_in_b(0x21);
}
uint8_t get_PIC1_mask(void) {
return port_in_b(0xA1);
}
int strcmp(const char* str1, const char* str2) {
int i = 0;
while (str1[i] && str1[i] == str2[i])
i++;
return str1[i] - str2[i];
}
void usage(void) {
vga_putstr(
"Usage:\n"
"clear -- Clears the screen.\n"
);
}
void kmain(void) {
char prompt[16];
hook_int(0x8, interrupt_pit_entry);
hook_int(0x9, interrupt_kbd_entry);
vga_init();
set_PIC1_mask(0xff);
set_PIC0_mask(0b11111100);
__asm__("sti");
vga_putstr("Real Mode 32 Research\n\n");
for (;;) {
vga_putstr(">> ");
kbd_getstr(prompt, 16);
if (!*prompt) continue;
vga_putstr("input: `"); vga_putstr(prompt); vga_putstr("`.\n");
if (!strcmp(prompt, "clear"))
vga_init();
else if (!strcmp(prompt, "usage"))
usage();
}
}
The new linker.ld:
Code: Select all
/* When this exectuable is converted to a binary file ENTRY doesn't
* have any meaning, however this will stop the linker warning
* about the entry point not being found */
ENTRY(_start)
SECTIONS
{
.text 0x7e00 : {
/* Ensure the .text.startup code appears before all other code */
*(.text.startup)
*(.text)
}
.data : {
*(.rodata)
*(.data)
}
.bss : SUBALIGN(4) {
__bss_start = .;
*(COMMON);
*(.bss)
}
. = ALIGN(4);
/* Linker symbols thats can be used for zeroing BSS section */
__bss_end = .;
__bss_size = ((__bss_end)-(__bss_start));
__bss_size_l = (__bss_size)>>2;
}
Update make_me.sh:
Code: Select all
#!/bin/bash
set -e
i386-elf-gcc -g -c payload.c -ffreestanding -o payload.o -masm=intel -Os -Wall
i386-elf-gcc -T linker.ld payload.o -o payload.elf -nostartfiles -nostdlib
i386-elf-objcopy payload.elf -Obinary payload.bin
nasm test.asm -f bin -o test.img
If you want to debug with QEMU and use the symbols from the intermediate ELF executable you can do something like this to launch QEMU with remote debugging:
Code: Select all
#!/bin/bash
qemu-system-i386 -hda test.img -S -s &
gdb payload.elf \
-ex 'target remote localhost:1234' \
-ex 'set architecture i386' \
-ex 'layout src' \
-ex 'layout reg' \
-ex 'break *kmain' \
-ex 'continue'
The other files remained as they were. These changes should also allow you to turn on optimizations with
-Os (optimize for size). Use
-O3 if you want to optimize for speed.