diff --git a/os/download.sh b/os/download.sh index c8a53e7..0bb59a6 100644 --- a/os/download.sh +++ b/os/download.sh @@ -1,19 +1,22 @@ #!/bin/bash set -e + # Downloads a release of ccc and puts all the stdump executables in the right # places. This file is called by the CI workflow for putting out new releases. +version='v1.1' + pushd $(dirname -- "$0") -wget https://github.com/chaoticgd/ccc/releases/download/v1.0/ccc_v1.0_linux.zip -wget https://github.com/chaoticgd/ccc/releases/download/v1.0/ccc_v1.0_mac.zip -wget https://github.com/chaoticgd/ccc/releases/download/v1.0/ccc_v1.0_windows.zip +wget "https://github.com/chaoticgd/ccc/releases/download/$(echo $version)/ccc_$(echo $version)_linux.zip" +wget "https://github.com/chaoticgd/ccc/releases/download/$(echo $version)/ccc_$(echo $version)_mac.zip" +wget "https://github.com/chaoticgd/ccc/releases/download/$(echo $version)/ccc_$(echo $version)_windows.zip" mkdir linux_x86_64 mkdir mac_x86_64 mkdir win_x86_64 -unzip -j ccc_v1.0_linux.zip ccc_v1.0_linux/stdump -d linux_x86_64 -unzip -j ccc_v1.0_mac.zip ccc_v1.0_mac/stdump -d mac_x86_64 -unzip -j ccc_v1.0_windows.zip ccc_v1.0_windows/stdump.exe -d win_x86_64 -rm ccc_v1.0_linux.zip -rm ccc_v1.0_mac.zip -rm ccc_v1.0_windows.zip +unzip -j "ccc_$(echo $version)_linux.zip" "ccc_$(echo $version)_linux/stdump" -d linux_x86_64 +unzip -j "ccc_$(echo $version)_mac.zip" "ccc_$(echo $version)_mac/stdump" -d mac_x86_64 +unzip -j "ccc_$(echo $version)_windows.zip" "ccc_$(echo $version)_windows/stdump.exe" -d win_x86_64 +rm "ccc_$(echo $version)_linux.zip" +rm "ccc_$(echo $version)_mac.zip" +rm "ccc_$(echo $version)_windows.zip" popd diff --git a/src/main/java/ghidra/emotionengine/symboltable/StabsAnalyzer.java b/src/main/java/ghidra/emotionengine/symboltable/StabsAnalyzer.java index a504c4a..bf06c0a 100644 --- a/src/main/java/ghidra/emotionengine/symboltable/StabsAnalyzer.java +++ b/src/main/java/ghidra/emotionengine/symboltable/StabsAnalyzer.java @@ -26,7 +26,7 @@ public class StabsAnalyzer extends AbstractAnalyzer { " instructions there).\n\n" + "For more information see:\n" + "https://github.com/chaoticgd/ccc"; - + public static final String OPTION_IMPORT_FUNCTIONS = "Import Functions"; public static final String OPTION_IMPORT_FUNCTIONS_DESC = "Import functions from the symbol table into Ghidra."; @@ -43,6 +43,10 @@ public class StabsAnalyzer extends AbstractAnalyzer { public static final String OPTION_LINE_NUMBERS_DESC = "Output source line numbers as end-of-line comments that will appear in the diassembly."; + public static final String OPTION_ONLY_RUN_ONCE = "Only Run Once"; + public static final String OPTION_ONLY_RUN_ONCE_DESC = + "Bail out if over 50% of the recovered types already exist to prevent the user from accidentally corrupting their file."; + public static final String OPTION_OVERRIDE_ELF_PATH = "Override ELF Path (Optional)"; public static final String OPTION_OVERRIDE_ELF_PATH_DESC = "Use and ELF file of your choice as input to stdump instead of the currently loaded program."; @@ -82,10 +86,11 @@ public boolean added(Program program, AddressSetView set, TaskMonitor monitor, M @Override public void registerOptions(Options options, Program program) { - options.registerOption(OPTION_IMPORT_FUNCTIONS, DEFAULT_OPTIONS.importFunctions, null, OPTION_IMPORT_FUNCTIONS); - options.registerOption(OPTION_IMPORT_GLOBALS, DEFAULT_OPTIONS.importGlobals, null, OPTION_IMPORT_GLOBALS); - options.registerOption(OPTION_INLINED_CODE, DEFAULT_OPTIONS.markInlinedCode, null, OPTION_INLINED_CODE); - options.registerOption(OPTION_LINE_NUMBERS, DEFAULT_OPTIONS.outputLineNumbers, null, OPTION_LINE_NUMBERS); + options.registerOption(OPTION_IMPORT_FUNCTIONS, DEFAULT_OPTIONS.importFunctions, null, OPTION_IMPORT_FUNCTIONS_DESC); + options.registerOption(OPTION_IMPORT_GLOBALS, DEFAULT_OPTIONS.importGlobals, null, OPTION_IMPORT_GLOBALS_DESC); + options.registerOption(OPTION_INLINED_CODE, DEFAULT_OPTIONS.markInlinedCode, null, OPTION_INLINED_CODE_DESC); + options.registerOption(OPTION_LINE_NUMBERS, DEFAULT_OPTIONS.outputLineNumbers, null, OPTION_LINE_NUMBERS_DESC); + options.registerOption(OPTION_ONLY_RUN_ONCE, DEFAULT_OPTIONS.onlyRunOnce, null, OPTION_ONLY_RUN_ONCE_DESC); options.registerOption(OPTION_OVERRIDE_ELF_PATH, DEFAULT_OPTIONS.overrideElfPath, null, OPTION_OVERRIDE_ELF_PATH_DESC); options.registerOption(OPTION_OVERRIDE_JSON_PATH, DEFAULT_OPTIONS.overrideJsonPath, null, OPTION_OVERRIDE_JSON_PATH_DESC); } @@ -96,6 +101,7 @@ public void optionsChanged(Options options, Program program) { importOptions.importGlobals = options.getBoolean(OPTION_IMPORT_GLOBALS, DEFAULT_OPTIONS.importGlobals); importOptions.markInlinedCode = options.getBoolean(OPTION_INLINED_CODE, DEFAULT_OPTIONS.markInlinedCode); importOptions.outputLineNumbers = options.getBoolean(OPTION_LINE_NUMBERS, DEFAULT_OPTIONS.outputLineNumbers); + importOptions.onlyRunOnce = options.getBoolean(OPTION_ONLY_RUN_ONCE, DEFAULT_OPTIONS.onlyRunOnce); importOptions.overrideElfPath = options.getString(OPTION_OVERRIDE_ELF_PATH, DEFAULT_OPTIONS.overrideElfPath); importOptions.overrideJsonPath = options.getString(OPTION_OVERRIDE_JSON_PATH, DEFAULT_OPTIONS.overrideJsonPath); } diff --git a/src/main/java/ghidra/emotionengine/symboltable/StabsImporter.java b/src/main/java/ghidra/emotionengine/symboltable/StabsImporter.java index 1c4e415..02aaf60 100644 --- a/src/main/java/ghidra/emotionengine/symboltable/StabsImporter.java +++ b/src/main/java/ghidra/emotionengine/symboltable/StabsImporter.java @@ -23,6 +23,7 @@ import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.DataUtilities; import ghidra.program.model.data.DataUtilities.ClearDataMode; import ghidra.program.model.data.PointerDataType; @@ -47,6 +48,7 @@ public static class ImportOptions { boolean importGlobals = true; boolean markInlinedCode = true; boolean outputLineNumbers = true; + boolean onlyRunOnce = true; String overrideElfPath = ""; String overrideJsonPath = ""; } @@ -139,6 +141,7 @@ public boolean doImport() { cleanupTemporaryFiles(); if(monitor.isCancelled()) { + log.appendMsg("STABS", "Import operation cancelled by user."); return false; } @@ -152,7 +155,14 @@ public boolean doImport() { return false; } + if(options.onlyRunOnce && shouldBailOut(program, ast)) { + log.appendMsg("STABS", "Import operation cancelled since it has already been run."); + return false; + } + + if(monitor.isCancelled()) { + log.appendMsg("STABS", "Import operation cancelled by user."); return false; } @@ -184,6 +194,24 @@ public void cleanupTemporaryFiles() { temporaryFiles.clear(); } + public boolean shouldBailOut(Program program, StdumpAST.ParsedJsonFile ast) { + if(ast.deduplicatedTypes.size() < 10) { + return false; + } + DataTypeManager dataTypeManager = program.getDataTypeManager(); + int existingTypes = 0; + int newTypes = 0; + for(StdumpAST.Node node : ast.deduplicatedTypes) { + if(dataTypeManager.getDataType("/" + node.name) != null) { + existingTypes++; + } else { + newTypes++; + } + } + return existingTypes > newTypes; + } + + public void importDataTypes(StdumpAST.ImporterState importer) { int type_count = importer.ast.deduplicatedTypes.size(); @@ -207,13 +235,14 @@ public void importDataTypes(StdumpAST.ImporterState importer) { // Create all the top-level enums, structs and unions first. for(int i = 0; i < type_count; i++) { StdumpAST.Node node = importer.ast.deduplicatedTypes.get(i); + node.setupConflictResolutionPostfix(importer); if(node instanceof StdumpAST.InlineEnum) { StdumpAST.InlineEnum inlineEnum = (StdumpAST.InlineEnum) node; DataType type = inlineEnum.createType(importer); importer.types.add(importer.programTypeManager.addDataType(type, null)); } else if(node instanceof StdumpAST.InlineStructOrUnion) { StdumpAST.InlineStructOrUnion structOrUnion = (StdumpAST.InlineStructOrUnion) node; - DataType type = structOrUnion.create_empty(importer); + DataType type = structOrUnion.createEmpty(importer); importer.types.add(importer.programTypeManager.addDataType(type, null)); } else { importer.types.add(null); @@ -224,6 +253,7 @@ public void importDataTypes(StdumpAST.ImporterState importer) { // Fill in the structs and unions recursively. for(int i = 0; i < type_count; i++) { StdumpAST.Node node = importer.ast.deduplicatedTypes.get(i); + node.setupConflictResolutionPostfix(importer); if(node instanceof StdumpAST.InlineStructOrUnion) { StdumpAST.InlineStructOrUnion struct_or_union = (StdumpAST.InlineStructOrUnion) node; DataType type = importer.types.get(i); diff --git a/src/main/java/ghidra/emotionengine/symboltable/StdumpAST.java b/src/main/java/ghidra/emotionengine/symboltable/StdumpAST.java index a682425..2d43d74 100644 --- a/src/main/java/ghidra/emotionengine/symboltable/StdumpAST.java +++ b/src/main/java/ghidra/emotionengine/symboltable/StdumpAST.java @@ -49,6 +49,7 @@ public static class ImporterState { ArrayList types = new ArrayList<>(); // (data type, size in bytes) ArrayList> stabsTypeNumberToDeduplicatedTypeIndex = new ArrayList<>(); HashMap typeNameToDeduplicatedTypeIndex = new HashMap<>(); + String conflictResolutionPostfix; // Ghidra objects. TaskMonitor monitor; @@ -104,11 +105,19 @@ public DataType createTypeImpl(ImporterState importer) { return Undefined1DataType.dataType; } - String generateName() { - if(conflict || name == null || name.isEmpty()) { - return prefix + name + "__" + Integer.toString(firstFile) + "_" + Integer.toString(stabsTypeNumber); + void setupConflictResolutionPostfix(ImporterState importer) { + if(conflict) { + importer.conflictResolutionPostfix = "__" + Integer.toString(firstFile) + "_" + Integer.toString(stabsTypeNumber); + } else { + importer.conflictResolutionPostfix = ""; + } + } + + String generateName(ImporterState importer) { + if(name == null || name.isEmpty()) { + return prefix + "__unnamed_" + Integer.toString(absoluteOffsetBytes) + importer.conflictResolutionPostfix; } - return prefix + name; + return prefix + name + importer.conflictResolutionPostfix; } } @@ -221,7 +230,7 @@ public static class InlineEnum extends Node { ArrayList constants = new ArrayList(); public DataType createTypeImpl(ImporterState importer) { - EnumDataType type = new EnumDataType(generateName(), 4); + EnumDataType type = new EnumDataType(generateName(importer), 4); for(EnumConstant constant : constants) { type.add(constant.name, constant.value); } @@ -236,13 +245,13 @@ public static class InlineStructOrUnion extends Node { ArrayList memberFunctions = new ArrayList(); public DataType createTypeImpl(ImporterState importer) { - DataType result = create_empty(importer); + DataType result = createEmpty(importer); fill(result, importer); return result; } - public DataType create_empty(ImporterState importer) { - String typeName = generateName(); + public DataType createEmpty(ImporterState importer) { + String typeName = generateName(importer); int sizeBytes = sizeBits / 8; DataType type; if(isStruct) { @@ -296,7 +305,7 @@ public DataType createVtable(ImporterState importer) { } } int vtableSize = (maxVtableIndex + 1) * 4; - StructureDataType vtable = new StructureDataType(generateName() + "__vtable", vtableSize, importer.programTypeManager); + StructureDataType vtable = new StructureDataType(generateName(importer) + "__vtable", vtableSize, importer.programTypeManager); for(Node node : memberFunctions) { if(node instanceof FunctionType) { FunctionType function = (FunctionType) node; diff --git a/src/main/java/ghidra/emotionengine/symboltable/StdumpParser.java b/src/main/java/ghidra/emotionengine/symboltable/StdumpParser.java index 88f9f42..9818016 100644 --- a/src/main/java/ghidra/emotionengine/symboltable/StdumpParser.java +++ b/src/main/java/ghidra/emotionengine/symboltable/StdumpParser.java @@ -17,7 +17,7 @@ import com.google.gson.stream.JsonReader; public class StdumpParser { - public static final String SUPPORTED_STDUMP_VERSION = "v1.0"; + public static final String SUPPORTED_STDUMP_VERSION = "v1.1"; public static final int SUPPORTED_FORMAT_VERSION = 7; public static StdumpAST.ParsedJsonFile readJson(byte[] json) throws FileNotFoundException {