Tabla de contenido:

Use Machine.net si desea crear una máquina virtual independiente de la plataforma con solo C#.
En este momento, solo es compatible con X64, así como el chipset Intel 8253, 8259 y HPET. Actualmente me estoy centrando en resolver los problemas existentes con el emulador X64 en lugar de agregar más posibilidades. Se agregarán nuevas características una vez que el emulador X64 comience a mostrar éxito, sin embargo , aún puede sugerir nuevas características si lo desea.
Apoye el proyecto colocando una estrella en este repositorio.
Cuando usa Machine.net, el paquete Nuget llamado ICED también está instalado. Este es un paquete popular para decodificar y codificar instrucciones, y se usa en Machine.net para decodificar las instrucciones.
Para comenzar, use ICED.intel para ensamblar las instrucciones que necesitamos. En nuestro caso, es:
mov rcx, 150
rep add rax, 4
Eso sería:
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 ) ;
}Y ahora, simplemente cree una nueva instancia de la clase Cpuruntime. Puede pasar la cantidad de memoria (en bytes) y el número de puertos de E/S. En nuestro caso, esta es una memoria de 64 kb y 8 puertos de E/S:
var runtime = new CpuRuntime ( memorySize : 65536 , ioPortCount : 8 ) ; Puede invocar el método .Run(in Instruction) en CpuRuntime para invocar una instrucción. Invocemos todas las instrucciones:
foreach ( var instr in instrs )
{
runtime . Run ( in instr ) ;
} El método .Run puede ser ligeramente limitante en caso de que desee admitir instrucciones de salto y rama. En ese caso, es posible cargar bytecode directo en RAM y cargarlo desde allí:
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 ( ) ;
}
}De hecho, X es 42. El método .rununtilnotbusy comienza a ejecutar instrucciones desde la memoria en CS: RIP o simplemente RIP por defecto. Tiene dos sobrecargas: una que toma int y otra que no. El que hace representa la cantidad máxima de instrucciones que debe ejecutar, lo cual es más seguro de usar si se preocupa en el caso del bucle infinito. El que no toma ningún parámetro seguirá funcionando hasta la instrucción HLT.
También puede acceder a la propiedad .ProcessorRegisters de la clase CpuRuntime para inspeccionar registros y banderas de CPU e incluso modificarlos en cualquier momento. Para ver el resultado, veremos el registro rax :
Console . WriteLine ( runtime . ProcessorRegisters . Rax ) ;Esto da como resultado 600, lo cual es correcto.
Para adjuntar dispositivos externos, puede hacer su propio puerto de E/S y poner lo que desee en las operaciones de lectura/escritura (sí, incluso crear una nueva ventana y mostrarlo, si lo desea).
var cpu = new CpuRuntime ( ioPortCount : 8 ) ;
ulong x = 42uL ;
cpu . IOPorts [ 1 ] = new InputOutputPort (
read : ( ) =>
{
return 1234uL ;
} ,
write : ( value ) =>
{
x = value ;
} ) ;Por ejemplo, si ejecutamos el siguiente código en la CPU emulada:
mov eax , 7777
out 1 , eax
in eax , 1Luego, puede ver que la CPU envió 7777 al puerto de E/S indexado 1 (los puertos de E/S están indexados a partir de 0), y EAX es igual a 1234 (consulte esta prueba unitaria, es bastante bueno):
Assert . Equal ( 7777uL , x ) ;
Assert . Equal ( 1234uL , cpu . ProcessorRegisters . Eax ) ;
// No failuresPara construir Machine.net, debe tener instalado .NET 8.0. Puede descargarlo desde el sitio web oficial de .NET.
Si prefiere con Visual Studio:
Si prefiere con .NET CLI:
dotnet build . O escriba dotnet build -c Release para construir el modo de lanzamiento (por ejemplo, si desea usar Machine.net en aplicaciones del mundo real con optimización habilitada).| Nombre de la biblioteca | URL Nuget | Código fuente de este repositorio |
|---|---|---|
| Machine.x64.component.registros | Haga clic para redirigir a la fuente |
Licencia MIT. Copyright (c) Winscripter, 2023-2024.