9

私が説明しようとしているアイデアの実装を見たり聞いたりしたことがあるかどうかを確認するために書いています。

組み込みターゲット用の printf スタイルのデバッグ ライブラリの開発に興味があります。ターゲットは非常に離れており、私とターゲットの間の通信帯域幅の予算は非常に厳しいため、非常に効率的な形式でデバッグ メッセージを取得できるようにしたいと考えています。

多くの場合、デバッグ ステートメントは次のようになります。

myDebugLibraryPrintf("Inside loop, processing item %d out of %d.\n", i, numItems);

もちろん、これをテキストに展開すると、出力される文字列は「Inside loop, processing item 5 out of 10.\n」のようなもので、合計で 42 バイトほどになります。このステートメントによって出力されるデータの 90% 以上は静的でリテラルであり、コンパイル時に認識されます。もちろん、「5」と「10」だけはコンパイル時にわかりません。

私がやりたいのは、これら 2 つの整数 (42 ではなく 8 バイト) だけを送り返すことです。そのデータを受信すると、受信したデータを「再構成」し、ここで私の場所で完全なデバッグ メッセージを出力できる、ある種の「デコーダ リング」ができます。

コンパイル時にすべての myDebugLibraryPrintf() ステートメントに一意の ID を自動的に (ビルド プロセスの一部として) 与え、それらの一意の ID を元の書式文字列にマップするテーブルを生成することで、"デコーダ リング" を生成します。次に、ターゲットで myDebugLibraryPrintf() が呼び出されるたびに、一意の ID と、フォーマット文字列にある"%d""%f"などの varargs 値が送信されますが、フォーマット文字列自体は送信されません。(おそらく今のところ"%s"項目を許可しないことにします...) 私の場所に戻ると、テーブル内の一意の ID を検索し、適切なフォーマット文字列を見つけ、それを使用して元のデバッグ メッセージを再構築するプログラムができます。 .

誰かがおそらく以前にこのアイデアを持っていたような気がします.コミュニティの誰かがそのようなものを見たことがあるでしょう(またはこれを行うオープンソースライブラリを知っていることさえあります).

制約:

  • 明確にするために、ここでは C/C++ を扱っており、printf() の 100% 完全な代替実装 (非リテラル形式文字列、%s(文字列) 形式指定子、またはより高度な)には興味がありません。 varargs リストに幅や精度を入れるような書式指定子は、%*.*dサポートする必要はありません。

  • ビルド プロセスの一部として文字列テーブルを自動的に生成して、従来の printf() を追加するだけでデバッグを追加できるようにしたいと考えています。最小限の労力以上が必要な場合、私のプロジェクトの誰もそれを使用しません。

  • 文字列テーブルを生成するビルド プロセスの一部として余分な作業を行うことは、ほぼ想定されています。幸いなことに、私はこのライブラリを使用することに関心のあるすべてのソース コードを管理しており、ビルド プロセスには多くの柔軟性があります。

ありがとう!

4

4 に答える 4

3

私は、このアイデアが定義済みの文字列セットで実装されているのを見たことがあります。コードは次のようになりますdebug_print(INSIDE_LOOP_MSG_ID, i, n)。開発者が新しいメッセージを追加したい場合、新しいテキストを特定のヘッダー ファイルに入れ、新しい ID を与える必要がありました。

通常の外観の print ステートメントからその場で生成するというアイデアは、興味深い挑戦だと思います。私は既存の実装に出くわしていません。

1 つのアイデアは、コンパイル時に最初の文字列引数をハッシュ値に変換するマクロ/テンプレートです。したがって、開発者は を書きdebug_print("test %d",i)、これは にコンパイルされdebug_port_send(0x1d3s, i)ます。受信側で使用する文字列とハッシュを抽出する後処理スクリプトを作成するのは簡単です。(ハッシュ衝突を解決する最も簡単な方法は、エラー メッセージを表示し、ユーザーに文言を少し変更させることです)。

編集:
上記のリンクでコンパイル時のハッシュでこれを試しました。

#define QQuot_(x) #x
#define QQuote(x) QQuot_(x)
#define Debug_Print(s, v) (Send( CONSTHASH(QQuote(__LINE__)##s), *((long*)&(v))))

void Send(long hash, long value)
{
   printf("Sending %x %x\n", hash, value); //replace with COMMS
}


int main()
{
   int i = 1;
   float f= 3.14f;
   Debug_Print("This is a test %d", i);
   i++;
   Debug_Print("This is a test %d", i);
   Debug_Print("This was test %f", f);
}

もう少し賢くすれば、複数の引数をサポートできます。逆アセンブリを調べると、すべてのハッシュが実際にコンパイル時に計算されていることがわかります。出力は期待どおりで、同一の文字列からの衝突はありません。(このページでは、16 進数が 3.14 に対して正しいことを確認しています):

Sending 94b7555c 1
Sending 62fce13e 2
Sending 506e9a0c 4048f5c3

ここで必要なのは、Debug_Print から文字列を抽出し、ハッシュを計算し、受信者側のテーブルに入力するコードで実行できるテキスト処理スクリプトだけです。受信者は、呼び出しからハッシュ値を取得し、それに付随Sendする文字列を検索し、それを引数と共に通常の printf 呼び出しに渡します。

唯一の問題は、コンパイル時のハッシュにネストされたマクロが、リファクタリング プラグインを混乱させ、IDE の応答性を阻害していることです。アドインを無効にすると、その問題が解消されました。

于 2011-08-02T18:36:15.120 に答える
1

ARM プラットフォームで同様のことを実現するものを見てきました。「組み込みトレース マクロセル」と呼ばれていると思います。一連のマクロTRACE_POWER_SYSTEM_VOLTAGE_REGULATOR_TRIGGER(inputX);は、ETM レジスタへの 2 つのレジスタ書き込みなどのステートメントを変換します。ただし、これは 16 ビット、32 ビット、および 64 ビットの整数のみを引数として受け入れることに注意してください。

ARM ツールを使用して、これらの (タイムスタンプ付き) バッファーを抽出できます。次に、コンパイル済みのちょっとしたトリックを適用して、最初の (インデックス) レジスタ書き込みを次のような出力ファイルに変換します。

timestamp  | POWER SYSTEM    |    VOLTAGE REGULATOR TRIGGER    | 0x2380FF23

引数のデータ型を決定するためにコードが調べられているので、気にする必要はありません。また、「リアルタイム」タイムスタンプ (電源投入後のミリ秒ではなく)、およびトレース ステートメントのファイル番号と行番号で注釈を付けることができます。

ARM は、この循環バッファを内部に (そして非常に迅速に) 格納するように設定されているため、本番環境で使用できます。ただし、ハードウェアのサポートがなくても... これのいくつかの側面は簡単に再現できます。

トレースを分析するときは、デバイスで実行されているコードの特定のバージョンに一致する「デコード」ファイルのみを使用することが非常に重要であることに注意してください。

于 2011-08-10T22:50:54.450 に答える
0

国際化を目的として文字列リテラルを抽出するための多くのツールを思い出しているようです。GNU文字列は、実行可能ファイルから直接文字列を抽出できます。これは、タスクの一部に役立つはずです。

于 2011-08-10T23:08:17.877 に答える
0

私は同じ問題を抱えていましたが、画像サイズを小さくしたかったのです(小さな埋め込みフラッシュのため)。私の解決策は、ファイル名と行 (14 ~ 20 バイト) を送信し、サーバー側にソース パーサーを配置して、実際のテキストのマップを生成することです。このようにして、実際のコードには「フォーマット」文字列は含まれませんが、各ファイルに対して単一の「ファイル名」文字列が含まれます。さらに、(コード内のすべての文字列を置き換えるのとは異なり) ファイル名を列挙型に簡単に置き換えて、COMM スループットを削減できます。

サンプルの疑似コードがアイデアを明確にするのに役立つことを願っています:

/* target code */
#define PRINT(format,...) send(__FILE__,__LINE__,__VA_ARGS__)
...

/* host code (c++) */
void PrintComm(istream& in)
{
    string fileName;
    int    line,nParams;
    int*   params;
    in>>fileName>>line>>nParams;
    if (nParams>0)
    {
        params = new int[nParams];
        for (int i=0; i<nParams; ++i)
            in>>params[i];
    }
    const char* format = FindFormat(fileName,line);
    ...
    delete[] params;
}
于 2013-07-28T11:32:07.870 に答える