次のようなグローバル変数がどのように扱われるかを確認する

clang -S -mllvm --x86-asm-syntax=intel global.c で以下のようなコードが生成される。

なるほど、グローバル変数 aDATA セクションにラベル _a が振られているようです。2倍の演算は shl によって左ビットシフトすることにより実現しているようです。

未定義のグローバル変数に関しては .comm という擬似命令を使って表現されるようです。次のCコードで確認してみます。

.comm についてリファレンスには以下のように記載してあります。

.comm name, size,alignment
The .comm directive allocates storage in the data section. The storage is referenced by the identifier name. Size is measured in bytes and must be a positive integer. Name cannot be predefined. Alignment is optional. If alignment is specified, the address of name is aligned to a multiple of alignment.

今回 add 関数は a1 を3倍した値を返すという内容にしてみました(名前変えるのわすれてた)。すると imul という命令が登場しました。こいつは単純なSigned Multiply を実現する命令です。dword ptr [rip + _a1] の値を 3倍して eax レジスタに格納するといったことをやっています。どうやら2のべき乗のときだけ shl 命令を使い、それ以外のときは imul 命令が使われる雰囲気がある。

ローカル変数

対してローカル変数はどう扱われるのか。簡単なCコードで確認してみる。

最適化をかけずにアセンブリコードを生成。

.cfiを省いたアセンブリコード。各行にコメントを付与した。_add で関数のおきまりの処理(push rbp, mov rbp, rsp)を行ったあと、rbp はスタックポインタと同じ位置を指しているはずだ。int b = 10; というコードはスタック領域に積む形で値 10 が格納されることにより実現されていることがわかる。ただし rsp は進んでいないため、スタックにプッシュしたことにはならない。すなわち、関数の外側からはローカル変数には(通常)アクセスできないような仕組みになっている。なるほど…という感じだ。あとは eax に演算処理結果を突っ込んでいって ret するという流れのようです。

ところで、このCコードにおけるローカル変数 b は明らかに無駄なコードです。最適化オプションをオンにして生成されるアセンブリコードを見てみます。

今度は、rbp-4 へ 10 の格納を行わず、直接 eax レジスタへ定数 10 を加算しているのが見て取れる。なるほどなぁ…って感想です。

参考文献

  • https://ja.wikibooks.org/wiki/X86%E3%82%A2%E3%82%BB%E3%83%B3%E3%83%96%E3%83%A9/GAS%E3%81%A7%E3%81%AE%E6%96%87%E6%B3%95
  • http://www.mztn.org/slasm/arm07.html
  • http://milkpot.sakura.ne.jp/note/x86.html
  • http://www7b.biglobe.ne.jp/~robe/pf/pf001.html
  • https://docs.oracle.com/cd/E26502_01/html/E28388/eoiyg.html
  • http://x86.renejeschke.de/html/file_module_x86_id_138.html
  • https://docs.oracle.com/cd/E19455-01/806-3773/instructionset-39/index.html

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です