読者です 読者をやめる 読者になる 読者になる

ELVM Compiler Infrastructure

最近作ってたオモチャがだいたいまとまってきました。

https://github.com/shinh/elvm

第12回 kernel/vm 勉強会で発表した時のスライド:

http://shinh.skr.jp/slide/elvm/000.html

これは何かというと、前作った bflisp を改良したり整理したりしたもので、 C 言語をシンプルな中間言語 (EIR) に変換する改造 8cc と、その中間言語Brainfuck をはじめとした他言語に変換するバックエンドから成り立っています。 bflisp との差分は、 Brainfuck 以外のバックエンドを追加しやすくしたり、バックエンドを C で書いて、完全に Brainfuck だけで 8cc.bf を再現することができるようにしたり、という感じです。

特に興味深いであろうバックエンドとしては、 Brainfuck, Unlambda (id:irori さん作), Piet, C-INTERCAL, Befunge, Whitespace などがあります。例えば Lisp を Piet で動かしたりもできるようになりました。 lisp.pnggimp で開いてスクリーンショットを撮ったこの画像なんかは結構お気にいりです。

JavaScript に変換することもできるので、デモサイトは ELVM を使って作った JS で作られています。

http://shinh.skr.jp/elvm/8cc.js.html

スライドにも書きましたが、「チューリング完全な言語互いに等価だからうんぬん…」というような、理論ではそうなんだけど…というようなことが実際確認されるのを見るのはなかなか楽しかったです。コンパイラいじり、 esolangs での計算プリミティブ作成パズル、最小限の libc 作り、など好きなトピックがたくさんあるという意味でも楽しくて良かったです。

Brainfuck interpreter in Ruby's Regexp

Ruby正規表現だけで Brainfuck インタプリタを作ることができました。正規表現の実行は =~ だけなので、ループなども正規表現の内部で実行してます。

https://github.com/shinh/hack/blob/master/bf_rb_reg/bf.rb

つまりどういうことができるかというと、 BF_REG という Regexp と BF_SUFFIX という文字列定数があって、 bf という文字列に格納された Brainfuck のコードを

BF_REG =~ bf + BF_SUFFIX

で実行することができます。出力は $~['o0'], $~['o1'], ... に入っているので、

output = ''
256.times do |i|
 o = $~["o#{i}"]
 break if !o
 output += o
end

的なコードで取り出すことができます。入力は ! の後に続いてる文字列を入力とする形式。Ruby正規表現は Turing 完全でないので、無限ループはできないようになっています。

仕様の一覧を並べると

  • 8bit Brainfuck
  • 入力の終端は -1
  • メモリは255番地まで
  • 出力文字数は256まで
  • 入力文字数は256まで
  • Brainfuck命令以外の文字があると正規表現がマッチに失敗する
  • 無限ループがあると正規表現がマッチに失敗する(正確には1つのループが256回連続回ったら止まるという仕様なので、本当は止まるコードもマッチ失敗することがありえます)

など。256まで、という制限は実装をラクにするためなので、本質的な制限ではないです、が無限にはできないです(入力文字数は無限にできるかも?)。この程度の制限があっても十分に実用的(?)で、例えば cal.bf も動いています。

$ echo 2016 10 | time ruby bf.rb cal.bf
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
ruby bf.rb cal.bf  1147.54s user 0.26s system 99% cpu 19:07.91 total

ただすごく遅くて、 cal.bf の実行に19分とかかかってました。1命令10msとかその程度だと思います。 + とか - が実に遅い。

こんなことできるとはつい最近まで思ってなかったのですが、 HITCON CTF で正規表現力の研鑽を積んだ結果、なんかできるような気がしてきて、やってみたらできました。

今思うに、一番重要なことは後方参照を更新できる、ということだったのかなと思います。 \g で呼び出された中にある後方参照は、呼び出されるたびに値が変わっていくという。

DEFCON CTF 本戦

binjaの現地枠がギリギリ空いてそうだったので、入れてもらいました。思ったことは、みんなすごいなあ、ということと、これしんどいなあということ、ルールはともかくみんなでわちゃわちゃやるの楽しいなということ、あとまあなんと言っても俺ふがいないな、ってことでした。

今回はCTFの前日に優勝賞金2億円のDARPA主催のCyber Grand Challenge(以下CGC)というのが行なわれ、それで優勝したAIを含めて15チームで同じフォーマットで競う、という趣旨でした。CGCはELFベースにABIを簡略化してあり、いつものようにシェルコードを実行するまではやる必要がなく、

  • type 1: EIPを指定されたところに飛ばす
  • type 2: 特定のアドレスにある、フラグページから4byte取り出す

のいずれかを達成すればOK、というルールでした。

準備

準備の段階では、 bjmit という名前でバイナリに mitigation (日本語でなんと言うのだろう) を入れるツールと、それの評価をするものを作っていました。バイナリジャンルの mitigation というのは ASLR や stack canary なんかがよく使われるものです。 stack canary みたいなのを入れるのはまぁまぁ大変なので、簡単に実装できるものをたくさん準備する、という方針で

  • retをフックして戻り先が元々.textだったか、callの直後に戻っているかをチェックする
  • jmp/callをフックして戻り先が元々.textだったかをチェックする
  • sub esp, XXをフックして未初期化スタックを使わないようにする
  • writeシステムコール相当をフックして、フラグページを直接writeできないようにする
  • mmap(PROT_EXEC)相当を禁止する
  • デフォルトでスタックがRWXなのでRWな空間に引っ越す
  • ヒープを適当にrandomizeする
  • 共通で使われるコードにある、いらないコードを削除もしくは総取っ替えし、ROPなどに使いにくくする

というような機能を作りました。安易な攻撃には最初の2つが特に効く感じで、前もってサンプルで試してる感じでは、まあ半分強くらいのサンプル攻撃が止まるな、ってところでした。バイナリいじってパフォーマンス(CPUメモリディスク使用量)が落ちると評価が下がってしまうんですが、そのへんもまぁ、問題によっては使えないけどギリギリOKかな、ってレベルでした。

mitigationというのは本質的にあまり意味がないものだと思っていて(おそらく世の中的にもそう思われていて)、やはりそういうゴマかしはさらなる回避策とのイタチごっこになるので、なんらかのsandboxで根本的に根本から断つのが筋というのが最近の流れだと思います。ですが、短期間のコンテストではまあまあ時間かせぎになるし(運が良ければ完治もしうる)、ということで作ってみた感じでした。C++アセンブリで書き散らかして、2000行くらい。libdisasmとlibdisasm_cfgというのを使った感じ。

初日

練習のために出てきたバイナリが非常に小さく、かつムダな初期化ルーチンが無くなっていたためmitigationを入れるとパフォーマンスの制約を満たさなそう。実際投稿してみて得られる点数は明らかにダメ。あー覚悟はしてたけどbjmit完全にムダだったかな、と思いつつ短期間で使える方法を考える…が思いつかない。

で本番のバイナリは初期化ルーチンが復活してて、手元の確認では問題ないぽい感じになってた…けど投稿してみると実行時間が2倍以上になってるとか言われて泣く。とりあえず速度2倍では0点のはずなので、それはよくないということで焦って、stack overflowしてるところだけmitigateするようないい加減な処置をする。

それでも遅い。そこで、んなわけないだろ!!てかこれ速度で点数引かれてなくね??みたいな感じで混乱しつつ、正当なパッチにした。相変わらず実行時間は2.6倍とか異様なことを言われる不思議大会。

もうこれ何信じたらいいんかわからん…ということでbjmitの積極的な適用ができなくなってしまう。が、この攻撃食らったぽい、というのをmitigationずみのバイナリで試すとたいてい止まっているし、パフォーマンスも手元で測る限りは問題ないし…ということで微妙に使ってみたり使わなかったり、というようなことをしてたと思う。

結論としては手元の評価は間違ってなかったぽいので、最初から手元で大丈夫だと思うだけの全力mitigationをバイナリの内容を考慮に入れつつかけておくべきだった。

2問目を解析してた人がこれはそこらじゅうバグだらけで大変、ということでここを修正すべき、みたいなことを教えてもらって適当にパッチ当てる。ただそれではその時検知されてた問題全ては塞がらなかったぽい…とかやってるうちに、もうこれ相手のバイナリパクろうぜーということで、バイナリパクりゲーになった。

その後出題された3問目もバイナリパクったので、これパッチ担当ていらないよねーと晩に相談する。あとbjmitは見てる限り結構止めてるので信じませんか…というような話もする。

あと運営が時間は内部的には計測できているが、表示上おかしくなっている。明日までに直す努力をするが、本当にできるかはわからない、とか意味不明なことを言っていた。表示上の問題てどう考えてもおかしいので、時間での減点を最初の練習の後くらいにすごくゆるくしたんじゃないかなぁ…と今では邪推している。

2日目

bjmitの評価をしつつ、見つけたバグを眺めていた。ムダに時間を使ったけど、特に発展がなくて残念だった。

とかやってる間に色々と検知できたバグとかの情報が来るので、パッチ書きをする。このへんで色々パッチ書いたけど、どうにも攻撃の数が増加するのにたいしてパッチ書きが後手後手に回ってる感じになる。方針が決まっていればパッチはかなり迅速に書ける準備してあったし実際書けたと思うんだけど、自分でコード読めてないから方針が立たなかったりと。いくつかバグふさいでも全部ふさがってないと更新する意味ないどころか損になる、というルールのせいで、これで全部、とわかるまでどうしてもバイナリ更新に臆病になってしまう。次々と新しいタイプの攻撃を検知してる中、もうバグが無いと確信するのは難しい…

この日から他チームがバックドアを仕込んでくるようになったんだけど、チームの人がバックドアを外す方法を確立しはじめる。結論としてはパッチを頑張って当てるより、バックドア外しの方が良かったのではないかと思う。

このあたりでbjmitがmitigateしただけのバイナリをパクられる。光栄です。bjmitの挙動はチームの人に説明してあるので、チームの人がmitigateを回避するexploitを書いて攻撃して笑う。たしかにある意味mitigateってバックドアみたいなもんだな…とか思う。

謎のパフォーマンス数値表示は相変わらずで、この日は実行時間30倍になってるぜーとか言われてても、点数的には問題ないとかなんとか。

この日、相手チームのバイナリでprogram headerが後ろについてるものがあって、ああたしかにアレ後ろにつけても問題ないよな…とか感心した。program headerを置いてあった領域がcode領域として使えるようになるのでオトク。

2日目が終わった後に、いくつかbjmitに対してやっておいたら良さそうなこととか、適当な自分用ツールなどを整備したりした。

3日目

眠くて疲れて、完全に脳が死んでいた。

前日終了間際に出題された複数バイナリ問題のパッチを準備しておく。開幕早々それを使うと全然動いてないと出て、bjmitのせいかと思ったけどbjmitは無罪でpatchが悪かった。今見るとなんでこんなので動くと思ったの、て感じのパッチで本当にひどい。結構な失点になったぽいので、本当に申し訳ない…

色々バグ探しとかやってたけど、bjmitの運用以外はほとんど意味ない感じになってしまってたと思う。メモリ使用量が増えてる問題があって焦ったけど、運営に聞くと問題ないとのことで、時間に続いてメモリ使用量も不正確だということがわかったりした。本当にひどい。

まとめると

1,3日目ひどくて失点しちゃったのが本当にすいませんすいません…という感じ。1日目はどっちかというと運営が悪いと思うんだけど、3日目は本当にひどかった。まあそれでなくてもパッチ担当はちょっとした失敗が結構でかい失点につながるので、かなりビクビクするルールだなぁ…などと思っていた。それ以外にも、どうもチーム連携みたいなの全然うまくできてないなあ、など反省が多い感じでした。

あとどうもCTF本番もいいんだけど、ツールの準備とかも楽しかったなと。なんかモノ作るの好きなんだなぁと、今更ながら。実際CTF中も私本人は全く信用できないけど、ツールは今思えばまぁまぁ信用できる動作してた気がします…

システムプログラミング会

すごい久々にイベントの(共同)主催的なことをしました

http://connpass.com/event/34995/

イメージとしては言語雑談会的な感じで、closedに人集めてウダウダやるつもりだったんですが、途中でPFNさんに場所貸していただけるということで、不特定な人を呼ぶことになって、当初想定してたより大袈裟な会になりました。適当に募集すると人が集まって自己顕示欲が満たされるとか、普段得られない種類のフィードバックが得られたり交流ができる、てのがあるわけですが、めんどくさいのとグダグダ雑談がしにくいのが難点ですね。まあ自分自身が一般的に募集されてる会で勉強した面もあるわけで、そういうのの恩返しになるかもという面もあり、まあ色々。

まとめとしてはkoieさんのブログやtogetterがまとまってそう

http://blog.livedoor.jp/hkoie/archives/55525878.html

http://togetter.com/li/995015

私の発表は

http://shinh.skr.jp/slide/kati_sp/000.html

ここに既に書いたことはざっくり飛ばして(それでも結構時間を使ってしまったけど…)、その後の細かいけど全体としてはそれなりに効いた最適化とか、straceいじって依存解析するプログラムを書いたような話をしました。後者の dsan のコードと改造 strace は

https://github.com/google/kati/tree/dsan/tools

https://github.com/shinh/strace

にあります。 dsan_record.py の方はコマンドをラップして入出力を記録できる感じになってます。

このコンセプトのドキュメントは https://docs.google.com/document/d/1eRSNE352rA6ocyoFljHQW2ySdhg9oKN8CHj2ljrlKX0/edit#

@mkasaharaさんのLD_PRELOADベースの依存解析は、LD_PRELOADというかlibcレイヤでファイルシステムAPIフックするってのは、色んな機会に既に5回とかそれ以上やったことあって、手軽で高速なんだけど、結構問題も多いと認識してたので使いませんでした。具体的な問題としては、再入してウゲーとか、fts_openみたいなあまり使われないlibc API対応とか、ダイナミックローダを始めとしたスタティックリンクされてるバイナリに無力とか。LD_PRELOADベースな話として、djbredoというアイデアを(僕から見れば中途半端に)実装した https://github.com/jekor/redohttps://github.com/apenwarr/redo 、あと @kazuho さんの https://github.com/kazuho/unco なんかも一応先行研究として眺めた記憶があります。 strace ベースの(これまた不完全な)やつだと https://github.com/nickstenning/trojans/blob/master/fabricate.py とか https://github.com/kgaughan/memoize.py/blob/master/memoize.py とか。

印象に残った話

@herumiさんの話: 途中に出てきた実行ファイル電子透かしは http://www1.cs.columbia.edu/~angelos/Papers/hydan.pdf とか先行研究が。

@tanaka_akrさんの話: Unix domain socketみたいな難しいAPIじゃなくてLinuxだけにしぼっても、EPERMとEACCESとENOTDIRとENOENTはどれが優先されるの…みたいなの難しいなあ…と2年ほど前に思ってたのを思い出しました。

@kumagiさんの話: L1と分岐予測のトレードオフてのはなるほどなぁ…とか

@hogegashiさんの話: signal話、こういう話好きなのだった。こういうの好きな人は http://pwnable.kr/ の tiny_hard やるといいと思う

@yamasaさんの話: 面白いなあと思うものの、まあ護身完成してればだいたいmutex使うよねという感も

懇親会含めて思ったのは、ネットワークとDBはさっぱりわかってないな…ということでした。

おまけでシステムプログラミングクイズとか

http://shinh.skr.jp/quiz/sp.html

1問目は EAX への代入で上位32bitがクリアされるよ、というのが題意です。 AX への代入ではだめ。

2問目は、「デバッグバイナリじゃないとシンボルが見れない」とか、「バックトレースが取れない」とかたまに聞くので…

3問目だけ異常難易度でした。第一引数が間違ってるので、第三引数の 2 は処理されないため、 [1] が戻ります。

4問目はspurious wakeupについて。

5問目はgenerationalも入れても良かったですね。

tweetした、 if (*p != 42) なのに printf してみると 42 って出るってコード

https://twitter.com/shinh/status/749272046924754945

#include <stdio.h>
 
__attribute__((noinline))
int func(int x, int y) {
  int* p = new int;
  if (*p != 42)
    printf("*p was not 42!!! *p=%d\n", *p);
  return y;
}
 
int main() {
  func(0, 42);
}

*p の値は初期化されてないため、未定義です。「未定義!=42」をclangはtrueにするみたいです。 printf の第二引数は未定義なため、第二引数に使うレジスタである RSI は全く変更されません。また、 p は未定義なまま一切使ってないため、 new 自体が消えてなくなります。よって、 func の第二引数のために RSI にセットされた 42 がそのまま残り、件の結果になります。

あと難しいクイズとして

http://shinh.skr.jp/quiz/signature.html

http://shinh.skr.jp/quiz/stdio.html

もあります。前者は異常にむずいです。後者は @unak さんの RubyIO の話 を聞きながら作ったものです。

defcon ctf qual 2016

binjaの人たちすごいなと見てた。今回は結構な時間参加できたんだけど、まあひょっとしたら節約できた時間もあるかもね、てくらいの貢献度だったと思う。まあ入れてもらってからまともに時間使って参加した回が無かったくらいの勢いだったから、少しはなんかできて良かった。

b3s23

ライフゲーム15ターン動かした後の状態をshellcodeとして実行します、て問題。空間がループしてないタイプのライフゲームなんで、端っこの方は大変使いにくい…ので最初の命令で端じゃないところに実行飛ばして、あとは適当に、て方針で。うまく使えるジャンプ作るのが結構大変で、適当な形にグライダーぶち当てて都合よく出てくるパターンを探した。

うまいこといって、 read(0, buf, buf) とかが呼べたけど buf をちょっと動かさないとなってあたりで、別の人が1行目だけでできたぽかったので、放棄。終わってからローカルで shellcode 動くところまで作っておいた。

https://gist.github.com/shinh/1891e3f346a1255fc06e8a7cbf63c756

15
       o oo
ooo    oo o


     oo
     o o                         o
     o         oo   oo          o o    oo                             o    o   oo     oo  oo oo
              o  o  oo  o oo     o    o  o   oo oo          oo        o   o o o  o    oo  oo oo
               oo       oo o           oo    oo oo          oo        o    o   oo

kiss

レジスタを全消去の後 RAX RBX RCX RDX はヒープ内の好きな場所指した状態、それで RIP を好きなとこ飛ばせますよって問題。 RSP をまともな値にせんとどうしようもないやろ…ということで RSP をリストアするであろう、 libc の longjmp とか setcontext とかを眺める。 RSP の値の一時退避先が R[ABCD]X なら良いだろう、てことで。まあでも R8 とか使ってて惜しかったりするものの見つからない。

で少し探索範囲を広げて RSP いじってるとこ探すと

   498b0:	48 89 dc             	mov    %rbx,%rsp
   498b3:	5f                   	pop    %rdi
   498b4:	48 85 ff             	test   %rdi,%rdi
   498b7:	74 08                	je     498c1 <addseverity@@GLIBC_2.2.5+0x591> ; [L5010]
   498b9:	e8 22 d9 ff ff       	callq  471e0 <setcontext@@GLIBC_2.2.5>

というのが見つかったので、これ都合良くないですか…と聞くと、他の人が使えそうといいうことで、後はやってくれた。

終わってからこれ何かなーと調べてみると、 makecontext の実装に使ってる、トランポリンにあたるコードらしい。 makecontext て戻り値の書き換えとか使ってるんだな…

crippled

寝て起きて二度寝するかなとその前にケータイを見ると、私が得意そう、と言われてる問題に気付く。やってみてわかったことはこんなもん誰でも解けるってことだけど、まあ簡単そうだったのでやった。

改造した C コンパイラを適当にカンで入力送って、 read(3, buf, 99) を成功させろ、て問題。ただし read を使おうとするとリンクエラー。 write のコードをダンプしてみると EAX セットするところすっとばせば ebx/ecx/edx がセットされつつ int 0x80 してくれて良さそうだったので、適当にアドレス探して終了。なんか足し算が引き算になったり、そういうよくわからん改造がいろいろされてたみたい。

int main() {
  char buf[99];
  void (*fn)()=((int)&main)+26;
  //write(1,fn,555);
  write(1,"hey",3);
  fn(3,buf,99);
  write(1,buf,99);
}

step

特定の4Bの鍵を使うとコードの一部が復元されるぽかったので、適当に総当たりしつつそれっぽいアセンブリが出るのを探した。その後予定があったので脱出。

そこまではクソ簡単で、その後は SIGTRAP が激しく起きつつ2段階目の復元が行なわれて、後は普通の rev ぽい感じ、という感じぽかった。

crunchtime

昔あった pwn の穴をふさいだよ、て問題。コードサイズ小さいので一通り眺めると、リークはたくさんあるけど、どうもexploitableなとこが見つからないなあとぼんやりしている。

他の人がフラグレジスタいじったら文字列命令の挙動変えれる、と言って、それだってことになる。 direction flag ていうやつ、全く知りませんでした。こんなのあったんだなあ…という感じ。このバグは最初の方の実験で引き起こしてて、あれヘンなこと起きてるな、てことは思ってたのに、すっかり忘れててダメだなあ…と思いました。

でまあ他の人がマジメにダンプ読んでるぽかったので、適当に遊んでると、割と簡単に RIP を書き換えれることに気付く。ただし RIP は CRC を計算した結果になってるので…ということで適当に総当たり書くも、ああ毎度違う値計算する必要ある上に時間制限あるからキツいか、と思う。

CRC は Quine 書いた時に、計算で解を出せるよね…?とか思ってたけど、そこらでコード見つからないな、と探す。そのうち他の人が過去の問題で CRC 探しに使った問題を出してきてくれたので、少しいじって今回のパラメータで動くようにする。

あとは、スタックのいじれるところ的に pivot ぽいことしないとだな…とかアレコレしてるうちに、スタックの大きいアドレスもいじれるようにやってた他の人が解いてくれる。

まとめ

書いてみると思ってた以上に何もしてなかった。まあいつもより他の人と少し協力とかできたのは良かったと思う。思うに最初の解析とか方針定まってからの実装とかでは、速度的に貢献できないなっていう。特に前者。

8cc.bf

https://github.com/shinh/bflisp/blob/master/8cc.bf

Brainfuckで動くCコンパイラができました。このコンパイラ自身で同じコンパイラコンパイルすることも可能です(ただし5時間以上かかる)。前回との差分はレジスタの幅とデータメモリ空間を24bitに広げたことと、いくつかバグをつぶしたことです。

これまでわざわざ手書きで書いていたBrainfuckのプログラムを、Cのコードから生成することでお手軽に作れるようになりました…とさ。 sizeof(int)==1 で 1byte==24bits だったりする変なアーキテクチャな上に、ビット演算とかがないですけど。

就職して9年が過ぎる

転職して7年が過ぎたというのを読んで気づいたんだけど、そろそろ入社後9年が経過したらしい。僕は結構長い期間をここで過ごしたことになるんだなと思った。ちょっと以前のことを振り返ってみようと思う。言うまでもないけどこれは僕の書ける範囲での個人的な感想と体験談であって会社の見解等を表しているものではない。

きっかけ

わりと重要でない

Borgチーム (の周辺)

いつのまにやらBorgという名前を普通に言って良くなっている。嬉しい。まあ当時もぶっちゃけ、秘密だから出してないっていうよりは、単に誰もアカデミア的なキャリアに興味が無いから出してなかったんだと思う(私見)。

さて、当時Borgというかクラスタマネージメントのあたりでは、コンピュータのリソースて適当にたくさん使ってるけど、これ節約したらすっげー支出減ったりしない?みたいなのがホットで、なんかとりあえず色々な人々が色んなことをやっていた。いくつかうまくやったチームがあって、実際すっげー減ったんじゃないかな。最初の3ヶ月は練習期間的な感じで、そういうのの一つでチマチマPython書いたりしてた。

で、日本に帰ってきて僕の入ったチームでは、色々なクラスタの変更の結果が予想しやすくなる、みたいなのを作っていた。Borg自体のコード使ったりもしてたこともあり、ちょっとパッチ投げたりもしたけど、まあ総じて重要度はそれほど高くないプロジェクトだったのかなぁと、今となっては思う。でも新卒で入った僕にわかるわけもなく、そしてエンジニアリング的にはすごく勉強になった。指導してくれた人達がすごかった。

glog (憧れの20%プロジェクトは80%より楽しくなかったでござるの巻)

当時僕はGoogleさっさと辞めると思っていた。しかしGoogleにあるものは色々便利なので、なんか色々opensourceになってると良いなあとか思っていた。Google入って最初の頃に特に感銘を受けた文化に、とりあえずログ残して、用途は後で考える、てのがあった。ログつーとアクセスログみたいなやつと、プログラムがこんなことあったぜ、て端末に吐き続けるログがある。前者は当然すさまじく重要なんだけど、後者も有意義。

まークラウドみたいな環境だと、たまーに起きた問題にgdbで対処する、なんてのはあまり現実的でないことが多く、ログとコード見比べて、ああここNULLになりうるな、みたいな感じのデバッグシーンが多い。まあそんな感じで、使いやすいロギングライブラリがあるってのは重要なのである。

で、このくらいの規模感のものなら僕個人でもopensource化とかできないかな?とか思ってやってみた。当時社員番号3番(つまり最初の従業員)の偉い人が色んなものをopensource化しており(gflagsやperftoolsが一例)、この人に相談するといいかな、って思ってメールしたりした。その偉い人は即日色良い返事をくれていたのだけど、パソコン音痴の僕はgmailにおかしなフィルタを設定してしまっており、彼の返事はなんだか目に入らないところにいっていた。1ヶ月ほどしてから、あれ、そういえばあれ返事もらえてないな?とメールボックスを検索したら返事くれてるのに気付いて、さすがに青冷めた。クソ偉い人にいきなりメールして、即日反応してくれたのに1ヶ月放置するクソ新卒である。その後、クソ新卒のコードレビューをずっとやってくれてたんだから3番良い人すぎた。

そのエピソードと、社内のコードがどう育ってきたかというのを読みつつopensourceとして出せるものにしていく考古学的作業は楽しかったが、リリースしてしまうと、*BSDで動かないとかなんとか、うん、めんどくさいね、てなった。

GSLB (のクライアント)

GSLBてのがあって、これクラスタ内じゃなくて、どのクラスタに行くべき?みたいなレベルのロードバランスしてるんですが、それのクライアントライブラリみたいなのを作っていた。

思うになんかこれは、「なんでこんな当然あるべきもんが無いの?」系のプロジェクトだったと思う。よって、テクニカルにはその前のプロジェクトの方が面白いことをやっていたように思うけど、こっちは作れば確実に成功するていう。僕が言い出したとかでは全然なく、指導してた偉い人がUSに行って面白いネタ無いって聞いてきて、こいうのあるらしいからお前やったら、て言われて気付いたらテックリードという象徴天皇みたいなのになってた。

GSLB上流てのはクソ忙しいチームであり彼らとのコミュニケーションが必須ということもあり、たいしたコード量じゃないのにすごい時間がかかる…みたいなプロジェクトだったけど、まあなんか評価はされるみたいな不思議な感じだった。テックリード的な役割て俺向いてないなぁ…とも感じた。でもびっくりするくらいコード書いてない割には評価は悪くないんだよね不思議。

なるほど、企業というのはコーディング力じゃなくて会社に対するインパクトで評価するのだな、という当たり前のことを実感したように思う。

Chrome (で落穂ひろい)

小さいチームはコミュニケーションコスト的に損、ということでインフラぽいプロジェクトが解体される。今思えば、目をひくほどの成果があるわけでも無い、という要因も重要だったと理解している。評価は悪くはないというだけであり、まあ誰にでもできることをやってたという感じで。

僕はなんとなく something new …てことで Chrome に魅かれていた。周りの人はそうでもない人が多くて、2,3人で Chrome やるぜって盛り上がってたと思うんだけど、 Chrome の当時の偉い人が日本に来て、 html5 やるぜーて騒いで、なんか周りの人も乗り気になった。そして東京に Chrome の結構大きい部隊ができた。

まわりもほとんど全員 Chrome 初心者、みたいな感じなので、みんなパッチ修行と称して、細かいバグ潰しては周りの人と情報交換して…みたいな感じでコードベースに慣れていった。割と適当なバグを重要性無視してプチプチ潰す、みたいなのは好きなので、まあプチプチしてた。当時 Chrome てコードが割と雑然としてて、僕綺麗な環境で働くより雑然としたとこの方が得意だってのもあって、まあまあ効率は悪くなかった気がする。やってた作業が重要なものだったかは謎だが。まあEUC-JPのURLコピペして動くようにとかは普通に役に立ったんでないかな。

WebKit (Appleとの謎の関係)

ある程度 Chrome に慣れたら、 html5 の機能を実装していこう、みたいになって、手つけられてない CSS3 とかやったら、みたいなことで WebKit に親しんでいった。そこで CSS の細かいバグプチプチなんかもやったのだけど、それよりも WebKit の開発サポートツールみたいなスクリプト群の出来の悪さや、コーディングスタイルはあるが強制されてないのでみんな破ってる、みたいな状況がうへえ、て思って、後半は C++ より開発サポートツールのために Python 書いてることが多い、みたいな感じになった。

そんなこんなで難しい C++ のコードとか書かずに Python パッチ量産した結果、パッチ数だけは溜まってる上に、やってる作業的に Apple の人に悪印象持たれてないということもあってか、割とさっくり WebKit reviewer というのになった。あとその後は印刷系の CSS3 のなんかとか少しやったと思う。

WebKit は総じて Chrome よりさらに雑然としてたのもあり、やるべきことが無数にあって、まあなんか思いついたとこいじるみたいな感じだった。何をやるべきかって考える時間が無いてのは僕にとっては良いことで、総じてそれなりの効率だったように思う。やってた作業が重要なものだったかは依然として謎だが。次のプロジェクトに移った後で起きた Blink フォークにはびっくりしたな…

goma (名前に意味はない)

東京オフィスでなんか面白いことやろうぜ、ということでオフィス内アイデアコンペみたいなのが行なわれる。アイデアの段階で半分死に、1週間でまた半分、1ヶ月でもう半分、みたいな感じのやつ。

当時 Chrome の開発には割と満足してたけど、グーグルの社内コードベースに比べて格段にビルドが遅いのがむかついていた。で、社内にあるスゴdistccみたいなやつを、そのままパクったらいいんじゃね、的に goma てのを始めた。社内のやつは社内コードベースに特化してるのだけど、まあそうじゃないとこから簡単にセットアップできます、みたいなコンセプトで。

なんかビルド2,3倍くらいは速くなったなー、ユーザ増やすために宣伝しないとかな?とか思ってるうちに、USのちょっと人気者みたいな人が、「goma 試したけど、これあればdistccとかもういらねーよOMGOMG」的なことを言ってくれたので、苦手な宣伝作業というのをしないでも勝手に流行った。なんか気がついたらChromeチームは全員goma使ってた。口コミの強さよ。

みんなが使うと色々問題が起きたり、新しいコンパイラが欲しいとか言ってきたり、などなど…ということでぼんやり平和に過ごしていた。この時 Mac サポートのために半分ネタで作った maloader が本当に使いものになったのは愉快なできごとであった。

ARC (低レイヤおいしいです)

当時僕は Google Native Client というやつの大ファンだった。今でもグーグルが作った一番クールなものの一つくらいに思っている。それの上で Android アプリを動かそうとしてるプロジェクトがある、という噂を聞いていた。入れてもらえないかなとか思ってたら、なんか長期出張から一時帰国してた同僚が誘ってくれて、やることになった。

これはもう本当に想像よりもクレイジーなプロジェクトに、すごい優秀な人達が全力で挑んでる、みたいな感じですごい楽しかった。僕はだいたい、3ヶ月くらい楽しーて仕事したと思ったら、ぼんやり気味な期間が後で来ることが多いんだけど、この時は1年近く楽しさが持続してたと思う。

POSIXPOSIXじゃないものでエミュレートしたり、BionicていうlibcをNaClにポートしたり、seccomp-bpfをNaClの強いsandboxのかわりに使うモードをNaClに足したり、みたいなことやってた。つまり何やってんだかよくわからないことやってた。

僕、社内で ChromeAndroid の存在を知った時は、「なんてアホなプロジェクトだ、こんなの成功するわけない!」て思ってたんだけど、数年後にはその両方とからむプロジェクトをしてたってのは、僕の先見性の無さがよくわかる話だと思う。

NaCl Development Environment (趣味20%)

NaCl ファンとして 趣味で TinyCC を NaCl にポートした上に NaCl にリターゲッティングしたりしてたんだけどGCC もポートしたりしてたので、 NaCl ターゲットの nacl-gcc を NaCl 上で動かす遊びとかをしてた。

ついでに基本的な Unix ツールBash on NaCl の上で動くようにしたりと。つまり Chrome だけあればネットワーク無しでも開発できる、てのが夢というわけ。

したら NaCl チームの人が気にいって、なんかよくわからないクロームエクステンションを作ってくれてた。

https://chrome.google.com/webstore/detail/nacl-development-environm/aljpgkjeipgnmdpikaajmnepbcfkglfa

kati (負けない)

Chrome から Android に移った人が、ビルド遅くて不愉快、 ninja+goma 良かった、みたいなことを言っている、という噂を聞く。 goma 時代にちょっと見た時はとりあえず make がアカンすぎるという感想で、 ARC 時代に Android.mk をだいぶ見慣れてたこともあって、「あーありゃGNU make作りなおすんが手っ取り早いね」「んじゃやってみろ」てな感じでやってみた。

なんか色々トントン拍子でいって、これも宣伝工作とかを特にすることもなく、 Android 原住民が超協力的な感じで、あっというまに Android にチェックインされ、 kati+ninja がデフォルトになり、 make でビルドする手段が消された。

最近は Android.mk の方も少しいじったりしてる。

総じて

自慢じゃないけどキャリアパスとか微塵も考えてなくて、偉くなるには人を指導しなければ、みたいなアドバイスは左の耳から出ていっているので、特にそういう点では、なんの参考にもならないと思う。思うに僕は運が良いと思う。そういう意味でも参考にならないと思う。まああと環境的にも他の企業とだいぶ違うと思う。

でもなんか、特に最近意識的にやってて、結構他の人/環境でも適用できるんでないかなと思ってることとして、小さい投資をたくさんする、てのがある。例えば kati 始めた時とかは、 GNU make のクローン作るとかいかにもできなさそうなんで、「とりあえず一ヶ月ください無理そうならさっさとやめます」みたいな感じではじめた。僕は普段、もう少し細かい単位でも、例えば「来週やってみてダメそうならやめる」みたいな感じで時間を無駄にすることをかなり頻繁にやっている。そういう試みの半分以上は失敗に終わるけど、たいして時間を投資してないのでたいした問題にならない。

これについて、個人的に重要だと思ってることがいくつかあって。まずあまりマジメに相談せず勝手にやるってこと。相談すると、5割くらいの確率で「俺がやってるプロジェクトでその問題は解決するよー」とかいう返事が帰ってくる気がする。やる気が損なわれる。「それうまくいかないと思うよ」て言われてやっぱり失敗したら恥ずかしいよね。明らかな結論が出ない場合、どっちのアプローチが優れてるかとか机上で考えるのも時間のムダ。1週間から1ヶ月しか投資しないなら、作ってみて比べりゃいいんで。あと少人数でやること。人数増えると引っ込みがつきにくくなっちゃって、失敗させるのが難しくなると思う。要はサンクコストでかくしないてことか。当たり前の話だなあ。

あと投資対象、なんとなくだけど、ぱっと思いついただけの、いかにもダメそうなアイデアが割とうまくいく気がしている。「次なにするべきか…」とか熟考した上で出てきたそれっぽいアイデアとか、なんでか知らんけどだいたいうまくいかん気がする。ちょっと理由を考えてみると、ぱっと思いつくようなアイデアは本当に必要性があるからぱっと思いつく、みたいな話かな。

小さい投資たくさんするってのは、割とグーグルから学んだことのように思う。僕がグーグルのいいところだと思ってることに、プロジェクトをガンガン潰す、てことがある。それも失敗してるプロジェクトだから潰す、とかいうレベルじゃなくて、成功してないプロジェクトはふっと潰れるし、小さいプロジェクトは成功してても潰れる。これ、潰されるプロジェクトの人としては、もちろんガッカリ事件になる(特にこれといった失敗をしてない時)。僕も Chrome に移る前に少しガッカリしたと思う。

でも、細かいのガンガン潰さないと、リソースに限りある中で新しいことできないんだよなーと気付いて、なんか新しいことできたから、むしろ潰してくれてありがとーと思うようになり、これ日頃の仕事的にも良い考え方だなーと思うようになった。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h