1 /* 2 * COPYRIGHT: GPL - See COPYING in the top level directory 3 * PROJECT: ReactOS Virtual DOS Machine 4 * FILE: subsystems/mvdm/ntvdm/dos/dos32krnl/process.c 5 * PURPOSE: DOS32 Processes 6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org> 7 * Hermes Belusca-Maito (hermes.belusca@sfr.fr) 8 */ 9 10 /* INCLUDES *******************************************************************/ 11 12 #include "ntvdm.h" 13 14 #define NDEBUG 15 #include <debug.h> 16 17 #include "emulator.h" 18 #include "cpu/cpu.h" 19 20 #include "dos.h" 21 #include "dos/dem.h" 22 #include "dosfiles.h" 23 #include "handle.h" 24 #include "process.h" 25 #include "memory.h" 26 27 #include "bios/bios.h" 28 29 #include "io.h" 30 #include "hardware/ps2.h" 31 32 #include "vddsup.h" 33 34 /* PRIVATE FUNCTIONS **********************************************************/ 35 36 static VOID DosInitPsp(IN WORD Segment, 37 IN WORD EnvBlock, 38 IN LPCSTR CommandLine, 39 IN LPCSTR ProgramName) 40 { 41 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Segment); 42 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1); 43 LPCSTR PspName; 44 USHORT i; 45 46 /* Link the environment block */ 47 PspBlock->EnvBlock = EnvBlock; 48 49 /* 50 * Copy the command line. 51 * Format of the CommandLine parameter: 1 byte for size; 127 bytes for contents. 52 */ 53 PspBlock->CommandLineSize = min(*(PBYTE)CommandLine, DOS_CMDLINE_LENGTH); 54 CommandLine++; 55 RtlCopyMemory(PspBlock->CommandLine, CommandLine, DOS_CMDLINE_LENGTH); 56 57 /* 58 * Initialize the owner name of the MCB of the PSP. 59 */ 60 61 /* Find the start of the file name, skipping all the path elements */ 62 PspName = ProgramName; 63 while (*ProgramName) 64 { 65 switch (*ProgramName++) 66 { 67 /* Path delimiter, skip it */ 68 case ':': case '\\': case '/': 69 PspName = ProgramName; 70 break; 71 } 72 } 73 /* Copy the file name up to the extension... */ 74 for (i = 0; i < sizeof(Mcb->Name) && PspName[i] != '.' && PspName[i] != '\0'; ++i) 75 { 76 Mcb->Name[i] = RtlUpperChar(PspName[i]); 77 } 78 /* ... and NULL-terminate if needed */ 79 if (i < sizeof(Mcb->Name)) Mcb->Name[i] = '\0'; 80 81 // FIXME: Initialize the FCBs 82 } 83 84 static inline VOID DosSaveState(VOID) 85 { 86 PDOS_REGISTER_STATE State; 87 WORD StackPointer = getSP(); 88 89 DPRINT1("\n" 90 "DosSaveState(before) -- SS:SP == %04X:%04X\n" 91 "Original CPU State =\n" 92 "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n" 93 "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X" 94 "\n", 95 getSS(), getSP(), 96 getDS(), getES(), getAX(), getCX(), 97 getDX(), getBX(), getBP(), getSI(), getDI()); 98 99 /* 100 * Allocate stack space for the registers. Note that we 101 * already have one word allocated (the interrupt number). 102 */ 103 StackPointer -= sizeof(DOS_REGISTER_STATE) - sizeof(WORD); 104 State = SEG_OFF_TO_PTR(getSS(), StackPointer); 105 setSP(StackPointer); 106 107 /* Save */ 108 State->DS = getDS(); 109 State->ES = getES(); 110 State->AX = getAX(); 111 State->CX = getCX(); 112 State->DX = getDX(); 113 State->BX = getBX(); 114 State->BP = getBP(); 115 State->SI = getSI(); 116 State->DI = getDI(); 117 118 DPRINT1("\n" 119 "DosSaveState(after) -- SS:SP == %04X:%04X\n" 120 "Saved State =\n" 121 "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n" 122 "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X" 123 "\n", 124 getSS(), getSP(), 125 State->DS, State->ES, State->AX, State->CX, 126 State->DX, State->BX, State->BP, State->SI, State->DI); 127 } 128 129 static inline VOID DosRestoreState(VOID) 130 { 131 PDOS_REGISTER_STATE State; 132 133 /* 134 * Pop the state structure from the stack. Note that we 135 * already have one word allocated (the interrupt number). 136 */ 137 State = SEG_OFF_TO_PTR(getSS(), getSP()); 138 139 DPRINT1("\n" 140 "DosRestoreState(before) -- SS:SP == %04X:%04X\n" 141 "Saved State =\n" 142 "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n" 143 "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X" 144 "\n", 145 getSS(), getSP(), 146 State->DS, State->ES, State->AX, State->CX, 147 State->DX, State->BX, State->BP, State->SI, State->DI); 148 149 setSP(getSP() + sizeof(DOS_REGISTER_STATE) - sizeof(WORD)); 150 151 /* Restore */ 152 setDS(State->DS); 153 setES(State->ES); 154 setAX(State->AX); 155 setCX(State->CX); 156 setDX(State->DX); 157 setBX(State->BX); 158 setBP(State->BP); 159 setSI(State->SI); 160 setDI(State->DI); 161 162 DPRINT1("\n" 163 "DosRestoreState(after) -- SS:SP == %04X:%04X\n" 164 "Restored CPU State =\n" 165 "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n" 166 "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X" 167 "\n", 168 getSS(), getSP(), 169 getDS(), getES(), getAX(), getCX(), 170 getDX(), getBX(), getBP(), getSI(), getDI()); 171 } 172 173 static WORD DosCopyEnvironmentBlock(IN LPCSTR Environment OPTIONAL, 174 IN LPCSTR ProgramName) 175 { 176 PCHAR Ptr, DestBuffer = NULL; 177 SIZE_T TotalSize = 0; 178 WORD DestSegment; 179 180 /* If we have an environment strings list, compute its size */ 181 if (Environment) 182 { 183 /* Calculate the size of the environment block */ 184 Ptr = (PCHAR)Environment; 185 while (*Ptr) Ptr += strlen(Ptr) + 1; 186 TotalSize = (ULONG_PTR)Ptr - (ULONG_PTR)Environment; 187 } 188 else 189 { 190 /* Empty environment string */ 191 TotalSize = 1; 192 } 193 /* Add the final environment block NULL-terminator */ 194 TotalSize++; 195 196 /* Add the two bytes for the program name tag */ 197 TotalSize += 2; 198 199 /* Add the string buffer size */ 200 TotalSize += strlen(ProgramName) + 1; 201 202 /* Allocate the memory for the environment block */ 203 DestSegment = DosAllocateMemory((WORD)((TotalSize + 0x0F) >> 4), NULL); 204 if (!DestSegment) return 0; 205 206 DestBuffer = (PCHAR)SEG_OFF_TO_PTR(DestSegment, 0); 207 208 /* If we have an environment strings list, copy it */ 209 if (Environment) 210 { 211 Ptr = (PCHAR)Environment; 212 while (*Ptr) 213 { 214 /* Copy the string and NULL-terminate it */ 215 strcpy(DestBuffer, Ptr); 216 DestBuffer += strlen(Ptr); 217 *(DestBuffer++) = '\0'; 218 219 /* Move to the next string */ 220 Ptr += strlen(Ptr) + 1; 221 } 222 } 223 else 224 { 225 /* Empty environment string */ 226 *(DestBuffer++) = '\0'; 227 } 228 /* NULL-terminate the environment block */ 229 *(DestBuffer++) = '\0'; 230 231 /* Store the special program name tag */ 232 *(DestBuffer++) = LOBYTE(DOS_PROGRAM_NAME_TAG); 233 *(DestBuffer++) = HIBYTE(DOS_PROGRAM_NAME_TAG); 234 235 /* Copy the program name after the environment block */ 236 strcpy(DestBuffer, ProgramName); 237 238 return DestSegment; 239 } 240 241 /* PUBLIC FUNCTIONS ***********************************************************/ 242 243 VOID DosClonePsp(WORD DestSegment, WORD SourceSegment) 244 { 245 PDOS_PSP DestPsp = SEGMENT_TO_PSP(DestSegment); 246 PDOS_PSP SourcePsp = SEGMENT_TO_PSP(SourceSegment); 247 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress); 248 249 /* Literally copy the PSP first */ 250 RtlCopyMemory(DestPsp, SourcePsp, sizeof(*DestPsp)); 251 252 /* Save the interrupt vectors */ 253 DestPsp->TerminateAddress = IntVecTable[0x22]; 254 DestPsp->BreakAddress = IntVecTable[0x23]; 255 DestPsp->CriticalAddress = IntVecTable[0x24]; 256 257 /* No parent PSP */ 258 DestPsp->ParentPsp = 0; 259 260 /* Set the handle table pointers to the internal handle table */ 261 DestPsp->HandleTableSize = DEFAULT_JFT_SIZE; 262 DestPsp->HandleTablePtr = MAKELONG(0x18, DestSegment); 263 264 /* Copy the parent handle table without referencing the SFT */ 265 RtlCopyMemory(FAR_POINTER(DestPsp->HandleTablePtr), 266 FAR_POINTER(SourcePsp->HandleTablePtr), 267 DEFAULT_JFT_SIZE); 268 } 269 270 VOID DosCreatePsp(WORD Segment, WORD ProgramSize) 271 { 272 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Segment); 273 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress); 274 275 RtlZeroMemory(PspBlock, sizeof(*PspBlock)); 276 277 /* Set the exit interrupt */ 278 PspBlock->Exit[0] = 0xCD; // int 0x20 279 PspBlock->Exit[1] = 0x20; 280 281 /* Set the number of the last paragraph */ 282 PspBlock->LastParagraph = Segment + ProgramSize; 283 284 /* Save the interrupt vectors */ 285 PspBlock->TerminateAddress = IntVecTable[0x22]; 286 PspBlock->BreakAddress = IntVecTable[0x23]; 287 PspBlock->CriticalAddress = IntVecTable[0x24]; 288 289 /* Set the parent PSP */ 290 PspBlock->ParentPsp = Sda->CurrentPsp; 291 292 if (Sda->CurrentPsp != SYSTEM_PSP) 293 { 294 /* Link to the parent's environment block */ 295 PspBlock->EnvBlock = SEGMENT_TO_PSP(Sda->CurrentPsp)->EnvBlock; 296 } 297 /* 298 else 299 { 300 PspBlock->EnvBlock = SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0); 301 } 302 */ 303 304 /* Copy the parent handle table */ 305 DosCopyHandleTable(PspBlock->HandleTable); 306 307 /* Set the handle table pointers to the internal handle table */ 308 PspBlock->HandleTableSize = DEFAULT_JFT_SIZE; 309 PspBlock->HandleTablePtr = MAKELONG(0x18, Segment); 310 311 /* Set the DOS version */ 312 // FIXME: This is here that SETVER stuff enters into action! 313 PspBlock->DosVersion = DosData->DosVersion; 314 315 /* Set the far call opcodes */ 316 PspBlock->FarCall[0] = 0xCD; // int 0x21 317 PspBlock->FarCall[1] = 0x21; 318 PspBlock->FarCall[2] = 0xCB; // retf 319 } 320 321 VOID DosSetProcessContext(WORD Segment) 322 { 323 Sda->CurrentPsp = Segment; 324 Sda->DiskTransferArea = MAKELONG(0x80, Segment); 325 } 326 327 DWORD DosLoadExecutableInternal(IN DOS_EXEC_TYPE LoadType, 328 IN LPBYTE ExeBuffer, 329 IN DWORD ExeBufferSize, 330 IN LPCSTR ExePath, 331 IN PDOS_EXEC_PARAM_BLOCK Parameters, 332 IN LPCSTR CommandLine OPTIONAL, 333 IN LPCSTR Environment OPTIONAL, 334 IN DWORD ReturnAddress OPTIONAL) 335 { 336 DWORD Result = ERROR_SUCCESS; 337 WORD Segment = 0; 338 WORD EnvBlock = 0; 339 WORD ExeSignature; 340 WORD LoadSegment; 341 WORD MaxAllocSize; 342 343 WORD FinalSS, FinalSP; 344 WORD FinalCS, FinalIP; 345 346 /* Buffer for command line conversion: 1 byte for size; 127 bytes for contents */ 347 CHAR CmdLineBuffer[1 + DOS_CMDLINE_LENGTH]; 348 349 DPRINT1("DosLoadExecutableInternal(%d, 0x%p, '%s', 0x%p, 0x%p, 0x%p)\n", 350 LoadType, ExeBuffer, ExePath, Parameters, CommandLine, Environment); 351 352 if (LoadType != DOS_LOAD_OVERLAY) 353 { 354 /* If an optional Win32 command line is given... */ 355 if (CommandLine) 356 { 357 /* ... convert it into DOS format */ 358 BYTE CmdLineLen; 359 360 PBYTE CmdLineSize = (PBYTE)CmdLineBuffer; 361 LPSTR CmdLineStart = CmdLineBuffer + 1; 362 LPSTR CmdLinePtr = CmdLineStart; 363 364 // For debugging purposes 365 RtlFillMemory(CmdLineBuffer, sizeof(CmdLineBuffer), 0xFF); 366 367 /* 368 * Set the command line: it is either an empty command line or has 369 * the format: " foo bar ..." (with at least one leading whitespace), 370 * and is then always followed by '\r' (and optionally by '\n'). 371 */ 372 CmdLineLen = (BYTE)strlen(CommandLine); 373 *CmdLineSize = 0; 374 375 /* 376 * Add the leading space if the command line is not empty 377 * and doesn't already start with some whitespace... 378 */ 379 if (*CommandLine && *CommandLine != '\r' && *CommandLine != '\n' && 380 *CommandLine != ' ' && *CommandLine != '\t') 381 { 382 (*CmdLineSize)++; 383 *CmdLinePtr++ = ' '; 384 } 385 386 /* Compute the number of characters we need to copy from the original command line */ 387 CmdLineLen = min(CmdLineLen, DOS_CMDLINE_LENGTH - *CmdLineSize); 388 389 /* The trailing '\r' or '\n' do not count in the PSP command line size parameter */ 390 while (CmdLineLen && (CommandLine[CmdLineLen - 1] == '\r' || CommandLine[CmdLineLen - 1] == '\n')) 391 { 392 CmdLineLen--; 393 } 394 395 /* Finally, set everything up */ 396 *CmdLineSize += CmdLineLen; 397 RtlCopyMemory(CmdLinePtr, CommandLine, CmdLineLen); 398 CmdLineStart[*CmdLineSize] = '\r'; 399 400 /* Finally make the pointer point to the static buffer */ 401 CommandLine = CmdLineBuffer; 402 } 403 else 404 { 405 /* 406 * ... otherwise, get the one from the parameter block. 407 * Format of the command line: 1 byte for size; 127 bytes for contents. 408 */ 409 ASSERT(Parameters); 410 CommandLine = (LPCSTR)FAR_POINTER(Parameters->CommandLine); 411 } 412 413 /* If no optional environment is given... */ 414 if (Environment == NULL) 415 { 416 ASSERT(Parameters); 417 /* ... get the one from the parameter block (if not NULL)... */ 418 if (Parameters->Environment) 419 Environment = (LPCSTR)SEG_OFF_TO_PTR(Parameters->Environment, 0); 420 /* ... or the one from the parent (otherwise) */ 421 else 422 Environment = (LPCSTR)SEG_OFF_TO_PTR(SEGMENT_TO_PSP(Sda->CurrentPsp)->EnvBlock, 0); 423 } 424 425 /* Copy the environment block to DOS memory */ 426 EnvBlock = DosCopyEnvironmentBlock(Environment, ExePath); 427 if (EnvBlock == 0) 428 { 429 Result = Sda->LastErrorCode; 430 goto Cleanup; 431 } 432 } 433 434 /* 435 * Check if this is an EXE file or a COM file by looking 436 * at the MZ signature: 437 * 0x4D5A 'MZ': old signature (stored as 0x5A, 0x4D) 438 * 0x5A4D 'ZM': new signature (stored as 0x4D, 0x5A) 439 */ 440 ExeSignature = *(PWORD)ExeBuffer; 441 if (ExeSignature == 'MZ' || ExeSignature == 'ZM') 442 { 443 /* EXE file */ 444 PIMAGE_DOS_HEADER Header; 445 DWORD BaseSize; 446 PDWORD RelocationTable; 447 PWORD RelocWord; 448 WORD RelocFactor; 449 WORD i; 450 451 /* Get the MZ header */ 452 Header = (PIMAGE_DOS_HEADER)ExeBuffer; 453 454 /* Get the base size of the file, in paragraphs (rounded up) */ 455 #if 0 // Normally this is not needed to check for the number of bytes in the last pages. 456 BaseSize = ((((Header->e_cp - (Header->e_cblp != 0)) * 512) + Header->e_cblp) >> 4) 457 - Header->e_cparhdr; 458 #else 459 // e_cp is the number of 512-byte blocks. 512 == (1 << 9) 460 // so this corresponds to (1 << 5) number of paragraphs. 461 // 462 // For DOS compatibility we need to truncate BaseSize to a WORD value. 463 // This fact is exploited by some EXEs which are bigger than 1 Mb while 464 // being able to load on DOS, the main EXE code loads the remaining data. 465 466 BaseSize = ((Header->e_cp << 5) - Header->e_cparhdr) & 0xFFFF; 467 #endif 468 469 if (LoadType != DOS_LOAD_OVERLAY) 470 { 471 BOOLEAN LoadHigh = FALSE; 472 DWORD TotalSize; 473 474 /* Find the maximum amount of memory that can be allocated */ 475 DosAllocateMemory(0xFFFF, &MaxAllocSize); 476 477 /* Compute the total needed size, in paragraphs */ 478 TotalSize = BaseSize + (sizeof(DOS_PSP) >> 4); 479 480 /* We must have the required minimum amount of memory. If not, bail out. */ 481 if (MaxAllocSize < TotalSize + Header->e_minalloc) 482 { 483 Result = ERROR_NOT_ENOUGH_MEMORY; 484 goto Cleanup; 485 } 486 487 /* Check if the program should be loaded high */ 488 if (Header->e_minalloc == 0 && Header->e_maxalloc == 0) 489 { 490 /* Yes it should. Use all the available memory. */ 491 LoadHigh = TRUE; 492 TotalSize = MaxAllocSize; 493 } 494 else 495 { 496 /* Compute the maximum memory size that can be allocated */ 497 if (Header->e_maxalloc != 0) 498 TotalSize = min(TotalSize + Header->e_maxalloc, MaxAllocSize); 499 else 500 TotalSize = MaxAllocSize; // Use all the available memory 501 } 502 503 /* Try to allocate that much memory */ 504 Segment = DosAllocateMemory((WORD)TotalSize, NULL); 505 if (Segment == 0) 506 { 507 Result = Sda->LastErrorCode; 508 goto Cleanup; 509 } 510 511 /* The process owns its memory */ 512 DosChangeMemoryOwner(Segment , Segment); 513 DosChangeMemoryOwner(EnvBlock, Segment); 514 515 /* Set INT 22h to the return address */ 516 ((PULONG)BaseAddress)[0x22] = ReturnAddress; 517 518 /* Create the PSP and initialize it */ 519 DosCreatePsp(Segment, (WORD)TotalSize); 520 DosInitPsp(Segment, EnvBlock, CommandLine, ExePath); 521 522 /* Calculate the segment where the program should be loaded */ 523 if (!LoadHigh) 524 LoadSegment = Segment + (sizeof(DOS_PSP) >> 4); 525 else 526 LoadSegment = Segment + TotalSize - BaseSize; 527 528 RelocFactor = LoadSegment; 529 } 530 else 531 { 532 ASSERT(Parameters); 533 LoadSegment = Parameters->Overlay.Segment; 534 RelocFactor = Parameters->Overlay.RelocationFactor; 535 } 536 537 /* Copy the program to the code segment */ 538 RtlCopyMemory(SEG_OFF_TO_PTR(LoadSegment, 0), 539 ExeBuffer + (Header->e_cparhdr << 4), 540 min(ExeBufferSize - (Header->e_cparhdr << 4), BaseSize << 4)); 541 542 /* Get the relocation table */ 543 RelocationTable = (PDWORD)(ExeBuffer + Header->e_lfarlc); 544 545 /* Perform relocations */ 546 for (i = 0; i < Header->e_crlc; i++) 547 { 548 /* Get a pointer to the word that needs to be patched */ 549 RelocWord = (PWORD)SEG_OFF_TO_PTR(LoadSegment + HIWORD(RelocationTable[i]), 550 LOWORD(RelocationTable[i])); 551 552 /* Add the relocation factor to it */ 553 *RelocWord += RelocFactor; 554 } 555 556 /* Set the stack to the location from the header */ 557 FinalSS = LoadSegment + Header->e_ss; 558 FinalSP = Header->e_sp; 559 560 /* Set the code segment/pointer */ 561 FinalCS = LoadSegment + Header->e_cs; 562 FinalIP = Header->e_ip; 563 } 564 else 565 { 566 /* COM file */ 567 568 if (LoadType != DOS_LOAD_OVERLAY) 569 { 570 /* Find the maximum amount of memory that can be allocated */ 571 DosAllocateMemory(0xFFFF, &MaxAllocSize); 572 573 /* Make sure it's enough for the whole program and the PSP */ 574 if (((DWORD)MaxAllocSize << 4) < (ExeBufferSize + sizeof(DOS_PSP))) 575 { 576 Result = ERROR_NOT_ENOUGH_MEMORY; 577 goto Cleanup; 578 } 579 580 /* Allocate all of it */ 581 Segment = DosAllocateMemory(MaxAllocSize, NULL); 582 if (Segment == 0) 583 { 584 Result = Sda->LastErrorCode; 585 goto Cleanup; 586 } 587 588 /* The process owns its memory */ 589 DosChangeMemoryOwner(Segment , Segment); 590 DosChangeMemoryOwner(EnvBlock, Segment); 591 592 /* Set INT 22h to the return address */ 593 ((PULONG)BaseAddress)[0x22] = ReturnAddress; 594 595 /* Create the PSP and initialize it */ 596 DosCreatePsp(Segment, MaxAllocSize); 597 DosInitPsp(Segment, EnvBlock, CommandLine, ExePath); 598 599 /* Calculate the segment where the program should be loaded */ 600 LoadSegment = Segment + (sizeof(DOS_PSP) >> 4); 601 } 602 else 603 { 604 ASSERT(Parameters); 605 LoadSegment = Parameters->Overlay.Segment; 606 } 607 608 /* Copy the program to the code segment */ 609 RtlCopyMemory(SEG_OFF_TO_PTR(LoadSegment, 0), 610 ExeBuffer, ExeBufferSize); 611 612 /* Set the stack to the last word of the segment */ 613 FinalSS = Segment; 614 FinalSP = 0xFFFE; 615 616 /* 617 * Set the value on the stack to 0x0000, so that a near return 618 * jumps to PSP:0000 which has the exit code. 619 */ 620 *((LPWORD)SEG_OFF_TO_PTR(Segment, 0xFFFE)) = 0x0000; 621 622 /* Set the code segment/pointer */ 623 FinalCS = Segment; 624 FinalIP = 0x0100; 625 } 626 627 if (LoadType == DOS_LOAD_AND_EXECUTE) 628 { 629 /* Save the program state */ 630 if (Sda->CurrentPsp != SYSTEM_PSP) 631 { 632 /* Push the task state */ 633 DosSaveState(); 634 635 DPRINT1("Sda->CurrentPsp = 0x%04x; Old LastStack = 0x%08x, New LastStack = 0x%08x\n", 636 Sda->CurrentPsp, SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack, MAKELONG(getSP(), getSS())); 637 638 /* Update the last stack in the PSP */ 639 SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack = MAKELONG(getSP(), getSS()); 640 } 641 642 /* Set the initial segment registers */ 643 setDS(Segment); 644 setES(Segment); 645 646 /* Set the stack */ 647 setSS(FinalSS); 648 setSP(FinalSP); 649 650 /* 651 * Set the other registers as in real DOS: some demos expect them so! 652 * See http://www.fysnet.net/yourhelp.htm 653 * and http://www.beroset.com/asm/showregs.asm 654 */ 655 setDX(Segment); 656 setDI(FinalSP); 657 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. 658 setSI(FinalIP); 659 660 setAX(0/*0xFFFF*/); // FIXME: fcbcode 661 setBX(0/*0xFFFF*/); // FIXME: fcbcode 662 setCX(0x00FF); 663 664 /* 665 * Keep critical flags, clear test flags (OF, SF, ZF, AF, PF, CF) 666 * and explicitely set the interrupt flag. 667 */ 668 setEFLAGS((getEFLAGS() & ~0x08D5) | 0x0200); 669 670 /* Notify VDDs of process execution */ 671 VDDCreateUserHook(Segment); 672 673 /* Execute */ 674 DosSetProcessContext(Segment); 675 CpuExecute(FinalCS, FinalIP); 676 } 677 else if (LoadType == DOS_LOAD_ONLY) 678 { 679 ASSERT(Parameters); 680 Parameters->StackLocation = MAKELONG(FinalSP, FinalSS); 681 Parameters->EntryPoint = MAKELONG(FinalIP, FinalCS); 682 } 683 684 Cleanup: 685 if (Result != ERROR_SUCCESS) 686 { 687 /* It was not successful, cleanup the DOS memory */ 688 if (EnvBlock) DosFreeMemory(EnvBlock); 689 if (Segment) DosFreeMemory(Segment); 690 } 691 692 return Result; 693 } 694 695 DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType, 696 IN LPCSTR ExecutablePath, 697 IN PDOS_EXEC_PARAM_BLOCK Parameters, 698 IN LPCSTR CommandLine OPTIONAL, 699 IN LPCSTR Environment OPTIONAL, 700 IN DWORD ReturnAddress OPTIONAL) 701 { 702 DWORD Result = ERROR_SUCCESS; 703 HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL; 704 DWORD FileSize; 705 LPBYTE Address = NULL; 706 CHAR FullPath[MAX_PATH]; 707 CHAR ShortFullPath[MAX_PATH]; 708 709 DPRINT1("DosLoadExecutable(%d, '%s', 0x%p, 0x%p, 0x%p)\n", 710 LoadType, ExecutablePath, Parameters, CommandLine, Environment); 711 712 /* Try to get the full path to the executable */ 713 if (GetFullPathNameA(ExecutablePath, sizeof(FullPath), FullPath, NULL)) 714 { 715 /* Get the corresponding short path */ 716 if (GetShortPathNameA(FullPath, ShortFullPath, sizeof(ShortFullPath))) 717 { 718 /* Use the shortened full path from now on */ 719 ExecutablePath = ShortFullPath; 720 } 721 } 722 723 /* Open a handle to the executable */ 724 FileHandle = CreateFileA(ExecutablePath, 725 GENERIC_READ, 726 FILE_SHARE_READ, 727 NULL, 728 OPEN_EXISTING, 729 FILE_ATTRIBUTE_NORMAL, 730 NULL); 731 if (FileHandle == INVALID_HANDLE_VALUE) 732 { 733 Result = GetLastError(); 734 goto Cleanup; 735 } 736 737 /* Get the file size */ 738 FileSize = GetFileSize(FileHandle, NULL); 739 740 /* Create a mapping object for the file */ 741 FileMapping = CreateFileMapping(FileHandle, 742 NULL, 743 PAGE_READONLY, 744 0, 745 0, 746 NULL); 747 if (FileMapping == NULL) 748 { 749 Result = GetLastError(); 750 goto Cleanup; 751 } 752 753 /* Map the file into memory */ 754 Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0); 755 if (Address == NULL) 756 { 757 Result = GetLastError(); 758 goto Cleanup; 759 } 760 761 Result = DosLoadExecutableInternal(LoadType, 762 Address, 763 FileSize, 764 ExecutablePath, 765 Parameters, 766 CommandLine, 767 Environment, 768 ReturnAddress); 769 770 Cleanup: 771 /* Unmap the file*/ 772 if (Address != NULL) UnmapViewOfFile(Address); 773 774 /* Close the file mapping object */ 775 if (FileMapping != NULL) CloseHandle(FileMapping); 776 777 /* Close the file handle */ 778 if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle); 779 780 return Result; 781 } 782 783 WORD DosCreateProcess(IN LPCSTR ProgramName, 784 IN PDOS_EXEC_PARAM_BLOCK Parameters, 785 IN DWORD ReturnAddress OPTIONAL) 786 { 787 DWORD Result = ERROR_SUCCESS; 788 DWORD BinaryType; 789 790 /* Get the binary type */ 791 if (!GetBinaryTypeA(ProgramName, &BinaryType)) return GetLastError(); 792 793 /* Check the type of the program */ 794 switch (BinaryType) 795 { 796 /* Those are handled by NTVDM */ 797 case SCS_WOW_BINARY: 798 { 799 DisplayMessage(L"Trying to load '%S'.\n" 800 L"WOW16 applications are not supported internally by NTVDM at the moment.\n" 801 L"Press 'OK' to continue.", 802 ProgramName); 803 // Fall through 804 } 805 case SCS_DOS_BINARY: 806 { 807 /* Load the executable */ 808 Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE, 809 ProgramName, 810 Parameters, 811 NULL, 812 NULL, 813 ReturnAddress); 814 if (Result != ERROR_SUCCESS) 815 { 816 DisplayMessage(L"Could not load '%S'. Error: %u", ProgramName, Result); 817 } 818 819 break; 820 } 821 822 /* Not handled by NTVDM */ 823 default: 824 { 825 LPSTR Environment = NULL; 826 CHAR CmdLine[MAX_PATH + DOS_CMDLINE_LENGTH + 1]; 827 LPSTR CmdLinePtr; 828 ULONG CmdLineSize; 829 830 /* Did the caller specify an environment segment? */ 831 if (Parameters->Environment) 832 { 833 /* Yes, use it instead of the parent one */ 834 Environment = (LPSTR)SEG_OFF_TO_PTR(Parameters->Environment, 0); 835 } 836 837 /* 838 * Convert the DOS command line to Win32-compatible format, by concatenating 839 * the program name with the converted command line. 840 * Format of the DOS command line: 1 byte for size; 127 bytes for contents. 841 */ 842 CmdLinePtr = CmdLine; 843 strncpy(CmdLinePtr, ProgramName, MAX_PATH); // Concatenate the program name 844 CmdLinePtr += strlen(CmdLinePtr); 845 *CmdLinePtr++ = ' '; // Add separating space 846 847 CmdLineSize = min(*(PBYTE)FAR_POINTER(Parameters->CommandLine), DOS_CMDLINE_LENGTH); 848 RtlCopyMemory(CmdLinePtr, 849 (LPSTR)FAR_POINTER(Parameters->CommandLine) + 1, 850 CmdLineSize); 851 /* NULL-terminate it */ 852 CmdLinePtr[CmdLineSize] = '\0'; 853 854 /* Remove any trailing return carriage character and NULL-terminate the command line */ 855 while (*CmdLinePtr && *CmdLinePtr != '\r' && *CmdLinePtr != '\n') CmdLinePtr++; 856 *CmdLinePtr = '\0'; 857 858 Result = DosStartProcess32(ProgramName, CmdLine, 859 Environment, ReturnAddress, 860 TRUE); 861 if (Result != ERROR_SUCCESS) 862 { 863 DisplayMessage(L"Could not load 32-bit '%S'. Error: %u", ProgramName, Result); 864 } 865 866 break; 867 } 868 } 869 870 return Result; 871 } 872 873 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode, WORD KeepResident) 874 { 875 WORD McbSegment = SysVars->FirstMcb; 876 PDOS_MCB CurrentMcb; 877 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress); 878 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp); 879 LPWORD Stack; 880 BYTE TerminationType; 881 882 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X, KeepResident 0x%04X\n", 883 Psp, ReturnCode, KeepResident); 884 885 /* Notify VDDs of process termination */ 886 VDDTerminateUserHook(Psp); 887 888 /* Check if this PSP is its own parent */ 889 if (PspBlock->ParentPsp == Psp) goto Done; 890 891 if (KeepResident == 0) 892 { 893 WORD i; 894 for (i = 0; i < PspBlock->HandleTableSize; i++) 895 { 896 /* Close the handle */ 897 DosCloseHandle(i); 898 } 899 } 900 901 /* Free the memory used by the process */ 902 while (TRUE) 903 { 904 /* Get a pointer to the MCB */ 905 CurrentMcb = SEGMENT_TO_MCB(McbSegment); 906 907 /* Make sure the MCB is valid */ 908 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z') break; 909 910 /* Check if this block was allocated by the process */ 911 if (CurrentMcb->OwnerPsp == Psp) 912 { 913 if (KeepResident) 914 { 915 /* Check if this is the PSP block and we should reduce its size */ 916 if ((McbSegment + 1) == Psp && KeepResident < CurrentMcb->Size) 917 { 918 /* Reduce the size of the block */ 919 DosResizeMemory(McbSegment + 1, KeepResident, NULL); 920 break; 921 } 922 } 923 else 924 { 925 /* Free this entire block */ 926 DosFreeMemory(McbSegment + 1); 927 } 928 } 929 930 /* If this was the last block, quit */ 931 if (CurrentMcb->BlockType == 'Z') break; 932 933 /* Update the segment and continue */ 934 McbSegment += CurrentMcb->Size + 1; 935 } 936 937 Done: 938 /* Restore the interrupt vectors */ 939 IntVecTable[0x22] = PspBlock->TerminateAddress; 940 IntVecTable[0x23] = PspBlock->BreakAddress; 941 IntVecTable[0x24] = PspBlock->CriticalAddress; 942 943 /* Update the current PSP with the parent's one */ 944 if (Psp == Sda->CurrentPsp) 945 { 946 DosSetProcessContext(PspBlock->ParentPsp); 947 if (Sda->CurrentPsp == SYSTEM_PSP) 948 { 949 // NOTE: we can also use the DOS BIOS exit code. 950 CpuUnsimulate(); 951 return; 952 } 953 } 954 955 /* Save the return code - Normal termination or TSR */ 956 TerminationType = (KeepResident != 0 ? 0x03 : 0x00); 957 Sda->ErrorLevel = MAKEWORD(ReturnCode, TerminationType); 958 959 DPRINT1("PspBlock->ParentPsp = 0x%04x; Sda->CurrentPsp = 0x%04x\n", 960 PspBlock->ParentPsp, Sda->CurrentPsp); 961 962 if (Sda->CurrentPsp != SYSTEM_PSP) 963 { 964 DPRINT1("Sda->CurrentPsp = 0x%04x; Old SS:SP = %04X:%04X going to be LastStack = 0x%08x\n", 965 Sda->CurrentPsp, getSS(), getSP(), SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack); 966 967 /* Restore the parent's stack */ 968 setSS(HIWORD(SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack)); 969 setSP(LOWORD(SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack)); 970 971 /* Pop the task state */ 972 DosRestoreState(); 973 } 974 975 /* Return control to the parent process */ 976 Stack = (LPWORD)SEG_OFF_TO_PTR(getSS(), getSP()); 977 Stack[STACK_CS] = HIWORD(PspBlock->TerminateAddress); 978 Stack[STACK_IP] = LOWORD(PspBlock->TerminateAddress); 979 } 980 981 /* EOF */ 982