Newer
Older
alicedbg / src / adbg / v2 / debugger / memory.d
module adbg.v2.debugger.memory;

import adbg.v2.debugger.process : adbg_process_t;
import adbg.include.c.stdlib : malloc, free, calloc;
import adbg.include.c.stdio;
import adbg.include.c.stdarg;
import core.stdc.string : memcpy;
import core.stdc.config : c_long;
import adbg.error;

version (Windows) {
	import core.sys.windows.windows;
	import adbg.include.windows.wow64;
	import adbg.include.windows.psapi_dyn;
} else version (Posix) {
	import core.sys.posix.sys.stat;
	import core.sys.posix.sys.wait : waitpid, SIGCONT, WUNTRACED;
	import core.sys.posix.signal : kill, SIGKILL, siginfo_t, raise;
	import core.sys.posix.sys.uio;
	import core.sys.posix.fcntl : open;
	import core.stdc.stdlib : exit, malloc, free;
	import adbg.include.posix.mann;
	import adbg.include.posix.ptrace;
	import adbg.include.posix.unistd;
	import adbg.include.linux.user;
	private enum __WALL = 0x40000000;
}

extern (C):

/// Read memory from child tracee.
///
/// Params:
/// 	tracee = Reference to tracee instance.
/// 	addr = Memory address (within the children address space).
/// 	data = Pointer to data.
/// 	size = Size of data.
///
/// Returns: Error code.
int adbg_memory_read(adbg_process_t *tracee, size_t addr, void *data, uint size) {
	if (tracee == null || data == null) {
		return adbg_oops(AdbgError.nullArgument);
	}
	
	//TODO: FreeBSD/NetBSD/OpenBSD: PT_IO
	//      Linux 6.2 (include/uapi/linux/ptrace.h) still has no PT_IO
	version (Windows) {
		if (ReadProcessMemory(tracee.hpid, cast(void*)addr, data, size, null) == 0)
			return adbg_oops(AdbgError.os);
		return 0;
	} else version (linux) { // Based on https://www.linuxjournal.com/article/6100
		import core.stdc.errno : errno;
		
		c_long *dest = cast(c_long*)data;	/// target
		int r = size / c_long.sizeof;	/// number of "long"s to read
		
		for (; r > 0; --r, ++dest, addr += c_long.sizeof) {
			errno = 0; // As manpage wants
			*dest = ptrace(PT_PEEKDATA, tracee.pid, addr, null);
			if (errno)
				return adbg_oops(AdbgError.os);
		}
		
		r = size % c_long.sizeof;
		if (r) {
			errno = 0;
			c_long l = ptrace(PT_PEEKDATA, tracee.pid, addr, null);
			if (errno)
				return adbg_oops(AdbgError.os);
			ubyte* dest8 = cast(ubyte*)dest, src8 = cast(ubyte*)&l;
			for (; r; --r) *dest8++ = *src8++; // inlined memcpy
		}
		return 0;
	} else return adbg_oops(AdbgError.unimplemented);
}

/// Write memory to debuggee child.
///
/// Params:
/// 	tracee = Reference to tracee instance.
/// 	addr = Memory address (within the children address space).
/// 	data = Pointer to data.
/// 	size = Size of data.
///
/// Returns: Error code.
int adbg_memory_write(adbg_process_t *tracee, size_t addr, void *data, uint size) {
	if (tracee == null || data == null) {
		return adbg_oops(AdbgError.nullArgument);
	}
	
	//TODO: FreeBSD/NetBSD/OpenBSD: PT_IO
	version (Windows) {
		if (WriteProcessMemory(tracee.hpid, cast(void*)addr, data, size, null) == 0)
			return adbg_oops(AdbgError.os);
		return 0;
	} else version (linux) { // Based on https://www.linuxjournal.com/article/6100
		import core.stdc.errno : errno;
		
		c_long *user = cast(c_long*)data;	/// user data pointer
		int i;	/// offset index
		int j = size / c_long.sizeof;	/// number of "blocks" to process
		
		for (; i < j; ++i, ++user) {
			if (ptrace(PT_POKEDATA, tracee.pid,
				addr + (i * c_long.sizeof), user) < 0)
				return adbg_oops(AdbgError.os);
		}
		
		j = size % c_long.sizeof;
		if (j) {
			if (ptrace(PT_POKEDATA, tracee.pid,
				addr + (i * c_long.sizeof), user) < 0)
				return adbg_oops(AdbgError.os);
		}
		return 0;
	} else return adbg_oops(AdbgError.unimplemented);
}

/// Memory permission access bits.
enum AdbgMemPerm : ushort {
	read	= 1,	/// Read permission
	write	= 1 << 1,	/// Write permission
	exec	= 1 << 3,	/// Execute permission
	private_	= 1 << 8,	/// Process memory is private
	shared_	= 1 << 9,	/// Process memory is shared
	
	// Common access patterns
	readWrite	= read | write,	/// Read and write permissions
	readExec	= read | exec,	/// Read and execution permissions
	all	= read | write | exec,	/// Read, write, and execute permissions
}

private enum MEM_MAP_NAME_LEN = 512;
/// Represents a mapped memory region
struct adbg_memory_map_t {
	/// Base memory region address.
	void *base;
	/// Size of region.
	size_t size;
	/// Access permissions.
	/// 
	int access;
	/// 
	char[MEM_MAP_NAME_LEN] name;
}

/// Memory options for adbg_memory_maps.
enum AdbgMapOpt {
	reserved_
	// Only get the memory regions for this process.
	// Type: None
	//processOnly	= 1,
	// With given Process ID instead
	// Permission issues may be raised
	//pid = 2,
	// Get this maximum amount of maps.
	//count	= 3,
}

/// Obtain the memory map for the current process.
///
/// This function allocates the list of results.
/// Memory allocated by this function can be freed using free(3).
/// This behavior may change in the future.
///
/// Params:
/// 	tracee = Tracee, in the ready or paused state.
/// 	mmaps = Reference to map list.
/// 	mcount = Reference to map count.
/// 	... = Options.
///
/// Returns: Error code.
int adbg_memory_maps(adbg_process_t *tracee, adbg_memory_map_t **mmaps, size_t *mcount, ...) {
	if (tracee == null || mmaps == null || mcount == null) {
		return adbg_oops(AdbgError.nullArgument);
	}
	
	// Get options
	va_list list = void;
	va_start(list, mcount);
L_OPT:
	switch (va_arg!int(list)) {
	case 0: break;
	default:
		return adbg_oops(AdbgError.invalidOption);
	}
	
	version (Trace) trace("tracee=%p mmaps=%p mcount=%p", tracee, mmaps, mcount);
	
	// Failsafe
	*mcount = 0;
	
	if (tracee.pid == 0) {
		return adbg_oops(AdbgError.notAttached);
	}
	
	version (Windows) {
		if (__dynlib_psapi_load())
			return adbg_oops(AdbgError.libLoader);
		
		enum SIZE = 512 * HMODULE.sizeof;
		HMODULE *mods = cast(HMODULE*)malloc(SIZE);
		DWORD needed = void;
		if (EnumProcessModules(tracee.hpid, mods, SIZE, &needed) == FALSE) {
			free(mods);
			return adbg_oops(AdbgError.os);
		}
		
		DWORD modcount = needed / HMODULE.sizeof;
		
		adbg_memory_map_t *map = *mmaps = cast(adbg_memory_map_t*)malloc(modcount * adbg_memory_map_t.sizeof);
		if (map == null) {
			free(mods);
			return adbg_oops(AdbgError.os);
		}
		
		size_t i; /// (user) map index
		for (DWORD mod_i; mod_i < modcount; ++mod_i) {
			HMODULE mod = mods[mod_i];
			MODULEINFO minfo = void;
			if (GetModuleInformation(tracee.hpid, mod, &minfo, MODULEINFO.sizeof) == FALSE) {
				continue;
			}
			// \Device\HarddiskVolume5\xyz.dll
			if (GetMappedFileNameA(tracee.hpid, minfo.lpBaseOfDll, map.name.ptr, MEM_MAP_NAME_LEN)) {
				// xyz.dll
				map.name[GetModuleBaseNameA(tracee.hpid, mod, map.name.ptr, MEM_MAP_NAME_LEN)] = 0;
			} else {
				map.name[0] = 0;
			}
			
			MEMORY_BASIC_INFORMATION mem = void;
			VirtualQuery(minfo.lpBaseOfDll, &mem, MEMORY_BASIC_INFORMATION.sizeof);
			
			// Needs a bit for Copy-on-Write?
			if (mem.AllocationProtect & PAGE_EXECUTE_WRITECOPY)
				map.access = AdbgMemPerm.readExec;
			else if (mem.AllocationProtect & PAGE_EXECUTE_READWRITE)
				map.access = AdbgMemPerm.all;
			else if (mem.AllocationProtect & PAGE_EXECUTE_READ)
				map.access = AdbgMemPerm.readExec;
			else if (mem.AllocationProtect & PAGE_EXECUTE)
				map.access = AdbgMemPerm.exec;
			else if (mem.AllocationProtect & PAGE_READONLY)
				map.access = AdbgMemPerm.read;
			else if (mem.AllocationProtect & PAGE_READWRITE)
				map.access = AdbgMemPerm.readWrite;
			else if (mem.AllocationProtect & PAGE_WRITECOPY)
				map.access = AdbgMemPerm.read;
			else
				map.access = 0;
			
			map.access |= mem.Type == MEM_PRIVATE ? AdbgMemPerm.private_ : AdbgMemPerm.shared_;
			
			map.base = minfo.lpBaseOfDll;
			map.size = minfo.SizeOfImage;
			
			++i; ++map;
		}
		
		free(mods);
		*mcount = i;
		return 0;
	} else version (linux) {
		// Inspired by libscanmem
		// https://github.com/scanmem/scanmem/blob/main/maps.c
		
		import core.stdc.stdlib : malloc, free;
		import core.sys.linux.unistd : readlink;
		import adbg.utils.strings : adbg_util_getline, adbg_util_getlinef;
		import core.sys.linux.unistd : read, close;
		import core.sys.linux.fcntl : open, O_RDONLY;
		
		*mcount = 0;
		
		// Formulate proc map path
		enum PROC_MAPS_LEN = 32;
		char[PROC_MAPS_LEN] proc_maps = void;
		snprintf(proc_maps.ptr, PROC_MAPS_LEN, "/proc/%u/maps", tracee.pid);
		version (Trace) trace("maps: %s", proc_maps.ptr);
		
		// Open process maps
		int fd_maps = open(proc_maps.ptr, O_RDONLY);
		if (fd_maps == -1)
			return adbg_oops(AdbgError.os);
		
		/*
		// Get proc exe path (e.g., /usr/bin/cat)
		enum PROC_EXE_LEN = 32;
		char[PROC_EXE_LEN] proc_exe = void;
		snprintf(proc_exe.ptr, PROC_EXE_LEN, "/proc/%u/exe", tracee.pid);
		
		// Read link from proc exe for process path
		enum EXE_PATH_LEN = 256;
		char[EXE_PATH_LEN] exe_path = void;
		version (Trace) trace("exe: %s", proc_exe.ptr);
		ssize_t linksz = readlink(proc_exe.ptr, exe_path.ptr, EXE_PATH_LEN);
		if (linksz > 0) {
			exe_path[linksz] = 0;
		} else { // Failed or empty
			exe_path[0] = 0;
		}*/
		
		// Allocate 2 MiB for input maps buffer
		// WebKit has about 164K worth of maps, for example
		// And then read as much as possible (not possible with fread!)
		enum READSZ = 2 * 1024 * 1024;
		//TODO: Consider mmap(2)
		char *procbuf = cast(char*)malloc(READSZ);
		if (procbuf == null) {
			version (Trace) trace("malloc failed");
			close(fd_maps);
			return adbg_oops(AdbgError.crt);
		}
		ssize_t readsz = read(fd_maps, procbuf, READSZ);
		if (readsz == -1) {
			version (Trace) trace("read failed");
			free(procbuf);
			close(fd_maps);
			return adbg_oops(AdbgError.os);
		}
		version (Trace) trace("flen=%zu", readsz);
		
		// Count number of newlines for number of items to allocate
		// Cut lines don't have newlines, so no worries here
		size_t itemcnt;
		for (size_t i; i < readsz; ++i)
			if (procbuf[i] == '\n') ++itemcnt;
		
		// Allocate map items
		version (Trace) trace("allocating %zu items", itemcnt);
		adbg_memory_map_t *map = *mmaps =
			cast(adbg_memory_map_t*)malloc(itemcnt * adbg_memory_map_t.sizeof);
		if (map == null) {
			free(procbuf);
			close(fd_maps);
			return adbg_oops(AdbgError.crt);
		}
		
		// Go through each entry, which may look like this (without header):
		// Address range             Perm Offset   Dev   inode      Path
		// 55adaf007000-55adaf009000 r--p 00000000 08:02 1311130    /usr/bin/cat
		// Perms: r=read, w=write, x=execute, s=shared or p=private (CoW)
		// Path: Path or [stack], [stack:%id] (3.4 to 4.4), [heap]
		//       [vdso]: virtual dynamic shared object: https://lwn.net/Articles/615809/
		//       [vvar]: Stores a "mirror" of kernel variables required by virt syscalls
		//       [vsyscall]: Legacy user-kernel (jump?) tables for some syscalls
		enum LINE_LEN = 256;
		char[LINE_LEN] line = void;
		size_t linesz = void; /// line size
		size_t srcidx; /// maps source buffer index
		size_t i; /// maps index
		//TODO: use a variant with mutable string and actively cuts lines
		while (adbg_util_getline(line.ptr, LINE_LEN, &linesz, procbuf, &srcidx)) {
			size_t range_start = void;
			size_t range_end   = void;
			char[4] perms      = void; // rwxp
			uint offset        = void;
			uint dev_major     = void;
			uint dev_minor     = void;
			uint inode         = void;
			
			if (sscanf(line.ptr, "%zx-%zx %4s %x %x:%x %u %512s",
				&range_start, &range_end,
				perms.ptr, &offset,
				&dev_major, &dev_minor,
				&inode, map.name.ptr) < 8) {
				continue;
			}
			
			// ELF load address regions
			//
			// When the ELF loader loads an executable or library image into
			// memory, there is one memory region per section created:
			// .text (r-x), .rodata (r--), .data (rw-), and .bss (rw-).
			//
			// The 'x' permission of .text is used to detect the load address
			// (start of memory region) and the end of the ELF file in memory.
			//
			// .bss section:
			// - Except for the .bss section, all memory sections typically
			//   have the same filename of the executable image.
			// - Empty filenames typically indicates .bss memory regions, and
			//   may be consecutive with .data memory regions.
			// - With some ELF images, .bss and .rodata may not be present.
			//
			// Resources:
			// http://en.wikipedia.org/wiki/Executable_and_Linkable_Format
			// http://wiki.osdev.org/ELF
			// http://lwn.net/Articles/531148/
			
			//TODO: Adjust memory region permissions like libscanmem does
			
			version (Trace) trace("entry: %zx %s", range_start, map.name.ptr);
			
			map.base = cast(void*)range_start;
			map.size = range_end - range_start;
			
			map.access = perms[3] == 'p' ? AdbgMemPerm.private_ : AdbgMemPerm.shared_;
			if (perms[0] == 'r') map.access |= AdbgMemPerm.read;
			if (perms[1] == 'w') map.access |= AdbgMemPerm.write;
			if (perms[2] == 'x') map.access |= AdbgMemPerm.exec;
			
			++i; ++map;
		}
		
		*mcount = i;
		free(procbuf);
		return 0;
	} else
		// FreeBSD: procstat(1) / pmap(9)
		// - https://man.freebsd.org/cgi/man.cgi?query=vm_map
		// - https://github.com/freebsd/freebsd-src/blob/main/lib/libutil/kinfo_getvmmap.c
		// - args[0] = CTL_KERN
		// - args[1] = KERN_PROC
		// - args[2] = KERN_PROC_VMMAP
		// - args[3] = pid
		// NetBSD: pmap(1) / uvm_map(9)
		// OpenBSD: procmap(1)
		// - kvm_open + kvm_getprocs + KERN_PROC_PID
		return adbg_oops(AdbgError.notImplemented);
}

void adbg_memory_maps_close(adbg_memory_map_t *maps) {
	if (maps) free(maps);
}

private bool adbg_mem_cmp_u8(void *v, void *c, size_t l) pure {
	return *cast(ubyte*)v != *cast(ubyte*)c;
}
private bool adbg_mem_cmp_u16(void *v, void *c, size_t l) pure {
	return *cast(ushort*)v != *cast(ushort*)c;
}
private bool adbg_mem_cmp_u32(void *v, void *c, size_t l) pure {
	return *cast(uint*)v != *cast(uint*)c;
}
private bool adbg_mem_cmp_u64(void *v, void *c, size_t l) pure {
	return *cast(ulong*)v != *cast(ulong*)c;
}
/*private bool adbg_mm_scan_u128(void *v, void *c, size_t l) {
	version (DigitalMars) {
		import core.simd : ubyte16, __simd, XMM, prefetch;
		ubyte16 v1 = void;
		ubyte16 v2 = void;
		prefetch!(false, 3)(v);
		prefetch!(false, 3)(c);
		v1 = *cast(ubyte16*)v;
		v2 = *cast(ubyte16*)c;
		return (cast(ubyte16)__simd(XMM.CMPSS, v1, v2, 0)).ptr[0] != 0;
	} else version (GNU) {
		
	} else version (LDC) {
		
	}
	version (D_SIMD) {
	} else {
		import core.stdc.string : memcmp;
		return memcmp(v, c, l) == 0;
	}
}*/
private bool adbg_mem_cmp_other(void *v, void *c, size_t l) pure {
	import core.stdc.string : memcmp;
	return memcmp(v, c, l) != 0;
}

/// Options for adbg_memory_scan.
enum AdbgScanOpt {
	/// Unaligned memory scans take a lot more time
	/// Type: bool
	/// Default: false
	unaligned	= 1,
	/// Set the initial capacity for results, other than the default.
	///
	/// Currently, the capacity does not increase dynamically.
	///
	/// Note: Currently, one entry is 24 Bytes.
	/// Type: int
	/// Default: 20_000
	capacity	= 3,
	// Report progress to callback.
	// Callback will report: stage name and a percentage on modules scanned.
	//progress_cb
	// Rescan this list instead. Don't forget to pass rescanListCount too.
	// Type: Internal
	//rescanList
	// Use this mmap list instead. Don't forget to pass customMMapCount too.
	//customMMap
	// To be used with customMMap.
	//customMMapCount
}

struct adbg_scan_t {
	adbg_process_t *process;
	adbg_scan_result_t *results;
	size_t result_count;
	
	adbg_memory_map_t *maps;
	size_t map_count;
}
struct adbg_scan_result_t {
	ulong address;
	adbg_memory_map_t *map; // base address
	union {
		ulong value_u64;
		uint value_u32;
		ushort value_u16;
		ubyte value__u8;
	}
}

//TODO: For future types, len(data) * capacity
//      Why? Data can be of any length (es. strings)
//      Bit of a à la Windows stragegy:
//      first buffer (uint.sizeof * capacity) is list of indexes
//      second buffer (len(str) * capacity) is list of strings
//      index points to list of strings
//      could/should make functions to help with that

/// Scan debuggee process memory for a specific value.
///
/// This function allocates the list to contain a list of 2000 items.
/// Memory allocated by this function can be freed using free(3).
/// This behavior may change in the future.
///
/// Example:
/// ---
/// adbg_scan_t scan;
/// int data = 42; // input
/// // Assume tracee is paused.
/// if (adbg_memory_scan(tracee, &scan, &data, int.sizeof, 0)) {
///     return; // Error
/// }
///
/// for (size_t i; i < count; ++i) {
///     printf("0x%llx", results[i]);
/// }
/// 
/// free(results);
/// ---
///
/// Params:
/// 	tracee = Tracee, in the ready or paused state.
/// 	data = Reference to user data.
/// 	size = Reference to user data size.
/// 	... = Options.
///
/// Returns: An instance of the scanner or null on error.
adbg_scan_t* adbg_memory_scan(adbg_process_t *tracee, void* data, size_t size, ...) {
	import adbg.v2.debugger.process : AdbgStatus;
	
	/// Until scanner gets better internals for variable-length
	/// data types.
	enum DATA_LIMIT = ulong.sizeof; 
	
	// Initial check and setup
	if (tracee == null || data == null) {
		adbg_oops(AdbgError.nullArgument);
		return null;
	}
	if (size == 0) {
		adbg_oops(AdbgError.scannerDataEmpty);
		return null;
	}
	if (size > DATA_LIMIT) {
		adbg_oops(AdbgError.scannerDataLimit);
		return null;
	}
	
	// Check debugger status
	switch (tracee.status) with (AdbgStatus) {
	case standby, paused, running: break;
	default:
		adbg_oops(AdbgError.notPaused);
		return null;
	}
	
	// Set options
	va_list list = void;
	va_start(list, size);
	int capacity = 20_000; /// Default amount of items to allocate.
L_OPT:
	switch (va_arg!int(list)) {
	case 0: break;
	case AdbgScanOpt.capacity:
		capacity = va_arg!int(list);
		goto L_OPT;
	default:
		adbg_oops(AdbgError.invalidOption);
		return null;
	}
	
	// Initial setup
	adbg_scan_t *scanner = cast(adbg_scan_t*)malloc(adbg_scan_t.sizeof);
	if (scanner == null) {
		adbg_oops(AdbgError.crt);
		return null;
	}
	scanner.process = tracee;
	
	// Get memory maps
	if (adbg_memory_maps(tracee, &scanner.maps, &scanner.map_count, 0))
		return null;
	
	// Get optimized compare func if able
	extern (C) bool function(void*, void*, size_t) cmp = void;
	switch (size) {
	case ulong.sizeof:	cmp = &adbg_mem_cmp_u64; break;
	case uint.sizeof:	cmp = &adbg_mem_cmp_u32; break;
	case ushort.sizeof:	cmp = &adbg_mem_cmp_u16; break;
	case ubyte.sizeof:	cmp = &adbg_mem_cmp_u8; break;
	default:		cmp = &adbg_mem_cmp_other;
	}
	
	// Make result list
	scanner.results = cast(adbg_scan_result_t*)calloc(capacity, adbg_scan_result_t.sizeof);
	if (scanner.results == null) {
		adbg_memory_scan_close(scanner);
		adbg_oops(AdbgError.crt);
		return null;
	}
	
	// Make read buffer
	ubyte *read_buffer = cast(ubyte*)malloc(size);
	if (read_buffer == null) {
		adbg_memory_scan_close(scanner);
		adbg_oops(AdbgError.crt);
		return null;
	}
	
	version (Trace) trace("modules=%u", cast(uint)scanner.map_count);
	
	// New scan: Scan per memory region
	enum PERMS = AdbgMemPerm.readWrite; /// Minimum permission access
	uint read_size = cast(uint)size;
	size_t i;
	scanner.result_count = 0;
	L_MODULE: for (size_t mi; mi < scanner.map_count; ++mi) {
		adbg_memory_map_t *map = &scanner.maps[mi];
		
		version (Trace) trace("perms=%x", map.access);
		
		//if ((map.access & PERMS) != PERMS)
		//	continue;
		
		//TODO: Module detection
		//      Scan through read+write non-exec sections
		
		void* start = map.base;
		void* end   = start + map.size;
		
		version (Trace) trace("start=%p end=%p", start, end);
		
		// Aligned reads for now
		for (; start + size < end; start += size) {
			// Read into buffer
			if (adbg_memory_read(tracee, cast(size_t)start, read_buffer, read_size)) {
				version (Trace)
					trace("read failed for %.512s", map.name.ptr);
				continue L_MODULE;
			}
			
			// Different data
			if (cmp(read_buffer, data, size)) {
				continue;
			}
			
			adbg_scan_result_t *result = &scanner.results[i++];
			result.address = cast(ulong)start;
			result.map = map;
			memcpy(&result.value_u64, read_buffer, size);
			
			// No more entries can be inserted
			if (i >= capacity)
				break L_MODULE;
		}
	}
	scanner.result_count = i;
	
	version (Trace) trace("results=%u", cast(uint)i);
	
	free(read_buffer); // Clear read buffer
	return scanner;
}

int adbg_memory_rescan(adbg_scan_t *scanner, void* data, size_t size) {
	// Initial check and setup
	if (scanner == null || data == null)
		return adbg_oops(AdbgError.nullArgument);
	if (size == 0)
		return adbg_oops(AdbgError.scannerDataEmpty);
	if (size > 8)
		return adbg_oops(AdbgError.scannerDataLimit);
	if (scanner.result_count == 0)
		return 0;
	// No prior scan performed
	if (scanner.process == null || scanner.map_count == 0) {
		scanner.result_count = 0;
		return 0;
	}
	
	// Make read buffer
	ubyte *read_buffer = cast(ubyte*)malloc(size);
	if (read_buffer == null)
		return adbg_oops(AdbgError.crt);
	
	// Get optimized compare func if able
	extern (C) bool function(void*, void*, size_t) cmp = void;
	switch (size) {
	case ulong.sizeof:	cmp = &adbg_mem_cmp_u64; break;
	case uint.sizeof:	cmp = &adbg_mem_cmp_u32; break;
	case ushort.sizeof:	cmp = &adbg_mem_cmp_u16; break;
	case ubyte.sizeof:	cmp = &adbg_mem_cmp_u8; break;
	default:		cmp = &adbg_mem_cmp_other;
	}
	
	uint read_size = cast(uint)size;
	// Strategy is to move items if we find a different value
	for (size_t i; i < scanner.result_count; ++i) {
		adbg_scan_result_t *result = &scanner.results[i];
		
		// Read into buffer
		// On fail: Could be that module was unloaded
		if (adbg_memory_read(scanner.process, cast(size_t)result.address, read_buffer, read_size)) {
			//TODO: trace()
			goto L_MOVE;
		}
		
		// Same data?
		if (cmp(read_buffer, &result.value_u64, size) == 0) {
			continue;
		}
		
	L_MOVE: // If data couldn't be read, or is different, then move results
		size_t c = --scanner.result_count;
		for (size_t ri = i; ri < c; ++ri) {
			memcpy(result, result + 1, adbg_scan_result_t.sizeof);
		}
	}
	
	return adbg_oops(AdbgError.unimplemented);
}

void adbg_memory_scan_close(adbg_scan_t *scanner) {
	if (scanner == null) return;
	if (scanner.maps) free(scanner.maps);
	if (scanner.results) free(scanner.results);
	free(scanner);
}