目录:

如果您想使用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。