gasのディレクティブについて

アセンブリコードをアセンブルすると機械語になる。こういう説明が世の中じゃされてるけど、でもそれは半分ウソ。アセンブリコードの中でも機械語に直接対応しないものがある。それがディレクティブと呼ばれているもので、ドットから始まる命令がこれにあたる。


疑似命令 (psuedo-ops) とも呼ばれるこのアセンブラディレクティブは、機械語への直接の対応ではなくて、アセンブラGNU as)に対する命令。
前回の記事で説明したのはセクションについてだけだったけれども、ディレクティブにはもっといろいろな意味がある。
以下、その具体的な例を挙げていくよ。

文字列を配置する [.ascii .asciz .string]

文字列を配置するディレクティブは三種類ある。 .ascii .asciz .stringの三つだ。
.ascii は、カンマで区切られたゼロ個以上の文字列を、\nや\t、\0などのエスケープ文字を展開しつつ配置してくれる。ヌル文字の添加は行わないので、Cの関数を呼び出す場合は自分でヌル文字を最後につける必要がある。
.asciz と .string も .ascii と同様に、カンマで区切られたゼロ個以上の文字列を、エスケープ文字の展開をしつつ配置する。ただしこちらは各文字列の終わりごとに自動的にヌル文字を添加する。ascizの最後のzはzeroですね。
(.asciz と .string の違いはよく分からない・・・。私の環境だと同一の動作をしているみたい。またhttp://www.oklab.org/gas.xhtmlによるとこの二つは同じ意味だとか。なんか意味の分化でもあったのか、それともstringがascizのエイリアスみたいなもんなのか・・・)


したがって、この三つは同じもの。

.ascii "hoge\0","log\0"
.asciz "hoge","log"
.string "hoge\0log"

どれも"hoge\0log\0"というascii文字列をオブジェクトファイルに配置してくれる。
これらの文字列を使うには、その文字列の前にラベルをつければ(ラベルがその文字列の先頭アドレスを指すわけね)良い。

.rodata
hello_str:
    .ascii "HI YOU FXXEN WORLD!!\0"
(中略)
movl $hello_str, (%esp)

データを設置する [.int .long .short .double .float]

だいたい想像がつくと思うけど、.int は int値を配置するし .double は double値を配置する。 .longは.intと同じ*1意味で、.shortは.wordと同じことが多い。使い方は、

.int 32767

などと書くだけ。その場所に32767をintで配置する。
バイトオーダーやintのサイズ、具体的な浮動小数点の形式などはアセンブラの設定により変化する、だそうだ。

アラインメントを設定する [.align]

データを配置するとなると、今度はデータ境界を気にしなければならなくなる。charなら1byteごとにアクセスできるから問題になることはなかなかないけど、32bit longは4byteごとでのアクセス以外では処理できず、Bus Error(SIGBUS)が発生する*2。アラインメント問題については、アラインメントでググったり、データ型のアラインメントとは何か,なぜ必要なのか?$B#C%W%m%0%i%_%s%0@lLg2]Dx!?Bh#4>O%a%b%j!?%"%i%$%s%a%s%H(Bのあたりを参照のこと。
そのような問題を避けるために、データを配置する位置をデータ境界に揃える必要が出てくる。それを行ってくれるのが .align ディレクティブだ。


例えば

    .ascii "pea"
bad_int:
    .int 84

このようにascii文字列の長さが4の倍数でなかったとき、続く命令の配置される場所はintのアラインメントの境界から外れてしまう。これでは bad_int にアクセスしようとしてもバスエラーが起きる。


そこで、.asciiの後に.alignを入れる。

    .ascii "pea"
    .align 4
good_int:
    .int 84

これを行うと、.asciiのあとに自動的にデータを詰めてくれ、次の命令がintの境界である4byte*3に合うようにしてくれる。

シンボルをリンカまで残す [.globl .global]

指定したシンボルをリンカに見えるようにするのが .globl 。例えば main というシンボルとて .globl 指定をしないとリンカまで伝わらない。他のオブジェクトファイルから利用されるシンボルを、他に見えるようにする目的ですね。main以外でも、グローバル変数なんかもこれを使う。
.globl自体は定義でなくて宣言*4なので、別途定義する必要があるよ。他のファイルに定義があればそれを指すようになる。
例えばこんな感じ。

.globl main   # これが宣言。グローバル
main:         # こっちが定義
    .int 195

グローバル変数の作成はこんな感じに。(デバッグ情報などは省略)

.globl common  # 宣言
    .data
    .align 4
common:        # 定義
    .int 400

データセクションに置いて、アラインメントを揃えて、最後にcommonというラベルをint 400のところに設定。これで$commonがint型変数の先頭アドレスになるね。


.globl と .global の両方が存在しているのは、他のアセンブラとのコンパチのためらしい。

BSSセクションのデータを作成する [.comm]

.comm varname, 4

とすると、varnameという名前で 4 bytesのサイズを指す共通なシンボルを宣言できる。これも宣言なので、他のファイルに同じシンボルの宣言や定義があればそれらとマージされる。
この変数が未定義セクション(BSS section)に割り当てられるのは、リンク時に他の場所に定義がなかったとき。

まとめ

他にも色々なアセンブラディレクティブがあるけれど、上記のものを見ればディレクティブがどのような役割を持っているかは分かると思う。より細かい話については、参考URLの部分を見てくだしあ。GNU as の info 情報が載ってます。
ディレクティブを使えば、gasも意外と強力な言語ですね。


ちなみにextern宣言してもアセンブリコード上にはまるきり何も現れませんでした。こういうシンボルがリンク時に他のオブジェクトファイルから定義を見つけられなかったときに「未解決のシンボルエラー」が発生するのだと思われますね。

「未解決のシンボルエラー」でここに来た人へ
次の三つを確認してください。

  • スペルミスは無い?
  • 呼ばれるより前に宣言してる?
  • ヘッダファイルの依存関係は大丈夫?

あとはリンクの間違いとかもあるけど、とりあえずその辺が90%以上だと思う。


[参考URL]
http://tigcc.ticalc.org/doc/gnuasm.html
http://uw713doc.sco.com/cgi-bin/info2html?(as.info)Index&lang=ja

*1:2008/05/10時点のGNU asのinfoより

*2:というアーキテクチャが多いそうだ。処理できるアーキテクチャでも処理効率がとっても落ちる

*3:2008/5/10現在の一般的な値ね

*4:not definition but declaration; エキスパートCプログラミング(日本語訳) p114の表現を拝借。定義は一回、宣言はいつも何度でも