タイトル/名前 | 更新者 | 更新日 |
---|---|---|
Assembler/ForFun(x86_32)/03, x86_32用 デバッグ機能有効化 Bochs をVC++2008でビルド | msakamoto-sf | 2010-09-19 20:59:41 |
Assembler/ForFun(x86_32)/04, 16bit BIOS with NASM | msakamoto-sf | 2010-09-19 20:59:21 |
Assembler/ForFun(x86_32)/05, 16bit BIOS with GNU as | msakamoto-sf | 2010-09-19 20:58:57 |
Assembler/ForFun(x86_32)/06, 32bit Windows with NASM | msakamoto-sf | 2010-09-19 20:58:42 |
Assembler/ForFun(x86_32)/07, 32bit Linux with GNU as | msakamoto-sf | 2010-09-19 20:58:15 |
C言語系/memos/binutils | msakamoto-sf | 2010-09-18 14:43:28 |
日記/2010/09/12/Winsock2, client-sideで shutdown(s, SD_SEND)後にrecv()が0を返す件 | msakamoto-sf | 2010-09-12 13:57:53 |
日記/2010/09/10/「DEBUG HACKS」読み終わった | msakamoto-sf | 2010-09-10 20:51:16 |
日記/2010/09/09/「バイオハザード3」を観てて | msakamoto-sf | 2010-09-09 23:42:42 |
日記/2010/09/09/「BINARY HACKS」読み終わった | msakamoto-sf | 2010-09-09 12:54:00 |
MBR以降のブートローダ周りのアセンブラプログラミングを楽しむのであれば、Windows/Linuxで動作するx86エミュレータ "Bochs" が便利です。Windows用のインストーラEXEをHPからダウンロード出来ます。
コンパイル時にデバッグ機能を有効化すると、BIOSが立ち上がる段階からステップ実行やレジスタ内容の確認が出来ます。Windows用のインストーラでセットアップするとデバッグ機能が有効化された"bochsdbg.exe"も同時にインストールされるので、わざわざコンパイルする必要もありません。
ところが、2010年9月時点での最新版、2.4.5のWindowsバイナリはx86_64をエミュレートするようコンパイルされています("--enable-x86-64"有効)。
x86の16bitや32bitの世界をBochsのデバッグ機能で楽しみたい場合は、"--enable-x86-64"を無効にして("--disable-x86-64")コンパイルする必要があります。
Linux/UNIX環境であればソースを展開して "./configure" で次のオプションを指定すればOKです。
--disable-x86-64 --enable-debugger --enable-disasm
本記事では Windows 環境の場合にBochs-2.4.5で上記オプションでビルドする手順を紹介します。
コンパイラは Visual C++ 2008 Express Edition SP1 を使います。Cygwin/MinGWのgccによるコンパイルは本記事では扱いません。
x86エミュレータであるBochsを使うことで、BIOSレベルでのプログラミングを楽しむことが出来ます。
今回はNASMを使って"Hello, World!"を出力させてみましょう。
なお、本記事ではMaster Boot Record (MBR)を使ったアセンブラプログラミングの詳細は解説しません。すでに入門レベルは経験済みの読者を想定しています。
GNU asはGNU binutilsに含まれているアセンブラです。
本記事では、Assembler/ForFun(x86_32)/04, 16bit BIOS with NASM で作成したHello, Worldプログラムを GNU as 版に書き直してみます。
これまでDOSやBIOS上でのアセンブラプログラミングを体験してきました。
本記事ではWindows上での32bitアセンブラプログラミングを体験してみます。
アセンブラはNASMを使い、リンカはVisualC++2008 Express Edition SP1のlink.exeを使います。
楽しむ為のアセンブラとして続けてきた "ForFun(x86_32)" の最後は、Linux上でgccを使ったアセンブラプログラミングを体験してみます。
今回はglibcのprintf()を使った定番の"Hello, World"をGNU asで作ってみます。直接システムコールを呼び出したり、libcを使わないアセンブラプログラミングについては、本記事末尾の参考図書の項をご覧下さい。
今回はglibcを使い、エントリポイントについてもglibcにお任せ、つまりmain()関数を用意します。
gccの場合、cdecl規約ではシンボル名を修飾しないので、"main", "printf"とそのまま書きます。
Web上にサンプルも豊富です。今回は次のようなソースになりました。
hello1.s:
.text .global main main: push %ebp mov %esp, %ebp push $hellomsg call printf mov $2, %eax mov %ebp, %esp pop %ebp ret .data hellomsg: .string "Hello, *nix World!\n"
コンパイルしてみます。今回はgccツールチェインの力で楽をしてみます。
$ as -o hello1.o hello1.s $ gcc -o hello1 hello1.o
あるいはいきなり、
$ gcc -o hello1 hello1.s
でもOKです。
実行してみます。
$ ./hello1 Hello, *nix World! $ echo $? 2
今回はgccツールチェインの力を借り、ライブラリもディストリビューション提供のglibcを使ったので、非常に簡潔なソースになりました。以下のCソースをアセンブラで書いたのと事実上変わりません。
hello.c:
#include <stdio.h> int main() { printf("Hello, *nix World!\n"); return 2; }
本格的に踏み込んで、Linuxの提供するシステムコール(割り込み)を直接呼んでみたり、glibcを使わずにldコマンドで手動リンクしたりといった高度な技法については以下の参考図書をご覧下さい。
参考図書:
"HACK#25 glibc を使わないで Hello World を書く"で、システムコール呼び出し+glibcを使わない"Hello, World"の作り方が紹介されています。
最後にまとめです。今回は gcc + glibc の力を借りて "Hello, World!" の GNU as 版を作ってみました。
本シリーズでは 16bit DOS/16bit BIOS/32bit Win32API/32bit linux と、様々な環境でアセンブラプログラミングを体験してきました。
特にx86のリアルモードの16bitアセンブラにウェイトをおいて紹介してきました。もしMBR以降のOSのブートに関わるアセンブラプログラミングと出会った時に、本シリーズでの体験を活かして頂ければ幸いです。
またそのような機会が無くとも、暇な時にお手持ちの環境で、手軽にx86アセンブラプログラミングを楽しんでみて下さい。
お手伝いの関係でWinsock2を触り始めているが、MSDN掲載の初歩のサンプルコードを打ち込んでみたら妙な挙動になって2時間ほど嵌ってしまった。
このサンプルコードでは send() 後に shutdown(SOCKET, SD_SEND) して「もうこれ以上クライアント側からの送信データはありませんよ」とサーバ側に明示した後、recv()で受信し、closesocket()を呼んでいる。
shutdown()は送信 or 受信 or 両方についてソケットを無効化する。もちろんPOSIXでも定義されているが、あまり見かけることはない。
一旦shutdown()を明示的に呼んでソケットを無効化したのち、recv() or send() で残りのデータ送受信処理を行い、closeするというのはMSDN上では「Graceful Shutdown」と呼ばれているらしい。
あまり見かけることがないのは、closesokcet()内で暗黙的にgraceful shutdownが呼ばれている為、明示的にshutdown()を呼ばなくても問題ないケースが多い為だろう。
ここからが本題だが、client-sideでの「send() -> shutdown(SD_SEND) -> recv()」で、recv()がいきなり0 = connection closeを返す場合がある。
実験してみたところ、サーバー側が先にFINパケットを送信すると、recv()が0を返す挙動を見つけた。
例えばJavaで作った単純な"echo"サーバーに接続したところ、問題なくrecv()は"echo"サーバーが送信してきたデータを受信出来た。
ところが、送信データを"GET / HTTP.1.0"にしてローカルのApache等に送信してみたところ、かならずrecv()が0を返してしまう。こちらのケースは、Apacheプロセス側が応答パケットの送信に続いてFINパケットまで送ってきている。つまりサーバー側から先に切断フェーズに進む形になる。どうもこのケースにおいては、クライアント側でshutdown(SD_SEND) -> recv() すると0が返されconnection closeとして動作するようだ。
更に、試しにクライアント側でshutdown()の前に適当なwaitを入れてみたところ(getchar())、その場合はrecv()が正常値を返すことを確認出来た。
前述の「graceful shutdown」MSDNでは「クライアント側からの切断」処理については書かれているが、「サーバー側が先に切断フェーズに入った場合」の挙動については書かれていない。
あまりまとまっていないが、shutdown()を明示的に呼び出すgraceful shutdownで期待したとおりの挙動にならない場合は、どちらが先にFINパケットを送り切断フェーズに入ったのかについて注意が必要。
というか、「読み流した」感じに終わってしまって、著者や出版社の方達になんだか申し訳ない。
とりあえずgdbの使い方を学べて、ユーザーランドでのデバッグhow-toトピックを幾つか(SIGSEGVとかValgrind)学べたので元は取れたかも。
本書の半分以上が、Linux Kernelのデバッグ技法になっているのは、著者達の普段のお仕事的に致し方なしといったところ。逆に言えばLinux Kernelのデバッグ技法や、実際にツールを使って手がかりを集め、推理し、問題箇所の特定まで書いてくれているので、そちらの分野の人達には便利かも。
正直Linux Kernel Debugの世界は今のところ興味がないしちんぷんかんぷんな世界なのだが、それでも、実際にデバッグしていく箇所などは一種の推理小説を読むように「雰囲気だけは」楽しめた。探偵が手がかりを集めて推理し、犯人を特定していく臨場感を楽しめる。
なので、本書の半分以上は雰囲気だけ楽しんで中身は頭に入ってない状態・・・「読み流した」感じ。
gdbの使い方の入り口、あるいは、もし将来Linux Kernelをデバッグする事になった場合のHOW-TO入り口として手元に置いておく。
地下基地のAI(人工知能)がホログラム投影(?)してる女の子、萌えポイントヒットしすぎて悶え死にそうになった。
真っ白な容姿に黒髪ストレートロング・・・うあぁぁああああああfdsぁkfjさっdkふぁいっsdカユ
ウマ
初版購入して4年間本棚に積まれていたが、やっと読み終えた。
教科書的な書き方はしていないのである程度の予備知識は要求されたが、トリビア・豆知識・雑学をあれこれつまみ食いしたり、広範囲に散らばりやすいLinux/UNIX世界のバイナリレイヤーの入り口をこの本一冊に集約するのに便利。binutilsのハンドブックとしても使える。
「なんでsoファイルを作る時は"-fPIC"が必要なんだろう」とか、「ld.soが実行可能でなおかつsoファイルとしてリンク可能なのはなんで?」等の疑問も解決出来た。