7
4

More than 3 years have passed since last update.

PIEと非PIEにおけるアセンブリでのグローバル変数アクセス

Last updated at Posted at 2020-04-26

グローバル変数を扱うアセンブリを書いているときに、最初よく分からずに非PIEでのコードを書いてしまっていた。
最近のLinuxディストリビューションにインストールされているGCCはデフォルトでPIEとしてリンクしようとするため、非PIEのアセンブリコードはリンクに失敗してしまう。
そのあたりで少し調べたことをメモしておこうと思う。

なお、この記事ではx86-64のアセンブリを対象とし、アセンブリの記法にはIntel記法を使用する。また、検証環境はLinuxベースを前提としている。

PIEとは

PIEとは、Position Independent Executable(位置独立実行形式)のことで、メモリアクセスが全て相対アドレスにより表現されており、メモリ上のどの位置に配置されても正しく実行できるというもの。
似たような用語でPICというものがあるが、こちらはPosition Independent Code(位置独立コード)のことで、PICで構成された実行形式をPIEと呼ぶ(と思う)。

PICは主に共有ライブラリなどで使用される技術だったが、最近はセキュリティの側面から実行形式にもPICを適用する流れがあるようだ。(位置独立コードのWikipedia記事

GCCの対応

GCCはもちろんPIEに対応しているが、GCCのビルド時のオプションに--enable-default-pieを付けるとデフォルトでPIEとして実行形式を出力するようになる。
自分の環境(Debian buster)にインストールされているGCCを確認すると、デフォルトPIEでビルドされていることが分かる。

$ gcc -v
...
Configured with: ... --enable-default-pie ...
...

確認用コード

やりたい処理をCで書くとこんな感じになる。整理のためのコードなので特に意味はない。

long g;

int main() {
    g = 1;  
    return g;
}

グローバル変数をlongで宣言しているのはアセンブリでレジスタの扱いを64ビットに揃えるためで、Cのコードでみた場合はintで宣言したほうが自然にみえる気もするが、アセンブリ優先で書いてみた。

非PIEのアセンブリ

非PIEのアセンブリはこのようになる。

global_no_pie.s
    .intel_syntax noprefix

    .bss
g:
    .long 0

    .text
    .globl  main
main:
    push    rbp 
    mov rbp, rsp 
    mov rax, OFFSET FLAT:g # グローバル変数のアドレスを取得
    mov QWORD PTR [rax], 1
    mov rax, QWORD PTR [rax]
    pop rbp
    ret 

OFFSET FLAT:gでグローバル変数gのアドレスを取得しているわけだが、これは絶対アドレスでの表現となる。そのため、このコードをPIEとして出力することができず、エラーになってしまう。

$ gcc global_no_pie.s -o global_no_pie
/usr/bin/ld: /tmp/ccJR4M58.o: relocation R_X86_64_32S against `.data' can not be used when making a PIE object; recompile with -fPIC
/usr/bin/ld: final link failed: nonrepresentable section on output
collect2: error: ld returned 1 exit status

エラーメッセージで「recompile with -fPIC」として出ているが、この場合は-fPICを付けても結果は変わらない。
このメッセージはリンカであるldが出力しているようで、おそらくldはアセンブリが直接渡されたのではなく、(高級言語からアセンブリを出力するという意味での)コンパイル時に-fPICがついていないため、PIEとしてリンクすることのできないアセンブリが出力され、それがldに渡されていると想定しているのだと思う。

特に理由がなければ、下で説明するようなPIEとして出力可能なコードにすべきとは思うが、-no-pieのオプションを付ければ非PIEとしてリンクすることもできる。

$ gcc global_no_pie.s -o global_no_pie -no-pie

PIEのアセンブリ

PIEとして出力できるアセンブリはこのようになる。

global_pie.s
    .intel_syntax noprefix

    .bss
g:
    .long 0

    .text
    .globl  main
main:
    push    rbp 
    mov rbp, rsp 
    lea rax, QWORD PTR g[rip] # グローバル変数のアドレスを取得
    mov QWORD PTR [rax], 1
    mov rax, QWORD PTR [rax]
    pop rbp
    ret 

やりたいことは非PIEと同じでグローバル変数のアドレスを取得したいのだが、ここではleaという命令を用いてripからの相対アドレスを計算している。ripはインストラクションポインタであるため、これから実行される命令がある場所からの相対値でグローバル変数にアクセスしているということになる。

おまけ

アセンブリについて調べたいとき、GCCが出力するアセンブリを確認することがよくあるが、gccやobjdumpのデフォルトではAT&T記法で出力されたり表示されたりする。Intel記法にするには以下のようにオプションを付ければいい。結構コマンドごとに違って覚えにくいんだよね、、、

$ objdump -M intel -d <file>
$ gcc <file> -S -masm=intel

gccで出力されるアセンブリでの(初期値なしの)グローバル変数の宣言には.commというディレクティブが使用されるようだ。.bssセクションに変数を確保することを意味するらしい。

    .comm   g, 8, 8

# これと同じ
    .bss
g:
    .long 0

まとめ

アセンブリでグローバル変数を扱う際の、PIE対応について整理した。
PIEと非PIEそれぞれでのリンカの処理とかもう少し深く理解したいが、まだまだ勉強不足。

7
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4