va_list
x86_64 ABI (Linux で使用されるもの) での表現のリファレンスを持っている人はいますか? スタックまたは引数が破損しているように見えるコードをデバッグしようとしていますが、何を見ているのかを理解するのに本当に役立ちます...
3 に答える
x86-64 System V ABi のドキュメントが役立つ場合があります。軽くても参考になります。
可変引数リストのリファレンスは 54 ページから始まり、56 ~ 57 ページのドキュメントに続きますva_list
。
va_list
タイプ_型は、マクロ
va_list
を実装するために必要な情報を含む 1 つの構造体の 1 つの要素を含む配列です。va_arg
型の C 定義を図va_list
3.34 に示します。図 3.34:
va_list
型宣言typedef struct { unsigned int gp_offset; unsigned int fp_offset; void *overflow_arg_area; void *reg_save_area; } va_list[1];
va_start
マクロ_マクロは
va_start
、次のように構造体を初期化します。
reg_save_area
このエレメントは、レジスター保管域の開始を指します。
overflow_arg_area
このポインターは、スタックに渡された引数を取得するために使用されます。スタックに渡された最初の引数がある場合は、そのアドレスで初期化され、スタック上の次の引数の先頭を指すように常に更新されます。
gp_offset
reg_save_area
この要素は、次に使用可能な汎用引数レジスタが保存される場所までのオフセットをバイト単位で保持します。すべての引数レジスタが使い果たされた場合、値 48 (6 * 8) に設定されます。
fp_offset
reg_save_area
この要素は、次に使用可能な浮動小数点引数レジスタが保存される場所までのオフセットをバイト単位で保持します。すべての引数レジスタが使い果たされた場合、値 304 (6 * 8 + 16 * 16) に設定されます。
問題はgccがva_list
配列型を作成することでした。私の機能は署名でした:
void foo(va_list ap);
ap
そして、別の関数へのポインターを渡したかったので、次のようにしました。
void foo(va_list ap)
{
bar(&ap);
}
残念ながら、配列型は関数の引数リストのポインタ型に減衰するため、元の構造体にポインタを渡すのではなく、ポインタをポインタに渡していました。
この問題を回避するために、コードを次のように変更しました。
void foo(va_list ap)
{
va_list ap2;
va_copy(ap2, ap);
bar(&ap2);
va_end(ap2);
}
これは私が思いついた唯一のポータブルソリューションでva_list
あり、配列型である可能性とそうでない可能性の両方を説明しています。
i386 アーキテクチャでは、va_list はポインタ型です。ただし、AMD64 アーキテクチャでは配列型です。違いはなんですか?実際、ポインタ型に & 演算を適用すると、このポインタ変数のアドレスが取得されます。しかし、配列型に & 操作を何度適用しても、値は同じであり、この配列のアドレスと等しくなります。
では、AMD64 で何をすべきでしょうか? 関数で va_list の変数を渡す最も簡単な方法は、* または & 演算子なしで渡すことです。
例えば:
void foo(const char *fmt, ...) {
va_list ap;
int cnt;
va_start(ap, fmt);
bar(fmt, ap);
va_end(ap);
return cnt;
}
void bar(const char *fmt, va_list ap) {
va_arg(ap, int);
//do something
test(ap);
}
void test(va_list ap) {
va_arg(ap, int);
//do something
}
それはうまくいきます!また、引数の数を気にする必要はありません。