返回归档

2.游戏内存模拟器

游戏内存模拟器

==DAY 2: 2026 年 3 月 11 日==

native-lib.cpp 实现了游戏内存分配模拟器,用于生成已知的内存访问模式,验证 ByteHook 监控工具的有效性。

设计思路

真实游戏的内存访问有明确规律:渲染缓冲区每帧访问、对象池高频创建销毁、资源包大块低频加载。这些特征难以在受控环境复现,所以需要一个可编程的模拟器。

模拟器采用双 SO 架构:

  • SO1 (native-lib):纯粹的业务逻辑,模拟游戏内存行为
  • SO2 (so2-hook):监控层,通过 ByteHook 拦截 SO1 的内存操作

这种分离的好处是 SO1 完全不感知监控存在,模拟结果更真实。

核心实现

模拟器实现了四种内存热度级别,对应游戏中典型的内存使用场景:

L1 Hot(渲染缓冲区) 模拟 GPU 渲染数据的特征:每 16ms 访问一次(60 FPS),持续扩容但永不释放。初始分配 10 个 64KB 缓冲区,每 100 帧增加 32KB。

L2 Warm(对象池) 模拟游戏对象的创建销毁:随机分配 256B-8KB,存活 1-10ms 后释放。这种高频小额分配是内存碎片的主要来源。

L3 Cool(配置缓存) 模拟关卡配置数据的加载:分配 16-256KB,保持 5 秒后清理一半。体现”定期清理”的缓存策略。

L4 Cold(资源包) 模拟贴图/模型资源:使用 mmap 分配 4MB,延迟初始化(只写第一页),保持 30 秒后卸载。

技术细节

四个热度级别分别运行在独立线程中,通过 atomic<bool> running 统一控制启停。每个线程持有独立的互斥锁,减少线程竞争。

L1 线程模拟 60 FPS 帧率,每次循环睡眠 16ms。L4 线程使用 mmap/munmap 而非 malloc/free,更接近真实资源加载行为。

停止时采用 detach 而非 join,避免 L4 线程的 30 秒睡眠阻塞 UI 主线程。

与 SO2 的配合

SO2 通过 ByteHook 拦截 SO1 中的 malloc/free/calloc/realloc/mmap/munmap,记录每次分配的:时间戳、大小、调用栈、线程 ID。

CSV 日志格式示例:

timestamp,type,ptr,req_size,act_size,tid,bt0,bt1,bt2,bt3,bt4

SO1 生成内存行为,SO2 捕获并记录,两者通过 JNI 接口与 Java 层交互。MainActivity 控制启停,并指定日志文件路径。

关键代码片段

内存热度级别定义

enum class MemHotness {
    L1_HOT = 1,   // 极热:每帧访问
    L2_WARM = 2,  // 温热:高频分配释放
    L3_COOL = 3,  // 凉爽:定期清理
    L4_COLD = 4   // 冰冷:大块mmap
};

L1 Hot 线程(渲染缓冲区模拟)

void hotThread() {
    // 初始分配10个64KB缓冲区
    for (int i = 0; i < 10; i++) {
        void* p = malloc(64 * 1024);
        hotMemory.emplace_back(p, 64*1024, MemHotness::L1_HOT);
    }
    
    int frame = 0;
    while (running) {
        // 每帧"访问"数据
        for (auto& block : hotMemory) {
            memset(block.ptr, frame % 256, 1024);
        }
        
        // 每100帧扩容32KB
        if (++frame % 100 == 0) {
            void* p = malloc(32 * 1024);
            hotMemory.emplace_back(p, 32*1024, MemHotness::L1_HOT);
        }
        
        this_thread::sleep_for(milliseconds(16)); // 60 FPS
    }
}

L4 Cold 线程(资源包模拟)

void coldThread() {
    while (running) {
        // 映射3个4MB资源包
        for (int i = 0; i < 3; i++) {
            void* p = mmap(nullptr, 4*1024*1024, 
                          PROT_READ | PROT_WRITE,
                          MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
            // 延迟初始化:只写第一页
            memset(p, 0xDD, 4096);
            coldMappings.emplace_back(p, 4*1024*1024);
        }
        
        this_thread::sleep_for(seconds(30));
        
        // 清理所有映射
        for (auto& [ptr, size] : coldMappings) {
            munmap(ptr, size);
        }
        coldMappings.clear();
    }
}

启停控制

void stop() {
    running = false;
    
    // 分离线程,不阻塞UI主线程
    for (auto& t : workers) {
        if (t.joinable()) t.detach();
    }
    
    this_thread::sleep_for(milliseconds(200));
    cleanup();
}