Page 1 of 3
GDT code - boom
Posted: Fri Mar 30, 2007 10:35 am
by ehird
This is crashing, and bochs gives almost no message - just something like "page splitting instruction". Now, this is my bad translation from bkerndev's NASM version, so I did something wrong, almost certainly. Any ideas? I can't see anything...
Code: Select all
# see gdt.c
.global gdt_flush
.extern gp
gdt_flush:
lgdt gp
mov 0x10, %ax
mov %ds, %ax
mov %es, %ax
mov %fs, %ax
mov %gs, %ax
mov %ss, %ax
jmp flush2
flush2:
ret
I've tried reversing the mov params in various ways, doing "ljmp", just "ret", etc... everything.
Re: GDT code - boom
Posted: Fri Mar 30, 2007 11:09 am
by urxae
ehird wrote:Code: Select all
# see gdt.c
.global gdt_flush
.extern gp
gdt_flush:
lgdt gp
mov 0x10, %ax
mov %ds, %ax
mov %es, %ax
mov %fs, %ax
mov %gs, %ax
mov %ss, %ax
jmp flush2
flush2:
ret
I'm pretty sure that jmp is supposed to be
to reload the cs segment value. (That assumes you left the code segment right after the null segment in the gdt)
What assembler are you using? The %'s would indicate gas, but the current operand order would indicate anything
but gas (except the first mov).
If it's gas:
* Reverse the mov parameters from the %ds line down
* The jump is written "ljmp $0x8,$flush2"
* Change the first mov to "mov $0x10,%ax" (note the $). The way it's currently written gas will emit code to load ax from memory address 0x10 instead of writing 0x10 to it, IIRC
If it's not gas you should probably remove the %'s and reverse the parameters of the first mov.
Posted: Fri Mar 30, 2007 11:15 am
by XCHG
Correct me if I'm wrong but AFAIK, for the real-mode caches to be flushed and renewed by 32-bit segment operands, you have to do a near jump BEFORE setting new values for segment registers such as "FS", "CS" and etc.
The Jump after the flush can be where you change the EIP to your kernel's entry. Therefore, you should have something like this:
Code: Select all
LGDT [GDTR]
JMP CODESEL:FLUSHCSEIP
[BITS 32]
FLUSHCSEIP:
MOV EAX , DATASEL
MOV DS , EAX
MOV ES , EAX
MOV FS , EAX
MOV GS , EAX
MOV SS , EAX
MOV ESP , 0xFFFF
JMP CODESEL:KERNELENTRY
Posted: Fri Mar 30, 2007 11:30 am
by ehird
Yep, it's gas. Trying those changes now, thanks urxae and XCHG
Edit: Hm nope.
Code: Select all
gdt_flush:
lgdt gp
mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
ljmp $0x08, $flush2
flush2:
ret
Code: Select all
00017504444e[CPU0 ] interrupt(): gate descriptor is not valid sys seg
00017504444e[CPU0 ] interrupt(): gate descriptor is not valid sys seg
00017504444i[CPU0 ] protected mode
00017504444i[CPU0 ] CS.d_b = 32 bit
00017504444i[CPU0 ] SS.d_b = 32 bit
00017504444i[CPU0 ] | EAX=00106814 EBX=0002bdc0 ECX=00000000 EDX=00000000
00017504444i[CPU0 ] | ESP=00106810 EBP=00106870 ESI=0002bf1a EDI=0002bf1b
00017504444i[CPU0 ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf ZF af PF cf
00017504444i[CPU0 ] | SEG selector base limit G D
00017504444i[CPU0 ] | SEG sltr(index|ti|rpl) base limit G D
00017504444i[CPU0 ] | CS:0008( 0001| 0| 0) 00000000 000fffff 1 1
00017504444i[CPU0 ] | DS:0000( 0000| 0| 0) 00000000 000fffff 1 1
00017504444i[CPU0 ] | SS:0010( 0002| 0| 0) 00000000 000fffff 1 1
00017504444i[CPU0 ] | ES:0000( 0000| 0| 0) 00000000 000fffff 1 1
00017504444i[CPU0 ] | FS:0000( 0000| 0| 0) 00000000 000fffff 1 1
00017504444i[CPU0 ] | GS:0000( 0000| 0| 0) 00000000 000fffff 1 1
00017504444i[CPU0 ] | EIP=001006fd (001006fd)
00017504444i[CPU0 ] | CR0=0x00000011 CR1=0 CR2=0x00000000
00017504444i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
00017504444i[CPU0 ] >> mov eax, dword ptr ds:0x100223 : A123021000
00017504444e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
00017504444i[SYS ] bx_pc_system_c::Reset(SOFTWARE) called
Posted: Fri Mar 30, 2007 4:45 pm
by Combuster
the code looks ok... the dump however gives the impression it has nothing to do with the code snippet given - only cs and ss are loaded while the code snippet given should load all segments.
Have you used bochs's debugger to find the line of code that causes the crash yet?
Posted: Fri Mar 30, 2007 5:32 pm
by Kevin McGuire
Code: Select all
interrupt() : gate descriptor is not valid sys seg
Seems to imply you might try looking at the IDT table, and making sure you have the fields correct and the CS descriptor for the entry is correct.
info idt when in debug mode will list the interrupt table.
Code: Select all
>> mov eax, dword ptr ds:0x100223 : A123021000
This might be the instruction EIP is at when the interrupt happens and fails.
This address is writable and readable in the Bochs emulator 2.1.1 that I use. Since you do not have paging enabled by looking at cr0 I would also assume that the mov instruction is where a interrupt happened.
Also the dump seems to correctly show the CS and DS descriptors loaded with the entire four gigabyte address space in scope. Try inserting a:
Right, after you load the segment registers. Then type
info gdt while in debug mode just to make certain they got loaded correctly. Even better insert a mov instruction for some data in memory to check DS after you load it.
Also by using EIP you can then turn around a use a linux program like objdump to dump the kernel executable contents if it is not a flat binary and find the function in which this happened in. EIP should fall right inside on of the virtual addresses in the object dump for a function symbol, or tell the linker to export a map file.
I am really sure it is not a flushing problem in Bochs, but I could be wrong. I would keep a flushing problem in the back of my mind and check out the IDT table first.
Also most likely the multiple interrupt message with no dump except the last one would show that the processor is having a fault, double faulting, then triple faulting
Posted: Sat Mar 31, 2007 4:39 am
by Combuster
It can't be an interrupt - bochs does not normally generate NMIs and interrupts are disabled.
What happens is that a null selector is used, causing a GPF, which in turn results in a triple fault for a lack of an IDT.
Intel wrote:A null segment selector can be loaded into the DS, ES, FS, or GS register, but any attempt to access a segment through one of these registers when it is loaded with a null segment selector results in a #GP exception being generated.
Why there are null selectors is probably the key event to track: I think the selectors are still loaded with their real-mode values.
Posted: Sat Mar 31, 2007 5:59 am
by ehird
OK, time to compile bochs' debugger I see
edit: info gdt generates a hundred damn pages of output.
edit: and bochs' debugger is annoying. I spent 20 minutes going through its startup then hit "enter" by mistake and made it reboot before I managed to look at what went wrong.
Posted: Sat Mar 31, 2007 12:41 pm
by iammisc
you put mov 0x10, %eax that should be mov $0x10, %eax
the $ in gas specifies a literal value, right now you are using the value at location 0x10 of memory.
Posted: Sat Mar 31, 2007 12:47 pm
by ehird
look at my updated code... i do use that
Posted: Sat Mar 31, 2007 1:46 pm
by mystran
With a very large chance, there's something wrong with either the descriptors in teh GDT, or the descriptor in teh register pointing to teh GDT (whatever it's name, you load it by LGDT anyway)..
Posted: Tue Apr 03, 2007 8:06 am
by ehird
Part 2... I trashed my kernel and started again using nasm so I didn't have to translate. So now my code is:
Code: Select all
global gdt_flush
extern gp
gdt_flush:
lgdt [gp]
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp 0x08:flush2
flush2:
ret
which is exactly in bkerndev except that i'm using elf, so no leading underscores.
This is gdt.c:
Code: Select all
#include <system.h>
struct gdt_entry {
unsigned short limit_low;
unsigned short base_low;
unsigned short base_middle;
unsigned char access;
unsigned char granularity;
unsigned char base_high;
} __attribute__((packed));
struct gdt_ptr {
unsigned short limit;
unsigned int base;
} __attribute__((packed));
struct gdt_entry gdt[3];
struct gdt_ptr gp;
/* code in start.asm */
extern void gdt_flush();
void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned
char access, unsigned char gran)
{
/* base address */
gdt[num].base_low = (base & 0xFFFF);
gdt[num].base_middle = (base >> 16) & 0xFF;
gdt[num].base_high = (base >> 24 & 0xFF);
/* limits */
gdt[num].limit_low = (limit & 0xFFFF);
gdt[num].granularity = ((limit >> 16) & 0x0F);
/* granularity + access */
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access = access;
}
void gdt_install(void)
{
/* setup the gdt pointer + limit */
gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
gp.base = &gdt;
/* NULL descriptor */
gdt_set_gate(0, 0, 0, 0, 0);
/* code segment: base address 0, limit 4GB, 4KB gran, 32-bit opcodes */
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
/* data segment: same as code segment, but for data */
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
/* install shiny new gdt */
gdt_flush();
}
This is almost 1:1 what's in bkerndev, yet qemu still locks up... very odd.
I have absolutely no idea what could be going wrong...
Posted: Tue Apr 03, 2007 11:46 am
by Combuster
you have a type error:
Code: Select all
...
struct gdt_entry gdt[3];
...
gp.base = &gdt;
...
the type of &gdt is (gdt_entry**)
gp.base should be the address of the gdt: i.e. of the type (gdt_entry*) (!)
Posted: Tue Apr 03, 2007 12:58 pm
by ehird
tried changing it to = gdt, changing the type in the struct to "struct gdt_entry *", same with ** and &gdt - no luck, still freezes. The code I posted is what the tutorial had, so...
Posted: Tue Apr 03, 2007 2:22 pm
by Combuster
There can be soooooooo many things wrong that fixing one thing doesnt help.
In short, it is time for Guru Meditation. Cross-check every line of code with a debugger to see wether it does exactly what you want. Try feeding gcc -Wall -Wextra -Werror, verify all assumptions and check every dusty corner of your code. (Bugs seem to like dust
)
Once you have gulped down 20 litres of coffee (or tea, if you're like me) and still can't find the problem, come back with the full source code + build instructions + binaries and we'll see what can be done.