BINARY HACKS

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

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

id:Ozyさんのところで紹介されていたので、実家に帰った際に大き目の本屋で買ってきました。
(今住んでる市には、小さな本屋しかなくて、BINARY HACKSは本屋を4件ぐらい探しましたが、どこにも置かれてませんでした)
 
UNIXのコマンドについてはほとんど知らないし、知らないままでよいのですが……gccのトランポリンの話とかはとても興味深く読ませていただきました。
 
私はほとんどの場合にWindowsでプログラムを組むので、UNIX系のことはよく知らないのですが、読んでいて少し引っかかったというか…少し気になったことがあったので、その辺をちょっと愚痴っぽく書いてみようかと思います。

書き換わったままの保障がないメモリ

どこが引っかかったのかというと、5章のランタイムHackのHACK#80「自己書き換えでプログラムの動作を変える」という節です。
メモリ上のマシン語コード自体を書き換えることで、変数なしで状態の切り替えを行う方法について書かれています。
メモリが少なかった大昔は、こういうことは結構頻繁に行われていたみたいですが、現代ではほとんど使う機会はないと思います。
まぁ、でも、せっかく取り上げられてるんだから、何かに使ってみたくなったプログラマも(常識的には居ないと考えたくもあるのですが)居るのでは? とか妄想しつつ、愚痴ってみます。
 
この手法、少なくともWindowsで使う場合には、注意すべき点があります。
それは何かというと、Windowsでは書き換えのリセットが起こりえます。
メモリ上のマシン語コードというのは、ファイル上の複写であるというのがWindowsでは前提となります。
ファイル上の複写であるマシン語コードというのは、「忘れてもまた読み込めば良い」のだとOSは思っていますので……メモリが足りなくなった際は、仮想メモリに退避させることもなく、完全にメモリ上から抹消してしまうことがあるわけです。
つまり、VirtualProtectとか使って(UNIXではmprotect関数なのかもしれないですが、WindowsではVirtualProtect関数が同じ役割をしている)メモリを書き込み可にしても、書き換え後のメモリの状態が保持され続ける保障はないわけです。
メモリ不足だとか、ちょっとしたOSの気分次第で、書き換える前の状態にメモリが戻る可能性が、常にあります。
 
これを回避したいならヒープ上にマシン語コードをコピーして、ヒープ上のマシン語の方を実行するようにすればよいのでしょうけれど……そこまでしてこのテクニックを使う必要はないわけで……素直に変数使って分岐とかさせるべきです。(よほど高速化のために、分岐すらも削りたいというのなら話は別ですが)
 
まぁ、そんなわけで……UNIXWindowsとは違ってそんな心配がないのか、それともこの本の著者がそういうことに気がつかなかったのか、あえて注釈を書かなかったのか……私はその辺はよく知らないのですが……読者は、こういう本に載ってることを何も調べもせず全部鵜呑みにしてしまうのは、よくないなぁ……とか、そんなことを思ったので、日記に書いてみました。
 

NULLと0は等しい?

もう1つ、4章のセキュアプログラミングHackのHACK#49の「64ビット環境で0とNULLの違いに気を付ける」という節にも愚痴があります。
 
ここでは、NULLは((void*)0)であって、0ではないのだから、場合によっては動作が違うよ……ということが書かれています。
 
でも面白いことに、C++ではNULLと0は完全に同じです。

#define NULL 0

とされています。
 
そもそも、もしもNULLの型がvoid*だったのなら、C++では、char*に対してそのままの代入が行えません。
なのでC++では、NULLと0は完全に同じものです。(UNIXだと、違うとでも言うのでしょうか?)
 
さて、BINARY HACKSのこの節の記述ですが、最初に、「Cでは、」と、Cだと限定していますので、この記事は間違ってはいないことになります。
 
C言語では確かに

#define NULL ((void*)0)

とされているからです。
 
しかしこの本は、いたるところで、C++の話題も持ち出しているのですから、これみよがしにCの場合は((void*)0)ですよ〜 と書かれていてC++の場合に((void*)0)ではなく0であることについて一切書かれていないのは、どうかと思います。
 
すなわち、この節で取り上げられてる

sp = foo("foo", "bar", 0);

で起こる64bit下のバグの問題は、C++の際には

sp = foo("foo", "bar", NULL);

であっても、同じバグに悩まされる可能性があるわけです。
(BINARY HACKSを持たない方のために補足しておきますと、fooは、...で表される可変長引数でva_listによって処理される関数です)
 
なのでこれはそもそも根本的な解決にはなっていなくて、

sp = foo("foo", "bar", (char*)NULL);

と書くべきである、と。……ここまで書いたなら、そこまで書かれていなければならないのではないかと、私は思うわけです。

総評

まぁ、私なんかがこういうあちこちで紹介されてる本に対してあれこれ批判を書くのは恐れ多いのかもしれませんが……
初版だからなのかもしれませんが、結構荒削りな一冊なんだなぁと思いました。
出版前に、どのぐらい下読みされたのか、少し怪しんでしまいます。
 
けれど内容的には、私の知らなくて、かつ面白そうな内容が結構含まれていて、買ってみて満足な一冊ではありました。