I've just started with os devel and I'm having problems with the GDT. I
use GRUB to load my C kernel and theree Isetup the
GDT struct, and the I load it. But when the ASM code tries to make the
far jump and return to kernel the machine triple faults and resets.
Can anyone help me, or even better point me out about some good texts
on the web about the topic.
Best regards, Ernesto
typedef struct {
DWORD sd_lolimit : 16; /* segment limit (bottom) */
DWORD sd_lobase : 24 __attribute__ ((packed));
/* segment base address (bottom) */
DWORD sd_type : 5; /* segment type */
DWORD sd_dpl : 2; /* privilege level */
DWORD sd_p : 1; /* segment descriptor present */
DWORD sd_hilimit : 4; /* segment limit (top) */
DWORD sd_xx : 2; /* unused */
DWORD sd_def32 : 1; /* 32 vs 16 bit size? */
DWORD sd_gran : 1; /* limit granularity (byte/page units)*/
DWORD sd_hibase : 8; /* segment base address (top) */
} segment_descriptor_t;
#define SDT_CODE 8
#define SDT_READ 2
#define SDT_WRITEENABLE 2
#define MAKE_SEGMENT_SELECTOR(idx, ldt, priv) ((idx << 3) | ldt << 2 |
priv)
segment_descriptor_t* gdt = NULL;
WORD big_code_segment;
WORD big_data_segment;
struct gdtp {
WORD limit;
segment_descriptor_t* addr;
} gdt_ptr;
struct gdtp* ptrgdt;
void create_gdt(void) {
segment_descriptor_t play; /* one to play with */
gdt = (segment_descriptor_t*)(0x00A00000); /* pick a place for now
*/
*((long long*)&play) = 0; /* set 8 bytes to 0 */
gdt[0] = play;
/* set up a descriptor for all data */
play.sd_type = SDT_WRITEENABLE;
play.sd_dpl = 0; /* most privileged only */
play.sd_lobase = 0x000000;
play.sd_hibase = 0x00;
play.sd_gran = 1; /* 4 KB increments next: */
play.sd_lolimit = 0xFFFF; /* all mem (4 GB) */
play.sd_hilimit = 0xF;
play.sd_p = 1; /* yes, it's present */
play.sd_def32 = 1; /* 32-bit */
gdt[1] = play;
big_data_segment = MAKE_SEGMENT_SELECTOR(1, 0, 0);
*((long long*)&play) = 0; /* set 8 bytes to 0 */
play.sd_type = SDT_CODE | SDT_READ;
play.sd_dpl = 0;
play.sd_lobase = 0x000000;
play.sd_hibase = 0x00;
play.sd_gran = 1;
play.sd_lolimit = 0xFFFF; /* all mem (4 GB) */
play.sd_hilimit = 0xF;
play.sd_p = 1;
play.sd_def32 = 1;
gdt[2] = play;
big_code_segment = MAKE_SEGMENT_SELECTOR(2, 0, 0);
}
void load_gdt(void) {
int i;
ptrgdt = &gdt_ptr;
gdt_ptr.addr = gdt;
gdt_ptr.limit = 8 * 3 - 1; // 3 items in it
printf("Loading GDT at 0x%x and setting code segment to 0x%x\n",
gdt, big_code_segment);
for (i = 0; i < 2000000000; i++) ; /* "pause" for a bit so I can
read the text */
asm volatile ("lgdt %0" : : "m" (ptrgdt) );
// asm volatile ("ljmp %0, $1f\n\t"
// "1:\n\t"
// : : "n" (MAKE_SEGMENT_SELECTOR(2, 0, 0)) );
// asm ("movw %0, %%ax\n\t"
// "movw %%ax, %%ds\n\t"
// "movw %%ax, %%ss\n\t"
// : : "m" (big_data_segment) );
asm ("movw %0, %%ax\n\t"
"movw %%ax, %%cs\n\t"
: : "m" (big_code_segment) );
}
(==end of code==)
Okay, so you can see at the bottom some commented-out code that I've
also tried. Any of this code causes the PC to crash or something (I'm
using VMWare Workstation, and it pops up a message box complaining.) if
I call create_gdt() followed by load_gdt(). If I comment out the code
that sets the segment registers (and the long jump), execution
continues (although not in the state I want it to be in). I suspect
something wrong with my segment selectors or something, but I really
don't know. I've been twidling with this all day.
Any assistance would be greatly appreciated. I will continue searching
more examples, tutorials, etc., to see if I find anything more
insightful than the 30 examples I've already looked at.
Thanks Much in advance,
Tyrel
Are you sure the above declaration gives you exactly what you want with all
these bit fields and packing??? Did you dump and check either disassembly of
the code that uses structures of this type or memory contents behind this
structure, byte per byte? This would be the 1st thing I'd check.
> #define SDT_CODE 8
> #define SDT_READ 2
> #define SDT_WRITEENABLE 2
>
> #define MAKE_SEGMENT_SELECTOR(idx, ldt, priv) ((idx << 3) | ldt << 2 |
> priv)
>
> segment_descriptor_t* gdt = NULL;
>
> WORD big_code_segment;
> WORD big_data_segment;
>
> struct gdtp {
> WORD limit;
> segment_descriptor_t* addr;
> } gdt_ptr;
Are you sure the above struct isn't broken by extra padding??? I'm not. You
must check it and fix if needed by either adding those weird attribute
packed things or better yet (nicer and supported by several different
compilers such as gcc, msvc++, watcom, etc):
#pragma pack (push, 1)
// define your structs here
#pragma pack (pop)
> struct gdtp* ptrgdt;
>
> void create_gdt(void) {
> segment_descriptor_t play; /* one to play with */
>
> gdt = (segment_descriptor_t*)(0x00A00000); /* pick a place for now
Is the A20 gate on for the above address to work?
> */
>
> *((long long*)&play) = 0; /* set 8 bytes to 0 */
I like the look of it :)
Moving to CS isn't allowed. Not with the MOV instruction, at least. You must
use either a far jump or a far return to change CS (well, a far call would
do too :). No other way.
> : : "m" (big_code_segment) );
> }
>
> (==end of code==)
>
> Okay, so you can see at the bottom some commented-out code that I've
> also tried. Any of this code causes the PC to crash or something (I'm
> using VMWare Workstation, and it pops up a message box complaining.) if
> I call create_gdt() followed by load_gdt(). If I comment out the code
> that sets the segment registers (and the long jump), execution
> continues (although not in the state I want it to be in). I suspect
> something wrong with my segment selectors or something, but I really
> don't know. I've been twidling with this all day.
>
> Any assistance would be greatly appreciated. I will continue searching
> more examples, tutorials, etc., to see if I find anything more
> insightful than the 30 examples I've already looked at.
>
> Thanks Much in advance,
> Tyrel
I might have missed something, but to me your structures are the first thing
to look at... Padding is a bad thing and it has happened to me and it may be
the case with you too now. Turn up the lights, don't play at random, just
dump the disassembly (the mighty -S switch! :) and constructed GDT and GDTR
(byte per byte, not field per field!) and make sure they're all OK.
Alex
I printed out the contents of the GDT after I built it, and it looks
like this:
0000000000000000
00CF92000000FFFF
00CF9A000000FFFF
These appear to be the same values that others use when they hard-code
their GDTs.
I've changed to use this instead of trying to mov into CS:
asm volatile ("ljmp %0, $1f\n\t"
"1:\n\t"
: : "n" (MAKE_SEGMENT_SELECTOR(2, 0, 0)) );
I made my gdt_ptr a union with a long long so I could print its
value.... although it's actually a 48-bit, not 64-bit, thingy, so part
of this is garbage (happens to be 0000 anyway) I separated the garbage
from the value itself:
0000 00A000000017
Which appears to be the correct address (0x00A00000) and limit (0x0017=
23).
When the ljmp is executed (or maybe shortly after that?), VMware
complains of an internal error (NOT_IMPLEMENTED) now... Before it was
complaining about a stack problem in kernel mode.
Also, after I sent my original message I noticed that I had something
backwards in my entries in the GDT. The S (system) flag was opposite of
what I thought it was--this has also been repaired now, although it
didn't seem to make a difference.
Any more suggestions would be welcome!
Thanks Again,
Tyrel
thanks for the advices. I already fixed the problem, checking the gdt
struct I had several error when setting the flags bits.
I'm using some #define macros for activating the bits and some of them
were wrong :)))
I feel so good......
Best regards, Ernesto
These are OK.
> I've changed to use this instead of trying to mov into CS:
> asm volatile ("ljmp %0, $1f\n\t"
> "1:\n\t"
> : : "n" (MAKE_SEGMENT_SELECTOR(2, 0, 0)) );
I'm not sure of the AT&T syntax...
Doesn't it have to be something like:
ljmp $%0,$1f
?
> I made my gdt_ptr a union with a long long so I could print its
> value.... although it's actually a 48-bit, not 64-bit, thingy, so part
> of this is garbage (happens to be 0000 anyway) I separated the garbage
> from the value itself:
> 0000 00A000000017
> Which appears to be the correct address (0x00A00000) and limit (0x0017=
> 23).
>
> When the ljmp is executed (or maybe shortly after that?),
put an equivalent of
l: jmp l
right after ljmp.
>VMware
> complains of an internal error (NOT_IMPLEMENTED) now... Before it was
> complaining about a stack problem in kernel mode.
Did you initalize SS anywhere? What about ESP? Or does GRUB do that for you?
If it does, what is SS:ESP, what is base addr and limit for the stack? If it
doesn't I'd not give a dime about your C code running on invalid stack...
Remember, dereferencing memory through EBP (which happens with local
variables and function arguments) involves SS, not DS! And since no
segmentation is assumed by gcc, DS and SS must either be equal or point to
the data segments of equal base address. Otherwise working with local and
global vars would give you different results (due to SS bing used with EBP
and ESP).
Alex
SS is initialized by GRUB. It says it is, like all the other segment
registers, pointing to a segment which has a base of 0 and 4GB limit. I
initialized ESP in assembly just before calling my cmain() function:
movl $(stack + KERNEL_INITIAL_STACK), %esp
(later in code:)
.comm stack, KERNEL_INITIAL_STACK
... where KERNEL_INITIAL_STACK is 4096, which should be plenty for now
(I'm only 2 functions deep, with only a few local variables, all of
which are pointers.)
I put an infinite loop right after the ljmp, as you suggested. The
"crash" still occurs, so it looks like the ljmp is causing the crash
(likely indirectly).
If there's anything else you have that might help, please let me know.
This is quite an annoying little bug :)
Thanks,
Tyrel
Selector value is also a constant, that's why I suggested a $.
> % is
> for stuff from GCC and registers (%% is to convince GCC that is really
> is a register).
I know. There are platforms on which (g)as-like assemblers don't use %% or
anything of that sort for register names, hence there can be funny name
collisions like newer CPU has a register whose name coincides with a name in
the old (and by defininition compatible due to CPU compatibility) code . :)
> SS is initialized by GRUB. It says it is, like all the other segment
> registers, pointing to a segment which has a base of 0 and 4GB limit. I
> initialized ESP in assembly just before calling my cmain() function:
> movl $(stack + KERNEL_INITIAL_STACK), %esp
> (later in code:)
> .comm stack, KERNEL_INITIAL_STACK
>
> ... where KERNEL_INITIAL_STACK is 4096, which should be plenty for now
> (I'm only 2 functions deep, with only a few local variables, all of
> which are pointers.)
>
> I put an infinite loop right after the ljmp, as you suggested. The
> "crash" still occurs, so it looks like the ljmp is causing the crash
> (likely indirectly).
>
> If there's anything else you have that might help, please let me know.
> This is quite an annoying little bug :)
Post the current code. All of it (asm and C).
Alex
--------8<-------- Makefile
CC = g:/cygwin/bin/gcc
LD = g:/cygwin/bin/ld
RM = g:/cygwin/bin/rm
DEFINITIONS = -DALPHA -DDEBUG
COPTS = -Wall -fno-builtin -nostdinc -I../tlibc/include/ $(DEFINITIONS)
all: kernel
kernel: main.o boot.o console.o memory.o
$(LD) --nostdlib -Ttext=0x100000 -e _start main.o boot.o console.o
memory.o -L../lib/ -lc -o kernel.pc
objcopy -O elf32-i386 kernel.pc kernel
console.o: console.c console.h basictypes.h
$(CC) -c console.c $(COPTS) -o console.o
main.o: main.c basictypes.h console.h multiboot.h tyrex.h memory.h
$(CC) -c main.c $(COPTS) -o main.o
boot.o: boot.S multiboot.h
$(CC) -c boot.S $(COPTS) -o boot.o
memory.o: memory.c memory.h
$(CC) -c memory.c $(COPTS) -o memory.o
clean:
-$(RM) *.o kernel.pc kernel
rebuild: clean all
--------8<-------- boot.S
#define ASM
#include "multiboot.h"
.text
.globl start, _start
start:
_start:
jmp multiboot_start
.align 4
multiboot_header:
.long MULTIBOOT_HEADER_MAGIC
.long MULTIBOOT_HEADER_FLAGS
.long -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
multiboot_start:
/* set up stack ptr */
movl $(stack + KERNEL_INITIAL_STACK), %esp
/* clear flags register */
pushl $0
popf
/* call main function... */
/* push params */
pushl %ebx /* multiboot header address */
pushl %eax /* magic value */
call _cmain
/* returned from the kernel? hmm.. */
pushl $final_message
call _cprint
loop:
hlt
jmp loop
final_message:
.asciz "\nSystem halted. Power off or reset."
.comm stack, KERNEL_INITIAL_STACK
--------8<-------- multiboot.h
#ifndef __MULTIBOOT_H__
#define __MULTIBOOT_H__
#include "basictypes.h"
#define MULTIBOOT_HEADER_MAGIC 0x1BADB002
/* we should be an ELF binary... */
#define MULTIBOOT_HEADER_FLAGS 0x00000003
/* bootloader gives us this; we need to check it */
#define MULTIBOOT_LOADER_MAGIC 0x2BADB002
#define KERNEL_INITIAL_STACK 0x4000 /* 16 KB should be
plenty */
#ifndef ASM /* be sure to not include these for assembly */
typedef struct multiboot_header {
DWORD magic;
DWORD flags;
DWORD checksum;
POINTER header_addr;
POINTER load_addr;
POINTER load_end_addr;
POINTER bss_end_addr;
POINTER entry_addr;
} multiboot_header_t;
typedef struct elf_section_header_table {
DWORD num;
DWORD size;
POINTER addr;
DWORD shndx;
} elf_section_header_table_t;
typedef struct {
BYTE part3;
BYTE part2;
BYTE part1;
BYTE drive;
} boot_device_t;
typedef struct multiboot_info {
DWORD flags;
DWORD mem_size_lower;
DWORD mem_size_upper;
boot_device_t boot_device;
POINTER cmdline;
DWORD mod_count;
POINTER mod_addr;
elf_section_header_table_t elf_section;
DWORD mmap_length;
POINTER mmap_addr;
DWORD drives_length;
POINTER drives_addr;
POINTER config_table;
POINTER boot_loader_name;
POINTER apm_table;
DWORD vbe_control_info;
DWORD vbe_mode_info;
WORD vbe_mode;
WORD vbe_interface_seg;
WORD vbe_interface_off;
WORD vbe_interface_len;
} multiboot_info_t;
typedef struct multiboot_module {
POINTER mod_start;
POINTER mod_end;
POINTER string;
DWORD reserved;
} multiboot_module_t;
typedef struct multiboot_memory_map {
DWORD size;
DWORD base_addr;
DWORD base_addr_high;
DWORD length;
DWORD length_high;
DWORD type;
} multiboot_memory_map_t;
typedef struct drive_info {
DWORD size; /* of structure? */
BYTE number;
BYTE mode; /* 0 = CHM, 1 = LBA */
WORD cylinders;
BYTE heads;
BYTE sectors; /* per track */
} drive_info_t;
typedef struct {
WORD version;
WORD cseg;
DWORD offset;
WORD cseg_16;
WORD desg;
WORD flags;
WORD cseg_len;
WORD cseg_16_len;
WORD dseg_len;
} apm_table_t;
#endif /* !ASM */
#endif
--------8<-------- basictypes.h
#ifndef __BASICTYPES_H__
#define __BASICTYPES_H__
#define QWORD unsigned long long
#define DWORD unsigned long
#define WORD unsigned short
#define BYTE unsigned char
#define BOOL unsigned char
#define NULL ((void*)0)
#define TRUE 1
#define FALSE 0
#define POINTER BYTE*
#endif
--------8<-------- memory.h
#ifndef __MEMORY_H__
#define __MEMORY_H__
#include "basictypes.h"
void create_gdt(void);
void load_gdt(void);
// here's a few macros to help us with the 1 MB mem map
#define QMAP_BYTE(a) (a / 8388608)
#define QMAP_BIT(a) ((a / 1048576) % 8)
#define QMAP_USE(a) qmem_map[QMAP_BYTE(a)] |= 1 << QMAP_BIT(a)
#define QMAP_FREE(a) qmem_map[QMAP_BYTE(a)] &= ~(1 << QMAP_BIT(a))
#define QMAP_ISUSED(a) (qmap_map[QMAP_BYTE(a)] & 1 << QMAP_BIT(a))
#pragma pack(push, 1)
typedef struct {
DWORD sd_lolimit : 16; /* segment limit (bottom) */
DWORD sd_lobase : 24 __attribute__ ((packed));
/* segment base address (bottom) */
DWORD sd_type : 5; /* segment type */
DWORD sd_dpl : 2; /* privilege level */
DWORD sd_p : 1; /* segment descriptor present */
DWORD sd_hilimit : 4; /* segment limit (top) */
DWORD sd_xx : 2; /* unused */
DWORD sd_def32 : 1; /* 32 vs 16 bit size? */
DWORD sd_gran : 1; /* limit granularity (byte/page units)*/
DWORD sd_hibase : 8; /* segment base address (top) */
} segment_descriptor_t;
#pragma pack(pop)
#define SDT_NOTSYSTEM 16
/** non-system types: */
#define SDT_CODE 8
/* if not code: */
#define SDT_ACCESSED 1
#define SDT_WRITEENABLE 2
#define SDT_EXPANSION 4
/* if code: */
#define SDT_READ 2
#define SDT_CONFORMING 4
/** system types: */
#define SDT_RESERVED0 0
#define SDT_TSS16AVL 1
#define SDT_LDT 2
#define SDT_TSS16BUSY 3
#define SDT_CALLGATE16 4
#define SDT_TASKGATE 5
#define SDT_INTGATE16 6
#define SDT_TRAPGATE16 7
#define SDT_RESERVED8 8
#define SDT_TSS32AVL 9
#define SDT_RESERVED10 10
#define SDT_TSS32BUSY 11
#define SDT_CALLGATE32 12
#define SDT_RESERVED13 13
#define SDT_INTGATE32 14
#define SDT_TRAPGATE32 15
#endif
--------8<-------- memory.c
#include "memory.h"
BYTE qmem_map[512];
segment_descriptor_t* gdt = NULL;
WORD big_code_segment;
WORD big_data_segment;
#pragma pack(push, 1)
struct gdtp {
union {
struct {
WORD limit;
segment_descriptor_t* addr;
};
long long llval;
};
} gdt_ptr;
#pragma pack(pop)
struct gdtp* ptrgdt;
#define MAKE_SEGMENT_SELECTOR(idx, ldt, priv) ((idx << 3) | ldt << 2 |
priv)
void create_gdt(void) {
/* to create the GDT (global descriptor table), we need to put
* some segment descriptors in it. These descriptors contain
* various information about memory segments in the system.
* We need at least 2 segments--one for code, and one for data--
* that describe the entire linear address space. These two
* segments will be used by the kernel for memory management
* and related tasks. It also needs a null entry at the start.
*/
segment_descriptor_t play; /* one to play with */
int i;
int j;
BOOL stop = FALSE;
/* first, pick a location for the GDT. We could be using up to
* 8192 entries, each 8 bytes, so 64 K is needed, at least.
* Since we've previously mapped memory down to the 1 MB level,
* let's just pick a meg to use.
* TODO: don't waste! (maybe we could put LDTs in the same meg?)
*/
for (i = 0; i < 512 && !stop; i++) {
for (j = 0; j < 8 && !stop; j++) {
if ((qmem_map[i] & (1 << j)) == 0) {
stop = TRUE;
break;
}
}
}
gdt = (segment_descriptor_t*)(i * 8388608 + j * 1048576);
*((long long*)&play) = 0; /* set 8 bytes to 0 */
gdt[0] = play;
/* set up a descriptor for all data */
play.sd_type = SDT_NOTSYSTEM | SDT_WRITEENABLE;
play.sd_dpl = 0; /* most privileged only */
play.sd_lobase = 0x000000;
play.sd_hibase = 0x00;
play.sd_gran = 1; /* 4 KB increments next: */
play.sd_lolimit = 0xFFFF; /* all mem (4 GB) */
play.sd_hilimit = 0xF;
play.sd_p = 1; /* yes, it's present */
play.sd_def32 = 1; /* 32-bit */
gdt[1] = play;
big_data_segment = MAKE_SEGMENT_SELECTOR(1, 0, 0);
*((long long*)&play) = 0; /* set 8 bytes to 0 */
play.sd_type = SDT_NOTSYSTEM | SDT_CODE | SDT_READ;
play.sd_dpl = 0;
play.sd_lobase = 0x000000;
play.sd_hibase = 0x00;
play.sd_gran = 1;
play.sd_lolimit = 0xFFFF; /* all mem (4 GB) */
play.sd_hilimit = 0xF;
play.sd_p = 1;
play.sd_def32 = 1;
gdt[2] = play;
big_code_segment = MAKE_SEGMENT_SELECTOR(2, 0, 0);
#ifdef DEBUG
printf("Contents of GDT:\n");
for (i = 0; i < 3; i++) {
long long num = *((long long*)&gdt[i]);
int bottom = (int)(num & 0xFFFFFFFF);
int top = (int)((num >> 32) & 0xFFFFFFFF);
printf(" %d: 0x%x 0x%x ", i, top, bottom);
}
#endif
}
void load_gdt(void) {
int i;
ptrgdt = &gdt_ptr;
gdt_ptr.addr = gdt;
gdt_ptr.limit = 8 * 3 - 1; // 3 items in it
printf("Loading GDT at 0x%x and setting code segment to 0x%x\n",
gdt, big_code_segment);
printf("GDT_PTR is: %x %x", (DWORD)(gdt_ptr.llval & 0xFFFFFFFF),
(DWORD)((gdt_ptr.llval >> 32) & 0xFFFFFFFF));
for (i = 0; i < 2000000000; i++) ;
asm ("lgdt %0" : : "m" (ptrgdt) );
asm volatile ("ljmp %0, $1f\n\t"
"1:\n\t"
: : "n" (MAKE_SEGMENT_SELECTOR(2, 0, 0)) );
asm ("2: jmp 2");
// asm ("movw %0, %%ax\n\t"
// "movw %%ax, %%ds\n\t"
// "movw %%ax, %%ss\n\t"
// : : "m" (big_data_segment) );
// asm ("movw %0, %%ax\n\t"
// "movw %%ax, %%cs\n\t"
// : : "m" (big_code_segment) );
// asm ("movw %0, %%ax\n\t"
// "movw %%ax, %%cs\n\t"
// : : "n" (MAKE_SEGMENT_SELECTOR(2, 0, 0)) );
// asm ("movw %0, %%cs" : : "m" (big_code_segment) );
}
----8<----
I also have a couple header and source files (console.h, console.c,
stdio.h, stdio.c, stdarg.h, stdarg.c, and another Makefile) which I
don't think need to be included here. Their purposes are fairly clear.
By the way, as you may have learned by reading this mess, I'm actually
doing a very lame map of memory (one bit per megabyte) in order to find
a free place I can for sure use in RAM. You can comment on these
efforts if you wish, but for now I'd just like my GDT to work :)
Thanks and Good Luck,
Tyrel
Where do the data sections start? I see you set where .text starts, but
that's only .text.
What's in the map file produced by ld (force it to, if doesn't)?
And I didn't see your cmain().
Alex
--------8<-------- main.c
#include "multiboot.h"
#include "tyrex.h"
#include "console.h"
#include <stdio.h>
#include "smallelf.h"
#include "memory.h"
#define CHECK_FLAG(f, n) ((f >> n) & 1)
BOOL glance_at_multiboot(multiboot_info_t* boot_info);
static BOOL have_mem_size = FALSE;
static DWORD mem_size_low = 0;
static DWORD mem_size_high = 0;
static BOOL have_boot_device = FALSE;
static boot_device_t boot_device;
static BOOL have_commandline = FALSE;
static char* commandline = NULL;
static BOOL have_bootloader_name = FALSE;
static char* bootloader_name = NULL;
static elf_section_header_table_t kernel_elf;
extern BYTE qmem_map[512];
void use_mem_range(DWORD base_addr, DWORD end_addr);
void free_mem_range(DWORD base_addr, DWORD end_addr);
void cmain(DWORD magic_value, multiboot_info_t* boot_info) {
int i;
cls();
if (magic_value != MULTIBOOT_LOADER_MAGIC) {
// I don't know how we got here, but we managed to get here
// and we weren't loaded by a Multiboot compatible boot loader
printf("ERROR: Kernel was not booted by Multiboot-compatible
boot loader.");
return;
}
printf("Kernel start. Tests: %d, %s, 0x%x.\n", -1234, "hello",
0x1000);
printf("Expected test result: -1234, hello, 0x1000\n\n");
cls();
printf("Welcome to %s %d.%d.%d%s.\n\nPlease wait while %s
loads...\n",
OS_FULL_NAME, OS_VERSION_MAJOR, OS_VERSION_MINOR,
OS_VERSION_BUILD,
OS_VERSION_EXTRA, OS_SHORT_NAME);
clocate(0, 15);
printf("\n%s\n%s", COPYRIGHT_SHORT, COPYRIGHT_NOTICE);
set_region(4, 15);
printf("Verifying bootloader information...\n");
if (!glance_at_multiboot(boot_info)) goto we_failed;
if (!have_mem_size) {
printf("ERROR: Bootloader did not provide memory size.\n");
goto we_failed;
} else {
printf("Analyzing memory (%d K)...\n", mem_size_high +
mem_size_low);
}
// let's create a quick map of physical memory.
// We will have one bit for each meg, so we need
// 4096 bits (512 bytes). No problem. We already put
// that in our data segment, so it's around somewhere.
// first, assume all memory is unusable
for (i = 0; i < 512; i++) {
qmem_map[i] = 0xFF;
}
#ifdef DEBUG
set_region(0, 24);
cls();
#endif /* DEBUG */
if (CHECK_FLAG(boot_info->flags, 6)) { // memory map
multiboot_memory_map_t* mm_item =
(multiboot_memory_map_t*)boot_info->mmap_addr;
#ifdef DEBUG
printf("Memory map @ 0x%x Size: %d\n", boot_info->mmap_addr,
boot_info->mmap_length);
#endif /* DEBUG */
while ((BYTE*)mm_item < boot_info->mmap_addr +
boot_info->mmap_length) {
if (mm_item->type == 1) {
free_mem_range(mm_item->base_addr, mm_item->base_addr +
mm_item->length);
#ifdef DEBUG
printf("RAM @ 0x%x Size: %d K. ", mm_item->base_addr,
(mm_item->length / 1024));
#endif /* DEBUG */
}
mm_item = (multiboot_memory_map_t*)
(((POINTER)mm_item) + mm_item->size +
sizeof(mm_item->size));
}
#ifdef DEBUG
printf("\n");
#endif
}
// locate memory used by kernel
{
elf_section_header_t* esh =
(elf_section_header_t*)kernel_elf.addr;
char* elfnames = (char*)esh[kernel_elf.shndx].addr;
for (i = 0; i < kernel_elf.num; i++) {
#ifdef DEBUG
printf("ELF '%s' @ 0x%x is %d bytes. ",
&elfnames[esh->name], esh->addr, esh->size);
#endif
if (esh->size > 0 && esh->type != SHT_NULL) {
use_mem_range((DWORD)esh->addr, (DWORD)esh->addr +
esh->size);
}
esh++;
}
#ifdef DEBUG
printf("\n");
#endif
}
// locate memory used by Bootloader?
use_mem_range((DWORD)boot_info, (DWORD)boot_info +
sizeof(*boot_info));
if (CHECK_FLAG(boot_info->flags, 6)) {
use_mem_range((DWORD)boot_info->mmap_addr,
(DWORD)boot_info->mmap_addr + boot_info->mmap_length);
}
if (CHECK_FLAG(boot_info->flags, 7)) {
use_mem_range((DWORD)boot_info->drives_addr,
(DWORD)boot_info->drives_addr + boot_info->drives_length);
}
if (CHECK_FLAG(boot_info->flags, 9)) {
char* endstr = (char*)boot_info->boot_loader_name;
while (*endstr) endstr++;
use_mem_range((DWORD)boot_info->boot_loader_name,
(DWORD)endstr);
}
if (CHECK_FLAG(boot_info->flags, 10)) {
apm_table_t* apmtable = (apm_table_t*)boot_info->apm_table;
use_mem_range((DWORD)apmtable, (DWORD)apmtable +
sizeof(apm_table_t));
// TODO: find the segments/offsets in this table and "use" them
too
}
#ifdef DEBUG
{ // print map
for (i = 0; i < 512; i++) {
char c = qmem_map[i];
int j;
int fullness = 0;
if (i % 64 == 0) {
printf("\n ");
}
for (j = 0; j < 8; j++) {
if (c & 1) fullness++;
c = c >> 1;
}
if (fullness == 8) {
c = 219;
} else if (fullness >= 6) {
c = 178;
} else if (fullness >= 3) {
c = 177;
} else if (fullness >= 1) {
c = 176;
} else {
c = '_';
}
putchar(c);
// OLD WAY: still here just in case we need to see the hex again
// if ((qmem_map[i] & 0x0F) == 0) printf("0");
// printf("%x",(DWORD)(((qmem_map[i] & 0xF0) >> 4) |
((qmem_map[i] & 0x0F) << 4)));
}
printf("\n");
}
#endif /* DEBUG */
create_gdt();
load_gdt();
we_failed:
set_region(0, 24);
clocate(0, 22);
}
void set_mem_range(DWORD base_addr, DWORD end_addr, BYTE val) {
int memstart_byte = QMAP_BYTE(base_addr);
int memstart_bit = QMAP_BIT(base_addr);
int memend_byte = QMAP_BYTE(end_addr);
int memend_bit = QMAP_BIT(end_addr);
int i;
// If we are FREE'ing, we need to be conservative.
// In other words, if we haven't freed the ENTIRE meg,
// don't mark it as free!
if (!val) {
if (base_addr % 1048576 != 0) memstart_bit++;
if (memstart_bit == 8) {
memstart_bit = 0;
memstart_byte++;
}
if (end_addr % 1048576 != 0) memend_bit--;
if (memend_bit == -1) {
memend_bit = 7;
memend_byte--;
}
}
if (memend_byte < memstart_byte) return;
// the easy case:
if (memstart_byte == memend_byte) {
for (i = memstart_bit; i <= memend_bit; i++) {
if (val) qmem_map[memstart_byte] |= (1 << i);
else qmem_map[memstart_byte] &= ~(1 << i);
}
return;
}
// do the first byte:
for (i = memstart_bit; i <= 7; i++) {
if (val) qmem_map[memstart_byte] |= (1 << i);
else qmem_map[memstart_byte] &= ~(1 << i);
}
// do in-between bytes:
for (i = memstart_byte + 1; i < memend_byte; i++) {
if (val) qmem_map[i] = 0xFF;
else qmem_map[i] = 0x00;
}
// do the last byte:
for (i = 0; i <= memend_bit; i++) {
if (val) qmem_map[memend_byte] |= (1 << i);
else qmem_map[memend_byte] &= ~(1 << i);
}
}
void use_mem_range(DWORD base_addr, DWORD end_addr) {
set_mem_range(base_addr, end_addr, 1);
}
void free_mem_range(DWORD base_addr, DWORD end_addr) {
set_mem_range(base_addr, end_addr, 0);
}
BOOL glance_at_multiboot(multiboot_info_t* boot_info) {
if (CHECK_FLAG(boot_info->flags, 0)) {
have_mem_size = TRUE;
mem_size_low = boot_info->mem_size_lower;
mem_size_high = boot_info->mem_size_upper;
}
if (CHECK_FLAG(boot_info->flags, 1)) {
have_boot_device = TRUE;
boot_device = boot_info->boot_device;
}
if (CHECK_FLAG(boot_info->flags, 2)) {
have_commandline = TRUE;
commandline = boot_info->cmdline;
}
if (CHECK_FLAG(boot_info->flags, 4)) {
/* a.out!??!? we're supposed to be ELF! */
printf("ERROR: Booted with a.out information, not ELF.\n");
return FALSE;
}
if (CHECK_FLAG(boot_info->flags, 5)) {
/* ELF information. We can use this to
* figure out where the kernel is, so
* we can make note of that when we set
* up memory later */
kernel_elf = boot_info->elf_section;
}
if (CHECK_FLAG(boot_info->flags, 9)) {
have_bootloader_name = TRUE;
bootloader_name = boot_info->boot_loader_name;
}
return TRUE;
}
--------8<--------
Also, the linker put the other sections in convenient places
automatically:
$ objdump -h kernel
kernel: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000fa0 00100000 00100000 00001000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000010 00101000 00101000 00002000 2**4
CONTENTS, ALLOC, LOAD, DATA
2 .rdata 00000430 00102000 00102000 00003000 2**4
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .bss 00004330 00103000 00103000 00003430 2**4
ALLOC
4 .idata 00000014 00108000 00108000 00004000 2**2
CONTENTS, ALLOC, LOAD, DATA
You'll note (now that you have main.c) that I read the locations of
this section information from the ELF information (provided via GRUB)
at run-time to make sure I don't overwrite them (although everything is
within the first meg still, so it doesn't make much difference for my
algorithm).
Thanks and sorry,
Tyrel
Thanks,
Tyrel
Your problem is that you named the labels as numbers. (G)AS problem is that
it doesn't warn you about it.
Simply prepend something like L to the label names and you'll be OK. This is
why I said "the equivalent of "2: jmp 2"" -- I didn't expect (G)AS to take
it literally. The problem can be seen through disassembly, which you must
master too to solve such stupid bugs. :)
HTH,
Alex
This is crazy...
I have looked a little at the map file, by the way, but not the actual
disassembly of the eventual outputed kernel, which could help a bit I
guess... I'll certainly let you know if I manage to find another
problem before you reply again.
In the meantime I'll be working on designing filesystems and APIs and
things... but I'd really like to get this part working :-)
Thanks Very Much, Again!
Tyrel
Why should it warn you that you're declaring local symbols?
Right,
MartinS
But as a number, like 2?
Alex
It surely could. It really does. :)
> I'll certainly let you know if I manage to find another
> problem before you reply again.
>
> In the meantime I'll be working on designing filesystems and APIs and
> things... but I'd really like to get this part working :-)
:)
The problem is with the GDT obviously, not the jump.
The only problematic place left as per my thinking was LGDT. Shall I
countinue? :))
The disaasembly which many (including you) avoid for their aren't good at
it, is actually a friend. With it I find that your code (sorry for using my
names)
{
char aGDTR[6]={0,1,2,3,4,5};
char* p = aGDTR;
asm volatile (
"lgdt %0\n"
:
:"m" (p)
);
}
turns into:
55 push ebp
89E5 mov ebp,esp
83EC28 sub esp,028
83E4F0 and esp,-010
B800000000 mov eax,000000000
29C4 sub esp,eax
C645E800 mov b,[ebp][-0018],000
C645E901 mov b,[ebp][-0017],001
C645EA02 mov b,[ebp][-0016],002
C645EB03 mov b,[ebp][-0015],003
C645EC04 mov b,[ebp][-0014],004
C645ED05 mov b,[ebp][-0013],005
8D45E8 lea eax,[ebp][-0018]
8945E4 mov [ebp][-001C],eax
0F0155E4 lgdt [ebp][-001C]
Or you could use the -S command switch of gcc to get the same w/o any
disassembly:
pushl %ebp
movl %esp, %ebp
subl $40, %esp
andl $-16, %esp
movl $0, %eax
subl %eax, %esp
movb $0, -24(%ebp)
movb $1, -23(%ebp)
movb $2, -22(%ebp)
movb $3, -21(%ebp)
movb $4, -20(%ebp)
movb $5, -19(%ebp)
leal -24(%ebp), %eax
movl %eax, -28(%ebp)
/APP
lgdt -28(%ebp)
Isn't anything ringing in your head already? :) Can you see exactly what
LGDT takes as input and what it should have?
Now, if I change asm a little bit:
asm volatile (
"lgdt (%0)\n"
:
:"r" (aGDTR)
);
I get:
pushl %ebp
movl %esp, %ebp
subl $40, %esp
andl $-16, %esp
movl $0, %eax
subl %eax, %esp
movb $0, -24(%ebp)
movb $1, -23(%ebp)
movb $2, -22(%ebp)
movb $3, -21(%ebp)
movb $4, -20(%ebp)
movb $5, -19(%ebp)
leal -24(%ebp), %eax
movl %eax, -28(%ebp)
leal -24(%ebp), %eax
/APP
lgdt (%eax)
which looks more like what's expected.
The moral is simple. Either you master the tool or you don't use it. Many
program in C/C++ and their code is usually bad because they don't know how
to program well in the language, they never read the standard nor listened
to what the good books say about it. Often C/C++ code is worse than ASM code
because the author doesn't precisely know what will happen with the code
should it move to a different compiler or different CPU -- a lot of things
are done by the compiler and the compiler is free to throw in lots of things
that would break the poorly written code and they often not known or
understood well. Similar to this with C/C++, we have the neat gcc inline
assembly facility with the wonderful AT&T syntax and gcc's rules/constraints
for the inline code. Both complicate simple things enough to make it
impossible do the them w/o special learning or analysis. And it's usually
not a good idea to put inline asm code into a C/C++ source. The reason for
any inline asm is optimization, such optimization that you can't get from
the compiler alone. And this particular case isn't any kind of optimization.
It's just the idea that only a few very CPU-specific asm instructions are
needed and it's kinda lame to make an additional asm file for them and
launch the assembler and link the extra file... But asm is inevitable in OS
dev, be it as little as 0.1% of the entire code, it must still be there and
often times better w/o any intermixing with C/C++ (e.g. ISR wrappers, full
asm code for all entry/exit points with all appropriate register
saving/restoring).
MASTER ASM AND DISASM, DON'T BE AFRAID! :)
Alex
> But as a number, like 2?
If followed by : (i. e. "2:"), yes, that's a local symbol...
If not followed by :, it's not a local symbol.
Right,
MartinS
I have thought several times about just moving all this GDT stuff to an
assembly file. I just didn't feel comfortable with AT&T syntax yet
(I've used MASM for years). I'll try that out and see how it goes, I
suppose. You'll be hearing from me again, whether good or bad news :)
Thanks again,
Tyrel
Then you're genius at making things hard :)
> *** Virtual machine kernel stack fault (hardware reset) ***
WHY??? actually, it may be that the jump works but still for some reason
there's a problem in addressing.
By any chance do you have a fully relocated image of your code (alone with
current map file)?
> I wasn't aware, by the way, that LGDT couldn't take a memory operand.
It can, as it does in normal non-inlined asm:
LGDT (FWORD PTR?) [GDTR_VARIABLE]
or something of that sort, which basically gives LGDT address of where the
new GDTR contents is stored and from where the CPU reads it.
> That's why I was using "m", not "r". And the compiler/assembler never
> complained...
That's the beauty of (G)AS -- it has never been meant for anything but the
back end for the C compiler.
> Line looks like this now:
> asm volatile ("lgdt (%0)" : : "r" (gdt_ptr) );
Yeah, that puts the value of gdt_ptr (address of the thing to which dgt_ptr
points to) to a register and then transforms the above to lgdt [register],
which is what's needed, but initially it lgdt was effectively taking
[&gdt_ptr].
> I have thought several times about just moving all this GDT stuff to an
> assembly file.
Good that you thought. Bad you didn't move it :)
> I just didn't feel comfortable with AT&T syntax yet
> (I've used MASM for years).
That's why I'm using NASM, after having used MASM, TASM and WASM for quite
some years too.
> I'll try that out and see how it goes, I
> suppose. You'll be hearing from me again, whether good or bad news :)
OK. :)
Alex
Yes, to learn properly use assembler only, no GRUB and write your own boot
sector. Its the best way, get down to the raw metal so to speak.
Anyway, best of luck with your GDT'ing,
Aaron
Tyrel
> Simply prepend something like L to the label names and you'll be OK. This
> is why I said "the equivalent of "2: jmp 2"" -- I didn't expect (G)AS to
> take it literally. The problem can be seen through disassembly, which you
> must master too to solve such stupid bugs. :)
The correct syntax for that is:
2: jmp 2b
However to save energy it would be better to do:
2: hlt
jmp 2b
--
Member AFFS, WYLUG, SWP (UK), UAF, RESPECT, StWC
OpenPGP key fingerprint: D0A6 F403 9745 CED4 6B3B 94CC 8D74 8FC9 9F7F CFE4
No to software patents! Victory to the iraqi resistance!
Yes, that seems to work. Though, I find numeric names bad.
> However to save energy it would be better to do:
>
> 2: hlt
> jmp 2b
:) I guess not many of OS hobbyists are at the point of considering size,
speed and power consumption seriously.
> --
...
> No to software patents! Victory to the iraqi resistance!
Agreed! & Yankees go home!
Alex
Interesting...
1) Why isn't the entire structure packed? It appears you only pack
sd_lobase... This could be a problem since you are declaring everything as
DWORD. 1 bit per DWORD, 2 bits per DWORD, etc... What is the sizeof
(segment_descriptor_t)? Is it 40 perhaps?
2) Ancient rule of C: _NEVER_ _EVER_ use unions!!! Why? Because the
compiler can reorganize or pack the layout in any manner it sees fit. There
is absolutely _NO_ standardization for unions. In other words, sd_gran
could be bit 0 or bit 31 or bit 9... The preferred method is to use a
struct with complete sizes (8,16,32,...) and use bithsifts and masks to
'and' and 'or' the proper bits. Using your notation:
typedef struct {
/* ... */
WORD sd_lolimit;
DWORD sd_base_type;
BYTE sd_hilimit_etc;
BYTE sd_hibase;
}
#define SD_GRAN 0x100
sd_hilimit_etc|=SD_GRAN; /* set granularity */
sd_base_type |= (base&0x00FFFFFF<<8); /*set base address */
> #define SDT_CODE 8
> #define SDT_READ 2
> #define SDT_WRITEENABLE 2
>
> #define MAKE_SEGMENT_SELECTOR(idx, ldt, priv) ((idx << 3) | ldt << 2 |
> priv)
>
> segment_descriptor_t* gdt = NULL;
>
> WORD big_code_segment;
> WORD big_data_segment;
>
> struct gdtp {
> WORD limit;
> segment_descriptor_t* addr;
> } gdt_ptr;
> struct gdtp* ptrgdt;
>
> void create_gdt(void) {
> segment_descriptor_t play; /* one to play with */
>
> gdt = (segment_descriptor_t*)(0x00A00000); /* pick a place for now
> */
This address space isn't allocated and may be outside your application
space. If you can't use malloc() (because you're inside a kernel), then
declare an array, near the declaration of 'gdt' above to allocate space:
segment_descriptor_t gdttbl[256];
>
> *((long long*)&play) = 0; /* set 8 bytes to 0 */
Works fine, but lookup memset().
I didn't review that since I believe you have other problems above.
> void load_gdt(void) {
> int i;
> ptrgdt = &gdt_ptr;
>
> gdt_ptr.addr = gdt;
> gdt_ptr.limit = 8 * 3 - 1; // 3 items in it
>
> printf("Loading GDT at 0x%x and setting code segment to 0x%x\n",
> gdt, big_code_segment);
> for (i = 0; i < 2000000000; i++) ; /* "pause" for a bit so I can
> read the text */
>
> asm volatile ("lgdt %0" : : "m" (ptrgdt) );
> // asm volatile ("ljmp %0, $1f\n\t"
> // "1:\n\t"
> // : : "n" (MAKE_SEGMENT_SELECTOR(2, 0, 0)) );
> // asm ("movw %0, %%ax\n\t"
> // "movw %%ax, %%ds\n\t"
> // "movw %%ax, %%ss\n\t"
> // : : "m" (big_data_segment) );
> asm ("movw %0, %%ax\n\t"
> "movw %%ax, %%cs\n\t"
> : : "m" (big_code_segment) );
> }
>
> (==end of code==)
Again, I didn't review that since I believe you have other problems above.
>
> Okay, so you can see at the bottom some commented-out code that I've
> also tried. Any of this code causes the PC to crash or something (I'm
> using VMWare Workstation, and it pops up a message box complaining.) if
> I call create_gdt() followed by load_gdt(). If I comment out the code
> that sets the segment registers (and the long jump), execution
> continues (although not in the state I want it to be in). I suspect
> something wrong with my segment selectors or something, but I really
> don't know. I've been twidling with this all day.
>
> Any assistance would be greatly appreciated. I will continue searching
> more examples, tutorials, etc., to see if I find anything more
> insightful than the 30 examples I've already looked at.
>
I've made a number of comments near above which I hope will help guide you
to your problems.
A couple other suggestions, avoid the use of Pascal style typedefs: WORD,
DWORD, etc. You should be able to use uint8_t, uint16_t, uint32_t, uint64_t
in stdint.h or you can use (for many 32-bit compilers) 'unsigned char',
'unsigned short', 'unsigned long', or 'unsigned long long'.
Rod Pemberton
Maybe. I've never used the combination of union and that packed attribute
thingy like in the above.
However, I've noticed nobody would actually put any code to check for the
structure sizes and member offsets. The whole subject of alignment is either
ignored altogether because isn't familiar or people pretend to know what's
going on or think they know what should happen and they're too self-assured
to afford an extra check.
> 2) Ancient rule of C: _NEVER_ _EVER_ use unions!!! Why? Because the
> compiler can reorganize or pack the layout in any manner it sees fit.
> There is absolutely _NO_ standardization for unions. In other words,
> sd_gran could be bit 0 or bit 31 or bit 9... The preferred method is
> to use a struct with complete sizes (8,16,32,...) and use bithsifts
> and masks to 'and' and 'or' the proper bits. Using your notation:
>
> typedef struct {
> /* ... */
> WORD sd_lolimit;
> DWORD sd_base_type;
> BYTE sd_hilimit_etc;
> BYTE sd_hibase;
> }
>
> #define SD_GRAN 0x100
>
> sd_hilimit_etc|=SD_GRAN; /* set granularity */
> sd_base_type |= (base&0x00FFFFFF<<8); /*set base address */
Yes, this is generally safer and portable between compilers and optimization
switches. But it's more coding work, more clutter and less clarity. So, to
use the unions and other implementation-specific things one must know
exactly how things work using this particular compiler and such and such
switches. If this all is known and there're no surprises, unions and the
like will work as expected because the expected coincides with the reality.
...
> A couple other suggestions, avoid the use of Pascal style typedefs:
> WORD, DWORD, etc. You should be able to use uint8_t, uint16_t,
> uint32_t, uint64_t in stdint.h or you can use (for many 32-bit
> compilers) 'unsigned char', 'unsigned short', 'unsigned long', or
> 'unsigned long long'.
Those may not exist if the compiler is old. If they don't, it's quite
natural to define one's own types with shorter names than standard types and
defined semantics, like:
typedef signed char schar8;
typedef unsigned short uint16;
typedef long int32;
These would have the expected signedness and at least the specified number
of bits.
If you want to see how much is a WORD or if WORD is defined ambiguously
(e.g. derived from a type that depends on the processor mode, e.g. 16 vs 32
vs 64-bit mode), then yes, some clarity is due in the name or code. But if
you only dislike the names in capitals, that's more of the coding style and
preferences.
Alex
I'm sorry Alexei. I didn't realize (at first) that someone spammed some
newsservers with old messages from 2004 and 2005. The posts on 6/8/06 and
6/9/06 around 1:00 am are all repeats. It appears that the better
newservers rejected them as old. So, there will probably never be a
response from Tyrel. But, I am surprised that since you participated in the
original thread, that you didn't recall it...
Rod Pemberton
> I am surprised that since you [Alexei] participated in
> the original thread, that you didn't recall it...
In truth, you should be surprised at how many times the group
answers the same questions, over and over and over again.
Or more importantly how polite and helpfull we are each and
every time. This is a great group!
Shall we rejoice somehow? :)
Alex