Table des matières:

Utilisez Machine.net si vous souhaitez créer une machine virtuelle indépendante de la plate-forme avec juste C #.
À l'heure actuelle, seul X64 est pris en charge, ainsi que le chipset Intel 8253, 8259 et HPET. Actuellement, je me concentre sur la résolution des problèmes existants avec l'émulateur X64 plutôt que sur l'ajout de plus de possibilités. De nouvelles fonctionnalités seront ajoutées une fois que l'émulateur X64 commencera à montrer le succès, mais vous pouvez toujours suggérer de nouvelles fonctionnalités si vous le souhaitez.
Veuillez soutenir le projet en plaçant une étoile sur ce référentiel.
Lorsque vous utilisez Machine.net, le package NuGet appelé Iced est également installé. Il s'agit d'un package populaire pour les instructions de décodage et d'encodage, et il est utilisé dans machine.net pour décoder les instructions.
Pour commencer, utilisez Iced.intel pour assembler les instructions dont nous avons besoin. Dans notre cas, c'est:
mov rcx, 150
rep add rax, 4
Ce serait:
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 ) ;
}Et maintenant, créez simplement une nouvelle instance de la classe cpuruntime. Vous pouvez passer la quantité de mémoire (en octets) et le nombre de ports d'E / S. Dans notre cas, il s'agit de la mémoire de 64 Ko et 8 ports d'E / S:
var runtime = new CpuRuntime ( memorySize : 65536 , ioPortCount : 8 ) ; Vous pouvez invoquer la méthode .Run(in Instruction) sur CpuRuntime pour invoquer une instruction. Invoquons toutes les instructions:
foreach ( var instr in instrs )
{
runtime . Run ( in instr ) ;
} La méthode .Run peut être légèrement limite au cas où vous souhaitez prendre en charge les instructions de saut et de branche. Dans ce cas, il est possible de charger des bytecodes directs dans RAM et de le charger à partir de là:
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 ( ) ;
}
}En effet, x est 42. La méthode .runUtilNotbusy commence à exécuter des instructions à partir de la mémoire à CS: RIP ou simplement RIP par défaut. Il a deux surcharges: une qui prend INT et une qui ne le fait pas. Celui qui représente la quantité maximale d'instructions qu'il devrait exécuter, ce qui est plus sûr à utiliser si vous vous inquiétez dans le cas d'une boucle infinie. Celui qui ne prend aucun paramètre continuera à fonctionner jusqu'à l'instruction HLT.
Vous pouvez également accéder à la propriété .ProcessorRegisters de la classe CpuRuntime pour inspecter les registres et les drapeaux du processeur et même les modifier à tout moment. Pour voir le résultat, nous consulterons le registre rax :
Console . WriteLine ( runtime . ProcessorRegisters . Rax ) ;Il en résulte 600, ce qui est correct.
Pour attacher des périphériques externes, vous pouvez créer votre propre port d'E / S et mettre ce que vous voulez dans les opérations de lecture / écriture (oui, même en créant une nouvelle fenêtre et en l'affichant, si vous le souhaitez).
var cpu = new CpuRuntime ( ioPortCount : 8 ) ;
ulong x = 42uL ;
cpu . IOPorts [ 1 ] = new InputOutputPort (
read : ( ) =>
{
return 1234uL ;
} ,
write : ( value ) =>
{
x = value ;
} ) ;Par exemple, si nous exécutons le code suivant sur le CPU imité:
mov eax , 7777
out 1 , eax
in eax , 1Ensuite, vous pouvez voir que le CPU a envoyé 7777 au port d'E / S indexé 1 (les ports d'E / S sont indexés à partir de 0), et EAX est égal à 1234 (consultez ce test unitaire, c'est assez cool):
Assert . Equal ( 7777uL , x ) ;
Assert . Equal ( 1234uL , cpu . ProcessorRegisters . Eax ) ;
// No failuresPour construire Machine.net, vous devez faire installer .net 8.0. Vous pouvez le télécharger à partir du site officiel .NET.
Si vous préférez avec Visual Studio:
Si vous préférez avec .net CLI:
dotnet build . Ou tapez dotnet build -c Release à construire en mode de version (par exemple si vous souhaitez utiliser Machine.net dans des applications réelles avec optimisation activée).| Nom de bibliothèque | URL Nuget | Code source sur ce dépôt |
|---|---|---|
| Machine.x64.component.registers | Cliquez pour rediriger vers Source |
Licence MIT. Copyright (c) Winscripter, 2023-2024.