mirror of
https://github.com/JHUAPL/CodeCut.git
synced 2026-01-09 13:28:06 -05:00
Adding recompilable C scripts - staging changes for next release
This commit is contained in:
63
codecut-gui/ghidra_scripts/decomprange.py
Normal file
63
codecut-gui/ghidra_scripts/decomprange.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from ghidra.app.decompiler import DecompInterface
|
||||
from ghidra.util.task import ConsoleTaskMonitor
|
||||
|
||||
|
||||
def decompile_user_functions_in_range(
|
||||
current_program,
|
||||
start_address_str,
|
||||
end_address_str,
|
||||
standard_library_namespaces=None,
|
||||
):
|
||||
if standard_library_namespaces is None:
|
||||
standard_library_namespaces = ['libc', 'libm', 'libpthread',
|
||||
'libdl', 'std']
|
||||
|
||||
def is_user_written_function(function):
|
||||
namespace = function.getParentNamespace().getName()
|
||||
return namespace not in standard_library_namespaces
|
||||
|
||||
#doesn't quite work right if you have overlapping functions
|
||||
#we should rewrite to use FunctionManager/getFunctionsAt functionality
|
||||
def getFunctions(start_address, end_address):
|
||||
current_addr = start_address
|
||||
while current_addr < end_address:
|
||||
function = \
|
||||
current_program.getFunctionManager().getFunctionContaining(current_addr)
|
||||
if function is not None:
|
||||
yield function
|
||||
current_addr = function.getBody().getMaxAddress().add(1)
|
||||
else:
|
||||
current_addr = current_addr.add(1)
|
||||
|
||||
start_address = \
|
||||
current_program.getAddressFactory().getAddress(start_address_str)
|
||||
end_address = \
|
||||
current_program.getAddressFactory().getAddress(end_address_str)
|
||||
|
||||
if start_address is None or end_address is None:
|
||||
print 'Invalid address range specified.'
|
||||
return
|
||||
|
||||
if start_address >= end_address:
|
||||
print 'Invalid address range: start address should be less than end address.'
|
||||
return
|
||||
|
||||
decompiler = DecompInterface()
|
||||
decompiler.openProgram(current_program)
|
||||
monitor = ConsoleTaskMonitor()
|
||||
|
||||
functions = list(getFunctions(start_address, end_address))
|
||||
user_written_functions = list(filter(is_user_written_function,
|
||||
functions))
|
||||
|
||||
decompiled_functions = {}
|
||||
for function in user_written_functions:
|
||||
decomp_result = decompiler.decompileFunction(function, 0,
|
||||
monitor)
|
||||
if decomp_result is not None:
|
||||
decompiled_functions[function.getName()] = \
|
||||
{'address': function.getEntryPoint(),
|
||||
'code': decomp_result.getDecompiledFunction().getC()}
|
||||
|
||||
return decompiled_functions
|
||||
|
||||
69
codecut-gui/ghidra_scripts/funcsig.py
Normal file
69
codecut-gui/ghidra_scripts/funcsig.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from ghidra.program.model.listing import Function
|
||||
from ghidra.program.model.symbol import SourceType
|
||||
|
||||
|
||||
def get_referenced_function_signatures(program, function_address,
|
||||
monitor):
|
||||
if function_address is None:
|
||||
popup('No address entered')
|
||||
return
|
||||
|
||||
function = \
|
||||
program.getFunctionManager().getFunctionAt(function_address)
|
||||
if function is None:
|
||||
popup('No function found at the given address')
|
||||
return
|
||||
|
||||
function_signatures = \
|
||||
get_referenced_function_signatures_base(function, monitor)
|
||||
|
||||
sigs = '\n/* Refereneced Signatures for: ' + function.getName() \
|
||||
+ ' */\n'
|
||||
for signature in function_signatures:
|
||||
sigs += signature
|
||||
|
||||
return sigs
|
||||
|
||||
|
||||
def get_referenced_function_signatures_base(function, monitor):
|
||||
if function is None:
|
||||
raise ValueError('function is none')
|
||||
|
||||
funcRefs = getFunctionReferences(function, monitor)
|
||||
|
||||
signatures = []
|
||||
for refFunc in funcRefs:
|
||||
if isUserDefinedFunction(refFunc):
|
||||
signatures.append(getFunctionSignature(refFunc) + ';\n')
|
||||
|
||||
return signatures
|
||||
|
||||
|
||||
def getFunctionReferences(function, monitor):
|
||||
refs = set()
|
||||
instructions = \
|
||||
function.getProgram().getListing().getInstructions(function.getBody(),
|
||||
True)
|
||||
for instr in instructions:
|
||||
flowType = instr.getFlowType()
|
||||
if flowType.isCall():
|
||||
target = instr.getOperandReferences(0)[0].getToAddress()
|
||||
func = \
|
||||
function.getProgram().getFunctionManager().getFunctionAt(target)
|
||||
if func is not None:
|
||||
refs.add(func)
|
||||
return refs
|
||||
|
||||
|
||||
def getFunctionSignature(function):
|
||||
sig = function.getPrototypeString(False, False)
|
||||
return sig
|
||||
|
||||
|
||||
def isUserDefinedFunction(function):
|
||||
symbol = function.getSymbol()
|
||||
namespace = symbol.getParentNamespace().getName()
|
||||
standard_libraries = ['libc', 'libm', 'libpthread', 'libdl', 'std']
|
||||
return not any(namespace.startswith(lib) for lib in
|
||||
standard_libraries)
|
||||
|
||||
64
codecut-gui/ghidra_scripts/generate_c.py
Normal file
64
codecut-gui/ghidra_scripts/generate_c.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from globalvars import get_global_variables
|
||||
from funcsig import get_referenced_function_signatures
|
||||
from decomprange import decompile_user_functions_in_range
|
||||
import re
|
||||
|
||||
|
||||
def fixCasting(code_str):
|
||||
pattern = re.compile(r"\b([a-zA-Z0-9._]+)\._(\d{0,4})_(\d{0,4})_")
|
||||
matches = pattern.finditer(code_str)
|
||||
|
||||
for match in matches:
|
||||
name = match.group(1)
|
||||
offset = match.group(2)
|
||||
size = match.group(3)
|
||||
|
||||
if size == '4':
|
||||
fixedref = '((int*)' + name + ')[' + offset + ']'
|
||||
code_str = code_str.replace(match.group(), fixedref)
|
||||
|
||||
if size == '1':
|
||||
fixedref = '*(uint8_t *)&' + name
|
||||
code_str = code_str.replace(match.group(), fixedref)
|
||||
|
||||
return code_str
|
||||
|
||||
|
||||
def generate_recompilable_c_code(
|
||||
start_addr,
|
||||
end_addr,
|
||||
currentProgram,
|
||||
monitor,
|
||||
):
|
||||
header_prefix = \
|
||||
'''/* Ghidra type resolution */
|
||||
#include "ghidra.h"
|
||||
|
||||
'''
|
||||
|
||||
referenced_funcs = ''
|
||||
in_range_c_code = \
|
||||
'''
|
||||
|
||||
/* Decompiled functions within address range: %s - %s */''' \
|
||||
% (start_addr, end_addr)
|
||||
|
||||
global_vars = get_global_variables(currentProgram, start_addr, end_addr)
|
||||
print('Global vars:')
|
||||
print(global_vars)
|
||||
|
||||
decompiled_functions = \
|
||||
decompile_user_functions_in_range(currentProgram, start_addr,
|
||||
end_addr)
|
||||
|
||||
for (function_name, function_data) in decompiled_functions.items():
|
||||
referenced_funcs += \
|
||||
get_referenced_function_signatures(currentProgram,
|
||||
function_data['address'], monitor)
|
||||
in_range_c_code += function_data['code']
|
||||
|
||||
c_code = header_prefix + global_vars + referenced_funcs \
|
||||
+ in_range_c_code
|
||||
c_code = fixCasting(c_code)
|
||||
return c_code
|
||||
|
||||
99
codecut-gui/ghidra_scripts/globalvars.py
Normal file
99
codecut-gui/ghidra_scripts/globalvars.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from ghidra.program.model.symbol import SourceType
|
||||
from ghidra.program.model.symbol import SymbolType
|
||||
from ghidra.program.model.address import AddressSet
|
||||
#from ghidra.program.flatapi.FlatProgramAPI import getFunctionAt, getDataAt
|
||||
from ghidra.program.model.data import Array, FloatDataType, \
|
||||
DoubleDataType
|
||||
|
||||
|
||||
def get_global_variables(program, start_addr, end_addr):
|
||||
global_vars = []
|
||||
symbol_table = program.getSymbolTable()
|
||||
start_address = \
|
||||
program.getAddressFactory().getAddress(start_addr)
|
||||
end_address = \
|
||||
program.getAddressFactory().getAddress(end_addr)
|
||||
addrset = AddressSet(start_address,end_address)
|
||||
#set.addRange(start_addr, end_addr)
|
||||
print(start_address, end_address)
|
||||
print(addrset)
|
||||
|
||||
#for symbol in symbol_table.getAllSymbols(False):
|
||||
for symbol in symbol_table.getSymbols(addrset,SymbolType.LABEL,True):
|
||||
print(symbol)
|
||||
if (symbol.getSymbolType() == SymbolType.LABEL):
|
||||
if (symbol.isGlobal()):
|
||||
if (not program.getListing().getFunctionAt(symbol.getAddress())):
|
||||
if (program.getListing().getDataAt(symbol.getAddress())):
|
||||
global_vars.append(symbol)
|
||||
|
||||
'''
|
||||
def is_user_defined(var):
|
||||
var_name = var.getName()
|
||||
var_addr = var.getAddress()
|
||||
|
||||
if var_name.startswith('__') or var_name.startswith('_'):
|
||||
return False
|
||||
|
||||
if var_name.startswith('imp_') or var_name.startswith('thunk_'):
|
||||
return False
|
||||
|
||||
if var_name.startswith('fde_') or var_name.startswith('cie_'):
|
||||
return False
|
||||
|
||||
if var_name.startswith('completed.0') \
|
||||
or var_name.startswith('data_start'):
|
||||
return False
|
||||
|
||||
if var_addr.toString().startswith('EXTERNAL:'):
|
||||
return False
|
||||
section_name = program.getMemory().getBlock(var_addr).getName()
|
||||
#if section_name not in ['.data', '.bss']:
|
||||
# return False
|
||||
|
||||
return True
|
||||
'''
|
||||
|
||||
|
||||
#global_vars = list(filter(is_user_defined, global_vars))
|
||||
#global_vars = list(filter(is_global_var, global_vars))
|
||||
|
||||
print("G vars first pass:")
|
||||
print(global_vars)
|
||||
|
||||
output = '/* Global Variables */\n'
|
||||
for var in global_vars:
|
||||
var_addr = var.getAddress()
|
||||
var_name = var.getName()
|
||||
|
||||
data = program.getListing().getDataAt(var_addr)
|
||||
if data is None:
|
||||
continue
|
||||
|
||||
dt = data.getDataType()
|
||||
dt_name = dt.getDisplayName()
|
||||
value = data.getValue()
|
||||
|
||||
pointer_count = 0
|
||||
while dt_name.endswith('*'):
|
||||
pointer_count += 1
|
||||
dt_name = dt_name[:-1].strip()
|
||||
|
||||
if isinstance(dt, Array):
|
||||
value = '{' + ', '.join(str(value[i]) for i in
|
||||
range(len(value))) + '}'
|
||||
elif isinstance(dt, FloatDataType) or isinstance(dt,
|
||||
DoubleDataType):
|
||||
value = '{:.6f}'.format(value)
|
||||
elif value is not None:
|
||||
value = str(value)
|
||||
else:
|
||||
value = ''
|
||||
|
||||
output += '{}{} {}{}{};\n'.format(dt_name, (' '
|
||||
if not dt_name.endswith('*') else ''), var_name, '*'
|
||||
* pointer_count, (' = {}'.format(value) if value else ''
|
||||
))
|
||||
|
||||
return output
|
||||
|
||||
@@ -1,44 +1,36 @@
|
||||
#@category AMP
|
||||
#@category AMP-Improved
|
||||
from generate_c import generate_recompilable_c_code
|
||||
import os
|
||||
from ghidra.util.task import TaskMonitor
|
||||
|
||||
from std import *
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Decompile all functions within a range of specified addresses
|
||||
# -------------------------------------------------------------
|
||||
# Definitions for decompiler to function
|
||||
program = getCurrentProgram()
|
||||
ifc = DecompInterface()
|
||||
ifc.setOptions(DecompileOptions())
|
||||
ifc.openProgram(program)
|
||||
def write_c_code_to_file(c_code, output_file_path):
|
||||
with open(output_file_path, 'w') as f:
|
||||
f.write(c_code)
|
||||
|
||||
inRangeFunc = []
|
||||
externFunc = []
|
||||
|
||||
# Take user input for address range
|
||||
args = getScriptArgs()
|
||||
start_addr = args[0]
|
||||
end_addr = args[1]
|
||||
|
||||
file = args[2]
|
||||
path = file.rsplit("/", 1)[0] + "/"
|
||||
if __name__ == '__main__':
|
||||
|
||||
start = int(start_addr, 16)
|
||||
end = int(end_addr, 16)
|
||||
args = getScriptArgs()
|
||||
start_addr = args[0]
|
||||
end_addr = args[1]
|
||||
|
||||
def main():
|
||||
# Get functions in range of specified addr
|
||||
# store functions in range -> inRangeFunc
|
||||
# store called functions from in range functions -> externFunc
|
||||
getFunctionsInRange(start, end, externFunc, inRangeFunc)
|
||||
|
||||
# Decompile in range functions and write to file
|
||||
writeToFile(file, externFunc, inRangeFunc)
|
||||
|
||||
# Produce "ghidra.h" file
|
||||
writeHeader(path)
|
||||
file_name = args[2]
|
||||
output_dir = file_name.rsplit("/", 1)[0] + "/"
|
||||
|
||||
# Fix ghidra decompiler access notation
|
||||
fixCasting(file)
|
||||
println("Recomp C Range Entry: %s - %s" % (start_addr, end_addr))
|
||||
c_code = generate_recompilable_c_code(start_addr, end_addr,
|
||||
currentProgram, monitor)
|
||||
|
||||
#file_name = currentProgram.getName()
|
||||
|
||||
#output_dir = askDirectory('Output Directory',
|
||||
# 'Save C code output'
|
||||
# ).getPath()
|
||||
|
||||
#output_file_path = os.path.join(output_dir, file_name)
|
||||
|
||||
write_c_code_to_file(c_code, file_name)
|
||||
|
||||
println('C code has been saved to %s' % file_name)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name="generator" content=
|
||||
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||
<META http-equiv="Content-Language" content="en-us">
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<META name="GENERATOR" content="Microsoft FrontPage 4.0">
|
||||
<META name="ProgId" content="FrontPage.Editor.Document">
|
||||
|
||||
<TITLE>Skeleton Help File for a Module</TITLE>
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY>
|
||||
<H1><a name="HelpAnchor"></a>Skeleton Help File for a Module</H1>
|
||||
|
||||
<P>This is a simple skeleton help topic. For a better description of what should and should not
|
||||
go in here, see the "sample" Ghidra extension in the Extensions/Ghidra directory, or see your
|
||||
favorite help topic. In general, language modules do not have their own help topics.</P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
@@ -41,6 +41,7 @@ import java.awt.event.KeyEvent;
|
||||
import java.io.*;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.Icon;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.*;
|
||||
@@ -526,7 +527,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
return symProvider.getCurrentSymbol() != null;
|
||||
}
|
||||
};
|
||||
ImageIcon icon = ResourceManager.loadImage("images/table_go.png");
|
||||
Icon icon = ResourceManager.loadImage("images/table_go.png");
|
||||
openRefsAction.setPopupMenuData(
|
||||
new MenuData(new String[] { "Symbol References" }, icon, popupGroup));
|
||||
openRefsAction.setToolBarData(new ToolBarData(icon));
|
||||
@@ -752,7 +753,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
}
|
||||
|
||||
private void createExportActions() {
|
||||
|
||||
//Need Decompiler extensions
|
||||
/*
|
||||
DockingAction exportDwarf =
|
||||
new DockingAction("export_dwarf", this.getName()) {
|
||||
@@ -794,12 +795,35 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
|
||||
};
|
||||
|
||||
MenuData menuData = new MenuData(new String[] {"Export", "Export to C" }, null, "Export");
|
||||
MenuData menuData = new MenuData(new String[] {"Export", "Select range to export to recomp. C (EXPERIMENTAL)" }, null, "Export");
|
||||
menuData.setMenuSubGroup("1");
|
||||
exportC.setPopupMenuData(menuData);
|
||||
//exportC.setAddToAllWindows(true);
|
||||
tool.addLocalAction(symProvider, exportC);
|
||||
|
||||
DockingAction exportCModule =
|
||||
new DockingAction("export_c_mod", this.getName()) {
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
return symProvider.getCurrentSymbol() != null;
|
||||
}
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
AddressRange theRange = CodecutUtils.getNamespaceRange(getProgram(), getSymbolProvider().getCurrentSymbol());
|
||||
exportC(theRange.getMinAddress().toString(), theRange.getMaxAddress().toString());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
menuData = new MenuData(new String[] {"Export", "Export module to recomp. C (EXPERIMENTAL)" }, null, "Export");
|
||||
menuData.setMenuSubGroup("1");
|
||||
exportCModule.setPopupMenuData(menuData);
|
||||
//exportC.setAddToAllWindows(true);
|
||||
tool.addLocalAction(symProvider, exportCModule);
|
||||
|
||||
//Need to get obj file output script to work without the parent ELF
|
||||
//Could check and only enable this if the loaded file was an ARM ELF
|
||||
/*
|
||||
DockingAction exportElf =
|
||||
new DockingAction("export_elf", this.getName()) {
|
||||
@Override
|
||||
@@ -808,7 +832,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
}
|
||||
@Override
|
||||
public void actionPerformed(ActionContext programContext) {
|
||||
Namespace ns = symProvider.getCurrentSymbol().getParentNamespace(); // for o output
|
||||
Namespace ns = symProvider.getCurrentSymbol().getParentNamespace(); // for .o output
|
||||
Msg.info(new Object(), ns.getName());
|
||||
GhidraState gstate = new GhidraState(tool, tool.getProject(), currentProgram, null, null, null);
|
||||
OFileExporter outputELF = new OFileExporter(gstate, ns.getName());
|
||||
@@ -822,12 +846,13 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
|
||||
};
|
||||
|
||||
menuData = new MenuData(new String[] {"Export", "Export to ELF" }, null, "Export");
|
||||
menuData = new MenuData(new String[] {"Export", "Export to ELF (EXPERIMENTAL)" }, null, "Export");
|
||||
menuData.setMenuSubGroup("1");
|
||||
exportElf.setPopupMenuData(menuData);
|
||||
//exportElf.setAddToAllWindows(true);
|
||||
tool.addLocalAction(symProvider, exportElf);
|
||||
|
||||
*/
|
||||
//Need Decompiler Extensions
|
||||
/*
|
||||
DockingAction updateMapping = new DockingAction("Update Decompiler Mapping", getName()) {
|
||||
@Override
|
||||
@@ -1033,7 +1058,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
stringInstance.getComponentPath(), null, 0, 0, 0);
|
||||
|
||||
accumulator.add(pl);
|
||||
monitor.checkCanceled();
|
||||
//monitor.checkCanceled();
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
|
||||
@@ -1181,7 +1206,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
|
||||
private File loadMapFile() {
|
||||
GhidraFileChooser fileChooser = new GhidraFileChooser(this.symProvider.getComponent());
|
||||
String dir = Preferences.getProperty(Preferences.LAST_IMPORT_DIRECTORY);
|
||||
String dir = Preferences.getProperty(Preferences.LAST_TOOL_IMPORT_DIRECTORY );
|
||||
if (dir != null) {
|
||||
File file = new File(dir);
|
||||
fileChooser.setCurrentDirectory(file);
|
||||
@@ -1194,7 +1219,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
if (file != null) {
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null) {
|
||||
Preferences.setProperty(Preferences.LAST_IMPORT_DIRECTORY, parent.getAbsolutePath());
|
||||
Preferences.setProperty(Preferences.LAST_TOOL_IMPORT_DIRECTORY, parent.getAbsolutePath());
|
||||
}
|
||||
if (!file.getName().endsWith(".map")) {
|
||||
Msg.showInfo(this, this.symProvider.getComponent(),
|
||||
@@ -1209,7 +1234,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
|
||||
protected File getMapFile() {
|
||||
GhidraFileChooser fileChooser = new GhidraFileChooser(this.symProvider.getComponent());
|
||||
String dir = Preferences.getProperty(Preferences.LAST_EXPORT_DIRECTORY);
|
||||
String dir = Preferences.getProperty(Preferences.LAST_TOOL_EXPORT_DIRECTORY);
|
||||
if (dir != null) {
|
||||
File file = new File(dir);
|
||||
fileChooser.setCurrentDirectory(file);
|
||||
@@ -1222,7 +1247,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
if (file != null) {
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null) {
|
||||
Preferences.setProperty(Preferences.LAST_EXPORT_DIRECTORY, parent.getAbsolutePath());
|
||||
Preferences.setProperty(Preferences.LAST_TOOL_EXPORT_DIRECTORY, parent.getAbsolutePath());
|
||||
}
|
||||
String name = file.getName();
|
||||
if (!file.getName().endsWith(".map")) {
|
||||
@@ -1282,7 +1307,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
|
||||
protected File getCFile() {
|
||||
GhidraFileChooser fileChooser = new GhidraFileChooser(this.symProvider.getComponent());
|
||||
String dir = Preferences.getProperty(Preferences.LAST_EXPORT_DIRECTORY);
|
||||
String dir = Preferences.getProperty(Preferences.LAST_TOOL_EXPORT_DIRECTORY);
|
||||
if (dir != null) {
|
||||
File file = new File(dir);
|
||||
fileChooser.setCurrentDirectory(file);
|
||||
@@ -1295,7 +1320,7 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
|
||||
if (file != null) {
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null) {
|
||||
Preferences.setProperty(Preferences.LAST_EXPORT_DIRECTORY, parent.getAbsolutePath());
|
||||
Preferences.setProperty(Preferences.LAST_TOOL_EXPORT_DIRECTORY, parent.getAbsolutePath());
|
||||
}
|
||||
String name = file.getName();
|
||||
if (!file.getName().endsWith(".c")) {
|
||||
|
||||
@@ -97,10 +97,10 @@ class NamespacePanel extends JPanel {
|
||||
nsList.sort(new Comparator<Namespace>() {
|
||||
@Override
|
||||
public int compare(Namespace ns1, Namespace ns2) {
|
||||
Address ns1MaxAddress = ns1.getBody().getMaxAddress();
|
||||
Address ns2MaxAddress = ns2.getBody().getMaxAddress();
|
||||
if (ns1MaxAddress != null && ns2MaxAddress != null) {
|
||||
if (ns1MaxAddress.getAddressableWordOffset() < ns2MaxAddress.getAddressableWordOffset()) {
|
||||
Address ns1MinAddress = ns1.getBody().getMinAddress();
|
||||
Address ns2MinAddress = ns2.getBody().getMinAddress();
|
||||
if (ns1MinAddress != null && ns2MinAddress != null) {
|
||||
if (ns1MinAddress.getAddressableWordOffset() < ns2MinAddress.getAddressableWordOffset()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user