課題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