1. 程式人生 > >為Bochs新增新的虛擬硬碟格式

為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 <stdarg.h>
#include <stdio.h>
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的引導進度條,並且沒有造成惡劣後果(但是,我可不保證你能和我一樣好運)。

​ 怎麼樣?很簡單吧。原始碼之下了無祕密。那就趕緊動手吧。