diff --git a/GPL/CabExtract/build.gradle b/GPL/CabExtract/build.gradle index cb46a44b5b..071e485e02 100644 --- a/GPL/CabExtract/build.gradle +++ b/GPL/CabExtract/build.gradle @@ -1,3 +1,5 @@ +apply from: file("../gpl.gradle").getCanonicalPath() + if (findProject(':Generic') != null) { apply from: "$rootProject.projectDir/gradle/nativeProject.gradle" apply from: "$rootProject.projectDir/gradle/distributableGPLModule.gradle" diff --git a/GPL/CabExtract/certification.manifest b/GPL/CabExtract/certification.manifest index b7acafafa0..d63ae0e1ac 100644 --- a/GPL/CabExtract/certification.manifest +++ b/GPL/CabExtract/certification.manifest @@ -4,3 +4,4 @@ Module.manifest||Public Domain||||END| build.gradle||Public Domain||||END| data/cabextract-1.6.tar.gz||GPL 3||||END| +settings.gradle||Public Domain||||END| diff --git a/GPL/CabExtract/settings.gradle b/GPL/CabExtract/settings.gradle new file mode 100644 index 0000000000..e69de29bb2 diff --git a/GPL/DMG/build.gradle b/GPL/DMG/build.gradle index f2a54df676..85f6b9221a 100644 --- a/GPL/DMG/build.gradle +++ b/GPL/DMG/build.gradle @@ -1,13 +1,13 @@ +apply from: file("../gpl.gradle").getCanonicalPath() + if (findProject(':Generic') != null) { apply from: "$rootProject.projectDir/gradle/javaProject.gradle" apply from: "$rootProject.projectDir/gradle/distributableGPLModule.gradle" rootProject.assembleDistribution { - doLast { // eliminate standard module lib directory def assemblePath = destinationDir.path + "/" + getZipPath(this.project) - println "DELETE: ${assemblePath}/lib" delete assemblePath + "/lib" } } @@ -28,6 +28,7 @@ eclipse.project.name = 'GPL DMG' * *********************************************************************************/ sourceSets { + dmg { java { srcDir 'src/dmg/java' diff --git a/GPL/DMG/certification.manifest b/GPL/DMG/certification.manifest index 21888c616d..7311651d24 100644 --- a/GPL/DMG/certification.manifest +++ b/GPL/DMG/certification.manifest @@ -16,3 +16,4 @@ data/os/win64/llio_amd64.dll||GPL 3||||END| data/os/win64/llio_i386.dll||GPL 3||||END| data/os/win64/llio_ia64.dll||GPL 3||||END| data/server_memory.cfg||Public Domain||||END| +settings.gradle||Public Domain||||END| diff --git a/GPL/DMG/settings.gradle b/GPL/DMG/settings.gradle new file mode 100644 index 0000000000..e69de29bb2 diff --git a/GPL/DemanglerGnu/build.gradle b/GPL/DemanglerGnu/build.gradle index dcf5d036bb..0e6cf5625e 100644 --- a/GPL/DemanglerGnu/build.gradle +++ b/GPL/DemanglerGnu/build.gradle @@ -1,3 +1,4 @@ +apply from: file("../gpl.gradle").getCanonicalPath() if (findProject(':Generic') != null) { apply from: "$rootProject.projectDir/gradle/nativeProject.gradle" diff --git a/GPL/DemanglerGnu/certification.manifest b/GPL/DemanglerGnu/certification.manifest index 8d6c35c7c8..6872775850 100644 --- a/GPL/DemanglerGnu/certification.manifest +++ b/GPL/DemanglerGnu/certification.manifest @@ -6,4 +6,5 @@ ##MODULE IP: Public Domain Module.manifest||Public Domain||||END| build.gradle||Public Domain||||END| +settings.gradle||Public Domain||||END| src/demangler_gnu/README.txt||Public Domain||||END| diff --git a/GPL/DemanglerGnu/settings.gradle b/GPL/DemanglerGnu/settings.gradle new file mode 100644 index 0000000000..e69de29bb2 diff --git a/GPL/GnuDisassembler/Module.manifest b/GPL/GnuDisassembler/Module.manifest new file mode 100644 index 0000000000..e69de29bb2 diff --git a/GPL/GnuDisassembler/README.txt b/GPL/GnuDisassembler/README.txt new file mode 100644 index 0000000000..1ffd524a36 --- /dev/null +++ b/GPL/GnuDisassembler/README.txt @@ -0,0 +1,20 @@ +The GnuDisassembler extension module must be built using gradle prior to its' use within Ghidra. + +This module provides the ability to leverage the binutils disassembler capabilities +for various processors as a means of verifying Sleigh disassembler output syntax. + +To build this extension for Linux or Mac OS X: + + 1. If building for an installation of Ghidra, copy the appropriate source distribution of + binutils into this module's root directory. If building within a git clone of the full + Ghidra source, copy binutils source distribution file into the ghidra.bin/GPL/GnuDisassembler + directory. + + The supported version and archive format is identified within the build.gradle file. + If a different binutils distribution is used the build.gradle and/or buildGdis.gradle + may require modification. + + 2. Run gradle from the module's root directory (see top of build.gradle file for + specific instructions). + +This resulting gdis executable will be located in build/os/. diff --git a/GPL/GnuDisassembler/build.gradle b/GPL/GnuDisassembler/build.gradle new file mode 100644 index 0000000000..fdb9f93f27 --- /dev/null +++ b/GPL/GnuDisassembler/build.gradle @@ -0,0 +1,69 @@ +// If extension module does not reside within the Ghidra GPL directory, the Ghidra installation directory +// must be specified either by setting the GHIDRA_INSTALL_DIR environment variable or Gradle +// project property: +// +// > export GHIDRA_INSTALL_DIR= +// > gradle build +// +// or +// +// > gradle -PGHIDRA_INSTALL_DIR= build +// +// In addition, the appropriate binutils source distribution archive must be placed +// within this module's directory (see below for binutils version and archive file naming. +// +// Gradle should be invoked from the directory of the extension module to build. Please see the +// application.gradle.version property in /Ghidra/application.properties +// for the correction version of Gradle to use for the Ghidra installation you specify. + +ext.binutils = "binutils-2.29.1" +ext.binutilsDistro = "${binutils}.tar.bz2" + +ext.ghidraInstallDir = null; + +if (file("../gpl.gradle").exists()) { + // Module is located within the Ghidra GPL directory + ext.ghidraInstallDir = file("../..").getCanonicalPath() + ext.binutilsLocation = file("${ghidraInstallDir}/../ghidra.bin/GPL/${name}").getCanonicalPath() + apply from: file("../gpl.gradle").getCanonicalPath() +} +else { + // various module placements for Ghidra installations + ext.binutilsLocation = projectDir + if (file("../../../GPL/gpl.gradle").exists()) { + // Handle GPL extension install within Ghidra Extensions directory + ext.ghidraInstallDir = file("../../..").getCanonicalPath() + } + else { + // Handle extension install outside of Ghidra installation - must specify Ghidra install path + if (System.env.GHIDRA_INSTALL_DIR) { + ext.ghidraInstallDir = System.env.GHIDRA_INSTALL_DIR + } + else if (project.hasProperty("GHIDRA_INSTALL_DIR")) { + ext.ghidraInstallDir = project.getProperty("GHIDRA_INSTALL_DIR") + } + } + if (ghidraInstallDir) { + if (ghidraInstallDir.replace("\\","/").endsWith("/")) { + ext.ghidraInstallDir = ghidraInstallDir.substring(0, ghidraInstallDir.length()-1) + } + println "Building with Ghidra installation at $ghidraInstallDir" + apply from: new File(ghidraInstallDir).getCanonicalPath() + "/GPL/gpl.gradle" + } + else { + throw new GradleException("GHIDRA_INSTALL_DIR is not defined!") + } +} + +if (findProject(':Generic') != null) { + // Handle integrated Ghidra build - do not build gdis native + apply from: "$rootProject.projectDir/gradle/distributableGPLExtension.gradle" + delete file("build/os"); // remove any prior build of gdis +} +else { + apply from: "${ghidraInstallDir}/GPL/nativeBuildProperties.gradle" + apply from: "buildGdis.gradle" +} + +apply plugin: 'eclipse' +eclipse.project.name = 'Xtra GPL GnuDisassembler' diff --git a/GPL/GnuDisassembler/buildGdis.gradle b/GPL/GnuDisassembler/buildGdis.gradle new file mode 100644 index 0000000000..2859dd073f --- /dev/null +++ b/GPL/GnuDisassembler/buildGdis.gradle @@ -0,0 +1,169 @@ +/******************************************************************************************* + * build.gradle file that applies this script must define two properties + * 1) binutilsLocation - the folder where the original binutils.zip lives + * 2) binutilsPrebuiltPath - the folder where the custom prebuilt binutils lives or will be built to + *******************************************************************************************/ + +defaultTasks 'assemble' + +ext.supportedPlatforms = ['osx64', 'linux64'] + +ext.binutilsResource = new File("${binutilsLocation}/${binutils}.tar.bz2") + +def binutilsUnpackDir = file("${project.buildDir}/${binutils}/") + +/****************************************************************************************** + * + * For each supported platform build the following tasks: + * buildBinutils_ builds binutils for the platform + * packageBinutilsDev_ creates the built bundle of stuf we need to build gdis + * unpackBinutilsPrebuilt_ unpacks the built bundle to be used to build gdis + * + ******************************************************************************************/ + +model { + platforms { + linux64 { + architecture 'x86_64' + operatingSystem 'linux' + } + osx64 { + architecture 'x86_64' + operatingSystem 'osx' + } + } + + components { + + gdis(NativeExecutableSpec) { + + // NOTE: Windows build requires Mingw and is very very slow and touchy + supportedPlatforms.each { targetPlatform it} + + sources { + c { + source { + srcDir "src/gdis/c" + include "disasm_1.c" + } + } + } + binaries { + all { + def binutilsArtifactsDir = file("build/binutils/${targetPlatform.name}") + if ((toolChain in Gcc) || (toolChain in Clang)) { + cCompiler.args "-I${binutilsArtifactsDir}/include", "-I${binutilsArtifactsDir}/bfd" + linker.args "-L${binutilsArtifactsDir}/lib", "-lopcodes", "-lbfd", "-liberty", "-lz", "-ldl" + } + } + } + } + } + + tasks.compileGdisOsx64ExecutableGdisC { + dependsOn 'copyBinutilsArtifcats_osx64' + } + tasks.compileGdisLinux64ExecutableGdisC { + dependsOn 'copyBinutilsArtifcats_linux64' + } + +} + +// change gdis linker output directory to build/os/ +gradle.taskGraph.whenReady { + def p = this.project + p.tasks.withType(LinkExecutable).each { t -> + File f = t.linkedFile.getAsFile().get() + String filename = f.getName() + NativePlatform platform = t.targetPlatform.get() + String osName = platform.getName() + t.linkedFile = p.file("build/os/${osName}/$filename") + } +} + +/******************************************************************************************* + * Task to unpack the standard binutils zip file + *******************************************************************************************/ +task binutilsUnpack { + description "Unpack binutils (for building gdis)" + group "Native Build Dependencies" + outputs.file { binutilsUnpackDir } + onlyIf { !binutilsUnpackDir.exists() } + + doFirst { + if (!binutilsResource.exists()) { + throw new GradleException("${binutilsResource.getCanonicalPath()} not found") + } + } + + doLast { + copy { + from tarTree(resources.bzip2("${binutilsResource}")) + into file("build") + } + } +} + +supportedPlatforms.each { platform -> + + def buildName = "buildBinutils_${platform}" + def postBuildName = "copyBinutilsArtifcats_${platform}" + + def configDir = file("build/config/${platform}") + def artifactsDir = file("build/binutils/${platform}") + + task(buildName) { + description "Configure and make binutils for $platform (for building gdis)" + group "Native Prebuild Dependencies" + + onlyIf { !configDir.exists() } + + dependsOn binutilsUnpack + + inputs.dir binutilsUnpackDir + outputs.dir configDir + + doLast { + + File binutilsDir = binutilsUnpackDir + delete configDir + + println "Configuring binutils - config directory: $configDir" + println "${binutilsDir}/configure --prefix=\"${configDir}\" --enable-targets=all --with-zlib=no --disable-nls --disable-werror" + configDir.mkdirs(); + exec { + workingDir configDir + commandLine "${binutilsDir}/configure", "--prefix=${configDir}", "--enable-targets=all", "--with-zlib=no", "--disable-nls", "--disable-werror" + } + + println "Building binutils - config directory: $configDir" + exec { + commandLine "make", "-C", "${configDir}", "all" + } + } + } + + task(postBuildName, type: Copy) { + description "Copy binutil artifcacts for $platform (for building gdis)" + group "Native Prebuild Dependencies" + + dependsOn buildName + + destinationDir = artifactsDir + + into("/include") { + from("${binutilsUnpackDir}/include") + include "**/*.h" + } + into("/bfd") { + from "${configDir}/bfd" + include "**/*.h" + } + into("/lib") { + from "${configDir}/bfd/libbfd.a" + from "${configDir}/libiberty/libiberty.a" + from "${configDir}/opcodes/libopcodes.a" + } + } + +} diff --git a/GPL/GnuDisassembler/certification.manifest b/GPL/GnuDisassembler/certification.manifest new file mode 100644 index 0000000000..f2619af2f6 --- /dev/null +++ b/GPL/GnuDisassembler/certification.manifest @@ -0,0 +1,14 @@ +##VERSION: 2.0 +##MODULE IP: GPL 2 +##MODULE IP: Public Domain +.classpath||GHIDRA||||END| +.project||GHIDRA||||END| +Module.manifest||Public Domain||||END| +README.txt||Public Domain||||END| +build.gradle||Public Domain||||END| +buildGdis.gradle||Public Domain||||END| +data/arm_test1.s||Public Domain||||END| +data/big.elf||Public Domain||||END| +data/little.elf||Public Domain||||END| +extension.properties||Public Domain||||END| +settings.gradle||Public Domain||||END| diff --git a/GPL/GnuDisassembler/data/arm_test1.s b/GPL/GnuDisassembler/data/arm_test1.s new file mode 100644 index 0000000000..dc814804a2 --- /dev/null +++ b/GPL/GnuDisassembler/data/arm_test1.s @@ -0,0 +1,7 @@ + .text +__start: + lw $t0, #4 + li $t1, #0 + add $t2, $t0, $t1 + done + diff --git a/GPL/GnuDisassembler/data/big.elf b/GPL/GnuDisassembler/data/big.elf new file mode 100644 index 0000000000..3185fed53f Binary files /dev/null and b/GPL/GnuDisassembler/data/big.elf differ diff --git a/GPL/GnuDisassembler/data/little.elf b/GPL/GnuDisassembler/data/little.elf new file mode 100644 index 0000000000..6efa60df3a Binary files /dev/null and b/GPL/GnuDisassembler/data/little.elf differ diff --git a/GPL/GnuDisassembler/extension.properties b/GPL/GnuDisassembler/extension.properties new file mode 100644 index 0000000000..5624d5632b --- /dev/null +++ b/GPL/GnuDisassembler/extension.properties @@ -0,0 +1,6 @@ +name=GnuDisassembler +description=GNU Disassembler. Extension is delivered unbuilt. See module README.txt for build instructions. +author=Ghidra Team +createdOn=6/18/2019 +version=@extversion@ +gpl=true diff --git a/GPL/GnuDisassembler/settings.gradle b/GPL/GnuDisassembler/settings.gradle new file mode 100644 index 0000000000..e69de29bb2 diff --git a/GPL/GnuDisassembler/src/gdis/c/disasm_1.c b/GPL/GnuDisassembler/src/gdis/c/disasm_1.c new file mode 100644 index 0000000000..6bebc1d15c --- /dev/null +++ b/GPL/GnuDisassembler/src/gdis/c/disasm_1.c @@ -0,0 +1,457 @@ +/* ### + * IP: GPL 2 + */ +#include "config.h" +#include +#include +#include +#include + +#include "bfd.h" +#include "dis-asm.h" +// #include "bucomm.h" // for set_default_bfd_target() + +#include "gdis.h" + +#define MAX_ASCII_CHAR_BYTE_STRING 256 + + +void listSupportedArchMachTargets(void) +{ + const char** targetList; + const char** archList; + int i, j; + + targetList = bfd_target_list(); + if(targetList != NULL){ + for(i=0, j=0; targetList[i] !=0; i++){ + printf("Supported Target: %s\n", targetList[i]); + } + } + printf("\ndone with targetList.\n"); + + archList = bfd_arch_list(); + if(archList != NULL){ + for(i=0, j=0; archList[i] !=0; i++){ + printf("Supported Arch: %s\n", archList[i]); + } + } + printf("\ndone with archList.\n"); +} + + + +/* sprintf to a "stream". */ +int objdump_sprintf (SFILE *f, const char *format, ...) +{ + + int i; + size_t n; + va_list args; + + va_start (args, format); + n = vsnprintf (f->buffer + f->pos, BUFF_SIZE, format, args); + strncat(disassembled_buffer, f->buffer, n); + va_end (args); + + return n; +} + + +void configureDisassembleInfo(bfd* abfd, + disassemble_info* info, + enum bfd_architecture arch, + unsigned long mach, + enum bfd_endian end) +{ + + memset(sfile.buffer, 0x00, BUFF_SIZE); + + INIT_DISASSEMBLE_INFO(*info, stdout, objdump_sprintf); + info->arch = (enum bfd_architecture) arch; + info->mach = mach; + info->flavour = bfd_get_flavour(abfd); + info->endian = end; + info->stream = (FILE*)&sfile; // set up our "buffer stream" + info->display_endian = BFD_ENDIAN_LITTLE; + /* Allow the target to customize the info structure. */ + disassemble_init_for_target(info); +} + +disassembler_ftype configureBfd(bfd* abfd, + enum bfd_architecture arch, + unsigned long mach, + enum bfd_endian endian, + disassemble_info* DI, + disassembler_ftype* disassemble_fn) +{ + struct bfd_target *xvec; + + abfd->flags |= EXEC_P; + + + // set up xvec byteorder. + xvec = (struct bfd_target *) malloc (sizeof (struct bfd_target)); + memset(xvec, 0x00, sizeof (struct bfd_target)); + memcpy (xvec, abfd->xvec, sizeof (struct bfd_target)); + xvec->byteorder = endian; + abfd->xvec = xvec; + + configureDisassembleInfo(abfd, DI, arch, mach, endian); + if(endian == BFD_ENDIAN_BIG){ + bfd_big_endian(abfd); + DI->display_endian = DI->endian = BFD_ENDIAN_BIG; + } + else{ + bfd_little_endian(abfd); + DI->display_endian = DI->endian = BFD_ENDIAN_LITTLE; + } + + /* + bfd_error_type err = bfd_get_error(); + printf("bfd_error_msg: %s.\n", bfd_errmsg(err)); + */ + + /* Use libopcodes to locate a suitable disassembler. */ + *disassemble_fn = NULL; + *disassemble_fn = disassembler (arch, endian == BFD_ENDIAN_BIG, mach, abfd); + if (!*disassemble_fn){ + printf("can't disassemble for arch 0x%08X, mach 0x%08lX\n", arch, mach); + exit(1); + } + + return *disassemble_fn; +} + + + +int disassemble_buffer( disassembler_ftype disassemble_fn, + disassemble_info *info, + int* offset, + PDIS_INFO pDisInfo) +{ + int i, j, size = 0; + int len = 0; + + while ( *offset < info->buffer_length ) { + /* call the libopcodes disassembler */ + memset(pDisInfo->disassemblyString, 0x00, MAX_DIS_STRING); + + /* set the insn_info_valid bit to 0, as explained in BFD's + * include/dis-asm.h. The bit will then be set to tell us + * whether the decoder supports "extra" information about the + * instruction. + */ + info->insn_info_valid = 0; + + size = (*disassemble_fn)(info->buffer_vma + *offset, info); + /* -- analyze disassembled instruction here -- */ + /* -- print any symbol names as labels here -- */ + + /* save off corresponding hex bytes */ + for ( j= 0,i = 0; i < 8; i++, j+=3) { + if ( i < size ){ + sprintf(&(pDisInfo->bytesBufferAscii[j]), "%02X ", info->buffer[*offset + i]); + pDisInfo->bytesBufferBin[i] = info->buffer[*offset + i]; + } + } + + /* add the augmented information to our disassembly info struct */ + pDisInfo->count = size; + pDisInfo->insn_info_valid = info->insn_info_valid; + pDisInfo->branch_delay_insns = info->branch_delay_insns; + pDisInfo->data_size = info->data_size; + pDisInfo->insn_type = info->insn_type; + pDisInfo->target = info->target; + pDisInfo->target2 = info->target2; + + strcat(&(pDisInfo->disassemblyString[0]), disassembled_buffer); + memset(disassembled_buffer, 0x00, BUFF_SIZE); + + if(size != 0){ + *offset += size; /* advance position in buffer */ + goto END; + } + } + +END: + return size; +} + +void processBuffer(unsigned char* buff, + int buff_len, + bfd_vma buff_vma, + disassembler_ftype disassemble_fn, + struct disassemble_info* DI) +{ + int bytesConsumed = -1; + int offset = 0; + int numDisassemblies = 0; + int i; + + DI->buffer = buff; /* buffer of bytes to disassemble */ + DI->buffer_length = buff_len; /* size of buffer */ + DI->buffer_vma = buff_vma; /* base RVA of buffer */ + + memset(disassemblyInfoBuffer, 0x00, sizeof(DIS_INFO)*MAX_NUM_ENTRIES); + + while((buff_len - offset) > 0 && bytesConsumed != 0 && numDisassemblies < MAX_NUM_ENTRIES){ + bytesConsumed = disassemble_buffer( disassemble_fn, DI, &offset, &(disassemblyInfoBuffer[numDisassemblies++])); + } + for (i = 0; i < numDisassemblies; i++) { + printf("%s\nInfo: %d,%d,%d,%d,%d\n", disassemblyInfoBuffer[i].disassemblyString, + disassemblyInfoBuffer[i].count, + disassemblyInfoBuffer[i].insn_info_valid, + disassemblyInfoBuffer[i].branch_delay_insns, + disassemblyInfoBuffer[i].data_size, + disassemblyInfoBuffer[i].insn_type); + } +} + +int main(int argc, char* argv[]){ + struct disassemble_info DI; + enum bfd_architecture arch; + struct bfd_arch_info ai; + unsigned long mach; + enum bfd_endian endian; + unsigned int end; + bfd_vma offset; + disassembler_ftype disassemble_fn; + char *target = default_target; + bfd *bfdfile; + unsigned long a,m; + char* byteString; + char elf_file_location[MAX_ELF_FILE_PATH_LEN]; + char arch_str[256]; + char mach_str[256]; + + if ( argc < 8) { + fprintf(stderr, "Usage: %s target-str, arch, mach, disassembly base-addr (for rel offsets instrs), full-path to Little and Big Elfs, big/little ascii-byte-string up to %d chars\n", argv[0], MAX_ASCII_CHAR_BYTE_STRING); + listSupportedArchMachTargets(); + const char** archList = bfd_arch_list(); + const bfd_arch_info_type* ait; + while(*archList != NULL){ + printf("checking against architecture: %s.\n", *archList); + ait = NULL; + ait = bfd_scan_arch(*archList); + if(ait != NULL){ + printf("archname: %s arch: 0x%08X, mach: 0x%08lX.\n", ait->arch_name, ait->arch, ait->mach); + } + archList++; + } + return(1); + } + + end = 0x00000000; + endian = (enum bfd_endian) 0x00; + mach = 0x00000000; + arch = (enum bfd_architecture) 0x00; + offset = 0x00000000; + + sscanf(argv[2], "%128s", arch_str); + sscanf(argv[3], "%18lX", &mach); + sscanf(argv[4], "%10X", &end); + sscanf(argv[5], "%18lX", &offset); + + // if arch starts with 0x, then parse a number + // else lookup the string in the table to get the arch, ignore the mach + if (arch_str[0] == '0' && arch_str[1] == 'x') { + sscanf(arch_str, "%10X", &arch); + } else { + const char** archList = bfd_arch_list(); + const bfd_arch_info_type* ait; + while(*archList != NULL){ + ait = bfd_scan_arch(*archList); + if(strcmp(arch_str, *archList)== 0){ + arch = ait->arch; + mach = ait->mach; + break; + } + ait = NULL; + archList++; + } + if (ait == NULL) { + printf("Couldn't find arch %s\n", arch_str); + return(-1); + } + } + + + endian = (enum bfd_endian) end; + /* open a correct type of file to fill in most of the required data. */ + + // printf("Arch is: 0x%08X, Machine is: 0x%08lX Endian is: 0x%02X.\n", arch, mach, endian); + + memset(elf_file_location, 0x00, MAX_ELF_FILE_PATH_LEN); + strncpy(elf_file_location, argv[6], MAX_ELF_FILE_PATH_LEN-sizeof(LITTLE_ELF_FILE)-2); // actual file name and nulls + + // arg[7] is either a hex string or the string "stdin", which + // triggers reading line by line from stdin. + + byteString = argv[7]; + int stdin_mode = 2; // use CLI + if (strcmp(byteString, "stdin") == 0) { + stdin_mode = 1; // use STDIN + } + + unsigned char byteBuffer[BYTE_BUFFER_SIZE]; + char byteStringBuffer[(BYTE_BUFFER_SIZE*2)]; + + char addressStringBuffer[128]; + + if (endian == BFD_ENDIAN_BIG){ + strcat(elf_file_location, BIG_ELF_FILE); + } + else { + strcat(elf_file_location, LITTLE_ELF_FILE); + } + + while (stdin_mode) { + + // convert user input AsciiHex to Binary data for processing + char tmp[3]; + unsigned int byteValue; + tmp[0] = tmp[1] = tmp[2] = 0x00; + + if (stdin_mode == 1) { // use stdin + // read in the address + if (fgets(addressStringBuffer, sizeof(addressStringBuffer), stdin)) { + + //fprintf(stderr, "read: %s\n", addressStringBuffer); + //char *p = strchr(addressStringBuffer, '\n'); + //if (p) { + // *p = '\0'; + //} + + sscanf(addressStringBuffer, "%18lX", &offset); + } + //getchar(); + // read in the ASCII hex string from stdin + if (fgets(byteStringBuffer, sizeof(byteStringBuffer), stdin)) { + + //fprintf(stderr, "read: %s\n", byteStringBuffer); + // remove trailing newline + char *p = strchr(byteStringBuffer, '\n'); + if (p) { + *p = '\0'; + } + //if (strcmp(byteStringBuffer, "EOF") == 0) { + //return 0; // terminate on EOF string + //} + } else { + fprintf(stderr, "exiting, no ASCII hex found\n"); + return 0; // finished! #TODO + } + + } else { + if(strlen(byteString) > BYTE_BUFFER_SIZE*2) { + fprintf(stderr, "Max ascii string size is %d you provided: %lu chars. Exiting.\n", BYTE_BUFFER_SIZE*2, + strlen(byteString)); + exit(-1); + } + strncpy(byteStringBuffer, byteString, BYTE_BUFFER_SIZE*2); + stdin_mode = 0; // break out of the while loop + } + + int size = strlen(byteStringBuffer); + if((size % 2) != 0){ + fprintf(stderr, "need even-number of ascii chars for byte-stream: (offset: %08lx, %s, %ld)\n", offset, byteStringBuffer, strlen(byteStringBuffer)); + exit(-1); + } + + memset(byteBuffer, 0x00, BYTE_BUFFER_SIZE); + + // + // TODO: + // check to make sure chars are only valid HEX. + // + int i, j; + for(i=j=0; (i < size) && (j < BYTE_BUFFER_SIZE); i+=2, j++){ + tmp[0] = byteStringBuffer[i]; + tmp[1] = byteStringBuffer[i+1]; + tmp[2] = 0; + sscanf(tmp, "%02X", &byteValue); + byteBuffer[j] = (unsigned char)byteValue; + } + + /* + for(j=0; j < BYTE_BUFFER_SIZE; j++){ + printf("0x%02X ", byteBuffer[j]); + } + */ + + bfd_init( ); + target = argv[1]; + bfd_set_default_target(target); + + // printf("Debug: BFD sample file: %s\n", elf_file_location); + // printf("Debug: LITTLE: %s\n", LITTLE_ELF_FILE); + // printf("Debug: BIG: %s\n", BIG_ELF_FILE); + + if(endian == BFD_ENDIAN_BIG){ + bfdfile = bfd_openr(elf_file_location, target ); + if ( ! bfdfile ) { + printf("Error opening BIG ELF file: %s\n", elf_file_location); + bfd_perror( "Error on bfdfile" ); + return(3); + } + } + else{ + bfdfile = bfd_openr(elf_file_location, target ); + if ( ! bfdfile ) { + printf("Error opening LITTLE ELF file: %s\n", elf_file_location); + // bfdfile = bfd_openr(elf_file_location, target ); + bfd_perror( "Error on bfdfile" ); + return(3); + } + } + + memset((void*) &DI, 0x00, sizeof(struct disassemble_info)); + + disassemble_fn = NULL; + + // important set up! + //--------------------------------------- + ai.arch = arch; + ai.mach = mach; + bfd_set_arch_info(bfdfile, &ai); + //--------------------------------------- + + /* + bfd_error_type err = bfd_get_error(); + printf("bfd_error_msg: %s.\n", bfd_errmsg(err)); + */ + + configureBfd(bfdfile, arch, mach, endian, &DI, &disassemble_fn); + + /* + err = bfd_get_error(); + printf("bfd_error_msg: %s.\n", bfd_errmsg(err)); + */ + + if (disassemble_fn == NULL){ + fprintf(stderr, "Error: disassemble_fn is NULL. Nothing I can do.\n"); + exit(1); + } + else{ + /* + printf("the disassemble_fn func pointer is: 0x%08X.\n", disassemble_fn); + printf("We can try to disassemble for this arch/mach. calling disassemble_init_for_target().\n"); + */ + disassemble_init_for_target(&DI); + + // go diassemble the buffer and build up the result in a accumulator string buffer. + processBuffer(byteBuffer, size >> 1, offset, disassemble_fn, &DI); // + + } + + free((void*)bfdfile->xvec); + bfd_close(bfdfile); + + printf("EOF\n"); + fflush(stdout); + + } // while loop on lines of stdin + + return 0; +} diff --git a/GPL/GnuDisassembler/src/gdis/c/gdis.h b/GPL/GnuDisassembler/src/gdis/c/gdis.h new file mode 100644 index 0000000000..62501d6641 --- /dev/null +++ b/GPL/GnuDisassembler/src/gdis/c/gdis.h @@ -0,0 +1,94 @@ +/* ### + * IP: GPL 2 + */ +#ifndef _GDIS_H_ +#define _GDIS_H_ + +#define BYTE_BUFFER_SIZE 128 + +#define LITTLE_ELF_FILE "little.elf" // built for intel x64 +#define BIG_ELF_FILE "big.elf" + +#define BUFF_SIZE 128 + +#define MAX_DIS_STRING 128 +#define MAX_BYTES_STRING 64 +#define MAX_BYTES 64 +#define MAX_NUM_ENTRIES 64 +#define MAX_ELF_FILE_PATH_LEN 512 + + +typedef struct _DIS_INFO_{ + char disassemblyString[MAX_DIS_STRING]; + char bytesBufferAscii[MAX_BYTES_STRING]; + unsigned char bytesBufferBin[MAX_BYTES]; + + int count; /* Number of bytes consumed */ + + char insn_info_valid; /* Branch info has been set. */ + char branch_delay_insns; /* How many sequential insn's will run before + a branch takes effect. (0 = normal) */ + char data_size; /* Size of data reference in insn, in bytes */ + enum dis_insn_type insn_type; /* Type of instruction */ + bfd_vma target; /* Target address of branch or dref, if known; + zero if unknown. */ + bfd_vma target2; /* Second target address for dref2 */ + +} DIS_INFO, *PDIS_INFO; + +static DIS_INFO disassemblyInfoBuffer[MAX_NUM_ENTRIES]; + +char mnemonic[32] = {0}, src[32] = {0}, dest[32] = {0}, arg[32] = {0}; +char disassembled_buffer[BUFF_SIZE]; + + +/* Pseudo FILE object for strings. */ +typedef struct +{ + // char *buffer; + char buffer[BUFF_SIZE]; + size_t pos; + size_t alloc; +} SFILE; + + +static SFILE sfile; + +static char *default_target = NULL; /* Default at runtime. */ + +// ------------------------------------------------------------------------ + +void listSupportedArchMachTargets(void); + +int objdump_sprintf (SFILE *f, const char *format, ...); + +void configureDisassembleInfo(bfd* abfd, + disassemble_info* info, + enum bfd_architecture arch, + unsigned long mach, + enum bfd_endian end); + +disassembler_ftype configureBfd(bfd* abfd, + enum bfd_architecture arch, + unsigned long mach, + enum bfd_endian endian, + disassemble_info* DI, + disassembler_ftype* disassemble_fn); + +int disassemble_buffer( disassembler_ftype disassemble_fn, + disassemble_info *info, + int* offset, + PDIS_INFO pDisInfo); + +void processBuffer(unsigned char* buff, + int buff_len, + bfd_vma buff_vma, + disassembler_ftype disassemble_fn, + struct disassemble_info* DI); + + + + + + +#endif diff --git a/GPL/certification.local.manifest b/GPL/certification.local.manifest index 7a8712d22e..e1eb6d38d3 100644 --- a/GPL/certification.local.manifest +++ b/GPL/certification.local.manifest @@ -1,5 +1,4 @@ ##VERSION: 2.0 ##MODULE IP: Public Domain -build.gradle||Public Domain||||END| +gpl.gradle||Public Domain||||END| nativeBuildProperties.gradle||Public Domain||||END| -settings.gradle||Public Domain||||END| diff --git a/GPL/build.gradle b/GPL/gpl.gradle similarity index 86% rename from GPL/build.gradle rename to GPL/gpl.gradle index 1c21e360d2..857b444393 100644 --- a/GPL/build.gradle +++ b/GPL/gpl.gradle @@ -1,11 +1,13 @@ -project.ext.BIN_REPO = file("${projectDir}/../../ghidra.bin").absolutePath + +// BIN_REPO only useable in full Ghidra source configuration +project.ext.BIN_REPO = file("../../../ghidra.bin").absolutePath project.ext.set("OS_NAMES", ["osx64", "win32", "win64", "linux64"]) /********************************************************************************* * Returns the local platform name. *********************************************************************************/ -String getCurrentPlatformName() { +ext.getCurrentPlatformName = { String osName = System.getProperty("os.name") String archName = System.getProperty("os.arch") @@ -37,10 +39,13 @@ String getCurrentPlatformName() { * Helper method that returns a file that is the same relative location in the bin repo * as the given project is in its repo. ******************************************************************************************/ -File getProjectLocationInBinRepo(Project p) { - String relativePath = getGhidraRelativePath(p) - +ext.getProjectLocationInBinRepo = { + String relativePath = getGhidraRelativePath(this.project) + println("RELATIVE: $relativePath") File binRepoRootProject = new File("${BIN_REPO}") + if (!binRepoRootProject.isDirectory()) { + throw new GradleException("Task requires Ghidra source and ghidra.bin") + } return new File(binRepoRootProject, relativePath) } /**************************************************************************************** diff --git a/GPL/settings.gradle b/GPL/settings.gradle deleted file mode 100644 index ede08281de..0000000000 --- a/GPL/settings.gradle +++ /dev/null @@ -1,4 +0,0 @@ - -include "DemanglerGnu" -include "DMG" -include "CabExtract" diff --git a/Ghidra/Extensions/SleighDevTools/Module.manifest b/Ghidra/Extensions/SleighDevTools/Module.manifest new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Ghidra/Extensions/SleighDevTools/build.gradle b/Ghidra/Extensions/SleighDevTools/build.gradle new file mode 100644 index 0000000000..189b77a16f --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/build.gradle @@ -0,0 +1,9 @@ +apply from: "$rootProject.projectDir/gradle/distributableGhidraExtension.gradle" +apply from: "$rootProject.projectDir/gradle/javaProject.gradle" +apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle" +apply plugin: 'eclipse' +eclipse.project.name = 'Xtra SleighDevTools' + +dependencies { + compile project(':Base') +} diff --git a/Ghidra/Extensions/SleighDevTools/certification.manifest b/Ghidra/Extensions/SleighDevTools/certification.manifest new file mode 100644 index 0000000000..89700bdbb2 --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/certification.manifest @@ -0,0 +1,8 @@ +##VERSION: 2.0 +.classpath||GHIDRA||||END| +.project||GHIDRA||||END| +Module.manifest||GHIDRA||||END| +build.gradle||GHIDRA||||END| +data/ExtensionPoint.manifest||GHIDRA||||END| +data/LanguageMap.txt||GHIDRA||||END| +extension.properties||GHIDRA||||END| diff --git a/Ghidra/Extensions/SleighDevTools/data/ExtensionPoint.manifest b/Ghidra/Extensions/SleighDevTools/data/ExtensionPoint.manifest new file mode 100644 index 0000000000..6d84c6480d --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/data/ExtensionPoint.manifest @@ -0,0 +1 @@ +ExternalDisassembler diff --git a/Ghidra/Extensions/SleighDevTools/data/LanguageMap.txt b/Ghidra/Extensions/SleighDevTools/data/LanguageMap.txt new file mode 100644 index 0000000000..7bd7388436 --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/data/LanguageMap.txt @@ -0,0 +1,13 @@ +// Format: LanguageID#CustomGDISExecutable +// +// Mapping of LanguageNameFromGhidra to external (gdis) architecture names is no longer done here. +// This functionality has been moved to each language's ldefs file. +// External names are mapped via 'external_name' tags in language definitions. +// The CustomGDISExecutable is found via a call to Application.getOSFile(), which will search in +// the platform-specific OS directory within all modules. +// +// Lines starting with "//" are not parsed. +// +// '*' can be used to wild-card parts of the languageID +// + diff --git a/Ghidra/Extensions/SleighDevTools/extension.properties b/Ghidra/Extensions/SleighDevTools/extension.properties new file mode 100644 index 0000000000..71c6912cf2 --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/extension.properties @@ -0,0 +1,5 @@ +name=SleighDevTools +description=Sleigh language development tools including external disassembler capabilities. The GnuDisassembler extension may be also be required as a disassembly provider. +author=Ghidra Team +createdOn=6/18/2019 +version=@extversion@ diff --git a/Ghidra/Extensions/SleighDevTools/ghidra_scripts/CompareSleighExternal.java b/Ghidra/Extensions/SleighDevTools/ghidra_scripts/CompareSleighExternal.java new file mode 100644 index 0000000000..49b60cf71b --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/ghidra_scripts/CompareSleighExternal.java @@ -0,0 +1,320 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Compare Sliegh disassembly with external disassembly results + +import java.util.HashMap; +import java.util.List; + +import ghidra.app.script.GhidraScript; +import ghidra.app.util.PseudoDisassembler; +import ghidra.app.util.PseudoInstruction; +import ghidra.app.util.disassemble.GNUExternalDisassembler; +import ghidra.program.disassemble.Disassembler; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.UnknownInstructionException; +import ghidra.program.model.listing.BookmarkType; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.scalar.Scalar; +import ghidra.util.exception.CancelledException; + +public class CompareSleighExternal extends GhidraScript { + + @Override + public void run() throws Exception { + if (currentProgram == null) { + return; + } + AddressSetView set = currentSelection; + if (set == null || set.isEmpty()) { + set = currentProgram.getMemory().getLoadedAndInitializedAddressSet(); + } + + putEquivalent("xzr", "x31"); // Think they messed up and allowed x31, there is no x31 + putEquivalent("wzr", "w31"); // Think they messed up and allowed w31, there is no w31 + putEquivalent("r12", "ip"); + + int completed = 0; + monitor.initialize(set.getNumAddresses()); + + AddressIterator addresses = set.getAddresses(true); + + PseudoDisassembler pseudoDisassembler = new PseudoDisassembler(currentProgram); + + GNUExternalDisassembler dis = new GNUExternalDisassembler(); + + long align = currentProgram.getLanguage().getInstructionAlignment(); + while (addresses.hasNext()) { + monitor.checkCanceled(); + Address addr = addresses.next(); + + completed++; + + // only on valid boundaries + if ((addr.getOffset() % align) != 0) { + continue; + } + clearBad(addr); + + monitor.setProgress(completed); + + CodeUnit cu = currentProgram.getListing().getCodeUnitAt(addr); + if (cu == null) { + continue; + } + String str = dis.getDisassembly(cu); + + str = str.toLowerCase(); + + PseudoInstruction pinst = null; + try { + pinst = pseudoDisassembler.disassemble(addr); + } catch (UnknownInstructionException e) { + // didn't get an instruction, did external not get one? + if (str.startsWith(".inst") && str.endsWith("undefined")) { + continue; + } + markErrorBad(addr,"Unimplemented Instruction", str); + continue; + } + // didn't get an instruction, did external not get one? + if (pinst == null && str.startsWith(".inst") && str.endsWith("undefined")) { + continue; + } + + if (pinst == null) { + markErrorBad(addr,"Unimplemented Instruction", str); + continue; + } + + // collapse both instruction to strings, compare removing whitespace, and to-lower + String pStr = pinst.toString().toLowerCase().replaceAll("\\s",""); + String eStr = str.toLowerCase().replaceAll("\\s", ""); + + // simple equivalence + if (pStr.equals(eStr)) { + continue; + } + + String mnemonic = pinst.getMnemonicString().toLowerCase(); + if (!str.startsWith(mnemonic)) { + markBad(addr,"Mnemonic Disagreement", str + " != " + mnemonic); + continue; + } + + int start = str.indexOf(" "); + + for (int opIndex = 0; opIndex < pinst.getNumOperands(); opIndex++) { + // try to parse the operand string from the instruction + int sepEnd = str.indexOf(",", start); + + String extOp = getExtOpStr(str, start, sepEnd); + start = sepEnd + 1; + + String valStr = null; + + // TODO: could remove all characters, making sure none are left! + int loc = 0; + boolean subRegList = false; + List opObjList = pinst.getDefaultOperandRepresentationList(opIndex); + for (Object object : opObjList) { + if (object instanceof Character) { + Character ch = (Character) object; + ch = Character.toLowerCase(ch); + loc = extOp.indexOf(ch); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+1); + continue; + } + if (ch.equals(',')) { + if (subRegList) { + continue; + } + // gotta move into next string, must be embedded comma + sepEnd = str.indexOf(",", start); + + extOp = getExtOpStr(str, start, sepEnd); + start = sepEnd + 1; + continue; + } + if (ch.equals(' ')) { + continue; + } + markBad(addr,"Missing String Markup", ch.toString()); + break; + } + if (object instanceof Scalar) { + // find the scalar, hex or decimal + Scalar scalar = (Scalar) object; + valStr = scalar.toString(16, false, false, "0x", ""); + loc = extOp.indexOf(valStr); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length()); + continue; + } + valStr = scalar.toString(16, true, false, "0x", ""); + loc = extOp.indexOf(valStr); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length()); + continue; + } + valStr = scalar.toString(10, false, true, "", ""); + loc = extOp.indexOf(valStr); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length()); + continue; + } + valStr = scalar.toString(10, false, false, "", ""); + loc = extOp.indexOf(valStr); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length()); + continue; + } + valStr = scalar.toString(16, false, false, "", ""); + loc = extOp.indexOf(valStr); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length()); + continue; + } + valStr = scalar.toString(16, true, false, "", ""); + loc = extOp.indexOf(valStr); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length()); + continue; + } + markBad(addr,"Missing Scalar", valStr); + break; + } + if (object instanceof Register) { + Register reg = (Register) object; + loc = extOp.indexOf(reg.getName().toLowerCase()); + if (loc != -1) { + // check for '-' first + if (extOp.charAt(0) == '-') { + extOp = extOp.substring(1); + loc = 0; + subRegList = false; + } + extOp = extOp.substring(0,loc) + extOp.substring(loc+reg.getName().length()); + if (extOp.length() > 0 && extOp.charAt(0) == '-') { + subRegList = true; + } + continue; + } + + // check for equivalent register + String equivReg = regGetEquivalent(reg.getName()); + if (equivReg != null) { + loc = extOp.indexOf(equivReg); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+equivReg.length()); + continue; + } + } + + loc = extOp.indexOf('-'); // could be a register list, assume we will find beginning and end register + if (loc != -1) { + continue; + } + markBad(addr,"Missing Register", reg.toString()); + break; + } + if (object instanceof Address) { + Address dest = (Address) object; + valStr = dest.toString(false,true); + valStr = "0x" + valStr; + loc = extOp.indexOf(valStr); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length()); + continue; + } + valStr = dest.toString(false,false); + valStr = "0x" + valStr; + loc = extOp.indexOf(valStr); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length()); + continue; + } + valStr = dest.toString(false,true); + loc = extOp.indexOf(valStr); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length()); + continue; + } + valStr = dest.toString(false,false); + loc = extOp.indexOf(valStr); + if (loc != -1) { + extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length()); + continue; + } + markBad(addr,"Missing Address", dest.toString()); + } + } + extOp = extOp.trim(); + if (extOp.length() > 0 && !extOp.startsWith(";") && !extOp.startsWith("//") && !extOp.equals("#") && !extOp.matches("[0x]+")) { + markBad(addr,"Missing characters", extOp); + } + } + } + + } + + HashMap equivRegisters = new HashMap(); + + private String regGetEquivalent(String name) { + return equivRegisters.get(name); + } + + private void putEquivalent(String name, String equiv) { + equivRegisters.put(name, equiv); + } + + private String getExtOpStr(String str, int start, int sepEnd) { + String opS = null; + if (start == -1) { + return ""; + } + if (sepEnd == -1) { + opS = str.substring(start); + } else { + opS = str.substring(start, sepEnd); + } + String extOp = opS.trim(); + return extOp; + } + + private void markBad(Address addr, String type, String error) { + currentProgram.getBookmarkManager().setBookmark(addr, BookmarkType.WARNING, + type, + error); + } + + private void markErrorBad(Address addr, String type, String error) { + currentProgram.getBookmarkManager().setBookmark(addr, BookmarkType.ERROR, + Disassembler.ERROR_BOOKMARK_CATEGORY, + error); + } + + private void clearBad(Address addr) { + AddressSet set = new AddressSet(addr); + try { + currentProgram.getBookmarkManager().removeBookmarks(set, BookmarkType.WARNING, monitor); + currentProgram.getBookmarkManager().removeBookmarks(set, BookmarkType.ERROR, monitor); + } catch (CancelledException e) { + // do nothing + } + } +} diff --git a/Ghidra/Extensions/SleighDevTools/ghidra_scripts/GNUDisassembleBlockScript.java b/Ghidra/Extensions/SleighDevTools/ghidra_scripts/GNUDisassembleBlockScript.java new file mode 100644 index 0000000000..e0ba52c84f --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/ghidra_scripts/GNUDisassembleBlockScript.java @@ -0,0 +1,79 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import ghidra.app.script.GhidraScript; +import ghidra.app.util.disassemble.GNUExternalDisassembler; +import ghidra.app.util.disassemble.GnuDisassembledInstruction; +import ghidra.program.model.address.Address; + +import java.util.List; + +public class GNUDisassembleBlockScript extends GhidraScript { + + @Override + protected void run() throws Exception { + + if (currentProgram == null || currentAddress == null) { + return; + } + + GNUExternalDisassembler dis = new GNUExternalDisassembler(); + + Address addr = currentAddress.getNewAddress(currentAddress.getOffset() & -32); // block aligned address + + List results = dis.getBlockDisassembly(currentProgram, addr, 5); + + if (results == null) { + println("Block Disassembly Failed!"); + return; + } + + int maxByteLen = 0; + for (GnuDisassembledInstruction result : results) { + maxByteLen = Math.max(maxByteLen, result.getNumberOfBytesInInstruction()); + } + + StringBuilder sb = new StringBuilder(); + for (GnuDisassembledInstruction result : results) { + sb.append(addr.toString()); + sb.append(' '); + int cnt = 0; + byte[] bytes = new byte[result.getNumberOfBytesInInstruction()]; + currentProgram.getMemory().getBytes(addr, bytes); + for (byte b : bytes) { + if (b >= 0 && b < 0x10) { + sb.append('0'); + } + sb.append(Integer.toHexString(b & 0xff)); + sb.append(' '); + ++cnt; + } + if (cnt < maxByteLen) { + int pad = (maxByteLen - cnt) * 3; + for (int i = 0; i < pad; i++) { + sb.append(' '); + } + } + sb.append(result.getInstruction()); + sb.append("\n"); + addr = addr.add(bytes.length); + } + if (sb.length() != 0) { + println("Block Disassembly:\n" + sb.toString()); + } + + } + +} diff --git a/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalDisassembler.java b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalDisassembler.java new file mode 100644 index 0000000000..0629e68642 --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalDisassembler.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.disassemble; + +import ghidra.program.model.lang.Language; +import ghidra.program.model.listing.CodeUnit; +import ghidra.util.classfinder.ExtensionPoint; + +public interface ExternalDisassembler extends ExtensionPoint { + + public String getDisassembly(CodeUnit cu) throws Exception; + + public String getDisassemblyOfBytes(Language language, boolean isBigEndian, long address, + byte[] byteString) throws Exception; + + public boolean isSupportedLanguage(Language language); + + public void destroy(); +} diff --git a/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalDisassemblyFieldFactory.java b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalDisassemblyFieldFactory.java new file mode 100644 index 0000000000..f8d99018ac --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalDisassemblyFieldFactory.java @@ -0,0 +1,166 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.disassemble; + +import java.awt.Color; +import java.math.BigInteger; +import java.util.*; + +import docking.widgets.fieldpanel.field.*; +import docking.widgets.fieldpanel.support.FieldLocation; +import ghidra.app.util.HighlightProvider; +import ghidra.app.util.viewer.field.*; +import ghidra.app.util.viewer.format.FieldFormatModel; +import ghidra.app.util.viewer.proxy.ProxyObj; +import ghidra.framework.options.Options; +import ghidra.framework.options.ToolOptions; +import ghidra.program.model.lang.Language; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.util.ProgramLocation; +import ghidra.util.classfinder.ClassSearcher; + +public class ExternalDisassemblyFieldFactory extends FieldFactory { + + private static List availableDisassemblers; + + private static synchronized List getAvailableDisassemblers() { + if (availableDisassemblers != null) { + return availableDisassemblers; + } + availableDisassemblers = new ArrayList<>(); + + // find the available external disassemblers + Set extDisassemblers = + ClassSearcher.getInstances(ExternalDisassembler.class); + + for (ExternalDisassembler disassember : extDisassemblers) { + availableDisassemblers.add(disassember); + } + return availableDisassemblers; + } + + public static final String FIELD_NAME = "External Disassembly"; + + public ExternalDisassemblyFieldFactory() { + super(FIELD_NAME); + } + + private ExternalDisassemblyFieldFactory(FieldFormatModel model, HighlightProvider hlProvider, + Options displayOptions, Options fieldOptions) { + super(FIELD_NAME, model, hlProvider, displayOptions, fieldOptions); + } + + @Override + public void fieldOptionsChanged(Options options, String optionName, Object oldValue, + Object newValue) { + // have no options + } + + @Override + public boolean acceptsType(int category, Class proxyObjectClass) { + return (category == FieldFormatModel.INSTRUCTION_OR_DATA); + } + + @Override + public FieldLocation getFieldLocation(ListingField bf, BigInteger index, int fieldNum, + ProgramLocation loc) { + if (loc instanceof ExternalDisassemblyFieldLocation) { + return new FieldLocation(index, fieldNum, + ((ExternalDisassemblyFieldLocation) loc).getRow(), + ((ExternalDisassemblyFieldLocation) loc).getCharOffset()); + } + return null; + } + + @Override + public ProgramLocation getProgramLocation(int row, int col, ListingField bf) { + ProxyObj proxy = bf.getProxy(); + Object obj = proxy.getObject(); + if (!(obj instanceof CodeUnit)) { + return null; + } + CodeUnit cu = (CodeUnit) obj; + + return new ExternalDisassemblyFieldLocation(cu.getProgram(), cu.getMinAddress(), row, col); + } + + @Override + public FieldFactory newInstance(FieldFormatModel formatModel, + HighlightProvider highlightProvider, ToolOptions options, ToolOptions fieldOptions) { + return new ExternalDisassemblyFieldFactory(formatModel, highlightProvider, options, + fieldOptions); + } + + private ExternalDisassembler getDisassembler(Language language) { + for (ExternalDisassembler disassembler : getAvailableDisassemblers()) { + if (disassembler.isSupportedLanguage(language)) { + return disassembler; + } + } + return null; + } + + @Override + public ListingField getField(ProxyObj proxy, int varWidth) { + if (!enabled) { + return null; + } + Object obj = proxy.getObject(); + if (!(obj instanceof CodeUnit)) { + return null; + } + CodeUnit cu = (CodeUnit) obj; + + try { + String disassembly = getDisassemblyText(cu); + if (disassembly == null) { + return null; + } + AttributedString text = new AttributedString(disassembly, Color.black, getMetrics()); + FieldElement fieldElement = new TextFieldElement(text, 0, 0); + return ListingTextField.createSingleLineTextField(this, proxy, fieldElement, + startX + varWidth, width, hlProvider); + } + catch (Exception e) { + return getErrorText(proxy, varWidth, e); + } + } + + private String getDisassemblyText(CodeUnit cu) throws Exception { + Language language = cu.getProgram().getLanguage(); + ExternalDisassembler disassembler = getDisassembler(language); + if (disassembler == null) { + return null; + } + String disassembly = disassembler.getDisassembly(cu); + if (disassembly == null) { + return null; + } + + return disassembly; + } + + private ListingTextField getErrorText(ProxyObj proxy, int varWidth, Exception e) { + String message = e.getMessage(); + if (message == null) { + message = e.toString(); + } + AttributedString errorText = new AttributedString(message, Color.red, getMetrics()); + FieldElement fieldElement = new TextFieldElement(errorText, 0, 0); + return ListingTextField.createSingleLineTextField(this, proxy, fieldElement, + startX + varWidth, width, hlProvider); + } +} diff --git a/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalDisassemblyFieldLocation.java b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalDisassemblyFieldLocation.java new file mode 100644 index 0000000000..c84b35d374 --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalDisassemblyFieldLocation.java @@ -0,0 +1,35 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.disassemble; + +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; + +public class ExternalDisassemblyFieldLocation extends ProgramLocation { + + public ExternalDisassemblyFieldLocation(Program program, Address addr, int row, int charOffset) { + super(program, addr, row, 0, charOffset); + } + + /** + * Get the row within a group of pcode strings. + */ + public ExternalDisassemblyFieldLocation() { + // for deserialization + } + +} diff --git a/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalStreamHandler.java b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalStreamHandler.java new file mode 100644 index 0000000000..4170f774ab --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/ExternalStreamHandler.java @@ -0,0 +1,43 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.disassemble; + +import ghidra.util.Msg; + +import java.io.*; + +public class ExternalStreamHandler extends Thread { + private InputStream inStream; + + ExternalStreamHandler(InputStream inStream) { + this.inStream = inStream; + } + + @Override + public void run() { + try { + InputStreamReader inStreamReader = new InputStreamReader(inStream); + BufferedReader buffReader = new BufferedReader(inStreamReader); + String line; + while ((line = buffReader.readLine()) != null) { + Msg.error(ExternalDisassemblyFieldFactory.class, "Error in Disassembler: " + line); + } + } + catch (IOException ioe) { + ioe.printStackTrace(); + } + } +} diff --git a/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/GNUExternalDisassembler.java b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/GNUExternalDisassembler.java new file mode 100644 index 0000000000..f71797c991 --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/GNUExternalDisassembler.java @@ -0,0 +1,646 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.disassemble; + +import java.io.*; +import java.util.*; + +import generic.jar.ResourceFile; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.MemoryByteProvider; +import ghidra.framework.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressOutOfBoundsException; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemBuffer; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.util.Msg; + +public class GNUExternalDisassembler implements ExternalDisassembler { + + private static final String UNSUPPORTED = "UNSUPPORTED"; + + // magic values for gdis that direct it to read bytes from stdin + private static final String READ_FROM_STDIN_PARAMETER = "stdin"; + private static final String SEPARATOR_CHARACTER = "\n"; + private static final String ADDRESS_OUT_OF_BOUNDS = "is out of bounds."; + private static final String ENDING_STRING = "EOF"; + private static final int NUM_BYTES = 32; + + private static final String MAP_FILENAME = "LanguageMap.txt"; + private static final String GNU_DISASSEMBLER_MODULE_NAME = "GnuDisassembler"; + private static final String GDIS_EXE = + Platform.CURRENT_PLATFORM.getOperatingSystem() == OperatingSystem.WINDOWS ? "gdis.exe" + : "gdis"; + + private static HashMap languageGdisMap; + private static File defaultGdisExecFile; + private static File gdisDataDirectory; + + private static Map configCache = new HashMap<>(); + private static boolean missingExtensionReported; + + private GdisConfig currentConfig; + private boolean hadFailure; + + private Process disassemblerProcess; + private BufferedReader buffReader; + private OutputStreamWriter outputWriter; + +// private LanguageID lastLanguageWarnedAbout; + + public GNUExternalDisassembler() throws Exception { + initialize(); + } + + @Override + public void destroy() { + if (disassemblerProcess != null) { + disassemblerProcess.destroy(); + } + } + + @Override + public boolean isSupportedLanguage(Language language) { + GdisConfig gdisConfig = checkLanguage(language); + return gdisConfig != null && gdisConfig.architecture != UNSUPPORTED; + } + + private static void reportMultipleMappings(Language language) { + List externalNames = language.getLanguageDescription().getExternalNames("gnu"); + if (externalNames != null && externalNames.size() > 1) { + LanguageID currentLanguageID = language.getLanguageID(); + StringBuilder sb = new StringBuilder(); + boolean prependSeparator = false; + for (String name : externalNames) { + if (prependSeparator) + sb.append(", "); + sb.append(name); + prependSeparator = true; + } + Msg.warn(GNUExternalDisassembler.class, + "Language " + currentLanguageID + " illegally maps to multiple (" + + externalNames.size() + ") external gnu names: " + sb.toString() + + ". The first external name will be used."); + } + } + + private static class GdisConfig { + + String languageId; + boolean isBigEndian; + + String architecture; + String machineId; + File gdisExecFile; + boolean usingDefault; + + GdisConfig(Language language, boolean isBigEndian) { + + this.languageId = language.getLanguageID().toString(); + this.isBigEndian = isBigEndian; + + List architectures = language.getLanguageDescription().getExternalNames("gnu"); + //get first non-null + if (architectures != null && architectures.size() > 0) { + architecture = architectures.get(0); + if (architectures.size() > 1) { + reportMultipleMappings(language); + } + } + if (architecture == null) { + architecture = UNSUPPORTED; + return; + } + + machineId = "0x0"; + + // handle numeric entry which combines architecture and machineId + if (architecture.startsWith("0x")) { + String[] parts = architecture.split(":"); + architecture = parts[0]; + machineId = parts[1]; + } + + gdisExecFile = languageGdisMap.get(languageId); + if (gdisExecFile == null) { + gdisExecFile = defaultGdisExecFile; + usingDefault = true; + } + } + + GdisConfig(Language lang) { + this(lang, lang.isBigEndian()); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof GdisConfig)) { + return false; + } + // assume config will match for a given language + return languageId.equals(((GdisConfig) obj).languageId); + } + + @Override + public int hashCode() { + return languageId.hashCode(); + } + } + + private static synchronized GdisConfig checkLanguage(Language lang) { + LanguageID languageId = lang.getLanguageID(); + if (configCache.containsKey(languageId)) { + return configCache.get(languageId); + } + GdisConfig config = new GdisConfig(lang); + if (config.architecture == UNSUPPORTED) { + Msg.warn(GNUExternalDisassembler.class, + "Language not supported (ldefs 'gnu' map entry not found): " + languageId); + } + else if (gdisDataDirectory == null) { + config = null; + if (!missingExtensionReported) { + missingExtensionReported = true; + Msg.showError(GNUExternalDisassembler.class, null, "GNU Disassembler Not Found", + "External Disassembler extension module not installed: " + + GNU_DISASSEMBLER_MODULE_NAME); + } + } + else if (config.gdisExecFile == null) { + boolean usingDefault = config.usingDefault; + config = null; + if (usingDefault) { + if (!missingExtensionReported) { + missingExtensionReported = true; + Msg.showError(GNUExternalDisassembler.class, null, "GNU Disassembler Not Found", + "External GNU Disassembler not found (requires install and build of " + + GNU_DISASSEMBLER_MODULE_NAME + " extension): " + GDIS_EXE); + } + } + else { + Msg.showError(GNUExternalDisassembler.class, null, "GNU Disassembler Not Found", + "External GNU Disassembler not found for language (" + lang.getLanguageID() + + ", see LanguageMap.txt)"); + } + } + configCache.put(languageId, config); + return config; + } + + private int pow2(int pow) { + int r = 1; + for (int i = 1; i <= pow; i++) { + r *= 2; + } + return r; + } + + /** + * Get detailed instruction list for a block of instructions. + * + * @param lang + * processor language (corresponding LanguageID must be defined + * within LanguageMap.txt) + * @param blockAddr + * start of block ( must be true: (offset & -(2^blockSizeFactor) + * == offset) + * @param blockSizeFactor + * the block size factor where blockSize = 2^blockSizeFactor + * (must be > 0) + * @param byteProvider + * provider for block of bytes to be disassembled starting at + * offset 0 + * @return list of instructions or null if language not supported by GNU + * Disassembler + * @throws Exception + */ + public List getBlockDisassembly(Language lang, Address blockAddr, + int blockSizeFactor, ByteProvider byteProvider) throws Exception { + + GdisConfig gdisConfig = checkLanguage(lang); + if (gdisConfig == null || gdisConfig.architecture == UNSUPPORTED) { + return null; + } + + if (blockSizeFactor < 0 || blockSizeFactor > 8) { + throw new IllegalArgumentException("blockSizeFactor must be > 0 and <= 8"); + } + int blockSize = pow2(blockSizeFactor); + + if ((blockAddr.getOffset() & -blockSize) != blockAddr.getOffset()) { + throw new IllegalArgumentException("Address must be block aligned"); + } + + long addressOffset = blockAddr.getAddressableWordOffset(); + String address = "0x" + Long.toHexString(addressOffset); + + // for aligned languages, don't try on non-aligned block addr/size. + int alignment = lang.getInstructionAlignment(); + if (blockAddr.getOffset() % alignment != 0) { + throw new IllegalArgumentException( + "Address does not satisfy instruction alignment constraint: " + alignment); + } + + String bytes = getBytes(byteProvider, blockSize); + + return runDisassembler(gdisConfig, address, bytes); + } + + public List getBlockDisassembly(Program program, Address addr, + int blockSizeFactor) throws Exception { + + if (blockSizeFactor < 0 || blockSizeFactor > 8) { + throw new IllegalArgumentException("blockSizeFactor must be > 0 and <= 8"); + } + int blockSize = pow2(blockSizeFactor); + + Address blockAddr = addr.getNewAddress(addr.getOffset() & -blockSize); // block + // aligned + // address + + return getBlockDisassembly(program.getLanguage(), blockAddr, blockSizeFactor, + new MemoryByteProvider(program.getMemory(), blockAddr)); + } + + @Override + public String getDisassembly(CodeUnit cu) throws Exception { + + GdisConfig gdisConfig = checkLanguage(cu.getProgram().getLanguage()); + if (gdisConfig == null || gdisConfig.architecture == UNSUPPORTED) { + return null; + } + + long addressOffset = cu.getAddress().getAddressableWordOffset(); + String address = "0x" + Long.toHexString(addressOffset); + + // for aligned languages, don't try on non-aligned locations. + if (cu.getMinAddress().getOffset() % + cu.getProgram().getLanguage().getInstructionAlignment() != 0) { + return ""; + } + + String bytes = getBytes(cu, NUM_BYTES); + if (bytes == null) { + return ""; + } + + List disassembly = runDisassembler(gdisConfig, address, bytes); + + if (disassembly == null || disassembly.size() == 0 || disassembly.get(0) == null) { + return "(bad)"; + } + return disassembly.get(0).toString(); + } + + // disassembler without having to have a code unit + @Override + public String getDisassemblyOfBytes(Language language, boolean isBigEndian, long addressOffset, + byte[] bytes) throws Exception { + + GdisConfig gdisConfig = new GdisConfig(language, isBigEndian); + if (gdisConfig.architecture == UNSUPPORTED || gdisConfig.gdisExecFile == null) { + return null; + } + + String bytesString = converBytesToString(bytes); + + String address = "0x" + Long.toHexString(addressOffset); + + List disassembly = + runDisassembler(gdisConfig, address, bytesString); + + if (disassembly == null || disassembly.isEmpty() || disassembly.get(0) == null) { + return "(bad)"; + } + return disassembly.get(0).toString(); + } + + private String converBytesToString(byte[] bytes) { + String byteString = null; + for (byte thisByte : bytes) { + String thisByteString = Integer.toHexString(thisByte); + if (thisByteString.length() == 1) + thisByteString = "0" + thisByteString; // pad single digits + if (thisByteString.length() > 2) + thisByteString = thisByteString.substring(thisByteString.length() - 2); + // append this byte's hex string to the larger word length string + byteString = byteString + thisByteString; + } + + return byteString; + } + + private boolean setupDisassembler(GdisConfig gdisConfig) { + + if (disassemblerProcess != null) { + disassemblerProcess.destroy(); + disassemblerProcess = null; + outputWriter = null; + buffReader = null; + } + + this.currentConfig = gdisConfig; + hadFailure = false; + + String endianString = gdisConfig.isBigEndian ? "0x00" : "0x01"; // 0x0 is big, 0x1 is little endian + + // NOTE: valid target must be specified but has no effect on results + String cmds[] = { gdisConfig.gdisExecFile.getAbsolutePath(), "pef", gdisConfig.architecture, + gdisConfig.machineId, endianString, "0x0", + gdisDataDirectory.getAbsolutePath() + File.separator, + GNUExternalDisassembler.READ_FROM_STDIN_PARAMETER }; + + StringBuilder buf = new StringBuilder(); + for (String str : cmds) { + boolean addQuotes = str.indexOf(' ') >= 0; + if (addQuotes) { + buf.append('\"'); + } + buf.append(str); + if (addQuotes) { + buf.append('\"'); + } + buf.append(' '); + } + Msg.debug(this, "Starting gdis: " + buf.toString()); + + try { + Runtime rt = Runtime.getRuntime(); + disassemblerProcess = rt.exec(cmds, null, gdisConfig.gdisExecFile.getParentFile()); + } + catch (IOException e) { + buf = new StringBuilder(); + for (String arg : cmds) { + buf.append("\""); + buf.append(arg); + buf.append("\" "); + } + Msg.debug(this, "GNU Disassembly setup failed, exec command: " + buf); + Msg.showError(this, null, "GNU Disassembler Error", + "Disassembler setup execution error: " + e.getMessage(), e); + hadFailure = true; + return false; + } + return true; + } + + private List runDisassembler(GdisConfig gdisConfig, + String addrString, String bytes) throws IOException { + + // if this is the first time running the disassembler process, or a + // parameter has changed (notably, not the address--we pass that in + // every time) + boolean sameConfig = gdisConfig.equals(currentConfig); + if (sameConfig && hadFailure) { + return null; + } + + if (disassemblerProcess == null || !sameConfig) { + + if (!setupDisassembler(gdisConfig)) + return null; + + outputWriter = new OutputStreamWriter(disassemblerProcess.getOutputStream()); + + InputStreamReader inStreamReader = + new InputStreamReader(disassemblerProcess.getInputStream()); + buffReader = new BufferedReader(inStreamReader); + + ExternalStreamHandler errorHandler = + new ExternalStreamHandler(disassemblerProcess.getErrorStream()); + errorHandler.start(); + } + + if (!disassemblerProcess.isAlive()) { + return null; // if process previously died return nothing - quickly + } + + String disassemblyRequest = addrString + SEPARATOR_CHARACTER + bytes + SEPARATOR_CHARACTER; + try { + outputWriter.write(disassemblyRequest); + outputWriter.flush(); + return getDisassembledInstruction(); + } + catch (IOException e) { + // force a restart of the disassembler on next call to this function + // TODO: Should we not do this to avoid repeated failure and severe slowdown? + // User must exit or switch configs/programs to retry after failure + //disassemblerProcess.destroy(); + //disassemblerProcess = null; // assumes process exit + Msg.error(this, "Last gdis request failed: " + disassemblyRequest); + throw new IOException("gdis execution error", e); + } + } + + private List getDisassembledInstruction() throws IOException { + + List results = new ArrayList<>(); + String instructionLine; + + boolean error = false; + do { + instructionLine = buffReader.readLine(); + if (!error && instructionLine != null && !instructionLine.equals(ENDING_STRING) && + (instructionLine.indexOf(ADDRESS_OUT_OF_BOUNDS) < 0) && + !instructionLine.startsWith("Usage:") && !instructionLine.startsWith("Debug:")) { + + String instructionMetadataLine = buffReader.readLine(); + if (!instructionMetadataLine.startsWith("Info: ")) { + // TODO, throw an "ExternalDisassemblerInterfaceException" + // or some such + error = true; // still need to consume remainder of input + continue; + } + String[] metadata = instructionMetadataLine.substring("Info: ".length()).split(","); + results.add(new GnuDisassembledInstruction(instructionLine.replace('\t', ' '), + Integer.parseInt(metadata[0]), "1".equals(metadata[1]), + Integer.parseInt(metadata[2]), Integer.parseInt(metadata[3]), + Integer.parseInt(metadata[4]))); + } + } + while (instructionLine != null && !instructionLine.equals(ENDING_STRING)); + + if (!disassemblerProcess.isAlive()) { + throw new IOException("GNU disassembler process died unexpectedly."); + } + + if (error) { + return null; + } + + return results; + } + + private String getBytes(ByteProvider byteProvider, int size) throws IOException { + StringBuffer byteString = new StringBuffer(); + for (int i = 0; i < size; i++) { + byteString.append(formatHexString(byteProvider.readByte(i))); + } + return byteString.toString(); + } + + private String getBytes(MemBuffer mem, int size) { + StringBuffer byteString = new StringBuffer(); + for (int i = 0; i < size; i++) { + try { + byteString.append(formatHexString(mem.getByte(i))); + } + catch (AddressOutOfBoundsException e) { + break; + } + catch (MemoryAccessException e) { + if (i > 0) { + break; + } + return null; + } + } + return byteString.toString(); + } + + private String formatHexString(byte byteToFix) { + String byteString = ""; + String singleByte = ""; + if (byteToFix < 0) { + singleByte = Integer.toHexString(byteToFix + 256); + } + else { + singleByte = Integer.toHexString(byteToFix); + } + if (singleByte.length() == 1) { + byteString += '0'; + } + byteString += singleByte; + return byteString; + } + + private static synchronized void initialize() throws Exception { + if (languageGdisMap != null) { + return; + } + + languageGdisMap = new HashMap<>(); + + try { + // sample elf files located in data directory + ResourceFile dataDir = + Application.getModuleSubDirectory(GNU_DISASSEMBLER_MODULE_NAME, "data"); + gdisDataDirectory = dataDir.getFile(false); + defaultGdisExecFile = Application.getOSFile(GNU_DISASSEMBLER_MODULE_NAME, GDIS_EXE); + } + catch (FileNotFoundException e) { + // ignore + } + + if (gdisDataDirectory == null) { + Msg.warn(GNUExternalDisassembler.class, + "Use of External GNU Disassembler requires installation of extension: " + + GNU_DISASSEMBLER_MODULE_NAME); + } + + initializeMaps(); + + if (defaultGdisExecFile == null || !defaultGdisExecFile.canExecute()) { + Msg.warn(GNUExternalDisassembler.class, + "External GNU Disassembler not found: " + GDIS_EXE); + defaultGdisExecFile = null; + } + } + + /** + * Process all language maps defined by any module. Any alternate external disassembler + * executable will be looked for within the os directory of the contributing module or + * within the gdis module + * @throws Exception + */ + private static void initializeMaps() { + for (ResourceFile file : Application.findFilesByExtensionInApplication(".txt")) { + if (MAP_FILENAME.equals(file.getName())) { + initializeMap(file); + } + } + } + + private static void initializeMap(ResourceFile mapFile) { + + ResourceFile moduleForResourceFile = Application.getModuleContainingResourceFile(mapFile); + if (moduleForResourceFile == null) { + Msg.error(GNUExternalDisassembler.class, + "Failed to identify module containing file: " + mapFile); + return; + } + + Reader mapFileReader = null; + try { + mapFileReader = new InputStreamReader(mapFile.getInputStream()); + BufferedReader reader = new BufferedReader(mapFileReader); + String line = null; + while ((line = reader.readLine()) != null) { + if (line.startsWith("//") || line.isEmpty()) { + continue; + } + String[] parts = line.split("#"); + if (parts.length > 1) { + + //System.out.println("found: " + parts[0] + " . " + parts[1]); + + // TODO: should probably store exe module/name in map and defer search + // until GdisConfig is created. This will allow us to complain about a + // missing exe when it is needed/used. + + String gdisExe = parts[1]; + if (Platform.CURRENT_PLATFORM.getOperatingSystem() == OperatingSystem.WINDOWS) { + gdisExe = gdisExe + ".exe"; + } + try { + File customGdisExecFile; + try { + customGdisExecFile = + Application.getOSFile(moduleForResourceFile.getName(), gdisExe); + } + catch (FileNotFoundException e) { + customGdisExecFile = Application.getOSFile(gdisExe); + } + languageGdisMap.put(parts[0], customGdisExecFile); + } + catch (FileNotFoundException e) { + Msg.error(GNUExternalDisassembler.class, + "External disassembler not found (" + parts[0] + "): " + gdisExe); + } + + } + } + } + catch (Exception e) { + Msg.error(GNUExternalDisassembler.class, + "Error reading from language mapping file: " + mapFile, e); + } + finally { + if (mapFileReader != null) { + try { + mapFileReader.close(); + } + catch (Exception e) { + // we tried + } + } + } + } +} diff --git a/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/GnuDisassembledInstruction.java b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/GnuDisassembledInstruction.java new file mode 100644 index 0000000000..27940c1604 --- /dev/null +++ b/Ghidra/Extensions/SleighDevTools/src/main/java/ghidra/app/util/disassemble/GnuDisassembledInstruction.java @@ -0,0 +1,82 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.disassemble; + +/** + * Holds the disassembled string of an instruction and the extra information + * (type, number of bytes disassembled to produce instruction, etc.) of bytes + * disassembled by the GNU disassembler. + */ +public class GnuDisassembledInstruction { + + private final String instruction; + private final int bytesInInstruction; + + private final int branchDelayInstructions; + private final int dataSize; + private final DIS_INSN_TYPE instructionType; + private final boolean isValid; + + // from GNU binutils include/dis-asm.h + enum DIS_INSN_TYPE { + dis_noninsn, /* Not a valid instruction. */ + dis_nonbranch, /* Not a branch instruction. */ + dis_branch, /* Unconditional branch. */ + dis_condbranch, /* Conditional branch. */ + dis_jsr, /* Jump to subroutine. */ + dis_condjsr, /* Conditional jump to subroutine. */ + dis_dref, /* Data reference instruction. */ + dis_dref2 /* Two data references in instruction. */ + } + + public GnuDisassembledInstruction(String instructionLine, int bytesInInstruction, + boolean isValid, int branchDelayInstructions, int dataSize, int disInsnTypeOrdinal) { + + this.instruction = instructionLine.trim(); + this.bytesInInstruction = bytesInInstruction; + + this.isValid = isValid; + this.branchDelayInstructions = branchDelayInstructions; + this.dataSize = dataSize; + this.instructionType = DIS_INSN_TYPE.values()[disInsnTypeOrdinal]; + } + + public int getNumberOfBytesInInstruction() { + return bytesInInstruction; + } + + public DIS_INSN_TYPE getInstructionType() { + return isValid ? instructionType : null; + } + + public int getBranchDelayInstructions() { + return isValid ? branchDelayInstructions : null; + } + + public int getDataSize() { + return isValid ? dataSize : null; + } + + public String getInstruction() { + return instruction; + } + + @Override + public String toString() { + return instruction; + } + +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java index fbd7d0f095..b65cc2504b 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java @@ -142,20 +142,22 @@ public class ClassSearcher { } catch (InstantiationException e) { Msg.showError(ClassSearcher.class, null, "Error Instantiating Extension Point", - "Error creating class " + clazz.getName() + " for extension " + c.getName() + + "Error creating class " + clazz.getSimpleName() + " for extension " + + c.getName() + ". Discovered class is not a concrete implementation or does not " + "have a nullary constructor!", e); } catch (IllegalAccessException e) { Msg.showError(ClassSearcher.class, null, "Error Instantiating Extension Point", - "Error creating class " + clazz.getName() + " for extension " + c.getName() + + "Error creating class " + clazz.getSimpleName() + " for extension " + + c.getName() + ". Discovered class does not have a public, default constructor!", e); } catch (SecurityException e) { - String message = "Error creating class " + clazz.getName() + " for extension " + - c.getName() + ". Security Exception!"; + String message = "Error creating class " + clazz.getSimpleName() + + " for extension " + c.getName() + ". Security Exception!"; Msg.showError(ClassSearcher.class, null, "Error Instantiating Extension Point", message, e); @@ -163,7 +165,7 @@ public class ClassSearcher { } catch (Exception e) { Msg.showError(ClassSearcher.class, null, "Error Creating Extension Point", - "Error creating class " + clazz.getName() + + "Error creating class " + clazz.getSimpleName() + " when creating extension points for " + c.getName(), e); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionDetailsPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionDetailsPanel.java index b1851cc3d5..95c46eb737 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionDetailsPanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionDetailsPanel.java @@ -36,6 +36,7 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel { private static SimpleAttributeSet authorAttrSet; private static SimpleAttributeSet createdOnAttrSet; private static SimpleAttributeSet versionAttrSet; + private static SimpleAttributeSet pathAttrSet; ExtensionDetailsPanel(ExtensionTablePanel tablePanel) { super(); @@ -105,6 +106,12 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel { insertRowValue(buffer, version, versionAttrSet); } + String installPath = details.getInstallPath(); + if (installPath != null) { + insertRowTitle(buffer, "Install Path"); + insertRowValue(buffer, installPath, pathAttrSet); + } + buffer.append(""); textLabel.setText(buffer.toString()); @@ -119,5 +126,6 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel { authorAttrSet = createAttributeSet(Color.BLUE); createdOnAttrSet = createAttributeSet(Color.BLUE); versionAttrSet = createAttributeSet(Color.BLUE); + pathAttrSet = createAttributeSet(Color.BLUE); } } diff --git a/gradle/certification.manifest b/gradle/certification.manifest index bf7e957f08..85841ce37a 100644 --- a/gradle/certification.manifest +++ b/gradle/certification.manifest @@ -1,5 +1,6 @@ ##VERSION: 2.0 README.txt||GHIDRA||||END| +distributableGPLExtension.gradle||GHIDRA||||END| distributableGPLModule.gradle||GHIDRA||||END| distributableGhidraExtension.gradle||GHIDRA||||END| distributableGhidraModule.gradle||GHIDRA||||END| diff --git a/gradle/distributableGPLExtension.gradle b/gradle/distributableGPLExtension.gradle new file mode 100644 index 0000000000..5c6534342e --- /dev/null +++ b/gradle/distributableGPLExtension.gradle @@ -0,0 +1,26 @@ +/***************************************************************************************** + This file is a "mix-in" gradle script that individual gradle projects should include if it + has content that should be included in a distribution as an extension. Including this + will cause all the standard module files to be included in the build as a sub-zipped extension. + + A gradle project can add itself as an extension to the build distribution by adding the + following to its build.gradle file: + + apply from: "$rootProject.projectDir/gradle/support/distributableGhidraModule.gradle" +*****************************************************************************************/ + +apply from: "$rootProject.projectDir/gradle/distributableGhidraExtension.gradle" + +zipExtensions { + def p = this.project + from (p.projectDir) { + exclude 'build/**' + exclude 'certification.manifest' + exclude "*.project" + exclude "*.classpath" + exclude '.settings/**' + exclude 'bin/**' + + into { getBaseProjectName(p) } + } +} diff --git a/gradle/root/eclipse.gradle b/gradle/root/eclipse.gradle index e501bf546b..96d71bb308 100644 --- a/gradle/root/eclipse.gradle +++ b/gradle/root/eclipse.gradle @@ -102,17 +102,17 @@ eclipse { type = 'EXCLUDE_ALL' matcher { id = 'org.eclipse.ui.ide.multiFilter' - arguments = '1.0-name-matches-false-true-CabExtract' + arguments = '1.0-name-matches-false-false-GhidraDocs' } - } + } resourceFilter { appliesTo = 'FILES_AND_FOLDERS' type = 'EXCLUDE_ALL' matcher { id = 'org.eclipse.ui.ide.multiFilter' - arguments = '1.0-name-matches-false-false-GhidraDocs' + arguments = '1.0-name-matches-false-true-CabExtract' } - } + } resourceFilter { appliesTo = 'FILES_AND_FOLDERS' type = 'EXCLUDE_ALL' @@ -129,6 +129,14 @@ eclipse { arguments = '1.0-name-matches-false-false-DemanglerGnu' } } + resourceFilter { + appliesTo = 'FILES_AND_FOLDERS' + type = 'EXCLUDE_ALL' + matcher { + id = 'org.eclipse.ui.ide.multiFilter' + arguments = '1.0-name-matches-false-true-GnuDisassembler' + } + } } }