O Spice86 é uma ferramenta para executar, engenheiro reverso e reescrever programas de DOS de modo real para os quais o código -fonte não está disponível.
A liberação está disponível no NUGET.
Os pré-liberações também estão disponíveis na página de liberação
Nota: Esta é uma porta e uma continuação do Java Spice86 original.
Requer .NET 9 e é executado no Windows, MacOS e Linux.
Reescrever um programa apenas do binário é uma tarefa difícil.
O Spice86 é uma ferramenta que ajuda a fazê -lo com uma abordagem de divisão e conquista metódica.
Processo geral:
Este é um programa .NET, você o executa com a linha de comando regular ou a execução do DOTNET. Exemplo com a execução de um programa chamado File.exe:
Spice86 -e file.exe
Arquivos com e arquivos BIOS também são suportados.
Recomenda -se definir a variável de ambiente Spice86_Dumps_Folder apontando para onde o emulador deve despejar os dados do tempo de execução. Se a variável estiver definida ou se -parâmetro -regredidoDDATAdirectory for passado, o emulador despejará várias informações sobre a execução lá. Se nada estiver definido, os dados serão despejados no diretório atual. Se já houver dados, o emulador o carregará primeiro e o concluirá, você não precisará começar de zero a cada vez!
--Ems (Default: false) Enables EMS memory. EMS adds 8 MB of memory accessible to DOS programs through the EMM Page Frame.
--A20Gate (Default: false) Disables the 20th address line to support programs relying on the rollover of memory addresses above the HMA (slightly above 1 MB).
-m, --Mt32RomsPath Zip file or directory containing the MT-32 ROM files
-c, --CDrive Path to C drive, default is exe parent
-r, --RecordedDataDirectory Directory to dump data to when not specified otherwise. Working directory if blank
-e, --Exe Required. Path to executable
-a, --ExeArgs List of parameters to give to the emulated program
-x, --ExpectedChecksum Hexadecimal string representing the expected SHA256 checksum of the emulated program
-f, --FailOnUnhandledPort (Default: false) If true, will fail when encountering an unhandled IO port. Useful to check for unimplemented hardware. false by default.
-g, --GdbPort gdb port, if empty gdb server will not be created. If not empty, application will pause until gdb connects
-o, --OverrideSupplierClassName Name of a class that will generate the initial function information. See documentation for more information.
-p, --ProgramEntryPointSegment (Default: 4096) Segment where to load the program. DOS PSP and MCB will be created before it.
-u, --UseCodeOverride (Default: true) <true or false> if false it will use the names provided by overrideSupplierClassName but not the code
-i, --InstructionsPerSecond <number of instructions that have to be executed by the emulator to consider a second passed> if blank will use time based timer.
-t, --TimeMultiplier (Default: 1) <time multiplier> if >1 will go faster, if <1 will go slower.
-d, --DumpDataOnExit (Default: true) When true, records data at runtime and dumps them at exit time
-h, --HeadlessMode (Default: false) Headless mode. If true, no GUI is shown.
-l, --VerboseLogs (Default: false) Enable verbose level logs
-w, --WarningLogs (Default: false) Enable warning level logs
-s, --SilencedLogs (Default: false) Disable all logs
-i, --InitializeDOS (Default: true) Install DOS interrupt vectors or not.
--StructureFile Path to a C header file that describes the structures in the application. Works best with exports from IDA or Ghidra
--help Display this help screen.
--version Display version information.
Spice86 fala o protocolo remoto do GDB:
Você precisa especificar uma porta para o servidor GDB iniciar ao iniciar o Spice86:
Spice86 --GdbPort=10000
O Spice86 aguardará o GDB se conectar antes de iniciar a execução para que você possa configurar pontos de interrupção e assim por diante.
Aqui está como se conectar do cliente da linha de comando do GDB e como definir a arquitetura:
(gdb) target remote localhost:10000
(gdb) set architecture i8086
Você pode adicionar pontos de interrupção, passo, visualizar a memória e assim por diante.
Exemplo com um ponto de interrupção no VGA VRAM escreve:
(gdb) watch *0xA0000
Montagem de visualização:
(gdb) layout asm
Removendo um ponto de interrupção:
(gdb) remove 1
Procurando uma sequência de bytes na memória (endereço inicial 0, comprimento F0000, ASCII bytes de 'Spice86' String):
(gdb) find /b 0x0, 0xF0000, 0x53, 0x70, 0x69, 0x63, 0x65, 0x38, 0x36
O GDB não suporta o endereçamento segmentado do Modo Real X86, portanto, os ponteiros precisam se referir ao endereço físico real na memória. VRAM no endereço A000: 0000 seria 0XA0000 no GDB.
Da mesma forma, a variável $ PC no GDB será exposta pelo Spice86 como o endereço físico apontado pelo CS: IP.
A lista de comandos personalizados pode ser exibida assim:
(gdb) monitor help
(gdb) monitor dumpall
Despeja tudo o que é descrito abaixo em uma foto. Os arquivos são criados na pasta Dump, conforme explicado aqui, vários arquivos são produzidos:
Quebrar após os ciclos da CPU emulados em X:
(gdb) monitor breakCycles 1000
Quebrar no final do programa emulado:
(gdb) monitor breakStop
#Refreshing tela ou buffers durante a depuração
(gdb) monitor vbuffer refresh
Para uma experiência agradável e produtiva com o GDB, o cliente Seergdb é altamente recomendado.
Exemplo concreto com Dune Cryo aqui.
Primeiro execute seu programa e verifique se tudo funciona bem no Spice86. Se você encontrar problemas, isso pode ser devido a recursos de hardware / DOS / BIOS não implementados.
Quando o Spice86 sair, ele deve despejar dados na pasta atual ou na pasta especificada pela variável Env
Abra os dados em Ghidra com o Spice86-Ghidra-Plugin e gere código. Você pode importar os arquivos gerados em um projeto de modelo que você gera através dos tempos Spice86-Dotnet:
dotnet new spice86.project
Você pode fornecer seu próprio código C# para substituir o código de montagem original do programa.
O Spice86 pode entrar em entrada uma instância de spice86.core.emulator.function.ioverridesuplier que cria um mapeamento entre o endereço de memória das funções e suas substituições C#.
Para um exemplo completo, você pode verificar o código -fonte de criogênico.
Aqui está um exemplo simples de como seria:
namespace My . Program ;
// This class is responsible for providing the overrides to spice86.
// There is only one per program you reimplement.
public class MyProgramOverrideSupplier : IOverrideSupplier {
public IDictionary < SegmentedAddress , FunctionInformation > GenerateFunctionInformations ( int programStartSegment ,
Machine machine ) {
Dictionary < SegmentedAddress , FunctionInformation > res = new ( ) ;
// In more complex examples, overrides may call each other
new MyOverrides ( res , programStartSegment , machine ) ;
return res ;
}
public override string ToString ( ) {
return "Overrides My program exe. class is " + GetType ( ) . FullName ;
}
}
// This class contains the actual overrides. As the project grows, you will probably need to split the reverse engineered code in several classes.
public class MyOverrides : CSharpOverrideHelper {
private MyOverridesGlobalsOnDs globalsOnDs ;
public MyOverrides ( IDictionary < SegmentedAddress , FunctionInformation > functionInformations , int segment , Machine machine ) {
// "myOverides" is a prefix that will be appended to all the function names defined in this class
base ( functionInformations , "myOverides" , machine ) ;
globalsOnDs = new MyOverridesGlobalsOnDs ( machine ) ;
// incUnknown47A8_0x1ED_0xA1E8_0xC0B8 will get executed instead of the assembly code when a call to 1ED:A1E8 is performed.
// Also when dumping functions, the name myOverides.incUnknown47A8 or instead of unknown
// Note: the segment is provided in parameter as spice86 can load executables in different places depending on the configuration
DefineFunction ( segment , 0xA1E8 , "incDialogueCount47A8" , IncDialogueCount47A8_0x1ED_0xA1E8_0xC0B8 ) ;
DefineFunction ( segment , 0x0100 , "addOneToAX" , AddOneToAX_0x1ED_0x100_0x1FD0 ) ;
}
public Action IncDialogueCount47A8_0x1ED_0xA1E8_0xC0B8 ( ) {
// Accessing the memory via accessors
globalsOnDs . SetDialogueCount47A8 ( globalsOnDs . GetDialogueCount47A8 ( ) + 1 ) ;
// Depends on the actual return instruction performed by the function, needed to be called from the emulated code as
// some programs like to mess with the stack ...
return NearRet ( ) ;
}
private Action AddOneToAX_0x1ED_0x100_0x1FD0 ( ) {
// Assembly for this would be
// INC AX
// RETF
// Note that you can access the whole emulator to change the state in the overrides.
state . AX ++ ;
return NearRet ( ) ;
}
}
// Memory accesses can be encapsulated into classes like this to give names to addresses and make the code shorter.
public class MyOverridesGlobalsOnDs : MemoryBasedDataStructureWithDsBaseAddress {
public DialoguesGlobalsOnDs ( Machine machine ) {
base ( machine ) ;
}
public void SetDialogueCount47A8 ( int value ) {
this . SetUint8 ( 0x47A8 , value ) ;
}
public int GetDialogueCount47A8 ( ) {
return this . GetUint8 ( 0x47A8 ) ;
}
}Lembre-se : você deve dizer ao Spice86 para usar as substituições do código da assembléia com o argumento da linha de comando "--UseCodeOverride true" ao depurar seu projeto.
Juntamente com o caminho obrigatório para o seu programa DOS, aprovou com o argumento --exepath.
O Spice86 vem com um depurador embutido que pode ser usado para depurar o programa emulado. É um depurador simples que permite inspecionar a memória, a desmontagem, os registros e a pilha.
O visualizador de estrutura permite inspecionar a memória de maneira estruturada. É útil inspecionar a memória como uma estrutura, como o DOS PSP, o DOS MCB, os registros VGA, etc.
Primeiro, você precisa de um arquivo de cabeçalho C que descreva as estruturas no aplicativo. Você pode gerar um com Ghidra ou Ida. Em seguida, você pode carregá -lo com o argumento --StructureFile CommandLine. Isso permitirá o botão "Visualização da estrutura" na guia Memória do depurador.
Lá você entra em um segmento: endereço de deslocamento e escolha a estrutura que deseja visualizar. A estrutura será exibida em uma visão de árvore e a memória em uma visão hexadecimal.
A exibição é atualizada sempre que o aplicativo for pausado, para que você possa passar pelo programa e ver como a estrutura muda. A exportação de um novo arquivo de cabeçalho C da GHIDRA ou da IDA também atualizará o visualizador da estrutura com as novas informações em tempo real.
Você também pode inserir a visualização da estrutura selecionando um intervalo de bytes na guia Memória e clicando com o botão direito do mouse.
É possível fornecer uma unidade C: para funções DOS emuladas com a opção -CDRIVE . O padrão é a pasta atual. Para alguns jogos, você pode precisar definir a unidade C para a pasta do jogo.
Você pode passar argumentos (máximo 127 chars!) Para o programa emulado com a opção --exeargs . O padrão está vazio.
O hardware do timer emulado do PC (Intel 8259) suporta o tempo de medição de qualquer:
A tela é atualizada 30 vezes por segundo e cada vez que é detectada uma espera de reejamento VGA (consulte Renderer.cs).
CPU:
Memória:
Gráficos:
DOS:
Entrada:
CD-ROM:
Som:
Nos sistemas nix, você precisará instalar a libportaudio. Sem ele, não haverá som.
Lista de compatibilidade disponível aqui.
dotnet build Spice86 -e < path to executable >ou use isso onde Spice86.csproj está localizado:
dotnet run -e < path to executable >Isso usa Ghidra e Java 17.
Antes de usá -lo, defina uma variável de ambiente denominada Spice86_Dumps_Folder apontando para uma pasta onde os dumps Spice86 estão localizados. Eles são gerados na saída.
Procedimento Geral, em ordem:
1.Ghidra's O próprio script 'ImportSymbolScript.py' (a entrada usada é "spice86dumpghidrasymbols.txt")
2. Auto-analisada de Ghidra (apenas ative 'pontos de entrada de desocupação')
3. Agora, você pode usar o plug -in.
Lembre -se: se Ghidra exibir sub -rotinas, use a tecla 'F' para convertê -las em funções. O gerador de código funciona apenas com funções.
Além disso, se você tem algum comportamento estranho, verifique se você tem Java 17 e apenas Java 17. É assim que Ghidra gosta.
Cryo Dune:
A interface do usuário é alimentada por Avalonia UI.
