Repository: NationalSecurityAgency/ghidra
Pull request #5004: Feature: UNIX A.out Loader
Author: login: colinbourassa
Labels: name: Feature: Loader
name: Status: Internal
description: This is being tracked internally by the Ghidra team
Created: 2023-02-17T22:20:08Z | Merged: 2025-03-20T16:04:41Z

This is a feature addition -- it adds a Loader for the old-style UNIX A.out format, which was used by BSD, SunOS, VxWorks, and other UNIX derivatives. This Loader was tested with a number of different A.out files across several architectures, although there are many combinations so future work may be required to address specific combinations of processor, executable type, and OS that require a different file offset (or load address) for .text/.data.

This would close issue #4943.


Diff:
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java
new file mode 100644
index 00000000000..87f22f23237
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java
@@ -0,0 +1,574 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.unixaout;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.data.CategoryPath;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.program.model.listing.Listing;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.util.CodeUnitInsertionException;
+import ghidra.util.exception.DuplicateNameException;
+
+public class UnixAoutHeader implements StructConverter {
+
+	public enum AoutType {
+		OMAGIC, NMAGIC, ZMAGIC, QMAGIC, CMAGIC, UNKNOWN
+	}
+
+	private BinaryReader reader;
+
+	private long binarySize;
+	private AoutType exeType;
+	private boolean machineTypeValid;
+	private String languageSpec;
+	private String compilerSpec = "default";
+	private long pageSize;
+
+	private boolean isNetBSD = false;
+	private boolean isSparc = false;
+
+	private long a_magic;
+	private long a_text;
+	private long a_data;
+	private long a_bss;
+	private long a_syms;
+	private long a_entry;
+	private long a_trsize;
+	private long a_drsize;
+
+	private long strSize;
+
+	private long txtOffset;
+	private long datOffset;
+	private long txtRelOffset;
+	private long datRelOffset;
+	private long symOffset;
+	private long strOffset;
+
+	private long txtAddr;
+	private long txtEndAddr;
+	private long datAddr;
+	private long bssAddr;
+
+	// The Linux implementation of a.out appears to start the .text content at
+	// file offset 0x400 (rather than immediately after the 0x20 bytes of header
+	// data). It's possible that there exist Linux a.out executabes with other
+	// (unintended?) header sizes caused by a mixture of 32- and 64-bit integers
+	// being padded out in the struct. The intended size is eight 32-bit words
+	// (32 bytes total.)
+	private static final int sizeOfExecHeader = 0x20;
+	private static final int sizeOfLongExecHeader = 0x400;
+
+	/**
+	 * Interprets binary data as an exec header from a UNIX-style a.out executable,
+	 * and validates the contained fields.
+	 *
+	 * @param provider       Source of header binary data
+	 * @param isLittleEndian Flag indicating whether to interpret the data as
+	 *                       little-endian.
+	 * @throws IOException
+	 */
+	public UnixAoutHeader(ByteProvider provider, boolean isLittleEndian) throws IOException {
+		this.reader = new BinaryReader(provider, isLittleEndian);
+
+		this.a_magic = reader.readNextUnsignedInt();
+		this.a_text = reader.readNextUnsignedInt();
+		this.a_data = reader.readNextUnsignedInt();
+		this.a_bss = reader.readNextUnsignedInt();
+		this.a_syms = reader.readNextUnsignedInt();
+		this.a_entry = reader.readNextUnsignedInt();
+		this.a_trsize = reader.readNextUnsignedInt();
+		this.a_drsize = reader.readNextUnsignedInt();
+		this.binarySize = reader.length();
+
+		checkExecutableType();
+
+		// NOTE: In NetBSD/i386 examples of a.out, the "new-style" 32-bit a_magic/midmag
+		// word
+		// is written in big-endian regardless of the data endianness in the rest of the
+		// file.
+		if ((this.exeType == AoutType.UNKNOWN) && isLittleEndian) {
+			this.a_magic = Integer.reverseBytes((int) this.a_magic);
+			checkExecutableType();
+		}
+
+		checkMachineTypeValidity(isLittleEndian);
+		determineTextOffset(reader, isLittleEndian);
+
+		this.datOffset = this.txtOffset + this.a_text;
+		this.txtRelOffset = this.datOffset + this.a_data;
+		this.datRelOffset = this.txtRelOffset + this.a_trsize;
+		this.symOffset = this.datRelOffset + this.a_drsize;
+		this.strOffset = this.symOffset + this.a_syms;
+
+		this.strSize = 0;
+		if (this.strOffset != 0 && (this.strOffset + 4) <= binarySize) {
+			this.strSize = reader.readUnsignedInt(this.strOffset);
+		}
+
+		determineTextAddr();
+		this.txtEndAddr = this.txtAddr + this.a_text;
+		this.datAddr = (this.exeType == AoutType.OMAGIC) ? this.txtEndAddr : segmentRound(this.txtEndAddr);
+		this.bssAddr = this.datAddr + this.a_data;
+	}
+
+	public BinaryReader getReader() {
+		return this.reader;
+	}
+
+	/**
+	 * Returns the processor/language specified by this header.
+	 */
+	public String getLanguageSpec() {
+		return this.languageSpec;
+	}
+
+	/**
+	 * Returns the compiler used by this executable. This is left as 'default' for
+	 * all machine types other than i386, where it is assumed to be gcc.
+	 */
+	public String getCompilerSpec() {
+		return this.compilerSpec;
+	}
+
+	/**
+	 * Returns the enumerated type of executable contained in this A.out file.
+	 */
+	public AoutType getExecutableType() {
+		return this.exeType;
+	}
+
+	/**
+	 * Returns an indication of whether this header's fields are all valid; this
+	 * includes the machine type, executable type, and section offsets.
+	 */
+	public boolean isValid() {
+		return isMachineTypeValid() &&
+				(this.exeType != AoutType.UNKNOWN) &&
+				areOffsetsValid();
+	}
+
+	public long getTextSize() {
+		return this.a_text;
+	}
+
+	public long getDataSize() {
+		return this.a_data;
+	}
+
+	public long getBssSize() {
+		return this.a_bss;
+	}
+
+	public long getSymSize() {
+		return this.a_syms;
+	}
+
+	public long getStrSize() {
+		return this.strSize;
+	}
+
+	public long getEntryPoint() {
+		return this.a_entry;
+	}
+
+	public long getTextRelocSize() {
+		return this.a_trsize;
+	}
+
+	public long getDataRelocSize() {
+		return this.a_drsize;
+	}
+
+	public long getTextOffset() {
+		return this.txtOffset;
+	}
+
+	public long getDataOffset() {
+		return this.datOffset;
+	}
+
+	public long getTextRelocOffset() {
+		return this.txtRelOffset;
+	}
+
+	public long getDataRelocOffset() {
+		return this.datRelOffset;
+	}
+
+	public long getSymOffset() {
+		return this.symOffset;
+	}
+
+	public long getStrOffset() {
+		return this.strOffset;
+	}
+
+	public long getTextAddr() {
+		return this.txtAddr;
+	}
+
+	public long getDataAddr() {
+		return this.datAddr;
+	}
+
+	public long getBssAddr() {
+		return this.bssAddr;
+	}
+
+	/**
+	 * Checks the magic word in the header for a known machine type ID, and sets the
+	 * languageSpec string accordingly.
+	 */
+	private void checkMachineTypeValidity(boolean readingAsLittleEndian) {
+
+		this.machineTypeValid = true;
+		this.pageSize = 4096;
+		final short machtype = (short) ((this.a_magic >> 16) & 0xFF);
+		final String readEndianness = readingAsLittleEndian ? "LE" : "BE";
+
+		switch (machtype) {
+			/**
+			 * Motorola 68K family
+			 */
+			case UnixAoutMachineType.M_68010:
+				this.languageSpec = "68000:BE:32:MC68010";
+				break;
+			case UnixAoutMachineType.M_68020:
+				this.languageSpec = "68000:BE:32:MC68020";
+				break;
+			case UnixAoutMachineType.M_M68K_NETBSD:
+				this.pageSize = 8192;
+			case UnixAoutMachineType.M_M68K4K_NETBSD:
+				this.isNetBSD = true;
+				this.languageSpec = "68000:BE:32:default";
+				break;
+
+			/**
+			 * SPARC family
+			 */
+			case UnixAoutMachineType.M_SPARC_NETBSD:
+				this.isNetBSD = true;
+			case UnixAoutMachineType.M_SPARC:
+			case UnixAoutMachineType.M_SPARCLET:
+				this.isSparc = true;
+				this.pageSize = 8192;
+				this.languageSpec = "sparc:BE:32:default";
+				break;
+			case UnixAoutMachineType.M_SPARC64_NETBSD:
+				this.isNetBSD = true;
+				this.isSparc = true;
+				this.languageSpec = "sparc:BE:64:default";
+				break;
+
+			/**
+			 * MIPS family
+			 */
+			case UnixAoutMachineType.M_PMAX_NETBSD:
+				this.isNetBSD = true;
+			case UnixAoutMachineType.M_MIPS1:
+			case UnixAoutMachineType.M_MIPS2:
+			case UnixAoutMachineType.M_R3000:
+				this.languageSpec = "MIPS:LE:32:default";
+				break;
+			case UnixAoutMachineType.M_MIPS:
+				this.languageSpec = "MIPS:BE:32:default";
+				break;
+
+			/**
+			 * National Semiconductor NS32000 family
+			 */
+			case UnixAoutMachineType.M_532_NETBSD:
+				this.isNetBSD = true;
+			case UnixAoutMachineType.M_NS32032:
+			case UnixAoutMachineType.M_NS32532:
+				this.languageSpec = "UNKNOWN:LE:32:default";
+				break;
+
+			/**
+			 * x86 family
+			 */
+			case UnixAoutMachineType.M_386_NETBSD:
+				this.isNetBSD = true;
+			case UnixAoutMachineType.M_386:
+			case UnixAoutMachineType.M_386_DYNIX:
+				this.compilerSpec = "gcc";
+				this.languageSpec = "x86:LE:32:default";
+				break;
+			case UnixAoutMachineType.M_X86_64_NETBSD:
+				this.compilerSpec = "gcc";
+				this.languageSpec = "x86:LE:64:default";
+				break;
+
+			/**
+			 * ARM family
+			 */
+			case UnixAoutMachineType.M_ARM6_NETBSD:
+				this.isNetBSD = true;
+			case UnixAoutMachineType.M_ARM:
+				this.languageSpec = "ARM:" + readEndianness + ":32:default";
+				break;
+			case UnixAoutMachineType.M_AARCH64:
+				this.languageSpec = "AARCH64:" + readEndianness + ":64:default";
+				break;
+
+			/**
+			 * RISC family
+			 */
+			case UnixAoutMachineType.M_OR1K:
+				this.languageSpec = "UNKNOWN:BE:32:default";
+				break;
+			case UnixAoutMachineType.M_RISCV:
+				this.languageSpec = "RISCV:LE:32:default";
+				break;
+			case UnixAoutMachineType.M_HPPA_OPENBSD:
+				this.languageSpec = "pa-risc:BE:32:default";
+				break;
+
+			/**
+			 * PowerPC family
+			 */
+			case UnixAoutMachineType.M_POWERPC_NETBSD:
+				this.isNetBSD = true;
+				this.languageSpec = "PowerPC:" + readEndianness + ":32:default";
+				break;
+			case UnixAoutMachineType.M_POWERPC64:
+				this.languageSpec = "PowerPC:" + readEndianness + ":64:default";
+				break;
+
+			/**
+			 * SuperH family
+			 * NOTE: It's unclear if there is support for SuperH SH-3 or SH-5 cores;
+			 * the primary SuperH language seems to support SH-1 and SH-2 variants
+			 * and the alternative is the SuperH4 language.
+			 */
+			case UnixAoutMachineType.M_SH3:
+			case UnixAoutMachineType.M_SH5_32:
+				this.languageSpec = "SuperH:BE:32:default";
+				break;
+			case UnixAoutMachineType.M_SH5_64:
+				this.languageSpec = "SuperH:BE:64:default";
+				break;
+
+			/**
+			 * VAX family
+			 */
+			case UnixAoutMachineType.M_VAX_NETBSD:
+				this.pageSize = 512;
+			case UnixAoutMachineType.M_VAX4K_NETBSD:
+				this.isNetBSD = true;
+				this.languageSpec = "UNKNOWN:LE:32:default";
+				break;
+
+			/**
+			 * Other
+			 */
+			case UnixAoutMachineType.M_CRIS:
+				this.languageSpec = "UNKNOWN:LE:32:default";
+				break;
+			case UnixAoutMachineType.M_ALPHA_NETBSD:
+				this.isNetBSD = true;
+			case UnixAoutMachineType.M_IA64:
+				this.languageSpec = "UNKNOWN:" + readEndianness + ":64:default";
+				break;
+			case UnixAoutMachineType.M_29K:
+			case UnixAoutMachineType.M_88K_OPENBSD:
+				this.languageSpec = "UNKNOWN:" + readEndianness + ":32:default";
+				break;
+			case UnixAoutMachineType.M_UNKNOWN:
+				this.languageSpec = "UNKNOWN:" + readEndianness + ":32:default";
+				break;
+			default:
+				this.machineTypeValid = false;
+		}
+
+		// Check that the detected architecture's endianness matches the endianness
+		// with which we're reading the file; if there's a mismatch, clear the
+		// machineTypeValid flag because this was evidently a false reading.
+		if (this.machineTypeValid) {
+			String[] languageTokens = this.languageSpec.split(":");
+			if ((languageTokens.length < 2) ||
+					!languageTokens[1].equalsIgnoreCase(readEndianness)) {
+				this.machineTypeValid = false;
+			}
+		}
+	}
+
+	/**
+	 * Returns a flag indicating whether the header contains a known machine type
+	 * ID.
+	 */
+	private boolean isMachineTypeValid() {
+		return this.machineTypeValid;
+	}
+
+	/**
+	 * Returns a flag indicating whether this header contains a representation of a
+	 * valid executable type.
+	 */
+	private void checkExecutableType() {
+		final short exetypeMagic = (short) (this.a_magic & 0xFFFF);
+
+		switch (exetypeMagic) {
+			case 0x111: // 0421: core file
+				this.exeType = AoutType.CMAGIC;
+				break;
+			case 0x108: // 0410: pure executable
+				this.exeType = AoutType.NMAGIC;
+				break;
+			case 0x107: // 0407: object file or impure executable
+				this.exeType = AoutType.OMAGIC;
+				break;
+			case 0x0CC: // 0314: demand-paged exe w/ header in .text
+				this.exeType = AoutType.QMAGIC;
+				break;
+			case 0x10B: // 0413: demand-paged executable
+				this.exeType = AoutType.ZMAGIC;
+				break;
+			default:
+				this.exeType = AoutType.UNKNOWN;
+		}
+	}
+
+	/**
+	 * Determines the offset in the binary file at which the .text segment begins.
+	 * This routine should attempt to replicate the logic from the N_TXTOFF macro
+	 * that appears in the different incarnations of a.out.h.
+	 *
+	 * NOTE: The FreeBSD imgact_aout.h implies that, if the a_magic word contains
+	 * ZMAGIC when read as little endian, the file offset for .text is __LDPGSZ;
+	 * otherwise, if a_magic contains ZMAGIC when read as big endian, the file
+	 * offset
+	 * for .text is 0. Indeed, it looks like NetBSD uses big-endian ordering for
+	 * the a_magic word even when the file contains code for a little-endian
+	 * processor.
+	 */
+	private void determineTextOffset(BinaryReader reader, boolean isLittleEndian) {
+
+		boolean isLinuxStyle = false;
+		final long fixedContentSize = this.a_text + this.a_data + this.a_syms + this.a_trsize + this.a_drsize;
+
+		// If the file is large enough to read at least one word beyond a long-style
+		// header
+		// of 0x400 bytes plus all the sections whose sizes are specified in the
+		// header...
+		if (reader.isValidIndex(sizeOfLongExecHeader + fixedContentSize)) {
+			try {
+				// The word that immediately follows the symbol table will contain the size of
+				// the string table.
+				final long stringTableLength = reader.readUnsignedInt(sizeOfLongExecHeader + fixedContentSize);
+				final long longHeaderExpectedFileSize = sizeOfLongExecHeader + fixedContentSize + stringTableLength;
+
+				// If the size of the file exactly matches what we'd expect if the .text content
+				// starts at offset 0x400 rather than 0, this implies that the a.out is a
+				// Linux-style binary.
+				if (this.binarySize == longHeaderExpectedFileSize) {
+					isLinuxStyle = true;
+				}
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+
+		if (isLinuxStyle && (this.exeType == AoutType.ZMAGIC)) {
+			// Linux ZMAGICs don't start the .text content until 0x400
+			this.txtOffset = sizeOfLongExecHeader;
+
+		} else if ((this.exeType == AoutType.QMAGIC) ||
+				(this.exeType == AoutType.ZMAGIC)) {
+			// ZMAGIC for other platforms (as well as QMAGIC) include the file header itself
+			// in the .text content
+			this.txtOffset = 0;
+
+		} else {
+			// Otherwise, the .text content starts immediately after the 0x20-byte header
+			this.txtOffset = sizeOfExecHeader;
+		}
+	}
+
+	/**
+	 * Uses the combination of executable type and architecture to set the
+	 * appropriate
+	 * base address of the .text segment when loaded.
+	 */
+	private void determineTextAddr() {
+
+		if ((this.isSparc && (this.exeType == AoutType.NMAGIC)) ||
+				(this.isNetBSD) ||
+				(this.exeType == AoutType.QMAGIC)) {
+			this.txtAddr = this.pageSize;
+
+		} else {
+			this.txtAddr = 0;
+		}
+	}
+
+	/**
+	 * Returns a flag indicating whether all the file offsets in the header
+	 * (for the segments of nonzero size) fall within the size of the file.
+	 */
+	private boolean areOffsetsValid() {
+		// Note that we can't check the string table validity because, if it
+		// doesn't exist, its offset will be computed to be beyond the end of
+		// the file. The string table is also not given an explicit size in
+		// the header.
+		boolean status = ((this.a_text == 0) || (this.txtOffset < this.binarySize) &&
+				((this.a_data == 0) || (this.datOffset < this.binarySize)) &&
+				((this.a_trsize == 0) || (this.txtRelOffset < this.binarySize)) &&
+				((this.a_drsize == 0) || (this.datRelOffset < this.binarySize)) &&
+				((this.a_syms == 0) || (this.symOffset < this.binarySize)));
+		return status;
+	}
+
+	/**
+	 * Rounds the provided address up to the next page boundary.
+	 */
+	private long segmentRound(long addr) {
+		final long mask = this.pageSize - 1;
+		long rounded = ((addr + mask) & ~mask);
+		return rounded;
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		String dtName = "exec";
+		Structure struct = new StructureDataType(new CategoryPath("/AOUT"), dtName, 0);
+		struct.add(DWORD, "a_midmag", null);
+		struct.add(DWORD, "a_text", null);
+		struct.add(DWORD, "a_data", null);
+		struct.add(DWORD, "a_bss", null);
+		struct.add(DWORD, "a_syms", null);
+		struct.add(DWORD, "a_entry", null);
+		struct.add(DWORD, "a_trsize", null);
+		struct.add(DWORD, "a_drsize", null);
+
+		return struct;
+	}
+
+	public void markup(Program program, Address headerAddress) throws CodeUnitInsertionException, DuplicateNameException, IOException {
+		Listing listing = program.getListing();
+		listing.createData(headerAddress, toDataType());
+	}
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutMachineType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutMachineType.java
new file mode 100644
index 00000000000..102f8e861e7
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutMachineType.java
@@ -0,0 +1,87 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.unixaout;
+
+public class UnixAoutMachineType {
+
+	// These values come from a combination of sources, including NetBSD's
+	// aout_mids.h
+	// and the GNU BFD Library's libaout.h.
+	//
+	// Note: some a.out header files list a few HP values (for the 300 Series, 800
+	// Series, etc.)
+	// and these values exceed a full eight-bit count. Occasionally, this is
+	// accounted for by
+	// extending the Machine ID field of the a_magic word two bits higher, leaving
+	// only six bits
+	// in the MSB for other flags. This may not be correct, because those high-value
+	// HP machine
+	// IDs probably only appear in HP UX binaries, which use a different format.
+	// (This format is
+	// still named "a.out", but has a completely different header and internal
+	// organization.)
+	// The 10-bit Machine ID field would also interfere with flags used by VxWorks,
+	// NetBSD, and
+	// probably others.
+
+	public final static short M_UNKNOWN = 0x00;
+	public final static short M_68010 = 0x01;
+	public final static short M_68020 = 0x02;
+	public final static short M_SPARC = 0x03;
+	public final static short M_R3000 = 0x04;
+	public final static short M_NS32032 = 0x40;
+	public final static short M_NS32532 = 0x45;
+	public final static short M_386 = 0x64;
+	public final static short M_29K = 0x65; // AMD 29000
+	public final static short M_386_DYNIX = 0x66; // i386-based Sequet machine running DYNIX
+	public final static short M_ARM = 0x67;
+	public final static short M_SPARCLET = 0x83; // Sparclet = M_SPARC + 128
+	public final static short M_386_NETBSD = 0x86; // NetBSD/i386
+	public final static short M_M68K_NETBSD = 0x87; // NetBSD/m68k, 8K pages
+	public final static short M_M68K4K_NETBSD = 0x88; // NetBSD/m68k, 4K pages
+	public final static short M_532_NETBSD = 0x89; // NetBSD/ns32k
+	public final static short M_SPARC_NETBSD = 0x8a; // NetBSD/sparc
+	public final static short M_PMAX_NETBSD = 0x8b; // NetBSD/pmax (MIPS little-endian)
+	public final static short M_VAX_NETBSD = 0x8c; // NetBSD/VAX (1K pages?)
+	public final static short M_ALPHA_NETBSD = 0x8d; // NetBSD/Alpha
+	public final static short M_MIPS = 0x8e; // big-endian
+	public final static short M_ARM6_NETBSD = 0x8f; // NetBSD/arm32
+	public final static short M_SH3 = 0x91;
+	public final static short M_POWERPC64 = 0x94; // PowerPC 64
+	public final static short M_POWERPC_NETBSD = 0x95; // NetBSD/PowerPC (big-endian)
+	public final static short M_VAX4K_NETBSD = 0x96; // NetBSD/VAX (4K pages)
+	public final static short M_MIPS1 = 0x97; // MIPS R2000/R3000
+	public final static short M_MIPS2 = 0x98; // MIPS R4000/R6000
+	public final static short M_88K_OPENBSD = 0x99; // OpenBSD/m88k
+	public final static short M_HPPA_OPENBSD = 0x9a; // OpenBSD/hppa (PA-RISC)
+	public final static short M_SH5_64 = 0x9b; // SuperH 64-bit
+	public final static short M_SPARC64_NETBSD = 0x9c; // NetBSD/sparc64
+	public final static short M_X86_64_NETBSD = 0x9d; // NetBSD/amd64
+	public final static short M_SH5_32 = 0x9e; // SuperH 32-bit (ILP 32)
+	public final static short M_IA64 = 0x9f; // Itanium
+	public final static short M_AARCH64 = 0xb7; // ARM AARCH64
+	public final static short M_OR1K = 0xb8; // OpenRISC 1000
+	public final static short M_RISCV = 0xb9; // RISC-V
+	public final static short M_CRIS = 0xff; // Axis ETRAX CRIS
+
+	/**
+	 * Machine IDs that should only appear in the incompatible HP UX a.out format:
+	 * HP300 (68020+68881): 0x12c
+	 * HP200/300 : 0x20c
+	 * HP800 : 0x20b
+	 */
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutRelocation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutRelocation.java
new file mode 100644
index 00000000000..91f46cb9a55
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutRelocation.java
@@ -0,0 +1,85 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.unixaout;
+
+import ghidra.app.util.opinion.UnixAoutProgramLoader;
+
+/**
+ * Represents the content of a single entry in the relocation table format used
+ * by the UNIX a.out executable.
+ */
+public class UnixAoutRelocation {
+	public long address;
+	public int symbolNum;
+	public byte flags;
+	public boolean pcRelativeAddressing;
+	public byte pointerLength;
+	public boolean extern;
+	public boolean baseRelative;
+	public boolean jmpTable;
+	public boolean relative;
+	public boolean copy;
+
+	/**
+	 * 
+	 * @param address First of the two words in the table entry (a 32-bit address)
+	 * @param flags   Second of the two words in the table entry (containing several
+	 *                bitfields)
+	 */
+	public UnixAoutRelocation(long address, long flags, boolean bigEndian) {
+		this.address = (0xFFFFFFFF & address);
+
+		if (bigEndian) {
+			this.symbolNum = (int) ((flags & 0xFFFFFF00) >> 8);
+			this.flags = (byte) (flags & 0xFF);
+			this.pcRelativeAddressing = ((flags & 0x80) != 0);
+			this.pointerLength = (byte) (1 << ((flags & 0x60) >> 5));
+			this.extern = ((flags & 0x10) != 0);
+			this.baseRelative = ((flags & 0x8) != 0);
+			this.jmpTable = ((flags & 0x4) != 0);
+			this.relative = ((flags & 0x2) != 0);
+			this.copy = ((flags & 0x1) != 0);
+		} else {
+			this.symbolNum = (int) (flags & 0x00FFFFFF);
+			this.flags = (byte) ((flags & 0xFF000000) >> 24);
+			this.pcRelativeAddressing = ((this.flags & 0x01) != 0);
+			this.pointerLength = (byte) (1 << ((this.flags & 0x06) >> 1));
+			this.extern = ((this.flags & 0x08) != 0);
+			this.baseRelative = ((this.flags & 0x10) != 0);
+			this.jmpTable = ((this.flags & 0x20) != 0);
+			this.relative = ((this.flags & 0x40) != 0);
+			this.copy = ((this.flags & 0x80) != 0);
+		}
+	}
+
+	public String getSymbolName(UnixAoutSymbolTable symtab) {
+		if (extern == true && symbolNum < symtab.size()) {
+			return symtab.get(symbolNum).name;
+		} else if (extern == false) {
+			switch (symbolNum) {
+				case 4:
+					return UnixAoutProgramLoader.dot_text;
+				case 6:
+					return UnixAoutProgramLoader.dot_data;
+				case 8:
+					return UnixAoutProgramLoader.dot_bss;
+			}
+		}
+
+		return null;
+	}
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutRelocationTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutRelocationTable.java
new file mode 100644
index 00000000000..a7823450751
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutRelocationTable.java
@@ -0,0 +1,110 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.util.bin.format.unixaout;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.program.model.data.ArrayDataType;
+import ghidra.program.model.data.CategoryPath;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.InvalidDataTypeException;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.program.model.listing.CodeUnit;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.Listing;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.program.model.util.CodeUnitInsertionException;
+import ghidra.util.exception.DuplicateNameException;
+
+public class UnixAoutRelocationTable implements Iterable<UnixAoutRelocation>, StructConverter {
+	private static final int ENTRY_SIZE = 8;
+
+	private final long fileSize;
+	private final List<UnixAoutRelocation> relocations;
+	private final UnixAoutSymbolTable symtab;
+
+	public UnixAoutRelocationTable(BinaryReader reader, long fileOffset, long fileSize, UnixAoutSymbolTable symtab)
+			throws IOException {
+		this.fileSize = fileSize;
+		this.relocations = new ArrayList<>();
+		this.symtab = symtab;
+
+		reader.setPointerIndex(fileOffset);
+
+		// read each relocation table entry
+		while (reader.getPointerIndex() < (fileOffset + fileSize)) {
+			long address = reader.readNextUnsignedInt();
+			long flags = reader.readNextUnsignedInt();
+
+			UnixAoutRelocation relocation = new UnixAoutRelocation(address, flags, reader.isBigEndian());
+			relocations.add(relocation);
+		}
+	}
+
+	@Override
+	public Iterator<UnixAoutRelocation> iterator() {
+		return relocations.iterator();
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		String dtName = "relocation_info";
+		Structure struct = new StructureDataType(new CategoryPath("/AOUT"), dtName, 0);
+		struct.setPackingEnabled(true);
+		try {
+			struct.add(DWORD, "r_address", null);
+			struct.addBitField(DWORD, 24, "r_symbolnum", null);
+			struct.addBitField(BYTE, 1, "r_pcrel", null);
+			struct.addBitField(BYTE, 2, "r_length", null);
+			struct.addBitField(BYTE, 1, "r_extern", null);
+			struct.addBitField(BYTE, 1, "r_baserel", null);
+			struct.addBitField(BYTE, 1, "r_jmptable", null);
+			struct.addBitField(BYTE, 1, "r_relative", null);
+			struct.addBitField(BYTE, 1, "r_copy", null);
+		} catch (InvalidDataTypeException e) {
+			throw new RuntimeException(e);
+		}
+
+		return new ArrayDataType(struct, (int) (fileSize / ENTRY_SIZE), ENTRY_SIZE);
+	}
+
+	public void markup(Program program, MemoryBlock block)
+			throws CodeUnitInsertionException, DuplicateNameException, IOException {
+		Listing listing = program.getListing();
+		Data array = listing.createData(block.getStart(), toDataType());
+
+		int idx = 0;
+		for (UnixAoutRelocation relocation : this) {
+			String name = relocation.getSymbolName(symtab);
+
+			if (!StringUtils.isBlank(name)) {
+				Data structData = array.getComponent(idx);
+				structData.setComment(CodeUnit.EOL_COMMENT, name);
+			}
+
+			idx++;
+		}
+	}
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutStringTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutStringTable.java
new file mode 100644
index 00000000000..9678b30af67
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutStringTable.java
@@ -0,0 +1,63 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.util.bin.format.unixaout;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.data.TerminatedStringDataType;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.Listing;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.program.model.util.CodeUnitInsertionException;
+
+public class UnixAoutStringTable {
+	private final BinaryReader reader;
+	private final long fileOffset;
+
+	public UnixAoutStringTable(BinaryReader reader, long fileOffset, long fileSize) {
+		this.reader = reader;
+		this.fileOffset = fileOffset;
+	}
+
+	public String readString(long stringOffset) {
+		if (fileOffset < 0) {
+			return null;
+		}
+		try {
+			return reader.readUtf8String(fileOffset + stringOffset).trim();
+		} catch (IOException e) {
+			// FIXME
+		}
+		return null;
+	}
+
+	public void markup(Program program, MemoryBlock block) throws CodeUnitInsertionException {
+		Listing listing = program.getListing();
+		Address address = block.getStart();
+		listing.createData(address, StructConverter.DWORD);
+
+		int strlen = 4;
+		while ((address.getOffset() + strlen) < block.getEnd().getOffset()) {
+			address = address.add(strlen);
+			Data str = listing.createData(address, TerminatedStringDataType.dataType, -1);
+			strlen = str.getLength();
+		}
+	}
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutSymbol.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutSymbol.java
new file mode 100644
index 00000000000..fdb8c2db6f1
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutSymbol.java
@@ -0,0 +1,92 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.util.bin.format.unixaout;
+
+/**
+ * Represents the content of a single entry in the symbol table format used by
+ * the UNIX a.out executable.
+ */
+public class UnixAoutSymbol {
+
+	public enum SymbolType {
+		N_UNDF, N_ABS, N_TEXT, N_DATA, N_BSS, N_INDR, N_FN, N_STAB, UNKNOWN
+	}
+
+	public enum SymbolKind {
+		AUX_FUNC, AUX_OBJECT, AUX_LABEL, UNKNOWN
+	}
+
+	public long nameStringOffset;
+	public String name;
+	public SymbolType type;
+	public SymbolKind kind;
+	public byte otherByte;
+	public short desc;
+	public long value;
+	public boolean isExt;
+
+	public UnixAoutSymbol(long nameStringOffset, byte typeByte, byte otherByte,
+			short desc, long value) {
+		this.nameStringOffset = nameStringOffset;
+		this.otherByte = otherByte;
+		this.desc = desc;
+		this.value = value;
+		this.isExt = (typeByte & 1) == 1;
+
+		switch (typeByte & 0xfe) {
+			case 0:
+				type = SymbolType.N_UNDF;
+				break;
+			case 2:
+				type = SymbolType.N_ABS;
+				break;
+			case 4:
+				type = SymbolType.N_TEXT;
+				break;
+			case 6:
+				type = SymbolType.N_DATA;
+				break;
+			case 8:
+				type = SymbolType.N_BSS;
+				break;
+			case 10:
+				type = SymbolType.N_INDR;
+				break;
+			default:
+				if ((typeByte & 0xfe) >= 0x20) {
+					type = SymbolType.N_STAB;
+				} else {
+					type = SymbolType.UNKNOWN;
+				}
+				break;
+		}
+
+		switch (otherByte & 0x0f) {
+			case 1:
+				kind = SymbolKind.AUX_OBJECT;
+				break;
+			case 2:
+				kind = SymbolKind.AUX_FUNC;
+				break;
+			case 3:
+				kind = SymbolKind.AUX_LABEL;
+				break;
+			default:
+				kind = SymbolKind.UNKNOWN;
+				break;
+		}
+	}
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutSymbolTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutSymbolTable.java
new file mode 100644
index 00000000000..e2a0d664e1e
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutSymbolTable.java
@@ -0,0 +1,121 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.util.bin.format.unixaout;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.app.util.opinion.UnixAoutProgramLoader;
+import ghidra.program.model.data.ArrayDataType;
+import ghidra.program.model.data.CategoryPath;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.program.model.listing.CodeUnit;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.Listing;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.program.model.util.CodeUnitInsertionException;
+import ghidra.util.exception.DuplicateNameException;
+
+public class UnixAoutSymbolTable implements Iterable<UnixAoutSymbol>, StructConverter {
+	private static final int ENTRY_SIZE = 12;
+
+	private final long fileSize;
+	private List<UnixAoutSymbol> symbols;
+
+	public UnixAoutSymbolTable(BinaryReader reader, long fileOffset, long fileSize, UnixAoutStringTable strtab, MessageLog log)
+			throws IOException {
+		this.fileSize = fileSize;
+		this.symbols = new ArrayList<>();
+
+		reader.setPointerIndex(fileOffset);
+		int idx = 0;
+
+		// read each symbol table entry
+		while (reader.getPointerIndex() < (fileOffset + fileSize)) {
+			long strOffset = reader.readNextUnsignedInt();
+			byte typeByte = reader.readNextByte();
+			byte otherByte = reader.readNextByte();
+			short desc = reader.readNextShort();
+			long value = reader.readNextUnsignedInt();
+
+			UnixAoutSymbol symbol = new UnixAoutSymbol(strOffset, typeByte, otherByte, desc, value);
+			if (symbol.type == UnixAoutSymbol.SymbolType.UNKNOWN) {
+				log.appendMsg(UnixAoutProgramLoader.dot_symtab, String.format("Unknown symbol type 0x%02x at symbol index %d", typeByte, idx));
+			}
+			symbols.add(symbol);
+
+			idx++;
+		}
+
+		// lookup and set each string table symbol name
+		for (UnixAoutSymbol symbol : this) {
+			symbol.name = strtab.readString(symbol.nameStringOffset);
+		}
+	}
+
+	@Override
+	public Iterator<UnixAoutSymbol> iterator() {
+		return symbols.iterator();
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		String dtName = "nlist";
+		Structure struct = new StructureDataType(new CategoryPath("/AOUT"), dtName, 0);
+		struct.add(DWORD, "n_strx", null);
+		struct.add(BYTE, "n_type", null);
+		struct.add(BYTE, "n_other", null);
+		struct.add(WORD, "n_desc", null);
+		struct.add(DWORD, "n_value", null);
+		return new ArrayDataType(struct, (int) (fileSize / ENTRY_SIZE), ENTRY_SIZE);
+	}
+
+    public UnixAoutSymbol get(int symbolNum) {
+        return symbols.get(symbolNum);
+    }
+
+    public long size() {
+        return symbols.size();
+    }
+
+	public void markup(Program program, MemoryBlock block) throws CodeUnitInsertionException, DuplicateNameException, IOException {
+		Listing listing = program.getListing();
+		Data array = listing.createData(block.getStart(), toDataType());
+
+		int idx = 0;
+		for (UnixAoutSymbol symbol : this) {
+			if (!StringUtils.isBlank(symbol.name)) {
+				Data structData = array.getComponent(idx);
+
+				if (structData != null) {
+					structData.setComment(CodeUnit.EOL_COMMENT, symbol.name);
+				}
+			}
+
+			idx++;
+		}
+	}
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java
new file mode 100644
index 00000000000..86e1ad1d8a1
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java
@@ -0,0 +1,165 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.util.opinion;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import ghidra.app.util.Option;
+import ghidra.app.util.OptionException;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.format.unixaout.UnixAoutHeader;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.framework.model.DomainObject;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressFactory;
+import ghidra.program.model.address.AddressSpace;
+import ghidra.program.model.lang.LanguageCompilerSpecPair;
+import ghidra.program.model.listing.Program;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * A {@link Loader} for processing UNIX-style A.out executables
+ *
+ * This style was also used by UNIX-like systems such as SunOS, BSD, and
+ * VxWorks, as well as some early distributions of Linux. Although there do
+ * exist implementations of A.out with 64-bit and GNU extensions, this loader
+ * does not currently support them.
+ *
+ * @see <a href="https://wiki.osdev.org/A.out">OSDev.org A.out</a>
+ * @see <a href="https://man.freebsd.org/cgi/man.cgi?a.out(5)">FreeBSD
+ *      manpage</a>
+ */
+public class UnixAoutLoader extends AbstractProgramWrapperLoader {
+	public static final String OPTION_NAME_BASE_ADDR = "Base Address";
+
+	@Override
+	public String getName() {
+		return "UNIX A.out executable";
+	}
+
+	/**
+	 * Retrieves the Address offset given in the "Base Address" option.
+	 * Returns 0 if the option could not be found or contains an invalid value.
+	 */
+	private long getBaseAddrOffset(List<Option> options) {
+		Address baseAddr = null;
+	
+		if (options != null) {
+			for (Option option : options) {
+				String optName = option.getName();
+				if (optName.equals(OPTION_NAME_BASE_ADDR)) {
+					baseAddr = (Address) option.getValue();
+				}
+			}
+		}
+
+		long offset = 0;
+		if (baseAddr != null) {
+			offset = baseAddr.getOffset();
+		}
+
+		return offset;
+	}
+
+	@Override
+	public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
+			Program program) {
+		Address baseAddr = null;
+
+		for (Option option : options) {
+			String optName = option.getName();
+			try {
+				if (optName.equals(OPTION_NAME_BASE_ADDR)) {
+					baseAddr = (Address) option.getValue();
+				}
+			} catch (Exception e) {
+				if (e instanceof OptionException) {
+					return e.getMessage();
+				}
+				return "Invalid value for " + optName + " - " + option.getValue();
+			}
+		}
+		if (baseAddr == null) {
+			return "Invalid base address";
+		}
+
+		return super.validateOptions(provider, loadSpec, options, program);
+	}
+
+	@Override
+	public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
+			DomainObject domainObject, boolean loadIntoProgram) {
+		Address baseAddr = null;
+
+		if (domainObject instanceof Program) {
+			Program program = (Program) domainObject;
+			AddressFactory addressFactory = program.getAddressFactory();
+			if (addressFactory != null) {
+				AddressSpace defaultAddressSpace = addressFactory.getDefaultAddressSpace();
+				if (defaultAddressSpace != null) {
+					baseAddr = defaultAddressSpace.getAddress(0);
+				}
+			}
+		}
+
+		List<Option> list = new ArrayList<Option>();
+		list.add(new Option(OPTION_NAME_BASE_ADDR, baseAddr, Address.class,
+				Loader.COMMAND_LINE_ARG_PREFIX + "-baseAddr"));
+
+		list.addAll(super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram));
+		return list;
+	}
+
+	@Override
+	public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
+		List<LoadSpec> loadSpecs = new ArrayList<>();
+
+		// Attempt to parse the header as both little- and big-endian.
+		// It is likely that only one of these will produce sensible values.
+		UnixAoutHeader hdrBE = new UnixAoutHeader(provider, false);
+		UnixAoutHeader hdrLE = new UnixAoutHeader(provider, true);
+		boolean beValid = false;
+
+		if (hdrBE.isValid()) {
+			final String lang = hdrBE.getLanguageSpec();
+			final String comp = hdrBE.getCompilerSpec();
+			loadSpecs.add(new LoadSpec(this, 0, new LanguageCompilerSpecPair(lang, comp), true));
+			beValid = true;
+		}
+		if (hdrLE.isValid()) {
+			final String lang = hdrLE.getLanguageSpec();
+			final String comp = hdrLE.getCompilerSpec();
+			loadSpecs.add(new LoadSpec(this, 0, new LanguageCompilerSpecPair(lang, comp), !beValid));
+		}
+
+		return loadSpecs;
+	}
+
+	@Override
+	protected void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
+			Program program, TaskMonitor monitor, MessageLog log)
+			throws CancelledException, IOException {
+		final boolean isLittleEndian = !program.getLanguage().isBigEndian();
+		final UnixAoutHeader header = new UnixAoutHeader(provider, isLittleEndian);
+
+		final UnixAoutProgramLoader loader = new UnixAoutProgramLoader(program, header, monitor, log);
+		loader.loadAout(getBaseAddrOffset(options));
+	}
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutProgramLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutProgramLoader.java
new file mode 100644
index 00000000000..9422d287e27
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutProgramLoader.java
@@ -0,0 +1,476 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.util.opinion;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import ghidra.app.util.MemoryBlockUtils;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.format.MemoryLoadable;
+import ghidra.app.util.bin.format.unixaout.UnixAoutHeader;
+import ghidra.app.util.bin.format.unixaout.UnixAoutRelocation;
+import ghidra.app.util.bin.format.unixaout.UnixAoutRelocationTable;
+import ghidra.app.util.bin.format.unixaout.UnixAoutStringTable;
+import ghidra.app.util.bin.format.unixaout.UnixAoutSymbol;
+import ghidra.app.util.bin.format.unixaout.UnixAoutSymbolTable;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.program.database.function.OverlappingFunctionException;
+import ghidra.program.database.mem.FileBytes;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressOverflowException;
+import ghidra.program.model.address.AddressSet;
+import ghidra.program.model.address.AddressSpace;
+import ghidra.program.model.listing.FunctionManager;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.Memory;
+import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.program.model.reloc.Relocation.Status;
+import ghidra.program.model.reloc.RelocationTable;
+import ghidra.program.model.symbol.SourceType;
+import ghidra.program.model.symbol.Symbol;
+import ghidra.program.model.symbol.SymbolIterator;
+import ghidra.program.model.symbol.SymbolTable;
+import ghidra.program.model.util.CodeUnitInsertionException;
+import ghidra.util.DataConverter;
+import ghidra.util.MonitoredInputStream;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.DuplicateNameException;
+import ghidra.util.exception.InvalidInputException;
+import ghidra.util.task.TaskMonitor;
+
+public class UnixAoutProgramLoader extends MemorySectionResolver {
+	private final static String BLOCK_SOURCE_NAME = "Unix Aout Loader";
+	private final int EXTERNAL_BLOCK_MIN_SIZE = 0x10000; // 64K
+
+	public final static String dot_text = ".text";
+	public final static String dot_data = ".data";
+	public final static String dot_bss = ".bss";
+	public final static String dot_rel_text = ".rel.text";
+	public final static String dot_rel_data = ".rel.data";
+	public final static String dot_strtab = ".strtab";
+	public final static String dot_symtab = ".symtab";
+
+	private final TaskMonitor monitor;
+	private final MessageLog log;
+	private final UnixAoutHeader header;
+
+	private FileBytes fileBytes;
+
+	private UnixAoutRelocationTable relText;
+	private UnixAoutRelocationTable relData;
+	private UnixAoutSymbolTable symtab;
+	private UnixAoutStringTable strtab;
+
+	private Map<String, Long> possibleBssSymbols = new HashMap<>();
+	private int extraBssSize = 0;
+	private int undefinedSymbolCount = 0;
+
+	public UnixAoutProgramLoader(Program program, UnixAoutHeader header, TaskMonitor monitor, MessageLog log) {
+		super(program);
+
+		this.monitor = monitor;
+		this.log = log;
+		this.header = header;
+	}
+
+	public void loadAout(long baseAddr) throws IOException, CancelledException {
+		log.appendMsg(String.format("----- Loading %s -----", header.getReader().getByteProvider().getAbsolutePath()));
+		log.appendMsg(String.format("Found a.out type %s.", header.getExecutableType().name()));
+
+		ByteProvider byteProvider = header.getReader().getByteProvider();
+
+		try {
+			buildTables(byteProvider);
+			preprocessSymbolTable();
+			loadSections(baseAddr, byteProvider);
+			loadSymbols();
+			applyRelocations(baseAddr, program.getMemory().getBlock(dot_text), relText);
+			applyRelocations(baseAddr, program.getMemory().getBlock(dot_data), relData);
+			markupSections();
+		} catch (AddressOverflowException | InvalidInputException | CodeUnitInsertionException | DuplicateNameException
+				| MemoryAccessException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private void buildTables(ByteProvider byteProvider) throws IOException {
+		if (header.getStrSize() > 0) {
+			strtab = new UnixAoutStringTable(header.getReader(), header.getStrOffset(), header.getStrSize());
+		}
+		if (header.getSymSize() > 0) {
+			symtab = new UnixAoutSymbolTable(header.getReader(), header.getSymOffset(), header.getSymSize(),
+					strtab, log);
+		}
+		if (header.getTextRelocSize() > 0) {
+			relText = new UnixAoutRelocationTable(header.getReader(), header.getTextRelocOffset(),
+					header.getTextRelocSize(), symtab);
+		}
+		if (header.getDataRelocSize() > 0) {
+			relData = new UnixAoutRelocationTable(header.getReader(), header.getDataRelocOffset(),
+					header.getDataRelocSize(), symtab);
+		}
+	}
+
+	private void preprocessSymbolTable() {
+		if (symtab == null) {
+			return;
+		}
+
+		boolean foundStabs = false;
+		for (UnixAoutSymbol symbol : symtab) {
+			switch (symbol.type) {
+				case N_UNDF:
+					if (symbol.value > 0) {
+						// This is a special case given by the A.out spec: if the linker cannot find
+						// this symbol in any of the other binary files, then the fact that it is
+						// marked as N_UNDF but has a non-zero value means that its value should be
+						// interpreted as a size, and the linker should reserve space in .bss for it.
+						possibleBssSymbols.put(symbol.name, symbol.value);
+					} else {
+						undefinedSymbolCount++;
+					}
+					break;
+				case N_STAB:
+					if (!foundStabs) {
+						foundStabs = true;
+						log.appendMsg(dot_symtab, "File contains STABS.");
+					}
+					break;
+				default:
+					break;
+			}
+		}
+
+		for (Long value : possibleBssSymbols.values()) {
+			extraBssSize += value;
+		}
+
+		if (extraBssSize > 0) {
+			log.appendMsg(dot_bss, String.format("Added %d bytes for N_UNDF symbols.", extraBssSize));
+		}
+	}
+
+	private void loadSections(long baseAddr, ByteProvider byteProvider)
+			throws AddressOverflowException, IOException, CancelledException {
+		monitor.setMessage("Loading FileBytes...");
+
+		try (InputStream fileIn = byteProvider.getInputStream(0);
+				MonitoredInputStream mis = new MonitoredInputStream(fileIn, monitor)) {
+			// Indicate that cleanup is not neccessary for cancelled import operation.
+			mis.setCleanupOnCancel(false);
+			fileBytes = program.getMemory().createFileBytes(byteProvider.getName(), 0, byteProvider.length(), mis,
+					monitor);
+		}
+
+		final AddressSpace defaultAddressSpace = program.getAddressFactory().getDefaultAddressSpace();
+		final Address otherAddress = AddressSpace.OTHER_SPACE.getMinAddress();
+		Address address;
+		Address nextFreeAddress = defaultAddressSpace.getAddress(0);
+
+		if (header.getTextOffset() != 0 || header.getTextSize() < 32) {
+			addInitializedMemorySection(null, 0, 32, otherAddress, "_aoutHeader", false, false, false, null, false,
+					false);
+		}
+		if (header.getTextSize() > 0) {
+			address = defaultAddressSpace.getAddress(baseAddr + header.getTextAddr());
+			nextFreeAddress = address.add(header.getTextSize());
+			addInitializedMemorySection(null, header.getTextOffset(), header.getTextSize(), address, dot_text, true,
+					true, true, null, false, true);
+		}
+		if (header.getDataSize() > 0) {
+			address = defaultAddressSpace.getAddress(baseAddr + header.getDataAddr());
+			nextFreeAddress = address.add(header.getDataSize());
+			addInitializedMemorySection(null, header.getDataOffset(), header.getDataSize(), address, dot_data, true,
+					true, false, null, false, true);
+		}
+		if ((header.getBssSize() + extraBssSize) > 0) {
+			address = defaultAddressSpace.getAddress(baseAddr + header.getBssAddr());
+			nextFreeAddress = address.add(header.getBssSize() + extraBssSize);
+			addUninitializedMemorySection(null, header.getBssSize() + extraBssSize, address, dot_bss, true, true, false,
+					null, false);
+		}
+		if (undefinedSymbolCount > 0) {
+			int externalSectionSize = undefinedSymbolCount * 4;
+			if (externalSectionSize < EXTERNAL_BLOCK_MIN_SIZE) {
+				externalSectionSize = EXTERNAL_BLOCK_MIN_SIZE;
+			}
+			addUninitializedMemorySection(null, externalSectionSize, nextFreeAddress, MemoryBlock.EXTERNAL_BLOCK_NAME, false, false, false, "NOTE: This block is artificial and is used to make relocations work correctly", false);
+		}
+		if (header.getStrSize() > 0) {
+			addInitializedMemorySection(null, header.getStrOffset(), header.getStrSize(), otherAddress, dot_strtab,
+					false, false, false, null, false, false);
+		}
+		if (header.getSymSize() > 0) {
+			addInitializedMemorySection(null, header.getSymOffset(), header.getSymSize(), otherAddress, dot_symtab,
+					false, false, false, null, false, false);
+		}
+		if (header.getTextRelocSize() > 0) {
+			addInitializedMemorySection(null, header.getTextRelocOffset(), header.getTextRelocSize(), otherAddress,
+					dot_rel_text, false, false, false, null, false, false);
+		}
+		if (header.getDataRelocSize() > 0) {
+			addInitializedMemorySection(null, header.getDataRelocOffset(), header.getDataRelocSize(), otherAddress,
+					dot_rel_data, false, false, false, null, false, false);
+		}
+
+		resolve(monitor);
+	}
+
+	private void loadSymbols() throws InvalidInputException {
+		monitor.setMessage("Loading symbols...");
+
+		if (symtab == null) {
+			return;
+		}
+
+		SymbolTable symbolTable = program.getSymbolTable();
+		FunctionManager functionManager = program.getFunctionManager();
+		MemoryBlock textBlock = program.getMemory().getBlock(dot_text);
+		MemoryBlock dataBlock = program.getMemory().getBlock(dot_data);
+		MemoryBlock bssBlock = program.getMemory().getBlock(dot_bss);
+		MemoryBlock externalBlock = program.getMemory().getBlock(MemoryBlock.EXTERNAL_BLOCK_NAME);
+
+		int extraBssOffset = 0;
+		int undefinedSymbolIdx = 0;
+		List<String> aliases = new ArrayList<>();
+
+		for (UnixAoutSymbol symbol : symtab) {
+			Address address = null;
+			MemoryBlock block = null;
+
+			switch (symbol.type) {
+				case N_TEXT:
+					address = textBlock != null ? textBlock.getStart().add(symbol.value) : null;
+					block = textBlock;
+					break;
+				case N_DATA:
+					address = dataBlock != null ? dataBlock.getStart().add(symbol.value) : null;
+					block = dataBlock;
+					break;
+				case N_BSS:
+					address = bssBlock != null ? bssBlock.getStart().add(symbol.value) : null;
+					block = bssBlock;
+					break;
+				case N_UNDF:
+					if (symbol.value > 0) {
+						address = bssBlock.getEnd().add(extraBssOffset);
+						block = bssBlock;
+						extraBssOffset += symbol.value;
+					} else {
+						address = externalBlock.getStart().add(undefinedSymbolIdx++ * 4);
+						block = externalBlock;
+						symbolTable.addExternalEntryPoint(address);
+					}
+					break;
+				case N_INDR:
+					aliases.add(symbol.name);
+					break;
+				case N_ABS:
+				case N_FN:
+				case N_STAB:
+				case UNKNOWN:
+					aliases.clear();
+					break;
+			}
+
+			if (address == null || block == null) {
+				continue;
+			}
+
+			switch (symbol.kind) {
+				case AUX_FUNC:
+					try {
+						functionManager.createFunction(symbol.name, address, new AddressSet(address),
+								SourceType.IMPORTED);
+					} catch (OverlappingFunctionException e) {
+						log.appendMsg(block.getName(), String.format(
+								"Failed to create function %s @ %s, creating symbol instead.", symbol.name, address));
+						symbolTable.createLabel(address, symbol.name, SourceType.IMPORTED);
+					}
+					break;
+				default:
+					Symbol label = symbolTable.createLabel(address, symbol.name, SourceType.IMPORTED);
+					if (symbol.isExt) {
+						label.setPrimary();
+					}
+					break;
+			}
+
+			for (String alias : aliases) {
+				symbolTable.createLabel(address, alias, SourceType.IMPORTED);
+			}
+
+			aliases.clear();
+		}
+	}
+
+	private void applyRelocations(long baseAddr, MemoryBlock targetBlock, UnixAoutRelocationTable relTable)
+			throws MemoryAccessException {
+		if (targetBlock == null || relTable == null) {
+			return;
+		}
+
+		monitor.setMessage(String.format("Applying relocations for section %s...", targetBlock.getName()));
+
+		DataConverter dc = DataConverter.getInstance(program.getLanguage().isBigEndian());
+		SymbolTable symbolTable = program.getSymbolTable();
+		RelocationTable relocationTable = program.getRelocationTable();
+		Memory memory = program.getMemory();
+		MemoryBlock textBlock = memory.getBlock(dot_text);
+		MemoryBlock dataBlock = memory.getBlock(dot_data);
+		MemoryBlock bssBlock = memory.getBlock(dot_bss);
+
+		int idx = 0;
+		for (UnixAoutRelocation relocation : relTable) {
+			Address targetAddress = targetBlock.getStart().add(relocation.address);
+
+			byte originalBytes[] = new byte[relocation.pointerLength];
+			targetBlock.getBytes(targetAddress, originalBytes);
+			long addend = dc.getValue(originalBytes, 0, relocation.pointerLength);
+
+			Long value = null;
+			Status status = Status.FAILURE;
+
+			if (relocation.baseRelative || relocation.jmpTable || relocation.relative || relocation.copy) {
+				status = Status.UNSUPPORTED;
+			} else {
+				if (relocation.extern == true && relocation.symbolNum < symtab.size()) {
+					SymbolIterator symbolIterator = symbolTable.getSymbols(symtab.get(relocation.symbolNum).name);
+					if (symbolIterator.hasNext()) {
+						value = symbolIterator.next().getAddress().getOffset();
+					}
+				} else if (relocation.extern == false) {
+					switch (relocation.symbolNum) {
+						case 4:
+							value = textBlock.getStart().getOffset();
+							break;
+						case 6:
+							value = dataBlock.getStart().getOffset();
+							break;
+						case 8:
+							value = bssBlock.getStart().getOffset();
+							break;
+					}
+				}
+			}
+
+			if (value != null) {
+				if (relocation.pcRelativeAddressing) {
+					// Addend is relative to start of target section.
+					value -= targetBlock.getStart().getOffset();
+				}
+
+				// Apply relocation.
+				byte newBytes[] = new byte[relocation.pointerLength];
+				dc.putValue(value + addend, relocation.pointerLength, newBytes, 0);
+				targetBlock.putBytes(targetAddress, newBytes);
+
+				status = Status.APPLIED;
+			}
+
+			if (status != Status.APPLIED) {
+				log.appendMsg(targetBlock.getName(),
+						String.format("Failed to apply relocation entry %d with type 0x%02x @ %s.", idx,
+								relocation.flags, targetAddress));
+			}
+
+			relocationTable.add(targetAddress, status, relocation.flags, new long[] { relocation.symbolNum },
+					originalBytes, relocation.getSymbolName(symtab));
+			idx++;
+		}
+	}
+
+	private void markupSections()
+			throws InvalidInputException, CodeUnitInsertionException, DuplicateNameException, IOException {
+		final AddressSpace defaultAddressSpace = program.getAddressFactory().getDefaultAddressSpace();
+		final FunctionManager functionManager = program.getFunctionManager();
+		final SymbolTable symbolTable = program.getSymbolTable();
+
+		monitor.setMessage("Marking up header...");
+
+		// Markup header.
+		Address headerAddress = null;
+		MemoryBlock aoutHeader = program.getMemory().getBlock("_aoutHeader");
+		MemoryBlock textBlock = program.getMemory().getBlock(dot_text);
+		if (aoutHeader != null) {
+			headerAddress = aoutHeader.getStart();
+		} else if (textBlock != null && header.getTextOffset() == 0 && header.getTextSize() >= 32) {
+			headerAddress = textBlock.getStart();
+		}
+		if (headerAddress != null) {
+			header.markup(program, headerAddress);
+		}
+
+		// Markup entrypoint.
+		if (header.getEntryPoint() != 0) {
+			Address address = defaultAddressSpace.getAddress(header.getEntryPoint());
+			try {
+				functionManager.createFunction("entry", address, new AddressSet(address), SourceType.IMPORTED);
+			} catch (OverlappingFunctionException e) {
+				log.appendMsg(dot_text, "Failed to create entrypoint function @ %s, creating symbol instead.");
+				symbolTable.createLabel(address, "entry", SourceType.IMPORTED);
+			}
+		}
+
+		monitor.setMessage("Marking up relocation tables...");
+
+		MemoryBlock relTextBlock = program.getMemory().getBlock(dot_rel_text);
+		if (relTextBlock != null) {
+			relText.markup(program, relTextBlock);
+		}
+
+		MemoryBlock relDataBlock = program.getMemory().getBlock(dot_rel_data);
+		if (relDataBlock != null) {
+			relData.markup(program, relDataBlock);
+		}
+
+		monitor.setMessage("Marking up symbol table...");
+
+		MemoryBlock symtabBlock = program.getMemory().getBlock(dot_symtab);
+		if (symtabBlock != null) {
+			symtab.markup(program, symtabBlock);
+		}
+
+		monitor.setMessage("Marking up string table...");
+
+		MemoryBlock strtabBlock = program.getMemory().getBlock(dot_strtab);
+		if (strtabBlock != null) {
+			strtab.markup(program, strtabBlock);
+		}
+	}
+
+	@Override
+	protected MemoryBlock createInitializedBlock(MemoryLoadable key, boolean isOverlay, String name, Address start,
+			long fileOffset, long length, String comment, boolean r, boolean w, boolean x, TaskMonitor monitor)
+			throws IOException, AddressOverflowException, CancelledException {
+		return MemoryBlockUtils.createInitializedBlock(program, isOverlay, name, start, fileBytes, fileOffset, length,
+				comment, BLOCK_SOURCE_NAME, r, w, x, log);
+	}
+
+	@Override
+	protected MemoryBlock createUninitializedBlock(MemoryLoadable key, boolean isOverlay, String name, Address start,
+			long length, String comment, boolean r, boolean w, boolean x)
+			throws IOException, AddressOverflowException, CancelledException {
+		return MemoryBlockUtils.createUninitializedBlock(program, isOverlay, name, start, length, comment, comment, r,
+				w, x, log);
+	}
+}
\ No newline at end of file


Reviews:
user: login: nightlark
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2023-03-30T00:16:17Z
user: login: ryanmkurtz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T13:29:14Z
user: login: nightlark
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-19T13:38:52Z
user: login: ryanmkurtz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T16:29:48Z
user: login: colinbourassa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T17:15:18Z

Review comments:
diff_hunk: @@ -0,0 +1,79 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.aout;
+
+public class UnixAoutMachineType {
+
+    // These values come from a combination of sources, including NetBSD's aout_mids.h
+    // and the GNU BFD Library's libaout.h.
+    //
+    // Note: some a.out header files list a few HP values (for the 300 Series, 800 Series, etc.)
+    // and these values exceed a full eight-bit count. Occasionally, this is accounted for by
+    // extending the Machine ID field of the a_magic word two bits higher, leaving only six bits
+    // in the MSB for other flags. This may not be correct, because those high-value HP machine
+    // IDs probably only appear in HP UX binaries, which use a different format. (This format is
+    // still named "a.out", but has a completely different header and internal organization.)
+    // The 10-bit Machine ID field would also interfere with flags used by VxWorks, NetBSD, and
+    // probably others.
+
+    public final static short M_OLDSUN2        = 0x00;
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutMachineType.java
user: login: nightlark
body: ```suggestion
    public final static short M_UNKNOWN        = 0x00;
```

This doesn't seem to be a reliable way of identifying "OLDSUN2" -- the sample I linked to in another comment is not for OLDSUN2 but also uses 0x00 in the machine type field; it looks like checking if it is big endian may help narrow things down closer to OLDSUN2, but then again, the comments in the magic pattern for a.out seem to indicate that several other target machines use the same type (or are just leaving it out).

```
# Big-endian 32-bit-int a.out, merged from sun (for old 68010 SunOS a.out),
# mips (for old 68020(!) SGI a.out), and netbsd (for old big-endian a.out).
```
created_at: 2023-03-29T23:36:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,501 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.aout;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+
+public class UnixAoutHeader {
+
+    enum ExecutableType {
+        OMAGIC, NMAGIC, ZMAGIC, QMAGIC, CMAGIC, UNKNOWN
+    }
+
+    private long binarySize;
+    private ExecutableType exeType;
+    private boolean machineTypeValid;
+    private String languageSpec;
+    private String compilerSpec = "default";
+    private long pageSize;
+
+    private boolean isOldSun2 = false;
+    private boolean isNetBSD = false;
+    private boolean isSparc = false;
+
+    private long a_magic;
+    private long a_text;
+    private long a_data;
+    private long a_bss;
+    private long a_syms;
+    private long a_entry;
+    private long a_trsize;
+    private long a_drsize;
+
+    private long txtOffset;
+    private long datOffset;
+    private long txtRelOffset;
+    private long datRelOffset;
+    private long symOffset;
+    private long strOffset;
+
+    private long txtAddr;
+    private long txtEndAddr;
+    private long datAddr;
+    private long bssAddr;
+
+    // The Linux implementation of a.out support might have a different header
+    // size, but it looks as though that might be caused by a strange mix of
+    // 32- and 64-bit integers being padded out in the struct. The intended
+    // size seems to be eight 32-bit words (32 bytes total.)
+    private static final int sizeOfExecHeader = 32;
+
+    /**
+     * Interprets binary data as an exec header from a UNIX-style a.out executable,
+     * and validates the contained fields.
+     *
+     * @param provider       Source of header binary data
+     * @param isLittleEndian Flag indicating whether to interpret the data as
+     *                       little-endian.
+     * @throws IOException
+     */
+    public UnixAoutHeader(ByteProvider provider, boolean isLittleEndian) throws IOException {
+        BinaryReader reader = new BinaryReader(provider, isLittleEndian);
+
+        this.a_magic = reader.readNextUnsignedInt();
+        this.a_text = reader.readNextUnsignedInt();
+        this.a_data = reader.readNextUnsignedInt();
+        this.a_bss = reader.readNextUnsignedInt();
+        this.a_syms = reader.readNextUnsignedInt();
+        this.a_entry = reader.readNextUnsignedInt();
+        this.a_trsize = reader.readNextUnsignedInt();
+        this.a_drsize = reader.readNextUnsignedInt();
+        this.binarySize = reader.length();
+
+        // NOTE: In NetBSD/i386 examples of a.out, the 32-bit a_magic/midmag word seems to
+        // always be written in big-endian regardless of the data endianness in the rest of
+        // the file. Are there other examples of this?
+        if (isLittleEndian) {
+            this.a_magic = Integer.reverseBytes((int)this.a_magic); 
+        }
+
+        checkExecutableType();
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java
user: login: nightlark
body: ```suggestion
        checkExecutableType();

        // NOTE: In NetBSD/i386 examples of a.out, the "new-style" 32-bit a_magic/midmag word
        // is written in big-endian regardless of the data endianness in the rest of the file.
        if ((this.exeType == ExecutableType.UNKNOWN) && isLittleEndian) {
            this.a_magic = Integer.reverseBytes((int)this.a_magic);
            checkExecutableType();
        }

```

This seems to be a NetBSD only thing; in the netbsd pattern used by the file command (https://github.com/file/file/blob/89f1ba6f/magic/Magdir/netbsd) there is this comment:
```
# All new-style magic numbers are in network byte order.
# The old-style magic numbers are indistinguishable from the same magic
# numbers used in other systems, and are handled, for all those systems,
# in aout.
```

So it seems this reversing the magic bytes if the rest of the file is little endian should only happen if the target machine is NetBSD.

---

The typical recognition for non-NetBSD machines has magic bytes that follow the endianness of the rest of the file: https://github.com/file/file/blob/89f1ba6f/magic/Magdir/aout

FreeBSD and PDP are also reading the magic bytes as little endian: https://github.com/file/file/blob/89f1ba/magic/Magdir/freebsd#LL77
https://github.com/file/file/blob/916754b9/magic/Magdir/pdp#L9
created_at: 2023-03-29T23:50:40Z
author_association: NONE
diff_hunk: @@ -0,0 +1,501 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.aout;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+
+public class UnixAoutHeader {
+
+    enum ExecutableType {
+        OMAGIC, NMAGIC, ZMAGIC, QMAGIC, CMAGIC, UNKNOWN
+    }
+
+    private long binarySize;
+    private ExecutableType exeType;
+    private boolean machineTypeValid;
+    private String languageSpec;
+    private String compilerSpec = "default";
+    private long pageSize;
+
+    private boolean isOldSun2 = false;
+    private boolean isNetBSD = false;
+    private boolean isSparc = false;
+
+    private long a_magic;
+    private long a_text;
+    private long a_data;
+    private long a_bss;
+    private long a_syms;
+    private long a_entry;
+    private long a_trsize;
+    private long a_drsize;
+
+    private long txtOffset;
+    private long datOffset;
+    private long txtRelOffset;
+    private long datRelOffset;
+    private long symOffset;
+    private long strOffset;
+
+    private long txtAddr;
+    private long txtEndAddr;
+    private long datAddr;
+    private long bssAddr;
+
+    // The Linux implementation of a.out support might have a different header
+    // size, but it looks as though that might be caused by a strange mix of
+    // 32- and 64-bit integers being padded out in the struct. The intended
+    // size seems to be eight 32-bit words (32 bytes total.)
+    private static final int sizeOfExecHeader = 32;
+
+    /**
+     * Interprets binary data as an exec header from a UNIX-style a.out executable,
+     * and validates the contained fields.
+     *
+     * @param provider       Source of header binary data
+     * @param isLittleEndian Flag indicating whether to interpret the data as
+     *                       little-endian.
+     * @throws IOException
+     */
+    public UnixAoutHeader(ByteProvider provider, boolean isLittleEndian) throws IOException {
+        BinaryReader reader = new BinaryReader(provider, isLittleEndian);
+
+        this.a_magic = reader.readNextUnsignedInt();
+        this.a_text = reader.readNextUnsignedInt();
+        this.a_data = reader.readNextUnsignedInt();
+        this.a_bss = reader.readNextUnsignedInt();
+        this.a_syms = reader.readNextUnsignedInt();
+        this.a_entry = reader.readNextUnsignedInt();
+        this.a_trsize = reader.readNextUnsignedInt();
+        this.a_drsize = reader.readNextUnsignedInt();
+        this.binarySize = reader.length();
+
+        // NOTE: In NetBSD/i386 examples of a.out, the 32-bit a_magic/midmag word seems to
+        // always be written in big-endian regardless of the data endianness in the rest of
+        // the file. Are there other examples of this?
+        if (isLittleEndian) {
+            this.a_magic = Integer.reverseBytes((int)this.a_magic); 
+        }
+
+        checkExecutableType();
+        checkMachineTypeValidity(isLittleEndian);
+        determineTextOffset(isLittleEndian);
+
+        this.datOffset = this.txtOffset + this.a_text;
+        this.txtRelOffset = this.datOffset + this.a_data;
+        this.datRelOffset = this.txtRelOffset + this.a_trsize;
+        this.symOffset = this.datRelOffset + this.a_drsize;
+        this.strOffset = this.symOffset + this.a_syms;
+
+        determineTextAddr();
+        this.txtEndAddr = this.txtAddr + this.a_text;
+        this.datAddr = (this.exeType == ExecutableType.OMAGIC) ?
+                this.txtEndAddr : segmentRound(this.txtEndAddr);
+        this.bssAddr = this.datAddr + this.a_data;
+    }
+
+    /**
+     * Returns the processor/language specified by this header.
+     */
+    public String getLanguageSpec() {
+        return this.languageSpec;
+    }
+
+    /**
+     * Returns the compiler used by this executable. This is left as 'default' for
+     * all machine types other than i386, where it is assumed to be gcc.
+     */
+    public String getCompilerSpec() {
+        return this.compilerSpec;
+    }
+
+    /**
+     * Returns the enumerated type of executable contained in this A.out file.
+     */
+    public ExecutableType getExecutableType() {
+        return this.exeType;
+    }
+
+    /**
+     * Returns an indication of whether this header's fields are all valid; this
+     * includes the machine type, executable type, and section offsets.
+     */
+    public boolean isValid() {
+        return isMachineTypeValid() &&
+                (this.exeType != ExecutableType.UNKNOWN) &&
+                areOffsetsValid();
+    }
+
+    public long getTextSize() {
+        return this.a_text;
+    }
+
+    public long getDataSize() {
+        return this.a_data;
+    }
+
+    public long getBssSize() {
+        return this.a_bss;
+    }
+
+    public long getSymSize() {
+        return this.a_syms;
+    }
+
+    public long getEntryPoint() {
+        return this.a_entry;
+    }
+
+    public long getTextRelocSize() {
+        return this.a_trsize;
+    }
+
+    public long getDataRelocSize() {
+        return this.a_drsize;
+    }
+
+    public long getTextOffset() {
+        return this.txtOffset;
+    }
+
+    public long getDataOffset() {
+        return this.datOffset;
+    }
+
+    public long getTextRelocOffset() {
+        return this.txtRelOffset;
+    }
+
+    public long getDataRelocOffset() {
+        return this.datRelOffset;
+    }
+
+    public long getSymOffset() {
+        return this.symOffset;
+    }
+
+    public long getStrOffset() {
+        return this.strOffset;
+    }
+
+    public long getTextAddr() {
+        return this.txtAddr;
+    }
+
+    public long getDataAddr() {
+        return this.datAddr;
+    }
+
+    public long getBssAddr() {
+        return this.bssAddr;
+    }
+
+    /**
+     * Checks the magic word in the header for a known machine type ID, and sets the
+     * languageSpec string accordingly.
+     */
+    private void checkMachineTypeValidity(boolean readingAsLittleEndian) {
+
+        this.machineTypeValid = true;
+        this.pageSize = 4096;
+        final short machtype = (short) ((this.a_magic >> 16) & 0xFF);
+        final String readEndianness = readingAsLittleEndian ? "LE" : "BE";
+
+        switch (machtype) {
+            /**
+             * Motorola 68K family
+             */
+        case UnixAoutMachineType.M_68010:
+            this.languageSpec = "68000:BE:32:MC68010";
+            break;
+        case UnixAoutMachineType.M_OLDSUN2:
+            this.isOldSun2 = true;
+            this.pageSize = 2048;
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java
user: login: nightlark
body: ```suggestion
```

As described elsewhere, the machine type == 0x00 case occurs for a variety of different machines (for both big and little endian magic bytes).
created_at: 2023-03-29T23:58:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,501 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.aout;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+
+public class UnixAoutHeader {
+
+    enum ExecutableType {
+        OMAGIC, NMAGIC, ZMAGIC, QMAGIC, CMAGIC, UNKNOWN
+    }
+
+    private long binarySize;
+    private ExecutableType exeType;
+    private boolean machineTypeValid;
+    private String languageSpec;
+    private String compilerSpec = "default";
+    private long pageSize;
+
+    private boolean isOldSun2 = false;
+    private boolean isNetBSD = false;
+    private boolean isSparc = false;
+
+    private long a_magic;
+    private long a_text;
+    private long a_data;
+    private long a_bss;
+    private long a_syms;
+    private long a_entry;
+    private long a_trsize;
+    private long a_drsize;
+
+    private long txtOffset;
+    private long datOffset;
+    private long txtRelOffset;
+    private long datRelOffset;
+    private long symOffset;
+    private long strOffset;
+
+    private long txtAddr;
+    private long txtEndAddr;
+    private long datAddr;
+    private long bssAddr;
+
+    // The Linux implementation of a.out support might have a different header
+    // size, but it looks as though that might be caused by a strange mix of
+    // 32- and 64-bit integers being padded out in the struct. The intended
+    // size seems to be eight 32-bit words (32 bytes total.)
+    private static final int sizeOfExecHeader = 32;
+
+    /**
+     * Interprets binary data as an exec header from a UNIX-style a.out executable,
+     * and validates the contained fields.
+     *
+     * @param provider       Source of header binary data
+     * @param isLittleEndian Flag indicating whether to interpret the data as
+     *                       little-endian.
+     * @throws IOException
+     */
+    public UnixAoutHeader(ByteProvider provider, boolean isLittleEndian) throws IOException {
+        BinaryReader reader = new BinaryReader(provider, isLittleEndian);
+
+        this.a_magic = reader.readNextUnsignedInt();
+        this.a_text = reader.readNextUnsignedInt();
+        this.a_data = reader.readNextUnsignedInt();
+        this.a_bss = reader.readNextUnsignedInt();
+        this.a_syms = reader.readNextUnsignedInt();
+        this.a_entry = reader.readNextUnsignedInt();
+        this.a_trsize = reader.readNextUnsignedInt();
+        this.a_drsize = reader.readNextUnsignedInt();
+        this.binarySize = reader.length();
+
+        // NOTE: In NetBSD/i386 examples of a.out, the 32-bit a_magic/midmag word seems to
+        // always be written in big-endian regardless of the data endianness in the rest of
+        // the file. Are there other examples of this?
+        if (isLittleEndian) {
+            this.a_magic = Integer.reverseBytes((int)this.a_magic); 
+        }
+
+        checkExecutableType();
+        checkMachineTypeValidity(isLittleEndian);
+        determineTextOffset(isLittleEndian);
+
+        this.datOffset = this.txtOffset + this.a_text;
+        this.txtRelOffset = this.datOffset + this.a_data;
+        this.datRelOffset = this.txtRelOffset + this.a_trsize;
+        this.symOffset = this.datRelOffset + this.a_drsize;
+        this.strOffset = this.symOffset + this.a_syms;
+
+        determineTextAddr();
+        this.txtEndAddr = this.txtAddr + this.a_text;
+        this.datAddr = (this.exeType == ExecutableType.OMAGIC) ?
+                this.txtEndAddr : segmentRound(this.txtEndAddr);
+        this.bssAddr = this.datAddr + this.a_data;
+    }
+
+    /**
+     * Returns the processor/language specified by this header.
+     */
+    public String getLanguageSpec() {
+        return this.languageSpec;
+    }
+
+    /**
+     * Returns the compiler used by this executable. This is left as 'default' for
+     * all machine types other than i386, where it is assumed to be gcc.
+     */
+    public String getCompilerSpec() {
+        return this.compilerSpec;
+    }
+
+    /**
+     * Returns the enumerated type of executable contained in this A.out file.
+     */
+    public ExecutableType getExecutableType() {
+        return this.exeType;
+    }
+
+    /**
+     * Returns an indication of whether this header's fields are all valid; this
+     * includes the machine type, executable type, and section offsets.
+     */
+    public boolean isValid() {
+        return isMachineTypeValid() &&
+                (this.exeType != ExecutableType.UNKNOWN) &&
+                areOffsetsValid();
+    }
+
+    public long getTextSize() {
+        return this.a_text;
+    }
+
+    public long getDataSize() {
+        return this.a_data;
+    }
+
+    public long getBssSize() {
+        return this.a_bss;
+    }
+
+    public long getSymSize() {
+        return this.a_syms;
+    }
+
+    public long getEntryPoint() {
+        return this.a_entry;
+    }
+
+    public long getTextRelocSize() {
+        return this.a_trsize;
+    }
+
+    public long getDataRelocSize() {
+        return this.a_drsize;
+    }
+
+    public long getTextOffset() {
+        return this.txtOffset;
+    }
+
+    public long getDataOffset() {
+        return this.datOffset;
+    }
+
+    public long getTextRelocOffset() {
+        return this.txtRelOffset;
+    }
+
+    public long getDataRelocOffset() {
+        return this.datRelOffset;
+    }
+
+    public long getSymOffset() {
+        return this.symOffset;
+    }
+
+    public long getStrOffset() {
+        return this.strOffset;
+    }
+
+    public long getTextAddr() {
+        return this.txtAddr;
+    }
+
+    public long getDataAddr() {
+        return this.datAddr;
+    }
+
+    public long getBssAddr() {
+        return this.bssAddr;
+    }
+
+    /**
+     * Checks the magic word in the header for a known machine type ID, and sets the
+     * languageSpec string accordingly.
+     */
+    private void checkMachineTypeValidity(boolean readingAsLittleEndian) {
+
+        this.machineTypeValid = true;
+        this.pageSize = 4096;
+        final short machtype = (short) ((this.a_magic >> 16) & 0xFF);
+        final String readEndianness = readingAsLittleEndian ? "LE" : "BE";
+
+        switch (machtype) {
+            /**
+             * Motorola 68K family
+             */
+        case UnixAoutMachineType.M_68010:
+            this.languageSpec = "68000:BE:32:MC68010";
+            break;
+        case UnixAoutMachineType.M_OLDSUN2:
+            this.isOldSun2 = true;
+            this.pageSize = 2048;
+        case UnixAoutMachineType.M_68020:
+            this.languageSpec = "68000:BE:32:MC68020";
+            break;
+        case UnixAoutMachineType.M_M68K_NETBSD:
+            this.pageSize = 8192;
+        case UnixAoutMachineType.M_M68K4K_NETBSD:
+            this.isNetBSD = true;
+            this.languageSpec = "68000:BE:32:default";
+            break;
+
+            /**
+             * SPARC family
+             */
+        case UnixAoutMachineType.M_SPARC_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_SPARC:
+        case UnixAoutMachineType.M_SPARCLET:
+            this.isSparc = true;
+            this.pageSize = 8192;
+            this.languageSpec = "sparc:BE:32:default";
+            break;
+        case UnixAoutMachineType.M_SPARC64_NETBSD:
+            this.isNetBSD = true;
+            this.isSparc = true;
+            this.languageSpec = "sparc:BE:64:default";
+            break;
+
+            /**
+             * MIPS family
+             */
+        case UnixAoutMachineType.M_PMAX_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_MIPS1:
+        case UnixAoutMachineType.M_MIPS2:
+        case UnixAoutMachineType.M_R3000:
+            this.languageSpec = "MIPS:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_MIPS:
+            this.languageSpec = "MIPS:BE:32:default";
+            break;
+
+            /**
+             * National Semiconductor NS32000 family
+             */
+        case UnixAoutMachineType.M_532_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_NS32032:
+        case UnixAoutMachineType.M_NS32532:
+            this.languageSpec = "UNKNOWN:LE:32:default";
+            break;
+
+            /**
+             * x86 family
+             */
+        case UnixAoutMachineType.M_386_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_386:
+        case UnixAoutMachineType.M_386_DYNIX:
+            this.compilerSpec = "gcc";
+            this.languageSpec = "x86:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_X86_64_NETBSD:
+            this.compilerSpec = "gcc";
+            this.languageSpec = "x86:LE:64:default";
+            break;
+
+            /**
+             * ARM family
+             */
+        case UnixAoutMachineType.M_ARM6_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_ARM:
+            this.languageSpec = "ARM:" + readEndianness + "32:default";
+            break;
+        case UnixAoutMachineType.M_AARCH64:
+            this.languageSpec = "AARCH64:" + readEndianness + "64:default";
+            break;
+
+            /**
+             * RISC family
+             */
+        case UnixAoutMachineType.M_OR1K:
+            this.languageSpec = "UNKNOWN:BE:32:default";
+            break;
+        case UnixAoutMachineType.M_RISCV:
+            this.languageSpec = "RISCV:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_HPPA_OPENBSD:
+            this.languageSpec = "pa-risc:BE:32:default";
+            break;
+
+            /**
+             * PowerPC family
+             */
+        case UnixAoutMachineType.M_POWERPC_NETBSD:
+            this.isNetBSD = true;
+            this.languageSpec = "PowerPC:" + readEndianness + "32:default";
+            break;
+        case UnixAoutMachineType.M_POWERPC64:
+            this.languageSpec = "PowerPC:" + readEndianness + "64:default";
+            break;
+
+            /**
+             * SuperH family
+             * Note: It's unclear if there is support for SuperH SH-3 or SH-5 cores;
+             * the primary SuperH language seems to support SH-1 and SH-2 variants
+             * and the alternative is the SuperH4 language.
+             */
+        case UnixAoutMachineType.M_SH3:
+        case UnixAoutMachineType.M_SH5_32:
+            this.languageSpec = "SuperH:BE:32:default";
+            break;
+        case UnixAoutMachineType.M_SH5_64:
+            this.languageSpec = "SuperH:BE:64:default";
+            break;
+
+            /**
+             * VAX family
+             */
+        case UnixAoutMachineType.M_VAX_NETBSD:
+            this.pageSize = 512;
+        case UnixAoutMachineType.M_VAX4K_NETBSD:
+            this.isNetBSD = true;
+            this.languageSpec = "UNKNOWN:LE:32:default";
+            break;
+
+            /**
+             * Other
+             */
+        case UnixAoutMachineType.M_CRIS:
+            this.languageSpec = "UNKNOWN:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_ALPHA_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_IA64:
+            this.languageSpec = "UNKNOWN:" + readEndianness + "64:default";
+            break;
+        case UnixAoutMachineType.M_29K:
+        case UnixAoutMachineType.M_88K_OPENBSD:
+            this.languageSpec = "UNKNOWN:" + readEndianness + "32:default";
+            break;
+        default:
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java
user: login: nightlark
body: ```suggestion
        case UnixAoutMachineType.M_UNKNOWN:
            this.languageSpec = "UNKNOWN:" + readEndianness + ":32:default";
            break;
        default:
```

Given the number of different machines that could be targeted when the machine type is 0x00, I think the best that can be concluded is that an UNKNOWN big/little endian 32-bit processor is the target. At least the loader will be useable after manually identifying the correct processor, rather than unuseable or misidentifying the machine type as using a 68k processor when it is not.
created_at: 2023-03-30T00:00:29Z
author_association: NONE
diff_hunk: @@ -0,0 +1,501 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.aout;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+
+public class UnixAoutHeader {
+
+    enum ExecutableType {
+        OMAGIC, NMAGIC, ZMAGIC, QMAGIC, CMAGIC, UNKNOWN
+    }
+
+    private long binarySize;
+    private ExecutableType exeType;
+    private boolean machineTypeValid;
+    private String languageSpec;
+    private String compilerSpec = "default";
+    private long pageSize;
+
+    private boolean isOldSun2 = false;
+    private boolean isNetBSD = false;
+    private boolean isSparc = false;
+
+    private long a_magic;
+    private long a_text;
+    private long a_data;
+    private long a_bss;
+    private long a_syms;
+    private long a_entry;
+    private long a_trsize;
+    private long a_drsize;
+
+    private long txtOffset;
+    private long datOffset;
+    private long txtRelOffset;
+    private long datRelOffset;
+    private long symOffset;
+    private long strOffset;
+
+    private long txtAddr;
+    private long txtEndAddr;
+    private long datAddr;
+    private long bssAddr;
+
+    // The Linux implementation of a.out support might have a different header
+    // size, but it looks as though that might be caused by a strange mix of
+    // 32- and 64-bit integers being padded out in the struct. The intended
+    // size seems to be eight 32-bit words (32 bytes total.)
+    private static final int sizeOfExecHeader = 32;
+
+    /**
+     * Interprets binary data as an exec header from a UNIX-style a.out executable,
+     * and validates the contained fields.
+     *
+     * @param provider       Source of header binary data
+     * @param isLittleEndian Flag indicating whether to interpret the data as
+     *                       little-endian.
+     * @throws IOException
+     */
+    public UnixAoutHeader(ByteProvider provider, boolean isLittleEndian) throws IOException {
+        BinaryReader reader = new BinaryReader(provider, isLittleEndian);
+
+        this.a_magic = reader.readNextUnsignedInt();
+        this.a_text = reader.readNextUnsignedInt();
+        this.a_data = reader.readNextUnsignedInt();
+        this.a_bss = reader.readNextUnsignedInt();
+        this.a_syms = reader.readNextUnsignedInt();
+        this.a_entry = reader.readNextUnsignedInt();
+        this.a_trsize = reader.readNextUnsignedInt();
+        this.a_drsize = reader.readNextUnsignedInt();
+        this.binarySize = reader.length();
+
+        // NOTE: In NetBSD/i386 examples of a.out, the 32-bit a_magic/midmag word seems to
+        // always be written in big-endian regardless of the data endianness in the rest of
+        // the file. Are there other examples of this?
+        if (isLittleEndian) {
+            this.a_magic = Integer.reverseBytes((int)this.a_magic); 
+        }
+
+        checkExecutableType();
+        checkMachineTypeValidity(isLittleEndian);
+        determineTextOffset(isLittleEndian);
+
+        this.datOffset = this.txtOffset + this.a_text;
+        this.txtRelOffset = this.datOffset + this.a_data;
+        this.datRelOffset = this.txtRelOffset + this.a_trsize;
+        this.symOffset = this.datRelOffset + this.a_drsize;
+        this.strOffset = this.symOffset + this.a_syms;
+
+        determineTextAddr();
+        this.txtEndAddr = this.txtAddr + this.a_text;
+        this.datAddr = (this.exeType == ExecutableType.OMAGIC) ?
+                this.txtEndAddr : segmentRound(this.txtEndAddr);
+        this.bssAddr = this.datAddr + this.a_data;
+    }
+
+    /**
+     * Returns the processor/language specified by this header.
+     */
+    public String getLanguageSpec() {
+        return this.languageSpec;
+    }
+
+    /**
+     * Returns the compiler used by this executable. This is left as 'default' for
+     * all machine types other than i386, where it is assumed to be gcc.
+     */
+    public String getCompilerSpec() {
+        return this.compilerSpec;
+    }
+
+    /**
+     * Returns the enumerated type of executable contained in this A.out file.
+     */
+    public ExecutableType getExecutableType() {
+        return this.exeType;
+    }
+
+    /**
+     * Returns an indication of whether this header's fields are all valid; this
+     * includes the machine type, executable type, and section offsets.
+     */
+    public boolean isValid() {
+        return isMachineTypeValid() &&
+                (this.exeType != ExecutableType.UNKNOWN) &&
+                areOffsetsValid();
+    }
+
+    public long getTextSize() {
+        return this.a_text;
+    }
+
+    public long getDataSize() {
+        return this.a_data;
+    }
+
+    public long getBssSize() {
+        return this.a_bss;
+    }
+
+    public long getSymSize() {
+        return this.a_syms;
+    }
+
+    public long getEntryPoint() {
+        return this.a_entry;
+    }
+
+    public long getTextRelocSize() {
+        return this.a_trsize;
+    }
+
+    public long getDataRelocSize() {
+        return this.a_drsize;
+    }
+
+    public long getTextOffset() {
+        return this.txtOffset;
+    }
+
+    public long getDataOffset() {
+        return this.datOffset;
+    }
+
+    public long getTextRelocOffset() {
+        return this.txtRelOffset;
+    }
+
+    public long getDataRelocOffset() {
+        return this.datRelOffset;
+    }
+
+    public long getSymOffset() {
+        return this.symOffset;
+    }
+
+    public long getStrOffset() {
+        return this.strOffset;
+    }
+
+    public long getTextAddr() {
+        return this.txtAddr;
+    }
+
+    public long getDataAddr() {
+        return this.datAddr;
+    }
+
+    public long getBssAddr() {
+        return this.bssAddr;
+    }
+
+    /**
+     * Checks the magic word in the header for a known machine type ID, and sets the
+     * languageSpec string accordingly.
+     */
+    private void checkMachineTypeValidity(boolean readingAsLittleEndian) {
+
+        this.machineTypeValid = true;
+        this.pageSize = 4096;
+        final short machtype = (short) ((this.a_magic >> 16) & 0xFF);
+        final String readEndianness = readingAsLittleEndian ? "LE" : "BE";
+
+        switch (machtype) {
+            /**
+             * Motorola 68K family
+             */
+        case UnixAoutMachineType.M_68010:
+            this.languageSpec = "68000:BE:32:MC68010";
+            break;
+        case UnixAoutMachineType.M_OLDSUN2:
+            this.isOldSun2 = true;
+            this.pageSize = 2048;
+        case UnixAoutMachineType.M_68020:
+            this.languageSpec = "68000:BE:32:MC68020";
+            break;
+        case UnixAoutMachineType.M_M68K_NETBSD:
+            this.pageSize = 8192;
+        case UnixAoutMachineType.M_M68K4K_NETBSD:
+            this.isNetBSD = true;
+            this.languageSpec = "68000:BE:32:default";
+            break;
+
+            /**
+             * SPARC family
+             */
+        case UnixAoutMachineType.M_SPARC_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_SPARC:
+        case UnixAoutMachineType.M_SPARCLET:
+            this.isSparc = true;
+            this.pageSize = 8192;
+            this.languageSpec = "sparc:BE:32:default";
+            break;
+        case UnixAoutMachineType.M_SPARC64_NETBSD:
+            this.isNetBSD = true;
+            this.isSparc = true;
+            this.languageSpec = "sparc:BE:64:default";
+            break;
+
+            /**
+             * MIPS family
+             */
+        case UnixAoutMachineType.M_PMAX_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_MIPS1:
+        case UnixAoutMachineType.M_MIPS2:
+        case UnixAoutMachineType.M_R3000:
+            this.languageSpec = "MIPS:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_MIPS:
+            this.languageSpec = "MIPS:BE:32:default";
+            break;
+
+            /**
+             * National Semiconductor NS32000 family
+             */
+        case UnixAoutMachineType.M_532_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_NS32032:
+        case UnixAoutMachineType.M_NS32532:
+            this.languageSpec = "UNKNOWN:LE:32:default";
+            break;
+
+            /**
+             * x86 family
+             */
+        case UnixAoutMachineType.M_386_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_386:
+        case UnixAoutMachineType.M_386_DYNIX:
+            this.compilerSpec = "gcc";
+            this.languageSpec = "x86:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_X86_64_NETBSD:
+            this.compilerSpec = "gcc";
+            this.languageSpec = "x86:LE:64:default";
+            break;
+
+            /**
+             * ARM family
+             */
+        case UnixAoutMachineType.M_ARM6_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_ARM:
+            this.languageSpec = "ARM:" + readEndianness + "32:default";
+            break;
+        case UnixAoutMachineType.M_AARCH64:
+            this.languageSpec = "AARCH64:" + readEndianness + "64:default";
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java
user: login: nightlark
body: ```suggestion
        case UnixAoutMachineType.M_ARM:
            this.languageSpec = "ARM:" + readEndianness + ":32:default";
            break;
        case UnixAoutMachineType.M_AARCH64:
            this.languageSpec = "AARCH64:" + readEndianness + ":64:default";
```

Missing `:`'s
created_at: 2023-03-30T00:14:40Z
author_association: NONE
diff_hunk: @@ -0,0 +1,501 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.aout;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+
+public class UnixAoutHeader {
+
+    enum ExecutableType {
+        OMAGIC, NMAGIC, ZMAGIC, QMAGIC, CMAGIC, UNKNOWN
+    }
+
+    private long binarySize;
+    private ExecutableType exeType;
+    private boolean machineTypeValid;
+    private String languageSpec;
+    private String compilerSpec = "default";
+    private long pageSize;
+
+    private boolean isOldSun2 = false;
+    private boolean isNetBSD = false;
+    private boolean isSparc = false;
+
+    private long a_magic;
+    private long a_text;
+    private long a_data;
+    private long a_bss;
+    private long a_syms;
+    private long a_entry;
+    private long a_trsize;
+    private long a_drsize;
+
+    private long txtOffset;
+    private long datOffset;
+    private long txtRelOffset;
+    private long datRelOffset;
+    private long symOffset;
+    private long strOffset;
+
+    private long txtAddr;
+    private long txtEndAddr;
+    private long datAddr;
+    private long bssAddr;
+
+    // The Linux implementation of a.out support might have a different header
+    // size, but it looks as though that might be caused by a strange mix of
+    // 32- and 64-bit integers being padded out in the struct. The intended
+    // size seems to be eight 32-bit words (32 bytes total.)
+    private static final int sizeOfExecHeader = 32;
+
+    /**
+     * Interprets binary data as an exec header from a UNIX-style a.out executable,
+     * and validates the contained fields.
+     *
+     * @param provider       Source of header binary data
+     * @param isLittleEndian Flag indicating whether to interpret the data as
+     *                       little-endian.
+     * @throws IOException
+     */
+    public UnixAoutHeader(ByteProvider provider, boolean isLittleEndian) throws IOException {
+        BinaryReader reader = new BinaryReader(provider, isLittleEndian);
+
+        this.a_magic = reader.readNextUnsignedInt();
+        this.a_text = reader.readNextUnsignedInt();
+        this.a_data = reader.readNextUnsignedInt();
+        this.a_bss = reader.readNextUnsignedInt();
+        this.a_syms = reader.readNextUnsignedInt();
+        this.a_entry = reader.readNextUnsignedInt();
+        this.a_trsize = reader.readNextUnsignedInt();
+        this.a_drsize = reader.readNextUnsignedInt();
+        this.binarySize = reader.length();
+
+        // NOTE: In NetBSD/i386 examples of a.out, the 32-bit a_magic/midmag word seems to
+        // always be written in big-endian regardless of the data endianness in the rest of
+        // the file. Are there other examples of this?
+        if (isLittleEndian) {
+            this.a_magic = Integer.reverseBytes((int)this.a_magic); 
+        }
+
+        checkExecutableType();
+        checkMachineTypeValidity(isLittleEndian);
+        determineTextOffset(isLittleEndian);
+
+        this.datOffset = this.txtOffset + this.a_text;
+        this.txtRelOffset = this.datOffset + this.a_data;
+        this.datRelOffset = this.txtRelOffset + this.a_trsize;
+        this.symOffset = this.datRelOffset + this.a_drsize;
+        this.strOffset = this.symOffset + this.a_syms;
+
+        determineTextAddr();
+        this.txtEndAddr = this.txtAddr + this.a_text;
+        this.datAddr = (this.exeType == ExecutableType.OMAGIC) ?
+                this.txtEndAddr : segmentRound(this.txtEndAddr);
+        this.bssAddr = this.datAddr + this.a_data;
+    }
+
+    /**
+     * Returns the processor/language specified by this header.
+     */
+    public String getLanguageSpec() {
+        return this.languageSpec;
+    }
+
+    /**
+     * Returns the compiler used by this executable. This is left as 'default' for
+     * all machine types other than i386, where it is assumed to be gcc.
+     */
+    public String getCompilerSpec() {
+        return this.compilerSpec;
+    }
+
+    /**
+     * Returns the enumerated type of executable contained in this A.out file.
+     */
+    public ExecutableType getExecutableType() {
+        return this.exeType;
+    }
+
+    /**
+     * Returns an indication of whether this header's fields are all valid; this
+     * includes the machine type, executable type, and section offsets.
+     */
+    public boolean isValid() {
+        return isMachineTypeValid() &&
+                (this.exeType != ExecutableType.UNKNOWN) &&
+                areOffsetsValid();
+    }
+
+    public long getTextSize() {
+        return this.a_text;
+    }
+
+    public long getDataSize() {
+        return this.a_data;
+    }
+
+    public long getBssSize() {
+        return this.a_bss;
+    }
+
+    public long getSymSize() {
+        return this.a_syms;
+    }
+
+    public long getEntryPoint() {
+        return this.a_entry;
+    }
+
+    public long getTextRelocSize() {
+        return this.a_trsize;
+    }
+
+    public long getDataRelocSize() {
+        return this.a_drsize;
+    }
+
+    public long getTextOffset() {
+        return this.txtOffset;
+    }
+
+    public long getDataOffset() {
+        return this.datOffset;
+    }
+
+    public long getTextRelocOffset() {
+        return this.txtRelOffset;
+    }
+
+    public long getDataRelocOffset() {
+        return this.datRelOffset;
+    }
+
+    public long getSymOffset() {
+        return this.symOffset;
+    }
+
+    public long getStrOffset() {
+        return this.strOffset;
+    }
+
+    public long getTextAddr() {
+        return this.txtAddr;
+    }
+
+    public long getDataAddr() {
+        return this.datAddr;
+    }
+
+    public long getBssAddr() {
+        return this.bssAddr;
+    }
+
+    /**
+     * Checks the magic word in the header for a known machine type ID, and sets the
+     * languageSpec string accordingly.
+     */
+    private void checkMachineTypeValidity(boolean readingAsLittleEndian) {
+
+        this.machineTypeValid = true;
+        this.pageSize = 4096;
+        final short machtype = (short) ((this.a_magic >> 16) & 0xFF);
+        final String readEndianness = readingAsLittleEndian ? "LE" : "BE";
+
+        switch (machtype) {
+            /**
+             * Motorola 68K family
+             */
+        case UnixAoutMachineType.M_68010:
+            this.languageSpec = "68000:BE:32:MC68010";
+            break;
+        case UnixAoutMachineType.M_OLDSUN2:
+            this.isOldSun2 = true;
+            this.pageSize = 2048;
+        case UnixAoutMachineType.M_68020:
+            this.languageSpec = "68000:BE:32:MC68020";
+            break;
+        case UnixAoutMachineType.M_M68K_NETBSD:
+            this.pageSize = 8192;
+        case UnixAoutMachineType.M_M68K4K_NETBSD:
+            this.isNetBSD = true;
+            this.languageSpec = "68000:BE:32:default";
+            break;
+
+            /**
+             * SPARC family
+             */
+        case UnixAoutMachineType.M_SPARC_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_SPARC:
+        case UnixAoutMachineType.M_SPARCLET:
+            this.isSparc = true;
+            this.pageSize = 8192;
+            this.languageSpec = "sparc:BE:32:default";
+            break;
+        case UnixAoutMachineType.M_SPARC64_NETBSD:
+            this.isNetBSD = true;
+            this.isSparc = true;
+            this.languageSpec = "sparc:BE:64:default";
+            break;
+
+            /**
+             * MIPS family
+             */
+        case UnixAoutMachineType.M_PMAX_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_MIPS1:
+        case UnixAoutMachineType.M_MIPS2:
+        case UnixAoutMachineType.M_R3000:
+            this.languageSpec = "MIPS:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_MIPS:
+            this.languageSpec = "MIPS:BE:32:default";
+            break;
+
+            /**
+             * National Semiconductor NS32000 family
+             */
+        case UnixAoutMachineType.M_532_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_NS32032:
+        case UnixAoutMachineType.M_NS32532:
+            this.languageSpec = "UNKNOWN:LE:32:default";
+            break;
+
+            /**
+             * x86 family
+             */
+        case UnixAoutMachineType.M_386_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_386:
+        case UnixAoutMachineType.M_386_DYNIX:
+            this.compilerSpec = "gcc";
+            this.languageSpec = "x86:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_X86_64_NETBSD:
+            this.compilerSpec = "gcc";
+            this.languageSpec = "x86:LE:64:default";
+            break;
+
+            /**
+             * ARM family
+             */
+        case UnixAoutMachineType.M_ARM6_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_ARM:
+            this.languageSpec = "ARM:" + readEndianness + "32:default";
+            break;
+        case UnixAoutMachineType.M_AARCH64:
+            this.languageSpec = "AARCH64:" + readEndianness + "64:default";
+            break;
+
+            /**
+             * RISC family
+             */
+        case UnixAoutMachineType.M_OR1K:
+            this.languageSpec = "UNKNOWN:BE:32:default";
+            break;
+        case UnixAoutMachineType.M_RISCV:
+            this.languageSpec = "RISCV:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_HPPA_OPENBSD:
+            this.languageSpec = "pa-risc:BE:32:default";
+            break;
+
+            /**
+             * PowerPC family
+             */
+        case UnixAoutMachineType.M_POWERPC_NETBSD:
+            this.isNetBSD = true;
+            this.languageSpec = "PowerPC:" + readEndianness + "32:default";
+            break;
+        case UnixAoutMachineType.M_POWERPC64:
+            this.languageSpec = "PowerPC:" + readEndianness + "64:default";
+            break;
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java
user: login: nightlark
body: ```suggestion
        case UnixAoutMachineType.M_POWERPC_NETBSD:
            this.isNetBSD = true;
            this.languageSpec = "PowerPC:" + readEndianness + ":32:default";
            break;
        case UnixAoutMachineType.M_POWERPC64:
            this.languageSpec = "PowerPC:" + readEndianness + ":64:default";
            break;
```

Missing `:`'s
created_at: 2023-03-30T00:15:10Z
author_association: NONE
diff_hunk: @@ -0,0 +1,501 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.aout;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+
+public class UnixAoutHeader {
+
+    enum ExecutableType {
+        OMAGIC, NMAGIC, ZMAGIC, QMAGIC, CMAGIC, UNKNOWN
+    }
+
+    private long binarySize;
+    private ExecutableType exeType;
+    private boolean machineTypeValid;
+    private String languageSpec;
+    private String compilerSpec = "default";
+    private long pageSize;
+
+    private boolean isOldSun2 = false;
+    private boolean isNetBSD = false;
+    private boolean isSparc = false;
+
+    private long a_magic;
+    private long a_text;
+    private long a_data;
+    private long a_bss;
+    private long a_syms;
+    private long a_entry;
+    private long a_trsize;
+    private long a_drsize;
+
+    private long txtOffset;
+    private long datOffset;
+    private long txtRelOffset;
+    private long datRelOffset;
+    private long symOffset;
+    private long strOffset;
+
+    private long txtAddr;
+    private long txtEndAddr;
+    private long datAddr;
+    private long bssAddr;
+
+    // The Linux implementation of a.out support might have a different header
+    // size, but it looks as though that might be caused by a strange mix of
+    // 32- and 64-bit integers being padded out in the struct. The intended
+    // size seems to be eight 32-bit words (32 bytes total.)
+    private static final int sizeOfExecHeader = 32;
+
+    /**
+     * Interprets binary data as an exec header from a UNIX-style a.out executable,
+     * and validates the contained fields.
+     *
+     * @param provider       Source of header binary data
+     * @param isLittleEndian Flag indicating whether to interpret the data as
+     *                       little-endian.
+     * @throws IOException
+     */
+    public UnixAoutHeader(ByteProvider provider, boolean isLittleEndian) throws IOException {
+        BinaryReader reader = new BinaryReader(provider, isLittleEndian);
+
+        this.a_magic = reader.readNextUnsignedInt();
+        this.a_text = reader.readNextUnsignedInt();
+        this.a_data = reader.readNextUnsignedInt();
+        this.a_bss = reader.readNextUnsignedInt();
+        this.a_syms = reader.readNextUnsignedInt();
+        this.a_entry = reader.readNextUnsignedInt();
+        this.a_trsize = reader.readNextUnsignedInt();
+        this.a_drsize = reader.readNextUnsignedInt();
+        this.binarySize = reader.length();
+
+        // NOTE: In NetBSD/i386 examples of a.out, the 32-bit a_magic/midmag word seems to
+        // always be written in big-endian regardless of the data endianness in the rest of
+        // the file. Are there other examples of this?
+        if (isLittleEndian) {
+            this.a_magic = Integer.reverseBytes((int)this.a_magic); 
+        }
+
+        checkExecutableType();
+        checkMachineTypeValidity(isLittleEndian);
+        determineTextOffset(isLittleEndian);
+
+        this.datOffset = this.txtOffset + this.a_text;
+        this.txtRelOffset = this.datOffset + this.a_data;
+        this.datRelOffset = this.txtRelOffset + this.a_trsize;
+        this.symOffset = this.datRelOffset + this.a_drsize;
+        this.strOffset = this.symOffset + this.a_syms;
+
+        determineTextAddr();
+        this.txtEndAddr = this.txtAddr + this.a_text;
+        this.datAddr = (this.exeType == ExecutableType.OMAGIC) ?
+                this.txtEndAddr : segmentRound(this.txtEndAddr);
+        this.bssAddr = this.datAddr + this.a_data;
+    }
+
+    /**
+     * Returns the processor/language specified by this header.
+     */
+    public String getLanguageSpec() {
+        return this.languageSpec;
+    }
+
+    /**
+     * Returns the compiler used by this executable. This is left as 'default' for
+     * all machine types other than i386, where it is assumed to be gcc.
+     */
+    public String getCompilerSpec() {
+        return this.compilerSpec;
+    }
+
+    /**
+     * Returns the enumerated type of executable contained in this A.out file.
+     */
+    public ExecutableType getExecutableType() {
+        return this.exeType;
+    }
+
+    /**
+     * Returns an indication of whether this header's fields are all valid; this
+     * includes the machine type, executable type, and section offsets.
+     */
+    public boolean isValid() {
+        return isMachineTypeValid() &&
+                (this.exeType != ExecutableType.UNKNOWN) &&
+                areOffsetsValid();
+    }
+
+    public long getTextSize() {
+        return this.a_text;
+    }
+
+    public long getDataSize() {
+        return this.a_data;
+    }
+
+    public long getBssSize() {
+        return this.a_bss;
+    }
+
+    public long getSymSize() {
+        return this.a_syms;
+    }
+
+    public long getEntryPoint() {
+        return this.a_entry;
+    }
+
+    public long getTextRelocSize() {
+        return this.a_trsize;
+    }
+
+    public long getDataRelocSize() {
+        return this.a_drsize;
+    }
+
+    public long getTextOffset() {
+        return this.txtOffset;
+    }
+
+    public long getDataOffset() {
+        return this.datOffset;
+    }
+
+    public long getTextRelocOffset() {
+        return this.txtRelOffset;
+    }
+
+    public long getDataRelocOffset() {
+        return this.datRelOffset;
+    }
+
+    public long getSymOffset() {
+        return this.symOffset;
+    }
+
+    public long getStrOffset() {
+        return this.strOffset;
+    }
+
+    public long getTextAddr() {
+        return this.txtAddr;
+    }
+
+    public long getDataAddr() {
+        return this.datAddr;
+    }
+
+    public long getBssAddr() {
+        return this.bssAddr;
+    }
+
+    /**
+     * Checks the magic word in the header for a known machine type ID, and sets the
+     * languageSpec string accordingly.
+     */
+    private void checkMachineTypeValidity(boolean readingAsLittleEndian) {
+
+        this.machineTypeValid = true;
+        this.pageSize = 4096;
+        final short machtype = (short) ((this.a_magic >> 16) & 0xFF);
+        final String readEndianness = readingAsLittleEndian ? "LE" : "BE";
+
+        switch (machtype) {
+            /**
+             * Motorola 68K family
+             */
+        case UnixAoutMachineType.M_68010:
+            this.languageSpec = "68000:BE:32:MC68010";
+            break;
+        case UnixAoutMachineType.M_OLDSUN2:
+            this.isOldSun2 = true;
+            this.pageSize = 2048;
+        case UnixAoutMachineType.M_68020:
+            this.languageSpec = "68000:BE:32:MC68020";
+            break;
+        case UnixAoutMachineType.M_M68K_NETBSD:
+            this.pageSize = 8192;
+        case UnixAoutMachineType.M_M68K4K_NETBSD:
+            this.isNetBSD = true;
+            this.languageSpec = "68000:BE:32:default";
+            break;
+
+            /**
+             * SPARC family
+             */
+        case UnixAoutMachineType.M_SPARC_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_SPARC:
+        case UnixAoutMachineType.M_SPARCLET:
+            this.isSparc = true;
+            this.pageSize = 8192;
+            this.languageSpec = "sparc:BE:32:default";
+            break;
+        case UnixAoutMachineType.M_SPARC64_NETBSD:
+            this.isNetBSD = true;
+            this.isSparc = true;
+            this.languageSpec = "sparc:BE:64:default";
+            break;
+
+            /**
+             * MIPS family
+             */
+        case UnixAoutMachineType.M_PMAX_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_MIPS1:
+        case UnixAoutMachineType.M_MIPS2:
+        case UnixAoutMachineType.M_R3000:
+            this.languageSpec = "MIPS:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_MIPS:
+            this.languageSpec = "MIPS:BE:32:default";
+            break;
+
+            /**
+             * National Semiconductor NS32000 family
+             */
+        case UnixAoutMachineType.M_532_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_NS32032:
+        case UnixAoutMachineType.M_NS32532:
+            this.languageSpec = "UNKNOWN:LE:32:default";
+            break;
+
+            /**
+             * x86 family
+             */
+        case UnixAoutMachineType.M_386_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_386:
+        case UnixAoutMachineType.M_386_DYNIX:
+            this.compilerSpec = "gcc";
+            this.languageSpec = "x86:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_X86_64_NETBSD:
+            this.compilerSpec = "gcc";
+            this.languageSpec = "x86:LE:64:default";
+            break;
+
+            /**
+             * ARM family
+             */
+        case UnixAoutMachineType.M_ARM6_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_ARM:
+            this.languageSpec = "ARM:" + readEndianness + "32:default";
+            break;
+        case UnixAoutMachineType.M_AARCH64:
+            this.languageSpec = "AARCH64:" + readEndianness + "64:default";
+            break;
+
+            /**
+             * RISC family
+             */
+        case UnixAoutMachineType.M_OR1K:
+            this.languageSpec = "UNKNOWN:BE:32:default";
+            break;
+        case UnixAoutMachineType.M_RISCV:
+            this.languageSpec = "RISCV:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_HPPA_OPENBSD:
+            this.languageSpec = "pa-risc:BE:32:default";
+            break;
+
+            /**
+             * PowerPC family
+             */
+        case UnixAoutMachineType.M_POWERPC_NETBSD:
+            this.isNetBSD = true;
+            this.languageSpec = "PowerPC:" + readEndianness + "32:default";
+            break;
+        case UnixAoutMachineType.M_POWERPC64:
+            this.languageSpec = "PowerPC:" + readEndianness + "64:default";
+            break;
+
+            /**
+             * SuperH family
+             * Note: It's unclear if there is support for SuperH SH-3 or SH-5 cores;
+             * the primary SuperH language seems to support SH-1 and SH-2 variants
+             * and the alternative is the SuperH4 language.
+             */
+        case UnixAoutMachineType.M_SH3:
+        case UnixAoutMachineType.M_SH5_32:
+            this.languageSpec = "SuperH:BE:32:default";
+            break;
+        case UnixAoutMachineType.M_SH5_64:
+            this.languageSpec = "SuperH:BE:64:default";
+            break;
+
+            /**
+             * VAX family
+             */
+        case UnixAoutMachineType.M_VAX_NETBSD:
+            this.pageSize = 512;
+        case UnixAoutMachineType.M_VAX4K_NETBSD:
+            this.isNetBSD = true;
+            this.languageSpec = "UNKNOWN:LE:32:default";
+            break;
+
+            /**
+             * Other
+             */
+        case UnixAoutMachineType.M_CRIS:
+            this.languageSpec = "UNKNOWN:LE:32:default";
+            break;
+        case UnixAoutMachineType.M_ALPHA_NETBSD:
+            this.isNetBSD = true;
+        case UnixAoutMachineType.M_IA64:
+            this.languageSpec = "UNKNOWN:" + readEndianness + "64:default";
+            break;
+        case UnixAoutMachineType.M_29K:
+        case UnixAoutMachineType.M_88K_OPENBSD:
+            this.languageSpec = "UNKNOWN:" + readEndianness + "32:default";
+            break;
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java
user: login: nightlark
body: ```suggestion
        case UnixAoutMachineType.M_IA64:
            this.languageSpec = "UNKNOWN:" + readEndianness + ":64:default";
            break;
        case UnixAoutMachineType.M_29K:
        case UnixAoutMachineType.M_88K_OPENBSD:
            this.languageSpec = "UNKNOWN:" + readEndianness + ":32:default";
            break;
```

Missing `:`'s
created_at: 2023-03-30T00:15:40Z
author_association: NONE
diff_hunk: @@ -0,0 +1,574 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.unixaout;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.data.CategoryPath;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.program.model.listing.Listing;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.util.CodeUnitInsertionException;
+import ghidra.util.exception.DuplicateNameException;
+
+public class UnixAoutHeader implements StructConverter {
+
+	public enum AoutType {
+		OMAGIC, NMAGIC, ZMAGIC, QMAGIC, CMAGIC, UNKNOWN
+	}
+
+	private BinaryReader reader;
+
+	private long binarySize;
+	private AoutType exeType;
+	private boolean machineTypeValid;
+	private String languageSpec;
+	private String compilerSpec = "default";
+	private long pageSize;
+
+	private boolean isNetBSD = false;
+	private boolean isSparc = false;
+
+	private long a_magic;
+	private long a_text;
+	private long a_data;
+	private long a_bss;
+	private long a_syms;
+	private long a_entry;
+	private long a_trsize;
+	private long a_drsize;
+
+	private long strSize;
+
+	private long txtOffset;
+	private long datOffset;
+	private long txtRelOffset;
+	private long datRelOffset;
+	private long symOffset;
+	private long strOffset;
+
+	private long txtAddr;
+	private long txtEndAddr;
+	private long datAddr;
+	private long bssAddr;
+
+	// The Linux implementation of a.out appears to start the .text content at
+	// file offset 0x400 (rather than immediately after the 0x20 bytes of header
+	// data). It's possible that there exist Linux a.out executabes with other
+	// (unintended?) header sizes caused by a mixture of 32- and 64-bit integers
+	// being padded out in the struct. The intended size is eight 32-bit words
+	// (32 bytes total.)
+	private static final int sizeOfExecHeader = 0x20;
+	private static final int sizeOfLongExecHeader = 0x400;
+
+	/**
+	 * Interprets binary data as an exec header from a UNIX-style a.out executable,
+	 * and validates the contained fields.
+	 *
+	 * @param provider       Source of header binary data
+	 * @param isLittleEndian Flag indicating whether to interpret the data as
+	 *                       little-endian.
+	 * @throws IOException
+	 */
+	public UnixAoutHeader(ByteProvider provider, boolean isLittleEndian) throws IOException {
+		this.reader = new BinaryReader(provider, isLittleEndian);
+
+		this.a_magic = reader.readNextUnsignedInt();
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java
user: login: ryanmkurtz
body: Does it ever make sense to read the magic as little endian?  The spec says it's always big endian.
created_at: 2025-03-19T13:29:14Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,574 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ghidra.app.util.bin.format.unixaout;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.data.CategoryPath;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.program.model.listing.Listing;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.util.CodeUnitInsertionException;
+import ghidra.util.exception.DuplicateNameException;
+
+public class UnixAoutHeader implements StructConverter {
+
+	public enum AoutType {
+		OMAGIC, NMAGIC, ZMAGIC, QMAGIC, CMAGIC, UNKNOWN
+	}
+
+	private BinaryReader reader;
+
+	private long binarySize;
+	private AoutType exeType;
+	private boolean machineTypeValid;
+	private String languageSpec;
+	private String compilerSpec = "default";
+	private long pageSize;
+
+	private boolean isNetBSD = false;
+	private boolean isSparc = false;
+
+	private long a_magic;
+	private long a_text;
+	private long a_data;
+	private long a_bss;
+	private long a_syms;
+	private long a_entry;
+	private long a_trsize;
+	private long a_drsize;
+
+	private long strSize;
+
+	private long txtOffset;
+	private long datOffset;
+	private long txtRelOffset;
+	private long datRelOffset;
+	private long symOffset;
+	private long strOffset;
+
+	private long txtAddr;
+	private long txtEndAddr;
+	private long datAddr;
+	private long bssAddr;
+
+	// The Linux implementation of a.out appears to start the .text content at
+	// file offset 0x400 (rather than immediately after the 0x20 bytes of header
+	// data). It's possible that there exist Linux a.out executabes with other
+	// (unintended?) header sizes caused by a mixture of 32- and 64-bit integers
+	// being padded out in the struct. The intended size is eight 32-bit words
+	// (32 bytes total.)
+	private static final int sizeOfExecHeader = 0x20;
+	private static final int sizeOfLongExecHeader = 0x400;
+
+	/**
+	 * Interprets binary data as an exec header from a UNIX-style a.out executable,
+	 * and validates the contained fields.
+	 *
+	 * @param provider       Source of header binary data
+	 * @param isLittleEndian Flag indicating whether to interpret the data as
+	 *                       little-endian.
+	 * @throws IOException
+	 */
+	public UnixAoutHeader(ByteProvider provider, boolean isLittleEndian) throws IOException {
+		this.reader = new BinaryReader(provider, isLittleEndian);
+
+		this.a_magic = reader.readNextUnsignedInt();
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/unixaout/UnixAoutHeader.java
user: login: nightlark
body: Some systems ostensibly use little endian ordering for the magic bytes: https://github.com/NationalSecurityAgency/ghidra/pull/5004#discussion_r1152596639
created_at: 2025-03-19T13:38:52Z
author_association: NONE
diff_hunk: @@ -0,0 +1,476 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.util.opinion;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import ghidra.app.util.MemoryBlockUtils;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.format.MemoryLoadable;
+import ghidra.app.util.bin.format.unixaout.UnixAoutHeader;
+import ghidra.app.util.bin.format.unixaout.UnixAoutRelocation;
+import ghidra.app.util.bin.format.unixaout.UnixAoutRelocationTable;
+import ghidra.app.util.bin.format.unixaout.UnixAoutStringTable;
+import ghidra.app.util.bin.format.unixaout.UnixAoutSymbol;
+import ghidra.app.util.bin.format.unixaout.UnixAoutSymbolTable;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.program.database.function.OverlappingFunctionException;
+import ghidra.program.database.mem.FileBytes;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressOverflowException;
+import ghidra.program.model.address.AddressSet;
+import ghidra.program.model.address.AddressSpace;
+import ghidra.program.model.listing.FunctionManager;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.Memory;
+import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.program.model.reloc.Relocation.Status;
+import ghidra.program.model.reloc.RelocationTable;
+import ghidra.program.model.symbol.SourceType;
+import ghidra.program.model.symbol.Symbol;
+import ghidra.program.model.symbol.SymbolIterator;
+import ghidra.program.model.symbol.SymbolTable;
+import ghidra.program.model.util.CodeUnitInsertionException;
+import ghidra.util.DataConverter;
+import ghidra.util.MonitoredInputStream;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.DuplicateNameException;
+import ghidra.util.exception.InvalidInputException;
+import ghidra.util.task.TaskMonitor;
+
+public class UnixAoutProgramLoader extends MemorySectionResolver {
+	private final static String BLOCK_SOURCE_NAME = "Unix Aout Loader";
+	private final int EXTERNAL_BLOCK_MIN_SIZE = 0x10000; // 64K
+
+	public final static String dot_text = ".text";
+	public final static String dot_data = ".data";
+	public final static String dot_bss = ".bss";
+	public final static String dot_rel_text = ".rel.text";
+	public final static String dot_rel_data = ".rel.data";
+	public final static String dot_strtab = ".strtab";
+	public final static String dot_symtab = ".symtab";
+
+	private final TaskMonitor monitor;
+	private final MessageLog log;
+	private final UnixAoutHeader header;
+
+	private FileBytes fileBytes;
+
+	private UnixAoutRelocationTable relText;
+	private UnixAoutRelocationTable relData;
+	private UnixAoutSymbolTable symtab;
+	private UnixAoutStringTable strtab;
+
+	private Map<String, Long> possibleBssSymbols = new HashMap<>();
+	private int extraBssSize = 0;
+	private int undefinedSymbolCount = 0;
+
+	public UnixAoutProgramLoader(Program program, UnixAoutHeader header, TaskMonitor monitor, MessageLog log) {
+		super(program);
+
+		this.monitor = monitor;
+		this.log = log;
+		this.header = header;
+	}
+
+	public void loadAout(long baseAddr) throws IOException, CancelledException {
+		log.appendMsg(String.format("----- Loading %s -----", header.getReader().getByteProvider().getAbsolutePath()));
+		log.appendMsg(String.format("Found a.out type %s.", header.getExecutableType().name()));
+
+		ByteProvider byteProvider = header.getReader().getByteProvider();
+
+		try {
+			buildTables(byteProvider);
+			preprocessSymbolTable();
+			loadSections(baseAddr, byteProvider);
+			loadSymbols();
+			applyRelocations(baseAddr, program.getMemory().getBlock(dot_text), relText);
+			applyRelocations(baseAddr, program.getMemory().getBlock(dot_data), relData);
+			markupSections();
+		} catch (AddressOverflowException | InvalidInputException | CodeUnitInsertionException | DuplicateNameException
+				| MemoryAccessException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private void buildTables(ByteProvider byteProvider) throws IOException {
+		if (header.getStrSize() > 0) {
+			strtab = new UnixAoutStringTable(header.getReader(), header.getStrOffset(), header.getStrSize());
+		}
+		if (header.getSymSize() > 0) {
+			symtab = new UnixAoutSymbolTable(header.getReader(), header.getSymOffset(), header.getSymSize(),
+					strtab, log);
+		}
+		if (header.getTextRelocSize() > 0) {
+			relText = new UnixAoutRelocationTable(header.getReader(), header.getTextRelocOffset(),
+					header.getTextRelocSize(), symtab);
+		}
+		if (header.getDataRelocSize() > 0) {
+			relData = new UnixAoutRelocationTable(header.getReader(), header.getDataRelocOffset(),
+					header.getDataRelocSize(), symtab);
+		}
+	}
+
+	private void preprocessSymbolTable() {
+		if (symtab == null) {
+			return;
+		}
+
+		boolean foundStabs = false;
+		for (UnixAoutSymbol symbol : symtab) {
+			switch (symbol.type) {
+				case N_UNDF:
+					if (symbol.value > 0) {
+						// This is a special case given by the A.out spec: if the linker cannot find
+						// this symbol in any of the other binary files, then the fact that it is
+						// marked as N_UNDF but has a non-zero value means that its value should be
+						// interpreted as a size, and the linker should reserve space in .bss for it.
+						possibleBssSymbols.put(symbol.name, symbol.value);
+					} else {
+						undefinedSymbolCount++;
+					}
+					break;
+				case N_STAB:
+					if (!foundStabs) {
+						foundStabs = true;
+						log.appendMsg(dot_symtab, "File contains STABS.");
+					}
+					break;
+				default:
+					break;
+			}
+		}
+
+		for (Long value : possibleBssSymbols.values()) {
+			extraBssSize += value;
+		}
+
+		if (extraBssSize > 0) {
+			log.appendMsg(dot_bss, String.format("Added %d bytes for N_UNDF symbols.", extraBssSize));
+		}
+	}
+
+	private void loadSections(long baseAddr, ByteProvider byteProvider)
+			throws AddressOverflowException, IOException, CancelledException {
+		monitor.setMessage("Loading FileBytes...");
+
+		try (InputStream fileIn = byteProvider.getInputStream(0);
+				MonitoredInputStream mis = new MonitoredInputStream(fileIn, monitor)) {
+			// Indicate that cleanup is not neccessary for cancelled import operation.
+			mis.setCleanupOnCancel(false);
+			fileBytes = program.getMemory().createFileBytes(byteProvider.getName(), 0, byteProvider.length(), mis,
+					monitor);
+		}
+
+		final AddressSpace defaultAddressSpace = program.getAddressFactory().getDefaultAddressSpace();
+		final Address otherAddress = AddressSpace.OTHER_SPACE.getMinAddress();
+		Address address;
+		Address nextFreeAddress = defaultAddressSpace.getAddress(0);
+
+		if (header.getTextOffset() != 0 || header.getTextSize() < 32) {
+			addInitializedMemorySection(null, 0, 32, otherAddress, "_aoutHeader", false, false, false, null, false,
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutProgramLoader.java
user: login: ryanmkurtz
body: Did you find that you actually needed the `MemorySectionResolver`, or were you just modeling things off of the `ElfLoader`?  If you just called `MemoryBlockUtils.createInitializedBlock()` directly here, were you running into conflicts?  If there was not real issue with conflicts, I'll probably remove the resolver for simplicity.  That was really only introduced because ELF is so complicated.
created_at: 2025-03-19T16:29:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,476 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.util.opinion;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import ghidra.app.util.MemoryBlockUtils;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.format.MemoryLoadable;
+import ghidra.app.util.bin.format.unixaout.UnixAoutHeader;
+import ghidra.app.util.bin.format.unixaout.UnixAoutRelocation;
+import ghidra.app.util.bin.format.unixaout.UnixAoutRelocationTable;
+import ghidra.app.util.bin.format.unixaout.UnixAoutStringTable;
+import ghidra.app.util.bin.format.unixaout.UnixAoutSymbol;
+import ghidra.app.util.bin.format.unixaout.UnixAoutSymbolTable;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.program.database.function.OverlappingFunctionException;
+import ghidra.program.database.mem.FileBytes;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressOverflowException;
+import ghidra.program.model.address.AddressSet;
+import ghidra.program.model.address.AddressSpace;
+import ghidra.program.model.listing.FunctionManager;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.Memory;
+import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.program.model.reloc.Relocation.Status;
+import ghidra.program.model.reloc.RelocationTable;
+import ghidra.program.model.symbol.SourceType;
+import ghidra.program.model.symbol.Symbol;
+import ghidra.program.model.symbol.SymbolIterator;
+import ghidra.program.model.symbol.SymbolTable;
+import ghidra.program.model.util.CodeUnitInsertionException;
+import ghidra.util.DataConverter;
+import ghidra.util.MonitoredInputStream;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.DuplicateNameException;
+import ghidra.util.exception.InvalidInputException;
+import ghidra.util.task.TaskMonitor;
+
+public class UnixAoutProgramLoader extends MemorySectionResolver {
+	private final static String BLOCK_SOURCE_NAME = "Unix Aout Loader";
+	private final int EXTERNAL_BLOCK_MIN_SIZE = 0x10000; // 64K
+
+	public final static String dot_text = ".text";
+	public final static String dot_data = ".data";
+	public final static String dot_bss = ".bss";
+	public final static String dot_rel_text = ".rel.text";
+	public final static String dot_rel_data = ".rel.data";
+	public final static String dot_strtab = ".strtab";
+	public final static String dot_symtab = ".symtab";
+
+	private final TaskMonitor monitor;
+	private final MessageLog log;
+	private final UnixAoutHeader header;
+
+	private FileBytes fileBytes;
+
+	private UnixAoutRelocationTable relText;
+	private UnixAoutRelocationTable relData;
+	private UnixAoutSymbolTable symtab;
+	private UnixAoutStringTable strtab;
+
+	private Map<String, Long> possibleBssSymbols = new HashMap<>();
+	private int extraBssSize = 0;
+	private int undefinedSymbolCount = 0;
+
+	public UnixAoutProgramLoader(Program program, UnixAoutHeader header, TaskMonitor monitor, MessageLog log) {
+		super(program);
+
+		this.monitor = monitor;
+		this.log = log;
+		this.header = header;
+	}
+
+	public void loadAout(long baseAddr) throws IOException, CancelledException {
+		log.appendMsg(String.format("----- Loading %s -----", header.getReader().getByteProvider().getAbsolutePath()));
+		log.appendMsg(String.format("Found a.out type %s.", header.getExecutableType().name()));
+
+		ByteProvider byteProvider = header.getReader().getByteProvider();
+
+		try {
+			buildTables(byteProvider);
+			preprocessSymbolTable();
+			loadSections(baseAddr, byteProvider);
+			loadSymbols();
+			applyRelocations(baseAddr, program.getMemory().getBlock(dot_text), relText);
+			applyRelocations(baseAddr, program.getMemory().getBlock(dot_data), relData);
+			markupSections();
+		} catch (AddressOverflowException | InvalidInputException | CodeUnitInsertionException | DuplicateNameException
+				| MemoryAccessException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private void buildTables(ByteProvider byteProvider) throws IOException {
+		if (header.getStrSize() > 0) {
+			strtab = new UnixAoutStringTable(header.getReader(), header.getStrOffset(), header.getStrSize());
+		}
+		if (header.getSymSize() > 0) {
+			symtab = new UnixAoutSymbolTable(header.getReader(), header.getSymOffset(), header.getSymSize(),
+					strtab, log);
+		}
+		if (header.getTextRelocSize() > 0) {
+			relText = new UnixAoutRelocationTable(header.getReader(), header.getTextRelocOffset(),
+					header.getTextRelocSize(), symtab);
+		}
+		if (header.getDataRelocSize() > 0) {
+			relData = new UnixAoutRelocationTable(header.getReader(), header.getDataRelocOffset(),
+					header.getDataRelocSize(), symtab);
+		}
+	}
+
+	private void preprocessSymbolTable() {
+		if (symtab == null) {
+			return;
+		}
+
+		boolean foundStabs = false;
+		for (UnixAoutSymbol symbol : symtab) {
+			switch (symbol.type) {
+				case N_UNDF:
+					if (symbol.value > 0) {
+						// This is a special case given by the A.out spec: if the linker cannot find
+						// this symbol in any of the other binary files, then the fact that it is
+						// marked as N_UNDF but has a non-zero value means that its value should be
+						// interpreted as a size, and the linker should reserve space in .bss for it.
+						possibleBssSymbols.put(symbol.name, symbol.value);
+					} else {
+						undefinedSymbolCount++;
+					}
+					break;
+				case N_STAB:
+					if (!foundStabs) {
+						foundStabs = true;
+						log.appendMsg(dot_symtab, "File contains STABS.");
+					}
+					break;
+				default:
+					break;
+			}
+		}
+
+		for (Long value : possibleBssSymbols.values()) {
+			extraBssSize += value;
+		}
+
+		if (extraBssSize > 0) {
+			log.appendMsg(dot_bss, String.format("Added %d bytes for N_UNDF symbols.", extraBssSize));
+		}
+	}
+
+	private void loadSections(long baseAddr, ByteProvider byteProvider)
+			throws AddressOverflowException, IOException, CancelledException {
+		monitor.setMessage("Loading FileBytes...");
+
+		try (InputStream fileIn = byteProvider.getInputStream(0);
+				MonitoredInputStream mis = new MonitoredInputStream(fileIn, monitor)) {
+			// Indicate that cleanup is not neccessary for cancelled import operation.
+			mis.setCleanupOnCancel(false);
+			fileBytes = program.getMemory().createFileBytes(byteProvider.getName(), 0, byteProvider.length(), mis,
+					monitor);
+		}
+
+		final AddressSpace defaultAddressSpace = program.getAddressFactory().getDefaultAddressSpace();
+		final Address otherAddress = AddressSpace.OTHER_SPACE.getMinAddress();
+		Address address;
+		Address nextFreeAddress = defaultAddressSpace.getAddress(0);
+
+		if (header.getTextOffset() != 0 || header.getTextSize() < 32) {
+			addInitializedMemorySection(null, 0, 32, otherAddress, "_aoutHeader", false, false, false, null, false,
path: Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutProgramLoader.java
user: login: colinbourassa
body: I don't remember there being any conflicts that drove this choice. It's been a while, but I recall modeling this off of another loader, so yes, removing the resolver for simplicity should be OK.
created_at: 2025-03-19T17:15:17Z
author_association: CONTRIBUTOR

PR comments:
user: login: ryanmkurtz
created_at: 2023-02-18T12:57:42Z
body: Thanks!  Would it be possible for you to attach some sample binaries to aid in our testing?
author_association: COLLABORATOR
user: login: colinbourassa
created_at: 2023-02-18T18:28:00Z
body: @ryanmkurtz - Certainly. The attached archive contains three different A.out examples:

* NetBSD executable for i386
* NetBSD executable for SPARC
* VxWorks object file for MC68020
    * This one is dynamically loaded by executable code in a VxWorks monolith, and therefore depends on a number of external symbols that will not be found when it is loaded in Ghidra. The Loader generates log entries notifying the user of this.

[aout-test-files.zip](https://github.com/NationalSecurityAgency/ghidra/files/10774914/aout-test-files.zip)

author_association: CONTRIBUTOR
user: login: ryanmkurtz
created_at: 2023-02-19T21:11:09Z
body: Thanks!
author_association: COLLABORATOR
user: login: nightlark
created_at: 2023-03-29T23:11:54Z
body: > @ryanmkurtz - Certainly. The attached archive contains three different A.out examples:
> 
> * NetBSD executable for i386
> * NetBSD executable for SPARC
> * VxWorks object file for MC68020
>   
>   * This one is dynamically loaded by executable code in a VxWorks monolith, and therefore depends on a number of external symbols that will not be found when it is loaded in Ghidra. The Loader generates log entries notifying the user of this.
> 
> [aout-test-files.zip](https://github.com/NationalSecurityAgency/ghidra/files/10774914/aout-test-files.zip)

The C3413 (Green CPU) firmware from https://www.sage-rtu.com/downloads.html is another example of a file in the a.out format (little-endian 32-bit for x86/VxWorks) -- the loader in this PR does not currently recognize it as an a.out file.
author_association: NONE
user: login: colinbourassa
created_at: 2023-03-31T13:41:57Z
body: @nightlark - Thanks very much for the suggestions. That all looks very reasonable so I've incorporated it in a new commit for this PR. During testing of the C3413 Green CPU firmware, I also found that my loader was not generating a .bss block when the A.out indicates a nonzero .bss size. This has also been resolved.
author_association: CONTRIBUTOR
user: login: jobermayr
created_at: 2023-04-12T15:46:58Z
body: ```
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java
index 41ff1cc7a..c165ebe2e 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java
@@ -25,6 +25,7 @@ import ghidra.app.util.bin.ByteProvider;
 import ghidra.app.util.bin.format.aout.UnixAoutHeader.ExecutableType;
 import ghidra.app.util.importer.MessageLog;
 import ghidra.app.util.opinion.AbstractProgramWrapperLoader;
+import ghidra.app.util.opinion.Loader;
 import ghidra.app.util.opinion.LoadSpec;
 import ghidra.framework.store.LockException;
 import ghidra.program.flatapi.FlatProgramAPI;
```

Fixes:
```
> Task :createJavadocs
/tmp/ghidra/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java:50: warning: Tag @link: reference not found: Loader
 * A {@link Loader} for processing UNIX-style A.out executables
     ^
/tmp/ghidra/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java:50: warning: Tag @link: reference not found: Loader
 * A {@link Loader} for processing UNIX-style A.out executables
     ^
/tmp/ghidra/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java:50: warning: Tag @link: reference not found: Loader
 * A {@link Loader} for processing UNIX-style A.out executables
     ^
/tmp/ghidra/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java:50: warning: Tag @link: reference not found: Loader
 * A {@link Loader} for processing UNIX-style A.out executables
     ^
4 warnings
```

author_association: CONTRIBUTOR
user: login: nightlark
created_at: 2023-04-18T16:43:46Z
body: The .bss fix seems to be causing an issue with Ghidra identifying some symbols that are located it that section -- specifically, it seems now two overlapping .bss sections are being created (one read-only and the other marked as read+write), which I guess is confusing Ghidra so it doesn't show the symbol names in the decompiler/disassembly views whereas before the .bss change it was.
author_association: NONE
user: login: colinbourassa
created_at: 2023-04-18T23:33:46Z
body: > The .bss fix seems to be causing an issue with Ghidra identifying some symbols that are located it that section -- specifically, it seems now two overlapping .bss sections are being created (one read-only and the other marked as read+write), which I guess is confusing Ghidra so it doesn't show the symbol names in the decompiler/disassembly views whereas before the .bss change it was.

I think I see the problem. Previously, I was creating the .bss section simply based on the total size of all the symbols that were marked N_UNDF in the symbol table, as this is apparently an indication that they should be given a location in .bss at the next available address. I neglected to change that logic when I added the fixed-size .bss allocation, so that explains why multiple overlapping sections are being created.

It seems that we probably need a hybrid of these approaches: .bss may need to be created after summing the sizes of the assigned symbols in that section, with that size being *added* to the .bss size given in the header. The dynamically allocated symbols (N_UNDF) would then be placed toward the end of the section (after the fixed size given in the header) while the .bss symbols that are given explicit addresses can be labeled at those addresses.
author_association: CONTRIBUTOR
user: login: nightlark
created_at: 2023-04-19T22:37:47Z
body: Does the .bss segment size defined in the file include enough "empty" space that the N_UNDF files might fit in it? I also noticed that reserving the space in bss only happens if `n_value` is nonzero, but didn't see any checks for the size when creating the label for entries in possibleBssSymbols.

Given how N_UNDF seems to be used for linking to external symbols, it would be interesting if eventually other libraries could be loaded and those symbols could be linked correctly.
author_association: NONE
user: login: colinbourassa
created_at: 2023-04-20T11:41:28Z
body: With this latest commit, the intended concept for handling .bss is as follows:

* Read the fixed .bss size from the file's header, but don't create .bss yet.
* Walk through the symbol table, using hash tables to keep track of symbols that have either of these flags:
  * `N_BSS`: a symbol that *will* be in the .bss section
  * `N_UNDF`: a symbol that *may* be in the .bss section (only if it does not appear in the relocation table as a result of being supplied by an external library)
* Compute a total size for all the N_UNDF symbols and add this to the .bss size given in the header. This is the maximum size that .bss would need to be to accommodate everything.
* Create .bss as an uninitialized block with this newly computed size.
* Walk through the list of N_BSS symbols that we created earlier, and place the label for each one at the given address in .bss.
* Create a "next available address" variable that will keep track of the first unallocated location in .bss beyond the initial fixed-size block. (This variable will be initialized with the .bss size from the header.)
* While walking through the relocation tables, use .bss space for any `N_UNDF` symbol that was not found in any global or local symbol table (i.e. any symbol that was not provided by another binary that was previously loaded.) Increment the "next available address" as we go.

I believe this approach will allow any combination of .bss symbols that are given an explicit location, and .bss symbols that need to be dynamically allocated to the first available space in .bss.

The auto-formatter in Eclipse made a mess of some long lines in these modules. I need to find a Ghidra style guide and/or the right beautifier settings to fix this.
author_association: CONTRIBUTOR
user: login: nneonneo
created_at: 2023-04-27T19:10:28Z
body: Another `a.out` file, this time for Linux-i386. It's the Netscape 0.96 Beta, as found in PlaidCTF 2020 (https://github.com/bluepichu/ctf-challenges/blob/master/plaidctf-2020/back_to_the_future/problem/worker/files/netscape).

[netscape.zip](https://github.com/NationalSecurityAgency/ghidra/files/11347144/netscape.zip)
author_association: CONTRIBUTOR
user: login: nightlark
created_at: 2023-04-27T22:20:07Z
body: I built a copy based on the latest changes -- it looked like it may still be struggling to create a `.bss` section correctly; from the C3413 test file, there is an a.out file named `vxWorks` in the OperatingSystem subfolder. The a.out loader printed a message saying it failed to create the .bss section (I believe in one of the earlier iterations several symbols were created in the .bss section), and I think the application firmware a.out file .bss section symbols might not be getting created now.
author_association: NONE
user: login: nneonneo
created_at: 2023-04-28T01:48:38Z
body: For what it's worth: my `netscape` binary is not loaded correctly. According to the Linux a.out loader, the ZMAGIC format will not include the first page (0x400 bytes) in the loaded image, i.e. `this.txtOffset` should be 0x400, not 0. The incorrect offset breaks string references and such (they're off by 0x400).
author_association: CONTRIBUTOR
user: login: colinbourassa
created_at: 2023-04-29T23:56:04Z
body: > For what it's worth: my `netscape` binary is not loaded correctly. According to the Linux a.out loader, the ZMAGIC format will not include the first page (0x400 bytes) in the loaded image, i.e. `this.txtOffset` should be 0x400, not 0. The incorrect offset breaks string references and such (they're off by 0x400).

`UnixAoutHeader::determineTextOffset()` was returning 0 for the file offset to the .text section content -- it was incorrectly assuming that all ZMAGIC files started their .text content at file offset 0. I think this error accounts for the 0x400 byte displacement you were seeing.

I don't know of an "official" way to differentiate between the UNIX/Solaris ZMAGIC executables that start their .text at file offset 0, and the Linux ZMAGIC executables that start their .text at file offset 0x400. This `netscape` binary is the first example I've seen of the Linux style.

The `netscape` binary lists a .text size of 0x199000 and a .data size of 0x89000, which agree with the file size as long as we allow the header to be padded out to the 0x400 boundary (which certainly appears to be the case), plus a single 32-bit word containing the value 4 at the end of the file. This last word is the size of the symbol string table including the word itself, so a string data length of zero.

I'm working on a solution that involves checking the file size against the total size of all the sections given by the header. If it's determined to be the Linux style, I will start reading the .text content from the file at 0x400 rather than 0. I should have time within the next few days to push an update.
author_association: CONTRIBUTOR
user: login: colinbourassa
created_at: 2023-04-30T00:06:15Z
body: > I built a copy based on the latest changes -- it looked like it may still be struggling to create a `.bss` section correctly; from the C3413 test file, there is an a.out file named `vxWorks` in the OperatingSystem subfolder. The a.out loader printed a message saying it failed to create the .bss section (I believe in one of the earlier iterations several symbols were created in the .bss section), and I think the application firmware a.out file .bss section symbols might not be getting created now.

I tried loading the file you mentioned and I see the same problem. It's caused by the symbol table being read improperly; specifically, the Loader was defaulting unknown symbol types to `N_UNDF`, which are treated as candidates for inclusion in `.bss` when their `n_value` is nonzero. Defaulting to `N_UNDF` is a bug, because it causes debugger-specific symbols to be incorrectly counted among these `.bss` potentials. The `n_value` fields for the debugger symbols are typically *addresses*, but were being accumulated for the required size of `.bss` (which ended up being larger than Ghidra could allocate, causing the failure you described.)

I will make an update to skip the symbol table entries for which `n_type & 0xe0 != 0`, as this indicates a `stab` symbol (debugger only.)

Edit: furthermore, this file highlights the possibility of the object code being loaded at an address other than the conventional 0, PAGE_SIZE, or 0x400. With a VxWorks installation set up to use a flat memory model, the content of the a.out will certainly need to be placed higher in memory. This particular file appears to be loaded at 0x00108000. I will probably be adding an option to the Loader to change the base address manually, as that information is not included in the a.out file.
author_association: CONTRIBUTOR
user: login: nneonneo
created_at: 2023-05-03T15:56:45Z
body: Thinking about the design a bit: perhaps we could add options for specifying the load address, and a drop-down for a “variant” when ambiguous? For example, if ZMAGIC is detected, you could provide a drop-down to pick Linux ZMAGIC or UNIX ZMAGIC, etc., defaulting to whichever one makes the file size work out. I feel like, given the number of different a.out implementations in existence, a variant selector will probably ultimately be necessary. 

I would personally prefer making this configurable when trying to apply heuristics that may not be precise, e.g. because the file is padded or contains extra data etc.
author_association: CONTRIBUTOR
user: login: colinbourassa
created_at: 2023-05-03T19:14:58Z
body: I'm not sure we need an option for Linux vs UNIX ZMAGIC. The difference is not explicit in the header -- they're both just called ZMAGIC (0x10B) -- but the filesize will conclusively determine which of the two ZMAGIC variants is being used. Also, we do not have drop-down lists available for Loader options. This was proposed by issue #1157 but has not yet been implemented.

If, in the future, we come across some A.out variants that introduce ambiguity which *can't* be resolved by analyzing the file, then I absolutely agree that a user-settable option field should be added at that point.

It definitely makes sense to add an option for the base load address, though. Since the base address can't be determined by analyzing the file header/contents, it makes sense to give the user the option to provide it. In most cases, it can be left at the default of 0 (i.e. when analyzing binaries intended for OSes with protected mode addressing) but it will be useful on systems that use a global address space. I tested it out on this `vxworks` binary, and the symbol table entries match up nicely to functions in .text when loading to a base of `0x00108000`.
author_association: CONTRIBUTOR
user: login: colinbourassa
created_at: 2023-05-03T23:08:14Z
body: Latest commit automatically differentiates between Linux and UNIX style ZMAGIC, and also adds a "base address" option for loading.
author_association: CONTRIBUTOR
user: login: nneonneo
created_at: 2023-05-04T02:50:19Z
body: @colinbourassa works for the netscape binary!
author_association: CONTRIBUTOR
user: login: boricj
created_at: 2023-09-07T20:49:47Z
body: Another set of artifact this can be tested against would be Debian Buzz (1.1)'s a.out toolchain files. These can be found at https://archive.debian.org/debian/dists/buzz/main/binary-i386/devel/ and Debian packages can be safely extracted with `dpkg --extract file.deb dir/` (I wouldn't recommend trying to install these fossils from the 90's onto a recent Debian installation).

Note that trying to import `/usr/i486-linuxaout/lib/libc.a` from libc4-dev-4.6.27-15.deb for example won't work without a bunch of fixes I've submitted in this PR: https://github.com/colinbourassa/ghidra/pull/1
author_association: NONE
user: login: boricj
created_at: 2023-10-05T15:59:13Z
body: I've made further improvements to the a.out loader in this PR: https://github.com/colinbourassa/ghidra/pull/2

I've used that version to successfully load an a.out object file, specifically an amalgamation of Slackware 2.3's `libc.a` which I've then used as a source for Ghidra's Version Tracking tool while reverse-engineering old a.out executables. Details (and the files) can be found in this forum thread: https://forums.atariage.com/topic/354341-porting-the-original-atari-jaguar-sdk-to-elf/
author_association: NONE
user: login: calmsacibis995
created_at: 2024-12-12T13:25:43Z
body: This improved a.out loader works on a custom build that I made.
author_association: NONE
user: login: colinbourassa
created_at: 2025-02-24T20:23:43Z
body: I've been successfully using this A.out loader for a while now. @ryanmkurtz - could this be considered for merging back to master?
author_association: CONTRIBUTOR
user: login: ryanmkurtz
created_at: 2025-02-25T12:14:03Z
body: I'll discuss it with the team this week.
author_association: COLLABORATOR
user: login: ryanmkurtz
created_at: 2025-03-18T15:15:35Z
body: @colinbourassa Can you rebase and force push this?  I'm ready to start taking a look at integrating it.  Thanks!
author_association: COLLABORATOR
user: login: ryanmkurtz
created_at: 2025-03-18T17:56:06Z
body: Actually a squash and a rebase would be ideal, so it removes the ticket references in the commit messages.  I can also do this stuff too if you'd prefer, and you don't mind me rewriting your commit (you'd still be author).
author_association: COLLABORATOR
user: login: colinbourassa
created_at: 2025-03-19T11:27:44Z
body: Still need to sync my fork and rebase. I should be able to get this done later today.
author_association: CONTRIBUTOR
user: login: colinbourassa
created_at: 2025-03-19T11:54:17Z
body: @ryanmkurtz - sync'd, squashed, and rebased
author_association: CONTRIBUTOR
user: login: ryanmkurtz
created_at: 2025-03-19T12:47:20Z
body: Thanks, taking a look today.  I think the main thing i may want to add is using the `.opinion` files to help in the selection of processing/compiler spec, if possible.
author_association: COLLABORATOR