🍣

GLOBAL_OFFSET_TABLE とそれの無効化について

2023/04/10に公開

はじめに

ベアメタルはソフトウェアを作っている中で、別ファイルのアセンブリ言語で定義した関数を、別ファイルの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]
問題の_GLOBAL_OFFSET_TABLE_
セクション .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