Índice:

Use o Machine.net se desejar criar uma máquina virtual independente da plataforma com apenas C#.
No momento, apenas o X64 é suportado, bem como o chipset e o hpet de Intel 8253, 8259. Atualmente, estou me concentrando em resolver problemas existentes com o emulador X64, em vez de adicionar mais possibilidades. Novos recursos serão adicionados quando o emulador X64 começar a mostrar sucesso, no entanto , você ainda pode sugerir novos recursos, se desejar.
Apoie o projeto colocando uma estrela neste repositório.
Quando você usa o Machine.net, o pacote NUGET chamado Iced também está instalado. Este é um pacote popular para decodificar e codificar instruções e é usado no Machine.net para decodificar instruções.
Para começar, use Iced.Intel para montar as instruções de que precisamos. No nosso caso, é:
mov rcx, 150
rep add rax, 4
Isso seria:
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 ) ;
}E agora, basta criar uma nova instância da classe cpuruntime. Você pode passar a quantidade de memória (em bytes) e o número de portas de E/S. No nosso caso, esta é uma memória de 64kb e 8 portas de E/S:
var runtime = new CpuRuntime ( memorySize : 65536 , ioPortCount : 8 ) ; Você pode invocar o método .Run(in Instruction) no CpuRuntime para invocar uma instrução. Vamos invocar todas as instruções:
foreach ( var instr in instrs )
{
runtime . Run ( in instr ) ;
} O método .Run pode ser um pouco limitador caso você queira apoiar instruções de salto e ramificação. Nesse caso, é possível carregar bytecode direto na RAM e carregá -lo a partir daí:
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 fato, x é 42. O método .Rununtilnotbusy começa a executar instruções da memória em CS: RIP ou apenas RIP por padrão. Ele tem duas sobrecargas: uma que leva o INT e outro que não. O que faz representa a quantidade máxima de instruções que ele deve executar, o que é mais seguro de usar se você estiver preocupado no caso de loop infinito. Aquele que não recebe parâmetros continuará sendo executado até a instrução HLT.
Você também pode acessar a propriedade .ProcessorRegisters da classe CpuRuntime para inspecionar os registros e sinalizadores da CPU e até modificá -los a qualquer momento. Para ver o resultado, veremos o registro rax :
Console . WriteLine ( runtime . ProcessorRegisters . Rax ) ;Isso resulta em 600, o que está correto.
Para anexar dispositivos externos, você pode fazer sua própria porta de E/S e colocar o que quiser em operações de leitura/gravação (sim, até criando uma nova janela e exibindo -a, se quiser).
var cpu = new CpuRuntime ( ioPortCount : 8 ) ;
ulong x = 42uL ;
cpu . IOPorts [ 1 ] = new InputOutputPort (
read : ( ) =>
{
return 1234uL ;
} ,
write : ( value ) =>
{
x = value ;
} ) ;Por exemplo, se executarmos o seguinte código na CPU emulada:
mov eax , 7777
out 1 , eax
in eax , 1Em seguida, você pode ver que a CPU enviou 7777 para a porta de E/S indexada 1 (as portas de E/S são indexadas a partir de 0) e o EAX é igual a 1234 (confira este teste de unidade, é muito legal):
Assert . Equal ( 7777uL , x ) ;
Assert . Equal ( 1234uL , cpu . ProcessorRegisters . Eax ) ;
// No failuresPara construir o Machine.net, você precisa ter o .NET 8.0 instalado. Você pode baixá -lo no site oficial .NET.
Se você preferir com o Visual Studio:
Se você preferir com .NET CLI:
dotnet build . Ou digite dotnet build -c Release para criar no modo de liberação (por exemplo, se você deseja usar o MACHING.NET em aplicativos do mundo real com otimização ativado).| Nome da biblioteca | NUGET URL | Código -fonte neste repositório |
|---|---|---|
| Machine.x64.component.registers | Clique para redirecionar para a fonte |
MIT Licença. Copyright (c) Winscript, 2023-2024.