If a DOS program shrinks it's own PSP segment down, corrupts the MCB block following it, and them does anything that causes allocation, follow apparent MS-DOS behavior by writing a new free MCB block following the PSP segment instead of crashing the emulation with an MCB chain corruption error.

This commit is contained in:
Jonathan Campbell
2025-10-06 00:23:19 -07:00
parent 6951493dc0
commit cbe963bc81

View File

@@ -28,6 +28,7 @@
// uncomment for alloc/free debug messages
#define DEBUG_ALLOC
uint16_t CONV_MAX_SEG = 0xA000;
Bitu UMB_START_SEG = 0x9FFF;
/* FIXME: This should be a variable that reflects the last RAM segment.
* That means 0x9FFF if 640KB or more, or a lesser value if less than 640KB */
@@ -38,7 +39,7 @@ uint16_t first_umb_size = 0x2000;
static uint16_t memAllocStrategy = 0x00;
static void DOS_Mem_E_Exit(const char *msg) {
static void DOS_Mem_MCBdump(void) {
uint16_t mcb_segment=dos.firstMCB;
DOS_MCB mcb(mcb_segment);
DOS_MCB mcb_next(0);
@@ -65,6 +66,10 @@ static void DOS_Mem_E_Exit(const char *msg) {
LOG_MSG("FINAL: Type=0x%02x(%c) Seg=0x%04x size=0x%04x name='%s'\n",
mcb.GetType(),c,mcb_segment+1,mcb.GetSize(),name);
LOG_MSG("End dump\n");
}
static void DOS_Mem_E_Exit(const char *msg) {
DOS_Mem_MCBdump();
#if C_DEBUG
LOG_MSG("DOS fatal memory error: %s",msg);
@@ -74,7 +79,7 @@ static void DOS_Mem_E_Exit(const char *msg) {
#endif
}
void DOS_CompressMemory(uint16_t first_segment=0/*default*/) {
void DOS_CompressMemory(uint16_t first_segment=0/*default*/,uint32_t healfrom=0xFFFFu) {
uint16_t mcb_segment=dos.firstMCB;
DOS_MCB mcb(mcb_segment);
DOS_MCB mcb_next(0);
@@ -82,8 +87,26 @@ void DOS_CompressMemory(uint16_t first_segment=0/*default*/) {
while (mcb.GetType()!='Z') {
if(counter++ > 10000000) DOS_Mem_E_Exit("DOS_CompressMemory: DOS MCB list corrupted.");
mcb_next.SetPt((uint16_t)(mcb_segment+mcb.GetSize()+1));
if (GCC_UNLIKELY((mcb_next.GetType()!=0x4d) && (mcb_next.GetType()!=0x5a))) DOS_Mem_E_Exit("Corrupt MCB chain");
const uint16_t nseg = (uint16_t)(mcb_segment+mcb.GetSize()+1);
mcb_next.SetPt(nseg);
if (GCC_UNLIKELY((mcb_next.GetType()!=0x4d) && (mcb_next.GetType()!=0x5a))) {
/* there are some programs that chain load other programs, but when they shrink their MCB down
* to their EXE resident size they put the stack or other data in the way of the next MCB free
* block header. Real MS-DOS appears in this case to just scan up to the last valid block and
* then right after it, write a new free MCB block there. We can't do this for the split memory
* layout of the PCjr emulation. */
if (nseg >= healfrom && (nseg+1u) < CONV_MAX_SEG && !(machine==MCH_PCJR)) {
LOG(LOG_DOSMISC,LOG_ERROR)("Corrupted MCB chain, but within the possible memory region of the DOS application.");
DOS_Mem_MCBdump();
LOG(LOG_DOSMISC,LOG_ERROR)("Declaring all memory past it as a free block. This is apparently MS-DOS behavior.");
mcb_next.SetSize(CONV_MAX_SEG - (nseg + 1u));
mcb_next.SetPSPSeg(MCB_FREE);
mcb_next.SetType('Z');
}
else {
DOS_Mem_E_Exit("Corrupt MCB chain");
}
}
if (mcb_segment >= first_segment && (mcb.GetPSPSeg()==MCB_FREE) && (mcb_next.GetPSPSeg()==MCB_FREE)) {
mcb.SetSize(mcb.GetSize()+mcb_next.GetSize()+1);
mcb.SetType(mcb_next.GetType());
@@ -184,7 +207,12 @@ uint16_t DOS_GetMaximumFreeSize(uint16_t minBlocks)
}
bool DOS_AllocateMemory(uint16_t * segment,uint16_t * blocks) {
DOS_CompressMemory();
DOS_MCB psp_mcb(dos.psp()-1);
DOS_CompressMemory(
0/*default scan*/,
dos.psp()+psp_mcb.GetSize()/*auto-heal broken MCB chain if it occurs anywhere past the PSP memory block*/);
uint16_t bigsize=0;
uint16_t mem_strat=memAllocStrategy;
uint16_t mcb_segment=dos.firstMCB;
@@ -197,7 +225,6 @@ bool DOS_AllocateMemory(uint16_t * segment,uint16_t * blocks) {
DOS_MCB mcb(0);
DOS_MCB mcb_next(0);
DOS_MCB psp_mcb(dos.psp()-1);
char psp_name[9];
psp_mcb.GetFileName(psp_name);
if (umb_start==UMB_START_SEG && (dos.loaded_codepage == 936 || dos.loaded_codepage == 950 || dos.loaded_codepage == 951)) {
@@ -640,6 +667,7 @@ void DOS_SetupMemory(void) {
/* map memory as normal, the BIOS initialization is the code responsible
* for subtracting 32KB from top of system memory for video memory. */
mcb.SetSize(/*normally 0x97FF*/(seg_limit-1) - DOS_MEM_START - mcb_sizes);
CONV_MAX_SEG = seg_limit;
} else if (machine==MCH_PCJR) {
/* If there is more than 128KB of RAM, then the MCB chain must be constructed
* to exclude video memory. In that case, the BIOS values in the BDA will report
@@ -656,6 +684,7 @@ void DOS_SetupMemory(void) {
mcb_devicedummy.SetPSPSeg(MCB_FREE);
mcb_devicedummy.SetSize(/*0x9FFF*/(seg_limit-1) - 0x2000);
mcb_devicedummy.SetType(0x5a);
CONV_MAX_SEG = seg_limit;
/* exclude PCJr graphics region */
mcb_devicedummy.SetPt((uint16_t)0x17ff);
@@ -670,6 +699,7 @@ void DOS_SetupMemory(void) {
else {
/* Normal MCB chain, nothing special */
mcb.SetSize(/*normally 0x97FF*/(seg_limit-1) - DOS_MEM_START - mcb_sizes);
CONV_MAX_SEG = seg_limit;
}
} else {
#ifndef DEBUG_ALLOC
@@ -684,6 +714,7 @@ void DOS_SetupMemory(void) {
/* complete memory up to 640k available */
/* last paragraph used to add UMB chain to low-memory MCB chain */
mcb.SetSize(/*0x9FFE*/(seg_limit-2) - DOS_MEM_START - mcb_sizes);
CONV_MAX_SEG = seg_limit-1;
}
dos.firstMCB=DOS_MEM_START;