我可以在Perl中更改已加载模块中的代码行吗?

时间:2010-09-06 05:41:40

标签: perl module

当我使用FLV::Info模块从多个FLV文件中提取元数据或合并多个FLV文件时,我经常收到“标记大小太小”错误,然后模块将拒绝工作。有人在三年前发布了一个错误报告here,但似乎没有解决办法。

好吧,最近我发现我是否只是在Tag.pm中注释掉以下代码行,这是FLV::Info的依赖模块之一,如下所示:

=pod
if ($datasize < 11)
   {
      die "Tag size is too small ($datasize) at byte " . $file->get_pos(-10);
   }
=cut
然后

FLV::Info将按预期方式开展工作。

我不确定这是一个非常愚蠢的问题,但我感到很好奇:

是否有一种简单的方法可以在不修改原始.pm文件的情况下更改已加载模块中的几行代码?

有任何想法,建议或意见吗? 总是如此谢谢:)

更新

非常感谢@Shwern。你的答案非常令人满意:)还要感谢@DVK的建议以及“猴子补丁”一词和@brian的书籍推荐。

以下是我对样本FLV文件测试的反馈,如果我使用原始模块而没有对其进行任何操作,则会导致“标记大小太小”错误。

“eval it back”方法解决了问题

use FLV::Info;

use Data::Dump::Streamer;
my $original = FLV::Tag->can("parse");
my $code = Dump($original)->Out;
#$code =~ s{\Qif ($datasize < 11)\E}{if (0)}; #This somehow won't work
$code =~ s{die "Tag}{warn "Tag}; #Let it warn but not die

no warnings 'redefine';
*FLV::Tag::parse = eval $code;

my $reader = FLV::Info->new();
$reader->parse('sample.flv');
my %info = $reader->get_info();
print "$info{video_count} video frames\n";
print $reader->report();

“过度死亡不死”方法也有效

BEGIN {
    *CORE::GLOBAL::die = sub { return CORE::die(@_) };
}
use FLV::Info;

{
    local *CORE::GLOBAL::die = sub {
         return if $_[0] =~ /^Tag size is too small/;
         return CORE::die(@_);
};

my $reader = FLV::Info->new();
$reader->parse('sample.flv');
my %info = $reader->get_info();
print "$info{video_count} video frames\n";
print $reader->report();
}

然而,“重新定义”方法并不像我预期的那样有效。

我复制并粘贴了原始的FLV :: Tag :: parse子例程,并按照我修改原始Tag.pm文件的方式注释掉了代码行:

use FLV::Info;
no warnings 'redefine';
*FLV::Tag::parse = sub {
    ...
    ...
=pod
   if ($datasize < 11)
   {
      die "Tag size is too small ($datasize) at byte " . $file->get_pos(-10);
   }
=cut
   ...
   ...
};

my $reader = FLV::Info->new();
$reader->parse('sample.flv');
my %info = $reader->get_info();
print "$info{video_count} video frames\n";
print $reader->report();

但是我收到了这个错误:

Unknown tag type 18 at byte 13 (0xd)

好吧,即使复制和粘贴完全相同的解析子程序而没有在重新定义中进行任何修改,我也会收到“未知标记类型”错误,而不是“标记尺寸太小”。

这很奇怪!

作为参考,“eval it back”和“override die to not die”方法将给我以下内容:

1992 video frames
File name                sample.flv
File size                5767831 bytes
Duration                 about 79.6 seconds
Video                    1992 frames
  codec                  AVC
  type                   interframe/keyframe
Audio                    1712 packets
  format                 AAC
  rate                   44100 Hz
  size                   16 bit
  type                   stereo
Meta                     1 event
  audiocodecid           10
  audiosamplerate        22050
  audiosamplesize        16
  audiosize              342817
  creationdate           unknown
  datasize               805
  duration               79.6
  filesize               5767869
  framerate              25
  height                 300
  keyframes              {
    >>>                    'filepositions' => [
    >>>                                         '780',
    >>>                                         '865',
    >>>                                         '1324122',
    >>>                                         '2348913',
    >>>                                         '2978630',
    >>>                                         '3479001',
    >>>                                         '3973756',
    >>>                                         '4476281',
    >>>                                         '4997226',
    >>>                                         '5391890'
    >>>                                       ],
    >>>                    'times' => [
    >>>                                 '0',
    >>>                                 '0',
    >>>                                 '9.6',
    >>>                                 '19.2',
    >>>                                 '28.8',
    >>>                                 '38.4',
    >>>                                 '46.32',
    >>>                                 '55.92',
    >>>                                 '64.88',
    >>>                                 '73.88'
    >>>                               ]
    >>>                  }
  lastkeyframetimestamp  73.88
  lasttimestamp          79.6
  metadatacreator        Manitu Group FLV MetaData Injector 2
  metadatadate           1281964633858
  stereo                 1
  videocodecid           7
  videosize              5424234
  width                  400

最终更新

我已经弄清楚为什么“重新定义”方法失败了,打开严格和警告的实用工具。感谢@Schwern的提醒:)

首先添加以下代码行(从FLV :: Util模块复制),然后重新定义FLV :: Tag :: parse子例程。

Readonly::Hash our %TAG_CLASSES => (
   8  => 'FLV::AudioTag',
   9  => 'FLV::VideoTag',
   18 => 'FLV::MetaTag',
);

2 个答案:

答案 0 :(得分:18)

简单?不,但你可以做一些疯狂的事情。这是一些不好的想法。

更明显的一个是将.pm文件的被黑客拷贝放入你的项目中,这是在系统版本之前看到的。

另一个是类似的,但要削减&amp;将整个例程粘贴到代码中,并在加载原始代码后将其注入。

use FLV::Tag;

no warnings 'redefine';
*FLV::Tag::parse = sub {
    ...copy of FLV::Tag::parse with your edits...
};

您可以覆盖die,以便在看到该消息时不会死亡。

BEGIN {
    # In order to override die() later, you must override it at compile time.
    *CORE::GLOBAL::die = sub { return CORE::die(@_) };
}

{
    local *CORE::GLOBAL::die = sub {
        return if $_[0] =~ /^Tag size too small/;
        return CORE::die(@_);
    }

    ...do your thing...
}

您可以将该子例程的内容转储回Perl,对代码执行字符串替换,然后将其重新评估。

use Data::Dump::Streamer;
my $original = FLV::Tag->can("parse");
my $code = Dump($original)->Out;
$code =~ s{\Qif ($datasize < 11)\E}{if( 0 )};

no warnings 'redefine';
*FLV::Tag::parse = eval $code;

或者你可以走这个子程序的操作码树并改变条件,我把它作为练习留给他们手上有更多时间的人。

他们都是坏主意。你最好只是更改代码并再次联系作者,让他们知道有新信息。

答案 1 :(得分:3)

好的,Schwern的答案非常彻底,但是这里的一种方法不那么“糟糕”......

使用他的第二种方法(将整个例程剪切并粘贴到您的代码中,并在加载原始代码后将其注入)...但是......在特定版本的FLV::Info(或{{ 1}})。

这样,你仍然有你的猴子补丁(这在技术上被认为是猴子补丁?),但是你删除了这种方法被认为是“坏”的一个最大原因 - 即,任何升级到模块都可能与您的自定义修补子程序冲突。如果使用版本检查来保护覆盖,则可以消除这种担忧。