目次
背景0からのGNU as programmingと言う感じで、勉強をまとめてみます。 諸般の事情で可能な限り headerファイルには依存しないコードにするため 本来は p/f dependencyを排除するために用意されている定義も敢えて 書き換えたりする方向でやってきます。
道具立てGNU binutil2.15同梱のGNU as/ldとかを使います。開発H/Wは例によって?、onnv b36/i386です。
Step 1 - 何をやればいいのかわからないので、i386でもいいから GCCにお願いするまずはGCCのソースを元に勉強してみましょう。最初の被験者はmemset(3C)にします。memset(3C)のプロトタイプ宣言は以下の通りです。 void *memset(void *s, int c, size_t n);この関数は The memset() function sets the first n bytes in memory area s to set the value of c(converted to an unsigned char). It returns s. 関数memset()はメモリ領域 s中の先頭 nバイトの全てを (unsigned charにキャストした) cの値にする。この関数は sを返す。プロトタイプ宣言中の size_tは p/f依存をなくすためのもので、 <sys/types.h>で以下の通り宣言されています。 #define _SIZE_T #if defined(_LP64) || defined(_I32LPx) typedef ulong_t size_t; /* size of something in bytes */ #else typedef uint_t size_t; /* (historical version) */ #endif #endif /* _SIZE_T */正直どっちでもいいのですが、 ここは size_t = ulong_t = unsigned longと言うことにします。 これで Cで実装すると、こんな感じになるでしょう。 typedef unsigned long size_t; void *mymemset(void *s, int c, size_t n) { unsigned char _c = (unsigned char)c; unsigned char *_s = s; size_t i; for ( i = 0; i < n; i++ ) { *_s = c; } return s; }これをGCCに -keeptempsオプション(コンパイル途中に生成した中間ファイルを残す)を つけてコンパイルすると、アセンブル対象の .sが出来ます。 .file "mymemset.c" .text .globl mymemset .type mymemset, @function mymemset: pushl %ebp movl %esp, %ebp subl $12, %esp movl 12(%ebp), %eax movb %al, -1(%ebp) movl 8(%ebp), %eax movl %eax, -8(%ebp) movl $0, -12(%ebp) .L2: movl -12(%ebp), %eax cmpl 16(%ebp), %eax jae .L3 movl -8(%ebp), %edx movl 12(%ebp), %eax movb %al, (%edx) leal -12(%ebp), %eax incl (%eax) jmp .L2 .L3: movl 8(%ebp), %eax leave ret .size mymemset, .-mymemset .ident "GCC: (GNU) 3.4.3 (csl-sol210-3_4-20050802)"まずは i386です。各行を順次読んで行く前に、これがどうオブジェクトファイルになるのか asを書けた後をbinutilの objdumpで見てみます。 ちなみにアセンブルして出来るオブジェクトファイルのサイズは 661バイトでした。 objdumpの引数は -sSdxで見ています。引数の意味はそれぞれ下記の通りです。
mymemset.o: file format elf32-i386 mymemset.o architecture: i386, flags 0x00000010: HAS_SYMS start address 0x00000000 PICなので、先頭アドレスは 0でロード時に解決されます。 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000035 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000000 00000000 00000000 0000006c 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 0000006c 2**2 ALLOC 3 .comment 0000002c 00000000 00000000 0000006c 2**0 CONTENTS, READONLY SYMBOL TABLE: 00000000 l df *ABS* 00000000 mymemset.c 00000000 l d .text 00000000 00000000 l d .data 00000000 00000000 l d .bss 00000000 00000000 l d .comment 00000000 00000000 g F .text 00000035 mymemset Contents of section .text: 0000 5589e583 ec0c8b45 0c8845ff 8b450889 U......E..E..E.. 0010 45f8c745 f4000000 008b45f4 3b451073 E..E......E.;E.s 0020 0f8b55f8 8b450c88 028d45f4 ff00ebe9 ..U..E....E..... 0030 8b4508c9 c3 .E... Contents of section .comment: 0000 00474343 3a202847 4e552920 332e342e .GCC: (GNU) 3.4. 0010 33202863 736c2d73 6f6c3231 302d335f 3 (csl-sol210-3_ 0020 342d3230 30353038 30322900 4-20050802). Disassembly of section .text: 00000000 <mymemset>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 0c sub $0xc,%esp 6: 8b 45 0c mov 0xc(%ebp),%eax 9: 88 45 ff mov %al,0xffffffff(%ebp) c: 8b 45 08 mov 0x8(%ebp),%eax f: 89 45 f8 mov %eax,0xfffffff8(%ebp) 12: c7 45 f4 00 00 00 00 movl $0x0,0xfffffff4(%ebp) 19: 8b 45 f4 mov 0xfffffff4(%ebp),%eax 1c: 3b 45 10 cmp 0x10(%ebp),%eax 1f: 73 0f jae 30 <mymemset+0x30> 21: 8b 55 f8 mov 0xfffffff8(%ebp),%edx 24: 8b 45 0c mov 0xc(%ebp),%eax 27: 88 02 mov %al,(%edx) 29: 8d 45 f4 lea 0xfffffff4(%ebp),%eax 2c: ff 00 incl (%eax) 2e: eb e9 jmp 19 <mymemset+0x19> 30: 8b 45 08 mov 0x8(%ebp),%eax 33: c9 leave 34: c3 retサクサク1行目から読んでみます。
寄り道1 makefileとりあえず作業のためのmakefileとmymemset()を呼び出すmainはこんな感じです。
void *mymeset(void *, int, size_t); int main() { char buf[255]; mymemcpy(buf, 0, 255); return (0); }
CC=gcc OBJS:sh=ls *.c | perl -e 'while(<>){s/?.c/.o/;print;}' # .c.o: $(CC) -c -o $@ $< -save-temps # all: $(OBJS) $(CC) -o main $(OBJS) main.o: main.c mymemset.o: mymemset.c clean: rm -f main *.[ois] coreホントは、OBJSを拾う所を tr(1)でなんとかしたかったのですが、 .cの .が正規表現扱いになる部分を ??065とかしてみても うまくいかなかったので諦めてperlにしちゃいました。orz 普段使いのshellが tcsh(1)なので、試験用echoが組み込みだったり /usr/xpg4/bin/echo だったり、makeもGNU make使ったり、make(1S)使ったりして 試行錯誤がかな〜り無駄足になっていたので、何とも言えません。 色々サポートしているのはいいことだと思うのですが。。。 寄り道2 別のコンパイラ出力コンパイラは SUNWsproの ccも用意してあったので、試しにこっちでも アセンブラソースを -Sオプションで作ってみました。.section .text,"ax" .align 4 .globl mymemset .type mymemset,@function .align 16 mymemset: pushl %ebp movl %esp,%ebp subl $28,%esp movl %ebx,-20(%ebp) movl %esi,-24(%ebp) movl %edi,-28(%ebp) .L14: / File mymemset.c: / Line 3 movl 12(%ebp), %eax movb %al,-5(%ebp) / Line 4 movl 8(%ebp), %eax movl %eax, -12(%ebp) / Line 6 movl $0, -16(%ebp) movl -16(%ebp), %eax cmpl 16(%ebp),%eax jae .L17 .L18: .L15: / Line 7 movl 12(%ebp), %eax movl -12(%ebp), %edx movb %al,0(%edx) / Line 6 movl -16(%ebp), %eax incl %eax movl %eax, -16(%ebp) movl -16(%ebp), %eax cmpl 16(%ebp),%eax jb .L15 .L19: .L17: / Line 10 movl 8(%ebp), %eax movl %eax, -4(%ebp) jmp .L13 .align 4 .L13: movl -4(%ebp), %eax movl -20(%ebp),%ebx movl -24(%ebp),%esi movl -28(%ebp),%edi leave ret .size mymemset,.-mymemset .section .bss,"aw" Bbss.bss: .type Bbss.bss,@object .size Bbss.bss,0 .section .data,"aw" Ddata.data: .type Ddata.data,@object .size Ddata.data,0 .section .rodata,"a" Drodata.rodata: .type Drodata.rodata,@object .size Drodata.rodata,0 .file "mymemset.c" .ident " cpp: Software Generation Utilities (SGU) SunOS/SVR4" .ident "acomp: Sun C 5.7 Patch 117837-04 2005/05/11" .xstabs ".stab.index","V=10.0;DBG_GEN=4.14.31;cd;backend;Xs;R=Sun C 5.7 Patch 117837-04 2005/05/11",60,0,0,0 .xstabs ".stab.index","/home/[伏せ字]/work/mymemset; /opt/SUNWspro/prod/bin/cc -Xs -YP,:/usr/ucblib:/opt/SUNWspro/prod/bin/../lib:/opt/SUNWspro/prod/bin:/usr/ccs/lib:/usr/lib -S -I/usr/ucbinclude -lucb -lsocket -lnsl -lelf -laio mymemset.c",52,0,0,0パッと見はだいぶ違う気がします。
寄り道3 デバッガでstep実行プログラミングをする場合はソースを読むことと、書いたものをデバッガで動かして、 様子を見ることが理解の早道だと信じているので、とりあえず読んで、書いて、 動かしてみるのがいつものパターンです。現状の開発環境の場合、デバッガはmdbとgdbが選択できるので、両方使ってみました。 mdbの場合とりあえずこの辺を押さえておけばいいでしょう。
gdbの場合
寄り道4 デバッガで実際に動かしてみるmdbで実際に動かして動作を見てみます。これに先立って寄り道1の main.cを元に ELF executableのmainを作ってます。
mdb: stop at main mdb: target stopped at: main: pushl %ebp
main: pushl %ebp main+1: movl %esp,%ebp 以下、省略
%cs = 0x003b %eax = 0x080479a4 %ds = 0x0043 %ebx = 0xbfffb840 %ss = 0x0043 %ecx = 0xbffb5244 libc.so.1`_sse_hw %es = 0x0043 %edx = 0x00000000 %fs = 0x0000 %esi = 0x08047a90 %gs = 0x01c3 %edi = 0x08047b64 %eip = 0x08050908 mymemset %ebp = 0x08047aac %kesp = 0x00000000 %eflags = 0x00000212 id=0 vip=0 vif=0 ac=0 vm=0 rf=0 nt=0 iopl=0x0 status=<of,df,IF,tf,sf,zf,AF,pf,cf> %esp = 0x0804797c %trapno = 0x3 %err = 0x0
mymemset: pushl %ebp mymemset+1: movl %esp,%ebp mymemset+3: subl $0xc,%esp mymemset+6: movl 0xc(%ebp),%eax 以下、省略
GNU asas絡みのメモを。Googleで調べれば意外と見つかるもんです。
GNU as Syntaxコメントコメント文が入っていると、アセンブル時は1つの空白になる。コメント用の特殊文字はアーキテクチャ依存であり、共通のコメントブロックは /* */だけ。 で、アーキテクチャ毎の違いは以下の通り。
dirctiveGNU asが解釈するdirctiveが多数用意されてます。section名(.textやら)以外のものは as directiveだと思っていいでしょう。アーキテクチャ依存がありますが、例えばこんなものがあります。
参考arm系
x86系
そのほか
|