首页
社区
课程
招聘
[原创]bochs2.2.1源代码分析 初始化过程
发表于: 2018-5-14 13:18 7771

[原创]bochs2.2.1源代码分析 初始化过程

2018-5-14 13:18
7771

出于个人兴趣,我决定读一读bochs的源码,一搜才发现网上的文档竟如此稀缺,除了官方稍显混乱的几本手册,能够找到的相对比较完整的资料就只有喻强的bochs项目源码分析与注释了,而且这个文档对于细节讲得不多,我初看的时候是一头雾水,断断续续摸索了好一阵,总算才理清了一些思路。由于代码篇幅较多,加上个人原因前后经历的时间也略长,中间有很多内容不是很熟悉了,想到了写点东西,一方面将之前所看部分重新梳理,另一方面可以方便后来人。

由于个人时间有限,我仅将现阶段已看完的部分整理出来,有很多细节没有搞透。虽然已经尽我所能,无奈个人能力不足,如有错误敬请各位批评指正,也欢迎大家补充。

Bochs有众多版本,每个版本有对应不同环境的多个变体,本文分析的是2.2.1 for win32版本(因为喻强的手册就是这个版本),用到的工具有VC6.0, VM12.5.5, SI4.0和Understand5.0

简单说来:

1.VC6.0+Windows 7+VMware12.5.5编译源码进行动态调试,bochs2.2.1 for win32的源码中已经建立好了工程,使用VC6.0直接编译即可

2.SI4.0+Understand5.0结合源码进行静态分析,用SI是因为习惯,用Understand是因为它那强大的图表功能

两个SI的技巧:

1.用SI4.0分析源码时使用Conditional Parsing功能,将不必要分析的代码略过;

2.由于代码中大量使用了SI未预定义的宏定义,需要修改c.tom文件以识别这些宏定义。

bochs2.2.1使用C++编写,涉及到了类,继承,虚函数等概念,并用到了一些宏,这里仅对阅读源码时必备的知识作简单阐述,对具体概念和背后机理不作介绍。

在宏定义里,a##b就是把a,b连接起来:

假如定义了#define f(a,b) a##b则f(1,2)就是12,得到的是一个数12。

#a就是把a转化成字符串:

假如定义了#define g(a) #a  则g(f(1,2))得到的是f(1,2)转化成的字符串”12”。

类的实例化过程需要调用构造函数,子类的实例化在调用自己的构造函数前需要调用父类的构造函数,对于子类没有显式调用父类构造函数的情况,可分为以下几种:

1.父类没有声明构造函数

(1).如果子类也没有声明自己的构造函数,则父类和子类均由编译器生成默认的构造函数;

(2).如果子类中声明了构造函数(无参或者带参),在创建子类对象时,先调用父类默认的构造函数(编译器自动生成),再调用子类的构造函数。

2.父类声明了无参构造函数

如果子类的构造函数没有显式地调用父类的构造,它将会调用父类的无参构造函数。也就是说,父类的无参构造函数将会被隐式地调用。

3.父类只声明了带参构造函数

因为父类只有带参的构造函数,所以如果子类中的构造函数没有显示地调用父类的带参构造函数,则会报错。也就是说,如果父类只声明了带参构造函数,子类则必须显示地调用父类的构造函数。

在bochs源码中大多以初始化列表的形式调用父类的带参构造函数:

bx_param_c::bx_param_c (bx_id id, char *name, char *description) : bx_object_c (id)    (siminterface.cpp, 797L)

或者以初始化列表形式初始化某一成员变量,但该变量是某个类的实例,因此还是会调用类的构造函数:

BX_CPU_C::BX_CPU_C(): bx_cpuid(0) ,local_apic (this)    (init.cpp, 34L)

local_apic是bx_local_apic_c类型的成员变量,为了初始化这个成员变量将会调用bx_local_apic_c的构造函数

一般而言,模块中定义了两种函数:导出函数(export function)和内部函数(internal function)。 
导出函数可以被其它模块调用,内部函数仅在定义它们的模块内部使用。

在要导出的函数、类、数据的声明前加上__declspec(dllexport)的修饰符,用来表示导出;

类似的,在声明前加上__declspec(dllimport),可以表示某个类、函数是从外部模块中导入的。

实际中通常会遇到以下情况:

两个模块使用一个函数或者类,一个是提供者,一个是使用者,二者之间的接口是头文件。在头文件中声明方法,在提供者那里函数或类应该被声明为__declspec(dllexport),在使用者那里,函数应该被声明为__declspec(dllimport)。

二者使用同一个头文件时可以使用条件编译:定义一个宏,针对提供者和使用者,设置不同的值。

#ifdef LABEL_VAR

#define LABEL_API __declspec(dllexport)

#else

#define LABEL_API __declspec(dllimport)

#endif

#include <string>

using std::string;

class LABEL_API LABEL_CLASS

{

STATEMENT

};

只需在提供者的头文件中定义LABEL_VAR,而使用者不定义LABEL_API,则能够很方便的实现函数形式的一致性。


在bochs中使用了宏定义BOCHSAPI实现上述功能

#if defined(WIN32) || defined(__CYGWIN__)

#  if BX_PLUGINS && defined(BX_PLUGGABLE)

//   #warning I will import DLL symbols from Bochs main program.

#    define BOCHSAPI __declspec(dllimport)

#  elif BX_PLUGINS

//   #warning I will export DLL symbols.

#    define BOCHSAPI __declspec(dllexport)

#  endif

可以认为使用BOCHSAPI宏修饰的一般都是全局使用的函数、结构或类

在bochs中还会经常看到BX_XXX_THIS这种形式的宏


以dma的相关宏为例(dma.h, 32L)

#if BX_USE_DMA_SMF

#  define BX_DMA_SMF  static

#  define BX_DMA_THIS theDmaDevice->

#else

#  define BX_DMA_SMF

#  define BX_DMA_THIS this->

#endif

如果有多个实例,目标成员变量或函数将被定义为静态,静态成员指同一个类所有实例共享的成员:当某个类的实例修改了静态成员变量,其修改值为该类的其它所有实例所见,静态成员函数可以理解为在类中但跟类的实例无关的函数。

非静态成员每个类的实例都会有一个。

解析命令行参数并修改相应的配置信息,读取用户配置信息,将相关的信息保存到内存中,初始化外设,初始化内存,初始化CPU,加载映像,开始模拟(顺序并非严格如此,过程存在交叉的情况)

初始化阶段最重要的工作就是根据用户输入的配置信息让CPU以不同的参数执行

bochs中定义了很多全局变量,有些变量的类型是一个类,在mainCRTStartup的_cinit上下断点,可以看到是在_initterm(__xc_a, __xc_z)中调用了这些类的构造函数。

具体说来在main前就实例化的对象有:

1.static logfunctions thePluginLog;  (main.cpp, 88L)

代码位于logio.cpp, 261L

2.bx_pc_system_c bx_pc_system; (main.cpp, 99L)

代码位于pc_system.cpp, 58L

3.bx_devices_c bx_devices; (devices.cpp, 42L)

代码位于devices.cpp, 48L

4.bx_slowdown_timer_c bx_slowdown_timer; (slowdown_timer.cpp, 53L)

代码位于slowdown_timer.cpp, 55L

5.bx_virt_timer_c bx_virt_timer; (virt_timer.cpp, 111L)

代码位于virt_timer.cpp, 132L

6.bx_pit_c bx_pit; (pit_wrap.cpp, 53L)

代码位于pit_wrap.cpp, 84L

7.class bx_ioapic_c bx_ioapic;(ioapic.cpp, 10L)

代码位于ioapic.cpp, 32L 还显式调用了父类的构造函数bx_generic_apic_c(apic.cpp, 39L)

8.class bx_vnet_locator_c : public eth_locator_c {...} bx_vnet_match; (eth_vnet.cpp, 219L)

代码位于eth_vnet.cpp, 221L 还显式调用了父类的构造函数eth_locator_c(eth.cpp, 50L)

9.class bx_win32_locator_c : public eth_locator_c {...} bx_win32_match; (eth_win32.cpp, 222L)

代码位于eth_win32.cpp, 224L 还显式调用了父类的构造函数eth_locator_c

10.class bx_null_locator_c : public eth_locator_c {...} bx_null_match; (eth_null.cpp, 67L)

代码位于eth_null.cpp, 69L 还显式调用了父类的构造函数eth_locator_c

11.BOCHSAPI BX_CPU_C    bx_cpu; (cpu.cpp, 60L)

代码位于init.cpp, 32L 还调用了local_apic的构造函数bx_local_apic_c (apic.cpp, 390L)

12.BOCHSAPI BX_MEM_C    bx_mem; (cpu.cpp, 61L)

代码位于misc_mem.cpp, 41L

13.bxPageWriteStampTable pageWriteStampTable; (cpu.cpp, 70L)

代码位于icache.h, 51L&&52L

14.bx_keymap_c bx_keymap;  (keymap.cpp, 75L)

代码位于keymap.cpp, 79L

入口函数main中将命令行参数传给了bx_startup_flags,这是一个名为bx_startup_flags_t的结构体(siminterface.h, 1525L),用于保存命令行参数。main最终调用了bxmain()函数,正如名字所暗示的那样,这个函数才是bochs的真正入口,main只是起到了一个封装的作用。


在bx_init_siminterface中实例化了两个对象

logfunctions():siminterface_log和bx_real_sim_c():SIM

定义在logio.cpp中

iofunction主要提供了IO的文件句柄和文件名,以及日志类的实例

logfunction主要实现了日志功能,对不同日志级别(类型)可以有不同的处理方式

日志级别(类型)有五种DEBUG, INFO, ERROR, PANIC和PASS

处理方式有四种IGNORE, REPORT, ASK和FATAL

默认的处理方式为

int logfunctions::default_onoff[N_LOGLEV] = {

  ACT_IGNORE,  //debug

  ACT_REPORT,  //info

  ACT_REPORT,  //error

  ACT_FATAL,    //panic

  ACT_FATAL    //pass

};

main.cpp中定义了一个全局变量static logfunctions thePluginLog;在mainCRTStartup中执行它的构造函数时初始化了全局变量io,参数是FILE*类型的stderr,在初始化io的过程中文件句柄logfd设为stderr,文件名logfn设为”/dev/stderr”,io创建的过程中会初始化一个新的logfunction对象。

logfunction的成员函数set(io)会将当前logfuction加入到io的logfn_list数组中,也就是说一个iofunction可以对应多个logfunction;而一个logfunction只对应一个iofunction


bochs中用到的主要类都以logfunctions为基类,而logfunctions类存在无参数的构造函数,因此以上的这些类都将默认的调用logfunctions的构造函数

定义在siminterface.cpp和siminterface.h中

bx_real_sim_c的构造函数中创建了一个数组param_registry(siminterface.cpp, 228L),这个数组中存放了所有的用户配置信息,bx_real_sim_c类是bx_simulator_interface_c类的子类,对于这两个类,源码中有较为详细的解释,以下内容来源于注释:

传统的bochs界面是一个有许多VGA显示块和按钮的窗口,开发者需要在gui下实现不同的版本,x.cpp, win32.cpp, beos.cpp, macintosh.cpp等,让gui.h和gui.cpp定义独立于平台的部分函数。很少有参数能够在运行时修改。

而现在bochs中引入了两个和用户界面相关的概念:CI(Configuration Interface)和VGAW(VGA display Window)

VGAW就是bochs的图形化界面;

CI是让用户能够编辑一系列配置和运行时选项而设计的用户接口,一些选项如内存大小,是否开启网卡应该在运行前启动,而如软盘映像,每秒种指令数等参数可以在运行时修改。

由于用户界面可以以多种形式表现,标准输入输出,图形化界面或web浏览器等等,不同的用户界面形式可能需要不同的CI实现版本,甚至可能以加载插件的形式实现用户界面,因此开发者将CI封装起来,设计了siminterface类,用户只需使用siminterface提供的接口就可以修改CI,这样一来使得CI的界面可以很容易的改变形式,同时siminterface也成为CI和虚拟机之间的桥梁。

CI和虚拟机是完全分离的,两者都可以独立编译,事实上CI的代码中不包含bochs.h头文件(这是虚拟机的核心头文件),所有CI对虚拟机的行为都已经封装在siminterface中了。

基类bx_simulator_interface_c中只包含虚函数,并且定义了CI使用的接口,实现均在子类bx_real_sim_c中。

在siminterface的实现中有一个很重要的概念——参数类,用户配置的参数是由一些基本的类型组成,如字符串,整型,bool量等,bochs中对这些类型进行了封装,每种类型都有各自的成员变量及方法,所有的定义都在siminterface.h中


使用understand能够很方便的得到UML类图,小图看不清,附件有jpg图


在UML类图中使用三层矩形框表示类,第一层显示类的名称;第二层是字段和属性;第三层是类的方法。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2018-5-14 19:18 被chugee编辑 ,原因: 重新编辑图片
上传的附件:
收藏
免费 1
支持
分享
最新回复 (8)
雪    币: 23080
活跃值: (3432)
能力值: (RANK:648 )
在线值:
发帖
回帖
粉丝
2
楼主你好,文章中的图片不清晰,麻烦上传下清晰的图片
2018-5-14 16:44
0
雪    币: 423
活跃值: (102)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
3
不好意思,已经重新上传了。传图片时总是失真,只好放压缩包里了
2018-5-14 19:21
0
雪    币: 23080
活跃值: (3432)
能力值: (RANK:648 )
在线值:
发帖
回帖
粉丝
4
感谢分享!
2018-5-14 19:50
0
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
2018-5-14 23:29
0
雪    币: 300
活跃值: (2477)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢楼主分享
2018-5-15 08:22
0
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
感谢分享,冒昧地问一下,喻强的手册是指?
2018-5-15 09:19
0
雪    币: 423
活跃值: (102)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
8
CSDN上面有,Bochs项目源码分析与注释.pdf
2018-5-15 09:21
0
雪    币: 8224
活跃值: (1296)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
感谢楼主分享,内容很详细
2018-5-16 10:28
0
游客
登录 | 注册 方可回帖
返回
//