多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php框架 > 框架設計 > 自制虛擬機系列第一部分:構思及匯編器

自制虛擬機系列第一部分:構思及匯編器

來源:程序員人生   發布時間:2016-07-11 08:16:09 閱讀次數:3672次

? Conmajia & icemanind 2012

本文根據How to Create Your Own Virtual Machine系列文章編譯,并進行了大量改造(已征得作者同意)。


瀏覽:上篇、下篇
下載:源代碼、英文教程(PDF)


敘言

By Conmajia

各位,你們正在瀏覽的這個系列的文章將從零開始,帶你1步1步設計并實現1個完全可運行的虛擬機(Virtual Machine)。我們將要使用C#語言,基于Microsoft .NET Framework 2.0運行庫來完成全部虛擬機的制作(出于兼容性斟酌,也是為了將主要精力集中在設計上)。因此,你需要具有最基本的.NET程序開發知識。也就是說,最少你應當會使用Visual Studio 2005(或更高版本),并且能成功運行自己的「Hello World」程序。
在開始設計前,讓我們先來了解1下虛擬機的相干知識。
虛擬機是1種摹擬硬件環境的中間件(Middleware),是1種高度隔離的軟件容器,它可以運行自己的操作系統和利用程序,就好像它是1臺物理計算機1樣。虛擬機的行動完全類似于1臺物理計算機,它包括自己的虛擬(即基于軟件實現的)CPU,有些乃至擴大了RAM、硬盤和網絡接口卡(NIC)等虛擬硬件。
操作系統沒法分辨虛擬機與物理機之間的差異,利用程序和網絡中的其他計算機也沒法分辨。即便是虛擬機本身也認為自己是1臺「真實的」計算機。不過,虛擬機完全由虛擬機軟件組成,不含任何硬件組件。因此,虛擬機具有物理硬件所沒有的很多獨特優勢。

虛擬機的優勢

1般而言,虛擬機具有以下4個關鍵特點:
1. 兼容性:虛擬機與所有標準的 x86 計算機都兼容
2. 隔離:虛擬機相互隔離,就像在物理上是分開的1樣
3. 封裝:虛擬機將全部計算環境封裝起來
4. 獨立于硬件:虛擬機獨立于底層硬件運行

好了,下面就開始設計我們自己的虛擬機。

設計虛擬機

我們要為這個虛擬機繪制1個藍圖。我們給虛擬機起名為:SunnyApril(簡稱SA)。為了簡化設計,SA被設計成1個16位的機器(這意味著她的CPU位寬是16-bit的)。這樣1來,SA能夠支持的地址空間就是0000H-FFFFH。現在我們為SA加入5個寄存器(Register)。寄存器是計算機硬件的1個重要概念和組件。寄存器是具有有限存貯容量(通常是1、2字節)的高速存儲部件,用來暫存指令、數據或地址。幾近所有的CPU和虛擬機中都包括有內建的寄存器。簡單來講,寄存器就是「CPU內部的內存」。
為了簡單,我們只設計了5個寄存器,分別是ABDXYAB寄存器是8位寄存器,可以保存0-FFH的無符號數或是80H-7FH的有符號數。XYD寄存器都是16位的,可以保存0-FFFFH的無符號數或是8000H-7FFFH的有符號數。一樣是為了設計簡便,目前我們只斟酌無符號數的情況,有符號數將在后面研究浮點數的時候1起進行。
D寄存器是1個特殊的16位寄存器。它的值是由AB寄存器的值合并而成,A保存了D的高8位值,B保存了低8位值。例如A寄存器值為3CHB寄存器值為10H,則D寄存器值為3C10H。反之,如果修改D寄存器值為07C0H,則A寄存器值變成07HB寄存器值變成C0H
下面的圖形象地說明了各寄存器的規格和之間的關系。

為了讓我們的虛擬性能在第1時間「反饋」運行結果,我們從64KB的內存空間中留出4000字節的空間(A000H-AFA0H)作「顯示器」緩存。我們模仿DOS下的匯編語言,用其中2000字節用于保存顯示字符(這樣可以得到80x25的字符屏幕),2000字節用于保存每一個字符的樣式。每一個樣式字節低3位分別表示前風景的紅、綠、藍色彩值,第4位表示明暗度,5⑺位一樣,用于表示背景色彩。樣式字節的最高位本來是表示是不是閃爍字符,但在我們的設計中不需要這個功能,所以直接疏忽。
接下來的工作就是設計能讓虛擬機運行起來的指令集(即字節碼)了。指令集和我們自制的「匯編語言」1起設計,簡便起見,先設計4個指令,如圖所示。

LDA指令(字節碼01H)為例,該指令將操作數(#41H)存入A寄存器,即「Load A」。由于操作數尋址方式太多,這里簡單地用「#」符號開端,表示「立即數」(模仿51單片機的匯編語言)。以「H」結尾的數字表示為16進制,類似的有「O」(8進制)、「B」(2進制)和「D」(10進制,可以省略)。
END指令(字節碼04H)表示程序結束。同時它后面的「標簽」表示程序的起始標簽,用于標注程序運行的開始位置。標簽是使用「:」半角冒號結尾的單獨成行的字母開頭的字符串,如START標簽就這樣書寫:

START:

接下來是設計編譯后的字節碼文件格式。大部份的2進制文件格式都是以1串「魔法數字」字符串開頭的。例如,DOS/Windows文件用「MZ」開頭,Java2進制文件用4字節的數字3405691582開始,用16進制表示就是「CAFEBABE」(咖啡寶貝)。我們的SunnyApril就使用「CONMAJIA」作為魔法數字。魔法數字以后是文件體偏移量,表示文件體(即程序字節碼)在文件中的起始位置。接著是程序長度,即文件體長度。履行地址表示字節碼履行起始地址,固定為0。(后續可能會改變)偏移段用于保存額外的數據或中斷向量表等,其長度為「偏移量⑴3」字節。文件頭后就是文件體,保存了程序編譯后的全部字節碼。文件結構參見下圖。

匯編器

現在我們可以開始動手設計匯編器了。這個匯編器將能夠把我們寫好的匯編源程序編譯后寫入到可以供虛擬機運行的2進制字節碼文件中。匯編文件格式以下:

[標簽:] <指令><空白><操作數>[空白]<換行>

其中,方括號[]中的內容是可選的。

注:以下內容和源代碼經過較大幅度的改造和優化,和原文差異較大,注意區分。

這就是我們的匯編源程序:

START: LDA #65 LDX #A000H STA X END START

這個程序的功能就是簡單地把字符A輸出到屏幕的左上角。第1行代碼定義了START標簽。第2即將立即數65(即ASCII代碼’A’)存入A寄存器。第3即將立即數A000H(即顯示緩存的起始地址,參見設計1節)存入X寄存器。第4行代碼將A寄存器中的值(65)存入X寄存器中的數值(A000H)代表的內存地址。最后用END結束程序。
下面我們運行Visual Studio,新建1個「Windows窗口利用程序」項目,選擇.NET Framework版本為2.0,仿照下面的截圖設計窗體。

其中,textBox1.Readonly屬性設置為truenumericUpDown1.Hexadecimal屬性設置為true
首先在窗體類中建立以下的變量。

Dictionary<string, UInt16> labelDict; UInt16 binaryLength; UInt16 executionAddress;

定義1個寄存器枚舉。

enum Registers { Unknown = 0, A = 4, B = 2, D = 1, X = 16, Y = 8 }

在窗體的構造函數中初始化變量和控件。

public Form1() { InitializeComponent(); labelDict = new Dictionary<string, ushort>(); binaryLength = 0; executionAddress = 0; numericUpDown1.Value = 0x200; }

button1的功能是打開「文件閱讀」對話框選擇需要匯編的源文件。雙擊button1,在生成的Click事件中輸入以下代碼:

OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "SunnyApril Assembly Files(*.asm)|*.asm"; ofd.DefaultExt = "asm"; ofd.FileName = string.Empty; if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) textBox1.Text = ofd.FileName; else textBox1.Clear();

button2功能是履行匯編,并生成2進制字節碼文件,主要代碼以下:

if (textBox1.Text == string.Empty) return; labelDict.Clear(); binaryLength = (UInt16)numericUpDown1.Value; FileInfo fi = new FileInfo(textBox1.Text); BinaryWriter output; FileStream fs = new FileStream( Path.Combine( fi.DirectoryName, fi.Name + ".sab"), FileMode.Create ); output = new BinaryWriter(fs); // magic word output.Write('C'); output.Write('O'); output.Write('N'); output.Write('M'); output.Write('A'); output.Write('J'); output.Write('I'); output.Write('A'); // org output.Write((UInt16)numericUpDown1.Value); // scan to ORG and start writing byte-code output.Seek((int)numericUpDown1.Value, SeekOrigin.Begin); // parse source code line-by-line TextReader input = File.OpenText(textBox1.Text); string line; while ((line = input.ReadLine()) != null) { parse(line.ToUpper(), output); dealedSize += line.Length; Invoker.Set(progressBar1, "Value", (int)((float)dealedSize / (float)totalSize * 100)); } input.Close(); // binary length & execution address (7 magic-word, 2 org before) output.Seek(10, SeekOrigin.Begin); output.Write(binaryLength); output.Write(executionAddress); output.Close(); fs.Close(); MessageBox.Show("Done!");

在這個方法中,通過1個while逐行解析源代碼(原作者是全文解析),解析方法以下:

private void parse(string line, BinaryWriter output) { // eat white spaces and comments line = cleanLine(line); if (line.EndsWith(":")) // label labelDict.Add(line.TrimEnd(new char[] { ':' }), binaryLength); else { // code Match m = Regex.Match(line, @"(\w+)\s(.+)"); string opcode = m.Groups[1].Value; string operand = m.Groups[2].Value; switch (opcode) { case "LDA": output.Write((byte)0x01); output.Write(getByteValue(operand)); binaryLength += 2; break; case "LDX": output.Write((byte)0x02); output.Write(getWordValue(operand)); binaryLength += 3; break; case "STA": output.Write((byte)0x03); // NOTE: No error handling. Registers r = (Registers)Enum.Parse(typeof(Registers), operand); output.Write((byte)r); binaryLength += 2; break; case "END": output.Write((byte)0x04); if (labelDict.ContainsKey(operand)) { output.Write(labelDict[operand]); binaryLength += 2; } binaryLength += 1; break; default: break; } } }

其中用到了讀取字節(byte)操作數的內部方法,以下所示。稍作改進可以很方便地支持多種數制。讀取字(Word)操作數的方法與此類似,不再另作說明。

private byte getByteValue(string operand) { byte ret = 0; if (operand.StartsWith("#")) { operand = operand.Remove(0, 1); char last = operand[operand.Length - 1]; if (char.IsLetter(last)) switch (last) { case 'H': // hex ret = Convert.ToByte(operand.Remove(operand.Length - 1, 1), 16); break; case 'O': // oct ret = Convert.ToByte(operand.Remove(operand.Length - 1, 1), 8); break; case 'B': // bin ret = Convert.ToByte(operand.Remove(operand.Length - 1, 1), 2); break; case 'D': // dec ret = Convert.ToByte(operand.Remove(operand.Length - 1, 1), 10); break; } else ret = byte.Parse(operand); } return ret; }

運行匯編器,對前面保存的demo1.asm文件進行匯編,得到demo1.sab2進制字節碼文件(SpringApril Binaries),該文件內容以下:

可以見到,匯編器忠實地完成了我們交代的任務,正確計算了文件大小,在0200H位置處開始,匯編出的字節碼為「01 00 02 00 00 03 10 04 00 02」,下面我們對比源程序進行檢驗。為了便于視察,再寫1遍源程序。

START: LDA #65 LDX #A000H STA X END START

第1行動START標簽,將地址0200H存入緩存(在文件中沒有體現)。
第2行LDA指令,存入字節碼01H,然后存入單字節操作數(A寄存器是8位寄存器)65,即41H
第3行LDX指令,存入字節碼02H,然后存入雙字節操作數(X寄存器是16位寄存器)A000H,由于計算機采取小端模式(低位在前),所以在文件中是以「00 A0」的情勢存儲的。
第4行STA指令,存入字節碼03H,然后存入Registers.X枚舉值(16,即01H)。
第5行END指令,存入字節碼04H,然后存入START標簽地址0200H(2字節,仍以小端模式存儲)。
根據以上分析,我們制作的匯編器完全符合設計。
下1步,我們將開始設計虛擬機,敬請期待。
歡迎各種建議意見。

(第1部份 完)

? Conmajia 2012, icemanind 2012

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 国产在线拍揄自揄视精品不卡 | 成人不卡 | 成人精品视频在线观看播放 | 午夜羞羞视频 | japanese18日本护士 | 女人18一级特级毛片免费看 | 中文精品久久久久国产不卡 | 爱爱欧美在线观看视频 | 亚洲欧美日韩高清在线看 | 91精品一区二区综合在线 | 秋霞午夜一级理论片久久 | 99影视在线视频免费观看 | 亚洲精品日韩在线一区 | 欧美亚洲另类在线 | 最近高清中文字幕免费 | 99久久国产综合精品女不卡 | 在线成人免费观看国产精品 | h网址在线观看 | 亚洲精品国产字幕久久不卡 | 欧美亚洲一区 | 久久国产欧美 | 欧美jizz19性欧美 | 日本一区二区日本免费 | 欧美在线精品永久免费播放 | 老司机精品视频午夜免费视频 | 春意午夜影院 | 天天综合视频网 | 亚洲国产欧美一区 | 欧美一级片网 | 欧美日韩亚洲国内综合网俺 | 成人国产一区二区三区精品 | 久久久久久久尹人综合网亚洲 | yellow中文字幕在线高清 | 一区二区三区四区在线免费观看 | 免费看欧美毛片大片免费看 | 欧美成人性色 | 日本最新伦中文字幕 | 欧美性视频在线 | 精品女人| 在线二区 | 久久综合精品国产一区二区三区无 |