プログラムがmain()にたどりつくまで
この話は、最近読んでいる
Binary Hacks ―ハッカー秘伝のテクニック100選
- 作者: 高林哲,鵜飼文敏,佐藤祐介,浜地慎一郎,首藤一幸
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2006/11/14
- メディア: 単行本(ソフトカバー)
- 購入: 23人 クリック: 383回
- この商品を含むブログ (223件) を見る
環境について
Fedora 17
gcc バージョン 4.7.2 20120921 (Red Hat 4.7.2-2) (GCC)
GNU bash, バージョン 4.2.39(1)-release (i686-redhat-linux-gnu)
です。
対象ソース
これが今回の対象です。いわゆるhello World!です。
[foo@localhost tmp]$ cat hello.c #include <stdio.h> int main(void) { printf("Hello World!"); return 0; }
最初にしたこと
straceコマンドにより、システムコールを調べた。
[foo@localhost tmp]$ strace ./hello execve("./hello", ["./hello"], [/* 45 vars */]) = 0 brk(0) = 0x94ec000 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7783000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=95319, ...}) = 0 mmap2(NULL, 95319, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb776b000 close(3) = 0 open("/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\2207\355F4\0\0\0"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=2011688, ...}) = 0 mmap2(0x46eba000, 1776316, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x46eba000 mprotect(0x47065000, 4096, PROT_NONE) = 0 mmap2(0x47066000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ab) = 0x47066000 mmap2(0x47069000, 10940, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x47069000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb776a000 set_thread_area({entry_number:-1 -> 6, base_addr:0xb776a6c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0x47066000, 8192, PROT_READ) = 0 mprotect(0x46eb6000, 4096, PROT_READ) = 0 munmap(0xb776b000, 95319) = 0 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7782000 write(1, "Hello World!", 12Hello World!) = 12 exit_group(0) = ? +++ exited with 0 +++
本を読むとbashの場合は、bash_4.2/execute.cmdのshell_execveからexecve(command, args, env);呼ばれている。ここのパラメータは、実行ファイル名, mainのargs, mainの環境変数がそのまま渡ってくるはずです。
execve関数は、glibc/sysdeps/unix/sysv/linux/execv.cで定義されているとあるが、実際にどうか調べてみる。
ファイルの最後にweak_alias(__execve, execve)とあり、
__execve(file, argv, envp)の定義があるのでおそらく、__execveに読み替えられると推測した。
__execveの最後でINLINE_SYSCALL (execve, 3, CHECK_STRING (file), ubp_argv, ubp_envp);としている。
glibc/sysdeps/unix/sysv/linux/i386/sysdep.hの中に定義がある。 >|| #undef INLINE_SYSCALL #define INLINE_SYSCALL(name, nr, args...) \ ({ \ unsigned int resultvar = INTERNAL_SYSCALL (name, , nr, args); \ if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (resultvar, ), 0)) \ { \ __set_errno (INTERNAL_SYSCALL_ERRNO (resultvar, )); \ resultvar = 0xffffffff; \ } \ (int) resultvar; })
INTERNAL_SYSCALLの定義も同じファイルにあり、下記のようになっている。いくつも定義があったが、
おそらくソフトウェア割り込み(int $80)をしているものだと思い下記定義を参照した。
# define INTERNAL_SYSCALL(name, err, nr, args...) \ ({ \ register unsigned int resultvar; \ EXTRAVAR_##nr \ asm volatile ( \ LOADARGS_##nr \ "movl %1, %%eax\n\t" \ "int $0x80\n\t" \ RESTOREARGS_##nr \ : "=a" (resultvar) \ : "i" (__NR_##name) ASMFMT_##nr(args) : "memory", "cc"); \ (int) resultvar; })
nameは、execveだったので、__NR_##nameは、__NR_execveに展開されます。
__NR_execveの値は、/usr/include/asm/unistd.hより11なので、eaxレジスタに11をセットして
ソフトウェア割り込みを発生しています。ここまでユーザ空間での動作です。
カーネル空間へ
SYSCALL_VECTOR(0x80)には、カーネル初期化時にlinux-3.6.9-2.fc17.i686/arch/x86/kernel/traps.cにある
__init trap_init()の中からset_system_trap_gate(SYSCALL_VECTOR, &system_call);を読んで初期化されています。
int $0x80で発生した割り込みで、system_callが呼ばれます。
実装は、linux-3.6.9-2.fc17.i686/arch/x86/kernel/entry_32.Sの中にある。
ENTRY(system_call) RING0_INT_FRAME # can't unwind into user space anyway pushl_cfi %eax # save orig_eax (省略) syscall_call: call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) # store the return value syscall_exit: LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt # setting need_resched or sigpending # between sampling and the iret TRACE_IRQS_OFF
grepしてそれらしいところも見つけました。こんな書き方あるのかい??
このファイルは,linux-3.6.9-2.fc17.i686/arch/x86/kernel/syscall_32.cです。
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { /* * Smells like a compiler bug -- it doesn't work * when the & below is removed. */ [0 ... __NR_syscall_max] = &sys_ni_syscall, #include <asm/syscalls_32.h> };
カーネルをビルドすると
linux-3.6.9-2.fc17.i686/arch/x86/include/generated/asm/syscalls_32.hができてその中は、
システムコール一覧がかかれている。(※ビルドしないとできない。)
syscalls_32.hがどうやって生成されるか調べる
linux-3.6.9-2.fc17.i686/arch/x86/syscalls/Makefileを見ると、
先頭にgenerated/asmと記載がある。
out := $(obj)/../include/generated/asm
このMakefileの中に規則がありそうなのでもう少し詳しくみていくと。
syscall32 := $(srctree)/$(src)/syscall_32.tbl syscall64 := $(srctree)/$(src)/syscall_64.tbl syshdr := $(srctree)/$(src)/syscallhdr.sh systbl := $(srctree)/$(src)/syscalltbl.sh
とあり、syscall_32.tblというファイルが怪しそう。
さらに、$(out)/syscalls_32.hのtargetがあるので、ここですね。
$(out)/syscalls_32.h: $(syscall32) $(systbl) $(call if_changed,systbl)
syscalltbl.shはwhile read nr abi name entry compactとしていて、ここの引数の数がsyscall_32.tblのフォーマットと同じであり、さらに結合すると__SYSCALL_I386となるため、ここでできる。
以上でシステムコールまわりの生成処理は、終わり。11は、__SYSCALL_I386(11, ptregs_execve, stub32_execve)なっている。
あとは、本のとおりソースを眺めた。ほとんど分からなかった。
Linux版のjavaで文字が□になる問題
linux版のastahのzip版をダウンロードして、さっそく起動したら、文字が□になる問題が発生しました。
世にいう豆腐問題です。ずっと前にも見たことがありましたが、どうやって解決したか忘れました。
とりあえず、いろいろ調べてみる。
利用しているJavaを調べる
[foo@localhost astah_community]$ which java /usr/bin/java [foo@localhost astah_community]$ ls -al /usr/bin/java lrwxrwxrwx. 1 root root 22 11月 15 06:34 /usr/bin/java -> /etc/alternatives/java [foo@localhost astah_community]$ alternatives --display java java -ステータスは自動です。 リンクは現在 /usr/lib/jvm/jre-1.7.0-openjdk/bin/java を指しています。 /usr/lib/jvm/jre-1.7.0-openjdk/bin/java - 優先項目 17009 [foo@localhost astah_community]$
これで、/usr/lib/jvm/jre-1.7.0-openjdk/bin/javaが利用されていることが分かりました。
Javaで利用してフォントを調べる
/usr/lib/jvm/jre-1.7.0-openjdk/lib/fontconfig.Fedora.properties.srcを調べる。
何か見るとdialog.bold.japanese-x0208=Sazanami GothicとかなっていてSazanamiってフォントを使うようになっているぽい。
さらに下を見るとfilename.Sazanami_Gothic=/usr/share/fonts/sazanami-fonts-gothic/sazanami-gothic.ttfとなっている。
あるのかどうか調べると
[foo@localhost astah_community]# ls -al /usr/share/fonts/sazanami-fonts-gothic ls: /usr/share/fonts/sazanami-fonts-gothic にアクセスできません: そのようなファイルやディレクトリはありません [foo@localhost astah_community]#
となりそのフォントがない。原因は、このフォントがないためですね。
解決方法は、2つです。
- sazanamiフォントを入れる。
- 別のフォントを参照するように変更する。
解決方法
今回は、2を選択します。
上のファイルを編集して参照先をかえてもいいですが、面倒なのでgoogleで検索するとjreでは、jre/lib/fonts/fallbackを作成して
そこにフォントを入れるらしい。
ディレクトリを作成
mkdir -p /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0/jre/lib/fonts/fallback
フォントを作成(VL-PGothicを使うようにした)
[foo@localhost astah_community]# ls -al /usr/lib/jvm/jre-1.7.0-openjdk/lib/fonts/fallback/ 合計 8 drwxr-xr-x. 2 root root 4096 12月 8 21:36 . drwxr-xr-x. 3 root root 4096 12月 8 21:35 .. lrwxrwxrwx. 1 root root 47 12月 8 21:36 VL-Gothic-Regular.ttf -> /usr/share/fonts/vlgothic/VL-Gothic-Regular.ttf lrwxrwxrwx. 1 root root 48 12月 8 21:36 VL-PGothic-Regular.ttf -> /usr/share/fonts/vlgothic/VL-PGothic-Regular.ttf
このあと起動したら、とりあえず、メニューとかは表示された。多分、これで直ったはず。
非同期シグナルを同期的に扱う
sigwaitを利用して、シグナル受信スレッドを作成してしまおうというのがアイディア。
#include <pthread.h> #include <sys/time.h> #include <sys/select.h> #include <signal.h> #include <stdio.h> void *wait_for_sighup(void *dmy) { int sig; //sig_wait関数はSIGHUPを待つ sigset_t ss; sigemptyset(&ss); sigaddset(&ss, SIGHUP); while(1) { //SIGHUPを待つ if(sigwait(&ss, &sig) == 0) { switch(sig) { case SIGHUP: printf("Hello async-signal-safe world!\n"); break; } } } return NULL; } int main(void) { sigset_t ss; pthread_t pt; pthread_attr_t atr; //SIGHUPをマスクする(SIGHUPが起こされても保留状態にする) sigemptyset(&ss); sigaddset(&ss, SIGHUP); sigprocmask(SIG_BLOCK, &ss, NULL); //SIGHUPを処理する専用スレッドを生成 pthread_attr_init(&atr); pthread_attr_setdetachstate(&atr, PTHREAD_CREATE_DETACHED); pthread_create(&pt, &atr, wait_for_sighup, NULL); while(1); return 0; }
■参考文献
Binary Hacks――ハッカー秘伝のテクニック100選
Javaでシグナルをキャッチする
linuxではお馴染み、シグナルをjavaでキャッチする方法です。
SIGHUPをキャッチします。
import sun.misc.Signal; import sun.misc.SignalHandler; class SignalTest { static int sig = 0; public static void main(String[] args) { Signal signal = new Signal("HUP"); Signal.handle(signal, new SignalHandler() { public void handle(Signal sigInst) { sig = 1; } }); while(sig == 0); } }
これをコンパイルして起動して別端末から、kill -HUP SinalTestのPID
をしてもプロセスがしなかった。
原因は、whileループで参照しているsigが最適化されて毎回参照していないためだ。
C言語でも最適化によって、この問題がおこるので、sig変数のみ最適化しないようにするための宣言があるそれが、
volatileです。javaでもあったので、使ってみた。
上のソースをsig変数をvolatile宣言する。こんな感じ。
volatile static int sig = 0;
これをコンパイルして起動して別端末から、kill -HUP SinalTestのPID
をするとちゃんと無限ループを抜ける。
Canvasを使う
本屋などにいくとhtml5でつくるゲームとかの本が結構あるので、少し触ってみた。ほとんど詳しくないので基礎的なことしかできなかった。とりあえず、キャンバスの背景設定と図形を書いて、定期的に移動するようにして見た。javascriptはまだ全然分からない。
index.hmtl
<!DOCTYPE html> <html lang="ja" dir="ltr"> <head> <title></title> <meta charset="UTF-8"/> <script src="./main.js"></script> </head> <body onload='main();'> <canvas id="canvas1" width="400" height="400"> </canvas> <div> <p>キャンバス上でクリックでスタート/ストップ</p> </div> </body> </html>
main.js
/** * 2012/12/05 * Canvasサンプル * * */ var canvas, ctx; var ball var v var isStopFlg; Ball = function(x, y, r) { this.x = x; this.y = y; this.r = r; } Velocity = function(x, y) { this.x = x; this.y = y; } function update() { ball.x += v.x; if( ball.x - ball.r < 0 || ball.x + ball.r > canvas.width ) { /* 次回の速度ベクトルを設定 */ v.x = -v.x; } ball.y += v.y; if( ball.y - ball.r < 0 || ball.y + ball.r > canvas.height ) { /* 次回の速度ベクトルを設定 */ v.y = -v.y; } } function draw() { /* 背景の描画 */ ctx.fillStyle="#000000"; ctx.fillRect(0, 0, canvas.width, canvas.height); /* ボールの描画 */ ctx.beginPath(); ctx.fillStyle = 'rgb(192, 80, 77)'; // 赤 ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2, false); ctx.fill(); } function timerHandler() { if (!isStopFlg) { update() draw() } /* 次回のタイマーハンドラの登録 */ setTimeout(timerHandler, 100); } function addEventListener() { canvas.addEventListener('click', clickHandler, false); } function clickHandler() { isStopFlg = !isStopFlg; } function main() { canvas = document.getElementById("canvas1"); ctx = canvas.getContext("2d"); ball = new Ball(100, 100, 25); v = new Velocity(5, 5); isStopFlg = false; draw() addEventListener() // 10ミリ秒でタイマーハンドラを呼び出す setTimeout(timerHandler, 10); }