Newer
Older
alicedbg / src / adbg / object / server.d
/// Object server.
///
/// The goal of the object/image loader is being able to obtain information
/// from object files such as:
/// - Object Type;
/// - Machine architecture;
/// - Symbols;
/// - Debugging information (types, etc.);
/// - And a few extras for dumping purposes.
///
/// Implementating these services from scratch gives a few benefits:
/// - Control, all information from objects is available.
/// - Flexbility, the operating system might limited in options for symbol discovery.
/// - Fallbacks, such as selecting at least one source for symbols.
///
/// The way this is structured is simple: This module provides a generic object API
/// and implements basic I/O for submodules to use.
/// 
/// The submodules that implementing specific object formats manage their own internal
/// memory buffers.
///
/// Authors: dd86k <dd@dax.moe>
/// Copyright: © dd86k <dd@dax.moe>
/// License: BSD-3-Clause-Clear
module adbg.object.server;

import adbg.debugger.memory : adbg_memory_read;
import adbg.debugger.process : adbg_process_t;
import adbg.error;
import adbg.machines : AdbgMachine, adbg_machine_name;
import adbg.utils.math;	// For MiB template
import adbg.object.formats;
import adbg.include.c.stdio;
import adbg.include.c.stdlib;
import adbg.include.c.stdarg;
import adbg.os.file;
import core.stdc.string;

extern (C):

// NOTE: Function names
//       At best, prefer adbg_object_OBJECT_xyz where OBJECT is the type
//       (e.g., pe, elf, etc.) to make things consistent. This is why
//       auxiliary names are simply "adbg_object_offset", for example.

// TODO: Consider structure definition, using a template
//      Uses:
//      - For swapping, uses less code than inlining it
//      - For displaying and using field offsets
// TODO: adbg_object_endiannes
//       Why? Machine module do not include endianness.
//       And would be beneficial when host has incompatible endianness.
// TODO: adbg_object_open_process(int pid, ...)
// TODO: adbg_object_open_buffer(void *buffer, size_t size, ...)
// TODO: Consider function to ease submodule setup
//       adbg_object_setup(AdbgObject type, size_t internal_buffer_size, void function(adbg_object_t*) fclose)

/// Executable or object file format.
enum AdbgObject {
	/// Raw binary file, or unknown object format.
	raw,
	/// Ditto
	unknown = raw,
	/// Mark Zbikowski format. (.exe)
	mz,
	/// New Executable format. (.exe)
	ne,
	/// Linked Executable/LX format. (.exe)
	lx,
	/// Portable Executable format. (.exe)
	pe,
	/// Executable and Linkable Format.
	elf,
	/// Mach Object format.
	macho,
	/// Microsoft Program Database format 2.0. (.pdb)
	pdb20,
	/// Microsoft Program Database format 7.0. (.pdb)
	pdb70,
	/// Windows memory dump format. (.dmp)
	dmp,
	/// Windows Minidump format. (.mdmp)
	mdmp,
	/// OMF object or library. (.obj, .lib)
	omf,
	/// COFF Library archive. (.lib)
	archive,
	/// COFF object. (.obj)
	coff,
	/// MSCOFF object. (.obj)
	mscoff,
}

/// Object origin. Used in adbg_object_read.
private
enum AdbgObjectOrigin {
	/// Object is unloaded, or the loading method is unknown.
	unknown,
	/// Object was loaded from disk.
	disk,
	/// Object was loaded from the debugger into memory.
	process,
	/// 
	userbuffer,
}

package
enum AdbgObjectInternalFlags {
	//TODO: Rename to swapped since this is a little confusing
	/// Object has its fields swapped because of its target endianness.
	swapped	= 0x1,
	/// Old alias for swapped.
	reversed = swapped,
}

struct adbg_section_t {
	void *header;
	size_t header_size;
	void *data;
	size_t data_size;
}

/// Represents a file object image.
///
/// All fields are used internally and should not be used directly.
struct adbg_object_t {
	// NOTE: It's important that none of the modules rely on a total size,
	//       like a file size, since the origin can be of any kind (e.g., a process).
	//       However, it must be seekable.
	deprecated union {
		struct {
			union {
				void   *buffer;	/// Buffer to file object.
				char   *bufferc;	/// Ditto
				wchar  *bufferw;	/// Ditto
				dchar  *bufferd;	/// Ditto
				ubyte  *buffer8;	/// Ditto
				ushort *buffer16;	/// Ditto
				uint   *buffer32;	/// Ditto
				ulong  *buffer64;	/// Ditto
			}
			
			/// File handle to object.
			FILE *file_handle;
			/// File size.
			ulong file_size;
			/// Allocated buffer size.
			size_t buffer_size;
		}
	}
	package union {
		struct {
			OSFILE *file;
		}
		struct {
			adbg_process_t *process;
			size_t location;
		}
		struct {
			void *user_buffer;
			size_t user_size;
		}
	}
	
	/// Object's loading origin.
	AdbgObjectOrigin origin;
	/// Loaded object format.
	AdbgObject format;
	
	// NOTE: Sub modules are free to use upper 16 bits
	/// Internal status flags. (e.g., swapping required)
	int status;
	
	// NOTE: This can be turned into a static buffer.
	/// Managed by the object handler.
	void *internal;
	
	package:
	
	/// Used to attach the unload function
	void function(adbg_object_t*) func_unload;
	
	//TODO: Deprecate *all* of this
	
	// Object properties.
	package
	struct adbg_object_properties_t {
		/// Target endianness is reversed and therefore fields needs
		/// to be byte-swapped.
		bool reversed;
		// Target object has code that can be run on this platform.
		// Examples: x86 on x86-64 or Arm A32 on Arm A64 via WoW64
		//TODO: bool platform_native;
		
		// Temp field to avoid free'ing non-allocated memory
		bool noalloc;
		
		// 
		size_t debug_offset;
	}
	deprecated
	adbg_object_properties_t p;
	
	// Pointers to machine-dependant structures
	//TODO: Move stuff like "reversed_"/"is64"/etc. fields into properties
	package
	union adbg_object_internals_t {
		// Main header. All object files have some form of header.
		void *header;
		
		struct pdb20_t {
			pdb20_file_header *header;
		}
		pdb20_t pdb20;
		
		struct pdb70_t {
			pdb70_file_header *header;
			ubyte *fpm;	/// Points to used FPM block
			void *dir;	/// Directory buffer (Stream 0)
			
			// Stream 0 meta
			uint strcnt;	/// Stream count
			uint *strsize;	/// Stream size (Points to Stream[0].size)
			uint *stroff;	/// Block IDs (Points to Stream[0].block[0])
			
			// Lookup table made from Stream 0
			pdb70_stream *strmap;	/// Stream mapping
		}
		pdb70_t pdb70;
		
		struct omf_t {
			omf_lib_header *header;
			int pgsize;
			int firstentry;
		}
		omf_t omf;
	}
	/// Internal object definitions.
	deprecated
	adbg_object_internals_t i;
}

/// Check if pointer is outside the object bounds.
/// Params:
/// 	o = Object instance.
/// 	p = Pointer.
/// Returns: True if outside bounds.
deprecated
bool adbg_object_outboundp(adbg_object_t *o, void *p) {
	version (Trace) trace("p=%zx", cast(size_t)p);
	return p < o.buffer || p >= o.buffer + o.file_size;
}
/// Check if pointer with length is outside the object bounds.
/// Params:
/// 	o = Object instance.
/// 	p = Pointer.
/// 	size = Data size.
/// Returns: True if outside bounds.
deprecated
bool adbg_object_outboundpl(adbg_object_t *o, void *p, size_t size) {
	version (Trace) trace("p=%zx length=%zu", cast(size_t)p, size);
	return p < o.buffer || p + size >= o.buffer + o.file_size;
}

/// Check if offset is within file boundaries
/// Params:
/// 	o = Object instance.
/// 	off = File offset.
/// Returns: True if in bounds.
deprecated
bool adbg_object_outbound(adbg_object_t *o, ulong off) {
	version (Trace) trace("offset=%llx", off);
	if (o == null) return true;
	return off >= o.file_size;
}

/// Check if offset with size is within file boundaries
/// Params:
/// 	o = Object instance.
/// 	off = File offset.
/// 	size = Data size.
/// Returns: True if in bounds.
deprecated
bool adbg_object_outboundl(adbg_object_t *o, ulong off, size_t size) {
	version (Trace) trace("offset=%llx length=%zu", off, size);
	if (o == null) return true;
	return off + size > o.file_size;
}

/// Get pointer from offset.
/// Params:
/// 	o = Object instance.
/// 	p = Destination pointer.
/// 	offset = File offset.
/// Returns: True if outside bounds.
deprecated
bool adbg_object_offset(adbg_object_t *o, void** p, ulong offset) {
	version (Trace) trace("p=%zx offset=%llx", cast(size_t)p, offset);
	if (p == null) return true;
	if (adbg_object_outbound(o, offset)) return true;
	*p = o.buffer + offset;
	return false;
}

/// Get pointer from offset with size.
/// Params:
/// 	o = Object instance.
/// 	p = Destination pointer.
/// 	offset = File offset.
/// 	size = Data size.
/// Returns: True if outside bounds.
deprecated
bool adbg_object_offsetl(adbg_object_t *o, void** p, ulong offset, size_t size) {
	version (Trace) trace("p=%zx offset=%llx length=%zu", cast(size_t)p, offset, size);
	if (p == null) return true;
	if (adbg_object_outboundl(o, offset, size)) return true;
	*p = o.buffer + offset;
	return false;
}
unittest {
	adbg_object_t o = void;
	o.buffer = cast(void*)0x10;
	o.file_size = 10_000;
	void *p;
	assert(adbg_object_offsetl(&o, &p, 0x10, 100) == false);
	assert(cast(size_t)p == 0x20);
}

/// Template helper to get pointer from offset with length automatically.
/// Params:
/// 	o = Object instance.
/// 	dst = Destination pointer.
/// 	offset = File offset.
/// Returns: True if outside bounds.
deprecated
bool adbg_object_offsett(T)(adbg_object_t *o, T* dst, ulong offset) {
	if (dst == null) return true;
	if (adbg_object_outboundl(o, offset, T.sizeof)) return true;
	static if (T.sizeof <= ulong.sizeof)
		*dst = *cast(T*)(o.buffer + offset);
	else
		static assert(0, "adbg_object_offsett memcpy TODO");
	return false;
}

/// Load an object from disk into memory.
///
/// This function allocates memory.
/// Params:
///   path = File path.
///   ... = Options. Terminated with 0.
/// Returns: Object instance, or null on error.
export
adbg_object_t* adbg_object_open_file(const(char) *path, ...) {
	version (Trace) trace("path=%s", path);
	
	adbg_object_t *o = cast(adbg_object_t*)malloc(adbg_object_t.sizeof);
	if (o == null) {
		adbg_oops(AdbgError.crt);
		return null;
	}
	
	//TODO: To remove
	o.file_handle = fopen(path, "rb");
	if (o.file_handle == null) {
		free(o);
		adbg_oops(AdbgError.crt);
		return null;
	}
	
	o.file = osfopen(path, OSFileOFlags.read);
	if (o.file == null) {
		free(o);
		adbg_oops(AdbgError.os);
		return null;
	}
	
	o.origin = AdbgObjectOrigin.disk;
	
	if (adbg_object_loadv(o)) {
		adbg_object_close(o);
		return null;
	}
	
	return o;
}

/// Close object instance.
/// Params: o = Object instance.
export
void adbg_object_close(adbg_object_t *o) {
	if (o == null)
		return;
	//TODO: Consider attaching function pointer for unloading.
	//      This would help submodule management.
	switch (o.format) with (AdbgObject) {
	case mz:	adbg_object_mz_unload(o); break;
	case ne:	adbg_object_ne_unload(o); break;
	case lx:	adbg_object_lx_unload(o); break;
	case pe:	adbg_object_pe_unload(o); break;
	case macho:	adbg_object_macho_unload(o); break;
	case elf:	adbg_object_elf_unload(o); break;
	case pdb70:
		//TODO: Remove junk
		with (o.i.pdb70) if (dir) free(dir);
		with (o.i.pdb70) if (strmap) free(strmap);
		break;
	case archive:	adbg_object_ar_unload(o); break;
	default:
	}
	if (o.file) osfclose(o.file);
	if (o.file_handle)
		fclose(o.file_handle);
	if (o.buffer && o.buffer_size)
		free(o.buffer);
	free(o);
}

int adbg_object_read(adbg_object_t *o, void *buffer, size_t rdsize, int flags = 0) {
	version (Trace) trace("buffer=%p rdsize=%zu", buffer, rdsize);
	
	if (o == null || buffer == null)
		return adbg_oops(AdbgError.invalidArgument);
	if (rdsize == 0)
		return 0;
	
	version (Trace) trace("origin=%d", o.origin);
	switch (o.origin) with (AdbgObjectOrigin) {
	case disk:
		int target = cast(int)rdsize;
		int r = osfread(o.file, buffer, target);
		version (Trace) trace("osfread=%d", r);
		if (r < 0)
			return adbg_oops(AdbgError.os);
		if (r < target)
			return adbg_oops(AdbgError.partialRead);
		return 0;
	case process:
		return adbg_memory_read(o.process, o.location, buffer, cast(uint)rdsize);
	//case userbuffer:
		//if (location + rsize >= o.buffer_size)
		//	return adbg_oops(AdbgError.objectOutsideAccess);
		//if (memcpy(buffer, o.buffer + location, rsize))
		//	return adbg_oops(AdbgError.crt);
	default:
	}
	
	return adbg_oops(AdbgError.unimplemented);
}

//TODO: adbg_object_readalloc_at: Allocate memory and read.
//      void* adbg_object_readalloc_at(adbg_object_t *o, long location, size_t rdsize, int flags)

/// Read raw data from object at absolute position.
/// Params:
/// 	o = Object instance.
/// 	location = Absolute file offset.
/// 	buffer = Buffer pointer.
/// 	rdsize = Size to read.
/// 	flags = Additional settings.
/// Returns: Zero on success; Otherwise error code.
int adbg_object_read_at(adbg_object_t *o, long location, void *buffer, size_t rdsize, int flags = 0) {
	version (Trace) trace("location=%lld buffer=%p rdsize=%zu", location, buffer, rdsize);
	
	if (o == null || buffer == null) {
		adbg_oops(AdbgError.invalidArgument);
		return -1;
	}
	if (rdsize == 0)
		return 0;
	
	switch (o.origin) with (AdbgObjectOrigin) {
	case disk:
		if (osfseek(o.file, location, OSFileSeek.start) < 0)
			return adbg_oops(AdbgError.os);
		break;
	case process:
		o.location = cast(size_t)location;
		break;
	default:
		return adbg_oops(AdbgError.unimplemented);
	}
	
	return adbg_object_read(o, buffer, rdsize);
}

/// Size of signature buffer.
private enum SIGMAX = MAX!(PDB20_MAGIC.length, PDB70_MAGIC.length);

/// Used in signature detection.
private
union SIGNATURE {
	ubyte[SIGMAX] buffer;
	ulong u64;
	uint u32;
	ushort u16;
	ubyte u8;
	mz_header_t mzheader;
}

// Object detection and loading
private
int adbg_object_loadv(adbg_object_t *o) {
	if (o == null)
		return adbg_oops(AdbgError.invalidArgument);
	
	o.status = 0;
	
	// Load minimum for signature detection
	// Also tests seeking in case this is a streamed input
	SIGNATURE sig = void;
	int siglen = osfread(o.file, &sig, SIGNATURE.sizeof); /// signature size
	version (Trace) trace("siglen=%d sigmax=%u", siglen, cast(uint)SIGMAX);
	if (siglen < 0)
		return adbg_oops(AdbgError.os);
	if (siglen <= uint.sizeof)
		return adbg_oops(AdbgError.objectTooSmall);
	if (osfseek(o.file, 0, OSFileSeek.start) < 0) // Reset offset, test seek
		return adbg_oops(AdbgError.os);
	
	// Magic detection over 8 Bytes
	if (siglen > PDB20_MAGIC.length &&
		memcmp(sig.buffer.ptr, PDB20_MAGIC.ptr, PDB20_MAGIC.length) == 0)
		return adbg_object_pdb20_load(o);
	if (siglen > PDB70_MAGIC.length &&
		memcmp(sig.buffer.ptr, PDB70_MAGIC.ptr, PDB70_MAGIC.length) == 0)
		return adbg_object_pdb70_load(o);
	
	// 64-bit signature detection
	version (Trace) trace("u64=%#x", sig.u64);
	if (siglen > ulong.sizeof) switch (sig.u64) {
	case AR_MAGIC:
		return adbg_object_ar_load(o);
	case PAGEDUMP32_MAGIC, PAGEDUMP64_MAGIC:
		return adbg_object_dmp_load(o);
	default:
	}
	
	// 32-bit signature detection
	version (Trace) trace("u32=%#x", sig.u32);
	if (siglen > uint.sizeof) switch (sig.u32) {
	case ELF_MAGIC:	// ELF
		return adbg_object_elf_load(o);
	case MACHO_MAGIC:	// Mach-O 32-bit
	case MACHO_MAGIC64:	// Mach-O 64-bit
	case MACHO_CIGAM:	// Mach-O 32-bit reversed
	case MACHO_CIGAM64:	// Mach-O 64-bit reversed
	case MACHO_FATMAGIC:	// Mach-O Fat
	case MACHO_FATCIGAM:	// Mach-O Fat reversed
		return adbg_object_macho_load(o, sig.u32);
	case MDMP_MAGIC:
		return adbg_object_mdmp_load(o);
	default:
	}
	
	// 16-bit signature detection
	version (Trace) trace("u16=%#x", sig.u16);
	if (siglen > ushort.sizeof) switch (sig.u16) {
	// Anonymous MSCOFF
	case 0:
		if (siglen > uint.sizeof && (sig.u32 >> 16) == 0xffff)
			return adbg_object_mscoff_load(o);
		break;
	// MZ executables
	case MAGIC_MZ:
		version (Trace) trace("e_lfarlc=%#x", sig.mzheader.e_lfarlc);
		
		// If e_lfarlc (relocation table) starts lower than e_lfanew,
		// then assume old MZ.
		// NOTE: e_lfarlc can point to 0x40.
		if (sig.mzheader.e_lfarlc < 0x40)
			return adbg_object_mz_load(o);
		
		// If e_lfanew points within (extended) MZ header
		if (sig.mzheader.e_lfanew <= mz_header_t.sizeof)
			return adbg_object_mz_load(o);
		
		// ReactOS checks if NtHeaderOffset is not higher than 256 MiB
		//TODO: Consider if malformed.
		if (sig.mzheader.e_lfanew >= MiB!256)
			return adbg_object_mz_load(o);
		
		uint newsig = void;
		if (adbg_object_read_at(o, sig.mzheader.e_lfanew, &newsig, uint.sizeof))
			return adbg_errno();
		
		version (Trace) trace("newsig=%#x", newsig);
		
		// 32-bit signature check
		switch (newsig) {
		case MAGIC_PE32:
			return adbg_object_pe_load(o, sig.mzheader.e_lfanew);
		default:
		}
		
		// 16-bit signature check
		switch (cast(ushort)newsig) {
		case NE_MAGIC:
			return adbg_object_ne_load(o, sig.mzheader.e_lfanew);
		case LX_MAGIC, LE_MAGIC:
			return adbg_object_lx_load(o, sig.mzheader.e_lfanew);
		default:
		}
		
		// If nothing matches, assume MZ
		return adbg_object_mz_load(o);
	// Old MZ magic or swapped
	case MAGIC_ZM:
		//TODO: pre-checked if only the signature is swapped
		goto case MAGIC_MZ;
	// COFF magics
	case COFF_MAGIC_I386:
	case COFF_MAGIC_I386_AIX:
	case COFF_MAGIC_AMD64:
	case COFF_MAGIC_IA64:
	case COFF_MAGIC_Z80:
	case COFF_MAGIC_MSP430:
	case COFF_MAGIC_TMS470:
	case COFF_MAGIC_TMS320C2800:
	case COFF_MAGIC_TMS320C5400:
	case COFF_MAGIC_TMS320C5500:
	case COFF_MAGIC_TMS320C5500P:
	case COFF_MAGIC_TMS320C6000:
	case COFF_MAGIC_MIPSEL:
		return adbg_object_coff_load(o);
	default:
	}
	
	// 8-bit signature detection
	version (Trace) trace("u8=%#x", sig.u8);
	switch (sig.u8) {
	case OMFRecord.LIBRARY: // OMF library header entry
	case OMFRecord.THEADR:  // First OMF object entry of THEADR
	case OMFRecord.LHEADR:  // First OMF object entry of LHEADR
		return adbg_object_omf_load(o, sig.u8);
	default:
	}
	
	return adbg_oops(AdbgError.objectUnknownFormat);
}

//TODO: Flags: Contains (default: exact), case insensitive, executable only, etc.
//TODO: unix archives (ar), by member name
adbg_section_t* adbg_object_search_section_by_name(adbg_object_t *o, const(char) *name, int flags = 0) {
	if (o == null || name == null) {
		adbg_oops(AdbgError.invalidArgument);
		return null;
	}
	
	// Search every section for its name
	void *section_header;
	size_t section_header_size;
	long section_offset;
	size_t section_size;
	switch (o.format) with (AdbgObject) {
	case pe:
		size_t i;
		for (pe_section_entry_t *s = void; (s = adbg_object_pe_section(o, i)) != null; ++i) {
			//TODO: Section name function (in case of long section names)
			if (strncmp(name, s.Name.ptr, s.Name.sizeof) == 0) {
				section_header = s;
				section_header_size = pe_section_entry_t.sizeof;
				section_offset = s.PointerToRawData;
				section_size = s.SizeOfRawData;
				break;
			}
		}
		break;
	case macho:
		int macho64 = adbg_object_macho_is_64bit(o);
		size_t ci;
		MACHO_FOR: for (macho_load_command_t *c = void; (c = adbg_object_macho_load_command(o, ci)) != null; ++ci) {
			size_t si;
			for (void *s = void; (s = adbg_object_macho_segment_section(o, c, si)) != null; ++si) {
				if (macho64) {
					macho_section64_t *s64 = cast(macho_section64_t*)s;
					
					if (strncmp(name, s64.sectname.ptr, s64.sectname.sizeof) == 0) {
						section_header = s64;
						section_header_size = macho_section64_t.sizeof;
						section_offset = s64.offset;
						section_size = s64.size;
						break MACHO_FOR;
					}
				} else { // 32-bit
					macho_section_t *s32 = cast(macho_section_t*)s;
				
					if (strncmp(name, s32.sectname.ptr, s32.sectname.sizeof) == 0) {
						section_header = s32;
						section_header_size = macho_section_t.sizeof;
						section_offset = s32.offset;
						section_size = s32.size;
						break MACHO_FOR;
					}
				}
			}
		}
		break;
	case elf:
		size_t i;
		switch (adbg_object_elf_class(o)) {
		case ELF_CLASS_32:
			for (Elf32_Shdr *s = void; (s = adbg_object_elf_shdr32(o, i)) != null; ++i) {
				const(char) *sname = adbg_object_elf_shdr32_name(o, s);
				if (sname == null)
					continue;
				if (strcmp(name, sname) == 0) {
					section_header = s;
					section_header_size = Elf32_Shdr.sizeof;
					section_offset = s.sh_offset;
					section_size = s.sh_size;
					break;
				}
			}
			break;
		case ELF_CLASS_64:
			for (Elf64_Shdr *s = void; (s = adbg_object_elf_shdr64(o, i)) != null; ++i) {
				const(char) *sname = adbg_object_elf_shdr64_name(o, s);
				if (sname == null)
					continue;
				if (strcmp(name, sname) == 0) {
					section_header = s;
					section_header_size = Elf32_Shdr.sizeof;
					section_offset = s.sh_offset;
					section_size = s.sh_size;
					break;
				}
			}
			break;
		default:
			return null;
		}
		break;
	default:
		adbg_oops(AdbgError.unavailable);
		return null;
	}
	
	// No section found
	if (section_header == null) {
		adbg_oops(AdbgError.unfindable);
		return null;
	}
	
	// Everything needs to be set. OK to do since this is internal information.
	assert(section_header);
	assert(section_header_size);
	assert(section_offset);
	assert(section_size);
	
	// Allocate the buffer to hold the section header and data
	size_t totalsize = adbg_section_t.sizeof + section_header_size + section_size;
	void *section_buffer = malloc(totalsize);
	if (section_buffer == null) {
		adbg_oops(AdbgError.crt);
		return null;
	}
	
	// Copy everything into the new buffer
	adbg_section_t *section = cast(adbg_section_t*)section_buffer;
	section.header_size = section_header_size;
	section.data_size = section_size;
	
	// Header starts after section metadata
	section.header = section_buffer + adbg_section_t.sizeof;
	memcpy(section.header, section_header, section_header_size);
	
	// Data starts after section header data
	section.data = section_buffer + adbg_section_t.sizeof + section_header_size;
	if (adbg_object_read_at(o, section_offset, section.data, section_size)) {
		free(section_buffer);
		return null; // function sets error
	}
	
	// Return buffer pointer
	return section;
}

void adbg_object_section_close(adbg_object_t *o, adbg_section_t *section) {
	if (section == null)
		return;
	free(section);
}

/// Get the size of the object. Only applies for objects loaded from disks.
/// Params: o = Object instance.
/// Returns: Size in bytes, or -1 on error.
long adbg_object_filesize(adbg_object_t *o) {
	if (o == null) {
		adbg_oops(AdbgError.invalidArgument);
		return -1;
	}
	if (o.file == null) {
		adbg_oops(AdbgError.uninitiated);
		return -1;
	}
	if (o.origin != AdbgObjectOrigin.disk) {
		adbg_oops(AdbgError.unavailable);
		return -1;
	}
	
	return osfsize(o.file);
}

/// Returns the first machine type the object supports.
/// Params: o = Object instance.
/// Returns: Machine value.
AdbgMachine adbg_object_machine(adbg_object_t *o) {
	if (o == null)
		return AdbgMachine.native;
	
	// TODO: Turn to function pointer
	switch (o.format) with (AdbgObject) {
	case mz:	return AdbgMachine.i8086;
	case ne:	return adbg_object_ne_machine(o);
	case lx:	return adbg_object_lx_machine(o);
	case pe:	return adbg_object_pe_machine(o);
	case macho:	return adbg_object_macho_machine(o);
	case elf:	return adbg_object_elf_machine(o);
	case coff:	return adbg_object_coff_machine(o);
	// TODO: For UNIX archives, get first object and return machine of sub object instance
	default:
	}
	return AdbgMachine.unknown;
}
const(char)* adbg_object_machine_string(adbg_object_t *o) {
	AdbgMachine mach = adbg_object_machine(o);
	return mach ? adbg_machine_name(mach) : `Unknown`;
}

/// Get the short name of the loaded object type.
/// Params: o = Object instance.
/// Returns: Object type name.
export
const(char)* adbg_object_type_shortname(adbg_object_t *o) {
	if (o == null)
		goto Lunknown;
	//TODO: Consider merging pdb20 and pdb70 to only "pdb"
	//TODO: Consider dropping "le" to stick only with "lx"
	final switch (o.format) with (AdbgObject) {
	case mz:	return "mz";
	case ne:	return "ne";
	case lx:	return adbg_object_lx_header_shortname(o);
	case pe:	return "pe32";
	case macho:	return "macho";
	case elf:	return "elf";
	case pdb20:	return "pdb20";
	case pdb70:	return "pdb70";
	case mdmp:	return "mdmp";
	case dmp:	return "dmp";
	case omf:	return "omf";
	case archive:	return "archive";
	case coff:	return "coff";
	case mscoff:	return "mscoff";
Lunknown:
	case unknown:	return "unknown";
	}
}

/// Get the full name of the loaded object type.
/// Params: o = Object instance.
/// Returns: Object type name.
export
const(char)* adbg_object_type_name(adbg_object_t *o) {
	if (o == null)
		goto Lunknown;
	final switch (o.format) with (AdbgObject) {
	case mz:	return `Mark Zbikowski`;
	case ne:	return `New Executable`;
	case lx:	return `Linked Executable`;
	case pe:	return `Portable Executable`;
	case macho:	return `Mach-O`;
	case elf:	return `Executable and Linkable Format`;
	case pdb20:	return `Program Database 2.0`;
	case pdb70:	return `Program Database 7.0`;
	case mdmp:	return `Windows Minidump`;
	case dmp:	return `Windows Memory Dump`;
	case omf:	return `Relocatable Object Module Format`;
	case archive:	return `Library Archive`;
	case coff:	return `Common Object File Format`;
	case mscoff:	return `Microsoft Common Object File Format`;
Lunknown:
	case unknown:	return "Unknown";
	}
}

// Printing purposes only
const(char)* adbg_object_kind_string(adbg_object_t *o) {
	if (o == null)
		return null;
	switch (o.format) with (AdbgObject) {
	case mz:	return adbg_object_mz_kind_string(o);
	case ne:	return adbg_object_ne_kind_string(o);
	case lx:	return adbg_object_lx_kind_string(o);
	case pe:	return adbg_object_pe_kind_string(o);
	case macho:	return adbg_object_macho_kind_string(o);
	case elf:	return adbg_object_elf_kind_string(o);
	case pdb20, pdb70:
		return `Debug Database`;
	case mdmp, dmp:
		return `Memory Dump`;
	case archive:
		return `Library`;
	case omf:
		return o.i.omf.firstentry ? `Library` : `Object`;
	case coff, mscoff:
		return `Object`;
	default:
	}
	return null;
}