Оглавление:

Используйте machine.net, если вы хотите создать независимую от платформу виртуальную машину только C#.
Прямо сейчас поддерживается только X64, а также чипсет Intel 8253, 8259 и HPET. В настоящее время я сосредотачиваюсь на решении существующих проблем с эмулятором X64, а не на добавлении больше возможностей. Новые функции будут добавлены после того, как эмулятор X64 начнет демонстрировать успех, однако вы все равно можете предложить новые функции, если хотите.
Пожалуйста, поддерживайте проект, поместив звезду в этом репозитории.
Когда вы используете machine.net, также устанавливается пакет Nuget, называемый ICED. Это популярный пакет для инструкций декодирования и кодирования, и он используется в Machine.net для декодирования инструкций.
Для начала используйте iced.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. Вы можете передать количество памяти (в байтах) и количество портов ввода -вывода. В нашем случае это память 64 КБ и 8 портов ввода/вывода:
var runtime = new CpuRuntime ( memorySize : 65536 , ioPortCount : 8 ) ; Вы можете вызвать метод .Run(in Instruction) на CpuRuntime , чтобы вызвать инструкцию. Давайте призваем все инструкции:
foreach ( var instr in instrs )
{
runtime . Run ( in instr ) ;
} Метод .Run может быть немного ограничивающим в случае, если вы хотите поддерживать инструкции по прыжкам и филиалам. В этом случае можно загрузить прямой байт -код в ОЗУ и загрузить его оттуда:
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, а один - нет. Тот, который представляет максимальный объем инструкций, которые он должен работать, что безопаснее в использовании, если вы беспокоитесь в случае бесконечной петли. Тот, который не принимает каких -либо параметров, будет продолжаться до тех пор, пока инструкция HLT.
Вы также можете получить доступ к собственности .ProcessorRegisters класса CpuRuntime , чтобы осмотреть регистры и флаги процессора и даже изменить их в любое время. Чтобы увидеть результат, мы просматриваем регистр rax :
Console . WriteLine ( runtime . ProcessorRegisters . Rax ) ;Это приводит к 600, что правильно.
Чтобы прикрепить внешние устройства, вы можете сделать свой собственный порт ввода -вывода и поместить все, что вы хотите в операциях чтения/записи (да, даже создание нового окна и отображение его, если хотите).
var cpu = new CpuRuntime ( ioPortCount : 8 ) ;
ulong x = 42uL ;
cpu . IOPorts [ 1 ] = new InputOutputPort (
read : ( ) =>
{
return 1234uL ;
} ,
write : ( value ) =>
{
x = value ;
} ) ;Например, если мы выполняем следующий код на эмулированном процессоре:
mov eax , 7777
out 1 , eax
in eax , 1Затем вы можете видеть, что ЦП отправил 7777 на порт ввода/вывода, индексированный 1 (порты ввода/вывода индексируются, начиная с 0), а 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 | Нажмите, чтобы перенаправить, чтобы |
MIT Лицензия. Copyright (C) Winscripter, 2023-2024.