[デバッグ] プログラム呼び出しスタックの記録プロファイル - バックトレースとバックトレース|ボトルネックを分析|バグがどこにあるかを分析する

目次

序章

バックトレース

プロファイルバックトレース

違い

プロファイルバックトレース

バックトレース


序章

バックトレース

バックトレースは、関数呼び出しスタックを生成するツールです。プログラムがクラッシュしたり例外が発生したりしたときに、バックトレースを使用して関数呼び出しスタック情報を取得できます。この情報は、プログラムの実行プロセスを理解し、問題の箇所を特定するのに役立ちます。

バックトレースを使用するには、通常、次の手順が必要です。

1. バックトレースを使用する必要があるコードに `#include <execinfo.h>` ヘッダー ファイルを導入します。

2. backtrace 関数を使用して関数呼び出しスタック情報を取得します: `void backtrace(void **buffer, int size)`

- **buffer**: 関数呼び出しスタック情報を格納するために使用されるバッファ (void* 型の配列)。
- **size**: バッファに格納できるポインタの数を示します。

3. backtrace_symbols 関数を使用して、関数ポインタを関数名に変換します: `char **backtrace_symbols(void *const *buffer, int size)`

- **buffer**: backtrace 関数によって取得された関数ポインターの配列。
- **size**: backtrace 関数によって取得される関数ポインタ配列の長さ。

このように、backtrace 関数を使用して関数呼び出しスタック情報を取得し、backtrace_symbols 関数を使用してポインタを関数名に変換して関数呼び出しスタックを取得できます。関数呼び出しスタック情報を取得すると同時に、関数呼び出しスタック情報をログに出力することもできるため、プログラムがクラッシュしたり例外が発生した箇所を特定するのに便利です。

プロファイルバックトレース

`profile-backtrace` は Linux システムのパフォーマンス分析用のツールで、開発者がプロ​​グラム内でより多くの CPU 時間を消費する関数を見つけ出し、プログラムのパフォーマンスの問題を特定するのに役立ちます。

「profile-backtrace」の動作原理は、プログラムをサンプリングすることにより、サンプリング間隔中にプログラム スタック情報が取得され、関数呼び出しスタックが生成され、これらの呼び出しスタックがカウントされて分析されます。最後に、呼び出し回数や呼び出し時間などの統計情報が出力されるので、プログラム内で時間のかかる関数を見つけ出し、最適化に役立てることができます。

パフォーマンス分析に「profile-backtrace」を使用する場合、通常は次の手順が必要です。

1. コンパイル時に `-g` オプションを追加して、関数名に簡単にアクセスできるようにデバッグ情報を生成します。

2. 性能テストプログラムを作成し、性能解析が必要な箇所に「マークポイント」を挿入します。

3. 性能テストプログラムをコンパイルします。

4. パフォーマンス分析のために「profile-backtrace」を実行します。たとえば、「perf Record -ecycles ./test」です。

5. 「パフォーマンス レポート」などのサンプリング データを処理および分析します。

「profile-backtrace」を使用すると、プログラムのパフォーマンスをシステムレベルで分析し、プログラム内のパフォーマンスのボトルネックの機能を迅速に見つけ出し、分析結果をパフォーマンスの最適化に使用します。

違い

プロファイルバックトレースとバックトレースの違い

「profile-backtrace」と「backtrace」はどちらも関数呼び出しスタック情報を取得するためのツールですが、設計目的と使用シナリオは異なります。

1. 設計目的: 「profile-backtrace」はパフォーマンス分析用のツールであり、開発者がプロ​​グラム内でより多くの CPU 時間を消費する関数を見つけてパフォーマンスの問題を特定するのに役立ちます。「backtrace」はデバッグ用のツールです。これは、開発者がプロ​​グラムのクラッシュや例外の発生時に関数呼び出しスタック情報を取得し、問題を特定するのに役立ちます。

2. 使用シナリオ: 「profile-backtrace」は通常、プログラムの実行パフォーマンスを最適化するために使用され、パフォーマンスのオーバーヘッドが低いプログラムには効果的ではない可能性があります。「backtrace」は通常、プログラムのデバッグに使用され、次のような開発者にとって非常に役立ちます。スタックトレース情報を取得します。

3. 実装方法: `profile-backtrace` はプログラムをサンプリングし、サンプリング間隔内でプログラム スタック情報を取得し、関数呼び出しスタックを生成し、これらの呼び出しスタックの統計と分析を実行します; `backtrace` はプログラムを通じて実行されますクラッシュまたは例外が発生した場合は、関数呼び出しスタック情報を取得して問題を特定します。

したがって、「profile-backtrace」と「backtrace」は 2 つの異なるツールであり、どちらも関数呼び出しスタック情報を取得しますが、適用可能なシナリオと目的が異なります。

「profile-backtrace」と「backtrace」は使用方法が若干異なりますので、以下に両ツールの使用方法を紹介します。

1. `profile-backtrace`の使い方

(1) デバッグ情報を生成するためにコンパイル時に `-g` オプションを追加します。

``
gcc -o プログラム -g プログラム.c
``

(2) パフォーマンス分析のためにコマンド ライン ツール「perf」を実行します。

```
perf レコード -g -a -e サイクル ./program
```

(3) サンプリングされたデータを処理して表示します。

```
パフォーマンスレポート
''`

このうち、「-g」オプションは関数呼び出しスタック情報の生成に使用され、「-a」オプションはすべてのプロセスまたはスレッドの記録に使用され、「-ecycles」オプションは収集されるイベントのタイプを指定するために使用されます。ここにCPUサイクルカウンターがあります。

2. バックトレースの使い方

(1) ヘッダーファイル`<execinfo.h>`をインポートします。 

```
#include <execinfo.h>
```

(2) 関数呼び出しスタック情報を取得する関数 `backtrace()` を追加します。

```
void *buffer[10];
int nptrs = backtrace(buffer, 10);
「」

(3) `backtrace_symbols()` 関数を使用して、関数ポインタを関数名に変換します。

```
char **文字列;
文字列 = backtrace_symbols(buffer, nptrs);
「」

(4) 関数呼び出しスタック情報を出力します。

```
for (int i = 0; i < nptrs; i++)
    printf("%s\n", strings[i]);
「」

上記の手順を実行すると、関数呼び出しスタック情報が出力され、プログラムのクラッシュや例外の原因を特定するのに役立ちます。

`backtrace`を使用する場合、コンパイル時にデバッグ情報を有効にする必要があるため、リリースプログラムの場合はデバッグに`backtrace`を使用できないことに注意してください。

プロファイルバックトレース

以下は、`profile-backtrace` の簡単な使用例です。これは、パフォーマンス分析に `profile-backtrace` を使用し、プログラム内でより多くの CPU 時間を消費する関数を見つける方法を示しています。

次のテスト プログラム test.c を想定します。

```c
#include <stdio.h>

int func1()
{     int a = 1、b = 2、c = 3、d = 4、e = 5;     a + b + c + d + eを返します。}


int func2()
{     int i, sum = 0;     for (i = 0; i < 10000; i++)         sum += i;     合計を返します。}




int main()
{     int i;     for (i = 0; i < 100000; i++)         func1();     for (i = 0; i < 100000; i++)         func2();     0を返します。} ```







プログラムには、いくつかの値の合計を計算する 2 つの関数 `func1()` と `func2()` が含まれており、これら 2 つの関数は `main()` 関数内で 100,000 回呼び出されます。

次に、「profile-backtrace」を使用してプログラムのプロファイリングを行います。

1. プログラムをコンパイルしてリンクし、「-g」オプションを追加します。

```
gcc -g -o テスト test.c
```

2. パフォーマンス分析のために「profile-backtrace」を実行します。

```
perf レコード -g -a -e サイクル ./test
```

このうち、「-g」オプションは関数呼び出しスタック情報の生成に使用され、「-a」オプションはすべてのプロセスまたはスレッドの記録に使用され、「-ecycles」オプションは収集されるイベントのタイプを指定するために使用されます。ここにCPUサイクルカウンターがあります。

3. サンプリング データを処理および分析します。

```
パフォーマンスレポート
''`

上記のコマンドを実行すると、「パフォーマンス レポート」ツールが開き、分析結果が生成され、プログラム内の各関数の実行回数や消費された CPU 時間などの情報が一覧表示されます。この例では、`func2()` がより多くの回数実行され、より多くの CPU 時間を消費し、プログラム内で主に時間を消費する関数です。

このように、「profile-backtrace」を使用すると、プログラム内のパフォーマンスの問題を迅速に特定し、最適化することができます。

バックトレース

以下は、バックトレースを使用して関数呼び出しスタック情報を取得する方法を示す簡単なバックトレースの使用例です。

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

#define サイズ 10

void func1(void);
void func2(void);
void func3(void);

int main()
{     func1();     0を返します。}


void func1()
{     void *buffer[SIZE];     int nptrs = backtrace(buffer, SIZE); //関数呼び出しスタック情報を取得     char **strings;


    printf("func1() 関数呼び出しスタック:\n");

    strings = backtrace_symbols(buffer, nptrs); //関数ポインタを関数名に変換します

    if (strings == NULL)
    {         perror("backtrace_symbols");         終了(EXIT_FAILURE);     }


    for (int i = 0; i < nptrs; ++i)
        printf("%s\n", strings[i]);

    無料(文字列);
    func2();
}

void func2()
{     func3(); }

void func3()
{     void *buffer[SIZE];     int nptrs = バックトレース(バッファ, SIZE);     char **文字列;


    printf("func3() 関数呼び出しスタック:\n");

    文字列 = backtrace_symbols(buffer, nptrs);

    if (strings == NULL)
    {         perror("backtrace_symbols");         終了(EXIT_FAILURE);     }


    for (int i = 0; i < nptrs; ++i)
        printf("%s\n", strings[i]);

    無料(文字列);
}


プログラムでは 3 つの関数 func1()、func2()、func3() が定義されており、func1() は main() 関数で呼び出されます。各関数内でbacktrace()関数が呼び出され、関数呼び出しスタック情報が取得されて出力されます。

上記のプログラムを実行すると、出力は次のようになります。

func1() 関数呼び出しスタック:
./a.out(func3+0x15) [0x4006df]
./a.out(func2+0x9) [0x4006f6]
./a.out(func1+0x1d) [0x40070d]
./a. out(main+0xb) [0x40072c]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1) [0x7f37cb87b291]
./a.out() [0x4005da]
func3() 関数呼び出しスタック:
./ a.out(func3+0x25) [0x4006e9]
./a.out(func2+0x9) [0x4006f6]
./a.out(func1+0x23) [0x400713]
./a.out(main+0xb) [0x40072c]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1) [0x7f37cb87b291]
./a.out() [0x4005da]

上記の出力はプログラム内の関数呼び出しスタック情報であり、プログラムのクラッシュや例外の原因を迅速に特定するのに役立ちます。バックトレースを使用する場合、コンパイル時にデバッグ情報を有効にする必要があり、コンパイラによってそれを有効にする方法が若干異なることに注意してください。


 

おすすめ

転載: blog.csdn.net/bandaoyu/article/details/131238385