mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-21 19:42:14 +00:00
GP-49 external disassembly field can now switch based upon context (implemented for ARM/Thumb)
This commit is contained in:
parent
6677daca41
commit
356ea446c7
@ -21,7 +21,7 @@ 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++){
|
||||
@ -29,7 +29,7 @@ void listSupportedArchMachTargets(void)
|
||||
}
|
||||
}
|
||||
printf("\ndone with targetList.\n");
|
||||
|
||||
|
||||
archList = bfd_arch_list();
|
||||
if(archList != NULL){
|
||||
for(i=0, j=0; archList[i] !=0; i++){
|
||||
@ -44,29 +44,29 @@ void listSupportedArchMachTargets(void)
|
||||
/* 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)
|
||||
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;
|
||||
@ -79,24 +79,24 @@ void configureDisassembleInfo(bfd* abfd,
|
||||
}
|
||||
|
||||
disassembler_ftype configureBfd(bfd* abfd,
|
||||
enum bfd_architecture arch,
|
||||
unsigned long mach,
|
||||
enum bfd_endian endian,
|
||||
disassemble_info* DI,
|
||||
disassembler_ftype* disassemble_fn)
|
||||
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);
|
||||
@ -106,29 +106,29 @@ disassembler_ftype configureBfd(bfd* abfd,
|
||||
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;
|
||||
}
|
||||
|
||||
return *disassemble_fn;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int disassemble_buffer( disassembler_ftype disassemble_fn,
|
||||
disassemble_info *info,
|
||||
int* offset,
|
||||
PDIS_INFO pDisInfo)
|
||||
disassemble_info *info,
|
||||
int* offset,
|
||||
PDIS_INFO pDisInfo)
|
||||
{
|
||||
int i, j, size = 0;
|
||||
int len = 0;
|
||||
@ -136,7 +136,7 @@ int disassemble_buffer( disassembler_ftype disassemble_fn,
|
||||
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
|
||||
@ -147,7 +147,7 @@ int disassemble_buffer( disassembler_ftype disassemble_fn,
|
||||
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 ){
|
||||
@ -155,7 +155,7 @@ int disassemble_buffer( disassembler_ftype disassemble_fn,
|
||||
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;
|
||||
@ -167,44 +167,44 @@ int disassemble_buffer( disassembler_ftype disassemble_fn,
|
||||
|
||||
strcat(&(pDisInfo->disassemblyString[0]), disassembled_buffer);
|
||||
memset(disassembled_buffer, 0x00, BUFF_SIZE);
|
||||
|
||||
|
||||
if(size != 0){
|
||||
*offset += size; /* advance position in buffer */
|
||||
goto END;
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +224,7 @@ int main(int argc, char* argv[]){
|
||||
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();
|
||||
@ -247,16 +247,16 @@ int main(int argc, char* argv[]){
|
||||
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);
|
||||
sscanf(arch_str, "%10X", &arch);
|
||||
} else {
|
||||
const char** archList = bfd_arch_list();
|
||||
const bfd_arch_info_type* ait;
|
||||
@ -271,17 +271,17 @@ int main(int argc, char* argv[]){
|
||||
archList++;
|
||||
}
|
||||
if (ait == NULL) {
|
||||
printf("Couldn't find arch %s\n", arch_str);
|
||||
return(-1);
|
||||
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
|
||||
|
||||
@ -294,59 +294,66 @@ int main(int argc, char* argv[]){
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
bfd_init();
|
||||
|
||||
while (stdin_mode) {
|
||||
|
||||
|
||||
//bfd_init();
|
||||
// convert user input AsciiHex to Binary data for processing
|
||||
char tmp[3];
|
||||
unsigned int byteValue;
|
||||
tmp[0] = tmp[1] = tmp[2] = 0x00;
|
||||
char disassemblerOptions[128] = {0};
|
||||
unsigned char byteBuffer[BYTE_BUFFER_SIZE] = {0};
|
||||
char byteStringBuffer[(BYTE_BUFFER_SIZE*2)] = {0};
|
||||
char addressStringBuffer[128] = {0};
|
||||
char bytesAndOptionsBuffer[BYTE_BUFFER_SIZE*2 + sizeof(disassemblerOptions)] = {0};
|
||||
|
||||
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');
|
||||
//Read in the rest of the input. There are two possible styles:
|
||||
//"old": input is just the bytes to disassemble
|
||||
//"new": input = bytes"*"<disassmbler_options>
|
||||
//for compatibility reasons, handle both styles
|
||||
if (fgets(bytesAndOptionsBuffer, sizeof(bytesAndOptionsBuffer), stdin)) {
|
||||
char *p = strchr(bytesAndOptionsBuffer, '*');
|
||||
if (p) {
|
||||
*p = '\0';
|
||||
strncpy(byteStringBuffer,bytesAndOptionsBuffer,(int) (p - bytesAndOptionsBuffer));
|
||||
strncpy(disassemblerOptions,p+1,sizeof(disassemblerOptions));
|
||||
p = strchr(disassemblerOptions,'\n');
|
||||
if (p){
|
||||
*p = '\0';
|
||||
}
|
||||
}
|
||||
else {
|
||||
//no "*" in the string, so no disassembly options
|
||||
//replace newline with null terminator
|
||||
strncpy(byteStringBuffer, bytesAndOptionsBuffer, sizeof(byteStringBuffer));
|
||||
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 {
|
||||
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));
|
||||
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);
|
||||
@ -378,9 +385,9 @@ int main(int argc, char* argv[]){
|
||||
for(j=0; j < BYTE_BUFFER_SIZE; j++){
|
||||
printf("0x%02X ", byteBuffer[j]);
|
||||
}
|
||||
*/
|
||||
|
||||
bfd_init( );
|
||||
*/
|
||||
|
||||
//bfd_init( );
|
||||
target = argv[1];
|
||||
bfd_set_default_target(target);
|
||||
|
||||
@ -392,7 +399,7 @@ int main(int argc, char* argv[]){
|
||||
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" );
|
||||
bfd_perror( "Error on bfdfile" );
|
||||
return(3);
|
||||
}
|
||||
}
|
||||
@ -401,34 +408,37 @@ int main(int argc, char* argv[]){
|
||||
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" );
|
||||
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));
|
||||
*/
|
||||
|
||||
*/
|
||||
|
||||
/*if (disassemblerOptions[0] != '\0'){
|
||||
DI.disassembler_options = disassemblerOptions;
|
||||
}*/
|
||||
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);
|
||||
@ -437,21 +447,25 @@ int main(int argc, char* argv[]){
|
||||
/*
|
||||
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);
|
||||
if (disassemblerOptions[0] != '\0'){
|
||||
DI.disassembler_options = disassemblerOptions;
|
||||
}
|
||||
|
||||
// go diassemble the buffer and build up the result in a accumulator string buffer.
|
||||
processBuffer(byteBuffer, size >> 1, offset, disassemble_fn, &DI); //
|
||||
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;
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ public interface ExternalDisassembler extends ExtensionPoint {
|
||||
|
||||
public String getDisassembly(CodeUnit cu) throws Exception;
|
||||
|
||||
public String getDisassemblyDisplayPrefix(CodeUnit cu) throws Exception;
|
||||
|
||||
public String getDisassemblyOfBytes(Language language, boolean isBigEndian, long address,
|
||||
byte[] byteString) throws Exception;
|
||||
|
||||
|
@ -150,7 +150,10 @@ public class ExternalDisassemblyFieldFactory extends FieldFactory {
|
||||
if (disassembly == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String prefix = disassembler.getDisassemblyDisplayPrefix(cu);
|
||||
if (prefix != null) {
|
||||
disassembly = prefix + " " + disassembly;
|
||||
}
|
||||
return disassembly;
|
||||
}
|
||||
|
||||
|
@ -18,19 +18,23 @@ package ghidra.app.util.disassemble;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jdom.*;
|
||||
import org.jdom.input.SAXBuilder;
|
||||
|
||||
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.lang.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.xml.XmlUtilities;
|
||||
import util.CollectionUtils;
|
||||
|
||||
public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
|
||||
@ -39,6 +43,7 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
// 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 OPTIONS_SEPARATOR = "*";
|
||||
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;
|
||||
@ -48,6 +53,8 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
private static final String GDIS_EXE =
|
||||
Platform.CURRENT_PLATFORM.getOperatingSystem() == OperatingSystem.WINDOWS ? "gdis.exe"
|
||||
: "gdis";
|
||||
private static final String EMPTY_DISASSEMBLER_OPTIONS = "";
|
||||
private static final String GDIS_OPTIONS_FILENAME_PROPERTY = "gdis.disassembler.options.file";
|
||||
|
||||
private static HashMap<String, File> languageGdisMap;
|
||||
private static File defaultGdisExecFile;
|
||||
@ -82,6 +89,21 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
return gdisConfig != null && gdisConfig.architecture != UNSUPPORTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisassemblyDisplayPrefix(CodeUnit cu) throws Exception {
|
||||
GdisConfig gdisConfig = checkLanguage(cu.getProgram().getLanguage());
|
||||
if (gdisConfig == null || gdisConfig.architecture == UNSUPPORTED) {
|
||||
return null;
|
||||
}
|
||||
Register contextRegister = gdisConfig.getContextRegister();
|
||||
if (contextRegister == null) {
|
||||
return null;
|
||||
}
|
||||
long value = getContextRegisterValue(cu, contextRegister);
|
||||
String option = gdisConfig.getDisplayPrefixMap().get(value);
|
||||
return option;
|
||||
}
|
||||
|
||||
private static void reportMultipleMappings(Language language) {
|
||||
List<String> externalNames = language.getLanguageDescription().getExternalNames("gnu");
|
||||
if (externalNames != null && externalNames.size() > 1) {
|
||||
@ -89,8 +111,9 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean prependSeparator = false;
|
||||
for (String name : externalNames) {
|
||||
if (prependSeparator)
|
||||
if (prependSeparator) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(name);
|
||||
prependSeparator = true;
|
||||
}
|
||||
@ -110,11 +133,17 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
String machineId;
|
||||
File gdisExecFile;
|
||||
boolean usingDefault;
|
||||
Register contextRegister;
|
||||
Map<Long, String> valueToOptionString;
|
||||
Map<Long, String> valueToDisplayPrefix;
|
||||
Language lang;
|
||||
String globalDisassemblerOptions;
|
||||
|
||||
GdisConfig(Language language, boolean isBigEndian) {
|
||||
|
||||
this.languageId = language.getLanguageID().toString();
|
||||
this.isBigEndian = isBigEndian;
|
||||
this.lang = language;
|
||||
|
||||
List<String> architectures = language.getLanguageDescription().getExternalNames("gnu");
|
||||
//get first non-null
|
||||
@ -143,12 +172,123 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
gdisExecFile = defaultGdisExecFile;
|
||||
usingDefault = true;
|
||||
}
|
||||
|
||||
List<String> gDisOptionsFile =
|
||||
language.getLanguageDescription().getExternalNames(GDIS_OPTIONS_FILENAME_PROPERTY);
|
||||
if (!CollectionUtils.isBlank(gDisOptionsFile)) {
|
||||
try {
|
||||
parseGdisOptionsFile(gDisOptionsFile.get(0));
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "Error reading gdis options file " + e.getMessage());
|
||||
contextRegister = null;
|
||||
valueToOptionString = null;
|
||||
valueToDisplayPrefix = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GdisConfig(Language lang) {
|
||||
this(lang, lang.isBigEndian());
|
||||
}
|
||||
|
||||
private void parseGdisOptionsFile(String fileName) throws IOException {
|
||||
LanguageDescription desc = lang.getLanguageDescription();
|
||||
if (!(desc instanceof SleighLanguageDescription)) {
|
||||
throw new IOException("Not a Sleigh Language: " + lang.getLanguageID());
|
||||
}
|
||||
|
||||
SleighLanguageDescription sld = (SleighLanguageDescription) desc;
|
||||
ResourceFile defsFile = sld.getDefsFile();
|
||||
ResourceFile parentFile = defsFile.getParentFile();
|
||||
ResourceFile gdisOpts = new ResourceFile(parentFile, fileName);
|
||||
SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false);
|
||||
try (InputStream fis = gdisOpts.getInputStream()) {
|
||||
Document doc = sax.build(fis);
|
||||
Element rootElem = doc.getRootElement();
|
||||
Element globalElement = rootElem.getChild("global");
|
||||
if (globalElement != null) {
|
||||
globalDisassemblerOptions = globalElement.getAttributeValue("optstring");
|
||||
}
|
||||
Element contextRegisterElement = rootElem.getChild("context_register");
|
||||
if (contextRegisterElement == null) {
|
||||
//no context_register element found in the xml file
|
||||
//this is not necessarily an error - might only be a global optstring
|
||||
//global optstring has already been parsed, so we're done
|
||||
if (globalElement != null) {
|
||||
Msg.info(this,
|
||||
"no context register element in " + gdisOpts.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
//no context register element or global element, error
|
||||
throw new JDOMException(
|
||||
"No context_register element or global element in gdis options file");
|
||||
}
|
||||
if (contextRegisterElement.getContentSize() == 0) {
|
||||
throw new JDOMException("No context register name provided.");
|
||||
}
|
||||
String contextRegisterName = contextRegisterElement.getContent(0).getValue();
|
||||
contextRegister = lang.getRegister(contextRegisterName);
|
||||
if (contextRegister == null) {
|
||||
//the context register named in the xml file does not exist in the sleigh language
|
||||
//this is an error
|
||||
throw new JDOMException("Unknown context register " + contextRegisterName +
|
||||
" for language " + lang.getLanguageID().getIdAsString());
|
||||
}
|
||||
valueToOptionString = new HashMap<>();
|
||||
valueToDisplayPrefix = new HashMap<>();
|
||||
Element options = rootElem.getChild("options");
|
||||
List<Element> optList = options.getChildren("option");
|
||||
for (Element opt : optList) {
|
||||
Long value = Long.decode(opt.getAttributeValue("value"));
|
||||
String optString = opt.getAttributeValue("optstring");
|
||||
valueToOptionString.put(value, optString);
|
||||
String displayPrefix = opt.getAttributeValue("display_prefix");
|
||||
valueToDisplayPrefix.put(value, displayPrefix);
|
||||
}
|
||||
|
||||
}
|
||||
catch (JDOMException e) {
|
||||
Msg.error(this, "Error reading " + fileName + ": " + e.getMessage());
|
||||
contextRegister = null;
|
||||
valueToOptionString = null;
|
||||
valueToDisplayPrefix = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the global disassembler options
|
||||
* @return global option string, or {@code null} if the
|
||||
* global is unspecified.
|
||||
*/
|
||||
public String getGlobalDisassemblerOptions() {
|
||||
return globalDisassemblerOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the context register determine by the gdis options file
|
||||
* @return the context register
|
||||
*/
|
||||
public Register getContextRegister() {
|
||||
return contextRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map from context register values to gdis disassembler options
|
||||
* @return map values->options
|
||||
*/
|
||||
public Map<Long, String> getOptionsMap() {
|
||||
return valueToOptionString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map from context register values to disassembly display prefixes
|
||||
* @return map values->prefixes
|
||||
*/
|
||||
public Map<Long, String> getDisplayPrefixMap() {
|
||||
return valueToDisplayPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof GdisConfig)) {
|
||||
@ -260,7 +400,7 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
|
||||
String bytes = getBytes(byteProvider, blockSize);
|
||||
|
||||
return runDisassembler(gdisConfig, address, bytes);
|
||||
return runDisassembler(gdisConfig, address, bytes, EMPTY_DISASSEMBLER_OPTIONS);
|
||||
}
|
||||
|
||||
public List<GnuDisassembledInstruction> getBlockDisassembly(Program program, Address addr,
|
||||
@ -272,8 +412,8 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
int blockSize = pow2(blockSizeFactor);
|
||||
|
||||
Address blockAddr = addr.getNewAddress(addr.getOffset() & -blockSize); // block
|
||||
// aligned
|
||||
// address
|
||||
// aligned
|
||||
// address
|
||||
|
||||
return getBlockDisassembly(program.getLanguage(), blockAddr, blockSizeFactor,
|
||||
new MemoryByteProvider(program.getMemory(), blockAddr));
|
||||
@ -301,7 +441,37 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
return "";
|
||||
}
|
||||
|
||||
List<GnuDisassembledInstruction> disassembly = runDisassembler(gdisConfig, address, bytes);
|
||||
String disOptions = EMPTY_DISASSEMBLER_OPTIONS;
|
||||
String globalOptions = gdisConfig.getGlobalDisassemblerOptions();
|
||||
boolean hasGlobal = false;
|
||||
if (globalOptions != null) {
|
||||
hasGlobal = true;
|
||||
//set the disOptions to the global options here in case there are global
|
||||
//options but no options for context register values
|
||||
disOptions = globalOptions;
|
||||
}
|
||||
Register contextRegister = gdisConfig.getContextRegister();
|
||||
if (contextRegister != null) {
|
||||
long value = getContextRegisterValue(cu, contextRegister);
|
||||
String contextRegisterValueOption = gdisConfig.getOptionsMap().get(value);
|
||||
if (contextRegisterValueOption == null) {
|
||||
Msg.warn(this, "No option for value " + value + " of context register " +
|
||||
contextRegister.getName());
|
||||
}
|
||||
else {
|
||||
//need to put a comma between the global options and the options
|
||||
//for this context register value
|
||||
if (hasGlobal) {
|
||||
disOptions = globalOptions + "," + contextRegisterValueOption;
|
||||
}
|
||||
//no global options, just send the options for this context register
|
||||
else {
|
||||
disOptions = contextRegisterValueOption;
|
||||
}
|
||||
}
|
||||
}
|
||||
List<GnuDisassembledInstruction> disassembly =
|
||||
runDisassembler(gdisConfig, address, bytes, disOptions);
|
||||
|
||||
if (disassembly == null || disassembly.size() == 0 || disassembly.get(0) == null) {
|
||||
return "(bad)";
|
||||
@ -324,7 +494,7 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
String address = "0x" + Long.toHexString(addressOffset);
|
||||
|
||||
List<GnuDisassembledInstruction> disassembly =
|
||||
runDisassembler(gdisConfig, address, bytesString);
|
||||
runDisassembler(gdisConfig, address, bytesString, EMPTY_DISASSEMBLER_OPTIONS);
|
||||
|
||||
if (disassembly == null || disassembly.isEmpty() || disassembly.get(0) == null) {
|
||||
return "(bad)";
|
||||
@ -332,14 +502,26 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
return disassembly.get(0).toString();
|
||||
}
|
||||
|
||||
private long getContextRegisterValue(CodeUnit cu, Register contextRegister) {
|
||||
ProgramContext context = cu.getProgram().getProgramContext();
|
||||
RegisterValue value = context.getRegisterValue(contextRegister, cu.getAddress());
|
||||
if (value != null) {
|
||||
return value.getUnsignedValue().longValue();
|
||||
}
|
||||
//we tried, return 0 to match SLEIGH default
|
||||
return 0;
|
||||
}
|
||||
|
||||
private String converBytesToString(byte[] bytes) {
|
||||
String byteString = null;
|
||||
for (byte thisByte : bytes) {
|
||||
String thisByteString = Integer.toHexString(thisByte);
|
||||
if (thisByteString.length() == 1)
|
||||
if (thisByteString.length() == 1) {
|
||||
thisByteString = "0" + thisByteString; // pad single digits
|
||||
if (thisByteString.length() > 2)
|
||||
}
|
||||
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;
|
||||
}
|
||||
@ -402,7 +584,7 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
}
|
||||
|
||||
private List<GnuDisassembledInstruction> runDisassembler(GdisConfig gdisConfig,
|
||||
String addrString, String bytes) throws IOException {
|
||||
String addrString, String bytes, String disassemblerOptions) 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
|
||||
@ -414,8 +596,9 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
|
||||
if (disassemblerProcess == null || !sameConfig) {
|
||||
|
||||
if (!setupDisassembler(gdisConfig))
|
||||
if (!setupDisassembler(gdisConfig)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
outputWriter = new OutputStreamWriter(disassemblerProcess.getOutputStream());
|
||||
|
||||
@ -432,7 +615,14 @@ public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
return null; // if process previously died return nothing - quickly
|
||||
}
|
||||
|
||||
String disassemblyRequest = addrString + SEPARATOR_CHARACTER + bytes + SEPARATOR_CHARACTER;
|
||||
String disassemblyRequest = addrString + SEPARATOR_CHARACTER + bytes;
|
||||
if (StringUtils.isEmpty(disassemblerOptions)) {
|
||||
disassemblyRequest += '\n';
|
||||
}
|
||||
else {
|
||||
disassemblyRequest += OPTIONS_SEPARATOR + disassemblerOptions + SEPARATOR_CHARACTER;
|
||||
}
|
||||
|
||||
try {
|
||||
outputWriter.write(disassemblyRequest);
|
||||
outputWriter.flush();
|
||||
|
@ -3,6 +3,7 @@ Module.manifest||GHIDRA||||END|
|
||||
build.gradle||GHIDRA||||END|
|
||||
data/languages/ARM.cspec||GHIDRA||||END|
|
||||
data/languages/ARM.dwarf||GHIDRA||||END|
|
||||
data/languages/ARM.gdis||GHIDRA||||END|
|
||||
data/languages/ARM.ldefs||GHIDRA||||END|
|
||||
data/languages/ARM.opinion||GHIDRA||||END|
|
||||
data/languages/ARM.sinc||GHIDRA||||END|
|
||||
|
41
Ghidra/Processors/ARM/data/languages/ARM.gdis
Normal file
41
Ghidra/Processors/ARM/data/languages/ARM.gdis
Normal file
@ -0,0 +1,41 @@
|
||||
<!--
|
||||
This file determines the disassembler options sent to the GNU external disassembler.
|
||||
You can see which options are available for all architectures in the objdump man page.
|
||||
Given a version of objdump compiled for a specific architecture, you can see what options
|
||||
are available with "objdump -i -m"
|
||||
|
||||
The options for ARM are:
|
||||
"reg-names-std" (the default)
|
||||
"reg-names-apcs"
|
||||
"reg-names-raw"
|
||||
"reg-names-apcs"
|
||||
"reg-names-special-apcs"
|
||||
"force-thumb" (force thumb disassembly)
|
||||
"no-force-thumb" (force arm disassembly)
|
||||
(see the objdump manpage for the precise details about the different register
|
||||
naming options)
|
||||
|
||||
"optstring" is the string of options, and "display_prefix" is an optional prefix prepended to the
|
||||
external disassembly field. In this file, "A: " is prepended to ARM disassembly, and
|
||||
"T: " is prepended to Thumb disassembly.
|
||||
|
||||
To send multiple options, concatenate them into a CSV list. For example, "no-force-thumb,reg-names-raw"
|
||||
is a valid optstring. You can also have a "global" element whose "optstring" attribute is
|
||||
always sent to the GNU disassembler.
|
||||
|
||||
If there is not a context register defined, the global optstring is sent by itself (see x86-16.gdis for
|
||||
an example). If there is a context register defined, the global optstring is prepended to the
|
||||
optstring determined by a context register value.
|
||||
|
||||
This file must be listed in the .ldefs file for the processor, e.g.
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
-->
|
||||
|
||||
<gdis>
|
||||
<context_register>TMode</context_register>
|
||||
<options>
|
||||
<option value="0x0" optstring="no-force-thumb" display_prefix = "A: "/>
|
||||
<option value="0x1" optstring="force-thumb" display_prefix = "T: "/>
|
||||
</options>
|
||||
</gdis>
|
||||
|
@ -14,6 +14,7 @@
|
||||
<compiler name="default" spec="ARM.cspec" id="default"/>
|
||||
<compiler name="Visual Studio" spec="ARM_win.cspec" id="windows"/>
|
||||
<external_name tool="gnu" name="iwmmxt"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="arm"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARMneon.dwarf"/>
|
||||
</language>
|
||||
@ -31,6 +32,7 @@
|
||||
<compiler name="default" spec="ARM.cspec" id="default"/>
|
||||
<compiler name="Visual Studio" spec="ARM_win.cspec" id="windows"/>
|
||||
<external_name tool="gnu" name="iwmmxt"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="arm"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARMneon.dwarf"/>
|
||||
</language>
|
||||
@ -63,6 +65,7 @@
|
||||
<description>Generic ARM/Thumb v8 big endian</description>
|
||||
<compiler name="default" spec="ARM.cspec" id="default"/>
|
||||
<external_name tool="gnu" name="iwmmxt"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="armb"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARMneon.dwarf"/>
|
||||
</language>
|
||||
@ -96,6 +99,7 @@
|
||||
<compiler name="default" spec="ARM.cspec" id="default"/>
|
||||
<compiler name="Visual Studio" spec="ARM_win.cspec" id="windows"/>
|
||||
<external_name tool="gnu" name="iwmmxt"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="arm"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARMneon.dwarf"/>
|
||||
</language>
|
||||
@ -128,6 +132,7 @@
|
||||
<description>Generic ARM/Thumb v7 big endian</description>
|
||||
<compiler name="default" spec="ARM.cspec" id="default"/>
|
||||
<external_name tool="gnu" name="iwmmxt"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="armb"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARMneon.dwarf"/>
|
||||
</language>
|
||||
@ -174,6 +179,7 @@
|
||||
<description>Generic ARM/Thumb v6 little endian</description>
|
||||
<compiler name="default" spec="ARM_v45.cspec" id="default"/>
|
||||
<external_name tool="gnu" name="xscale"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="arm"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARM.dwarf"/>
|
||||
<!-- change DWARF register mapping to ARMneon.dwarf if VFPv2 is enabled -->
|
||||
@ -192,6 +198,7 @@
|
||||
<description>Generic ARM/Thumb v6 big endian</description>
|
||||
<compiler name="default" spec="ARM_v45.cspec" id="default"/>
|
||||
<external_name tool="gnu" name="xscale"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="armb"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARM.dwarf"/>
|
||||
<!-- change DWARF register mapping to ARMneon.dwarf if VFPv2 is enabled -->
|
||||
@ -210,6 +217,7 @@
|
||||
<description>Generic ARM/Thumb v5 little endian (T-variant)</description>
|
||||
<compiler name="default" spec="ARM_v45.cspec" id="default"/>
|
||||
<external_name tool="gnu" name="armv5t"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="arm"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARM.dwarf"/>
|
||||
</language>
|
||||
@ -226,6 +234,7 @@
|
||||
<description>Generic ARM/Thumb v5 big endian (T-variant)</description>
|
||||
<compiler name="default" spec="ARM_v45.cspec" id="default"/>
|
||||
<external_name tool="gnu" name="armv5t"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="armb"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARM.dwarf"/>
|
||||
</language>
|
||||
@ -274,6 +283,7 @@
|
||||
<description>Generic ARM/Thumb v4 little endian (T-variant)</description>
|
||||
<compiler name="default" spec="ARM_v45.cspec" id="default"/>
|
||||
<external_name tool="gnu" name="armv4t"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="arm"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARM.dwarf"/>
|
||||
</language>
|
||||
@ -290,6 +300,7 @@
|
||||
<description>Generic ARM/Thumb v4 big endian (T-variant)</description>
|
||||
<compiler name="default" spec="ARM_v45.cspec" id="default"/>
|
||||
<external_name tool="gnu" name="armv4t"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="ARM.gdis"/>
|
||||
<external_name tool="IDA-PRO" name="armb"/>
|
||||
<external_name tool="DWARF.register.mapping.file" name="ARM.dwarf"/>
|
||||
</language>
|
||||
|
@ -30,6 +30,7 @@ data/languages/sha.sinc||GHIDRA||||END|
|
||||
data/languages/smx.sinc||GHIDRA||||END|
|
||||
data/languages/x86-16-real.pspec||GHIDRA||||END|
|
||||
data/languages/x86-16.cspec||GHIDRA||||END|
|
||||
data/languages/x86-16.gdis||GHIDRA||||END|
|
||||
data/languages/x86-16.pspec||GHIDRA||||END|
|
||||
data/languages/x86-64-gcc.cspec||GHIDRA||||END|
|
||||
data/languages/x86-64-win.cspec||GHIDRA||||END|
|
||||
|
3
Ghidra/Processors/x86/data/languages/x86-16.gdis
Normal file
3
Ghidra/Processors/x86/data/languages/x86-16.gdis
Normal file
@ -0,0 +1,3 @@
|
||||
<gdis>
|
||||
<global optstring="intel"/>
|
||||
</gdis>
|
@ -58,6 +58,8 @@
|
||||
<external_name tool="IDA-PRO" name="80486r"/>
|
||||
<external_name tool="IDA-PRO" name="80586r"/>
|
||||
<external_name tool="IDA-PRO" name="metapc"/>
|
||||
<external_name tool="gnu" name="i8086"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="x86-16.gdis"/>
|
||||
</language>
|
||||
<language processor="x86"
|
||||
endian="little"
|
||||
@ -71,6 +73,8 @@
|
||||
<description>Intel/AMD 16-bit x86 Protected Mode</description>
|
||||
<compiler name="default" spec="x86-16.cspec" id="default"/>
|
||||
<external_name tool="IDA-PRO" name="8086p"/>
|
||||
<external_name tool="gnu" name="i8086"/>
|
||||
<external_name tool="gdis.disassembler.options.file" name="x86-16.gdis"/>
|
||||
</language>
|
||||
<language processor="x86"
|
||||
endian="little"
|
||||
|
Loading…
Reference in New Issue
Block a user