このGNUC/ C ++コード(現在のスタックポインター値を取得するために使用するインラインアセンブリのスタイルのためにGNU)は、スタックポインターの現在の値(16進数)を出力します。
static __inline__ void printESP(void) {
static const hexdigit[] = "0123456789ABCDEF\n";
uintptr_t SP;
__asm__ __volatile__("mov %%esp, %0\n\t" : "=r"(SP));
write(1, &hexdigit[SP >> 28], 1);
write(1, &hexdigit[0xf & SP >> 24], 1);
write(1, &hexdigit[0xf & SP >> 20], 1);
write(1, &hexdigit[0xf & SP >> 16], 1);
write(1, &hexdigit[0xf & SP >> 12], 1);
write(1, &hexdigit[0xf & SP >> 8], 1);
write(1, &hexdigit[0xf & SP >> 4], 1);
write(1, &hexdigit[0xf & SP], 1);
write(1, &hexdigit[16], 1);
}
のためstatic __inline__
、ほとんどの状況でgcc(実際には、関数ポインターを使用する場合を除く)は実際にはこの関数を作成しませんが、呼び出しサイトに直接コードを埋め込みます。
次のような、より手の込んだ作品を作成することができます。
static __inline__ printESP(void)
{
static const char hexdigit[] = "0123456789ABCDEF\n";
uintptr_t SP;
char buf[9];
__asm__ __volatile__("mov %%esp, %0\n\t" : "=r"(SP));
buf[0] = hexdigit[SP >> 28];
buf[1] = hexdigit[0xf & SP >> 24];
buf[2] = hexdigit[0xf & SP >> 20];
buf[3] = hexdigit[0xf & SP >> 16];
buf[4] = hexdigit[0xf & SP >> 12];
buf[5] = hexdigit[0xf & SP >> 8];
buf[6] = hexdigit[0xf & SP >> 4];
buf[7] = hexdigit[0xf & SP];
buf[8] = hexdigit[16];
write(1, buf, 9);
}
これにより、スタック上に一時バッファが構築され、write()
1回だけ呼び出されます。
2番目の部分のコードの欠点は、実際にスタックスペースを消費することです。つまり、たとえば、スタックをオーバーフローしたり、スタックポインターを破損した関数から呼び出すと、クラッシュします。アドレスを文字ごとに出力する些細な最初のものは、まだ機能する可能性があります。
最初のアセンブリコードに関する注意:これはグローバルバッファを使用しているため、スレッドセーフではありません。複数のスレッドから同時に呼び出すと、変数が破損し、ガベージが発生します。いいえ、私はそれを「修正」するのを手伝うつもりはありません。
アプローチに関する一般的な注意:個人的には、それがデバッガーの目的だと思います。この特定のタイプのランタイムインストルメンテーション(この方法で-値を直接出力することによる)は、かなり有用ではありません。ランタイムインストルメンテーションの場合は、メモリ内のトレースバッファにログを記録します。ここでは、(部分的な)スタックトレース、スレッドID、タイムスタンプ、その他のレジスタ値などの拡張情報と、スタックポインタだけを記録できます。インメモリ(リング)バッファの使用は、ファイル記述子1の特別な意味に依存せず、スタックポインタ値のみよりも何が起こっているかをより説明する「コンテキスト」情報を許可します。
また、値が100%正確である必要がない場合は、代わりにgcc組み込みを使用できます。uintptr_t SP = (uintptr_t)__builtin_frame_address(0)
インラインアセンブリはまったく必要ありません。違いは、関数へのエントリ時にスタックポインターを提供し、callsiteの現在のスタックポインターを提供しないことです。つまり、ローカル変数やその他の「一時的」によるスタックの使用を考慮しません。