Page 2 of 4

Re: Debugging help for protected mode entry+exit experiment

Posted: Sun Sep 20, 2020 2:54 pm
by Octocontrabass
You haven't fixed all of the issues I noted earlier. In particular, failing to perform a far JMP/CALL immediately after setting or clearing CR0.PE could explain some of the unstable behavior.

Re: Debugging help for protected mode entry+exit experiment

Posted: Mon Sep 21, 2020 1:36 am
by alexfru
You're corrupting whatever is at the beginning of .bss with the following code:

Code: Select all

	; fixup selector part of IDT entries
	mov	cx, (our_idt.end - our_idt) / 8 + 1
	mov	di, our_idt+2
.iloop:	mov	word [di], 08h  ; 08 is code segment selector.
	add	di, 8   ; advance 1 IDT entry.
	loop .iloop
Some times "ALIGN 16" in .bss helps you hide this error. Other times it does not.

Re: Debugging help for protected mode entry+exit experiment

Posted: Mon Sep 21, 2020 3:50 am
by awik
PeterX wrote:
awik wrote:It would be interesting to know if:
(a) you get the identical .COM file from assembling the source code with NASM, and
(b) how far (what stage; see lower right corner of screen) the program runs on your system (metal or virtual).
(a) Yes, my NASM output is identical to your .COM file.
(b) On DosBox 0.74-3 (on my Manjaro Linux) I get an 'A' at the lower right corner.

Greetings
Peter
I forgot to mention:

Each time the program comes to a halt, try pressing <ESC> once, and if no response, try pressing it again.

-Albert.

Re: Debugging help for protected mode entry+exit experiment

Posted: Mon Sep 21, 2020 5:51 am
by PeterX
awik wrote:Each time the program comes to a halt, try pressing <ESC> once, and if no response, try pressing it again.
OK. Now, after I see 'A', I press ESC once and the Dosbox exits, showing the message on the console:

Code: Select all

terminate called after throwing an instance of 'char*'
Greetings
Peter

Re: Debugging help for protected mode entry+exit experiment

Posted: Mon Sep 21, 2020 7:17 am
by awik
PeterX wrote:Maybe I don't understand things correctly, but doesn't DOS handle the stack by itself? So why change SP at the start of the .COM program?
DOS allocates all free memory (or, maybe just the largest contiguous block) for the program and loads it at offset 100h in the allocated segment. It loads CS: DS: SS: ES: with the segment address. DOS sets SP to 0xFFFE. So the stack will be 64K minus the size of the program. I do not need all that memory, and I don't need such a large stack. Therefore, I change SP and free all the memory above the start of the (reduced) stack.

-Albert.

Re: Debugging help for protected mode entry+exit experiment

Posted: Mon Sep 21, 2020 8:27 am
by awik
alexfru wrote:You're corrupting whatever is at the beginning of .bss with the following code:

Code: Select all

	; fixup selector part of IDT entries
	mov	cx, (our_idt.end - our_idt) / 8 + 1
	mov	di, our_idt+2
.iloop:	mov	word [di], 08h  ; 08 is code segment selector.
	add	di, 8   ; advance 1 IDT entry.
	loop .iloop
Some times "ALIGN 16" in .bss helps you hide this error. Other times it does not.
You're right. Good catch!

I removed the IDT fixup loop, because it wasn't needed, as the selector (8) is known at assembly-time.

-Albert.

Re: Debugging help for protected mode entry+exit experiment

Posted: Mon Sep 21, 2020 8:41 am
by awik
Octocontrabass wrote:You haven't fixed all of the issues I noted earlier. In particular, failing to perform a far JMP/CALL immediately after setting or clearing CR0.PE could explain some of the unstable behavior.
I followed your advice, replacing the RETF(D)s with JMP FAR [ptr], and I put the JMPs immediately after MOV CR0,EAX.

As expected, it did not help. I did not expect it to help, because my belief was (and remains) that the current descriptor remains in effect until the segment register is (re)loaded, and because interrupts were disabled, there was no way the registers would be unexpectedly reloaded without my doing so. Indeed, this is how the so-called "unreal" (or "big real") mode works, and is nothing knew.

-Albert.

Re: Debugging help for protected mode entry+exit experiment

Posted: Mon Sep 21, 2020 8:59 am
by awik
I think fixing the BSS corruption Alex pointed out has resolved the strange problem of later code seeming to affect earlier code.

I also solved the problem of being unable to exit 32-bit mode. Apparently you can't return to 16-bit mode just by loading the CS register with a real mode segment address -- you will remain in 32-bit mode. You have to first, while in protected mode, load a 16-bit code segment selector.

I am now getting all the way to stage 'F'. The only problem seems to be that the program fails to return to DOS.

Here is a .zip file containing source and binary for the current revision:
https://drive.google.com/file/d/102TBfT ... sp=sharing

Cheers,
Albert.

Re: Debugging help for protected mode entry+exit experiment

Posted: Mon Sep 21, 2020 9:32 am
by PeterX
awik wrote:I am now getting all the way to stage 'F'. The only problem seems to be that the program fails to return to DOS.

Here is a .zip file containing source and binary for the current revision:
https://drive.google.com/file/d/102TBfT ... sp=sharing
With DosBox it works, it returns to DOS,

Greetings
Peter

Re: Debugging help for protected mode entry+exit experiment

Posted: Mon Sep 21, 2020 11:54 am
by alexfru
There's another glaring problem. The program runs with the PIC configuration done by DOS/BIOS and doesn't properly disable/mask or handle IRQ2 and up. I can't tell at the moment if this precludes the program from successful exit to DOS.

Re: Debugging help for protected mode entry+exit experiment

Posted: Mon Sep 21, 2020 11:33 pm
by awik
alexfru wrote:There's another glaring problem. The program runs with the PIC configuration done by DOS/BIOS and doesn't properly disable/mask or handle IRQ2 and up. I can't tell at the moment if this precludes the program from successful exit to DOS.
I'm aware of this. I would have implemented at least EOI for these interrupts if any one of them actually occurred while the program is running. If an interrupt above 0x1F occurred, I assume I would get a GPF (int 0x0D).

By the time I try to return to DOS, the IDTR has been restored to 0x3FF/0x00000000, or whatever it was when I saved the old IDTR.

-Albert.

Re: Debugging help for protected mode entry+exit experiment

Posted: Tue Sep 22, 2020 9:45 am
by awik
All right...

I cleaned up, took what I've learnt, and put together a new test program to enter protected mode, go back to real mode, and return to DOS. A lot of it was rewritten from scratch.

The code:

Code: Select all

;
; Sample program to enter and exit protected mode.
;
ORG 100h

%define BSS_OLD_SEG      0
%define BSS_GDTR         2
%define BSS_IDTR      0x0A
%define BSS_SAVE_GDTR 0x12
%define BSS_SAVE_IDTR 0x1A

main:
	smsw	ax
	test	ax, 1
	jnz .err
	call setvideoseg
	mov	di, (10*80)*2
	cld
	mov	ax, 0x0F00 | '0'
	stosw
	call waitesc16
	mov	bp, bss_start
	mov	ax, cs
	mov	[bp+BSS_OLD_SEG], ax
	xor	dx, dx
	mov	dl, ah
	mov	cl, 4
	shr	dl, cl
	shl	ax, cl
	;
	; Fixups:
	;
	add	[ze_gdt.sel08+2], ax
	adc	[ze_gdt.sel08+4], dl
	add	[ze_gdt.sel10+2], ax
	adc	[ze_gdt.sel10+4], dl
	add	[ze_gdt.sel20+2], ax
	adc	[ze_gdt.sel20+4], dl
	;
	; Save existing GDTR and IDTR.
	;
	sgdt	[bp+BSS_SAVE_GDTR]
	sidt	[bp+BSS_SAVE_IDTR]
	;
	; Make new GDTR:
	;
	push	ax
	push	dx
	add	ax, ze_gdt
	adc	dl, 0
	mov	word [bp+BSS_GDTR], ze_gdt.end-ze_gdt-1
	mov	[bp+BSS_GDTR+2], ax
	mov	[bp+BSS_GDTR+4], dx
	;
	; Make new INTR -- this one is currently a dummy.
	;
	pop	dx
	pop	ax
	add	ax, ze_idt
	adc	dl, 0
	mov	word [bp+BSS_IDTR], ze_idt.end-ze_idt-1
	mov	[bp+BSS_IDTR+2], ax
	mov	[bp+BSS_IDTR+4], dx
	;
	; Load GTDR and switch to protected mode.
	;
	cli
	lgdt	[bp+BSS_GDTR]
	lidt	[bp+BSS_IDTR]
	mov	eax,cr0
	or	al, 1
	mov	cr0,eax
.inpm:	;
	; We are in protected mode.  The segments remain in their old/
	; existing real mode configuration.  Therefore we can still use
	; the previously loaded segment for the video memory.
	;
	smsw	ax
	test	al, 1
	mov	ah, 0x0F
	jz .rm
	mov	al, 'P'
	jmp .cmm
.rm:	mov	al, 'R'
.cmm:	stosw
	call waitesc16
	;
	; Generate a FAR jump to set one of our GDT code segment selectors,
	; namely the 16-bit one.
	;
	push	word 0x20		; 16-bit code seg.
	push	word .begin_pm16
	retf
ALIGN 2
.begin_pm16:
	;
	; Now running with protected mode CS.
	;
	mov	ax, 0x0F00 | 'A'
	stosw
	call waitesc16
	;
	; Load PM data and stack selector.
	;
	mov	ax, 0x10		; 16-bit data/stack seg.
	mov	ds, ax
	mov	ss, ax
	;
	; "Exercise" the stack.
	;
	pusha
	popa
	mov	ax, 0x0F00 | 'B'
	stosw
	call waitesc16
.doexitpm:
	;
	; Exit protected mode.
	;
	mov	eax,cr0
	and	al, ~1
	mov	cr0,eax
	;
	; Restore old GDTR and IDTR
	;
	lgdt	[bp+BSS_SAVE_GDTR]
	lidt	[bp+BSS_SAVE_IDTR]
.outofpm:
	; We are in real mode, but still running with protected mode
	; selectors in the segment registers, except for ES which still
	; remains unchanged ever since we loaded it with the real mode
	; video memory address (0xB800) before the switch to PM.
	;
	; Now, generate a FAR jump to restore CS to its real mode setting.
	; Next, restore the data/stack segment registers.
	;
	mov	ax, [bp+BSS_OLD_SEG]
	push	ax
	push	word .load_cs
	retf
ALIGN 2
.load_cs:
	mov	ds, ax
	mov	ss, ax
	;
	; All segments now restored to their real mode configuration.
	;
	mov	ax, 0x0F00 | 'C'
	stosw
	call waitesc16
	;
	; Re-enable interrupts and return to DOS with status 0.
	;
	sti
	xor	al, al
	jmp .term
.err:	mov	al, 1
.term:	mov	ah, 4Ch
	int 21h


ALIGN 2
setvideoseg:
	push	ax
	smsw	ax
	test	al, 1
	jnz .1
	mov	ax, 0xB800
	jmp .2
.1:	mov	ax, 0x0018	; video segment selector
.2:	mov	es, ax
	pop	ax
	ret

waitesc16:
	pushf
	push	ax
.1:	in	al, 60h
	cmp	al, 1
	jne .1
.2:	in	al, 60h
	cmp	al, 1
	je .2
	pop	ax
	popf
	ret

SEGMENT _DATA

ALIGN 8
ze_gdt:
	dd 0, 0		; entry 0: unused
.sel08: ; code segment selector (32-bit)
	dw 0xFFFF  ; +0  limit bits 0-15
	dw 0       ; +2  base addr. 0-15	-- must fixup
	db 0       ; +4  base addr. 16-23	--  "
	db 0x9B    ; +5  PDdSType, *P=present, D=DPL, S=0=system descr. type
	db 0x40    ; +6  GBLALimt, G=gran., B=32bit, L=64bit-, A=avail.
	db 0       ; +7  base addr. 24-31
.sel10: ; data and stack segment selector (16-bit)
	dw 0xFFFF  ; +0  limit bits 0-15
	dw 0       ; +2  base addr. 0-15	-- must fixup
	db 0       ; +4  base addr. 16-23	--  "
	db 0x93    ; +5  PDdSType, *P=present, D=DPL, S=0=sys.desc, d.seg+w
	db 0x00    ; +6  GBLALimt, G=gran., B=16bit*, L=64bit-, A=avail.
	db 0x00    ; +7  base addr. 24-31
.sel18: ; text mode VGA memory
	dw 0xFFF   ; +0  limit 4095
	dw 0x8000  ; +2  low 16 bits of base addr.
	db 0x0B    ; +4  bits 16-23 of base addr.
	db 0x93    ; +5  PDdSType, *P=present, D=DPL, S=0=sys.desc, d.seg+w
	db 0x00    ; +6  GBLALimt, G=gran., B=16bit*, L=64bit-, A=avail.
	db 0x00    ; +7  base addr. 24-31
.sel20: ; code segment selector (16-bit)
	dw 0xFFFF  ; +0  limit bits 0-15
	dw 0       ; +2  base addr. 0-15	-- must fixup
	db 0       ; +4  base addr. 16-23	--  "
	db 0x9B    ; +5  PDdSType, *P=present, D=DPL, S=0=system descr. type
	db 0x00    ; +6  GBLALimt, G=gran., B=0=16bit*, L=64bit-, A=avail.
	db 0       ; +7  base addr. 24-31
.sel28: ; flat code segment selector (32-bit, 4 GB)
	dw 0xFFFF  ; +0  limit bits 0-15
	dw 0       ; +2  base addr. 0-15
	db 0       ; +4  base addr. 16-23
	db 0x9B    ; +5  PDdSType, *P=present, D=DPL, S=0=system descr. type
	db 0xCF    ; +6  GBLALimt, G=gran*, B=1=32bit*, L=64bit-, A=avail.
	db 0       ; +7  base addr. 24-31
.sel30: ; flat data and stack segment selector (32-bit, 4 GB)
	dw 0xFFFF  ; +0  limit bits 0-15
	dw 0       ; +2  base addr. 0-15
	db 0       ; +4  base addr. 16-23
	db 0x93    ; +5  PDdSType, *P=present, D=DPL, S=0=sys.desc, d.seg+w
	db 0xCF    ; +6  GBLALimt, G=gran., B=16bit*, L=64bit-, A=avail.
	db 0x00    ; +7  base addr. 24-31
.end:

ze_idt:
	dd 0,0
.end:

SEGMENT .bss
ALIGN 16
bss_start:


; vim: syn=nasm:
The shortcomings of this program, are a) that it doesn't test 32-bit mode, and b) that the IDT is just a dummy, so it can't do STI before exiting protected mode and restoring the real mode IDTR.

Any advice on how to fix these shortcomings in a "clean" way?

-Albert.

Re: Debugging help for protected mode entry+exit experiment

Posted: Tue Sep 22, 2020 6:36 pm
by Octocontrabass
awik wrote:Any advice on how to fix these shortcomings in a "clean" way?
Stop using DOS? Stop trying to return to real mode?

There isn't really a clean way to handle IRQs, since the IRQ handlers installed by the BIOS and by DOS expect to run in real mode. Acknowledging the IRQ without actually doing anything can cause the computer to lock up waiting for an IRQ that will never come.

Dropping back into real mode to dispatch IRQ handlers puts you well into "DOS extender" territory, which is not a place I would want to be.

At the very least, switching between 16-bit and 32-bit protected mode is easy: just switch out your code and stack segment registers. (Be careful: if your code and stack segments don't match, stack-related instructions may require size overrides.)

And you're still not using a far JMP to enter or exit protected mode. :wink:

Re: Debugging help for protected mode entry+exit experiment

Posted: Tue Sep 22, 2020 11:12 pm
by alexfru
If you're still interested in virtual 8086 mode, have a look at tut15. It puts the CPU in it and launches COMMAND.COM. There's no 32-bit code, though, and it's a bit heavy on TSS use.
And there are a few things not entirely correct (the far jump after the mode switches isn't there, there's port 70h reading, etc), but it gives one an idea how to handle IRQs and the int instruction while in virtual 8086 mode.

Re: Debugging help for protected mode entry+exit experiment

Posted: Wed Sep 23, 2020 2:37 am
by awik
Octocontrabass wrote:Stop using DOS? Stop trying to return to real mode?
Returning to the environment from which the program was launched is part of the "specification" for this program, and maybe later versions. Writing a boot sector, such as if writing my own operating system loader, is harder to debug, and is beyond the scope of the current program. I intend to do it some time, but not now.
There isn't really a clean way to handle IRQs, since the IRQ handlers installed by the BIOS and by DOS expect to run in real mode. Acknowledging the IRQ without actually doing anything can cause the computer to lock up waiting for an IRQ that will never come.
Acknowledging an interrupt just signals that the system is ready for the next interrupt. Whether it is necessary to do more than just an EOI will depend on the specifics of the device. In the case of the timer interrupt, it is apparently sufficient to do just the EOI.
Dropping back into real mode to dispatch IRQ handlers puts you well into "DOS extender" territory, which is not a place I would want to be.
It is a can of worms that would be easier not to open, but I'm afraid it is something I have to learn.
At the very least, switching between 16-bit and 32-bit protected mode is easy: just switch out your code and stack segment registers. (Be careful: if your code and stack segments don't match, stack-related instructions may require size overrides.)
I read about this in the Intel IA-32 "System Programming" manual". The way I understood it, the bitness specification in the stack descriptor determines whether SP or ESP will be used by default. Is this the correct interpretation?
And you're still not using a far JMP to enter or exit protected mode. :wink:
I tested that, and it did not make a difference. That is because the key requirement is to load CS with a new value, and RETF accomplishes that just fine.

-Albert.