OS自作などでアセンブリでなにかの処理を書いた後に、これをCやRustなどから呼び出すことがあると思います。
このようなことをしている時に稀にリンカが超巨大エラーメッセージを吐いてズッコケることがありませんか?私はあります。
さて、このエラーをチンタラ解読するとアセンブリで書いた関数が再配置で呼び出し元の関数と離れすぎて相対ジャンプできないんだけど、みたいなことが書いてあります。例えば以下のようなエラーを吐いている場合などです。
relocation R_X86_64_PLT32 out of range: -549755221402 is not in [-2147483648, 2147483647]
「じゃあアセンブリの関数を呼び出し元の近くに配置してくれればええやんけ…」と思ってもやってくれないので、どうにかするしかないわけです。
手始めにstripなどされてないバイナリをobjdump -x
でアセンブリのラベルがどう認識されているかを調べてみます。
アセンブリで
.section .text
hoge:
mov rax, rdi
mul rdi
ret
などと書いたとします。
すると、objdump -x
ではffffff8000100000 l .text 0000000000 hoge
などと表示されます。
ここでCやRustで書かれた関数を見るとffffff8000200000 l F .text 000000003f foo
という感じの表示になっています。
さて、違いを見比べるとアセンブリで書いた関数は「Fのマークがない」「サイズが0」という点が異なるとわかります。特にサイズが0だと言うことはリロケーションができそうになさそうです。これで関数の再配置ができなくてリンカがコケるのではないかと思うわけです。
ここでアセンブリのラベルを高級言語での関数と同等の表示がされるようにしてみます。
まずは、「F」のマークをつけたいと思います。おそらく「Function」の略でしょうが、これは単に.text
セクションに置くだけではつかないようです。
ここでELF形式で出力する場合は、.type
という擬似命令を使用できます。hogeの上に.type
を追加してみます。
.section .text
.type hoge, %function
hoge:
mov rax, rdi
mul rdi
ret
これで再度objdumpの結果を見てみるとffffff8000100000 l F .text 0000000000 hoge
などと表示されているはずです。ひとまずは関数として認識させられたようです。
問題は関数のサイズをどう認識させるかです。これがないと、どこまでがhogeなのかがリンカからわかりません。
ネットを探し回っていると.size
という擬似命令を見つけました。
これは.size label_name, size
でlabel_name
のサイズをsize
であると指定する命令のようです。
これでsize
にhogeのretまでのバイト数を明記すれば良さそうです。
ただ、毎度計算するのは面倒なので”.
“を使って、楽をします。
.section .text
.type hoge, %function
hoge:
mov rax, rdi
mul rdi
ret
.size hoge, . - hoge
“.
“は現在のアドレスを示すので、関数の先頭のアドレスを示すラベルとの引き算で関数のサイズが計算できます。
これでビルドしてobjdumpの結果を見ると、ffffff8000100000 l F .text 0000000007 hoge
というふうにサイズもしっかり記載されてます。
これでhogeがリロケーションされ呼び出し元の近くに配置してリンカのエラーがなくなることもあります。(別のことが原因の場合もある)
この記事の元ツイート: https://twitter.com/PG_MANA_/status/1550674356929724416