phys_read/write functions are not used often enough to worry too much about performance, so to avoid emulator segfaults, put the system memory range check in the phys_read/write functions themselves. Add comments about the meaning of MemBase and MemSize and how they will be used when DOSBox-X eventually supports memory sizes larger than 4GB

This commit is contained in:
Jonathan Campbell 2024-12-22 13:59:42 -08:00
parent 205480815e
commit 3f41eb6123
3 changed files with 55 additions and 23 deletions

View File

@ -39,6 +39,7 @@ typedef uint64_t PhysPt64; /* guest physical memory pointer */
typedef int32_t MemHandle;
extern HostPt MemBase;
extern size_t MemSize;
HostPt GetMemBase(void);
bool MEM_A20_Enabled(void);
@ -184,25 +185,59 @@ void phys_writes(PhysPt addr, const char* string, Bitu length);
/* WARNING: These will cause a segfault or out of bounds access IF
* addr is beyond the end of memory */
/* 2024/12/22: Looking across the DOSBox-X codebase, these functions
* aren't used TOO often, and where they are used, some
* code has memory range checks anyway. So it doesn't hurt
* performance very much if at all to just put the range
* check here instead, in order not to segfault if somehow
* the address given is beyond end of system memory. --J.C.
*
* There is one more important detail here. These functions
* take only a 32-bit physical address. Which means if more
* than 32 address bits are enabled on the CPU and the OS
* has PSE/PAE page tables enabled, these functions will not
* be able to reach above 4GB. Given how memory will be
* segmented between the 'below 4GB' and 'above 4GB' regions,
* if emulating 4GB or more, that is perfectly fine. S3 XGA
* and ISA DMA emulation will never reach above 4GB anyway.
*
* The way the range check is done is ideal for performance,
* yet may fail to work correctly if MemSize is very close
* to zero, low enough that subtraction would cause it to
* wrap back around to the largest possible value. The code,
* when MemBase is a valid pointer, will never set MemSize
* that small. */
static INLINE void phys_writeb(const PhysPt addr,const uint8_t val) {
host_writeb(MemBase+addr,val);
if (addr < MemSize)
host_writeb(MemBase+addr,val);
}
static INLINE void phys_writew(const PhysPt addr,const uint16_t val) {
host_writew(MemBase+addr,val);
if (addr < (MemSize-1u))
host_writew(MemBase+addr,val);
}
static INLINE void phys_writed(const PhysPt addr,const uint32_t val) {
host_writed(MemBase+addr,val);
if (addr < (MemSize-3u))
host_writed(MemBase+addr,val);
}
static INLINE uint8_t phys_readb(const PhysPt addr) {
return host_readb(MemBase+addr);
if (addr < MemSize)
return host_readb(MemBase+addr);
else
return 0xFF;
}
static INLINE uint16_t phys_readw(const PhysPt addr) {
return host_readw(MemBase+addr);
if (addr < (MemSize-1u))
return host_readw(MemBase+addr);
else
return 0xFFFFu;
}
static INLINE uint32_t phys_readd(const PhysPt addr) {
return host_readd(MemBase+addr);
if (addr < (MemSize-3u))
return host_readd(MemBase+addr);
else
return 0xFFFFFFFFu;
}
/* These don't check for alignment, better be sure it's correct */

View File

@ -797,14 +797,8 @@ initpage_retry:
PhysPt dirEntryAddr = GetPageDirectoryEntryAddr(lin_addr);
// Range check to avoid emulator segfault: phys_readd() reads from MemBase+addr and does NOT range check.
// Needed to avoid segfault when running 1999 demo "Void Main" in a bootable Windows 95 image in pure DOS mode.
if ((dirEntryAddr+4) <= (MEM_TotalPages()<<12u)) {
dir_entry.load=phys_readd(dirEntryAddr);
}
else {
LOG(LOG_CPU,LOG_WARN)("Page directory access beyond end of memory, page %08x >= %08x",
(unsigned int)(dirEntryAddr>>12u),(unsigned int)MEM_TotalPages());
dir_entry.load=0xFFFFFFFF;
}
// 2024/12/22: phys_readx() does range checking now
dir_entry.load=phys_readd(dirEntryAddr);
if (!dir_entry.dirblock.p) {
// table pointer is not present, do a page fault
@ -851,14 +845,8 @@ initpage_retry:
else {
PhysPt tableEntryAddr = GetPageTableEntryAddr(lin_addr, dir_entry);
// Range check to avoid emulator segfault: phys_readd() reads from MemBase+addr and does NOT range check.
if ((tableEntryAddr+4) <= (MEM_TotalPages()<<12u)) {
table_entry.load=phys_readd(tableEntryAddr);
}
else {
LOG(LOG_CPU,LOG_WARN)("Page table entry access beyond end of memory, page %08x >= %08x",
(unsigned int)(tableEntryAddr>>12u),(unsigned int)MEM_TotalPages());
table_entry.load=0xFFFFFFFF;
}
// 2024/12/22: phys_readx() does range checking now
table_entry.load=phys_readd(tableEntryAddr);
// set page table accessed (IA manual: A is set whenever the entry is
// used in a page translation)

View File

@ -215,7 +215,14 @@ uint32_t MEM_get_address_bits4GB() { /* some code cannot yet handle values large
return memory.address_bits;
}
/* WARNING: When DOSBox-X enables emulation of more than 4GB of RAM, this MemBase and MemSize will only reflect the memory below 4GB.
* Which means phys_readx/writex(), which are limited to the first 4GB anyway (32-bit addresses), cannot be used to poke at
* memory above 4GB. Instead of extending MemBase and the singular allocation block to 4GB or larger, the memory above 4GB
* is a different block. The reason for this is that the gap that needs to be left open for PCI devices and the ROM BIOS is
* large enough that such an arrangement would lead to the waste of about 64MB of emulator memory, which is significant, while
* the 384KB wasted at the 8086 1MB limit is too small to worry about. */
HostPt MemBase = NULL;
size_t MemSize = 0;
class UnmappedPageHandler : public PageHandler {
public:
@ -1333,7 +1340,7 @@ void mem_writed(PhysPt address,uint32_t val) {
}
void phys_writes(PhysPt addr, const char* string, Bitu length) {
for(Bitu i = 0; i < length; i++) host_writeb(MemBase+addr+i,(uint8_t)string[i]);
for(Bitu i = 0; i < length && (addr+i) < MemSize; i++) host_writeb(MemBase+addr+i,(uint8_t)string[i]);
}
#include "control.h"
@ -1882,6 +1889,7 @@ void ShutDownRAM(Section * sec) {
#endif
MemBase = NULL;
}
MemSize = 0;
ACPI_free();
}
@ -2014,6 +2022,7 @@ void Init_RAM() {
#else // C_GAMELINK
MemBase = new(std::nothrow) uint8_t[memory.pages*4096];
#endif // C_GAMELINK
MemSize = size_t(memory.pages*4096);
if (!MemBase) E_Exit("Can't allocate main memory of %d KB",(int)memsizekb);
/* Clear the memory, as new doesn't always give zeroed memory
* (Visual C debug mode). We want zeroed memory though. */