Newer
Older
alicedbg / src / adbg / debugger / memory.d
/// Utility function for memory management.
///
/// Authors: dd86k <dd@dax.moe>
/// Copyright: © dd86k <dd@dax.moe>
/// License: BSD-3-Clause-Clear
module adbg.debugger.memory;

import adbg.debugger.process : adbg_process_t;
import adbg.include.c.stdlib;
import adbg.include.c.stdio;
import adbg.include.c.stdarg;
import core.stdc.string : memcpy, strncpy;
import core.stdc.config : c_long;
import adbg.error;
import adbg.utils.math;

// NOTE: Linux ptrace memory I/O based on https://www.linuxjournal.com/article/6100
//       However, when possible, /proc/PID/mem is used.
//       On BSD, PT_IO is used.

version (Windows) {
	import core.sys.windows.winbase; // WriteProcessMemory
	import core.sys.windows.winnt;
	import adbg.include.windows.wow64apiset;
	import adbg.include.windows.psapi_dyn;
	import adbg.include.windows.ntdll;
} else version (Posix) {
	import core.sys.posix.sys.stat;
	import core.sys.posix.sys.uio;
	import core.sys.posix.fcntl;
	import adbg.include.posix.mann;
	import adbg.include.posix.ptrace;
	import adbg.include.posix.unistd;
	
	version (linux) {
		import adbg.include.linux.user;
		import core.stdc.errno : errno;
	}
}

extern (C):

/// Get the system configured size of a page, typically target's smallest size.
/// Returns: Page size in bytes; Or 0 on error.
size_t adbg_memory_pagesize() {
	version (Windows) {
		SYSTEM_INFO sysinfo = void;
		GetSystemInfo(&sysinfo);
		return sysinfo.dwPageSize;
	} else version (Posix) {
		// NOTE: sysconf, on error, returns -1
		c_long r = sysconf(_SC_PAGE_SIZE);
		return r < 0 ? 0 : r;
	}
}

//TODO: adbg_memory_hugesize
//      Windows: GetLargePageMinimum
//      Linux: /proc/meminfo:Hugepagesize
/*size_t adbg_memory_hugepagesize() {
}*/

//TODO: Provide a way to return number of bytes written/read
//      1. Could make size parameter a pointer
//      2. Could return a ptrdiff_t, -1 on error
/// 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.invalidArgument);
	
//TODO: FreeBSD/NetBSD/OpenBSD: 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) {
	// /mem handle is set and ready to be used
	if (tracee.mhandle > 0) {
LREAD:
		if (read(tracee.mhandle, data, size) < 1)
			return adbg_oops(AdbgError.os);
	}
	// /mem handle is not opened, try to
	else if (tracee.mhandle && tracee.memfailed == 0) {
		char[32] pathbuf = void;
		snprintf(pathbuf.ptr, 32, "/proc/%d/mem", tracee.pid);
		tracee.mhandle = open(pathbuf.ptr, O_RDWR);
		// Success? Try reading
		if (tracee.mhandle)
			goto LREAD;
		// On failure, mark fail and proceed to try ptrace fallback
		tracee.memfailed = 1;
	}
	
	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 // version (linux)
	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.invalidArgument);
	
	//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) {
	// /mem handle is set and ready to be used
	if (tracee.mhandle > 0) {
LWRITE:
		if (write(tracee.mhandle, data, size) < 1)
			return adbg_oops(AdbgError.os);
	}
	// /mem handle is not opened, try to
	else if (tracee.mhandle && tracee.memfailed == 0) {
		char[32] pathbuf = void;
		snprintf(pathbuf.ptr, 32, "/proc/%d/mem", tracee.pid);
		tracee.mhandle = open(pathbuf.ptr, O_RDWR);
		// Success? Try reading
		if (tracee.mhandle)
			goto LWRITE;
		// On failure, mark fail and proceed to try ptrace fallback
		tracee.memfailed = 1;
	}
	
	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);
	}
	
	//TODO: Save remainder before writing
	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 // version (linux)
	return adbg_oops(AdbgError.unimplemented);
}

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

/// Page usage.
enum AdbgPageUse : ubyte {
	/// Unknown.
	unknown,
	/// Private memory.
	resident,
	/// Slice or memory-mapped file.
	fileview,
	/// Module, like a shared object or dynamic linked library.
	module_,
}

private enum MEM_MAP_NAME_LEN = 512;
//TODO: Map groups
/// Represents a mapped memory region.
struct adbg_memory_map_t {
	//TODO: type (file, free, commited, etc.)
	/// Base memory region address.
	void *base;
	/// Size of region.
	size_t size;
	/// Access permissions.
	ushort access;
	/// Page type (private, image, view, etc.)
	ubyte type;
	/// Page attributes (large, etc.)
	ubyte attributes;
	//TODO: Should take this out into its own function
	//      e.g., adbg_memory_get_map_name()
	/// Module name or mapped file.
	char[MEM_MAP_NAME_LEN] name;
}

//TODO: Options for process modules and process memory regions separatively
//TODO: Option to include free/reserved memory regions (linux: ---p)
// Memory options for adbg_memory_maps.
/*enum AdbgMapOpt {
	// Only get the memory regions for this process.
	// Type: int
	// Default: 0 (false)
	//processOnly	= 2,
	// With given Process ID instead
	// Permission issues may be raised
	//pid = 2,
}*/

//TODO: Process name hash (here cached in structure or on stack)
//      Function will eventually have to filter out of a lot of entries
//      Especially on Linux.
/// Obtain the memory map of modules for the current process.
///
/// To close, call adbg_memory_maps_close. On error, all memory buffers
/// are cleaned.
/// 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.invalidArgument);
	
	// Get options
	va_list list = void;
	va_start(list, mcount);
	int options;
L_OPTION:
	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.debuggerUnattached);
	
	// Add EnumPageFilesA?
version (Windows) {
	if (__dynlib_psapi_load()) // EnumProcessModules, QueryWorkingSet
		return adbg_errno();
	
	size_t uindex; /// (user) map index
	
	// Create user buffer
	adbg_memory_map_t *map = *mmaps = cast(adbg_memory_map_t*)malloc(2048 * adbg_memory_map_t.sizeof);
	if (map == null)
		return adbg_oops(AdbgError.os);
	
	//
	// Query memory regions for process
	//
	
	// NOTE: Putty 0.80 will have around 1095 entries
	uint bfsz = MiB!1;
	PSAPI_WORKING_SET_INFORMATION *mbinfo =
		cast(PSAPI_WORKING_SET_INFORMATION*)malloc(bfsz);
	if (mbinfo == null)
		return adbg_oops(AdbgError.crt);
	
	// NOTE: NtPssCaptureVaSpaceBulk is only available since Windows 10 20H1
	// This queries workset addresses regardless of size, page-bounded.
	// e.g., it will add 0x30000 and 0x31000 as entries, despite being a 8K "block".
LRETRY:
	uint r = QueryWorkingSet(tracee.hpid, mbinfo, bfsz);
	switch (r) {
	case 0:
		return adbg_oops(AdbgError.os);
	case ERROR_BAD_LENGTH:
		bfsz = cast(uint)(mbinfo.NumberOfEntries * ULONG_PTR.sizeof);
		mbinfo = cast(PSAPI_WORKING_SET_INFORMATION*)realloc(mbinfo, bfsz);
		if (mbinfo == null)
			return adbg_oops(AdbgError.crt);
		goto LRETRY;
	default:
	}
	
	scope(exit) free(mbinfo);
	
	size_t pagesize = adbg_memory_pagesize();
	if (pagesize == 0)
		return adbg_oops(AdbgError.os);
	
	//TODO: Huge page support
	//TODO: Use MEMORY_BASIC_INFORMATION32 or 64 depending on wow64
	
	//PSAPI_WORKING_SET_EX_INFORMATION wsinfoex = void;
	for (size_t i; i < mbinfo.NumberOfEntries; ++i) {
		// NOTE: Win64 doesn't populate block flag bits
		PSAPI_WORKING_SET_BLOCK *blk = &mbinfo.WorkingSetInfo.ptr[i];
		
		/*TODO: Large page support + adjustment needed
		
		wsinfoex.VirtualAddress = cast(void*)blk.VirtualPage;
		if (QueryWorkingSetEx(tracee.hpid, &wsinfoex,
			PSAPI_WORKING_SET_EX_INFORMATION.sizeof) == 0)
			continue;
		
		import adbg.utils.bit : adbg_bits_extract32;
		
		uint pageflags = cast(uint)wsinfoex.VirtualAttributes.Flags;
		
		version (Trace) trace("page=%zx attr=%zx valid=%d share=%d prot=%d shared=%d node=%d locked=%d large=%d bad=%d",
			cast(size_t)wsinfoex.VirtualAddress,
			wsinfoex.VirtualAttributes.Flags,
			adbg_bits_extract32(pageflags, 1, 0),
			adbg_bits_extract32(pageflags, 3, 1),
			adbg_bits_extract32(pageflags, 11, 4),
			adbg_bits_extract32(pageflags, 1, 15),
			adbg_bits_extract32(pageflags, 6, 16),
			adbg_bits_extract32(pageflags, 1, 22),
			adbg_bits_extract32(pageflags, 1, 23),
			adbg_bits_extract32(pageflags, 1, 31));*/
		
		// Query with whatever page value
		MEMORY_BASIC_INFORMATION mem = void;
		if (VirtualQueryEx(tracee.hpid, cast(void*)blk.VirtualPage,
			&mem, MEMORY_BASIC_INFORMATION.sizeof) == 0) {
			continue;
		}
		
		// Skip memory region when non-commited
		// Usually never happens with addresses given by QueryWorkingSet
		if (mem.State & (MEM_FREE | MEM_RESERVE))
			continue;
		
		// Get mapped file
		if (GetMappedFileNameA(tracee.hpid, mem.BaseAddress, map.name.ptr, MEM_MAP_NAME_LEN)) {
			map.name[GetModuleFileNameExA(tracee.hpid, null, map.name.ptr, MEM_MAP_NAME_LEN)] = 0;
		} else {
			map.name[0] = 0;
		}
		
		// Adjust protection bits
		map.access = mem.Type & MEM_PRIVATE ? AdbgMemPerm.private_ : 0;
		if (mem.Protect & PAGE_EXECUTE_WRITECOPY)
			map.access |= AdbgMemPerm.readExec;
		else if (mem.Protect & PAGE_EXECUTE_READWRITE)
			map.access |= AdbgMemPerm.all;
		else if (mem.Protect & PAGE_EXECUTE_READ)
			map.access |= AdbgMemPerm.readExec;
		else if (mem.Protect & PAGE_EXECUTE)
			map.access |= AdbgMemPerm.exec;
		else if (mem.Protect & PAGE_WRITECOPY)
			map.access |= AdbgMemPerm.read;
		else if (mem.Protect & PAGE_READWRITE)
			map.access |= AdbgMemPerm.readWrite;
		else if (mem.Protect & PAGE_READONLY)
			map.access |= AdbgMemPerm.read;
		
		if (mem.Type & MEM_IMAGE)
			map.type = AdbgPageUse.module_;
		else if (mem.Type & MEM_MAPPED)
			map.type = AdbgPageUse.fileview;
		else if (mem.Type & MEM_PRIVATE)
			map.type = AdbgPageUse.resident;
		else
			map.type = AdbgPageUse.unknown;
		
		map.base = mem.BaseAddress;
		map.size = mem.RegionSize;
		
		++uindex; ++map;
		
		// Memory region is less or equal to pagesize?
		// No further adjustments to do
		if (mem.RegionSize <= pagesize)
			continue;
		
		// Otherwise, get ready to skip future pages if they are
		// related (memory address and size follows by pagesize).
		void *end = mem.BaseAddress + mem.RegionSize;
		while (i + 1 < mbinfo.NumberOfEntries) {
			void *page = cast(void*)mbinfo.WorkingSetInfo.ptr[i + 1].VirtualPage;
			if (VirtualQueryEx(tracee.hpid, page,
				&mem, MEMORY_BASIC_INFORMATION.sizeof) == 0)
				break;
			if (mem.BaseAddress > end) break;
			++i;
		}
	}
	
	//
	// Query modules for process
	//
	
	// Allocate temp buffer for module handles
	uint buffersz = cast(uint)(512 * HMODULE.sizeof);
	HMODULE *mods = cast(HMODULE*)malloc(buffersz);
	if (mods == null)
		return adbg_oops(AdbgError.crt);
	scope(exit) free(mods);
	
	// Enum process modules
	DWORD needed = void; //TODO: Could re-use this with option
	if (EnumProcessModules(tracee.hpid, mods, buffersz, &needed) == FALSE)
		return adbg_oops(AdbgError.os);
	
	DWORD modcount = needed / HMODULE.sizeof;
	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;
		}
		
		// Get base name (e.g., from \Device\HarddiskVolume5\xyz.dll)
		if (GetMappedFileNameA(tracee.hpid, minfo.lpBaseOfDll, map.name.ptr, MEM_MAP_NAME_LEN)) {
			map.name[GetModuleFileNameExA(tracee.hpid, mod, map.name.ptr, MEM_MAP_NAME_LEN)] = 0;
		} else {
			map.name[0] = 0;
		}
		
		//TODO: version (Win64) if (proc.wow) use MEMORY_BASIC_INFORMATION32
		
		MEMORY_BASIC_INFORMATION mem = void;
		if (VirtualQueryEx(tracee.hpid, minfo.lpBaseOfDll, &mem, MEMORY_BASIC_INFORMATION.sizeof) == 0) {
			continue;
		}
		
		// Adjust protection bits
		map.access = mem.Type & MEM_PRIVATE ? AdbgMemPerm.private_ : 0;
		if (mem.Protect & PAGE_EXECUTE_WRITECOPY)
			map.access |= AdbgMemPerm.readExec;
		else if (mem.Protect & PAGE_EXECUTE_READWRITE)
			map.access |= AdbgMemPerm.all;
		else if (mem.Protect & PAGE_EXECUTE_READ)
			map.access |= AdbgMemPerm.readExec;
		else if (mem.Protect & PAGE_EXECUTE)
			map.access |= AdbgMemPerm.exec;
		else if (mem.Protect & PAGE_WRITECOPY)
			map.access |= AdbgMemPerm.read;
		else if (mem.Protect & PAGE_READWRITE)
			map.access |= AdbgMemPerm.readWrite;
		else if (mem.Protect & PAGE_READONLY)
			map.access |= AdbgMemPerm.read;
		
		map.type = AdbgPageUse.module_;
		map.base = minfo.lpBaseOfDll;
		map.size = minfo.SizeOfImage;
		
		++uindex; ++map;
	}
	
	*mcount = uindex;
	return 0;
} else version (linux) {
	// Inspired by libscanmem
	// https://github.com/scanmem/scanmem/blob/main/maps.c
	
	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);
	scope(exit) close(fd_maps);
	
	/*
	// 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 enough for maps buffer
	// For example: One Firefox process has around 149 KiB worth of
	// maps data with 1953 entries.
	enum READSZ = MiB!1;
	char *procbuf = cast(char*)malloc(READSZ);
	if (procbuf == null)
		return adbg_oops(AdbgError.crt);
	scope(exit) free(procbuf);
	
	// Read maps, as much as possible
	ssize_t readsz = read(fd_maps, procbuf, READSZ);
	if (readsz == -1)
		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)
		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/rwxs
		uint offset        = void;
		uint dev_major     = void;
		uint dev_minor     = void;
		uint inode         = void;
		
		//TODO: Check for (deleted) column (last)
		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;
		}
		
		// Skip regions with empty permissions as they seem unallocated
		if (perms[0] == '-' && perms[1] == '-' && perms[2] == '-')
			continue;
		
		// NOTE: ELF regions with same executable path
		//
		// section  perms  comment
		// .text:   r-x
		// .rodata: r--    could be absent
		// .data:   rw-
		// .bss:    rw-    empty path and inode=0, could be absent
		
		//TODO: Adjust memory region permissions like libscanmem does
		
		version (Trace) trace("entry: %zu %zx %s", i, range_start, map.name.ptr);
		
		map.base = cast(void*)range_start;
		map.size = range_end - range_start;
		
		bool priv = perms[3] == 'p';
		
		//if (offset)
		//	map.type = AdbgPageUse.view;
		//TODO: procfs name
		//else if (strcmp(procname, map.name.ptr) == 0)
		//	map.type = AdbgPageUse.image;
		//else
			map.type = AdbgPageUse.resident;
		
		map.access = priv ? AdbgMemPerm.private_ : 0;
		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;
	}
	
	version (Trace) trace("finished");
	*mcount = i;
	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);
}

/// Close the memory maps structure previously created by adbg_memory_maps.
/// Params: maps = Maps array.
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: int
	/// 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.
/// 	datasize = 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 datasize, ...) {
	import adbg.debugger.process : AdbgProcStatus;
	
	/// Until scanner gets better internals for variable-length
	/// data types. Don't want to scan gigabyte-sized types now.
	enum DATA_LIMIT = 4096;
	
	/// Default amount of items to allocate.
	enum DEFAULT_CAPACITY = 20_000;
	
	enum OPT_UNALIGNED = 1;
	
	// Initial check and setup
	if (tracee == null || data == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	if (datasize == 0) {
		adbg_oops(AdbgError.scannerDataEmpty);
		return null;
	}
	if (datasize > DATA_LIMIT) {
		adbg_oops(AdbgError.scannerDataLimit);
		return null;
	}
	
	// Check debugger status
	switch (tracee.status) with (AdbgProcStatus) {
	case standby, paused, running: break;
	default:
		adbg_oops(AdbgError.debuggerUnpaused);
		return null;
	}
	
	// Get options
	va_list list = void;
	va_start(list, datasize);
	int options;
	int capacity = DEFAULT_CAPACITY; // For results
L_OPT:
	switch (va_arg!int(list)) {
	case 0: break;
	case AdbgScanOpt.unaligned:
		if (va_arg!int(list)) options |= OPT_UNALIGNED;
		goto L_OPT;
	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 (datasize) {
	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*)malloc(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(datasize);
	if (read_buffer == null) {
		adbg_memory_scan_close(scanner);
		adbg_oops(AdbgError.crt);
		return null;
	}
	scope(exit) free(read_buffer);
	
	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)datasize;
	size_t jmpsize = options & OPT_UNALIGNED ? 1 : datasize;
	size_t modcount = scanner.map_count;
	size_t i;
	scanner.result_count = 0;
	//TODO: Consider reading a page's worth instead of T.sizeof
	//TODO: Skip non-residential entries (waiting on Linux fix)
LENTRY:	for (size_t mi; mi < modcount; ++mi) {
		adbg_memory_map_t *map = &scanner.maps[mi];
		
		version (Trace) trace("perms=%x", map.access);
		
		//if ((map.access & PERMS) != PERMS)
		//	continue;
		
		void* start = map.base;
		void* end   = start + map.size;
		
		version (Trace) trace("start=%p end=%p", start, end);
		
		// Aligned reads for now
		for (; start + datasize < end; start += jmpsize) {
			// 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 LENTRY;
			}
			
			// Different data
			if (cmp(read_buffer, data, datasize)) {
				continue;
			}
			
			// Add result
			adbg_scan_result_t *result = &scanner.results[i++];
			result.address = cast(ulong)start;
			result.map = map;
			memcpy(&result.value_u64, read_buffer, datasize);
			
			// No more entries can be inserted
			if (i >= capacity)
				break LENTRY;
		}
	}
	scanner.result_count = i;
	
	version (Trace) trace("results=%u", cast(uint)i);
	
	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.invalidArgument);
	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);
}