为Bochs添加新的虚拟硬盘格式

1.1、问题的引出

​ 对于虚拟机来说,硬盘其实就是一个文件。虚拟机里面的操作系统对硬盘的所有操作都是对该硬盘的操作。说白了就是虚拟机欺骗的操作系统,让它认贼作父。当然这个贼并没有任何的恶意,相反对于用户来说,反而是好事。比如我们备份虚拟机的硬盘就非常方便,把虚拟硬盘文件备份一下就可以了。当然,虚拟机也可以使用真实硬盘。这可是千真万确的呦。但是,很不幸的是Bochs并没有提供这种支持。
​ 当然啦,大多数情况下,我们使用Bochs都是用来调试自己写的小操作系统。所以,也没有使用真实硬盘的需求。但是,对于各种虚拟机来说,都自成一家,分别定义了自己的虚拟硬盘文件格式。这样一来,我们使用VBox安装的操作系统就无法在Qemu里面使用,当然也无法在Bochs里面使用。当然,我们完全可以在Qemu里面再装一个嘛。硬盘空间问题先不说,Bochs里面怎么安装个Windows Xp需要多长时间啊。真的是很难以估算啊(因为我还从来没有尝试成功过,即便是我耐心的等了两三个小时)。而,有时候我们又特别想看看Windows XP的引导过程,怎么办呢?调试一下呗。用什么调试?当然是Bochs啦。但是,怎么在Bochs里面安装Windows XP啊。
很早的时候,我们想了一个办法。那就是用Qemu安装,然后再用Bochs来运行并调试。之所以这样做是因为,Qemu的一种虚拟硬盘格式和Bochs是相同的。那就是Flat类型。但是,如果想看看Vista的呢?怎么办?还用老办法?告诉你,行不通喽。因为在Qemu里面安装不了Vista(原因不再我们的讨论之列)。
怎么办呢?是不是没解了?
​ 怎么会没解呢?别忘了Bochs可是开源的。开源就意味着你完全可以对Bochs进行改造。既然它原本不支持,那么就给他添加上嘛?(会不会很难呢?接着看吧。)

1.2、Bochs现在已经支持的硬盘格式

​ Bochs已经支持flat, concat, external, dll, sparse, vmware3, vmware4, undoable, growing, volatile, z-undoable, z-volatile等12种之多。对于各种类型的详细描述可以查看Bochs的使用手册。我们经常使用的应该就是flat和growing。不过要说的是上面列出的类型中很多已经被禁用掉了。比如dll类型,这种类型就是通过一个dll去读写一个虚拟硬盘文件。只是很可惜,该类型被禁用掉了。原因我就清楚了。因为,即便是打开这个宏后就无法编译成功。那么,我们不妨给它添加一种新的类型。

1.3、为Bochs添加一种新的硬盘格式

​ 对于每个虚拟机都会有一个配置文件(例如bochsrc.bxrc)。其中,会有一行描述了硬盘的信息:
ata0-master: type=disk, mode=flat, path="c.img", cylinders=615, heads=6, spt=17
这一行的意思就是第1个IDE接口上的主硬盘(ata0-master)插的是一个硬盘(type=disk),其类型是平坦型(mode=flat)的,虚拟硬盘文件是c.img,该硬盘的CHS参数为:615,6,17。
​ 于是,Bochs启动的时候就会执行如下代码:
iodev\harddrv.cpp==>void bx_hard_drive_c::init(void)第282行

if (SIM->get_param_enum("type", base)->get() == BX_ATA_DEVICE_DISK) {
        BX_DEBUG(("Hard-Disk on target %d/%d",channel,device));
        BX_HD_THIS channels[channel].drives[device].device_type = IDE_DISK;
        sprintf(sbtext, "HD:%d-%s", channel, device?"S":"M");
        BX_HD_THIS channels[channel].drives[device].statusbar_id =
          bx_gui->register_statusitem(sbtext);

        int cyl = SIM->get_param_num("cylinders", base)->get();
        int heads = SIM->get_param_num("heads", base)->get();
        int spt = SIM->get_param_num("spt", base)->get();
        Bit64u disk_size = (Bit64u)cyl * heads * spt * 512;

        /* instantiate the right class */
        image_mode = SIM->get_param_enum("mode", base)->get();
        switch (image_mode) {

          case BX_ATA_MODE_FLAT:
            BX_INFO(("HD on ata%d-%d: '%s' 'flat' mode ", channel, device,
                     SIM->get_param_string("path", base)->getptr()));
            channels[channel].drives[device].hard_drive = new default_image_t();
            break;
                     ......
          default:
            BX_PANIC(("HD on ata%d-%d: '%s' unsupported HD mode : %s:%d", channel, device,
                      SIM->get_param_string("path", base)->getptr(),
                      atadevice_mode_names[image_mode]));
            break;
              }

​ 上面的代码的意思很清楚,就是从配置文件中读出配置信息,如CHS参数。然后根据硬盘模式判断到底是哪种类型。然后将该硬盘的hard_drive指向相关的类。比如Flat类型的是default_image_t,Growing就是growing_image_t……而这些类又都有一个相同的基类device_image_t。所以,无论格式如何变化,读写接口都是相同的。如果找不到呢?那就通知用户,您的虚拟硬盘类型不支持。
​ 现在应该就很明了了。如果我们想添加一种新的格式,只需要新定义一种类型。然后对应的对应一个相关的访问类即可。
​ 考虑到,我们的虚拟硬盘格式可能会复杂多变。我们不妨把读写文件的代码写到一个Dll里面。这样,以后当我们需要变换硬盘格式时就很简单了。只需要把我们写的那个Dll换一下就可以了。那么,如果我们想扩展多个类型呢?反不能每多一种都改一次Bochs的源码吧?解决起来很简单,我们只需要把Dll的名字和硬盘的类型名结合起来,然后在Default里面进行处理,再通过类型名加载对应的Dll不就可以了。例如我们添加VBoxGrowing类型的硬盘其Dll就叫做VBoxGrowing.dll。为了防止重名,我们规定这些Dll都被放在程序所在的VDisk子目录下。所以,我们将上面的代码修改如下:

......
          default:
                     BX_INFO(("HD on ata%d-%d: '%s' '%s' mode ", channel, device,
                            SIM->get_param_string("path", base)->getptr()));
                            channels[channel].drives[device].hard_drive = new dll2_image_t(SIM->get_param_string("path", base)->getptr());
            break;
        }

​ 注意,上面所说的仅仅是主要改动。并不是全部改动。其实,主要是对配置文件的读取部分修改了一点,将mode的enum类型改成了string类型。
​ dll2_image_t就是我们用来调用对应Dll的类。定义如下:

typedef int (*dll2_open)(const char * pathname, int flag);
typedef void (*dll2_close)();
typedef ULONG64 (*dll2_seek)(ULONG64 offset, int whence);
typedef ULONG64 (*dll2_size)();
typedef long (*dll2_read)(void* buf, size_t count);
typedef long (*dll2_write)(const void* buf, size_t count);
class dll2_image_t : public device_image_t
{
public:
       dll2_image_t(const char* dllName);
       ~dll2_image_t();
       // Open a image. Returns non-negative if successful.
       int open(const char* pathname){return open(pathname, O_RDWR);};

       // Open an image with specific flags. Returns non-negative if successful.
       int open(const char* pathname, int flags);

       // Close the image.
       void close(){m_close();};

       // Position ourselves. Return the resulting offset from the
       // beginning of the file.
       Bit64s lseek(Bit64s offset, int whence){return m_seek(offset, whence);};

       // Read count bytes to the buffer buf. Return the number of
       // bytes read (count).
       ssize_t read(void* buf, size_t count){return m_read(buf, count);};

       // Write count bytes from buf. Return the number of bytes
       // written (count).
       ssize_t write(const void* buf, size_t count){return m_write(buf, count);};

private:
       dll2_open m_open;
       dll2_close m_close;
       dll2_seek m_seek;
       dll2_read m_read;
       dll2_write m_write;
       HMODULE m_hDll;

};

dll2_image_t::dll2_image_t(const char* dllName):device_image_t()
{
       char szDllName[MAX_PATH] = {0};
       sprintf(szDllName, "vdisk\\%s.dll", dllName);
       m_hDll = LoadLibrary(szDllName);
       if (m_hDll)
       {
              m_open = (dll2_open)GetProcAddress(m_hDll, "vdOpen");
              if (!m_open)
              {
                     BX_PANIC(("No vdOpen() in vdisk.dll!"));
              }
              m_close = (dll2_close)GetProcAddress(m_hDll, "vdClose");
              if (!m_close)
              {
                     BX_PANIC(("No vdClose() in vdisk.dll!"));
              }
              m_seek = (dll2_seek)GetProcAddress(m_hDll, "vdSeek");
              if (!m_seek)
              {
                     BX_PANIC(("No vdSeek() in vdisk.dll!"));
              }
              m_read = (dll2_read)GetProcAddress(m_hDll, "vdRead");
              if (!m_read)
              {
                     BX_PANIC(("No vdRead() in vdisk.dll!"));
              }
              m_write = (dll2_write)GetProcAddress(m_hDll, "vdWrite");
              if (!m_write)
              {
                     BX_PANIC(("No vdWrite() in vdisk.dll!"));
              }
       }
       else
       {
              BX_PANIC(("Can't find out Hard Drive DLL:%s!", szDllName));
              m_open = NULL;
              m_close = NULL;
              m_seek = NULL;
              m_read = NULL;
              m_write = NULL;
       }
}

dll2_image_t::~dll2_image_t()
{
       if (m_hDll)
       {
              FreeLibrary(m_hDll);
              m_hDll = NULL;
       }
}

int dll2_image_t::open(const char* pathname, int flag)
{
       if (!m_open)
       {
              return -1;
       }
       int iRet = m_open(pathname, flag);
       if (iRet < 0)
       {
              BX_ERROR(("Open file(%s:%d) failed!", pathname, flag));
       }

       dll2_size size = (dll2_size)GetProcAddress(m_hDll, "vdSize");
       if (!size)
       {
              BX_PANIC(("No vdSize() in vdisk.dll!"));
              return -1;
       }

       hd_size = size();
       return iRet;
}

​ 代码非常简单。就不再详细描述了。基本逻辑就是通过类型名找到对应的Dll并导出相关的函数。然后,依次调用而已。
​ 下面我们再看看一个简单的硬盘类型格式,其实就是Flat格式。我们不妨定义它为myFlat类型。

#include 
#include 
HANDLE g_hFile = NULL;
char g_szFileName[MAX_PATH] = {0};

#define LOG_FILE "myFlat.log"

void LOG(LPTSTR lpszFormat, ...)
{
       va_list arg;
       char szBuf[1024] = {0};
       va_start (arg, lpszFormat);
       vsprintf (szBuf, lpszFormat, arg);
       va_end (arg);

       OutputDebugString(szBuf);

       FILE * f = fopen(LOG_FILE, "a+");
       if (f)
       {
              fwrite(szBuf, 1, strlen(szBuf), f);
              fclose(f);
              f = NULL;
       }
}

int vdOpen(const char * pathname, int flag)
{
       g_hFile = CreateFile(pathname,
              GENERIC_READ|GENERIC_WRITE,
              FILE_SHARE_READ|FILE_SHARE_WRITE,
              NULL,
              OPEN_EXISTING,
              0,
              0);
       if (g_hFile == INVALID_HANDLE_VALUE)
       {
              LOG("myFlat::Open file %s failed!flag=%d, ErrorNO=%ld",
                     pathname,
                     flag,
                     GetLastError());
              return -1;
       }
       strcpy(g_szFileName, pathname);
       return (int)g_hFile;
}

void vdClose()
{
       if (g_hFile != INVALID_HANDLE_VALUE)
       {
              CloseHandle(g_hFile);
              g_hFile = INVALID_HANDLE_VALUE;
              memset(g_szFileName, 0, sizeof(g_szFileName));
       }
}

ULONG64 vdSeek(ULONG64 offset, int whence)
{
       LARGE_INTEGER liOffset;
       liOffset.QuadPart = offset;
       liOffset.LowPart = SetFilePointer(g_hFile, liOffset.LowPart, &liOffset.HighPart, whence);
       if (liOffset.LowPart == INVALID_FILE_SIZE)
       {
              LOG("vdisk::SetFilePointer(%s, %I64u, %d) failed!", g_szFileName, liOffset.QuadPart, whence);
       }
       return liOffset.QuadPart;
}

#define HDDEV_HEAD "\\\\.\\physicaldrive"

ULONG64 vdSize()
{
       if (strncmp(g_szFileName, HDDEV_HEAD, strlen(HDDEV_HEAD)) == 0)
       {
              // TODO:支持获取的大小硬盘
              return 0;
       }
       DWORD dwHigh = 0;
       LARGE_INTEGER liSize;
       liSize.LowPart = GetFileSize(g_hFile, &dwHigh);
       if (liSize.LowPart == INVALID_FILE_SIZE)
       {
              LOG("myFlat::GetFileSize(%s) failed!", g_szFileName);
              return 0;
       }
       return liSize.QuadPart;
}

long vdRead(void* buf, size_t count)
{
       DWORD dwRead = 0;
       if (!ReadFile(g_hFile, buf, count, &dwRead, 0))
       {
              LOG("myFlat::ReadFile(%s, %lu) failed!", g_hFile, count);
              return 0;
       }
       return dwRead;
}

long vdWrite(const void* buf, size_t count)
{
       DWORD dwWrite = 0;
       if (!WriteFile(g_hFile, buf, count, &dwWrite, 0))
       {
              LOG("myFlat::WriteFile(%s, %lu) failed!", g_hFile, count);
              return 0;
       }
       return dwWrite;
}

​ 同样,我也不对这段代码进行详细的注解(它们实在是太简单了,不是吗?)。需要说的是,这个格式可是支持真实硬盘的,不信的话你就把你的虚拟硬盘文件指定为:\.\physicaldrive0试一下。如果CHS参数正确的话,你肯定可以看到你操作系统的引导画面。不过,这样做是有一定危险的喔。你想想啊,你现在正在运行着的系统在哪儿啊?不就是在\.\physicaldrive0这个文件里面吗?如果再在虚拟机里面运行一遍会怎样呢?可以告诉的是,我试过,且已经很到了Windows的引导进度条,并且没有造成恶劣后果(但是,我可不保证你能和我一样好运)。

​ 怎么样?很简单吧。源码之下了无秘密。那就赶紧动手吧。

你可能感兴趣的:(为Bochs添加新的虚拟硬盘格式)