Skip to content

Commit

Permalink
Add the --map option to trace.py
Browse files Browse the repository at this point in the history
  • Loading branch information
skoolkid committed Dec 23, 2024
1 parent 3604cc9 commit 63f2064
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 9 deletions.
14 changes: 12 additions & 2 deletions c/csimulator.c
Original file line number Diff line number Diff line change
Expand Up @@ -5356,18 +5356,19 @@ static PyObject* CSimulator_accept_interrupt(CSimulatorObject* self, PyObject* a
}

static PyObject* CSimulator_trace(CSimulatorObject* self, PyObject* args, PyObject* kwds) {
static char* kwlist[] = {"", "", "", "", "", "", "", "", "", NULL};
static char* kwlist[] = {"", "", "", "", "", "", "", "", "", "", NULL};
PyObject* start_obj;
PyObject* stop_obj;
unsigned long long max_operations;
unsigned long long max_time;
int interrupts;
PyObject* draw;
PyObject* exec_map;
PyObject* keyboard;
PyObject* disassemble;
PyObject* trace;

if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOKKiOOOO", kwlist, &start_obj, &stop_obj, &max_operations, &max_time, &interrupts, &draw, &keyboard, &disassemble, &trace)) {
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOKKiOOOOO", kwlist, &start_obj, &stop_obj, &max_operations, &max_time, &interrupts, &draw, &exec_map, &keyboard, &disassemble, &trace)) {
return NULL;
}

Expand Down Expand Up @@ -5431,6 +5432,15 @@ static PyObject* CSimulator_trace(CSimulatorObject* self, PyObject* args, PyObje
return NULL;
}

if (exec_map != Py_None) {
PyObject* addr = PyLong_FromLong(pc);
int rv = PySet_Add(exec_map, addr);
Py_XDECREF(addr);
if (rv == -1) {
return NULL;
}
}

if (trace == Py_None) {
CHECK_SIGNALS;
} else {
Expand Down
20 changes: 17 additions & 3 deletions skoolkit/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def __init__(self, simulator, border, out7ffd, outfffd, ay, outfe):
self.out_times = []
self.keyboard = None

def run(self, start, stop, max_operations, max_tstates, interrupts, draw, trace_line, prefix, byte_fmt, word_fmt):
def run(self, start, stop, max_operations, max_tstates, interrupts, draw, exec_map, trace_line, prefix, byte_fmt, word_fmt):
simulator = self.simulator
memory = simulator.memory
registers = simulator.registers
Expand All @@ -67,7 +67,7 @@ def run(self, start, stop, max_operations, max_tstates, interrupts, draw, trace_
tf = lambda pc, i, t0: print(trace_line.format(pc=pc, i=i, r=r, t=t0, m=memory))
else:
df = tf = None
stop_cond, operations = simulator.trace(start, stop, max_operations, max_time, interrupts, draw, keyboard, df, tf)
stop_cond, operations = simulator.trace(start, stop, max_operations, max_time, interrupts, draw, exec_map, keyboard, df, tf)
else:
opcodes = simulator.opcodes
frame_duration = simulator.frame_duration
Expand All @@ -86,6 +86,9 @@ def run(self, start, stop, max_operations, max_tstates, interrupts, draw, trace_
opcodes[memory[pc]]()
tstates = registers[25]

if exec_map is not None:
exec_map.add(pc)

if interrupts and registers[26] and tstates % frame_duration < int_active:
simulator.accept_interrupt(registers, memory, pc)
tstates = registers[25]
Expand Down Expand Up @@ -285,8 +288,12 @@ def run(snafile, options, config):
draw = screen.draw
else:
draw = None
if options.map:
exec_map = set()
else:
exec_map = None
tracer.run(start, options.stop, options.max_operations, options.max_tstates,
options.interrupts, draw, trace_line, prefix, byte_fmt, word_fmt)
options.interrupts, draw, exec_map, trace_line, prefix, byte_fmt, word_fmt)
rt = time.time() - begin
if len(simulator.memory) == 65536:
cpu_freq = 3500000
Expand All @@ -306,6 +313,11 @@ def run(snafile, options, config):
print(f'Sound duration: {z80t} T-states ({z80s:.3f}s)')
lines = textwrap.wrap(simplify(delays, options.depth), 78)
print('Delays:\n {}'.format('\n '.join(lines)))
if options.map:
with open(options.map, 'w') as f:
for addr in sorted(exec_map):
f.write(f'${addr:04X}\n')
print(f'Wrote {options.map}')
for fname in options.dump:
ext = fname.lower()[-4:]
if ext == '.wav':
Expand Down Expand Up @@ -347,6 +359,8 @@ def main(args):
help='Simplify audio delays to this depth (default: 2).')
group.add_argument('-I', '--ini', dest='params', metavar='p=v', action='append', default=[],
help="Set the value of the configuration parameter 'p' to 'v'. This option may be used multiple times.")
group.add_argument('--map', metavar='FILE',
help="Log addresses of executed instructions to a file.")
group.add_argument('-m', '--max-operations', metavar='MAX', type=int, default=0,
help='Maximum number of instructions to execute.')
group.add_argument('-M', '--max-tstates', metavar='MAX', type=int, default=0,
Expand Down
2 changes: 2 additions & 0 deletions sphinx/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Changelog
enable instruction comment generation)
* :ref:`trace.py` now responds to keypresses while running with screen contents
displayed
* Added the ``--map`` option to :ref:`trace.py` (for writing a code execution
map file)
* Added the ``UserAgent`` configuration parameter for
:ref:`tap2sna.py <tap2sna-conf>` (to specify the User-Agent header in
HTTP/HTTPS requests)
Expand Down
6 changes: 4 additions & 2 deletions sphinx/source/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ to be a binary file.
The ``-m`` option may be used to specify a code execution map to use when
generating a control file. The supported file formats are:

* Files created by the ``--map`` option of :ref:`rzxplay.py`
* Files created by the ``--map`` option of :ref:`rzxplay.py` or :ref:`trace.py`
* Profiles created by the Fuse emulator
* Code execution logs created by the SpecEmu, Spud and Zero emulators
* Map files created by the SpecEmu and Z80 emulators
Expand Down Expand Up @@ -2031,6 +2031,7 @@ To list the options supported by `trace.py`, run it with no arguments::
-D, --decimal Show decimal values in verbose mode.
-I p=v, --ini p=v Set the value of the configuration parameter 'p' to
'v'. This option may be used multiple times.
--map FILE Log addresses of executed instructions to a file.
-m MAX, --max-operations MAX
Maximum number of instructions to execute.
-M MAX, --max-tstates MAX
Expand Down Expand Up @@ -2150,7 +2151,8 @@ Configuration parameters may also be set on the command line by using the
+---------+-------------------------------------------------------------------+
| Version | Changes |
+=========+===================================================================+
| 9.5 | Responds to keypresses while the screen is displayed |
| 9.5 | Responds to keypresses while the screen is displayed; added the |
| | ``--map`` option |
+---------+-------------------------------------------------------------------+
| 9.4 | Added the ``--screen`` option; added support for writing a PNG |
| | file after execution has completed; added the ``PNGScale``, |
Expand Down
4 changes: 2 additions & 2 deletions sphinx/source/man/sna2ctl.py.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ OPTIONS

-m, --map `FILE`
Specify a code execution map to use. Code execution maps produced by
``rzxplay.py`` and by the Fuse, SpecEmu, Spud, Zero and Z80 Spectrum
emulators are supported.
``rzxplay.py`` and ``trace.py``, and by the Fuse, SpecEmu, Spud, Zero and Z80
Spectrum emulators are supported.

-o, --org `ADDR`
Specify the origin address of a binary file. The default origin address is
Expand Down
3 changes: 3 additions & 0 deletions sphinx/source/man/trace.py.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ OPTIONS
overriding any value found in ``skoolkit.ini``. This option may be used
multiple times.

--map FILE
Log addresses of executed instructions to a file.

-m, --max-operations `MAX`
Maximum number of instructions to execute. Overrides the `STOP` address (if
given).
Expand Down
40 changes: 40 additions & 0 deletions tests/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def test_default_option_values(self):
self.assertEqual(options.depth, 2)
self.assertEqual([], options.dump)
self.assertTrue(options.interrupts)
self.assertIsNone(options.map)
self.assertEqual(options.max_operations, 0)
self.assertEqual(options.max_tstates, 0)
self.assertIsNone(options.org)
Expand Down Expand Up @@ -1323,6 +1324,45 @@ def test_option_I_overrides_config_read_from_file(self):
self.assertEqual(['TraceLine=Goodbye'], options.params)
self.assertEqual(config['TraceLine'], 'Goodbye')

def test_option_map(self):
data = (
0xAF, # $8000 XOR A
0x3C, # $8001 INC A
0x18, 0x01, # $8002 JR $8005
0x00, # $8004 NOP
0x3D, # $8005 DEC A
0xCD, 0x0C, 0x80, # $8006 CALL $800C
0xC3, 0x0E, 0x80, # $8009 JP $800E
0xC9, # $800C RET
0x00, # $800D NOP
0x3C, # $800E INC A
)
mapfile = 'exec.map'
binfile = self.write_bin_file(data, suffix='.bin')
start = 32768
stop = start + len(data)
output, error = self.run_trace(f'-n -o {start} -S {stop} --map {mapfile} {binfile}')
self.assertEqual(error, '')
exp_output = f"""
Stopped at ${stop:04X}
Wrote {mapfile}
"""
self.assertEqual(dedent(exp_output).strip(), output.rstrip())
self.assertTrue(os.path.isfile(mapfile))
exp_map = """
$8000
$8001
$8002
$8005
$8006
$8009
$800C
$800E
"""
with open(mapfile) as f:
map_contents = f.read()
self.assertEqual(dedent(exp_map).lstrip(), map_contents)

def test_option_max_operations(self):
data = [
0xAF, # XOR A
Expand Down

0 comments on commit 63f2064

Please sign in to comment.