Box86源码解读记录

1. 背景说明

Github地址:https://github.com/ptitSeb/box86

官方推荐的视频教程:Box86/Box64视频教程网盘

2. 程序执行主体图

Box86版本: Box86 with Dynarec v0.3.4

在这里插入图片描述
主函数会执行一大堆的初始化工作,包括但不限于:BOX上下文 、内存部局、空间分配、x86指令模拟器、ELF文件 、依赖库文件、所有准备工作完成之后,在x86模拟器中按着IP指针,开始顺序解释执行代码块

3. 代码说明-main函数

/src/main.c
因为main函数代码量比较大,下边是精简后的代码,省略了大部分非主干流程代码,这样看起来就容易很多了。

int main(int argc, const char **argv, char **env)
{
    init_malloc_hook();
    init_auxval(argc, argv, environ?environ:env);
   .........
    //创建一个Box86的上下文、这个上下文将会持有着,在转译时可能用到的全部信息
    my_context = NewBox86Context(argc - nextarg);

    //检查并读取必须用到的环境变量信息(例如:LD_LIBRARY_PATH、BOX86_EMULATED_LIBS、libssl.so、BOX86_PATH 等等)
    LoadEnvVars(my_context);
    .........
    //通过 ParseElfHeader 方法将当前要执行 ELF 文件的头信息解析出对应的 elfheader_t 结构 
    elfheader_t *elf_header = LoadAndCheckElfHeader(f, my_context->fullpath, 1);

    /* 将 ELF 头信息添加到当前Box86的上下文中
 CalcLoadAddr(elf_header); //这个函数的作用是根据 ELF 文头信息来计算或确定一些关键的内存布局参数。(包括虚拟地址(vaddr)、物理地址(paddr)、内存大小(memsz)、对齐(align)、栈大小(stacksz)、栈对齐(stackalign)、TLS(线程局部存储)等等)*/
    AddElfHeader(my_context, elf_header);

//根据 ELF 头部信息设置内存布局
    if(CalcLoadAddr(elf_header)) {
        printf_log(LOG_NONE, "Error: reading elf header of %s\n", my_context->fullpath);
        free_contextargv();
        FreeBox86Context(&my_context);
        FreeCollection(&ld_preload);
        FreeElfHeader(&elf_header);
        return -1;
    }
    
    //根据 ELF 头部信息和内存布局参数,来为 ELF 文件的加载分配内存。
    if(AllocLoadElfMemory(my_context, elf_header, 1)) {
        printf_log(LOG_NONE, "Error: loading elf %s\n", my_context->fullpath);
        free_contextargv();
        FreeBox86Context(&my_context);
        FreeCollection(&ld_preload);
        FreeElfHeader(&elf_header);
        return -1;
    }
    
    /*设置bss段空间以及未初始化的静态和全局变量空间
CalcStackSize(my_context); //计算栈空间大小以及内存对齐方式,初始值为8M空间、4Byte对齐,再根据context->elfs数组值来遍历,如果数组中有值大于初始值,则调整为数组中的最大值, 然后用 mmap 分配空间*/
    my_context->brk = ElfGetBrk(elf_header);
    .........
    
    //通过Box86的上下文配置,调用internalX86Setup创建了一个X86运行的模拟器
    x86emu_t *emu = NewX86Emu(my_context, my_context->ep, (uintptr_t)my_context->stack, my_context->stacksz, 0);
    // stack setup is much more complicated then just that!
    SetupInitialStack(emu); //设置x86模拟器初始栈
    SetupX86Emu(emu);

    //按照X86应用程序的逻辑main函数的加载过程一样 ,将应用程序的参数加到EAX和EBX,以此来模拟X86程序的启动
    SetEAX(emu, my_context->argc);
    SetEBX(emu, (uint32_t)my_context->argv);
    .........
    
    //负责解析ELF文件中的符号信息,并将它们分类到不同的符号表中,以便于后续的符号解析和链接使用
    AddSymbols(my_context->maplib, GetMapSymbols(elf_header), GetWeakSymbols(elf_header), GetLocalSymbols(elf_header), elf_header);
    .........
    
    //负责在加载主 ELF 文件时,将其信息添加到链接映射中,以便动态链接器可以正确地解析符号和执行重定位。
    AddMainElfToLinkmap(elf_header);
    .........
    //解析 ELF 文件的动态依赖,并加载相应的库
    if(LoadNeededLibs(elf_header, my_context->maplib, 0, 0, my_context, emu)) {
        printf_log(LOG_NONE, "Error: loading needed libs in elf %s\n", my_context->argv[0]);
        FreeBox86Context(&my_context);
        return -1;
    }
    .........
    //解释运行X86程序 
    DynaRun(emu);
    // Get EAX
    int ret = GetEAX(emu);
    .........
    return ret;
}

4. 代码说明-DBGetBlock函数

DBGetBlock 函数是动态二进制翻译器中用于获取或创建动态块(dynablock)的公共接口。这个函数的主要目的是根据给定的地址获取一个已经存在的动态块,或者在不存在的情况下创建一个新的动态块。dynablock 是一个关键的数据结构,它代表了一个已经翻译过的代码块。Dynarec 是一种优化技术,用于提高模拟或跨平台执行代码的性能。它通过将目标代码(通常是二进制格式的机器代码)实时翻译成宿主机器的指令集来工作。它减少了每次执行时的翻译开销。此外,通过维护跳转表和哈希值,它提高了代码的执行速度和可靠性。

dynablock 的生命周期
创建:当模拟的程序首次尝试执行某个地址范围的代码时,Dynarec 会创建一个新的 dynablock。
翻译:Dynarec 将目标代码翻译成宿主机器的指令集,并存储在 dynablock 中。
执行:宿主机器直接执行 dynablock 中的翻译后的代码。
验证:在某些情况下,Dynarec 可能会重新验证 dynablock 的有效性,比如当它所在的内存页是热页时。
更新:如果 dynablock 被验证为无效,或者目标代码发生了变化,Dynarec 会更新或替换 dynablock。
释放:当 dynablock 不再需要时,比如模拟的程序已经执行完毕或者 dynablock 变得无效,它会被释放。

下边是精简后的代码,省略了部分流程代码、增加了注释:

dynablock_t* DBGetBlock(x86emu_t* emu, uintptr_t addr, int create)
{
	//获取或创建动态块(dynablock)的内部函数,首先检查地址是否有效,然后尝试获取(已经存在)或创建一个动态块
    dynablock_t *db = internalDBGetBlock(emu, addr, addr, create, 1);
    
	//并在获取到动态块后进行一些额外的检查和操作,比如验证动态块的有效性,以及是否需要重新保护内存页
    if(db && db->done && db->block && getNeedTest(addr)) {
        if(db->always_test)
            sched_yield();  // 让出当前线程执行权的函数,以允许其他线程运行
		
		//检查动态块的地址是否落在 hotpage 内、hotpage 表示频繁访问的内存页
        if(AreaInHotPage((uintptr_t)db->x86_addr, (uintptr_t)db->x86_addr + db->x86_size - 1)) {
            emu->test.test = 0;
            if(box86_dynarec_fastpage) {
				//用于计算动态块的哈希值,以便验证动态块的一致性
                uint32_t hash = X31_hash_code(db->x86_addr, db->x86_size);
                if(hash==db->hash) { // seems ok, run it without reprotecting it
	                //用于加速执行的优化技术,跳转表是一个数据结构,包含了目标代码地址到翻译后代码地址的映射。通过这个映射,Dynarec可以快速跳转到已翻译的代码,因此达到不需要每次都进行完整的翻译过程。
                    setJumpTableIfRef(db->x86_addr, db->block, db->jmpnext);
                    return db;
                }
                db->done = 0;   // 动态块已经无效,不能使用了
				.........
                return NULL;    // not building a new one, it's still a hotpage
            } else {
                return NULL;
            }
        }
        
        .........
            //动态地改变内存页的保护属性,以支持动态二进制翻译器的内存管理需求
            protectDB((uintptr_t)db->x86_addr, db->x86_size);
            // fill back jumptable
            if(isprotectedDB((uintptr_t)db->x86_addr, db->x86_size) && !db->always_test) {
           	    //用于加速执行的优化技术,跳转表是一个数据结构,包含了目标代码地址到翻译后代码地址的映射。通过这个映射,Dynarec可以快速跳转到已翻译的代码,因此达到不需要每次都进行完整的翻译过程。
                setJumpTableIfRef(db->x86_addr, db->block, db->jmpnext);
            }
        .........       
}

5. 代码说明-xxx函数

6. 代码说明-xxx函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xuanwenchao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值