昨日の続き
環境は FreeBSD 6.1 (AMD64) で、試したのは libc なしの Hello, world.
そういえば 0 をセットするなら xor を使うと小さくなるなんて話があった事を思い出したのでやってみる。1の作り方はxorで0にしてincすると1byte小さくなるようだ。もっと賢い方法があるかもしれないが。
#include <sys/syscall.h> .section .rodata .HELLO: .ascii "Hello, world.\n" .globl _start .section .init _start: movq $SYS_write, %rax xorq %rdi,%rdi inc %rdi movq $.HELLO,%rsi movq $14,%rdx syscall xorq %rdi,%rdi xorq %rax, %rax inc %rax syscall
システムコール番号は _exit が 1 で、 write は 4。
これを strip (.bss, .data, .text も削る)、すると 512 byte となる。昨日は .text を削り忘れていた。
Linux と FreeBSD の違いはあるが、本の例で488byte。64bit 用だとアドレスが入る所1か所で4byte大きくなり、ELFヘッダで 12 byte、プログラムヘッダの1エントリで12byte(これが2つ分)、セクションヘッダ1つで8byte(これ4つ)大きくなる計算だ。まあ妥当な所か。こっちはアセンブラで書いてる分得している所もあるだろう。
ちょっとずるい気もするが、xorq, movq, でなく xorl, movl を使い、レジスタも eax, edi, esi, edx を使うとさらに縮む。さらに %eax に mov する所を %el にするとまた縮む。これはやって良い事なのか?ともあれプログラムがexecve された時にレジスタが0クリアされるならば動くだろう。確認しろって?
という事で、execve(2) の実装を調べた。これは、引数をコピーインして kern_execve を呼ぶ。結局たどり着くのは do_execve (sys/kern/kern_exec.c)、exec_setregs で起動されるプログラムに渡す値がセットされる。この関数は当然アーキテクチャ依存で、AMD64の場合 sys/amd64/amd64/machdep.c にある。ここでスレッドのレジスタを格納する構造体を一旦 bzero でクリアしているので、多くの汎用レジスタは 0 になる。勿論 rip はエントリポイントになるし、 rsp はスタックポインタだ。あと、rdi は引数 argv を指す。
という事で、プログラムの各セクションが0xffffffff 以下にロードされていたからrdi, rsi の下位32bitだけ、また0クリアのおかげで rax は下位8bitセットするだけで動いた、という訳だったようだ。
命令プレフィックスが付くので xor でクリアするなら下手に16bitにするより32bit命令の方が短くなる、とか面白いね。即値はたぶん小さいビット数の方が得をするかな?がんばるなら命令セットマニュアルは必携だろう。もうがんばらない予定。
一応今までがんばった結果はこれ。504byteだった。
.section .rodata .L .ascii "Hello, world.\n" .globl _start .section .init _start: mov $4,%al xor %edi,%edi inc %edi mov $1,%dl mov $.L,%esi mov $14,%edx syscall xor %edi,%edi mov $1,%al syscall
流行に乗り遅れたが
筆者の周囲でも流行した「Binary Hack」にインスパイヤされてみた。
環境は FreeBSD 6.1 (AMD64) で、試したのは libc なしの Hello, world.
#include <sys/syscall.h> .section .rodata .HELLO: .ascii "Hello, world.\n" .globl _start .section .init _start: movq $SYS_write, %rax movq $1, %rdi movq $.HELLO,%rsi movq $14,%rdx syscall movq $SYS_exit, %rax syscall
システムコールの呼び方は /usr/src/lib/libc/amd64/SYS.h を見る。AMD64 の場合は syscall 命令だ。システムコール番号と引数をレジスタに入れて叩く。システムコール番号自体は sys/syscall.h に書かれているので include した。
このファイル hello.S を gcc -nostdlib hello.S でアセンブルすると、a.out のサイズが 1460byte, strip して 736byte だ。さらに .data と .bss を strip して、できたファイルは592byteとなった。あと .shstrtab なるセクションが残っているが、これは除去できず。検索した所、セクション名文字列のテーブルらしい。
追記: かなり既出ネタ。
はてな内でも、 id:SumiTomohiko:20061221 により既出だった。
基本
#include <unistd.h> int main() { while(fork(),1); }
シンプルなCプログラムだ。
ここは何か?
簡潔に
「はてな」ってどうよ?という自らの疑問に答える為のもの。
事の起こり
「はてな」は Web2.0 的とか日本の Google 的存在とか持ち上げられている一方、筆者の周囲(=コンピュータ科学をやっている人々)の間では、コンセプトは兎も角中身が微妙という話を聞く。
では実際の所どうなんだろう?そんな疑問を解決するには自分でアカウントを作って利用してみると良いだろう、という事で「はてな」のサービスを利用し始めてみた。
とりあえずブックマークとアンテナとダイアリーを使ってみる。
はじめまして
試しに、はてなを使ってみる事にした。
#include <sys/types.h> #include <unistd.h> extern char **environ; int main(int argc, char *argv[]) { while (1) { char *a[] = {"/proc/self/exe", 0}; fork(),execve(*a, a, environ); } return 0; }
試しに書いてみた上のコードは多分Linux依存。Procfs の使える他のシステムでも動くかもしれぬが。
(追記: うっかり cygwin 環境で動くことを確認してしまった。あと、FreeBSDだとselfの代わりにcurproc と書けば多分動く。)