GDT.EXE -- An OS/2 Global Descriptor Table Debugger
(may also be used to wrap fish or clean baseboards)
"To tell you the honest truth, I get a little
uneasy at the thought of device drivers by the
-- Noel J. Bergman
CIS PCmagnet, 25-Oct-1988
"Protected mode? What protected mode?"
-- Ross Nelson
BIX os.2/kernel #909
GDT.EXE (version 0.30103) walks through the 286 protected-mode
Global Descriptor Table (GDT) as used by OS/2. It relies on a small
device driver, DEVHLP.SYS, which must be installed in your
CONFIG.SYS file (device=devhlp.sys). (DEVHLP.SYS has been
undergoing changes, so you should use the version of DEVHLP.SYS found
in GDT.ARC, even if you already have a version of DEVHLP.SYS.)
GDT.EXE comes with a state-of-the-art graphical windowed
user-interface. Just kidding! (Perhaps) it _should_ come with such
an interface, but actually GDT.EXE works more like EDLIN or DEBUG.
To print out the entire GDT, just type W (and a carriage
return), and the programs does a "walk" through the entire
table. The display looks like this:
GDT at 11FE80 <636 entries>
Type H for help
[seg 0008] 11FE80 [Writable data]
[seg 0010] 009BE4 [Busy TSS gate]
[seg 0018] 121A88 [Writable data]
[seg 0020] 000000 [Writable data]
[seg 0028] 1F5A60 [LDT]
[seg 0030] 1F5A60 [Writable data]
[seg 0038] 1F94A0 [Writable data]
[seg 0040] 000400 [Writable data]
[seg 0050] 009780 [Writable data]
[seg 0060] 009A00 [Read-only data]
[seg 0070] 009780 [Read-only data]
[seg 0078] 1F1980 [Writable data]
[seg] is the segment number in hex. Since the protection level
is encoded inside the segment number, for each segment number shown
here, there are another three aliases. The segment numbers shown by
GDT.EXE are for Ring 0.
The next entry, which looks like 123456, is the 24-bit physical
address of the base of this segment. (Note that these are 24-bit
physical addresses, not the 20-bit ones you're used to from DOS.)
This number is in hex.
In a 286 operating system (and OS/2 is right now very much a 286
OS, even if you're running it on a 386 machine), a segment can be
from 0 to 64k bytes long. The offset into the segment of the very
last byte is indicated by the next entry in GDT.EXE's display, which
looks like . This number is in decimal.
Segments can have various attributes, also known as access
rights. The next entry displays the hex code for a segment's access
rights, for example . Since this is available using the LAR
instruction, in GDT.EXE the information itself is called the LAR,
even though that isn't quite right.
Since few people can be expected to remember that, e.g., E4 is a
call gate, the next (and last) entry displayed when you do a walk is
a brief textual description of the segment like "Readable code",
"Writable data," "Pungent aroma."
One type of segment is so different from the others that it's
displayed quite differently, however. This is the call gate. (This
documentation is not intended to teach the workings of the 286. For
that, turn to a good book on the 286, repeat, 286, not 386 [trying to
decipher the GDT under OS/2 using documentation for the 386 is a
mistake; take my word for it], such as Stephen P. Morse and Douglas
J. Albert, THE 80286 ARCHITECTURE; Ed Strauss, INSIDE THE 80286, or
Marcel Proust, A LA RECHERCHE DU 286.)
For a call gate, the display looks like this:
DOSALLOCSHRSEG (5 wds)
DOSGETSHRSEG (4 wds)
DOSGIVESEG (4 wds)
DOSGETSEG (1 wds)
Rather than display the segment number as [seg 0F10], for a call
gate the same information is displayed as . This
is the address you would get back from calling DosGetProcAddr on a
DOSCALLS (OS/2 kernel) function. Actually, it's not quite what you
would get back: 0F10:0000 represents a Ring 0 segment; its Ring 3
equivalent is 0F13:0000.
The next field, e.g.,
, shows the code that
the call gate points to. Note that segment 00B0 (in this case) is
just another entry in the GDT. (I'll jump ahead of myself and
tell you that if you just wanted to find out about segment 00B0,
you could type . b0 [a dot followed by a space followed by B0,
followed of course by the carriage return].) This other entry had
better be code, or we're in trouble.
The next field, e.g., , is the 24-bit physical
address for the first opcode of the code pointed to by the call gate
in the house that Jack built. (In fact, all this code seems to have
as its first instruction yet another CALL, so there's even one more
level of indirection.) This physical address is derived by adding
together the physical address of the base of segment 00B0 (in this
case) and the offset given in the call gate itself.
The next field, e.g., DOSGETFRAMIS, represents the name of the
OS/2 function for which the call gate is the entry point. If the
name is followed by an asterisk, this indicates that this is either
a new and/or undocumented function, e.g., DOSICANONICALIZE or
The final field, e.g., (4 wds), indicates that this function
expects 4 words (8 bytes) worth of arguments. Of course, this isn't
such a revelation when you're talking about DOSGETFRAMIS (which,
as we all know, does in fact take 4 words: WORD, PTR WORD, WORD),
but it might be useful for someone to know that the undocumented (?)
DOSQSYSINFO takes 4 words.
The LAR for a call gate is E4, and if you just wanted to display
all call gates, you could ask GDT.EXE to "search" for them:
$ s e4
Likewise, if you were interested in finding segments marked
as read-only data, one thing to try would be:
$ s f1
[seg 0060] 009A00 [Read-only data]
[seg 03E0] 1FBE20 [Read-only data]
Now, segment 0x60 is sort of interesting: it's the global
information segment maintained by OS/2 (so far most of the data
structures we've been looking at are pure 286, having little or
nothing to do with OS/2 per se, but this one is certainly an OS/2
To actually look at the bytes in segment 0x60, we can once
again rely on the intuitive user-friendly interface of GDT.EXE.
In this case, use the ? command:
$ ? 60
[seg 0060] 009A00 [Read-only data]
009A00 55 97 AD 23 84 2A 7B 00 17 39 09 03 FF FF 36 01 U...............
009A10 13 0C C4 07 01 0A 0A 00 05 10 05 01 09 00 01 03 ................
009A20 20 00 F8 00 01 00 00 00 00 00 00 00 00 00 00 00 ................
009A30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
009A40 00 00 00 00 00 00 10 .......
You can continually type ? x60 at the $ prompt (or, if you have
the ALIAS command-line editor, you can just press the up-arrow key),
and each time you display the GIS it will be a little different, since
OS/2 updates the time kept in the GIS.
One can use the ? command to display any segment. However, in
the case of a call gate, it won't do you much good since (a) it's
code you want, and (b) in a call gate the fields are all mixed up.
Therefore, GDT.EXE provides the U command (for unassemble).
Actually, it's not GDT.EXE that provides this -- I wouldn't know
where to begin to write an unassembler, so CodeView (CVP.EXE) is run
as a child process. In the case of the call gate:
DOSALLOCSHRSEG (5 wds)
you would issue the command:
$ u 00b0:0b4e
because you want to disassemble the code at 00B0:0B4E (remember, it's
"just" a call gate at 0F10:0000). GDT.EXE will bring up CodeView
(which takes quite a while!), and all sorts of trash will be spewed
out to your screen, including a line like
garbage RET; BR0
Ignore that! It's just control returning to the debugger, at which
point it picks up our instruction to unassemble. With most of
these DOSxxxx call gates, what you'll probably see as the first
instruction is a line like
garbage garbage garbage CALL 74A0
At the CodeView prompt, you can now type
> u 74a0
and so on. Just q (quit) when you're done. You'll be returned to
the GDT.EXE $ prompt.
When you tire of GDT.EXE, type q to quit back to OS/2.
Other GDT.EXE commands include:
! -- run another OS/2 program
$ ! cd \os2\forth && start forth
$ ! cd \os2\xlisp && start os2xlisp
$ ! dir
H -- displays an out-of-date help message
P -- displays bytes at an arbitrary physical address
$ p b 8000 40
$ p f e008 100
R -- change radix (radix always entered in decimal)
$ r 10
$ r 16
Not-so-blue-sky: Since in GDT.EXE we can manipulate call gates as data,
we can probably also make them point to our own code, just an interrupt
handler under MS-DOS. This would be useful for writing front-ends to,
or replacements for, various DOSxxxx calls. Presumably one would name
this facility DosSetProcAddr, since we already have DosGetProcAddr.
Or, since there is VioRegister and KbdRegister, perhaps the facilities
provided in DEVHLP.SYS could be used to build a DosRegister (not to
be confused with a bridal registry, by the way). I can think of one
DOSxxx function that could use a better front-end, for example:
DosGetProcAddr doesn't accept ASCIIZ names for DOSCALLS; couldn't
we just write a preprocessor for it, point the call gate at our
code (presumably fiddling around with protection levels), have our
code call ("chain_intr," as it were) to the code that the call gate
originally pointed to? This would also be handy for writing home-brew
-- Andrew Schulman
32 Andrew (!) Street
Cambridge MA 02139
18 December 1988 // twas the week before Xmas...
31 December 1988// revised: the great new year's eve debug session