core dumpでcode golf
いかに短いコードでcore dumpさせるか

     野村裕佑(@yunomu11)
事の起こり

私が管理してるサーバでcoreファイルがディスク容量
を食いつぶすトラブルがあった

解決策を打った

試しにcore dumpするプログラム(C言語)を書いた

「なんでそれでcore dumpするの?」って聞かれて、深
く考えると面白いかもと思った
core dumpとは

UNIX/Linux系OSの機能
プロセス実行時のエラーにより、プロセスの動作継続
が不可能だとOSが判断した時、プロセス強制終了と
同時にメモリ内容などをディスクに書き込む
 書き込み先のファイルを通称coreと呼ぶ
 coreはプロセスの死体。死ぬ直前の情報(メモリ内
 容など)が詰まってる→バグ解析に役立つ
アクセス保護違反・存在しない命令・その他不整合
 segmentation fault/bus error
要するにcore dumpとは


不正な操作でプロセスが死ぬ時に起きる

不正な操作とは:アクセス保護違反、不正な命
令、ハードウェア故障、など………

普通はプログラムのバグ
core dumpさせるためには

実行時エラーが起きるプログラムを書いて
    プロセスを殺せばいい

       やってみた
   ついでにgolfに挑戦してみた
       (C言語)
確実に殺す

                              32bit版UNIX/Linux系限定
int main()
{                             Warningも出ない完璧なcode
    int *a;
    a = (int *) 0xc0000000;   メモリの0xc0000000番地に
    *a = 0;
                              0を書き込む
    return 0;
}
                              size: 69bytes
なんで死ぬの
0

             32bit版UNIX/Linux系OS限定
     ユーザ
             プロセスは4GBの仮想メモリを持つ
     領域
             3GB以降の領域はOSが予約

3G            OSしかアクセス出来ない
     OS領域     → アクセス保護違反
4G
ほぼ確実に殺す


int main()
{                      64bit版やWindowsでもいけるが、
    int *a;            落ちない場合もある
    a = (int *) 0;
    *a = 0;            メモリの0番地に0を書き込む
    return 0;
}
                       size: 60bytes
なんで死ぬの/死なないの

       仮想               物理
      メモリ              メモリ


物理メモリを細かく分割し、必要な時に必要な部分だけに割り当
てる(仮想記憶)
物理メモリが割り当てられていないアドレスにアクセスするとpage
fault例外(page not presented)が起きる
多くの場合、0番地には物理メモリを割り当てない
 Cでは存在しないアドレスを0と表現(NULL pointer)
 NULLにアクセスした時に確実にエラーにするため(一節では)
短くする(golf)
int main()
{
    int *a;
    a = (int *) 0;
    *a = 0;               変数宣言と同時に初期化する。
    return 0;
}                           基本中の基本
int main()
{                         size: 56bytes
    int *a = (int *) 0;   (実際にはこれを書いた)
    *a = 0;
    return 0;
}
もっと短くする
int main()
{
    int *a = (int *) 0;   Warningを恐れない
    *a = 0;
    return 0;               mainの戻り値省略(intになる)
}
                            キャスト省略(どうせ値は0だし)
main()                      return省略(勝手にその時のeaxレ
{
                            ジスタの値が返るようになる)
   int *a = 0;
   *a = 0;                size: 33bytes
}
もっともっと短くする

main()           アドレスを初期化しない
{
   int *a = 0;     物理メモリの状況により適当な
   *a = 0;         値が入る
}                  →適当なアドレスに書き込むこ
                   とになる
main()
{                  最近のOSでは0の事が多い
   int *a;         (セキュリティ的観点で0初期化さ
   *a = 0;
                   れる)
}
                 size: 29bytes
発想を変える

main()                変数要らない疑惑
{
   int *a = 0;          アドレス指定で値を直接書き込む
   *a = 0;
}                       演算子順位に注意

                           キャストはポインタより強い
main()
{                          良い子は
   *(int *) 0 = 0;         *((int *) 0) = 0; 
}                          と書きましょう

                      size: 28bytes
もっと発想を変える

                    ゼロ除算例外の方が短くね?

                     ゼロ除算はCPUが検出する例外

main()               最適化でコードが消える事もある
{
   int i = 0 / 0;    “int i=”を消してもコンパイルでき
}                    るが”0/0”は実行されない

                    size: 27bytes(そんなに短くならな
                    かった)

                     でも空白多いから可能性アリ?
余白を消す


main(){int*a;*a=0;}    可読性なんて二の次

                         スペース、タブを可能な限り
main(){*(int*)0=0;}      消す

                         改行も1バイト(削除対象)
main(){int i=0/0;}     size: 20bytes, 20bytes, 19bytes
                       この辺が限界
さらに短くするために
もっと根本から発想を変える
そもそもmain()とは

C言語プログラムのエントリポイント(入り口)

crt0.oにより呼び出される(crt1.o, crt3.oなど色々)

 /usr/lib/bcc/{arch}/{ver}/crt?.o あたりにある(たぶん)

 gccのコンパイル時に自動的にリンクされる部品

 OSから呼ばれるエントリポイント(start)がこの中に

 実行前の準備をする(シグナルハンドラ、引数、環
 境変数など)
crt0.oからのmain()呼び出し
/*
 argc, argvなどの準備
                             引数argc, argvを設定して
*/
 exit(main(argc, argv));     main()を呼び出す
           コンパイル
push %eax # argv             main()の戻り値をexitに渡す
push %ebx # argc
call main
                             アセンブリにするとこんな
add $8, %esp
push %eax # retval of main   感じ(as, i386)
call exit
add $4, %esp
crt0.oからのmain()呼び出しの実態

 mainという名前のグローバルシンボルを呼び出してい
 るだけ

  シンボル:変数名、関数名、ラベル、のようなもの

 C言語ではstaticなどをつけない関数・変数はグローバ
 ルシンボル

  関数と変数は本質的に同じもの

   ラベルの位置に命令があるかデータがあるか

   命令もデータもバイト列である事に変わりはない
mainを変数にする
                   mainラベルを呼び出した先に2が
                   ある
int main=2;        i386系CPUに2と対応する命令が
          コンパイル    存在しない(たぶん)
main: .byte 2
                     無効オペコード例外
                     CPUのverにより通る可能性
 ↓動いてしまう例↓
                   定数はテキスト部に配置される
int main = 0xc3;     実行権限エラーにはならない
 main: ret           正確にはrodata部
何もしない関数呼び出し        size: 12bytes
もっとがんばる

main=2;           グローバルシンボルの型は
                  省略可能
main: .byte 2
                    関数と同じ(たぶんintになる)
  ↑さっきと同じ↑
                    そもそもアセンブリ同じだし

                  (もうプログラムに見えない)

                  size: 8bytes
高級なエディタなんて使ってるから

“main=2;” 7文字なのに8bytesとはこれいかに
ファイルの最後に改行が入ってました
 最近勝手に補完するエディタが多いので注意
 vimとかemacsとかは余計なお世話をする
viを使うか、以下のようにやると確実
# echo -n “main=2;” > coredump.c
size: 7bytes (私の限界)
※訂正:viも改行補完する方でした。emacsは設定
          で変更できるそうです。
結論


main=2;
使った武器


C言語・コンパイル後の機械語コード

UNIX/Linux系OSのメモリ管理

プログラム実行の仕組み

i386系CPUの命令と例外

(割と私の全力)
追記(2012/07/06)
                 もっと短くなった

これでいいんじゃね?       mainを初期化しない

   by @mayahjp     BSS部に変数領域が確保される

                      mainがBSS部を指す
 main;
                   データ領域をコードとして実
                   行しようとして保護違反

                      古いOSだと実行できるかも

                 size: 5bytes

core dumpでcode golf