summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorOxore <oxore@protonmail.com>2024-02-05 01:20:51 +0300
committerOxore <oxore@protonmail.com>2024-02-05 01:21:59 +0300
commit21a9aa92a7cf8767a0fcb33858546dea744c4071 (patch)
treea5313fdaff5c0ed2d3db416d027e6df21d3cd7ff /src/main.cpp
parent9fd2eba95beb6c9ce6fb26e1442aa2f68aac9b1f (diff)
Organize source code and tests
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp836
1 files changed, 836 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..a6f73b3
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,836 @@
+/* SPDX-License-Identifier: Unlicense
+ */
+
+#include "elf_image.h"
+#include "data_buffer.h"
+#include "disasm.h"
+#include "common.h"
+
+#define OPTPARSE_IMPLEMENTATION
+#define OPTPARSE_API static
+#include "optparse/optparse.h"
+
+#include <cassert>
+#include <cinttypes>
+#include <cstdio>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
+#include <climits>
+
+enum class DisasmMapType {
+ kTraced,
+ kRaw,
+};
+
+class DisasmMap {
+ const DisasmMapType _type;
+ DisasmNode *_map[kDisasmMapSizeElements]{};
+ constexpr DisasmNode *findNodeByAddress(uint32_t address) const;
+ DisasmNode &insertNode(uint32_t address, NodeType);
+ DisasmNode &insertReferencedBy(
+ const uint32_t by_addr,
+ const uint32_t ref_addr,
+ const NodeType type,
+ const ReferenceType ref_type);
+ constexpr bool canBeAllocated(const DisasmNode& node) const;
+public:
+ constexpr const DisasmNode *FindNodeByAddress(uint32_t address) const
+ {
+ return findNodeByAddress(address);
+ };
+ void InsertNode(uint32_t address, NodeType type)
+ {
+ assert(_type == DisasmMapType::kTraced);
+ insertNode(address, type);
+ }
+ void Disasm(const DataView &code, const Settings &, size_t from=0, bool nested=false);
+ DisasmMap(DisasmMapType type): _type(type) {}
+ ~DisasmMap();
+};
+
+constexpr DisasmNode *DisasmMap::findNodeByAddress(uint32_t address) const
+{
+ if (address < kRomSizeBytes)
+ return _map[address / kInstructionSizeStepBytes];
+ return nullptr;
+}
+
+static constexpr uint32_t AlignInstructionAddress(const uint32_t address)
+{
+ return address & ~1UL;
+}
+
+DisasmNode &DisasmMap::insertNode(const uint32_t address, const NodeType type)
+{
+ auto *node = findNodeByAddress(address);
+ if (node) {
+ // Instruction nodes take precedence over data nodes. If a node that
+ // was previously accessed only as data now turns out to be an
+ // instruction, then it must become an instruction node.
+ if (IsInstruction(type) && !IsInstruction(node->type)) {
+ *const_cast<NodeType*>(&node->type) = type;
+ // Make sure it is OpCode::kNone so it will be properly disassembled
+ node->op = Op{};
+ }
+ return *node;
+ }
+ node = new DisasmNode(DisasmNode{type, AlignInstructionAddress(address)});
+ assert(node);
+ _map[address / kInstructionSizeStepBytes] = node;
+ return *node;
+}
+
+DisasmNode &DisasmMap::insertReferencedBy(
+ const uint32_t by_addr,
+ const uint32_t ref_addr,
+ const NodeType type,
+ const ReferenceType ref_type)
+{
+ auto &ref_node = insertNode(ref_addr, type);
+ ref_node.AddReferencedBy(by_addr, ref_type);
+ return ref_node;
+}
+
+constexpr bool DisasmMap::canBeAllocated(const DisasmNode& node) const
+{
+ const auto size = node.size / kInstructionSizeStepBytes;
+ const auto *const node_real = findNodeByAddress(node.address);
+ for (size_t i = 1; i < size; i++) {
+ const auto *const ptr = _map[node.address / kInstructionSizeStepBytes + i];
+ if (ptr != nullptr && ptr != node_real) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static constexpr ReferenceType ReferenceTypeFromRefKindMask1(const RefKindMask ref_kinds)
+{
+ return (ref_kinds & kRefCallMask)
+ ? ReferenceType::kCall
+ : (ref_kinds & kRef1ReadMask)
+ ? ReferenceType::kRead
+ : (ref_kinds & kRef1WriteMask)
+ ? ReferenceType::kWrite
+ : ReferenceType::kBranch;
+}
+
+static constexpr ReferenceType ReferenceTypeFromRefKindMask2(const RefKindMask ref_kinds)
+{
+ return (ref_kinds & kRefCallMask)
+ ? ReferenceType::kCall
+ : (ref_kinds & kRef2ReadMask)
+ ? ReferenceType::kRead
+ : (ref_kinds & kRef2WriteMask)
+ ? ReferenceType::kWrite
+ : ReferenceType::kBranch;
+}
+
+static constexpr bool IsNextLikelyAnInstruction(const Op &op)
+{
+ return (op.opcode != OpCode::kNone &&
+ op.opcode != OpCode::kRaw &&
+ !IsBRA(op) &&
+ op.opcode != OpCode::kJMP &&
+ op.opcode != OpCode::kRTS &&
+ op.opcode != OpCode::kRTE &&
+ op.opcode != OpCode::kSTOP);
+}
+
+void DisasmMap::Disasm(
+ const DataView &code, const Settings &s, size_t at, bool nested)
+{
+ // Some of logic of this function is covered by integration tests in
+ // `test_walk_and_follow_jumps.bash`.
+ bool inside_code_span = nested;
+ while (at < Min(kRomSizeBytes, code.size)) {
+ DisasmNode *node;
+ if (_type == DisasmMapType::kTraced) {
+ node = _map[at / kInstructionSizeStepBytes];
+ if (!node) {
+ if (inside_code_span) {
+ node = &insertNode(at, NodeType::kTracedInstruction);
+ } else {
+ at += kInstructionSizeStepBytes;
+ continue;
+ }
+ }
+ } else {
+ node = &insertNode(at, NodeType::kTracedInstruction);
+ }
+ if (node->op.opcode == OpCode::kNone || inside_code_span) {
+ const auto size = node->Disasm(code);
+ assert(size >= kInstructionSizeStepBytes);
+ if (canBeAllocated(*node)) {
+ // Spread across the size
+ for (size_t o = kInstructionSizeStepBytes; o < size; o++) {
+ _map[(node->address + o) / kInstructionSizeStepBytes] = node;
+ }
+ } else {
+ node->DisasmAsRaw(code);
+ }
+ }
+ inside_code_span = s.walk && IsNextLikelyAnInstruction(node->op);
+ if (nested && !inside_code_span) {
+ return;
+ }
+ at += node->size;
+ // NOTE: There is not much information about a reference passed further,
+ // so just don't add a reference of immediate if s.imm_labels is false
+ // enabled.
+ const bool has_ref1 = (node->ref_kinds & kRef1ImmMask)
+ ? s.imm_labels
+ : (node->ref_kinds & kRef1Mask);
+ const bool has_code_ref1 = node->ref1_addr < code.size && has_ref1;
+ if (has_code_ref1) {
+ const NodeType type = (node->ref_kinds & (kRef1ReadMask | kRef1WriteMask))
+ ? NodeType::kData : NodeType::kRefInstruction;
+ const auto ref_type = ReferenceTypeFromRefKindMask1(node->ref_kinds);
+ auto &ref_node = insertReferencedBy(
+ node->address, node->ref1_addr, type, ref_type);
+ if (ref_node.op.opcode == OpCode::kNone) {
+ if (s.follow_jumps) {
+ Disasm(code, s, ref_node.address, true);
+ } else {
+ ref_node.DisasmAsRaw(code);
+ }
+ }
+ }
+ const bool has_ref2 = (node->ref_kinds & kRef2Mask);
+ const bool has_code_ref2 = (has_ref2 && node->ref2_addr < code.size);
+ if (has_code_ref2) {
+ const NodeType type = (node->ref_kinds & (kRef2ReadMask | kRef2WriteMask))
+ ? NodeType::kData : NodeType::kRefInstruction;
+ const auto ref_type = ReferenceTypeFromRefKindMask2(node->ref_kinds);
+ auto &ref_node = insertReferencedBy(
+ node->address, node->ref2_addr, type, ref_type);
+ if (ref_node.op.opcode == OpCode::kNone) {
+ if (s.follow_jumps) {
+ Disasm(code, s, ref_node.address, true);
+ } else {
+ ref_node.DisasmAsRaw(code);
+ }
+ }
+ }
+ }
+}
+
+DisasmMap::~DisasmMap()
+{
+ for (size_t i = 0; i < kDisasmMapSizeElements; i++) {
+ auto *const node = _map[i];
+ if (!node) {
+ continue;
+ }
+ const auto size = node->size / kInstructionSizeStepBytes;
+ for (size_t o = 0; o < size; o++) {
+ assert(_map[i + o] == node);
+ _map[i + o] = nullptr;
+ }
+ delete node;
+ i += size - 1;
+ }
+}
+
+static size_t RenderRawDataComment(
+ char *out, size_t out_sz, uint32_t address, size_t instr_sz, const DataView &code)
+{
+ size_t overall_sz{};
+ for (size_t i = 0; i < instr_sz; i += kInstructionSizeStepBytes)
+ {
+ overall_sz += Min(
+ out_sz - overall_sz,
+ snprintf(
+ out + overall_sz,
+ out_sz - overall_sz,
+ " %04x",
+ GetU16BE(code.buffer + address + i)));
+ }
+ overall_sz += Min(
+ out_sz - overall_sz,
+ snprintf(out + overall_sz, out_sz - overall_sz, " @%08x", address));
+ return overall_sz;
+}
+
+static constexpr const char *ReferenceTypeToString(ReferenceType type)
+{
+ switch (type) {
+ case ReferenceType::kUnknown: return "UNKNOWN";
+ case ReferenceType::kCall: return "CALL";
+ case ReferenceType::kBranch: return "BRANCH";
+ case ReferenceType::kRead: return "READ";
+ case ReferenceType::kWrite: return "WRITE";
+ }
+ return "UNKN";
+}
+
+static constexpr bool ShouldPrintAsRaw(const Op& op)
+{
+ if (op.arg1.type == ArgType::kImmediate) {
+ if (op.opcode == OpCode::kADD || op.opcode == OpCode::kSUB ||
+ op.opcode == OpCode::kAND || op.opcode == OpCode::kOR ||
+ op.opcode == OpCode::kEOR || op.opcode == OpCode::kCMP)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+static constexpr bool HasCallReference(const DisasmNode &node)
+{
+ for (const ReferenceNode *ref{node.ref_by}; ref; ref = ref->next) {
+ for (size_t i = 0; i < ref->refs_count; i++) {
+ if (ref->refs[i].type == ReferenceType::kCall) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static constexpr size_t GetNodeSizeByAddress(const DisasmMap &disasm_map, const uint32_t address)
+{
+ const auto *node = disasm_map.FindNodeByAddress(address);
+ if (node == nullptr) {
+ return kInstructionSizeStepBytes;
+ }
+ return node->size;
+}
+
+static constexpr bool IsLocalLocation(const DisasmMap &disasm_map, const DisasmNode &node)
+{
+ for (const ReferenceNode *ref{node.ref_by}; ref; ref = ref->next) {
+ for (size_t i = 0; i < ref->refs_count; i++) {
+ const ReferenceRecord &ref_rec = ref->refs[i];
+ if (ref_rec.type == ReferenceType::kCall) {
+ // Locals are definitely not made for calls
+ return false;
+ }
+ const bool forward = ref_rec.address < node.address;
+ const size_t min_addr = forward ? ref_rec.address : node.address;
+ const size_t start = min_addr + GetNodeSizeByAddress(disasm_map, min_addr);
+ const size_t max_addr = forward ? node.address : ref_rec.address;
+ const size_t end = max_addr + (forward ? 0 : GetNodeSizeByAddress(disasm_map, min_addr));
+ for (size_t addr = start; addr < end;) {
+ const auto *intermediate_node = disasm_map.FindNodeByAddress(addr);
+ if (intermediate_node) {
+ if (intermediate_node->ref_by) {
+ // Another labeled node detected on the jump path, hence
+ // current node's location cannot be considered local
+ return false;
+ }
+ addr += intermediate_node->size;
+ } else {
+ addr += kInstructionSizeStepBytes;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+static constexpr const char *StringWihoutFristNChars(const char *str, const size_t n)
+{
+ for (size_t i = 0, tab = 0; i < n && *str; i++, str++) {
+ if (*str == '\t') {
+ tab++;
+ if (tab == 7) {
+ tab = 0;
+ str++;
+ }
+ } else {
+ str++;
+ }
+ }
+ return str;
+}
+
+static void RenderNodeDisassembly(
+ FILE *const output,
+ const DisasmMap &disasm_map,
+ const DataView &code,
+ const Settings &s,
+ const DisasmNode &node)
+{
+ if (node.ref_by) {
+ const bool is_local = IsLocalLocation(disasm_map, node);
+ if (s.labels && !(s.short_ref_local_labels && is_local)) {
+ const bool export_this_function = s.export_functions && HasCallReference(node);
+ const bool export_this_label = s.export_all_labels ||
+ (s.export_labels && node.ref_by && (node.ref_by->refs_count > 1)) ||
+ export_this_function;
+ if (export_this_label) {
+ fprintf(output, "\n%s.globl\tL%08x\n", s.indent, node.address);
+ if (export_this_function) {
+ fprintf(output, "%s.type\tL%08x, @function\n", s.indent, node.address);
+ }
+ }
+ }
+ if (s.xrefs_from && !(s.short_ref_local_labels && is_local)) {
+ fprintf(output, "| XREFS:\n");
+ for (const ReferenceNode *ref{node.ref_by}; ref; ref = ref->next) {
+ if (ref->refs_count == 0) {
+ continue;
+ }
+ fprintf(output, "|");
+ for (size_t i = 0; i < ref->refs_count; i++) {
+ const ReferenceRecord r = ref->refs[i];
+ fprintf(output, " %s @%08x", ReferenceTypeToString(r.type), r.address);
+ }
+ fprintf(output, "\n");
+ }
+ }
+ if (s.labels) {
+ if (s.short_ref_local_labels && is_local) {
+ fprintf(output, "1:%s", StringWihoutFristNChars(s.indent, (sizeof "1:") - 1));
+ } else {
+ fprintf(output, "L%08x:\n", node.address);
+ }
+ }
+ }
+ assert(node.op.opcode != OpCode::kNone);
+ if (ShouldPrintAsRaw(node.op)) {
+ auto raw = Op::Raw(GetU16BE(code.buffer + node.address));
+ raw.FPrint(output, s.indent, s.imm_hex);
+ uint32_t i = kInstructionSizeStepBytes;
+ for (; i < node.size; i += kInstructionSizeStepBytes) {
+ char arg_str[kArgsBufferSize]{};
+ const auto arg = Arg::Raw(GetU16BE(code.buffer + node.address + i));
+ arg.SNPrint(arg_str, kArgsBufferSize);
+ fprintf(output, ", %s", arg_str);
+ }
+ } else {
+ const bool with_ref = node.ref_kinds && s.labels && (s.abs_labels || s.rel_labels);
+ const auto *ref1 = (node.ref_kinds & kRef1Mask)
+ ? disasm_map.FindNodeByAddress(node.ref1_addr) : nullptr;
+ const auto *ref2 = (node.ref_kinds & kRef2Mask)
+ ? disasm_map.FindNodeByAddress(node.ref2_addr) : nullptr;
+ const uint32_t ref1_addr = (with_ref && ref1) ? ref1->address : 0;
+ const uint32_t ref2_addr = (with_ref && ref2) ? ref2->address : 0;
+ if (with_ref && (ref1 || ref2)) {
+ const RefKindMask ref_kinds =
+ (s.abs_labels
+ ? ((ref1 ? (node.ref_kinds & kRef1AbsMask) : 0) |
+ (ref2 ? (node.ref_kinds & kRef2AbsMask) : 0))
+ : 0) |
+ (s.rel_labels
+ ? ((ref1 ? (node.ref_kinds & kRef1RelMask) : 0) |
+ (ref2 ? (node.ref_kinds & kRef2RelMask) : 0))
+ : 0) |
+ ((s.imm_labels && ref1) ? (node.ref_kinds & kRef1ImmMask) : 0) |
+ (node.ref_kinds & (kRefDataMask | kRefPcRelFix2Bytes));
+ const bool ref1_is_local = !ref1 || IsLocalLocation(disasm_map, *ref1);
+ char ref1_label[32]{};
+ if (ref1) {
+ if (s.short_ref_local_labels && ref1_is_local) {
+ const char dir = ref1_addr <= node.address ? 'b' : 'f';
+ snprintf(ref1_label, (sizeof ref1_label), "1%c", dir);
+ } else {
+ snprintf(ref1_label, (sizeof ref1_label), "L%08x", ref1_addr);
+ }
+ }
+ const bool ref2_is_local = !ref2 || IsLocalLocation(disasm_map, *ref2);
+ char ref2_label[32]{};
+ if (ref2) {
+ if (s.short_ref_local_labels && ref2_is_local) {
+ const char dir = ref2_addr <= node.address ? 'b' : 'f';
+ snprintf(ref2_label, (sizeof ref2_label), "1%c", dir);
+ } else {
+ snprintf(ref2_label, (sizeof ref2_label), "L%08x", ref2_addr);
+ }
+ }
+ node.op.FPrint(
+ output,
+ s.indent,
+ s.imm_hex,
+ ref_kinds,
+ ref1_label,
+ ref2_label,
+ node.address,
+ ref1_addr,
+ ref2_addr);
+ const bool ref1_from_imm_ok = ((node.ref_kinds & kRef1ImmMask) ? s.imm_labels : true);
+ if (s.xrefs_to && !(s.short_ref_local_labels && ref1_is_local) && ref1_from_imm_ok)
+ {
+ fprintf(output, " | L%08x", ref1_addr);
+ }
+ if (s.xrefs_to && !(s.short_ref_local_labels && ref2_is_local)) {
+ fprintf(output, " | L%08x", ref2_addr);
+ }
+ } else {
+ node.op.FPrint(output, s.indent, s.imm_hex);
+ }
+ }
+ if (s.raw_data_comment) {
+ char raw_data_comment[100]{};
+ RenderRawDataComment(
+ raw_data_comment,
+ (sizeof raw_data_comment) - 1,
+ node.address,
+ node.size, code);
+ fprintf(output, " |%s", raw_data_comment);
+ }
+ fprintf(output, "\n");
+}
+
+static void RenderDisassembly(
+ FILE *const output, const DisasmMap &disasm_map, const DataView &code, const Settings &s)
+{
+ for (size_t i = 0; i < code.size;) {
+ const DisasmNode *node = disasm_map.FindNodeByAddress(i);
+ if (node) {
+ RenderNodeDisassembly(output, disasm_map, code, s, *node);
+ i += node->size;
+ } else {
+ auto raw = Op::Raw(GetU16BE(code.buffer + i));
+ raw.FPrint(output, s.indent, s.imm_hex);
+ fprintf(output, "\n");
+ i += kInstructionSizeStepBytes;
+ }
+ }
+}
+
+static void ParseTraceData(DisasmMap &disasm_map, const DataView &trace_data)
+{
+ // FIXME make a full blown parser with various radixes support and different
+ // trace types support
+ bool parse = true;
+ for (size_t i = 0; i < trace_data.size; i++) {
+ if (trace_data.buffer[i] == '\n' || trace_data.buffer[i] == '\r') {
+ parse = true;
+ } else if (parse) {
+ errno = 0;
+ const char *startptr = reinterpret_cast<const char *>(trace_data.buffer + i);
+ char *endptr = nullptr;
+ const long address = strtol(startptr, &endptr, 10);
+ if ((address == LONG_MAX || address == LONG_MIN) && errno == ERANGE) {
+ // Parsing error, just skip
+ } else if (startptr == endptr) {
+ // Parsing error, just skip
+ } else if (address % 2) {
+ fprintf(stderr, "Error: Uneven PC values are not supported (got PC=0x%08lx), exiting\n", address);
+ exit(1);
+ } else if (static_cast<unsigned long>(address) > kRomSizeBytes) {
+ fprintf(stderr, "Error: PC values > 4MiB are not supported (got PC=0x%08lx), exiting\n", address);
+ exit(1);
+ } else {
+ // Valid value
+ disasm_map.InsertNode(address, NodeType::kTracedInstruction);
+ }
+ if (startptr != endptr) {
+ i += endptr - startptr - 1;
+ }
+ parse = false;
+ }
+ }
+}
+
+static size_t ReadFromStream(DataBuffer &db, FILE *stream)
+{
+ assert(db.buffer && db.buffer_size >= db.kInitialSize);
+ while (1) {
+ const size_t read_size = db.buffer_size - db.occupied_size;
+ const size_t fread_ret = fread(
+ db.buffer + db.occupied_size, sizeof(*db.buffer), read_size, stream);
+ db.occupied_size += fread_ret;
+ if (fread_ret >= db.buffer_size) {
+ assert(fread_ret == db.buffer_size);
+ db.Expand(db.buffer_size * 2);
+ } else {
+ const int err = errno;
+ if (feof(stream)) {
+ break;
+ } else if (ferror(stream)) {
+ fprintf(stderr, "ReadFromStream: fread(%zu): Error (%d): \"%s\"\n", read_size, err, strerror(err));
+ return EXIT_FAILURE;
+ } else if (db.buffer_size == db.occupied_size) {
+ db.Expand(db.buffer_size * 2);
+ } else {
+ assert(false);
+ }
+ }
+ }
+ return db.occupied_size;
+}
+
+static DisasmMap *NewDisasmMap(FILE *trace_stream)
+{
+ if (trace_stream == nullptr) {
+ DisasmMap *disasm_map = new DisasmMap{DisasmMapType::kRaw};
+ assert(disasm_map);
+ return disasm_map;
+ }
+ // Read trace file into buffer
+ DataBuffer trace_data{};
+ const size_t trace_size = ReadFromStream(trace_data, trace_stream);
+ if (trace_size == 0) {
+ fprintf(stderr, "ReadFromStream(trace_data, trace_stream): Error: No data has been read\n");
+ return nullptr;
+ }
+ // Parse trace file into map
+ DisasmMap *disasm_map = new DisasmMap{DisasmMapType::kTraced};
+ assert(disasm_map);
+ ParseTraceData(*disasm_map, trace_data.View());
+ return disasm_map;
+}
+
+static int M68kDisasm(
+ FILE *input_stream, FILE *output_stream, FILE *trace_stream, const Settings &s)
+{
+ // Read input file into buffer
+ DataBuffer input{};
+ const size_t input_size = ReadFromStream(input, input_stream);
+ if (input_size == 0) {
+ fprintf(stderr, "ReadFromStream(input, input_stream): Error: No data has been read\n");
+ return EXIT_FAILURE;
+ }
+ const ELF::Image elf(static_cast<DataBuffer&&>(input));
+ if (s.bfd == BFDTarget::kELF && !elf.IsValid()) {
+ fprintf(stderr, "Error: ELF image is not valid: %s\n", elf.Error());
+ return EXIT_FAILURE;
+ }
+ const bool from_elf = s.bfd == BFDTarget::kELF || (s.bfd == BFDTarget::kAuto && elf.IsValid());
+ const DataView code(from_elf ? elf.ProgramView() : elf.Data().View());
+ assert(code.buffer != nullptr);
+ assert(code.size != 0);
+ // It is not worth it to check this somewhere while disassembling or
+ // emitting. Odd size is just not supported.
+ if (code.size % 2) {
+ fprintf(stderr, "M68kDisasm: Error: code blob must be of even size\n");
+ return EXIT_FAILURE;
+ }
+ auto *disasm_map = NewDisasmMap(trace_stream);
+ if (disasm_map == nullptr) {
+ return EXIT_FAILURE;
+ }
+ // Disasm into output map
+ disasm_map->Disasm(code, s);
+ // Print output into output_stream
+ RenderDisassembly(output_stream, *disasm_map, code, s);
+ delete disasm_map;
+ return EXIT_SUCCESS;
+}
+
+static bool FeatureStringHasPrefixNo(const char *feature)
+{
+ assert(feature);
+ // There is also implicit, embedded and free check for null terminator
+ if (feature[0] == 'n' && feature[1] == 'o' && feature[2] == '-') {
+ return true;
+ }
+ return false;
+}
+
+static bool ApplyFeature(Settings& s, const char *feature_arg)
+{
+ struct {
+ bool Settings::* setting;
+ const char* feature_name;
+ } const features[]{
+ { &Settings::raw_data_comment, "rdc" },
+ { &Settings::labels, "labels" },
+ { &Settings::rel_labels, "rel-labels" },
+ { &Settings::abs_labels, "abs-labels" },
+ { &Settings::imm_labels, "imm-labels" },
+ { &Settings::short_ref_local_labels, "short-ref-local-labels" },
+ { &Settings::export_labels, "export-labels" },
+ { &Settings::export_all_labels, "export-all-labels" },
+ { &Settings::export_functions, "export-functions" },
+ { &Settings::xrefs_from, "xrefs-from" },
+ { &Settings::xrefs_to, "xrefs-to" },
+ { &Settings::imm_hex, "imm-hex" },
+ { &Settings::follow_jumps, "follow-jumps" },
+ { &Settings::walk, "walk" },
+ };
+ constexpr size_t sizeof_no_prefix = (sizeof "no-") - 1;
+ const bool disable = FeatureStringHasPrefixNo(feature_arg);
+ const char *const feature = feature_arg + (disable ? sizeof_no_prefix : 0);
+ for (size_t i = 0; i < (sizeof features) / (sizeof *features); i++) {
+ if (0 == strcmp(feature, features[i].feature_name)) {
+ s.*(features[i].setting) = !disable;
+ return true;
+ }
+ }
+ return false;
+}
+
+static void PrintUsage(FILE *s, const char *argv0)
+{
+ // Please, keep all lines in 80 columns range when printed.
+ fprintf(s,
+ "Usage: %s [options] <input-file-name>\n"
+ "Options:\n"
+ " -h, --help, Show this message.\n"
+ " -o, --output, Where to write disassembly to (stdout if not set)\n"
+ " -t, --pc-trace, File containing PC trace\n"
+ " --indent, Specify instruction indentation, e.g. \"\t\",\n"
+ " Single tab is used by default.\n"
+ " -f, --feature=[no-]<feature>\n"
+ " Enable or disable (with \"no-\" prefix) a feature.\n"
+ " Available features described below under the\n"
+ " \"Feature flags\" section.\n"
+ " -b, --bfd-target=bfdname\n"
+ " Specify target object format as `bfdname`. Will attempt\n"
+ " to detect automatically if not set. Only `auto,\n"
+ " `binary` and `elf` are currently supported.\n"
+ " <input_file_name> Binary or elf file with the machine code to disassemble\n"
+ "Feature flags:\n"
+ " rdc Print raw data comment.\n"
+ " labels Print labels above all places that have jumps from\n"
+ " somewhere.\n"
+ " rel-labels Use label instead of number on relative branch or call.\n"
+ " abs-labels Use label instead of number on absolute branch or call.\n"
+ " imm-labels Use label instead of number when immediate value moved\n"
+ " to address register.\n"
+ " short-ref-local-labels\n"
+ " Use local labels (numbers) for short jumps or loops.\n"
+ " Jump is considered short when it does not cross other\n"
+ " labels and has no calls.\n"
+ " export-labels Add `.globl` preamble to labels referenced two or more\n"
+ " times.\n"
+ " export-all-labels Add `.globl` preamble to all labels.\n"
+ " export-functions Add `.globl` and `.type @funciton` preamble to a label\n"
+ " referenced as a call.\n"
+ " xrefs-from Print xrefs comments above all places that have xrefs.\n"
+ " xrefs-to Print xrefs comments after all branch instructions.\n"
+ " imm-hex Print all immediate values as hexadecimal numbers.\n"
+ " follow-jumps Follow jumps to statically known locations.\n"
+ " walk Try best to detect further instructions following known\n"
+ " traced locations without overcommitting.\n"
+ , argv0);
+}
+
+int main(int, char* argv[])
+{
+ struct optparse_long longopts[] = {
+ {"help", 'h', OPTPARSE_NONE},
+ {"output", 'o', OPTPARSE_REQUIRED},
+ {"pc-trace", 't', OPTPARSE_REQUIRED},
+ {"feature", 'f', OPTPARSE_REQUIRED},
+ {"bfd-target", 'b', OPTPARSE_REQUIRED},
+ {"indent", 80, OPTPARSE_REQUIRED},
+ {},
+ };
+ const char *trace_file_name = nullptr;
+ const char *output_file_name = nullptr;
+ const char *input_file_name = nullptr;
+ Settings s{};
+ struct optparse options;
+ optparse_init(&options, argv);
+ // Parse opts
+ int option;
+ while ((option = optparse_long(&options, longopts, NULL)) != -1) {
+ switch (option) {
+ case 'h':
+ PrintUsage(stdout, argv[0]);
+ return EXIT_SUCCESS;
+ break;
+ case 'o':
+ output_file_name = options.optarg;
+ break;
+ case 't':
+ trace_file_name = options.optarg;
+ break;
+ case 'f':
+ if (!ApplyFeature(s, options.optarg)) {
+ fprintf(stderr, "main: Error: Unknown feature \"%s\", exiting\n", options.optarg);
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'b':
+ {
+ const auto *bfd_str = options.optarg;
+ if (0 == strcmp(bfd_str, "auto")) {
+ s.bfd = BFDTarget::kAuto;
+ } else if (0 == strcmp(bfd_str, "binary")) {
+ s.bfd = BFDTarget::kBinary;
+ } else if (0 == strcmp(bfd_str, "elf")) {
+ s.bfd = BFDTarget::kELF;
+ } else {
+ fprintf(
+ stderr,
+ "Unknown BFD target specified: \"%s\". "
+ "Refer to usage below to find correct BFD values.\n",
+ bfd_str);
+ PrintUsage(stderr, argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+ break;
+ case 80:
+ s.indent = options.optarg;
+ break;
+ case '?':
+ fprintf(stderr, "main: optparse_long: Error: \"%s\"\n", options.errmsg);
+ return EXIT_FAILURE;
+ }
+ }
+ // Parse input file name
+ char *arg;
+ while ((arg = optparse_arg(&options))) {
+ if (input_file_name == nullptr) {
+ input_file_name = arg;
+ } else {
+ fprintf(stderr, "error: too many free arguments provided\n");
+ return EXIT_FAILURE;
+ }
+ }
+ // Open the files
+ FILE *input_stream = nullptr;
+ FILE *output_stream = stdout;
+ FILE *trace_stream = nullptr;
+ if (input_file_name) {
+ if (0 == strcmp(input_file_name, "-")) {
+ input_stream = stdin;
+ } else {
+ input_stream = fopen(input_file_name, "r");
+ }
+ if (input_stream == nullptr) {
+ const int err = errno;
+ fprintf(stderr, "main: fopen(\"%s\", \"r\"): Error (%d): \"%s\"\n", input_file_name, err, strerror(err));
+ return EXIT_FAILURE;
+ }
+ } else {
+ fprintf(stderr, "main: Error: no input file name specified, see usage below.\n");
+ PrintUsage(stderr, argv[0]);
+ return EXIT_FAILURE;
+ }
+ if (output_file_name) {
+ output_stream = fopen(output_file_name, "w");
+ if (output_stream == nullptr) {
+ const int err = errno;
+ fprintf(stderr, "main: fopen(\"%s\", \"w\"): Error (%d): \"%s\"\n", output_file_name, err, strerror(err));
+ fclose(input_stream);
+ return EXIT_FAILURE;
+ }
+ }
+ if (trace_file_name) {
+ if (0 == strcmp(trace_file_name, "-")) {
+ if (input_stream == stdin) {
+ fprintf(stderr, "error: trace stream and input stream cannot be both stdin\n");
+ return EXIT_FAILURE;
+ }
+ trace_stream = stdin;
+ } else {
+ trace_stream = fopen(trace_file_name, "r");
+ }
+ if (trace_stream == nullptr) {
+ const int err = errno;
+ fprintf(stderr, "main: fopen(\"%s\", \"r\"): Error (%d): \"%s\"\n", trace_file_name, err, strerror(err));
+ fclose(input_stream);
+ fclose(output_stream);
+ return EXIT_FAILURE;
+ }
+ }
+ // Run the program
+ const int ret = M68kDisasm(input_stream, output_stream, trace_stream, s);
+ if (trace_stream != nullptr) {
+ fclose(trace_stream);
+ }
+ fclose(output_stream);
+ fclose(input_stream);
+ return ret;
+}