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 )