mirror of
https://github.com/reactos/reactos.git
synced 2024-08-26 05:08:39 +00:00
982 lines
32 KiB
C
982 lines
32 KiB
C
/*
|
|
* COPYRIGHT: GPL - See COPYING in the top level directory
|
|
* PROJECT: ReactOS Virtual DOS Machine
|
|
* FILE: subsystems/mvdm/ntvdm/dos/dos32krnl/process.c
|
|
* PURPOSE: DOS32 Processes
|
|
* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
|
|
* Hermes Belusca-Maito (hermes.belusca@sfr.fr)
|
|
*/
|
|
|
|
/* INCLUDES *******************************************************************/
|
|
|
|
#include "ntvdm.h"
|
|
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
#include "emulator.h"
|
|
#include "cpu/cpu.h"
|
|
|
|
#include "dos.h"
|
|
#include "dos/dem.h"
|
|
#include "dosfiles.h"
|
|
#include "handle.h"
|
|
#include "process.h"
|
|
#include "memory.h"
|
|
|
|
#include "bios/bios.h"
|
|
|
|
#include "io.h"
|
|
#include "hardware/ps2.h"
|
|
|
|
#include "vddsup.h"
|
|
|
|
/* PRIVATE FUNCTIONS **********************************************************/
|
|
|
|
static VOID DosInitPsp(IN WORD Segment,
|
|
IN WORD EnvBlock,
|
|
IN LPCSTR CommandLine,
|
|
IN LPCSTR ProgramName)
|
|
{
|
|
PDOS_PSP PspBlock = SEGMENT_TO_PSP(Segment);
|
|
PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
|
|
LPCSTR PspName;
|
|
USHORT i;
|
|
|
|
/* Link the environment block */
|
|
PspBlock->EnvBlock = EnvBlock;
|
|
|
|
/*
|
|
* Copy the command line.
|
|
* Format of the CommandLine parameter: 1 byte for size; 127 bytes for contents.
|
|
*/
|
|
PspBlock->CommandLineSize = min(*(PBYTE)CommandLine, DOS_CMDLINE_LENGTH);
|
|
CommandLine++;
|
|
RtlCopyMemory(PspBlock->CommandLine, CommandLine, DOS_CMDLINE_LENGTH);
|
|
|
|
/*
|
|
* Initialize the owner name of the MCB of the PSP.
|
|
*/
|
|
|
|
/* Find the start of the file name, skipping all the path elements */
|
|
PspName = ProgramName;
|
|
while (*ProgramName)
|
|
{
|
|
switch (*ProgramName++)
|
|
{
|
|
/* Path delimiter, skip it */
|
|
case ':': case '\\': case '/':
|
|
PspName = ProgramName;
|
|
break;
|
|
}
|
|
}
|
|
/* Copy the file name up to the extension... */
|
|
for (i = 0; i < sizeof(Mcb->Name) && PspName[i] != '.' && PspName[i] != '\0'; ++i)
|
|
{
|
|
Mcb->Name[i] = RtlUpperChar(PspName[i]);
|
|
}
|
|
/* ... and NULL-terminate if needed */
|
|
if (i < sizeof(Mcb->Name)) Mcb->Name[i] = '\0';
|
|
|
|
// FIXME: Initialize the FCBs
|
|
}
|
|
|
|
static inline VOID DosSaveState(VOID)
|
|
{
|
|
PDOS_REGISTER_STATE State;
|
|
WORD StackPointer = getSP();
|
|
|
|
DPRINT1("\n"
|
|
"DosSaveState(before) -- SS:SP == %04X:%04X\n"
|
|
"Original CPU State =\n"
|
|
"DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
|
|
"DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
|
|
"\n",
|
|
getSS(), getSP(),
|
|
getDS(), getES(), getAX(), getCX(),
|
|
getDX(), getBX(), getBP(), getSI(), getDI());
|
|
|
|
/*
|
|
* Allocate stack space for the registers. Note that we
|
|
* already have one word allocated (the interrupt number).
|
|
*/
|
|
StackPointer -= sizeof(DOS_REGISTER_STATE) - sizeof(WORD);
|
|
State = SEG_OFF_TO_PTR(getSS(), StackPointer);
|
|
setSP(StackPointer);
|
|
|
|
/* Save */
|
|
State->DS = getDS();
|
|
State->ES = getES();
|
|
State->AX = getAX();
|
|
State->CX = getCX();
|
|
State->DX = getDX();
|
|
State->BX = getBX();
|
|
State->BP = getBP();
|
|
State->SI = getSI();
|
|
State->DI = getDI();
|
|
|
|
DPRINT1("\n"
|
|
"DosSaveState(after) -- SS:SP == %04X:%04X\n"
|
|
"Saved State =\n"
|
|
"DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
|
|
"DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
|
|
"\n",
|
|
getSS(), getSP(),
|
|
State->DS, State->ES, State->AX, State->CX,
|
|
State->DX, State->BX, State->BP, State->SI, State->DI);
|
|
}
|
|
|
|
static inline VOID DosRestoreState(VOID)
|
|
{
|
|
PDOS_REGISTER_STATE State;
|
|
|
|
/*
|
|
* Pop the state structure from the stack. Note that we
|
|
* already have one word allocated (the interrupt number).
|
|
*/
|
|
State = SEG_OFF_TO_PTR(getSS(), getSP());
|
|
|
|
DPRINT1("\n"
|
|
"DosRestoreState(before) -- SS:SP == %04X:%04X\n"
|
|
"Saved State =\n"
|
|
"DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
|
|
"DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
|
|
"\n",
|
|
getSS(), getSP(),
|
|
State->DS, State->ES, State->AX, State->CX,
|
|
State->DX, State->BX, State->BP, State->SI, State->DI);
|
|
|
|
setSP(getSP() + sizeof(DOS_REGISTER_STATE) - sizeof(WORD));
|
|
|
|
/* Restore */
|
|
setDS(State->DS);
|
|
setES(State->ES);
|
|
setAX(State->AX);
|
|
setCX(State->CX);
|
|
setDX(State->DX);
|
|
setBX(State->BX);
|
|
setBP(State->BP);
|
|
setSI(State->SI);
|
|
setDI(State->DI);
|
|
|
|
DPRINT1("\n"
|
|
"DosRestoreState(after) -- SS:SP == %04X:%04X\n"
|
|
"Restored CPU State =\n"
|
|
"DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
|
|
"DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
|
|
"\n",
|
|
getSS(), getSP(),
|
|
getDS(), getES(), getAX(), getCX(),
|
|
getDX(), getBX(), getBP(), getSI(), getDI());
|
|
}
|
|
|
|
static WORD DosCopyEnvironmentBlock(IN LPCSTR Environment OPTIONAL,
|
|
IN LPCSTR ProgramName)
|
|
{
|
|
PCHAR Ptr, DestBuffer = NULL;
|
|
SIZE_T TotalSize = 0;
|
|
WORD DestSegment;
|
|
|
|
/* If we have an environment strings list, compute its size */
|
|
if (Environment)
|
|
{
|
|
/* Calculate the size of the environment block */
|
|
Ptr = (PCHAR)Environment;
|
|
while (*Ptr) Ptr += strlen(Ptr) + 1;
|
|
TotalSize = (ULONG_PTR)Ptr - (ULONG_PTR)Environment;
|
|
}
|
|
else
|
|
{
|
|
/* Empty environment string */
|
|
TotalSize = 1;
|
|
}
|
|
/* Add the final environment block NULL-terminator */
|
|
TotalSize++;
|
|
|
|
/* Add the two bytes for the program name tag */
|
|
TotalSize += 2;
|
|
|
|
/* Add the string buffer size */
|
|
TotalSize += strlen(ProgramName) + 1;
|
|
|
|
/* Allocate the memory for the environment block */
|
|
DestSegment = DosAllocateMemory((WORD)((TotalSize + 0x0F) >> 4), NULL);
|
|
if (!DestSegment) return 0;
|
|
|
|
DestBuffer = (PCHAR)SEG_OFF_TO_PTR(DestSegment, 0);
|
|
|
|
/* If we have an environment strings list, copy it */
|
|
if (Environment)
|
|
{
|
|
Ptr = (PCHAR)Environment;
|
|
while (*Ptr)
|
|
{
|
|
/* Copy the string and NULL-terminate it */
|
|
strcpy(DestBuffer, Ptr);
|
|
DestBuffer += strlen(Ptr);
|
|
*(DestBuffer++) = '\0';
|
|
|
|
/* Move to the next string */
|
|
Ptr += strlen(Ptr) + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Empty environment string */
|
|
*(DestBuffer++) = '\0';
|
|
}
|
|
/* NULL-terminate the environment block */
|
|
*(DestBuffer++) = '\0';
|
|
|
|
/* Store the special program name tag */
|
|
*(DestBuffer++) = LOBYTE(DOS_PROGRAM_NAME_TAG);
|
|
*(DestBuffer++) = HIBYTE(DOS_PROGRAM_NAME_TAG);
|
|
|
|
/* Copy the program name after the environment block */
|
|
strcpy(DestBuffer, ProgramName);
|
|
|
|
return DestSegment;
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS ***********************************************************/
|
|
|
|
VOID DosClonePsp(WORD DestSegment, WORD SourceSegment)
|
|
{
|
|
PDOS_PSP DestPsp = SEGMENT_TO_PSP(DestSegment);
|
|
PDOS_PSP SourcePsp = SEGMENT_TO_PSP(SourceSegment);
|
|
LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
|
|
|
|
/* Literally copy the PSP first */
|
|
RtlCopyMemory(DestPsp, SourcePsp, sizeof(*DestPsp));
|
|
|
|
/* Save the interrupt vectors */
|
|
DestPsp->TerminateAddress = IntVecTable[0x22];
|
|
DestPsp->BreakAddress = IntVecTable[0x23];
|
|
DestPsp->CriticalAddress = IntVecTable[0x24];
|
|
|
|
/* No parent PSP */
|
|
DestPsp->ParentPsp = 0;
|
|
|
|
/* Set the handle table pointers to the internal handle table */
|
|
DestPsp->HandleTableSize = DEFAULT_JFT_SIZE;
|
|
DestPsp->HandleTablePtr = MAKELONG(0x18, DestSegment);
|
|
|
|
/* Copy the parent handle table without referencing the SFT */
|
|
RtlCopyMemory(FAR_POINTER(DestPsp->HandleTablePtr),
|
|
FAR_POINTER(SourcePsp->HandleTablePtr),
|
|
DEFAULT_JFT_SIZE);
|
|
}
|
|
|
|
VOID DosCreatePsp(WORD Segment, WORD ProgramSize)
|
|
{
|
|
PDOS_PSP PspBlock = SEGMENT_TO_PSP(Segment);
|
|
LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
|
|
|
|
RtlZeroMemory(PspBlock, sizeof(*PspBlock));
|
|
|
|
/* Set the exit interrupt */
|
|
PspBlock->Exit[0] = 0xCD; // int 0x20
|
|
PspBlock->Exit[1] = 0x20;
|
|
|
|
/* Set the number of the last paragraph */
|
|
PspBlock->LastParagraph = Segment + ProgramSize;
|
|
|
|
/* Save the interrupt vectors */
|
|
PspBlock->TerminateAddress = IntVecTable[0x22];
|
|
PspBlock->BreakAddress = IntVecTable[0x23];
|
|
PspBlock->CriticalAddress = IntVecTable[0x24];
|
|
|
|
/* Set the parent PSP */
|
|
PspBlock->ParentPsp = Sda->CurrentPsp;
|
|
|
|
if (Sda->CurrentPsp != SYSTEM_PSP)
|
|
{
|
|
/* Link to the parent's environment block */
|
|
PspBlock->EnvBlock = SEGMENT_TO_PSP(Sda->CurrentPsp)->EnvBlock;
|
|
}
|
|
/*
|
|
else
|
|
{
|
|
PspBlock->EnvBlock = SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0);
|
|
}
|
|
*/
|
|
|
|
/* Copy the parent handle table */
|
|
DosCopyHandleTable(PspBlock->HandleTable);
|
|
|
|
/* Set the handle table pointers to the internal handle table */
|
|
PspBlock->HandleTableSize = DEFAULT_JFT_SIZE;
|
|
PspBlock->HandleTablePtr = MAKELONG(0x18, Segment);
|
|
|
|
/* Set the DOS version */
|
|
// FIXME: This is here that SETVER stuff enters into action!
|
|
PspBlock->DosVersion = DosData->DosVersion;
|
|
|
|
/* Set the far call opcodes */
|
|
PspBlock->FarCall[0] = 0xCD; // int 0x21
|
|
PspBlock->FarCall[1] = 0x21;
|
|
PspBlock->FarCall[2] = 0xCB; // retf
|
|
}
|
|
|
|
VOID DosSetProcessContext(WORD Segment)
|
|
{
|
|
Sda->CurrentPsp = Segment;
|
|
Sda->DiskTransferArea = MAKELONG(0x80, Segment);
|
|
}
|
|
|
|
DWORD DosLoadExecutableInternal(IN DOS_EXEC_TYPE LoadType,
|
|
IN LPBYTE ExeBuffer,
|
|
IN DWORD ExeBufferSize,
|
|
IN LPCSTR ExePath,
|
|
IN PDOS_EXEC_PARAM_BLOCK Parameters,
|
|
IN LPCSTR CommandLine OPTIONAL,
|
|
IN LPCSTR Environment OPTIONAL,
|
|
IN DWORD ReturnAddress OPTIONAL)
|
|
{
|
|
DWORD Result = ERROR_SUCCESS;
|
|
WORD Segment = 0;
|
|
WORD EnvBlock = 0;
|
|
WORD ExeSignature;
|
|
WORD LoadSegment;
|
|
WORD MaxAllocSize;
|
|
|
|
WORD FinalSS, FinalSP;
|
|
WORD FinalCS, FinalIP;
|
|
|
|
/* Buffer for command line conversion: 1 byte for size; 127 bytes for contents */
|
|
CHAR CmdLineBuffer[1 + DOS_CMDLINE_LENGTH];
|
|
|
|
DPRINT1("DosLoadExecutableInternal(%d, 0x%p, '%s', 0x%p, 0x%p, 0x%p)\n",
|
|
LoadType, ExeBuffer, ExePath, Parameters, CommandLine, Environment);
|
|
|
|
if (LoadType != DOS_LOAD_OVERLAY)
|
|
{
|
|
/* If an optional Win32 command line is given... */
|
|
if (CommandLine)
|
|
{
|
|
/* ... convert it into DOS format */
|
|
BYTE CmdLineLen;
|
|
|
|
PBYTE CmdLineSize = (PBYTE)CmdLineBuffer;
|
|
LPSTR CmdLineStart = CmdLineBuffer + 1;
|
|
LPSTR CmdLinePtr = CmdLineStart;
|
|
|
|
// For debugging purposes
|
|
RtlFillMemory(CmdLineBuffer, sizeof(CmdLineBuffer), 0xFF);
|
|
|
|
/*
|
|
* Set the command line: it is either an empty command line or has
|
|
* the format: " foo bar ..." (with at least one leading whitespace),
|
|
* and is then always followed by '\r' (and optionally by '\n').
|
|
*/
|
|
CmdLineLen = (BYTE)strlen(CommandLine);
|
|
*CmdLineSize = 0;
|
|
|
|
/*
|
|
* Add the leading space if the command line is not empty
|
|
* and doesn't already start with some whitespace...
|
|
*/
|
|
if (*CommandLine && *CommandLine != '\r' && *CommandLine != '\n' &&
|
|
*CommandLine != ' ' && *CommandLine != '\t')
|
|
{
|
|
(*CmdLineSize)++;
|
|
*CmdLinePtr++ = ' ';
|
|
}
|
|
|
|
/* Compute the number of characters we need to copy from the original command line */
|
|
CmdLineLen = min(CmdLineLen, DOS_CMDLINE_LENGTH - *CmdLineSize);
|
|
|
|
/* The trailing '\r' or '\n' do not count in the PSP command line size parameter */
|
|
while (CmdLineLen && (CommandLine[CmdLineLen - 1] == '\r' || CommandLine[CmdLineLen - 1] == '\n'))
|
|
{
|
|
CmdLineLen--;
|
|
}
|
|
|
|
/* Finally, set everything up */
|
|
*CmdLineSize += CmdLineLen;
|
|
RtlCopyMemory(CmdLinePtr, CommandLine, CmdLineLen);
|
|
CmdLineStart[*CmdLineSize] = '\r';
|
|
|
|
/* Finally make the pointer point to the static buffer */
|
|
CommandLine = CmdLineBuffer;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* ... otherwise, get the one from the parameter block.
|
|
* Format of the command line: 1 byte for size; 127 bytes for contents.
|
|
*/
|
|
ASSERT(Parameters);
|
|
CommandLine = (LPCSTR)FAR_POINTER(Parameters->CommandLine);
|
|
}
|
|
|
|
/* If no optional environment is given... */
|
|
if (Environment == NULL)
|
|
{
|
|
ASSERT(Parameters);
|
|
/* ... get the one from the parameter block (if not NULL)... */
|
|
if (Parameters->Environment)
|
|
Environment = (LPCSTR)SEG_OFF_TO_PTR(Parameters->Environment, 0);
|
|
/* ... or the one from the parent (otherwise) */
|
|
else
|
|
Environment = (LPCSTR)SEG_OFF_TO_PTR(SEGMENT_TO_PSP(Sda->CurrentPsp)->EnvBlock, 0);
|
|
}
|
|
|
|
/* Copy the environment block to DOS memory */
|
|
EnvBlock = DosCopyEnvironmentBlock(Environment, ExePath);
|
|
if (EnvBlock == 0)
|
|
{
|
|
Result = Sda->LastErrorCode;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if this is an EXE file or a COM file by looking
|
|
* at the MZ signature:
|
|
* 0x4D5A 'MZ': old signature (stored as 0x5A, 0x4D)
|
|
* 0x5A4D 'ZM': new signature (stored as 0x4D, 0x5A)
|
|
*/
|
|
ExeSignature = *(PWORD)ExeBuffer;
|
|
if (ExeSignature == 'MZ' || ExeSignature == 'ZM')
|
|
{
|
|
/* EXE file */
|
|
PIMAGE_DOS_HEADER Header;
|
|
DWORD BaseSize;
|
|
PDWORD RelocationTable;
|
|
PWORD RelocWord;
|
|
WORD RelocFactor;
|
|
WORD i;
|
|
|
|
/* Get the MZ header */
|
|
Header = (PIMAGE_DOS_HEADER)ExeBuffer;
|
|
|
|
/* Get the base size of the file, in paragraphs (rounded up) */
|
|
#if 0 // Normally this is not needed to check for the number of bytes in the last pages.
|
|
BaseSize = ((((Header->e_cp - (Header->e_cblp != 0)) * 512) + Header->e_cblp) >> 4)
|
|
- Header->e_cparhdr;
|
|
#else
|
|
// e_cp is the number of 512-byte blocks. 512 == (1 << 9)
|
|
// so this corresponds to (1 << 5) number of paragraphs.
|
|
//
|
|
// For DOS compatibility we need to truncate BaseSize to a WORD value.
|
|
// This fact is exploited by some EXEs which are bigger than 1 Mb while
|
|
// being able to load on DOS, the main EXE code loads the remaining data.
|
|
|
|
BaseSize = ((Header->e_cp << 5) - Header->e_cparhdr) & 0xFFFF;
|
|
#endif
|
|
|
|
if (LoadType != DOS_LOAD_OVERLAY)
|
|
{
|
|
BOOLEAN LoadHigh = FALSE;
|
|
DWORD TotalSize;
|
|
|
|
/* Find the maximum amount of memory that can be allocated */
|
|
DosAllocateMemory(0xFFFF, &MaxAllocSize);
|
|
|
|
/* Compute the total needed size, in paragraphs */
|
|
TotalSize = BaseSize + (sizeof(DOS_PSP) >> 4);
|
|
|
|
/* We must have the required minimum amount of memory. If not, bail out. */
|
|
if (MaxAllocSize < TotalSize + Header->e_minalloc)
|
|
{
|
|
Result = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Check if the program should be loaded high */
|
|
if (Header->e_minalloc == 0 && Header->e_maxalloc == 0)
|
|
{
|
|
/* Yes it should. Use all the available memory. */
|
|
LoadHigh = TRUE;
|
|
TotalSize = MaxAllocSize;
|
|
}
|
|
else
|
|
{
|
|
/* Compute the maximum memory size that can be allocated */
|
|
if (Header->e_maxalloc != 0)
|
|
TotalSize = min(TotalSize + Header->e_maxalloc, MaxAllocSize);
|
|
else
|
|
TotalSize = MaxAllocSize; // Use all the available memory
|
|
}
|
|
|
|
/* Try to allocate that much memory */
|
|
Segment = DosAllocateMemory((WORD)TotalSize, NULL);
|
|
if (Segment == 0)
|
|
{
|
|
Result = Sda->LastErrorCode;
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* The process owns its memory */
|
|
DosChangeMemoryOwner(Segment , Segment);
|
|
DosChangeMemoryOwner(EnvBlock, Segment);
|
|
|
|
/* Set INT 22h to the return address */
|
|
((PULONG)BaseAddress)[0x22] = ReturnAddress;
|
|
|
|
/* Create the PSP and initialize it */
|
|
DosCreatePsp(Segment, (WORD)TotalSize);
|
|
DosInitPsp(Segment, EnvBlock, CommandLine, ExePath);
|
|
|
|
/* Calculate the segment where the program should be loaded */
|
|
if (!LoadHigh)
|
|
LoadSegment = Segment + (sizeof(DOS_PSP) >> 4);
|
|
else
|
|
LoadSegment = Segment + TotalSize - BaseSize;
|
|
|
|
RelocFactor = LoadSegment;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Parameters);
|
|
LoadSegment = Parameters->Overlay.Segment;
|
|
RelocFactor = Parameters->Overlay.RelocationFactor;
|
|
}
|
|
|
|
/* Copy the program to the code segment */
|
|
RtlCopyMemory(SEG_OFF_TO_PTR(LoadSegment, 0),
|
|
ExeBuffer + (Header->e_cparhdr << 4),
|
|
min(ExeBufferSize - (Header->e_cparhdr << 4), BaseSize << 4));
|
|
|
|
/* Get the relocation table */
|
|
RelocationTable = (PDWORD)(ExeBuffer + Header->e_lfarlc);
|
|
|
|
/* Perform relocations */
|
|
for (i = 0; i < Header->e_crlc; i++)
|
|
{
|
|
/* Get a pointer to the word that needs to be patched */
|
|
RelocWord = (PWORD)SEG_OFF_TO_PTR(LoadSegment + HIWORD(RelocationTable[i]),
|
|
LOWORD(RelocationTable[i]));
|
|
|
|
/* Add the relocation factor to it */
|
|
*RelocWord += RelocFactor;
|
|
}
|
|
|
|
/* Set the stack to the location from the header */
|
|
FinalSS = LoadSegment + Header->e_ss;
|
|
FinalSP = Header->e_sp;
|
|
|
|
/* Set the code segment/pointer */
|
|
FinalCS = LoadSegment + Header->e_cs;
|
|
FinalIP = Header->e_ip;
|
|
}
|
|
else
|
|
{
|
|
/* COM file */
|
|
|
|
if (LoadType != DOS_LOAD_OVERLAY)
|
|
{
|
|
/* Find the maximum amount of memory that can be allocated */
|
|
DosAllocateMemory(0xFFFF, &MaxAllocSize);
|
|
|
|
/* Make sure it's enough for the whole program and the PSP */
|
|
if (((DWORD)MaxAllocSize << 4) < (ExeBufferSize + sizeof(DOS_PSP)))
|
|
{
|
|
Result = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Allocate all of it */
|
|
Segment = DosAllocateMemory(MaxAllocSize, NULL);
|
|
if (Segment == 0)
|
|
{
|
|
Result = Sda->LastErrorCode;
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* The process owns its memory */
|
|
DosChangeMemoryOwner(Segment , Segment);
|
|
DosChangeMemoryOwner(EnvBlock, Segment);
|
|
|
|
/* Set INT 22h to the return address */
|
|
((PULONG)BaseAddress)[0x22] = ReturnAddress;
|
|
|
|
/* Create the PSP and initialize it */
|
|
DosCreatePsp(Segment, MaxAllocSize);
|
|
DosInitPsp(Segment, EnvBlock, CommandLine, ExePath);
|
|
|
|
/* Calculate the segment where the program should be loaded */
|
|
LoadSegment = Segment + (sizeof(DOS_PSP) >> 4);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Parameters);
|
|
LoadSegment = Parameters->Overlay.Segment;
|
|
}
|
|
|
|
/* Copy the program to the code segment */
|
|
RtlCopyMemory(SEG_OFF_TO_PTR(LoadSegment, 0),
|
|
ExeBuffer, ExeBufferSize);
|
|
|
|
/* Set the stack to the last word of the segment */
|
|
FinalSS = Segment;
|
|
FinalSP = 0xFFFE;
|
|
|
|
/*
|
|
* Set the value on the stack to 0x0000, so that a near return
|
|
* jumps to PSP:0000 which has the exit code.
|
|
*/
|
|
*((LPWORD)SEG_OFF_TO_PTR(Segment, 0xFFFE)) = 0x0000;
|
|
|
|
/* Set the code segment/pointer */
|
|
FinalCS = Segment;
|
|
FinalIP = 0x0100;
|
|
}
|
|
|
|
if (LoadType == DOS_LOAD_AND_EXECUTE)
|
|
{
|
|
/* Save the program state */
|
|
if (Sda->CurrentPsp != SYSTEM_PSP)
|
|
{
|
|
/* Push the task state */
|
|
DosSaveState();
|
|
|
|
DPRINT1("Sda->CurrentPsp = 0x%04x; Old LastStack = 0x%08x, New LastStack = 0x%08x\n",
|
|
Sda->CurrentPsp, SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack, MAKELONG(getSP(), getSS()));
|
|
|
|
/* Update the last stack in the PSP */
|
|
SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack = MAKELONG(getSP(), getSS());
|
|
}
|
|
|
|
/* Set the initial segment registers */
|
|
setDS(Segment);
|
|
setES(Segment);
|
|
|
|
/* Set the stack */
|
|
setSS(FinalSS);
|
|
setSP(FinalSP);
|
|
|
|
/*
|
|
* Set the other registers as in real DOS: some demos expect them so!
|
|
* See http://www.fysnet.net/yourhelp.htm
|
|
* and http://www.beroset.com/asm/showregs.asm
|
|
*/
|
|
setDX(Segment);
|
|
setDI(FinalSP);
|
|
setBP(0x091E); // DOS base stack pointer relic value. In MS-DOS 5.0 and Windows' NTVDM it's 0x091C. This is in fact the old SP value inside DosData disk stack.
|
|
setSI(FinalIP);
|
|
|
|
setAX(0/*0xFFFF*/); // FIXME: fcbcode
|
|
setBX(0/*0xFFFF*/); // FIXME: fcbcode
|
|
setCX(0x00FF);
|
|
|
|
/*
|
|
* Keep critical flags, clear test flags (OF, SF, ZF, AF, PF, CF)
|
|
* and explicitely set the interrupt flag.
|
|
*/
|
|
setEFLAGS((getEFLAGS() & ~0x08D5) | 0x0200);
|
|
|
|
/* Notify VDDs of process execution */
|
|
VDDCreateUserHook(Segment);
|
|
|
|
/* Execute */
|
|
DosSetProcessContext(Segment);
|
|
CpuExecute(FinalCS, FinalIP);
|
|
}
|
|
else if (LoadType == DOS_LOAD_ONLY)
|
|
{
|
|
ASSERT(Parameters);
|
|
Parameters->StackLocation = MAKELONG(FinalSP, FinalSS);
|
|
Parameters->EntryPoint = MAKELONG(FinalIP, FinalCS);
|
|
}
|
|
|
|
Cleanup:
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
/* It was not successful, cleanup the DOS memory */
|
|
if (EnvBlock) DosFreeMemory(EnvBlock);
|
|
if (Segment) DosFreeMemory(Segment);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
|
|
IN LPCSTR ExecutablePath,
|
|
IN PDOS_EXEC_PARAM_BLOCK Parameters,
|
|
IN LPCSTR CommandLine OPTIONAL,
|
|
IN LPCSTR Environment OPTIONAL,
|
|
IN DWORD ReturnAddress OPTIONAL)
|
|
{
|
|
DWORD Result = ERROR_SUCCESS;
|
|
HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
|
|
DWORD FileSize;
|
|
LPBYTE Address = NULL;
|
|
CHAR FullPath[MAX_PATH];
|
|
CHAR ShortFullPath[MAX_PATH];
|
|
|
|
DPRINT1("DosLoadExecutable(%d, '%s', 0x%p, 0x%p, 0x%p)\n",
|
|
LoadType, ExecutablePath, Parameters, CommandLine, Environment);
|
|
|
|
/* Try to get the full path to the executable */
|
|
if (GetFullPathNameA(ExecutablePath, sizeof(FullPath), FullPath, NULL))
|
|
{
|
|
/* Get the corresponding short path */
|
|
if (GetShortPathNameA(FullPath, ShortFullPath, sizeof(ShortFullPath)))
|
|
{
|
|
/* Use the shortened full path from now on */
|
|
ExecutablePath = ShortFullPath;
|
|
}
|
|
}
|
|
|
|
/* Open a handle to the executable */
|
|
FileHandle = CreateFileA(ExecutablePath,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
if (FileHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
Result = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Get the file size */
|
|
FileSize = GetFileSize(FileHandle, NULL);
|
|
|
|
/* Create a mapping object for the file */
|
|
FileMapping = CreateFileMapping(FileHandle,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
0,
|
|
0,
|
|
NULL);
|
|
if (FileMapping == NULL)
|
|
{
|
|
Result = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Map the file into memory */
|
|
Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
|
|
if (Address == NULL)
|
|
{
|
|
Result = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
Result = DosLoadExecutableInternal(LoadType,
|
|
Address,
|
|
FileSize,
|
|
ExecutablePath,
|
|
Parameters,
|
|
CommandLine,
|
|
Environment,
|
|
ReturnAddress);
|
|
|
|
Cleanup:
|
|
/* Unmap the file*/
|
|
if (Address != NULL) UnmapViewOfFile(Address);
|
|
|
|
/* Close the file mapping object */
|
|
if (FileMapping != NULL) CloseHandle(FileMapping);
|
|
|
|
/* Close the file handle */
|
|
if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
|
|
|
|
return Result;
|
|
}
|
|
|
|
WORD DosCreateProcess(IN LPCSTR ProgramName,
|
|
IN PDOS_EXEC_PARAM_BLOCK Parameters,
|
|
IN DWORD ReturnAddress OPTIONAL)
|
|
{
|
|
DWORD Result = ERROR_SUCCESS;
|
|
DWORD BinaryType;
|
|
|
|
/* Get the binary type */
|
|
if (!GetBinaryTypeA(ProgramName, &BinaryType)) return GetLastError();
|
|
|
|
/* Check the type of the program */
|
|
switch (BinaryType)
|
|
{
|
|
/* Those are handled by NTVDM */
|
|
case SCS_WOW_BINARY:
|
|
{
|
|
DisplayMessage(L"Trying to load '%S'.\n"
|
|
L"WOW16 applications are not supported internally by NTVDM at the moment.\n"
|
|
L"Press 'OK' to continue.",
|
|
ProgramName);
|
|
// Fall through
|
|
}
|
|
case SCS_DOS_BINARY:
|
|
{
|
|
/* Load the executable */
|
|
Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
|
|
ProgramName,
|
|
Parameters,
|
|
NULL,
|
|
NULL,
|
|
ReturnAddress);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
DisplayMessage(L"Could not load '%S'. Error: %u", ProgramName, Result);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Not handled by NTVDM */
|
|
default:
|
|
{
|
|
LPSTR Environment = NULL;
|
|
CHAR CmdLine[MAX_PATH + DOS_CMDLINE_LENGTH + 1];
|
|
LPSTR CmdLinePtr;
|
|
ULONG CmdLineSize;
|
|
|
|
/* Did the caller specify an environment segment? */
|
|
if (Parameters->Environment)
|
|
{
|
|
/* Yes, use it instead of the parent one */
|
|
Environment = (LPSTR)SEG_OFF_TO_PTR(Parameters->Environment, 0);
|
|
}
|
|
|
|
/*
|
|
* Convert the DOS command line to Win32-compatible format, by concatenating
|
|
* the program name with the converted command line.
|
|
* Format of the DOS command line: 1 byte for size; 127 bytes for contents.
|
|
*/
|
|
CmdLinePtr = CmdLine;
|
|
strncpy(CmdLinePtr, ProgramName, MAX_PATH); // Concatenate the program name
|
|
CmdLinePtr += strlen(CmdLinePtr);
|
|
*CmdLinePtr++ = ' '; // Add separating space
|
|
|
|
CmdLineSize = min(*(PBYTE)FAR_POINTER(Parameters->CommandLine), DOS_CMDLINE_LENGTH);
|
|
RtlCopyMemory(CmdLinePtr,
|
|
(LPSTR)FAR_POINTER(Parameters->CommandLine) + 1,
|
|
CmdLineSize);
|
|
/* NULL-terminate it */
|
|
CmdLinePtr[CmdLineSize] = '\0';
|
|
|
|
/* Remove any trailing return carriage character and NULL-terminate the command line */
|
|
while (*CmdLinePtr && *CmdLinePtr != '\r' && *CmdLinePtr != '\n') CmdLinePtr++;
|
|
*CmdLinePtr = '\0';
|
|
|
|
Result = DosStartProcess32(ProgramName, CmdLine,
|
|
Environment, ReturnAddress,
|
|
TRUE);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
DisplayMessage(L"Could not load 32-bit '%S'. Error: %u", ProgramName, Result);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode, WORD KeepResident)
|
|
{
|
|
WORD McbSegment = SysVars->FirstMcb;
|
|
PDOS_MCB CurrentMcb;
|
|
LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
|
|
PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
|
|
LPWORD Stack;
|
|
BYTE TerminationType;
|
|
|
|
DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X, KeepResident 0x%04X\n",
|
|
Psp, ReturnCode, KeepResident);
|
|
|
|
/* Notify VDDs of process termination */
|
|
VDDTerminateUserHook(Psp);
|
|
|
|
/* Check if this PSP is its own parent */
|
|
if (PspBlock->ParentPsp == Psp) goto Done;
|
|
|
|
if (KeepResident == 0)
|
|
{
|
|
WORD i;
|
|
for (i = 0; i < PspBlock->HandleTableSize; i++)
|
|
{
|
|
/* Close the handle */
|
|
DosCloseHandle(i);
|
|
}
|
|
}
|
|
|
|
/* Free the memory used by the process */
|
|
while (TRUE)
|
|
{
|
|
/* Get a pointer to the MCB */
|
|
CurrentMcb = SEGMENT_TO_MCB(McbSegment);
|
|
|
|
/* Make sure the MCB is valid */
|
|
if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z') break;
|
|
|
|
/* Check if this block was allocated by the process */
|
|
if (CurrentMcb->OwnerPsp == Psp)
|
|
{
|
|
if (KeepResident)
|
|
{
|
|
/* Check if this is the PSP block and we should reduce its size */
|
|
if ((McbSegment + 1) == Psp && KeepResident < CurrentMcb->Size)
|
|
{
|
|
/* Reduce the size of the block */
|
|
DosResizeMemory(McbSegment + 1, KeepResident, NULL);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Free this entire block */
|
|
DosFreeMemory(McbSegment + 1);
|
|
}
|
|
}
|
|
|
|
/* If this was the last block, quit */
|
|
if (CurrentMcb->BlockType == 'Z') break;
|
|
|
|
/* Update the segment and continue */
|
|
McbSegment += CurrentMcb->Size + 1;
|
|
}
|
|
|
|
Done:
|
|
/* Restore the interrupt vectors */
|
|
IntVecTable[0x22] = PspBlock->TerminateAddress;
|
|
IntVecTable[0x23] = PspBlock->BreakAddress;
|
|
IntVecTable[0x24] = PspBlock->CriticalAddress;
|
|
|
|
/* Update the current PSP with the parent's one */
|
|
if (Psp == Sda->CurrentPsp)
|
|
{
|
|
DosSetProcessContext(PspBlock->ParentPsp);
|
|
if (Sda->CurrentPsp == SYSTEM_PSP)
|
|
{
|
|
// NOTE: we can also use the DOS BIOS exit code.
|
|
CpuUnsimulate();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Save the return code - Normal termination or TSR */
|
|
TerminationType = (KeepResident != 0 ? 0x03 : 0x00);
|
|
Sda->ErrorLevel = MAKEWORD(ReturnCode, TerminationType);
|
|
|
|
DPRINT1("PspBlock->ParentPsp = 0x%04x; Sda->CurrentPsp = 0x%04x\n",
|
|
PspBlock->ParentPsp, Sda->CurrentPsp);
|
|
|
|
if (Sda->CurrentPsp != SYSTEM_PSP)
|
|
{
|
|
DPRINT1("Sda->CurrentPsp = 0x%04x; Old SS:SP = %04X:%04X going to be LastStack = 0x%08x\n",
|
|
Sda->CurrentPsp, getSS(), getSP(), SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack);
|
|
|
|
/* Restore the parent's stack */
|
|
setSS(HIWORD(SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack));
|
|
setSP(LOWORD(SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack));
|
|
|
|
/* Pop the task state */
|
|
DosRestoreState();
|
|
}
|
|
|
|
/* Return control to the parent process */
|
|
Stack = (LPWORD)SEG_OFF_TO_PTR(getSS(), getSP());
|
|
Stack[STACK_CS] = HIWORD(PspBlock->TerminateAddress);
|
|
Stack[STACK_IP] = LOWORD(PspBlock->TerminateAddress);
|
|
}
|
|
|
|
/* EOF */
|