Improve waterbox "lazystates"
Cores that used the .invisible section to store data were saving it; this was a regression from before, so PCFX states should be back down to the previous release size, or perhaps a bit smaller. Add the ability to dirty track libco cothreads, as used in the bsnes core. This saves a lot of space in those states and they're now quite competitive in size.
This commit is contained in:
parent
c4586545fa
commit
7792eb2e80
Binary file not shown.
|
@ -21,7 +21,7 @@ namespace BizHawk.BizInvoke
|
|||
void Deactivate();
|
||||
/// <summary>
|
||||
/// Change protection on [start, start + size), guaranteed to be page aligned and in the committed area.
|
||||
/// Will only be called when active.
|
||||
/// Will only be called when active. Will not be called with RW_Invisible, which is a front end artifact.
|
||||
/// </summary>
|
||||
void Protect(ulong start, ulong size, MemoryBlock.Protection prot);
|
||||
/// <summary>
|
||||
|
@ -38,7 +38,11 @@ namespace BizHawk.BizInvoke
|
|||
/// automatically changing to RW and setting DidChange
|
||||
/// </summary>
|
||||
/// <param name="dest">Caller-owned array that the PAL will overwrite with page data</param>
|
||||
void GetWriteStatus(WriteDetectionStatus[] dest);
|
||||
/// <param name="pagedata">
|
||||
/// Caller-owned array that should indicate which areas were set to RW_Stack.
|
||||
/// Will not be modified by callee. Some implementations need this to get all of the correct information in dest.
|
||||
/// </param>
|
||||
void GetWriteStatus(WriteDetectionStatus[] dest, MemoryBlock.Protection[] pagedata);
|
||||
/// <summary>
|
||||
/// Sets the current write detection status on each page in the block. Pages marked with CanChange
|
||||
/// that are also committed and set to R, will not trigger a segmentation violation on write; instead
|
||||
|
|
|
@ -121,7 +121,7 @@ namespace BizHawk.BizInvoke
|
|||
{
|
||||
EnsureActive();
|
||||
if (_sealed)
|
||||
_pal.GetWriteStatus(_dirtydata);
|
||||
_pal.GetWriteStatus(_dirtydata, _pageData);
|
||||
_pal.Deactivate();
|
||||
Active = false;
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ namespace BizHawk.BizInvoke
|
|||
if (length == 0)
|
||||
return;
|
||||
if (_sealed)
|
||||
_pal.GetWriteStatus(_dirtydata);
|
||||
_pal.GetWriteStatus(_dirtydata, _pageData);
|
||||
|
||||
// Note: asking for prot.none on memory that was not previously committed, commits it
|
||||
|
||||
|
@ -166,7 +166,8 @@ namespace BizHawk.BizInvoke
|
|||
for (int i = pstart; i <= pend; i++)
|
||||
{
|
||||
_pageData[i] = prot;
|
||||
if (prot == Protection.RW)
|
||||
// inform the low level code what addresses might fault on it
|
||||
if (prot == Protection.RW || prot == Protection.RW_Stack)
|
||||
_dirtydata[i] |= WriteDetectionStatus.CanChange;
|
||||
else
|
||||
_dirtydata[i] &= ~WriteDetectionStatus.CanChange;
|
||||
|
@ -192,12 +193,35 @@ namespace BizHawk.BizInvoke
|
|||
ulong zstart = GetStartAddr(ps);
|
||||
ulong zend = GetStartAddr(i + 1);
|
||||
var prot = _pageData[i];
|
||||
if (_sealed && prot == Protection.RW)
|
||||
// adjust frontend notion of prot to the PAL layer's expectation
|
||||
if (prot == Protection.RW_Stack)
|
||||
{
|
||||
if (!_sealed)
|
||||
{
|
||||
// don't activate this protection yet
|
||||
prot = Protection.RW;
|
||||
}
|
||||
else
|
||||
{
|
||||
var didChange = (_dirtydata[i] & WriteDetectionStatus.DidChange) != 0;
|
||||
if (didChange)
|
||||
// don't needlessly retrigger
|
||||
prot = Protection.RW;
|
||||
}
|
||||
}
|
||||
else if (prot == Protection.RW_Invisible)
|
||||
{
|
||||
// this never matters to the backend
|
||||
prot = Protection.RW;
|
||||
}
|
||||
else if (_sealed && prot == Protection.RW)
|
||||
{
|
||||
var didChange = (_dirtydata[i] & WriteDetectionStatus.DidChange) != 0;
|
||||
if (!didChange)
|
||||
// set to trigger if we have not before
|
||||
prot = Protection.R;
|
||||
}
|
||||
|
||||
_pal.Protect(zstart, zend - zstart, prot);
|
||||
ps = i + 1;
|
||||
}
|
||||
|
@ -228,7 +252,7 @@ namespace BizHawk.BizInvoke
|
|||
{
|
||||
EnsureActive();
|
||||
EnsureSealed();
|
||||
_pal.GetWriteStatus(_dirtydata);
|
||||
_pal.GetWriteStatus(_dirtydata, _pageData);
|
||||
|
||||
w.Write(MAGIC);
|
||||
w.Write(Start);
|
||||
|
@ -244,7 +268,7 @@ namespace BizHawk.BizInvoke
|
|||
ulong endAddr = Start + CommittedSize;
|
||||
for (p = 0, addr = Start; addr < endAddr; p++, addr += 4096)
|
||||
{
|
||||
if ((_dirtydata[p] & WriteDetectionStatus.DidChange) != 0)
|
||||
if (_pageData[p] != Protection.RW_Invisible && (_dirtydata[p] & WriteDetectionStatus.DidChange) != 0)
|
||||
{
|
||||
// TODO: It's slow to toggle individual pages like this.
|
||||
// Maybe that's OK because None is not used much?
|
||||
|
@ -256,13 +280,43 @@ namespace BizHawk.BizInvoke
|
|||
_pal.Protect(addr, 4096, Protection.None);
|
||||
}
|
||||
}
|
||||
// Console.WriteLine($"{Start:x16}");
|
||||
// var voom = _pageData.Take((int)(CommittedSize >> 12)).Select(p =>
|
||||
// {
|
||||
// switch (p)
|
||||
// {
|
||||
// case Protection.None: return ' ';
|
||||
// case Protection.R: return 'R';
|
||||
// case Protection.RW: return 'W';
|
||||
// case Protection.RX: return 'X';
|
||||
// case Protection.RW_Invisible: return '!';
|
||||
// case Protection.RW_Stack: return '+';
|
||||
// default: return '?';
|
||||
// }
|
||||
// }).Select((c, i) => new { c, i }).GroupBy(a => a.i / 60);
|
||||
// var zoom = _dirtydata.Take((int)(CommittedSize >> 12)).Select(p =>
|
||||
// {
|
||||
// switch (p)
|
||||
// {
|
||||
// case WriteDetectionStatus.CanChange: return '.';
|
||||
// case WriteDetectionStatus.CanChange | WriteDetectionStatus.DidChange: return '*';
|
||||
// case 0: return ' ';
|
||||
// case WriteDetectionStatus.DidChange: return '!';
|
||||
// default: return '?';
|
||||
// }
|
||||
// }).Select((c, i) => new { c, i }).GroupBy(a => a.i / 60);
|
||||
// foreach (var l in voom.Zip(zoom, (a, b) => new { a, b }))
|
||||
// {
|
||||
// Console.WriteLine("____" + new string(l.a.Select(a => a.c).ToArray()));
|
||||
// Console.WriteLine("____" + new string(l.b.Select(a => a.c).ToArray()));
|
||||
// }
|
||||
}
|
||||
|
||||
public void LoadState(BinaryReader r)
|
||||
{
|
||||
EnsureActive();
|
||||
EnsureSealed();
|
||||
_pal.GetWriteStatus(_dirtydata);
|
||||
_pal.GetWriteStatus(_dirtydata, _pageData);
|
||||
|
||||
if (r.ReadUInt64() != MAGIC || r.ReadUInt64() != Start || r.ReadUInt64() != Size)
|
||||
throw new InvalidOperationException("Savestate internal mismatch");
|
||||
|
@ -297,8 +351,9 @@ namespace BizHawk.BizInvoke
|
|||
{
|
||||
var dirty = (_dirtydata[p] & WriteDetectionStatus.DidChange) != 0;
|
||||
var newDirty = (newDirtyData[p] & WriteDetectionStatus.DidChange) != 0;
|
||||
var inState = newPageData[p] != Protection.RW_Invisible && newDirty;
|
||||
|
||||
if (dirty || newDirty)
|
||||
if (dirty || inState)
|
||||
{
|
||||
// must write out changed data
|
||||
|
||||
|
@ -306,7 +361,10 @@ namespace BizHawk.BizInvoke
|
|||
if (_pageData[p] != Protection.RW)
|
||||
_pal.Protect(addr, 4096, Protection.RW);
|
||||
|
||||
if (newDirty)
|
||||
// NB: There are some weird behaviors possible if a block transitions to or from RW_Invisible,
|
||||
// but nothing really "broken" (as far as I know); just reflecting the fact that you cannot track it.
|
||||
|
||||
if (inState)
|
||||
{
|
||||
// changed data comes from the savestate
|
||||
r.Read(buff, 0, 4096);
|
||||
|
@ -350,7 +408,21 @@ namespace BizHawk.BizInvoke
|
|||
public static MemoryBlock Create(ulong size) => Create(0, size);
|
||||
|
||||
/// <summary>Memory protection constant</summary>
|
||||
public enum Protection : byte { None, R, RW, RX }
|
||||
|
||||
public enum Protection : byte
|
||||
{
|
||||
None,
|
||||
R,
|
||||
RW,
|
||||
RX,
|
||||
/// <summary>
|
||||
/// This area should not be tracked for changes, and should not be saved.
|
||||
/// </summary>
|
||||
RW_Invisible,
|
||||
/// <summary>
|
||||
/// This area may be used as a stack and should use a change detection that will work on stacks.
|
||||
/// (In windows, this is an inferior detection that triggers on reads. In Linux, this flag has no effect.)
|
||||
/// </summary>
|
||||
RW_Stack,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ namespace BizHawk.BizInvoke
|
|||
throw new InvalidOperationException($"{nameof(mprotect)}() returned {errorCode}!");
|
||||
}
|
||||
|
||||
public void GetWriteStatus(WriteDetectionStatus[] dest)
|
||||
public void GetWriteStatus(WriteDetectionStatus[] dest, Protection[] pagedata)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
|
@ -87,6 +87,11 @@ namespace BizHawk.BizInvoke
|
|||
case Protection.R: p = Kernel32.MemoryProtection.READONLY; break;
|
||||
case Protection.RW: p = Kernel32.MemoryProtection.READWRITE; break;
|
||||
case Protection.RX: p = Kernel32.MemoryProtection.EXECUTE_READ; break;
|
||||
// VEH can't work when the stack is not writable, because VEH is delivered on that selfsame stack. The kernel
|
||||
// simply declines to return to user mode on a first chance exception if the stack is not writable.
|
||||
// So we use guard pages instead, which are much worse because reads set them off as well, but it doesn't matter
|
||||
// in this case.
|
||||
case Protection.RW_Stack: p = Kernel32.MemoryProtection.READWRITE | Kernel32.MemoryProtection.GUARD_Modifierflag; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(prot));
|
||||
}
|
||||
return p;
|
||||
|
@ -107,12 +112,41 @@ namespace BizHawk.BizInvoke
|
|||
}
|
||||
}
|
||||
|
||||
public void GetWriteStatus(WriteDetectionStatus[] dest)
|
||||
public void GetWriteStatus(WriteDetectionStatus[] dest, Protection[] pagedata)
|
||||
{
|
||||
var p = (IntPtr)WinGuard.ExamineTripGuard(Z.UU(_start), Z.UU(_size));
|
||||
if (p == IntPtr.Zero)
|
||||
throw new InvalidOperationException($"{nameof(WinGuard.ExamineTripGuard)}() returned NULL!");
|
||||
Marshal.Copy(p, (byte[])(object)dest, 0, dest.Length);
|
||||
|
||||
// guard pages do not trigger VEH when they are actually being used as a stack and there are other
|
||||
// free pages below them, so virtualquery to get that information out
|
||||
Kernel32.MEMORY_BASIC_INFORMATION mbi;
|
||||
for (int i = 0; i < pagedata.Length;)
|
||||
{
|
||||
if (pagedata[i] == Protection.RW_Stack)
|
||||
{
|
||||
var q = ((ulong)i << WaterboxUtils.PageShift) + _start;
|
||||
Kernel32.VirtualQuery(Z.UU(q), &mbi, Z.SU(sizeof(Kernel32.MEMORY_BASIC_INFORMATION)));
|
||||
var pstart = (int)(((ulong)mbi.BaseAddress - _start) >> WaterboxUtils.PageShift);
|
||||
var pend = pstart + (int)((ulong)mbi.RegionSize >> WaterboxUtils.PageShift);
|
||||
if (pstart != i)
|
||||
throw new Exception("Huh?");
|
||||
|
||||
if ((mbi.Protect & Kernel32.MemoryProtection.GUARD_Modifierflag) == 0)
|
||||
{
|
||||
// tripped!
|
||||
for (int j = pstart; j < pend; j++)
|
||||
dest[j] |= WriteDetectionStatus.DidChange;
|
||||
}
|
||||
i = pend;
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void SetWriteStatus(WriteDetectionStatus[] src)
|
||||
|
@ -212,6 +246,34 @@ namespace BizHawk.BizInvoke
|
|||
}
|
||||
|
||||
public static readonly IntPtr INVALID_HANDLE_VALUE = Z.US(0xffffffffffffffff);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MEMORY_BASIC_INFORMATION
|
||||
{
|
||||
public IntPtr BaseAddress;
|
||||
public IntPtr AllocationBase;
|
||||
public MemoryProtection AllocationProtect;
|
||||
public UIntPtr RegionSize;
|
||||
public StateEnum State;
|
||||
public MemoryProtection Protect;
|
||||
public TypeEnum Type;
|
||||
}
|
||||
public enum StateEnum : uint
|
||||
{
|
||||
MEM_COMMIT = 0x1000,
|
||||
MEM_FREE = 0x10000,
|
||||
MEM_RESERVE = 0x2000
|
||||
}
|
||||
|
||||
public enum TypeEnum : uint
|
||||
{
|
||||
MEM_IMAGE = 0x1000000,
|
||||
MEM_MAPPED = 0x40000,
|
||||
MEM_PRIVATE = 0x20000
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern UIntPtr VirtualQuery(UIntPtr lpAddress, MEMORY_BASIC_INFORMATION* lpBuffer, UIntPtr dwLength);
|
||||
}
|
||||
|
||||
private static unsafe class WinGuard
|
||||
|
|
|
@ -44,9 +44,8 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
/// </summary>
|
||||
private ulong _postSealWriteStart;
|
||||
/// <summary>
|
||||
/// Where the saveable program data begins
|
||||
/// Where executable data ends
|
||||
/// </summary>
|
||||
private ulong _saveStart;
|
||||
private ulong _execEnd;
|
||||
|
||||
public ElfLoader(string moduleName, byte[] fileData, ulong assumedStart, bool skipCoreConsistencyCheck, bool skipMemoryConsistencyCheck)
|
||||
|
@ -125,7 +124,6 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
|
||||
_writeStart = WaterboxUtils.AlignDown(writable.Min(s => s.LoadAddress));
|
||||
_postSealWriteStart = WaterboxUtils.AlignDown(postSealWritable.Min(s => s.LoadAddress));
|
||||
_saveStart = WaterboxUtils.AlignDown(saveable.Min(s => s.LoadAddress));
|
||||
_execEnd = WaterboxUtils.AlignUp(executable.Max(s => s.LoadAddress + s.Size));
|
||||
|
||||
// validate; this may require linkscript cooperation
|
||||
|
@ -133,13 +131,6 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
// in the wrong place (because the linkscript doesn't know "eventually readonly")
|
||||
if (_execEnd > _writeStart)
|
||||
throw new InvalidOperationException($"ElfLoader: Executable data to {_execEnd:X16} overlaps writable data from {_writeStart}");
|
||||
|
||||
var actuallySaved = allocated.Where(a => a.LoadAddress + a.Size > _saveStart);
|
||||
var oopsSaved = actuallySaved.Except(saveable);
|
||||
foreach (var s in oopsSaved)
|
||||
{
|
||||
Console.WriteLine($"ElfLoader: Section {s.Name} will be saved, but that was not expected");
|
||||
}
|
||||
}
|
||||
|
||||
PrintSections();
|
||||
|
@ -159,12 +150,11 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
Console.WriteLine($"Mounted `{ModuleName}` @{Memory.Start:x16}");
|
||||
foreach (var s in _elf.Sections.OrderBy(s => s.LoadAddress))
|
||||
{
|
||||
Console.WriteLine(" @{0:x16} {1}{2}{3}{4} `{5}` {6} bytes",
|
||||
Console.WriteLine(" @{0:x16} {1}{2}{3} `{4}` {5} bytes",
|
||||
s.LoadAddress,
|
||||
(s.Flags & SectionFlags.Allocatable) != 0 ? "R" : " ",
|
||||
(s.Flags & SectionFlags.Writable) != 0 ? "W" : " ",
|
||||
(s.Flags & SectionFlags.Executable) != 0 ? "X" : " ",
|
||||
s.LoadAddress + s.Size > _saveStart ? "V" : " ",
|
||||
s.Name,
|
||||
s.Size);
|
||||
}
|
||||
|
@ -172,8 +162,9 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
|
||||
private void PrintTopSavableSymbols()
|
||||
{
|
||||
// TODO: With change detection, this isn't so great anymore.
|
||||
// It could look at actual intersections with what we saved, but that would mean going into MemoryBlock
|
||||
var tops = _allSymbols
|
||||
.Where(s => s.Value + s.Size > _saveStart)
|
||||
.OrderByDescending(s => s.Size)
|
||||
.Where(s => s.Size >= 20 * 1024)
|
||||
.Take(30)
|
||||
|
@ -182,7 +173,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
|
||||
if (tops.Count > 0)
|
||||
{
|
||||
Console.WriteLine("Top savestate symbols:");
|
||||
Console.WriteLine("Top potential savestate symbols:");
|
||||
foreach (var text in tops)
|
||||
{
|
||||
Console.WriteLine(text);
|
||||
|
@ -206,7 +197,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set normal (post-seal) memory protections
|
||||
/// Set memory protections, pre or post seal
|
||||
/// </summary>
|
||||
private void Protect()
|
||||
{
|
||||
|
@ -215,6 +206,20 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
Memory.Protect(Memory.Start, _execEnd - Memory.Start, MemoryBlock.Protection.RX);
|
||||
Memory.Protect(_execEnd, writeStart - _execEnd, MemoryBlock.Protection.R);
|
||||
Memory.Protect(writeStart, Memory.EndExclusive - writeStart, MemoryBlock.Protection.RW);
|
||||
if (_invisible != null)
|
||||
{
|
||||
if (!WaterboxUtils.Aligned(_invisible.LoadAddress))
|
||||
throw new InvalidOperationException("Invisible section start not aligned! Check linker script");
|
||||
var end = WaterboxUtils.AlignUp(_invisible.LoadAddress + _invisible.Size);
|
||||
if (_sectionsByName.Values.Any(s => s != _invisible && s.LoadAddress < end && s.LoadAddress >= _invisible.LoadAddress))
|
||||
{
|
||||
throw new InvalidOperationException("Invisible section end not aligned, or not padded! Check linker script");
|
||||
}
|
||||
Memory.Protect(
|
||||
_invisible.LoadAddress,
|
||||
_invisible.Size,
|
||||
MemoryBlock.Protection.RW_Invisible);
|
||||
}
|
||||
}
|
||||
|
||||
// connect all of the .wbxsyscall stuff
|
||||
|
|
|
@ -170,8 +170,19 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
private bool EnsureMapped(int startPage, int pageCount)
|
||||
{
|
||||
for (int i = startPage; i < startPage + pageCount; i++)
|
||||
{
|
||||
if (_pages[i] == FREE)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private bool EnsureMappedNonStack(int startPage, int pageCount)
|
||||
{
|
||||
for (int i = startPage; i < startPage + pageCount; i++)
|
||||
{
|
||||
if (_pages[i] == FREE || _pages[i] == MemoryBlock.Protection.RW_Stack)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -197,7 +208,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
|
||||
var oldStartPage = GetPage(start);
|
||||
var oldNumPages = WaterboxUtils.PagesNeeded(oldSize);
|
||||
if (!EnsureMapped(oldStartPage, oldNumPages))
|
||||
if (!EnsureMappedNonStack(oldStartPage, oldNumPages))
|
||||
return 0;
|
||||
var oldProt = _pages[oldStartPage];
|
||||
|
||||
|
@ -252,11 +263,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
|
||||
public bool Unmap(ulong start, ulong size)
|
||||
{
|
||||
return Protect(start, size, FREE);
|
||||
}
|
||||
|
||||
public bool Protect(ulong start, ulong size, MemoryBlock.Protection prot)
|
||||
{
|
||||
// TODO: eliminate copy+pasta between unmap and protect
|
||||
if (start < Memory.Start || start + size > Memory.EndExclusive || size == 0)
|
||||
return false;
|
||||
|
||||
|
@ -265,6 +272,21 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
if (!EnsureMapped(startPage, numPages))
|
||||
return false;
|
||||
|
||||
ProtectInternal(startPage, numPages, FREE, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Protect(ulong start, ulong size, MemoryBlock.Protection prot)
|
||||
{
|
||||
// TODO: eliminate copy+pasta between unmap and protect
|
||||
if (start < Memory.Start || start + size > Memory.EndExclusive || size == 0)
|
||||
return false;
|
||||
|
||||
var startPage = GetPage(start);
|
||||
var numPages = WaterboxUtils.PagesNeeded(size);
|
||||
if (!EnsureMappedNonStack(startPage, numPages))
|
||||
return false;
|
||||
|
||||
ProtectInternal(startPage, numPages, prot, true);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
case PROT_WRITE:
|
||||
return Z.SS(MAP_FAILED); // write only????
|
||||
case PROT_READ | PROT_WRITE:
|
||||
mprot = MemoryBlock.Protection.RW;
|
||||
mprot = (flags & MAP_STACK) != 0 ? MemoryBlock.Protection.RW_Stack : MemoryBlock.Protection.RW;
|
||||
break;
|
||||
case PROT_READ:
|
||||
mprot = MemoryBlock.Protection.R;
|
||||
|
|
|
@ -305,17 +305,6 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
}
|
||||
_module.SealImportsAndTakeXorSnapshot();
|
||||
_mmapheap?.Memory.Seal();
|
||||
|
||||
// MemoryBlock's lazy write detection can't work on the current stack, because the VEH handler runs in the stack
|
||||
// so it has to already be writable. This isn't a problem for normal stacks, which are outside the cycle, but
|
||||
// co stacks have to be fully probed before they're used as stacks.
|
||||
|
||||
IntPtr co_probe;
|
||||
if ((co_probe = _module.GetProcAddrOrZero("co_probe")) != IntPtr.Zero)
|
||||
{
|
||||
Console.WriteLine("Calling co_probe().");
|
||||
CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer<Action>(co_probe)();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -362,7 +351,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
}
|
||||
|
||||
private const ulong MAGIC = 0x736b776162727477;
|
||||
private const ulong WATERBOXSTATEVERSION = 1;
|
||||
private const ulong WATERBOXSTATEVERSION = 2;
|
||||
|
||||
public void SaveStateBinary(BinaryWriter bw)
|
||||
{
|
||||
|
|
|
@ -17,18 +17,12 @@
|
|||
// and include guard space between the stack and the storage
|
||||
|
||||
typedef struct {
|
||||
// used by coswap.s
|
||||
// used by coswap.s, has to be at the beginning of the struct
|
||||
uint64_t jmp_buf[32];
|
||||
// lowest pointer in the stack; when we go below here, we'll hit the fake guard pages and overflow
|
||||
uint64_t* stack_bottom;
|
||||
// pointer just off the top (starting) end of the stack
|
||||
uint64_t* stack_top;
|
||||
// total size that went to mmap
|
||||
uint64_t mmap_size;
|
||||
uint64_t padding[477];
|
||||
// will be protected to unreadable, unwritable
|
||||
uint64_t guard[0x3000 / sizeof(uint64_t)];
|
||||
uint64_t stack[0];
|
||||
// points to the lowest address in the stack
|
||||
void* stack;
|
||||
// length of the stack that we allocated in bytes
|
||||
uint64_t stack_size;
|
||||
} cothread_impl;
|
||||
|
||||
// the cothread that represents the real host thread we started from
|
||||
|
@ -36,78 +30,39 @@ static cothread_impl co_host_buffer;
|
|||
// what cothread are we in right now
|
||||
static cothread_impl* co_active_handle;
|
||||
|
||||
// a list of all real cothreads (does not include co_host_buffer)
|
||||
#define MAX_THREADS 64
|
||||
static cothread_impl* allthreads[MAX_THREADS];
|
||||
|
||||
static cothread_impl* alloc_thread(uint64_t size)
|
||||
{
|
||||
cothread_impl** dest = NULL;
|
||||
for (int i = 0; i < MAX_THREADS; i++)
|
||||
{
|
||||
if (!allthreads[i])
|
||||
{
|
||||
dest = &allthreads[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dest)
|
||||
cothread_impl* co = calloc(1, sizeof(*co));
|
||||
if (!co)
|
||||
return NULL;
|
||||
|
||||
// align up to 4k
|
||||
size = (size + 4095) & ~4095;
|
||||
|
||||
uint64_t alloc_size = sizeof(cothread_impl) + size;
|
||||
|
||||
cothread_impl* co = mmap(NULL, alloc_size,
|
||||
PROT_READ | PROT_WRITE, MAP_ANONYMOUS, -1, 0);
|
||||
|
||||
if (co == (cothread_impl*)(-1))
|
||||
co->stack = mmap(NULL, size,
|
||||
PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_STACK, -1, 0);
|
||||
|
||||
if (co->stack == (void*)(-1))
|
||||
{
|
||||
free(co);
|
||||
return NULL;
|
||||
|
||||
co->stack_bottom = &co->stack[0];
|
||||
co->stack_top = &co->stack[size / sizeof(uint64_t)];
|
||||
co->mmap_size = alloc_size;
|
||||
|
||||
for (int i = 0; i < 0x3000 / sizeof(uint64_t); i++)
|
||||
co->guard[i] = 0xdeadbeeffeedface;
|
||||
|
||||
if (mprotect(&co->guard[0], sizeof(co->guard), PROT_NONE) != 0)
|
||||
abort();
|
||||
|
||||
*dest = co;
|
||||
}
|
||||
co->stack_size = size;
|
||||
return co;
|
||||
}
|
||||
|
||||
static void free_thread(cothread_impl* co)
|
||||
{
|
||||
cothread_impl** src = NULL;
|
||||
for (int i = 0; i < MAX_THREADS; i++)
|
||||
{
|
||||
if (allthreads[i] == co)
|
||||
{
|
||||
src = &allthreads[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!src)
|
||||
if (munmap(co->stack, co->stack_size) != 0)
|
||||
abort();
|
||||
|
||||
if (mprotect(&co->guard[0], sizeof(co->guard), PROT_READ | PROT_WRITE) != 0)
|
||||
abort();
|
||||
uint64_t alloc_size = co->mmap_size;
|
||||
memset(co, 0, alloc_size);
|
||||
if (munmap(co, alloc_size) != 0)
|
||||
abort();
|
||||
|
||||
*src = NULL;
|
||||
free(co);
|
||||
}
|
||||
|
||||
extern void co_swap(cothread_t, cothread_t);
|
||||
extern void co_swap(cothread_impl*, cothread_impl*);
|
||||
|
||||
static void crash(void)
|
||||
{
|
||||
assert(0); /* called only if cothread_t entrypoint returns */
|
||||
__asm__("int3"); // called only if cothread_t entrypoint returns
|
||||
}
|
||||
|
||||
ECL_EXPORT void co_clean(void)
|
||||
|
@ -115,23 +70,6 @@ ECL_EXPORT void co_clean(void)
|
|||
memset(&co_host_buffer, 0, sizeof(co_host_buffer));
|
||||
}
|
||||
|
||||
ECL_EXPORT void co_probe(void)
|
||||
{
|
||||
// VEH is delivered on the same thread that experienced the exception.
|
||||
// That means the stack has to be writable already, so the waterbox host's fault detector won't work
|
||||
// when we're on a readonly cothread stack. So we conservatively probe this entire stack before switching to it.
|
||||
for (int i = 0; i < MAX_THREADS; i++)
|
||||
{
|
||||
if (allthreads[i])
|
||||
{
|
||||
uint64_t volatile* p = (uint64_t volatile*)allthreads[i]->stack_bottom;
|
||||
uint64_t* pend = allthreads[i]->stack_top;
|
||||
for (; p < pend; p++)
|
||||
*p = *p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cothread_t co_active(void)
|
||||
{
|
||||
if (!co_active_handle)
|
||||
|
@ -147,10 +85,10 @@ cothread_t co_create(unsigned int sz, void (*entrypoint)(void))
|
|||
|
||||
if ((co = alloc_thread(sz)))
|
||||
{
|
||||
uint64_t* p = co->stack_top; // seek to top of stack
|
||||
uint64_t* p = (uint64_t*)((char*)co->stack + co->stack_size); // seek to top of stack
|
||||
*--p = (uint64_t)crash; // crash if entrypoint returns
|
||||
*--p = (uint64_t)entrypoint; // start of function */
|
||||
*(uint64_t*)co = (uint64_t)p; // stack pointer
|
||||
*--p = (uint64_t)entrypoint; // start of function
|
||||
co->jmp_buf[0] = (uint64_t)p; // stack pointer
|
||||
}
|
||||
|
||||
return co;
|
||||
|
@ -166,6 +104,8 @@ static uint64_t hostend;
|
|||
|
||||
void co_switch(cothread_t handle)
|
||||
{
|
||||
cothread_impl* co = handle;
|
||||
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
if (co_active_handle == &co_host_buffer)
|
||||
|
@ -187,13 +127,12 @@ void co_switch(cothread_t handle)
|
|||
else
|
||||
{
|
||||
// migrating onto cothread; compute its extents we allocated them
|
||||
cothread_impl* co = handle;
|
||||
start = (uintptr_t)co->stack_bottom;
|
||||
end = (uintptr_t)co->stack_top;
|
||||
start = (uintptr_t)co->stack;
|
||||
end = start + co->stack_size;
|
||||
}
|
||||
__asm__("movq %0, %%gs:0x08":: "r"(end));
|
||||
__asm__("movq %0, %%gs:0x10":: "r"(start));
|
||||
|
||||
register cothread_t co_previous_handle = co_active_handle;
|
||||
co_swap(co_active_handle = handle, co_previous_handle);
|
||||
register cothread_impl* co_previous_handle = co_active_handle;
|
||||
co_swap(co_active_handle = co, co_previous_handle);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue