Perl で学ぶ x86 アセンブラ入門 (Shibuya.pm#11発表資料)

前々回のShibuya.pmテクニカルトーク(XS Nite)では、PerlのC言語拡張モジュールを作成する方法について詳しく解説しましたが、ちょっとしたことを実現するだけでも、PerlVMの構造を理解し、大量のCのマクロを使いこなす必要がありました。やっぱりXSは面倒だよね、ということで、複雑なC言語のレイヤーを飛び越えて一気にPerlからマシン語の世界に飛び込んだらどうなるか?ということをテーマに今回の「no Perl; use x86;」というスローガンを掲げてみました。

■ 発表資料: Perl で学ぶx86アセンブラ入門

4/22に開催したShibuya Perl Mongers テクニカルトーク #11「no Perl; use x86;」での私の発表資料を公開します。

 

■ Perlの正規表現で書く x86 JIT Compiler

#!/usr/bin/perl
use DynaLoader;
$ENV{PERL_SIGNALS}||($ENV{PERL_SIGNALS}="unsafe",exec$^X,$0,@ARGV);
my $x86="\x0f\x0b";$SIG{ILL}=sub{$x86=~s/\x0f\x0b/\xc3\x90/; warn"JIT"};
DynaLoader::dl_install_xsub("X",unpack"L",pack"P",$x86);&X; print"ok\n";
実行結果
% perl ud2jit.pl
JIT at ud2jit.pl line 4.
ok

このプログラムの解説

せっかくなのでこのプログラムが何をしているのか1行1行詳しく見ていくことにします。

use DynaLoader;

今回のShibuya.pm#11で毎度お馴染みの DynaLoader Hacks のために必要なモジュール宣言です。

$ENV{PERL_SIGNALS}||($ENV{PERL_SIGNALS}="unsafe",exec$^X,$0,@ARGV);

ここでは環境変数PERL_SIGNALSが定義されていないときは$ENV{PERL_SIGNALS}="unsafe"で環境変数を代入した後、exec($^X,$0,@ARGV)で自分自身をもう一度実行しなおすようにしています。これは、次で説明するシグナルハンドラ$SIG{ILL}にPerlサブルーチンのフックをしかけるために、環境変数PERL_SIGNALS="unsafe"を設定してからperlコマンドを実行するようにしているためです。

my $x86="\x0f\x0b";$SIG{ILL}=sub{$x86=~s/\x0f\x0b/\xc3\x90/; warn"JIT"};

変数$x86に2byteの機械語"\x0f\x0b"をPerlの文字列として代入しています。
次では、シグナルハンドラ$SIG{ILL}を定義して、$x86=~s/\x0f\x0b/\xc3\x90/;として、機械語の命令をPerlの正規表現で自己書き換えするようにしています。正規表現の置換が終了したらwarn"JIT"で「JIT at ud2jit.pl line 4.」と現在実行しているPerlプログラムのファイル名と行数が表示されます。

このUD2命令("\x0f\x0b")はPentium Pro で追加されたオフィシャルなundefined(未定義)命令ということなのですが、元々未定義の命令領域だったものなのでPentium Pro以前のPentium、i486、i386でも SIGILL(4) illegal hardware instruction が発生します。UD2命令はオフィシャルに動作が定義された未定義命令ということで、その副作用から様々な応用例が開発されています。一つはx86に依存した言語処理系VMの高速化(最適化)などです。

「“ud2” は x86 が未定義命令例外を発生する命令。この命令を読むと CPU はそれ以降の命令の Prefetch を停止する。これにより無駄な Bus Traffic と I-Cache の使用を抑制する。」(Shibuya.lisp Tech Talk #1)

ちなみに、未定義命令を実行して例外が発生した場合、プログラムカウンタEIPは未定義命令の先頭のままなので(プロセッサが何バイト進めばよいかわからないため)、自己書き換えしたい場合は未定義命令そのものを書き換える必要があります。これはINT命令などでソフトウェア割り込みを発生させたときとは動作が異なるので注意が必要です。

DynaLoader::dl_install_xsub("X",unpack"L",pack"P",$x86);&X; print"ok\n";

DynaLoader::dl_install_xsubで変数$x86に代入されているC文字列のポインタをDynaLoader::dl_install_xsubに渡して、コンパイル済みのCライブラリの関数ではなく、直接$x86に記述した機械語を呼び出しています。
無事に$SIG{ILL}に登録されたシグナルハンドラが呼び出されて自己書き換えが行なわれたらx86のRET命令"\xc3"で呼び出し元に戻り、Perlの処理が続行され、print"ok\n";が実行されokと表示されます。

ちなみに、"\x90"はx86でnop命令を意味しています。このnop命令も奥が深く、最速の1byte nop、2byte nop、3byte nop命令などがプロセッサ毎に定義されていて、ユニバーサルなx86イディオムが各所で研究開発され、その成果が取り込まれていたりします。
一部はLinux kernelのソースコードなどに反映されたりしています。

■ おまけ:Inline/x86.pm

package Inline::x86;

use DynaLoader();
use Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(x86_sub);

sub x86_sub {
  my ($func, $x86) = @_;
  if ($^O eq "linux") {
    require 'syscall.ph';
    my $size = int(2+length($x86)/4096)*4096;
    syscall(&SYS_mprotect,(unpack"L",pack"P",$x86)&~4095,$size,7);
  }
  DynaLoader::dl_install_xsub(caller(0)."::$func",
                              unpack"L",pack"P",$x86);
}

1;

みたいなPerlモジュールを作っておくと、

use Inline::x86 qw(x86_sub);

x86_sub function1 => do {
  "\x90\x90\x90". # nop x 3
  "\xc3";         # ret
};

のように x86の実行コードをPerlからスマートに呼び出せるようになるので便利です。

■ まとめ

「XSを書くよりもx86を直接書いたほうが覚えることが少なくて簡単でしょ?」

■ Shibuya.pm#11 感想リンク

今回はustream中継は行ないませんでしたが、こんなニッチなネタにも関わらず定員200名近くの会場がいっぱいになってびっくりです。次々回はもっと突っ込んだ64bit最適化の話とかができれば嬉しいです。次回Shibuya.pmは低レベルでない通常のPerlネタを考えていますのでどうぞご安心下さいませ。

  1. Security&Trustウォッチ(58) Perl Mongersはセキュリティの夢を見るか? − @IT
  2. 今日はShibuya.pm #11の日です – a geek
  3. Shibuya.pmを聞きにいったらドッペルゲンガー…? – nikki.da!
  4. Shibuya Perl Mongers #11 に参加して – makotoworldのはてなダイアリー
  5. 汚いなさすがPerlきたない – 糸且之入Eヨ言己
  6. セミナーのレポート書ける人って凄い – だるろぐ
  7. Shibuya.pm#11で発表しました – use GFx::WebLog;
  8. Shibuya.pmに行った件 – ハギプラン(愛称:ハプラン)
  9. perlでdtrace環境構築 – handlerの日記
  10. Shibuya Perl Mongersテクニカルトーク#11 でLTしてきました – 葉っぱ日記
  11. Shibuya Perl Mongersテクニカルトーク#11@リクルート本社サウスホール – jitsu102の日記
  12. JPA#1 and Shibuya.pm#11 – Learn to Crawl
  13. Ptrace と TOCTOU 攻撃 – id:kazuhookuのメモ置き場
  14. Shibuya.pmに参加してきました – DesigningとEngineeringの架け橋
  15. shibuya.pmのLT – べにんじょのやっぽ
  16. 活動報告:元現役高校生サーバー管理者の考察日誌 – CNET Japan
  17. 이빨까기인형 :: [ Perl ] Shibuya.pm TechTalk #11 에 다녀와서…
  18. Shibuya.pmテクニカルトーク#11いってきた – 発言注意!
  19. WindowsユーザのためのはじめてのPerlプログラミング
  20. 終わった – webGOTH
  21. にひりずむ::しんぷる:Shibuya Perl Mongersテクニカルトーク#11に滑り込みで行って来た
  22. JPAとかShibuya.pmとか – Charsbar::Note

最後に、無茶なお願いにもかかわらずShibuya.pmでの講演を快諾していただいた皆さん、「no Peel; use x86;」なShibuya,omにご来場いただいたPerlモンガーもしくは他の言語使いの皆さん、当日会場でログをまとめていただいたhirataraさん、本当にありがとうございました。

コメントは受け付けていません。