私はAndroidデバイス上のARMCortex-Aをターゲットとするコードを(GNUアセンブラとコンパイラを使用して)作成しており、AssemblyとCの間のインターフェイスを試みています。特に、AssemblyからCで記述された関数を呼び出すことに興味があります。.extern
ディレクティブやC関数の宣言asm
など、いろいろ試してみ__asm__
ましたが、どれもうまくいかなかったので、最小限の例を探しています。そのような例への参照も同様に歓迎されます。
4 に答える
ARM ARMを読むか、命令セットがすべてであることを知っている必要があります。通常は、次のようなことを行います。
asm:
bl cfun
c:
void cfun ( void )
{
}
これを自分で試すことができます。gnu asとgccの場合、これは問題なく機能します。clangを使用してcコードをオブジェクトに取得し、gnuをアセンブラの場合も問題なく機能するはずです。何を使用しているかわからない。
上記の問題は、blのリーチが限られていることです。
if ConditionPassed(cond) then
if L == 1 then
LR = address of the instruction after the branch instruction
PC = PC + (SignExtend_30(signed_immed_24) << 2)
bl命令がリンクレジスタをbl命令の後の命令に設定することを知っている場合、プログラムカウンターレジスタについて読むと、次のようになります。
For an ARM instruction, the value read is the address of the instruction
plus 8 bytes. Bits [1:0] of this
value are always zero, because ARM instructions are always word-aligned.
したがって、asmを次のように表示すると、次のようになります。
mov lr,pc
ldr pc,=cfun
あなたが得る
d6008034: e1a0e00f mov lr, pc
d6008038: e51ff000 ldr pc, [pc, #-0] ; d6008040
...
d6008040: d60084c4 strle r8, [r0], -r4, asr #9
アセンブラは、ldr pcの到達範囲内にあるメモリ位置を予約し(可能であれば、そうでない場合はエラーを生成します)、命令の完全な32ビットアドレスを配置します。リンカは後でこのアドレスに外部アドレスを入力します。そうすれば、アドレス空間内の任意のアドレスに到達できます。
そのようなアセンブラゲームをプレイしたくなく、制御したい場合は、関数のアドレスを保持する場所を作成し、それを自分でPCにロードします。
mov lr,pc
ldr pc,cfun_addr
...
cfun_addr:
.word cfun
編集済み:
d6008034: e1a0e00f mov lr, pc
d6008038: e51ff000 ldr pc, [pc, #-0] ; d6008040 <cfun_addr>
...
d6008040 <cfun_addr>:
d6008040: d60084c4 strle r8, [r0], -r4, asr #9
最後に、ARMとthumbが混在している、または可能である(たとえば、mov pc、lrの代わりにbx lrを使用する)現代のARMの世界に移動したい場合は、bxを使用する必要があります。
add lr,pc,#4
ldr r1,cfun_addr
bx r1
...
cfun_addr:
.word cfun
もちろん、それを行うには別のレジスタが必要です。保存したい場合は、Cへの呼び出しの前後にリンクレジスタと他のレジスタをプッシュアンドポップすることを忘れないでください。
最小限の実行可能なarmv7の例
この質問は、「ARM呼び出し規約(AAPCS)とは何ですか」というものです。例a.S
:
/* Make the glibc symbols visible. */
.extern exit, puts
.data
msg: .asciz "hello world"
.text
.global main
main:
/* r0 is the first argument. */
ldr r0, =msg
bl puts
mov r0, #0
bl exit
次に、Ubuntu 16.04で:
sudo apt-get install gcc-arm-linux-gnueabihf qemu-user-static
# Using GCC here instead of as + ld without arguments is needed
# because GCC knows where the C standard library is.
arm-linux-gnueabihf-gcc -o a.out a.S
qemu-arm-static -L /usr/arm-linux-gnueabihf a.out
出力:
hello world
より複雑な例で犯す最も簡単な間違いは、スタックが8バイトに整列されている必要があることを忘れることです。たとえば、次のようにします。
push {ip, lr}
それ以外の:
push {lr}
一般化されたボイラープレートを使用したGitHubの例:https ://github.com/cirosantilli/arm-assembly-cheat/blob/82e915e1dfaebb80683a4fd7bba57b0aa99fda7f/c_from_arm.S
の仕様armeabi-v7a
、呼び出しスタックの記述、レジスタ(呼び出し先と呼び出し元)などが必要です。次に、コンパイルされたCコードからのアセンブリ出力で構文などを確認します。共有ライブラリまたは再配置可能で関数を呼び出そうとすると、事態はさらに複雑になります。オブジェクト。
ブレットが言うように、あなたが本当にしなければならないのは、正しい値を正しいレジスタに入れ、関数のアドレスへのリンク付きで分岐することだけです。コンパイルされた関数が上書きするレジスタと、返される前に復元されるレジスタに注意する必要があります。これらはすべて、infocentre.arm.comのABIドキュメントに記載されています。また、スタックレジスタがコンパイラが期待するものに設定されていることを確認する必要があります。また、他のレジスタも設定されていることを確認する必要があります(PICモードの場合)。
しかし、本当にアセンブラファイルにコードを書く必要がありますか?
GCCの「asm」機能を使用する場合は、アセンブラフラグメントを(好きなだけ)通常のC関数に埋め込んで、都合のよいときにいつでもCに戻すことができます。
Cのガビンを持っていてもうまくいかない場合もありますが、C関数を呼び出すことができれば、そうではないと思います。
さて、なぜアセンブラを使う必要があるのでしょうか…。とにかくCは基本的に高級アセンブラですか?