為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的引導進度條,並且沒有造成惡劣後果(但是,我可不保證你能和我一樣好運)。
怎麼樣?很簡單吧。原始碼之下了無祕密。那就趕緊動手吧。