24

C ++プログラムで関数のサイズを見つけようとしているので、この質問を読んでいました。プラットフォーム固有の方法がある可能性があることを示唆しています。私のターゲットプラットフォームはWindowsです

私が現在頭の中で持っている方法は次のとおりです
。1。関数へのポインターを取得します。2。3
のマシンコード値に達するまでポインター(&カウンター)をインクリメントしret
ます。カウンターは関数のサイズになりますか?

編集1:「サイズ」の意味を明確にするために、関数を構成するバイト数(マシンコード)を意味します。
Edit2:なぜ、またはこれで何をするつもりなのかを尋ねるコメントがいくつかあります。正直な答えは、私には意図がなく、関数のコンパイル前の長さを知ることの利点を実際に理解することはできません。(確かにいくつかありますが)

これは私には有効な方法のようですが、これは機能しますか?

4

17 に答える 17

16

うわー、私は常に関数サイズカウントを使用していますが、それにはたくさんの用途があります。信頼できますか?とんでもない。標準のC ++ですか?とんでもない。しかし、そのため、新しいバージョンをリリースするたびに、逆アセンブラーでチェックして、機能することを確認する必要があります。コンパイラ フラグは、順序を台無しにする可能性があります。

static void funcIwantToCount()
{
   // do stuff
}
static void funcToDelimitMyOtherFunc()
{
   __asm _emit 0xCC
   __asm _emit 0xCC
   __asm _emit 0xCC
   __asm _emit 0xCC
}

int getlength( void *funcaddress )
{
   int length = 0;
   for(length = 0; *((UINT32 *)(&((unsigned char *)funcaddress)[length])) != 0xCCCCCCCC; ++length);
   return length;
}

静的関数の方がうまくいくようです。グローバルな最適化はそれを殺すことができます.

PS私は、なぜこれをやりたいのか、それは不可能なのかなどと尋ねる人が嫌いです。これらの質問をするのはやめてください。あなたは愚かに聞こえます。新製品はほとんどの場合、利用可能なものの限界を超えるため、プログラマーは非標準的なことをするように求められることがよくあります。そうでない場合、あなたの製品はおそらくすでに行われたことの焼き直しです。つまらない!!!

于 2012-04-09T09:30:15.067 に答える
15

いいえ、これは機能しません:

  1. ret関数に含まれる命令が 1 つだけであるという保証はありません。
  2. 単一retの .

最初の問題は、コーディング スタイルを制限して、たとえば、関数内の戻り点を 1 つだけにすることで回避できる可能性がありますが、もう 1 つの問題は基本的に逆アセンブラーを必要とするため、個々の命令を区別できます。

于 2011-04-13T21:12:52.297 に答える
13

関数のすべてのブロックを取得することは可能ですが、関数の「サイズ」を尋ねるのは不自然です。最適化されたコードは、コード ブロックを実行順に再配置し、めったに使用されないブロック (例外パス) をモジュールの外側の部分に移動します。詳細については、リンク時のコード生成で Visual C++ がこれをどのように実現するかについて、「プロファイルに基づく最適化」を参照してください。したがって、関数はアドレス 0x00001000 で開始し、0x00001100 で分岐して 0x20001000 のジャンプと ret になり、いくつかの例外処理コード 0x20001000 を持つことができます。0x00001110 で、別の関数が開始されます。関数の「サイズ」は?それは 0x00001000 から +0x20001000 にまたがりますが、その範囲内のいくつかのブロックしか「所有」しません。したがって、あなたの質問は聞かないでください。

このコンテキストには、関数が持つ命令の総数 (プログラム シンボル データベースとイメージから判断できます) などの他の有効な質問があり、さらに重要なこととして、内部で頻繁に実行されるコード パスの命令の数はいくつですか。関数。これらはすべて、パフォーマンス測定のコンテキストで通常尋ねられる質問であり、コードを計測して非常に詳細な回答を提供できるツールがあります。

メモリ内のポインターを追跡して検索してretも、どこにも行けないのではないかと心配しています。現代のコードは、それよりもはるかに複雑です。

于 2011-04-13T21:23:23.700 に答える
7

これはうまくいきません... ジャンプ、ダミーret、そしてジャンプのターゲットがある場合はどうなるでしょうか? あなたのコードはだまされます。

一般に、これを 100% の精度で行うことは不可能です。これは、停止問題を解決するようなもので、すべてのコード パスを予測する必要があるためです。独自の逆アセンブラーを実装すると、「かなり良い」精度が得られますが、想像するほど簡単な解決策はありません。

「トリック」は、探している関数のにある関数のコードを見つけることです。これにより、特定の (危険な) 仮定を仮定すると、かなり良い結果が得られます。しかし、関数の後にどの関数が来るかを知る必要があります。これは、最適化の後、把握するのが非常に困難です。


編集1:

ret関数がまったく命令終わっていない場合はどうなりますか? jmp呼び出し元に戻る可能性は十分にあります (可能性は低いですが)。


編集2:

少なくとも x86 には可変長命令があることを忘れないでください...


アップデート:

フロー分析は停止問題を解決することと同じではないと言う人のために:

次のようなコードがあるとどうなるか考えてみてください。

foo:
    ....
    jmp foo

関数の終わりを理解するために毎回ジャンプをたどる必要があり、自己変更コードを扱っているかどうかわからないため、最初の回を過ぎてそれを無視することはできません。(たとえば、それ自体を変更する C++ コードにインライン アセンブリを含めることができます。) メモリの他の場所に拡張する可能性が非常に高いため、フォールス ネガティブを許容しない限り、アナライザーは無限ループで終了します (または終了する必要があります)。

停止問題と同じじゃない?

于 2011-04-13T21:08:42.673 に答える
3

これ、非常に限られたシナリオで機能します。私が書いたコード挿入ユーティリティの一部で使用しています。どこで情報を見つけたか覚えていませんが、次のものがあります (VS2005 の C++):

#pragma runtime_checks("", off)

static DWORD WINAPI InjectionProc(LPVOID lpvParameter)
{
    // do something
    return 0;
}

static DWORD WINAPI InjectionProcEnd()
{
    return 0;
}

#pragma runtime_checks("", on)

そして、私が持っている他の機能で:

size_t cbInjectionProc = (size_t)InjectionProcEnd - (size_t)InjectionProc;

これを機能させるには、いくつかの最適化をオフにして、関数を静的として宣言する必要があります。詳細は覚えていません。これが正確なバイト数かどうかはわかりませんが、十分に近いものです。サイズは即時関数のサイズのみです。その関数によって呼び出される可能性のある他の関数は含まれません。このような極端なエッジケースを除けば、「関数のサイズ」は無意味で役に立ちません。

于 2011-04-14T00:02:28.160 に答える
3

私はこれを投稿して、次の2つのことを言います。

1)ここで与えられた答えのほとんどは本当に悪く、簡単に壊れてしまいます. C 関数ポインター (関数名を使用) を使用する場合debug、実行可能ファイルのビルド、および場合によっては他の状況で、関数本体自体を持たないJMP shimを指す場合があります。これが例です。以下で定義した関数に対して次のことを行うと:

FARPROC pfn = (FARPROC)some_function_with_possibility_to_get_its_size_at_runtime;

pfnI get (例: )は0x7FF724241893これを指しますが、これは単なるJMP指示です:

ここに画像の説明を入力

さらに、コンパイラはこれらのシムのいくつかをネストしたり、関数コードを分岐して、複数のエピローグまたはret命令を持つようにすることができます。一体、それはret命令さえも使用しないかもしれません。次に、関数自体がソース コードで定義した順序でコンパイルおよびリンクされるという保証はありません。

これらはすべてアセンブリ言語で実行できますが、C や C++ では実行できません。

2)というわけで、上記は悪いニュースでした。良いニュースは、元の質問に対する答えは、はい、正確な関数サイズを取得する方法(またはハック) があるということですが、次の制限があります。

  • Windows の 64 ビット実行可能ファイルでのみ機能します。

  • これは明らかに Microsoft 固有のものであり、移植性がありません。

  • これは実行時に行う必要があります。

概念は単純です。SEH がx64 Windows バイナリに実装されている方法を利用します。IMAGE_DIRECTORY_ENTRY_EXCEPTIONコンパイラは、正確な関数サイズを取得するために使用できるPE32+ ヘッダー (オプションのヘッダーのディレクトリ) に各関数の詳細を追加します。(ご参考までに、この情報は、ブロック内の例外のキャッチ、処理、巻き戻しに使用されます。)__try/__except/__finally

簡単な例を次に示します。

//You will have to call this when your app initializes and then
//cache the size somewhere in the global variable because it will not
//change after the executable image is built.

size_t fn_size; //Will receive function size in bytes, or 0 if error
some_function_with_possibility_to_get_its_size_at_runtime(&fn_size);

その後:

#include <Windows.h>

//The function itself has to be defined for two types of a call:
// 1) when you call it just to get its size, and
// 2) for its normal operation
bool some_function_with_possibility_to_get_its_size_at_runtime(size_t* p_getSizeOnly = NULL)
{
    //This input parameter will define what we want to do:
    if(!p_getSizeOnly)
    {
        //Do this function's normal work
        //...

        return true;
    }
    else
    {
        //Get this function size
        //INFO: Works only in 64-bit builds on Windows!
        size_t nFnSz = 0;

        //One of the reasons why we have to do this at run-time is
        //so that we can get the address of a byte inside 
        //the function body... we'll get it as this thread context:
        CONTEXT context = {0};
        RtlCaptureContext(&context);

        DWORD64 ImgBase = 0;
        RUNTIME_FUNCTION* pRTFn = RtlLookupFunctionEntry(context.Rip, &ImgBase, NULL);
        if(pRTFn)
        {
            nFnSz = pRTFn->EndAddress - pRTFn->BeginAddress;
        }

        *p_getSizeOnly = nFnSz;
        return false;
    }
}
于 2018-02-10T07:07:00.113 に答える
2

これに対する本当の解決策は、コンパイラのドキュメントを掘り下げることです。私たちが使用する ARM コンパイラは、アセンブリ ダンプ (code.dis) を生成するように作成できます。このアセンブリ ダンプから、特定のマングルされた関数ラベルと次のマングルされた関数ラベルの間のオフセットを差し引くのはかなり簡単です。

ただし、Windows ターゲットでこれを行うためにどのツールが必要になるかはわかりません。この質問への回答に記載されているツールが、探しているツールのようです。

また、私 (埋め込み領域で作業している) は、コンパイル後の分析について話していると想定していたことにも注意してください。これらの中間ファイルは、ビルドの一部としてプログラムで調べることができる場合があります。

  • ターゲット関数は別のオブジェクトにあります
  • ビルドシステムに依存関係が教えられました
  • コンパイラがこれらのオブジェクト ファイルをビルドすることは確かです。

なぜこの情報を知りたいのか、私にはよくわからないことに注意してください。私は過去に、メモリ内の非常に特定の場所に特定のコードのチャンクを収めることができるようにするために、これを必要としていました。これがより一般的なデスクトップ OS ターゲットでどのような目的を持つのか、私は知りたいと認めざるを得ません。

于 2011-04-13T21:14:19.793 に答える
1

標準 C++ には、関数のサイズまたは長さを取得する機能はありません。
ここで私の答えを参照してください: 関数を割り当てられたメモリにロードして、そこから実行することは可能ですか?

一般に、関数のサイズを知ることは、組み込みシステムで実行可能コードを読み取り専用ソース (またはシリアル フラッシュなどの低速メモリ デバイス) から RAM にコピーするときに使用されます。デスクトップやその他のオペレーティング システムは、動的ライブラリや共有ライブラリなどの他の手法を使用して関数をメモリに読み込みます。

于 2011-04-13T23:24:56.153 に答える
1

「関数のサイズ」とはどういう意味ですか?

関数ポインターを意味する場合、32ビットシステムでは常にわずか4バイトです。

コードのサイズを意味する場合は、生成されたコードを逆アセンブルして、エントリ ポイントと最も近いret呼び出しを見つける必要があります。これを行う 1 つの方法は、関数の最初と最後で命令ポインター レジスタを読み取ることです。

関数の平均的なケースで呼び出される命令の数を把握したい場合は、プロファイラーを使用して、リタイアした命令の数を呼び出しの数で割ることができます。

于 2011-04-13T21:11:55.033 に答える
1

C++ では、関数サイズの概念はありません。前述のすべてに加えて、プリプロセッサ マクロも不確定なサイズになります。命令語の数を数えたい場合、コンパイルされるまで存在しないため、C++ ではそれを行うことはできません。

于 2011-04-13T21:12:01.837 に答える
1

msvc で作成された Windows プログラムで動作すると思います。分岐に関しては、'ret' は常に最後に来るようです (早期に戻る分岐があっても、最後に行くために jne を実行します)。ただし、x86 では可変長であるため、現在のオペコードの長さを把握するには、ある種の逆アセンブラー ライブラリが必要です。これを行わないと、誤検知が発生します。

これがキャッチされない場合があっても驚かないでしょう。

于 2011-04-13T21:20:36.763 に答える
1

関数を取得したアドレスに PAGE_EXECUTE_READWRITE を設定するだけです。次に、すべてのバイトを読み取ります。バイト「0xCC」を取得した場合、関数の最後が actual_reading_address - 1 であることを意味します。

于 2011-07-27T13:41:49.950 に答える
0

GCC を使用すると、それほど難しくありません。

void do_something(void) { 
   printf("%s!", "Hello your name is Cemetech"); 
   do_something_END: 
} 

... 

   printf("size of function do_something: %i", (int)(&&do_something_END - (int)do_something));
于 2012-01-08T16:50:32.807 に答える
0

それは古い質問ですが、それでも...

Windows x64 の場合、すべての関数には、関数のオフセットとサイズを含む関数テーブルがあります。https://docs.microsoft.com/en-us/windows/win32/debug/pe-format . この関数テーブルは、例外がスローされたときにアンワインドに使用されます。

とはいえ、これにはインライン化などの情報は含まれていません。また、人々がすでに指摘している他のすべての問題も含まれていません...

于 2019-10-12T15:10:47.457 に答える