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さん、本当にありがとうございました。

ブログのお引っ越し(MovableType3→TypePad)

先週から今週にかけてブログの移転作業を行いました。

■ 1. 経緯

今までのサイボウズ・ラボのブログは、自社サーバ上でMovableType3を長らく運用してきたのですが、
大量のトラックバック・コメントSPAMへの対応負荷も無視できなくなり、
MovableType4にバージョンアップするのも面倒なので(既にバージョンアップライセンスの販売は終了していて、MT4に移行するにしても新規にライセンスを購入しなおさないといけないため)、
この際、サイボウズ・ラボとサイボウズ本社の技術者のブログをまとめて TypePad に移行することにしました。

■ 2. RSS/Atomフィードのリダイレクト

livedoor Readerでは巡回対象のフィードを取得する際にHTTPリダイレクトの処理を行っています。
  • 代表的なものとして「301リダイレクト(恒久的な移転)」と「302リダイレクト(一時的な移転)」をサポートしています。
    • 301 Moved Permanentlyによるリダイレクトの場合、巡回対象のURLが置き換わります。
    • 302 Moved Temporarily(または302 Found)は巡回対象のURLはそのままで、リダイレクト先のURLを取得します。

    • 301以外の3xxレスポンスによるリダイレクトの場合は、巡回対象のURLの書き換えは起こりません。
.htaccess等を使ったリダイレクトの設定が可能な場合は、livedoor Readerの購読者を移転先のブログに引き継ぐことができます。
ということでしたので、旧サーバのApache上で 301 のリダイレクト設定を行いました。
RewriteEngine On
RewriteRule ^/blog/takesako/index.xml http://developer.cybozu.co.jp/takesako/rss.xml [R=301,L]
RewriteRule ^/blog/takesako/atom.xml http://developer.cybozu.co.jp/takesako/atom.xml [R=301,L]

とりあえず、LDRではそのまま購読者を引き継ぐことが出来ました。
その他のRSSリーダの動作は確認しきれていませんが、とりあえずこのような対処で移行してみました。
(もしもお使いのソフトウェアで不都合がある場合はご連絡いただけると有難いです)

■ 3. はてなブックマークの引継ぎ

過去記事についたはてなブックマークの関連付けを残しておきたい、という要望に応えるために
MovableTypeからエクスポートするときに
以下のHTMLをBODYの先頭に追加して対応してみました。

BODY: 
<div align="right"><a href="http://b.hatena.ne.jp/entry/<$MTEntryPermalink$>"><img
src="http://b.hatena.ne.jp/entry/image/<$MTEntryPermalink$>" border="0" alt="[B!]"></a></div>
<$MTEntryBody$>

■ 4. permalinkの移行

Movable Type 4 では、インポート時の BASENAME に対応しているみたいなのですが、
困ったことに、TypePad では対応していないようでした。

TypePadでデータをインポートすると、「BASENAME」の項目が無視されるため(対応していないため)、
permalinkが日本語を含んだTITLEから自動で再生成されてしまいます。

(例)エントリーのタイトル:「TypePadでBASENAMEの項目がインポート時に無視される」

 旧URL:http://labs.cybozu.co.jp/blog/takesako/20xx/xx/old_filename.html
   ↓そのままインポートするとHTMLのファイル名が変わってしまう問題が・・・
 新URL:http://developer.cybozu.co.jp/takesako/20xx/xx/typepad_basename.html

これだと、

RewriteRule ^/blog/takesako/(.*)\.html$ http://developer.cybozu.co.jp/takesako/$1.html [R=301,L] 

のような単純なリダイレクト設定では記事の転送ができなくなってしまうので、
困ってしまうことになります。

■ 5. Movable Type 3 からのエクスポート

この BASENAME の件ですが、TITLE フィールドに <$MTEntryBasename$> を指定して
MTからエクスポート → TypePad にインポートするとファイル名が保持されることがわかったので、
今まで使用していなかった KEYWORDS: フィールドに <$MTEntryTitle$> を退避して、
インポート後に手作業でブログのタイトルをカット&ペーストして元に戻して対応することにしました。

Movable Type 4 以前のバージョンからのブログ記事インポートからの変更点は以下の通りです。

<MTEntries lastn="99999">AUTHOR: <$MTEntryAuthor$>
TITLE: <$MTEntryTitle$> <$MTEntryBasename$>
BASENAME: <$MTEntryBasename$>
STATUS: <$MTEntryStatus$>
ALLOW COMMENTS: <$MTEntryFlag flag="allow_comments"$>
CONVERT BREAKS: <$MTEntryFlag flag="convert_breaks"$>
ALLOW PINGS: <$MTEntryFlag flag="allow_pings"$>
PRIMARY CATEGORY: <$MTEntryCategory$>
<MTEntryAdditionalCategories>CATEGORY: <$MTCategoryLabel$>
</MTEntryAdditionalCategories>
DATE: <$MTEntryDate format="%m/%d/%Y %I:%M:%S %p"$>
TAGS: <MTEntryTags glue=",">"<$MTTagName quote="1"$>"</MTEntryTags>
-----
BODY:
<$MTEntryBody$>
-----
EXTENDED BODY:
<$MTEntryMore$>
-----
EXCERPT:
<$MTEntryExcerpt$>
-----
KEYWORDS:
<$MTEntryKeywords$> <$MTEntryTitle$>
-----
<MTIfNonZero tag="MTEntryCommentCount"><MTComments>COMMENT:
AUTHOR: <$MTCommentAuthor default=""$>
EMAIL: <$MTCommenterEmail$>
URL: <$MTCommentURL$>
IP: <$MTCommentIP$>
DATE: <$MTCommentDate format="%m/%d/%Y %I:%M:%S %p"$>
<$MTCommentBody convert_breaks="0"$>
-----
</MTComments></MTIfNonZero><MTIfNonZero tag="MTEntryTrackbackCount"><MTPings>PING:
TITLE: <$MTPingTitle$>
URL: <$MTPingURL$>
IP: <$MTPingIP$>
BLOG NAME: <$MTPingBlogName$>
DATE: <$MTPingDate format="%m/%d/%Y %I:%M:%S %p"$>
<$MTPingExcerpt$>
-----
</MTPings></MTIfNonZero>
--------
</MTEntries>

こうして、昔の記事のファイル名を維持しながら TypePad に移行することができました。

私の場合はエントリー数が少なかったので手作業でも何とかなりましたが、
エントリー数が多いブログだとこの方法はお勧めできませんね。。。

■ 6. まとめ

ということで、無事TypePadに移行できましたので、引き続きこちらでもブログ記事を書いていこうと思います。よろしくお願いいたします。

■ 7. 追記(2009/06/10)

一部の旧URL http://labs.cybozu.co.jp/blog/takesako/2008/12/caravan_takamatsu.html からのリダイレクト、
正しいとび先は http://developer.cybozu.co.jp/takesako/2008/12/caravan_takamat.html に失敗していました。

これはTypePad上で扱えるブログ記事のファイル名の長さに制限があるのが原因みたいで、リダイレクトURL生成時に上限の15文字以内に切り詰めるようにリダイレクトルールを見直し設定変更しました。

RewriteRule ^/blog/takesako/(.*)\.html$ http://developer.cybozu.co.jp/takesako/$1.html [R=301,L] 
  ↓16byte以上のときは15byteに切り詰める
RewriteRule ^/blog/takesako/(..../../.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?).*\.html$ http://developer.cybozu.co.jp/takesako/$1.html [R=301,L]
RewriteRule ^/blog/takesako/(.*)\.html$ http://developer.cybozu.co.jp/takesako/$1.html [R=301,L]

hasegawaさん、ご指摘どうもありがとうございました。