GLOBAL_OFFSET_TABLE とそれの無効化について
はじめに
ベアメタルはソフトウェアを作っている中で、別ファイルのアセンブリ言語で定義した関数を、別ファイルのC/C++で書いているソースコードで関数ポインタを参照した場合に以下のような_GLOBAL_OFFSET_TABLE_
が定義されてしまい、結果として正常に動作しない問題が起きたのでちょっと記事にしてみました。
4028b140: aa0303e0 mov x0, x3
4028b144: 97fffb9e bl 40289fbc <memcpy>
4028b148: d0000260 adrp x0, 402d9000 <_GLOBAL_OFFSET_TABLE_>
4028b14c: f9400401 ldr x1, [x0, #8]
4028b150: f94027e0 ldr x0, [sp, #72]
セクション .got の逆アセンブル:
00000000402d9000 <_GLOBAL_OFFSET_TABLE_>:
...
402d9008: 40280800 .inst 0x40280800 ; undefined
402d900c: 00000000 udf #0
402d9010: 40291230 .inst 0x40291230 ; undefined
402d9014: 00000000 udf #0
セクション .got.plt の逆アセンブル:
00000000402d9018 <.got.plt>:
...
セクション .bss の逆アセンブル:
GLOBAL_OFFSET_TABLE とは何か?
GLOBAL_OFFSET_TABLE
(GOT)は、共有ライブラリや動的リンクされたオブジェクトファイル内に存在する特別なセクションの1つです。GOTは、共有ライブラリが使用する関数やデータオブジェクトのグローバルなオフセット(位置)を保存するテーブルです。
共有ライブラリは、リンク時にはまだ実際のアドレスが決まっていないため、GOTを使用してシンボルのアドレスを解決します。GOTは、共有ライブラリの関数やデータオブジェクトのアドレスが最初にアクセスされるときに、動的リンカによって更新されます。これにより、実行時にシンボルを解決できるようになります。
例えば、ある共有ライブラリが printf 関数を使用する場合、そのライブラリのコードは printf 関数の実際のアドレスを知りません。そのため、GOTには printf 関数のグローバルオフセットが保存されており、ライブラリが printf を呼び出すと、動的リンカはGOTを更新し、実際の printf 関数のアドレスに置き換えます。
GLOBAL_OFFSET_TABLE を使わないようにする方法
-fvisibility=hidden
オプション
通常は、コンパイル時の-fvisibility=hidden
オプションを利用すれば解決するはずなのですが、今回のケースではこれは動作しませんでした。。
visibility("hidden")
の追加
visibility("hidden")を関数の宣言に追加することで、その関数のシンボルがデフォルトで公開されず、GOTが使用されなくなります。つまり、関数が静的リンクされた場合、その関数は実際に呼び出される前にアドレスが解決され、GOTが必要なくなります。
void KernelSwitchFromKThread() __attribute__((visibility("hidden")));
一方、関数が共有ライブラリに含まれている場合、visibility("hidden")を使用すると、リンカはその関数が外部からアクセスされないと判断し、GOTのエントリを削除して関数を直接呼び出します。
また、これを利用することで関数が静的リンクされ、パフォーマンスの向上も期待できます。
Discussion