4

C コードの一部に、barを呼び出す関数fooがあるとします。bar内では、アセンブリを使用して、barが戻るアドレスを取得できます。この情報を使用してfooのアドレスを特定するにはどうすればよいですか?

1 つのアプローチは、fooが戻る戻りアドレスを取得し、 fooを呼び出す call命令のオペコードからアドレスを取得することです。ただし、これにはどの呼び出し方法 (例: オフセット/絶対) が使用されているかを知る必要があるため、信頼できません。発信者のアドレスを特定する簡単な方法はありますか?

編集: この質問は、32 ビット Intel UNIX マシンでの IA32 アセンブリに関するものであることを忘れていました。

4

3 に答える 3

3

Linux では、次を使用dladdr()して、呼び出し元の関数を解決するために使用できます。

#define _GNU_SOURCE
#include <dlfcn.h>

...

void *retAddr = __builtin_extract_return_addr(__builtin_return_address(0));
Dl_info d;
(void)dladdr(retAddr, &d);
printf("%s called from %s + 0x%p\n",
    __FUNC__,
    d.dli_sname,
    (retAddr - d.dli_saddr));

詳細については、GCC のドキュメント__builtin_return_address()と Linux のマンページdladdr(3)を参照してください。

この関数dladdr()は、Solaris/MacOSX/*BSD でも使用できますが、表示されるようにする以外のプリプロセッサ定義が必要_GNU_SOURCEです。それぞれのオペレーティング システムのマンページを参照してください ...

編集:これはシンボル テーブルの存在に依存しているため、削除されたバイナリでは正常に解決されない可能性があることに注意してください。上記にエラー処理を追加しようとはしていません。一般に、どのタイプの自動バックトレース (関数名解決を伴う) サポートも、シンボル テーブルが取り除かれることを好みません。

非常に簡単なものとして、私は時々単純に使用します:

#include <execinfo.h>

...

void *retAddr[10];
backtrace_symbols_fd(retAddr, backtrace(retaddr, 10), STDERR_FILENO);

10 エントリの深いスタック トレースを取得するためです。繰り返しますが、symtab が削除されていないことに依存しています。複数のアドレスを解決しているため、これにはパフォーマンスのペナルティがあります。

Edit2: シンボル テーブル (とりわけ、実行可能ファイル/ライブラリ内の関数の開始アドレスサイズを含む) がなければ、「開始アドレス」とは何かという情報はかなり無意味です。CPU 自体に関する限り、命令ポインターが特定の時点で現在の場所にどのように到着したかについての記録は実際にはありません。適切に構造化され、コンパイラによって生成されたコードとして、CPU に対して「有効」です。x86 命令は可変サイズであり、オペコード マップは高密度です。gotojmpほぼすべてのランダムなバイトシーケンスが「有効な」命令ストリームを構成するのに十分です。したがって、バイナリ コードのヒューリスティック逆アセンブルは、100% 安全な方法ではありません。

その意味で、シンボル テーブルは、デバッガーの "マーカー" も確立します。シンボル テーブルに記録されている関数の開始アドレスで逆アセンブルを開始すると、有効な命令ストリームを見つけることが期待できます。また、バックトレースで見つかった戻りアドレスが実際にcall命令の前にあることを検証することにより、クロス検証できます。

于 2013-06-10T15:07:20.273 に答える
2

通常のページ フレームが存在し、それbarが通常の呼び出し (レジスタ間接呼び出しとは対照的に) で呼び出されて、アドレスを取得し、barさらに 1 レベル「外に出て」call bar命令を見つけると仮定します。

スタック内fooは次のようになります。

.
.
parameters to bar (if any)
return address, i.e. address following 'call bar'
saved base page (ebp register) value
locals to bar
...
parameters to foo (if any)
return address, i.e. address following 'call foo' within bar
saved base page (ebp register) value
locals to foo

barしたがって、 内からのアドレスを取得するにはfoo、次のようにします (これは私の頭から離れているため、多少の調整が必要になる場合がありますが、一般的な考え方は理解できるはずです)。

mov eax, [ebp]   // load calling scope (bar's) frame pointer
mov eax, [eax+4] // load the return address for bar
mov edx, [eax-4] // load offset from the call instruction that called bar
lea eax, eax+edx // adjust (or something similar) to convert from offset to abs
于 2013-06-09T07:18:13.823 に答える
2

1 つのアプローチは、foo が戻る戻りアドレスを取得し、foo を呼び出す call 命令のオペコードからアドレスを取得することです。

え?これにより、 fooではなくbarのアドレスが得られます。

必要なのは、戻りアドレスよりも低い最上位のプロシージャ エントリ ポイントだけです。

于 2013-06-09T03:53:10.687 に答える