Document

Tiny hello
hello world
hello.c をコンパイルする。
%gcc -o hello hello.c
ファイルサイズを確認。
%wc -c hello
4507 hello
つまり、4.5 KB くらいある。hello world だけで何故
そんなに大きいのか?
hello world
hello.c をコンパイルする。
%gcc -o hello hello.c
ファイルサイズを確認。
%wc -c hello
4507 hello
つまり、4.5 KB くらいある。hello world だけで何故
そんなに大きいのか?
=> gcc -v で何をしているのかチェック
gcc -v
%gcc -v hello.c -o hello.o
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.4.5-8' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-lang
Thread model: posix
gcc version 4.4.5 (Debian 4.4.5-8)
COLLECT_GCC_OPTIONS='-v' '-o' 'hello.o' '-mtune=generic' '-march=i586'
/usr/lib/gcc/i486-linux-gnu/4.4.5/cc1 -quiet -v hello.c -quiet -dumpbase hello.c -mtune=generic -march=i586 -auxbase hello -version -o /tmp/c
ignoring nonexistent directory "/usr/local/include/i486-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/i486-linux-gnu/4.4.5/../../../../i486-linux-gnu/include"
ignoring nonexistent directory "/usr/include/i486-linux-gnu"
#include "..." search starts here:
#include <...> search starts here:
/usr/local/include
/usr/lib/gcc/i486-linux-gnu/4.4.5/include
/usr/lib/gcc/i486-linux-gnu/4.4.5/include-fixed
/usr/include
End of search list.
GNU C (Debian 4.4.5-8) version 4.4.5 (i486-linux-gnu)
compiled by GNU C version 4.4.5, GMP version 4.3.2, MPFR version 3.0.0-p3.
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 0192d925385d4e6642a93c63f245f907
COLLECT_GCC_OPTIONS='-v' '-o' 'hello.o' '-mtune=generic' '-march=i586'
as -V -Qy -o /tmp/cczJa9u6.o /tmp/ccIK75de.s
GNU assembler version 2.20.1 (i486-linux-gnu) using BFD version (GNU Binutils for Debian) 2.20.1-system.20100303
COMPILER_PATH=/usr/lib/gcc/i486-linux-gnu/4.4.5/:/usr/lib/gcc/i486-linux-gnu/4.4.5/:/usr/lib/gcc/i486-linux-gnu/:/usr/lib/gcc/i486-linux-gnu/4
LIBRARY_PATH=/usr/lib/gcc/i486-linux-gnu/4.4.5/:/usr/lib/gcc/i486-linux-gnu/4.4.5/:/usr/lib/gcc/i486-linux-gnu/4.4.5/../../../../lib/:/lib/../lib/:/u
COLLECT_GCC_OPTIONS='-v' '-o' 'hello.o' '-mtune=generic' '-march=i586'
/usr/lib/gcc/i486-linux-gnu/4.4.5/collect2 --build-id --eh-frame-hdr -m elf_i386 --hash-style=both -dynamic-linker /lib/ld-linux.so.2 -o hello.o /us
リンクされているオブジェクト
リンクする際に様々な *.o ファイルをリンクしている。
ldd で確認すると
%ldd hello
linux-gate.so.1 => (0xb77ca000)
libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7668000)
/lib/ld-linux.so.2 (0xb77cb000)
な感じでリンクしているが、*.o は glibc (libc.so) の初期化コードを含んでいて、
これが原因でサイズが大きい。
以下、余計なオブジェクト一覧。
*
*
*
*
*
crtbegin.o
crt1.o
crti.o
crtend.o
crtn.o
write 関数を呼べば?
hello_write.c をビルド。
#include <unistd.h>
int main(void){
/* fd=1 だと stdout */
write(1,"Hello World\n",12);
return 0;
}
%gcc -o hello_write hello_write.c
%./hello_write
ちゃんと動く。
write 関数を呼べば?
%ldd hello_write
linux-gate.so.1 => (0xb7863000)
libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7701000)
/lib/ld-linux.so.2 (0xb7864000)
%wc -c hello_write
4534 hello_write
な感じでサイズは小さくなっていない。
write 関数を呼べば?
%ldd hello_write
linux-gate.so.1 => (0xb7863000)
libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7701000)
/lib/ld-linux.so.2 (0xb7864000)
%wc -c hello_write
4534 hello_write
な感じでサイズは小さくなっていない。
=>glibc のwriteを呼んでいる。
カーネルのシステムコールを直接叩く
www.kernel.org から kernel source を適当に取ってくる。
が、現行のコードだとソースの構造が割と変わっていて
良く分からないので、binary hacks に書かれている通りの
構造になっている version を適当に。
今回は linux-2.6.18.8 を取ってきた。
#本当は現行 version のでやりたかったのだが、ソースを追い切れず。
#根性無しでごめんなさい…。
ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.18.8.tar.bz2
からwget
カーネルのシステムコールを直接叩く
動かしたいコードは
write(1,"Hello World\n",12);
なので、
linux-2.6.18.8/include/asm-i386/unistd.h
を覗いて、write関数っぽいのを探す。
カーネルのシステムコールを直接叩く
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{\
long __res; \
__asm__ volatile ("push %%ebx ; movl %2,%%ebx ; int $0x80 ; p
: "=a" (__res) \
: "0" (__NR_##name),"ri" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)) : "memory"); \
__syscall_return(type,__res); \
}
を入れれば動きそう。
カーネルのシステムコールを直接叩く
__syscall_return というマクロを中で使っているので、
これの定義も取ってきて、
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{\
long __res; \
__asm__ volatile ("push %%ebx ; movl %2,%%ebx ; int $0x80 ; pop %%ebx" \
: "=a" (__res) \
: "0" (__NR_##name),"ri" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)) : "memory"); \
__syscall_return(type,__res); \
}
void hello() {
write(1, "Hello World\n", 12);
}
な感じ。
カーネルのシステムコールを直接叩く
これを以下のようにコンパイル & リンクする。
%gcc -m32 -Os -fno-builtin -fomit-frame-pointer -fno-ident -c tiny_hello.c
%ld -m elf_i386 --entry=hello -o hello tiny_hello.o
カーネルのシステムコールを直接叩く
これを以下のようにコンパイル & リンクする。
%gcc -m32 -Os -fno-builtin -fomit-frame-pointer -fno-ident -c tiny_hello.c
%ld -m elf_i386 --entry=hello -o hello tiny_hello.o
だが、これだと SEGV で落ちる。
普段は glibc がやってくれているが、今回は自分で exit()しなければならない。
そこで、exit() も動かす。
カーネルのシステムコールを直接叩く
なので、
#define _syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{\
long __res; \
__asm__ volatile ("push %%ebx ; movl %2,%%ebx ; int $0x80 ; pop %%ebx" \
: "=a" (__res) \
: "0" (__NR_##name),"ri" ((long)(arg1)) : "memory"); \
__syscall_return(type,__res); \
}
inline _syscall1(int, exit, int, status);
も追加しなければならない。
カーネルのシステムコールを直接叩く
結局、コードは
#include <asm/unistd.h>
#define __syscall_return(type, res) (type)(res)
#define _syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{\
long __res; \
__asm__ volatile ("push %%ebx ; movl %2,%%ebx ; int $0x80 ; pop %%ebx" \
: "=a" (__res) \
: "0" (__NR_##name),"ri" ((long)(arg1)) : "memory"); \
__syscall_return(type,__res); \
}
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{\
long __res; \
__asm__ volatile ("push %%ebx ; movl %2,%%ebx ; int $0x80 ; pop %%ebx" \
: "=a" (__res) \
: "0" (__NR_##name),"ri" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)) : "memory"); \
__syscall_return(type,__res); \
}
inline _syscall1(int, exit, int, status);
inline _syscall3(int, write, int, fd, const void*, buf, unsigned long, count);
void hello() {
write(1, "Hello World\n", 12);
exit(0);
}
な感じになる。
カーネルのシステムコールを直接叩く
これを同じようにコンパイル & リンクする。
%gcc -m32 -Os -fno-builtin -fomit-frame-pointer -fno-ident -c tiny_hello.c
%ld -m elf_i386 --entry=hello -o hello tiny_hello.o
%wc -c hello
strip するともう少し縮む。
%strip hello
%wc -c hello
別のアプローチ:実行形式を作る
直接 ELF オブジェクトを吐く。要するに、ELF の形式を守って
オブジェクト内に write(1,"Hello World\n",12) を呼ぶコードを置けば良い。
# http://d.hatena.ne.jp/yupo5656/20061112/p2
# が詳しい。
別のアプローチ:実行形式を作る
#include <elf.h>
#include <unistd.h> // write
#define PAGE_ALIGN(adr) ((adr) & ~(0x1000 - 1)) // 16進下3桁を切り捨てるだけ
#define LOAD_ADDRESS PAGE_ALIGN(0x12345678) // 0x12345000にロード
#define STRING_LEN 12
#define TO_STR(s) TO_STR_(s)
#define TO_STR_(s) #s
#define ECX \
TO_STR(LOAD_ADDRESS + 52 + 32) // LOAD_ADDRESS + sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr)
__asm__ ("start_:
\r\n"
"movl $4, %eax
\r\n" // eax: system call number (__NR_write)
"movl $1, %ebx
\r\n" // ebx: fd (stdout)
"movl $" ECX ", %ecx \r\n" // ecx: addr
"movl $13, %edx
\r\n" // edx: len
"int $0x80
\r\n"
"movl $1, %eax
\r\n" // eax: system call number (__NR_exit)
"movl $0, %ebx
\r\n" // ebx: exit code
"int $0x80
\r\n"
"end_:
");
extern char *start_, *end_;
別のアプローチ:実行形式を作る
void out_elf_header() {
Elf32_Ehdr ehdr = {
.e_ident
= { ELFMAG0, ELFMAG1, ELFMAG2 ,ELFMAG3,
ELFCLASS32, ELFDATA2LSB, EV_CURRENT, ELFOSABI_SYSV },
.e_type
= ET_EXEC,
.e_machine = EM_386,
.e_version = EV_CURRENT,
.e_entry
= LOAD_ADDRESS + sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr) + STRING_LEN,
.e_phoff
= sizeof(Elf32_Ehdr),
.e_shoff
= 0, // dummy
.e_flags
= 0x0,
.e_ehsize = sizeof(Elf32_Ehdr),
.e_phentsize = sizeof(Elf32_Phdr),
.e_phnum
= 1,
.e_shentsize = 0, // dummy
.e_shnum
= 0,
.e_shstrndx = 0, // dummy
};
}
write(1, &ehdr, sizeof(Elf32_Ehdr));
別のアプローチ:実行形式を作る
void out_program_header() {
uintptr_t code_len = (uintptr_t)&end_ - (uintptr_t)&start_;
Elf32_Phdr phdr = {
.p_type = PT_LOAD,
.p_offset = 0x0,
.p_vaddr = LOAD_ADDRESS,
.p_paddr = 0, // dummy
.p_filesz = sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr) + STRING_LEN + code_len,
.p_memsz = sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr) + STRING_LEN + code_len,
.p_flags = PF_R | PF_X,
.p_align = 0x1000,
};
}
write(1, &phdr, sizeof(Elf32_Phdr));
void out_code() {
uintptr_t code_len = (uintptr_t)&end_ - (uintptr_t)&start_;
write(1, &start_, code_len);
}
int main() {
out_elf_header();
out_program_header();
write(1, "hello world\n", 12);
out_code();
return 0;
}
別のアプローチ:実行形式を作る
これで、以下のようにしてみよう。
%gcc tiny_hello2.c
%a.out > tiny_hello2
%chmod +x tiny_hello2
%./tiny_hello2
動いた!!
目指せバイナリアン!!
世の中は、バイナリアンと呼ばれるマシンコードやオブジェクトコードに
慣れ親しんでいる人達が居る。先述のサイトもバイナリアンな方のとこだが、
ここで、"Code Golf"なる遊びを見付けた。
http://shinh.skr.jp/dat_dir/golf_prosym.pdf
これは処理系の癖を理解して、如何に少ないコードで目的の動作を
行うかというゲームっぽい。
ELF Golf?
他に、"ELF Golf" というものもあるらしい。
http://shinh.skr.jp/binary/fsij061115/index.cgi?i=0
組込みプログラミングを行うには、
プロセッサ、デバイス、OS、ブートローダ等を完全に理解し、
その上でコードを書かなければならない。
組込みのエキスパートを目指すならば、是非、バイナリアンになりましょう!!
バイナリアン度チェック
http://0xcc.net/binhacks/quiz.html
にある。ちなみに、私がやってみたところ、
あなたのバイナリアン度は66点です。
すばらしいバイナリ度をお持ちのあなたは Binary Hacks にきっと夢中
になるはず。役に立たなそうなハックも、ちょっとしたはずみで役に立
つハックに変身することも。深追い運も急上昇中かも!?
だった。割としょっく…。
Beating the averages
http://practical-scheme.net/trans/beating-the-averages-j.html
Beating the averages
1995年当時、私達は、多分競争相手は理解してないであろうあること
を知っていた。いや、今でも理解している人はほとんどいないかもしれ
ない。もしあなたが、あなたのサーバー上でだけ走るソフトウェアを書
くのなら、あなたは自分の好むどんな言語でも使えるということだ。デ
スクトップソフトを書いている時にはこうはいかない。そのデスクトップ
のオペレーティングシステムがサポートする言語への強いバイアスが
ある。 10年前なら、アプリケーションを書くということはCで書くというこ
とだった。でもWebベースのソフトウェアなら、そして特に言語とOSの
ソースコードを持っているなら、あなたは好きな言語を使うことができる。
(snip)
Beating the averages
実験の結果はどうだったかって? 実際驚くことに、うまく行ったんだ。結果的に私達に
は20から30にものぼる競争相手が現われたが、どれひとつとして私達のソフトウェア
を打ち負かすことはできなかった。私達のWYSIWYGのオンラインストアビルダーは
サーバー側で走っているのに、ユーザーからはまるでデスクトップアプリケーションを
使っているかのように感じられた。競争相手はCGIスクリプトを使っていた。そして機能
的にも、私達は相手のはるか先を行っていた。しばしば、競争相手は追い詰められて、
私達のソフトに無い機能を入れようと試みた。しかし、Lispのおかげで私達の開発サイ
クルは非常に速く、相手がプレスリリースを出した1日2日後にもう同様の機能を作った
こともしばしばある。プレスリリースをカバーした記者が私達のところに取材に来る頃に
は、こちらにももう新しい機能が追加されていたのだ。
きっと競争相手には、私達が何か秘密兵器を持っているかのように見えたに違いない。
彼等の暗号通信を解読しているとか、そんなことだ。事実、私達は秘密兵器を持ってい
たんだが、それは彼等が思っているよりずっと簡単なことだった。誰も彼等の計画を私
達に漏らしたりしなかった。単に、私達が他の誰よりも素早くソフトウェアを開発できた
というだけのことだ。
(snip)
Beating the averages
私達の秘密兵器も同じようなものだ。私達はソフトウェアを、括弧だらけの奇怪な構文
を持つ、妙ちきりんな人工知能言語で書いた。正直なところ何年もの間、私はLispがそ
んなふうに呼ばれることが気にさわっていたのだが、今やそれは私達のアドバンテージ
となったのだ。ビジネスでは、競争相手が理解出来ない技術的アドバンテージを持って
いることほど価値あることは無い。ビジネスでは、驚きは軍隊ほどに価値がある。
そして、ちょっと恥をしのんで告白すると、私達がViawebを作っている間、私はLispにつ
いて一切公言しなかった。プレス発表でも言わなかったし、私達のWebサイトで "Lisp"
を検索しても見つかるのは私の書いたLispに関する 2冊の書物のタイトルだけだ。これ
は偶然そうなったわけじゃない。ベンチャー企業はライバルになるべく情報を漏らさない
ものだ。もしライバルが、私達のソフトが何で書かれているか知らないのなら、あるいは
気にしないのなら、私はそれをそのままにしておきたかった [注2]。
Beating the averages
私達の技術を一番理解していたのは私達の顧客だった。お客さんはViawebが何の言
語で書かれているかなんて気にしないが、ソフトがとにかく使えるということに気付いて
くれた。私達のソフトを使えば、見栄えがいいオンラインストアを文字通り数分のうちに
作ることができた。そしてほとんど口コミによって私達は顧客を獲得していった。 1996年
の終りまでに、70店程がオンラインストアを実現した。1997年の終りには500店になった
その6ヵ月後、Yahooが私達の会社を買い取った時、顧客数は1070店になっていた。こ
んにち、私達のソフトウェアはYahoo Storeとしてこの領域のマーケットを独占している。
それはYahooの中で最も利益を上げている部分の一つであり、それによって作られた
オンラインストアが Yahoo Shoppingの基礎となっている。私は1999年にYahooを離れ
たので、現在どのくらいのユーザーがいるのかは知らないが、最後ノ聞いた時はだいた
い14000ということだった。
時々、Yahoo Storeは今でもLispを使っているのかと聞かれる。答えはイエスだ。
Lispコードはそっくりそのまま、まだある。 Yahooはサーバー側のソフトウェアを、
エリック・レイモンドが薦めた5つの言語全てを使って書いている。
(snip)
Beating the averages
私達の技術を一番理解していたのは私達の顧客だった。お客さんはViawebが何の言
語で書かれているかなんて気にしないが、ソフトがとにかく使えるということに気付いて
くれた。私達のソフトを使えば、見栄えがいいオンラインストアを文字通り数分のうちに
作ることができた。そしてほとんど口コミによって私達は顧客を獲得していった。 1996年
の終りまでに、70店程がオンラインストアを実現した。1997年の終りには500店になった
その6ヵ月後、Yahooが私達の会社を買い取った時、顧客数は1070店になっていた。こ
んにち、私達のソフトウェアはYahoo Storeとしてこの領域のマーケットを独占している。
それはYahooの中で最も利益を上げている部分の一つであり、それによって作られた
オンラインストアが Yahoo Shoppingの基礎となっている。私は1999年にYahooを離れ
たので、現在どのくらいのユーザーがいるのかは知らないが、最後ノ聞いた時はだいた
い14000ということだった。
時々、Yahoo Storeは今でもLispを使っているのかと聞かれる。答えはイエスだ。
Lispコードはそっくりそのまま、まだある。 Yahooはサーバー側のソフトウェアを、
エリック・レイモンドが薦めた5つの言語全てを使って書いている。
(snip)
Beating the averages
もしあなたがベンチャーで働いているなら、競争相手の実力を見積もる便利なヒントを
あげよう。人材募集内容を見るんだ。彼等のウェブには他に見栄えのいい写真や美辞
麗句で彩られているだろうけど、募集内容だけは彼等が一番欲しているものを的確に
表現しているはずだ。でなければ見当違いの人しか応募して来ないからね。
Viawebで働いていた期間、私は人材募集記事をたくさん見た。新しい競争相手は毎月
のように現われた。いつも最初に私がしたことは、まずオンラインデモがあるかどうかを
チェックして、それから人材募集を見ることだった。 1~2年したら、警戒すべきライバル
とそうでないところとを見分けられるようになった。人材募集がいかにもITといった匂い
をただよわせていればいるほど、その企業は脅威ではない。一番安全なのはOracleの
経験者を募集しているところだ。そういうところを警戒する必要は全く無い。また、Java
やC++プログラマを募集しているところも安全だ。もしPerlやPythonプログラマを募集
していたら、ちょっと気を付けたほうがいい。その企業の、少なくとも技術部門は本物の
ハッカーがやっている可能性が高いからだ。もし私がLispハッカーの募集広告を目にし
ていたら、きっとかなり心配していただろう。
Beating the averages
Lispの本を書いていた時、私は皆がLispを分かってくれたらいいと願って
いたものだ。 Viawebを立ち上げた時、私の見方は変わった。Lispを分か
って欲しい。但し競争相手以外に、だ。
SICPを読みましょう。
顧客が本当に必要だったもの
顧客が本当に必要だったもの
おわり