QEMU学习实况(一)· 先从 main() 开始

276 阅读2分钟

编写一个简单的 C 程序,打印 “hello,world ” 。使用 RISC-V GNU Toolchain 编译(这里采用芯来的 工具链,版本是2020.08 ),先试一试 32 位的,然后用 qemu user 模式运行看看效果。

#include <stdio.h>
int main(void)
{
    printf("hello, zeorn!\n");
    return 0;
}

编译 C 程序:

 $ riscv-nuclei-elf-gcc -o  main main.c

然后使用 qemu-user 来运行:

$ qemu-riscv32 main
hello, zeorn!

然后使用 gdb 进行 qemu-user 模式的调试:

$ gdb qemu-riscv32
(gdb) set args main
(gdb) b main
Breakpoint 1 at 0x162b82: file ../linux-user/main.c, line 671.
(gdb) run

如果一切顺利的话,将会在 main() 函数停下来。

此时先不要着急调试,通过 Vscode 阅读一下 main() 函数的源代码,建立一个初步印象和宏观的逻辑,之后遇到搞不通的,再通过 gdb 来调试,观察代码的运行逻辑。

这里简单贴一下 main 函数的代码(不必要的细节先略过):

/* file ../linux-user/main.c, line 671 */
int main(int argc, char **argv, char **envp)
{
    /* 局部变量的定义,此处省略 ... */
  
    error_init(argv[0]);
    module_call_init(MODULE_INIT_TRACE);
    qemu_init_cpu_list();
    module_call_init(MODULE_INIT_QOM);

    envlist = envlist_create();

    /*
     * add current environment into the list
     * envlist_setenv adds to the front of the list; to preserve environ
     * order add from back to front
     */
    for (wrk = environ; *wrk != NULL; wrk++) {
        continue;
    }
    while (wrk != environ) {
        wrk--;
        (void) envlist_setenv(envlist, *wrk);
    }

    /* Read the stack limit from the kernel.  If it's "unlimited",
       then we can do little else besides use the default.  */
    {
        struct rlimit lim;
        if (getrlimit(RLIMIT_STACK, &lim) == 0
            && lim.rlim_cur != RLIM_INFINITY
            && lim.rlim_cur == (target_long)lim.rlim_cur
            && lim.rlim_cur > guest_stack_size) {
            guest_stack_size = lim.rlim_cur;
        }
    }

    cpu_model = NULL;

    qemu_add_opts(&qemu_trace_opts);
    qemu_plugin_add_opts();

    optind = parse_args(argc, argv);

    qemu_set_log_filename_flags(last_log_filename,
                                last_log_mask | (enable_strace * LOG_STRACE),
                                &error_fatal);

    if (!trace_init_backends()) {
        exit(1);
    }
    trace_init_file();
    qemu_plugin_load_list(&plugins, &error_fatal);

    /* Zero out regs */
    memset(regs, 0, sizeof(struct target_pt_regs));

    /* Zero out image_info */
    memset(info, 0, sizeof(struct image_info));

    memset(&bprm, 0, sizeof (bprm));

    /* Scan interp_prefix dir for replacement files. */
    init_paths(interp_prefix);

    init_qemu_uname_release();

    /*
     * Manage binfmt-misc open-binary flag
     */
    execfd = qemu_getauxval(AT_EXECFD);
    if (execfd == 0) {
        execfd = open(exec_path, O_RDONLY);
        if (execfd < 0) {
            printf("Error while loading %s: %s\n", exec_path, strerror(errno));
            _exit(EXIT_FAILURE);
        }
    }

    /* Resolve executable file name to full path name */
    if (realpath(exec_path, real_exec_path)) {
        exec_path = real_exec_path;
    }

    /*
     * get binfmt_misc flags
     */
    preserve_argv0 = !!(qemu_getauxval(AT_FLAGS) & AT_FLAGS_PRESERVE_ARGV0);

    /*
     * Manage binfmt-misc preserve-arg[0] flag
     *    argv[optind]     full path to the binary
     *    argv[optind + 1] original argv[0]
     */
    if (optind + 1 < argc && preserve_argv0) {
        optind++;
    }

    if (cpu_model == NULL) {
        cpu_model = cpu_get_model(get_elf_eflags(execfd));
    }
    cpu_type = parse_cpu_option(cpu_model);

    /* init tcg before creating CPUs and to get qemu_host_page_size */
    {
        AccelState *accel = current_accel();
        AccelClass *ac = ACCEL_GET_CLASS(accel);

        accel_init_interfaces(ac);
        object_property_set_bool(OBJECT(accel), "one-insn-per-tb",
                                 opt_one_insn_per_tb, &error_abort);
        ac->init_machine(NULL);
    }
    cpu = cpu_create(cpu_type);
    env = cpu->env_ptr;
    cpu_reset(cpu);
    thread_cpu = cpu;

    /*
     * Reserving too much vm space via mmap can run into problems
     * with rlimits, oom due to page table creation, etc.  We will
     * still try it, if directed by the command-line option, but
     * not by default.
     */
    max_reserved_va = MAX_RESERVED_VA(cpu);
    if (reserved_va != 0) {
        if ((reserved_va + 1) % qemu_host_page_size) {
            char *s = size_to_str(qemu_host_page_size);
            fprintf(stderr, "Reserved virtual address not aligned mod %s\n", s);
            g_free(s);
            exit(EXIT_FAILURE);
        }
        if (max_reserved_va && reserved_va > max_reserved_va) {
            fprintf(stderr, "Reserved virtual address too big\n");
            exit(EXIT_FAILURE);
        }
    } else if (HOST_LONG_BITS == 64 && TARGET_VIRT_ADDR_SPACE_BITS <= 32) {
        /* MAX_RESERVED_VA + 1 is a large power of 2, so is aligned. */
        reserved_va = max_reserved_va;
    }

    /*
     * Temporarily disable
     *   "comparison is always false due to limited range of data type"
     * due to comparison between (possible) uint64_t and uintptr_t.
     */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtype-limits"

    /*
     * Select an initial value for task_unmapped_base that is in range.
     */
    if (reserved_va) {
        if (TASK_UNMAPPED_BASE < reserved_va) {
            task_unmapped_base = TASK_UNMAPPED_BASE;
        } else {
            /* The most common default formula is TASK_SIZE / 3. */
            task_unmapped_base = TARGET_PAGE_ALIGN(reserved_va / 3);
        }
    } else if (TASK_UNMAPPED_BASE < UINTPTR_MAX) {
        task_unmapped_base = TASK_UNMAPPED_BASE;
    } else {
        /* 32-bit host: pick something medium size. */
        task_unmapped_base = 0x10000000;
    }
    mmap_next_start = task_unmapped_base;

    /* Similarly for elf_et_dyn_base. */
    if (reserved_va) {
        if (ELF_ET_DYN_BASE < reserved_va) {
            elf_et_dyn_base = ELF_ET_DYN_BASE;
        } else {
            /* The most common default formula is TASK_SIZE / 3 * 2. */
            elf_et_dyn_base = TARGET_PAGE_ALIGN(reserved_va / 3) * 2;
        }
    } else if (ELF_ET_DYN_BASE < UINTPTR_MAX) {
        elf_et_dyn_base = ELF_ET_DYN_BASE;
    } else {
        /* 32-bit host: pick something medium size. */
        elf_et_dyn_base = 0x18000000;
    }

#pragma GCC diagnostic pop

    {
        Error *err = NULL;
        if (seed_optarg != NULL) {
            qemu_guest_random_seed_main(seed_optarg, &err);
        } else {
            qcrypto_init(&err);
        }
        if (err) {
            error_reportf_err(err, "cannot initialize crypto: ");
            exit(1);
        }
    }

    target_environ = envlist_to_environ(envlist, NULL);
    envlist_free(envlist);

    /*
     * Read in mmap_min_addr kernel parameter.  This value is used
     * When loading the ELF image to determine whether guest_base
     * is needed.  It is also used in mmap_find_vma.
     */
    {
        FILE *fp;

        if ((fp = fopen("/proc/sys/vm/mmap_min_addr", "r")) != NULL) {
            unsigned long tmp;
            if (fscanf(fp, "%lu", &tmp) == 1 && tmp != 0) {
                mmap_min_addr = tmp;
                qemu_log_mask(CPU_LOG_PAGE, "host mmap_min_addr=0x%lx\n",
                              mmap_min_addr);
            }
            fclose(fp);
        }
    }

    /*
     * We prefer to not make NULL pointers accessible to QEMU.
     * If we're in a chroot with no /proc, fall back to 1 page.
     */
    if (mmap_min_addr == 0) {
        mmap_min_addr = qemu_host_page_size;
        qemu_log_mask(CPU_LOG_PAGE,
                      "host mmap_min_addr=0x%lx (fallback)\n",
                      mmap_min_addr);
    }

    /*
     * Prepare copy of argv vector for target.
     */
    target_argc = argc - optind;
    target_argv = calloc(target_argc + 1, sizeof (char *));
    if (target_argv == NULL) {
        (void) fprintf(stderr, "Unable to allocate memory for target_argv\n");
        exit(EXIT_FAILURE);
    }

    /*
     * If argv0 is specified (using '-0' switch) we replace
     * argv[0] pointer with the given one.
     */
    i = 0;
    if (argv0 != NULL) {
        target_argv[i++] = strdup(argv0);
    }
    for (; i < target_argc; i++) {
        target_argv[i] = strdup(argv[optind + i]);
    }
    target_argv[target_argc] = NULL;

    ts = g_new0(TaskState, 1);
    init_task_state(ts);
    /* build Task State */
    ts->info = info;
    ts->bprm = &bprm;
    cpu->opaque = ts;
    task_settid(ts);

    fd_trans_init();

    ret = loader_exec(execfd, exec_path, target_argv, target_environ, regs,
        info, &bprm);
    if (ret != 0) {
        printf("Error while loading %s: %s\n", exec_path, strerror(-ret));
        _exit(EXIT_FAILURE);
    }

    for (wrk = target_environ; *wrk; wrk++) {
        g_free(*wrk);
    }

    g_free(target_environ);

    if (qemu_loglevel_mask(CPU_LOG_PAGE)) {
        FILE *f = qemu_log_trylock();
        if (f) {
            page_dump(f);
            qemu_log_unlock(f);
        }
    }

    target_set_brk(info->brk);
    syscall_init();
    signal_init();

    /* Now that we've loaded the binary, GUEST_BASE is fixed.  Delay
       generating the prologue until now so that the prologue can take
       the real value of GUEST_BASE into account.  */
    tcg_prologue_init(tcg_ctx);

    target_cpu_copy_regs(env, regs);

    if (gdbstub) {
        if (gdbserver_start(gdbstub) < 0) {
            fprintf(stderr, "qemu: could not open gdbserver on %s\n",
                    gdbstub);
            exit(EXIT_FAILURE);
        }
        gdb_handlesig(cpu, 0);
    }

    cpu_loop(env);
    /* never exits */
    return 0;
}