2017-12-29 05:47:36 +00:00
|
|
|
#include <criterion/criterion.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
2017-12-31 05:02:30 +00:00
|
|
|
#include "mos6502.h"
|
2017-12-29 05:47:36 +00:00
|
|
|
#include "mos6502.dis.h"
|
|
|
|
#include "mos6502.enums.h"
|
|
|
|
|
2017-12-29 21:55:25 +00:00
|
|
|
/*
|
|
|
|
* BUFSIZ is the normal block-buffer size that a FILE stream would use
|
|
|
|
* (possibly amongst other things).
|
|
|
|
*/
|
|
|
|
static char buf[BUFSIZ];
|
2017-12-29 05:47:36 +00:00
|
|
|
|
2017-12-29 21:55:25 +00:00
|
|
|
/*
|
|
|
|
* This is the file stream we will be using to write our disassembly
|
|
|
|
* code into.
|
|
|
|
*/
|
2017-12-29 05:47:36 +00:00
|
|
|
static FILE *stream = NULL;
|
2017-12-31 05:02:30 +00:00
|
|
|
static mos6502 *cpu = NULL;
|
2017-12-31 04:50:23 +00:00
|
|
|
|
2017-12-29 05:47:36 +00:00
|
|
|
|
|
|
|
static void
|
|
|
|
setup()
|
|
|
|
{
|
2017-12-29 21:55:25 +00:00
|
|
|
// Ok, so...there's some...trickery going on here. As you might
|
|
|
|
// guess by the file path being /dev/null.
|
|
|
|
stream = fopen("/dev/null", "w");
|
2017-12-29 05:47:36 +00:00
|
|
|
if (stream == NULL) {
|
|
|
|
perror("Could not open temporary file for mos6502 disassembly tests");
|
|
|
|
}
|
|
|
|
|
2017-12-29 21:55:25 +00:00
|
|
|
// The C standard library allows us to set an arbitrary buffer for a
|
|
|
|
// file stream. It also allows us to fully buffer the file stream,
|
|
|
|
// which means nothing is written to file until an fflush() or an
|
|
|
|
// fclose() is called (or something else I can't think of). So we
|
|
|
|
// can use the FILE abstraction to write our disassembly results
|
|
|
|
// into, but use an underlying string buffer that we can easily
|
|
|
|
// check with Criterion. Uh, unless we blow out the buffer size...
|
|
|
|
// don't do that :D
|
2017-12-29 05:47:36 +00:00
|
|
|
setvbuf(stream, buf, _IOFBF, 256);
|
2017-12-31 04:50:23 +00:00
|
|
|
|
2017-12-31 05:02:30 +00:00
|
|
|
cpu = mos6502_create();
|
2017-12-29 05:47:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
teardown()
|
|
|
|
{
|
|
|
|
fclose(stream);
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_free(cpu);
|
2017-12-29 05:47:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
assert_buf(const char *str)
|
|
|
|
{
|
2017-12-29 21:55:25 +00:00
|
|
|
// This will set the cursor position in the file back to the start
|
|
|
|
// of the file stream.
|
2017-12-29 05:47:36 +00:00
|
|
|
rewind(stream);
|
2017-12-29 21:55:25 +00:00
|
|
|
|
|
|
|
// Our actual assertion. The downside to doing it this way is that
|
|
|
|
// when Criterion flags an assertion failure, it'll highlight _this_
|
|
|
|
// line in the file, not in the test. It might be worth macroifying
|
|
|
|
// this code.
|
2017-12-29 05:47:36 +00:00
|
|
|
cr_assert_str_eq(buf, str);
|
2017-12-29 21:55:25 +00:00
|
|
|
|
|
|
|
// We're not sure what previous tests may have run, and where NUL
|
|
|
|
// characters were set therein, so to be safe we wipe out the full
|
|
|
|
// contents of the test buffer after every test.
|
|
|
|
memset(buf, 0, BUFSIZ);
|
2017-12-29 05:47:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TestSuite(mos6502_dis, .init = setup, .fini = teardown);
|
|
|
|
|
|
|
|
Test(mos6502_dis, operand)
|
|
|
|
{
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, ABS, 0x1234);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("$1234");
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, ABX, 0x1234);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("$1234,X");
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, ABY, 0x1234);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("$1234,Y");
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, IMM, 0x12);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("#$12");
|
2017-12-29 23:08:25 +00:00
|
|
|
|
2018-01-09 22:24:51 +00:00
|
|
|
vm_segment_set(cpu->memory, 0x1234, 0x48);
|
|
|
|
vm_segment_set(cpu->memory, 0x1235, 0x34);
|
2017-12-31 04:50:23 +00:00
|
|
|
|
2017-12-29 23:08:25 +00:00
|
|
|
// For JMPs and JSRs (and BRKs), this should be a label and not a
|
|
|
|
// literal value. So we need to test both the literal and
|
|
|
|
// jump-labeled version.
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, IND, 0x1234);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("($1234)");
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_jump_label(cpu, 0x1234, 0, IND);
|
|
|
|
mos6502_dis_operand(cpu, stream, 0, IND, 0x1234);
|
2018-01-09 22:24:51 +00:00
|
|
|
assert_buf("ADDR_3448");
|
2017-12-29 23:08:25 +00:00
|
|
|
|
|
|
|
// Let's undo our label above...
|
|
|
|
mos6502_dis_jump_unlabel(0x1234);
|
|
|
|
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, IDX, 0x12);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("($12,X)");
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, IDY, 0x34);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("($34),Y");
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, ZPG, 0x34);
|
2018-01-09 22:24:51 +00:00
|
|
|
assert_buf("$34 ");
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, ZPX, 0x34);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("$34,X");
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, ZPY, 0x34);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("$34,Y");
|
|
|
|
|
|
|
|
// These should both end up printing nothing
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, ACC, 0);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("");
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 0, IMP, 0);
|
2017-12-29 05:47:36 +00:00
|
|
|
assert_buf("");
|
|
|
|
|
2017-12-29 23:08:25 +00:00
|
|
|
// Test a forward jump (operand < 128)
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 500, REL, 52);
|
2018-01-09 22:24:51 +00:00
|
|
|
assert_buf("ADDR_228");
|
2017-12-29 23:08:25 +00:00
|
|
|
|
|
|
|
// Test a backward jump (operand >= 128)
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_operand(cpu, stream, 500, REL, 152);
|
2018-01-09 22:24:51 +00:00
|
|
|
assert_buf("ADDR_18c");
|
2017-12-29 05:47:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Test(mos6502_dis, instruction)
|
|
|
|
{
|
|
|
|
#define TEST_INST(x) \
|
|
|
|
mos6502_dis_instruction(stream, x); \
|
|
|
|
assert_buf(#x)
|
|
|
|
|
|
|
|
TEST_INST(ADC);
|
|
|
|
TEST_INST(AND);
|
|
|
|
TEST_INST(ASL);
|
|
|
|
TEST_INST(BCC);
|
|
|
|
TEST_INST(BCS);
|
|
|
|
TEST_INST(BEQ);
|
|
|
|
TEST_INST(BIT);
|
|
|
|
TEST_INST(BMI);
|
|
|
|
TEST_INST(BNE);
|
|
|
|
TEST_INST(BPL);
|
|
|
|
TEST_INST(BRK);
|
|
|
|
TEST_INST(BVC);
|
|
|
|
TEST_INST(BVS);
|
|
|
|
TEST_INST(CLC);
|
|
|
|
TEST_INST(CLD);
|
|
|
|
TEST_INST(CLI);
|
|
|
|
TEST_INST(CLV);
|
|
|
|
TEST_INST(CMP);
|
|
|
|
TEST_INST(CPX);
|
|
|
|
TEST_INST(CPY);
|
|
|
|
TEST_INST(DEC);
|
|
|
|
TEST_INST(DEX);
|
|
|
|
TEST_INST(DEY);
|
|
|
|
TEST_INST(EOR);
|
|
|
|
TEST_INST(INC);
|
|
|
|
TEST_INST(INX);
|
|
|
|
TEST_INST(INY);
|
|
|
|
TEST_INST(JMP);
|
|
|
|
TEST_INST(JSR);
|
|
|
|
TEST_INST(LDA);
|
|
|
|
TEST_INST(LDX);
|
|
|
|
TEST_INST(LDY);
|
|
|
|
TEST_INST(LSR);
|
|
|
|
TEST_INST(NOP);
|
|
|
|
TEST_INST(ORA);
|
|
|
|
TEST_INST(PHA);
|
|
|
|
TEST_INST(PHP);
|
|
|
|
TEST_INST(PLA);
|
|
|
|
TEST_INST(PLP);
|
|
|
|
TEST_INST(ROL);
|
|
|
|
TEST_INST(ROR);
|
|
|
|
TEST_INST(RTI);
|
|
|
|
TEST_INST(RTS);
|
|
|
|
TEST_INST(SBC);
|
|
|
|
TEST_INST(SEC);
|
|
|
|
TEST_INST(SED);
|
|
|
|
TEST_INST(SEI);
|
|
|
|
TEST_INST(STA);
|
|
|
|
TEST_INST(STX);
|
|
|
|
TEST_INST(STY);
|
|
|
|
TEST_INST(TAX);
|
|
|
|
TEST_INST(TAY);
|
|
|
|
TEST_INST(TSX);
|
|
|
|
TEST_INST(TXA);
|
|
|
|
TEST_INST(TXS);
|
|
|
|
TEST_INST(TYA);
|
|
|
|
}
|
|
|
|
|
|
|
|
Test(mos6502_dis, expected_bytes)
|
|
|
|
{
|
|
|
|
#define TEST_BYTES(x, y) \
|
|
|
|
cr_assert_eq(mos6502_dis_expected_bytes(x), y)
|
|
|
|
|
|
|
|
TEST_BYTES(ACC, 0);
|
|
|
|
TEST_BYTES(ABS, 2);
|
|
|
|
TEST_BYTES(ABX, 2);
|
|
|
|
TEST_BYTES(ABY, 2);
|
|
|
|
TEST_BYTES(IMM, 1);
|
|
|
|
TEST_BYTES(IMP, 0);
|
|
|
|
TEST_BYTES(IND, 2);
|
|
|
|
TEST_BYTES(IDX, 1);
|
|
|
|
TEST_BYTES(IDY, 1);
|
|
|
|
TEST_BYTES(REL, 1);
|
|
|
|
TEST_BYTES(ZPG, 1);
|
|
|
|
TEST_BYTES(ZPX, 1);
|
|
|
|
TEST_BYTES(ZPY, 1);
|
|
|
|
}
|
|
|
|
|
2017-12-29 21:31:05 +00:00
|
|
|
Test(mos6502_dis, opcode)
|
2017-12-29 05:47:36 +00:00
|
|
|
{
|
|
|
|
int bytes;
|
|
|
|
|
2017-12-31 05:02:30 +00:00
|
|
|
vm_segment_set(cpu->memory, 0, 0x29); // AND (imm)
|
|
|
|
vm_segment_set(cpu->memory, 1, 0x38);
|
2017-12-29 05:47:36 +00:00
|
|
|
|
2017-12-31 05:02:30 +00:00
|
|
|
bytes = mos6502_dis_opcode(cpu, stream, 0);
|
2018-01-09 22:24:51 +00:00
|
|
|
assert_buf(" AND #$38 ; pc=$0000 cy=02: 29 38\n");
|
2017-12-29 05:47:36 +00:00
|
|
|
cr_assert_eq(bytes, 2);
|
|
|
|
}
|
2017-12-29 21:31:05 +00:00
|
|
|
|
|
|
|
Test(mos6502_dis, scan)
|
|
|
|
{
|
2017-12-31 05:02:30 +00:00
|
|
|
vm_segment_set(cpu->memory, 0, 0x29); // AND (imm)
|
|
|
|
vm_segment_set(cpu->memory, 1, 0x38);
|
|
|
|
vm_segment_set(cpu->memory, 2, 0x88); // DEY (imp)
|
|
|
|
vm_segment_set(cpu->memory, 3, 0x6C); // JMP (ind)
|
2018-01-09 22:24:51 +00:00
|
|
|
vm_segment_set(cpu->memory, 4, 0x34);
|
|
|
|
vm_segment_set(cpu->memory, 5, 0x12);
|
2017-12-29 21:31:05 +00:00
|
|
|
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_scan(cpu, stream, 0, 6);
|
2017-12-29 21:31:05 +00:00
|
|
|
|
2018-01-09 22:24:51 +00:00
|
|
|
// FIXME: scan does not currently advance the PC byte; _should_ it?
|
|
|
|
// I'm honestly not sure. There are definitely times (e.g. during
|
|
|
|
// runtime operation) when you don't want it to, but as a standalone
|
|
|
|
// disassembler, it feels less useful when PC isn't emulated.
|
|
|
|
assert_buf(" AND #$38 ; pc=$0000 cy=02: 29 38\n"
|
|
|
|
" DEY ; pc=$0000 cy=02: 88\n"
|
|
|
|
" JMP ($1234) ; pc=$0000 cy=05: 6c 34 12\n"
|
2017-12-29 21:31:05 +00:00
|
|
|
);
|
|
|
|
}
|
2017-12-29 23:08:25 +00:00
|
|
|
|
2018-01-07 22:30:52 +00:00
|
|
|
/*
|
|
|
|
* These tests are all contained within the jump_label test below.
|
|
|
|
* Test(mos6502_dis, jump_unlabel)
|
|
|
|
* Test(mos6502_dis, is_jump_label)
|
|
|
|
*/
|
2017-12-29 23:08:25 +00:00
|
|
|
Test(mos6502_dis, jump_label)
|
|
|
|
{
|
|
|
|
cr_assert_eq(mos6502_dis_is_jump_label(123), false);
|
2017-12-31 04:50:23 +00:00
|
|
|
|
2018-01-09 22:24:51 +00:00
|
|
|
vm_segment_set(cpu->memory, 123, 5);
|
|
|
|
vm_segment_set(cpu->memory, 124, 0);
|
2017-12-29 23:08:25 +00:00
|
|
|
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_jump_label(cpu, 123, 0, IND);
|
2017-12-31 04:50:23 +00:00
|
|
|
cr_assert_eq(mos6502_dis_is_jump_label(5), true);
|
2017-12-29 23:08:25 +00:00
|
|
|
mos6502_dis_jump_unlabel(123);
|
|
|
|
cr_assert_eq(mos6502_dis_is_jump_label(123), false);
|
2017-12-29 23:14:20 +00:00
|
|
|
|
|
|
|
// Testing forward relative
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_jump_label(cpu, 123, 10, REL);
|
2017-12-29 23:14:20 +00:00
|
|
|
cr_assert_eq(mos6502_dis_is_jump_label(123 + 10), true);
|
|
|
|
mos6502_dis_jump_unlabel(123 + 10);
|
|
|
|
|
|
|
|
// Testing backward relative
|
2017-12-31 05:02:30 +00:00
|
|
|
mos6502_dis_jump_label(cpu, 133, 1000, REL);
|
2017-12-29 23:14:20 +00:00
|
|
|
cr_assert_eq(mos6502_dis_is_jump_label(133 + 1000 - 256), true);
|
|
|
|
mos6502_dis_jump_unlabel(133 + 1000 - 256);
|
2017-12-29 23:08:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Test(mos6502_dis, label)
|
|
|
|
{
|
|
|
|
mos6502_dis_label(stream, 123);
|
2018-01-09 22:24:51 +00:00
|
|
|
assert_buf("ADDR_7b");
|
2017-12-29 23:08:25 +00:00
|
|
|
}
|