Search code examples
assemblydos

Why does there appear to be an inconsistency with the DOS PSP's unformatted parameter area?


For both MS-DOS and IBM-DOS, the latter of which I am using, starting at address 80h, there is a reserved portion in memory for command line arguments passed along. Address 80h is a counter for how long this is, and address 81h is where the arguments in ASCII begin. The problem is that this region of memory seems to rarely actually work as intended.

When I load into debug.com with the parameter LOADME, the memory looks a bit like this.

-D 80 8F
1039:0080  00 0D 4C 4F 41 44 4D 45-0D 00 00 00 00 00 00 00  ..LOADME........

The inconsistency here is address 80H, which should contain a counter but evidently only reads out a 0 despite LOADME being there in its entirety. Things only become more inconsistent if I feed a larger parameter into the program. For comparison, here's how the parameter LOADME NOW looks.

-D 80 8F
1039:0080  04 20 4E 4F 57 0D 4D 45-20 4E 4F 57 0D 00 00 00  . NOW.ME NOW....

Now the counter reads 4, which is closer to being correct, but that's still not correct. Furthermore, LOAD has been dropped entirely from the parameter list. I've scrapped together two programs as well, one taken from StackOverflow and one of my own design. The StackOverflow program works flawlessly, able to output the correct input.

MOV AH, 40
MOV BX, 0001
XOR CH, CH
MOV CL, [0080]
MOV DX, 0081
INT 21 ; Write directly to device 1, with memory address 80h as your counter and your string beginning at 81
MOV AX, 4C00
INT 21 ; Exit

No matter what you feed this program, it will be able to output to the screen the exact data right back at you. Unfortunately, this is very limited in uses outside of making a copy of the echo command.

In turn, I've created this program which lets me copy the portion of memory at 81H and try to read it. This program does not work as intended, and mostly spits out gibberish data the moment you run it. It is possible that this due to a programming mistake since this is my first unguided attempt at a program, but it helps get my point across.

XOR AX, AX ; Clearing out the registers.
XOR BX, BX
XOR CX, CX
XOR DX, DX
MOV DX, 0300 ; A variable offset that I left unused
MOV AX, [0080] ; The counter
MOV BX, 0080 ; The current read position
ADD BX, 01; Begin loop
MOV CX, [BX]
ADD BX, 0300 ; Jump to our offset
MOV [BX], CX
SUB BX, 0300 ; Remove our offset in memory. 
DEC AX
JNZ 0111
ADD BX, 0301 ; Loop ended here.
MOV CX, 0024 ; Now, at the end of the string, add a $.
MOV [BX], CX
MOV AH, 09
MOV BX, 301
INT 21 ; Execute "display string"
MOV AX, 4C
INT 21 ; End program

It will certainly read where the parameter is, but ignoring the fact that it tends to overwrite where the argument is, it doesn't appear to do so consistently, flashing only snippets of what I expect the output to be before erasing them.

Is this really a problem with IBM-DOS, or is debug.com playing tricks on me and I'm a bad programmer?


Solution

  • Well, actually this works correctly because

    80h - Number of bytes on command-line

    81h–FFh - Command-line tail (terminated by a 0Dh)

    In first example 0Dh offset is 81h so 80h = 0

    In second example 0Dh offset is 85h so 80h = 4

    LOADME is not program name found in current directory.

    debug programname params


    Update 1:

    Debug LOADME

    Offset 81h looks like:

    (20h space)LOADME(0dh)

    Then system checks if LOADME is correct path to file. It is not so check if params exists? No, there is nothing more so 0dh is printed at 81h.

    (0dh)LOADME(0dh)


    Debug LOADME NOW

    Offset 81h looks like:

    (20h space)LOADME(20h space)NOW(0dh)

    LOADME is not correct path to file so print this at 81h = 0dh, now buffer looks like:

    (0dh)LOADME(20h space)NOW(0dh)

    and check next input which is (20h space)NOW(0dh). So override, starting from 81h.

    (20h space)NOW(0dh)ME(20h space)NOW(0dh)


    Update 2:

    I don't know what to add more. I can write things but if this will be Your answer You looking for?

    DOS can load and execute two types of files, com and exe. Executed program with its environment is called process. DOS can run one process at a time. Except of resident programs which are in memory. Process can call another process temporarily suspending its work. And then programs create hierarchy of parent-child processes. In most cases parent will be command.com. Offset 16h in PSP shows segment of parent process so if I run debug.exe parent process segment is 0b18 (command.com)

    C:>debug.exe
    -d 0
    1181:0000  CD 20 FF 9F 00 9A EE FE-1D F0 4F 03 E5 0B 8A 03
    1181:0010  E5 0B 17 03 E5 0B 18 0B-01 01 01 00 02 FF FF FF
    1181:0020  FF FF FF FF FF FF FF FF-FF FF FF FF 20 09 4E 01
    ...
    1181:0080  00 0D 63 6F 6D 6D 61 6E-64 2E 63 6F 6D 0D 65 79
    

    And if I check segment 0b18 indeed this is PSP of command.com. Parent process segment is 0b18 meaning that command.com has no parent process. Points to itself.

    0B18:0000  CD 20 FF 9F 00 9A F0 FE-1D F0 7E 01 18 0B 4A 01
    0B18:0010  18 0B 55 01 18 0B 18 0B-01 01 01 00 02 FF FF FF
    0B18:0020  FF FF FF FF FF FF FF FF-FF FF FF FF C3 0B F2 05
    ...
    1181:0080  00 0D 63 6F 6D 6D 61 6E-64 2E 63 6F 6D 0D 65 79
    
    And there is something called DTA (Disk transfer area)

    Source Wikipedia

    The initial address for the DTA was set to overlay the area in the PSP (at address 0x80) where the command line arguments were stored, such that a program needed to parse this area for command line arguments before invoking DOS functions that made use of the DTA (such as reading in a file record), unless the program took care to change the address of the DTA to some other memory region (or not use the DTA/FCB functions altogether, which soon became deprecated in favour of file handles).

    You can change this with ah=1ah int 21h and ask for DTA adress ah=2fh int 21h. Just what I found, never tested this functions.


    Update 3:

    If I run debug testcode.exe -a abc -3 memory dump looks like this:

    0BA0:0000  CD 20 FF 9F 00 9A F0 FE-1D F0 4F 03 9A 05 8A 03
    0BA0:0010  9A 05 17 03 9A 05 89 05-01 01 01 00 02 FF FF FF
    0BA0:0020  FF FF FF FF FF FF FF FF-FF FF FF FF 47 0B 4C 01
    .....
    0BA0:0080  0A 20 2D 61 20 61 62 63-20 2D 33 0D 78 65 20 2D  . -a abc -3.xe -
    

    This is testcode.exe, at offset 81h we can see exact list of parameters. Parent process is located here 05 89 and it is debug.exe At offset 81h are debug's parameters.

    0589:0000  CD 20 FF 9F 00 9A F0 FE-1D F0 DE 01 1E 04 4B 01
    0589:0010  1E 04 56 01 1E 04 1E 04-01 01 01 00 02 FF FF FF
    0589:0020  FF FF FF FF FF FF FF FF-FF FF FF FF 2F 05 B9 49
    ..... 0589:0080 17 20 74 65 73 74 63 6F-64 65 2E 65 78 65 20 2D . testcode.exe - 0589:0090 61 20 61 62 63 20 2D 33-0D 20 54 33 0D 74 2E 65 a abc -3. T3.t.e

    Parent process of debug is located here 04 1E and it is cmd.exe because I ran this under windows this time.

    041E:0000  CD 20 FF 9F 00 9A F0 FE-1D F0 7F 01 1E 04 4B 01
    041E:0010  1E 04 56 01 1E 04 1E 04-01 01 01 00 02 FF FF FF
    041E:0020  FF FF FF FF FF FF FF FF-FF FF FF FF C9 04 FA 05
    .....
    041E:0080  17 20 74 65 73 74 63 6F-64 65 2E 65 78 65 20 2D  . testcode.exe -
    041E:0090  61 20 61 62 63 20 2D 33-0D 20 54 33 0D 74 2E 65  a abc -3. T3.t.e