Debugging help for protected mode entry+exit experiment
-
- Member
- Posts: 5568
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Debugging help for protected mode entry+exit experiment
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
You're corrupting whatever is at the beginning of .bss with the following code:
Some times "ALIGN 16" in .bss helps you hide this error. Other times it does not.
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
Re: Debugging help for protected mode entry+exit experiment
I forgot to mention:PeterX wrote:(a) Yes, my NASM output is identical to your .COM file.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).
(b) On DosBox 0.74-3 (on my Manjaro Linux) I get an 'A' at the lower right corner.
Greetings
Peter
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
OK. Now, after I see 'A', I press ESC once and the Dosbox exits, showing the message on the console:awik wrote:Each time the program comes to a halt, try pressing <ESC> once, and if no response, try pressing it again.
Code: Select all
terminate called after throwing an instance of 'char*'
Peter
Re: Debugging help for protected mode entry+exit experiment
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.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?
-Albert.
Re: Debugging help for protected mode entry+exit experiment
You're right. Good catch!alexfru wrote:You're corrupting whatever is at the beginning of .bss with the following code:
Some times "ALIGN 16" in .bss helps you hide this error. Other times it does not.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
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
I followed your advice, replacing the RETF(D)s with JMP FAR [ptr], and I put the JMPs immediately after MOV CR0,EAX.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.
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
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.
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
With DosBox it works, it returns to DOS,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
Greetings
Peter
Re: Debugging help for protected mode entry+exit experiment
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
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).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.
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
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:
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.
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:
Any advice on how to fix these shortcomings in a "clean" way?
-Albert.
-
- Member
- Posts: 5568
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Debugging help for protected mode entry+exit experiment
Stop using DOS? Stop trying to return to real mode?awik wrote:Any advice on how to fix these shortcomings in a "clean" way?
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.
Re: Debugging help for protected mode entry+exit experiment
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.
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
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.Octocontrabass wrote:Stop using DOS? Stop trying to return to real mode?
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.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.
It is a can of worms that would be easier not to open, but I'm afraid it is something I have to learn.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.
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?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 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.And you're still not using a far JMP to enter or exit protected mode.
-Albert.