foo()
関数のどこかで呼び出される関数bar()
の内部にいる場合、この戻りアドレスはスタックにプッシュされることを知っています。
#include <stdio.h>
void foo()
{
unsigned int x;
printf("inside foo %x\n", &x);
}
int main()
{
foo();
printf("in main\n");
return 0;
}
上記のコードでは、foo関数がアクティブなときに、スタックに最初にプッシュされたローカル変数のアドレスを取得します。スタック上のこの変数の前のどこかにプッシュされたリターンアドレス(メインはfooと呼ばれます)にアクセスするにはどうすればよいですか?その場所は固定されており、最初のローカル変数を基準にしてアクセスできますか?どうすれば変更できますか?
編集:私の環境は、gccコンパイラを備えたx86プロセッサ上のUbuntu9.04です。
これにはgccが組み込まれています:void * __builtin_return_address (unsigned int level)
http://gcc.gnu.org/onlinedocs/gcc/Return-Address.html を参照してください。
一部のアーキテクチャでは、最初のパラメータに関連するスタックで見つけることができます。たとえば、ia32では、パラメータが(逆の順序で)プッシュされてから、リターンアドレスをプッシュする呼び出しが行われます。スタックはほとんどの場合(およびia32では)下向きに成長することを忘れないでください。技術的には[〜#〜] abi [〜#〜]または呼び出し規約(言語とハードウェアプラットフォームではリンケージ規約と呼ばれることもあります)、実際には、プロシージャがマシンを呼び出す方法を知っているかどうかを推測できます。 opは機能します。
関数への最初のパラメーターとスタック上の戻りアドレスの位置との関係は、ローカルと戻りアドレスの間の関係よりも、確実に固定された値である可能性がはるかに高くなります。ただし、ローカルのアドレスと最初のパラメータのアドレスを確実に印刷することができ、PCはその中間にあることがよくあります。
$ expand < ra.c
#include <stdio.h>
int main(int ac, char **av) {
printf("%p\n", __builtin_return_address(0));
return 0;
}
$ cc -Wall ra.c; ./a.out
0xb7e09775
$
ローカル変数を宣言すると、それらもスタック上にあります-たとえば、xです。
次に、int * xptr
を宣言して&x
に初期化すると、xを指します。
少し前に覗くためにそのポインタをデクリメントしたり、後で見るためにポインタをインクリメントしたりすることを(大いに)妨げるものはありません。どこかにあなたの帰りの住所があります。
リターンアドレスがどこにあるかを知るには、 呼び出し規約 が何であるかを知る必要があります。これは通常、コンパイラによって設定され、プラットフォームによって異なりますが、Windowsで__declspec(stdcall)
を使用するなど、プラットフォーム固有の方法で強制することができます。最適化コンパイラは、外部スコープを持たない関数に対して独自の呼び出し規約を考案する場合もあります。
コンパイラの組み込みを使用して戻りアドレスを取得する場合を除いて、値を取得するにはインラインアセンブラを使用する必要があります。デバッグで機能するように見える他の手法は、コンパイラの最適化がそれらを台無しにするのに非常に脆弱です。
あなたはそのようにスタックの周りを調べることができます
// assuming a 32 bit machine here
void digInStack(void) {
int i;
long sneak[1];
// feel free to adjust the search limits
for( i = -32; i <= 32; ++i) {
printf("offset %3d: data 0x%08X\n", i, sneak[i]);
}
}
Cは配列のインデックス作成方法にあまりこだわらないことで有名なので、これを回避できます。ここでは、スタック上でダミー配列を宣言し、それを基準にして+/-を覗きます。
Rob Walkerが指摘したように、見ているデータを理解するには、コンパイラーの呼び出し規約を確実に知る必要があります。いくつかの関数のアドレスを出力して、同様の範囲にある値を探し、ダミー配列を基準にして、戻りアドレスがどこにあるかを直感的に理解することができます。
注意事項-必要なものをすべて読んでください。ただし、(a)スタックのどの部分を変更するかについて完全に確信している場合、または(b)単に興味深いものを見たい場合を除いて、その配列を使用して何も変更しないでください。 /予測不可能なクラッシュモード。
また、一般に、リターンアドレスがスタック上、または実際にはRAM内のどこかにあるというC言語による保証はまったくないことにも注意してください。
リターンアドレスをレジスタに格納するプロセッサアーキテクチャがあり、呼び出しがネストを開始したときにのみRAM)に頼ります。リターンアドレス用に個別のスタックがあり、読み取りできないアーキテクチャもあります。これらは両方とも、Cコンパイラを実装することができます。
これが、環境をより明確にする必要がある理由です。