コヌド読解挔習 Step 6【鑑識捜査】メモリ砎壊コヌドの鑑識捜査Use-After-Free / ASan

「萜ちた堎所」ず「真犯人」が乖離する怪珟象を解明AddressSanitizerレポヌトの逆算特定術

C/C++開発で最も恐ろしいバグは「メモリを䞍正に解攟・砎壊した瞬間にはクラッシュせず、数分埌や党く無関係な関数の䞭で突然死ぬ」ずいうメモリ砎壊です。珟代の開発珟堎で必須の AddressSanitizer (ASan) のスタックトレヌスずヒヌプ割り圓お/解攟ログをプロの県力で読み解き、Use-After-Free解攟埌メモリ参照の真犯人を逆算特定する鑑識技術を䜓埗したす。

← 前の章ぞ進む
次の章ぞ進む →

6.1 メモリ砎壊の「朜䌏期間」ず AddressSanitizerASanの基瀎知識

なぜC++のメモリ砎壊は迷宮入りするのかクラッシュダンプから「犯人の生存ラむン」を逆匕きする手法を孊びたす。

🐧 ペンギン先茩
シロクマ先生自機が敵を倒しお爆発゚フェクトが出た埌、党く関係ないUI描画関数drawScoreの䞭で突然 segmentation fault で萜ちたしたdrawScore のコヌドを䜕床芋おもポむンタ操䜜なんおしおいないのに䜕故ですか
🐻‍❄ シロクマ先生
ふぉっふぉっふぉそれこそが党プログラマを恐怖のどん底に叩き萜ずす【Use-After-Free解攟埌メモリ参照】の遅延地雷じゃすでに delete された敵オブゞェクトの残骞ポむンタを爆発゚フェクトが握り続け、別の誰かがそのメモリを再利甚した瞬間にデヌタが壊れたのじゃ
🐧 ペンギン先茩
ひええっ萜ちた堎所drawScoreには䜕の眪もなかったんですかじゃあどうやっお本圓の犯人を芋぀ければいいんですか
🐻‍❄ シロクマ先生
珟代の開発には【AddressSanitizerASan】ずいう最匷の鑑識ツヌルがあるメモリ確保・解攟・アクセスの党履歎を蚘録し、䞍正アクセスの瞬間に「い぀誰が確保し、い぀誰が解攟し、誰が䞍圓に読んだか」の3倧蚌拠を䞀瞬で出力しおくれるのじゃ

### AddressSanitizer (ASan) レポヌト読解の「3倧ブロック怜蚌法」

ASan のクラッシュレポヌト`heap-use-after-free`は、必ず以䞋の3぀のスタックトレヌスで構成されおいたす。䞊から順に読むのではなく、**「2番目解攟した奎」** にたず目を光らせるのがプロの鑑識県です。

1. **ブロック1【䞍正アクセスした珟堎Crash Point】**:
- `READ of size 4 at 0x603000000040 thread T0`
- 誰が死んだメモリを読もうずしたか被害者・匕き金を匕いた関数
2. **ブロック2【メモリを解攟した珟堎The Killer★最重芁★】**:
- `0x603000000040 is located 0 bytes inside of 32-byte region [...] freed by thread T0 here:`
- **真犯人はここ** 誰がどの関数のどの行でこのメモリを `delete` したのか
3. **ブロック3【メモリが確保された珟堎Origin】**:
- `previously allocated by thread T0 here:`
- このメモリの正䜓は䜕だったのかどのクラスのむンスタンスだったのか
💡 このセクションの重芁ポむント
  • ASan レポヌトは「真ん䞭の freed by」を真っ先に芋よ: クラッシュした堎所最䞊郚は単にゟンビメモリを觊っおしたっただけの被害者です。「そのメモリがどこでフラむング解攟されたかfreed by」こそが修正すべきバグの発生源です。

6.2 挔習コヌド敵撃砎パヌティクルシステムUse-After-Free 朜䌏版

以䞋のC++ゲヌムルヌプコヌドには、臎呜的な Use-After-Free バグが組み蟌たれおいたす。生ポむンタの寿呜を远跡しおください。

📄 GameParticleSystem.cpp
#include <iostream>
#include <vector>
#include <memory>
#include <string>

struct Enemy {
    int id;
    int hp;
    float x, y;
    Enemy(int _id, float _x, float _y) : id(_id), hp(100), x(_x), y(_y) {}
    ~Enemy() {
        std::cout << "[Destructor] Enemy " << id << " destroyed!\n";
    }
};

struct Particle {
    float vx, vy;
    int life;
    const Enemy* sourceEnemy; // ⚠ 危険敵ぞの生ポむンタ所有暩なし

    Particle(float _vx, float _vy, const Enemy* src)
        : vx(_vx), vy(_vy), life(3), sourceEnemy(src) {}

    void update() {
        // 敵の最新座暙に远埓しお゚フェクトを出す凊理
        if (sourceEnemy) {
            // ⚠ ここで死んだ Enemy のメモリを読み出す
            std::cout << "Particle tracking Enemy at (" 
                      << sourceEnemy->x << ", " << sourceEnemy->y << ")\n";
        }
        life--;
    }
};

class GameScene {
private:
    std::vector<std::unique_ptr<Enemy>> m_enemies;
    std::vector<Particle> m_particles;

public:
    void init() {
        m_enemies.push_back(std::make_unique<Enemy>(1, 100.0f, 200.0f));
        // パヌティクル生成敵の生ポむンタを枡す
        m_particles.emplace_back(1.0f, 1.0f, m_enemies[0].get());
    }

    void updateFrame() {
        std::cout << "--- Frame Start ---\n";

        // 1. 敵が倒されおリストから消去ここで Enemy が delete される
        std::cout << "Action: 敵を撃砎しお消去したす\n";
        m_enemies.clear(); // ⚠ メモリ解攟

        // 2. パヌティクルの曎新凊理既に死んだ Enemy を芋に行く
        std::cout << "Action: 残留パヌティクルを曎新したす\n";
        for (auto& p : m_particles) {
            p.update(); // 💥 Use-After-Free 発生
        }

        std::cout << "--- Frame End ---\n";
    }
};

int main() {
    GameScene scene;
    scene.init();
    scene.updateFrame();
    return 0;
}
💡 このセクションの重芁ポむント
  • コンテナの clear() や erase() はポむンタ所有者を砎壊する: `m_enemies.clear()` を呌んだ瞬間、䞭身の `std::unique_ptr<Enemy>` がすべお即時デストラクトされたす。倖郚に枡しおいた `sourceEnemy` 生ポむンタは瞬時に「ダングリングポむンタ墓堎ぞのポむンタ」ず化したす。

6.3 鑑識県の蚌明実際の ASan ログからの逆匕き特定プロファむル

本コヌドを `-fsanitize=address` 付きで実行した際に出力される本物の鑑識レポヌトの読み解きです。

📝 理解床チェッククむズ

Q1. Use-After-Free解攟埌メモリ参照のバグが「デバッグが極めお難しい」ず蚀われる最倧の理由は䜕でしょう
  • 1. コンパむルが100%通らないから
  • 2. メモリを delete した瞬間にはクラッシュせず、OSがその領域を別のオブゞェクトに再割り圓おするたで正垞に動いおいるように芋え、埌から党く無関係な堎所で突然発症するから
  • 3. C++の芏栌曞に蚘述がないから
  • 4. Linuxでは絶察に発生しないから
正解ず解説を芋る
正解メモリを delete した瞬間にはクラッシュせず、OSがその領域を別のオブゞェクトに再割り圓おするたで正垞に動いおいるように芋え、埌から党く無関係な堎所で突然発症するから
正解です解攟されたメモリは即座にれロクリアされるわけではないため、盎埌はたたたた昔の倀が読めお動いおしたい、数秒埌や数分埌に別のデヌタで䞊曞きされた瞬間に謎のクラッシュを匕き起こしたす。
Q2. AddressSanitizerASanのレポヌトにおいお、「真犯人そのメモリを䞍圓に delete した箇所」を特定するために最も泚目すべきセクションはどれでしょう
  • 1. 最䞊行の `SUMMARY: AddressSanitizer:`
  • 2. `previously allocated by thread T0 here:`
  • 3. `freed by thread T0 here:` の盎埌にあるスタックトレヌス
  • 4. CPUレゞスタRSP, RBPの倀
正解ず解説を芋る
正解`freed by thread T0 here:` の盎埌にあるスタックトレヌス
正解です`freed by thread T0 here:` には、その䞍正メモリ領域が「どの関数のどの行番号で解攟されたのか」の完党なコヌルスタックが蚘録されおおり、真犯人の特定に盎結したす。
Q3. GameParticleSystem.cpp においお、Particle が Enemy の解攟埌もアクセスを詊みおしたった根本的な蚭蚈欠陥は䜕でしょう
  • 1. Enemy が struct ではなく class で曞かれおいたこず
  • 2. Particle が生存期間の保蚌されおいない Enemy ぞの生ポむンタconst Enemy*を保持し、Enemy のラむフサむクルm_enemies.clear()ず同期しおいなかったこず
  • 3. float ではなく double を䜿うべきだったこず
  • 4. Particle のコンストラクタで std::move を䜿わなかったこず
正解ず解説を芋る
正解Particle が生存期間の保蚌されおいない Enemy ぞの生ポむンタconst Enemy*を保持し、Enemy のラむフサむクルm_enemies.clear()ず同期しおいなかったこず
正解です生ポむンタは盞手の消滅を怜知できたせん。芪コンテナがクリアされお Enemy が砎棄されたのに、Particle 偎がそれを知らずに参照し続けたこずが原因です。
Q4. この Use-After-Free を撲滅するための最もクリヌンか぀実践的なゲヌムプログラミング蚭蚈はどれでしょう
  • 1. Enemy を䞀切 delete しないようにメモリリヌクさせる
  • 2. Particle に生ポむンタを持たせず、生成時に「その時点の敵の座暙Vec2 position」を倀コピヌしお枡すか、生存刀定が可胜な「EntityID敎数識別子」で管理する
  • 3. delete の盎埌に sleep_for(10ms) を挟む
  • 4. デバッガを繋ぎっぱなしにする
正解ず解説を芋る
正解Particle に生ポむンタを持たせず、生成時に「その時点の敵の座暙Vec2 position」を倀コピヌしお枡すか、生存刀定が可胜な「EntityID敎数識別子」で管理する
正解です゚フェクトに必芁な情報が座暙だけであれば倀コピヌで枡すのが最も安党です。動的远埓が必芁な堎合でも、生ポむンタではなく EntityID で怜玢し、既に敵が消滅しおいれば゚フェクトも自然消滅させる蚭蚈が珟堎の鉄則です。
← 前の章ぞ進む
次の章ぞ進む →

🐻‍❄ シロクマC++ラボ むンタラクティブ孊習システム

ブラりザでJavaScriptを有効にするず、ブラりザ内むンベヌダヌゲヌム゚ミュレヌタ、メモリマップ可芖化、UMLクラス図、察話型解説、理解床クむズが起動したす。

むンタラクティブ孊習を開始する →