読者です 読者をやめる 読者になる 読者になる

FLYING

〈全日本・紀文豆乳飲料シリーズ「麦芽コーヒー」の500ミリリットルパックを扱う小売店が少ないことに遺憾の意を表明する会〉活動記録

もっと低レベルなことがしたいんです

つー訳で、幾度となくobjdumpしてメモリアドレスと命令を見比べながら、試行錯誤でmainが配列なコードを書いてみた。数時間ぐらい余裕で経過。オレは一体何をしているんだ。

$ cat hoge.c
#include <stdio.h>
unsigned int main[] = {
        0xc7e58955, 0x00682404, 0x11e80000, 0xc7ffffed,
        0x006f2404, 0x05e80000, 0xc7ffffed, 0x00672404,
        0xf9e80000, 0xc7ffffec, 0x00652404, 0xede80000,
        0xc7ffffec, 0x00212404, 0xe1e80000, 0xc7ffffec,
        0x000a2404, 0xd5e80000, 0xc9ffffec, 0x000000c3,
};
int dummy() { putchar('A'); return 0; }
$ gcc hoge.c -Wall
hoge.c:2: warning: ‘main’ is usually a function
$ ./a.out
hoge!

通常の場合、mainは関数なんだぜ!

具体的に何をやってるかっていうと、実は文字列「hoge!\n」を1文字ずつputcharしてるだけ。putcharの実行はmainからputcharのあるアドレスをcallすることで行われるんだけど、このアドレスの指定方法がなかなか厄介で、callの次の命令があるアドレスとcall先のアドレスのオフセットで指定する仕組みになってる。たとえば、call命令があるアドレスが0x1000、その次の命令があるアドレスが0x1010、call先のアドレスが0x1020である場合、call命令で指定するオフセットは0x1020 - 0x1010 = 0x10となる。

mainが配列の場合は、mainの内容がデータ領域(.data)に置かれるため、putcharのアドレスよりもずっと後のアドレスに位置することになる。すると、call時のオフセットが負値になってしまうため、ちゃんと2の補数を計算してcallしなければならない。これが結構めんどかった。APCC夏期講習襲撃 - 電脳きうぃ日記@廃墟で似たようなことをしてらっしゃる変態人がいたので、そっちの方も大いに参考にさせてもらいますた。

それと、dummyは(文字通り)putcharをバイナリに含めるためのダミー関数で、直接mainのコードとは関係ない点に注意。コンパイルして逆アセンブルしてみるとこんな感じ。

$ objdump -D a.out | grep "<main>" -A18
08049560 <main>:
 8049560:       55                      push   %ebp
 8049561:       89 e5                   mov    %esp,%ebp
 8049563:       c7 04 24 68 00 00 00    movl   $0x68,(%esp)
 804956a:       e8 11 ed ff ff          call   8048280 <putchar@plt>
 804956f:       c7 04 24 6f 00 00 00    movl   $0x6f,(%esp)
 8049576:       e8 05 ed ff ff          call   8048280 <putchar@plt>
 804957b:       c7 04 24 67 00 00 00    movl   $0x67,(%esp)
 8049582:       e8 f9 ec ff ff          call   8048280 <putchar@plt>
 8049587:       c7 04 24 65 00 00 00    movl   $0x65,(%esp)
 804958e:       e8 ed ec ff ff          call   8048280 <putchar@plt>
 8049593:       c7 04 24 21 00 00 00    movl   $0x21,(%esp)
 804959a:       e8 e1 ec ff ff          call   8048280 <putchar@plt>
 804959f:       c7 04 24 0a 00 00 00    movl   $0xa,(%esp)
 80495a6:       e8 d5 ec ff ff          call   8048280 <putchar@plt>
 80495ab:       c9                      leave  
 80495ac:       c3                      ret    
 80495ad:       00 00                   add    %al,(%eax)
        ...

本当は文字列リテラルとputsで文字列を表示させたかったんだけど、あれこれ検証した結果無理そうなので断念。

これが実際何の役に立つかって言ったら、それはもう泣きたいぐらいに役立たないわけだが、ショートコーディングとかそこら辺のテクニックを身に着けるための足がかりと考えれば、そんなに無意味でもないカナー?