7

I'm trying to display error messages like a lot of MS-DOS (4.0+) utilities do. They don't embed messages like "Too many parameters" and "Invalid switch" in the executables, but rather look them up through an interface. So far, my research has led me to Int 2F, AX=122Eh. I've found some documentation on it in a couple of places: http://www.ctyme.com/intr/rb-4414.htm https://www.delorie.com/djgpp/doc/rbinter/id/30/44.html

As I read, it should return the address of a message table, and if the segment is 0001h, I can call it with DL=08h to get a retriever function. This all works well in MS-DOS 6.22. I can make the call to for the initial error type (e.g. 02h - Get parameter error table), then when I know its segment is 0001h, immediately call 08h and get a retriever function, which I can then build a stack for and far return into:

    mov ax, 122Eh
    mov dx, 0002h
    int 2Fh         ; Returns table address in ES:DI

    mov si, di      ; Store the offset as we'll need it for the retriever function

    mov dx, es
    cmp dx, 0001h
    jne parse_table ; Segment != 0001h => This is a data table, should start with FF, etc.

    mov dx, 0008h   ; Otherwise, we need to get the retriever function
    int 2Fh

    mov bx, offset return_here
    push cs         ; Build the return stack
    push bx
    push es
    push di

    mov ax, 0002h   ; Error code for "Required parameter missing"
    mov di, si

    retf
return_here:
    ; Display the error message (length-terminated string)

And it works well. I get the message in ES:DI and I can display it. The problem I have is with MS-DOS 4, and 4.01 specifically. When I make calls for error tables in MS-DOS 4.00, I get the addresses from my initial call (no need to call 08h). When I do the same for MS-DOS 4.01, for DL=00h and DL=02h, I get a phoney 0001 segment, but if I call DL=08h, nothing gets put in ES:DI. The address remains as whatever it was previously, which leads me to believe it's a dud method in DOS 4.

Under notes in the Int 2F, AH=122Eh documentation I've found, it says:

Notes: If the returned segment on a "get" is 0001h, then the offset specifies the offset of the error message table within COMMAND.COM, and the procedure returned by DL=08h should be called.

But the same Notes section goes on to talk about specifics of DOS 5, leading me to believe it might be a DOS 5+ thing.

I tried walking the PSP to get COMMAND.COM's segment myself and the result isn't quite right. For 02h, for instance, here's a dump of what I see initially (obviously incorrect):

Parameter error table:     0001:1536
1536    5D 23 F6 C3 21 75 62 F6 C3 10 74 5D 8A 45 04 1E ]#..!ub...t].E..
1546    57 0E 1F BF 4C 02 8B 5D 02 8B 3D 8E DB 83 FF FF W...L..]..=.....
1556    74 4A 38 45 04 75 EF 8B 5D 23 F6 C3 20 74 E7 80 tJ8E.u..]#.. t..
1566    F3 20 89 5D 23 5F 1F 33 DB 80 CB 20 09 5D 23 2E . .]#_.3... .]#.
1576    80 3E C7 01 01 74 22 2E 80 3E C4 01 02 75 17 1E .>...t"..>...u..
1586    57 50 8A 45 05 8A E0 33 FF 8E DF 86 06 04 05 3A WP.E...3.......:
1596    E0 58 5F 1F 74 03 E8 2F 11 5B 58 C3 F9 5F 1F EB .X_.t../.[X.._..
15A6    F8 B0 08 EB 02 B0 07 F9 C3 B0 0F E9 15 01 2E C6 ................
15B6    06 56 02 02 8B DF E8 2A FF 8A 45 10 2E A2 54 02 .V.....*..E...T.
15C6    E3 E6 F7 45 23 00 02 75 DC 2E 89 0E 58 02 2E 89 ...E#..u....X...
15D6    26 6A 02 8B C2 33 F6 03 D1 83 D6 00 83 7D 0E 00 &j...3.......}..
15E6    74 0C 83 FE 00 75 BA 3B 55 0E 77 B5 EB 11 2E 03 t....u.;U.w.....
15F6    36 8B 0A 3B 75 1D 72 07 77 A7 3B 55 1B 77 A2 2E 6..;u.r.w.;U.w..
1606    8B 16 8B 0A 03 45 17 13 55 19 2E A3 8D 0A 1E 33 .....E..U......3
1616    C0 8E D8 C5 36 78 00 2E 89 36 62 02 2E 8C 1E 64 ....6x...6b....d
1626    02 1F F7 45 23 01 00 75 09 E8 01 FF E8 53 12 E8 ...E#..u.....S..

And here's me trying to fix it by substituting COMMAND.COM's segment directly:

Parameter error table:     1060:1536
1536    53 65 63 74 6F 72 20 6E 6F 74 20 66 6F 75 6E 64 Sector not found
1546    1A 50 72 69 6E 74 65 72 20 6F 75 74 20 6F 66 20 .Printer out of 
1556    70 61 70 65 72 20 65 72 72 6F 72 11 57 72 69 74 paper error.Writ
1566    65 20 66 61 75 6C 74 20 65 72 72 6F 72 10 52 65 e fault error.Re
1576    61 64 20 66 61 75 6C 74 20 65 72 72 6F 72 0F 47 ad fault error.G
1586    65 6E 65 72 61 6C 20 66 61 69 6C 75 72 65 11 53 eneral failure.S
1596    68 61 72 69 6E 67 20 76 69 6F 6C 61 74 69 6F 6E haring violation
15A6    0E 4C 6F 63 6B 20 76 69 6F 6C 61 74 69 6F 6E 13 .Lock violation.
15B6    49 6E 76 61 6C 69 64 20 64 69 73 6B 20 63 68 61 Invalid disk cha
15C6    6E 67 65 0F 46 43 42 20 75 6E 61 76 61 69 6C 61 nge.FCB unavaila
15D6    62 6C 65 19 53 79 73 74 65 6D 20 72 65 73 6F 75 ble.System resou
15E6    72 63 65 20 65 78 68 61 75 73 74 65 64 12 43 6F rce exhausted.Co
15F6    64 65 20 70 61 67 65 20 6D 69 73 6D 61 74 63 68 de page mismatch
1606    0C 4F 75 74 20 6F 66 20 69 6E 70 75 74 17 49 6E .Out of input.In
1616    73 75 66 66 69 63 69 65 6E 74 20 64 69 73 6B 20 sufficient disk 
1626    73 70 61 63 65 0E 07 8D 3E 50 14 81 C1 E1 01 C3 space...>P......

I get dropped midway into a string table.

Worse, with DL=00h, I get:

Standard error table:      1060:1665
1665    41 4E 44 2E 43 4F 4D 00 50 52 4F 4D 50 54 3D 24 AND.COM.PROMPT=$
1675    70 24 67 00 00 75 69 72 65 64 20 70 61 72 61 6D p$g..uired param
1685    65 74 65 72 20 6D 69 73 73 69 6E 67 0E 49 6E 76 eter missing.Inv
1695    61 6C 69 64 20 73 77 69 74 63 68 0F 49 6E 76 61 alid switch.Inva
16A5    6C 69 64 20 6B 65 79 77 6F 72 64 24 50 61 72 61 lid keywordPara
16B5    6D 65 74 65 72 20 76 61 6C 75 65 20 6E 6F 74 20 meter value not 
16C5    69 6E 20 61 6C 6C 6F 77 65 64 20 72 61 6E 67 65 in allowed range
16D5    1B 50 61 72 61 6D 65 74 65 72 20 76 61 6C 75 65 .Parameter value
16E5    20 6E 6F 74 20 61 6C 6C 6F 77 65 5A D0 11 F0 8D  not alloweZ....
16F5    61 6D 65 54 50 30 30 00 61 6C 75 CD 20 C0 9F 00 ameTP00.alu. ...
1705    9A F0 FE 1D F0 47 01 60 10 56 01 60 10 BF 05 60 .....G.`.V.`...`
1715    10 60 10 01 03 01 00 02 FF FF FF FF FF FF FF FF .`..............
1725    FF FF FF FF FF FF FF C9 0E C4 FF D0 11 14 00 18 ................
1735    00 D0 11 FF FF FF FF 00 00 00 00 00 00 00 00 00 ................
1745    00 00 00 00 00 00 00 00 00 00 00 CD 21 CB 00 00 ............!...
1755    00 00 00 00 00 00 00 00 20 20 20 20 20 20 20 20 ........        

which looks like a message table that was resident, but is now being used for other things. And I know there's a /MSG switch for COMMAND.COM that might change this, but I need to focus on a standard environment.

I had a look at the MS-DOS 4 source code and it doesn't seem like they're doing anything greatly different to get their message tables, albeit everything seems to work for them:

MSGSERV.ASM

    XOR CX,CX                        ;;AN000;  Reset to zero
    MOV ES,CX                        ;;AN000;
    XOR DI,DI                        ;;AN000;
    MOV AX,DOS_GET_EXT_PARSE_ADD             ;;AN000;; 2FH Interface
    MOV DL,DOS_GET_EXTENDED              ;;AN000;; Where are the Extended errors in COMMAND.COM
    INT 2FH                      ;;AN000;; Private interface
    MOV WORD PTR $M_RT.$M_EXT_COMMAND+2,ES       ;;AN000;;  Move into first avaliable table location
    MOV WORD PTR $M_RT.$M_EXT_COMMAND,DI         ;;AN000;;

My question is: how can I correctly get an error message like "Too many parameters" from MS-DOS, specifically 4.01? And if my method is completely wrong, what interrupts / calls should I be using instead?

7
  • This is on my to do list to study further, as I want to make the lMS-DOS (my fork) utilities work without the message tables. Commented Aug 21 at 12:44
  • "When I make calls for error tables in MS-DOS 4.00, I get the addresses from my initial call (no need to call 08h). When I do the same for MS-DOS 4.01, for DL=00h and DL=02h, I get a phoney 0001 segment, but if I call DL=08h, nothing gets put in ES:DI. The address remains as whatever it was previously, which leads me to believe it's a dud method in DOS 4." Is this right? There's a difference between v4.00 and v4.01 ? Which binaries do you use? Commented Aug 21 at 12:53
  • This is the handler for int 2Fh function 122Eh in lMS-DOS: hg.pushbx.org/ecm/msdos4/file/c904365bc6ef/src/DOS/… The values are all filled from the shell it appears. Commented Aug 21 at 13:11
  • 2
    Yes, but to clarify: MS-DOS 4.00 gives me a direct pointer to the table for all table types (00h, 02h, 04h). MS-DOS 4.01 gives me a false segment (0001) for types 00h and 02h. In MS-DOS 4.01, type 04h gives me a table just list 4.00 does. The table format when it's returned is exactly as outlined on the reference pages in my question. The binaries I'm using are Microsoft MS-DOS 4.00 (10-6-1988) (5.25-360k) and Microsoft MS-DOS 4.01 (4-7-1989) (3.5-720k) from WinWorldPC. Commented Aug 21 at 13:13
  • 1
    The defaults for all five tables/pointers is all-zeroes: hg.pushbx.org/ecm/msdos4/file/c904365bc6ef/src/DOS/… Commented Aug 21 at 13:14

1 Answer 1

9

I'm a little embarrassed. It turns out, there was an issue with how I was calling function 08h that only manifested in DOS 4.00. Specifically, this section:

    mov ax, 122Eh
    mov dx, 0002h
    int 2Fh         ; Returns table address in ES:DI

    mov si, di      ; Store the offset as we'll need it for the retriever function

    mov dx, es
    cmp dx, 0001h
    jne parse_table ; Segment != 0001h => This is a data table, should start with FF, etc.

>>> mov ax, 122Eh <<<
    mov dx, 0008h   ; Otherwise, we need to get the retriever function
    int 2Fh

The statement I've highlighted with angle brackets is required. AX is overwritten by 122Eh in MS-DOS 4.00. After making this correction, I was able to fill out the rest of the code required. Below is a working, minimal example for anyone interested:

.8086
.model tiny
.code
org 100h

ERROR_NUMBER  EQU 02h
TABLE_NUMBER  EQU 02h

code:
    mov ax, 122Eh            ; Try to get the address of the requested error table
    mov dl, TABLE_NUMBER
    int 2Fh                  ; Returns table address in ES:DI

    mov ax, es               ; Check whether the segment is the sentinel value, 0001
    cmp ax, 0001h
    jne parse_table          ; If it's not, the table is in RAM and we can parse it

    mov si, di               ; Store DI for later

    mov ax, 122Eh            ; Get the address of the error message retriever function
    mov dl, 08h
    int 2Fh                  ; Returns retriever function address in ES:DI

        ; Set up the call stack so that we return to return_here
    mov bx, offset return_here
    push cs
    push bx
    push es
    push di

    mov  ax, ERROR_NUMBER
    mov  di, si          ; Set up AX and the DI parameters for the call
                             ; (copy the saved value in SI)

    retf                     ; Return into the far pointer
return_here:
    ; The error message is now available at ES:DI
    ; The first byte at ES:[DI] is the length of the string
    ; The string itself starts at ES:[DI+1]

    push es
    pop ds                   ; Set DS to the segment of the error message

do_print:
    xor cx, cx
    mov cl, [di]             ; Move the length of the string into CX

    mov dx, di               ; DX needs to point to the offset of the string
    inc dx                   ; Increment past the length
    mov ah, 40h              ; AH=40h - Write file or device
    mov bx, 01h              ; BX=1   - Handle for standard output
    int 21h                  ; Call DOS

exit_program:
    int 20h                  ; Exit without a return code

parse_table:
    push es
    pop ds                   ; Set DS to the segment of the error message
    add di, 4                ; First four bytes of the table should be: FF 04 00 xx (num of entries)
                             ; Skip over those uninteresting bytes
    mov ah, 30h              ; AH=30h - Get DOS version
    int 21h                  ; Call DOS
    cmp al, 5                ; Compare resulting major version (AL) to 5
    jae parse_table_dos_5

parse_table_dos_4:
    ; In DOS 4, the table is a series of two-word pairs: error number & size
    cmp byte ptr [di], ERROR_NUMBER
                             ; Look at the first byte for the error number
    je @f                    ; Exit the loop if we find it
    add di, 4                ; Add two words and keep going
    jmp parse_table_dos_4    ; XXX: if this table is bad, the program may hang, but this code is small
@@:
    mov ax, [di+2]           ; Skip one word past the index pointer and grab the offset stored there
    jmp return_to_print

parse_table_dos_5:
    mov bx, ERROR_NUMBER
    dec bl                   ; Move to the (n-1)th entry
    shl bl, 1                ; BX *= 2
    add di, bx               ; Add this to the index pointer
    mov ax, [di]             ; Grab what is at the index pointer

return_to_print:
    add di, ax               ; Add the offset to DI. DI now points to the string
    jmp do_print

end code

The table parsing stuff is VERY minimal. DOS 4 and 5 store their tables in different ways: DOS 4 has a sparse index and DOS 5 has dummy entries for missing values. If someone wanted to, he could rewrite this with a more robust parser, but this is just under 100 bytes when compiled.

The above example should print "Required parameter missing" in DOS 4/5/6.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.