アセンブリでラベルを関数として認識させる

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

投稿者: PG_MANA

支離滅裂な自称プログラマー。 C,C++,Rust,JavaScript,PHP,HTML,CSS,OS自作,openSUSE,Arch,旅行 なんか色々してる人 #seccamp 17 19 20 23 #OtakuAssembly