diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | Readme.md | 34 | ||||
-rw-r--r-- | src/common.h | 7 | ||||
-rw-r--r-- | src/disasm.cpp | 7 | ||||
-rw-r--r-- | src/disasm.h | 2 | ||||
-rw-r--r-- | src/gnu.cpp | 29 | ||||
-rw-r--r-- | src/gnu.h | 13 | ||||
-rw-r--r-- | src/m68k.cpp | 565 | ||||
-rw-r--r-- | src/m68k.h | 62 | ||||
-rw-r--r-- | src/main.cpp | 128 | ||||
-rw-r--r-- | tests/test.bash | 25 | ||||
-rw-r--r-- | tests/test_random.bash | 2 | ||||
-rw-r--r-- | tests/test_sierra.bash | 777 |
13 files changed, 1392 insertions, 260 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index fb6d9b1..640937b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,6 @@ add_executable(m68k-disasm src/main.cpp src/data_buffer.cpp src/disasm.cpp - src/gnu.cpp src/m68k.cpp src/elf_image.cpp ) @@ -1,15 +1,19 @@ # Motorola 68000 Disassembler -> Disassemble into what `as` can assemble back +> Disassemble some M68000 machine code into what an assembler can assemble again This project aims to be a disassembler that is capable to produce assembly code -that GNU AS will translate into the same original machine code. It's only use -case for now is Sega Mega Drive / Genesis ROM hacking. I failed to find any way -to disassemble SMD ROMs in such a way that it would be possible to assemble it -back with GNU AS. All disassemblers I tried produce either syntactically -incompatible assembly listing, or it is not the same as original binary after -translation into machine code. So I decided to build my own disassembler, that -will do exactly what I need with full control over the process and maybe more. +that an assembler will translate into the same original machine code. It could +be called a *matching disassembling*. GNU AS and Sierra ASM68 assemblers are +currently supported. + +It's only use case for now is Sega Mega Drive / Genesis ROM hacking. I failed to +find any way to disassemble SMD ROMs in such a way that it would be possible to +assemble it back into binary. All disassemblers I tried produce either +syntactically incompatible assembly listing, or it is not the same as original +binary after translation into machine code. So I decided to build my own +disassembler, that will do exactly what I need with full control over the +process and maybe more.  @@ -112,9 +116,10 @@ To get detailed help you can run: Goals of this Motorola 68000 disassembler project in this particular repo: - Support all Motorola 68000 ISA instructions. -- Flawless compatibility with GNU AS syntax. It should always emit the code on - which GNU AS produces absolutely identical binary (with or without linkage) - without errors or warnings, unless some peculiar flags has been specified. +- Flawless compatibility with GNU AS and Sierra ASM68 syntax. It should always + emit the code on which GNU AS produces absolutely identical binary (with or + without linkage) without errors or warnings, unless some peculiar flags has + been specified. - Support PC trace tables. With trace tables it will disassemble traced PC locations only, without attempt to disassemble everything, because not everything is instruction, some code is just data. @@ -148,6 +153,9 @@ What is **not** the goal (at least not in this repo): - It generates GNU AS compatible listing, that may be translated back to machine code using `m68k-none-elf-as` in the way that it matches original binary file, no matter what. +- It generates Sierra ASM68 compatible listing, that may be translated back to + machine code using `ASM68` in DOS environment in the way that it matches + original binary file, no matter what. - It generates labels for all jump instructions (JSR, JMP, BRA, Bcc and DBcc) if jump location is inside the code being disassembled. This feature can be enabled with `-flabels`, `-frel-labels` and `-fabs-labels` options, all at @@ -160,6 +168,10 @@ What is **not** the goal (at least not in this repo): `--pc-trace=file` to disassemble only what is supposed to be instructions and leave all the rest as raw data. Otherwise it will try to disassemble everything. +- Automatic and non-committing code discovery with jump follow with `-fwalk` and + `-ffollow-jumps`. I.e. when PC trace table provided, the disassembler will try + it's best to disassemble non-traced continuous code spans, until unknown + instruction or unconditional branch encountered. ### Limitations diff --git a/src/common.h b/src/common.h index 1cd0d9c..7b91847 100644 --- a/src/common.h +++ b/src/common.h @@ -17,6 +17,11 @@ enum class SplitPointType { kFunction, }; +enum class TargetAssembler { + kGnuAs = 0, + kSierraAsm68, +}; + struct SplitParams { SplitPointType type{}; size_t alignment{}; @@ -39,10 +44,12 @@ struct Settings { bool follow_jumps{}; bool walk{}; bool symbols{}; + bool dot_size_spec{}; BFDTarget bfd{}; const char *indent{"\t"}; const char *output_dir_path{}; SplitParams split{}; + TargetAssembler target_asm{}; }; using RefKindMask = unsigned; diff --git a/src/disasm.cpp b/src/disasm.cpp index 847d11d..572030d 100644 --- a/src/disasm.cpp +++ b/src/disasm.cpp @@ -182,8 +182,11 @@ void DisasmMap::Disasm( } else { node = &insertNode(at, NodeType::kTracedInstruction); } - if (node->op.opcode == OpCode::kNone || inside_code_span) { - const auto size = node->Disasm(code); + const bool perform_disasm = node->op.opcode == OpCode::kNone || + (_type == DisasmMapType::kRaw && node->op.opcode == OpCode::kRaw) || + inside_code_span; + if (perform_disasm) { + const auto size = node->Disasm(code, s); assert(size >= kInstructionSizeStepBytes); if (canBeAllocated(*node)) { // Spread across the size diff --git a/src/disasm.h b/src/disasm.h index 90906ed..a805df6 100644 --- a/src/disasm.h +++ b/src/disasm.h @@ -56,7 +56,7 @@ struct DisasmNode { /*! Disassembles instruction with arguments * returns size of whole instruction with arguments in bytes */ - size_t Disasm(const DataView &code); + size_t Disasm(const DataView &code, const Settings &); size_t DisasmAsRaw(const DataView &code); void AddReferencedBy(uint32_t address, ReferenceType); ~DisasmNode(); diff --git a/src/gnu.cpp b/src/gnu.cpp deleted file mode 100644 index 7f8ebe1..0000000 --- a/src/gnu.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: Unlicense - */ - -#include "gnu.h" - -using namespace Gnu; - -static constexpr const char *SymbolTypeToElfTypeString(SymbolType t) -{ - switch (t) { - case SymbolType::kNone: return nullptr; - case SymbolType::kFunction: return "function"; - case SymbolType::kObject: return "object"; - } - return nullptr; -} - -void Gnu::EmitSymbolMetadata(FILE *out, const char *indent, const Symbol &symbol) -{ - const char *const type = SymbolTypeToElfTypeString(symbol.type); - if (type) { - fprintf(out, "%s.type\t%s, @%s\n", indent, symbol.name, type); - } -} - -void Gnu::EmitSymbolSize(FILE *out, const char *indent, const char *sym_name) -{ - fprintf(out, "%s.size\t%s,.-%s\n", indent, sym_name, sym_name); -} diff --git a/src/gnu.h b/src/gnu.h deleted file mode 100644 index 882fc7c..0000000 --- a/src/gnu.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -/* SPDX-License-Identifier: Unlicense - */ - -#include "disasm.h" - -#include <cstdio> - -namespace Gnu { -void EmitSymbolMetadata(FILE *, const char *indent, const Symbol &); -void EmitSymbolSize(FILE *, const char *indent, const char *sym_name); -} diff --git a/src/m68k.cpp b/src/m68k.cpp index 74df143..fe96ee6 100644 --- a/src/m68k.cpp +++ b/src/m68k.cpp @@ -28,25 +28,35 @@ enum class ShiftKind: int { kRotate = 3, }; -constexpr Arg FetchImmediate(const uint32_t address, const DataView &code, const OpSize s) +constexpr Arg FetchImmediate( + const uint32_t address, const DataView &code, const Settings &s, const OpSize opsize) { - if (s == OpSize::kInvalid) { + if (opsize == OpSize::kInvalid) { return Arg{}; - } else if (s == OpSize::kLong) { + } else if (opsize == OpSize::kLong) { if (address + kInstructionSizeStepBytes < code.size) { const int32_t value = GetI32BE(code.buffer + address); return Arg::Immediate(value); } } else if (address < code.size) { const int16_t value = GetI16BE(code.buffer + address); - if (s == OpSize::kByte) { + if (opsize == OpSize::kByte) { + if (s.target_asm == TargetAssembler::kSierraAsm68) { + // Sierra does not allow 0xffffffffxx as negative value for byte + // size operations. It basically never fills the high byte in + // this case. + if (uint16_t(value) & 0xff00u) { + return Arg::ImmediateInvalid(uint16_t(value) & 0xffu); + } + return Arg::Immediate(int8_t(uint16_t(value) & 0xffu)); + } // Technically it is impossible to have value lower that -128 in 8 // bits signed integer, but the second byte being 0xff is actually // a valid thing and it is how values from -255 to -129 are // represented. if (value > 255 || value < -255) { // Invalid immediate value for instruction with .b suffix - return Arg{}; + return Arg::ImmediateInvalid(value); } } return Arg::Immediate(value); @@ -55,7 +65,12 @@ constexpr Arg FetchImmediate(const uint32_t address, const DataView &code, const } constexpr Arg FetchArg( - const uint32_t address, const DataView &code, const int m, const int xn, const OpSize s) + const uint32_t address, + const DataView &code, + const Settings &s, + const int m, + const int xn, + const OpSize opsize) { switch (m) { case 0: // Dn @@ -71,6 +86,9 @@ constexpr Arg FetchArg( case 5: // (d16, An), Additional Word if (address < code.size) { const int16_t d16 = GetI16BE(code.buffer + address); + if (s.target_asm == TargetAssembler::kSierraAsm68 && d16 == 0) { + return Arg::D16AnAddrInvalid(xn, d16); + } return Arg::D16AnAddr(xn, d16); } break; @@ -125,7 +143,7 @@ constexpr Arg FetchArg( } break; case 4: // #imm - return FetchImmediate(address, code, s); + return FetchImmediate(address, code, s, opsize); case 5: // Does not exist case 6: // Does not exist case 7: // Does not exist @@ -137,12 +155,16 @@ constexpr Arg FetchArg( } static Arg FetchArg( - const uint32_t address, const DataView &code, const uint16_t instr, const OpSize s) + const uint32_t address, + const DataView &code, + const Settings &s, + const uint16_t instr, + const OpSize opsize) { const int addrmode = instr & 0x3f; const int m = (addrmode >> 3) & 7; const int xn = addrmode & 7; - return FetchArg(address, code, m, xn, s); + return FetchArg(address, code, s, m, xn, opsize); } static size_t disasm_verbatim(DisasmNode &node, const uint16_t instr) @@ -152,10 +174,10 @@ static size_t disasm_verbatim(DisasmNode &node, const uint16_t instr) } static size_t disasm_jsr_jmp( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const OpSize opsize = OpSize::kWord; - const auto a = FetchArg(node.address + kInstructionSizeStepBytes, code, instr, opsize); + const auto a = FetchArg(node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (a.mode) { case AddrMode::kInvalid: case AddrMode::kDn: // 4e80..4e87 / 4ec0..4ec7 @@ -219,7 +241,7 @@ static size_t disasm_ext(DisasmNode &node, const OpSize opsize, const Arg arg) } static size_t disasm_ext_movem( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const auto dir = static_cast<MoveDirection>((instr >> 10) & 1); const unsigned m = (instr >> 3) & 7; @@ -238,7 +260,7 @@ static size_t disasm_ext_movem( return disasm_verbatim(node, instr); } const auto a = FetchArg( - node.address + kInstructionSizeStepBytes * 2, code, m, xn, opsize); + node.address + kInstructionSizeStepBytes * 2, code, s, m, xn, opsize); switch (a.mode) { case AddrMode::kInvalid: case AddrMode::kDn: // 4880..4887 / 4c80..4c87 / 48c0..48c7 / 4cc0..4cc7 @@ -299,11 +321,11 @@ static size_t disasm_ext_movem( } static size_t disasm_lea( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const OpSize opsize = OpSize::kLong; const auto addr = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (addr.mode) { case AddrMode::kInvalid: case AddrMode::kDn: @@ -339,11 +361,11 @@ static size_t disasm_lea( } static size_t disasm_chk( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const OpSize opsize = OpSize::kWord; const auto src = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (src.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -371,7 +393,7 @@ static size_t disasm_chk( } static size_t disasm_bra_bsr_bcc( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings& s) { const int16_t dispmt0 = static_cast<int8_t>(instr & 0xff); if (dispmt0 == -1) { @@ -393,10 +415,19 @@ static size_t disasm_bra_bsr_bcc( ? dispmt0 : GetI16BE(code.buffer + node.address + kInstructionSizeStepBytes)); const uint32_t ref_addr = static_cast<uint32_t>(node.address + dispmt); Condition condition = static_cast<Condition>((instr >> 8) & 0xf); - // False condition Indicates BSR - node.ref1_addr = ref_addr; - node.ref_kinds = kRef1RelMask | ((condition == Condition::kF) ? kRefCallMask : 0); - node.op = Op{OpCode::kBcc, opsize, condition, Arg::Displacement(dispmt)}; + // "short branch to next instruction is illegal" in Sierra. + const bool branch_to_next = dispmt == kInstructionSizeStepBytes; + // Sierra wont take odd displacement and will spit out the following: + // "branch to/from odd address: destination address is odd" + const bool odd = dispmt % int16_t(2); + if (s.target_asm == TargetAssembler::kSierraAsm68 && (branch_to_next || odd)) { + node.op = Op{OpCode::kBcc, opsize, condition, Arg::DisplacementInvalid(dispmt)}; + } else { + node.ref1_addr = ref_addr; + // False condition Indicates BSR + node.ref_kinds = kRef1RelMask | ((condition == Condition::kF) ? kRefCallMask : 0); + node.op = Op{OpCode::kBcc, opsize, condition, Arg::Displacement(dispmt)}; + } return node.size; } @@ -413,14 +444,14 @@ static OpCode OpCodeForBitOps(const unsigned opcode) } static size_t disasm_movep( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const unsigned dn = ((instr >> 9) & 7); const unsigned an = instr & 7; const OpSize opsize = ((instr >> 6) & 1) ? OpSize::kLong : OpSize::kWord; const auto dir = static_cast<MoveDirection>(!((instr >> 7) & 1)); const auto addr = FetchArg( - node.address + kInstructionSizeStepBytes, code, 5, an, opsize); + node.address + kInstructionSizeStepBytes, code, s, 5, an, opsize); if (addr.mode == AddrMode::kInvalid) { // Boundary check failed, most likely return disasm_verbatim(node, instr); @@ -439,11 +470,12 @@ static size_t disasm_src_arg_bitops_movep( DisasmNode &node, const uint16_t instr, const DataView &code, + const Settings &s, const bool has_dn_src = true) { const unsigned m = (instr >> 3) & 7; if ((m == 1) && has_dn_src) { - return disasm_movep(node, instr, code); + return disasm_movep(node, instr, code, s); } const unsigned dn = ((instr >> 9) & 7); const unsigned xn = instr & 7; @@ -453,6 +485,7 @@ static size_t disasm_src_arg_bitops_movep( const auto src = FetchArg( node.address + kInstructionSizeStepBytes, code, + s, (has_dn_src) ? 0 : 7, dn, opsize0); @@ -465,8 +498,8 @@ static size_t disasm_src_arg_bitops_movep( assert(dn == 4); assert(src.mode == AddrMode::kImmediate); } - const auto dst = FetchArg( - node.address + kInstructionSizeStepBytes + src.Size(opsize0), code, m, xn, opsize0); + const uint32_t addr = node.address + kInstructionSizeStepBytes + src.Size(opsize0); + const auto dst = FetchArg(addr, code, s, m, xn, opsize0); const unsigned opcode = (instr >> 6) & 3; switch (dst.mode) { case AddrMode::kInvalid: @@ -498,12 +531,12 @@ static size_t disasm_src_arg_bitops_movep( return node.size = kInstructionSizeStepBytes + src.Size(opsize0) + dst.Size(opsize0); } -static size_t disasm_bitops(DisasmNode &n, const uint16_t i, const DataView &c) +static size_t disasm_bitops(DisasmNode &n, const uint16_t i, const DataView &c, const Settings &s) { - return disasm_src_arg_bitops_movep(n, i, c, false); + return disasm_src_arg_bitops_movep(n, i, c, s, false); } -static size_t disasm_logical_immediate_to( +static size_t disasm_logical_immediate_to_ccr_or_sr( DisasmNode &node, OpCode opcode, OpSize opsize, Arg imm) { node.op = Op::Typical(opcode, opsize, imm, (opsize == OpSize::kByte) ? Arg::CCR() : Arg::SR()); @@ -527,11 +560,11 @@ static OpCode OpCodeForLogicalImmediate(const unsigned opcode) } static size_t disasm_bitops_movep( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const bool has_source_reg = (instr >> 8) & 1; if (has_source_reg) { - return disasm_src_arg_bitops_movep(node, instr, code); + return disasm_src_arg_bitops_movep(node, instr, code, s); } const unsigned opcode = (instr >> 9) & 7; if (opcode == 7) { @@ -539,7 +572,7 @@ static size_t disasm_bitops_movep( return disasm_verbatim(node, instr); } if (opcode == 4) { - return disasm_bitops(node, instr, code); + return disasm_bitops(node, instr, code, s); } const int m = (instr >> 3) & 7; const int xn = instr & 7; @@ -560,17 +593,17 @@ static size_t disasm_bitops_movep( return disasm_verbatim(node, instr); } } - const auto src = FetchImmediate(node.address + kInstructionSizeStepBytes, code, opsize); + auto src = FetchImmediate(node.address + kInstructionSizeStepBytes, code, s, opsize); if (src.mode == AddrMode::kInvalid) { return disasm_verbatim(node, instr); } assert(src.mode == AddrMode::kImmediate); const OpCode mnemonic = OpCodeForLogicalImmediate(opcode); if (m == 7 && xn == 4) { - return disasm_logical_immediate_to(node, mnemonic, opsize, src); + return disasm_logical_immediate_to_ccr_or_sr(node, mnemonic, opsize, src); } - const auto dst = FetchArg( - node.address + kInstructionSizeStepBytes + src.Size(opsize), code, m, xn, opsize); + auto dst = FetchArg( + node.address + kInstructionSizeStepBytes + src.Size(opsize), code, s, m, xn, opsize); switch (dst.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -588,26 +621,41 @@ static size_t disasm_bitops_movep( break; case AddrMode::kD16PCAddr: case AddrMode::kD8PCXiAddr: - if (opcode != 6) { - // PC relative destination address argument available for CMPI only - return disasm_verbatim(node, instr); - } + // According to "The 68000 Instruction Set" documentation I own, PC + // relative destination address argument *is* available for CMPI and for + // CMPI only, but neither GNU nor Sierra allow it for 68000 ISA. GNU AS + // considers it to be a feature of 68020, which is currently not + // supported. + // + // Hence, if 68020 will ever be supported, the next line should be + // wrapped with conditional expression based on an ISA choice. + dst.is_invalid = true; break; case AddrMode::kImmediate: return disasm_verbatim(node, instr); } + if (s.target_asm == TargetAssembler::kSierraAsm68) { + // For ADDI and SUBI Sierra will emit ADDQ and SUBQ if immediate value + // is positive and is less than or equal 8, i.e. if it fits ADDQ or SUBQ + // respectively. + if (mnemonic == OpCode::kADDI || mnemonic == OpCode::kSUBI) { + if (src.lword > 0 && src.lword <= 8) { + src.is_invalid = true; + } + } + } node.op = Op::Typical(mnemonic, opsize, src, dst); return node.size = kInstructionSizeStepBytes + src.Size(opsize) + dst.Size(opsize); } static size_t disasm_move_movea( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const int opsize_raw = (instr >> 12) & 3; const OpSize opsize = (opsize_raw == 1) ? OpSize::kByte : (opsize_raw == 3 ? OpSize::kWord : OpSize::kLong); const auto src = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (src.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -641,7 +689,7 @@ static size_t disasm_move_movea( const int m = (instr >> 6) & 7; const int xn = (instr >> 9) & 7; const auto dst = FetchArg( - node.address + kInstructionSizeStepBytes + src.Size(opsize), code, m, xn, opsize); + node.address + kInstructionSizeStepBytes + src.Size(opsize), code, s, m, xn, opsize); switch (dst.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -685,11 +733,11 @@ static size_t disasm_move_movea( } static size_t disasm_move_from_sr( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const auto opsize = OpSize::kWord; const auto dst = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (dst.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -715,11 +763,15 @@ static size_t disasm_move_from_sr( } static size_t disasm_move_to( - DisasmNode &node, const uint16_t instr, const DataView &code, const ArgType reg) + DisasmNode &node, + const uint16_t instr, + const DataView &code, + const Settings &s, + const ArgType reg) { const auto opsize = OpSize::kWord; const auto src = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (src.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -739,7 +791,7 @@ static size_t disasm_move_to( case AddrMode::kImmediate: break; } - node.op = Op::Typical(OpCode::kMOVE, opsize, src, Arg{{reg}, {0}}); + node.op = Op::Typical(OpCode::kMOVE, opsize, src, Arg{{reg}, false, {0}}); return node.size = kInstructionSizeStepBytes + src.Size(opsize); } @@ -756,26 +808,26 @@ static OpCode opcode_for_negx_clr_neg_not(const unsigned opcode) } static size_t disasm_move_negx_clr_neg_not( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const auto opsize = static_cast<OpSize>((instr >> 6) & 3); const unsigned opcode = (instr >> 9) & 3; if (opsize == OpSize::kInvalid) { switch (opcode) { case 0: - return disasm_move_from_sr(node, instr, code); + return disasm_move_from_sr(node, instr, code, s); case 1: return disasm_verbatim(node, instr); case 2: - return disasm_move_to(node, instr, code, ArgType::kCCR); + return disasm_move_to(node, instr, code, s, ArgType::kCCR); case 3: - return disasm_move_to(node, instr, code, ArgType::kSR); + return disasm_move_to(node, instr, code, s, ArgType::kSR); } assert(false); return disasm_verbatim(node, instr); } const auto a = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (a.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -808,11 +860,11 @@ static size_t disasm_trivial( } static size_t disasm_tas( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const auto opsize = OpSize::kByte; const auto a = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (a.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -838,7 +890,7 @@ static size_t disasm_tas( } static size_t disasm_tst_tas_illegal( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const auto opsize = static_cast<OpSize>((instr >> 6) & 3); const int m = (instr >> 3) & 7; @@ -847,9 +899,9 @@ static size_t disasm_tst_tas_illegal( if (m == 7 && xn == 4){ return disasm_trivial(node, OpCode::kILLEGAL); } - return disasm_tas(node, instr, code); + return disasm_tas(node, instr, code, s); } - const auto a = FetchArg(node.address + kInstructionSizeStepBytes, code, m, xn, opsize); + auto a = FetchArg(node.address + kInstructionSizeStepBytes, code, s, m, xn, opsize); switch (a.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -864,8 +916,17 @@ static size_t disasm_tst_tas_illegal( case AddrMode::kD8AnXiAddr: case AddrMode::kWord: case AddrMode::kLong: + break; case AddrMode::kD16PCAddr: case AddrMode::kD8PCXiAddr: + // According to "The 68000 Instruction Set" documentation I own, PC + // relative destination address argument *is* available for TST, but + // neither GNU nor Sierra allow it for 68000 ISA. GNU AS considers it to + // be a feature of 68020, which is currently not supported. + // + // Hence, if 68020 will ever be supported, the next line should be + // wrapped with conditional expression based on an ISA choice. + a.is_invalid = true; break; case AddrMode::kImmediate: return disasm_verbatim(node, instr); @@ -881,7 +942,8 @@ static size_t disasm_trap(DisasmNode &node, const uint16_t instr) return node.size = kInstructionSizeStepBytes; } -static size_t disasm_link_unlink(DisasmNode &node, const uint16_t instr, const DataView &code) +static size_t disasm_link_unlink( + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const bool unlk = (instr >> 3) & 1; const unsigned xn = instr & 7; @@ -890,7 +952,8 @@ static size_t disasm_link_unlink(DisasmNode &node, const uint16_t instr, const D return node.size = kInstructionSizeStepBytes; } const auto opsize = OpSize::kWord; - const auto src = FetchImmediate(node.address + kInstructionSizeStepBytes, code, opsize); + const auto src = FetchImmediate( + node.address + kInstructionSizeStepBytes, code, s, opsize); if (src.mode != AddrMode::kImmediate) { return disasm_verbatim(node, instr); } @@ -912,12 +975,13 @@ static size_t disasm_move_usp(DisasmNode &node, const uint16_t instr) return node.size = kInstructionSizeStepBytes; } -static size_t disasm_nbcd_swap_pea(DisasmNode &node, const uint16_t instr, const DataView &code) +static size_t disasm_nbcd_swap_pea( + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const bool is_nbcd = !((instr >> 6) & 1); const OpSize opsize0 = OpSize::kWord; const auto arg = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize0); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize0); bool is_swap{}; switch (arg.mode) { case AddrMode::kInvalid: @@ -965,9 +1029,10 @@ static size_t disasm_nbcd_swap_pea(DisasmNode &node, const uint16_t instr, const return node.size = kInstructionSizeStepBytes + arg.Size(opsize0); } -static size_t disasm_stop(DisasmNode &node, const uint16_t instr, const DataView &code) +static size_t disasm_stop( + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { - const auto a = FetchImmediate(node.address + kInstructionSizeStepBytes, code, OpSize::kWord); + const auto a = FetchImmediate(node.address + kInstructionSizeStepBytes, code, s, OpSize::kWord); if (a.mode != AddrMode::kImmediate) { return disasm_verbatim(node, instr); } @@ -975,19 +1040,20 @@ static size_t disasm_stop(DisasmNode &node, const uint16_t instr, const DataView return node.size = kInstructionSizeStepBytes * 2; } -static size_t disasm_chunk_4(DisasmNode &node, const uint16_t instr, const DataView &code) +static size_t disasm_chunk_4( + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { if ((instr & 0xf900) == 0x4000) { - return disasm_move_negx_clr_neg_not(node, instr, code); + return disasm_move_negx_clr_neg_not(node, instr, code, s); } else if ((instr & 0xff80) == 0x4800) { // NOTE: EXT is handled with MOVEM - return disasm_nbcd_swap_pea(node, instr, code); + return disasm_nbcd_swap_pea(node, instr, code, s); } else if ((instr & 0xff00) == 0x4a00) { - return disasm_tst_tas_illegal(node, instr, code); + return disasm_tst_tas_illegal(node, instr, code, s); } else if ((instr & 0xfff0) == 0x4e40) { return disasm_trap(node, instr); } else if ((instr & 0xfff0) == 0x4e50) { - return disasm_link_unlink(node, instr, code); + return disasm_link_unlink(node, instr, code, s); } else if ((instr & 0xfff0) == 0x4e60) { return disasm_move_usp(node, instr); } else if ((instr & 0xfff8) == 0x4e70) { @@ -996,7 +1062,7 @@ static size_t disasm_chunk_4(DisasmNode &node, const uint16_t instr, const DataV } else if (instr == 0x4e71) { return disasm_trivial(node, OpCode::kNOP); } else if (instr == 0x4e72) { - return disasm_stop(node, instr, code); + return disasm_stop(node, instr, code, s); } else if (instr == 0x4e73) { return disasm_trivial(node, OpCode::kRTE); } else if (instr == 0x4e75) { @@ -1007,21 +1073,25 @@ static size_t disasm_chunk_4(DisasmNode &node, const uint16_t instr, const DataV return disasm_trivial(node, OpCode::kRTR); } } else if ((instr & 0xff80) == 0x4e80) { - return disasm_jsr_jmp(node, instr, code); + return disasm_jsr_jmp(node, instr, code, s); } else if ((instr & 0xfb80) == 0x4880) { - return disasm_ext_movem(node, instr, code); + return disasm_ext_movem(node, instr, code, s); } else if ((instr & 0xf1c0) == 0x41c0) { - return disasm_lea(node, instr, code); + return disasm_lea(node, instr, code, s); } else if ((instr & 0xf1c0) == 0x4180) { - return disasm_chk(node, instr, code); + return disasm_chk(node, instr, code, s); } return disasm_verbatim(node, instr); } static size_t disasm_addq_subq( - DisasmNode &node, const uint16_t instr, const DataView &code, const OpSize opsize) + DisasmNode &node, + const uint16_t instr, + const DataView &code, + const Settings &s, + const OpSize opsize) { - const auto a = FetchArg(node.address + kInstructionSizeStepBytes, code, instr, opsize); + const auto a = FetchArg(node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (a.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -1054,13 +1124,18 @@ static size_t disasm_addq_subq( return node.size = kInstructionSizeStepBytes + a.Size(opsize); } -static size_t disasm_dbcc(DisasmNode &node, const uint16_t instr, const DataView &code) +static size_t disasm_dbcc( + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { if (node.address + kInstructionSizeStepBytes >= code.size) { return disasm_verbatim(node, instr); } const int16_t dispmt_raw = GetI16BE(code.buffer + node.address + kInstructionSizeStepBytes); const int32_t dispmt = dispmt_raw + kInstructionSizeStepBytes; + // Sierra wont take odd displacement and will spit out the following: + // "branch to/from odd address: destination address is odd" + const bool invalid_dispmt = (s.target_asm == TargetAssembler::kSierraAsm68) && + (dispmt % int32_t(2)); node.ref2_addr = static_cast<uint32_t>(node.address + dispmt); node.ref_kinds = kRef2RelMask; node.op = Op{ @@ -1068,23 +1143,24 @@ static size_t disasm_dbcc(DisasmNode &node, const uint16_t instr, const DataView OpSize::kWord, static_cast<Condition>((instr >> 8) & 0xf), Arg::AddrModeXn(ArgType::kDn, (instr & 7)), - Arg::Displacement(dispmt), + invalid_dispmt ? Arg::DisplacementInvalid(dispmt) : Arg::Displacement(dispmt), }; return node.size = kInstructionSizeStepBytes * 2; } -static size_t disasm_scc_dbcc(DisasmNode &node, const uint16_t instr, const DataView &code) +static size_t disasm_scc_dbcc( + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const OpSize opsize = OpSize::kWord; const auto a = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (a.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); case AddrMode::kDn: // 5xc0..5xc7, Dn break; case AddrMode::kAn: // 5xc8..5xcf, An - return disasm_dbcc(node, instr, code); + return disasm_dbcc(node, instr, code, s); case AddrMode::kAnAddr: // 5xd0..5xd7 case AddrMode::kAnAddrIncr: // 5xd8..5xdf case AddrMode::kAnAddrDecr: // 5xe0..5xe7 @@ -1103,13 +1179,14 @@ static size_t disasm_scc_dbcc(DisasmNode &node, const uint16_t instr, const Data return node.size = kInstructionSizeStepBytes + a.Size(opsize); } -static size_t disasm_addq_subq_scc_dbcc(DisasmNode &n, const uint16_t instr, const DataView &c) +static size_t disasm_addq_subq_scc_dbcc( + DisasmNode &n, const uint16_t instr, const DataView &c, const Settings &s) { const auto opsize = static_cast<OpSize>((instr >> 6) & 3); if (opsize == OpSize::kInvalid) { - return disasm_scc_dbcc(n, instr, c); + return disasm_scc_dbcc(n, instr, c, s); } - return disasm_addq_subq(n, instr, c, opsize); + return disasm_addq_subq(n, instr, c, s, opsize); } static size_t disasm_moveq(DisasmNode &node, const uint16_t instr) @@ -1130,11 +1207,12 @@ static size_t disasm_divu_divs_mulu_muls( DisasmNode &node, const uint16_t instr, const DataView &code, + const Settings &s, const OpCode opcode) { const auto opsize = OpSize::kWord; const auto src = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (src.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -1180,12 +1258,13 @@ static size_t disasm_or_and( DisasmNode &node, const uint16_t instr, const DataView &code, + const Settings &s, const OpSize opsize, const OpCode opcode) { const bool dir_to_addr = (instr >> 8) & 1; const auto addr = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (addr.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -1229,7 +1308,7 @@ static size_t disasm_or_and( } static size_t disasm_divu_divs_sbcd_or( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { // Also ensures that opsize == OpSize::kByte, i.e. 0b00 if ((instr & 0x1f0) == 0x100) { @@ -1239,17 +1318,21 @@ static size_t disasm_divu_divs_sbcd_or( if (opsize == OpSize::kInvalid) { const bool is_signed = (instr >> 8) & 1; const auto opcode = is_signed ? OpCode::kDIVS : OpCode::kDIVU; - return disasm_divu_divs_mulu_muls(node, instr, code, opcode); + return disasm_divu_divs_mulu_muls(node, instr, code, s, opcode); } - return disasm_or_and(node, instr, code, opsize, OpCode::kOR); + return disasm_or_and(node, instr, code, s, opsize, OpCode::kOR); } static size_t disasm_adda_suba_cmpa( - DisasmNode &node, const uint16_t instr, const DataView &code, const OpCode opcode) + DisasmNode &node, + const uint16_t instr, + const DataView &code, + const Settings &s, + const OpCode opcode) { const OpSize opsize = static_cast<OpSize>(((instr >> 8) & 1) + 1); const auto src = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (src.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -1277,12 +1360,13 @@ static size_t disasm_add_sub_cmp( DisasmNode &node, const uint16_t instr, const DataView &code, + const Settings &s, const OpCode opcode, const OpSize opsize, const bool dir_to_addr) { const auto addr = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (addr.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -1354,11 +1438,12 @@ static size_t disasm_cmpm(DisasmNode &node, const uint16_t instr) return node.size = kInstructionSizeStepBytes + src.Size(opsize) + dst.Size(opsize); } -static size_t disasm_eor(DisasmNode &node, const uint16_t instr, const DataView &code) +static size_t disasm_eor( + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3); const auto addr = FetchArg( - node.address + kInstructionSizeStepBytes, code, instr, opsize); + node.address + kInstructionSizeStepBytes, code, s, instr, opsize); switch (addr.mode) { case AddrMode::kInvalid: return disasm_verbatim(node, instr); @@ -1386,21 +1471,21 @@ static size_t disasm_eor(DisasmNode &node, const uint16_t instr, const DataView } static size_t disasm_eor_cmpm_cmp_cmpa( - DisasmNode &node, const uint16_t instr, const DataView &code) + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3); if (opsize == OpSize::kInvalid) { - return disasm_adda_suba_cmpa(node, instr, code, OpCode::kCMPA); + return disasm_adda_suba_cmpa(node, instr, code, s, OpCode::kCMPA); } const bool dir_to_addr = ((instr >> 8) & 1); if (!dir_to_addr) { - return disasm_add_sub_cmp(node, instr, code, OpCode::kCMP, opsize, dir_to_addr); + return disasm_add_sub_cmp(node, instr, code, s, OpCode::kCMP, opsize, dir_to_addr); } const int m = (instr >> 3) & 7; if (m == 1) { return disasm_cmpm(node, instr); } - return disasm_eor(node, instr, code); + return disasm_eor(node, instr, code, s); } static size_t disasm_exg(DisasmNode &node, const uint16_t instr) @@ -1416,14 +1501,13 @@ static size_t disasm_exg(DisasmNode &node, const uint16_t instr) const int xi = (instr >> 9) & 7; const auto src = (m == 3) ? Arg::An(xi) : Arg::Dn(xi); const auto dst = (m == 2) ? Arg::Dn(xn) : Arg::An(xn); - // GNU AS does not accept size suffix for EXG, although it's size is always - // long word. - const auto opsize = OpSize::kNone; + const auto opsize = OpSize::kLong; node.op = Op::Typical(OpCode::kEXG, opsize, src, dst); return node.size = kInstructionSizeStepBytes + src.Size(opsize) + dst.Size(opsize); } -static size_t disasm_chunk_c(DisasmNode &node, const uint16_t instr, const DataView &code) +static size_t disasm_chunk_c( + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { if ((instr & 0x1f0) == 0x100) { return disasm_addx_subx_abcd_sbcd(node, instr, OpCode::kABCD); @@ -1432,28 +1516,34 @@ static size_t disasm_chunk_c(DisasmNode &node, const uint16_t instr, const DataV if (opsize == OpSize::kInvalid) { const bool is_signed = (instr >> 8) & 1; const auto opcode = is_signed ? OpCode::kMULS : OpCode::kMULU; - return disasm_divu_divs_mulu_muls(node, instr, code, opcode); + return disasm_divu_divs_mulu_muls(node, instr, code, s, opcode); } const unsigned m_split = instr & 0x1f8; if (m_split == 0x188 || m_split == 0x148 || m_split == 0x140) { return disasm_exg(node, instr); } - return disasm_or_and(node, instr, code, opsize, OpCode::kAND); + return disasm_or_and(node, instr, code, s, opsize, OpCode::kAND); } static size_t disasm_add_sub_x_a( - DisasmNode &node, const uint16_t instr, const DataView &code, const OpCode opcode) + DisasmNode &node, + const uint16_t instr, + const DataView &code, + const Settings &s, + const OpCode opcode) { const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3); if (opsize == OpSize::kInvalid) { - return disasm_adda_suba_cmpa(node, instr, code, (opcode == OpCode::kSUB) ? OpCode::kSUBA : OpCode::kADDA); + return disasm_adda_suba_cmpa( + node, instr, code, s, (opcode == OpCode::kSUB) ? OpCode::kSUBA : OpCode::kADDA); } const bool dir_to_addr = (instr >> 8) & 1; const unsigned m = (instr >> 3) & 7; if (dir_to_addr && (m == 0 || m == 1)) { - return disasm_addx_subx_abcd_sbcd(node, instr, (opcode == OpCode::kSUB) ? OpCode::kSUBX : OpCode::kADDX); + return disasm_addx_subx_abcd_sbcd( + node, instr, (opcode == OpCode::kSUB) ? OpCode::kSUBX : OpCode::kADDX); } - return disasm_add_sub_cmp(node, instr, code, opcode, opsize, dir_to_addr); + return disasm_add_sub_cmp(node, instr, code, s, opcode, opsize, dir_to_addr); } static OpCode ShiftKindToOpcode(const ShiftKind k, const ShiftDirection d) @@ -1474,22 +1564,24 @@ static OpCode ShiftKindToOpcode(const ShiftKind k, const ShiftDirection d) static bool IsValidShiftKind(const ShiftKind k) { - return static_cast<int>(k) < 4; + return static_cast<unsigned>(k) < 4u; } -static size_t disasm_shift_rotate(DisasmNode &node, const uint16_t instr, const DataView &code) +static size_t disasm_shift_rotate( + DisasmNode &node, const uint16_t instr, const DataView &code, const Settings &s) { - const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3); + const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3u); const unsigned xn = instr & 7; - const uint8_t rotation = (instr >> 9) & 7; + const uint8_t rotation = (instr >> 9) & 7u; const ShiftKind kind = (opsize == OpSize::kInvalid) ? static_cast<ShiftKind>(rotation) - : static_cast<ShiftKind>((instr >> 3) & 3); + : static_cast<ShiftKind>((instr >> 3) & 3u); if (!IsValidShiftKind(kind)) { return disasm_verbatim(node, instr); } + const auto dir = static_cast<ShiftDirection>((instr >> 8) & 1u); const auto dst = (opsize == OpSize::kInvalid) - ? FetchArg(node.address + kInstructionSizeStepBytes, code, instr, opsize) + ? FetchArg(node.address + kInstructionSizeStepBytes, code, s, instr, opsize) : Arg::Dn(xn); if (opsize == OpSize::kInvalid) { switch (dst.mode) { @@ -1515,52 +1607,48 @@ static size_t disasm_shift_rotate(DisasmNode &node, const uint16_t instr, const case AddrMode::kImmediate: return disasm_verbatim(node, instr); } - } - const unsigned imm = ((rotation - 1) & 7) + 1; - const unsigned src = (opsize == OpSize::kInvalid) ? 1 : rotation; - const auto dir = static_cast<ShiftDirection>((instr >> 8) & 1); - if (opsize == OpSize::kInvalid) { - node.op = Op::Typical(ShiftKindToOpcode(kind, dir), opsize, dst); + node.op = Op::Typical(ShiftKindToOpcode(kind, dir), OpSize::kWord, dst); } else { - const unsigned m = (instr >> 5) & 1; - const auto arg1 = m ? Arg::AddrModeXn(ArgType::kDn, src) : Arg::Immediate(imm); + const unsigned imm = ((rotation - 1) & 7u) + 1u; + const unsigned m = (instr >> 5) & 1u; + const auto arg1 = m ? Arg::AddrModeXn(ArgType::kDn, rotation) : Arg::Immediate(imm); node.op = Op::Typical(ShiftKindToOpcode(kind, dir), opsize, arg1, dst); } return node.size = kInstructionSizeStepBytes + dst.Size(opsize); } -static size_t m68k_disasm(DisasmNode &n, uint16_t i, const DataView &c) +static size_t m68k_disasm(DisasmNode &n, uint16_t i, const DataView &c, const Settings &s) { switch ((i & 0xf000) >> 12) { case 0x0: - return disasm_bitops_movep(n, i, c); + return disasm_bitops_movep(n, i, c, s); case 0x1: case 0x2: case 0x3: - return disasm_move_movea(n, i, c); + return disasm_move_movea(n, i, c, s); case 0x4: - return disasm_chunk_4(n, i, c); + return disasm_chunk_4(n, i, c, s); case 0x5: - return disasm_addq_subq_scc_dbcc(n, i, c); + return disasm_addq_subq_scc_dbcc(n, i, c, s); case 0x6: - return disasm_bra_bsr_bcc(n, i, c); + return disasm_bra_bsr_bcc(n, i, c, s); case 0x7: return disasm_moveq(n, i); case 0x8: - return disasm_divu_divs_sbcd_or(n, i, c); + return disasm_divu_divs_sbcd_or(n, i, c, s); case 0x9: - return disasm_add_sub_x_a(n, i, c, OpCode::kSUB); + return disasm_add_sub_x_a(n, i, c, s, OpCode::kSUB); case 0xa: // Does not exist return disasm_verbatim(n, i); case 0xb: - return disasm_eor_cmpm_cmp_cmpa(n, i, c); + return disasm_eor_cmpm_cmp_cmpa(n, i, c, s); case 0xc: - return disasm_chunk_c(n, i, c); + return disasm_chunk_c(n, i, c, s); case 0xd: - return disasm_add_sub_x_a(n, i, c, OpCode::kADD); + return disasm_add_sub_x_a(n, i, c, s, OpCode::kADD); case 0xe: - return disasm_shift_rotate(n, i, c); + return disasm_shift_rotate(n, i, c, s); case 0xf: // Does not exist return disasm_verbatim(n, i); @@ -1569,7 +1657,7 @@ static size_t m68k_disasm(DisasmNode &n, uint16_t i, const DataView &c) return disasm_verbatim(n, i); } -size_t DisasmNode::Disasm(const DataView &code) +size_t DisasmNode::Disasm(const DataView &code, const Settings &s) { // We assume that machine have no MMU and ROM data always starts at 0 assert(this->address < code.size); @@ -1579,7 +1667,7 @@ size_t DisasmNode::Disasm(const DataView &code) ref2_addr = 0; const uint16_t instr = GetU16BE(code.buffer + this->address); if (IsInstruction(this->type)) { - return m68k_disasm(*this, instr, code); + return m68k_disasm(*this, instr, code, s); } else { // Data should not be disassembled return disasm_verbatim(*this, instr); @@ -1742,8 +1830,19 @@ static const char *ToString(const OpCode opcode, const Condition condition) return "?"; } -static const char *ToString(const OpSize s) +static const char *ToString(const OpSize s, bool with_dot) { + if (with_dot) { + switch (s) { + case OpSize::kNone: return ""; + case OpSize::kByte: return ".b"; + case OpSize::kShort: return ".s"; + case OpSize::kWord: return ".w"; + case OpSize::kLong: return ".l"; + } + assert(false); + return ""; + } switch (s) { case OpSize::kNone: return ""; case OpSize::kByte: return "b"; @@ -1770,13 +1869,13 @@ static unsigned RegNum(const uint8_t xi) return xi & 0x7; } -static size_t snprint_reg_mask( +static size_t snprint_reg_mask_gnu( char *const buf, const size_t bufsz, const uint32_t regmask_arg, const ArgType arg_type) { const uint32_t regmask = regmask_arg & 0xffff; size_t written = 0; - bool first_printed = 0; size_t span = 0; + bool first_printed = false; // 17-th bit used to close the span with 0 value unconditionally for (int i = 0; i < 17; i++) { const uint32_t mask = (i <= 15) @@ -1784,7 +1883,7 @@ static size_t snprint_reg_mask( : 0; const bool hit = regmask & mask; const bool span_open = hit && span == 0; - const bool span_closed = !hit && span > 1; + const bool span_closed = (!hit && span > 1); const int printable_i = i - (span_closed ? 1 : 0); const int id = printable_i % 8; const char regtype = (printable_i >= 8) ? 'a' : 'd'; @@ -1804,11 +1903,67 @@ static size_t snprint_reg_mask( return written; } -int SNPrintArg( +/*! _. + * + * This function splits spans of d0-d7 and a0-a7, unlike snprint_reg_mask_gnu, + * which may produce spans across d-regs and a-regs like d0-a7. + */ +static size_t snprint_reg_mask_sierra( + char *const buf, const size_t bufsz, const uint32_t regmask_arg, const ArgType arg_type) +{ + const uint32_t regmask = regmask_arg & 0xffff; + size_t written = 0; + size_t span = 0; + bool first_printed = false; + bool dn_passed = false; + // 17-th bit used to close the span with 0 value unconditionally + for (int i = 0; i < 17; i++) { + const uint32_t mask = (i <= 15) + ? (1 << ((arg_type == ArgType::kRegMaskPredecrement) ? (15 - i) : i)) + : 0; + const bool hit = regmask & mask; + if (dn_passed && i == 8) { + span = 0; + } + const bool span_open = (hit && span == 0) && !(!dn_passed && i == 8); + const bool span_closed = !hit || (!dn_passed && i == 8); + const int printable_i = i - (span_closed ? 1 : 0); + const int id = printable_i % 8; + const char regtype = (printable_i >= 8) ? 'a' : 'd'; + if (span_open || (span_closed && span > 1)) { + const char *const delimiter = span_open ? (first_printed ? "/" : "") : "-"; + const size_t remaining = bufsz - written; + const int ret = snprintf(buf + written, remaining, "%s%c%d", delimiter, regtype, id); + assert(ret > 0); + assert(static_cast<unsigned>(ret) >= sizeof("d0")-1); + assert(static_cast<unsigned>(ret) <= sizeof("-d0")-1); + written += Min(remaining, ret); + first_printed = true; + } + span = hit ? span + 1 : 0; + if (!dn_passed && i == 8) { + // We need to repeat the iteration on a0 (i == 8) because we closed + // the span and it will get open again + i--; + dn_passed = true; + } + } + assert(written < bufsz); // Output must not be truncated + return written; +} + +int SNPrintArgRaw(char *const buf, const size_t bufsz, const Arg &arg) +{ + return snprintf(buf, bufsz, "0x%04x", arg.uword); +} + +static int SNPrintArg( char *const buf, const size_t bufsz, const Arg &arg, + const OpCode opcode, const bool imm_as_hex, + const TargetAssembler target_asm, const RefKindMask ref_kinds, const char *const label, const uint32_t self_addr, @@ -1819,20 +1974,47 @@ int SNPrintArg( assert(false); break; case ArgType::kRaw: - return snprintf(buf, bufsz, "0x%04x", arg.uword); + return SNPrintArgRaw(buf, bufsz, arg); case ArgType::kDn: + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "d%d", arg.xn); + } return snprintf(buf, bufsz, "%%d%d", arg.xn); case ArgType::kAn: + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "a%u", arg.xn); + } return snprintf(buf, bufsz, "%%a%u", arg.xn); case ArgType::kAnAddr: + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "(a%u)", arg.xn); + } return snprintf(buf, bufsz, "%%a%u@", arg.xn); case ArgType::kAnAddrIncr: + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "(a%u)+", arg.xn); + } return snprintf(buf, bufsz, "%%a%u@+", arg.xn); case ArgType::kAnAddrDecr: + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "-(a%u)", arg.xn); + } return snprintf(buf, bufsz, "%%a%u@-", arg.xn); case ArgType::kD16AnAddr: + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "%d(a%u)", arg.d16_an.d16, arg.d16_an.an); + } return snprintf(buf, bufsz, "%%a%u@(%d:w)", arg.d16_an.an, arg.d16_an.d16); case ArgType::kD8AnXiAddr: + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf( + buf, bufsz, "%d(a%u,%c%u.%c)", + arg.d8_an_xi.d8, + arg.d8_an_xi.an, + RegChar(arg.d8_an_xi.xi), + RegNum(arg.d8_an_xi.xi), + SizeSpecChar(arg.d8_an_xi.xi)); + } return snprintf( buf, bufsz, "%%a%u@(%d,%%%c%u:%c)", arg.d8_an_xi.an, @@ -1846,13 +2028,22 @@ int SNPrintArg( const char c = arg.type == ArgType::kLong ? 'l' : 'w'; if (ref_kinds & kRefAbsMask) { if (static_cast<uint32_t>(arg.lword) == ref_addr) { + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "%s.%c", label, c); + } return snprintf(buf, bufsz, "%s:%c", label, c); } else { // It has to be AFTER the label we are gonna reference here assert(static_cast<uint32_t>(arg.lword) > ref_addr); + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "%s+%d.%c", label, arg.lword - ref_addr, c); + } return snprintf(buf, bufsz, "%s+%d:%c", label, arg.lword - ref_addr, c); } } else { + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "0x%x.%c", arg.lword, c); + } return snprintf(buf, bufsz, "0x%x:%c", arg.lword, c); } } @@ -1867,15 +2058,51 @@ int SNPrintArg( const uint32_t arg_addr = self_addr + arg.d16_pc.d16 + kInstructionSizeStepBytes + (has_fix ? kInstructionSizeStepBytes : 0); if (arg_addr == ref_addr) { + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "%s(pc)", label); + } return snprintf(buf, bufsz, "%%pc@(%s:w)", label); } else { assert(arg_addr > ref_addr); + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "%s+%d(pc)", label, arg_addr - ref_addr); + } return snprintf(buf, bufsz, "%%pc@(%s+%d:w)", label, arg_addr - ref_addr); } } else { + if (TargetAssembler::kSierraAsm68 == target_asm) { + // XXX: It may depend on the current instruction size, so adding + // kInstructionSizeStepBytes may be invalid in some cases. + const int displacement = arg.d16_pc.d16 + BasePartSize(opcode); + if (displacement == 0) { + return snprintf(buf, bufsz, ".(pc)"); + } + return snprintf(buf, bufsz, ".%s%d(pc)", + displacement >= 0 ? "+" : "", + displacement); + } return snprintf(buf, bufsz, "%%pc@(%d:w)", arg.d16_pc.d16); } case ArgType::kD8PCXiAddr: + if (TargetAssembler::kSierraAsm68 == target_asm) { + // XXX: It may depend on the current instruction size, so adding + // kInstructionSizeStepBytes may be invalid in some cases. + const int displacement = arg.d8_pc_xi.d8 + BasePartSize(opcode); + if (displacement == 0) { + return snprintf( + buf, bufsz, ".(pc,%c%u.%c)", + RegChar(arg.d8_pc_xi.xi), + RegNum(arg.d8_pc_xi.xi), + SizeSpecChar(arg.d8_pc_xi.xi)); + } + return snprintf( + buf, bufsz, ".%s%d(pc,%c%u.%c)", + displacement >= 0 ? "+" : "", + displacement, + RegChar(arg.d8_pc_xi.xi), + RegNum(arg.d8_pc_xi.xi), + SizeSpecChar(arg.d8_pc_xi.xi)); + } return snprintf( buf, bufsz, "%%pc@(%d,%%%c%u:%c)", arg.d8_pc_xi.d8, @@ -1898,7 +2125,11 @@ int SNPrintArg( } case ArgType::kRegMask: case ArgType::kRegMaskPredecrement: - return snprint_reg_mask(buf, bufsz, arg.uword, arg.type); + if (target_asm == TargetAssembler::kGnuAs) { + return snprint_reg_mask_gnu(buf, bufsz, arg.uword, arg.type); + } else { + return snprint_reg_mask_sierra(buf, bufsz, arg.uword, arg.type); + } case ArgType::kDisplacement: if (ref_kinds & kRefRelMask) { if (static_cast<uint32_t>(self_addr + arg.lword) == ref_addr) { @@ -1908,13 +2139,25 @@ int SNPrintArg( return snprintf(buf, bufsz, "%s+%d", label, (self_addr + arg.lword) - ref_addr); } } else { + if (arg.lword == 0) { + return snprintf(buf, bufsz, "."); + } return snprintf(buf, bufsz, ".%s%d", arg.lword >= 0 ? "+" : "", arg.lword); } case ArgType::kCCR: + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "ccr"); + } return snprintf(buf, bufsz, "%%ccr"); case ArgType::kSR: + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "sr"); + } return snprintf(buf, bufsz, "%%sr"); case ArgType::kUSP: + if (TargetAssembler::kSierraAsm68 == target_asm) { + return snprintf(buf, bufsz, "usp"); + } return snprintf(buf, bufsz, "%%usp"); } assert(false); @@ -1924,8 +2167,7 @@ int SNPrintArg( int FPrintOp( FILE *const stream, const Op &op, - const char *const indent, - const bool imm_as_hex, + const Settings &s, const RefKindMask ref_kinds, const char *const ref1_label, const char *const ref2_label, @@ -1935,16 +2177,22 @@ int FPrintOp( { assert(op.opcode != OpCode::kNone); char mnemonic_str[kMnemonicBufferSize]{}; + const bool gnu = s.target_asm == TargetAssembler::kGnuAs; // XXX GNU AS does not know ABCD.B, it only knows ABCD, but happily consumes // SBCD.B and others. That's why it is OpSize::kNone specifically for ABCD // mnemonic. It is probably a bug in GNU AS. - const OpSize size_spec{(op.opcode == OpCode::kABCD) ? OpSize::kNone : op.size_spec}; + const bool abcd = op.opcode == OpCode::kABCD; + // XXX GNU AS does not accept size suffix for EXG, although it's size is + // always long word. + const bool exg = op.opcode == OpCode::kEXG; + const OpSize size_spec{(gnu && (abcd || exg)) ? OpSize::kNone : op.size_spec}; + const bool with_dot = TargetAssembler::kSierraAsm68 == s.target_asm || s.dot_size_spec; snprintf( mnemonic_str, kMnemonicBufferSize, "%s%s", ToString(op.opcode, op.condition), - ToString(size_spec)); + ToString(size_spec, with_dot)); if (op.arg1.type != ArgType::kNone) { char arg1_str[kArgsBufferSize]{}; const RefKindMask ref1_kinds = ref_kinds & (kRef1Mask | kRefPcRelFix2Bytes); @@ -1952,7 +2200,10 @@ int FPrintOp( // argument is plain address register, status register or condition code // register. USP is not the case because it's value may be moved only to // or from An register. - const bool imm_as_hex_2 = imm_as_hex || + const bool imm_as_hex = s.imm_hex || + op.arg1.type == ArgType::kAn || + op.arg1.type == ArgType::kCCR || + op.arg1.type == ArgType::kSR || op.arg2.type == ArgType::kAn || op.arg2.type == ArgType::kCCR || op.arg2.type == ArgType::kSR; @@ -1960,7 +2211,9 @@ int FPrintOp( arg1_str, kArgsBufferSize, op.arg1, - imm_as_hex_2, + op.opcode, + imm_as_hex, + s.target_asm, ref1_kinds, ref1_label, self_addr, @@ -1972,16 +2225,20 @@ int FPrintOp( arg2_str, kArgsBufferSize, op.arg2, - false, + op.opcode, + // Second arg may be immediate when dealing with "link", so + // imm_as_hex is relevant here as well. + imm_as_hex, + s.target_asm, ref2_kinds, ref2_label, self_addr, ref2_addr); - return fprintf(stream, "%s%s %s,%s", indent, mnemonic_str, arg1_str, arg2_str); + return fprintf(stream, "%s%s %s,%s", s.indent, mnemonic_str, arg1_str, arg2_str); } else { - return fprintf(stream, "%s%s %s", indent, mnemonic_str, arg1_str); + return fprintf(stream, "%s%s %s", s.indent, mnemonic_str, arg1_str); } } else { - return fprintf(stream, "%s%s", indent, mnemonic_str); + return fprintf(stream, "%s%s", s.indent, mnemonic_str); } } @@ -177,6 +177,7 @@ struct Arg { ArgType type{ArgType::kNone}; AddrMode mode; }; + bool is_invalid{}; union { int32_t lword{}; ///< kLong, kWord, kDisplacement, kImmediate uint16_t uword; ///< kRegMask, kRaw @@ -213,7 +214,7 @@ struct Arg { return 0; } static constexpr auto AddrModeXn(const ArgType type, const uint8_t xn) { - Arg a{{type}, {0}}; + Arg a{{type}, false, {0}}; a.xn = xn; return a; } @@ -230,67 +231,83 @@ struct Arg { } static constexpr auto D16AnAddr(const uint8_t xn, const int16_t d16) { - Arg a{{ArgType::kD16AnAddr}, {0}}; + Arg a{{ArgType::kD16AnAddr}, false, {0}}; + a.d16_an = D16AnPCAddr{xn, d16}; + return a; + } + static constexpr auto D16AnAddrInvalid(const uint8_t xn, const int16_t d16) + { + Arg a{{ArgType::kD16AnAddr}, true, {0}}; a.d16_an = D16AnPCAddr{xn, d16}; return a; } static constexpr auto D16PCAddr(const int16_t d16) { - Arg a{{ArgType::kD16PCAddr}, {0}}; + Arg a{{ArgType::kD16PCAddr}, false, {0}}; a.d16_pc = D16AnPCAddr{0, d16}; return a; } static constexpr auto Word(const int16_t w) { - Arg a{{ArgType::kWord}, {0}}; + Arg a{{ArgType::kWord}, false, {0}}; a.lword = w; return a; } static constexpr auto Long(const int32_t l) { - Arg a{{ArgType::kLong}, {0}}; + Arg a{{ArgType::kLong}, false, {0}}; a.lword = l; return a; } static constexpr auto D8AnXiAddr( const uint8_t xn, const uint8_t xi, const OpSize s, const int8_t d8) { - Arg a{{ArgType::kD8AnXiAddr}, {0}}; + Arg a{{ArgType::kD8AnXiAddr}, false, {0}}; a.d8_an_xi = D8AnPCXiAddr{xn, uint8_t(xi | (s == OpSize::kLong ? 0x10u : 0u)), d8}; return a; } static constexpr auto D8PCXiAddr( const uint8_t xn, const uint8_t xi, const OpSize s, const int8_t d8) { - Arg a{{ArgType::kD8PCXiAddr}, {0}}; + Arg a{{ArgType::kD8PCXiAddr}, false, {0}}; a.d8_pc_xi = D8AnPCXiAddr{xn, uint8_t(xi | (s == OpSize::kLong ? 0x10u : 0u)), d8}; return a; } static constexpr auto Immediate(const int32_t value) { - Arg a{{ArgType::kImmediate}, {0}}; + Arg a{{ArgType::kImmediate}, false, {0}}; + a.lword = value; + return a; + } + static constexpr auto ImmediateInvalid(const int32_t value) { + Arg a{{ArgType::kImmediate}, true, {0}}; a.lword = value; return a; } static constexpr auto RegMask(const uint16_t regmask) { - Arg a{{ArgType::kRegMask}, {0}}; + Arg a{{ArgType::kRegMask}, false, {0}}; a.uword = regmask; return a; } static constexpr auto RegMaskPredecrement(const uint16_t regmask) { - Arg a{{ArgType::kRegMaskPredecrement}, {0}}; + Arg a{{ArgType::kRegMaskPredecrement}, false, {0}}; a.uword = regmask; return a; } static constexpr auto Displacement(const int32_t displacement) { - Arg a{{ArgType::kDisplacement}, {0}}; + Arg a{{ArgType::kDisplacement}, false, {0}}; + a.lword = displacement; + return a; + } + static constexpr auto DisplacementInvalid(const int32_t displacement) { + Arg a{{ArgType::kDisplacement}, true, {0}}; a.lword = displacement; return a; } - static constexpr auto CCR() { return Arg{{ArgType::kCCR}, {0}}; } - static constexpr auto SR() { return Arg{{ArgType::kSR}, {0}}; } - static constexpr auto USP() { return Arg{{ArgType::kUSP}, {0}}; } + static constexpr auto CCR() { return Arg{{ArgType::kCCR}, false, {0}}; } + static constexpr auto SR() { return Arg{{ArgType::kSR}, false, {0}}; } + static constexpr auto USP() { return Arg{{ArgType::kUSP}, false, {0}}; } static constexpr auto Raw(const uint16_t instr) { - Arg a{{ArgType::kRaw}, {0}}; + Arg a{{ArgType::kRaw}, false, {0}}; a.uword = instr; return a; } @@ -325,22 +342,13 @@ static constexpr inline bool IsBRA(Op op) return op.opcode == OpCode::kBcc && op.condition == Condition::kT; } -int SNPrintArg( - char *buf, - size_t bufsz, - const Arg &, - bool imm_as_hex = false, - RefKindMask ref_kinds = 0, - const char *label = nullptr, - uint32_t self_addr = 0, - uint32_t ref_addr = 0); +int SNPrintArgRaw(char *buf, size_t bufsz, const Arg &); /// Return value means nothing and should be ignored int FPrintOp( FILE *, const Op &op, - const char *const indent, - const bool imm_as_hex, + const Settings &settings, RefKindMask ref_kinds = 0, const char *ref1_label = nullptr, const char *ref2_label = nullptr, @@ -348,7 +356,7 @@ int FPrintOp( uint32_t ref1_addr = 0, uint32_t ref2_addr = 0); -constexpr size_t BaseInstructionSize(OpCode opcode) +constexpr size_t BasePartSize(OpCode opcode) { switch (opcode) { case OpCode::kMOVEM: diff --git a/src/main.cpp b/src/main.cpp index 1125df0..f952f38 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,7 +4,6 @@ #include "elf_image.h" #include "data_buffer.h" #include "disasm.h" -#include "gnu.h" #include "common.h" #define OPTPARSE_IMPLEMENTATION @@ -71,6 +70,9 @@ static constexpr bool ShouldPrintAsRaw(const Op& op) return true; } } + if (op.arg1.is_invalid || op.arg2.is_invalid) { + return true; + } return false; } @@ -258,6 +260,64 @@ static FILE *OpenNewPartFile(const char *dir, uint32_t address) return output; } +static constexpr const char *SymbolTypeToElfTypeString(SymbolType t) +{ + switch (t) { + case SymbolType::kNone: return nullptr; + case SymbolType::kFunction: return "function"; + case SymbolType::kObject: return "object"; + } + return nullptr; +} + +static constexpr unsigned SymbolTypeToSierraTypeNumber(SymbolType t) +{ + switch (t) { + case SymbolType::kNone: return 0; + case SymbolType::kFunction: return 0x20; + case SymbolType::kObject: return 0x30; + } + return 0; +} + +static void EmitSymbolMetadata(FILE *out, const Symbol &symbol, const Settings &s) +{ + switch (s.target_asm) { + case TargetAssembler::kGnuAs: + { + const char *const type = SymbolTypeToElfTypeString(symbol.type); + if (type) { + fprintf(out, "%s.type\t%s, @%s\n", s.indent, symbol.name, type); + } + } + return; + case TargetAssembler::kSierraAsm68: + { + // TODO figure out what is 17-th bit + const unsigned type = 0x10000 | SymbolTypeToSierraTypeNumber(symbol.type); + // TODO figure out how to determine storage class + const int storage_class = 2; + fprintf(out, "%s.def\t%s\\\t.val\t%s\\\t.scl\t%d\\\t.type\t0x%x\\\t.endef\n", + s.indent, symbol.name, symbol.name, storage_class, type); + } + return; + } + assert(0); +} + +static void EmitSymbolSize(FILE *out, const char *sym_name, const Settings &s) +{ + switch (s.target_asm) { + case TargetAssembler::kGnuAs: + fprintf(out, "%s.size\t%s,.-%s\n", s.indent, sym_name, sym_name); + return; + case TargetAssembler::kSierraAsm68: + fprintf(out, "%s.def\t%s\\\t.val\t.\\\t.scl\t-1\\\t.endef\n", s.indent, sym_name); + return; + } + assert(0); +} + struct EmitContext { FILE *output{}; // symbol_index starts with 1 because 0 is a special null symbol @@ -308,7 +368,8 @@ static bool EmitNodeDisassembly( if (export_this_label) { fprintf(output, "\n%s.globl\t%s\n", s.indent, name); if (export_this_function) { - fprintf(output, "%s.type\t%s, @function\n", s.indent, name); + const auto symbol = Symbol{0, SymbolType::kFunction, name, 0}; + EmitSymbolMetadata(output, symbol, s); } } } @@ -321,12 +382,20 @@ static bool EmitNodeDisassembly( } } while (0); if (s.xrefs_from && (have_symbol || !is_local)) { - fprintf(output, "| XREFS:\n"); + if (s.target_asm == TargetAssembler::kGnuAs) { + fprintf(output, "| XREFS:\n"); + } else { + fprintf(output, "; XREFS:\n"); + } for (const ReferenceNode *ref{node.ref_by}; ref; ref = ref->next) { if (ref->refs_count == 0) { continue; } - fprintf(output, "|"); + if (s.target_asm == TargetAssembler::kGnuAs) { + fprintf(output, "|"); + } else { + 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); @@ -336,16 +405,12 @@ static bool EmitNodeDisassembly( } assert(node.op.opcode != OpCode::kNone); if (ShouldPrintAsRaw(node.op)) { - FPrintOp( - output, - Op::Raw(GetU16BE(code.buffer + node.address)), - s.indent, - s.imm_hex); + FPrintOp(output, Op::Raw(GetU16BE(code.buffer + node.address)), s); 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)); - SNPrintArg(arg_str, kArgsBufferSize, arg); + SNPrintArgRaw(arg_str, kArgsBufferSize, arg); fprintf(output, ", %s", arg_str); } } else { @@ -403,8 +468,7 @@ static bool EmitNodeDisassembly( FPrintOp( output, node.op, - s.indent, - s.imm_hex, + s, ref_kinds, ref1_label, ref2_label, @@ -413,13 +477,21 @@ static bool EmitNodeDisassembly( ref2_addr); const bool ref1_from_imm_ok = ((node.ref_kinds & kRef1ImmMask) ? s.imm_labels : true); if (s.xrefs_to && ref1 && !ref1_is_local && ref1_from_imm_ok) { - fprintf(output, " | XREF1 @%08x", ref1_addr); + if (s.target_asm == TargetAssembler::kGnuAs) { + fprintf(output, " | XREF1 @%08x", ref1_addr); + } else { + fprintf(output, " ; XREF1 @%08x", ref1_addr); + } } if (s.xrefs_to && ref2 && !ref2_is_local) { - fprintf(output, " | XREF2 @%08x", ref2_addr); + if (s.target_asm == TargetAssembler::kGnuAs) { + fprintf(output, " | XREF2 @%08x", ref2_addr); + } else { + fprintf(output, " ; XREF2 @%08x", ref2_addr); + } } } else { - FPrintOp(output, node.op, s.indent, s.imm_hex); + FPrintOp(output, node.op, s); } } if (s.raw_data_comment && (traced || s.raw_data_comment_all)) { @@ -429,7 +501,11 @@ static bool EmitNodeDisassembly( (sizeof raw_data_comment) - 1, node.address, node.size, code); - fprintf(output, " |%s", raw_data_comment); + if (s.target_asm == TargetAssembler::kGnuAs) { + fprintf(output, " |%s", raw_data_comment); + } else { + fprintf(output, " ;%s", raw_data_comment); + } } fprintf(output, "\n"); return true; @@ -445,9 +521,9 @@ static void EmitNonCodeSymbols( continue; } fprintf(output, "\n%s.globl\t%s\n", s.indent, symbol.name); - Gnu::EmitSymbolMetadata(output, s.indent, symbol); + EmitSymbolMetadata(output, symbol, s); fprintf(output, "%s = 0x%08x\n", symbol.name, symbol.address); - Gnu::EmitSymbolSize(output, s.indent, symbol.name); + EmitSymbolSize(output, symbol.name, s); } } @@ -542,7 +618,7 @@ static bool EmitDisassembly( const size_t symtab_size = disasm_map.SymbolsCount(); if (disasm_map.Symtab() != nullptr && symtab_size > 0) { for (const char *name = ctx.pending_size.TakeNext(address); name;) { - Gnu::EmitSymbolSize(ctx.output, s.indent, name); + EmitSymbolSize(ctx.output, name, s); name = ctx.pending_size.TakeNext(address); } for (; ctx.symbol_index < symtab_size; ctx.symbol_index++) { @@ -572,7 +648,7 @@ static bool EmitDisassembly( if (symbol.type == SymbolType::kFunction) { ctx.last_rendered_function_symbol_addr = address; } - Gnu::EmitSymbolMetadata(out, s.indent, symbol); + EmitSymbolMetadata(out, symbol, s); if (symbol.size > 0) { ctx.pending_size.Add(address + symbol.size, symbol.name); } @@ -739,6 +815,7 @@ static bool ApplyFeature(Settings& s, const char *feature_arg) { &Settings::follow_jumps, "follow-jumps" }, { &Settings::walk, "walk" }, { &Settings::symbols, "symbols" }, + { &Settings::dot_size_spec, "dot-size-spec" }, }; constexpr size_t sizeof_no_prefix = (sizeof "no-") - 1; const bool disable = FeatureStringHasPrefixNo(feature_arg); @@ -776,6 +853,7 @@ static void PrintUsage(FILE *s, const char *argv0) " -b, --bfd-target=BFD Specify target object format. Will attempt to detect\n" " automatically if not set. Only `auto`, `binary` and\n" " `elf` are currently supported.\n" + " --sierra-asm68 Produce assembly listing for Sierra ASM68.EXE.\n" " <input_file_name> Binary or elf file with the machine code to disassemble\n" " ('-' means stdin).\n" "Feature flags:\n" @@ -807,6 +885,8 @@ static void PrintUsage(FILE *s, const char *argv0) " traced locations without overcommitting.\n" " symbols Extract and apply symbols from input file if available.\n" " ELF symbols only are currently supported.\n" + " dot-size-spec Use dot to separate mnemonic and size specifier.\n" + " E.g.: \"cmpm.l\" instead of \"cmpml\".\n" , argv0); } @@ -850,6 +930,7 @@ int main(int, char* argv[]) {"feature", 'f', OPTPARSE_REQUIRED}, {"bfd-target", 'b', OPTPARSE_REQUIRED}, {"indent", 80, OPTPARSE_REQUIRED}, + {"sierra-asm68", 82, OPTPARSE_NONE}, {}, }; const char *trace_file_name = nullptr; @@ -895,6 +976,9 @@ int main(int, char* argv[]) return EXIT_FAILURE; } break; + case 82: + s.target_asm = TargetAssembler::kSierraAsm68; + break; case 'f': if (!ApplyFeature(s, options.optarg)) { fprintf(stderr, "main: Error: Unknown feature \"%s\", exiting\n", options.optarg); @@ -929,6 +1013,10 @@ int main(int, char* argv[]) return EXIT_FAILURE; } } + if (s.target_asm != TargetAssembler::kGnuAs) { + // This is a GNU specific feature + s.short_ref_local_labels = false; + } // Parse input file name char *arg; while ((arg = optparse_arg(&options))) { diff --git a/tests/test.bash b/tests/test.bash index 6025908..7292c90 100644 --- a/tests/test.bash +++ b/tests/test.bash @@ -349,10 +349,27 @@ run_test_simple "andib negative to Dn" "\x00\x07\xff\x80" run_test_simple "andiw negative to Dn" "\x00\x45\xff\x80" run_test_simple "andil negative to Dn" "\x00\x83\xff\x80\x00\x00" run_test_simple "addiw zero to (An)+" "\x06\x5a\x00\x00" -run_test_simple "subiw zero from -(An)" "\x06\x62\x00\x00" +run_test_simple "addiw zero from -(An)" "\x06\x62\x00\x00" +run_test_simple "subiw zero from -(An)" "\x04\x62\x00\x00" +# ANDI does not support An (direct address reg) addressing mode. +run_test_expect_short "andi.w #0x3,a2" "\x06\x4a\x00\x03" +# SUBI does not support An (direct address reg) addressing mode. +run_test_expect_short "subi.w #0x3,a2" "\x04\x4a\x00\x03" +run_test_simple "subi.w #0x3,%a2@-" "\x04\x62\x00\x03" +run_test_simple "subi.w #0x4,%a2@+" "\x04\x5a\x00\x04" +run_test_simple "subi.w #0x5,%a2@" "\x04\x52\x00\x05" +run_test_simple "subi.w #0x6,%d2" "\x04\x42\x00\x06" +run_test_simple "andi.w #0x3,%a2@-" "\x04\x62\x00\x03" +run_test_simple "andi.w #0x4,%a2@+" "\x04\x5a\x00\x04" +run_test_simple "andi.w #0x5,%a2@" "\x04\x52\x00\x05" +run_test_simple "subi.w #0x6,%d2" "\x04\x42\x00\x06" run_test_simple "cmpib zero to (An)" "\x0c\x12\x00\x20" run_test_simple "cmpiw zero to (An)" "\x0c\x52\x00\x30" run_test_simple "cmpil zero to (An)" "\x0c\x92\x00\x00\x00\x40" +# PC-relative argument is allowed only for m68020 and higher +run_test_expect_short "cmpib #0x0,%pc@(-2:w)" "\x0c\x3a\x00\x00\xff\xfe" +run_test_expect_short "cmpib #0x0,%pc@(-2,%d4:w)" "\x0c\x3a\x00\x00\x40\xfe" + # From random tests run_test_expect_short "cmpil with invalid opsize" "\x0c\xe4\x26\xa3" @@ -362,6 +379,9 @@ run_test_simple "tas Dn" "\x4a\xc2" run_test_simple "tstb Dn" "\x4a\x02" run_test_simple "tstw Dn" "\x4a\x42" run_test_simple "tstl Dn" "\x4a\x82" +# PC-relative argument is allowed only for m68020 and higher +run_test_expect_short "tstl %pc@(-2:w)" "\x4a\xba\xff\xfe" +run_test_expect_short "tstl %pc@(-2,%d4:w)" "\x4a\xba\x40\xfe" run_test_expect_short "tas (d16,PC)" "\x4a\xfa\xff\xff" run_test_expect_short "tas (d8,PC,Xi)" "\x4a\xfb\x00\x00" @@ -460,6 +480,9 @@ run_test_simple "movel (d8,PC,Xi) to Dn" "\x24\x3b\xa8\x90" run_test_simple "movel #imm to Dn" "\x24\x3c\xa8\x90\x00\x00" run_test_simple "moveal Dn" "\x20\x41" run_test_simple "moveal #imm" "\x20\x7c\xa8\x90\x00\x00" +# Zero displacement "(d16,An)" works for GNU, but won't work for Sierra +run_test_simple "movel %a2@(0),%a5(14024:w)" "\x2b\x6a\x00\x00\x36\xc8" +run_test_simple "movel %a2(14024:w),%a5@(0)" "\x2b\x6a\x36\xc8\x00\x00" # From random tests # diff --git a/tests/test_random.bash b/tests/test_random.bash index 3c7a0d2..60e66a0 100644 --- a/tests/test_random.bash +++ b/tests/test_random.bash @@ -30,7 +30,7 @@ run_test_random() { echo -ne "Test random, pass ${pass_number}... " dd if=/dev/urandom of=${file_orig_bin} bs=1024 count=${blocks_count} >/dev/null 2>&1 ${DISASM} -o ${file_asm} ${file_orig_bin} - ${AS} -o ${file_as_o} ${file_asm} + ${AS} -m68000 -o ${file_as_o} ${file_asm} ${LD} -o ${file_as_elf} ${file_as_o} ${OBJCOPY} ${file_as_elf} -O binary ${file_as_bin} if ! cmp ${file_orig_bin} ${file_as_bin}; then diff --git a/tests/test_sierra.bash b/tests/test_sierra.bash new file mode 100644 index 0000000..8e3be61 --- /dev/null +++ b/tests/test_sierra.bash @@ -0,0 +1,777 @@ +#!/usr/bin/env bash +# +# SPDX-License-Identifier: Unlicense +# +# Tests against examples known to be translated by Sierra ASM68 without any +# problem. + +DISASM="../cmake-build/m68k-disasm --sierra-asm68 -fimm-hex -ffollow-jumps" +TEST_DIR=/tmp/m68k-disasm-tests + +set -e +CRED="\033[31m" +CGREEN="\033[32m" +CRST="\033[39m" + +rm -rf ${TEST_DIR} +mkdir -p ${TEST_DIR} + +run_test() { + local test_name=$1 + local test_name_sanitized=${test_name//[^a-zA-Z0-9_\-]/-} + local data=$2 + local file_orig_bin=${TEST_DIR}/${test_name_sanitized}.orig.bin + local file_orig_asm=${TEST_DIR}/${test_name_sanitized}.orig.asm + local file_asm=${TEST_DIR}/${test_name_sanitized}.asm + echo -ne "Test \"${test_name}\"... " + echo -ne "${data}" >"${file_orig_bin}" + echo -e "\t${test_name}" >"${file_orig_asm}" + # When testing cases with .short literals it sometimes it emits multiple + # lines. Here these additional lines are added to the reference asm file. + shift 2 + for line in "$@"; do + echo -e "\t${line}" >>"${file_orig_asm}" + done + ${DISASM} -o ${file_asm} ${file_orig_bin} + if ! cmp "${file_orig_asm}" "${file_asm}" >/dev/null 2>&1; then + echo -e "${CRED}FAIL${CRST}: expected and output listings do not match" + diff --color=always -u "${file_orig_asm}" "${file_asm}" + elif [[ ! "${test_name}" =~ ".short" ]] && grep ".short" ${file_asm} >/dev/null 2>&1; then + echo -e "${CRED}FAIL${CRST}: .short emitted" + cat ${file_asm} + else + echo -e "${CGREEN}OK${CRST}" + #cat ${file_asm} + fi +} + +# bxxx cmpm +# +run_test "cmpm.b (a0)+,(a0)+" "\xb1\x08" +run_test "cmpm.w (a0)+,(a0)+" "\xb1\x48" +run_test "cmpm.l (a0)+,(a0)+" "\xb1\x88" + +# bxxx eor +# +run_test "eor.b d2,d1" "\xb5\x01" +run_test "eor.b d2,(a1)" "\xb5\x11" +run_test "eor.b d2,(a1)+" "\xb5\x19" +run_test "eor.w d2,-(a1)" "\xb5\x61" +run_test "eor.l d2,0xffff0000.l" "\xb5\xb9\xff\xff\x00\x00" + +# bxxx cmp +# +run_test "cmp.b d1,d2" "\xb4\x01" +# Seemingly cmp.b a1,d2, but "byte operations on address registers are illegal", +# hence it has to be a literal data. +run_test ".short 0xb409" "\xb4\x09" +run_test "cmp.w a1,d2" "\xb4\x49" +run_test "cmp.b (a1),d2" "\xb4\x11" +run_test "cmp.b (a1)+,d2" "\xb4\x19" +run_test "cmp.b -(a1),d2" "\xb4\x21" +# It turns out the pc-relative addressing mode differs from GNU AS when +# displacement is set as literal number. +run_test "cmp.l .+5(pc,a0.w),d0" "\xb0\xbb\x80\x03" +run_test "cmp.l .+5(pc,a0.l),d0" "\xb0\xbb\x88\x03" +run_test "cmp.l .+3(pc,a0.l),d0" "\xb0\xbb\x88\x01" +run_test "cmp.l .+1(pc,a0.l),d0" "\xb0\xbb\x88\xff" +run_test "cmp.l .(pc,a0.l),d0" "\xb0\xbb\x88\xfe" +run_test "cmp.l .-1(pc,a0.l),d0" "\xb0\xbb\x88\xfd" +run_test "cmp.l .-3(pc,a0.l),d0" "\xb0\xbb\x88\xfb" +run_test "cmp.l .-5(pc,a0.l),d0" "\xb0\xbb\x88\xf9" +run_test "cmp.w 0xffff88ff.w,d0" "\xb0\x78\x88\xff" +# Sierra would emit CMPI for "cmp #imm,Xn", so we disassemble it as short +run_test ".short 0xb6bc, 0x44d1, 0xe6e9" "\xb6\xbc\x44\xd1\xe6\xe9" + +# bxxx cmpa +# +run_test "cmpa.w d1,a2" "\xb4\xc1" +run_test "cmpa.l a2,a5" "\xbb\xca" +run_test "cmpa.w (a2)+,a5" "\xba\xda" +run_test "cmpa.l 0x80000000.l,a5" "\xbb\xf9\x80\x00\x00\x00" +run_test "cmpa.w #0x100,a5" "\xba\xfc\x01\x00" +run_test "cmpa.l #0x80000000,a5" "\xbb\xfc\x80\x00\x00\x00" + +# cxxx divu divs +# +run_test "divu.w d6,d1" "\x82\xc6" +run_test "divs.w (a6),d1" "\x83\xd6" +run_test "divu.w (a6)+,d1" "\x82\xde" +run_test "divs.w -(a6),d1" "\x83\xe6" +run_test "divu.w -24576(a6),d1" "\x82\xee\xa0\x00" +run_test "divs.w -16(a6,d6.l),d1" "\x83\xf6\x68\xf0" +run_test "divu.w 0x3000.w,d1" "\x82\xf8\x30\x00" +run_test "divs.w 0x80000000.l,d1" "\x83\xf9\x80\x00\x00\x00" +run_test "divu.w .+1(pc),d1" "\x82\xfa\xff\xff" +run_test "divs.w .+1(pc,a1.w),d1" "\x83\xfb\x90\xff" +run_test "divu.w #0x3000,d1" "\x82\xfc\x30\x00" + +# cxxx mulu muls +# +run_test "mulu.w d6,d1" "\xc2\xc6" +run_test "muls.w (a6),d1" "\xc3\xd6" +run_test "mulu.w (a6)+,d1" "\xc2\xde" +run_test "muls.w -(a6),d1" "\xc3\xe6" +run_test "mulu.w -24576(a6),d1" "\xc2\xee\xa0\x00" +run_test "muls.w -16(a6,d6.l),d1" "\xc3\xf6\x68\xf0" +run_test "mulu.w 0x3000.w,d1" "\xc2\xf8\x30\x00" +run_test "muls.w 0x80000000.l,d1" "\xc3\xf9\x80\x00\x00\x00" +run_test "mulu.w .+1(pc),d1" "\xc2\xfa\xff\xff" +run_test "muls.w .+1(pc,a1.w),d1" "\xc3\xfb\x90\xff" +run_test "mulu.w #0x3000,d1" "\xc2\xfc\x30\x00" + +# cxxx exg +# +run_test "exg.l d6,d1" "\xcd\x41" +run_test "exg.l d6,a1" "\xcd\x89" +run_test "exg.l a6,a1" "\xcd\x49" + +# cxxx and +# +run_test "and.b d1,d2" "\xc4\x01" +# Short because direct address register access mode is forbidden for and.b +run_test ".short 0xc409" "\xc4\x09" +# Short because direct address register access mode is forbidden for and.w +run_test ".short 0xc449" "\xc4\x49" +run_test "and.b (a1),d2" "\xc4\x11" +run_test "and.b (a1)+,d2" "\xc4\x19" +run_test "and.w -(a1),d2" "\xc4\x61" +run_test "and.l .+9(pc,a4.l),d0" "\xc0\xbb\xc8\x07" +# Sierra would emit ANDI for "and #imm,Xn", so we disassemble it as short +run_test ".short 0xc6bc, 0x44d1, 0xe6e9" "\xc6\xbc\x44\xd1\xe6\xe9" + +# cxxx abcd +# +run_test "abcd.b d1,d0" "\xc1\x01" +run_test "abcd.b -(a1),-(a0)" "\xc1\x09" + +# 8xxx sbcd +# +run_test "sbcd.b d1,d0" "\x81\x01" +run_test "sbcd.b -(a1),-(a0)" "\x81\x09" + +# 8xxx or +# +run_test "or.b d1,d2" "\x84\x01" +# Short because direct address register access mode is forbidden for or.b +run_test ".short 0x8409" "\x84\x09" +# Short because direct address register access mode is forbidden for or.w +run_test ".short 0x8449" "\x84\x49" +run_test "or.b (a1),d2" "\x84\x11" +run_test "or.b (a1)+,d2" "\x84\x19" +run_test "or.w -(a1),d2" "\x84\x61" +run_test "or.l .+9(pc,a0.l),d0" "\x80\xbb\x88\x07" +# Sierra would emit ORI for "or #imm,Xn", so we disassemble it as short +run_test ".short 0x86bc, 0x44d1, 0xe6e9" "\x86\xbc\x44\xd1\xe6\xe9" +# swapped register direction seems to be impossible to get legally +run_test ".short 0x8142" "\x81\x42" + +# 48xx nbcd swap pea +# +run_test "swap.w d7" "\x48\x47" +run_test "swap.w d2" "\x48\x42" +run_test "pea.l (a0)" "\x48\x50" +run_test "pea.l -32768(a0)" "\x48\x68\x80\x00" +run_test "pea.l -2(a7,a1.w)" "\x48\x77\x90\xfe" +run_test "pea.l .+32769(pc)" "\x48\x7a\x7f\xff" +run_test "pea.l .(pc,d6.l)" "\x48\x7b\x68\xfe" +run_test "nbcd.b d3" "\x48\x03" +run_test "nbcd.b (a4)" "\x48\x14" +run_test "nbcd.b (a4)+" "\x48\x1c" +run_test "nbcd.b -(a5)" "\x48\x25" +run_test "nbcd.b -32768(a0)" "\x48\x28\x80\x00" +run_test "nbcd.b -2(a7,a1.w)" "\x48\x37\x90\xfe" + +# 48xx ext +# +run_test "ext.w d7" "\x48\x87" +run_test "ext.l d4" "\x48\xc4" + +# exxx asl, asr, lsl, lsr, roxl, roxr, rol, ror +# +run_test "asr.b d1,d2" "\xe2\x22" +run_test "asr.b #0x1,d2" "\xe2\x02" +run_test "asr.b #0x8,d2" "\xe0\x02" +run_test "asl.b #0x7,d2" "\xef\x02" +run_test "asr.w d1,d2" "\xe2\x62" +run_test "asr.l d1,d2" "\xe2\xa2" +run_test "asl.w #0x6,d3" "\xed\x43" +run_test "asl.l #0x5,d3" "\xeb\x83" +run_test "asr.w (a0)" "\xe0\xd0" +run_test "lsr.w (a0)+" "\xe2\xd8" +run_test "roxr.w -(a0)" "\xe4\xe0" +run_test "ror.w 256(a7)" "\xe6\xef\x01\x00" +# Found on random tests for GNU, just let it be here too +run_test "lsr.b d1,d4" "\xe2\x2c" + +# 9xxx subx +# +run_test "subx.b d0,d0" "\x91\x00" +run_test "subx.w d7,d1" "\x93\x47" +run_test "subx.l d6,d2" "\x95\x86" +run_test "subx.b -(a0),-(a0)" "\x91\x08" +run_test "subx.w -(a7),-(a1)" "\x93\x4f" +run_test "subx.l -(a6),-(a2)" "\x95\x8e" + +# 9xxx suba +# +run_test "suba.w d1,a2" "\x94\xc1" +run_test "suba.l a2,a5" "\x9b\xca" +run_test "suba.w (a2)+,a5" "\x9a\xda" +run_test "suba.w #0x100,a5" "\x9a\xfc\x01\x00" +run_test "suba.l #0x80000000,a5" "\x9b\xfc\x80\x00\x00\x00" + +# 9xxx sub +# +run_test "sub.b d1,d2" "\x94\x01" +# Raw instead of "sub.b a1,d2", because "byte operations on address registers +# are illegal" in Sierra +run_test ".short 0x9409" "\x94\x09" +run_test "sub.w a1,d2" "\x94\x49" +run_test "sub.b (a1),d2" "\x94\x11" +run_test "sub.b (a1)+,d2" "\x94\x19" +run_test "sub.b -(a1),d2" "\x94\x21" +# Sierra would emit SUBI for "sub #imm,Xn", so we disassemble it as short +run_test ".short 0x96bc, 0x44d1, 0xe6e9" "\x96\xbc\x44\xd1\xe6\xe9" + +# dxxx addx +# +run_test "addx.b d0,d0" "\xd1\x00" +run_test "addx.w d7,d1" "\xd3\x47" +run_test "addx.l d6,d2" "\xd5\x86" +run_test "addx.b -(a0),-(a0)" "\xd1\x08" +run_test "addx.w -(a7),-(a1)" "\xd3\x4f" +run_test "addx.l -(a6),-(a2)" "\xd5\x8e" + +# dxxx adda +# +run_test "adda.w d1,a2" "\xd4\xc1" +run_test "adda.l a2,a5" "\xdb\xca" +run_test "adda.w (a2)+,a5" "\xda\xda" +run_test "adda.w #0x100,a5" "\xda\xfc\x01\x00" +run_test "adda.l #0x80000000,a5" "\xdb\xfc\x80\x00\x00\x00" + +# dxxx add +# +run_test "add.b d1,d2" "\xd4\x01" +# Short because "byte operations on address registers are illegal" +# Otherwise it could be "add.b a1,d2" +run_test ".short 0xd409" "\xd4\x09" +run_test "add.w a1,d2" "\xd4\x49" +run_test "add.b (a1),d2" "\xd4\x11" +run_test "add.b (a1)+,d2" "\xd4\x19" +run_test "add.b -(a1),d2" "\xd4\x21" +run_test "add.l .+1(pc,a0.l),d0" "\xd0\xbb\x88\xff" +# Sierra would emit addi.l for "add.l #0x44d1e6e9,d6", so we disassemble it as +# short +run_test ".short 0xd6bc, 0x44d1, 0xe6e9" "\xd6\xbc\x44\xd1\xe6\xe9" + +# 4xxx chk.w +# +run_test "chk.w d2,d3" "\x47\x82" +run_test "chk.w (a0),d0" "\x41\x90" +run_test "chk.w (a3)+,d3" "\x47\x9b" +run_test "chk.w -32768(a1),d3" "\x47\xa9\x80\x00" +run_test "chk.w 127(a2,a2.l),d3" "\x47\xb2\xa8\x7f" + +# 4xxx lea.l +# +run_test "lea.l (a0),a0" "\x41\xd0" +run_test "lea.l -32768(a1),a3" "\x47\xe9\x80\x00" +run_test "lea.l 127(a2,a2.l),a3" "\x47\xf2\xa8\x7f" +run_test "lea.l .+32769(pc),a3" "\x47\xfa\x7f\xff" +run_test "lea.l .-126(pc,a2.l),a3" "\x47\xfb\xa8\x80" + +# 0xxx movep +# +run_test "movep.w 160(a3),d0" "\x01\x0b\x00\xa0" +run_test "movep.l 160(a2),d1" "\x03\x4a\x00\xa0" +run_test "movep.w d2,160(a1)" "\x05\x89\x00\xa0" +run_test "movep.l d3,160(a0)" "\x07\xc8\x00\xa0" + +# 0xxx bitwise ops +# +run_test "btst.l #0x6,d7" "\x08\x07\x00\x06" +run_test "btst.b #0x6,(a7)" "\x08\x17\x00\x06" +run_test "btst.b #0x6,0xff000000.l" "\x08\x39\x00\x06\xff\x00\x00\x00" +run_test "btst.b d1,0xff000000.l" "\x03\x39\xff\x00\x00\x00" +run_test "bchg.b d2,0xff000000.l" "\x05\x79\xff\x00\x00\x00" +# Sierra emits a couple of warnings here, but nevertheless produces the same +# expected machine code as GNU AS. The warnings are: +# "warning: argument is out of range: immediate data" +# "warning: argument is out of range: bit number (applied modulo 8)" +run_test "bchg.b #0x79,-11406(a6)" "\x08\x6e\x00\x79\xd3\x72" +run_test "bclr.b d3,0xff000000.l" "\x07\xb9\xff\x00\x00\x00" +run_test "bset.b d4,0xff000000.l" "\x09\xf9\xff\x00\x00\x00" +# This is basically "btst.b #0x1021,0xff000000.l". Sierra says: +# warning: argument is out of range: immediate data +# warning: argument is out of range: bit number (applied modulo 8) +run_test ".short 0x0839, 0x1021, 0xff00, 0x0000" "\x08\x39\x10\x21\xff\x00\x00\x00" + +# 0xxx immediate ops +# +run_test "ori.b #0x0,d0" "\x00\x00\x00\x00" +run_test "ori.b #0x0,ccr" "\x00\x3c\x00\x00" +run_test "ori.b #0x1,ccr" "\x00\x3c\x00\x01" +run_test "ori.b #0x7f,ccr" "\x00\x3c\x00\x7f" +run_test "ori.b #0xffffff80,ccr" "\x00\x3c\x00\x80" +run_test "ori.b #0xffffffff,ccr" "\x00\x3c\x00\xff" +# Sierra says "warning: argument is out of range: immediate data" when you try +# use something greater that 0xff with "ori.b #imm,ccr". Negative values are +# also take up only a lower byte, high byte is always zero from Sierra output. +run_test ".short 0x003c, 0x0100" "\x00\x3c\x01\x00" +run_test ".short 0x003c, 0xff80" "\x00\x3c\xff\x80" +run_test ".short 0x003c, 0xffff" "\x00\x3c\xff\xff" +run_test "ori.w #0x0,sr" "\x00\x7c\x00\x00" +run_test "ori.w #0xa,sr" "\x00\x7c\x00\x0a" +run_test "ori.w #0xffffffff,sr" "\x00\x7c\xff\xff" +run_test "ori.w #0xffffff80,sr" "\x00\x7c\xff\x80" +run_test "andi.w #0xa,sr" "\x02\x7c\x00\x0a" +run_test "eori.w #0xa,sr" "\x0a\x7c\x00\x0a" +run_test "andi.b #0xa,ccr" "\x02\x3c\x00\x0a" +run_test "eori.b #0xa,ccr" "\x0a\x3c\x00\x0a" +run_test "ori.b #0xa,d7" "\x00\x07\x00\x0a" +run_test "ori.w #0xa,d5" "\x00\x45\x00\x0a" +run_test "ori.l #0xa,d3" "\x00\x83\x00\x00\x00\x0a" +run_test "ori.b #0xffffff80,d7" "\x00\x07\x00\x80" +# Same here for "ori.w" with "warning: argument is out of range: immediate data" +run_test ".short 0x0007, 0xff80" "\x00\x07\xff\x80" +run_test "ori.w #0xffffff80,d5" "\x00\x45\xff\x80" +run_test "ori.l #0xff800000,d3" "\x00\x83\xff\x80\x00\x00" +run_test "addi.w #0x0,(a2)+" "\x06\x5a\x00\x00" +run_test "addi.w #0x0,-(a2)" "\x06\x62\x00\x00" +run_test "subi.w #0x0,(a2)+" "\x04\x5a\x00\x00" +run_test "subi.w #0x0,-(a2)" "\x04\x62\x00\x00" +# SUBI does not support An (direct address reg) addressing mode. +run_test ".short 0x044a" "\x04\x4a\x00\x12" \ + ".short 0x0012" +# ANDI does not support An (direct address reg) addressing mode. +run_test ".short 0x064a" "\x06\x4a\x00\x12" \ + ".short 0x0012" +# Small (less then or equal to 8) nonzero positive immediate values cause Sierra +# to produce SUBQ instead of SUBI and ADDQ instead of ADDI. +# +# This is "addi.w #0x4,(a2)+" +run_test ".short 0x065a, 0x0004" "\x06\x5a\x00\x04" +# This is "addi.w #0x3,-(a2)" +run_test ".short 0x0662, 0x0003" "\x06\x62\x00\x03" +# This is "addi.w #0x5,(a2)" +run_test ".short 0x0652, 0x0005" "\x06\x52\x00\x05" +# This is "addi.w #0x6,d2" +run_test ".short 0x0642, 0x0006" "\x06\x42\x00\x06" +# This is "subi.w #0x4,(a2)+" +run_test ".short 0x045a, 0x0004" "\x04\x5a\x00\x04" +# This is "subi.w #0x3,-(a2)" +run_test ".short 0x0462, 0x0003" "\x04\x62\x00\x03" +# This is "subi.w #0x5,(a2)" +run_test ".short 0x0452, 0x0005" "\x04\x52\x00\x05" +# This is "subi.w #0x6,d2" +run_test ".short 0x0442, 0x0006" "\x04\x42\x00\x06" +run_test "cmpi.b #0x20,(a2)" "\x0c\x12\x00\x20" +run_test "cmpi.w #0x30,(a2)" "\x0c\x52\x00\x30" +run_test "cmpi.l #0x40,(a2)" "\x0c\x92\x00\x00\x00\x40" +# When given "cmpi.b #0x0,.(pc)" or "cmpi.b #0x0,.(pc,d4)" Sierra says: +# "illegal addressing mode: pc-relative indirect" +run_test ".short 0x0c3a, 0x0000, 0xfffe" "\x0c\x3a\x00\x00\xff\xfe" +run_test ".short 0x0c3a, 0x0000, 0x40fe" "\x0c\x3a\x00\x00\x40\xfe" +# From random tests: cmpi.? with invalid opsize 0b11 +run_test ".short 0x0ce4" "\x0c\xe4\xff\xff" \ + ".short 0xffff" + +# 4axx +# +run_test "tas.b d2" "\x4a\xc2" +run_test "tst.b d2" "\x4a\x02" +run_test "tst.w d2" "\x4a\x42" +run_test "tst.l d2" "\x4a\x82" +# When given "tst.l .(pc)" or "tst.l .(pc,d4)" Sierra says: +# "illegal addressing mode: pc-relative indirect" +run_test ".short 0x4aba, 0xfffe" "\x4a\xba\xff\xfe" +run_test ".short 0x4aba, 0x40fe" "\x4a\xba\x40\xfe" + +# For "tas .+1(pc)" Sierra says: "illegal addressing mode: pc-relative indirect" +run_test ".short 0x4afa" "\x4a\xfa\xff\xff" \ + ".short 0xffff" +# For "tas.b .+1(pc,a0.l)" Sierra says: +# "illegal addressing mode: pc-relative indirect" +run_test ".short 0x4afb" "\x4a\xfb\x88\xff" \ + ".short 0x88ff" + +# 4xxx +# +run_test "negx.b d4" "\x40\x04" +run_test "clr.b d5" "\x42\x05" +run_test "neg.b d6" "\x44\x06" +run_test "not.b d7" "\x46\x07" +run_test "negx.w d4" "\x40\x44" +run_test "clr.w d5" "\x42\x45" +run_test "neg.w d6" "\x44\x46" +run_test "not.w d7" "\x46\x47" +run_test "negx.l d4" "\x40\x84" +run_test "clr.l d5" "\x42\x85" +run_test "neg.l d6" "\x44\x86" +run_test "not.l d7" "\x46\x87" + +# 4e4x +# +run_test "trap #0x0" "\x4e\x40" +run_test "trap #0x8" "\x4e\x48" +run_test "trap #0xf" "\x4e\x4f" + +# 4e5x +# +run_test "link.w a2,#0x100" "\x4e\x52\x01\x00" +run_test "link.w a2,#0xffffffff" "\x4e\x52\xff\xff" +run_test "link.w a2,#0xffff8000" "\x4e\x52\x80\x00" +run_test "unlk a2" "\x4e\x5a" + +# 4e6x +# +run_test "move.l a2,usp" "\x4e\x62" +run_test "move.l usp,a7" "\x4e\x6f" + +# 4xxx +# +run_test "move.w sr,d1" "\x40\xc1" +run_test "move.w d3,sr" "\x46\xc3" +# For some reason move to CCR is word only instruction, but CCR is byte sized +# register. +run_test "move.w d2,ccr" "\x44\xc2" + +# 70xx / 72xx/ 74xx / 76xx / 78xx / 7axx / 7cxx / 7exx +# +run_test "moveq.l #0x0,d0" "\x70\x00" +run_test "moveq.l #0x1,d2" "\x74\x01" +run_test "moveq.l #0x7f,d7" "\x7e\x7f" +run_test "moveq.l #0xffffffff,d5" "\x7a\xff" +run_test "moveq.l #0xffffff80,d1" "\x72\x80" + +# 1xxx [xxxx [xxxx]] +# +run_test "move.b d1,d0" "\x10\x01" +# For "move.b a1,d0" Sierra says: +# "byte operations on address registers are illegal" +run_test ".short 0x1009" "\x10\x09" +run_test "move.b (a1),d0" "\x10\x11" +run_test "move.b (a1)+,d0" "\x10\x19" +run_test "move.b -(a1),d0" "\x10\x21" +run_test "move.b -789(a1),d0" "\x10\x29\xfc\xeb" +run_test "move.b 112(a1,a1.l),d0" "\x10\x31\x98\x70" +run_test "move.b 0xffff9870.w,d0" "\x10\x38\x98\x70" +run_test "move.b 0x30303070.l,d0" "\x10\x39\x30\x30\x30\x70" +run_test "move.b .-787(pc),d0" "\x10\x3a\xfc\xeb" +run_test "move.b .+114(pc,a2.l),d0" "\x10\x3b\xa8\x70" +run_test "move.b #0xffffffff,d0" "\x10\x3c\x00\xff" +# For "move.b #0x7fff,d0" Sierra says: +# "warning: argument is out of range: immediate data" +# And then places zeros in the high byte, instead of the "7f" part. So it is +# impossible to get this sequence from a legal instruction. +run_test ".short 0x103c, 0x7fff" "\x10\x3c\x7f\xff" + +# 3xxx [xxxx [xxxx]] +# +run_test "move.w d2,d7" "\x3e\x02" +run_test "move.w a2,d7" "\x3e\x0a" +run_test "move.w (a2),d7" "\x3e\x12" +run_test "move.w (a2)+,d0" "\x30\x1a" +run_test "move.w -(a2),d0" "\x30\x22" +run_test "move.w 16383(a2),d0" "\x30\x2a\x3f\xff" +run_test "move.w -128(a2,a1.w),d0" "\x30\x32\x90\x80" +run_test "move.w 0xffff9080.w,d0" "\x30\x38\x90\x80" +run_test "move.w 0xaaaaaaaa.l,d0" "\x30\x39\xaa\xaa\xaa\xaa" +run_test "move.w .+16385(pc),d0" "\x30\x3a\x3f\xff" +run_test "move.w .-126(pc,a2.w),d0" "\x30\x3b\xa0\x80" +run_test "move.w #0xffffa5a5,d0" "\x30\x3c\xa5\xa5" +run_test "movea.w d1,a0" "\x30\x41" +run_test "movea.w #0xffffa890,a0" "\x30\x7c\xa8\x90" + +# 2xxx [xxxx [xxxx]] +# +run_test "move.l d5,d2" "\x24\x05" +run_test "move.l a5,d2" "\x24\x0d" +run_test "move.l (a5),d2" "\x24\x15" +run_test "move.l (a5)+,d2" "\x24\x1d" +run_test "move.l -(a5),d2" "\x24\x25" +run_test "move.l 30752(a5),d2" "\x24\x2d\x78\x20" +run_test "move.l -112(a5,a1.l),d2" "\x24\x35\x98\x90" +run_test "move.l 0x7890.w,d2" "\x24\x38\x78\x90" +run_test "move.l 0x7890.l,d2" "\x24\x39\x00\x00\x78\x90" +run_test "move.l .+30754(pc),d2" "\x24\x3a\x78\x20" +run_test "move.l .-110(pc,a2.l),d2" "\x24\x3b\xa8\x90" +run_test "move.l #0xa8900000,d2" "\x24\x3c\xa8\x90\x00\x00" +run_test "movea.l d1,a0" "\x20\x41" +run_test "movea.l #0xa8900000,a0" "\x20\x7c\xa8\x90\x00\x00" +# Sierra interprets "move.l 0(a2),14024(a5)" as "move.l (a2),14024(a5)" and +# won't produce displacement in "(d16,An)" situation if it is zero. +run_test ".short 0x2b6a, 0x0000, 0x36c8" "\x2b\x6a\x00\x00\x36\xc8" +run_test ".short 0x2b6a, 0x36c8, 0x0000" "\x2b\x6a\x36\xc8\x00\x00" +exit + +# From random tests of GNU, so let it be here too +# +run_test "move.l .-14(pc,a0.l),(a3)+" "\x26\xfb\x88\xf0\x4e\x71" \ + "nop" + +# 4890 xxx +# +run_test "movem.w d0,(a0)" "\x48\x90\x00\x01" +run_test "movem.w d0-d1/a0-a1,(a0)" "\x48\x90\x03\x03" +run_test "movem.l d0-d1/d3-d4/d6-d7/a1-a2/a4-a5/a7,(a0)" "\x48\xd0\xb6\xdb" +run_test "movem.w d0/d2/d4/d6/a0/a2/a4/a6,(a0)" "\x48\x90\x55\x55" +run_test "movem.l d1/d3/d5/d7/a1/a3/a5/a7,(a0)" "\x48\xd0\xaa\xaa" +run_test "movem.l d0-d7/a0-a7,(a0)" "\x48\xd0\xff\xff" +run_test "movem.w d0-d7/a0-a7,-(a0)" "\x48\xa0\xff\xff" +run_test "movem.l d0-d7/a0-a7,12317(a0)" "\x48\xe8\xff\xff\x30\x1d" +run_test "movem.w d0-d7/a0-a7,10(a7,d4.l)" "\x48\xb7\xff\xff\x48\x0a" +run_test "movem.l d0-d7/a0-a7,0xffff8010.w" "\x48\xf8\xff\xff\x80\x10" +run_test "movem.w d0-d7/a0-a7,0x7ff0.l" "\x48\xb9\xff\xff\x00\x00\x7f\xf0" +run_test "movem.w (a0),d0-d7/a0-a7" "\x4c\x90\xff\xff" +run_test "movem.l (a0)+,d0-d7/a0-a7" "\x4c\xd8\xff\xff" +run_test "movem.w 12317(a0),d0-d7/a0-a7" "\x4c\xa8\xff\xff\x30\x1d" +run_test "movem.l 10(a7,d4.l),d0-d7/a0-a7" "\x4c\xf7\xff\xff\x48\x0a" +run_test "movem.w 0xffff8010.w,d0-d7/a0-a7" "\x4c\xb8\xff\xff\x80\x10" +run_test "movem.l 0x7ff0.l,d0-d7/a0-a7" "\x4c\xf9\xff\xff\x00\x00\x7f\xf0" +fi +run_test "movem.w .-13978(pc),d2/d4/d7/a0-a2/a5-a6" "\x4c\xba\x67\x94\xc9\x62" + +# From random tests of GNU, so let it be here too +# "movem.w ???,(a2)" trucated +run_test ".short 0x4892" "\x48\x92" + +# 5x38 / 5x78 / 5xb8 (xxx).W +# +run_test "addq.b #0x8,0x73.w" "\x50\x38\x00\x73" +run_test "addq.l #0x4,0xffff8014.w" "\x58\xb8\x80\x14" + +# 5x39 / 5x79 / 5xb9 (xxx).L +# +run_test "addq.w #0x5,0x18fc0000.l" "\x5a\x79\x18\xfc\x00\x00" +run_test "addq.l #0x1,0xf1000001.l" "\x52\xb9\xf1\x00\x00\x01" + +# 5x30..5x37 / 5x70..5x77 / 5xb0..5xb7, (d16, An, Xi), Brief Extension Word +# +run_test "addq.b #0x8,115(a7,d0.w)" "\x50\x37\x00\x73" +run_test "addq.w #0x5,-4(a2,d1.l)" "\x5a\x72\x18\xfc" +run_test "addq.l #0x1,-127(a3,a3.w)" "\x52\xb3\xb0\x81" + +# 5x28..5x2f / 5x68..5x6f / 5xa8..5xaf, (d16, An), Displacement Word +# +run_test "addq.b #0x8,128(a7)" "\x50\x2f\x00\x80" +run_test "addq.w #0x5,-772(a2)" "\x5a\x6a\xfc\xfc" +run_test "addq.l #0x1,-1(a3)" "\x52\xab\xff\xff" + +# 5x20..5x27 / 5x60..5x67 / 5xa0..5xa7, -(An) +# +run_test "addq.b #0x8,-(a7)" "\x50\x27" +run_test "addq.w #0x5,-(a2)" "\x5a\x62" +run_test "addq.l #0x1,-(a3)" "\x52\xa3" + +# 5x18..5x1f / 5x58..5x5f / 5x98..5x9f, (An)+ +# +run_test "addq.b #0x8,(a7)+" "\x50\x1f" +run_test "addq.w #0x5,(a2)+" "\x5a\x5a" +run_test "addq.l #0x1,(a5)+" "\x52\x9d" + +# 5x10..5x17 / 5x50..5x57 / 5x90..5x97, (An) +# +run_test "addq.b #0x8,(a7)" "\x50\x17" +run_test "addq.w #0x5,(a2)" "\x5a\x52" +run_test "addq.l #0x1,(a3)" "\x52\x93" + +# 5x08..5x0f / 5x48..5x4f / 5x88..5x8f, An +# +# NOTE: addq.b with An does not exits +run_test "addq.w #0x6,a7" "\x5c\x4f" +run_test "addq.l #0x1,a5" "\x52\x8d" + +# 5x00..5x07 / 5x40..5x47 / 5x80..5x87, Dn +# +run_test "addq.b #0x8,d7" "\x50\x07" +run_test "addq.w #0x5,d2" "\x5a\x42" +run_test "addq.l #0x1,d3" "\x52\x83" + +# 50f9 xxxx +# +run_test "sf.b 0x74.l" "\x51\xf9\x00\x00\x00\x74" +run_test "sf.b 0xc0febabe.l" "\x51\xf9\xc0\xfe\xba\xbe" + +# 50f8 xxxx +# +run_test "sf.b 0x66.w" "\x51\xf8\x00\x66" +run_test "sf.b 0xffff80c4.w" "\x51\xf8\x80\xc4" + +# 51f0 xxxx +# +run_test "sf.b 4(a4,a3.w)" "\x51\xf4\xb0\x04" +run_test "sf.b -14(a3,d6.w)" "\x51\xf3\x60\xf2" + +# 5fe8 xxxx +# +run_test "sle.b 160(a0)" "\x5f\xe8\x00\xa0" +run_test "sle.b -7166(a0)" "\x5f\xe8\xe4\x02" + +# 5ee1 +# +run_test "sgt.b -(a1)" "\x5e\xe1" + +# 56df +# +run_test "sne.b (a7)+" "\x56\xdf" + +# 5dd3 +# +run_test "slt.b (a3)" "\x5d\xd3" + +# 57cx +# +run_test "seq.b d1" "\x57\xc1" + +# 50cf xxxx +# +run_test "dbt.w d7,.-2" "\x50\xcf\xff\xfc" +run_test "dbt.w d7,.+266" "\x50\xcf\x01\x08" +run_test "dbt.w d7,.+2" "\x50\xcf\x00\x00" +# When given "dbt.w d7,.+3" Sierra says: +# "branch to/from odd address: destination address is odd" +run_test ".short 0x50cf, 0x0001" "\x50\xcf\x00\x01" + +# 50c9 7ffe +# +# From random tests +run_test "dbt.w d1,.+32768" "\x50\xc9\x7f\xfe" + +# 60xx +# +run_test "bra.s .-2" "\x60\xfc" +run_test "bra.s ." "\x60\xfe" +run_test "bra.s .+10" "\x60\x08" +# When given "bra.s .+2" Sierra says: +# "short branch to next instruction is illegal" +run_test ".short 0x6000" "\x60\x00" +# When given "bra.s .+3" Sierra says: +# "branch to/from odd address: destination address is odd" +run_test ".short 0x6001" "\x60\x01" + +# 60xx (xxxx) +# +run_test "bra.w .-2000" "\x60\x00\xf8\x2e" +run_test "bra.w .+1000" "\x60\x00\x03\xe6" +# When given "bra.w .+2" Sierra says: +# "short branch to next instruction is illegal" +run_test ".short 0x6000, 0x0000" "\x60\x00\x00\x00" +# When given "bra.w .+3" Sierra says: +# "branch to/from odd address: destination address is odd" +run_test ".short 0x6000, 0x0001" "\x60\x00\x00\x01" + +# 61xx (xxxx) +# +run_test "bsr.s .-118" "\x61\x88" +run_test "bsr.w .+1000" "\x61\x00\x03\xe6" + +# 6xxx +# +run_test "bhi.s .+12" "\x62\x0a" +run_test "bls.s .+12" "\x63\x0a" +run_test "bcc.s .+12" "\x64\x0a" +run_test "bcs.s .+12" "\x65\x0a" +run_test "bne.s .+12" "\x66\x0a" +run_test "beq.s .+12" "\x67\x0a" +run_test "bvc.s .+12" "\x68\x0a" +run_test "bvs.s .+12" "\x69\x0a" +run_test "bpl.s .+12" "\x6a\x0a" +run_test "bmi.s .+12" "\x6b\x0a" +run_test "bge.s .+12" "\x6c\x0a" +run_test "blt.s .+12" "\x6d\x0a" +run_test "bgt.s .+12" "\x6e\x0a" +run_test "ble.s .+12" "\x6f\x0a" + +# 4afc +# +# reset +# +run_test "illegal" "\x4a\xfc" + +# 4e70 +# +# reset +run_test "reset" "\x4e\x70" + +# 4e71 +# +# nop +run_test "nop" "\x4e\x71" + +# 4e72 xxxx +# +run_test "stop #0x8" "\x4e\x72\x00\x08" +run_test "stop #0xffffffff" "\x4e\x72\xff\xff" + +# 4e73 +# +# rte +run_test "rte" "\x4e\x73" + +# 4e75 +# +# rts +run_test "rts" "\x4e\x75" + +# 4e76 +# +# trapv +run_test "trapv" "\x4e\x76" + +# 4e77 +# +# rtr +run_test "rtr" "\x4e\x77" + +# 4e90..4e97 +# +run_test "jsr (a1)" "\x4e\x91" + +# (4ea8..4eaf) xxxx +# +run_test "jsr 0(a0)" "\x4e\xa8\x00\x00" +run_test "jsr 10(a1)" "\x4e\xa9\x00\x0a" +run_test "jsr -32753(a2)" "\x4e\xaa\x80\x0f" + +# (4eb0..4eb7) xxxx +# +run_test "jsr 15(a1,d0.w)" "\x4e\xb1\x00\x0f" +run_test "jsr -16(a0,d0.w)" "\x4e\xb0\x00\xf0" +run_test "jsr 0(a0,d0.w)" "\x4e\xb0\x00\x00" +run_test "jsr 10(a0,a0.w)" "\x4e\xb0\x80\x0a" +run_test "jsr 12(a0,d0.l)" "\x4e\xb0\x08\x0c" +run_test "jsr -80(a0,d0.l)" "\x4e\xb0\x08\xb0" +run_test "jsr 15(a0,d1.w)" "\x4e\xb0\x10\x0f" +run_test "jsr 17(a2,a1.w)" "\x4e\xb2\x90\x11" + +# 4eb8 xxxx Word displacement +# +run_test "jsr 0x0.w" "\x4e\xb8\x00\x00" +run_test "jsr 0x1f.w" "\x4e\xb8\x00\x1f" +run_test "jsr 0xffff8a0c.w" "\x4e\xb8\x8a\x0c" + +# 4eb9 xxxx Long displacement +# +run_test "jsr 0x0.l" "\x4e\xb9\x00\x00\x00\x00" +run_test "jsr 0x10bb431f.l" "\x4e\xb9\x10\xbb\x43\x1f" +run_test "jsr 0x80ccd98a.l" "\x4e\xb9\x80\xcc\xd9\x8a" + +# 4eba xxxx +# +run_test "jsr .+2(pc)" "\x4e\xba\x00\x00" +run_test "jsr .+33(pc)" "\x4e\xba\x00\x1f" +run_test "jsr .-30194(pc)" "\x4e\xba\x8a\x0c" + +# 4ebb xxxx +# +run_test "jsr .-14(pc,d0.w)" "\x4e\xbb\x00\xf0" +run_test "jsr .+2(pc,d0.w)" "\x4e\xbb\x00\x00" +run_test "jsr .+12(pc,a0.w)" "\x4e\xbb\x80\x0a" +run_test "jsr .+14(pc,d0.l)" "\x4e\xbb\x08\x0c" +run_test "jsr .-78(pc,d0.l)" "\x4e\xbb\x08\xb0" + +run_test "jsr .+17(pc,d1.w)" "\x4e\xbb\x10\x0f" +run_test "jsr .+19(pc,a1.w)" "\x4e\xbb\x90\x11" |