BATCHFILE = """\ c:\\%s rem end """ CONFIG = """\ $_hdimage = "dXXXXs/c:hdtype1 +1" $_floppy_a = "" """ def memory_hma_freespace(self): if 'FDPP' in self.version: self.mkfile("userhook.sys", """DOS=HIGH\n""", newline="\r\n") self.mkfile("testit.bat", BATCHFILE % 'hmaspace', newline="\r\n") self.mkcom_with_ia16("hmaspace", r""" #include #include #include int main(int argc, char *argv[]) { union REGS r = {}; struct SREGS rs; int ret = 0; r.x.ax = 0x4a01; // get hma free space int86x(0x2f, &r, &r, &rs); if (r.x.bx == 0) { printf("INFO: DOS not using HMA(BX==0)\n"); ret += 1; } if (rs.es == 0xffff && r.x.di == 0xffff) { printf("INFO: DOS not using HMA(ES:DI==FFFF:FFFF)\n"); ret += 1; } if (ret != 0) { printf("FAIL: No HMA available\n"); return ret; } printf("INFO: HMA free space == 0x%04x\n", r.x.bx); printf("INFO: HMA area at %04X:%04X\n", rs.es, r.x.di); printf("PASS: HMA available\n"); return 0; } """) results = self.runDosemu("testit.bat", config=CONFIG) self.assertIn("PASS:", results) def memory_hma_alloc(self): if 'FDPP' in self.version: self.mkfile("userhook.sys", """DOS=HIGH\n""", newline="\r\n") self.mkfile("testit.bat", BATCHFILE % 'hmaalloc', newline="\r\n") self.mkcom_with_ia16("hmaalloc", r""" #include #include #include int main(int argc, char *argv[]) { union REGS r = {}; struct SREGS rs; int ret = 0; r.x.ax = 0x4a02; // get hma memory block r.x.bx = 35; // alloc 35 bytes, should get rounded up to 48 int86x(0x2f, &r, &r, &rs); printf("INFO: HMA size returned %d\n", r.x.bx); printf("INFO: HMA block allocated at %04X:%04X\n", rs.es, r.x.di); if (r.x.bx != 48) { printf("WARN: HMA returned size unexpected\n"); ret += 1; } if (rs.es != 0xffff) { printf("WARN: HMA block allocated in wrong segment\n"); ret += 1; } if (ret != 0) { printf("FAIL: Test failed\n"); return ret; } printf("PASS: HMA allocation successful\n"); return 0; } """) results = self.runDosemu("testit.bat", config=CONFIG) self.assertIn("PASS:", results) def memory_hma_alloc3(self): if 'FDPP' in self.version: self.mkfile("userhook.sys", """DOS=HIGH\n""", newline="\r\n") self.mkfile("testit.bat", BATCHFILE % 'hmaalloc', newline="\r\n") self.mkcom_with_ia16("hmaalloc", r""" #include #include #include struct HMCB { uint8_t signature[2]; // "MS" uint16_t owner; // 0000=free, 0001=DOS, FF33=IO.SYS, FFFF=MSDOS.SYS uint16_t size; // bytes not including this header uint16_t next; // offset of next memory block in segment FFFFh, or 0000h if last uint8_t reserved[8]; // unused (explicitly set to 0 for MS-DOS 7.10) }; /* Windows95 - DOS KERNEL - (DE)ALLOCATE HMA MEMORY BLOCK AX = 4A03h CX = segment of block's owner??? DL = subfunction 00h allocate block BX = number of bytes Return: DI=FFFFh if unable to allocate ES:DI -> allocated block // We do get the allocated size in BX, RBIL misses this 01h resize block // Seemingly can grow as well as shrink ES:DI -> previously-allocated block BX = new size in bytes Return: DI=FFFFh if unable to allocate ES:DI -> reallocated block // Maybe we get the reallocated size in BX Win95 FE & SE never seem to free the initial block after creating the replacement. In addition, subsequent frees of the replacement block fail. 02h free block ES:DI -> block to be freed Return: Nothing, but checking the ID in the requested block can indicate whether the block was freed. */ int main(int argc, char *argv[]) { union REGS r = {}; struct SREGS rs; int ret; struct HMCB hmcb; uint16_t hma_seg, hma_off; // Alloc subfunction ret = 0; r.x.ax = 0x4a03; // hma alloc/realloc/free memory block r.x.bx = 35; // alloc 35 bytes, should get rounded up to 48 r.x.cx = __libi86_get_cs(); // segment of block's owner r.h.dl = 0; // subfunction 0 - alloc printf("INFO: Int2f/4a03, dl=0 alloc\n"); int86x(0x2f, &r, &r, &rs); printf("INFO: HMA size returned %d\n", r.x.bx); printf("INFO: HMA block allocated at %04X:%04X\n", rs.es, r.x.di); // Copy back to near structure to make printfs easier memset(&hmcb, 0, sizeof(hmcb)); _fmemcpy(&hmcb, MK_FP(rs.es, r.x.di - 0x10), sizeof(hmcb)); printf("INFO: \"%c%c\" // signature\n", hmcb.signature[0], hmcb.signature[1]); printf("INFO: %04X // owner, our CS = %04X\n", hmcb.owner, __libi86_get_cs()); printf("INFO: %04X // size (hex bytes)\n", hmcb.size); printf("INFO: %04X // offset to next HMA block\n", hmcb.next); if (r.x.di == 0xffff) { printf("WARN: HMA returned offset indicates failure (DI == 0xffff)\n"); ret += 1; } if (r.x.bx != 48) { printf("WARN: HMA returned size unexpected (BX == %d)\n", r.x.bx); ret += 1; } if (r.x.bx != hmcb.size) { printf("WARN: hmcb.size != HMA returned size (%d != %d)\n", hmcb.size, r.x.bx); ret += 1; } if (ret != 0) { printf("FAIL: Alloc test failed\n"); return ret; } hma_seg = rs.es; hma_off = r.x.di; printf("\n"); #if 0 // Disable resize tests as the function doesn't seem to work on Win95 SE // allocating multiple blocks and preventing final free. // Realloc subfunction (shrink block) ret = 0; r.x.ax = 0x4a03; // hma alloc/realloc/free memory block r.x.bx = 29; // realloc 29 bytes, should get rounded up to 32 r.x.cx = __libi86_get_cs(); // segment of block's owner r.h.dl = 1; // subfunction 1 - realloc rs.es = hma_seg; // old block seg:off r.x.di = hma_off; printf("INFO: Int2f/4a03, dl=1 realloc (shrink)(%04X:%04X)\n", rs.es, r.x.di); int86x(0x2f, &r, &r, &rs); printf("INFO: HMA size returned %d\n", r.x.bx); printf("INFO: HMA block reallocated at %04X:%04X\n", rs.es, r.x.di); // Copy back to near structure to make printfs easier memset(&hmcb, 0, sizeof(hmcb)); _fmemcpy(&hmcb, MK_FP(rs.es, r.x.di - 0x10), sizeof(hmcb)); printf("INFO: \"%c%c\" // signature\n", hmcb.signature[0], hmcb.signature[1]); printf("INFO: %04X // owner, our CS = %04X\n", hmcb.owner, __libi86_get_cs()); printf("INFO: %04X // size (hex bytes)\n", hmcb.size); printf("INFO: %04X // offset to next HMA block\n", hmcb.next); if (r.x.di == 0xffff) { printf("WARN: HMA returned offset indicates failure (DI == 0xffff)\n"); ret += 1; } if (r.x.bx != 32) { printf("WARN: HMA returned size unexpected (BX == %d)\n", r.x.bx); ret += 1; } if (r.x.bx != hmcb.size) { printf("WARN: hmcb.size != HMA returned size (%d != %d)\n", hmcb.size, r.x.bx); ret += 1; } if (ret != 0) { printf("FAIL: Realloc(shrink) test failed\n"); return ret; } hma_seg = rs.es; hma_off = r.x.di; printf("\n"); // Realloc subfunction (grow block) ret = 0; r.x.ax = 0x4a03; // hma alloc/realloc/free memory block r.x.bx = 65; // realloc 65 bytes, should get rounded up to 80 r.x.cx = __libi86_get_cs(); // segment of block's owner r.h.dl = 1; // subfunction 1 - realloc rs.es = hma_seg; // old block seg:off r.x.di = hma_off; printf("INFO: Int2f/4a03, dl=1 realloc (grow)(%04X:%04X)\n", rs.es, r.x.di); int86x(0x2f, &r, &r, &rs); printf("INFO: HMA size returned %d\n", r.x.bx); printf("INFO: HMA block reallocated at %04X:%04X\n", rs.es, r.x.di); // Copy back to near structure to make printfs easier memset(&hmcb, 0, sizeof(hmcb)); _fmemcpy(&hmcb, MK_FP(rs.es, r.x.di - 0x10), sizeof(hmcb)); printf("INFO: \"%c%c\" // signature\n", hmcb.signature[0], hmcb.signature[1]); printf("INFO: %04X // owner, our CS = %04X\n", hmcb.owner, __libi86_get_cs()); printf("INFO: %04X // size (hex bytes)\n", hmcb.size); printf("INFO: %04X // offset to next HMA block\n", hmcb.next); if (r.x.di == 0xffff) { printf("WARN: HMA returned offset indicates failure (DI == 0xffff)\n"); ret += 1; } if (r.x.bx != 80) { printf("WARN: HMA returned size unexpected (BX == %d)\n", r.x.bx); ret += 1; } if (r.x.bx != hmcb.size) { printf("WARN: hmcb.size != HMA returned size (%d != %d)\n", hmcb.size, r.x.bx); ret += 1; } if (ret != 0) { printf("FAIL: Realloc(grow) test failed\n"); return ret; } hma_seg = rs.es; hma_off = r.x.di; printf("\n"); #endif // Free block subfunction ret = 0; r.x.ax = 0x4a03; // hma alloc/realloc/free memory block r.x.cx = __libi86_get_cs(); // segment of block's owner r.h.dl = 2; // subfunction 2 - free rs.es = hma_seg; // old block seg:off r.x.di = hma_off; printf("INFO: Int2f/4a03, dl=2 free (%04X:%04X)\n", rs.es, r.x.di); int86x(0x2f, &r, &r, &rs); // Free doesn't return anything to indicate success, so we have to check the // requested block to see if it's still marked as ours // Copy back to near structure to make access easier memset(&hmcb, 0, sizeof(hmcb)); _fmemcpy(&hmcb, MK_FP(rs.es, r.x.di - 0x10), sizeof(hmcb)); if (hmcb.signature[0] == 'M' && hmcb.signature[1] == 'S' && hmcb.owner == __libi86_get_cs()) { printf("WARN: Freed block still marked as ours\n"); ret += 1; } if (ret != 0) { printf("FAIL: Free test failed\n"); return ret; } printf("\n"); printf("PASS: HMA alloc/realloc/free successful\n"); return 0; } """) results = self.runDosemu("testit.bat", config=CONFIG) self.assertIn("PASS:", results) def memory_hma_chain(self): if 'FDPP' in self.version: self.mkfile("userhook.sys", """DOS=HIGH\n""", newline="\r\n") self.mkfile("testit.bat", BATCHFILE % 'hmachain', newline="\r\n") self.mkcom_with_ia16("hmachain", r""" #include #include #include struct HMCB { uint8_t signature[2]; // "MS" uint16_t owner; // 0000=free, 0001=DOS, FF33=IO.SYS, FFFF=MSDOS.SYS uint16_t size; // bytes not including this header uint16_t next; // offset of next memory block in segment FFFFh, or 0000h if last uint8_t reserved[8]; // unused (explicitly set to 0 for MS-DOS 7.10) }; /* INT 2F U - Windows95 - DOS KERNEL - GET START OF HMA MEMORY CHAIN AX = 4A04h Return: AX = 0000h if function supported ES:DI -> first HMA memory control block (see #02800) */ int main(int argc, char *argv[]) { union REGS r = {}; struct SREGS rs; int ret; struct HMCB hmcb; // Alloc subfunction ret = 0; r.x.ax = 0x4a04; // get start of hma chain printf("INFO: Int2f/4a04\n"); int86x(0x2f, &r, &r, &rs); if (r.x.ax != 0) { printf("WARN: HMA get chain returned (0x%04x), unsupported\n", r.x.ax); ret += 1; } else { printf("INFO: HMA head at %04X:%04X\n", rs.es, r.x.di); // Copy back to near structure to make printfs easier memset(&hmcb, 0, sizeof(hmcb)); _fmemcpy(&hmcb, MK_FP(rs.es, r.x.di), sizeof(hmcb)); printf("INFO: \"%c%c\" // signature\n", hmcb.signature[0], hmcb.signature[1]); printf("INFO: %04X // owner\n", hmcb.owner); printf("INFO: %04X // size (hex bytes)\n", hmcb.size); printf("INFO: %04X // offset to next HMA block\n", hmcb.next); if (hmcb.signature[0] != 'M' || hmcb.signature[1] != 'S') { printf("WARN: HMA head signature incorrect\n"); ret += 1; } } if (ret != 0) { printf("FAIL: HMA get chain failed\n"); return ret; } printf("PASS: HMA get chain successful\n"); return 0; } """) results = self.runDosemu("testit.bat", config=CONFIG) self.assertIn("PASS:", results) def memory_hma_a20(self): if 'FDPP' in self.version: self.mkfile("userhook.sys", """DOS=HIGH\n""", newline="\r\n") self.mkfile("testit.bat", BATCHFILE % 'hmaa20', newline="\r\n") self.mkcom_with_ia16("hmaa20", r""" #include #include #include #define TSTRING "HELLO THERE" int main(int argc, char *argv[]) { union REGS r = {}; struct SREGS rs; char testbuf[sizeof(TSTRING)]; char savebuf[sizeof(TSTRING)]; char __far *hma_location; char __far *cnv_location; int ret = 0; int a20; int res1, res2, val1, val2; r.x.ax = 0x4a02; // get hma memory block r.x.bx = 35; // alloc 35 bytes, should get rounded up to 48 int86x(0x2f, &r, &r, &rs); printf("INFO: HMA size returned %d\n", r.x.bx); printf("INFO: HMA block allocated at %04X:%04X\n", rs.es, r.x.di); if (rs.es != 0xffff) { printf("WARN: HMA block allocated in wrong segment\n"); ret += 1; } // Note: wrapped address will always be in first segment hma_location = MK_FP(rs.es, r.x.di); cnv_location = MK_FP(0x0000, ((rs.es << 4) + r.x.di) & 0xffff); _disable(); // Get A20 status r.x.ax = 0x2402; r.x.cflag = 1; int86x(0x15, &r, &r, &rs); if (!r.x.cflag) { a20 = r.h.al; } else { a20 = 1; // default to on } res1 = r.x.cflag; val1 = r.h.al; val2 = r.h.ah; // Save memory _fmemcpy(savebuf, cnv_location, sizeof(TSTRING)); // Write to HMA memory _fmemcpy(hma_location, TSTRING, sizeof(TSTRING)); // Read from the wrapped address _fmemcpy(testbuf, cnv_location, sizeof(TSTRING)); // Restore corrupted memory as soon as possible _fmemcpy(cnv_location, savebuf, sizeof(TSTRING)); _enable(); if (!res1) { printf("INFO: A20 is initially %s\n", val1 == 0 ? "disabled" : "enabled"); } else { printf("WARN: A20 status could not be determined (0x%02x)\n", val2); ret += 1; } // Compare to make sure we are not writing in the wrong place if (memcmp(testbuf, TSTRING, sizeof(TSTRING)) == 0) { printf("WARN: Wrapping is evident\n"); ret += 1; } else { printf("INFO: Wrapping is not evident\n"); } _disable(); // Disable the A20 to enable the wrapping of memory r.x.ax = 0x2400; r.x.cflag = 1; int86x(0x15, &r, &r, &rs); res1 = r.x.cflag; if (res1) val1 = r.h.ah; // Save memory _fmemcpy(savebuf, cnv_location, sizeof(TSTRING)); // Write to HMA memory _fmemcpy(hma_location, TSTRING, sizeof(TSTRING)); // Read from the wrapped address _fmemcpy(testbuf, cnv_location, sizeof(TSTRING)); // Restore corrupted memory as soon as possible _fmemcpy(cnv_location, savebuf, sizeof(TSTRING)); // Maybe reenable the A20 to return to normal if (a20) { r.x.ax = 0x2401; r.x.cflag = 1; int86x(0x15, &r, &r, &rs); res2 = r.x.cflag; val2 = r.h.ah; } _enable(); if (!res1) { printf("INFO: A20 successfully disabled\n"); } else { printf("WARN: A20 could not be disabled (0x%02x)\n", val1); ret += 1; } if (a20) if (!res2) { printf("INFO: A20 successfully reenabled\n"); } else { printf("WARN: A20 could not be reenabled\n (0x%02x)\n", val2); ret += 1; } // Compare if (memcmp(testbuf, TSTRING, sizeof(TSTRING)) == 0) { printf("INFO: Wrapping is evident and expected\n"); } else { printf("WARN: Wrapping is not evident\n"); ret += 1; } if (ret != 0) { printf("FAIL: Test failed\n"); return ret; } printf("PASS: HMA allocation successful\n"); return 0; } """) results = self.runDosemu("testit.bat", config=CONFIG) self.assertIn("PASS:", results)