目次

背景

0からのGNU as programmingと言う感じで、勉強をまとめてみます。 諸般の事情で可能な限り headerファイルには依存しないコードにするため 本来は p/f dependencyを排除するために用意されている定義も敢えて 書き換えたりする方向でやってきます。

  • ってことで、8/16頃に続きを書く予定 -- 2006-08-06 (Xxx) xx:xx:xx
  • なんですが、結局かいてません。^^; -- 2006-09-26 (Tue) 17:19:41
  • 希望者がいれば、続きを執筆しようかとw -- 2006-09-26 (Tue) 17:20:43

道具立て

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@objectSUNWsproもこれです。
%function%object
functionobject
STT_FUNCTIONSTT_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 DocumentationARMの提供する公式ドキュメント。日本語資料はmailで登録があってウザいが、本家は好きに持ってけ状態で好印象。
http://www.nk.rim.or.jp/~jun/Jun's Home PageGNU asの書き物がある。
http://vsync.org/arm/ARM情すずめ愛好会のARM7のニーモニックまとめ。いろいろあって仕事でARM9の.sと戦うことになったんで。ひさしびりだな。
http://www.bomber.co.jp/chaola/docs/ARM/inst_ARM.htmlARM7TDMI ARM-state Instruction setChaola D.C.さんのdead stock・・なのかな?
http://nocash.emubase.de/gbatek.htmno$gba@gbatek specificationGBAの開発情報。寧ろ、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 HLAWin/Linux向けに0から書いてある書籍をPDFで配布している。流石大学関連。
http://homepage1.nifty.com/herumi/prog/prog.htmlintroduction to x86-asm午後のコーダの作者の解説

そのほか

URLサイト名補足
http://sourceware.org/binutils/docs-2.17/Documentation for binutilsGNU binutil2.17のonlineマニュアル
http://www.sra.co.jp/wingnut/ld/ld-ja.htmlGNUリンカLDの使い方binutils2.11のonlineマニュアルの日本語訳版。
http://docs.sun.com/app/docs/doc/816-1681Assembly Language Reference Manualいろいろあってこっちも読まんとアカンので。
http://www.iecc.com/linker/Linkers and Loaders翻訳されている書籍『Linkers and Loaders』のサポートサイト。英語版の書籍そのまま+演習課題のscriptがある。

Last-modified: 2006-09-26 17:20:43