アセンブリで定義された関数を呼び出すCコードがあります。例として、foo.cに次のものが含まれているとします。
int bar(int x); /* returns 2x */
int main(int argc, char *argv[]) { return bar(7); }
また、bar.sには、x86アセンブリでのbar()の実装が含まれています。
.global bar
bar: movl 4(%esp), %eax
addl %eax, %eax
ret
Linuxでは、次のようにこれらのソースを簡単にコンパイルしてGCCにリンクできます。
% gcc -o test foo.c bar.s
% ./test; echo $?
14
MinGWを搭載したWindowsでは、これは「「バー」への未定義の参照」のエラーで失敗します。これの原因は、WindowsではC呼び出し規約の関数のすべての識別子にアンダースコアが付いていることが判明しましたが、アセンブリで「バー」が定義されているため、このプレフィックスを取得せず、リンクが失敗します。 (したがって、エラーメッセージは、実際には、バーではなくシンボル_barがないことについて不平を言っています。)
要約する:
% gcc -c foo.c bar.s
% nm foo.o bar.o
foo.o:
00000000 b .bss
00000000 d .data
00000000 t .text
U ___main
U _bar
00000000 T _main
bar.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 T bar
今の問題は、どうすればこれをうまく解決できるかということです。 Windows専用で書いている場合は、bar.sの識別子にアンダースコアを追加するだけで済みますが、Linuxではコードが壊れます。 gccの-fleading-underscore
および-fno-leading-underscore
オプションを確認しましたが、どちらも(少なくともWindowsでは)何もしないようです。
私が今見ている唯一の選択肢は、アセンブリファイルをCプリプロセッサに渡し、WIN32が定義されている場合は、宣言されたすべてのシンボルを手動で再定義することですが、それもあまりきれいではありません。
誰かがこれに対するクリーンな解決策を持っていますか?おそらく私が監督したコンパイラオプション?たぶんGNUアセンブラは、この特定のシンボルがC呼び出し規約を使用して関数を参照し、そのようにマングルする必要があることを特定する方法をサポートしていますか?他のアイデアはありますか?
1つのオプションは危険ですが、GCCにABIで必要な先頭のアンダースコアを省略するように説得することです。
このオプションとそれに対応する
-fno-leading-underscore
は、オブジェクトファイルでのCシンボルの表現方法を強制的に変更します。 1つの用途は、レガシーアセンブリコードとのリンクを支援することです。警告:
-fleading-underscore
スイッチにより、GCCは、そのスイッチなしで生成されたコードとバイナリ互換ではないコードを生成します。これを使用して、デフォルト以外のアプリケーションバイナリインターフェイスに準拠します。すべてのターゲットがこのスイッチを完全にサポートしているわけではありません。
もう1つのより安全なオプションは、使用する名前をGCCに明示的に指示することです。
5.39アセンブラコードで使用される名前の制御
次のように、宣言子の後に
asm
(または__asm__
)キーワードを記述することにより、C関数または変数のアセンブラーコードで使用する名前を指定できます。int foo asm ("myfoo") = 2;
これは、アセンブラコードの変数
foo
に使用される名前が「myfoo' rather than the usual \``_foo
」であることを指定します。通常、C関数または変数の名前の前にアンダースコアが付いているシステムでは、この機能を使用すると、アンダースコアで始まらないリンカーの名前を定義できます。
非静的ローカル変数にはアセンブラー名がないため、この機能を非静的ローカル変数で使用することは意味がありません。変数を特定のレジスタに配置しようとしている場合は、 Explicit Reg Vars を参照してください。 GCCは現在、警告付きのそのようなコードを受け入れますが、将来、警告ではなくエラーを発行するように変更される可能性があります。
関数definition;でこのように
asm
を使用することはできません。ただし、次のように、関数の定義の前に関数の宣言を記述し、そこにasm
を配置することで、同じ効果を得ることができます。extern func () asm ("FUNC"); func (x, y) int x, y; /* ... */
選択したアセンブラ名が他のアセンブラシンボルと競合しないことを確認するのはあなた次第です。また、レジスタ名を使用しないでください。これにより、完全に無効なアセンブラコードが生成されます。 GCCには、静的変数をレジスタに格納する機能がまだありません。おそらくそれが追加されるでしょう。
あなたの場合、
extern int bar(int x) asm("bar");
「bar
はccall関数ですがasm名 `` bar` 'を使用します」とGCCに伝える必要があります。
Cプリプロセッサを使用してアセンブリを前処理し、マクロを使用してWindowsで欠落しているアンダースコアを追加できます。まず、アセンブリファイルの名前をbar.sからbar.S(大文字の「S」)に変更する必要があります。これにより、gccはcppを使用してファイルを前処理するように指示されます。
不足しているアンダースコアを追加するには、次のようにマクロ「cdecl」を定義できます。
#if defined(__WIN32__)
# define cdecl(s) _##s
#else
# define cdecl(s) s
#endif
次に、次のように使用します。
.global cdecl(bar)
cdecl(bar):
movl 4(%esp), %eax
addl %eax, %eax
ret
Mac OSXでも先頭にアンダースコアが必要なため、次のようにマクロの最初の行を更新できます。
#if defined(__WIN32__) || defined(__Apple__)
二度宣言できますか?
.global bar
.global _bar
しばらくの間Assemblyを作成していませんが、.global識別子はラベルのように機能しますか?
ELFターゲットのコンパイラーは、デフォルトで先頭の下線を追加しません。 ELF形式(Linuxの場合)にコンパイルするときに-fleading-underscore
を追加できます。 makefileで条件を使用します。
参照: http://opencores.org/openrisc,gnu_toolchain (「グローバル名を変更しない」のページ上の検索を実行します)