DPMI and youth: A bad combination
bY _y

Introduction.

You ever wanted to code some PMODE stuff (non-v86) and you noticed the absolute lack of information on the subject? I have noticed that too, I found two books that touch on the subject and one is in Hungarian (!!!) and I, of course, can't speak Hungarian, so I am screwed on that one, but anyways, this text is here to help you with the aspects of coding under DPMI- induced protected mode, sadly no v86 stuff here, there's even LESS information on that then there is for creationism (luckily for us).

Essay.

How do I detect a DPMI host? How do I get into protected mode? How can I make the guys at school stop laughing at me and calling me a homosexual? Most of these questions should be floating through your mind right now, and I'll address them all (yes, even the last one :)) eventually in this document.

What is DPMI, anyways?

DPMI stands for DOS Protected-Mode Interface. DPMI is a set of functions that can take you straight into protected mode, access all of the resources of a system (and as we all know, you can do some really wicked things when you have access to all of the resources) and save you the trouble of actually dealing with v86 and other nasty things.

Detecting a DPMI host.

Before you actually enter Protected Mode, you'll need to check to make sure that you CAN enter (obviously).
To do this, you use an interrupt :

mov ax, 1687h
int 2fh
or ax, ax
jnz @@no_dpmi_host

Now, on return from this call, the INT returns some important information that you'd better make sure you note.

Save these values, they're gonna be important for you.

To actually enter pmode, you set ES to a segment that has the amount of memory needed as was specified in SI (in paragraphs). To avoid the hassle of actually allocating memory, you can create a segment filled with undefined data and use that as the segment for DPMI to use.

After this, call the value saved as ES:DI as a dword pointer. If the carry flag is set after this call, then there was a problem, and you're not operating in protected mode, you're actually operating in real-mode still. You'll probably want to print an error message and quit if the jump to protected mode is unsuccessful.

After this, you will be operating in protected mode in all it's glory. But whatcha gonna do, huh? Why do you want to go into pmode, huh? What can you do in PMODE that you can't do in realmode?

A whole hell of a lot, actually, the most notable and important being access to all the memory in a system, easy communications with realmode segments (i.e. the video segment, bios data segment, rom char segment, etc), and 32-bit registers and addressing. Well, technically you can already use 32-bit in a 386, but..... well, it's not the same, you don't have all the memory, and all the compiler really does is insert/not insert a 0x66 prefix onto some instructions to extend them to their 32-bit versions.

EXTENDED REGISTERS AND 32-BIT ADDRESSING

If you've never worked with 32-bit assembly (hey, that's a good topic for another essay!) or Win32 assembly, it's likely you don't know much about the 32-bit registers. So, here is a quick digression:

General Registers.

16-bit register
High 8Low 8

This is a general 16-bit register (AX, BX, CX, or DX only; SI and DI are a different way) and it's divisions: the high byte is called xH, where the x stands for a, b, c, or d, depending on which register it is. The low byte is named similarly, as xL, with x{a,b,c,d}.

Data Registers.

Data Register
Whole 16 Bits

DI and SI are arranged so that they can't be accessed in 8-bit byte divisions. Same goes for SP, BP, and IP (although you can't access IP directly), and the segments as well, i.e. CS, DS, ES.

How things change in 32-bit.

In 32-bit, things change and stay the same at the same time. The 32-bit general registers (eax, ebx, ecx, edx) can still be accessed in terms of xL and xH, and even xX, where x{a,b,c,d}.

32-Bit General Registers.

32-bit register
High 16 BitsHigh 8Low 8

You can't access the upper 16 bits directly, like you can access the lower 16-bit as ax, bx, cx, or dx. If you DID want to access them, Intel provides a nice instruction to do so, BSWAP.

BSWAP.

BSWAP is a nice little instruction that will swap upper word (word=16 bits) with the lower word. This can be useful in some cases, however, not always. Example:

bswap eax
mov ax, 1234h
bswap eax

versus:

and eax, 0ffffh
add eax, 12340000h

32-bit Data Registers.

32-bit Data Register
High 16 BitsLow 16 Bits

Much the same as the general registers, you can't access the high word of ESI or EDI without BSWAP, but you can access the low word normally with DI or SI. Also note, that in 32-bits THERE IS NO CHANGE IN THE FORMAT OF THE SEGMENT REGISTERS. No ECS, no EDS, these are the same as the 16-bit segments. Notably, though; there are two new segment registers for you to use on the 386: FS and GS. These can be used just as regular segment registers can be.

32-bit Addressing.

Under Protected Mode and Windows (but not DOS in real mode), all of the addresses are 32-bits, whereas in DOS and Win 3.x, these were 16-bits. If you ever coded anything under either of these platforms, you probably know something about the segmentation problems that were a freakin' nightmare that you had to deal with very often, where the maximum data a 16-bit segment could hold was 64k, and so if you wanted any more than this, you paid very dearly for it, and it was very difficult to code a program and not bash the keyboard in after a segmentation problem.

The maximum data a 32-bit segment (note: 32-bit segments are called descriptors) can hold is four gigabytes. If you have a segmentation problem after this, then, throw away your PC and buy yourself an Apple ][-E, and never think of writing any program that needs more than say, a gig of memory. For shame.

In Protected Mode, you must create a descriptor, then you must set the limit on the amount of memory that this descriptor can access, and then you must actually allocate the memory to the descriptor.

Be sure to free the allocated memory and delete the descriptor before you close the program, it's not nice to the system to do otherwise.

Back To PMODE : Actually Using it.

If you've coded in ASM in the past, you know how much you use interrupts, and how vital interrupts can be to your programs. If you try to code something like this in PMODE, you'll get a rude shock:

xor ax, ax
int 33h

Yep. A shock indeed. You can't just call real-mode interrupts from protected mode. You have to simulate them. You have to save the registers you are going to use into a structure, and then pass this structure to the interrupt you plan on using. Example:

mov [savedAx], 0; put a zero into the saved regs. structure
push es; save ES just in case
les di, SavedRegs; load ES:DI with the desc:offset of regs
xor cx, cx; this specifies copy 0 words from the stack
mov bx, 33h; bl contains the interrupt to simulate
mov ax, 300h; 300h is AX's needed value for this function
int 31h; call the interrupt
pop es; restore ES just in case

Notice the SavedRegs structure? It contains all the registers you use and has the following format:

SavedRegs label dword
_esi dd 0
_edi dd 0
_ebp dd 0
reserved dd 0
_ebx dd 0
_edx dd 0
_ecx dd 0
_eax dd 0
_flags dw 0
_es dw 0
_ds dw 0
_fs dw 0
_gs dw 0
_ip dw 0
_cs dw 0
_sp dw 0
_ss dw 0

When the interrupt is simulated, these values are passed to it; so, when simulating an interrupt, just fill this structure in with whatever values you want, and upon the return from the interrupt, this structure will be filled with the values of the registers that the interrupt would normally return.

There is, however, one interrupt that DPMI will intercept and treat as normal, and that's intrerrupt 21, function 4ch. This is, of course, the function that terminates a program. If you call this function, DPMI will terminate your program for you. Example:

; some code
; end of program:
mov ax, 4c00h
int 21h

Accessing Real Mode Memory.

If you want to read some data you already have in another segment, or perhaps you want to be able to write to some real-mode segment such as the video segment (which is a good thing), then you need to map this segment to a descriptor. This is very, very easy, as it turns out, because DPMI provides a function to do this for us:

mov ax, 2; 31h/2h function : map seg. to descriptor
mov bx, 0a000h; bx is the segment we want to map
int 31h; call the function

If the carry flag is not set after this interrupt, AX contains the descriptor that is mapped from the real-mode segment. To, say, plot a pixel after this function, you could do something like the following:

mov gs, ax
mov gs:[di], bl

Just like in real mode!

The final aspect of Protected Mode that I am going to cover in this document is how to allocate memory. I went over the theory in the 32-bit addressing section of the 32-bit digression section, but here's how to actually put it into practice:

  1. Create one LDT descriptor. Save this descriptor.
  2. Allocate the memory block.
  3. Set the base address of the descriptor to this memory region.
  4. Set the limit of the selector.
  5. When the program is done, release the descriptor.
  6. Release the memory block.

#1 - Creation of a LDT descriptor.

DPMI wouldn't be very useful if it didn't have a function to create descriptors, and therefore, it has this function. It's function 0h of interrupt 31h, and requires the number of descriptors wanted to be in cx.

xor ax, ax; 31h/0h - Allocate LDT descriptor
mov cx, 1; one descriptor
int 31h; allocate this descriptor

If the carry flag is not set after this int, then AX contains the new descriptor. You definately will want to save this value.

#2 - Allocation of memory block.

This is another one of those vital functions that DPMI provides. It's very easy to use, too.

mov ax, 501h; 31h/501h - Allocate memory block
mov bx, highword; bx contains the top word of the
mov cx, lowword; dword of the amount of memory
int 31h; requested; cx, the low word.

If the carry flag is not set after this function, SI:DI contains a handle to this memory. Save this handle; you need this to free the memory in the end. BX:CX contains the linear address of the memory, which is not very useful to you right now. You should now set the base address of the descriptor you created to this address with the next function.

#3 - Set the base address of a selector.

Once you have a selector and some memory, you can make the base address of this new selector this linear memory region (I.e., desc:0 becomes the start of this memory region). This is done with function 07h of Int 31h.

mov ax, 7h
mov bx, someselector
mov cx, high_linear_address_of_memory_block; NOTE! this is BX from
mov dx, low_linear_address_of_memory_block; allocation, and DX, old CX.
int 31h; perform the setting.

If the carry flag is not set after this, then the descriptor has the base address of the memory you allocated. This function returns no other values.

#4 - Setting the limit of the selector.

This is accomplished with function 8h of Interrupt 31h, and all it does is set the limit of the amount of memory that a particular descriptor can access.

mov ax, 8h
mov bx, someselector
mov cx, high_word_of_amount_of_memory_you_allocated_for_this_descriptor
mov dx, low_word_of_amount_of_memory_you_allocated_for_this_descriptor
int 31h

If the carry flag is not set, then your descriptor is fully ready to be used, written to, read from, etc in your program.
After this, you can do anything. But, everything comes to an end, just as this document is, and sometime, you'll have to give up this memory. This is done easily.

#5 - Release of a descriptor.

This is just as easy as creating a selector. Function 001h of Interrupt 31h takes care of this for you.

mov ax, 1h      ; release a selector
mov bx, someselector
int 31h

After this, you should release the memory that you allocated for your precious descriptor:

#6 - Release of a memory block.

If you saved the values that the allocation returned to you in SI:DI (which was the memory block's handle), then this is easy as pie. If you did not save these values, then this is impossible as pi.

mov ax, 502h ; release memory block
mov si, high_word_of_mem_handle
mov di, low_word_of_mem_handle
int 31h

Summary.

You can do quite a bit with all this protected mode shit, especially 3d/graphics/memory intensive stuff. Or you could go with real mode and worry yourself over 64k memory limits and 16-bit applications. It's your choice.

Code.

Four programs are included.

You might also want to check out the official dpmi specification.

Greetings.

CoRN, you leeto, keep up the site, I love it already, just get some more llamas to contribute! :)

ZEN, you confuse me quite a bit, but oh well, keep up the leetness.

Blorght, you're gone now, as it seems, but when you were there it was a great time. Three cheers for whenever you come back.

Cali, you give me the warez I need without question, so thanks :)

Justarius and Rudeboy, together we make a very humorous trio, you two 0wn me sometimes. Good job.

Iczelion, you're a pretty leet guy.

Tin, we may mock eachother a lot, but deep down, you're a pal.

josephCo, you have a way of making unrelated things both funny and personal. If that's good. :)

Quantico, you're in UCF now, big man in a big group, very elite, I wish you the best of luck.

Vizion ("Jizzin")/CORE, funny guy, you are, helpful, too. Leeto.

The_Owl, we're not very good friends, that is to say, we're not very personal, but I greet you all the same as I greet everyone else. Hi, Owl. :)

Messages.

You know what? I can't help you if some people think you're a homosexual. Do you know why? An anonymous survey of people I know on irc reveals that over 50% of them think I'm a blatant homosexual, which is bullshit, what the fuck does IRC tell about a person? nada.

anyways...
later
_y
2/5/99
10:04 PM