1083 lines
46 KiB
Plaintext
1083 lines
46 KiB
Plaintext
Date: Mon, 18 Mar 91 15:03:13 EST
|
|
From: Andrew Schulman <andrew@pharlap.com>
|
|
Subject: UNDOCUDOS errata -- part 1 of 3
|
|
|
|
Errata, Commentary, and "Release Notes" for
|
|
UNDOCUMENTED DOS:
|
|
A Programmer's Guide to Reserved MS-DOS Functions and Data Structures
|
|
by Andrew Schulman, Raymond J. Michels, Jim Kyle,
|
|
Tim Paterson, David Maxey, and Ralf Brown
|
|
(Reading MA: Addison-Wesley, 1990, 694 pp., $39.95, ISBN 0-201-57064-5)
|
|
|
|
Errata, Commentary, and "Release Notes" - 9 March 1991
|
|
compiled by Andrew Schulman
|
|
andrew@pharlap.com
|
|
CIS 76320,302
|
|
(617) 661-1510 x238
|
|
|
|
Here are some corrections and commentary to UNDOCUMENTED DOS.
|
|
Thanks to everyone who sent in corrections! (Your help is
|
|
acknowledged in the appropriate place below.)
|
|
|
|
NOTE: If you are having problems reading either of the two disks
|
|
that come with UNDOCUDOS, please contact the publisher, Addison-Wesley,
|
|
for replacement disks. Call 617-944-3700, and ask for Debby McKenna.
|
|
|
|
|
|
11 As an example of the prevalent attitude towards using undocumented
|
|
features, it might have been useful to include a quotation, such
|
|
as "It has been our experience that 'undocumented goodies' are
|
|
interesting to look at, but dangerous to include in software that
|
|
is intended for general distribution" (Paul Yao, _Peter Norton's
|
|
Windows 3.0 Power Programming Techniques_, New York: Bantam Books,
|
|
December 1990, p. 108; this is quite a good book, by the way).
|
|
The best counter-example is Windows 3.0 itself, which (as shown
|
|
on p. 18 of UNDOCUDOS) makes extremely heavy use of
|
|
undocumented DOS functions; another example is Microsoft's
|
|
Windows debugger CVW, which relies heavily on such undocumented
|
|
Windows functions as WinDebug() and SetEventHook().
|
|
|
|
16 In the UNDOC.SCR Intrspy script, "DS:SI-byte" and the two
|
|
occurrences of "DS:DX-byte" should read "DS:SI->byte" and
|
|
"DS:DX->byte" (arrow -> not hyphen -).
|
|
|
|
18 The reference to "DOSSPY" should read "INTRSPY."
|
|
|
|
19 "used in chapter 5 on TSRs": the DOS SDA is also used extensively
|
|
in chapter 4 on the DOS file system and network redirector.
|
|
|
|
26 "Get List of List" should of course read "Get List of Lists"
|
|
(by the way, the actual name for this in the MS-DOS source code
|
|
is apparently SYSVARS).
|
|
|
|
28 The section asserting that 25% of DOS is undocumented was not
|
|
meant to be taken too seriously.
|
|
|
|
37 The second paragraph should also note that the _asm keyword
|
|
produces the MSC 6 warning "C4204: in-line assembler precludes
|
|
global optimizations."
|
|
|
|
45 Two readers, Martin Heller (BIX: mheller) and Terrence Vaughn
|
|
(CIS: 72327,2442) found that the assembly-language code at the
|
|
bottom of the page has an incorrect conditional jump and a
|
|
missing label. The block at the bottom of the page should read:
|
|
|
|
jne dos3up ; DOS 3+ ; CHANGED
|
|
mov si, 10h ; DOS 2.x
|
|
jmp short get
|
|
dos3up: cmp al, 3 ; CHANGED
|
|
jne ofs21
|
|
and ah, ah ; DOS 3.0
|
|
|
|
The code continues as is on the top of the next page.
|
|
|
|
47 "Testing simply for equality (for example version >= 4)" is
|
|
confused. Should read "==" not ">=" because we're trying
|
|
to provide an example of what _not_ to do. Testing if
|
|
(version >= 4) is the correct way to do it; testing if
|
|
(version == 4) is the wrong way to do it. (This correction
|
|
is probably more confusing than our original typographical
|
|
error!)
|
|
|
|
65 Four lines from the bottom, "interrupt handle" should read
|
|
"interrupt handler."
|
|
|
|
70 "Programs written for Microsoft Windows 3.0 can make
|
|
undocumented DOS calls without any special handling. This
|
|
includes Windows 3.0 running in 386 enhanced mode."
|
|
|
|
Ahem! Boy, is this statement wrong. I had tested only one
|
|
undocumented DOS call from a Windows program when I wrote
|
|
that. It was the LASTDRIVE check from Chapter 2, where
|
|
INT 21h AH=52h is called, and then the LASTDRIVE byte is
|
|
read out of the List of Lists.
|
|
|
|
It turns out, this _just happened_ to work, but I should still
|
|
have known better than to make this stupid claim. Windows
|
|
3.0 programs in Standard and Enhanced modes are running in
|
|
protected mode (which is why Windows is finally a usable
|
|
product). Thus, the same restrictions noted elsewhere in Chapter
|
|
2 for making undocumented DOS calls from protected mode apply
|
|
to Windows programs as well. The LASTDRIVE check just happened
|
|
to work, because INT 21h AH=52h seems to be transparently
|
|
supported in protected mode (this can't be relied on, however)
|
|
and because reading the LASTDRIVE byte out of the List of Lists
|
|
doesn't involve any further pointer dereferencing. For example,
|
|
trying to walk the DOS device chain or MCB chain would _not_
|
|
have worked; other undocumented DOS calls (e.g., INT 21h
|
|
AX=5D06h) are not supported at all in protected mode.
|
|
|
|
In order to make undocumented DOS calls from a Windows program
|
|
running in Standard or Enhanced mode (the only modes that matter),
|
|
you must use the DOS Protected Mode Interface (DPMI) function
|
|
to "Simulate Real Mode Interrupt" (INT 31h AX=0300h). In almost
|
|
all cases, you will then need to map one or more real-mode
|
|
pointers into your protected-mode address space. This can be
|
|
done using either DPMI (the Allocate LDT Descriptors [INT 31h
|
|
AX=0000h], Get Descriptor [INT 31h AX=000Bh] and Set Descriptor
|
|
[INT 31h AX=000Ch] calls) or using the Windows AllocSelector()
|
|
call together with -- ta da! -- some undocumented Windows calls
|
|
(which will be the subject of a forthcoming book by A. Schulman,
|
|
D. Maxey, et al., titled _Undocumented Windows Programming_):
|
|
|
|
/* a few useful undocumented KERNEL functions */
|
|
extern DWORD FAR PASCAL GetSelectorBase(unsigned sel);
|
|
extern DWORD FAR PASCAL GetSelectorLimit(unsigned sel);
|
|
extern void FAR PASCAL SetSelectorBase(unsigned sel, DWORD base);
|
|
extern void FAR PASCAL SetSelectorLimit(unsigned sel, DWORD limit);
|
|
|
|
In other cases (for example, INT 21h AH=60h), you will need to
|
|
allocate a conventional-memory buffer for use by an undocumented
|
|
DOS call. Use the Windows GlobalDosAlloc() call for that.
|
|
|
|
In any case, the statement that Windows programs "can make
|
|
undocumented DOS calls without any special handling" couldn't
|
|
be further from the truth. The above notes should get you
|
|
started. In addition, a forthcoming PC MAGAZINE Lab Notes
|
|
by A. Schulman (tentatively titled "Moving DOS Programs to
|
|
Windows with DPMI") will contain further information on this
|
|
topic, as will a forthcoming book by A. Schulman and D. Maxey,
|
|
tentatively titled _The DOS Programmer's Guide to Microsoft
|
|
Windows_ (Addison-Wesley, due December 1991).
|
|
|
|
Those interested in exploring the innards of Windows might
|
|
want to check out Alan Cobb's pamphlet _Reverse Engineering
|
|
Windows and OS/2 Software_. Contact Alan at CIS 73170,3543
|
|
or BIX/MCI (AlanCobb).
|
|
|
|
73 Discussion of Phar Lap 386|DOS-Extender: "those weird calls (like
|
|
undocumented DOS) not transparently supported in protected mode."
|
|
In 386|DOS-Extender 3.0, many undocumented DOS calls _are_
|
|
supported in protected mode, so this statement is no longer
|
|
quite accurate. For example, INT 21h AH=52h returns in ES:EBX
|
|
a pointer to the List of Lists. However, any far pointers
|
|
in the data structure remain real-mode far pointers, so these
|
|
would need special handling.
|
|
|
|
The LASTDRIVE example is inadequate to bring out the issue
|
|
of using far pointers in the List of Lists. In Windows 3.0
|
|
protected mode, LASTDRIVE just happened to work. The same
|
|
thing would happen in 386|DOS-Extender 3.0. In the second
|
|
edition of UNDOCUDOS, we will need an additional example
|
|
for these environments.
|
|
|
|
Also note that Phar Lap now has a second product,
|
|
286|DOS-Extender, with a different API from our 386
|
|
product. In 286|DOS-Extender, you would call DosRealIntr()
|
|
to issue an INT 21h AH=52h, and then call DosMapRealSeg()
|
|
to map any real-mode pointers into your program's
|
|
protected-mode address space. Several programs from
|
|
UNDOCUDOS were ported to protected mode using
|
|
286|DOS-Extender, and appear in its _Developer's Guide_.
|
|
|
|
74 the parenthetical remark should read: "(note that INT 2Fh
|
|
AH=16h and AH=17h are the Microsoft Windows interface
|
|
for non-Windows applications; for more information, see
|
|
the INTRLIST.EXE database on disk)." A detailed discussion
|
|
of the Windows INT 2Fh functions will appear in the
|
|
forthcoming book by Schulman and Maxey, noted earlier.
|
|
|
|
75 The structures at the top of the page are wrong. A better one
|
|
is:
|
|
|
|
/* structure of a protected-mode descriptor */
|
|
typedef struct {
|
|
unsigned limit, addr_lo;
|
|
unsigned char addr_hi, access, reserved, addr_xhi;
|
|
} DESCRIPTOR;
|
|
|
|
If you still want the access-rights byte to use an ACCESS
|
|
bit field, rather than a plain unsigned char, then you must
|
|
ensure that the bit field occupies only one byte. Microsoft
|
|
C allows the following non-standard use of unsigned char
|
|
in a bit field, so:
|
|
|
|
typedef struct {
|
|
unsigned char accessed : 1;
|
|
unsigned char read_write : 1;
|
|
unsigned char conf_exp : 1;
|
|
unsigned char code : 1;
|
|
unsigned char xsystem : 1;
|
|
unsigned char dpl : 2;
|
|
unsigned char present : 1;
|
|
} ACCESS;
|
|
|
|
An improved version of LDDPMI.C appears at the end of these
|
|
release notes. (The improved version now works with Borland
|
|
C++ 2.0, as well as with Microsoft C 6.0 and higher.)
|
|
|
|
76 In dpmi_init(), after the call to _dos_allocmem(), the
|
|
following line of code should be added:
|
|
|
|
dpmi_flags &= ~1; // this is a 16-bit protected-mode program
|
|
|
|
When INT 2Fh AH=1687h returns the DPMI flags in BX, the bottom
|
|
bit (dpmi_flags & 1) indicates whether the DPMI host supports
|
|
32-bit programs (Windows 3.0 enhanced mode does). But when
|
|
passing flags back into the "Real to Protected Mode
|
|
Switch Entry Point" via (*dpmi)(), the bottom bit of the
|
|
flags passed in AX indicates whether _this_ is a 32-bit
|
|
program. LDDPMI is a 16-bit program, so the bit must be turned
|
|
off.
|
|
|
|
79 Before the call to dpmi_set_descriptor(), it would have been
|
|
much simpler to call a dpmi_get_descriptor() function for some
|
|
known selector (like the program's DS), and then just change
|
|
the appropriate base-address and limit fields.
|
|
|
|
80 "LDDPMI uses functions such as pmode_printf() rather than
|
|
plain old printf().... Most DPMI servers will in fact
|
|
provide protected-mode INT 21h services (the Windows 3.x
|
|
DOS extender does, for example), but that is a facility
|
|
provided by the DPMI server, not by DPMI itself."
|
|
|
|
This is literally correct, but it still was foolish not to
|
|
go ahead and use plain old printf(), since any DPMI
|
|
server will provide the necessary underlying INT 21h services
|
|
in protected mode. For example, see the program HELLOPMW.C
|
|
in Ray Duncan, "An Introduction to the DOS Protected Mode
|
|
Interface," _PC Magazine_, 12 February 1991, p. 370.
|
|
(Duncan's three-part series on DPMI in _PC Magazine_,
|
|
12 February 1991, 26 February 1991, and 12 March 1991, is
|
|
an excellent introduction to the topic.)
|
|
|
|
In fact, this could have been used as an opportunity to
|
|
explore yet another semi-undocumented aspect of Windows,
|
|
since the fact that Windows provides INT 21h services
|
|
in protected mode is kept pretty well hidden (talk about
|
|
hiding your light under a bushel!). The only documentation
|
|
is a brief (five-page) document titled "Windows INT 21H and
|
|
NetBIOS Support for DPMI," which is included in a packet of
|
|
Microsoft Windows development notes (Part No. 050-030-313).
|
|
(Note also that many more DPMI calls are actually supported
|
|
by Windows than this document indicates.)
|
|
|
|
88 "INT 4Bh is used for 'DMA Services'": the actual title of
|
|
the specification is "Virtual DMA Services (VDS), and is
|
|
available as Microsoft Part No. 098-10869. It is also
|
|
supported by Windows 3.0 Enhanced mode and 386MAX.
|
|
|
|
89 change "is still 138,000 bytes" to "are still 138,000 bytes"
|
|
|
|
90 It is useful to add one line of code to function walk(), so
|
|
that the ending address of the MCB chain (usually A000h) is
|
|
displayed. Change the case 'Z' block so that it reads:
|
|
|
|
case 'Z' : /* Zbikowski : end of MCB chain */
|
|
display(mcb);
|
|
printf("%04X\n", FP_SEG(mcb) + mcb->size + 1);
|
|
return;
|
|
|
|
98-9 Ralf Brown (ralf@cs.cmu.edu) points out that INT F0h or so
|
|
through FFh contain garbage which appears to pointing into
|
|
one of the currently loaded programs, because the BIOS uses
|
|
the upper end of the interrupt vector table as a stack
|
|
during bootup.
|
|
|
|
100 The assertion that the C free() function, or the Pascal
|
|
dispose() function, indirectly uses the DOS memory functions
|
|
isn't quite true. Calls like free() or dispose() don't call
|
|
INT 21h Function 49h (Release Memory Block); instead, they
|
|
simply put freed memory blocks back on a free list. To actually
|
|
release freed memory blocks back to the operating system, you
|
|
need to use a function like _heapmin() in Microsoft C 6.0 or
|
|
mark()/release() in Turbo Pascal.
|
|
|
|
102 "and will stop searching": Ralf Brown states that this is not
|
|
true. As it turns out, all three allocation strategies search
|
|
the entire memory chain. Any search sets all three memory-block
|
|
variables for all three strategies in the DOS SDA (at offsets
|
|
1Eh, 20h, and 22h; see UNDOCUDOS, pp. 551, 557), and then
|
|
returns the appropriate one.
|
|
|
|
111 "COMMAND.COM is always its own parent, and so..." Fine, but
|
|
we never explain _why_ COMMAND.COM is always its own
|
|
parent. Here's a good explanation from BIX:
|
|
|
|
ibm.dos/secrets.3 #1106, from drifkind, 1510 chars,
|
|
Fri Jan 25 20:26:19 1991
|
|
--------------------------
|
|
TITLE: Zombie COMMAND.COM rises from dead
|
|
|
|
(I'm practicing to write headlines for the National Enquirer.)
|
|
|
|
COMMAND.COM contains the default INT 24h handler, the one that
|
|
prints "Abort, Retry, Ignore" and so on. What happens when a
|
|
critical error occurs while executing an internal DOS command?
|
|
Obviously, the INT 24h handler knows that COMMAND.COM is running
|
|
and does something other than abort if you press "A", right?
|
|
|
|
No, in fact the critical error handler does nothing special. If
|
|
you press "A", it returns 2 and DOS terminates the current
|
|
process. So why doesn't COMMAND.COM go away?
|
|
|
|
When DOS terminates a process, it uses the "parent PID" field in
|
|
the process's PSP to figure out what process is going to get
|
|
control when this one terminates. If the parent PID is the same
|
|
as the current PID, however, it does not deallocate the program's
|
|
memory blocks before exiting. COMMAND.COM sets the parent PID
|
|
field equal to its own PID, and points the termination address
|
|
(at offset 0Ah in the PSP) back into itself. The result is that,
|
|
on exit, the current program stays active and retains control.
|
|
|
|
We all know that if you run COMMAND.COM with the "/p" switch, it
|
|
does not terminate when you type "exit". In fact, it DOES exit.
|
|
The difference is that, with "/p", it does not restore the
|
|
original parent PID and termination addresses, so DOS more or
|
|
less ignores the 4Ch service request, just transferring control
|
|
back into COMMAND.COM.
|
|
|
|
And that is why COMMAND.COM's parent PID field points to itself.
|
|
|
|
122-4 The DEVCON program has been ported to protected mode: see
|
|
Phar Lap 286|DOS-Extender _Developer's Guide_, pp. 159-165.
|
|
|
|
125 Change "if you run DEVCON a dash" to "if you run DEVCON with
|
|
a dash".
|
|
|
|
125 Change "it's often forgetten" to "it's often forgotten".
|
|
|
|
137 The block of code in while (CmdPkt.nunits--), before the
|
|
call to INT 21h AH=53h, should somewhere explain what we're
|
|
doing:
|
|
|
|
_ES = CmdPkt.brkseg; /* DS:SI -> BIOS Parameter Block */
|
|
_DS = CmdPkt.inpseg; /* ES:BP -> Disk Parameter Block */
|
|
|
|
139 "The function copyptr()... could have been written in C,
|
|
but doing so would have required the kind of convoluted
|
|
expressions that have given C the reputation of being a
|
|
'write-only' language." Well, I don't know, but the following
|
|
looks pretty simple to me:
|
|
|
|
typedef void far *FP;
|
|
void copyptr (FP far *src, FP far *dst) { *dst = *src; }
|
|
|
|
156 "Not long after that (but before the introduction of DOS
|
|
2.0), an extra sector was added to the format, bringing the
|
|
storage capacity up to the 360KB we know today." In fact,
|
|
Tim Paterson assures us that was done in DOS 2.0.
|
|
|
|
157 Re: the mentions of S=0, drifkin (BIX) points out that normal
|
|
PC block devices number physical sectors starting with 1,
|
|
not 0. Change 'em all to S=1.
|
|
|
|
158 Change "major reasons many users to upgrade" to "major reasons
|
|
for...".
|
|
|
|
158 "Two copies of the FAT are normally maintained by DOS, but
|
|
no real reason for doing so has been determined." This was
|
|
a pretty foolish remark, considering that Tim Paterson is
|
|
one of the coauthors, and we only needed to ask him why he
|
|
did this! The answer is that DOS maintains two copies of
|
|
the FAT is case of _physical_ disk errors; Microsoft used
|
|
three FATs in standalone BASIC, and this is where the
|
|
idea of using multiple FATs came from.
|
|
|
|
158 On the last line, the phrase "together with a flag bit
|
|
in the format records" is, as Peter Schultz (CIS 70216,074)
|
|
pointed out, rather vague. Jim Kyle explains that it really
|
|
isn't a "flag bit," but rather the top four bits in the
|
|
DPB highest-cluster word (DPB offset 0Dh; see UNDOCUDOS,
|
|
p. 507). Checking these bits is preferable to using the boot
|
|
record.
|
|
|
|
159 "Any other value indicates..." In fact, drifkind (BIX)
|
|
cautions us not to forget about (F)FF7, which marks bad
|
|
clusters, and (F)FF0 through (F)FF6, which are reserved.
|
|
|
|
160 "E5h, which is a valid character for use in a filename":
|
|
it's valid in DOS 3+.
|
|
|
|
160 "If the first byte of the filename is E5h..." Why E5h?
|
|
Tim Paterson says because 8" SSSD disks came preformatted
|
|
with E5h bytes. A disk out of the box thus looked empty,
|
|
and was essentially ready-to-go, though the FAT still
|
|
needed to be cleared (which was done with the built-in
|
|
CLEAR command in pre-IBM DOS).
|
|
|
|
161 FAKEFRMT: roedy (BIX) points out that this utility "will
|
|
inadvertently bring bad tracks back into active duty."
|
|
Tim Paterson states that FAKEFRMT shouldn't need to
|
|
rewrite the boot sector. These issues will be taken up
|
|
in the second edition.
|
|
|
|
161 "(Byte 2 of the sector for a 12-bit FAT)" should refer
|
|
instead to Byte 3. (Another catch by Ralf Brown.)
|
|
|
|
166 LoL+10h: The last sentence on the page asserts that "if
|
|
larger, this value is replaced by the new maximum value."
|
|
Ralf Brown, in yet another catch, points out that LoL+10h
|
|
is actually increased only for the built-in device drivers
|
|
located in IO.SYS; if the driver's value is greater than
|
|
LoL+10h for installable drivers, the loader complains that
|
|
the sector size is too large.
|
|
|
|
168 "(available directory in 4+" should read "(available directly
|
|
in 4+".
|
|
|
|
176 To the phrase "This means that all the named devices seem to
|
|
exist in all directories of the file system," add the
|
|
parenthetical remark that they also exist in subdirectory \DEV,
|
|
even if no such subdirectory exists on disk.
|
|
|
|
179 "it differed the data thatwas" should read "it differed from
|
|
the data that was" (two mistakes!; who the #$%*& edited this
|
|
stuff?!).
|
|
|
|
182 "Local Description Table" should of course read "Local Descriptor
|
|
Table".
|
|
|
|
186 "file stem" should read "file system".
|
|
|
|
190-1 The TRUENAME program has been ported to protected mode: see
|
|
Phar Lap 286|DOS-Extender _Developer's Guide_, pp. 86-92.
|
|
|
|
191 The variable "s" serves no purpose in main(), and in fact
|
|
could potentially cause a problem (ya see it?). Change main()
|
|
to the following:
|
|
|
|
main(int argc, char *argv[])
|
|
{
|
|
char buf[128];
|
|
if (argc < 2)
|
|
ret("usage: dospath <filename>", 1);
|
|
if (_osmajor < 3)
|
|
ret("requires DOS 3.0 or greater", 1);
|
|
|
|
if (truename(argv[1], buf))
|
|
ret(buf, 0);
|
|
else
|
|
ret("invalid filename", 1);
|
|
}
|
|
|
|
193 "The first SFT appears to always hold five possible open-file
|
|
entries": Ralf Brown explains that this is because the first
|
|
SFT is compiled right into MSDOS.SYS, for DOS 2.0 through 4.0.
|
|
|
|
197 Neil Rubenking (CIS 72241,50) found that struct file didn't
|
|
work under DOS 3.0. See corrections for p. 527 below for
|
|
the correct SFT structure for DOS 3.0.
|
|
|
|
198 In the function is_psp() the magic number 0x20CD is never
|
|
explained. This is merely the opcode for the INT 20h
|
|
instruction, interpreted as an unsigned quantity.
|
|
|
|
199 The test "FP int2e = (FP) GETVECT(0x2E)" will of course fail
|
|
if COMMAND.COM, or a program that mimicks COMMAND.COM's use
|
|
of INT 2Eh, is not present. For example, what happens when
|
|
the user is running SH.EXE from the MKS Toolkit as their
|
|
DOS SHELL=?
|
|
|
|
199 The IS_AUX(), IS_CON(), and IS_PRN() macros are all missing
|
|
a test for (s[3] == ' '). Otherwise, we would match possible
|
|
device names such as "AUXIL", "CONTOUR", and "PRNACHO".
|
|
|
|
210-3 "More File Handles": After UNDOCUDOS was already out, a
|
|
useful article on this topic appeared: David Burki, "DOS
|
|
File Handle Limits," _TECH Specialist_, February 1991,
|
|
pp. 51-62.
|
|
|
|
212 Rather than fail if (new_max > files()), it probably would
|
|
have been a good idea to show how to grow the SFT tables,
|
|
a la Quarterdeck's FILES.COM program (included with QEMM).
|
|
|
|
213 FHANDLE.C is an okay program, but it needs to show things
|
|
more from a C perspective. We at least need to explain why
|
|
we use _dos_open() and not open() or fopen(). We need to
|
|
explain why increasing the number of DOS file handles
|
|
doesn't give your C program more FILE* capacity.
|
|
This is an incredibly common question. For now, if
|
|
you do need to increase the number of FILE* in your Microsoft
|
|
C program, note that _NFILE can be changed in the startup
|
|
code (see MSC6 STARTUP\CRT0DAT.ASM).
|
|
|
|
214-5 Roger Jackson (CIS 76535,75) points out that MOV.C doesn't
|
|
compile with Microsoft C. The problem is that FP_SEG()
|
|
and FP_OFF() as used here depend on the Turbo C++ style of
|
|
these macros. As noted in UNDOCUDOS p. 51, Microsoft C's version
|
|
of these macros requires an lvalue. To fix MOV.C for MSC,
|
|
change the two blocks of FP_SEG/FP_OFF code:
|
|
|
|
void canonicalize(filespec,canonical,errorlevel)
|
|
// ...
|
|
void far *lvalue;
|
|
regs.h.ah = 0x60 ;
|
|
lvalue = filespec;
|
|
regs.x.si = FP_OFF(lvalue) ;
|
|
segregs.ds = FP_SEG(lvalue) ;
|
|
lvalue = canonical;
|
|
regs.x.di = FP_OFF(lvalue) ;
|
|
segregs.es = FP_SEG(lvalue) ;
|
|
// ...
|
|
|
|
void far *lvalue;
|
|
dpl.ax = 0x5600 ; /* indirect function is rename */
|
|
lvalue = &source;
|
|
dpl.dx = FP_OFF(lvalue) ;
|
|
dpl.ds = FP_SEG(lvalue) ; /* DS:DX old filespec */
|
|
lvalue = ⌖
|
|
dpl.di = FP_OFF(lvalue) ;
|
|
dpl.es = FP_SEG(lvalue) ; /* ES:DI new filespec */
|
|
|
|
229-30 Subfunctions 0Eh, 0Fh, 11h, 13h, and 17h: for each, add an
|
|
additonal required input:
|
|
|
|
SDA.CURR_CDS = Current Directory Structure (CDS) for
|
|
drive with file
|
|
|
|
The entries for these subfunctions are already correct in
|
|
the appendix to UNDOCUDOS, pp. 607-612.
|
|
|
|
257 The usage message should point out that PHANTOM -u uninstalls
|
|
the Phantom drive.
|
|
|
|
276-7 Tim Paterson pointed out that the "; Microsoft C 6.0 only"
|
|
comment next to MOV SP, BP (which appears once on each page)
|
|
is pretty confused. First of all, the MOV SP, BP must of
|
|
course be balanced with the earlier MOV BP, SP. Second of
|
|
all, this optional save/restore of the stack pointer has
|
|
to do merely with whether the compiler uses the stack or
|
|
a register for the variable i. This has nothing to do with
|
|
MSC 6.0. Thus, the comment should be removed.
|
|
|
|
277 "the large amount of space used for our three-line
|
|
interrupt handler should go unnoticed." This was supposed to
|
|
say "should _not_ go unnoticed." See S. Freud, _Psychopathology
|
|
of Everyday Life_, for further details.
|
|
|
|
280 "When coding in assembly language, you can easily come up
|
|
with this number..." Perhaps for .COM files, but certainly
|
|
for .EXE TSRs, E. Nicholas Cupery (CIS 72657,3646) points
|
|
out that the number is _not_ so easy to come up with!
|
|
In fact, our statement was a pure "exercise left for the
|
|
reader" cop-out (that is, we didn't know the answer either,
|
|
so we just pretended it was a trivial operation).
|
|
|
|
Ted Mirecki (CIS 72631,25; author of the wonderful
|
|
"Tech Notebook" series in the defunct _PC Tech Journal_),
|
|
responding to comments by Cupery and Mike R. Lovett
|
|
(CIS 72361,3715), made the following suggestion for deriving
|
|
the memory footprint of a multi-segment TSR:
|
|
|
|
"Instead of calculating the size of each segment & adding
|
|
the sizes together, calc the parag address of each segment end and
|
|
select the highest one. Then subtract the parag address of the PSP,
|
|
and voila, you have the number of parags taken up by your program.
|
|
|
|
"Say the label of the end of a particular segment is ENDLBL.
|
|
Then to get its parag address, do the following steps:
|
|
Add 15 to offset of ENDLBL (rounds up to next parag boundary)
|
|
Shift it right 4 bits (gets # of parags in segment)
|
|
Add to Segment of ENDLBL (gives parag addr of end of seg).
|
|
|
|
"You can either repeat this for all your segs & choose the highest,
|
|
or arrange for the segments to be loaded in some particular order
|
|
& do it only for the topmost one.
|
|
|
|
"Then get the PSP segment and subtract it from the above."
|
|
|
|
280 Jerry Watkins (CIS 70521,2401) points out the MSC memory map
|
|
isn't quite right. In DGROUP, the stack appears lower in
|
|
memory than the near heap. (In your copy of UNDOCUDOS, just
|
|
swap the two lines "STACK" and "NEAR HEAP".)
|
|
|
|
296 "need to examine is that one that" should read "need to examine
|
|
is the one that".
|
|
|
|
320 Several readers have noted that the discussion of the INT 2Ah
|
|
AH=8xh critical-section functions is skimpy. This will be
|
|
beefed-up either in a future "Release Notes" for the book,
|
|
or at least in the second edition. Jack Brennan
|
|
(John.Brennan@vi.ri.cmu.edu) made the following comments on
|
|
INT 2Ah Functions 80h/81h:
|
|
|
|
"These calls are only made by the DOS kernel if they are enabled
|
|
by some rather specialized code. All network redirectors that I
|
|
have seen enable the calls, as does Windows 3.0 in 386 Enhanced
|
|
mode (but not in Real or Standard mode). Basically, what needs to
|
|
be done is as follows:
|
|
|
|
"1. Find a table of offsets at location 02C3 in the IBMDOS segment.
|
|
This loc (02C3) is hard-coded into MSREDIR, the Microsoft Net
|
|
Redirector. The table is zero terminated.
|
|
|
|
"2. For each offset in the table, poke the hex value 50 into the byte
|
|
at IBMDOS:offset. This replaces a RET instruction with a PUSH AX
|
|
instruction, allowing the applicable subroutines to execute.
|
|
|
|
"I am only certain of this with DOS 3.1 to 3.31.
|
|
|
|
"I believe that this enabling code would need to be added to the
|
|
example TSR in Chapter 5 of Undocumented DOS (TSREXAMP.C) in order
|
|
to allow the TSR to be robust in a non-networked, non-Windows,
|
|
non-MSCDEX, etc., environment (assuming compiling with DOS_SWAP
|
|
enabled)."
|
|
|
|
322 The test at the bottom of the page, if (dos_level == 4), is
|
|
confusing, or will be when DOS 5 comes out. Note that at
|
|
the beginning of the function, we set:
|
|
|
|
else if (_osmajor >= 4)
|
|
dos_level == 4;
|
|
|
|
This is rather confusing. In any case, this code should work
|
|
with DOS 5.
|
|
|
|
325 In the paragraph at the bottom of the page, "(unless, of
|
|
course, a critical section has been flagged via INT 2Fh)"
|
|
should of course read "via INT 2Ah."
|
|
|
|
328 The subhead "TSFILE" should read "TSRFILE".
|
|
|
|
332 "which we can be used" should read "which can be used".
|
|
|
|
380 In TSHELL.C, it looks as if TSHELL passes an improperly
|
|
formatted argument list to COMMAND.COM, but Jim Kyle swears
|
|
up and down that the code works; it's too tricky, but it's
|
|
not wrong.
|
|
|
|
393 "find what is called the 'active' environment, _not_ the
|
|
master environment." Actually, it seems we made things more
|
|
difficult than necessary. In fact, the "active" environment
|
|
is most of the time the one you want. For example, ENVEDT,
|
|
which works off the master environment, doesn't change the
|
|
correct environment when running under Windows.
|
|
|
|
398 "For example, if a .BAT file containing a SET statement is
|
|
compiled with BAT2EXEC, it fails unexpectedly under this
|
|
situation producing an 'Out of environment space' message."
|
|
Doug Boling, author of BAT2EXEC (which appeared in _PC Magazine_,
|
|
August 1990), thinks we probably had an old copy of BAT2EXEC.
|
|
The latest versions of _PC Magazine_ utilities can be downloaded
|
|
from PCMagnet on CompuServe.
|
|
|
|
405 The discussion of INT 2Eh should note that the real "meat" on
|
|
this weird aspect of undocumented DOS can be found in Daniel
|
|
E. Greenberg, "Reentering the DOS Shell," _Programmer's
|
|
Journal_, May-June 1990, pp. 28-36. This article is the
|
|
definitive piece on INT 2Eh.
|
|
|
|
408 In the comments to TEST2E.C, the Microsoft C compilation
|
|
instructions should refer to SEND2E.C, not "send2e.asm".
|
|
|
|
409 We need to discuss the issue of running INT 2Eh "clients" like
|
|
TEST2E.EXE from within a batch file. Michael Mefford ("Running
|
|
Programs Painlessly," _PC Magazine_, 16 February 1988) claims
|
|
that programs using INT 2Eh "will not execute batch files nor
|
|
work from within a batch file." Jeff Prosise, in a good recent
|
|
article on undocumented DOS ("Undocumented DOS
|
|
Functions," _PC Magazine_, 12 February 1991) states: "Be careful
|
|
about how you call interrupt 2Eh. If you aren't, you can crash
|
|
your system in certain very common situations. The main one if
|
|
if the program you're using is running under a batch file.
|
|
Since INT 2Eh is nonreentrant, DOS uses it to run batch files.
|
|
So if you run a batch file using INT 2Eh from your program,
|
|
your system will crash."
|
|
|
|
We too have had problems running programs that use INT 2Eh
|
|
from within a batch file, but nothing so dramatic as crashing
|
|
the system. Instead, we have found simply that EXIT is not
|
|
handled properly and that memory can be lost. In any case,
|
|
the use (or refraining from use!) of INT 2Eh in a batch
|
|
file needs further discussion in the next edition of UNDOCUDOS.
|
|
|
|
449 "protected-mode debugging requires an interface more like
|
|
that of OS/2's DosPTrace()." At the time, this was just a wild
|
|
guess. It turned out to be true. If you have the Windows SDK,
|
|
run EXEHDR \WINDOWS\SYSTEM\WINDEBUG.DLL, and you'll find
|
|
the description "Ptrace for Windows." In fact, the undocumented
|
|
WinDebug() function is nearly identical to the poorly-
|
|
documented DosPTrace() function in OS/2. Microsoft claims
|
|
that WinDebug() will completely go away in Windows 3.1, to be
|
|
replaced by a openly-documented new interface. In the meantime,
|
|
and perhaps for some time even after 3.1 is released, WinDebug()
|
|
is quite important.
|
|
|
|
453 At the bottom of the page, the phrase "as noted below, INTRSPY
|
|
also uses -> to indicate fields in a structure" should be
|
|
deleted; it's not true. INTRSPY uses -> solely to indicate
|
|
that a register pair should be treated as a pointer to some
|
|
type. (For example, ds:dx->byte,asciiz,64.)
|
|
|
|
458 At the bottom of the page, "The following command used to
|
|
run CMDSPY.EXE" should refer instead to INTRSPY.EXE.
|
|
|
|
466 "or it parameters replaceable from the DOS command line":
|
|
delete the word "it."
|
|
|
|
482 "that function is not available provided by the Compaq ROM
|
|
BIOS": delete the word "available."
|
|
|
|
495 Dan Lanciani's name was unfortunately omitted from the list
|
|
of major contributors of undocumented-DOS material to the
|
|
Interrupt List maintained by Ralf Brown. Dan contributed
|
|
INT 21h and INT 2Fh material. Sorry, Dan.
|
|
|
|
502-4 History buffs may want to replace the vague note "appears to
|
|
be for CP/M compatibility" for Functions 18h, 1Dh, 1Eh, and 20h
|
|
with the actual names of the CP/M-80 and CP/M-86 functions for which
|
|
holes were apparently left:
|
|
18h: Get Bit Map of Active Drives
|
|
1Dh: Get Bit Map of Read-Only Drives
|
|
1Eh: Set File Attributes
|
|
20h: Get/Set User (Sublibrary) Number
|
|
(See David Cortesi, "CP/M-86 vs. MS-DOS: A Technical Comparison,"
|
|
_Dr. Dobb's Journal_, July 1982, pp. 14-27; in DDJ Vol. 7,
|
|
pp. 280-291. The article notes that "MSDOS was commissioned by
|
|
IBM and produced by the Microsoft Corporation from a base
|
|
written by Seattle Computer Products, Inc. Lifeboat Associates,
|
|
an important software vendor, has undertaken to market the
|
|
system for other 8086-based machines, and to encourage the
|
|
development of application programs for it." Gee, wonder if
|
|
it'll be successful....)
|
|
|
|
513 21/4B/03: Bob Moote of Phar Lap Software (rwm@pharlap.com) has
|
|
reported a bug in INT 21h Function 4Bh Subfunction 03h (Load
|
|
Overlay). If there is additional data located in the file after
|
|
the program (i.e., 21/4B/03 does not hit EOF; e.g., a program
|
|
with a bound-in DOS extender), Load Overlay will load up to 512
|
|
extra bytes, overflowing your buffer.
|
|
|
|
514 The note "DOS 2.x destroys all registers" should be amended to
|
|
note that in DOS 3+, the BX and DX registers are still bashed.
|
|
(This is noted correctly on p. 429 of UNDOCUDOS.)
|
|
|
|
527 Neil Rubenking found that the structure provided for the SFT
|
|
did not work in DOS 3.0. Robin Walker (RDHW@phoenix.cambridge.edu.uk)
|
|
supplied the correct information. Thanks, Robin!! Here 'tis:
|
|
|
|
Format of DOS 3.0 system file tables and FCB tables:
|
|
|
|
Offset Size Description
|
|
00h DWORD pointer to next file table
|
|
04h WORD number of files in this table
|
|
06h 38h bytes per file *** NB ***
|
|
|
|
Offset Size Description
|
|
|
|
00h-1Eh as for DOS 3.1+
|
|
|
|
1Fh WORD byte offset of directory entry within sector ** NB **
|
|
21h 11 BYTES filename in FCB format (no path/period, blank-padded)
|
|
2Ch DWORD (if SHARE loaded) ptr to prev SFT sharing same file
|
|
30h WORD (if SHARE loaded) ??? network machine number, I guess
|
|
32h WORD (if SHARE loaded) PSP segment of file's owner
|
|
34h WORD (if SHARE loaded) offs in SHARE code seg of share rec
|
|
36h WORD ??? only seen 0000h *** NB ***
|
|
|
|
The format of sharing records looks the same as already listed.
|
|
|
|
544-5 Has anyone found what 2F/57/02, 2F/57/03, and 2F/57/04 do?
|
|
A reader needs this information! If you know anything, please
|
|
contact andrew@pharlap.com.
|
|
|
|
581-2 2A/80, 2A/81: Please see the notes above (p.320).
|
|
|
|
597 2F/10/00: Note that DOS 4.01 loads share for media >32M, but
|
|
only for FCB support. The file-sharing code is not turned on
|
|
until the first call to 2F/10/00. Also note that Microsoft
|
|
Windows 3.0 enhanced mode provides its own built-in implementation
|
|
of SHARE.
|
|
|
|
;----------------------------------------------------------------------
|
|
|
|
/*
|
|
LDDPMI.C -- undocumented DOS call from DPMI
|
|
|
|
Revised substantially from the version in UNDOCUMENTED DOS, pp. 74-80
|
|
|
|
Works with Microsoft C 6.0 (or higher) and Borland C++ 2.0 (or higher)
|
|
(Some of the _asm convolutions were needed for Borland C++)
|
|
|
|
sample output:
|
|
in protected mode
|
|
Real mode DOS List Of Lists = 028E:0026
|
|
Protected DOS List Of Lists = 00AD:0026
|
|
LASTDRIVE=E
|
|
|
|
Microsoft C 6.0 (or higher): cl -AS lddpmi.c
|
|
Borland C++ 2.0 (or higher): bcc -ms lddpmi.c
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <dos.h>
|
|
|
|
#ifdef __TURBOC__
|
|
#pragma inline
|
|
#define _dos_allocmem(x,y) (allocmem(x, y) != -1)
|
|
#endif
|
|
|
|
#define ABSADDR(seg, ofs) \
|
|
((((unsigned long) seg) << 4) + ((ofs) & 0xFFFF))
|
|
|
|
#pragma pack(1)
|
|
|
|
typedef struct {
|
|
unsigned long edi, esi, ebp, reserved, ebx, edx, ecx, eax;
|
|
unsigned flags, es, ds, fs, gs, ip, cs, sp, ss;
|
|
} RMODE_CALL;
|
|
|
|
typedef struct {
|
|
unsigned char accessed : 1;
|
|
unsigned char read_write : 1;
|
|
unsigned char conf_exp : 1;
|
|
unsigned char code : 1;
|
|
unsigned char xsystem : 1;
|
|
unsigned char dpl : 2;
|
|
unsigned char present : 1;
|
|
} ACCESS;
|
|
|
|
/* structure of a protected-mode descriptor */
|
|
typedef struct {
|
|
unsigned limit, addr_lo;
|
|
unsigned char addr_hi;
|
|
ACCESS access;
|
|
unsigned char reserved, addr_xhi;
|
|
} DESCRIPTOR;
|
|
|
|
typedef enum { FALSE, TRUE } BOOL;
|
|
|
|
BOOL dpmi_rmode_intr(unsigned intno, unsigned flags,
|
|
unsigned copywords, RMODE_CALL far *rmode_call);
|
|
|
|
void dos_exit(unsigned char err)
|
|
{
|
|
_asm mov al, err
|
|
_asm mov ah, 04ch
|
|
_asm int 21h
|
|
}
|
|
|
|
void fail(char *s) { puts(s); dos_exit(1); }
|
|
|
|
/* Determines if DPMI is present and, if so, switches into
|
|
protected mode */
|
|
BOOL dpmi_init(void)
|
|
{
|
|
void (far *dpmi)();
|
|
unsigned hostdata_seg, hostdata_para, dpmi_flags;
|
|
|
|
_asm {
|
|
mov ax, 1687h // test for DPMI presence
|
|
int 2Fh
|
|
and ax, ax
|
|
jnz nodpmi // if (AX == 0) DPMI is present
|
|
mov dpmi_flags, bx
|
|
mov hostdata_para, si // paras for DPMI host private data
|
|
mov dpmi, di
|
|
mov dpmi+2, es // DPMI protected-mode switch entry point
|
|
jmp short gotdpmi
|
|
}
|
|
nodpmi:
|
|
return FALSE;
|
|
gotdpmi:
|
|
if (_dos_allocmem(hostdata_para, &hostdata_seg) != 0)
|
|
fail("can't allocate memory");
|
|
|
|
/* enter protected mode */
|
|
_asm {
|
|
mov ax, hostdata_seg
|
|
mov es, ax
|
|
mov ax, dpmi_flags
|
|
}
|
|
(*dpmi)();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Performs a real-mode interrupt from protected mode */
|
|
BOOL dpmi_rmode_intr(unsigned intno, unsigned flags,
|
|
unsigned copywords, RMODE_CALL far *rmode_call)
|
|
{
|
|
if (flags) intno |= 0x100;
|
|
_asm {
|
|
push di
|
|
push bx
|
|
push cx
|
|
mov ax, 0300h // simulate real-mode interrupt
|
|
mov bx, intno // interrupt number, flags
|
|
mov cx, copywords; // words to copy from pmode to rmode stack
|
|
les di, rmode_call // ES:DI = address of rmode call struct
|
|
int 31h // call DPMI
|
|
jc error
|
|
mov ax, 1 // return TRUE
|
|
jmp short done
|
|
}
|
|
error:
|
|
_asm mov ax, 0 // return FALSE
|
|
done:
|
|
_asm pop cx
|
|
_asm pop bx
|
|
_asm pop di
|
|
}
|
|
|
|
/* Allocates a single protected-mode LDT selector */
|
|
unsigned dpmi_sel(void)
|
|
{
|
|
_asm {
|
|
mov ax, 0 // Allocate LDT Descriptors
|
|
mov cx, 1 // allocate just one
|
|
int 31h // call DPMI
|
|
jc err
|
|
jmp short done // AX holds new LDT selector
|
|
}
|
|
err:
|
|
_asm mov ax, 0 // failed
|
|
done:;
|
|
}
|
|
|
|
BOOL dpmi_set_descriptor(unsigned pmodesel, DESCRIPTOR far *d)
|
|
{
|
|
_asm {
|
|
push di
|
|
push bx
|
|
mov ax, 000ch // Set Descriptor
|
|
mov bx, pmodesel // protected mode selector
|
|
les di, d // descriptor
|
|
int 31h // call DPMI
|
|
jc error
|
|
mov ax, 1 // return TRUE
|
|
jmp short done
|
|
}
|
|
error:
|
|
_asm mov ax, 0 // return FALSE
|
|
done:
|
|
_asm pop di
|
|
_asm pop bx
|
|
}
|
|
|
|
BOOL dpmi_get_descriptor(unsigned pmodesel, DESCRIPTOR far *d)
|
|
{
|
|
_asm {
|
|
push di
|
|
mov ax, 000bh // Get Descriptor
|
|
mov bx, word ptr pmodesel // protected mode selector
|
|
les di, dword ptr d // descriptor
|
|
int 31h // call DPMI
|
|
jc error
|
|
mov ax, 1 // return TRUE
|
|
jmp short done
|
|
}
|
|
error:
|
|
_asm xor ax, ax // return FALSE
|
|
done:
|
|
_asm pop di
|
|
}
|
|
|
|
BOOL dpmi_sel_free(unsigned pmodesel)
|
|
{
|
|
_asm {
|
|
mov ax, 0001h // Free LDT Descriptor
|
|
mov bx, pmodesel // selector to free
|
|
int 31h // call DPMI
|
|
jc error
|
|
mov ax, 1 // return TRUE
|
|
jmp short done
|
|
}
|
|
error:
|
|
_asm mov ax, 0 // return FALSE
|
|
done:;
|
|
}
|
|
|
|
void far *get_doslist(void)
|
|
{
|
|
_asm {
|
|
xor bx, bx
|
|
mov es, bx
|
|
mov ah, 52h
|
|
int 21h
|
|
mov dx, es
|
|
mov ax, bx
|
|
}
|
|
}
|
|
|
|
main()
|
|
{
|
|
DESCRIPTOR d;
|
|
RMODE_CALL r;
|
|
void far *fp;
|
|
char far *doslist = (char far *) 0;
|
|
unsigned long addr;
|
|
unsigned pmodesel;
|
|
unsigned offset, lastdrv_ofs, lastdrv;
|
|
|
|
/* program requires small model! */
|
|
assert((sizeof(void*) == 2) && (sizeof(void (*)()) == 2));
|
|
|
|
assert(sizeof(ACCESS) == 1);
|
|
assert(sizeof(DESCRIPTOR) == 8);
|
|
|
|
/* Determine if DPMI present and, if so, switch to protected mode */
|
|
if (dpmi_init())
|
|
puts("now in protected mode");
|
|
else
|
|
fail("DPMI not present");
|
|
|
|
/* Call INT 21h AH=52h (Get DOS List Of Lists) */
|
|
memset(&r, 0, sizeof(RMODE_CALL));
|
|
r.eax = 0x5200;
|
|
if (! dpmi_rmode_intr(0x21, 0, 0, &r))
|
|
fail("DPMI rmode intr failed");
|
|
FP_SEG(doslist) = r.es;
|
|
FP_OFF(doslist) = r.ebx;
|
|
printf("Real mode DOS List Of Lists = %Fp\r\n", doslist);
|
|
|
|
/* doslist now holds a real-mode address: in order to address it
|
|
in protected mode, allocate an LDT descriptor and set its
|
|
contents; when done, deallocate the LDT descriptor
|
|
*/
|
|
if (! (pmodesel = dpmi_sel()))
|
|
fail("DPMI can't alloc pmode selector");
|
|
|
|
/* set size of segment */
|
|
d.limit = 0xFFFF;
|
|
|
|
/* set base address of segment */
|
|
addr = ABSADDR(r.es, 0);
|
|
d.addr_lo = addr & 0xFFFF;
|
|
d.addr_hi = addr >> 16;
|
|
d.addr_xhi = 0; /* IMPORTANT! */
|
|
|
|
/* set access-rights of segment */
|
|
d.access.accessed = 0; /* never been used */
|
|
d.access.read_write = 1; /* read-write */
|
|
d.access.conf_exp = 0; /* not a stack */
|
|
d.access.code = 0; /* data */
|
|
d.access.xsystem = 1; /* not system descriptor */
|
|
fp = (void far *) main;
|
|
d.access.dpl = FP_SEG(fp) & 3; /* protection level */
|
|
d.access.present = 1; /* it's present in memory */
|
|
d.reserved = 0;
|
|
|
|
if (! dpmi_set_descriptor(pmodesel, &d))
|
|
fail("DPMI can't set descriptor");
|
|
|
|
FP_SEG(doslist) = pmodesel; /* convert to protected-mode address */
|
|
FP_OFF(doslist) = r.ebx;
|
|
printf("Protected mode DOS List Of Lists = %Fp\r\n", doslist);
|
|
|
|
/* now have protected-mode selector to DOS List of Lists */
|
|
/* Get LASTDRIVE number, print LASTDRIVE letter */
|
|
lastdrv = doslist[_osmajor==3 && _osminor==0 ? 0x1b : 0x21];
|
|
printf("LASTDRIVE=%c\r\n", 'A' - 1 + lastdrv);
|
|
|
|
if (! dpmi_sel_free(pmodesel))
|
|
fail("DPMI can't free selector");
|
|
|
|
/* in protected mode, flush output and quit */
|
|
fflush(stdout);
|
|
dos_exit(0);
|
|
|
|
dpmifail:
|
|
fail("DPMI failure");
|
|
}
|
|
|
|
;----------------------------------------------------------------------
|
|
|
|
THE END?
|
|
|
|
|