2

[これは具体的には PC/Visual C++ 用です (ただし、他の回答は非常にわかりやすいでしょう :))]

ポインターがスタック内のオブジェクトから来ているかどうかをどのように判断できますか? 例えば:

int g_n = 0;

void F()
{
    int *pA = &s_n;
    ASSERT_IS_POINTER_ON_STACK(pA);
    int i = 0;
    int *pB = &i;
    ASSERT_IS_POINTER_ON_STACK(pB);
}

したがって、2 番目のアサート(pB)のみがトリップするはずです。インライン アセンブリを使用して、それが SS セグメント レジスタ内にあるかどうかを判断することを考えています。このための組み込み関数があるかどうか、またはこれを行う簡単な方法があるかどうかは誰にもわかりませんか?

ありがとう!RC

4

9 に答える 9

2

何をするにしても、それは非常にプラットフォーム固有であり、移植性がありません。あなたがそれで大丈夫だと仮定して、読み進めてください。ポインターがスタック内のどこかを指している場合、そのポインターは現在のスタック ポインター%espとスタックの一番上の間に配置されます。

スタックのトップを取得する 1 つの方法は、 の先頭でそれを読み込むことですmain()。ただし、これにはいくつかの問題があります。 - C ランタイムが入る前にスタックを初期化するため、スタックのトップは実際にはわずかに高くなりますmain() - C++ では、グローバル オブジェクトのコンストラクタが前に呼び出されmain() ます - アプリケーションがマルチスレッド化されている場合、各スレッドには独自の個別のスタック。その場合、スタックのベースを記述するスレッドローカル変数が必要になります

現在のスタック ポインターを取得する 1 つの方法は、インライン アセンブリを使用することです。

uint32_t GetESP(void)
{
    uint32_t ret;
    asm
    {
        mov esp, ret
    }
    return ret;
}

インライン化と最適化に注意してください! オプティマイザーは、このコードを壊す可能性があります。

于 2008-12-03T19:36:46.150 に答える
2

質問を 2 番目にします - なぜ知る必要があるのですか? これから良いことはありません。

コンパイラがポインタ比較で合理的なことを行い、スタックが成長する場合、この方法は機能する可能性があると思います。

static void * markerTop = NULL;

int main()
{
    char topOfStack;
    markerTop = &topOfStack;
    ...
}

bool IsOnStack(void * p)
{
    char bottomOfStack;
    void * markerBottom = &bottomOfStack;
    return (p > markerBottom) && (p < markerTop);
}
于 2008-12-03T19:37:59.207 に答える
1

Visual C とアサートを指定したので、デバッグ ビルドを使用できると仮定します。その場合、この特定のコンパイラがメモリ チェックのために配置するフェンスポストを利用できます。

#define IS_POINTER_TO_STACK(vp)   (*((int*)(vp)-1)==0xCCCCCCCC)

デバッグビルドでは、これらすべてのケースで正しく機能しました:

#define ASSERT(v)  printf("assert: %d\n", v);  //so it doesn't really quit
int g_n = 0;
void test_indirectly(void* vp) {
    ASSERT(IS_POINTER_TO_STACK(vp));
}
void F() {
    int *pA = &g_n;
    ASSERT(IS_POINTER_TO_STACK(pA));         //0

    int i = 0;
    int j = 0;
    int *pB = &i;
    ASSERT(IS_POINTER_TO_STACK(pB));         //1
    ASSERT(IS_POINTER_TO_STACK(&j));         //1

    int *pC = (int*)malloc(sizeof(int));
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    free(pC);
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    pC = new int;
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    delete pC;

    char* s = "HelloSO";
    char w[6];
    ASSERT(IS_POINTER_TO_STACK("CONSTANT")); //0
    ASSERT(IS_POINTER_TO_STACK(s));          //0
    ASSERT(IS_POINTER_TO_STACK(&w[0]));      //1
    test_indirectly(&s);                     //1

    int* pD; //uninit
    ASSERT(IS_POINTER_TO_STACK(pD));    //runtime error check

}

(ただし、メモリが初期化されていないために実行時エラーが発生した最後のエラーを除きますが、それでもポインタを検証する目的には役立ちます。)

これはデバッグ ビルドでのみ機能します。リリース ビルドでは、すべてのビルドで false が報告されます。

于 2008-12-06T00:09:11.887 に答える
1

技術的に言えば、ポータブル C ではわかりません。引数のスタックは、すべてではありませんが多くのコンパイラで受け入れられるハードウェアの詳細です。一部のコンパイラは、可能な場合 (つまり、fastcall) に引数にレジスタを使用します。

特に Windows NT で作業している場合は、NtCurrentTeb() の呼び出しからスレッド実行ブロックを取得する必要があります。 Joe Duffy のブログにはこれに関する情報があり、そこからスタック範囲を取得できます。範囲内のポインターを確認すると、準備完了です。

于 2008-12-03T19:37:06.883 に答える
0

Visual C++ や、最新の Windows (または古い 32 ビット OS/2) プラットフォームで実行されるほとんどのものについては、信じられません。これらのプラットフォームでは、スタックが動的に成長します。つまり、スタックの新しいページが割り当てられるのは、プログラムは、スタックの現在割り当てられているチャンクの一番上にある、特別に細工されたメモリの 4 KB ブロック (とにかく 32 ビット Windows の場合) である、いわゆるガード ページにアクセスしようとします。オペレーティング システムは、プログラムがこのガード ページにアクセスしようとしたときに生成される例外をインターセプトし、(1) その代わりに、現在割り当てられているスタックの一番上に通常の有効なスタックの新しいページをマップし、(2) 別のページを作成します。新しい上部のすぐ上にページをガードして、後で必要に応じてスタックを拡張できるようにします。OS は、スタックが制限に達するまでこれを行います。通常、この制限は非常に高く設定されています。

また、プログラムがスタックの未割り当て部分に属しているがガード ページの上にあるアドレスにアクセスしようとすると、OS がこれを解釈する方法がないため、プログラムがクラッシュします。理論的には、ポインタがタスクのスタック セグメントに属している場合でも、プログラムはアドレス空間外のメモリにアクセスしようとします。

ただし、アドレスがスタックの割り当てられた部分 (つまり、「スタック上のオブジェクト」) に属しているかどうかを確認する方法が必要な場合は、Joe Duffy のブログへの参照が役立ちます。そこに記載されている StackLimit を使用しないでください。このスレッドで既に説明されている他のメソッドを使用してスタックの現在のトップを取得するため、スタック全体ではなく、おそらく部分的に割り当てられていないスタックの割り当てられた部分を操作します。

于 2008-12-03T21:14:00.993 に答える
0

私は、スタックサイズを調整し続ける環境でこれを確実に行うのはかなり難しいと言う人々に同意します。

しかし、「なぜ?」のように。people - おもしろいことに、今日は小さな組み込みプラットフォームでこれをやりたかったのです。オブジェクトへのポインターを受け取り、関数が戻った後もしばらくの間そのポインターを保持する関数があります (ポインターが指すデータを処理しているため)。

関数の呼び出し元に自動変数のアドレスを渡させたくないのは、データがまだ処理されている間にデータが踏みにじられたくないからです。呼び出し元は、静的データまたは const データのアドレスを渡す必要があり、素敵な 'ASSERT_IS_ON_STACK()' マクロが役立つかもしれません。

ポータブル?全くない。恐ろしいキャンディーマシンインターフェース?絶対。

これが小さな組み込みシステムの性質であり、適切なアサーションが役立ちます。

于 2008-12-03T21:29:24.113 に答える
0

「なぜ」という質問を無視します...トップスタックフレームを制御できる場合の簡単な方法の1つは、グローバル変数をスタックオブジェクトのアドレスに設定し、ターゲットポインターがこのアドレスと、スタック上に作成する変数のアドレスの間。

void* TopOfStack; // someone must populate this in the first stack frame

bool IsOnTheStack(void* p)
{
  int x;

  return (size_t) p < (size_t) TopOfTheStack &&
         (size_t) p > (size_t) &x;
}

もちろん、TopOfTheStack スレッドをローカルにしない限り、これは複数のスレッドでは機能しません。

コンパイラによるスタックの最適化も問題を引き起こす可能性があります。

于 2008-12-03T19:37:19.863 に答える
-1

はい、これは移植性が非常に低いことは承知していますが、これは内部アプリが他のハードウェアの機能を模倣するためのものです。スレッド実行ブロックが適しているようです。

于 2008-12-03T20:02:41.187 に答える
-1

わかりました、「理由」について:

一部のプロセッサのメモリ コントローラは、DMA を実行したり、スタック セグメントとの間でメモリをマップしたりできません。したがって、クロス プラットフォームの世界では、そこからデータを送信していないことを確認するには、クロス プラットフォームのアサートが非常に役立ちます。

于 2008-12-04T01:42:30.390 に答える