Page 1 of 3

[Solved] GDT doesn't work

Posted: Thu Jun 28, 2012 1:58 am
by leyley
I am trying to write a program under protected mode, but it seems it doesn't work after entering protected mode, some evidences show the reason is jmp instruction, but I don't know how to write this jmp. Any one can help me? The source code is shown below:

Code: Select all


/* Boot file of nosdl;
 * This file is loaded at 0x8000;
 * Enter the protected mode and run init;
 * The max size of this file is 8k;
 */

#define BOOTSEG 0x8000	/* where I am in; */

/* Generate a gdt with 32bits base, 20bits limit and 12bits flag; 
 * We use a 32bits limit (low 20bits available) and
 * 16bits flag (lower 4bits of higher byte are always 0) here; 
 */

.text
.code16
.globl _start
_start:
	movl $0xB800,%eax
	movl %eax,%gs
	mov $'0',%al
	mov $0x0C,%ah
	movl %eax,%gs:((80*0+5)*2)
/* Prepare to enter the protected mode; */
__enter:
	/* Clear interrupt flags; */
	cli
	/* Load GDT; */
	lgdt gdtptr
	/* Reset registers; */
	movl $sel_kdata,%eax
	movl %eax,%ds
	movl %eax,%es
	movl %eax,%fs
	movl %eax,%gs
	/* Open A20 Line; */
	inb $0x92,%al
	orb $0b00000010,%al
	outb %al,$0x92
	/* Set cr0, enter protected mode; */
	movl %cr0,%eax
	orl $0x01,%eax
	movl %eax,%cr0
	ljmp $0x10,$(__enter32)
/*
 * Here we enter the 32bits world;
 * The next step is init idt and other things;
 */
__enter32:
.code32
	/* Display a P; */
	xorl %eax,%eax
	movl $sel_video,%eax
	movl %eax,%gs
	movl $((80*0+10)*2),%edi
	mov $0x0F,%ah
	mov $'P',%al
	movl %eax,%gs:(%edi)
	jmp .
/* GDT table; */
.code16
gdt: 		.quad 0x0000000000000000	/* Not used; */
gdt_kstack: .quad 0x00CF92000000FFFF	/* Reserved for kernel stack; 0x08; */
gdt_kcode:  .quad 0x00CF9A000000FFFF	/* Kernel code segment; 0x10; */
gdt_kdata:  .quad 0x00CF92000000FFFF	/* Kernel data segment; 0x18; */
gdt_ustack: .quad 0x00CFF2000000FFFF	/* Reserved for user stack; 0x20; */
gdt_ucode:  .quad 0x00CFFA000000FFFF	/* User code segment; 0x28; */
gdt_udata:  .quad 0x00CFF2000000FFFF	/* User data segment; 0x30; */
gdt_video:  .quad 0x00CFF200B800FFFF	/* Video buffer; 0x38; */
		    .quad 0x0000000000000000	/* Not used; */
.set gdtlen, (.-gdt)	/* GDT Length; */
gdtptr: 	.2byte (gdtlen-1)	/* GDT Limit; */
gdtbase:	.4byte gdt	/* GDT Base address; */
/* Selectors; */
.set sel_kcode, gdt_kcode-gdt
.set sel_kdata, gdt_kdata-gdt
.set sel_ucode, gdt_ucode-gdt
.set sel_udata, gdt_udata-gdt
.set sel_video, gdt_video-gdt
Environment:
CentOS 6.2 x86_64
Virtualbox 4.1.16
gcc 4.4.6
make 3.81
-------------------------------------------------------------------------
Thanks!

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 2:13 am
by qw
Is DS set properly before loading the GDT?

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 2:18 am
by leyley
Hobbes wrote:Is DS set properly before loading the GDT?
I'm not sure... How to set DS properly? Before the definition of GDT or after it?

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 2:21 am
by qw
DS should be set to BOOTSEG before you load the GDT because LGDT works relative to DS like most memory accesses.

Code: Select all

	/* Load GDT; */
	movw $BOOTSEG, %ax
	movw %ax, %ds
	lgdt gdtptr	/* implicit ds:gdtptr */
Also, when your code is located in a segment other than zero, then the GDT base address is not linear. Try:

Code: Select all

gdtbase:	.4byte BOOTSEG * 16 + gdt	/* GDT Base address; */

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 2:33 am
by leyley
Still not work...
I added a

Code: Select all

movl $BOOTSEG,%eax
movl %eax,%ds
before cli, set the gdtbase as BOOTSEG*16+gdt;
I do not understand why I need multiply BOOTSEG and 16... Confused.
New code:

Code: Select all


/* Boot file of nosdl;
 * This file is loaded at 0x8000;
 * Enter the protected mode and run init;
 * The max size of this file is 8k;
 */

#define BOOTSEG 0x8000	/* where I am in; */

/* Generate a gdt with 32bits base, 20bits limit and 12bits flag; 
 * We use a 32bits limit (low 20bits available) and
 * 16bits flag (lower 4bits of higher byte are always 0) here; 
 */

.text
.code16
.globl _start
_start:
	movl $0xB800,%eax
	movl %eax,%gs
	mov $'0',%al
	mov $0x0C,%ah
	movl %eax,%gs:((80*0+5)*2)
/* Prepare to enter the protected mode; */
__enter:
	/* Reset registers; */
	movl $BOOTSEG,%eax
	movl %eax,%ds
	/* Clear interrupt flags; */
	cli
	/* Load GDT; */
	lgdt gdtptr
	/* Open A20 Line; */
	inb $0x92,%al
	orb $0b00000010,%al
	outb %al,$0x92
	/* Set cr0, enter protected mode; */
	movl %cr0,%eax
	orl $0x01,%eax
	movl %eax,%cr0
	ljmp $0x10,$(__enter32)
/*
 * Here we enter the 32bits world;
 * The next step is init idt and other things;
 */
__enter32:
.code32
	/* Display a P; */
	xorl %eax,%eax
	movl $sel_video,%eax
	movl %eax,%gs
	movl $((80*0+10)*2),%edi
	mov $0x0F,%ah
	mov $'P',%al
	movl %eax,%gs:(%edi)
	jmp .
/* GDT table; */
.code16
gdt: 		.quad 0x0000000000000000	/* Not used; */
gdt_kstack: .quad 0x00CF92000000FFFF	/* Reserved for kernel stack; 0x08; */
gdt_kcode:  .quad 0x00CF9A000000FFFF	/* Kernel code segment; 0x10; */
gdt_kdata:  .quad 0x00CF92000000FFFF	/* Kernel data segment; 0x18; */
gdt_ustack: .quad 0x00CFF2000000FFFF	/* Reserved for user stack; 0x20; */
gdt_ucode:  .quad 0x00CFFA000000FFFF	/* User code segment; 0x28; */
gdt_udata:  .quad 0x00CFF2000000FFFF	/* User data segment; 0x30; */
gdt_video:  .quad 0x00CFF200B800FFFF	/* Video buffer; 0x38; */
		    .quad 0x0000000000000000	/* Not used; */
.set gdtlen, (.-gdt)	/* GDT Length; */
gdtptr: 	.2byte (gdtlen-1)	/* GDT Limit; */
gdtbase:	.4byte BOOTSEG*16+gdt	/* GDT Base address; */
/* Selectors; */
.set sel_kcode, gdt_kcode-gdt
.set sel_kdata, gdt_kdata-gdt
.set sel_ucode, gdt_ucode-gdt
.set sel_udata, gdt_udata-gdt
.set sel_video, gdt_video-gdt

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 2:40 am
by Combuster
You do happen to know the difference between Real Mode addressing and protected mode? Both add the offset of the segment to the address. The difference is that that offset is segment value * 16 in real mode and typically 0 in protected mode. So each memory reference in code gets modified.

You say your code is loaded at 0x8000. Is that a segment value? is that a physical address? What is the code being linked at? does the linker assign address 0 to the first line of code? does it assign 0x8000 to the first line of code?

Once you got those basic things, determine what the actual value of gdtptr is, what it's physical location in memory is, and therefore, what the value of DS needs to be.

Hobbes has already been using his crystal ball to complete his set of needed assumptions about all these things, but they won't lead to a fix if they are wrong - and your explanation is not conclusive.

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 2:50 am
by qw
That's funny, my crystal ball usually does not fail :wink: I was indeed assuming that the code is loaded at 8000:0000H since the constant is called BOOTSEG.

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 2:50 am
by leyley
Thanks! I will check these basic things again and go back to the program.
actually, I used ljmp $(0x8000>>4),$0x00 to jump here(I loaded the boot at 0x8000 by using int $0x13). I think it should be a physical address. Here is the sector boot code:

Code: Select all


/* This is the boot loader of nosdl (Network Operating System Developed by Ley);
 * The boot starts at 0x7c00;
 */
 
#define STACKSEG 0x7bff	/* Use 0x0000 - 0x7bff as our stack; 31k; */ 
#define BOOTSEG 0x7c00	/* Bios read boot here; 1k; */
#define KERNELSEG 0x8000	/* Kernel load here; */
#define TEMPSEG 0X9000	/* Store data temprary; */

#define DISK_SPC 0x12 /* Sectors per track; Since we use ext2; */

#define BLK_SIZE 0x400	/* ext2 default block size; */
#define GI_OFF 0x08	/* Offset of inode table bitmap in group block; */
#define IR_OFF 0x80	/* Root offset of inode; */
#define ID_OFF 0x28 /* Offset of data block in inode; */
#define DNS_OFF 0x06	/* Offset of length of filename in directory; */
#define DN_OFF 0x08 /* Offset of filename in diectory; */

/* Functions; */
	.type __calchs,@function
	.type __diskread,@function
	.type __putstr,@function
	.type __putchr,@function
	
/* #define putc(c) pushw %ax; pushw %bx; mov $c,%al; call __putchr; popw %bx; popw %bx */
#define puts(s,l) pushl %ebp; pushl %ecx; movl $s,%ebp; movl $l,%ecx; call __putstr; inc %dh; popl %ecx; popl %ebp
#define setcur(r,c) mov $r,%dh; mov $c,%dl

.text
.code16
.globl _start
	jmp _start
	nop
VERSION:.word 0x0001 /* Boot Loader version; */
SIGNATURE:.ascii "nosl" /* Signature of loader, must be 4 letters; */
SYSTEM_START:.4byte 0x00001000 /* The address where system start; */
_start:
	jmp __main
__main:
	/* Init stack; */
	xorl %eax,%eax
	movw %ax,%ss
	movl $STACKSEG,%esp
	movl %esp,%ebp
	/* %dh - currenct row; %dl -current column; 
	* %dh will be increased automatically by calling puts;	
	*/
	/* Reset cursor; */
	setcur(0,0)
	puts(banner,banner_size)
	/* Read Group description block; */
	xorl %eax,%eax
	mov $0x04,%al
	call __calchs
	mov $0x01,%al
	movw $KERNELSEG,%bx
	call __diskread
	jc __rderror
	xorl %eax,%eax
	movl (KERNELSEG+GI_OFF),%eax	/* Inode bittmap; */
	imul $0x02,%eax
	/* Get directory; */	
	call __calchs
	movw $TEMPSEG,%bx
	mov $0x04,%al
	call __diskread
	jc __rderror
	movl (TEMPSEG+IR_OFF+ID_OFF),%eax	/* Root directory; */
	imul $0x02,%eax
	call __calchs
	mov $0x01,%al
	movw $KERNELSEG,%bx
	call __diskread
	jc __rderror
	xorl %ecx,%ecx
	movl $KERNELSEG,%ecx
__findboot:
	xorl %eax,%eax
	mov DNS_OFF(%ecx),%al
	cmp $boot_fn_size,%al
	jne __findgoon
__cmpname:
	movl %ecx,%eax
	addl $DN_OFF,%eax
	movl $boot_file,%ebx
	call __cmpstr
	cmp $0x00,%al
	je __found
	xorl %eax,%eax
	movl $boot_fn_size,%eax
__findgoon:
__extpn:	/* Extend pointer until %al%4 is 0; */
	pushl %eax
	mov $0x04,%bl
	div %bl
	cmp $0x00,%ah
	jne __extgoon
	popl %eax
	addl %eax,%ecx
	addl $DN_OFF,%ecx
	cmpl $(KERNELSEG+0x400),%ecx	/* Find filename in 1k; */
	jb __findboot
__notfound:
	puts(boot_not_found,bn_size)
	jmp __boot_failed
__extgoon:
	popl %eax
	inc %al
	jmp __extpn
__found:
	/* Save file inode offset; */
	movl (%ecx),%eax
	movl %eax,BOOT_OFF
	/* Get file inode; */
	xorl %ecx,%ecx
	movl $TEMPSEG,%ecx
	subl $0x01,%eax
	imul $IR_OFF,%eax
	addl %eax,%ecx
	xorl %eax,%eax
	movl ID_OFF(%ecx),%eax
	imul $0x02,%eax
	call __calchs
	mov $0x10,%al
	movw $KERNELSEG,%bx
	call __diskread
	jc __rderror
	/* Load kernel;
	 * Real mode addressing;
	 */
	ljmp $(KERNELSEG>>4),$0x0000
	jmp .
__rderror:
	puts(rd_err,rd_err_size)
	jmp __boot_failed
__boot_failed:
	puts(fail_str,fail_size)
	jmp .	
/* Calculate CHS from %ax;
 * This function prepared CHS for int 0x13, ah=0x02;
 * It will modify %ax,%cx,%dx;
 */
__calchs:
	pushl %ebx
	xorl %ebx,%ebx
	mov $DISK_SPC,%bl
	div %bl
	inc %ah
	mov %ah,%cl
	mov %al,%dh
	and $0x01,%dh
	shr $0x01,%al
	mov %al,%ch
	popl %ebx
	ret
/* Load disk into memory;
 */
__diskread:
	mov $0x00,%dl	/* Floopy disk; */
	mov $0x02,%ah
	int $0x13
	ret
/* Print string;
 * %dh - row, %dl - column;
 * when null return;
 */
__putstr:
	movl $0x000f,%ebx
	movl $0x1301,%eax
	int $0x10
	ret
/* Output a character 
 * from %al;
 */
/* __putchr:
	xorw %bx,%bx
	mov $0x0e,%ah
	int $0x10
	ret */
/* Compare string from %ax and %bx;
 * return %al, 0: equal; >0: %ax is great than %bx; <0: %ax is less than %bx;
 */
__cmpstr:
	pushl %edi
	pushl %esi
	movl %eax,%esi
	movl %ebx,%edi
__cmpstart:
	mov (%esi),%al
	mov (%edi),%bl
	cmp $0x00,%al
	je __equal
	cmp %al,%bl
	jg __bigthan
	jb __lessthan
	incl %edi
	incl %esi
	jmp __cmpstart
__bigthan:
	mov $0x01,%ax
	jmp __cmpret
__lessthan:
	mov $-1,%al
	jmp __cmpret
__equal:
	mov $0x00,%al
	jmp __cmpret
__cmpret:
	popl %edi
	popl %esi
	ret
BOOT_OFF:.4byte 0
banner_start:
banner:.ascii "boot>"
banner_end:
.set banner_size, banner_end-banner_start
fail_start:
fail_str:.ascii "Boot failed."
fail_end:
.set fail_size, fail_end-fail_start
rd_err_start:
rd_err:.ascii "Disk error."
rd_err_end:
.set rd_err_size, rd_err_end-rd_err_start
boot_file_start:
boot_file:.ascii "boot"
boot_file_end:
.set boot_fn_size, boot_file_end-boot_file_start
bn_start:
boot_not_found:.ascii "No kernel."
bn_end:
.set bn_size, bn_end-bn_start
.word 0xaa55
The code above loads the boot at 0x8000:0x0000(I think);
The floppy has an ext2 filesystem;
I'm new to this field and this program is just for practise :-)

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 2:55 am
by qw
Yes, you should really read about segmentation in Real Mode.

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 2:58 am
by leyley
ah? int 0x13 used 0x8000, how it becomes 0x800?
Actually I have try several jmps before and it shows that my code is loaded at 0x8000

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 3:02 am
by qw
-

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 3:09 am
by qw
leyley wrote:ah? int 0x13 used 0x8000, how it becomes 0x800?
Actually I have try several jmps before and it shows that my code is loaded at 0x8000
INT 0x13 loads the code into a buffer pointed to by ES:BX. ES happens to be zero, lucky for you, since you did not set it. You did set BX to 0x8000. What goes into BX is the offset and not the segment address. Now 0x0000:0x8000 refers to the same physical address as 0x0800:0x0000. You should decide what segment address to use (0x0000 or 0x0800) and stick to it.

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 3:10 am
by leyley
Hobbes wrote:-
I see... finally I understand how the real mode addressing works, it shifts a register 4 bits left then adds a offset to make a 20bits address...Thanks!

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 3:17 am
by leyley

Code: Select all

movw $KERNELSEG,%bx
movw %bx,%es
xorl %ebx,%ebx
Now it has been loaded at 0x8000:0x0000

Re: GDT doesn't work

Posted: Thu Jun 28, 2012 3:17 am
by qw
You're welcome. Now can you calculate the linear address of the GDT?