20

The context is that I have additional data in the .exe file and wish to access it.

I know this is possible, as plenty of programs would store extra data after the loadable part of an MZ .exe — most 32-bit programs would do this; the 16-bit part would set up the DPMI environment and then load the bulk of the data from the rest of the file. But to do this, I need to know where the file is. I'd expect there to be some reference (either a file handle or an old-fashioned FCB) in the PSP, but there's nothing I can find. I have found a few suggestions that it's passed in in the environment somewhere, but can't find a definitive reference...

1
  • 7
    After the environment's double-NUL byte (which marks the end of the regular variables) there may be a word with the value 1. Following that word there will then be an ASCIZ string with the executed pathname. You will have to open this with 21.3D or 21.6C yourself. Also answered in bttr-software.de/forum/forum_entry.php?id=17398
    – ecm
    Commented Aug 8, 2022 at 22:47

2 Answers 2

16

[Edit: Ooops, that question already been answered a few years ago on Stack Overflow]

I'd expect there to be some reference (either a file handle or an old-fashioned FCB) in the PSP, but there's nothing I can find.

Almost. While there is no file handle or FCB to the program file, as it is already closed before execution starts, DOS (starting with 3.0) stores the program name as ASCIZ within the environment memory block, right after all environment variables.

To retrieve

  • Test for min DOS version 3.0
  • Get PSP address from DOS
  • Get the environment segment from offset 002Ch of the PSP
  • Advance to after the last environment variable
    • marked by two consecutive null bytes
  • There's a word with the number of strings following
  • If at least 1, the program name follows
  • Fetch the program name (a full ASCIZ path name)
  • Open that file
  • Do whatever you want to do :))

This works for .COM and .EXE programs - but only with DOS 3.0 and above.


In Assembly (COM or EXE) this may look a bit like:

    MOV   AH,30h         ; Get DOS version
    INT   21h
    JC    LOWDOS         ; Call failed -> DOS 1.x ?
    CMP   AL,3           ; At least DOS 3
    JAE   DOSOK          ; Yes -> continue
LOWDOS:  ; DOS version too low: terminate with exit code = 126
    MOV   AX,4C7Eh       ; Try to do it DOS 2 style
    INT   21h
    INT   20h            ; Just in case this is really DOS 1.x
DOSOK:
    MOV   AH,51h         ; Get active PSP
    INT   21h            ; Will never return an error
    MOV   ES,BX          ; Segment of PSP
    MOV   AX,ES:[2Ch]    ; Get Environment Segment from PSP
    MOV   ES,AX          ; to ES
    XOR   DI,DI          ; Point ES:DI at start of environment vars
    XOR   AX,AX          ; Search for 0-bytes
    CLD                  ; Upward
    MOV   CX,32767       ; Maximum size of an environment segment
SKIPVAR:
;Loop skipping all environment variables
    REPNE SCASB          ; Skip a string
    JCXZ  NFND           ; Searched max.length without finding a zero
    CMP   AL,BYTE ES:[DI] ; next byte as well Zero ?
    JNE   SKIPVAR        ; No -> Skip next var
    INC   DI             ; Skip over zero byte
    CMP   WORD ES:[DI],AX; String count not zero ?
    JE    NFND           ; No -> Not Found
    INC   DI             ; Skip string count
    INC   DI
FOUND:
; Full Program Name at ES:DI
; Do whatever is to be done
; like opening it via 3Dh:

    PUSH  DS
    MOV   AX,ES
    MOV   DS,AX
    MOV   DX,DI           ; Filename in DS:DX
    MOV   AL,<openmode>   ; Read/Write/etc...
    MOV   CL,0
    MOV   AH,3Dh          ; Open File
    INT   21h
    POP   DS
    JNC   FILE_OPENED
; Open Error: terminate with exit code = openerror code 
    MOV   AH,4Ch
    INT   21h
NFND:
; Name Not Found: terminate with exit code = 127
    MOV   AX,4C7Fh
    INT   21h

FILE_OPENED:
; Read or write from/to the program file
; File Handle in AX

    ...

    END

Rather easy, isn't it?

14
  • 5
    Actually, this "feature" was never documented anywhere, so it's not in the list of officially supported MSDOS-APIs - I can still confirm it works well with any DOSes starting from 3.3 to 6.0. While MSDOS is unlikely to change much during the next few years ( ;) ), your mileage using many of the non-MS DOSes (like DRDOS or OS/2) might vary.
    – tofro
    Commented Aug 9, 2022 at 8:33
  • 8
    DR DOS and OS/2 store the executable path in the same location after the environment variables. I think this was even documented in the DR DOS system programming manual. Commented Aug 9, 2022 at 10:57
  • 2
    Wow, that's awful. But then, this is DOS, so I'm not sure what I was expecting... I have, BTW, tracked down the DJGPP exe stub loader, which has a full production quality example: delorie.com/bin/cvsweb.cgi/djgpp/src/stub/stub.asm?rev=1.8 One fun gotcha is that the program may be run with no free file handles, making it impossible to open the exe unless you free some up first! Commented Aug 9, 2022 at 11:02
  • 1
    Your desire to keep it simpler was already in the mention ORG 100h that will produce a .COM program where all 4 segment registers will be equal to each other. Therefore reading from the PSP would never require any segment override prefix. I agree about trying to reduce assumptions, but in case of a .COM file the assumptions about CS and DS are already fixed. Not having the CS: override and removing the ORG 100h would even yield valid code for an .EXE program as then DS automatically points at the PSP totally different from CS.
    – Sep Roland
    Commented Dec 2, 2023 at 16:51
  • 1
    @SepRoland MS-DOS version 1 style error return is CF unchanged, al = 0. This is why function 71h subfunctions are said to return the code 7100h in ax when no LFN interface is installed, it is by default the CF unchanged al = 0 return. (Some handlers do now return CY al = 0 though, to harden things.)
    – ecm
    Commented Dec 2, 2023 at 20:57
14

If definitive references is what you’re looking for, the usual one to consult is the RBIL:

Format of Program Segment Prefix (PSP):
Offset  Size    Description     (Table 01378)
[…]
 2Ch    WORD    DOS 2+ segment of environment for process (see #01379)
[…]


Format of environment block:
Offset  Size    Description     (Table 01379)
 00h  N BYTEs   first environment variable, ASCIZ string of form "var=value"
      N BYTEs   second environment variable, ASCIZ string
        ...
      N BYTEs   last environment variable, ASCIZ string of form "var=value"
        BYTE    00h
---DOS 3.0+ ---
        WORD    number of strings following environment (normally 1)
      N BYTEs   ASCIZ full pathname of program owning this environment
                other strings may follow

As this is a pathname and not an already-open handle, you will have to open the file yourself, which is of course subject to the usual race conditions. (Not that anyone cares about such things when writing for DOS.)

2
  • What usual race conditions do you mean? What race conditions there could even be?
    – Justme
    Commented Aug 9, 2022 at 21:14
  • 7
    Someone could've deleted or renamed or replaced the program file between the time when your program was launched and the time when you (re)open the file. Not a major concern on a single-user OS like MS-DOS, but there have been many security issues caused by such race conditions on multi-user systems, like the famous Unix setuid script vulnerability. Commented Aug 9, 2022 at 21:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .