目錄:

如果您想使用C#創建獨立於平台的虛擬機,請使用Machine.net。
目前,僅支持X64,以及Intel 8253,8259芯片組和HPET。目前,我專注於解決X64模擬器的現有問題,而不是添加更多可能性。一旦X64仿真器開始顯示成功,將添加新功能,但是,如果需要,您仍然可以建議新功能。
請通過將星星放在此存儲庫上來支持該項目。
當您使用Machine.net時,也安裝了稱為ICED的Nuget軟件包。這是用於解碼和編碼說明的流行軟件包,它在Machine.net中用於解碼說明。
首先,使用Ice.Intel組裝我們需要的說明。在我們的情況下,是:
mov rcx, 150
rep add rax, 4
那將是:
using Iced . Intel ;
using Machine . X64 . Runtime ;
using static Iced . Intel . AssemblerRegisters ;
var assembler = new Assembler ( 64 ) ;
assembler . mov ( rcx , 150 ) ;
assembler . rep . add ( rax , 4 ) ;
var stream = new MemoryStream ( ) ;
var streamCodeWriter = new StreamCodeWriter ( stream ) ;
assembler . Assemble ( streamCodeWriter , rip : 0uL ) ;
stream . Position = 0 ;
var reader = new StreamCodeReader ( stream ) ;
var decoder = Decoder . Create ( 64 , reader ) ;
decoder . IP = 0 ;
var instrs = new List < Instruction > ( ) ;
while ( stream . Position < stream . Length )
{
decoder . Decode ( out var instr ) ;
instrs . Add ( instr ) ;
}現在,只需創建CPURUNTIME類的新實例即可。您可以傳遞內存量(以字節為單位)和I/O端口的數量。在我們的情況下,這是64kb內存和8個I/O端口:
var runtime = new CpuRuntime ( memorySize : 65536 , ioPortCount : 8 ) ;您可以在CpuRuntime上調用.Run(in Instruction)方法來調用指令。讓我們調用所有說明:
foreach ( var instr in instrs )
{
runtime . Run ( in instr ) ;
}如果您想支持跳躍和分支說明,則.Run方法可能會略有限制。在這種情況下,可以將Direct Bytecode加載到RAM中並從那裡加載:
var cpu = new CpuRuntime ( ioPortCount : 8 ) ;
ulong x = 0uL ;
cpu . IOPorts [ 1 ] = new InputOutputPort (
read : ( ) =>
{
return 1234uL ;
} ,
write : ( value ) =>
{
x = value ;
} ) ;
byte [ ] code = CodeGen . MakeBranchTestCode_1 ( ) ;
cpu . LoadProgram ( code , 0uL ) ;
cpu . ProcessorRegisters . Cs = 0 ;
cpu . ProcessorRegisters . Rip = 0 ;
cpu . Use8086Compatibility ( ) ;
cpu . SetRsp ( 0x400uL ) ;
try
{
cpu . RunUntilNotBusy ( 35 ) ;
}
catch ( ArithmeticException )
{
throw new InvalidOperationException ( cpu . LastOrExecutingInstruction . Code . ToString ( ) ) ;
}
Assert . Equal ( 42uL , x ) ;
static class CodeGen
{
public static byte [ ] MakeBranchTestCode_1 ( )
{
var assembler = new Assembler ( 64 ) ;
Label lblA = assembler . CreateLabel ( "A" ) ;
Label lblC = assembler . CreateLabel ( "C" ) ;
Label lblB = assembler . CreateLabel ( "B" ) ;
assembler . Label ( ref lblA ) ;
assembler . mov ( ax , 42 ) ;
assembler . @out ( 1 , ax ) ;
assembler . call ( lblB ) ;
assembler . Label ( ref lblC ) ;
assembler . mov ( ax , bx ) ;
assembler . @out ( 1 , ax ) ;
assembler . hlt ( ) ;
assembler . Label ( ref lblB ) ;
assembler . mov ( bx , ax ) ;
assembler . call ( lblC ) ;
return Assemble ( assembler ) ;
}
private static byte [ ] Assemble ( Assembler assembler )
{
using var memoryStream = new MemoryStream ( ) ;
assembler . Assemble ( new StreamCodeWriter ( memoryStream ) , 0uL ) ;
return memoryStream . ToArray ( ) ;
}
}實際上,x是42。 .rununtilnotbusy方法開始從CS:RIP或僅撕裂的內存運行指令。它有兩個過載:一個佔INT,一個不帶INT。確實代表應運行的最大指令量的一個,如果您在無限循環的情況下擔心,則可以使用。在HLT指令之前,不使用任何參數的一個將繼續運行。
您還可以訪問CpuRuntime類的.ProcessorRegisters屬性,以檢查CPU寄存器和標誌,甚至可以隨時修改它們。要查看結果,我們將查看rax寄存器:
Console . WriteLine ( runtime . ProcessorRegisters . Rax ) ;這將導致600,這是正確的。
要連接外部設備,您可以製作自己的I/O端口,並將您想要的任何內容都放在讀/寫操作中(是的,即使創建一個新窗口並在需要的話上顯示)。
var cpu = new CpuRuntime ( ioPortCount : 8 ) ;
ulong x = 42uL ;
cpu . IOPorts [ 1 ] = new InputOutputPort (
read : ( ) =>
{
return 1234uL ;
} ,
write : ( value ) =>
{
x = value ;
} ) ;例如,如果我們在模擬CPU上執行以下代碼:
mov eax , 7777
out 1 , eax
in eax , 1然後,您可以看到CPU已將7777發送給I/O端口索引1(從0開始索引I/O端口),EAX等於1234(請查看此單元測試,非常酷):
Assert . Equal ( 7777uL , x ) ;
Assert . Equal ( 1234uL , cpu . ProcessorRegisters . Eax ) ;
// No failures要構建Machine.net,您需要安裝.NET 8.0。您可以從官方.NET網站下載它。
如果您喜歡Visual Studio:
如果您喜歡.NET CLI:
dotnet build 。或鍵入dotnet build -c Release以在發行模式下構建(例如,如果要在現實世界應用中使用Machine.net並啟用了“啟用優化”)。| 庫名稱 | Nuget URL | 此存儲庫上的源代碼 |
|---|---|---|
| Machine.x64.component.registers | 單擊重定向以源 |
麻省理工學院許可證。版權(C)Winscripter,2023-2024。