課題5: メモリデバッグツールの構築

マンダトリー要件 (80点)

Part 1: デバッグアロケータの実装 (20点)

メモリ破損を検出できるデバッグアロケータを実装してください。

要件:

const std = @import("std");

pub const DebugAllocator = struct {
    parent: std.mem.Allocator,
    allocations: std.AutoHashMap(usize, AllocationMetadata),
    mutex: std.Thread.Mutex,

    const AllocationMetadata = struct {
        size: usize,
        alignment: u8,
        guard_before: [16]u8,
        guard_after: [16]u8,
        allocation_time: i64,
        stack_trace: [4]usize,
    };

    const GUARD_PATTERN: [16]u8 = [_]u8{0xDE} ** 16;
    const FREED_PATTERN: u8 = 0xAA;

    pub fn init(parent: std.mem.Allocator) !DebugAllocator {
        // TODO: 実装
        // - allocationマップを初期化
        // - mutexを初期化
    }

    pub fn deinit(self: *DebugAllocator) void {
        // TODO: 実装
        // - 未解放の割り当てをレポート
        // - リソースをクリーンアップ
    }

    pub fn allocator(self: *DebugAllocator) std.mem.Allocator {
        // TODO: 実装
    }

    fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
        // TODO: 実装
        // 1. Guard + Data + Guard のレイアウトで割り当て
        // 2. ガード領域を0xDEで初期化
        // 3. メタデータを記録
        // 4. スタックトレースをキャプチャ
    }

    fn free(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void {
        // TODO: 実装
        // 1. ガード領域をチェック
        // 2. 破損があればパニック
        // 3. データ領域を0xAAで埋める(use-after-free検出用)
        // 4. メタデータから削除
    }

    pub fn checkIntegrity(self: *DebugAllocator) !void {
        // TODO: 実装
        // 全ての有効な割り当てのガード領域をチェック
    }
};

テストケース:

test "DebugAllocator - normal usage" {
    var debug = try DebugAllocator.init(std.testing.allocator);
    defer debug.deinit();

    const allocator = debug.allocator();

    const buf = try allocator.alloc(u8, 100);
    defer allocator.free(buf);

    // 通常の使用では問題なし
    @memset(buf, 42);
}

test "DebugAllocator - detects overflow" {
    var debug = try DebugAllocator.init(std.testing.allocator);
    defer debug.deinit();

    const allocator = debug.allocator();
    const buf = try allocator.alloc(u8, 10);

    // オーバーフローを作成(実際には境界チェックでキャッチされる)
    // でもデバッグアロケータは解放時にガードをチェック

    allocator.free(buf); // ガードが破損していればパニック
}

Part 2: メモリプロファイラー (20点)

メモリ使用パターンを分析するプロファイラーを実装してください。

要件:

pub const MemoryProfiler = struct {
    parent: std.mem.Allocator,
    samples: std.ArrayList(Sample),
    size_distribution: std.AutoHashMap(usize, usize),
    start_time: i64,

    pub const Sample = struct {
        timestamp_ms: i64,
        active_allocations: usize,
        total_bytes: usize,
        fragmentation_ratio: f32,
    };

    pub fn init(parent: std.mem.Allocator) !MemoryProfiler {
        // TODO: 実装
    }

    pub fn deinit(self: *MemoryProfiler) void {
        // TODO: 実装
    }

    pub fn allocator(self: *MemoryProfiler) std.mem.Allocator {
        // TODO: 実装
    }

    pub fn takeSample(self: *MemoryProfiler) !void {
        // TODO: 実装
        // 現在のメモリ状態をサンプリング
    }

    pub fn printReport(self: *MemoryProfiler, writer: anytype) !void {
        // TODO: 実装
        // レポートを生成:
        // - 時系列のメモリ使用量グラフ(ASCIIアート)
        // - サイズ分布のヒストグラム
        // - ピークメモリ使用量
        // - 平均割り当てサイズ
    }

    pub fn visualizeTimeline(self: *MemoryProfiler, writer: anytype) !void {
        // TODO: 実装
        // メモリ使用量の時系列グラフを表示
        //
        // Memory Usage Over Time:
        // 1000 |     ╭─╮
        //  800 |   ╭─╯ ╰╮
        //  600 | ╭─╯    ╰─╮
        //  400 |─╯        ╰──
        //  200 |
        //    0 └──────────────
        //      0  5 10 15 20 (seconds)
    }
};

テストケース:

test "MemoryProfiler - tracks usage" {
    var profiler = try MemoryProfiler.init(std.testing.allocator);
    defer profiler.deinit();

    const allocator = profiler.allocator();

    // 様々なサイズを割り当て
    const buf1 = try allocator.alloc(u8, 100);
    try profiler.takeSample();

    const buf2 = try allocator.alloc(u8, 500);
    try profiler.takeSample();

    allocator.free(buf1);
    try profiler.takeSample();

    allocator.free(buf2);
    try profiler.takeSample();

    // レポートを出力
    var buffer = std.ArrayList(u8).init(std.testing.allocator);
    defer buffer.deinit();

    try profiler.printReport(buffer.writer());
    std.debug.print("{s}\n", .{buffer.items});
}

Part 3: Use-After-Free検出器 (20点)

解放済みメモリへのアクセスを検出する機能を実装してください。

要件:

pub const UseAfterFreeDetector = struct {
    parent: std.mem.Allocator,
    freed_regions: std.ArrayList(FreedRegion),
    max_tracked_frees: usize,

    const FreedRegion = struct {
        start: usize,
        end: usize,
        freed_at: i64,
        stack_trace: [4]usize,
    };

    pub fn init(parent: std.mem.Allocator, max_tracked: usize) !UseAfterFreeDetector {
        // TODO: 実装
    }

    pub fn deinit(self: *UseAfterFreeDetector) void {
        // TODO: 実装
    }

    pub fn allocator(self: *UseAfterFreeDetector) std.mem.Allocator {
        // TODO: 実装
    }

    fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
        // TODO: 実装
        // 通常の割り当て
    }

    fn free(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void {
        // TODO: 実装
        // 1. 解放されたリージョンを記録
        // 2. メモリをpoisonパターンで埋める
        // 3. 古いエントリを削除(max_tracked_frees制限)
    }

    pub fn checkAccess(self: *UseAfterFreeDetector, ptr: usize) !void {
        // TODO: 実装
        // アクセスが解放済み領域内かチェック
        // もしそうならエラーを返す
    }

    pub fn printFreedRegions(self: *UseAfterFreeDetector, writer: anytype) !void {
        // TODO: 実装
        // 解放済み領域のリストを表示
    }
};

テストケース:

test "UseAfterFreeDetector - detects access" {
    var detector = try UseAfterFreeDetector.init(std.testing.allocator, 100);
    defer detector.deinit();

    const allocator = detector.allocator();

    const buf = try allocator.alloc(u8, 100);
    const ptr = @intFromPtr(buf.ptr);

    allocator.free(buf);

    // 解放後のアクセスをチェック
    try std.testing.expectError(error.UseAfterFree, detector.checkAccess(ptr));
}

Part 4: メモリダンプビジュアライザー (20点)

メモリの内容を視覚的に表示するツールを実装してください。

要件:

pub const MemoryDumper = struct {
    pub fn dumpMemory(
        writer: anytype,
        ptr: [*]const u8,
        len: usize,
        bytes_per_line: usize,
    ) !void {
        // TODO: 実装
        // 16進ダンプとASCII表示
        //
        // 出力例:
        // 0x00001000:  48 65 6c 6c 6f 20 57 6f 72 6c 64 00 de de de de  |Hello World.....|
        // 0x00001010:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
    }

    pub fn dumpWithHighlight(
        writer: anytype,
        ptr: [*]const u8,
        len: usize,
        highlight_offset: usize,
        highlight_len: usize,
    ) !void {
        // TODO: 実装
        // 特定の範囲をハイライト表示
        //
        // 0x00001000:  48 65 6c 6c 6f 20 57 6f 72 6c 64 00 de de de de
        //              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (highlighted)
    }

    pub fn compareMemory(
        writer: anytype,
        ptr1: [*]const u8,
        ptr2: [*]const u8,
        len: usize,
    ) !void {
        // TODO: 実装
        // 2つのメモリ領域を比較して差分を表示
        //
        // Memory 1:  48 65 6c 6c 6f 20 57 6f 72 6c 64 00
        // Memory 2:  48 65 6c 6c 6f 21 57 6f 72 6c 64 00
        // Diff:                     ^^  (byte 5 differs)
    }

    pub fn analyzeMemoryPattern(
        writer: anytype,
        ptr: [*]const u8,
        len: usize,
    ) !void {
        // TODO: 実装
        // メモリパターンを分析
        // - NULL埋め領域
        // - 繰り返しパターン
        // - ASCII文字列
        // - 数値データ
    }
};

テストケース:

test "MemoryDumper - basic dump" {
    const data = "Hello, World! This is a test.";

    var buffer = std.ArrayList(u8).init(std.testing.allocator);
    defer buffer.deinit();

    try MemoryDumper.dumpMemory(
        buffer.writer(),
        data.ptr,
        data.len,
        16,
    );

    std.debug.print("{s}\n", .{buffer.items});
}

test "MemoryDumper - highlight" {
    const data = "0123456789ABCDEF";

    var buffer = std.ArrayList(u8).init(std.testing.allocator);
    defer buffer.deinit();

    try MemoryDumper.dumpWithHighlight(
        buffer.writer(),
        data.ptr,
        data.len,
        5,
        5,
    );

    std.debug.print("{s}\n", .{buffer.items});
}

ボーナス課題 (20点)

Bonus 1: マルチスレッド対応デバッグツール (10点)

スレッドセーフなデバッグアロケータを実装してください。

要件:

pub const ThreadSafeDebugAllocator = struct {
    parent: std.mem.Allocator,
    allocations: std.AutoHashMap(usize, AllocationInfo),
    mutex: std.Thread.Mutex,
    thread_stats: std.AutoHashMap(std.Thread.Id, ThreadStats),

    const AllocationInfo = struct {
        size: usize,
        thread_id: std.Thread.Id,
        timestamp: i64,
    };

    const ThreadStats = struct {
        allocations: usize,
        deallocations: usize,
        total_bytes: usize,
    };

    // TODO: 実装
    // - スレッドごとの統計
    // - データ競合の検出
    // - スレッド間でのメモリ移動の追跡
};

Bonus 2: メモリ使用量アラートシステム (10点)

閾値ベースのアラートシステムを実装してください。

要件:

pub const MemoryAlertSystem = struct {
    profiler: *MemoryProfiler,
    thresholds: AlertThresholds,
    alerts: std.ArrayList(Alert),

    pub const AlertThresholds = struct {
        max_total_bytes: usize,
        max_active_allocations: usize,
        max_growth_rate: f32,  // bytes/second
    };

    pub const Alert = struct {
        level: Level,
        message: []const u8,
        timestamp: i64,
        metrics: MetricsSnapshot,

        pub const Level = enum {
            warning,
            critical,
        };

        pub const MetricsSnapshot = struct {
            total_bytes: usize,
            active_allocations: usize,
            growth_rate: f32,
        };
    };

    pub fn init(profiler: *MemoryProfiler, thresholds: AlertThresholds) !MemoryAlertSystem {
        // TODO: 実装
    }

    pub fn checkThresholds(self: *MemoryAlertSystem) !void {
        // TODO: 実装
        // 現在のメトリクスを閾値と比較
        // 超過していればアラートを生成
    }

    pub fn printAlerts(self: *MemoryAlertSystem, writer: anytype) !void {
        // TODO: 実装
        // 全てのアラートを表示
    }
};

評価基準

項目 配点
Part 1: デバッグアロケータ 20点
Part 2: メモリプロファイラー 20点
Part 3: Use-After-Free検出 20点
Part 4: メモリダンプ 20点
**マンダトリー合計** **80点**
Bonus 1: マルチスレッド対応 10点
Bonus 2: アラートシステム 10点
**ボーナス合計** **20点**

採点詳細

Part 1 (20点):

  • ガード領域の実装 (8点)
  • メタデータ管理 (6点)
  • 破損検出 (6点)

Part 2 (20点):

  • サンプリング機能 (8点)
  • レポート生成 (6点)
  • グラフ表示 (6点)

Part 3 (20点):

  • 解放リージョン追跡 (8点)
  • アクセスチェック (8点)
  • スタックトレース (4点)

Part 4 (20点):

  • 16進ダンプ (6点)
  • ハイライト表示 (6点)
  • 比較機能 (4点)
  • パターン分析 (4点)
  • 合格基準

  • マンダトリー: 64点以上(80%)で合格
  • ボーナス: 優秀な実装に対する追加評価
  • 提出方法

  • すべてのコードを exercise05/ ディレクトリに配置
  • ファイル構成:
  • exercise05/
    ├── debug_allocator.zig
    ├── memory_profiler.zig
    ├── use_after_free_detector.zig
    ├── memory_dumper.zig
    ├── tests.zig
    ├── bonus1_threadsafe.zig      (オプション)
    └── bonus2_alerts.zig          (オプション)
    

  • テスト実行:
  • zig test exercise05/tests.zig
    

    ヒント

    DebugAllocator

    // メモリレイアウト
    const Layout = struct {
        guard_before: [16]u8,
        user_data: []u8,
        guard_after: [16]u8,
    };
    
    // ガードチェック
    fn checkGuards(header: *const Layout, footer: *const Layout) !void {
        for (header.guard_before, 0..) |byte, i| {
            if (byte != GUARD_PATTERN[i]) {
                return error.BufferUnderrun;
            }
        }
    
        for (footer.guard_after, 0..) |byte, i| {
            if (byte != GUARD_PATTERN[i]) {
                return error.BufferOverflow;
            }
        }
    }
    

    MemoryProfiler

    // ASCIIグラフ描画
    fn drawBar(writer: anytype, value: usize, max: usize, width: usize) !void {
        const bar_len = (value * width) / max;
    
        var i: usize = 0;
        while (i < bar_len) : (i += 1) {
            try writer.print("█", .{});
        }
    
        while (i < width) : (i += 1) {
            try writer.print("░", .{});
        }
    }
    

    UseAfterFreeDetector

    // リージョンチェック
    fn isInFreedRegion(self: *UseAfterFreeDetector, addr: usize) ?*FreedRegion {
        for (self.freed_regions.items) |*region| {
            if (addr >= region.start and addr < region.end) {
                return region;
            }
        }
        return null;
    }
    

    MemoryDumper

    // 16進フォーマット
    fn printHexLine(writer: anytype, data: []const u8) !void {
        for (data) |byte| {
            try writer.print("{x:0>2} ", .{byte});
        }
    }
    
    // ASCII表示
    fn printAsciiLine(writer: anytype, data: []const u8) !void {
        for (data) |byte| {
            if (std.ascii.isPrint(byte)) {
                try writer.print("{c}", .{byte});
            } else {
                try writer.print(".", .{});
            }
        }
    }
    

    デバッグのコツ

  • 段階的実装:
// まず基本機能を実装
// 次に高度な機能を追加
// 最後にエラーハンドリングを強化

  • 小さなテストから:
test "simple allocation and free" {
    // 最もシンプルなケースから始める
}

  • ログ出力を活用:
std.debug.print("alloc: ptr=0x{x}, size={}\n", .{ @intFromPtr(ptr), size });

参考資料

  • Valgrind Source Code: https://sourceware.org/git/valgrind.git
  • AddressSanitizer: https://github.com/google/sanitizers
  • Electric Fence: https://elinux.org/Electric_Fence
  • Memory Debugging in Linux: https://elinux.org/Memory_Debugging