CPU-Z80-Disassembler
view release on metacpan or search on metacpan
lib/CPU/Z80/Disassembler/Instruction.pm view on Meta::CPAN
#------------------------------------------------------------------------------
=head1 NAME
CPU::Z80::Disassembler::Instruction - One Z80 disassembled instruction
=cut
#------------------------------------------------------------------------------
use strict;
use warnings;
use Asm::Z80::Table;
use CPU::Z80::Disassembler::Memory;
use CPU::Z80::Disassembler::Format;
our $VERSION = '1.02';
#------------------------------------------------------------------------------
=head1 SYNOPSIS
use CPU::Z80::Disassembler::Instruction;
$instr = CPU::Z80::Disassembler::Instruction->disassemble(
$memory, $addr, $limit_addr);
$instr = CPU::Z80::Disassembler::Instruction->defb($memory, $addr, $count);
$instr = CPU::Z80::Disassembler::Instruction->defb2($memory, $addr, $count);
$instr = CPU::Z80::Disassembler::Instruction->defw($memory, $addr, $count);
$instr = CPU::Z80::Disassembler::Instruction->defm($memory, $addr, $length);
$instr = CPU::Z80::Disassembler::Instruction->defmz($memory, $addr);
$instr = CPU::Z80::Disassembler::Instruction->defm7($memory, $addr);
$instr = CPU::Z80::Disassembler::Instruction->org($memory, $addr);
$instr->addr; $instr->next_addr;
$instr->bytes; $instr->opcode; $instr->N; $instr->NN; $instr->DIS; $instr->STR;
$instr->comment;
print $instr->dump;
print $instr->asm;
print $instr->as_string, "\n";
=head1 DESCRIPTION
This module represents one disassembled instruction. The object is
constructed by one of the factory methods, and has attributes to ease the
interpretation of the instruction.
=head1 CONSTRUCTORS
=head2 disassemble
Factory method to create a new object by disassembling the given
L<CPU::Z80::Disassembler::Memory|CPU::Z80::Disassembler::Memory> object
at the given address.
The C<$limit_addr> argument, if defined, tells the disassembler to select
the longest possible instruction, that does not use the byte at C<$limit_add>.
The default is to select the shortest possible instruction.
For example, the sequence of bytes C<62 6B> is decoded as C<ld h,d> if
C<$limit_addr> is undef.
If C<$limit_addr> is defined with any value different from C<$addr + 1>, where
the second byte is stored, then the same sequence of bytes is decoded as
C<ld hl,de>.
To decode standard Z80 instructions, do not pass the C<$limit_addr> argument.
To decode extended Z80 instructions, pass the address of the next label after
C<$addr>, or 0x10000 to get always the longest instruction.
If the instruction at the given address is an invalid opcode, or if there
are no loaded bytes at the given address, the instrution object is not
constructed and the factory returns C<undef>.
=head2 defb
Factory method to create a new object by disassembling a C<defb> instruction
at the given address, reading one or C<$count> byte(s) from memory.
=head2 defb2
Same as defb but shows binary data.
=head2 defw
Factory method to create a new object by disassembling a C<defw> instruction
at the given address, reading one or C<$count> word(s) from memory.
=head2 defm
Factory method to create a new object by disassembling a C<defm> instruction
at the given address, reading C<$length> character(s) from memory.
=head2 defmz
Factory method to create a new object by disassembling a C<defmz> instruction
at the given address, reading character(s) from memory until a zero terminator
is found.
=head2 defm7
Factory method to create a new object by disassembling a C<defm7> instruction
at the given address, reading character(s) from memory until a character
with bit 7 set is found.
=head2 org
Factory method to create a new ORG instruction.
=cut
#------------------------------------------------------------------------------
=head1 ATTRIBUTES
=head2 memory
Point to the memory object from where this instruction was disassembled.
=head2 addr
Address of the instruction.
lib/CPU/Z80/Disassembler/Instruction.pm view on Meta::CPAN
'is_code', # true for a Z80 assembly instruction,
# false for def*, org, ...
);
#------------------------------------------------------------------------------
sub format {
my($self) = @_;
$self->_format({}) unless $self->_format;
$self->_format;
}
#------------------------------------------------------------------------------
my %default_format = (
N => \&format_hex2,
N2 => \&format_bin8,
NN => \&format_hex4,
DIS => \&format_dis,
STR => \&format_str,
);
#------------------------------------------------------------------------------
sub next_addr {
my($self) = @_;
$self->addr + $self->size;
}
#------------------------------------------------------------------------------
sub next_code {
my($self) = @_;
my @ret;
push @ret, $self->NN if $self->is_branch;
push @ret, $self->next_addr unless $self->is_break_flow;
@ret;
}
#------------------------------------------------------------------------------
sub bytes {
my($self) = @_;
my @bytes;
for my $addr ($self->addr .. $self->next_addr - 1) {
push @bytes, $self->memory->peek($addr);
}
\@bytes;
}
#------------------------------------------------------------------------------
# predicates
sub is_call { shift->opcode =~ /call|rst/ }
sub is_branch { shift->opcode =~ /jp .*NN|jr|djnz|call|rst/ }
sub is_break_flow { shift->opcode =~ /ret$|reti|retn|call NN|rst|jr NN|jp NN|jp \(\w+\)|org/ }
#------------------------------------------------------------------------------
sub disassemble {
my($class, $memory, $addr, $limit_addr) = @_;
my $self = bless { memory => $memory,
addr => $addr,
is_code => 1,
}, $class;
# save bytes of all decoded instructions
my @found; # other instructions found
my $table = Asm::Z80::Table->disasm_table;
for ( ;
# exit if second instruction goes over limit, e.g. label
! (defined($limit_addr) && @found && $addr == $limit_addr) ;
$addr++
) {
# fetch
my $byte = $memory->peek($addr);
last unless defined $byte; # unloaded memory
# lookup in table
if (exists $table->{N}) {
die if defined $self->N;
$self->N( $byte );
$table = $table->{N};
}
elsif (exists $table->{NNl}) {
die if defined $self->NN;
$self->NN( $memory->peek16u($addr++) );
$table = $table->{NNl}{NNh};
}
elsif (exists $table->{NNo}) {
die if defined $self->NN;
$self->NN( $addr + 1 + $memory->peek8s($addr) );
$table = $table->{NNo};
}
elsif (exists $table->{DIS}) {
die if defined $self->DIS;
$self->DIS( $memory->peek8s($addr) );
$table = $table->{DIS};
}
elsif (exists $table->{'DIS+1'}) {
die unless defined $self->DIS;
if ( $self->DIS + 1 != $memory->peek8s($addr) ) {
last; # abort search
}
$table = $table->{'DIS+1'};
}
elsif (! exists $table->{$byte}) {
last; # abort search
}
else {
$table = $table->{$byte};
}
# check for end
if (exists $table->{''}) { # possible finish
push @found, [ [@{$table->{''}}], $addr + 1 ];
# save this instance, copy
last unless defined $limit_addr; # no limit -> shortest instr
# continue for composite instruction
}
}
# return undef if no instrution found
return undef unless @found;
( run in 0.525 second using v1.01-cache-2.11-cpan-39bf76dae61 )