撇开已经介绍过的字节码后端 tchrist,只讨论 C 后端,perlcc 所做的就是将编译的 perl 程序的 optree 转换为 C 程序,然后编译。该 C 程序将在运行时将该 optree 重建到内存中,并基本上像 perl 通常那样执行它。这样做的目的实际上只是为了加快常规 perl 代码的编译时间。
你的程序的那个 optree 然后在PL_main_root 全局变量中可用。我们已经有一个名为B::Deparse 的模块,它能够使用optree 并将它们转换为大致相当于编译optree 的原始代码的源代码。它恰好有一个 compile 方法,该方法返回一个 coderef,该代码在执行时将打印 PL_main_root 的解析结果。
还有 C 函数 Perl_eval_pv,您可以使用它从 C 空间评估 Perl sn-ps。
$ echo 'print 42, "\\n"' > foo.pl
$ perl foo.pl
42
$ perlcc foo.pl
$ ./a.out
42
$ gdb a.out
...
(gdb) b perl_run
Breakpoint 1 at 0x4570e5: file perl.c, line 2213.
(gdb) r
...
Breakpoint 1, perl_run (my_perl=0xa11010) at perl.c:2213
(gdb) p Perl_eval_pv (my_perl, "use B::Deparse; B::Deparse->compile->()", 1)
print 42, "\n";
$1 = (SV *) 0xe47b10
当然,通常的 B::Deparse 警告适用,但这对于逆向工程肯定会很方便。在大多数情况下,实际上重构原始源代码是不可能的,即使它适用于上述示例。
要让 B::Deparse 给你一些合理的东西,你必须做的确切的 gdb 魔法也很大程度上取决于你的 perl。我正在使用带有 ithreads 的 perl,因此是多重性的。这就是我传递my_perl 变量的原因。其他 perls 可能不需要。此外,如果有人剥离了 perlcc 编译的二进制文件,事情会变得有点困难,但同样的技术仍然有效。
你也可以用它来编译任何你可以在程序执行期间随时以某种方式获得的optree。看看 B::Deparse 的 compile sub 并做一些类似的事情,除了为你想要转储的任何 optree 提供一个 B 对象而不是 B::main_root。
同样的事情也适用于提到的 perlcc 的字节码后端。我不完全确定名为 CC 的优化 C 后端。