01291b45065bcbc606b10b57416faa8880f7cc3a
[reactos.git] / subsystems / mvdm / ntvdm / dos / dos32krnl / process.c
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 ULONG 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 */