Hatena::ブログ(Diary)

Accept Things このページをアンテナに追加 RSSフィード

2008-11-23

実はDrizzleの開発に参加してます

長い間ブログを更新できてなくてすみませんでした。

これまでずっと何年もかかって、自分にとっての適度な複雑さ(壁を乗り越える喜び)を持った問題を探し求めてました。Linux Kernel解読、Ruby開発、Emacsコードリーディング、などなど微々たるものですが色々かじってきました。どれも得るものは大きかったのですが、自分の中でどれも何かしっくりこない感じでした。

それで、これは!と思えるものがやっと見つかりました。

Drizzleです。

実は少し前からDrizzleの開発に参加しています。"mysql"を"drizzle"にひたすら置換するといった地味な作業をちょくちょくやってました。(今の自分にできることはこれくらいなので...)

http://jpipes.com/index.php?/archives/272-Drizzle-Cirrus-Milestone-Moving-Forward.html

また、Drizzleの各種メッセージ(エラーメッセージなど)の日本語への翻訳作業なんかも、ちょっとやってます。

Drizzleの開発者のみなさんはとてもフレンドリーで親切です。世界中から凄腕の技術者が集まっています。英語でのコミュニケーションが少し大変ですが、逆に面白いです。

日々サービス開発や製品開発でMySQLのお世話になっている方、今度はDrizzleの開発で恩返しってのはどうでしょう?Drizzleの開発はまだまだはじまったばかり。スキルさえあれば、面白い問題をどんどん解決できるフィールドがそこにはあります。

自分はまだまだで、今は本当に小さい問題しか解決できるスキルを持っていません。なので、ちょっと悔しいです。貢献したくても、自分の力不足を痛感する毎日。自分ができることを少しずつ広げていって、Drizzleのコア開発者と言われるくらいになれたらなぁと妄想を抱いています。ちなみに今はSTLの勉強中。

それではDrizzleの開発でお会いましょう!

お待ちしています。

2008-02-11

GNU Emacs に内蔵されている Lisp インタプリタを C インタフェースから操作する実験

はじめに

今から約 1 ヶ月前、GNU Emacs のコードリーディングをはじめました。祝日などの時間を使って少しずつ GNU Emacs の解析を進めました。今日、ようやくコードリーディングを開始した時に立てた目標が達成できたので、このエントリーを書くことにしました。

GNU Emacs のコードリーディングをはじめた当初の目標は、GNU Emacs に内蔵された 「Lisp インタプリタ」をどうにかショートカットして「C インタフェース」から直接操作することでした。

では、なぜそのような目標を設定したのか少し説明します。

GNU Emacs は、Lisp システムと密に連携してエディタの機能を統合しています。GNU Emacs の世界は Lisp システムによって支配されていると言えます。したがって、GNU Emacs の仕組みを理解するには GNU Emacs に内蔵された Lisp システムの理解が必要不可欠です。確かに Lisp の言語的な側面を学びたいならば、ELisp を書けば、Lisp 自身の勉強にはなるでしょうが、私はそこにはあまり興味がありません。それよりも、むしろどのようにして Lisp システムが GNU Emacsとうまく統合されているのかに興味があります。このような興味があったため、Lisp システムが用意している様々な C インタフェースを通して、Lisp インタプリタを直接操作することを目標にしました。

手がかり

Lisp インタプリタを自由に操作するには、以下を知る必要がありました。

  1. GNU Emacs のコードに散在する DEFUN マクロの役割
  2. Read: 式を Lisp インタプリタが解釈可能な構文木データに変換する方法
  3. Eval: Lisp インタプリタの核の動作方法
  4. Print: Lisp インタプリタで評価した結果を印字可能な文字列に変換する方法
DEFUN マクロ: Lisp と C インタフェースを同時に定義し、Lisp の組み込み関数を作る

GNU Emacs が記述されている C のコードのファイルをいくつもざっと見ていくと、Lisp の世界と C の世界をうまくつなぎ合わせるためのマクロに気がつきました。DEFUN マクロです。例えば、eval 関数は、DEFUNマクロを使って以下のように定義されています。重要なのは、DEFUN マクロの第一引数(eval 関数だと"eval")と第二引数(eval 関数だと、Feval)、そして定義しようとしている関数への引数(eval 関数だと、Lisp_Object form)です。DEFUN マクロを使うことで、Lisp と C インタフェースを同時に定義しつつ、ELisp からも利用できる組み込み関数を作ることができます。

emacs-22.1/src/eval.c

DEFUN ("eval", Feval, Seval, 1, 1, 0,
       doc: /* Evaluate FORM and return its value.  */)
     (form)
     Lisp_Object form;
{
  Lisp_Object fun, val, original_fun, original_args;
  Lisp_Object funcar;
  struct backtrace backtrace;
  struct gcpro gcpro1, gcpro2, gcpro3;

  if (handling_signal)
    abort ();

  if (SYMBOLP (form))
    return Fsymbol_value (form);
  if (!CONSP (form))
    return form;

  QUIT;
[...]

DEFUN で定義された他の関数と比較すればわかるのですが、C インタフェースからアクセスできる関数は、prefix として、必ず"F"がつきます。また、C からアクセスできる関数の名前は、"_"を使って単語を区切っていくのに対し、Lisp からアクセスできる関数は、"-"を使って区切られます。これらのルールは GNU Emacs のコード全体でのルールになっているようです。

emacs-22.1/src/buffer.c

DEFUN ("set-buffer-multibyte", Fset_buffer_multibyte, Sset_buffer_multibyte,
       1, 1, 0,
[...]

こうして、DEFUN マクロの仕組みを理解することで、GDB から Lisp システムをつなぐ重要なインタフェースブレークポイントを自由に置けるようになりました。例えば、こんな感じでブレークポイントを置いて、バックトレースを取れるようになりました。

% cd ~/src/emacs-22.1/src
% gdb ./emacs
[...]
(gdb) b Feval
Breakpoint 3 at 0xe6ebf: file eval.c, line 2205.
(gdb) r
Starting program: /Users/ysano/src/emacs-22.1/src/emacs -geometry 80x40+0+0
Reading symbols for shared libraries .....................................................................+ done
Reading symbols for shared libraries . done

Breakpoint 3, Feval (form=1762389) at eval.c:2205
2205      if (handling_signal)
gdb) bt
#0  Feval (form=1762389) at eval.c:2205
#1  0x000e97f1 in internal_lisp_condition_case (var=41944073, bodyform=1762389, handlers=1762509) at eval.c:1426
#2  0x00117470 in Fbyte_code (bytestr=1762011, vector=1762028, maxdepth=9) at bytecode.c:869
#3  0x000e76d3 in funcall_lambda (fun=1761972, nargs=1, arg_vector=0xbfffeb44) at eval.c:3184
#4  0x000e7b93 in Ffuncall (nargs=2, args=0xbfffeb40) at eval.c:3054
#5  0x0011853a in Fbyte_code (bytestr=1762923, vector=1762940, maxdepth=2) at bytecode.c:679
#6  0x000e76d3 in funcall_lambda (fun=1762900, nargs=0, arg_vector=0xbfffecd4) at eval.c:3184
#7  0x000e7b93 in Ffuncall (nargs=1, args=0xbfffecd0) at eval.c:3054
#8  0x000e8f60 in call0 (fn=42299985) at eval.c:2761
#9  0x0000bfbd in init_display () at dispnew.c:6937
#10 0x00079e2c in main (argc=3, argv=0xbfffee44) at emacs.c:1658

Lisp Backtrace:
"face-set-after-frame-default" (0x1500fdc)
"tty-set-up-initial-frame-faces" (0xb217)

Lisp 対話モード

Read-Eval-Print については、EmacsLisp 対話モードがヒントになりました。Lisp 対話モード (M-x lisp-interaction-mode) では、カーソルの直前にある S 式を C-j で評価してくれます。この Lisp 対話モードでは、まさに Read => Eval => Print を行っています。したがって、Lisp 対話モードの仕組みを調べれば、解決の糸口が見つかるだろうと予想しました。

Lisp 対話モードになった状態で、M-x describe-mode を実行し、C-j が何の関数にバインドされているのか調べました。そうすると、C-j は eval-print-last-sexp 関数にバインドされていることがわかりました。

Commands:
Delete converts tabs to spaces as it moves back.
Paragraphs are separated only by blank lines.
Semicolons start comments.
key             binding
---             -------

TAB		lisp-indent-line
C-j		eval-print-last-sexp
ESC		Prefix Command
DEL		backward-delete-char-untabify

M-TAB		lisp-complete-symbol
C-M-q		indent-pp-sexp
C-M-x		eval-defun

C-M-q		indent-sexp  (shadowed)

grep で eval-print-last-sexp 関数の定義を調べました。

(注意: このエントリーを書いていて気がついたのですが、C-j にバインドされているのは、本当は edebug-eval-print-last-sexp ではなく eval-print-last-sexp です。しかし、結果として、edebug-eval-print-last-sexp の方が役に立ったので、こちらを使います。)

emacs-22.1/lisp/emacs-lisp/edebug.el

(defun edebug-eval-print-last-sexp ()
  "Evaluate sexp before point in outside environment; insert value.
This prints the value into current buffer."
  (interactive)
  (let* ((edebug-form (edebug-last-sexp))
     (edebug-result-string
      (edebug-outside-excursion
       (edebug-safe-prin1-to-string (edebug-safe-eval edebug-form))))
     (standard-output (current-buffer)))
    (princ "\n")
    ;; princ the string to get rid of quotes.
    (princ edebug-result-string)
    (princ "\n")
    ))   

edebug-last-sexp 関数が怪しそうだったので、edebug-last-sexp 関数の定義を見ました。emacs-22.1/lisp ディレクトリ以下の中には、read-from-string 関数の定義は見つかりませんでした。

emacs-22.1/lisp/emacs-lisp/edebug.el

(defun edebug-last-sexp ()
  ;; Return the last sexp before point in current buffer.
  ;; Assumes Emacs Lisp syntax is active.
  (car 
   (read-from-string
    (buffer-substring
     (save-excursion
       (forward-sexp -1)
       (point))
     (point)))))

そこで、emacs-22.1/src 以下で grep してみたところ、C 言語で定義された関数が見つかりました。先ほど説明した DEFUN マクロで定義されています。どうやら、この関数を使えば、文字列として表現された S 式を Lisp インタプリタ (eval) が解釈できる内部形式(構文木)に変換できそうです。

emacs-22.1/src/lread.c

DEFUN ("read-from-string", Fread_from_string, Sread_from_string, 1, 3, 0,
       doc: /* Read one Lisp expression which is represented as text by STRING.
Returns a cons: (OBJECT-READ . FINAL-STRING-INDEX).
START and END optionally delimit a substring of STRING from which to read;
 they default to 0 and (length STRING) respectively.  */)
     (string, start, end)
     Lisp_Object string, start, end;
{
  Lisp_Object ret;
  CHECK_STRING (string);
  /* read_internal_start sets read_from_string_index. */
  ret = read_internal_start (string, start, end);
  return Fcons (ret, make_number (read_from_string_index));
}
評価した結果を印字可能な文字列に

Read-Eval-Print の処理が書かれた Elisp (emacs-22.1/lisp/emacs-lisp/edebug.el) を読んでも、評価した結果を印字可能な文字列に変換する方法はわかりませんでした。

そこで、違うソースコードを読みあさりました。そうすると、eval-buffer 関数の中で、readevalloop と言ういかにも怪しい関数を見つけました。

emacs-22.1/src/lread.c

DEFUN ("eval-buffer", Feval_buffer, Seval_buffer, 0, 5, "",
       doc: /* Execute the current buffer as Lisp code.
Programs can pass two arguments, BUFFER and PRINTFLAG.
BUFFER is the buffer to evaluate (nil means use current buffer).
PRINTFLAG controls printing of output:
A value of nil means discard it; anything else is stream for print.

If the optional third argument FILENAME is non-nil,
it specifies the file name to use for `load-history'.
The optional fourth argument UNIBYTE specifies `load-convert-to-unibyte'
for this invocation.

The optional fifth argument DO-ALLOW-PRINT, if non-nil, specifies that
`print' and related functions should work normally even if PRINTFLAG is nil.

This function preserves the position of point.  */)
     (buffer, printflag, filename, unibyte, do_allow_print)
     Lisp_Object buffer, printflag, filename, unibyte, do_allow_print;
{
[...]
  readevalloop (buf, 0, filename, Feval,
		!NILP (printflag), unibyte, Qnil, Qnil, Qnil);
[...]
}

コードを追ってみると、Fprin1 関数を使えば、Feval で評価した結果を印字可能な文字列に変換できることがわかりました。

emacs-22.1/src/lread.c

/* UNIBYTE specifies how to set load_convert_to_unibyte
   for this invocation.
   READFUN, if non-nil, is used instead of `read'.

   START, END specify region to read in current buffer (from eval-region).
   If the input is not from a buffer, they must be nil.  *

static void
readevalloop (readcharfun, stream, sourcename, evalfun,
	      printflag, unibyte, readfun, start, end)
     Lisp_Object readcharfun;
     FILE *stream;
     Lisp_Object sourcename;
     Lisp_Object (*evalfun) ();
     int printflag;
     Lisp_Object unibyte, readfun;
     Lisp_Object start, end;
{
[...]
      /* Now eval what we just read.  */
      val = (*evalfun) (val);

      if (printflag)
	{
	  Vvalues = Fcons (val, Vvalues);
	  if (EQ (Vstandard_output, Qt))
	    Fprin1 (val, Qnil);
	  else
	    Fprint (val, Qnil);
	}
[...]

Fprin1 関数でも、機能的には十分でしたが、標準出力などの stream に対して結果を出力するのではなく、文字列データとして出力する関数があれば良いなあと思いました。それで、探してみると、Fprin1_to_string 関数でそれができることがわかりました。

emacs-22.1/src/print.c

DEFUN ("prin1-to-string", Fprin1_to_string, Sprin1_to_string, 1, 2, 0,
       doc: /* Return a string containing the printed representation of OBJECT.
OBJECT can be any Lisp object.  This function outputs quoting characters
when necessary to make output that `read' can handle, whenever possible,
unless the optional second argument NOESCAPE is non-nil.  For complex objects,
the behavior is controlled by `print-level' and `print-length', which see.

OBJECT is any of the Lisp data types: a number, a string, a symbol,
a list, a buffer, a window, a frame, etc.

A printed representation of an object is text which describes that object.  */)
     (object, noescape)
     Lisp_Object object, noescape;
{
[...]

統合

ここまで調査してきた結果を統合して、GNU Emacs を改造しました。簡単のため、emacs引数に S 式を与え、それを評価した結果を標準出力に出力するようにしました。

こんなコードを作りました。

emacs.c の main 関数の最後にある Frecursive_edit 関数を実行しないようにして、その直前で S 式を評価するコードを組み込みました。Frecursive_edit 関数を実行しないと、Emacs は編集のためのユーザインタフェースを立ち上げません。Frecursive_edit 関数の直前は、Feval 関数で S 式を評価するための準備が十分にできているため、この場所にコードを書くことにしました。

build_string 関数は、C の文字列を Lisp_Object 形式に変換します。Fread_from_string 関数で、S 式を構文木に変換します。その結果は、cons セルになっていて、ほしいのは car の方なので、Fcdr 関数を使っています。構文木を Feval 関数に与え、その結果を Fprin1_to_string 関数で、印字可能な文字列に変換しています。XSTRING マクロは、Lisp_Object から C の文字列にアクセスするために使いました。

--- emacs.c.orig	2008-02-11 09:28:08.000000000 +0900
+++ emacs.c	2008-02-11 12:04:49.000000000 +0900
@@ -1758,8 +1758,32 @@
   tzset ();
 #endif /* defined (LOCALTIME_CACHE) */
 
+#define HACK_REP
+#ifdef HACK_REP
+  {
+    Lisp_Object str;
+    Lisp_Object expr_and_pos;
+    Lisp_Object expr;
+    Lisp_Object result;
+
+    if (argc < 2) {
+      fprintf(stderr, "Usage: %s sexp\n", argv[0]);
+      fprintf(stderr, "Example: %s \"(+ 1 2)\"\n", argv[0]);
+      exit(1);
+    }
+
+    str = build_string(argv[1]);
+    expr_and_pos = Fread_from_string(str, Qnil, Qnil);
+    expr = Fcar(expr_and_pos);
+    result = Feval(expr);
+  
+    str = Fprin1_to_string(result, Qt);
+    printf("=> %s\n", XSTRING(str)->data);
+  }
+#else /* HACK_REP */
   /* Enter editor command loop.  This never returns.  */
   Frecursive_edit ();
+#endif /* HACK_REP */
   /* NOTREACHED */
   return 0;
 }

ビルドして実行するとこんな感じで S 式を評価してくれます。

% cd ~/src/emacs-22.1/src
% make temacs
% ./temacs "(+ 1 2)"
=> 3
% ./temacs "(cons 'hello 'world)"
=> (hello . world)
% ./temacs "(car(cons 'hello 'world))"
=> hello

実行ファイルが emacs ではなく temacs となっている所に注意して下さい。実行ファイル emacs は、実行ファイル temacs によって生成されます。しかし、今回 Emacs を改造して、S 式の評価以外何もできないようにしたので、temacs ファイルから emacs ファイルを生成できなくなりました。そこで、temacs を使うようにしました。

まとめ

GUN Emacs を改造して、C インタフェースから S 式を評価できるようになりました。今回の実験によって、GNU EmacsLisp システムについてより一層理解が深まりました。

実は、GNU EmacsGDBデバッグしやすくするための工夫が入っています。例えば、GDB セッション中に Lisp_Object の内容をインスペクトする方法なんかが提供されています。また今度の機会に紹介できたらなと思います。

2008-01-06

Emacs のコードリーディングのための Carbon Emacs のビルド

Mac OS XCarbon Emacs を自力でビルドできるかどうか試してみたところ、あっさりできました。

なぜ、Carbon Emacs を自力でビルドしたくなったのかと言うと、梅田望夫さんの例の本を読んで、Richard Stallman によって書かれた Emacs のコードを趣味で読んでみたくなったからです。

それで、コードを読むからには、まずは自力でビルドできないといけないかなと思いました。

ビルドログ

備忘録のためにビルドの際の作業ログを残しておきます。

% uname -rs
Darwin 8.10.1
% cd ~/src
% wget http://ftp.gnu.org/gnu/emacs/emacs-22.1.tar.gz
% tar zxvf emacs-22.1.tar.gz
% cd emacs-22.1
% ./configure --enable-carbon-app=/Users/ysano/carbon
% make
% sudo make install

正常にビルドが完了すると、以下に Carbon Emacs がインストールされました。

/Users/ysano/carbon/Emacs.app/Contents/MacOS/EmacS

規模の下調べ

Emacs のソースの量を簡単に調べてみたところ、約 36 万行。

読むのはなかなか手強そう。。。

% cd ~/src/emacs-22.1/src
% find . -type f | egrep "\.(c|h)$"  | xargs cat | wc -l
  358500

main 関数はどこ?

今は Emacs がどのようにして立ち上がるのか興味があるので、まず main 関数を調べてみることにしました。

main 関数の場所を grep で探してみました。

main の後は、当然「(」が続くだろうと予想して grep してみたところ、よくわからないスタートアップルーチンしかヒットしませんでした。

% cd ~/src/emacs-22.1/src
% grep -rn "main(" .
./ecrt0.c:89:extern     void    free(), (*_libc_free) (); extern int main();
./ecrt0.c:90:std_$call void  unix_$main();
./ecrt0.c:97:   unix_$main(main);       /* no return */
./regex.c:1655:/* BEWARE, the value `20' is hard-coded in emacs.c:main().  */

明らかにおかしいことに気がつきました。

そこで、GDB を使って調べてみることにしました。

main 関数に break point を打ってみました。

そうすると、emacs.c で main 関数が定義されていることがわかりました。

こういう時に GDB は便利ですね。

% gdb carbon/Emacs.app/Contents/MacOS/Emacs
(gdb) b main
(gdb) run
[...]
Breakpoint 1 at 0x778c1: file emacs.c, line 810.

実際にソースを見てみると、main と「(」の間に半角スペースがあることが入っていました。

grep で 期待していた main 関数がヒットしなかったのは、これが原因だったということがわかりました。

他の関数の定義も見てみた所、関数名と「(」の間には半角スペースが入っていました。

こういうコーディングスタイルのコードを読むのは初めてです。

% emacs emacs.c
[...]
/* ARGSUSED */
int
main (argc, argv 
#ifdef VMS
, envp 
#endif
)
     int argc;
     char **argv;
#ifdef VMS
     char **envp;
#endif
{
[...]
static void
sort_args (argc, argv)
     int argc;
     char **argv;
{ 
[...]

GNU Global を使うという手もあった

そう言えば、GNU Global の存在を忘れていました。

GNU Global でも関数が定義されている場所を手軽に検索できますが、探している関数の候補が複数あり、かつ GDB で適切な break point を打てるような場合は、やはり GDB を使うのが一番早いかと思います。

% cd ~/src/emacs-22.1/src
% gtags -v
% global -x main
main              794 emacs.c          main (argc, argv
main             1002 getloadavg.c     main (argc, argv)
main             10523 macterm.c        #undef main
main             10525 macterm.c        main (void)
main              477 mktime.c         main (argc, argv)
main               58 prefix-args.c    main (argc, argv)
main              830 termcap.c        main (argc, argv)
main              331 tparam.c         main (argc, argv)
main              745 xrdb.c           main (argc, argv)

この続きは?

また時間を作って、ゆっくり Emacs のコードを読んでみたいなぁと思います。

2007-12-16

ディレクトリスタックを zsh 間で共有してみよう

zsh を hack して、ディレクトリスタックを zsh 間で共有できるようにしてみました。

# 長い間お蔵入りになっていたネタを引っぱりだしてきました。


zsh を使っている方の多くは、zshGNU Screen と組み合わせて利用されているケースが多いと思います。そして、zsh のコマンド履歴を、GNU Screen の中で立ち上げた zsh 間で共有するように設定している方も多いのではないでしょうか。コマンド履歴を共有できる便利さを知っている方なら、「ディレクトリスタックも共有してみたい!」と一度は考えた方もおられるかもしれません。しかしながら、従来の zsh では、zsh 間でディレクトリスタックを共有することはできませんでした。そのような設定項目はありませんでした。そこで、zsh でコマンド履歴が zsh 間で共有できるのと同じ発想で、ディレクトリスタックも共有できれば便利ではないかと思い、zsh を hack して、それを実現してみした。


まだ、最低限の機能しか実装していません。多分、バグもあります。ファイルベースでディレクトリスタックの内容を共有するのですが、zsh 間でファイルの整合性を保証するような実装にはなっていません。つまり、ファイルの排他処理はまだできていません。


興味のある方は一度試してみて下さい。

以下のコマンドで、実験的にディレクトリスタックを共有できるようにした zsh をインストールできます。--enable-share-dirstack オプションを指定することで、ディレクトリスタックの共有が有効になります。必要に応じて、他のオプションを有効にして下さい。

% cd /usr/local/src
% wget http://nchc.dl.sourceforge.net/sourceforge/zsh/zsh-4.3.4.tar.gz
% tar zxvf zsh-4.3.4.tar.gz
% cd zsh-4.3.2
% wget http://vaio.redirectme.net/patch/zsh-4.3.2-dirstack-sharing.20071216.patch
% patch -p0 < zsh-4.3.2-dirstack-sharing.20071216.patch
% ./configure --prefix=/usr/local/zsh_hack --enable-share-dirstack
% make
% sudo make install

2007-12-09

こんなブログでも

読んでくれる方がいるのだから、少しずつ記事を書ける状態を作っていきたい。

2007-12-08

特別な一日

今日は僕にとって特別な一日でした。

id:amachang からの熱いメッセージを聞いて本当に感動しました。

いっぱい共感しました。こんなに共感できたのは久しぶりです。

自分の中で忘れかけていた感覚を思い起こしてくれました。

id:amachang

今日は本当にお疲れさまでした。

またゆっくりお話したいですね ^^


僕が思うに、id:amachang は C 言語をもっとさわれるようになれば、もっともっとできることが広がるはず。

GDB を使って、いろいろソフトを解析して遊んでいたら着実に C 言語の運用力は向上します。

GDB はマジでおすすめです。恐れずに GDB 使ってみてください。


僕は C 言語はそれだけ大切な言語だと思っています。


応援してます!

2007-04-07

多面的に人や会社を見ようよ。良い所を見つめようよ。絶対その方が面白いよ。

知性なき「はてな」−本の読めない技術者・伊藤直也 (セックスなんてくそくらえ)

久しぶり「カチン」ときた。これはスルーすべきじゃないと思った。僕は、日本語が得意じゃないから、誤解やうまく表現できないこともあると思うけど、ちょっと思ったことを書いてみようと思う。発散していて読みにくいかもしれないけど、ごめんなさい。

まず、伊藤直也さんの本棚を単にブログのエントリーで見ただけで、伊藤直也さんが本が読めない(読まない)技術者って決めつけるなんて単細胞すぎない?頭が硬すぎる。というか、ネットとかの媒体を通してしか、はてな伊藤直也さんを見ていないのに、はてなや彼の何がわかるのか。

確かに、ブログで紹介されていた伊藤直也さんの本棚をパッと見ただけでは、noon75さんがそういう印象を持つ気持ちはわからないでもない。noon75さんは、はてな伊藤直也さんを、ブラウザを通して見えるテキストとか画像からしか見たことが無いから、そんな印象を持ったのかな。伊藤直也さんが本が読めない(読まない)技術者かどうかを、そんなブログのエントリだけで決めつけるのは、非常識だと思う。

実は僕は一年ほど前に、はてなで働きたくて、採用面談などで、はてなの社員の皆様のお世話になったことがあります。その時に伊藤直也さんとも、もちろんお話をしました。採用面談をした後に、はてなの職場で、はてなスタッフの皆様と飲み会がありました。お酒を飲みながら伊藤直也さんを見ていて思ったことは、伊藤直也さんは心が広くて暖かい人柄の持ち主だということです。教養もあって、一緒にお話をしていてもとても面白い方です。飲み会の場にいて、直感的に「この人だから、はてなのCTOが務まるんだな」と思いました。僕は、伊藤直也さんを実際に自分の目で見て、お話したことがあるだけに、短絡的に伊藤直也さんが本が読めない(読まない)技術者というふうに決めつけられて、DISられるのがとても悔しい。はてな伊藤直也さんのことを良く知るPerlコミュニティーのみんなも、こんなDISられかたして悔しいと思わない?

自分の大切な漫画を本棚に置いておいて何が悪い?「本」って活字ばっかりで書かれたものじゃないと「本」として一人前に扱われないの?noon75さんは、漫画という芸術をバカにしているように、僕には思える。漫画からも学んだり得られらたりするものは多いのでは?どんな本でも、自分にとって大切なメッセージが込められていると思ったら、漫画であろうと何であろうと関係無くかけがいのないモノになるんじゃないの。そんな本を、自分の本棚に大切にとっておきたい。僕はそう思う。

実は、僕は最近、東京の方に引っ越してきて、京都の実家に大量の本を置いて上京してきました。本当に必要と思う本だけを選んだので、僕も伊藤直也さんのような本棚になりました。オライリーの本が多いかな。だから、僕も今自分の本棚を撮って、ブログのエントリーを書いたら、同じような印象を持たれちゃうかな。

あとスターウォーズを見てワクワクするのが悪い?一緒に働くメンバー全員が、「同じ価値観を少しでも多く共有すること」って凄い大切だと思う。

本から学ぶことは、とても多いと思うし、それ抜きではやっていけない。それは認める。でも、本から学ぶことって、一生の人生の中でどれだけの割合を占めるのかな?僕は、「本」から学べるものって、人生の全体からして、少なくとも1番では無いと思う。つまり、「本」が一番って価値観は持ってない。

僕は、前に、にぽたん(id:nipotan)さんをDISってしまって、凄い後悔して反省して、どうしようもないくらい落ち込んだことがある。だからDISる人の気持ちも、逆にDISられる人の気持ちも良くわかる。DISることで、逆に真実が見えてくることがあるとは思うけれども、やっぱりDISられた人の気持ちを考えると、もう絶対に人を簡単にDISるなんてできない。もしかしたら、今僕が書いたこのエントリーを読むと、今僕がnoon75さんのことをDISってると思うかもしれない。けど、そうじゃない。何と言うか、僕は、noon75さんにはもっと冷静になって大人の議論をしてほしいと願っている。DISることよりも、もっと大切なことが無いか、noon75さんにはもっと考えてほしい。そして、noon75さんには、noon75さん自身がとった言動が、はてな伊藤直也さんにどのような影響(ポジティブな面と、ネガティブな面)を与えたのか、もう一度冷静になって考えてほしい。

悪い所を指摘する時は、良い所も一緒に言うようにすれば、指摘した側も指摘された側も、悪い気持ちにはならないんじゃないか、このエントリーを書いていて、ふと単純に思った。そんな単純には、うまく行かないかな。難しいな。

最後に、僕がnoon75さんに贈りたいメッセージがあります。それは「多面的に人や会社を見ようよ。良い所を見つめようよ。絶対その方が面白いよ。」です。人や会社ってそんなに単純じゃなくて、数えきれない次元の数の評価軸があると、僕は思います。そして、悪い所もあれば、良い所も絶対あります。良い所を見つけて、指摘してあげるのって、楽しいと思いませんか?良い所を褒めてあげて、それによって、さらにその人や会社が成長して、世界中の人々を幸せにしていってもらえば、結局自分自身にとっての幸せにもつながると僕は思います。

これまで全く知らなかったnoon75さんがとった言動には一部、僕は感謝しています。なぜなら、noon75さんがそんなエントリーを書かなかったら、今僕は、このエントリーを通して、はてな伊藤直也さんのこと、本に対する考え方、僕が過去に犯した過ち、について書こうとは思わなかったはずだから。チャンスを与えてくれたことには感謝しています。

本当にありがとうございます。

2007-03-13

Vim: 「*」キーでハイライト検索した時にカーソルが次の候補に移動しないようにする方法

検索する際に,"hlsearch" オプションを有効にして,検索語をハイライトし,視認性を高める."*" コマンドでカーソル位置の単語をサクッと検索する.

Peace Pipe: 効率的なテキスト編集の7つの習慣

確かに「*」キーでハイライト検索するとサクッと検索できて便利です。しかし、Vimデフォルトの設定では、ヒットした文字列が複数ある場合、カーソルが次の候補に移動してしまい、ちょっとビックリします。カーソルは次の候補に移動しないで単に、現在のカーソルにあるキーワードをハイライトしてくれるだけで十分です。

.vimrcに以下の設定を書いておけば、勝手に次の候補にカーソルが移動しなくなります。

% vim ~/.vimrc
[...]
nmap * *N

仕掛けは簡単で、「*」キーで次の候補に移動してしまったカーソルを「N」コマンドで強制的に戻しているだけです。

2007-02-12

Vim: GNU Screenの環境下でctagsによるタグジャンプの操作を快適にする設定

ctagsとvimを連携させて使うと、C言語などのプログラムをコーディング中に、関数など定義位置に簡単にジャンプできて便利です。デフォルトvimの設定では、"Ctrl + ]"で関数などの定義位置に移動し、"Ctrl + t"で元の位置に戻れます。

GNU Screenで新しいウインドウを開いたりする時に使うprefixキーは、デフォルトではCtrl + aです。しかし、それをカスタマイズしてCtrl + tに変更している方も多いはずです。僕もCtrl + tに変更しています。(zshではCtrl + aを押した時に、カーソルが先頭に行くのですが、そのキーバインドGNU Screenのprefixキーがかぶらないないようにするために、変更しています。)

ここで少し困ったことが起きます。prefixキーをCtrl + tに変更したGNU Screenvimを一緒に使うと、vimでタグジャンプから戻るためのコマンド(Ctrl + t)が使えなくなります。Ctrl + tを押すと、それはGNU Screenでのprefixキーとして認識されてしまいます。GNU Screenvimでのキーバインドの衝突を回避する必要があります。

そこで、.vimrcに以下の設定を追加します。この設定をすると、"Ctrl + t"の代わりに、"Ctrl + ["でタグジャンプから戻れるようになります。"Ctrl + ]"でタグジャンプするので、その逆の操作は"Ctrl + ["でできれば直感的だと考えました。

% vim ~/.vimrc
[...]
nmap <c-[>  :pop<CR>

2007-02-11

YARV: GDBとちょっとしたHackによる仮想マシン内のスタックの状態変化の観察

はじめに

仮想マシンが導入されたRubyの動作を理解するには、仮想マシン内のスタックの状態変化を頭の中でイメージできるようになることが重要です。そこで今回は、仮想マシン内のスタックの状態変化を追いやすくする小さなツールを作り、そのツールをGDBと組み合わせて使うことで、仮想マシン内のスタックの状態変化を観察できるようにする方法を紹介します。なお、使用するソースコードはr11701です。

ツールの作成

仮想マシン内のスタックの状態変化を追いやすくするために、以下の機能をrubyに追加します。

  • breakpoint: RubyプログラムからSIGTRAPを発生させる
  • dump_stack: スレッドに関連づけられているスタックの中身をinspectした状態でダンプする

前者を実装すると、GDB上でRubyプログラムを実行し、「breakpoint」と書かれた所が実行された時に、制御がGDBに移るようにできます。これによって、関心のある位置におけるスタックの状態を、GDBで簡単に調査できるようになります。

vm_dump.cで定義されているvm_stack_dump_raw関数を使うと、スレッドに関連づけされている、スタックと制御フレームをダンプできます。しかし、この関数は、スタックの中身を生のVALUEでダンプするため、何のデータがスタックに格納されているのかわかりづらいという問題があります。そこで、後者を実装し、スタックに何が格納されているのか簡単に確認できるようにします。

ちなみに、vm_stack_dump_raw関数は以下のようなダンプを出力します。stack frameの一番右側の列が、VALUEを16進数で表現した値です。

(gdb) p vm_stack_dump_raw(th, th->cfp)
-- stack frame ------------
0000 (0x605000): 00000004
0001 (0x605004): 00000005
0002 (0x605008): 004ce4d8
0003 (0x60500c): 004ce4c4
0004 (0x605010): 004ce4b0
0005 (0x605014): 00000004
0006 (0x605018): 00000001
0007 (0x60501c): 00000004
0008 (0x605020): 00000004
0009 (0x605024): 00000001 <- lfp <- dfp
-- control frame ----------
c:0004 p:---- s:0010 b:0010 l:000009 d:000009 CFUNC  :breakpoint
c:0003 p:0063 s:0007 b:0007 l:000006 d:000006 TOP    stack.rb:21
c:0002 p:---- s:0002 b:0002 l:000001 d:000001 FINISH 
c:0001 p:---- s:0000 b:-001 l:000000 d:000000 ------ 
---------------------------

作成したツールのソースコードは、以下の通りです。

前者については、int3ソフトウェア割り込みが発生するようにしています。rb_define_global_functionのAPIを使って、rb_f_breakpoint関数を、Rubyプログラムからbreakpoint関数として利用できるようにしています。簡単のため、object.cのInit_Object関数の最後で、その関連付けをするようにしました。

後者については、rb_p関数を使って、スタックの中身をinspectした形でダンプするようにしています。スタックの底からtopに向かってダンプします。vm_dump.cに定義します。

% svn diff
Index: object.c
===================================================================
--- object.c    (revision 11701)
+++ object.c    (working copy)
@@ -2284,6 +2284,13 @@
  *  <code>Symbol</code> (such as <code>:name</code>).
  */
 
+VALUE
+rb_f_breakpoint(void)
+{
+    asm volatile("int3");
+    return 0;
+}
+
 void
 Init_Object(void)
 {
@@ -2465,4 +2472,6 @@
     id_eql = rb_intern("eql?");
     id_inspect = rb_intern("inspect");
     id_init_copy = rb_intern("initialize_copy");
+
+    rb_define_global_function("breakpoint", rb_f_breakpoint, 0);
 }
Index: vm_dump.c
===================================================================
--- vm_dump.c   (revision 11701)
+++ vm_dump.c   (working copy)
@@ -608,3 +608,14 @@
     }
 #endif
 }
+
+void
+dump_stack(rb_thread_t *th)
+{
+    VALUE *p = th->stack;
+    while (p < th->cfp->sp) {
+        fprintf(stderr, "%p: ", p);
+        rb_p(*p);
+        p++;
+    }
+}

GDBによる観察

以下のような、RubyプログラムとGDBを使って、仮想マシン内のスタックの状態変化を観察してみます。このプログラムに与えられた引数によって、breakpointが実行される位置が変更できるようにしてあります。また、トップレベルとfooメソッドとbarメソッドで、ローカル変数が作られています。

% vim stack.rb
$arg = ARGV.shift.to_i || 1

def foo(x, y, z)
  a = "a (foo)"
  breakpoint if $arg == 2
  bar(x, y)
  breakpoint if $arg == 4
end

def bar(x, y)
  b = "b (bar)"
  puts x
  puts y 
  breakpoint if $arg == 3
end

x = "x (top)"
y = "y (top)"
z = "z (top)"

breakpoint if $arg == 1

foo(x, y, z)

それでは、このstack.rbをGDB上で動作させてみます。

まず、stack.rbに引数「1」を与えてrunしてみます。

そうすると、SIGTRAPシグナルでGDBに制御が移ります。SIGTRAPシグナルが発生したのは、先ほど作ったrb_f_breakpoint関数の中でint3ソフトウェア割り込みが発生させたためです。

% ./ruby -v
ruby 1.9.0 (2007-02-11 patchlevel 0) [i686-darwin8.8.1]
% gdb --quiet ./ruby
Reading symbols for shared libraries ... done
(gdb) run stack.rb 1
Starting program: /Users/ysano/ruby-trunk/ruby stack.rb 1
Reading symbols for shared libraries .... done

Program received signal SIGTRAP, Trace/breakpoint trap.
rb_f_breakpoint () at object.c:2292
2292    }
(gdb) bt
#0  rb_f_breakpoint () at object.c:2292
#1  0x0008788f in th_eval (th=0x2ce000, initial=0) at insns.def:1289
#2  0x0008a47b in th_eval_body (th=0x2ce000) at vm.c:1598
#3  0x0008a76f in rb_thread_eval (th=0x2ce000, iseqval=5040120) at vm.c:1804
#4  0x00002e91 in ruby_exec_internal () at eval.c:228
#5  0x00002ebc in ruby_exec () at eval.c:241
#6  0x00006fc0 in ruby_run () at eval.c:260
#7  0x00002533 in main (argc=3, argv=0xbffffa18, envp=0xbffffa28) at main.c:47

フレーム#1に移動して、先ほど作ったdump_stack関数で、th_eval_body関数の第一引数に渡されたスレッド(th)に関連付けされたスタックをダンプしてみます。ダンプされた結果を見ると、0x605008〜0x605010に、トップレベルで定義したローカル変数がスタックに積まれていることがわかります。(その他の値がなぜこのように積まれているのかは、まだ理解できていません。。。)

(gdb) f 1
#1  0x0008788f in th_eval (th=0x2ce000, initial=0) at insns.def:1289
1289        macro_eval_invoke_method(recv, klass, id, num, mn, blockptr);
(gdb) whatis th
type = rb_thread_t *
(gdb) p dump_stack(th)
0x605000: nil
0x605004: 2
0x605008: "x (top)"
0x60500c: "y (top)"
0x605010: "z (top)"
0x605014: nil
0x605018: 0
0x60501c: nil
0x605020: nil
0x605024: 0
$1 = void
(gdb) 

次に、stack.rbの引数に2を与えて、再度観察してみます。

fooメソッドが呼ばれた時点で、実行が停止します。

スタックが拡張されていることがわかります。

0x605020〜0x605028は、fooメソッドに渡された引数で、0x60502cは、fooメソッド内で定義したローカル変数aです。

それらの前にある0x60501cのnilは、foo(関数風メソッド)を呼び出した時に積まれた、ダミーのレシーバオブジェクトです。

0x605018と0x605034では0が出力されていますが、これは恐らくspecvalです。(specvalは論理的な固まりの境界を表現しているのかな?)

(gdb) run stack.rb 2
Starting program: /Users/ysano/ruby-trunk/ruby stack.rb 2
Reading symbols for shared libraries .... done

Program received signal SIGTRAP, Trace/breakpoint trap.
rb_f_breakpoint () at object.c:2292
2292    }
(gdb) f 1
#1  0x0008788f in th_eval (th=0x2ce000, initial=0) at insns.def:1289
1289        macro_eval_invoke_method(recv, klass, id, num, mn, blockptr);
(gdb) p dump_stack(th)
0x605000: nil
0x605004: 2
0x605008: "x (top)"
0x60500c: "y (top)"
0x605010: "z (top)"
0x605014: nil
0x605018: 0
0x60501c: nil
0x605020: "x (top)"
0x605024: "y (top)"
0x605028: "z (top)"
0x60502c: "a (foo)"
0x605030: nil
0x605034: 0
0x605038: nil
0x60503c: nil
0x605040: 0
$1 = void
(gdb)

しつこく、stack.rbの引数に3を与えて、再度観察してみます。

スタックがさらに拡張されました。規則性が見えてきました。

(gdb) run stack.rb 3
Starting program: /Users/ysano/ruby-trunk/ruby stack.rb 3
Reading symbols for shared libraries .... done
x (top)
y (top)

Program received signal SIGTRAP, Trace/breakpoint trap.
rb_f_breakpoint () at object.c:2292
2292    }
(gdb) f 1
#1  0x0008788f in th_eval (th=0x2ce000, initial=0) at insns.def:1289
1289        macro_eval_invoke_method(recv, klass, id, num, mn, blockptr);
(gdb) p dump_stack(th)
0x605000: nil
0x605004: 2
0x605008: "x (top)"
0x60500c: "y (top)"
0x605010: "z (top)"
0x605014: nil
0x605018: 0
0x60501c: nil
0x605020: "x (top)"
0x605024: "y (top)"
0x605028: "z (top)"
0x60502c: "a (foo)"
0x605030: nil
0x605034: 0
0x605038: nil
0x60503c: "x (top)"
0x605040: "y (top)"
0x605044: "b (bar)"
0x605048: nil
0x60504c: 0
0x605050: nil
0x605054: nil
0x605058: 0
$1 = void
(gdb) 

最後に、stack.rbの引数に4を与えて、実行すると、引数に2を渡した時とスタックが同じ状態になります。(試してみて確認してみて下さい。) このような結果になったのは、両者のbreakpointが同じスコープで実行されたからです。

おわりに

今回は、仮想マシン内のスタックの状態変化を観察してみました。観察をしやすくするために、rubyGDBと連携して利用するツールを組み込みました。観察を通して、スタックの状態変化を頭の中で少しイメージできるようになりました。

スタックの状態変化は、仮想マシン内の状態変化のごく一部に過ぎません。今後は、他の状態変化についてもGDBで動的解析できるような工夫をして、理解を深めたいと思います。

参考文献

今回紹介したbreakpointメソッドの実装には、Binary Hacksで解説されているHack #92 「Cのプログラムの中でブレークポイントを設定する」を用いました。

Binary Hacks ―ハッカー秘伝のテクニック100選

Binary Hacks ―ハッカー秘伝のテクニック100選