diff --git a/output/linguard.so b/output/linguard.so
new file mode 100644
index 0000000000..e0a5910687
Binary files /dev/null and b/output/linguard.so differ
diff --git a/src/BizHawk.BizInvoke/MemoryBlock.cs b/src/BizHawk.BizInvoke/MemoryBlock.cs
index 1ae0fb1fec..fe8c4edae4 100644
--- a/src/BizHawk.BizInvoke/MemoryBlock.cs
+++ b/src/BizHawk.BizInvoke/MemoryBlock.cs
@@ -25,7 +25,7 @@ namespace BizHawk.BizInvoke
_dirtydata = (WriteDetectionStatus[])(object)new byte[GetPage(EndExclusive - 1) + 1];
_pal = OSTailoredCode.IsUnixHost
- ? (IMemoryBlockPal)new MemoryBlockUnixPal(Start, Size)
+ ? (IMemoryBlockPal)new MemoryBlockLinuxPal(Start, Size)
: new MemoryBlockWindowsPal(Start, Size);
}
diff --git a/src/BizHawk.BizInvoke/MemoryBlockLinuxPal.cs b/src/BizHawk.BizInvoke/MemoryBlockLinuxPal.cs
new file mode 100644
index 0000000000..48caa1595e
--- /dev/null
+++ b/src/BizHawk.BizInvoke/MemoryBlockLinuxPal.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Runtime.InteropServices;
+using static BizHawk.BizInvoke.MemoryBlock;
+using static BizHawk.BizInvoke.POSIXLibC;
+
+namespace BizHawk.BizInvoke
+{
+ internal sealed unsafe class MemoryBlockLinuxPal : IMemoryBlockPal
+ {
+ /*
+ Differences compared with MemoryBlockWindowsPal:
+ 1) Commit is handled by only mapping up to the commit size, and then expanding commit is handled by unmap + truncate + remap.
+ So all unmanaged structures (including LinGuard) are always looking at the committed size, not total size.
+ 2) Because of sigaltstack, RW_Stack is not needed and is made to behave the same as regular write guarding.
+ */
+
+ /// handle returned by
+ private int _fd = -1;
+ private ulong _start;
+ private ulong _size;
+ private ulong _committedSize;
+ private bool _active;
+
+ ///
+ /// Reserve bytes to later be swapped in, but do not map them
+ ///
+ /// eventual mapped address
+ ///
+ ///
+ /// failed to get file descriptor
+ ///
+ public MemoryBlockLinuxPal(ulong start, ulong size)
+ {
+ // Console.WriteLine($".ctor {start:x16} {size:x16}");
+ _start = start;
+ _size = size;
+ _fd = memfd_create("MemoryBlockUnix", 1 /*MFD_CLOEXEC*/);
+ if (_fd == -1)
+ throw new InvalidOperationException($"{nameof(memfd_create)}() failed with error {Marshal.GetLastWin32Error()}");
+ }
+
+ public void Dispose()
+ {
+ if (_fd == -1)
+ return;
+ if (_active)
+ {
+ try
+ {
+ Deactivate();
+ }
+ catch
+ {}
+ }
+ close(_fd);
+ _fd = -1;
+ GC.SuppressFinalize(this);
+ }
+
+ ~MemoryBlockLinuxPal()
+ {
+ Dispose();
+ }
+
+ public void Activate()
+ {
+ if (_committedSize > 0)
+ {
+ var ptr = mmap(Z.US(_start), Z.UU(_committedSize),
+ MemoryProtection.Read | MemoryProtection.Write | MemoryProtection.Execute,
+ 17, // MAP_SHARED | MAP_FIXED
+ _fd, IntPtr.Zero);
+ if (ptr != Z.US(_start))
+ {
+ throw new InvalidOperationException($"{nameof(mmap)}() failed with error {Marshal.GetLastWin32Error()}");
+ }
+ // Console.WriteLine($"Add {_start} {_committedSize}");
+ if ((IntPtr)LinGuard.AddTripGuard(Z.UU(_start), Z.UU(_committedSize)) == IntPtr.Zero)
+ {
+ throw new InvalidOperationException($"{nameof(LinGuard.AddTripGuard)}() returned NULL");
+ }
+ }
+ _active = true;
+ }
+
+ public void Deactivate()
+ {
+ if (_committedSize > 0)
+ {
+ var errorCode = munmap(Z.US(_start), Z.UU(_committedSize));
+ if (errorCode != 0)
+ throw new InvalidOperationException($"{nameof(munmap)}() failed with error {Marshal.GetLastWin32Error()}");
+ // Console.WriteLine($"Remove {_start} {_committedSize}");
+ if (!LinGuard.RemoveTripGuard(Z.UU(_start), Z.UU(_committedSize)))
+ throw new InvalidOperationException($"{nameof(LinGuard.RemoveTripGuard)}() returned FALSE");
+ }
+ _active = false;
+ }
+
+ public void Commit(ulong length)
+ {
+ // Console.WriteLine($"commit {length:x16}");
+ Deactivate();
+ var errorCode = ftruncate(_fd, Z.US(length));
+ if (errorCode != 0)
+ throw new InvalidOperationException($"{nameof(ftruncate)}() failed with error {Marshal.GetLastWin32Error()}");
+ _committedSize = length;
+ Activate();
+ }
+
+ private static MemoryProtection ToMemoryProtection(Protection prot)
+ {
+ switch (prot)
+ {
+ case Protection.None:
+ return MemoryProtection.None;
+ case Protection.R:
+ return MemoryProtection.Read;
+ case Protection.RW:
+ return MemoryProtection.Read | MemoryProtection.Write;
+ case Protection.RX:
+ return MemoryProtection.Read | MemoryProtection.Execute;
+ case Protection.RW_Invisible:
+ return MemoryProtection.Read | MemoryProtection.Write;
+ case Protection.RW_Stack:
+ // because of sigaltstack, LinGuard has no issues with readonly stacks and the special distinction that
+ // the windows port draws between stack vs non stack memory is ignored here
+ return MemoryProtection.Read;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(prot));
+ }
+ }
+
+ public void Protect(ulong start, ulong size, Protection prot)
+ {
+ // Console.WriteLine($"protect {start:x16} {size:x16} {prot}");
+ var errorCode = mprotect(
+ Z.US(start),
+ Z.UU(size),
+ ToMemoryProtection(prot)
+ );
+ if (errorCode != 0)
+ throw new InvalidOperationException($"{nameof(mprotect)}() failed with error {Marshal.GetLastWin32Error()}!");
+ }
+
+ public void GetWriteStatus(WriteDetectionStatus[] dest, Protection[] pagedata)
+ {
+ if (_committedSize > 0)
+ {
+ // Console.WriteLine($"Examine {_start} {_committedSize}");
+ var p = (IntPtr)LinGuard.ExamineTripGuard(Z.UU(_start), Z.UU(_committedSize));
+ if (p == IntPtr.Zero)
+ throw new InvalidOperationException($"{nameof(LinGuard.ExamineTripGuard)}() returned NULL!");
+ Marshal.Copy(p, (byte[])(object)dest, 0, (int)(_committedSize >> WaterboxUtils.PageShift));
+ }
+ }
+
+ public void SetWriteStatus(WriteDetectionStatus[] src)
+ {
+ if (_committedSize > 0)
+ {
+ // Console.WriteLine($"Examine {_start} {_committedSize}");
+ var p = (IntPtr)LinGuard.ExamineTripGuard(Z.UU(_start), Z.UU(_committedSize));
+ if (p == IntPtr.Zero)
+ throw new InvalidOperationException($"{nameof(LinGuard.ExamineTripGuard)}() returned NULL!");
+ Marshal.Copy((byte[])(object)src, 0, p, (int)(_committedSize >> WaterboxUtils.PageShift));
+ }
+ }
+
+ private static unsafe class LinGuard
+ {
+ ///
+ /// Add write detection to an area of memory. Any page in the specified range that has CanChange
+ /// set and triggers an access violation on write
+ /// will be noted, set to read+write permissions, and execution will be continued.
+ /// CALLER'S RESPONSIBILITY: All addresses are page aligned.
+ /// CALLER'S RESPONSIBILITY: No other thread enters any LinGuard function, or trips any tracked page during this call.
+ /// CALLER'S RESPONSIBILITY: Pages to be tracked are mprotected to R. Pages with write permission
+ /// cause no issues, but they will not trip.
+ ///
+ /// The same information as ExamineTripGuard, or null on failure
+ [DllImport("linguard.so")]
+ public static extern WriteDetectionStatus* AddTripGuard(UIntPtr start, UIntPtr length);
+ ///
+ /// Remove write detection from the specified addresses.
+ /// CALLER'S RESPONSIBILITY: All addresses are page aligned.
+ /// CALLER'S RESPONSIBILITY: No other thread enters any LinGuard function, or trips any tracked guard page during this call.
+ ///
+ /// false on failure (usually, the address range did not match a known one)
+ [DllImport("linguard.so")]
+ public static extern bool RemoveTripGuard(UIntPtr start, UIntPtr length);
+ ///
+ /// Examines a previously installed guard page detection.
+ /// CALLER'S RESPONSIBILITY: All addresses are page aligned.
+ /// CALLER'S RESPONSIBILITY: No other thread enters any LinGuard function, or trips any tracked guard page during this call.
+ ///
+ ///
+ /// A pointer to an array of bytes, one byte for each memory page in the range. Caller should set CanChange on pages to
+ /// observe, and read back DidChange to see if things changed.
+ ///
+ [DllImport("linguard.so")]
+ public static extern WriteDetectionStatus* ExamineTripGuard(UIntPtr start, UIntPtr length);
+ }
+ }
+}
diff --git a/src/BizHawk.BizInvoke/MemoryBlockUnixPal.cs b/src/BizHawk.BizInvoke/MemoryBlockUnixPal.cs
deleted file mode 100644
index 56250aadaf..0000000000
--- a/src/BizHawk.BizInvoke/MemoryBlockUnixPal.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using static BizHawk.BizInvoke.MemoryBlock;
-using static BizHawk.BizInvoke.POSIXLibC;
-
-namespace BizHawk.BizInvoke
-{
- public sealed class MemoryBlockUnixPal : IMemoryBlockPal
- {
- /// handle returned by
- private int _fd = -1;
- private ulong _start;
- private ulong _size;
- private ulong _committedSize;
-
- ///
- /// Reserve bytes to later be swapped in, but do not map them
- ///
- /// eventual mapped address
- ///
- ///
- /// failed to get file descriptor
- ///
- public MemoryBlockUnixPal(ulong start, ulong size)
- {
- // Console.WriteLine($".ctor {start:x16} {size:x16}");
- _start = start;
- _size = size;
- _fd = memfd_create("MemoryBlockUnix", 1 /*MFD_CLOEXEC*/);
- if (_fd == -1)
- throw new InvalidOperationException($"{nameof(memfd_create)}() failed with error {Marshal.GetLastWin32Error()}");
- }
-
- public void Dispose()
- {
- if (_fd == -1)
- return;
- close(_fd);
- _fd = -1;
- GC.SuppressFinalize(this);
- }
-
- ~MemoryBlockUnixPal()
- {
- Dispose();
- }
-
- public void Activate()
- {
- if (_committedSize > 0)
- {
- var ptr = mmap(Z.US(_start), Z.UU(_committedSize),
- MemoryProtection.Read | MemoryProtection.Write | MemoryProtection.Execute,
- 17, // MAP_SHARED | MAP_FIXED
- _fd, IntPtr.Zero);
- if (ptr != Z.US(_start))
- throw new InvalidOperationException($"{nameof(mmap)}() failed with error {Marshal.GetLastWin32Error()}");
- }
- }
-
- public void Deactivate()
- {
- if (_committedSize > 0)
- {
- var errorCode = munmap(Z.US(_start), Z.UU(_committedSize));
- if (errorCode != 0)
- throw new InvalidOperationException($"{nameof(munmap)}() failed with error {Marshal.GetLastWin32Error()}");
- }
- }
-
- public void Commit(ulong length)
- {
- // Console.WriteLine($"commit {length:x16}");
- Deactivate();
- var errorCode = ftruncate(_fd, Z.US(length));
- if (errorCode != 0)
- throw new InvalidOperationException($"{nameof(ftruncate)}() failed with error {Marshal.GetLastWin32Error()}");
- _committedSize = length;
- Activate();
- }
-
- public void Protect(ulong start, ulong size, Protection prot)
- {
- // Console.WriteLine($"protect {start:x16} {size:x16} {prot}");
- var errorCode = mprotect(
- Z.US(start),
- Z.UU(size),
- prot.ToMemoryProtection()
- );
- if (errorCode != 0)
- throw new InvalidOperationException($"{nameof(mprotect)}() failed with error {Marshal.GetLastWin32Error()}!");
- }
-
- public void GetWriteStatus(WriteDetectionStatus[] dest, Protection[] pagedata)
- {
- // TODO
- }
-
- public void SetWriteStatus(WriteDetectionStatus[] src)
- {
- // TODO
- }
- }
-}
diff --git a/src/BizHawk.BizInvoke/MemoryBlockWindowsPal.cs b/src/BizHawk.BizInvoke/MemoryBlockWindowsPal.cs
index bbb9305e7a..df67e22a96 100644
--- a/src/BizHawk.BizInvoke/MemoryBlockWindowsPal.cs
+++ b/src/BizHawk.BizInvoke/MemoryBlockWindowsPal.cs
@@ -12,7 +12,7 @@ namespace BizHawk.BizInvoke
private IntPtr _handle;
private ulong _start;
private ulong _size;
- private bool _guardActive;
+ private bool _active;
///
/// Reserve bytes to later be swapped in, but do not map them
@@ -54,7 +54,7 @@ namespace BizHawk.BizInvoke
{
throw new InvalidOperationException($"{nameof(WinGuard.AddTripGuard)}() returned NULL");
}
- _guardActive = true;
+ _active = true;
}
public void Deactivate()
@@ -63,7 +63,7 @@ namespace BizHawk.BizInvoke
throw new InvalidOperationException($"{nameof(Kernel32.UnmapViewOfFile)}() returned NULL");
if (!WinGuard.RemoveTripGuard(Z.UU(_start), Z.UU(_size)))
throw new InvalidOperationException($"{nameof(WinGuard.RemoveTripGuard)}() returned FALSE");
- _guardActive = false;
+ _active = false;
}
public void Protect(ulong start, ulong size, Protection prot)
@@ -101,13 +101,17 @@ namespace BizHawk.BizInvoke
{
if (_handle != IntPtr.Zero)
{
+ if (_active)
+ {
+ try
+ {
+ Deactivate();
+ }
+ catch
+ {}
+ }
Kernel32.CloseHandle(_handle);
_handle = IntPtr.Zero;
- if (_guardActive)
- {
- WinGuard.RemoveTripGuard(Z.UU(_start), Z.UU(_size));
- _guardActive = false;
- }
GC.SuppressFinalize(this);
}
}
diff --git a/src/BizHawk.BizInvoke/POSIXLibC.cs b/src/BizHawk.BizInvoke/POSIXLibC.cs
index c5337f22c0..b62d2fbbc8 100644
--- a/src/BizHawk.BizInvoke/POSIXLibC.cs
+++ b/src/BizHawk.BizInvoke/POSIXLibC.cs
@@ -31,19 +31,5 @@ namespace BizHawk.BizInvoke
/// 32-bit signed int
[Flags]
public enum MemoryProtection : int { None = 0x0, Read = 0x1, Write = 0x2, Execute = 0x4 }
-
- public static MemoryProtection ToMemoryProtection(this Protection prot)
- {
- switch (prot)
- {
- case Protection.None: return MemoryProtection.None;
- case Protection.R: return MemoryProtection.Read | MemoryProtection.Write; // FIXME
- case Protection.RW: return MemoryProtection.Read | MemoryProtection.Write;
- case Protection.RX: return MemoryProtection.Read | MemoryProtection.Execute;
- case Protection.RW_Invisible: return MemoryProtection.Read | MemoryProtection.Write;
- case Protection.RW_Stack: return MemoryProtection.Read | MemoryProtection.Write; // FIXME
- default: throw new ArgumentOutOfRangeException(nameof(prot));
- }
- }
}
}
diff --git a/waterbox/linguard/Makefile b/waterbox/linguard/Makefile
new file mode 100644
index 0000000000..6c48b41f08
--- /dev/null
+++ b/waterbox/linguard/Makefile
@@ -0,0 +1,13 @@
+ifeq (,$(findstring Linux,$(shell uname)))
+$(error This must be built with linux)
+endif
+
+linguard.so: linguard.c
+ gcc -Wall -O2 -s -o $@ $< -shared -fPIC
+
+install: linguard.so
+ cp $< ../../output
+clean:
+ rm linguard.so
+
+.PHONY: install clean
diff --git a/waterbox/linguard/linguard.c b/waterbox/linguard/linguard.c
new file mode 100644
index 0000000000..cd7fb647df
--- /dev/null
+++ b/waterbox/linguard/linguard.c
@@ -0,0 +1,279 @@
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define MAX_TRIPS 64
+
+typedef struct {
+ uintptr_t start;
+ uintptr_t length;
+ uint8_t tripped[0];
+} tripwire_t;
+
+static tripwire_t* Trips[MAX_TRIPS];
+static int HandlerInstalled;
+
+static char altstack[SIGSTKSZ];
+
+static struct sigaction sa_old;
+
+static void SignalHandler(int sig, siginfo_t* info, void* ucontext)
+{
+ uintptr_t faultAddress = (uintptr_t)info->si_addr;
+ for (int i = 0; i < MAX_TRIPS; i++)
+ {
+ if (Trips[i] && faultAddress >= Trips[i]->start && faultAddress < Trips[i]->start + Trips[i]->length)
+ {
+ uintptr_t page = (faultAddress - Trips[i]->start) >> 12;
+ if (Trips[i]->tripped[page] & 1) // should change
+ {
+ if (mprotect((void*)(faultAddress & ~0xffful), 0x1000, PROT_READ | PROT_WRITE) != 0)
+ {
+ abort();
+ while (1)
+ ;
+ }
+
+ Trips[i]->tripped[page] = 3; // did change
+ return;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ if (sa_old.sa_flags & SA_SIGINFO)
+ sa_old.sa_sigaction(sig, info, ucontext);
+ else
+ sa_old.sa_handler(sig);
+}
+
+static int InstallHandler()
+{
+ stack_t ss;
+ ss.ss_flags = 0;
+ ss.ss_sp = altstack;
+ ss.ss_size = sizeof(altstack);
+
+ if (sigaltstack(&ss, NULL) != 0)
+ {
+ fprintf(stderr, "sigaltstack: %i\n", errno);
+ return 0;
+ }
+
+ struct sigaction sa;
+ sa.sa_sigaction = SignalHandler;
+ sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
+ sigfillset(&sa.sa_mask);
+
+ if (sigaction(SIGSEGV, &sa, &sa_old) != 0)
+ {
+ fprintf(stderr, "sigaction: %i\n", errno);
+ return 0;
+ }
+ return 1;
+}
+
+uint8_t* AddTripGuard(uintptr_t start, uintptr_t length)
+{
+ if (!HandlerInstalled)
+ {
+ if (!InstallHandler())
+ return NULL;
+ HandlerInstalled = 1;
+ }
+
+ uintptr_t npage = length >> 12;
+ for (int i = 0; i < MAX_TRIPS; i++)
+ {
+ if (!Trips[i])
+ {
+ Trips[i] = calloc(1, sizeof(*Trips[i]) + npage);
+ if (!Trips[i])
+ return NULL;
+ Trips[i]->start = start;
+ Trips[i]->length = length;
+ return &Trips[i]->tripped[0];
+ }
+ }
+ return NULL;
+}
+
+int64_t RemoveTripGuard(uintptr_t start, uintptr_t length)
+{
+ for (int i = 0; i < MAX_TRIPS; i++)
+ {
+ if (Trips[i] && Trips[i]->start == start && Trips[i]->length == length)
+ {
+ free(Trips[i]);
+ Trips[i] = NULL;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+uint8_t* ExamineTripGuard(uintptr_t start, uintptr_t length)
+{
+ for (int i = 0; i < MAX_TRIPS; i++)
+ {
+ if (Trips[i] && Trips[i]->start == start && Trips[i]->length == length)
+ return &Trips[i]->tripped[0];
+ }
+ return NULL;
+}
+
+
+/*
+
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+ struct uffdio_writeprotect wp;
+ wp.range.start = start;
+ wp.range.len = length;
+ wp.mode = 0;
+ if (ioctl(fd, UFFDIO_WRITEPROTECT, &wp) == -1)
+ {
+ free(Trips[i]);
+ Trips[i] = NULL;
+ return NULL;
+ }
+
+static void v(const char* msg, int value)
+{
+ if (v < 0)
+ {
+ printf("ERROR: %s %i\n", msg, errno);
+ exit(1);
+ }
+}
+
+const uint64_t addr = 0x36f00000000ul;
+const uint64_t size = 0x100000ul;
+
+static void* threadproc(void* arg)
+{
+ int wpfd = (int)(long)arg;
+ // should be able to register once for a large range,
+ // then wp smaller ranges
+ struct uffdio_register uffdio_register;
+ uffdio_register.range.start = addr;
+ uffdio_register.range.len = size;
+
+ uffdio_register.mode = UFFDIO_REGISTER_MODE_WP;
+
+ v("ioctl:UFFDIO_REGISTER", ioctl(wpfd, UFFDIO_REGISTER, &uffdio_register));
+ v("uffdio_register.ioctls", (uffdio_register.ioctls & UFFD_API_RANGE_IOCTLS) == UFFD_API_RANGE_IOCTLS ? 0 : -1);
+
+ // wp
+ {
+ struct uffdio_writeprotect wp;
+ wp.range.start = addr;
+ wp.range.len = size;
+ wp.mode = UFFDIO_WRITEPROTECT_MODE_WP;
+ v("ioctl:UFFDIO_WRITEPROTECT", ioctl(wpfd, UFFDIO_WRITEPROTECT, &wp));
+ }
+
+ while (1)
+ {
+ struct uffd_msg msg;
+
+ int nb = read(wpfd, &msg, sizeof(msg));
+ if (nb == -1)
+ {
+ if (errno == EAGAIN)
+ continue;
+ v("read", errno);
+ }
+ if (nb != sizeof(msg))
+ v("sizeof(msg)", -1);
+ if (msg.event & UFFD_EVENT_PAGEFAULT)
+ {
+ printf("==> Event is pagefault on %p flags 0x%llx write? 0x%llx wp? 0x%llx\n"
+ , (void *)msg.arg.pagefault.address
+ , msg.arg.pagefault.flags
+ , msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE
+ , msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP
+ );
+ }
+ if (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP)
+ {
+ // send write unlock
+ struct uffdio_writeprotect wp;
+ wp.range.start = addr;
+ wp.range.len = size;
+ wp.mode = 0;
+ printf("sending !UFFDIO_WRITEPROTECT event to userfaultfd\n");
+ v("ioctl:UFFDIO_WRITEPROTECT", ioctl(wpfd, UFFDIO_WRITEPROTECT, &wp));
+ }
+ }
+ return NULL;
+}
+
+int main(void)
+{
+ int fd = memfd_create("pewps", MFD_CLOEXEC);
+ v("memfd_create", fd);
+ printf("fd: %i\n", fd);
+ v("ftruncate", ftruncate(fd, size));
+ char* ptr = mmap((void*)addr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED, fd, 0);
+ if (ptr == MAP_FAILED || ptr != (void*)addr)
+ v("mmap", (int)(long)ptr);
+ v("mprotect", mprotect(ptr, size, PROT_READ | PROT_WRITE));
+
+ int wpfd = syscall(SYS_userfaultfd, O_CLOEXEC);
+ v("SYS_userfaultfd", wpfd);
+
+ struct uffdio_api uffdio_api;
+ uffdio_api.api = UFFD_API;
+ uffdio_api.features = 0;
+ v("ioctl:UFFDIO_API", ioctl(wpfd, UFFDIO_API, &uffdio_api));
+
+ v("uffdio_api.api", uffdio_api.api == UFFD_API ? 0 : -1);
+
+
+ pthread_t thr;
+ v("pthread_create", pthread_create(&thr, NULL, threadproc, (void*)(long)wpfd));
+
+ for (uint64_t a = addr; a < addr + size; a += 4096)
+ {
+ sleep(1);
+ printf("Gonna read");
+ if (*(uint64_t*)a == 77777)
+ {
+ printf("Lucky!");
+ }
+ sleep(1);
+ printf("Gonna write");
+ strcpy((void*)a, "string in mem area");
+ printf("%s\n", (char*)a);
+ }
+}
+
+*/
diff --git a/waterbox/linguard/linguard.so b/waterbox/linguard/linguard.so
new file mode 100644
index 0000000000..e0a5910687
Binary files /dev/null and b/waterbox/linguard/linguard.so differ