SPICE86 est un outil pour exécuter, rétro-ingénieur et réécrire les programmes DOS de mode réel pour lesquels le code source n'est pas disponible.
La version est disponible sur Nuget.
Des pré-sorties sont également disponibles sur la page de version
Remarque: Il s'agit d'un port et d'une continuation du Java Spice86 d'origine.
Il nécessite .NET 9 et s'exécute sur Windows, MacOS et Linux.
La réécriture d'un programme à partir du binaire uniquement est une tâche difficile.
SPICE86 est un outil qui vous aide à le faire avec une approche méthodique de division et de conquête.
Processus général:
Il s'agit d'un programme .NET, vous l'exécutez avec la ligne de commande régulière ou l'exécution DOTNET. Exemple avec l'exécution d'un programme appelé file.exe:
Spice86 -e file.exe
Les fichiers com et les fichiers BIOS sont également pris en charge.
Il est recommandé de définir la variable d'environnement SPICE86_DUMPS_FOLDER pointant vers où l'émulateur doit vider les données d'exécution. Si la variable est définie ou si - le paramètre de datadirectoral recordé est passé, l'émulateur déversera un tas d'informations sur l'exécution là-bas. Si rien n'est défini, les données seront jetées dans le répertoire actuel. S'il y a déjà des données là-bas, l'émulateur le chargera d'abord et le terminera, vous n'avez pas besoin de commencer à partir de zéro à chaque fois!
--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 parle le protocole distant GDB:
Vous devez spécifier un port pour le démarrage du serveur GDB lors du lancement de Spice86:
Spice86 --GdbPort=10000
SPICE86 attendra que GDB se connecte avant de commencer l'exécution afin que vous puissiez configurer les points d'arrêt et ainsi de suite.
Voici comment se connecter à partir du client de la ligne de commande GDB et comment définir l'architecture:
(gdb) target remote localhost:10000
(gdb) set architecture i8086
Vous pouvez ajouter des points d'arrêt, une étape, une visualisation de la mémoire, etc.
Exemple avec un point d'arrêt sur VGA VRAM écrit:
(gdb) watch *0xA0000
Assemblage de la vision:
(gdb) layout asm
Supprimer un point d'arrêt:
(gdb) remove 1
Recherche d'une séquence d'octets en mémoire (Adresse de démarrage 0, longueur F0000, octets ASCII de la chaîne 'Spice86'):
(gdb) find /b 0x0, 0xF0000, 0x53, 0x70, 0x69, 0x63, 0x65, 0x38, 0x36
GDB ne prend pas en charge l'adressage segmenté en mode X86, les pointeurs doivent donc se référer à l'adresse physique réelle en mémoire. VRAM à l'adresse a000: 0000 serait 0xa0000 dans GDB.
De même, la variable $ PC dans GDB sera exposée par Spice86 comme l'adresse physique pointée par CS: IP.
La liste des commandes personnalisées peut être affichée comme ceci:
(gdb) monitor help
(gdb) monitor dumpall
Jette tout ce qui est décrit ci-dessous en un seul coup. Les fichiers sont créés dans le dossier de vidage comme expliqué ici plusieurs fichiers sont produits:
Break après x cycles de CPU imités:
(gdb) monitor breakCycles 1000
Break à la fin du programme émulé:
(gdb) monitor breakStop
#ReftHing Écran ou tampons lors du débogage
(gdb) monitor vbuffer refresh
Pour une expérience agréable et productive avec GDB, le client SEERGDB est fortement recommandé.
Exemple concret avec Cryo Dune ici.
Exécutez d'abord votre programme et assurez-vous que tout fonctionne bien dans Spice86. Si vous rencontrez des problèmes, cela pourrait être dû aux fonctionnalités matérielles / dos / bios non implémentées.
Lorsque Spice86 sort, il doit vider les données dans le dossier actuel ou dans le dossier spécifié par variable Env
Ouvrez les données dans Ghidra avec le Spice86-Ghidra-Plugin et générez du code. Vous pouvez importer les fichiers générés dans un projet de modèle que vous générez via le Spice86-Dotnet-Templates:
dotnet new spice86.project
Vous pouvez fournir votre propre code C # pour remplacer le code d'assemblage original du programme.
Spice86 peut prendre en entrée une instance de Spice86.core.emulator.function.ioverRideupplier qui construit un mappage entre l'adresse mémoire des fonctions et leurs remplacements C #.
Pour un exemple complet, vous pouvez vérifier le code source de cryogénique.
Voici un exemple simple de la façon dont il ressemblerait:
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 ) ;
}
}N'oubliez pas : vous devez dire à SPICE86 d'utiliser vos remplacements de code d'assembly avec l'argument de la ligne de commande "- UsecodeOverride true" lors de la débogage de votre projet.
Parallèlement au chemin obligatoire vers votre programme DOS, passé avec l'argument - Exepath.
SPICE86 est livré avec un débogueur intégré qui peut être utilisé pour déboguer le programme imité. Il s'agit d'un simple débogueur qui vous permet d'inspecter la mémoire, le démontage, les registres et la pile.
Le spectateur de structure vous permet d'inspecter la mémoire de manière structurée. Il est utile d'inspecter la mémoire en tant que structure, comme le DOS PSP, le DOS MCB, les registres VGA, etc.
Vous avez d'abord besoin d'un fichier d'en-tête C qui décrit les structures de l'application. Vous pouvez en générer un avec Ghidra ou Ida. Ensuite, vous pouvez le charger avec l'argument de la ligne de commande --StructureFile . Cela permettra le bouton "Affichage de la structure" dans l'onglet Mémoire du débogueur.
Là, vous entrez un segment: Adresse décalée et choisissez la structure que vous souhaitez afficher. La structure sera affichée dans une vue d'arbre et la mémoire dans une vue hexadécimale.
L'affichage se met à jour chaque fois que l'application est interrompue, vous pouvez donc parcourir le programme et voir comment la structure change. L'exportation d'un nouveau fichier d'en-tête C de Ghidra ou IDA mettra également à jour la visionneuse de structure avec les nouvelles informations en temps réel.
Vous pouvez également saisir la vue de la structure en sélectionnant une plage d'octets dans l'onglet Mémoire et en cliquant avec le bouton droit dessus.
Il est possible de fournir un lecteur C: pour les fonctions DOS imitées avec l'option --cdrive . La valeur par défaut est le dossier actuel. Pour certains jeux, vous devrez peut-être définir le lecteur C dans le dossier du jeu.
Vous pouvez transmettre des arguments (Chars max 127!) Au programme imité avec l'option --exeargs . La valeur par défaut est vide.
Le matériel de minuterie imité du PC (Intel 8259) prend en charge le temps de mesure de soit:
L'écran est actualisé 30 fois par seconde et chaque fois qu'une attente de rétrace VGA est détectée (voir renderer.cs).
CPU:
Mémoire:
Graphique:
DOS:
Saisir:
CD-ROM:
Son:
Sur * Nix Systems, vous devrez installer LibportAudio. Sans cela, il n'y aura pas de son.
Liste de compatibilité disponible ici.
dotnet build Spice86 -e < path to executable >ou utilisez-le où se trouve Spice86.csproj:
dotnet run -e < path to executable >Cela utilise Ghidrera et Java 17.
Avant de l'utiliser, définissez une variable d'environnement nommée Spice86_Dumps_Folder pointant vers un dossier où se trouvent les décharges Spice86. Ils sont générés à la sortie.
Procédure générale, dans l'ordre:
1. Script de Ghidredra 'ImportsymbolScript.py' (l'entrée utilisée est "Spice86DumpGhidrasymbols.txt")
2. Auto-analyse de Ghididra (activer uniquement les «points d'entrée dissasé»)
3. Maintenant, vous pouvez utiliser le plugin.
N'oubliez pas: si Ghidra affiche les sous-programmes, utilisez la touche «F» pour les convertir en fonctions. Le générateur de code fonctionne uniquement avec les fonctions.
De plus, si vous avez un comportement étrange, assurez-vous d'avoir Java 17 et Java 17. C'est comme ça que Ghidra aime.
Cryo dune:
L'interface utilisateur est alimentée par Avalonia UI.
