差分表示
- 最後の更新で追加された行はこのように表示します。
- 最後の更新で削除された行は
このように表示します。
* 背景
0からのGNU as programmingと言う感じで、勉強をまとめてみます。
諸般の事情で可能な限り headerファイルには依存しないコードにするため
本来は p/f dependencyを排除するために用意されている定義も敢えて
書き換えたりする方向でやってきます。
-ってことで、8/16頃に続きを書く予定 -- &new{2006-08-06 (Xxx) xx:xx:xx};
-なんですが、結局かいてません。^^; -- &new{2006-09-26 (Tue) 17:19:41};
#comment(noname)
* 道具立て
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で見ています。引数の意味はそれぞれ下記の通りです。
- -s : 指定したセクションに対して、それぞれの内容をdumpを出力する。
- -S : (可能であれば)ソースコードを逆アセンブル結果と混在させて表示する。-dが暗黙のうちに指定される。
- -d : 逆アセンブルした結果を出力する。
- -x : 表示可能な全てのヘッダ情報を表示する。
-Sを指定しているので、-dはいらないのですが何となく指定しています。
ダンプ結果の個々の部分のコメントはそのまま追記しています。
---(
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行目 ''.file "mymemset.c"''
-- .fileは asm directive(指示語)です。基本的に . で始まるwordは directiveです。
-- .fileは互換性のために用意されてるだけで、プログラム自体にはあまり関係ありません。つけておくと、このあと他のオブジェクトとリンクされたときの、リンク後のオブジェクトが持つシンボルテーブルに、このオブジェクトの提供する関数名の区分けとしてファイル名が入るようになります。
- 2行目 ''.text''
-- これ以降を text segumentと指示します。textセグメントとは ELFのブロックで実行部や定数値を入れておく場所になります。
-- 引数の有無で subsectionの数が指定されるようですが、必要になるまで放置。orz
- 3行目 ''.global mymemset''
-- 引数のシンボルをシンボルテーブルに載せて、後のld(リンカ)がリンクすることが出来るようにします。この場合、このアセンブルコード中で定義されている(はず)の mymemsetだけを ldのリンク対象として公開することになります。
-- .globalの別名として .globlがあるようです。
- 4行目 ''.type mymemset, @function''
-- 公開するシンボルの型を定義します。COFF版とELF版がありますが、COFF版は無視です。
-- 基本形は .type [name], type description です。
-- type descには functionか objectの2択で、GNU asは他の様々な asmとの互換性のために以下の書式を許しています。
,funtionの書式,objectの書式,何で使ってるか(わかれば)
,#function,#object,
,@function,@object,SUNWsproもこれです。
,%function,%object,
,"function","object",
,STT_FUNCTION,STT_OBJECT,
- 5行目 ''mymemset:''
-- 行頭から英数文字or .で始まり、最後に :とあるものはラベルになります。詳細は後述。
-- ここでは mymemsetと言うラベルを定義しています。4行目の公開対象のシンボルになっています。
- 6行目 ''pushl %ebp''
-- ようやく i386の mnemonic codeの登場です。
-- pushl %ebpはお決まりなので無視でもいいですが、bpを待避してます。
-- stackは呼び出し前に使っていた場所に乗せて使うので、呼び出し前のstack frameのベースは触れないようにする必要があります。
-- mnemonicの基本形は pushですが、operandの型がlongの場合 lをsuffixにつけます。
-- %ebpは i386の ebpレジスタです。%[レジスタ名]の書式でレジスタを表します。
- 7行目 ''movl %esp, %ebp''
-- お決まりです。新しいstackを使うためにフレームポインタをセットし直し、spが今のbpの場所を指すようにします。
- 8行目 ''subl $12, %esp''
-- espの値を現在のebp(スタックのベース)の値に対して 12ずらして(=除算して)います。
-- 前述のこのアセンブルソースの元になった Cソースではauto変数を3個使っています。このため stackに
auto変数を格納する領域を用意する必要があります。IPL32(=Int Pointer Longは32bit)なので、unsigned char _c(1byte)と unsigned char *_s(4bytes)、size_t(=unsigned int) i(4bytes)の合計 9bytesですが、alignment上 4x3=12バイトにしているようです。
- 9行目 ''movl 0xc(%ebp),%eax''
-- 汎用レジスタ %eaxに %ebp + 0xcアドレスにある値を loadしてます。0xc(%ebp)は GNUasの i386 Dependな書式(Syntax->i386 depend->Memory Reference)で AT&Tスタイルです。intelスタイルの場合は [%ebp + 0xc]。
- 10行目 ''movb %al,-1(%ebp)''
-- %eaxの8bit分の下位4ビットを[%ebp-1]にロードしてます。
** 寄り道1 makefile
とりあえず作業のためのmakefileとmymemset()を呼び出すmainはこんな感じです。
- main.c
---(
void *mymeset(void *, int, size_t);
int main() {
char buf[255];
mymemcpy(buf, 0, 255);
return (0);
}
---)
- makefile
---(
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の場合
とりあえずこの辺を押さえておけばいいでしょう。
,コマンド,概要
,[symbol]::bp,[symbol]をbreak pointに設定する
,::run,プログラムを実行する
,[symbol]::dis,[symbol]から逆アセンブルする
,[address]::dump,[address]からのメモリをダンプする
,::help [cmd],コマンドラインヘルプ
,::next,次のinstractionに進む。関数呼び出しは全てを行って、制御を戻す。
,::step,次のinstractionに進む。別関数に行った場合、その一つめのinstractionを実行して、制御を戻す。
,::regs,レジスタの内容を表示する
,::stack,スタックを表示する
,$C,スタックをアドレス込みで表示する
,$q,終了する
,::quit,終了する
*** gdbの場合
,コマンド,概要
,b(reak) [symbol],[symbol]をbreak pointに設定する
,r(un),プログラムを実行する
,disas(semble) [symbol],[symbol]から逆アセンブルする
,x/[size] [address],[address]からのメモリをダンプする。[size]にはsizeとlengthを指定する
,stepi(=si),次のinstractionに進む。別関数に行った場合、その一つめのinstractionを実行して、制御を戻す。
,i(nfo) r(egister),レジスタの内容を表示する
,i(nfo) f(rame),スタックを表示する
,q(uit),終了する
** 寄り道4 デバッガで実際に動かしてみる
mdbで実際に動かして動作を見てみます。これに先立って寄り道1の main.cを元に
ELF executableのmainを作ってます。
- 1. 起動
-- mdb main
- 2. break pointの設定
-- > ::bp main
-- > ::bp mymemset
-- break pointは mainとmymemsetにしました。
- 3. 実行
-- > ::run
-- 実行すると、こんなログがでます。
---(
mdb: stop at main
mdb: target stopped at:
main: pushl %ebp
---)
-- mainに入った最初の部分で停止してます。試しに mainのdisassembleコードを見てみるとこんな感じです。
-- > main::dis
---(
main: pushl %ebp
main+1: movl %esp,%ebp
以下、省略
---)
- 4. mymemsetまで進める
-- > ::cont
-- mainはどうでもいいので、先に進めてみます。mainの時と同じようなログがでます。
- 5. レジスタをみる
-- > ::regs
-- この時点ではまだbp/spの設定もしていないので、入ったばかりの状態です。
---(
%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
---)
-- bpは 0x08047aac、spは 0x0804797cですが、最初の数個でこれをずらしています。
-- > mymemset::dis
---(
mymemset: pushl %ebp
mymemset+1: movl %esp,%ebp
mymemset+3: subl $0xc,%esp
mymemset+6: movl 0xc(%ebp),%eax
以下、省略
---)
-- 4行目の 0xc(%ebp),%eaxはスタックの値を axレジスタに移して、処理しようという所です。
- 6. step実行でスタック操作をみる
-- > ::step
-- step実行明けも、break pointの時と同じログがでます。最初は pushl %ebpをしているので、stack pointer(=%esp)の値が -4されて、0x0804797cから 0x08047978に変わります。
-- > ::step
-- 次は %ebpに %espをcopyして、スタックのベースを切り替えるので、%ebpの値も 0x08047aacから 0x08047978に変わります。
- 7. stackを覗いて引数を見てみる
-- この時点でスタックを覗いて見ましょう。
--
* GNU as
as絡みのメモを。Googleで調べれば意外と見つかるもんです。
** GNU as Syntax
*** コメント
コメント文が入っていると、アセンブル時は1つの空白になる。~
コメント用の特殊文字はアーキテクチャ依存であり、共通のコメントブロックは /* */だけ。
で、アーキテクチャ毎の違いは以下の通り。
- ARM
-- @ 以降はコメント扱い。
-- 行頭の # はコメント行扱い
-- 文中の # or $は即値のoperand
- i386
-- / 以降はコメント扱い。
- sparc
-- ! 以降はコメント扱い。
*** dirctive
GNU asが解釈するdirctiveが多数用意されてます。section名(.textやら)以外のものは
as directiveだと思っていいでしょう。アーキテクチャ依存がありますが、例えばこんなものがあります。
- .ascii
--
- .long
--
* 参考
** arm系
,URL,サイト名,補足
,http://www.arm.com/documentation/,ARM Documentation,ARMの提供する公式ドキュメント。日本語資料はmailで登録があってウザいが、本家は好きに持ってけ状態で好印象。
,http://www.nk.rim.or.jp/~jun/,Jun's Home Page,GNU asの書き物がある。
,http://vsync.org/arm/,ARM情,すずめ愛好会のARM7のニーモニックまとめ。いろいろあって仕事でARM9の.sと戦うことになったんで。ひさしびりだな。
,http://www.bomber.co.jp/chaola/docs/ARM/inst_ARM.html,ARM7TDMI ARM-state Instruction set,Chaola D.C.さんのdead stock・・なのかな?
,http://nocash.emubase.de/gbatek.htm,no$gba@gbatek specification,GBAの開発情報。寧ろ、ARM7TDMIの情報拾うため。
** x86系
,URL,サイト名,補足
,http://www5c.biglobe.ne.jp/~ecb/assembler/assembler00.html,アセンブラ入門,丁寧に書いてあるので読みやすい。
,http://ray.sakura.ne.jp/asm/index.html,最適化のためのアセンブラ入門,同上。この上のところもそうだが、wiki以外のHTMLを編集する気にはなれなくなってもう久しい自分には、あのように綺麗で読みやすくて内容もある書き物が出来る人はすごいと思う。
,http://webster.cs.ucr.edu/AoA/,Art of Assembly Language Programming and HLA,Win/Linux向けに0から書いてある書籍をPDFで配布している。流石大学関連。
,http://homepage1.nifty.com/herumi/prog/prog.html,introduction to x86-asm,午後のコーダの作者の解説
** そのほか
,URL,サイト名,補足
,http://sourceware.org/binutils/docs-2.17/,Documentation for binutils,GNU binutil2.17のonlineマニュアル
,http://www.sra.co.jp/wingnut/ld/ld-ja.html,GNUリンカLDの使い方,binutils2.11のonlineマニュアルの日本語訳版。
,http://docs.sun.com/app/docs/doc/816-1681,Assembly Language Reference Manual,いろいろあってこっちも読まんとアカンので。
,http://www.iecc.com/linker/,Linkers and Loaders,翻訳されている書籍『Linkers and Loaders』のサポートサイト。英語版の書籍そのまま+演習課題のscriptがある。
* 背景
0からのGNU as programmingと言う感じで、勉強をまとめてみます。
諸般の事情で可能な限り headerファイルには依存しないコードにするため
本来は p/f dependencyを排除するために用意されている定義も敢えて
書き換えたりする方向でやってきます。
-ってことで、8/16頃に続きを書く予定 -- &new{2006-08-06 (Xxx) xx:xx:xx};
-なんですが、結局かいてません。^^; -- &new{2006-09-26 (Tue) 17:19:41};
-希望者がいれば、続きを執筆しようかとw -- &new{2006-09-26 (Tue) 17:20:43};
#comment(noname)
* 道具立て
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で見ています。引数の意味はそれぞれ下記の通りです。
- -s : 指定したセクションに対して、それぞれの内容をdumpを出力する。
- -S : (可能であれば)ソースコードを逆アセンブル結果と混在させて表示する。-dが暗黙のうちに指定される。
- -d : 逆アセンブルした結果を出力する。
- -x : 表示可能な全てのヘッダ情報を表示する。
-Sを指定しているので、-dはいらないのですが何となく指定しています。
ダンプ結果の個々の部分のコメントはそのまま追記しています。
---(
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行目 ''.file "mymemset.c"''
-- .fileは asm directive(指示語)です。基本的に . で始まるwordは directiveです。
-- .fileは互換性のために用意されてるだけで、プログラム自体にはあまり関係ありません。つけておくと、このあと他のオブジェクトとリンクされたときの、リンク後のオブジェクトが持つシンボルテーブルに、このオブジェクトの提供する関数名の区分けとしてファイル名が入るようになります。
- 2行目 ''.text''
-- これ以降を text segumentと指示します。textセグメントとは ELFのブロックで実行部や定数値を入れておく場所になります。
-- 引数の有無で subsectionの数が指定されるようですが、必要になるまで放置。orz
- 3行目 ''.global mymemset''
-- 引数のシンボルをシンボルテーブルに載せて、後のld(リンカ)がリンクすることが出来るようにします。この場合、このアセンブルコード中で定義されている(はず)の mymemsetだけを ldのリンク対象として公開することになります。
-- .globalの別名として .globlがあるようです。
- 4行目 ''.type mymemset, @function''
-- 公開するシンボルの型を定義します。COFF版とELF版がありますが、COFF版は無視です。
-- 基本形は .type [name], type description です。
-- type descには functionか objectの2択で、GNU asは他の様々な asmとの互換性のために以下の書式を許しています。
,funtionの書式,objectの書式,何で使ってるか(わかれば)
,#function,#object,
,@function,@object,SUNWsproもこれです。
,%function,%object,
,"function","object",
,STT_FUNCTION,STT_OBJECT,
- 5行目 ''mymemset:''
-- 行頭から英数文字or .で始まり、最後に :とあるものはラベルになります。詳細は後述。
-- ここでは mymemsetと言うラベルを定義しています。4行目の公開対象のシンボルになっています。
- 6行目 ''pushl %ebp''
-- ようやく i386の mnemonic codeの登場です。
-- pushl %ebpはお決まりなので無視でもいいですが、bpを待避してます。
-- stackは呼び出し前に使っていた場所に乗せて使うので、呼び出し前のstack frameのベースは触れないようにする必要があります。
-- mnemonicの基本形は pushですが、operandの型がlongの場合 lをsuffixにつけます。
-- %ebpは i386の ebpレジスタです。%[レジスタ名]の書式でレジスタを表します。
- 7行目 ''movl %esp, %ebp''
-- お決まりです。新しいstackを使うためにフレームポインタをセットし直し、spが今のbpの場所を指すようにします。
- 8行目 ''subl $12, %esp''
-- espの値を現在のebp(スタックのベース)の値に対して 12ずらして(=除算して)います。
-- 前述のこのアセンブルソースの元になった Cソースではauto変数を3個使っています。このため stackに
auto変数を格納する領域を用意する必要があります。IPL32(=Int Pointer Longは32bit)なので、unsigned char _c(1byte)と unsigned char *_s(4bytes)、size_t(=unsigned int) i(4bytes)の合計 9bytesですが、alignment上 4x3=12バイトにしているようです。
- 9行目 ''movl 0xc(%ebp),%eax''
-- 汎用レジスタ %eaxに %ebp + 0xcアドレスにある値を loadしてます。0xc(%ebp)は GNUasの i386 Dependな書式(Syntax->i386 depend->Memory Reference)で AT&Tスタイルです。intelスタイルの場合は [%ebp + 0xc]。
- 10行目 ''movb %al,-1(%ebp)''
-- %eaxの8bit分の下位4ビットを[%ebp-1]にロードしてます。
** 寄り道1 makefile
とりあえず作業のためのmakefileとmymemset()を呼び出すmainはこんな感じです。
- main.c
---(
void *mymeset(void *, int, size_t);
int main() {
char buf[255];
mymemcpy(buf, 0, 255);
return (0);
}
---)
- makefile
---(
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の場合
とりあえずこの辺を押さえておけばいいでしょう。
,コマンド,概要
,[symbol]::bp,[symbol]をbreak pointに設定する
,::run,プログラムを実行する
,[symbol]::dis,[symbol]から逆アセンブルする
,[address]::dump,[address]からのメモリをダンプする
,::help [cmd],コマンドラインヘルプ
,::next,次のinstractionに進む。関数呼び出しは全てを行って、制御を戻す。
,::step,次のinstractionに進む。別関数に行った場合、その一つめのinstractionを実行して、制御を戻す。
,::regs,レジスタの内容を表示する
,::stack,スタックを表示する
,$C,スタックをアドレス込みで表示する
,$q,終了する
,::quit,終了する
*** gdbの場合
,コマンド,概要
,b(reak) [symbol],[symbol]をbreak pointに設定する
,r(un),プログラムを実行する
,disas(semble) [symbol],[symbol]から逆アセンブルする
,x/[size] [address],[address]からのメモリをダンプする。[size]にはsizeとlengthを指定する
,stepi(=si),次のinstractionに進む。別関数に行った場合、その一つめのinstractionを実行して、制御を戻す。
,i(nfo) r(egister),レジスタの内容を表示する
,i(nfo) f(rame),スタックを表示する
,q(uit),終了する
** 寄り道4 デバッガで実際に動かしてみる
mdbで実際に動かして動作を見てみます。これに先立って寄り道1の main.cを元に
ELF executableのmainを作ってます。
- 1. 起動
-- mdb main
- 2. break pointの設定
-- > ::bp main
-- > ::bp mymemset
-- break pointは mainとmymemsetにしました。
- 3. 実行
-- > ::run
-- 実行すると、こんなログがでます。
---(
mdb: stop at main
mdb: target stopped at:
main: pushl %ebp
---)
-- mainに入った最初の部分で停止してます。試しに mainのdisassembleコードを見てみるとこんな感じです。
-- > main::dis
---(
main: pushl %ebp
main+1: movl %esp,%ebp
以下、省略
---)
- 4. mymemsetまで進める
-- > ::cont
-- mainはどうでもいいので、先に進めてみます。mainの時と同じようなログがでます。
- 5. レジスタをみる
-- > ::regs
-- この時点ではまだbp/spの設定もしていないので、入ったばかりの状態です。
---(
%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
---)
-- bpは 0x08047aac、spは 0x0804797cですが、最初の数個でこれをずらしています。
-- > mymemset::dis
---(
mymemset: pushl %ebp
mymemset+1: movl %esp,%ebp
mymemset+3: subl $0xc,%esp
mymemset+6: movl 0xc(%ebp),%eax
以下、省略
---)
-- 4行目の 0xc(%ebp),%eaxはスタックの値を axレジスタに移して、処理しようという所です。
- 6. step実行でスタック操作をみる
-- > ::step
-- step実行明けも、break pointの時と同じログがでます。最初は pushl %ebpをしているので、stack pointer(=%esp)の値が -4されて、0x0804797cから 0x08047978に変わります。
-- > ::step
-- 次は %ebpに %espをcopyして、スタックのベースを切り替えるので、%ebpの値も 0x08047aacから 0x08047978に変わります。
- 7. stackを覗いて引数を見てみる
-- この時点でスタックを覗いて見ましょう。
--
* GNU as
as絡みのメモを。Googleで調べれば意外と見つかるもんです。
** GNU as Syntax
*** コメント
コメント文が入っていると、アセンブル時は1つの空白になる。~
コメント用の特殊文字はアーキテクチャ依存であり、共通のコメントブロックは /* */だけ。
で、アーキテクチャ毎の違いは以下の通り。
- ARM
-- @ 以降はコメント扱い。
-- 行頭の # はコメント行扱い
-- 文中の # or $は即値のoperand
- i386
-- / 以降はコメント扱い。
- sparc
-- ! 以降はコメント扱い。
*** dirctive
GNU asが解釈するdirctiveが多数用意されてます。section名(.textやら)以外のものは
as directiveだと思っていいでしょう。アーキテクチャ依存がありますが、例えばこんなものがあります。
- .ascii
--
- .long
--
* 参考
** arm系
,URL,サイト名,補足
,http://www.arm.com/documentation/,ARM Documentation,ARMの提供する公式ドキュメント。日本語資料はmailで登録があってウザいが、本家は好きに持ってけ状態で好印象。
,http://www.nk.rim.or.jp/~jun/,Jun's Home Page,GNU asの書き物がある。
,http://vsync.org/arm/,ARM情,すずめ愛好会のARM7のニーモニックまとめ。いろいろあって仕事でARM9の.sと戦うことになったんで。ひさしびりだな。
,http://www.bomber.co.jp/chaola/docs/ARM/inst_ARM.html,ARM7TDMI ARM-state Instruction set,Chaola D.C.さんのdead stock・・なのかな?
,http://nocash.emubase.de/gbatek.htm,no$gba@gbatek specification,GBAの開発情報。寧ろ、ARM7TDMIの情報拾うため。
** x86系
,URL,サイト名,補足
,http://www5c.biglobe.ne.jp/~ecb/assembler/assembler00.html,アセンブラ入門,丁寧に書いてあるので読みやすい。
,http://ray.sakura.ne.jp/asm/index.html,最適化のためのアセンブラ入門,同上。この上のところもそうだが、wiki以外のHTMLを編集する気にはなれなくなってもう久しい自分には、あのように綺麗で読みやすくて内容もある書き物が出来る人はすごいと思う。
,http://webster.cs.ucr.edu/AoA/,Art of Assembly Language Programming and HLA,Win/Linux向けに0から書いてある書籍をPDFで配布している。流石大学関連。
,http://homepage1.nifty.com/herumi/prog/prog.html,introduction to x86-asm,午後のコーダの作者の解説
** そのほか
,URL,サイト名,補足
,http://sourceware.org/binutils/docs-2.17/,Documentation for binutils,GNU binutil2.17のonlineマニュアル
,http://www.sra.co.jp/wingnut/ld/ld-ja.html,GNUリンカLDの使い方,binutils2.11のonlineマニュアルの日本語訳版。
,http://docs.sun.com/app/docs/doc/816-1681,Assembly Language Reference Manual,いろいろあってこっちも読まんとアカンので。
,http://www.iecc.com/linker/,Linkers and Loaders,翻訳されている書籍『Linkers and Loaders』のサポートサイト。英語版の書籍そのまま+演習課題のscriptがある。
Last-modified: 2006-09-26 17:20:43