3

GNU libc のバックトレースインサーキット エミュレーター/デバッガーは、コードを新しいプラットフォームに移植する場合、特にターゲットがZ80などのマイクロCコンパイラーである場合、常に利用できるとは限りません。(通常、プログラムのバグはどこかで「ハングアップ」するか、ガジェットをクラッシュさせます。)

printf を手動で挿入する古典的な「オオカミフェンシング」方法に代わるものはありますか? C プログラムへのトレースとバックトレースを含むプログラムの開発中に、コーダーが実行できる (C 拡張機能を使用しない) シンプルで移植可能なものはありますか?

ところで:関連するstackoverflowに関する他のいくつかの質問がありますが、これらは両方ともGNU GLIBCのバックトレースを使用し、バックトレースは多くの場合コンパイラ/実装固有です:

4

3 に答える 3

4

これが私の答えの核の核です:いくつかのコードを書いてください。

私の答えの核心は次のとおりです。コンパイラが常にローカルをスタックに割り当てる場合、...

関数の名前を記録するすべての関数エントリでスタックにblobを追加し、いくつかのマジックナンバーを投入して、スタックのスマッシュをキャッチします。

typedef struct stack_debug_blob_ {
    int magic1;
    const char * function_name;
    int magic2;
    struct stack_debug_blob_ * called_by;
    int magic3;
} stack_debug_blob;

stack_debug_blob * top_of_stack_debug_blobs = 0;

関数の名前をとってマクロENTER(f)を作成します。マクロは、{を開いた後のすべての関数のコードの最初の行にある必要があります。(const)char *関数名へのポインター、スタック上の前の構造体へのポインター、および正気度をチェックするためのいくつかのマジックナンバーを含む構造体を追加します。この新しい構造体でblobスタックポインタの上部をポイントします。

#define ENTER(f)                                                \
stack_debug_blob new_stack_debug_blob = {                       \
    MAGIC1, (f), MAGIC2, top_of_stack_debug_blobs, MAGIC3};     \
stack_debug_blob * evil_hack = (top_of_stack_debug_blobs = (&new_stack_debug_blob))

可能な限り移植性を保つために、ENTERでできることは、変数を宣言して初期化することだけです。したがって、evil_hackは、変数を初期化するだけでなく、少し余分な計算を行います。

ポインタとマジックナンバーをチェックするblobのリストをたどる関数を作成します。混乱していることがわかった場合は、エラーを通知する必要があります(stderrに出力する、while(1){/ * nada * /}でCPUをロックする、デバッガーに入る...ハードウェアによって異なります)。

ブロブのスタックをチェックするマクロEXIT()を作成し、リンクリストから最上位のリンクを解除します。すべての関数の出口に配置する必要があります。

#define EXIT() do {                                            \
    check_debug_blobs();                                       \
    top_of_stack_debug_blobs = new_stack_debug_blob.called_by; \
    new_stack_debug_blob.magic1 -= 1; /* paranoia */           \
} while (0)

おそらく、すべてのreturnをRETURNマクロ呼び出しに置き換える必要もあります。RETURNマクロはEXITと同じですが、} while(0)の前にreturnがあります。

関数名を出力するblobのリストをたどる関数を作成し、おそらくスタックトレースやバックトレースのような名前を付けます。

ENTER(f)、EXIT()、RETURN(x)を呼び出して、Cコードをインストルメント化するプログラムを作成します。

あなたがそれを楽しむことができるようにいくつかの詳細を省略しました...

uclibcのバックトレースで利用可能な移植も参照してください。

于 2010-07-19T05:33:43.083 に答える
2

@jsl4tv の提案と同じ基本的な考え方を使用する実装がRosettaCode.orgにあります。

例として、「 hang」が組み込まれた次の古典的な C コードがあるとします。

#include <stdio.h>
#include <stdlib.h>

void inner(int k)
{
   for(;;){} /* hang */
}

void middle(int x, int y)
{
  inner(x*y);
}

void outer(int a, int b, int c)
{
  middle(a+b, b+c);
}

int main()
{
  outer(2,3,5);
  return(EXIT_SUCCESS);
}

#STACK_TRACE_ON を定義し、#include "stack_trace.h" をRosettaCode.orgから取得してから、必要な場所に BEGIN(f)/END を挿入します。

#include <stdio.h>
#include <stdlib.h>

#define STACK_TRACE_ON /* compile in these "stack_trace" routines */
#include "stack_trace.h"

void inner(int k)
BEGIN(inner)
   print_indent(); printf("*** Now dump the stack ***\n");
   print_stack_trace();
   for(;;){} /* hang */
END

void middle(int x, int y)
BEGIN(middle)
  inner(x*y);
END

void outer(int a, int b, int c)
BEGIN(outer)
  middle(a+b, b+c);
END

int main()
BEGIN(main)
  stack_trace.on = TRUE; /* turn on runtime tracing */
  outer(2,3,5);
  stack_trace.on = FALSE;
  RETURN(EXIT_SUCCESS);
END

プロデュース:

stack_trace_test.c:19: BEGIN outer[0x80487b4], stack(depth:1, size:60)
stack_trace_test.c:14:   BEGIN middle[0x8048749], stack(depth:2, size:108)
stack_trace_test.c:8:     BEGIN inner[0x80486d8], stack(depth:3, size:156)
stack_trace_test.c:8:       *** Now dump the stack ***
stack_trace_test.c:8:   inner[0x80486d8]        --- stack(depth:4, size:156) ---
stack_trace_test.c:14:  middle[0x8048749]       --- stack(depth:3, size:108) ---
stack_trace_test.c:19:  outer[0x80487b4]        --- stack(depth:2, size:60) ---
stack_trace_test.c:24:  main[0x804882a] --- stack(depth:1, size:0) ---
stack_trace_test.c:8:       --- (depth 4) ---

この BEGIN ~ END メソッドの洗練された [オープン ソース] バージョンは完璧です。(例外処理のための「FINALLY」句がある場合は特に)。

ヒント/URLは大歓迎です。

于 2010-07-19T23:42:48.487 に答える
0

Symbian では、コード アドレスのようなものを探してレジスタとスタックを調べるために作成されたスクリプトがいくつかありました。

これは移植性がありませんが、コードの装飾にも依存しません。これは、バイト数が重要なプラットフォームでは必要なトレードオフでした...そして Z80 ほど制限されていませんでした! ただし、フレームポインターなどを使用せずにコンパイルするには十分に制限されています。

フレーム ポインターを使用せずにスタックからバックトレースを計算するには、スタックを下に移動するのではなく、上に移動する必要があります。

于 2010-07-17T23:51:27.737 に答える