PSP

45 views
Skip to first unread message

muta...@gmail.com

unread,
Mar 28, 2021, 6:57:15 AMMar 28
to
Does anyone have the exact bytes in a MSDOS PSP as documented here:

INTERRUP.F:

Format of Program Segment Prefix (PSP):
Offset Size Description (Table 01378)
00h 2 BYTEs INT 20 instruction for CP/M CALL 0 program termination
the CDh 20h here is often used as a signature for a valid PSP
02h WORD segment of first byte beyond memory allocated to program
04h BYTE (DOS) unused filler
(OS/2) count of fake DOS version returns
05h BYTE CP/M CALL 5 service request (FAR CALL to absolute 000C0h)
BUG: (DOS 2+ DEBUG) PSPs created by DEBUG point at 000BEh
06h WORD CP/M compatibility--size of first segment for .COM files
08h 2 BYTEs remainder of FAR JMP at 05h

It's just offset x'05' and x'08' and x'09' that I am
interested in. How did they (allegedly) organize
a call to 0000:00C0? And they're claiming both a
CALL and a JMP. Which one is it?

Thanks. Paul.

JJ

unread,
Mar 28, 2021, 8:03:49 AMMar 28
to
Both. It's just a splitted FAR CALL instruction.
i.e. @05h= instruction, @06h= operand.

muta...@gmail.com

unread,
Mar 28, 2021, 10:10:53 PMMar 28
to
@06 on CP/M is the address of the BIOS in high
memory (within 64K) but that is sort of not
possible with MSDOS.

So @06 on MSDOS is instead the segment size
according to RBIL above.

It is @08 that has what is required for MSDOS.

I'm curious as to what exactly that is. A split
instruction that skips the word at @06? That's
a pretty strange split.

BFN. Paul.

JJ

unread,
Mar 29, 2021, 1:26:50 PMMar 29
to
>
>> Both. It's just a splitted FAR CALL instruction.
>> i.e. @05h= instruction, @06h= operand.
>
> @06 on CP/M is the address of the BIOS in high
> memory (within 64K) but that is sort of not
> possible with MSDOS.
>
> So @06 on MSDOS is instead the segment size
> according to RBIL above.

Oops. That should be @08h. Not @06h. Sorry.

> I'm curious as to what exactly that is. A split
> instruction that skips the word at @06? That's
> a pretty strange split.
>
> BFN. Paul.

Not sure, but I'm guessing that @05h and @08 were a later CP/M
implementation. i.e. @05 was initially used for different purpose or was
reserved.

muta...@gmail.com

unread,
Mar 30, 2021, 5:20:10 AMMar 30
to
On Tuesday, March 30, 2021 at 4:26:50 AM UTC+11, JJ wrote:

> >> Both. It's just a splitted FAR CALL instruction.
> >> i.e. @05h= instruction, @06h= operand.
> >
> > @06 on CP/M is the address of the BIOS in high
> > memory (within 64K) but that is sort of not
> > possible with MSDOS.
> >
> > So @06 on MSDOS is instead the segment size
> > according to RBIL above.

> Oops. That should be @08h. Not @06h. Sorry.

> > I'm curious as to what exactly that is. A split
> > instruction that skips the word at @06? That's
> > a pretty strange split.

> Not sure, but I'm guessing that @05h and @08 were a later CP/M
> implementation. i.e. @05 was initially used for different purpose or was
> reserved.

I assume that under CP/M the instruction was
3 bytes - @05, @06, @07 - which resulted in a
jump to the BIOS.

What I want to know is how MSDOS managed to
make @05, @08 and @09 a far call to 00C0. If
we can see a PSP from MSDOS for a .com file
it should have the answer. If we can disassemble
that, anyway.

BFN. Paul.

muta...@gmail.com

unread,
Mar 30, 2021, 3:47:54 PMMar 30
to
On Tuesday, March 30, 2021 at 8:20:10 PM UTC+11, muta...@gmail.com wrote:

> What I want to know is how MSDOS managed to
> make @05, @08 and @09 a far call to 00C0. If
> we can see a PSP from MSDOS for a .com file
> it should have the answer. If we can disassemble
> that, anyway.

I wrote this program:

C:\devel\develop>type dumppsp.c
/*********************************************************************/
/* */
/* This Program Written by Paul Edwards. */
/* Released to the Public Domain */
/* */
/*********************************************************************/
/*********************************************************************/
/* */
/* dumppsp - dump a PSP to psp.dat */
/* probably works best for a .com file */
/* */
/*********************************************************************/

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
FILE *fq;

fq = fopen("psp.dat", "wb");
if (fq == NULL)
{
printf("failed to open psp.dat\n");
return (EXIT_FAILURE);
}
fwrite(NULL, 1, 256, fq);
fclose(fq);
return (0);
}

And compiled it like this:

C:\devel\develop>wcl -mt dumppsp.c
Open Watcom C/C++16 Compile and Link Utility Version 1.6
Portions Copyright (c) 1988-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.
wcc dumppsp.c -ms
Open Watcom C16 Optimizing Compiler Version 1.6
Portions Copyright (c) 1984-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.
dumppsp.c: 30 lines, included 1349, 0 warnings, 0 errors
Code size: 65
wlink @__wcl__.lnk
Open Watcom Linker Version 1.6
Portions Copyright (c) 1985-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.
loading object files
searching libraries
creating a DOS .COM executable


And you can see it created a .com file automatically.


Running under MSDOS 5.0 gives this PSP:

C:\devel\bochs>hexdump psp.dat
000000 CD20C09F 009AF0FE 1DF0DC01 F70E4B01 . ............K.
000010 F70E5601 F70EF70E 01010100 0203FFFF ..V.............
000020 FFFFFFFF FFFFFFFF FFFFFFFF 0E10A837 ...............7
000030 24101400 18002410 FFFFFFFF 00000000 $.....$.........
000040 05000000 00000000 00000000 00000000 ................
000050 CD21CB00 00000000 00000000 00414243 .!...........ABC
000060 20202020 20202020 00000000 00444546 .....DEF
000070 20202020 20202020 00000000 00000000 ........
000080 0C204162 63204465 66204768 690D726F . Abc Def Ghi.ro
000090 6D0D0000 00000000 00000000 00000000 m...............
0000A0 00000000 00000000 00000000 00000000 ................
0000B0 00000000 00000000 00000000 00000000 ................
0000C0 00000000 00000000 00000000 00000000 ................
0000D0 00000000 00000000 00000000 00000000 ................
0000E0 00000000 00000000 00000000 00000000 ................
0000F0 00000000 00000000 00000000 00000000 ................


The "rom" you see mentioned there is leftover junk from
this in my autoexec.bat:

rem mscdex /d:cdrom


Here are all the PSP offsets from INTERRUP.F:

Format of Program Segment Prefix (PSP):
Offset Size Description (Table 01378)
00h 2 BYTEs INT 20 instruction for CP/M CALL 0 program termination
the CDh 20h here is often used as a signature for a valid PSP
02h WORD segment of first byte beyond memory allocated to program
04h BYTE (DOS) unused filler
(OS/2) count of fake DOS version returns
05h BYTE CP/M CALL 5 service request (FAR CALL to absolute 000C0h)
BUG: (DOS 2+ DEBUG) PSPs created by DEBUG point at 000BEh
06h WORD CP/M compatibility--size of first segment for .COM files
08h 2 BYTEs remainder of FAR JMP at 05h
0Ah DWORD stored INT 22 termination address
0Eh DWORD stored INT 23 control-Break handler address
12h DWORD DOS 1.1+ stored INT 24 critical error handler address
16h WORD segment of parent PSP
18h 20 BYTEs DOS 2+ Job File Table, one byte per file handle, FFh = closed
2Ch WORD DOS 2+ segment of environment for process (see #01379)
2Eh DWORD DOS 2+ process's SS:SP on entry to last INT 21 call
32h WORD DOS 3+ number of entries in JFT (default 20)
34h DWORD DOS 3+ pointer to JFT (default PSP:0018h)
38h DWORD DOS 3+ pointer to previous PSP (default FFFFFFFFh in 3.x)
used by SHARE in DOS 3.3
3Ch BYTE DOS 4+ (DBCS) interim console flag (see AX=6301h)
Novell DOS 7 DBCS interim flag as set with AX=6301h
(possibly also used by Far East MS-DOS 3.2-3.3)
3Dh BYTE (APPEND) TrueName flag (see INT 2F/AX=B711h)
3Eh BYTE (Novell NetWare) flag: next byte initialized if CEh
(OS/2) capabilities flag
3Fh BYTE (Novell NetWare) Novell task number if previous byte is CEh
40h 2 BYTEs DOS 5+ version to return on INT 21/AH=30h
42h WORD (MSWindows3) selector of next PSP (PDB) in linked list
Windows keeps a linked list of Windows programs only
44h WORD (MSWindows3) "PDB_Partition"
46h WORD (MSWindows3) "PDB_NextPDB"
48h BYTE (MSWindows3) bit 0 set if non-Windows application (WINOLDAP)
49h BYTE unused by DOS versions <= 6.00
4Ch WORD (MSWindows3) "PDB_EntryStack"
4Eh 2 BYTEs unused by DOS versions <= 6.00
50h 3 BYTEs DOS 2+ service request (INT 21/RETF instructions)
53h 2 BYTEs unused in DOS versions <= 6.00
55h 7 BYTEs unused in DOS versions <= 6.00; can be used to make first FCB
into an extended FCB
5Ch 16 BYTEs first default FCB, filled in from first commandline argument
overwrites second FCB if opened
6Ch 16 BYTEs second default FCB, filled in from second commandline argument
overwrites beginning of commandline if opened
7Ch 4 BYTEs unused
80h 128 BYTEs commandline / default DTA
command tail is BYTE for length of tail, N BYTEs for the tail,
followed by a BYTE containing 0Dh


It is interesting to see the first 2 of my command line
arguments being converted into FCBs.

Next, an attempt to disassemble!

BFN. Paul.

muta...@gmail.com

unread,
Mar 30, 2021, 3:50:09 PMMar 30
to
On Wednesday, March 31, 2021 at 6:47:54 AM UTC+11, muta...@gmail.com wrote:

> Running under MSDOS 5.0 gives this PSP:

Sorry. Forgot to mention I ran it as:

dumppsp Abc Def Ghi

BFN. Paul.

muta...@gmail.com

unread,
Mar 30, 2021, 3:57:41 PMMar 30
to
On Wednesday, March 31, 2021 at 6:47:54 AM UTC+11, muta...@gmail.com wrote:

> > What I want to know is how MSDOS managed to
> > make @05, @08 and @09 a far call to 00C0. If
> > we can see a PSP from MSDOS for a .com file
> > it should have the answer. If we can disassemble
> > that, anyway.

> 05h BYTE CP/M CALL 5 service request (FAR CALL to absolute 000C0h)
> 06h WORD CP/M compatibility--size of first segment for .COM files
> 08h 2 BYTEs remainder of FAR JMP at 05h

> 000000 CD20C09F 009AF0FE 1DF0DC01 F70E4B01 . ............K.

So:

05h = 9A
06/07 = FEF0
08h = 1D
09h = F0

So the 06/07 "segment size" of nearly 64k is basically
just maxing out the memory available to the .com
program.

But how does 9A + 1D + F0 translate into a call to 00C0?

Maybe it is relative to the load point. Let me see if I
can extract that.

BFN. Paul.

muta...@gmail.com

unread,
Mar 30, 2021, 4:14:01 PMMar 30
to
I couldn't get it to give up "cs", but I did get it to give up "ds":

char buf[10];

int main(void)
{
FILE *fq;

printf("loaded at %lx\n", (unsigned long)(char far *)buf);

A:\>dumppsp Abc Def Ghi
loaded at 10242f0e

And the PSP didn't change:
C:\devel\bochs>hexdump psp.dat
000000 CD20C09F 009AF0FE 1DF0DC01 F70E4B01 . ............K.

I can't see how to convert 1D/F0 and 1024 into 00C0.

BFN. Paul.

muta...@gmail.com

unread,
Apr 2, 2021, 1:06:07 AMApr 2
to
I tried seeing what wdis thought of those bytes, by doing this:

dosstart.asm:

nop
nop
nop
nop
nop

db 09ah
db 0f0h
db 0feh
db 01dh
db 0f0h

But somehow wdis knew those were db not instructions,
and showed them as db (also it knows whether I coded
"db 090h" instead of "nop").

So I changed them all to nop then ran this:

zap dosstart.obj 0x160 0x09a
zap dosstart.obj 0x161 0x0f0
zap dosstart.obj 0x162 0x0fe
zap dosstart.obj 0x163 0x01d
zap dosstart.obj 0x164 0x0f0

But still wdis didn't like it:

C:\devel\pdos\pdpclib>wdis dosstart.obj >temp.txt
Ran out of memory in during initial reading of the object file or bad object file record


Note that my object file was built like this:

wasm -q -DWATCOM dosstart.asm

BFN. Paul.

Marc 'BlackJack' Rintsch

unread,
May 3, 2021, 7:46:55 AMMay 3
to
On Tue, 30 Mar 2021 12:57:40 -0700, muta...@gmail.com wrote:

> On Wednesday, March 31, 2021 at 6:47:54 AM UTC+11, muta...@gmail.com wrote:
>
>> > What I want to know is how MSDOS managed to
>> > make @05, @08 and @09 a far call to 00C0. If
>> > we can see a PSP from MSDOS for a .com file
>> > it should have the answer. If we can disassemble
>> > that, anyway.
>
>> 05h BYTE CP/M CALL 5 service request (FAR CALL to absolute 000C0h)
>> 06h WORD CP/M compatibility--size of first segment for .COM files
>> 08h 2 BYTEs remainder of FAR JMP at 05h
>
>> 000000 CD20C09F 009AF0FE 1DF0DC01 F70E4B01 . ............K.
>
> So:
>
> 05h = 9A
> 06/07 = FEF0
> 08h = 1D
> 09h = F0
>
> So the 06/07 "segment size" of nearly 64k is basically
> just maxing out the memory available to the .com
> program.
>
> But how does 9A + 1D + F0 translate into a call to 00C0?

Let's fire up debug.exe and see what those bytes look like disassembled:

```
C:\>debug
-e 1000 9a f0 fe 1d f0
-u 1000 1004
07D7:1000 9AF0FE1DF0 CALL F01D:FEF0
```

So it is not a distributed instruction but all bytes together are the (far)
CALL. Does it lead to address 000c0h? Yes it does:

```
>>> hex((0xf01d << 4) + 0xfef0)
'0x1000c0'

>>> hex((0xf01d << 4) + 0xfef0 & 0xfffff)
'0xc0'
```

Apparently DOS puts the size at 06h/07h and calculates a segment for 08h/09h
that gives an effective address of 000c0h, taking into account that the number
”wraps” at 20 bits back to zero.

Ciao,
Marc 'BlackJack' Rintsch
--
“It is well known that a vital ingredient of success is
not knowing that what you're attempting can't be done.”
-- Terry Pratchett, Equal Rites

muta...@gmail.com

unread,
May 3, 2021, 8:38:46 AMMay 3
to
On Monday, May 3, 2021 at 9:46:55 PM UTC+10, Marc 'BlackJack' Rintsch wrote:

Thanks so much for resolving that mystery!

> >> 05h BYTE CP/M CALL 5 service request (FAR CALL to absolute 000C0h)
> >> 06h WORD CP/M compatibility--size of first segment for .COM files
> >> 08h 2 BYTEs remainder of FAR JMP at 05h

It's a FAR CALL, not a FAR JMP.

> > But how does 9A + 1D + F0 translate into a call to 00C0?
> Let's fire up debug.exe and see what those bytes look like disassembled:
>
> ```
> C:\>debug
> -e 1000 9a f0 fe 1d f0
> -u 1000 1004
> 07D7:1000 9AF0FE1DF0 CALL F01D:FEF0

Cool. I'd forgotten about debug, and forgotten how to use it too.

> So it is not a distributed instruction but all bytes together are the (far)
> CALL. Does it lead to address 000c0h? Yes it does:
>
> ```
> >>> hex((0xf01d << 4) + 0xfef0)
> '0x1000c0'
>
> >>> hex((0xf01d << 4) + 0xfef0 & 0xfffff)
> '0xc0'
> ```

Wonderful.

> Apparently DOS puts the size at 06h/07h and calculates a segment for 08h/09h
> that gives an effective address of 000c0h, taking into account that the number
> ”wraps” at 20 bits back to zero.

And that explains why so much care needed to be
given to disable the A20 line. I was wondering who
was coding stuff that relied on address wrap.

BFN. Paul.

muta...@gmail.com

unread,
May 4, 2021, 9:49:24 PMMay 4
to
On Monday, May 3, 2021 at 10:38:46 PM UTC+10, muta...@gmail.com wrote:

> > >>> hex((0xf01d << 4) + 0xfef0 & 0xfffff)
> > '0xc0'

INT 30H has some documentation of the above 0xc0 too:

http://www.ctyme.com/intr/int-30.htm

Note that 30H = decimal 48, and 48 * 4 = 192, the
position of INT 30H. 192 is 0xc0 as expected.

Also the code extends to the INT 31H position:

http://www.ctyme.com/intr/int-31.htm

BFN. Paul.

muta...@gmail.com

unread,
May 7, 2021, 9:11:40 PMMay 7
to
On Monday, May 3, 2021 at 10:38:46 PM UTC+10, muta...@gmail.com wrote:
> On Monday, May 3, 2021 at 9:46:55 PM UTC+10, Marc 'BlackJack' Rintsch wrote:

> > hex((0xf01d << 4) + 0xfef0)
> > '0x1000c0'

> > Apparently DOS puts the size at 06h/07h and calculates a segment for 08h/09h
> > that gives an effective address of 000c0h, taking into account that the number
> > ”wraps” at 20 bits back to zero.

> And that explains why so much care needed to be
> given to disable the A20 line. I was wondering who
> was coding stuff that relied on address wrap.

Actually, the A20 line doesn't need to be disabled to
solve this problem. What is needed is for MSDOS to
install a JMP to 00C0 at 0x1000C0.

BFN. Paul.
Reply all
Reply to author
Forward
0 new messages