如何处理存储在Perl中的二进制文件中的操作码?

时间:2011-11-21 18:35:04

标签: perl binary

Creating Your Own Abstract Processor 是一篇关于我想要做的快速论文。

http://malcon.org/ctm.rar是关于模拟器应该是什么的确切规范。

我在Perl中编写了一种模拟器。该程序将读入二进制文件,并将二进制的十六进制处理为动作,处理器如何工作,但基于不同操作码的软件。只是一个挑战。

无论如何,我不完全确定如何将二进制文件读入数组,然后将数组中的十六进制分配给操作码。这是我的代码的一部分:

my $opcode='';
my $file= "path/to/file";
my $IP=0;
my $SP=0;
my $FLAG=0;
my $A=0;
my $B=0;
my $X=0;
my @STACK=(0);

open(F, "<".$file) || die("Could not open file");
binmode(F);
my @ROM = <F>;
close(F);

while($IP >= 0)
{
    $opcode="$ROM[$IP]";
    if ($opcode eq 11) {
        $A = $STACK[$SP];
        $IP++;
    }
    if ($opcode eq 12) {
        $B = $STACK[$SP];
        $IP++;
    }
    if ($opcode eq 13) {
        $A = $B;
        $IP++;
    }
    if ($opcode eq 14) {
        $B = $A;
        $IP++;
    }

这只是我需要帮助的代码的一部分。如果您因某些原因需要查看更多内容,请告诉我。

所以我改变了我的代码以反映上面的答案,我的代码现在看起来像这样:

my $opcode='';
my $file= "CTM-bootrom";
my $IP=0;
my $SP=0;
my $FLAG=0;
my $A=0;
my $B=0;
my $X=0;
my @STACK=(0);

open my $ROM, '<:raw', $file or die "Cannot open '$file': $!";

{
    local $/ = \1; # Read one byte at a time
    while (my $byte = <$ROM>){
        while($IP >= 0)
        {
            $opcode=$ROM[$IP];
            if ($opcode eq 11) {
                $A = $STACK[$SP];
                $IP++;
            }
            if ($opcode eq 12) {
                $B = $STACK[$SP];
                $IP++;
            }
            if ($opcode eq 13) {
                $A = $B;
                $IP++;
            }

但现在我收到了一个错误:

  

在Aod8.pl第73行的字符串eq中使用未初始化的值$ opcode,

&LT; $ ROM&GT;在我看来,操作码初始化...如何解决这个问题呢?

4 个答案:

答案 0 :(得分:2)

我不一定认为一次读取二进制一个字符是个好主意(尽管对于足够小的文件,你大部分时间都会从缓冲区中取出)。从概念上讲,将文件读入平面阵列会更好。你可以使用

来实现
my @ROM = do {
    local $/ = \1;
    map ord, <$fh>;
};

我认为这回答了你问题的要点。

现在,关于不将整个文件读入数组,而是将文件本身用作可执行映像的想法,这是我根据本文的早期版本和反馈编写的一个小脚本。

请注意,整个标量文件句柄是提供脚手架来运行通过复制和粘贴链接的文章中包含的ROM而不必创建二进制文件。

#!/usr/bin/env perl

use strict;
use warnings;

use Fcntl ':seek';
use Try::Tiny;

my %ROMS = (

    rom1 => [0x04, 0x41, 0x09, 0x02, 0x0a],

    rom2 => [
        0x04, 0x41, 0x09, 0x02,
        0x07, 0x09, 0x02, 0x07,
        0x09, 0x02, 0x07, 0x09,
        0x02, 0x07, 0x09, 0x02,
        0x0A,
    ],

    rom3 => [
        0x04,   69, 0x09, 0x02, 0x04,  110, 0x09, 0x02,
        0x04,  116, 0x09, 0x02, 0x04,  101, 0x09, 0x02,
        0x04,  114, 0x09, 0x02, 0x04,   32, 0x09, 0x02,
        0x04,   97, 0x09, 0x02, 0x04,   32, 0x09, 0x02,
        0x04,   67, 0x09, 0x02, 0x04,  104, 0x09, 0x02,
        0x04,   97, 0x09, 0x02, 0x04,  114, 0x09, 0x02,
        0x04,   32, 0x09, 0x02, 0x04,   58, 0x09, 0x02,
        0x04,   32, 0x09, 0x02, 0x04,   50, 0x03, 0x01,
        0x04,    2, 0x03, 0x04,   89, 0x09, 0x02, 0x04,
         111, 0x09, 0x02, 0x04,  117, 0x09, 0x02, 0x04,
         32,  0x09, 0x02, 0x04,   84, 0x09, 0x02, 0x04,
         121, 0x09, 0x02, 0x04,  112, 0x09, 0x02, 0x04,
         101, 0x09, 0x02, 0x04,  100, 0x09, 0x02, 0x04,
          32, 0x09, 0x02, 0x04,   58, 0x09, 0x02, 0x04,
          32, 0x09, 0x02, 0x04,   50, 0x03, 0x02, 0x0A,
    ]

);

for my $rom (sort keys %ROMS) {
    my $rom_s = join '', map chr, @{ $ROMS{ $rom } };

    open my $rom_h, '<:raw', \$rom_s
        or die "Cannot open handle to ROM string: $!\n";

    print "Executing $rom\n";

    try {
        execute($rom_h);
    }
    catch {
        print "\n$rom: $_\n";
    };

    close $rom_h
        or die "Cannot close handle to ROM string: $!\n";
}

sub get_next_byte {
    my ($fh) = @_;
    my $byte = do {
        local $/ = \1;
        scalar <$fh>;
    };

    return unless defined $byte;

    $byte = ord $byte;

    return $byte;
}

sub execute {
    my ($ROM) = @_;

    my $FLAG = 0;
    my $SP = 0;
    my $X = 0;
    my @STACK;

    my @machine = (

        # NOP
        sub {},

        # INPUT
        sub { $STACK[$SP] = ord(getc STDIN) },

        # OUTPUT
        sub { printf STDOUT '%c', $STACK[$SP] },

        # MOV SP, X
        sub { $SP = $X },

        # MOV X, DATA
        sub {
            $X = get_next_byte($ROM);
        },

        # CMP X, DATA
        sub {
            $FLAG = $X - get_next_byte($ROM);
        },

        # JE
        sub {
            my $offset = get_next_byte($ROM);

            if ($FLAG == 0) {
                seek $ROM, $offset, SEEK_CUR
            }
        },

        # INC X
        sub { $X += 1 },

        # INC SP
        sub { $SP += 1 },

        # MOV [SP], X
        sub { $STACK[$SP] = $X },

        # HALT
        sub {
            die "HALT\n";
        },
    );

    while (1) {
        my $opcode = get_next_byte($ROM);

        last unless defined $opcode;

        if (($opcode >= 0) and ($opcode < @machine)) {
            $machine[ $opcode ]->();
        }
        else {
            die sprintf(
                "Invalid opcode '%02x' at offset '%x'\n",
                $opcode, $.,
            );
        }
    }
}

输出:

Executing rom1
A
rom1: HALT

Executing rom2
ABCDE
rom2: HALT

Executing rom3
Enter a Char : d
You Typed : d
rom3: HALT

答案 1 :(得分:1)

如果$opcode为undef,则$ROM[$IP]为undef。

在代码的第二个版本中,您没有显示正在填充的@ROM,因此$ROM[$IP]当前为@ROM就不足为奇了。

代码的第一个版本更接近您想要的。但是,@ROM初始化不正确。文件的每一行(甚至不是由行组成)都分配给@ROM的元素,但是您希望将文件的每个字节分配给my @ROM = do { open(my $fh, '<', $file) or die("Can't open ROM file \"$file\": $!\n"); binmode($fh); local $/; # Read entire file at once. map ord, split //, <$fh> }; 的元素。这样做如下:

my @STACK;
my $A  = 0;
my $B  = 0;
my $SP = 0;
my $IP = 0;
for (;;) {
   die(sprintf("Bad address 0x%04X\n", $IP)) if $IP >= @ROM;
   my $instruction = $ROM[$IP++];

   if    ($opcode == 0x11) { $A = $STACK[$SP]; }
   elsif ($opcode == 0x12) { $B = $STACK[$SP]; }
   elsif ($opcode == 0x13) { $A = $B; }
   ...
   else { die(sprintf("Bad opcode 0x%02X\n", $opcode)); }
}

在修复剩下的代码之后,它看起来如下:

:raw

请注意binmode==并不完全相同,尽管有相关文档。

注意使用或ord将字符转换为数字值,并使用数字比较运算符(11)来比较这些数字。

请注意,我使用的是十六进制数字。十六进制(或者可能是八进制,取决于被模拟的机器)将比使用十进制更清晰,因为类似的操作码往往以位而不是数字变化。此外,您作为提供者的数字已经是十六进制,因此操作码$char_from_file eq 11 # XXX What you had. $char_from_file eq chr(0x11) # Ok, but a bit suboptimal. ord($char_from_file) == 0x11 # Ok. What I have. 实际上是十七

{{1}}

答案 2 :(得分:0)

如果您只想逐字节地将文件拆分为@ROM,请尝试

local $/; # get the whole file once, see perldoc perlvar for details
my @ROM = split //, <F>;

答案 3 :(得分:0)

我现在得到了......现在一切都按照需要运作了:

my $IP=0;
my $FLAG=0;
my $SP=0;
my $A=0;
my $B=0;
my @STACK=(0);
my $byte=0;
$| = 1;

open(BOOTROM, "<bootrom.txt");
binmode(BOOTROM);

my (@ROM, $instruction);
while ((read BOOTROM, $instruction, 1) != 0) {
    @ROM[$IP] = $instruction;
    $IP++;
}
close(BOOTROM);

$IP = 0;
while ($IP >= 0 && $byte != 0x3C) {
    $byte = ord(@ROM[$IP]);

    if ($byte == 0x11) {
        $A = (@STACK[$SP]);
        $IP++;
    }
    elsif ($byte == 0x12) {
        $B = (@STACK[$SP]);
        $IP++;
    }
    elsif ($byte == 0x13) {
        $A = $B;
        $IP++;
    }
    elsif ($byte == 0x14) {
        $B = $A;
        $IP++;
    }