assemblynasmx86-16dos

How to implement Unreal mode with NASM (DOS)?


I'd like to implement Unreal mode (access all 4GB memory) with NASM in DOS. I've found a TASM implementation:

.386p

RealSeg Segment Para Public Use16
Assume cs:RealSeg, ds:RalSeg

Real_Start:
cli

push cs
pop ds

mov ax,3
int 10h

mov ah,9
lea dx,Ex5_Str
int 21h

mov eax,cs
shl eax,4
add GDT_Base,eax
lgdt GDT_Value

mov eax,CR0
or al,1
mov CR0,eax
jmp $+2

mov ax,Real4G
mov es,ax

mov eax,CR0
and al,not 1
mov CR0,eax
jmp $+2

mov eax,'AAAA'
mov ecx,40
mov edi,0b8000h

L1:
mov es:[edi],eax
add edi,4
loop L1

mov ah,4ch
int 21h

Exit:
mov ah,9
int 21h
mov ah,4ch
int 21h

Ex5_Str db 10,13,10,13
db '4GB memory OK',10,13,36
RelSeg Ends

StackSeg Segment Stack Use16
db 400h dup (?)
StackSeg Ends

End Real_Start

Or here is a FASM implementation:

struc GDT_STR
        
                s0_15   dw ?
                b0_15   dw ?
                b16_23  db ?
                flags   db ?
                access  db ?
                b24_31  db ?
ENDS        

SEGMENT CODE16 USE16 PUBLIC
ASSUME CS:CODE16

; GDT definitions
gdt_start dw gdt_size
gdt_ptr dd 0
dummy_descriptor GDT_STR <0,0,0,0,0,0>
code16_descriptor  GDT_STR <0ffffh,0,0,9ah,0,0>    ; 64k 16-bit code
data32_descriptor  GDT_STR <0ffffh,0,0,92h,0cfh,0> ; 4GB 32-bit data,   92h = 10010010b = Presetn , DPL 00, No System, Data Read/Write
gdt_size = $-(dummy_descriptor)

dummy_idx       = 0h    ; dummy selector
code16_idx      =       08h             ; offset of 16-bit code segment in GDT
data32_idx      =       10h             ; offset of 32-bit data  segment in GDT

PUBLIC _EnterUnreal
PROC _EnterUnreal FAR

    PUSHAD
    PUSH DS
    
    PUSH CS
    POP DS
    
    mov     ax,CODE16 ; get 16-bit code segment into AX
    shl     eax,4           ; make a physical address
    mov     [ds:code16_descriptor.b0_15],ax ; store it in the dscr
    shr     eax,8
    mov     [ds:code16_descriptor.b16_23],ah

    XOR eax,eax
    mov     [ds:data32_descriptor.b0_15],ax ; store it in the dscr
    mov     [ds:data32_descriptor.b16_23],ah

    
    ; Set gdt ptr
    xor eax,eax
    mov     ax,CODE16
    shl     eax,4
    add     ax,offset dummy_descriptor
    mov     [gdt_ptr],eax

    
    cli
    mov bx,offset gdt_start
    lgdt [bx]
    mov eax,cr0
    or al,1
    mov cr0,eax 
    
    mov ax,data32_idx
    mov fs,ax
    
    mov     eax,cr0         
    and     al,not 1        
    mov     cr0,eax         

    MOV AX,0
    MOV FS,AX
    POP DS
    POPAD    
    
    RETF

ENDP

I'd like to use NASM, but it is very difficult to translate below listed instructions to NASM:

Do anybody has any working NASM Unreal mode implementation? Or can somebody help to translate either of the above examples to NASM?


Solution

  • You can create the GDT_STR structure in NASM this way:

    struc GDT_STR
        .s0_15:   resw 1
        .b0_15:   resw 1
        .b16_23:  resb 1
        .flags:   resb 1
        .access:  resb 1
        .b24_31:  resb 1
    endstruc
    

    and then you instantiate these structures like this:

    dummy_descriptor:
        istruc GDT_STR
            at .s0_15,  dw 0
            at .b0_15,  dw 0
            at .b16_23, db 0
            at .flags,  db 0
            at .access, db 0
            at .b24_31, db 0
        iend
    
    code32_descriptor:
        istruc GDT_STR
            at .s0_15,  dw 0xffff
            at .b0_15,  dw 0
            at .b16_23, db 0
            at .flags,  db 0x92
            at .access, db 0xcf
            at .b24_31, db 0
        iend
    

    The jmp $+2 jumps to the next instruction in 16-bit code. In the context of the unreal mode code the relative jump to the next instruction clears the instruction prefetch queue. You can replace it with something like:

        jmp next1                   ; Flush the instruction prefetch queue
    next1:
    

    The code uses NASM to generate an obj file that can be linked into an executable. See the comments at the top of the code for instructions on assembling and linking.

    ; Assemble with
    ;     nasm -f obj unreal.asm -o unreal.obj
    ;
    ; Link with Watcom Linker to unreal.exe
    ;     wlink format dos file unreal.obj
    ;
    ; Or link with MS 16-bit segmented linker
    ;     link16 unreal.obj;
    ;
    ; Or link with TLink (Turbo link)
    ;     tlink unreal.obj
        
    ; Define GDT_STR - a GDT descriptor entry
    struc GDT_STR
        .s0_15:   resw 1
        .b0_15:   resw 1
        .b16_23:  resb 1
        .flags:   resb 1
        .access:  resb 1
        .b24_31:  resb 1
    endstruc
    
    bits 16
    
    segment data
    align 4
    
    gdt:
    FLAT_SEL  equ .flat  - .start
    
    .start:
    .null:
        istruc GDT_STR
            at .s0_15,  dw 0
            at .b0_15,  dw 0
            at .b16_23, db 0
            at .flags,  db 0
            at .access, db 0
            at .b24_31, db 0
        iend
    
    .flat:
        istruc GDT_STR
            at .s0_15,  dw 0xffff
            at .b0_15,  dw 0
            at .b16_23, db 0
            at .flags,  db 0x92
            at .access, db 0xcf
            at .b24_31, db 0
        iend
    .end:
    
    .gdtr:
        dw .end - .start - 1
                                    ; limit (Size of GDT - 1)
        dd .start                   ; base of GDT. Needs to be fixed up at runtime
    
    in_pmode_str:  db "Processor already in protected mode - exiting", 0x0a, 0x0d, "$"
    
    
    section code
        ; ..start is a special label for the DOS entry point
    
    ..start:
        ; Save initial EFLAGS state
        pushfd
        cli
    
        ; Initialize DS to our DOS DATA segment (needed for EXEs)
        ; SS:SP will be set by the DOS EXE loader to our stack segment
        mov ax, data
        mov ds, ax
    
    check_pmode:
        smsw ax
        test ax, 0x1                ; Check if we are already in protected mode
                                    ;    This may be the case if we are in a VM8086 task.
                                    ;    EMM386 and other expanded memory manager often
                                    ;    run DOS in a VM8086 task. DOS extenders will have
                                    ;    the same effect
    
        jz not_prot_mode            ; If not in protected mode proceed to switch
        mov dx, in_pmode_str        ;    otherwise print an error and exit back to DOS
        mov ah, 0x9
        int 0x21                    ; Print Error
        jmp exit                    ; Exit program
    
    not_prot_mode:
        ; Apply a fixup to  the GDTR base to convert to a linear address
        mov eax, ds
        shl eax, 4
        add [gdt.gdtr+2], eax
        lgdt [gdt.gdtr]             ; Load our GDT
    
        mov cx, ds                  ; Save DS so it can be restored
        mov eax, cr0
        or al, 1
        mov cr0, eax                ; Set protected mode flag
        jmp .next1                  ; Flush the instruction pefetch queue
    .next1:
    
        ; In 16-bit protected mode
        ; Since we aren't changing CS
        ;     we don't need to enter 32-bit protected mode
        mov bx, FLAT_SEL
        mov ss, bx
        mov ds, bx
        mov es, bx
        mov fs, bx
        mov gs, bx
    
        and al, ~1
        mov cr0, eax                ; Unset protected mode flag
        jmp .next2                  ; Flush the instruction pefetch queue
    .next2:
    
        ; Unreal mode here
        ; Restore SS=DS=ES
        mov ss, cx
        mov ds, cx
        mov es, cx
    
        ; Print UNR to the screen using a flat 4GiB selector
        ; This code won't work in regular real mode
        xor ax, ax
        mov fs, ax
    
        mov word fs:[dword 0xb8000+80*2*3+0], 0x57<<8 | 'U'
        mov word fs:[dword 0xb8000+80*2*3+2], 0x57<<8 | 'N'
        mov word fs:[dword 0xb8000+80*2*3+4], 0x57<<8 | 'R'
    
    exit:
        ; Restore initial EFLAGS state
        popfd
    
        ; DOS exit
        mov ax, 0x4c00
        int 0x21
    
    segment stack stack
        resb 1024
    

    Note: Getting into unreal mode won't work if the processor is already in protected mode (ie executing as a v8086 task). This can be the case with EMM386, DOS extenders and code that relies on DPMI.