• 使用Bochs学习硬件原理


    什么是Bochs?

    简单地说,Bochs是一款仿真软件,可以用软件的方式模拟硬件的工作。同类软件有Qemu,仿真软件与虚拟机(hypervisor)还不完全相同,仿真软件是完全软件模拟硬件,而虚拟机软件(比如Vmware, VirtualBox)是利用主机的硬件进行工作。

    Bochs的主页地址:http://bochs.sourceforge.net/

    Bochs软件的下载地址:http://sourceforge.net/projects/bochs/files/bochs/

    Bochs的使用

    Bochs的使用依赖配置文件,通过配置文件指定不同的硬件,以及指定存储介质的映像文件(BIOS的ROM文件、磁盘文件等)。

    在Windows环境下安装过Bochs后,在配置文件上右键菜单会出现Run, Debug的菜单选项,从而启动运行或者调试。

    Bochs调试器

    Bochs的调试器命令与gdb命令十分相似,但是更加强大。简单介绍几条命令的使用:

       1: #流程控制
       2: c             #continue, 继续执行
       3: s [count]     #step, 单步执行count次
       4: #断点
       5: vb seg:off    #设置逻辑地址断点
       6: lb addr       #设置物理地址断点
       7: info break    #查看断点
       8: d n           #删除断点
       9: #查看内存
      10: x/n[bhwg][xduotc] #查看内存
      11: [bhwg]        #显示单元大小,分别代表byte, half, word, giant word
      12: [xduotc]      #显示格式,分别代表hex, dec, unsigned, octal, binary, char
      13: #查看寄存器
      14: r             #查看基本寄存器
      15: sreg          #查看段寄存器

     

    更多的命令,请输入help查看。

    在Windows环境下编译Bochs源代码

    首先说一下编译源码的动机,当我们安装了Bochs之后就已经可以使用它来运行或者调试一个被仿真的系统了。 这种调试类似于gdb,调试目标是运行在Bochs之上的系统。

    然而,我们知道,既然Bochs是一个开源的项目,以通过软件的方式仿真了硬件系统,那么我们就可以通过查看Bochs的源码来学习相关的硬件知识(比如Intel体系结构,BIOS,DMA等)了。

    从上面的下载地址下载一份源代码,解压后,能看到vs2008/bochs.sln文件,从而打开Visual Studio项目进行编译。

    默认配置选项中没有包含对bochsdbg的支持,因此我们需要重新运行configure程序,悲剧的是configure是Linux下面的程序,我们可以通过以下方式来达到同样的目的:

    1. 1. 安装mingw,以及msys,将msys/bin目录添加到系统PATH环境变量中;
    2. 2. 修改源码目录下的.conf.win32_vcpp文件,添加
         1: --enable-debugger --enable-disasm
    3. 3. 打开Visual Studio的Prompt命令行,cd到源码目录下,运行
         1: bash.exe .conf.win32_vcpp

    完成以上步骤之后,就可以编译出具有debug功能的Bochs可执行程序了。

    Bochs是怎样处理调试命令的?

    我们可以在位置上设置断点:

       1: void bx_dbg_user_input_loop(void) /*dbg_main.cc*/

    然后在调试窗口中输入命令

       1: r

    程序会在这两个断点处中断,这个bx_dbg_user_input_loop函数就是不断接收调试命令的循环体,它会把接收到的调试命令经过lex&yacc框架进行解析,然后调用到相应的handler来处理调试请求。

    这些handler都在debug.h文件中进行声明,比如处理r命令的handler定义为

       1: void bx_dbg_info_registers_command(int);

    在该函数的定义处设置断点,我们就能够了解到Bochs是怎样处理r这样的调试请求的。

    通过跟踪几个调试命令的实现,我们发现了三个重要的全局变量:

       1: BOCHSAPI BX_CPU_C bx_cpu;
       2: BOCHSAPI BX_MEM_C bx_mem;
       3: bx_devices_c bx_devices;

    分别保存着用来描述CPU、内存和外部设备的数据结构。

    指令IN和OUT是如何处理的?

    由于我们希望通过Bochs来学习硬件相关的内容,所以会对IN和OUT这两条指令很感兴趣,因为CPU就是通过这两条指令与外部设备之间进行协调工作的。

    我们通过尝试,找到了下面这个函数

       1: /*
       2:  * Write a byte of data to the IO memory address space.
       3:  */
       4:  
       5:   void BX_CPP_AttrRegparmN(3)
       6: bx_devices_c::outp(Bit16u addr, Bit32u value, unsigned io_len)

    bx_devices会在内部维护一个外设端口对应的读和写的handler的数组

       1: struct io_handler_struct **read_port_to_handler;
       2: struct io_handler_struct **write_port_to_handler;

    这是两个二维指针数组,用端口号作为下标可以找到某个端口对应的读写处理函数,默认会把每个handler都设置成io_write_handlers

       1: /* set handlers to the default one */
       2:  for (i=0; i < PORTS; i++) {
       3:    read_port_to_handler[i] = &io_read_handlers;
       4:    write_port_to_handler[i] = &io_write_handlers;
       5:  }

    通过查找函数

       1: #define DEV_register_ioread_handler(b,c,d,e,f) bx_devices.register_io_read_handler(b,c,d,e,f)
       2: #define DEV_register_iowrite_handler(b,c,d,e,f) bx_devices.register_io_write_handler(b,c,d,e,f)

    我们可以找到哪些设备支持了自己的IO读写功能,以及其对应的handler。

    以DMA为例,我们可以找到如下的注册handler代码

       1: // 0000..000F
       2: for (i=0x0000; i<=0x000F; i++) {
       3:   DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1);
       4:   DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3);
       5: }
       6:  
       7: // 00080..008F
       8: for (i=0x0080; i<=0x008F; i++) {
       9:   DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1);
      10:   DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3);
      11: }
      12:  
      13: // 000C0..00DE
      14: for (i=0x00C0; i<=0x00DE; i+=2) {
      15:   DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1);
      16:   DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3);
      17: }

    只要在DMA模块的read_handler和write_handler处理设置断点,我们就可以动态地调试DMA的处理逻辑了。

    经过了以上的准备工作之后,我们就可以开始调试一个具体的系统了。我是以DLX为目标进行调试的,在调试过程中,我们可以一步一步地了解到从计算机加电后执行BIOS开机自检程序,到加载MBR,通过LILO一步一步地把Linux操作系统启动起来的全过程,一个奇妙的旅程即将开始!

    有人已经这样做了,并且根据Bochs的代码,出了一本书:http://www.mouseos.com/books/x86-64/index.html

  • 相关阅读:
    分享一个利用HTML5制作的海浪效果代码
    3人从小公寓创业,到世界最大引擎公司,Unity创始人谈14年...
    决策树--从原理到实现
    使用行为树(Behavior Tree)实现游戏AI
    FSM(状态机)、HFSM(分层状态机)、BT(行为树)的区别
    相遇3000亿美金之巅,阿里腾讯战力与血值几何?
    深入浅出聊Unity3D项目优化:从Draw Calls到GC
    Unity性能优化专题---腾讯牛人分享经验
    高通创始人复盘30年发展历程
    Gym
  • 原文地址:https://www.cnblogs.com/long123king/p/3414884.html
Copyright © 2020-2023  润新知