mirror of
https://github.com/holub/mame
synced 2025-04-26 18:23:08 +03:00

Disassemblers are now independant classes. Not only the code is cleaner, but unidasm has access to all the cpu cores again. The interface to the disassembly method has changed from byte buffers to objects that give a result to read methods. This also adds support for lfsr and/or paged PCs.
205 lines
8.2 KiB
ReStructuredText
205 lines
8.2 KiB
ReStructuredText
The device_disasm_interface and the disassemblers
|
|
=================================================
|
|
|
|
1. Capabilities
|
|
---------------
|
|
|
|
The disassemblers are classes that provide disassembly and opcode
|
|
meta-information for the cpu cores and **unidasm**. The
|
|
**device_disasm_interface** connects a cpu core with its disassembler.
|
|
|
|
2. The disassemblers
|
|
--------------------
|
|
|
|
2.1. Definition
|
|
~~~~~~~~~~~~~~~
|
|
|
|
A disassembler is a class that derives from
|
|
**util::disasm_interface**. It then has two required methods to
|
|
implement, **opcode_alignment** and **disassemble**, and 6 optional,
|
|
**interface_flags**, **page_address_bits**, **pc_linear_to_real**,
|
|
**pc_real_to_linear**, and one with four possible variants,
|
|
**decrypt8/16/32/64**.
|
|
|
|
|
|
2.2. opcode_alignment
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
| u32 \ **opcode_alignment**\ () const
|
|
|
|
Returns the required alignment of opcodes by the cpu, in PC-units. In
|
|
other words, the required alignment for the PC register of the cpu.
|
|
Tends to be 1 (almost everything), 2 (68000...), 4 (mips, ppc...),
|
|
which an exceptional 8 (tms 32082 parallel processor) and 16
|
|
(tms32010, instructions are 16-bits aligned and the PC targets bits).
|
|
It must be a power-of-two or things will break.
|
|
|
|
Note that processors like the tms32031 which have 32-bits instructions
|
|
but where the PC targets 32-bits values have an alignment of 1.
|
|
|
|
2.3. disassemble
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
| offs_t \ **disassemble**\ (std::ostream &stream, offs_t pc, const data_buffer &opcodes, const data_buffer ¶ms)
|
|
|
|
This is the method where the real work is done. This method must
|
|
disassemble the instruction at address *pc* and write the result to
|
|
*stream*. The values to decode are retrieved from the *opcode*
|
|
buffer. A **data_buffer** object offers four accessor methods:
|
|
|
|
| u8 util::disasm_interface::data_buffer::\ **r8**\ (offs_t pc) const
|
|
| u16 util::disasm_interface::data_buffer::\ **r16**\ (offs_t pc) const
|
|
| u32 util::disasm_interface::data_buffer::\ **r32**\ (offs_t pc) const
|
|
| u64 util::disasm_interface::data_buffer::\ **r64**\ (offs_t pc) const
|
|
|
|
They read the data at a given address and take endianness and
|
|
nonlinear PCs for larger-than-bus-width accesses. The debugger
|
|
variant also caches the read data in one block, so for that reason one
|
|
should not read data too far from the base pc (e.g. stay within 16K or
|
|
so, careful when trying to follow indirect accesses).
|
|
|
|
A number of CPUs have an external signal that splits fetches into an
|
|
opcode part and a parameter part. This is for instance the M1 signal
|
|
of the z80 or the SYNC signal of the 6502. Some systems present
|
|
different values to the cpu depending on whether that signal is
|
|
active, usually for protection purposes. On these cpus the opcode
|
|
part should be read from the *opcode* buffer, and the parameter part
|
|
from the *params* buffer. They will or will not be the same buffer
|
|
depending on the system itself.
|
|
|
|
The method returns the size of the instruction in PC units, with a
|
|
maximum of 65535. In addition, if possible, the disassembler should
|
|
give some meta-information about the opcode by OR-ing in into the
|
|
result:
|
|
|
|
* **STEP_OVER** for subroutine calls or auto-decrementing loops. If there is some delay slots, also OR with **step_over_extra**\ (n) where n is the number of instruction slots.
|
|
* **STEP_OUT** for the return-from-subroutine instructions
|
|
|
|
In addition, to indicated that these flags are supported, OR the
|
|
result with **SUPPORTED**\ . An annoying number of disassemblers lies
|
|
about that support (e.g. they do a or with **SUPPORTED** without even
|
|
generating the **STEP_OVER** or **STEP_OUT** information). Don't do
|
|
that, it breaks the step over/step out functionality of the debugger.
|
|
|
|
2.4. interface_flags
|
|
~~~~~~~~~~~~~~~~~~~~
|
|
|
|
| u32 **interface_flags**\ () const
|
|
|
|
That optional method indicates specifics of the disassembler. Default
|
|
of zero is correct most of the time. Possible flags, which need to be
|
|
OR-ed together, are:
|
|
|
|
* **NONLINEAR_PC**\ : stepping to the next opcode or the next byte of the opcode is not adding one to pc. Used for old LFSR-based PCs.
|
|
* **PAGED**\ : PC wraps at a page boundary
|
|
* **PAGED2LEVEL**\ : not only PC wraps at some kind of page boundary, but there are two levels of paging
|
|
* **INTERNAL_DECRYPTION**\ : there is some decryption tucked between reading from AS_PROGRAM and the actual disassembler
|
|
* **SPLIT_DECRYPTION**\ : there is some decryption tucked between reading from AS_PROGRAM and the actual disassembler, and that decryption is different for opcodes and parameters
|
|
|
|
Note that in practice non-linear pc systems are also paged, that
|
|
**PAGED2LEVEL** implies **PAGED**, and that **SPLIT_DECRYPTION**
|
|
implies **DECRYPTION**.
|
|
|
|
|
|
2.5. pc_linear_to_real and pc_real_to_linear
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
| offs_t **pc_linear_to_real**\ (offs_t pc) const
|
|
| offs_t **pc_real_to_linear**\ (offs_t pc) const
|
|
|
|
These methods should be present only when **NONLINEAR_PC** is set in
|
|
the interface flags. They must convert pc to and from a value to a
|
|
linear domain where the instruction parameters and next instruction
|
|
are reached by incrementing the value. **pc_real_to_linear** converts
|
|
to that domain, **pc_linear_to_real** converts back from that domain.
|
|
|
|
|
|
2.6. page_address_bits
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
| u32 **page_address_bits**\ () const
|
|
|
|
Present on when **PAGED** or **PAGED2LEVEL** is set, gives the number
|
|
of address bits in the lowest page.
|
|
|
|
|
|
2.7. page2_address_bits
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
| u32 **page2_address_bits**\ () const
|
|
|
|
Present on when **PAGED2LEVEL** is set, gives the number
|
|
of address bits in the upper page.
|
|
|
|
2.8. decryptnn
|
|
~~~~~~~~~~~~~~
|
|
|
|
| u8 **decrypt8**\ (u8 value, offs_t pc, bool opcode) const
|
|
| u16 **decrypt16**\ (u16 value, offs_t pc, bool opcode) const
|
|
| u32 **decrypt32**\ (u32 value, offs_t pc, bool opcode) const
|
|
| u64 **decrypt64**\ (u64 value, offs_t pc, bool opcode) const
|
|
|
|
One of these must be defined when **INTERNAL_DECRYPTION** or
|
|
**SPLIT_DECRYPTION** is set. The chosen one is the one which takes
|
|
what **opcode_alignment** represents in bytes.
|
|
|
|
That method decrypts a given value read from address pc (from
|
|
AS_PROGRAM) and gives the result which will be passed to the
|
|
disassembler. In the split decryption case, opcode indicates whether
|
|
we're in the opcode (true) or parameter (false) part of the
|
|
instruction.
|
|
|
|
|
|
3. Disassembler interface, device_disasm_interface
|
|
--------------------------------------------------
|
|
|
|
3.1. Definition
|
|
~~~~~~~~~~~~~~~
|
|
|
|
A CPU core derives from **device_disasm_interface** through
|
|
**cpu_device**\ . One method has to be implemented,
|
|
**create_disassembler**\ .
|
|
|
|
3.2. create_disassembler
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
| util::disasm_interface \*\ **create_disassembler**\ ()
|
|
|
|
That method must return a pointer to a newly allocated disassembler
|
|
object. The caller takes ownership and handles the lifetime.
|
|
|
|
THis method will be called at most one in the lifetime of the cpu
|
|
object.
|
|
|
|
4. Disassembler configuration and communication
|
|
-----------------------------------------------
|
|
|
|
Some disassemblers need to be configured. Configuration can be
|
|
unchanging (static) for the duration of the run (cpu model type for
|
|
instance) or dynamic (state of a flag or a user preference). Static
|
|
configuration can be done through either (a) parameter(s) to the
|
|
disassembler constructor, or through deriving a main disassembler
|
|
class. If the information is short and its semantics obvious (like a
|
|
model name), feel free to use a parameter. Otherwise derive the
|
|
class.
|
|
|
|
Dynamic configuration must be done by first defining a nested public
|
|
struct called "config" in the disassembler, with virtual destructor
|
|
and pure virtual methods to pull the required information. A pointer
|
|
to that struct should be passed to the disassembler constructor. The
|
|
cpu core should then add a derivation from that config struct and
|
|
implement the methods. Unidasm will have to derive a small class from
|
|
the config class to give the information.
|
|
|
|
5. Missing stuff
|
|
----------------
|
|
|
|
There currently is no way for the debugger GUI to add per-core
|
|
configuration. It is needed for in particular the s2650 and the
|
|
saturn cores. It should go through the cpu core class itself, since
|
|
it's pulled from the config struct.
|
|
|
|
There is support missing in unidasm for per-cpu configuration. That's
|
|
needed for a lot of things, see the unidasm source code for the
|
|
current list ("Configuration missing" comments).
|