分类: C/C++
2010-09-30 20:02:02
上手来看看mipsel-linux-user的运行过程,此文分几次完成.
环境ubuntu10.04, eclipse-CDT, mipsel-linux-gcc
还是要提一下,编译交叉编译器的时候, 可以使用buildroot系统,它把gcc和uclibc都编译好了,放在相同的prefix路径下, 而且需要注意-with-sysroot参数是如何让编译好的gcc自动链接uclibc的.
用mipsel-linux-gcc 直接编译helloworld即可,生成了mipsELF文件.
eclipse CDT非常好用,我用它来调试qemu. disassembly窗口可以指定一个地址看反汇编,虽然对mips指令无意义,但是可以观看TCG的翻译结果.
Configure qemu:
./configure --target-list=mipsel-linux-user --enable-debug-tcg --enable-debug --disable-kvm --disable-xen –interp-prefix=/opt/a/cross-mipsel –prefix=opt/a/qemu-mipsel
其中interp-prefix指向了目标c库的root目录,对于cross工具一套来说,和其prefix是同一个路径.
编译完之后,就可以用eclipse进行调试了.
把qemu的参数设置为helloworld elf文件,然后启动调试.
执行顺序:
main函数入口在linux-user/main.c
首先要做的事就是设置各种环境变量.让linux app拥有和host相同的环境变量,然后开始解析各种命令行选项,这些省略不说.
第一个调用的关键函数是cpu_exec_init_all(), 初始化host这边的运行时环境.
cpu_gen_init()
tcg_context_init() // 这一部分初始化TCG, 为各数据结构分配内存,然后生成一组指令序列用于频繁的TCG进入与离开的环境切换,这也是最早被生成的TCG代码部分. 为什么要生成这部分?因为保存上下文的哪些寄存器是能因为ABI的不同而异, 而且使用一段编译时的代码段可能操作上不如TCG直观吧.
tcg_target_init()
tcg_target_qemu_prologue() //生成切换上下文代码
code_gen_alloc()
map_exec() //把刚才Gen出来的代码块所在的页变成RWX属性!(缓冲区:code_gen_prologue)
page_init() //扫描/proc/self/maps获得当前qemu进程的地址映射, 对每一个页,调用::
page_set_flags() //首先检查当前页是否可写,如果可写则标记”原来可写”,另外,还要调用::
page_find_alloc函数检查时候是一个被映射到target的内存的页,如果当前页被映射到target的目标内存中,那么这块内存就是target的内存了.如果target内存有该动,就需要检查是不是自修改代码了,后话以后再说.在init阶段调用这个函数不是为了这个目的.因为现在target内存是完全空的.
Return;
然后执行cpu_init(), 初始化target的一些配置
确定CPU model, 比如是24k还是34k之类的
构造CPUMIPSState结构(env),它是target的根.
cpu_exec_init() 初始化一些数据结构
fpu_init() //浮点结构和寄存器等等
mvp_init() //MT-ASE, See Mips32 ISA vol.3 : 9.1 CP0
mips_tcg_init() //初始化TCG的所有寄存器映像,分配空间,调用tcg_register_helper函数定义很多指令的helper(都是宏,而且在编译时生成,获取比较麻烦).
Return;
cpu_init之后,得到了唯一的,也是重要的产物:env,它描述了全部CPU的运行状态.
从/proc/sys/vm/mmap_min_addr获得mmap允许映射的最低地址空间,我这里得到了65536.
然后把余下的命令行参数都传给target,当然是为了给target一个完整的userspace的感觉.
然后初始化TaskState数据结构.TaskState在linux-user/qemu.h里定义, 主要记录了一些与user process有关的内容,比如pid, 打开的文件映像, 环境变量, signal状态和队列, 以及一个栈. 这个数据结构保存的都独立于CPU和存储的,可以理解为硬件无关/进程相关的信息集合.
初始化结束后, env的opaque指针就指向TaskState.
此时,硬件和软件的环境都初始化完成了.
下一步就是loader_exec() // 函数所在的linux-user/linuxload.c文件就是从kernel里移植来的.
prepare_binprm() // 检查elf文件,是否可以被load, 如检查x位权限
检查elf文件头,并且load_elf_binary() //linux too
target_set_brk() //
下面的部分待续.
syscall_init();
这一段就是按照linux-user/syscall_types.h 的定义来初始化syscall
signal_init();