Inhaltsverzeichnis:

Verwenden Sie Machine.net, wenn Sie eine plattformunabhängige virtuelle Maschine mit nur C#erstellen möchten.
Derzeit wird nur X64 sowie der Intel 8253, 8259 Chipsatz und HPET unterstützt. Derzeit konzentriere ich mich darauf, vorhandene Probleme mit dem X64 -Emulator zu lösen, anstatt mehr Möglichkeiten hinzuzufügen. Neue Funktionen werden hinzugefügt, sobald der X64 -Emulator den Erfolg zeigt. Sie können jedoch weiterhin neue Funktionen vorschlagen, wenn Sie möchten.
Bitte unterstützen Sie das Projekt, indem Sie einen Stern in dieses Repository platzieren.
Wenn Sie machine.net verwenden, wird auch das Nuget -Paket icED installiert. Dies ist ein beliebtes Paket für die Dekodierung und Codierung von Anweisungen und wird in machine.net verwendet, um Anweisungen zu dekodieren.
Verwenden Sie zunächst ICED.intel, um die Anweisungen zusammenzustellen, die wir benötigen. In unserem Fall ist es:
mov rcx, 150
rep add rax, 4
Das wäre:
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 ) ;
}Und jetzt erstellen Sie einfach eine neue Instanz der Cpuruntime -Klasse. Sie können die Speichermenge (in Bytes) und die Anzahl der E/A -Ports übergeben. In unserem Fall ist dies 64 KB Speicher und 8 E/A -Ports:
var runtime = new CpuRuntime ( memorySize : 65536 , ioPortCount : 8 ) ; Sie können die .Run(in Instruction) auf CpuRuntime aufrufen, um eine Anweisung aufzurufen. Rufen wir alle Anweisungen auf:
foreach ( var instr in instrs )
{
runtime . Run ( in instr ) ;
} Die .Run -Methode kann geringfügig einschränkend sein, falls Sie Sprung- und Zweiganweisungen unterstützen möchten. In diesem Fall ist es möglich, direkte Bytecode in RAM zu laden und von dort aus zu laden:
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 ( ) ;
}
}In der Tat ist x 42. Die .rununtilnotbusy -Methode beginnt standardmäßig Anweisungen aus dem Speicher bei CS: RIP oder RIP. Es hat zwei Überladungen: eine, die int nimmt und eine, die es nicht tut. Diejenige, die die maximale Anweisungen darstellt, die er ausführen sollte, ist sicherer zu bedienen, wenn Sie sich im Falle einer unendlichen Schleife Sorgen machen. Derjenige, der keine Parameter annimmt, läuft bis zur HLT -Anweisung weiter.
Sie können auch auf das Eigentum von .ProcessorRegisters der CpuRuntime -Klasse zugreifen, um CPU -Register und -Flags zu inspizieren und diese sogar jederzeit zu ändern. Um das Ergebnis zu sehen, werden wir das rax -Register anzeigen:
Console . WriteLine ( runtime . ProcessorRegisters . Rax ) ;Dies führt zu 600, was korrekt ist.
Um externe Geräte anzuhängen, können Sie Ihren eigenen E/A -Anschluss herstellen und in Lese-/Schreibvorgängen alles einfügen (ja, sogar ein neues Fenster erstellen und es anzeigen, wenn Sie möchten).
var cpu = new CpuRuntime ( ioPortCount : 8 ) ;
ulong x = 42uL ;
cpu . IOPorts [ 1 ] = new InputOutputPort (
read : ( ) =>
{
return 1234uL ;
} ,
write : ( value ) =>
{
x = value ;
} ) ;Wenn wir beispielsweise den folgenden Code auf der emulierten CPU ausführen:
mov eax , 7777
out 1 , eax
in eax , 1Anschließend können Sie sehen, dass die CPU 7777 an E/A -Port indexiert ist 1 (E/A -Ports werden ab 0 indexiert) und EAX entspricht 1234 (siehe diesen Einheitstest, es ist ziemlich cool):
Assert . Equal ( 7777uL , x ) ;
Assert . Equal ( 1234uL , cpu . ProcessorRegisters . Eax ) ;
// No failuresUm Machine.net zu erstellen, müssen Sie .NET 8.0 installieren lassen. Sie können es von der offiziellen .NET -Website herunterladen.
Wenn Sie mit Visual Studio bevorzugen:
Wenn Sie mit .NET CLI bevorzugen:
dotnet build . Oder geben Sie dotnet build -c Release ein, um im Release -Modus zu erstellen (z. B. wenn Sie Machine.net in realen Apps mit aktivierter Optimierung verwenden möchten).| Bibliotheksname | Nuget URL | Quellcode auf diesem Repo |
|---|---|---|
| Machine.x64.component.register | Klicken Sie hier, um zur Quelle umzuleiten |
MIT -Lizenz. Copyright (C) Winscripter, 2023-2024.