Compare commits

...

87 Commits

Author SHA1 Message Date
Robert Xiao
0dc18c0efc
Merge 8bfd0c8528 into 28846ef279 2024-06-28 13:19:47 +08:00
ghidra1
28846ef279 GP-0 Corrected formatting issue 2024-06-26 16:55:02 -04:00
Ryan Kurtz
e7595341c4 Merge remote-tracking branch 'origin/GP-4722_ryanmkurtz_omf-markup' 2024-06-26 11:30:09 -04:00
Ryan Kurtz
bf71142709 Merge remote-tracking branch
'origin/GP-4709_d-millar_HexInteger_ext--SQUASHED' (Closes #6658,
Closes #6659)
2024-06-26 11:26:51 -04:00
Ryan Kurtz
36a707471e GP-4722: Marking up OMF records 2024-06-26 11:13:44 -04:00
ghidra1
4b30e484b0 GP-4508 revised address range space checks 2024-06-26 09:13:11 -04:00
d-millar
ae3f6feb70 GP-4709: post-post-post review
GP-4709: post=post review - test logic in error
GP-4709: post=post review - test logic in error
GP-4709: post-review fixes
GP-4709: going for simplicity
GP-4709: reverting most stuff
GP-4709: fix for replaceValue logic
GP-4709: extensions to HexInteger
2024-06-26 09:09:28 -04:00
Ryan Kurtz
2b73a6157f Merge remote-tracking branch
'origin/GP-4707_ryanmkurtz_headless--SQUASHED' (Closes #6639)
2024-06-25 13:41:24 -04:00
Ryan Kurtz
34272fd3ff GP-4707: Improved handling of custom headless command line arguments that start with dash 2024-06-25 13:38:53 -04:00
ghidra1
3b6d5e43ce GP-0 Corrected test failure fallout from GP-4682 datatype resolution
change related to temporary source archive
2024-06-25 13:21:27 -04:00
Ryan Kurtz
b86ad84c04 Merge remote-tracking branch 'origin/patch' 2024-06-25 12:16:37 -04:00
Ryan Kurtz
72d4a342a6 Merge remote-tracking branch
'origin/GP-4710_Dan_gdbGeneralRegistersFallback' into patch
(Closes #6635)
2024-06-25 12:14:02 -04:00
Ryan Kurtz
b68fa6c745 Merge remote-tracking branch
'origin/GP-3491-dragonmacher-decompiler-find-window--SQUASHED'
(Closes #5317, #538)
2024-06-25 12:03:04 -04:00
dragonmacher
62f41a7179 GP-3491 - Decompiler - Added a Search All button to the Find Dialog that will show a table of results 2024-06-25 11:59:05 -04:00
Ryan Kurtz
a977a35f5f Merge remote-tracking branch 'origin/GP-4703_ghizard_MDMang_multipass_on_demangleType_method' 2024-06-25 11:57:01 -04:00
Ryan Kurtz
d5cbda1e21 Merge remote-tracking branch 'origin/GP-4708_RecoverClassesFillOutStructure' 2024-06-25 06:34:53 -04:00
Ryan Kurtz
4d8ec78908 Merge remote-tracking branch
'origin/GP-4508_ghidra1_ProgramContextMergeForOverlays' (Closes #6403)
2024-06-25 06:01:35 -04:00
ghidra007
02aba11104 GP-4708 updated createStructure to fix logic error. 2024-06-24 23:11:26 +00:00
caheckman
184c657cfd GP-4708 Adjustments to RecoverClassesFromRTTIScript and
FillOutStructureHelper
2024-06-24 23:11:26 +00:00
ghidra1
13821930da GP-4508 Corrected program context mutli-user merge issue for overlays 2024-06-24 16:11:31 -04:00
Ryan Kurtz
e9e4ee48ce Merge remote-tracking branch 'origin/patch' 2024-06-24 13:59:30 -04:00
Ryan Kurtz
21a3896018 Merge remote-tracking branch 'origin/GP-4682_ghidra1_eBPFAnalysis--SQUASHED' 2024-06-24 13:50:03 -04:00
ghidra1
eb5e6a323a GP-4682 cleanup eBPF analyzers and BPF helper function identification 2024-06-24 12:39:52 -04:00
ghidra1
ea785546cf GP-0 Fix URL error handling 2024-06-24 11:46:51 -04:00
Dan
008a4ef948 GP-4710: Fallback to all registers if "general" is not a group.
Fixes #6635
2024-06-24 10:08:46 -04:00
Ryan Kurtz
5ab72bf4f2 Merge remote-tracking branch 'origin/patch' 2024-06-20 14:30:06 -04:00
Ryan Kurtz
627e3f14fa Merge remote-tracking branch
'origin/GP-4704_d-millar_endianness_fix--SQUASHED' into patch
(Closes #6656)
2024-06-20 14:26:37 -04:00
Ryan Kurtz
b4ef357e53 Merge branch 'GP-0_ryanmkurtz_PR-6657_0x-Singularity_jdk22-build' 2024-06-20 14:13:16 -04:00
Ryan Kurtz
6b94d4b69b GP-0: Formatting 2024-06-20 14:11:31 -04:00
0xGrizzly
2fc70183e5 Fix initialization of OBJECT_HASHCODE in PrivatelyQueuedListener 2024-06-20 12:00:43 -04:00
d-millar
625df03c15 GP-4704: expanded gdb.parameter logic
GP-4704: expanded gdb.parameter logic
2024-06-20 11:42:25 -04:00
ghizard
8336bdde74 GP-4703 - MDMang - retry on demangleType 2024-06-20 14:03:47 +00:00
Ryan Kurtz
0e33958c76 Merge remote-tracking branch
'origin/GP-3494-dragonmacher-decompiler-highlight-nav-v2--SQUASHED'
(Closes #538)
2024-06-20 06:25:47 -04:00
dragonmacher
9f8b03a90f GP-3494 - Decompiler - Updated the middle-mouse toggle to be persistent when moving the cursor; Added actions for navigating the middle-moused tokens 2024-06-18 14:32:00 -04:00
Robert Xiao
8bfd0c8528 Update WebAssembly module for 11.1 2024-06-10 22:56:30 +08:00
Kristoffer Søholm
0a60c86f9f Remove unneeded import, fix copy paste error 2024-06-10 08:38:03 +08:00
Kristoffer Søholm
9e4ec76092 Fix build with Ghidra 11.0 2024-06-10 08:38:03 +08:00
Robert Xiao
90725b4fa3 Eliminate a few warnings about laneidx* tables 2024-06-10 08:38:03 +08:00
Robert Xiao
12d7843524 implement clz, ctz, popcnt 2024-06-10 08:38:03 +08:00
Robert Xiao
9ed7675169 spectest bugfix: disable C stack detection for wast tests
Some of the tests (e.g. global.wast) were triggering the C stack detection
logic, which was causing problems for the test.
2024-06-10 08:38:03 +08:00
Robert Xiao
b40d3705e5 spectest bugfix: fix parsing of select funcref
Parsing of `select funcref` (1C 01 70) was failing because SLEIGH was committing
to one of the select forms (in this case, `select valtype_64`) using a
rudimentary mask check before going on to fully check the final byte. The LEB128
parsing mixed with the `bytev` checking resulted in a very simplistic mask check
for `select valtype_64`, which passed for 0x70, and resulted in a parsing
failure when `0x70` later failed to match `0x7e` or `0x7c`. The
DebugSleighInstructionParse script was instrumental in identifying this
behaviour.

Instead, the better solution is to check for the 0x01 LEB128 count as early as
possible, and only then examine the final type byte.

This change also improves the display of "ref.null t" and "select t*"
instructions by using symbolic type names.
2024-06-10 08:38:03 +08:00
Robert Xiao
9c248a0e86 spectest bugfix: encode externref as nonzero integers
(ref.extern 0) is non-null, so we need to encode externrefs in order to pass
a test in ref_is_null.
2024-06-10 08:38:03 +08:00
Robert Xiao
1dfb40b6c5 spectest bugfix: fix implementation of float max/min/trunc
max and min weren't handling NaNs correctly, and trunc was just not correct at
all (it was limited to i32/i64 range). This fixes a bunch of failures in
f32/f64.wast.
2024-06-10 08:38:03 +08:00
Robert Xiao
e8a3145455 Add the spectest script 2024-06-10 08:38:03 +08:00
Robert Xiao
449da2d0ea spectest bugfix: escape custom section names to ensure valid datatype/memory block names
custom section names can have arbitrary Unicode content, so we should use
escapeJava to clean them.
2024-06-10 08:38:03 +08:00
Robert Xiao
d638c1bf6b spectest bugfix: be careful about zero-sized chunks.
`readNextByteArray` may still throw an EOFError even if the read size is zero,
which can happen if we're decoding the very last object in the file.
2024-06-10 08:38:03 +08:00
Robert Xiao
ccd27f1c40 Don't create empty segments; this avoids angry warnings in the log 2024-06-10 08:38:03 +08:00
Robert Xiao
0d27154046 spectest bugfix: element section type param ("flags") is actually LEB128
This is tested by binary-leb128, and previously was failing to load the module
when the element section type parameter was encoded as a multi-byte LEB128.
However, since we weren't checking to see whether the modules loaded
successfully, this failed silently. The addition of WasmAnalysis forces us to
check whether the Wasm module parses correctly.
2024-06-10 08:38:03 +08:00
Robert Xiao
453fd1b751 Provide a simulateCall convenience function to set up a simulated call frame 2024-06-10 08:38:03 +08:00
Robert Xiao
e7490326d6 spectest bugfix: zero out locals when emulating
This fixes some failures from call_indirect, among others. Instead of using an
uponentry injection, this new implementation hijacks the ".locals" directive to
add a CallOther pcodeop, which is usable from both the decompiler and emulator.
2024-06-10 08:38:03 +08:00
Robert Xiao
4fa6ea3dab spectest bugfix: fix copysign to work with nans, etc.
Fixes test failures in f{32,64}_bitwise and float_misc.
2024-06-10 08:38:03 +08:00
Robert Xiao
c21512cc50 spectest bugfix: fix br_table semantics when unreachable
When unreachable, br_table may underflow the stack, which is ok. However,
br_table needs to push Unknown types back on the stack, rather than the expected
types. This subtlety was caught by the unreached-valid meet-bottom testcase.
2024-06-10 08:38:03 +08:00
Robert Xiao
86e552decd spectest bugfix: fix table.grow semantics
table.grow produces an i32 (the old table size); this was incorrectly omitted
and caused validation failures on modules containing that instruction.
2024-06-10 08:38:02 +08:00
Robert Xiao
7fb06d3be0 spectest bugfix: avoid crash on parsing select after br
After a br, the remaining code in the block is unreachable and types are no
longer tracked on the stack. Thus, multiple null types can creep in, which was
causing (among other things) crashes when analyzing a select instruction after a
br.
2024-06-10 08:38:02 +08:00
Robert Xiao
95eb40a244 spectest bugfix: initialize tables and element segments with 0x00
Tables and element segments apparently default to holding null refs, and since
we're using 0x00000000 for null (`ref.null`, `ref.is_null`), we need to ensure
that they are initialized appropriately.

In the future, we may want to revisit whether zero is the right value for null -
e.g. can zero be a valid value for some reference type?
2024-06-10 08:38:02 +08:00
Robert Xiao
97b74acde9 spectest bugfix: don't crash when jumping to no-context addresses
The VM may jump to an uninitialized address such as 0x00000000. If no context is
available at that address, this would crash the emulator before the instruction
can even be hit (e.g. for a breakpoint).
2024-06-10 08:38:02 +08:00
Robert Xiao
5b06763908 spectest bugfix: do not use float2float on constants
float2float may modify the floating point value, but f{32,64}.const instructions
are supposed to load the raw constant value. Fixes test failures on
float_literals.
2024-06-10 08:38:02 +08:00
Robert Xiao
2abda76bc0 SIMD refactoring: implement some bitwise and arith ops in P-code 2024-06-10 08:38:02 +08:00
Robert Xiao
d3ebad765a SIMD refactoring: implement shuffle in P-code 2024-06-10 08:38:02 +08:00
Robert Xiao
da7d334871 SIMD refactoring: implement extract and replace in P-code 2024-06-10 08:38:02 +08:00
Robert Xiao
784ef5f543 SIMD refactoring: implement splat in P-code 2024-06-10 08:38:02 +08:00
Robert Xiao
1700bf6adb Remove helpPath task - not needed after a7668c7f8 2024-06-10 08:38:02 +08:00
Robert Xiao
06fe349e14 WebAssembly: minor updates to sync to master 2024-06-10 08:38:02 +08:00
Robert Xiao
9684b738c7 WebAssembly: fix load32/load64, simplify some SIMD pcode
load32/load64 were incorrectly taking a laneidx, causing disassembly failures.
Also, take this opportunity to implement some of the SIMD operations using
common pcodeops. This will reduce emulation burden, as well as making loads and
stores explicit for dataflow analysis.
2024-06-10 08:38:02 +08:00
Robert Xiao
67f16d97d6 WebAssembly: Simplify laneidx vector operands.
laneidx is actually just a byte, not a LEB128. Attempting to use 16 arguments in
a pcodeop hits a hardcoded limit of 8 args in PcodeEmit, so switch i8x16.shuffle
to using 4 32-bit arguments instead. This is the same syntax as used by
wasm-objdump by default.
2024-06-10 08:38:02 +08:00
Robert Xiao
1e5572ac52 WebAssembly: add a SIMD pcodetest.
Similarly to the x86 AVX2 test, this will allow us to do (basic) testing of Wasm
SIMD opcodes via autovectorization. Only the -O3 binary is expected to contain
SIMD opcodes.
2024-06-10 08:38:02 +08:00
Robert Xiao
93bff2b669 WebAssembly: Fix pcode_conversions_Main test.
This is fixed by implementing unsigned-to-float conversions properly.
2024-06-10 08:38:02 +08:00
Robert Xiao
00722cb6ae WebAssembly: implement pcodetest tests.
We pass almost every test with -O0 and -O3, with the exception of pcode_conversions_Main.

There's a rather ugly hack needed to convert certain function pointers from
table indices (used by the actual code) to byte addresses (used by the emulator
to set PC). The way this is implemented is decidedly not ideal; a much better
solution would be to somehow hook readCodePointer in the
ProcessorEmulatorTestAdapter subclass; this would also enable the "procedure
descriptor indirection" fix to be moved into a processor-specific
implementation.

Changing the structs to protected in `ProcessorEmulatorTestAdapter` is for
convenience, so that the subclass does not need to go look those up again.

The zero-size check added to `BytesPcodeExecutorStateSpace#read` fixes a bug
which caused an exception when reading 0 bytes (`offset + size - 1` is not a
valid calculation in that case).
2024-06-10 08:38:02 +08:00
Robert Xiao
6726b40fc3 WebAssembly: add wasm build instructions to pcodetest
This successfully builds a working binary with Clang 15.

- `mem*` definitions in `misc_BODY.c` and new `main` definition in `tpp.py`
  are for C99 compatibility
- `encoding='utf8'` in build.py produces a more readable log
- `TestInfo_force` in pcode_test.c ensures that the entire `MainInfo` structure
  is included in the binary
2024-06-10 08:38:02 +08:00
Robert Xiao
907a7b52e9 WebAssembly: update ConstantExpression comment 2024-06-10 08:38:01 +08:00
Robert Xiao
681617cc84 WebAssembly: Add a stack analyzer which creates C stack variables for Wasm.
The traditional stack analyzer, StackVariableAnalyzer
(NewFunctionStackAnalysisCmd) depends on having registers that contain stack
addresses visible as assembly operands. However, due to the Wasm disassembler
design, which hides the Wasm stack registers from the disassembly, this stack
analysis cannot automatically extract stack variables.

As a fix, adapt FunctionStackAnalysisCmd to operate on the hidden Wasm stack
register operands to detect operations that interact with C stack addresses.

This helps with cross references to C stack variables, as well as allowing C
stack variables to be properly interacted with in the decompiler.
2024-06-10 08:38:01 +08:00
Robert Xiao
277c0461d8 WebAssembly: Mark __wasm prototypes as register strategies 2024-06-10 08:38:01 +08:00
Robert Xiao
b250cb9de0 WebAssembly: Add a new script to handle "flat" dyncall namespaces.
It seems like LLVM, for instance, uses table indices as function pointers, so
this script is likely to be useful for anything compiled with LLVM. As a guess,
analyze_dyncalls is probably only useful for programs compiled using the
Emscripten fastcomp backend.
2024-06-10 08:38:01 +08:00
Robert Xiao
55ad5fd854 WebAssembly: disable 2+ output vars for pos-stack as it is unsupported.
Without this, we get the error "<pentry> tags within a group must be
distinguished by size or type" when attempting to load the pos-stack compiler
spec. This will break functions that have multiple output arguments, but those
are expected to be rare (and they are not well-supported by Ghidra anyway).
2024-06-10 08:38:01 +08:00
Robert Xiao
74b5f591e7 WebAssembly: update to use standard LEB128 utilities 2024-06-10 08:38:01 +08:00
Robert Xiao
dccfe6625d WebAssembly: remove now-deleted DataTypeConflictException 2024-06-10 08:38:01 +08:00
Robert Xiao
caf8c116de WebAssembly: update help stylesheet to recommended style 2024-06-10 08:38:01 +08:00
Robert Xiao
e744db5a1d WebAssembly: create code length data to avoid undefined bytes 2024-06-10 08:38:01 +08:00
Robert Xiao
48b2a2e385 WebAssembly: SIMD support.
Disassembly and verification are implemented, but almost all of the SIMD opcodes
are just stubbed out with pcodeops for now, so semantics aren't implemented. This
is probably good enough for now.
2024-06-10 08:38:01 +08:00
Robert Xiao
54467230bd WebAssembly: correctly handle u32 opcodes.
0xFC (and 0xFD) have a u32 as the second operand, so we need to handle this
correctly.
2024-06-10 08:38:01 +08:00
Robert Xiao
97a4fb1948 WebAssembly: Emulation support
This patch implements a EmulateInstructionStateModifier for Wasm which provides
support for emulating Wasm instructions.

The memory contents must contain a full module for this to work, because
instruction semantics still depend on module details (e.g. the type of certain
operations depends on metadata like the types of imports or globals).
2024-06-10 08:38:01 +08:00
Robert Xiao
8d9cf40cac WebAssembly: extend regs to 16 bytes to prepare for vector insts
Vector instructions will require 128-bit values, so prepare for that change by
widening all registers to 128 bits.
2024-06-10 08:38:01 +08:00
Robert Xiao
593b824b13 WebAssembly: load .module directly to 0x8000000 in RAM
This allows the whole module to be loaded in memory and eliminates the previous
duplication of the .code bytes in the .module and the .function bytes in RAM.
2024-06-10 08:38:01 +08:00
Robert Xiao
e692786367 WebAssembly: reduce dependency on Program class.
Using Program throughout is rather lazy, and in many cases totally unnecessary.
Using finer-grained interfaces and classes like AddressFactory allows usage of
the code in non-Program contexts, such as emulation.
2024-06-10 08:38:01 +08:00
Robert Xiao
34f2ce8eee WebAssembly: make funcref/externref 32 bits in size.
The use of 64 bits was mostly a debugging feature to ensure we didn't accidentally
mix references with normal types. 64 bit addresses cause some problems, though,
such as 64-bit immediates (e.g. from ref.func) not always being treated as addresses
in the decompiler.
2024-06-10 08:38:00 +08:00
Robert Xiao
3c3a19fa2f WebAssembly: add a help page to explain the finer points of the extension.
I was hoping to add this to a Processors help submenu, but it doesn't exist,
so I put it in the Appendix instead for now.
2024-06-10 08:38:00 +08:00
Robert Xiao
58c48b654a WebAssembly support.
Merged from https://github.com/nneonneo/ghidra-wasm-plugin/.

Closes #2937.
2024-06-10 08:38:00 +08:00
199 changed files with 19569 additions and 2506 deletions

View File

@ -104,7 +104,7 @@ def get_arch():
def get_endian():
parm = gdb.parameter('endian')
if parm != 'auto':
if not parm in ['', 'auto', 'default']:
return parm
# Once again, we have to hack using the human-readable 'show'
show = gdb.execute('show endian', to_string=True)
@ -132,7 +132,7 @@ def get_osabi():
def compute_ghidra_language():
# First, check if the parameter is set
lang = gdb.parameter('ghidra-language')
if lang != 'auto':
if not lang in ['', 'auto', 'default']:
return lang
# Get the list of possible languages for the arch. We'll need to sift
@ -157,7 +157,7 @@ def compute_ghidra_language():
def compute_ghidra_compiler(lang):
# First, check if the parameter is set
comp = gdb.parameter('ghidra-compiler')
if comp != 'auto':
if not comp in ['', 'auto', 'default']:
return comp
# Check if the selected lang has specific compiler recommendations

View File

@ -381,7 +381,10 @@ class RegisterDesc(namedtuple('BaseRegisterDesc', ['name'])):
def get_register_descs(arch, group='all'):
if hasattr(arch, "registers"):
return arch.registers(group)
try:
return arch.registers(group)
except ValueError: # No such group, or version too old
return arch.registers()
else:
descs = []
regset = gdb.execute(

View File

@ -33,15 +33,17 @@ public class PrivatelyQueuedListener<P> {
DataStructureErrorHandlerFactory.createListenerErrorHandler();
protected class ListenerHandler implements InvocationHandler {
private static final Method OBJECT_HASHCODE;
static {
private static final Method OBJECT_HASHCODE = initObjectHashCode();
private static Method initObjectHashCode() {
try {
OBJECT_HASHCODE = Object.class.getMethod("hashCode");
return Object.class.getMethod("hashCode");
}
catch (NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
}
protected final Class<P> iface;
public ListenerHandler(Class<P> iface) {

View File

@ -48,7 +48,7 @@ class BuildUtil:
f = subprocess.PIPE
if verbose: self.log_info(string)
try:
sp = subprocess.Popen(cmd, stdout=f, stderr=subprocess.PIPE)
sp = subprocess.Popen(cmd, stdout=f, stderr=subprocess.PIPE, encoding="utf8")
except OSError as e:
self.log_err("Command: " + string)
self.log_err(e.strerror)

View File

@ -15,6 +15,13 @@
*/
#include "pcode_test.h"
#include "big_struct.h"
#ifdef HAS_LIBC
#include <string.h>
#else
void *memset(void *b, int c, size_t len);
void *memcpy(void *dst, const void *src, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);
#endif
static i4 int_expectedValue;
static i4 int_actualValue;

View File

@ -72,6 +72,11 @@ static TestInfo MainInfo = {
mainFunctionInfoTable, /* function table */
};
NOINLINE TestInfo TestInfo_force(void)
{
return MainInfo;
}
NOINLINE void TestInfo_reset(void)
{
MainInfo.numpass = 0;

View File

@ -732,3 +732,21 @@ PCodeTest({
'language_id': 'Xtensa:BE:32:default',
'ccflags': '-L %(toolchain_dir)s/lib/gcc/xtensa-elf/%(gcc_version)s',
})
PCodeTest({
'name': 'WebAssembly',
'compile_exe': 'bin/clang',
'toolchain': 'LLVM/llvm',
'toolchain_type': 'llvm',
'ccflags': '-std=c99 --target=wasm32 -Wl,--export-dynamic -Wl,--no-gc-sections -Wl,--no-entry',
'language_id': 'Wasm:LE:32:default',
})
PCodeTest({
'name': 'WebAssembly_SIMD',
'compile_exe': 'bin/clang',
'toolchain': 'LLVM/llvm',
'toolchain_type': 'llvm',
'ccflags': '-std=c99 --target=wasm32 -msimd128 -Wl,--export-dynamic -Wl,--no-gc-sections -Wl,--no-entry',
'language_id': 'Wasm:LE:32:default',
})

View File

@ -78,6 +78,8 @@ class PCodeTestBuild(BuildUtil):
return PCodeBuildCCS(pcode_test)
elif pcode_test.config.toolchain_type == 'sdcc':
return PCodeBuildSDCC(pcode_test)
elif pcode_test.config.toolchain_type == 'llvm':
return PCodeBuildLLVM(pcode_test)
else:
raise Exception(pcode_test.config.format('Toolchain type %(toolchain_type)s not known'))
@ -320,6 +322,55 @@ class PCodeBuildCCS(PCodeTestBuild):
self.log_err('output not created %s' % output_file)
return
class PCodeBuildLLVM(PCodeTestBuild):
def __init__(self, PCodeTest):
super(PCodeBuildLLVM, self).__init__(PCodeTest)
# Set options for compiler depending on needs.
def cflags(self, output_file):
f = []
f += ['-DHAS_FLOAT=1' if self.config.has_float else '-DHAS_FLOAT_OVERRIDE=1']
f += ['-DHAS_DOUBLE=1' if self.config.has_double else '-DHAS_DOUBLE_OVERRIDE=1']
f += ['-DHAS_LONGLONG=1' if self.config.has_longlong else '-DHAS_LONGLONG_OVERRIDE=1']
if self.config.has_shortfloat: f += ['-DHAS_SHORTFLOAT=1']
if self.config.has_vector: f += ['-DHAS_VECTOR=1']
if self.config.has_decimal128: f += ['-DHAS_DECIMAL128=1']
if self.config.has_decimal32: f += ['-DHAS_DECIMAL32=1']
if self.config.has_decimal64: f += ['-DHAS_DECIMAL64=1']
f += ['-DNAME=NAME:%s' % output_file]
f += ['-static', '-Wno-unused-macros', '-nodefaultlibs', '-nostartfiles', '-fno-builtin']
f += self.config.ccflags.split()
f += self.config.add_ccflags.split()
return f
def compile(self, input_files, opt_cflag, output_base):
# Name the output file, and delete it if it exists
output_file = '%s.out' % (output_base)
self.remove(output_file)
# Construct the compile command line and execute it
cmp = self.which('compile_exe')
cmd = [cmp] + input_files + self.cflags(output_file) + [opt_cflag, "-o", output_file]
out, err = self.run(cmd)
if out: self.log_info(out)
# print error messages, which may just be warnings
if err: self.log_warn(err)
# return now if the error preempted the binary
if not self.is_readable_file(output_file):
self.log_err('output not created %s' % output_file)
return
class PCodeBuildGCC(PCodeTestBuild):
def __init__(self, PCodeTest):

View File

@ -197,7 +197,7 @@ void %(main)s(TestInfo* not_used) {
self.c_write('')
#for l in extern_lines:
# self.c_write(l)
self.c_write('void main(void) {')
self.c_write('int main(int argc, char **argv) {')
self.c_write('\tTestInfo info;')
#for l in main_lines:
# self.c_write(l)

View File

@ -609,6 +609,13 @@ xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-
<P><A name="h"></A><FONT size="7"><B>H</B></FONT></P>
<BLOCKQUOTE>
<H2><A name="HexShort"></A>Hex Short</H2>
<BLOCKQUOTE>
<P>A display format in the <A href="#ByteViewer">Byte Viewer</A> used to display short
values in hex.</P>
</BLOCKQUOTE>
<H2><A name="HexInteger"></A>Hex Integer</H2>
<BLOCKQUOTE>
@ -616,6 +623,20 @@ xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-
values in hex.</P>
</BLOCKQUOTE>
<H2><A name="HexLong"></A>Hex Long</H2>
<BLOCKQUOTE>
<P>A display format in the <A href="#ByteViewer">Byte Viewer</A> used to display long
values in hex.</P>
</BLOCKQUOTE>
<H2><A name="HexLongLong"></A>Hex Long Long</H2>
<BLOCKQUOTE>
<P>A display format in the <A href="#ByteViewer">Byte Viewer</A> used to display longlong
values in hex.</P>
</BLOCKQUOTE>
<H2><A name="HijackedFile"></A>Hijacked File</H2>
<BLOCKQUOTE>

View File

@ -42,11 +42,10 @@ public class SetRegisterCmd implements Command<Program> {
* A null value indicates that no value should be associated over the range.
*/
public SetRegisterCmd(Register register, Address start, Address end, BigInteger value) {
if (start.getAddressSpace() != end.getAddressSpace()) {
if (!start.getAddressSpace().equals(end.getAddressSpace())) {
throw new IllegalArgumentException(
"start and end address must be in the same address space");
"start and end address must be within the same address space");
}
this.register = register;
this.start = start;
this.end = end;

View File

@ -15,6 +15,14 @@
*/
package ghidra.app.merge.listing;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.util.ArrayList;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import ghidra.app.merge.MergeConstants;
import ghidra.app.merge.ProgramMultiUserMergeManager;
import ghidra.app.merge.tool.ListingMergePanel;
@ -24,17 +32,10 @@ import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.util.*;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.util.ArrayList;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* <code>RegisterMergeManager</code> handles the merge for a single named register.
*/
@ -98,6 +99,17 @@ class RegisterMergeManager implements ListingMergeConstants {
* @param latestChanges the address set of changes between original and latest versioned program.
* @param myChanges the address set of changes between original and my modified program.
*/
/**
* Creates a RegisterMergeManager.
* @param registerName
* @param mergeManager
* @param resultPgm the program to be updated with the result of the merge.
* @param originalPgm the program that was checked out.
* @param latestPgm the latest checked-in version of the program.
* @param myPgm the program requesting to be checked in.
* @param latestChanges the address set of changes between original and latest versioned program.
* @param myChanges the address set of changes between original and my modified program.
*/
RegisterMergeManager(String registerName, ProgramMultiUserMergeManager mergeManager,
Program resultPgm, Program originalPgm, Program latestPgm, Program myPgm,
ProgramChangeSet latestChanges, ProgramChangeSet myChanges) {
@ -123,10 +135,7 @@ class RegisterMergeManager implements ListingMergeConstants {
}
}
/* (non-Javadoc)
* @see ghidra.app.merge.MergeResolver#apply()
*/
public void apply() {
void apply() {
conflictOption = conflictPanel.getSelectedOptions();
// If the "Use For All" check box is selected
@ -138,27 +147,10 @@ class RegisterMergeManager implements ListingMergeConstants {
merge(min, max, resultReg);
}
/* (non-Javadoc)
* @see ghidra.app.merge.MergeResolver#cancel()
*/
public void cancel() {
void cancel() {
conflictOption = CANCELED;
}
/* (non-Javadoc)
* @see ghidra.app.merge.MergeResolver#getDescription()
*/
public String getDescription() {
return "Merge Register";
}
/* (non-Javadoc)
* @see ghidra.app.merge.MergeResolver#getName()
*/
public String getName() {
return "Register Merger";
}
/**
*
* @param monitor
@ -167,9 +159,8 @@ class RegisterMergeManager implements ListingMergeConstants {
if (conflictSet != null) {
return; //This method only needs to be called once.
}
RegisterConflicts rc =
new RegisterConflicts(registerName, originalContext, latestContext, myContext,
resultContext);
RegisterConflicts rc = new RegisterConflicts(registerName, originalContext, latestContext,
myContext, resultContext);
Memory resultMem = resultPgm.getMemory();
AddressSetView myDiffs =
rc.getRegisterDifferences(registerName, originalContext, myContext, mySet, monitor);
@ -177,8 +168,8 @@ class RegisterMergeManager implements ListingMergeConstants {
conflictSet = new AddressSet();
rvrs = rc.getConflicts(setToCheck, monitor);
if (rvrs.length > 0) {
for (int j = 0; j < rvrs.length; j++) {
conflictSet.add(rvrs[j]);
for (AddressRange rvr : rvrs) {
conflictSet.add(rvr);
}
}
autoSet = setToCheck.subtract(conflictSet);
@ -187,20 +178,19 @@ class RegisterMergeManager implements ListingMergeConstants {
/**
* Merges all the register values for the named register being managed by this merge manager.
* @param monitor the monitor that provides feedback to the user.
* @throws ProgramConflictException
* @throws CancelledException if the user cancels
*/
public void merge(TaskMonitor monitor) throws CancelledException {
monitor.setMessage("Auto-merging " + registerName +
" Register Values and determining conflicts.");
monitor.setMessage(
"Auto-merging " + registerName + " Register Values and determining conflicts.");
determineConflicts(monitor);
// Auto merge any program context changes from my program where the
// resulting program has the mem addresses but the latest doesn't.
AddressRangeIterator arIter = autoSet.getAddressRanges();
try {
while (arIter.hasNext() && !monitor.isCancelled()) {
while (arIter.hasNext() && !monitor.isCancelled()) {
try {
AddressRange range = arIter.next();
Address rangeMin = range.getMinAddress();
Address rangeMax = range.getMaxAddress();
@ -215,9 +205,10 @@ class RegisterMergeManager implements ListingMergeConstants {
valueRange.getMaxAddress(), value);
}
}
}
catch (ContextChangeException e) {
// ignore since processor context-register is not handled by this merge manager
catch (ContextChangeException e) {
// processor context-register is not handled here and should not occur
throw new AssertException(e);
}
}
int totalConflicts = rvrs.length;
@ -298,7 +289,8 @@ class RegisterMergeManager implements ListingMergeConstants {
resultContext.setValue(resultRegister, minAddress, maxAddress, myValue);
}
catch (ContextChangeException e) {
// ignore since this merge manager does not handle the processor context register
// processor context-register is not handled here and should not occur
throw new AssertException(e);
}
}
@ -316,7 +308,8 @@ class RegisterMergeManager implements ListingMergeConstants {
}
private void showMergePanel(final Address minAddress, final Address maxAddress,
final BigInteger latestValue, final BigInteger myValue, final BigInteger originalValue) {
final BigInteger latestValue, final BigInteger myValue,
final BigInteger originalValue) {
this.min = minAddress;
this.max = maxAddress;
try {
@ -339,9 +332,8 @@ class RegisterMergeManager implements ListingMergeConstants {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
VerticalChoicesPanel panel =
getConflictsPanel(minAddress, maxAddress, latestValue, myValue,
originalValue, changeListener);
VerticalChoicesPanel panel = getConflictsPanel(minAddress, maxAddress,
latestValue, myValue, originalValue, changeListener);
listingMergePanel.setBottomComponent(panel);
}
});
@ -377,12 +369,11 @@ class RegisterMergeManager implements ListingMergeConstants {
conflictPanel.clear();
}
conflictPanel.setTitle("\"" + registerName + "\" Register Value");
String text =
"Register: " + ConflictUtility.getEmphasizeString(registerName) +
ConflictUtility.spaces(4) + "Address Range: " +
ConflictUtility.getAddressString(minAddress) + " - " +
ConflictUtility.getAddressString(maxAddress) +
"<br>Select the desired register value for the address range.";
String text = "Register: " + ConflictUtility.getEmphasizeString(registerName) +
ConflictUtility.spaces(4) + "Address Range: " +
ConflictUtility.getAddressString(minAddress) + " - " +
ConflictUtility.getAddressString(maxAddress) +
"<br>Select the desired register value for the address range.";
conflictPanel.setHeader(text);
conflictPanel.setRowHeader(getRegisterInfo(-1, null));
conflictPanel.addRadioButtonRow(getRegisterInfo(MergeConstants.LATEST, latestValue),
@ -431,7 +422,8 @@ class RegisterMergeManager implements ListingMergeConstants {
Register conflictResultReg;
RegisterConflicts(String registerName, ProgramContext originalContext,
ProgramContext latestContext, ProgramContext myContext, ProgramContext resultContext) {
ProgramContext latestContext, ProgramContext myContext,
ProgramContext resultContext) {
this.conflictRegisterName = registerName;
this.conflictOriginalContext = originalContext;
this.conflictLatestContext = latestContext;
@ -496,13 +488,11 @@ class RegisterMergeManager implements ListingMergeConstants {
ArrayList<AddressRange> conflicts = new ArrayList<AddressRange>();
AddressSet tempLatestChanges =
getRegisterDifferences(conflictRegisterName, conflictOriginalContext,
conflictLatestContext, addressSet, monitor);
AddressSet tempLatestChanges = getRegisterDifferences(conflictRegisterName,
conflictOriginalContext, conflictLatestContext, addressSet, monitor);
AddressSet tempMyChanges =
getRegisterDifferences(conflictRegisterName, conflictOriginalContext,
conflictMyContext, addressSet, monitor);
AddressSet tempMyChanges = getRegisterDifferences(conflictRegisterName,
conflictOriginalContext, conflictMyContext, addressSet, monitor);
AddressSet bothChanged = tempMyChanges.intersect(tempLatestChanges);
@ -513,19 +503,16 @@ class RegisterMergeManager implements ListingMergeConstants {
Address rangeMin = range.getMinAddress();
Address rangeMax = range.getMaxAddress();
AddressRangeIterator it1 =
conflictLatestContext.getRegisterValueAddressRanges(conflictLatestReg,
rangeMin, rangeMax);
AddressRangeIterator it2 =
conflictMyContext.getRegisterValueAddressRanges(conflictMyReg, rangeMin,
rangeMax);
AddressRangeIterator it1 = conflictLatestContext
.getRegisterValueAddressRanges(conflictLatestReg, rangeMin, rangeMax);
AddressRangeIterator it2 = conflictMyContext
.getRegisterValueAddressRanges(conflictMyReg, rangeMin, rangeMax);
AddressRangeIterator it = new CombinedAddressRangeIterator(it1, it2);
while (it.hasNext()) {
AddressRange addrRange = it.next();
BigInteger lastestValue =
conflictLatestContext.getValue(conflictLatestReg,
addrRange.getMinAddress(), false);
BigInteger lastestValue = conflictLatestContext.getValue(conflictLatestReg,
addrRange.getMinAddress(), false);
BigInteger myValue =
conflictMyContext.getValue(conflictMyReg, addrRange.getMinAddress(), false);
boolean sameValue =

View File

@ -67,8 +67,7 @@ public class FindPossibleReferencesPlugin extends Plugin {
final static String RESTORE_SELECTION_ACTION_NAME = "Restore Direct Refs Search Selection";
final static String SEARCH_DIRECT_REFS_ACTION_NAME = "Search for Direct References";
final static String SEARCH_DIRECT_REFS_ACTION_HELP = "Direct_Refs_Search_Alignment";
private DockingAction action;
private ArrayList<TableComponentProvider<ReferenceAddressPair>> providerList;
private List<TableComponentProvider<ReferenceAddressPair>> providerList;
public FindPossibleReferencesPlugin(PluginTool tool) {
super(tool);
@ -98,22 +97,21 @@ public class FindPossibleReferencesPlugin extends Plugin {
}
private void createActions() {
action = new ActionBuilder(SEARCH_DIRECT_REFS_ACTION_NAME, getName())
.menuPath(ToolConstants.MENU_SEARCH, "For Direct References")
.menuGroup("search for", "DirectReferences")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, SEARCH_DIRECT_REFS_ACTION_NAME))
.description(getPluginDescription().getDescription())
.withContext(ListingActionContext.class, true)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.onAction(this::findReferences)
.enabledWhen(this::hasCorrectAddressSize)
.buildAndInstall(tool);
new ActionBuilder(SEARCH_DIRECT_REFS_ACTION_NAME, getName())
.menuPath(ToolConstants.MENU_SEARCH, "For Direct References")
.menuGroup("search for", "DirectReferences")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, SEARCH_DIRECT_REFS_ACTION_NAME))
.description(getPluginDescription().getDescription())
.withContext(ListingActionContext.class, true)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.onAction(this::findReferences)
.enabledWhen(this::hasCorrectAddressSize)
.buildAndInstall(tool);
}
private boolean hasCorrectAddressSize(NavigatableActionContext context) {
int size =
context.getProgram().getAddressFactory().getDefaultAddressSpace().getSize();
int size = context.getProgram().getAddressFactory().getDefaultAddressSpace().getSize();
if ((size == 64) || (size == 32) || (size == 24) || (size == 16) || (size == 20) ||
(size == 21)) {
return true;
@ -142,10 +140,10 @@ public class FindPossibleReferencesPlugin extends Plugin {
localAction.setHelpLocation(
new HelpLocation(HelpTopics.SEARCH, RESTORE_SELECTION_ACTION_NAME));
String group = "selection";
localAction.setMenuBarData(
new MenuData(new String[] { "Restore Search Selection" }, group));
localAction.setPopupMenuData(
new MenuData(new String[] { "Restore Search Selection" }, group));
localAction
.setMenuBarData(new MenuData(new String[] { "Restore Search Selection" }, group));
localAction
.setPopupMenuData(new MenuData(new String[] { "Restore Search Selection" }, group));
localAction.setDescription(
"Sets the program selection back to the selection this search was based upon.");
@ -194,9 +192,8 @@ public class FindPossibleReferencesPlugin extends Plugin {
return;
}
if (currentProgram.getMemory()
.getBlock(
fromAddr)
.getType() == MemoryBlockType.BIT_MAPPED) {
.getBlock(fromAddr)
.getType() == MemoryBlockType.BIT_MAPPED) {
Msg.showWarn(getClass(), null, "Search For Direct References",
"Cannot search for direct references on bit memory!");
return;

View File

@ -45,6 +45,8 @@ import ghidra.util.HelpLocation;
import ghidra.util.table.*;
import ghidra.util.table.actions.DeleteTableRowAction;
import ghidra.util.table.actions.MakeProgramSelectionAction;
import utility.function.Callback;
import utility.function.Dummy;
public class TableComponentProvider<T> extends ComponentProviderAdapter
implements TableModelListener, NavigatableRemovalListener {
@ -60,11 +62,13 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
private String programName;
private String windowSubMenu;
private List<ComponentProviderActivationListener> activationListenerList = new ArrayList<>();
private Callback closedCallback = Dummy.callback();
private Navigatable navigatable;
private SelectionNavigationAction selectionNavigationAction;
private DockingAction selectAction;
private DockingAction removeItemsAction;
private DockingAction externalGotoAction;
private Function<MouseEvent, ActionContext> contextProvider = null;
@ -170,7 +174,7 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
selectionNavigationAction
.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation"));
DockingAction externalGotoAction = new DockingAction("Go to External Location", getName()) {
externalGotoAction = new DockingAction("Go to External Location", getName()) {
@Override
public void actionPerformed(ActionContext context) {
gotoExternalAddress(getSelectedExternalAddress());
@ -203,9 +207,21 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
new MenuData(new String[] { "GoTo External Location" }, icon, null));
externalGotoAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Navigation"));
plugin.getTool().addLocalAction(this, selectAction);
plugin.getTool().addLocalAction(this, selectionNavigationAction);
plugin.getTool().addLocalAction(this, externalGotoAction);
tool.addLocalAction(this, selectAction);
tool.addLocalAction(this, selectionNavigationAction);
tool.addLocalAction(this, externalGotoAction);
}
public void removeAllActions() {
tool.removeLocalAction(this, externalGotoAction);
tool.removeLocalAction(this, selectAction);
tool.removeLocalAction(this, selectionNavigationAction);
// this action is conditionally added
if (removeItemsAction != null) {
tool.removeAction(removeItemsAction);
removeItemsAction = null;
}
}
public void installRemoveItemsAction() {
@ -295,6 +311,8 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
@Override
public void closeComponent() {
this.closedCallback.call();
if (navigatable != null) {
navigatable.removeNavigatableListener(this);
}
@ -419,4 +437,12 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
public void setActionContextProvider(Function<MouseEvent, ActionContext> contextProvider) {
this.contextProvider = contextProvider;
}
/**
* Sets a listener to know when this provider is closed.
* @param c the callback
*/
public void setClosedCallback(Callback c) {
this.closedCallback = Dummy.ifNull(c);
}
}

View File

@ -0,0 +1,46 @@
/* ###
* 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.bin.format.omf;
import java.io.IOException;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.DataType;
import ghidra.util.exception.DuplicateNameException;
public class Omf2or4 implements StructConverter {
private int length;
private long value;
public Omf2or4(int length, long value) {
this.length = length;
this.value = value;
}
public int length() {
return length;
}
public long value() {
return value;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
return length == 2 ? WORD : DWORD;
}
}

View File

@ -32,9 +32,9 @@ public class OmfComdatExternalSymbol extends OmfExternalSymbol {
long max = reader.getPointerIndex() + getRecordLength() - 1;
while (reader.getPointerIndex() < max) {
int nameIndex = OmfRecord.readIndex(reader);
int type = OmfRecord.readIndex(reader);
externalLookups.add(new ExternalLookup(nameIndex, type));
OmfIndex nameIndex = OmfRecord.readIndex(reader);
OmfIndex type = OmfRecord.readIndex(reader);
externalLookups.add(new ExternalLookup(nameIndex.value(), type.value()));
}
readCheckSumByte(reader);

View File

@ -27,8 +27,8 @@ public class OmfComdefRecord extends OmfExternalSymbol {
long max = reader.getPointerIndex() + getRecordLength() - 1;
while (reader.getPointerIndex() < max) {
String name = OmfRecord.readString(reader);
int typeIndex = OmfRecord.readIndex(reader);
OmfString name = OmfRecord.readString(reader);
OmfIndex typeIndex = OmfRecord.readIndex(reader);
byte dataType = reader.readNextByte();
int byteLength = 0;
if (dataType == 0x61) { // FAR data, reads numElements and elSize
@ -40,7 +40,7 @@ public class OmfComdefRecord extends OmfExternalSymbol {
// Values 1 thru 5f plus 61, read the byte length
byteLength = readCommunalLength(reader);
}
symbols.add(new OmfSymbol(name, typeIndex, 0, dataType, byteLength));
symbols.add(new OmfSymbol(name.str(), typeIndex.value(), 0, dataType, byteLength));
}
readCheckSumByte(reader);
}

View File

@ -19,6 +19,8 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import ghidra.app.util.bin.BinaryReader;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class OmfCommentRecord extends OmfRecord {
// Language translator comment
@ -34,7 +36,7 @@ public class OmfCommentRecord extends OmfRecord {
private byte commentType;
private byte commentClass;
private String value;
private OmfString value;
public OmfCommentRecord(BinaryReader reader) throws IOException {
readRecordHeader(reader);
@ -46,7 +48,7 @@ public class OmfCommentRecord extends OmfRecord {
case COMMENT_CLASS_DEFAULT_LIBRARY:
byte[] bytes = reader.readNextByteArray(getRecordLength() -
3 /* 3 = sizeof(commentType+commentClass+trailing_crcbyte*/);
value = new String(bytes, StandardCharsets.US_ASCII); // assuming ASCII
value = new OmfString(bytes.length, new String(bytes, StandardCharsets.US_ASCII)); // assuming ASCII
break;
case COMMENT_CLASS_LIBMOD:
value = readString(reader);
@ -67,6 +69,24 @@ public class OmfCommentRecord extends OmfRecord {
}
public String getValue() {
return value;
return value.str();
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
int strlen = getRecordLength() - 3;
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
struct.add(BYTE, "type", null);
struct.add(WORD, "length", "");
struct.add(BYTE, "comment_type", null);
struct.add(BYTE, "comment_class", null);
if (strlen > 0) {
struct.add(new StringDataType(), strlen, "str", null);
}
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
return struct;
}
}

View File

@ -23,21 +23,21 @@ import ghidra.app.util.bin.BinaryReader;
* Object representing data loaded directly into the final image.
*/
public abstract class OmfData extends OmfRecord implements Comparable<OmfData> {
protected int segmentIndex;
protected long dataOffset;
protected OmfIndex segmentIndex;
protected Omf2or4 dataOffset;
/**
* @return get the segments index for this datablock
*/
public int getSegmentIndex() {
return segmentIndex;
return segmentIndex.value();
}
/**
* @return the starting offset, within the loaded image, of this data
*/
public long getDataOffset() {
return dataOffset;
return dataOffset.value();
}
/**
@ -47,7 +47,7 @@ public abstract class OmfData extends OmfRecord implements Comparable<OmfData> {
*/
@Override
public int compareTo(OmfData o) {
return Long.compare(dataOffset, o.dataOffset);
return Long.compare(dataOffset.value(), o.dataOffset.value());
}
/**

View File

@ -27,7 +27,7 @@ public class OmfEnumeratedData extends OmfData {
readRecordHeader(reader);
long start = reader.getPointerIndex();
segmentIndex = OmfRecord.readIndex(reader);
dataOffset = OmfRecord.readInt2Or4(reader, hasBigFields()) & 0xffffffffL;
dataOffset = OmfRecord.readInt2Or4(reader, hasBigFields());
streamOffset = reader.getPointerIndex();
streamLength = getRecordLength() - 1 - (int) (streamOffset - start);
reader.setPointerIndex(streamOffset + streamLength); // Skip over the data when reading header

View File

@ -20,11 +20,17 @@ import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class OmfExternalSymbol extends OmfRecord {
private boolean isStatic;
protected List<OmfSymbol> symbols = new ArrayList<>();
private record Reference(OmfString name, OmfIndex type) {}
private List<Reference> refs = new ArrayList<>();
protected OmfExternalSymbol(boolean isStatic) {
this.isStatic = isStatic;
@ -36,9 +42,10 @@ public class OmfExternalSymbol extends OmfRecord {
long max = reader.getPointerIndex() + getRecordLength() - 1;
while (reader.getPointerIndex() < max) {
String name = OmfRecord.readString(reader);
int type = OmfRecord.readIndex(reader);
symbols.add(new OmfSymbol(name, type, 0, 0, 0));
OmfString name = OmfRecord.readString(reader);
OmfIndex type = OmfRecord.readIndex(reader);
refs.add(new Reference(name, type));
symbols.add(new OmfSymbol(name.str(), type.value(), 0, 0, 0));
}
readCheckSumByte(reader);
@ -51,4 +58,19 @@ public class OmfExternalSymbol extends OmfRecord {
public boolean isStatic() {
return isStatic;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
struct.add(BYTE, "type", null);
struct.add(WORD, "length", null);
for (Reference ref : refs) {
struct.add(ref.name.toDataType(), "name", null);
struct.add(ref.type.toDataType(), "type", null);
}
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
return struct;
}
}

View File

@ -17,41 +17,52 @@ package ghidra.app.util.bin.format.omf;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
public class OmfFileHeader extends OmfRecord {
private String objectName; // Name of the object module
private String libModuleName = null; // Name of the module (within a library)
private String translator = null; // Usually the compiler/linker used to produce this object
private OmfString objectName; // Name of the object module
private String libModuleName = null; // Name of the module (within a library)
private String translator = null; // Usually the compiler/linker used to produce this object
private boolean isLittleEndian;
private ArrayList<String> nameList = new ArrayList<String>(); // Indexable List of segment, group, ... names
private ArrayList<OmfSegmentHeader> segments = new ArrayList<OmfSegmentHeader>();
private ArrayList<OmfGroupRecord> groups = new ArrayList<OmfGroupRecord>();
private ArrayList<OmfExternalSymbol> externsymbols = new ArrayList<OmfExternalSymbol>();
private ArrayList<OmfSymbolRecord> symbols = new ArrayList<OmfSymbolRecord>();
private ArrayList<OmfFixupRecord> fixup = new ArrayList<OmfFixupRecord>();
private ArrayList<OmfSegmentHeader> extraSeg = null; // Holds implied segments that don't have official header record
private List<OmfRecord> records = new ArrayList<>();
private List<String> nameList = new ArrayList<>(); // Indexable List of segment, group, ... names
private List<OmfSegmentHeader> segments = new ArrayList<>();
private List<OmfGroupRecord> groups = new ArrayList<>();
private List<OmfExternalSymbol> externsymbols = new ArrayList<>();
private List<OmfSymbolRecord> symbols = new ArrayList<>();
private List<OmfFixupRecord> fixup = new ArrayList<>();
private List<OmfSegmentHeader> extraSeg = null; // Holds implied segments that don't have official header record
// private OmfModuleEnd endModule = null;
private boolean format16bit;
public OmfFileHeader(BinaryReader reader) throws IOException {
readRecordHeader(reader);
objectName = readString(reader); // This is usually the source code filename
objectName = readString(reader); // This is usually the source code filename
readCheckSumByte(reader);
isLittleEndian = reader.isLittleEndian();
}
/**
* {@return the list of records}
*/
public List<OmfRecord> getRecords() {
return records;
}
/**
* This is usually the original source filename
* @return the name
*/
public String getName() {
return objectName;
return objectName.str();
}
/**
@ -88,42 +99,42 @@ public class OmfFileHeader extends OmfRecord {
/**
* @return the list of segments in this file
*/
public ArrayList<OmfSegmentHeader> getSegments() {
public List<OmfSegmentHeader> getSegments() {
return segments;
}
/**
* @return the list of segments which are Borland extensions
*/
public ArrayList<OmfSegmentHeader> getExtraSegments() {
public List<OmfSegmentHeader> getExtraSegments() {
return extraSeg;
}
/**
* @return the list of group records for this file
*/
public ArrayList<OmfGroupRecord> getGroups() {
public List<OmfGroupRecord> getGroups() {
return groups;
}
/**
* @return the list of symbols that are external to this file
*/
public ArrayList<OmfExternalSymbol> getExternalSymbols() {
public List<OmfExternalSymbol> getExternalSymbols() {
return externsymbols;
}
/**
* @return the list of symbols exported by this file
*/
public ArrayList<OmfSymbolRecord> getPublicSymbols() {
public List<OmfSymbolRecord> getPublicSymbols() {
return symbols;
}
/**
* @return the list of relocation records for this file
*/
public ArrayList<OmfFixupRecord> getFixups() {
public List<OmfFixupRecord> getFixups() {
return fixup;
}
@ -313,14 +324,15 @@ public class OmfFileHeader extends OmfRecord {
public static OmfFileHeader parse(BinaryReader reader, TaskMonitor monitor, MessageLog log)
throws IOException, OmfException {
OmfRecord record = OmfRecord.readRecord(reader);
if (!(record instanceof OmfFileHeader)) {
if (!(record instanceof OmfFileHeader header)) {
throw new OmfException("Object file does not start with proper header");
}
OmfFileHeader header = (OmfFileHeader) record;
header.records.add(header);
OmfData lastDataBlock = null;
while (true) {
record = OmfRecord.readRecord(reader);
header.records.add(record);
if (monitor.isCancelled()) {
break;
@ -403,8 +415,8 @@ public class OmfFileHeader extends OmfRecord {
* @param groups is the list of specific segments that are grouped together in memory
* @throws OmfException for malformed index/alignment/combining fields
*/
public static void doLinking(long startAddress, ArrayList<OmfSegmentHeader> segments,
ArrayList<OmfGroupRecord> groups) throws OmfException {
public static void doLinking(long startAddress, List<OmfSegmentHeader> segments,
List<OmfGroupRecord> groups) throws OmfException {
// Link anything in groups first
for (int i = 0; i < groups.size(); ++i) {
OmfGroupRecord group = groups.get(i);
@ -489,4 +501,16 @@ public class OmfFileHeader extends OmfRecord {
private static void logRecord(String description, OmfRecord record, MessageLog log) {
log.appendMsg(description + " (" + record + ")");
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
struct.add(BYTE, "type", null);
struct.add(WORD, "length", null);
struct.add(objectName.toDataType(), "name", null);
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
return struct;
}
}

View File

@ -67,10 +67,10 @@ public class OmfFixupRecord extends OmfRecord {
private byte first;
private byte hiFixup;
private byte fixData;
private int index;
private int frameDatum;
private int targetDatum;
private int targetDisplacement;
private OmfIndex index;
private OmfIndex frameDatum;
private OmfIndex targetDatum;
private Omf2or4 targetDisplacement;
/**
* Read the next subrecord from the input reader
@ -85,7 +85,7 @@ public class OmfFixupRecord extends OmfRecord {
int method;
final var rec = new Subrecord();
rec.first = reader.readNextByte();
rec.index = -1;
rec.index = new OmfIndex(1, -1);
if (rec.isThreadSubrecord()) {
method = rec.getThreadMethod();
if (method < 4) {
@ -93,8 +93,8 @@ public class OmfFixupRecord extends OmfRecord {
}
return rec;
}
rec.targetDisplacement = 0;
rec.targetDatum = 0;
rec.targetDisplacement = new Omf2or4(2, 0);
rec.targetDatum = new OmfIndex(1, 0);
rec.hiFixup = reader.readNextByte();
rec.fixData = reader.readNextByte();
method = rec.getFrameMethod();
@ -135,7 +135,7 @@ public class OmfFixupRecord extends OmfRecord {
* @return Get the index for explicit thread or frame
*/
public int getIndex() {
return index;
return index.value();
}
/**
@ -170,11 +170,11 @@ public class OmfFixupRecord extends OmfRecord {
}
public int getTargetDatum() {
return targetDatum;
return targetDatum.value();
}
public int getTargetDisplacement() {
return targetDisplacement;
return (int) targetDisplacement.value();
}
public int getLocationType() {

View File

@ -17,6 +17,7 @@ package ghidra.app.util.bin.format.omf;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.program.model.address.Address;
@ -24,7 +25,7 @@ import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
public class OmfGroupRecord extends OmfRecord {
private int groupNameIndex;
private OmfIndex groupNameIndex;
private String groupName;
private long vma = -1; // Assigned (by linker) starting address of the whole group
private GroupSubrecord[] group;
@ -72,7 +73,7 @@ public class OmfGroupRecord extends OmfRecord {
}
public int getSegmentIndex(int i) {
return group[i].segmentIndex;
return group[i].segmentIndex.value();
}
public Address getAddress(Language language) {
@ -80,19 +81,19 @@ public class OmfGroupRecord extends OmfRecord {
return addrSpace.getAddress(vma);
}
public void resolveNames(ArrayList<String> nameList) throws OmfException {
if (groupNameIndex <= 0) {
public void resolveNames(List<String> nameList) throws OmfException {
if (groupNameIndex.value() <= 0) {
throw new OmfException("Cannot have unused group name");
}
if (groupNameIndex > nameList.size()) {
if (groupNameIndex.value() > nameList.size()) {
throw new OmfException("Group name index out of bounds");
}
groupName = nameList.get(groupNameIndex - 1);
groupName = nameList.get(groupNameIndex.value() - 1);
}
public static class GroupSubrecord {
private byte componentType;
private int segmentIndex;
private OmfIndex segmentIndex;
public static GroupSubrecord read(BinaryReader reader) throws IOException {
GroupSubrecord subrec = new GroupSubrecord();

View File

@ -0,0 +1,46 @@
/* ###
* 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.bin.format.omf;
import java.io.IOException;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.DataType;
import ghidra.util.exception.DuplicateNameException;
public class OmfIndex implements StructConverter {
private int length;
private int value;
public OmfIndex(int length, int value) {
this.length = length;
this.value = value;
}
public int length() {
return length;
}
public int value() {
return value;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
return length == 2 ? WORD : BYTE;
}
}

View File

@ -78,7 +78,7 @@ public class OmfIteratedData extends OmfData {
* Contain the definition of one part of a datablock with possible recursion
*/
public static class DataBlock {
private int repeatCount;
private Omf2or4 repeatCount;
private int blockCount;
private byte[] simpleBlock = null;
private DataBlock[] nestedBlock = null;
@ -86,7 +86,7 @@ public class OmfIteratedData extends OmfData {
public static DataBlock read(BinaryReader reader, boolean hasBigFields) throws IOException {
DataBlock subblock = new DataBlock();
subblock.repeatCount = OmfRecord.readInt2Or4(reader, hasBigFields);
subblock.blockCount = reader.readNextShort() & 0xffff;
subblock.blockCount = reader.readNextUnsignedShort();
if (subblock.blockCount == 0) {
int size = reader.readNextByte() & 0xff;
subblock.simpleBlock = new byte[size];
@ -110,7 +110,7 @@ public class OmfIteratedData extends OmfData {
* @return The position after the block
*/
public int fillBuffer(byte[] buffer, int pos) {
for (int i = 0; i < repeatCount; ++i) {
for (int i = 0; i < (int) repeatCount.value(); ++i) {
if (simpleBlock != null) {
for (byte element : simpleBlock) {
buffer[pos] = element;
@ -139,7 +139,7 @@ public class OmfIteratedData extends OmfData {
length += block.getLength();
}
}
return length * repeatCount;
return length * (int) repeatCount.value();
}
/**

View File

@ -17,24 +17,39 @@ package ghidra.app.util.bin.format.omf;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class OmfNamesRecord extends OmfRecord {
private ArrayList<String> name;
private List<OmfString> names = new ArrayList<>();
public OmfNamesRecord(BinaryReader reader) throws IOException {
readRecordHeader(reader);
long max = reader.getPointerIndex() + getRecordLength() - 1;
name = new ArrayList<String>();
while (reader.getPointerIndex() < max) {
String nm = OmfRecord.readString(reader);
name.add(nm);
names.add(OmfRecord.readString(reader));
}
readCheckSumByte(reader);
}
public void appendNames(ArrayList<String> namelist) {
namelist.addAll(name);
public void appendNames(List<String> namelist) {
namelist.addAll(names.stream().map(name -> name.str()).toList());
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
struct.add(BYTE, "type", null);
struct.add(WORD, "length", null);
for (OmfString name : names) {
struct.add(name.toDataType(), -1, "name", null);
}
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
return struct;
}
}

View File

@ -20,8 +20,11 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public abstract class OmfRecord {
public abstract class OmfRecord implements StructConverter {
public final static byte RHEADR = (byte) 0x6E; // Obsolete
public final static byte REGINT = (byte) 0x70; // Obsolete
public final static byte REDATA = (byte) 0x72; // Obsolete
@ -68,6 +71,8 @@ public abstract class OmfRecord {
public final static byte START = (byte) 0xF0;
public final static byte END = (byte) 0xF1;
protected static final String CATEGORY_PATH = "/OMF";
protected byte recordType;
protected int recordLength;
protected long recordOffset;
@ -120,22 +125,26 @@ public abstract class OmfRecord {
return (reader.readNextByte() & 0xff);
}
public static int readInt2Or4(BinaryReader reader, boolean isBig) throws IOException {
if (isBig)
return reader.readNextInt();
return (reader.readNextShort() & 0xffff);
public static Omf2or4 readInt2Or4(BinaryReader reader, boolean isBig) throws IOException {
if (isBig) {
return new Omf2or4(4, reader.readNextInt());
}
return new Omf2or4(2, reader.readNextUnsignedShort());
}
public static int readIndex(BinaryReader reader) throws IOException {
public static OmfIndex readIndex(BinaryReader reader) throws IOException {
int length;
int indexWord;
byte firstByte = reader.readNextByte();
if ((firstByte & 0x80) != 0) {
indexWord = (firstByte & 0x7f) * 0x100 + (reader.readNextByte() & 0xff);
length = 2;
}
else {
indexWord = firstByte;
length = 1;
}
return indexWord;
return new OmfIndex(length, indexWord);
}
public static OmfRecord readRecord(BinaryReader reader) throws IOException, OmfException {
@ -215,9 +224,9 @@ public abstract class OmfRecord {
* @return the read OMF string
* @throws IOException if an IO-related error occurred
*/
public static String readString(BinaryReader reader) throws IOException {
public static OmfString readString(BinaryReader reader) throws IOException {
int count = reader.readNextByte() & 0xff;
return reader.readNextAsciiString(count);
return new OmfString(count, reader.readNextAsciiString(count));
}
/**
@ -249,4 +258,16 @@ public abstract class OmfRecord {
return String.format("name: %s, type: 0x%x, offset: 0x%x, length: 0x%x",
getRecordName(recordType & (byte) 0xfe), recordType, recordOffset, recordLength);
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
struct.add(BYTE, "type", null);
struct.add(WORD, "length", null);
struct.add(new ArrayDataType(BYTE, getRecordLength() - 1, 1), "contents", null);
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(CATEGORY_PATH));
return struct;
}
}

View File

@ -17,24 +17,25 @@ package ghidra.app.util.bin.format.omf;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.*;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.OmfLoader;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Language;
import ghidra.util.exception.DuplicateNameException;
public class OmfSegmentHeader extends OmfRecord {
private byte segAttr; // first byte of Segment Attributes
private int frameNumber;
private int offset;
private long segmentLength;
private int segmentNameIndex;
private int classNameIndex;
private int overlayNameIndex;
private Omf2or4 segmentLength;
private OmfIndex segmentNameIndex;
private OmfIndex classNameIndex;
private OmfIndex overlayNameIndex;
private String segmentName;
private String className;
private String overlayName;
@ -48,10 +49,10 @@ public class OmfSegmentHeader extends OmfRecord {
OmfSegmentHeader(int num, int datatype) {
// generate a special Borland header
segAttr = (byte) 0xa9;
segmentLength = 0;
segmentNameIndex = 0;
classNameIndex = 0;
overlayNameIndex = 0;
segmentLength = new Omf2or4(2,0);
segmentNameIndex = new OmfIndex(1, 0);
classNameIndex = new OmfIndex(1, 0);
overlayNameIndex = new OmfIndex(1, 0);
overlayName = "";
if (datatype == 1) {
segmentName = "EXTRATEXT_";
@ -90,7 +91,7 @@ public class OmfSegmentHeader extends OmfRecord {
offset = reader.readNextByte() & 0xff;
vma = (long) frameNumber + offset;
}
segmentLength = OmfRecord.readInt2Or4(reader, hasBigFields) & 0xffffffffL;
segmentLength = OmfRecord.readInt2Or4(reader, hasBigFields);
segmentNameIndex = OmfRecord.readIndex(reader);
classNameIndex = OmfRecord.readIndex(reader);
overlayNameIndex = OmfRecord.readIndex(reader);
@ -98,10 +99,10 @@ public class OmfSegmentHeader extends OmfRecord {
int B = (segAttr >> 1) & 1;
if (B == 1) { // Ignore the segmentLength field
if (getRecordType() == OmfRecord.SEGDEF) {
segmentLength = 0x10000L; // Exactly 64K segment
segmentLength = new Omf2or4(segmentLength.length(), 0x10000L); // Exactly 64K segment
}
else {
segmentLength = 0x100000000L; // Exactly 4G segment
segmentLength = new Omf2or4(segmentLength.length(), 0x100000000L); // Exactly 4G segment
}
}
}
@ -189,7 +190,7 @@ public class OmfSegmentHeader extends OmfRecord {
* @return the length of the segment in bytes
*/
public long getSegmentLength() {
return segmentLength;
return segmentLength.value();
}
/**
@ -277,7 +278,7 @@ public class OmfSegmentHeader extends OmfRecord {
throw new OmfException("Unsupported alignment type");
}
vma = firstValidAddress;
firstValidAddress = vma + segmentLength;
firstValidAddress = vma + segmentLength.value();
return firstValidAddress;
}
@ -288,33 +289,33 @@ public class OmfSegmentHeader extends OmfRecord {
* @param nameList is the array of names associated with the file
* @throws OmfException for improper name indices
*/
protected void resolveNames(ArrayList<String> nameList) throws OmfException {
if (segmentNameIndex == 0) {
protected void resolveNames(List<String> nameList) throws OmfException {
if (segmentNameIndex.value() == 0) {
segmentName = ""; // Name is unused
}
else {
if (segmentNameIndex > nameList.size()) {
if (segmentNameIndex.value() > nameList.size()) {
throw new OmfException("Segment name index out of bounds");
}
segmentName = nameList.get(segmentNameIndex - 1);
segmentName = nameList.get(segmentNameIndex.value() - 1);
}
if (classNameIndex == 0) {
if (classNameIndex.value() == 0) {
className = "";
}
else {
if (classNameIndex > nameList.size()) {
if (classNameIndex.value() > nameList.size()) {
throw new OmfException("Class name index out of bounds");
}
className = nameList.get(classNameIndex - 1);
className = nameList.get(classNameIndex.value() - 1);
}
if (overlayNameIndex == 0) {
if (overlayNameIndex.value() == 0) {
overlayName = "";
}
else {
if (overlayNameIndex > nameList.size()) {
if (overlayNameIndex.value() > nameList.size()) {
throw new OmfException("Overlay name index out of bounds");
}
overlayName = nameList.get(overlayNameIndex - 1);
overlayName = nameList.get(overlayNameIndex.value() - 1);
}
// Once we know the class name, we can make some educated guesses about read/write/exec permissions
@ -347,8 +348,8 @@ public class OmfSegmentHeader extends OmfRecord {
*/
protected void appendEnumeratedData(OmfEnumeratedData rec) {
long blockend = rec.getDataOffset() + rec.getLength();
if (blockend > segmentLength) {
segmentLength = blockend;
if (blockend > segmentLength.value()) {
segmentLength = new Omf2or4(segmentLength.length(), blockend);
}
dataBlocks.add(rec);
}
@ -380,7 +381,7 @@ public class OmfSegmentHeader extends OmfRecord {
this.log = log;
pointer = 0;
dataUpNext = 0;
if (pointer < segmentLength) {
if (pointer < segmentLength.value()) {
establishNextBuffer();
}
}
@ -423,7 +424,7 @@ public class OmfSegmentHeader extends OmfRecord {
}
}
// We may have filler after the last block
long size = segmentLength - pointer;
long size = segmentLength.value() - pointer;
if (size > OmfLoader.MAX_UNINITIALIZED_FILL) {
throw new IOException("Large hole at the end of OMF segment: " + segmentName);
}
@ -436,7 +437,7 @@ public class OmfSegmentHeader extends OmfRecord {
@Override
public int read() throws IOException {
if (pointer < segmentLength) {
if (pointer < segmentLength.value()) {
if (bufferpointer < buffer.length) {
pointer++;
return buffer[bufferpointer++] & 0xff;
@ -454,4 +455,25 @@ public class OmfSegmentHeader extends OmfRecord {
}
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
struct.add(BYTE, "type", null);
struct.add(WORD, "length", null);
struct.add(BYTE, "segment_attr", null);
int A = (segAttr >> 5) & 7;
if (A == 0) {
struct.add(WORD, "frame_number", null);
struct.add(BYTE, "offset", null);
}
struct.add(segmentLength.toDataType(), "segment_length", null);
struct.add(segmentNameIndex.toDataType(), "segment_name_index", null);
struct.add(classNameIndex.toDataType(), "class_name_index", null);
struct.add(overlayNameIndex.toDataType(), "overlay_name_index", null);
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
return struct;
}
}

View File

@ -0,0 +1,54 @@
/* ###
* 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.bin.format.omf;
import java.io.IOException;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class OmfString implements StructConverter {
private int length;
private String str;
public OmfString(int length, String str) {
this.length = length;
this.str = str;
}
public int length() {
return length;
}
public String str() {
return str;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
if (length == 0) {
return BYTE;
}
StructureDataType struct = new StructureDataType("OmfString", 0);
struct.add(BYTE, "length", "");
struct.add(new StringDataType(), length, "str", null);
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
return struct;
}
}

View File

@ -20,13 +20,18 @@ import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class OmfSymbolRecord extends OmfRecord {
private int baseGroupIndex;
private int baseSegmentIndex;
private OmfIndex baseGroupIndex;
private OmfIndex baseSegmentIndex;
private int baseFrame;
private boolean isStatic;
private OmfSymbol[] symbol;
private List<Reference> refs = new ArrayList<>();
private record Reference(OmfString name, Omf2or4 offset, OmfIndex type) {}
public OmfSymbolRecord(BinaryReader reader, boolean isStatic) throws IOException {
this.isStatic = isStatic;
@ -35,17 +40,18 @@ public class OmfSymbolRecord extends OmfRecord {
boolean hasBigFields = hasBigFields();
baseGroupIndex = OmfRecord.readIndex(reader);
baseSegmentIndex = OmfRecord.readIndex(reader);
if (baseSegmentIndex == 0) {
baseFrame = reader.readNextShort() & 0xffff;
if (baseSegmentIndex.value() == 0) {
baseFrame = reader.readNextUnsignedShort();
}
ArrayList<OmfSymbol> symbollist = new ArrayList<OmfSymbol>();
while (reader.getPointerIndex() < max) {
String name = OmfRecord.readString(reader);
long offset = OmfRecord.readInt2Or4(reader, hasBigFields) & 0xffffffffL;
int type = OmfRecord.readIndex(reader);
OmfSymbol subrec = new OmfSymbol(name, type, offset, 0, 0);
OmfString name = OmfRecord.readString(reader);
Omf2or4 offset = OmfRecord.readInt2Or4(reader, hasBigFields);
OmfIndex type = OmfRecord.readIndex(reader);
OmfSymbol subrec = new OmfSymbol(name.str(), type.value(), offset.value(), 0, 0);
symbollist.add(subrec);
refs.add(new Reference(name, offset, type));
}
readCheckSumByte(reader);
symbol = new OmfSymbol[symbollist.size()];
@ -57,11 +63,11 @@ public class OmfSymbolRecord extends OmfRecord {
}
public int getGroupIndex() {
return baseGroupIndex;
return baseGroupIndex.value();
}
public int getSegmentIndex() {
return baseSegmentIndex;
return baseSegmentIndex.value();
}
public int numSymbols() {
@ -76,4 +82,29 @@ public class OmfSymbolRecord extends OmfRecord {
return List.of(symbol);
}
public int getBaseFrame() {
return baseFrame;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
struct.add(BYTE, "type", null);
struct.add(WORD, "length", null);
struct.add(baseGroupIndex.toDataType(), "base_group_index", null);
struct.add(baseSegmentIndex.toDataType(), "base_segment_index", null);
if (baseSegmentIndex.value() == 0) {
struct.add(WORD, "base_frame", null);
}
for (Reference ref : refs) {
struct.add(ref.name.toDataType(), "name", null);
struct.add(ref.offset.toDataType(), "offset", null);
struct.add(ref.type.toDataType(), "type", null);
}
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
return struct;
}
}

View File

@ -17,9 +17,9 @@ package ghidra.app.util.headless;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.*;
import java.util.*;
import java.util.stream.Collectors;
import generic.stl.Pair;
import ghidra.*;
@ -37,7 +37,76 @@ import ghidra.util.exception.InvalidInputException;
*/
public class AnalyzeHeadless implements GhidraLaunchable {
/**
* Headless command line arguments.
* <p>
* NOTE: Please update 'analyzeHeadlessREADME.html' if changing command line parameters
*/
private enum Arg {
//@formatter:off
IMPORT("-import", true, "[<directory>|<file>]+"),
PROCESS("-process", true, "[<project_file>]"),
PRE_SCRIPT("-prescript", true, "<ScriptName>"),
POST_SCRIPT("-postscript", true, "<ScriptName>"),
SCRIPT_PATH("-scriptPath", true, "\"<path1>[;<path2>...]\""),
PROPERTIES_PATH("-propertiesPath", true, "\"<path1>[;<path2>...]\""),
SCRIPT_LOG("-scriptlog", true, "<path to script log file>"),
LOG("-log", true, "<path to log file>"),
OVERWRITE("-overwrite", false),
RECURSIVE("-recursive", false),
READ_ONLY("-readOnly", false),
DELETE_PROJECT("-deleteproject", false),
NO_ANALYSIS("-noanalysis", false),
PROCESSOR("-processor", true, "<languageID>"),
CSPEC("-cspec", true, "<compilerSpecID>"),
ANALYSIS_TIMEOUT_PER_FILE("-analysisTimeoutPerFile", true, "<timeout in seconds>"),
KEYSTORE("-keystore", true, "<KeystorePath>"),
CONNECT("-connect", false, "[<userID>]"),
PASSWORD("-p", false),
COMMIT("-commit", false, "[\"<comment>\"]]"),
OK_TO_DELETE("-okToDelete", false),
MAX_CPU("-max-cpu", true, "<max cpu cores to use>"),
LIBRARY_SEARCH_PATHS("-librarySearchPaths", true, "<path1>[;<path2>...]"),
LOADER("-loader", true, "<desired loader name>"),
LOADER_ARGS(Loader.COMMAND_LINE_ARG_PREFIX + "-", true, "<loader argument value>") {
@Override
public boolean matches(String arg) {
return arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX + "-");
}
};
//@formatter:on
private String name;
private boolean requiresSubArgs;
private String subArgFormat;
private Arg(String name, boolean requiresSubArgs, String subArgFormat) {
this.name = name;
this.requiresSubArgs = requiresSubArgs;
this.subArgFormat = subArgFormat;
}
private Arg(String name, boolean requiresSubArgs) {
this(name, requiresSubArgs, "");
}
public String usage() {
return "%s%s%s".formatted(name, subArgFormat.isEmpty() ? "" : " ", subArgFormat);
}
public boolean matches(String arg) {
return arg.equalsIgnoreCase(name);
}
@Override
public String toString() {
return name;
}
}
private static final int EXIT_CODE_ERROR = 1;
private static final Set<String> ALL_ARG_NAMES =
Arrays.stream(Arg.values()).map(a -> a.name).collect(Collectors.toSet());
/**
* The entry point of 'analyzeHeadless.bat'. Parses the command line arguments to the script
@ -64,7 +133,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
if (args[0].startsWith("ghidra:")) {
optionStartIndex = 1;
try {
ghidraURL = new URL(args[0]);
ghidraURL = new URI(args[0]).toURL();
}
catch (MalformedURLException e) {
System.err.println("Invalid Ghidra URL: " + args[0]);
@ -98,10 +167,10 @@ public class AnalyzeHeadless implements GhidraLaunchable {
File logFile = null;
File scriptLogFile = null;
for (int argi = optionStartIndex; argi < args.length; argi++) {
if (checkArgument("-log", args, argi)) {
if (checkArgument(Arg.LOG, args, argi)) {
logFile = new File(args[++argi]);
}
else if (checkArgument("-scriptlog", args, argi)) {
else if (checkArgument(Arg.SCRIPT_LOG, args, argi)) {
scriptLogFile = new File(args[++argi]);
}
}
@ -158,7 +227,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
String languageId = null;
String compilerSpecId = null;
String keystorePath = null;
String serverUID = null;
String userId = null;
boolean allowPasswordPrompt = false;
List<Pair<String, String[]>> preScripts = new LinkedList<>();
List<Pair<String, String[]>> postScripts = new LinkedList<>();
@ -166,57 +235,57 @@ public class AnalyzeHeadless implements GhidraLaunchable {
for (int argi = startIndex; argi < args.length; argi++) {
String arg = args[argi];
if (checkArgument("-log", args, argi)) {
if (checkArgument(Arg.LOG, args, argi)) {
// Already processed
argi++;
}
else if (checkArgument("-scriptlog", args, argi)) {
else if (checkArgument(Arg.SCRIPT_LOG, args, argi)) {
// Already processed
argi++;
}
else if (arg.equalsIgnoreCase("-overwrite")) {
else if (checkArgument(Arg.OVERWRITE, args, argi)) {
options.enableOverwriteOnConflict(true);
}
else if (arg.equalsIgnoreCase("-noanalysis")) {
else if (checkArgument(Arg.NO_ANALYSIS, args, argi)) {
options.enableAnalysis(false);
}
else if (arg.equalsIgnoreCase("-deleteproject")) {
else if (checkArgument(Arg.DELETE_PROJECT, args, argi)) {
options.setDeleteCreatedProjectOnClose(true);
}
else if (checkArgument("-loader", args, argi)) {
else if (checkArgument(Arg.LOADER, args, argi)) {
loaderName = args[++argi];
}
else if (arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX)) {
if (args[argi + 1].startsWith("-")) {
else if (checkArgument(Arg.LOADER_ARGS, args, argi)) {
if (ALL_ARG_NAMES.contains(args[argi + 1])) {
throw new InvalidInputException(args[argi] + " expects value to follow.");
}
loaderArgs.add(new Pair<>(arg, args[++argi]));
}
else if (checkArgument("-processor", args, argi)) {
else if (checkArgument(Arg.PROCESSOR, args, argi)) {
languageId = args[++argi];
}
else if (checkArgument("-cspec", args, argi)) {
else if (checkArgument(Arg.CSPEC, args, argi)) {
compilerSpecId = args[++argi];
}
else if (checkArgument("-prescript", args, argi)) {
else if (checkArgument(Arg.PRE_SCRIPT, args, argi)) {
String scriptName = args[++argi];
String[] scriptArgs = getSubArguments(args, argi);
String[] scriptArgs = getSubArguments(args, argi, ALL_ARG_NAMES);
argi += scriptArgs.length;
preScripts.add(new Pair<>(scriptName, scriptArgs));
}
else if (checkArgument("-postscript", args, argi)) {
else if (checkArgument(Arg.POST_SCRIPT, args, argi)) {
String scriptName = args[++argi];
String[] scriptArgs = getSubArguments(args, argi);
String[] scriptArgs = getSubArguments(args, argi, ALL_ARG_NAMES);
argi += scriptArgs.length;
postScripts.add(new Pair<>(scriptName, scriptArgs));
}
else if (checkArgument("-scriptPath", args, argi)) {
else if (checkArgument(Arg.SCRIPT_PATH, args, argi)) {
options.setScriptDirectories(args[++argi]);
}
else if (checkArgument("-propertiesPath", args, argi)) {
else if (checkArgument(Arg.PROPERTIES_PATH, args, argi)) {
options.setPropertiesFileDirectories(args[++argi]);
}
else if (checkArgument("-import", args, argi)) {
else if (checkArgument(Arg.IMPORT, args, argi)) {
File inputFile = null;
try {
inputFile = new File(args[++argi]);
@ -242,7 +311,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
nextArg = args[++argi];
// Check if next argument is a parameter
if (nextArg.charAt(0) == '-') {
if (ALL_ARG_NAMES.contains(nextArg)) {
argi--;
break;
}
@ -258,29 +327,29 @@ public class AnalyzeHeadless implements GhidraLaunchable {
filesToImport.add(otherFile);
}
}
else if ("-connect".equals(args[argi])) {
else if (checkArgument(Arg.CONNECT, args, argi)) {
if ((argi + 1) < args.length) {
arg = args[argi + 1];
if (!arg.startsWith("-")) {
if (!ALL_ARG_NAMES.contains(arg)) {
// serverUID is optional argument after -connect
serverUID = arg;
userId = arg;
++argi;
}
}
}
else if ("-commit".equals(args[argi])) {
else if (checkArgument(Arg.COMMIT, args, argi)) {
String comment = null;
if ((argi + 1) < args.length) {
arg = args[argi + 1];
if (!arg.startsWith("-")) {
// comment is optional argument after -commit
if (!ALL_ARG_NAMES.contains(arg)) {
// commit is optional argument after -commit
comment = arg;
++argi;
}
}
options.setCommitFiles(true, comment);
}
else if (checkArgument("-keystore", args, argi)) {
else if (checkArgument(Arg.KEYSTORE, args, argi)) {
keystorePath = args[++argi];
File keystore = new File(keystorePath);
if (!keystore.isFile()) {
@ -288,13 +357,13 @@ public class AnalyzeHeadless implements GhidraLaunchable {
keystore.getAbsolutePath() + " is not a valid keystore file.");
}
}
else if (arg.equalsIgnoreCase("-p")) {
else if (checkArgument(Arg.PASSWORD, args, argi)) {
allowPasswordPrompt = true;
}
else if ("-analysisTimeoutPerFile".equalsIgnoreCase(args[argi])) {
else if (checkArgument(Arg.ANALYSIS_TIMEOUT_PER_FILE, args, argi)) {
options.setPerFileAnalysisTimeout(args[++argi]);
}
else if ("-process".equals(args[argi])) {
else if (checkArgument(Arg.PROCESS, args, argi)) {
if (options.runScriptsNoImport) {
throw new InvalidInputException(
"The -process option may only be specified once.");
@ -302,7 +371,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
String processBinary = null;
if ((argi + 1) < args.length) {
arg = args[argi + 1];
if (!arg.startsWith("-")) {
if (!ALL_ARG_NAMES.contains(arg)) {
// processBinary is optional argument after -process
processBinary = arg;
++argi;
@ -310,11 +379,11 @@ public class AnalyzeHeadless implements GhidraLaunchable {
}
options.setRunScriptsNoImport(true, processBinary);
}
else if ("-recursive".equals(args[argi])) {
else if (checkArgument(Arg.RECURSIVE, args, argi)) {
Integer depth = null;
if ((argi + 1) < args.length) {
arg = args[argi + 1];
if (!arg.startsWith("-")) {
if (!ALL_ARG_NAMES.contains(arg)) {
// depth is optional argument after -recursive
try {
depth = Integer.parseInt(arg);
@ -327,10 +396,10 @@ public class AnalyzeHeadless implements GhidraLaunchable {
}
options.enableRecursiveProcessing(true, depth);
}
else if ("-readOnly".equalsIgnoreCase(args[argi])) {
else if (checkArgument(Arg.READ_ONLY, args, argi)) {
options.enableReadOnlyProcessing(true);
}
else if (checkArgument("-max-cpu", args, argi)) {
else if (checkArgument(Arg.MAX_CPU, args, argi)) {
String cpuVal = args[++argi];
try {
options.setMaxCpu(Integer.parseInt(cpuVal));
@ -339,12 +408,15 @@ public class AnalyzeHeadless implements GhidraLaunchable {
throw new InvalidInputException("Invalid value for max-cpu: " + cpuVal);
}
}
else if ("-okToDelete".equalsIgnoreCase(args[argi])) {
else if (checkArgument(Arg.OK_TO_DELETE, args, argi)) {
options.setOkToDelete(true);
}
else if (checkArgument("-librarySearchPaths", args, argi)) {
else if (checkArgument(Arg.LIBRARY_SEARCH_PATHS, args, argi)) {
LibrarySearchPathManager.setLibraryPaths(args[++argi].split(";"));
}
else if (ALL_ARG_NAMES.contains(args[argi])) {
throw new AssertionError("Valid option was not processed: " + args[argi]);
}
else {
throw new InvalidInputException("Bad argument: " + arg);
}
@ -362,7 +434,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
// Set up optional Ghidra Server authenticator
try {
options.setClientCredentials(serverUID, keystorePath, allowPasswordPrompt);
options.setClientCredentials(userId, keystorePath, allowPasswordPrompt);
}
catch (IOException e) {
throw new InvalidInputException(
@ -438,47 +510,48 @@ public class AnalyzeHeadless implements GhidraLaunchable {
* @param execCmd the command used to run the headless analyzer from the calling method.
*/
public static void usage(String execCmd) {
System.out.println("Headless Analyzer Usage: " + execCmd);
System.out.println(" <project_location> <project_name>[/<folder_path>]");
System.out.println(
" | ghidra://<server>[:<port>]/<repository_name>[/<folder_path>]");
System.out.println(
" [[-import [<directory>|<file>]+] | [-process [<project_file>]]]");
System.out.println(" [-preScript <ScriptName>]");
System.out.println(" [-postScript <ScriptName>]");
System.out.println(" [-scriptPath \"<path1>[;<path2>...]\"]");
System.out.println(" [-propertiesPath \"<path1>[;<path2>...]\"]");
System.out.println(" [-scriptlog <path to script log file>]");
System.out.println(" [-log <path to log file>]");
System.out.println(" [-overwrite]");
System.out.println(" [-recursive]");
System.out.println(" [-readOnly]");
System.out.println(" [-deleteProject]");
System.out.println(" [-noanalysis]");
System.out.println(" [-processor <languageID>]");
System.out.println(" [-cspec <compilerSpecID>]");
System.out.println(" [-analysisTimeoutPerFile <timeout in seconds>]");
System.out.println(" [-keystore <KeystorePath>]");
System.out.println(" [-connect <userID>]");
System.out.println(" [-p]");
System.out.println(" [-commit [\"<comment>\"]]");
System.out.println(" [-okToDelete]");
System.out.println(" [-max-cpu <max cpu cores to use>]");
System.out.println(" [-loader <desired loader name>]");
// ** NOTE: please update 'analyzeHeadlessREADME.html' if changing command line parameters **
StringBuilder sb = new StringBuilder();
final String INDENT = " ";
sb.append("Headless Analyzer Usage: %s\n".formatted(execCmd));
sb.append(INDENT + "<project_location> <project_name>[/<folder_path>]\n");
sb.append(INDENT + " | ghidra://<server>[:<port>]/<repository_name>[/<folder_path>]\n");
for (Arg arg : Arg.values()) {
switch (arg) {
case IMPORT -> {
// Can't use both IMPORT and PROCESS, so must handle the usage a little
// differently
sb.append(
INDENT + "[[%s] | [%s]]\n".formatted(arg.usage(), Arg.PROCESS.usage()));
}
case PROCESS -> {
// Handled above by IMPORT
}
case LOADER_ARGS -> {
// Loader args are a little different because we don't know the full
// argument name ahead of time...just what it starts with
sb.append(INDENT + "[%s<loader argument name> %s]\n"
.formatted(Arg.LOADER_ARGS.name, Arg.LOADER_ARGS.subArgFormat));
}
default -> {
sb.append(INDENT + "[%s]\n".formatted(arg.usage()));
}
}
}
if (Platform.CURRENT_PLATFORM.getOperatingSystem() != OperatingSystem.WINDOWS) {
System.out.println();
System.out.println(
sb.append("\n");
sb.append(
" - All uses of $GHIDRA_HOME or $USER_HOME in script path must be" +
" preceded by '\\'");
" preceded by '\\'\n");
}
System.out.println();
System.out.println(
sb.append("\n");
sb.append(
"Please refer to 'analyzeHeadlessREADME.html' for detailed usage examples " +
"and notes.");
"and notes.\n");
System.out.println();
sb.append("\n");
System.out.println(sb);
System.exit(EXIT_CODE_ERROR);
}
@ -486,23 +559,22 @@ public class AnalyzeHeadless implements GhidraLaunchable {
usage("analyzeHeadless");
}
private String[] getSubArguments(String[] args, int argi) {
List<String> subArgs = new LinkedList<>();
private String[] getSubArguments(String[] args, int argi, Set<String> argNames) {
List<String> subArgs = new ArrayList<>();
int i = argi + 1;
while (i < args.length && !args[i].startsWith("-")) {
while (i < args.length && !argNames.contains(args[i])) {
subArgs.add(args[i++]);
}
return subArgs.toArray(new String[0]);
return subArgs.toArray(new String[subArgs.size()]);
}
private boolean checkArgument(String optionName, String[] args, int argi)
private boolean checkArgument(Arg arg, String[] args, int argi)
throws InvalidInputException {
// everything after this requires an argument
if (!optionName.equalsIgnoreCase(args[argi])) {
if (!arg.matches(args[argi])) {
return false;
}
if (argi + 1 == args.length) {
throw new InvalidInputException(optionName + " requires an argument");
if (arg.requiresSubArgs && argi + 1 == args.length) {
throw new InvalidInputException(args[argi] + " requires an argument");
}
return true;
}

View File

@ -879,7 +879,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
}
monitor.initialize(totalCount);
ElfRelocationContext context = ElfRelocationContext.getRelocationContext(this, symbolMap);
ElfRelocationContext<?> context = ElfRelocationContext.getRelocationContext(this, symbolMap);
try {
for (ElfRelocationTable relocationTable : relocationTables) {
monitor.checkCancelled();
@ -894,7 +894,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
}
private void processRelocationTable(ElfRelocationTable relocationTable,
ElfRelocationContext context, TaskMonitor monitor) throws CancelledException {
ElfRelocationContext<?> context, TaskMonitor monitor) throws CancelledException {
Address defaultBase = getDefaultAddress(elf.adjustAddressForPrelink(0));
AddressSpace defaultSpace = defaultBase.getAddressSpace();
@ -953,7 +953,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
}
private void processRelocationTableEntries(ElfRelocationTable relocationTable,
ElfRelocationContext context, AddressSpace relocationSpace, long baseWordOffset,
ElfRelocationContext<?> context, AddressSpace relocationSpace, long baseWordOffset,
TaskMonitor monitor) throws CancelledException {
if (context != null) {

View File

@ -27,9 +27,9 @@ import ghidra.app.util.bin.format.omf.*;
import ghidra.app.util.bin.format.omf.OmfFixupRecord.Subrecord;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Undefined;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
@ -47,7 +47,7 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
public final static long IMAGE_BASE = 0x2000; // Base offset to start loading segments
public final static long MAX_UNINITIALIZED_FILL = 0x2000; // Maximum zero bytes added to pad initialized segments
private ArrayList<OmfSymbol> externsyms = new ArrayList<>();
private List<OmfSymbol> externsyms = new ArrayList<>();
/**
* OMF usually stores a string describing the compiler that produced it in a
@ -137,19 +137,42 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
// We don't use the file bytes to create block because the bytes are manipulated before
// forming the block. Creating the FileBytes anyway in case later we want access to all
// the original bytes.
MemoryBlockUtils.createFileBytes(program, provider, monitor);
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
try {
processSegmentHeaders(reader, header, program, monitor, log);
processPublicSymbols(header, program, monitor, log);
processExternalSymbols(header, program, monitor, log);
processRelocations(header, program, monitor, log);
markupRecords(program, fileBytes, header, log, monitor);
}
catch (AddressOverflowException e) {
throw new IOException(e);
}
}
private void markupRecords(Program program, FileBytes fileBytes, OmfFileHeader fileHeader,
MessageLog log, TaskMonitor monitor) {
monitor.setMessage("Marking up records...");
int size =
fileHeader.getRecords().stream().mapToInt(r -> r.getRecordLength() + 3).sum();
try {
Address recordSpaceAddr = AddressSpace.OTHER_SPACE.getAddress(0);
MemoryBlock headerBlock = MemoryBlockUtils.createInitializedBlock(program, true,
"RECORDS", recordSpaceAddr, fileBytes, 0, size, "", "", false,
false, false, log);
Address start = headerBlock.getStart();
for (OmfRecord record : fileHeader.getRecords()) {
DataUtilities.createData(program, start.add(record.getRecordOffset()),
record.toDataType(), -1, DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
}
}
catch (Exception e) {
log.appendMsg("Failed to markup records");
}
}
/**
* Log a (hopefully) descriptive error, if we can't process a specific relocation
* @param program is the Program
@ -181,7 +204,7 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
MessageLog log) {
Language language = program.getLanguage();
OmfFixupRecord.Subrecord[] targetThreads = new Subrecord[4];
ArrayList<OmfGroupRecord> groups = header.getGroups();
List<OmfGroupRecord> groups = header.getGroups();
long targetAddr; // Address of item being referred to
Address locAddress; // Location of data to be patched
DataConverter converter = DataConverter.getInstance(!header.isLittleEndian());
@ -364,7 +387,7 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
final Language language = program.getLanguage();
ArrayList<OmfSegmentHeader> segments = header.getSegments();
List<OmfSegmentHeader> segments = header.getSegments();
for (OmfSegmentHeader segment : segments) {
if (monitor.isCancelled()) {
break;
@ -432,9 +455,9 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
MessageLog log) {
SymbolTable symbolTable = program.getSymbolTable();
ArrayList<OmfSymbolRecord> symbols = header.getPublicSymbols();
ArrayList<OmfSegmentHeader> segments = header.getSegments();
ArrayList<OmfGroupRecord> groups = header.getGroups();
List<OmfSymbolRecord> symbols = header.getPublicSymbols();
List<OmfSegmentHeader> segments = header.getSegments();
List<OmfGroupRecord> groups = header.getGroups();
Language language = program.getLanguage();
monitor.setMessage("Creating Public Symbols");
@ -536,7 +559,7 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
private void processExternalSymbols(OmfFileHeader header, Program program, TaskMonitor monitor,
MessageLog log) {
ArrayList<OmfExternalSymbol> symbolrecs = header.getExternalSymbols();
List<OmfExternalSymbol> symbolrecs = header.getExternalSymbols();
if (symbolrecs.size() == 0) {
return;
}

View File

@ -93,34 +93,28 @@ public class ProgramOpener {
*/
public Program openProgram(ProgramLocator locator, TaskMonitor monitor) {
if (locator.isURL()) {
try {
return openURL(locator, monitor);
}
catch (CancelledException e) {
return null;
}
catch (IOException e) {
Msg.showError(this, null, "Program Open Failed",
"Failed to open Ghidra URL: " + locator.getURL());
}
return null;
return openURL(locator, monitor);
}
return openProgram(locator, locator.getDomainFile(), monitor);
}
private Program openURL(ProgramLocator locator, TaskMonitor monitor)
throws CancelledException, IOException {
private Program openURL(ProgramLocator locator, TaskMonitor monitor) {
URL ghidraUrl = locator.getURL();
AtomicReference<Program> openedProgram = new AtomicReference<>();
GhidraURLQuery.queryUrl(ghidraUrl, new GhidraURLResultHandlerAdapter() {
@Override
public void processResult(DomainFile domainFile, URL url, TaskMonitor m) {
Program p = openProgram(locator, domainFile, m); // may return null
openedProgram.set(p);
}
}, monitor);
try {
GhidraURLQuery.queryUrl(ghidraUrl, new GhidraURLResultHandlerAdapter() {
@Override
public void processResult(DomainFile domainFile, URL url, TaskMonitor m) {
Program p = openProgram(locator, domainFile, m); // may return null
openedProgram.set(p);
}
}, monitor);
}
catch (IOException | CancelledException e) {
// IOException reported to user by GhidraURLResultHandlerAdapter
return null;
}
return openedProgram.get();
}

View File

@ -209,8 +209,8 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E
private File resourcesTestDataDir;
private FileDataTypeManager archiveDtMgr;
private Structure testInfoStruct;
private Structure groupInfoStruct;
protected Structure testInfoStruct;
protected Structure groupInfoStruct;
private ParallelInstructionLanguageHelper parallelHelper;

View File

@ -53,8 +53,10 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
ProgramContext pc = program.getProgramContext();
Register regDR0 = pc.getRegister(regNameDR0);
// Initially Direction was 0x1e240
setRegValue(pc, addr("1002085"), addr("1002100"), regDR0, 0x5L);
setRegValue(pc, addr("TextOverlay:1001700"), addr("TextOverlay:1001780"), regDR0,
0x5L);
}
@Override
@ -63,6 +65,9 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
Register regDR0 = pc.getRegister(regNameDR0);
setRegValue(pc, addr("1002000"), addr("1002074"), regDR0, 0x22L);
setRegValue(pc, addr("TextOverlay:1001630"), addr("TextOverlay:1001680"), regDR0,
0x22L);
}
});
@ -89,6 +94,34 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
for (Address a = addr("1002101"); a.compareTo(addr("1002150")) <= 0; a = a.add(0x1L)) {
assertUndefinedRegValue("DR0", a);
}
// Check Overlay
// Neither set it
for (Address a = addr("TextOverlay:1001629"); a
.compareTo(addr("TextOverlay:1001629")) <= 0; a = a.add(0x1L)) {
assertUndefinedRegValue("DR0", a);
}
// From MY
for (Address a = addr("TextOverlay:1001630"); a
.compareTo(addr("TextOverlay:1001680")) <= 0; a = a.add(0x1L)) {
assertRegValue("DR0", a, 0x22L);
}
// Neither set it
for (Address a = addr("TextOverlay:1001681"); a
.compareTo(addr("TextOverlay:10016ff")) <= 0; a = a.add(0x1L)) {
assertUndefinedRegValue("DR0", a);
}
// From LATEST
for (Address a = addr("TextOverlay:1001700"); a
.compareTo(addr("TextOverlay:1001780")) <= 0; a = a.add(0x1L)) {
assertRegValue("DR0", a, 0x5L);
}
// Neither set it
for (Address a = addr("TextOverlay:1001781"); a
.compareTo(addr("TextOverlay:100182f")) <= 0; a = a.add(0x1L)) {
assertUndefinedRegValue("DR0", a);
}
}
@Test
@ -100,7 +133,6 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
ProgramContext pc = program.getProgramContext();
Register regDR0 = pc.getRegister(regNameDR0);
// Initially Direction was 0x1e240
setRegValue(pc, addr("10022d4"), addr("10022d9"), regDR0, 0x66L);
}
@ -807,6 +839,9 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
setRegValue(pc, addr("10022d4"), addr("10022e5"), reg1, 0x66L);
setRegValue(pc, addr("10022ee"), addr("10022fc"), reg1, 0x44L);
setRegValue(pc, addr("TextOverlay:1001700"), addr("TextOverlay:1001700"), reg1,
0x44L);
}
@Override
@ -816,6 +851,9 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
setRegValue(pc, addr("10022d4"), addr("10022e5"), reg1, 0x7L);
setRegValue(pc, addr("10022ee"), addr("10022fc"), reg1, 0x5L);
setRegValue(pc, addr("TextOverlay:1001700"), addr("TextOverlay:1001700"), reg1,
0x5L);
}
});
@ -824,6 +862,8 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
chooseRadioButton(CHECKED_OUT_BUTTON_NAME);
checkDisplayValues(Long.valueOf(0x44L), Long.valueOf(0x5L), (Long) null);
chooseRadioButton(CHECKED_OUT_BUTTON_NAME);
checkDisplayValues(Long.valueOf(0x44L), Long.valueOf(0x5L), (Long) null);
chooseRadioButton(CHECKED_OUT_BUTTON_NAME);
waitForMergeCompletion();
for (Address a = addr("10022d4"); a.compareTo(addr("10022e5")) <= 0; a = a.add(0x1L)) {
@ -832,6 +872,7 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
for (Address a = addr("10022ee"); a.compareTo(addr("10022fc")) <= 0; a = a.add(0x1L)) {
assertRegValue("DR0", a, 0x5L);
}
assertRegValue("DR0", addr("TextOverlay:1001700"), 0x5L);
}
@Test

View File

@ -372,6 +372,11 @@ public abstract class AbstractEditorTest extends AbstractGhidraHeadedIntegration
return (dtc != null) ? dtc.getLength() : -1;
}
protected DataType getDataType(Composite c, int index) {
DataTypeComponent dtc = c.getComponent(index);
return (dtc != null) ? dtc.getDataType() : null;
}
protected DataType getDataType(int index) {
DataTypeComponent dtc = getComponent(index);
return (dtc != null) ? dtc.getDataType() : null;

View File

@ -89,7 +89,7 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest {
programDTM.remove(complexStructure, TaskMonitor.DUMMY);
programDTM.getCategory(pgmRootCat.getCategoryPath())
.removeCategory("Temp", TaskMonitor.DUMMY);
.removeCategory("Temp", TaskMonitor.DUMMY);
waitForSwing();
@ -486,15 +486,17 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest {
@Test
public void testComponentDataTypeRemoved() {
// Get the data types we want to hold onto for comparison later
DataType dt3 = getDataType(complexStructure, 3);
DataType dt5 = getDataType(complexStructure, 5);
DataType dt8 = getDataType(complexStructure, 8);
DataType dt10 = getDataType(complexStructure, 10);
init(complexStructure, pgmTestCat);
DataType undef = DataType.DEFAULT;
assertEquals(23, model.getNumComponents());
// Clone the data types we want to hold onto for comparison later, since reload can close the viewDTM.
DataType dt3 = getDataType(3).clone(programDTM);
DataType dt5 = getDataType(5).clone(programDTM);
DataType dt8 = getDataType(8).clone(programDTM);
DataType dt10 = getDataType(10).clone(programDTM);
runSwing(
() -> complexStructure.getDataTypeManager().remove(simpleUnion, TaskMonitor.DUMMY));
@ -521,9 +523,8 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest {
waitForSwing();
assertTrue(simpleStructure.isEquivalent(getDataType(0)));
runSwing(() -> simpleStructure.getDataTypeManager()
.remove(
simpleStructure, TaskMonitor.DUMMY));
runSwing(
() -> simpleStructure.getDataTypeManager().remove(simpleStructure, TaskMonitor.DUMMY));
waitForSwing();
assertEquals(29, model.getNumComponents());// becomes undefined bytes
}
@ -581,18 +582,21 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest {
@Test
public void testComponentDataTypeReplaced() throws Exception {
// Get the data types we want to hold onto for comparison later
DataType dt15 = getDataType(complexStructure, 15);
DataType dt16 = getDataType(complexStructure, 16);
DataType dt18 = getDataType(complexStructure, 18);
DataType dt19 = getDataType(complexStructure, 19);
DataType dt20 = getDataType(complexStructure, 20);
String dt21Name = getDataType(complexStructure, 21).getName();
DataType dt22 = getDataType(complexStructure, 22);
init(complexStructure, pgmTestCat);
int numComps = model.getNumComponents();
int len = model.getLength();
// Clone the data types we want to hold onto for comparison later, since reload can close the viewDTM.
DataType dt15 = getDataType(15).clone(programDTM);
DataType dt16 = getDataType(16).clone(programDTM);
DataType dt18 = getDataType(18).clone(programDTM);
DataType dt19 = getDataType(19).clone(programDTM);
DataType dt20 = getDataType(20).clone(programDTM);
String dt21Name = getDataType(21).getName();
DataType dt22 = getDataType(22).clone(programDTM);
assertEquals(87, complexStructure.getComponent(16).getDataType().getLength());
assertEquals(29, complexStructure.getComponent(19).getDataType().getLength());
assertEquals(24, complexStructure.getComponent(20).getDataType().getLength());

View File

@ -502,8 +502,9 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest {
@Test
public void testCloseEditorProviderAndSave() throws Exception {
Window dialog;
DataType oldDt = complexStructure.clone(null);
init(complexStructure, pgmTestCat, false);
DataType oldDt = model.viewComposite.clone(null);
// Change the structure
runSwingLater(() -> {
@ -538,8 +539,9 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest {
@Test
public void testCloseEditorAndNoSave() throws Exception {
DataType oldDt = complexStructure.clone(null);
init(complexStructure, pgmTestCat, false);
DataType oldDt = model.viewComposite.clone(null);
// Change the structure
runSwing(() -> {

View File

@ -779,15 +779,18 @@ public class UnionEditorActions1Test extends AbstractUnionEditorTest {
@Test
public void testApplyNameChange() throws Exception {
DataType viewCopy = complexUnion.clone(null);
init(complexUnion, pgmTestCat, false);
model.setName("FooBarUnion");
DataType viewCopy = model.viewComposite.clone(null);
assertTrue(complexUnion.isEquivalent(model.viewComposite));
assertTrue(viewCopy.isEquivalent(complexUnion));
assertEquals("FooBarUnion", model.getCompositeName());
assertEquals("complexUnion", complexUnion.getName());
assertTrue(viewCopy.isEquivalent(model.viewComposite));
invoke(applyAction);
assertTrue(viewCopy.isEquivalent(complexUnion));
assertTrue(viewCopy.isEquivalent(model.viewComposite));

View File

@ -200,8 +200,9 @@ public class UnionEditorProviderTest extends AbstractUnionEditorTest {
@Test
public void testCloseEditorProviderAndSave() throws Exception {
Window dialog;
DataType oldDt = complexUnion.clone(null);
init(complexUnion, pgmTestCat, false);
DataType oldDt = model.viewComposite.clone(null);
// Change the union.
Swing.runLater(() -> {
@ -236,8 +237,9 @@ public class UnionEditorProviderTest extends AbstractUnionEditorTest {
@Test
public void testCloseEditorAndNoSave() throws Exception {
Window dialog;
DataType oldDt = complexUnion.clone(null);
init(complexUnion, pgmTestCat, false);
DataType oldDt = model.viewComposite.clone(null);
// Change the union.
Swing.runLater(() -> {

View File

@ -465,12 +465,6 @@ public class FakeSharedProject {
void refresh() {
DefaultProjectData projectData = getProjectData();
try {
projectData.refresh(true);
}
catch (IOException e) {
// shouldn't happen
throw new AssertionFailedError("Unable to refresh project " + this);
}
projectData.refresh(true);
}
}

View File

@ -202,6 +202,7 @@ class MergeProgramGenerator_DiffTestPrograms implements MergeProgramGenerator {
builder.createMemory(".data", "0x1008000", 0x600);
builder.createMemory(".datau", "0x1008600", 0x1344);
builder.createMemory(".rsrc", "0x100a000", 0x5400);
builder.createOverlayMemory("TextOverlay", "0x01001630", 0x200);
// for FunctionMergeManager2Test
//

View File

@ -87,17 +87,41 @@
<p>This view does not support <a href="#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Add_Byteviewer_HexInteger_Panel"></a><a name="HexInteger"></a>HexInteger </h3>
<h3><a name="Add_Byteviewer_HexShortPanel"></a><a name="HexShort"></a>Hex Short</h3>
<blockquote>
<p>This format shows four byte numbers represented as an eight digit hex number.&nbsp;</p>
<p>This format shows two-byte numbers represented as an four-digit hex number.&nbsp;</p>
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
is changed, both bytes associated with this address are rendered in
<font color="#ff0000"> red</font> to denote the change.</p>
</blockquote>
<h3><a name="Add_Byteviewer_HexInteger_Panel"></a><a name="HexInteger"></a>Hex Integer</h3>
<blockquote>
<p>This format shows four-byte numbers represented as an eight-digit hex number.&nbsp;</p>
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
is changed, all four bytes associated with this address are rendered in
<font color="#ff0000"> red</font> to denote the change.</p>
</blockquote>
<h3><a name="Add_Byteviewer_HexLong_Panel"></a><a name="HexLongr"></a>Hex Long</h3>
<blockquote>
<p>This format shows eight-byte numbers represented as an 16-digit hex number.&nbsp;</p>
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
is changed, all eight bytes associated with this address are rendered in
<font color="#ff0000"> red</font> to denote the change.</p>
</blockquote>
<h3><a name="Add_Byteviewer_HexLongLong_Panel"></a><a name="HexLongLong"></a>Hex Long Long</h3>
<blockquote>
<p>This format shows 16-byte numbers represented as an 32-digit hex number.&nbsp;</p>
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
is changed, all 16 bytes associated with this address are rendered in
<font color="#ff0000"> red</font> to denote the change.</p>
</blockquote>
<h3><a name="Add_Byteviewer_Integer_Panel"></a><a name="Integer"></a>Integer&nbsp; </h3>
<blockquote>
<p>This view shows four byte numbers represented in decimal format.&nbsp;</p>
<p>This view shows four-byte numbers represented in decimal format.&nbsp;</p>
<p> This view does not support <a href="#EditBytes">editing</a>.</p>
</blockquote>

View File

@ -912,9 +912,8 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
ByteField getField(BigInteger index, int fieldNum) {
if (indexMap != null) {
int fieldOffset = indexMap.getFieldOffset(index, fieldNum, fieldFactories);
if (fieldNum < fieldFactories.length) {
return (ByteField) fieldFactories[fieldOffset].getField(index);
return (ByteField) fieldFactories[fieldNum].getField(index);
}
}
return null;

View File

@ -78,6 +78,19 @@ class FileByteBlock implements ByteBlock {
return 0;
}
/* (non-Javadoc)
* @see ghidra.app.plugin.core.format.ByteBlock#getShort(int)
*/
public short getShort(BigInteger bigIndex) throws ByteBlockAccessException {
int index = bigIndex.intValue();
if (index < buf.length) {
byte[] b = new byte[2];
System.arraycopy(buf, index, b, 0, b.length);
return converter.getShort(b);
}
return 0;
}
/* (non-Javadoc)
* @see ghidra.app.plugin.core.format.ByteBlock#getInt(int)
*/
@ -114,6 +127,18 @@ class FileByteBlock implements ByteBlock {
}
}
/* (non-Javadoc)
* @see ghidra.app.plugin.core.format.ByteBlock#setShort(int, short)
*/
public void setShort(BigInteger bigIndex, short value) throws ByteBlockAccessException {
int index = bigIndex.intValue();
if (index < buf.length) {
byte[] b = new byte[2];
converter.putShort(b, 0, value);
System.arraycopy(b, 0, buf, index, b.length);
}
}
/* (non-Javadoc)
* @see ghidra.app.plugin.core.format.ByteBlock#setInt(int, int)
*/

View File

@ -225,6 +225,44 @@ public class MemoryByteBlock implements ByteBlock {
}
}
/**
* Get the short value at the given index.
*
* @param index byte index
* @throws ByteBlockAccessException if the block cannot be read
* @throws IndexOutOfBoundsException if the given index is not in this block.
*/
@Override
public short getShort(BigInteger index) throws ByteBlockAccessException {
Address addr = getAddress(index);
try {
return memory.getShort(addr, bigEndian);
}
catch (MemoryAccessException e) {
throw new ByteBlockAccessException(e.getMessage());
}
}
/**
* Set the short at the given index.
*
* @param index byte index
* @param value value to set
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if the given index is not in this block.
*/
@Override
public void setShort(BigInteger index, short value) throws ByteBlockAccessException {
Address addr = getAddress(index);
checkEditsAllowed(addr, 2);
try {
memory.setShort(addr, value, bigEndian);
}
catch (MemoryAccessException e) {
throw new ByteBlockAccessException(e.getMessage());
}
}
/**
* Return true if this block can be modified.
*/

View File

@ -61,6 +61,15 @@ public interface ByteBlock {
return true;
}
/**
* Get the short value at the given index.
* @param index byte index
* @throws ByteBlockAccessException if the block cannot be read
* @throws IndexOutOfBoundsException if the given index is not in this
* block.
*/
public short getShort(BigInteger index) throws ByteBlockAccessException;
/**
* Get the int value at the given index.
* @param index byte index
@ -89,6 +98,16 @@ public interface ByteBlock {
*/
public void setByte(BigInteger index, byte value) throws ByteBlockAccessException;
/**
* Set the short at the given index.
* @param index byte index
* @param value value to set
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if the given index is not in this
* block.
*/
public void setShort(BigInteger index, short value) throws ByteBlockAccessException;
/**
* Set the int at the given index.
* @param index byte index

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,236 +15,22 @@
*/
package ghidra.app.plugin.core.format;
import ghidra.util.HelpLocation;
import java.math.BigInteger;
/**
* Converts byte values to Integer represented as an 8 digit hex number.
* Converts byte values to Integer represented as an 4-byte/8-digit hex number.
*/
public class HexIntegerFormatModel implements UniversalDataFormatModel {
private int symbolSize;
public class HexIntegerFormatModel extends HexValueFormatModel {
public HexIntegerFormatModel() {
symbolSize = 8;
super("Hex Integer", 4);
}
/**
* Get the name of this formatter.
*/
public String getName() {
return "HexInteger";
}
/**
* Get the number of bytes to make a unit; in this case,
* returns 4.
*/
public int getUnitByteSize() {
return 4;
}
/**
* Returns the byte used to generate the character at a given
* position.
* @param position number in the range 0 to 7
*/
public int getByteOffset(ByteBlock block, int position) {
int o = position / 2;
if (block.isBigEndian()) {
return o;
}
return 3 - o;
}
/**
* Given the byte offset into a unit, get the column position.
*/
public int getColumnPosition(ByteBlock block, int byteOffset) {
if (byteOffset > 3) {
throw new IllegalArgumentException("invalid byteOffset: " + byteOffset);
}
if (block.isBigEndian()) {
return byteOffset * 2;
}
return (3 - byteOffset) * 2;
}
/**
* Gets the number of characters required to display a
* unit.
* @return 4 for number of characters in the integer representation.
*/
public int getDataUnitSymbolSize() {
return symbolSize;
}
/**
* Gets the string representation at the given index in the block.
* @param block block to change
* @param index byte index into the block
* @throws ByteBlockAccessException if the block cannot be read
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
@Override
public String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException {
int i = block.getInt(index);
String str = Integer.toHexString(i);
return pad(str);
}
/**
* Returns true to allow values to be changed.
*/
public boolean isEditable() {
return true;
}
/**
* Overwrite a value in a ByteBlock.
* @param block block to change
* @param index byte index into the block
* @param pos The position within the unit where c will be the
* new character.
* @param c new character to put at pos param
* @return true if the replacement is legal, false if the
* replacement value would not make sense for this format, e.g.
* attempt to put a 'z' in a hex unit.
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
public boolean replaceValue(ByteBlock block, BigInteger index, int charPosition, char c)
throws ByteBlockAccessException {
if (charPosition < 0 || charPosition > symbolSize - 1) {
return false;
}
char[] charArray = { c };
String s = new String(charArray);
try {
Integer.parseInt(s, 16);
}
catch (Exception e) {
return false;
}
byte cb = Byte.parseByte(new String(charArray), 16);
// get the correct byte offset based on position
int byteOffset = getByteOffset(block, charPosition);
BigInteger saveIndex = index;
index = index.add(BigInteger.valueOf(byteOffset));
byte b = block.getByte(index);
b = adjustByte(b, cb, charPosition);
int intValue = getInt(block, saveIndex, b, byteOffset);
block.setInt(saveIndex, intValue);
return true;
}
/**
* Get number of units in a group. A group may represent
* multiple units shown as one entity. This format does not
* support groups.
*/
public int getGroupSize() {
return 1;
}
/**
* Set the number of units in a group. This format does not
* support groups.
* @throws UnsupportedOperationException
*/
public void setGroupSize(int groupSize) {
throw new UnsupportedOperationException("groups are not supported");
}
/**
* Get the number of characters separating units.
*/
public int getUnitDelimiterSize() {
return 1;
}
/**
* @see ghidra.app.plugin.core.format.DataFormatModel#validateBytesPerLine(int)
*/
public boolean validateBytesPerLine(int bytesPerLine) {
return bytesPerLine % 4 == 0;
}
/////////////////////////////////////////////////////////////////
// *** private methods ***
/////////////////////////////////////////////////////////////////
/**
* Returns value with leading zeros.
*/
private String pad(String value) {
StringBuffer sb = new StringBuffer();
int len = symbolSize - value.length();
for (int i = 0; i < len; i++) {
sb.append("0");
}
sb.append(value);
return sb.toString();
}
/**
* adjust byte b to use either the upper 4 bits or
* the lower 4 bits of newb according to charPosition.
*/
private byte adjustByte(byte b, byte newb, int charPosition) {
if (charPosition % 2 == 0) {
// its the high order byte
b &= 0x0f;
newb <<= 4;
}
else {
b &= 0xf0;
}
b += newb;
return b;
}
private int getInt(ByteBlock block, BigInteger offset, byte newb, int byteOffset) {
byte[] b = new byte[4];
try {
for (int i = 0; i < b.length; i++) {
b[i] = block.getByte(offset.add(BigInteger.valueOf(i)));
}
b[byteOffset] = newb;
if (block.isBigEndian()) {
return (b[0] << 24) | ((b[1] << 16) & 0x00FF0000) | ((b[2] << 8) & 0x0000FF00) |
(b[3] & 0x000000FF);
}
return (b[3] << 24) | ((b[2] << 16) & 0x00FF0000) | ((b[1] << 8) & 0x0000FF00) |
(b[0] & 0x000000FF);
}
catch (ByteBlockAccessException e) {
}
return 0;
}
/* (non-Javadoc)
* @see ghidra.app.plugin.format.DataFormatModel#getHelpLocation()
*/
public HelpLocation getHelpLocation() {
return new HelpLocation("ByteViewerPlugin", "HexInteger");
}
public void dispose() {
}
}

View File

@ -0,0 +1,36 @@
/* ###
* 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.plugin.core.format;
import java.math.BigInteger;
/**
* Converts byte values to Long represented as an 8-byte/16--digit hex number.
*/
public class HexLongFormatModel extends HexValueFormatModel {
public HexLongFormatModel() {
super("Hex Long", 8);
}
@Override
public String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException {
long l = block.getLong(index);
String str = Long.toHexString(l);
return pad(str);
}
}

View File

@ -0,0 +1,39 @@
/* ###
* 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.plugin.core.format;
import java.math.BigInteger;
/**
* Converts byte values to LongLong represented as an 16-byte/32-digit hex number.
*/
public class HexLongLongFormatModel extends HexValueFormatModel {
public HexLongLongFormatModel() {
super("Hex Long Long", 16);
}
@Override
public String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException {
long l0 = block.getLong(index);
String str0 = pad(Long.toHexString(l0));
long l1 = block.getLong(index.add(BigInteger.valueOf(8)));
String str1 = pad(Long.toHexString(l1));
String str = str1.substring(16) + str0.substring(16);
return pad(str);
}
}

View File

@ -0,0 +1,36 @@
/* ###
* 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.plugin.core.format;
import java.math.BigInteger;
/**
* Converts byte values to Short represented as an 2-byte/4-digit hex number.
*/
public class HexShortFormatModel extends HexValueFormatModel {
public HexShortFormatModel() {
super("Hex Short", 2);
}
@Override
public String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException {
short s = block.getShort(index);
String str = Integer.toHexString(s & 0xFFFF);
return pad(str);
}
}

View File

@ -0,0 +1,167 @@
/* ###
* 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.plugin.core.format;
import java.math.BigInteger;
import ghidra.util.HelpLocation;
/**
* Converts byte values to value represented as a 2, 4, 8, or 16-byte hex number.
*/
public abstract class HexValueFormatModel implements UniversalDataFormatModel {
protected String name;
private int symbolSize;
protected int nbytes;
public HexValueFormatModel(String name, int nbytes) {
this.name = name;
this.nbytes = nbytes;
symbolSize = nbytes * 2;
}
@Override
public String getName() {
return name;
}
@Override
public int getUnitByteSize() {
return nbytes;
}
@Override
public int getByteOffset(ByteBlock block, int position) {
int o = position / 2;
if (block.isBigEndian()) {
return o;
}
return nbytes - 1 - o;
}
@Override
public int getColumnPosition(ByteBlock block, int byteOffset) {
if (byteOffset > nbytes - 1) {
throw new IllegalArgumentException("invalid byteOffset: " + byteOffset);
}
if (block.isBigEndian()) {
return byteOffset * 2;
}
return (nbytes - 1 - byteOffset) * 2;
}
@Override
public int getDataUnitSymbolSize() {
return symbolSize;
}
@Override
public abstract String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException;
@Override
public boolean isEditable() {
return true;
}
@Override
public boolean replaceValue(ByteBlock block, BigInteger index, int charPosition, char c)
throws ByteBlockAccessException {
if (charPosition < 0 || charPosition > symbolSize - 1) {
// Not sure how this is possible, but...
return false;
}
char[] charArray = { c };
byte cb = Byte.parseByte(new String(charArray), 16);
// get the correct byte offset based on position
int byteOffset = getByteOffset(block, charPosition);
index = index.add(BigInteger.valueOf(byteOffset));
byte b = block.getByte(index);
b = adjustByte(b, cb, charPosition);
block.setByte(index, b);
return true;
}
@Override
public int getGroupSize() {
return 1;
}
/**
* Set the number of units in a group. This format does not
* support groups.
*/
@Override
public void setGroupSize(int groupSize) {
throw new UnsupportedOperationException("groups are not supported");
}
@Override
public int getUnitDelimiterSize() {
return 1;
}
@Override
public boolean validateBytesPerLine(int bytesPerLine) {
return bytesPerLine % nbytes == 0;
}
/**
* Returns value with leading zeros.
*/
protected String pad(String value) {
StringBuffer sb = new StringBuffer();
int len = symbolSize - value.length();
for (int i = 0; i < len; i++) {
sb.append("0");
}
sb.append(value);
return sb.toString();
}
/**
* adjust byte b to use either the upper 4 bits or
* the lower 4 bits of newb according to charPosition.
*/
private byte adjustByte(byte b, byte newb, int charPosition) {
if (charPosition % 2 == 0) {
// its the high order byte
b &= 0x0f;
newb <<= 4;
}
else {
b &= 0xf0;
}
b += newb;
return b;
}
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("ByteViewerPlugin", "HexValue");
}
@Override
public void dispose() {
}
}

View File

@ -305,7 +305,7 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
field = asciiC.getField(loc.getIndex(), loc.getFieldNum());
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, field.getForeground());
final ByteViewerComponent hexIntC = findComponent(panel, "HexInteger");
final ByteViewerComponent hexIntC = findComponent(panel, "Hex Integer");
SwingUtilities.invokeAndWait(() -> panel.setCurrentView(hexIntC));
loc = getFieldLocation(addr);
@ -456,14 +456,14 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
}
@Test
public void testUndoRedoHexInteger() throws Exception {
public void testUndoRedoHexShort() throws Exception {
env.showTool();
addViews();
final Address addr = getAddr(0x01001003);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
ByteViewerComponent c = findComponent(panel, "HexInteger");
ByteViewerComponent c = findComponent(panel, "Hex Short");
panel.setCurrentView(c);
SwingUtilities.invokeAndWait(() -> {
@ -504,6 +504,228 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
}
@Test
public void testUndoRedoHexInteger() throws Exception {
env.showTool();
addViews();
final Address addr = getAddr(0x01001003);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
ByteViewerComponent c = findComponent(panel, "Hex Integer");
panel.setCurrentView(c);
SwingUtilities.invokeAndWait(() -> {
FieldLocation loc = getFieldLocation(addr);
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol());
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
});
byte value = program.getMemory().getByte(addr);
SwingUtilities.invokeAndWait(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
FieldLocation loc = getFieldLocation(addr);
KeyEvent ev =
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_A, 'a');
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol(), currentComponent.getCurrentField());
});
program.flushEvents();
assertEquals((byte) 0xa0, memory.getByte(addr));
undo(program);
assertEquals(value, memory.getByte(addr));
FieldLocation loc = getFieldLocation(addr);
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
Color fg = field.getForeground();
assertTrue(fg == null ||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
redo(program);
// field color should show edit color
loc = getFieldLocation(addr);
field = c.getField(loc.getIndex(), loc.getFieldNum());
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, field.getForeground());
}
@Test
public void testUndoRedoHexLong() throws Exception {
env.showTool();
addViews();
final Address addr = getAddr(0x01001003);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
ByteViewerComponent c = findComponent(panel, "Hex Long");
panel.setCurrentView(c);
SwingUtilities.invokeAndWait(() -> {
FieldLocation loc = getFieldLocation(addr);
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol());
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
});
byte value = program.getMemory().getByte(addr);
SwingUtilities.invokeAndWait(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
FieldLocation loc = getFieldLocation(addr);
KeyEvent ev =
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_A, 'a');
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol(), currentComponent.getCurrentField());
});
program.flushEvents();
assertEquals((byte) 0xa0, memory.getByte(addr));
undo(program);
assertEquals(value, memory.getByte(addr));
FieldLocation loc = getFieldLocation(addr);
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
Color fg = field.getForeground();
assertTrue(fg == null ||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
redo(program);
// field color should show edit color
loc = getFieldLocation(addr);
field = c.getField(loc.getIndex(), loc.getFieldNum());
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, field.getForeground());
}
@Test
public void testUndoRedoHexLongLong() throws Exception {
env.showTool();
addViews();
final Address addr = getAddr(0x01001003);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
ByteViewerComponent c = findComponent(panel, "Hex Long Long");
panel.setCurrentView(c);
SwingUtilities.invokeAndWait(() -> {
FieldLocation loc = getFieldLocation(addr);
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol());
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
});
byte value = program.getMemory().getByte(addr);
SwingUtilities.invokeAndWait(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
FieldLocation loc = getFieldLocation(addr);
KeyEvent ev =
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_A, 'a');
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol(), currentComponent.getCurrentField());
});
program.flushEvents();
assertEquals((byte) 0xa0, memory.getByte(addr));
undo(program);
assertEquals(value, memory.getByte(addr));
FieldLocation loc = getFieldLocation(addr);
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
Color fg = field.getForeground();
assertTrue(fg == null ||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
redo(program);
// field color should show edit color
loc = getFieldLocation(addr);
field = c.getField(loc.getIndex(), loc.getFieldNum());
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, field.getForeground());
}
@Test
public void testUndoRedoHexShort2() throws Exception {
// remove code browser plugin so the cursor position does not
// get changed because of location events that the code browser
// generates.
tool.removePlugins(new Plugin[] { cbPlugin });
env.showTool();
addViews();
ByteViewerComponent c = findComponent(panel, "Hex Short");
panel.setCurrentView(c);
// make 3 changes
// verify that the Undo button is enabled and undo can be done 5 times
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
SwingUtilities.invokeAndWait(() -> {
Address addr = getAddr(0x01001003);
FieldLocation loc = getFieldLocation(addr);
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
});
SwingUtilities.invokeAndWait(() -> {
char[] values = { 'a', '1', '2' };
int[] keyCodes =
{ KeyEvent.VK_P, KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_B, KeyEvent.VK_3 };
ByteViewerComponent currentComponent = panel.getCurrentComponent();
for (int i = 0; i < 3; i++) {
FieldLocation loc = currentComponent.getCursorLocation();
KeyEvent ev = new KeyEvent(currentComponent, 0, new Date().getTime(), 0,
keyCodes[i], values[i]);
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol(), currentComponent.getCurrentField());
}
});
program.flushEvents();
waitForSwing();
for (int i = 0; i < 3; i++) {
assertTrue(program.canUndo());
undo(program);
FieldLocation loc = c.getCursorLocation();
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
Color fg = field.getForeground();
if (i == 2) {
assertTrue(fg == null ||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
}
else {
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
}
}
assertTrue(!program.canUndo());
for (int i = 0; i < 3; i++) {
redo(program);
FieldLocation loc = c.getCursorLocation();
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
Color fg = field.getForeground();
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
}
assertTrue(program.canUndo());
}
@Test
public void testUndoRedoHexInteger2() throws Exception {
// remove code browser plugin so the cursor position does not
@ -514,7 +736,7 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
env.showTool();
addViews();
ByteViewerComponent c = findComponent(panel, "HexInteger");
ByteViewerComponent c = findComponent(panel, "Hex Integer");
panel.setCurrentView(c);
// make 5 changes
// verify that the Undo button is enabled and undo can be done 5 times
@ -579,6 +801,160 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
assertTrue(program.canUndo());
}
@Test
public void testUndoRedoHexLong2() throws Exception {
// remove code browser plugin so the cursor position does not
// get changed because of location events that the code browser
// generates.
tool.removePlugins(new Plugin[] { cbPlugin });
env.showTool();
addViews();
ByteViewerComponent c = findComponent(panel, "Hex Long");
panel.setCurrentView(c);
// make 9 changes
// verify that the Undo button is enabled and undo can be done 5 times
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
SwingUtilities.invokeAndWait(() -> {
Address addr = getAddr(0x01001003);
FieldLocation loc = getFieldLocation(addr);
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
});
SwingUtilities.invokeAndWait(() -> {
char[] values = { 'a', '1', '2', 'b', '3', 'c', '4', 'd', '5' };
int[] keyCodes =
{ KeyEvent.VK_P, KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_B, KeyEvent.VK_3,
KeyEvent.VK_C, KeyEvent.VK_4, KeyEvent.VK_D, KeyEvent.VK_5 };
ByteViewerComponent currentComponent = panel.getCurrentComponent();
for (int i = 0; i < 9; i++) {
FieldLocation loc = currentComponent.getCursorLocation();
KeyEvent ev = new KeyEvent(currentComponent, 0, new Date().getTime(), 0,
keyCodes[i], values[i]);
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol(), currentComponent.getCurrentField());
}
});
program.flushEvents();
waitForSwing();
for (int i = 0; i < 9; i++) {
assertTrue(program.canUndo());
undo(program);
FieldLocation loc = c.getCursorLocation();
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
Color fg = field.getForeground();
if (i == 8) {
assertTrue(fg == null ||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
}
else {
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
}
}
assertTrue(!program.canUndo());
for (int i = 0; i < 9; i++) {
redo(program);
FieldLocation loc = c.getCursorLocation();
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
Color fg = field.getForeground();
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
}
assertTrue(program.canUndo());
}
@Test
public void testUndoRedoHexLongLong2() throws Exception {
// remove code browser plugin so the cursor position does not
// get changed because of location events that the code browser
// generates.
tool.removePlugins(new Plugin[] { cbPlugin });
env.showTool();
addViews();
ByteViewerComponent c = findComponent(panel, "Hex Long Long");
panel.setCurrentView(c);
// make 9 changes
// verify that the Undo button is enabled and undo can be done 5 times
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
SwingUtilities.invokeAndWait(() -> {
Address addr = getAddr(0x01001003);
FieldLocation loc = getFieldLocation(addr);
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
});
SwingUtilities.invokeAndWait(() -> {
char[] values = { 'a', '1', '2', 'b', '3', 'c', '4', 'd', '5', 'e', '6', 'f', '7', 'a',
'8', 'b', '9' };
int[] keyCodes =
{ KeyEvent.VK_P, KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_B, KeyEvent.VK_3,
KeyEvent.VK_C, KeyEvent.VK_4, KeyEvent.VK_D, KeyEvent.VK_5, KeyEvent.VK_E, KeyEvent.VK_6,
KeyEvent.VK_F, KeyEvent.VK_7, KeyEvent.VK_G, KeyEvent.VK_8, KeyEvent.VK_H, KeyEvent.VK_9 };
ByteViewerComponent currentComponent = panel.getCurrentComponent();
for (int i = 0; i < 17; i++) {
FieldLocation loc = currentComponent.getCursorLocation();
KeyEvent ev = new KeyEvent(currentComponent, 0, new Date().getTime(), 0,
keyCodes[i], values[i]);
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol(), currentComponent.getCurrentField());
}
});
program.flushEvents();
waitForSwing();
for (int i = 0; i < 17; i++) {
assertTrue(program.canUndo());
undo(program);
FieldLocation loc = c.getCursorLocation();
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
Color fg = field.getForeground();
if (i == 16) {
assertTrue(fg == null ||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
}
else {
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
}
}
assertTrue(!program.canUndo());
for (int i = 0; i < 17; i++) {
redo(program);
FieldLocation loc = c.getCursorLocation();
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
Color fg = field.getForeground();
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
}
assertTrue(program.canUndo());
}
@Test
public void testEditInputHex() throws Exception {
env.showTool();
@ -717,12 +1093,129 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
assertEquals(value, program.getMemory().getByte(getAddr(0x01001000)));
}
@Test
public void testEditModeHexShort() throws Exception {
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Hex Short");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001003));
SwingUtilities.invokeAndWait(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
});
assertTrue(action.isSelected());
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, c.getFocusedCursorColor());
SwingUtilities.invokeAndWait(() -> {
KeyEvent ev = new KeyEvent(c, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
c.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol(),
c.getCurrentField());
});
program.flushEvents();
assertEquals((byte) 0x10, program.getMemory().getByte(getAddr(0x01001003)));
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
((ByteField) c.getCurrentField()).getForeground());
SwingUtilities.invokeAndWait(() -> {
action.setSelected(false);
action.actionPerformed(new DefaultActionContext());
});
assertTrue(!action.isSelected());
assertEquals(ByteViewerComponentProvider.CURSOR_ACTIVE_COLOR,
c.getFocusedCursorColor());
}
@Test
public void testEditModeHexInteger() throws Exception {
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "HexInteger");
final ByteViewerComponent c = findComponent(panel, "Hex Integer");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001003));
SwingUtilities.invokeAndWait(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
});
assertTrue(action.isSelected());
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, c.getFocusedCursorColor());
SwingUtilities.invokeAndWait(() -> {
KeyEvent ev = new KeyEvent(c, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
c.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol(),
c.getCurrentField());
});
program.flushEvents();
assertEquals((byte) 0x10, program.getMemory().getByte(getAddr(0x01001003)));
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
((ByteField) c.getCurrentField()).getForeground());
SwingUtilities.invokeAndWait(() -> {
action.setSelected(false);
action.actionPerformed(new DefaultActionContext());
});
assertTrue(!action.isSelected());
assertEquals(ByteViewerComponentProvider.CURSOR_ACTIVE_COLOR,
c.getFocusedCursorColor());
}
@Test
public void testEditModeHexLong() throws Exception {
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Hex Long");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001003));
SwingUtilities.invokeAndWait(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
});
assertTrue(action.isSelected());
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, c.getFocusedCursorColor());
SwingUtilities.invokeAndWait(() -> {
KeyEvent ev = new KeyEvent(c, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
c.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol(),
c.getCurrentField());
});
program.flushEvents();
assertEquals((byte) 0x10, program.getMemory().getByte(getAddr(0x01001003)));
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
((ByteField) c.getCurrentField()).getForeground());
SwingUtilities.invokeAndWait(() -> {
action.setSelected(false);
action.actionPerformed(new DefaultActionContext());
});
assertTrue(!action.isSelected());
assertEquals(ByteViewerComponentProvider.CURSOR_ACTIVE_COLOR,
c.getFocusedCursorColor());
}
@Test
public void testEditModeHexLongLong() throws Exception {
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Hex Long Long");
panel.setCurrentView(c);
final ToggleDockingAction action =
@ -1563,7 +2056,10 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
setViewSelected(dialog, "Ascii", true);
setViewSelected(dialog, "Octal", true);
setViewSelected(dialog, "HexInteger", true);
setViewSelected(dialog, "Hex Short", true);
setViewSelected(dialog, "Hex Integer", true);
setViewSelected(dialog, "Hex Long", true);
setViewSelected(dialog, "Hex Long Long", true);
pressButtonByText(dialog.getComponent(), "OK");
}

View File

@ -237,13 +237,72 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
assertEquals(insertionStr, findLabelStr(panel, "Insertion"));
}
@Test
public void testHexShortView() throws Exception {
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Hex Short");
panel.setCurrentView(c);
assertEquals(8, c.getNumberOfFields());
assertEquals(2, c.getDataModel().getUnitByteSize());
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
});
// verify that the 2 bytes are represented as an 4 digit hex number
assertEquals(4, c.getCurrentField().getNumCols(loc.getRow()));
}
@Test
public void testOtherEditsHexShort() throws Exception {
// verify that the 4 byte string is rendered in red when a byte
// is changed from another view, e.g. Ascii or Hex
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Ascii");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
KeyEvent ev =
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol(), currentComponent.getCurrentField());
});
program.flushEvents();
final ByteViewerComponent hexComp = findComponent(panel, "Hex Short");
runSwing(() -> {
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
FieldLocation l = hexComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
hexComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
});
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
((ByteField) hexComp.getCurrentField()).getForeground());
}
@Test
public void testHexIntegerView() throws Exception {
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "HexInteger");
final ByteViewerComponent c = findComponent(panel, "Hex Integer");
panel.setCurrentView(c);
assertEquals(4, c.getNumberOfFields());
assertEquals(4, c.getDataModel().getUnitByteSize());
@ -282,7 +341,125 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
});
program.flushEvents();
final ByteViewerComponent hexComp = findComponent(panel, "HexInteger");
final ByteViewerComponent hexComp = findComponent(panel, "Hex Integer");
runSwing(() -> {
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
FieldLocation l = hexComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
hexComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
});
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
((ByteField) hexComp.getCurrentField()).getForeground());
}
@Test
public void testHexLongView() throws Exception {
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Hex Long");
panel.setCurrentView(c);
assertEquals(2, c.getNumberOfFields());
assertEquals(8, c.getDataModel().getUnitByteSize());
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
});
// verify that the 8 bytes are represented as an 8 digit hex number
assertEquals(16, c.getCurrentField().getNumCols(loc.getRow()));
}
@Test
public void testOtherEditsHexLong() throws Exception {
// verify that the 4 byte string is rendered in red when a byte
// is changed from another view, e.g. Ascii or Hex
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Ascii");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
KeyEvent ev =
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol(), currentComponent.getCurrentField());
});
program.flushEvents();
final ByteViewerComponent hexComp = findComponent(panel, "Hex Long");
runSwing(() -> {
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
FieldLocation l = hexComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
hexComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
});
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
((ByteField) hexComp.getCurrentField()).getForeground());
}
@Test
public void testHexLongLongView() throws Exception {
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Hex Long Long");
panel.setCurrentView(c);
assertEquals(1, c.getNumberOfFields());
assertEquals(16, c.getDataModel().getUnitByteSize());
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
});
// verify that the 16 bytes are represented as an 32 digit hex number
assertEquals(32, c.getCurrentField().getNumCols(loc.getRow()));
}
@Test
public void testOtherEditsHexLongLong() throws Exception {
// verify that the 4 byte string is rendered in red when a byte
// is changed from another view, e.g. Ascii or Hex
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Ascii");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
action.setSelected(true);
action.actionPerformed(new DefaultActionContext());
KeyEvent ev =
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol(), currentComponent.getCurrentField());
});
program.flushEvents();
final ByteViewerComponent hexComp = findComponent(panel, "Hex Long Long");
runSwing(() -> {
ProgramByteBlockSet blockset =
@ -402,7 +579,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
setViewSelected(dialog, "Ascii", true);
setViewSelected(dialog, "Octal", true);
setViewSelected(dialog, "HexInteger", true);
setViewSelected(dialog, "Hex Integer", true);
setViewSelected(dialog, "Integer", true);
pressButtonByText(dialog.getComponent(), "OK");
waitForSwing();
@ -805,7 +982,10 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
setViewSelected(dialog, "Ascii", true);
setViewSelected(dialog, "Octal", true);
setViewSelected(dialog, "HexInteger", true);
setViewSelected(dialog, "Hex Short", true);
setViewSelected(dialog, "Hex Integer", true);
setViewSelected(dialog, "Hex Long", true);
setViewSelected(dialog, "Hex Long Long", true);
pressButtonByText(dialog.getComponent(), "OK");
waitForSwing();
}

View File

@ -22,7 +22,6 @@ import java.util.stream.Collectors;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
import generic.theme.GColor;
import ghidra.app.decompiler.ClangSyntaxToken;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.component.*;
@ -120,7 +119,7 @@ public class DiffClangHighlightController extends LocationClangHighlightControll
List<ClangToken> tokens = addPrimaryHighlightToTokensForParenthesis(
(ClangSyntaxToken) tok, defaultParenColor);
reHighlightDiffs(tokens);
addBraceHighlight((ClangSyntaxToken) tok, defaultParenColor);
addPrimaryHighlightToTokensForBrace((ClangSyntaxToken) tok, defaultParenColor);
}
TokenBin tokenBin = null;

View File

@ -18,8 +18,6 @@ package classrecovery;
import java.util.*;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.decompiler.component.DecompilerUtils;
import ghidra.app.decompiler.util.FillOutStructureHelper;
import ghidra.app.decompiler.util.FillOutStructureHelper.OffsetPcodeOpPair;
import ghidra.app.util.opinion.PeLoader;
@ -1480,10 +1478,7 @@ public class RTTIWindowsClassRecoverer extends RTTIClassRecoverer {
}
}
DecompileOptions decompileOptions =
DecompilerUtils.getDecompileOptions(serviceProvider, program);
FillOutStructureHelper fillStructHelper =
new FillOutStructureHelper(program, decompileOptions, monitor);
FillOutStructureHelper fillStructHelper = new FillOutStructureHelper(program, monitor);
for (Function constructor : constructorList) {
@ -1568,7 +1563,7 @@ public class RTTIWindowsClassRecoverer extends RTTIClassRecoverer {
monitor.checkCancelled();
fillStructHelper.processStructure(highVariable, function, true, false);
fillStructHelper.processStructure(highVariable, function, true, false, null);
List<OffsetPcodeOpPair> stores = fillStructHelper.getStorePcodeOps();
stores = removePcodeOpsNotInFunction(function, stores);
@ -2484,7 +2479,7 @@ public class RTTIWindowsClassRecoverer extends RTTIClassRecoverer {
baseClassDescriptorAddress.toString());
continue;
}
// Continue if the class has mult inh but base class is not on the parent list
if (!recoveredClass.getParentList().contains(baseClass)) {
continue;

View File

@ -498,16 +498,6 @@ public class RecoveredClass {
String fieldName = newComponent.getFieldName();
String comment = newComponent.getComment();
// if it is any empty placeholder structure - replace with
// undefined1 dt
if (newComponentDataType instanceof Structure &&
newComponentDataType.isNotYetDefined()) {
computedClassStructure.replaceAtOffset(offset, new Undefined1DataType(), 1,
fieldName, comment);
continue;
}
// if new component is an existing class data type pointer then replace current item
// with a void pointer of same size if there is room
if (newComponentDataType instanceof Pointer &&
@ -529,9 +519,19 @@ public class RecoveredClass {
// if the new component is a non-empty structure, check to see if the current
// structure has undefined or equivalent components and replace with new struct if so
if (newComponentDataType instanceof Structure) {
// if new component is any empty placeholder structure AND if the existing component
// is undefined then replace with undefined1 dt
if (newComponentDataType.isNotYetDefined()) {
if (Undefined.isUndefined(currentComponentDataType)) {
computedClassStructure.replaceAtOffset(offset, new Undefined1DataType(), 1,
fieldName, comment);
}
continue;
}
if (EditStructureUtils.hasReplaceableComponentsAtOffset(computedClassStructure,
offset, (Structure) newComponentDataType, monitor)) {
offset, (Structure)newComponentDataType, monitor)) {
boolean successfulClear =
EditStructureUtils.clearLengthAtOffset(computedClassStructure, offset,
length, monitor);

View File

@ -22,8 +22,6 @@ import java.util.stream.Collectors;
import ghidra.app.cmd.function.ApplyFunctionSignatureCmd;
import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.cmd.label.SetLabelPrimaryCmd;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.decompiler.component.DecompilerUtils;
import ghidra.app.decompiler.util.FillOutStructureHelper;
import ghidra.app.decompiler.util.FillOutStructureHelper.OffsetPcodeOpPair;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReference;
@ -1135,10 +1133,7 @@ public class RecoveredClassHelper {
highVariables
.addAll(getVariableThatStoresVftablePointer(highFunction, firstVftableReference));
DecompileOptions decompileOptions =
DecompilerUtils.getDecompileOptions(serviceProvider, program);
FillOutStructureHelper fillStructHelper =
new FillOutStructureHelper(program, decompileOptions, monitor);
FillOutStructureHelper fillStructHelper = new FillOutStructureHelper(program, monitor);
Address vftableAddress = null;
for (HighVariable highVariable : highVariables) {
@ -1146,7 +1141,8 @@ public class RecoveredClassHelper {
monitor.checkCancelled();
Structure structure =
fillStructHelper.processStructure(highVariable, function, true, false);
fillStructHelper.processStructure(highVariable, function, true, false,
decompilerUtils.getDecompilerInterface());
NoisyStructureBuilder componentMap = fillStructHelper.getComponentMap();
@ -1202,13 +1198,12 @@ public class RecoveredClassHelper {
Address address = getTargetAddressFromPcodeOp(pcodeOp);
if (address.equals(vftableReference)) {
Varnode[] inputs = pcodeOp.getInputs();
for (Varnode input : inputs) {
monitor.checkCancelled();
if (input.getHigh() != null) {
highVars.add(input.getHigh());
}
Varnode input = pcodeOp.getInput(1);
if (input.getDef() != null && input.getDef().getOpcode() == PcodeOp.CAST) {
input = input.getDef().getInput(0);
}
if (input.getHigh() != null) {
highVars.add(input.getHigh());
}
}
}
@ -5855,16 +5850,13 @@ public class RecoveredClassHelper {
highVariables
.addAll(getVariableThatStoresVftablePointer(highFunction, firstVftableReference));
DecompileOptions decompileOptions =
DecompilerUtils.getDecompileOptions(serviceProvider, program);
FillOutStructureHelper fillStructHelper =
new FillOutStructureHelper(program, decompileOptions, monitor);
FillOutStructureHelper fillStructHelper = new FillOutStructureHelper(program, monitor);
for (HighVariable highVariable : highVariables) {
monitor.checkCancelled();
fillStructHelper.processStructure(highVariable, function, true, false);
fillStructHelper.processStructure(highVariable, function, true, false, null);
List<OffsetPcodeOpPair> stores = fillStructHelper.getStorePcodeOps();
stores = removePcodeOpsNotInFunction(function, stores);

View File

@ -4370,7 +4370,8 @@
<title>Middle-Click</title>
<para>
Highlights every occurrence of a variable, constant, or operator represented by the selected
token, within the Decompiler window.
token, within the Decompiler window. There are actions available from the popup menu and from
the keyboard to navigate to each highlighted token.
</para>
</sect2>
@ -4770,6 +4771,17 @@
</para>
</sect2>
<sect2 id="GoToMiddleMouseHighlight">
<title>Go To Next/Previous Highlight</title>
<para>
These actions are available from the popup menu and keyboard. Only tokens highlighted from the
middle-mouse will be navigated. <emphasis role="bold">Shift-Comma</emphasis> will go to the
previous highlighted token. <emphasis role="bold">Shift-Period</emphasis> will go to the
next highlighted token. These key bindings can be changed via the
<link xlink:href="help/topics/Tool/ToolOptions_Dialog.htm#KeyBindings_Option">Tool Options Dialog</link>.
</para>
</sect2>
<sect2 id="ActionHighlight">
<title>Highlight</title>
<para>

View File

@ -533,8 +533,10 @@
<p>
Highlights every occurrence of a variable, constant, or operator represented by the selected
token, within the Decompiler window.
token, within the Decompiler window. There are actions available from the popup menu and from
the keyboard to navigate to each highlighted token.
</p>
</div>
</div>
@ -958,6 +960,19 @@
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="GoToMiddleMouseHighlight"></a>Go To Next/Previous Highlight</h3></div></div></div>
<p>
These actions are available from the popup menu and keyboard. Only tokens highlighted from the
middle-mouse will be navigated. <span class="bold"><strong>Shift-Comma</strong></span> will
go to the previous highlighted token. <span class="bold"><strong>Shift-Period</strong></span>
will go to the next highlighted token. These key bindings can be changed via the
<a class="ulink" href="help/topics/Tool/ToolOptions_Dialog.htm#KeyBindings_Option" target="_top">Tool Options Dialog</a>.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="ActionHighlight"></a>Highlight</h3></div></div></div>

View File

@ -25,7 +25,7 @@ import java.util.*;
*/
public class ClangLine {
private int indent_level;
private ArrayList<ClangToken> tokens;
private List<ClangToken> tokens;
private int lineNumber;
public ClangLine(int lineNumber, int indent) {
@ -35,7 +35,7 @@ public class ClangLine {
}
public String getIndentString() {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < indent_level; i++) {
buffer.append(PrettyPrinter.INDENT_STRING);
}
@ -51,7 +51,7 @@ public class ClangLine {
tok.setLineParent(this);
}
public ArrayList<ClangToken> getAllTokens() {
public List<ClangToken> getAllTokens() {
return tokens;
}
@ -95,7 +95,6 @@ public class ClangLine {
if (isCallout) {
buffy.append(end);
}
}
return buffy.toString();

View File

@ -37,7 +37,7 @@ public class PrettyPrinter {
private Function function;
private ClangTokenGroup tokgroup;
private ArrayList<ClangLine> lines = new ArrayList<>();
private List<ClangLine> lines = new ArrayList<>();
private NameTransformer transformer;
/**
@ -58,7 +58,7 @@ public class PrettyPrinter {
private void padEmptyLines() {
for (ClangLine line : lines) {
ArrayList<ClangToken> tokenList = line.getAllTokens();
List<ClangToken> tokenList = line.getAllTokens();
if (tokenList.size() == 0) {
ClangToken spacer = ClangToken.buildSpacer(null, line.getIndent(), INDENT_STRING);
spacer.setLineParent(line);
@ -72,11 +72,11 @@ public class PrettyPrinter {
}
/**
* Returns an array list of the C language lines contained in the
* Returns a list of the C language lines contained in the
* C language token group.
* @return an array list of the C language lines
* @return a list of the C language lines
*/
public ArrayList<ClangLine> getLines() {
public List<ClangLine> getLines() {
return lines;
}
@ -86,8 +86,7 @@ public class PrettyPrinter {
* @return a string of readable C code
*/
public DecompiledFunction print() {
StringBuffer buff = new StringBuffer();
StringBuilder buff = new StringBuilder();
for (ClangLine line : lines) {
buff.append(line.getIndentString());
List<ClangToken> tokens = line.getAllTokens();

View File

@ -15,8 +15,7 @@
*/
package ghidra.app.decompiler;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.*;
/**
* An iterator over ClangToken objects. The iterator walks a tree of ClangNode objects based on
@ -126,7 +125,7 @@ public class TokenIterator implements Iterator<ClangToken> {
* @param forward is true for a forward iterator, false for a backward iterator
*/
public TokenIterator(ClangToken token, boolean forward) {
ArrayList<ClangTokenGroup> groupList = new ArrayList<>();
List<ClangTokenGroup> groupList = new ArrayList<>();
ClangNode node = token.Parent();
while (node != null) {
groupList.add((ClangTokenGroup) node);
@ -154,7 +153,7 @@ public class TokenIterator implements Iterator<ClangToken> {
* @param forward is true for a forward iterator, false for a backward iterator
*/
public TokenIterator(ClangTokenGroup group, boolean forward) {
ArrayList<ClangTokenGroup> groupList = new ArrayList<>();
List<ClangTokenGroup> groupList = new ArrayList<>();
ClangNode node = group;
while (node instanceof ClangTokenGroup) {
ClangTokenGroup curGroup = (ClangTokenGroup) node;

View File

@ -19,6 +19,7 @@ import java.awt.Color;
import java.util.*;
import java.util.function.Supplier;
import generic.json.Json;
import ghidra.app.decompiler.*;
/**
@ -95,7 +96,7 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter {
}
Supplier<? extends Collection<ClangToken>> tokens = () -> highlights.keySet();
ColorProvider colorProvider = t -> highlights.get(t);
ColorProvider colorProvider = new MappedTokenColorProvider(highlights);
decompilerPanel.addHighlighterHighlights(this, tokens, colorProvider);
clones.forEach(c -> c.applyHighlights());
@ -155,6 +156,25 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter {
@Override
public String toString() {
return super.toString() + ' ' + id;
return Json.toString(this, "matcher", "id");
}
private class MappedTokenColorProvider implements ColorProvider {
private Map<ClangToken, Color> highlights;
MappedTokenColorProvider(Map<ClangToken, Color> highlights) {
this.highlights = highlights;
}
@Override
public Color getColor(ClangToken token) {
return highlights.get(token);
}
@Override
public String toString() {
return "Token Matcher Color " + matcher.toString();
}
}
}

View File

@ -20,8 +20,6 @@ import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.collections4.map.LazyMap;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
@ -42,7 +40,7 @@ import util.CollectionUtils;
*
* <p>This class maintains the following types of highlights:
* <UL>
* <LI>Primary Highlights - triggered by user clicking and some user actions; considered transient
* <LI> Context Highlights - triggered by user clicking and some user actions; considered transient
* and get cleared whenever the location changes. These highlights show state such as the
* current field, impact of a variable (via a slicing action), or related syntax (such as
* matching braces)
@ -77,21 +75,8 @@ public abstract class ClangHighlightController {
protected Color defaultHighlightColor = DEFAULT_HIGHLIGHT_COLOR;
protected Color defaultParenColor = DEFAULT_HIGHLIGHT_COLOR;
private TokenHighlights primaryHighlightTokens = new TokenHighlights();
private Map<Function, List<ClangDecompilerHighlighter>> secondaryHighlightersbyFunction =
LazyMap.lazyMap(new HashMap<>(), f -> new ArrayList<>());
// store the secondary highlighters here in addition to the map below so that we may discern
// between secondary highlights and highlight service highlights
private Set<ClangDecompilerHighlighter> secondaryHighlighters = new HashSet<>();
// all highlighters, including secondary and highlight service highlighters
private Map<ClangDecompilerHighlighter, TokenHighlights> highlighterHighlights =
new HashMap<>();
// color supplier for secondary highlights
private TokenHighlightColors secondaryHighlightColors = new TokenHighlightColors();
private TokenHighlights contextHighlightTokens = new TokenHighlights();
private UserHighlights userHighlights = new UserHighlights();
/**
* A counter to track updates so that clients know when a buffered highlight request is invalid
@ -113,21 +98,8 @@ public abstract class ClangHighlightController {
* color.
* @return the color provider
*/
public ColorProvider getRandomColorProvider() {
return token -> secondaryHighlightColors.getColor(token.getText());
}
/**
* Returns the token that has the primary highlight applied, if any. If multiple tokens are
* highlighted, then the return value is arbitrary.
* @return the highlighted text
*/
public String getPrimaryHighlightedText() {
ClangToken highlightedToken = getHighlightedToken();
if (highlightedToken != null) {
return highlightedToken.getText();
}
return null;
public ColorProvider getGeneratedColorProvider() {
return new GeneratedColorProvider();
}
/**
@ -139,8 +111,8 @@ public abstract class ClangHighlightController {
return updateId;
}
public boolean hasPrimaryHighlight(ClangToken token) {
return primaryHighlightTokens.contains(token);
public boolean hasContextHighlight(ClangToken token) {
return contextHighlightTokens.contains(token);
}
public boolean hasSecondaryHighlight(ClangToken token) {
@ -148,26 +120,19 @@ public abstract class ClangHighlightController {
}
public boolean hasSecondaryHighlights(Function function) {
return !secondaryHighlightersbyFunction.get(function).isEmpty();
return userHighlights.hasSecondaryHighlights(function);
}
public Color getSecondaryHighlight(ClangToken token) {
DecompilerHighlighter highlighter = getSecondaryHighlighter(token);
if (highlighter != null) {
TokenHighlights highlights = highlighterHighlights.get(highlighter);
HighlightToken hlToken = highlights.get(token);
return hlToken.getColor();
}
return null;
return userHighlights.getSecondaryHighlight(token);
}
public TokenHighlightColors getSecondaryHighlightColors() {
return secondaryHighlightColors;
return userHighlights.getSecondaryHighlightColors();
}
public TokenHighlights getPrimaryHighlights() {
return primaryHighlightTokens;
return contextHighlightTokens;
}
/**
@ -177,8 +142,8 @@ public abstract class ClangHighlightController {
* @param function the function
* @return the highlighters
*/
public Set<ClangDecompilerHighlighter> getSecondaryHighlighters(Function function) {
return new HashSet<>(secondaryHighlightersbyFunction.get(function));
public Set<DecompilerHighlighter> getSecondaryHighlighters(Function function) {
return userHighlights.getSecondaryHighlighters(function);
}
/**
@ -187,11 +152,8 @@ public abstract class ClangHighlightController {
* function-specific.
* @return the highlighters
*/
public Set<ClangDecompilerHighlighter> getGlobalHighlighters() {
Set<ClangDecompilerHighlighter> allHighlighters = highlighterHighlights.keySet();
Set<ClangDecompilerHighlighter> results = new HashSet<>(allHighlighters);
results.removeAll(secondaryHighlighters);
return results;
public Set<DecompilerHighlighter> getGlobalHighlighters() {
return userHighlights.getGlobalHighlighters();
}
/**
@ -201,7 +163,7 @@ public abstract class ClangHighlightController {
* @see #getPrimaryHighlights()
*/
public TokenHighlights getHighlighterHighlights(DecompilerHighlighter highlighter) {
return highlighterHighlights.get(highlighter);
return userHighlights.getHighlights(highlighter);
}
/**
@ -209,8 +171,8 @@ public abstract class ClangHighlightController {
* @return token or null
*/
public ClangToken getHighlightedToken() {
if (primaryHighlightTokens.size() == 1) {
HighlightToken hlToken = CollectionUtils.any(primaryHighlightTokens);
if (contextHighlightTokens.size() == 1) {
HighlightToken hlToken = CollectionUtils.any(contextHighlightTokens);
return hlToken.getToken();
}
return null;
@ -236,7 +198,7 @@ public abstract class ClangHighlightController {
updateHighlightColor(token);
};
doClearHighlights(primaryHighlightTokens, clearAll);
doClearHighlights(contextHighlightTokens, clearAll);
notifyListeners();
}
@ -262,10 +224,9 @@ public abstract class ClangHighlightController {
* @param tokens the tokens
*/
public void togglePrimaryHighlights(Color hlColor, Supplier<List<ClangToken>> tokens) {
boolean isAllHighlighted = true;
for (ClangToken otherToken : tokens.get()) {
if (!hasPrimaryHighlight(otherToken)) {
for (ClangToken token : tokens.get()) {
if (!hasContextHighlight(token)) {
isAllHighlighted = false;
break;
}
@ -276,8 +237,7 @@ public abstract class ClangHighlightController {
clearPrimaryHighlights();
if (isAllHighlighted) {
// nothing to do; we toggled from 'all on' to 'all off'
return;
return; // nothing to do; we toggled from 'all on' to 'all off'
}
addPrimaryHighlights(tokens, hlColor);
@ -289,9 +249,11 @@ public abstract class ClangHighlightController {
*/
public void removeSecondaryHighlights(Function f) {
List<ClangDecompilerHighlighter> highlighters = secondaryHighlightersbyFunction.get(f);
for (ClangDecompilerHighlighter highlighter : highlighters) {
TokenHighlights highlights = highlighterHighlights.get(highlighter);
List<DecompilerHighlighter> highlighters =
userHighlights.getSecondaryHighlightersByFunction(f);
for (DecompilerHighlighter highlighter : highlighters) {
TokenHighlights highlights = userHighlights.getHighlights(highlighter);
Consumer<ClangToken> clearHighlight = token -> updateHighlightColor(token);
doClearHighlights(highlights, clearHighlight);
}
@ -305,38 +267,16 @@ public abstract class ClangHighlightController {
* @see #removeSecondaryHighlights(Function)
*/
public void removeSecondaryHighlights(ClangToken token) {
DecompilerHighlighter highlighter = getSecondaryHighlighter(token);
DecompilerHighlighter highlighter = userHighlights.getSecondaryHighlighter(token);
if (highlighter != null) {
highlighter.dispose(); // this will call removeHighlighterHighlights()
}
notifyListeners();
}
private DecompilerHighlighter getSecondaryHighlighter(ClangToken token) {
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
TokenHighlights highlights = highlighterHighlights.get(highlighter);
HighlightToken hlToken = highlights.get(token);
if (hlToken != null) {
return highlighter;
}
}
return null;
}
public void removeHighlighter(DecompilerHighlighter highlighter) {
removeHighlighterHighlights(highlighter);
highlighterHighlights.remove(highlighter);
secondaryHighlighters.remove(highlighter);
Collection<List<ClangDecompilerHighlighter>> lists =
secondaryHighlightersbyFunction.values();
for (List<ClangDecompilerHighlighter> highlighters : lists) {
if (highlighters.remove(highlighter)) {
break;
}
}
userHighlights.remove(highlighter);
}
/**
@ -345,7 +285,7 @@ public abstract class ClangHighlightController {
*/
public void removeHighlighterHighlights(DecompilerHighlighter highlighter) {
TokenHighlights highlighterTokens = highlighterHighlights.get(highlighter);
TokenHighlights highlighterTokens = userHighlights.get(highlighter);
if (highlighterTokens == null) {
return;
}
@ -361,59 +301,56 @@ public abstract class ClangHighlightController {
* @param function the function
* @param highlighter the highlighter
*/
public void addSecondaryHighlighter(Function function, ClangDecompilerHighlighter highlighter) {
// Note: this highlighter has likely already been added the the this class, but has not
// yet been bound to the given function.
secondaryHighlightersbyFunction.get(function).add(highlighter);
secondaryHighlighters.add(highlighter);
highlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
public void addSecondaryHighlighter(Function function, DecompilerHighlighter highlighter) {
userHighlights.addSecondaryHighlighter(function, highlighter);
}
// Note: this is used for all highlight types, secondary and highlighter service highlighters
public void addHighlighter(ClangDecompilerHighlighter highlighter) {
highlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
userHighlights.add(highlighter);
}
// Note: this is used for all highlight types, secondary and highlighter service highlights
public void addHighlighterHighlights(ClangDecompilerHighlighter highlighter,
Supplier<? extends Collection<ClangToken>> tokens,
ColorProvider colorProvider) {
public void addHighlighterHighlights(DecompilerHighlighter highlighter,
Supplier<? extends Collection<ClangToken>> tokens, ColorProvider colorProvider) {
Objects.requireNonNull(highlighter);
TokenHighlights highlighterTokens =
highlighterHighlights.computeIfAbsent(highlighter, k -> new TokenHighlights());
TokenHighlights highlighterTokens = userHighlights.add(highlighter);
addTokensToHighlights(tokens.get(), colorProvider, highlighterTokens);
}
private void addPrimaryHighlights(Supplier<? extends Collection<ClangToken>> tokens,
Color hlColor) {
ColorProvider colorProvider = token -> hlColor;
addTokensToHighlights(tokens.get(), colorProvider, primaryHighlightTokens);
addPrimaryHighlights(tokens.get(), hlColor);
}
private void addPrimaryHighlights(Collection<ClangToken> tokens, Color hlColor) {
ColorProvider colorProvider = new DefaultColorProvider("Tokens Highlight Color", hlColor);
addTokensToHighlights(tokens, colorProvider, contextHighlightTokens);
}
public void addPrimaryHighlights(ClangNode parentNode, Set<PcodeOp> ops, Color hlColor) {
addPrimaryHighlights(parentNode, token -> {
PcodeOp op = token.getPcodeOp();
return ops.contains(op) ? hlColor : null;
});
ColorProvider colorProvider = new DefaultColorProvider("PcodeOp Highlight Color", hlColor) {
@Override
public Color getColor(ClangToken token) {
PcodeOp op = token.getPcodeOp();
return ops.contains(op) ? hlColor : null;
}
};
addPrimaryHighlights(parentNode, colorProvider);
}
public void addPrimaryHighlights(ClangNode parentNode, ColorProvider colorProvider) {
Set<ClangToken> tokens = new HashSet<>();
gatherAllTokens(parentNode, tokens);
addTokensToHighlights(tokens, colorProvider::getColor, primaryHighlightTokens);
addTokensToHighlights(tokens, colorProvider, contextHighlightTokens);
}
private void addPrimaryHighlights(Collection<ClangToken> tokens, Color hlColor) {
ColorProvider colorProvider = token -> hlColor;
addTokensToHighlights(tokens, colorProvider, primaryHighlightTokens);
}
private void addTokensToHighlights(Collection<ClangToken> tokens,
ColorProvider colorProvider, TokenHighlights currentHighlights) {
private void addTokensToHighlights(Collection<ClangToken> tokens, ColorProvider colorProvider,
TokenHighlights currentHighlights) {
updateId++;
@ -441,7 +378,7 @@ public abstract class ClangHighlightController {
}
private void updateHighlightColor(ClangToken t) {
// set the color to the current combined value of both highlight types
// set the color to the current combined value of all highlight types
Color combinedColor = getCombinedColor(t);
t.setHighlight(combinedColor);
}
@ -469,7 +406,7 @@ public abstract class ClangHighlightController {
// note: not sure whether we should always blend all colors or decide to allow some
// highlighters have precedence for highlighting
HighlightToken primaryHl = primaryHighlightTokens.get(t);
HighlightToken primaryHl = contextHighlightTokens.get(t);
Color blendedHlColor = blendHighlighterColors(t);
List<Color> allColors = new ArrayList<>();
@ -506,12 +443,12 @@ public abstract class ClangHighlightController {
return null; // not sure if this can happen
}
Set<ClangDecompilerHighlighter> global = getGlobalHighlighters();
Set<ClangDecompilerHighlighter> secondary = getSecondaryHighlighters(function);
Iterable<ClangDecompilerHighlighter> it = CollectionUtils.asIterable(global, secondary);
Set<DecompilerHighlighter> global = getGlobalHighlighters();
Set<DecompilerHighlighter> secondary = getSecondaryHighlighters(function);
Iterable<DecompilerHighlighter> it = CollectionUtils.asIterable(global, secondary);
Color lastColor = null;
for (ClangDecompilerHighlighter highlighter : it) {
TokenHighlights highlights = highlighterHighlights.get(highlighter);
for (DecompilerHighlighter highlighter : it) {
TokenHighlights highlights = userHighlights.get(highlighter);
HighlightToken hlToken = highlights.get(token);
if (hlToken == null) {
continue;
@ -542,6 +479,24 @@ public abstract class ClangHighlightController {
return highFunction.getFunction();
}
protected void addPrimaryHighlightToTokensForBrace(ClangSyntaxToken token,
Color highlightColor) {
if (DecompilerUtils.isBrace(token)) {
highlightBrace(token, highlightColor);
notifyListeners();
}
}
private void highlightBrace(ClangSyntaxToken startToken, Color highlightColor) {
ClangSyntaxToken matchingBrace = DecompilerUtils.getMatchingBrace(startToken);
if (matchingBrace != null) {
matchingBrace.setMatchingToken(true); // this is a signal to the painter
addPrimaryHighlights(Set.of(matchingBrace), highlightColor);
}
}
/**
* If input token is a parenthesis, highlight all tokens between it and its match
* @param tok potential parenthesis token
@ -609,23 +564,6 @@ public abstract class ClangHighlightController {
return results;
}
public void addBraceHighlight(ClangSyntaxToken token, Color highlightColor) {
if (DecompilerUtils.isBrace(token)) {
highlightBrace(token, highlightColor);
notifyListeners();
}
}
private void highlightBrace(ClangSyntaxToken startToken, Color highlightColor) {
ClangSyntaxToken matchingBrace = DecompilerUtils.getMatchingBrace(startToken);
if (matchingBrace != null) {
matchingBrace.setMatchingToken(true); // this is a signal to the painter
addPrimaryHighlights(Set.of(matchingBrace), highlightColor);
}
}
public void addListener(ClangHighlightListener listener) {
listeners.add(listener);
}
@ -642,9 +580,21 @@ public abstract class ClangHighlightController {
public void dispose() {
listeners.clear();
primaryHighlightTokens.clear();
secondaryHighlighters.clear();
secondaryHighlightersbyFunction.clear();
highlighterHighlights.clear();
contextHighlightTokens.clear();
userHighlights.dispose();
}
private class GeneratedColorProvider implements ColorProvider {
@Override
public Color getColor(ClangToken token) {
return userHighlights.getSecondaryColor(token.getText());
}
@Override
public String toString() {
return "Generated Color Provider " + userHighlights.getAppliedColorsString();
}
}
}

View File

@ -19,11 +19,7 @@ import java.awt.*;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.*;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.SearchLocation;
import docking.widgets.fieldpanel.Layout;
import docking.widgets.fieldpanel.LayoutModel;
import docking.widgets.fieldpanel.field.*;
@ -31,16 +27,12 @@ import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.*;
import ghidra.app.decompiler.*;
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
import ghidra.app.util.viewer.field.CommentUtils;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.HighFunction;
import ghidra.util.Msg;
/**
*
*
* Control the GUI layout for displaying tokenized C code
*/
public class ClangLayoutController implements LayoutModel, LayoutModelListener {
@ -53,10 +45,10 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
private Field[] fieldList; // Array of fields comprising layout
private FontMetrics metrics;
private FieldHighlightFactory hlFactory;
private ArrayList<LayoutModelListener> listeners;
private List<LayoutModelListener> listeners;
private Color[] syntaxColor; // Foreground colors.
private BigInteger numIndexes = BigInteger.ZERO;
private ArrayList<ClangLine> lines = new ArrayList<>();
private List<ClangLine> lines = new ArrayList<>();
private boolean showLineNumbers = true;
@ -71,7 +63,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
buildLayouts(null, null, null, false);
}
public ArrayList<ClangLine> getLines() {
public List<ClangLine> getLines() {
return lines;
}
@ -248,7 +240,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
}
}
private void splitToMaxWidthLines(ArrayList<String> res, String line) {
private void splitToMaxWidthLines(List<String> res, String line) {
int maxchar;
if ((maxWidth == 0) || (indentWidth == 0)) {
maxchar = 40;
@ -257,7 +249,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
maxchar = maxWidth / indentWidth;
}
String[] toklist = line.split("[ \t]+");
StringBuffer buf = new StringBuffer();
StringBuilder buf = new StringBuilder();
int cursize = 0;
boolean atleastone = false;
int i = 0;
@ -275,7 +267,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
res.add(finishLine);
cursize = 5;
atleastone = false;
buf = new StringBuffer();
buf = new StringBuilder();
buf.append(" ");
}
else {
@ -302,7 +294,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
return false; // No error message to add
}
String[] errlines_init = errmsg.split("[\n\r]+");
ArrayList<String> errlines = new ArrayList<>();
List<String> errlines = new ArrayList<>();
for (String element : errlines_init) {
splitToMaxWidthLines(errlines, element);
}
@ -337,255 +329,13 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
return null;
}
//==================================================================================================
// Search Related Methods
//==================================================================================================
private SearchLocation findNextTokenGoingForward(
java.util.function.Function<String, SearchMatch> matcher, String searchString,
FieldLocation currentLocation) {
int row = currentLocation.getIndex().intValue();
for (int i = row; i < fieldList.length; i++) {
ClangTextField field = (ClangTextField) fieldList[i];
String partialLine =
getTextLineFromOffset((i == row) ? currentLocation : null, field, true);
SearchMatch match = matcher.apply(partialLine);
if (match == SearchMatch.NO_MATCH) {
continue;
}
if (i == row) { // cursor is on this line
//
// The match start for all lines without the cursor will be relative to the start
// of the line, which is 0. However, when searching on the row with the cursor,
// the match start is relative to the cursor position. Update the start to
// compensate for the difference between the start of the line and the cursor.
//
String fullLine = field.getText();
int cursorOffset = fullLine.length() - partialLine.length();
match.start += cursorOffset;
match.end += cursorOffset;
}
FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field);
FieldLocation fieldLocation =
new FieldLocation(i, pair.getFieldNumber(), 0, pair.getColumn());
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
searchString, true);
}
return null;
}
private SearchLocation findNextTokenGoingBackward(
java.util.function.Function<String, SearchMatch> matcher, String searchString,
FieldLocation currentLocation) {
int row = currentLocation.getIndex().intValue();
for (int i = row; i >= 0; i--) {
ClangTextField field = (ClangTextField) fieldList[i];
String textLine =
getTextLineFromOffset((i == row) ? currentLocation : null, field, false);
SearchMatch match = matcher.apply(textLine);
if (match != SearchMatch.NO_MATCH) {
FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field);
FieldLocation fieldLocation =
new FieldLocation(i, pair.getFieldNumber(), 0, pair.getColumn());
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
searchString, false);
}
}
return null;
}
public SearchLocation findNextTokenForSearchRegex(String searchString,
FieldLocation currentLocation, boolean forwardSearch) {
Pattern pattern = null;
try {
pattern = Pattern.compile(searchString, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
}
catch (PatternSyntaxException e) {
Msg.showError(this, decompilerPanel, "Regular Expression Syntax Error", e.getMessage());
return null;
}
Pattern finalPattern = pattern;
if (forwardSearch) {
java.util.function.Function<String, SearchMatch> function = textLine -> {
Matcher matcher = finalPattern.matcher(textLine);
if (matcher.find()) {
int start = matcher.start();
int end = matcher.end();
return new SearchMatch(start, end, textLine);
}
return SearchMatch.NO_MATCH;
};
return findNextTokenGoingForward(function, searchString, currentLocation);
}
java.util.function.Function<String, SearchMatch> reverse = textLine -> {
Matcher matcher = finalPattern.matcher(textLine);
if (!matcher.find()) {
return SearchMatch.NO_MATCH;
}
int start = matcher.start();
int end = matcher.end();
// Since the matcher can only match from the start to end of line, we need
// to find all matches and then take the last match
while (matcher.find()) {
start = matcher.start();
end = matcher.end();
}
return new SearchMatch(start, end, textLine);
};
return findNextTokenGoingBackward(reverse, searchString, currentLocation);
}
public SearchLocation findNextTokenForSearch(String searchString, FieldLocation currentLocation,
boolean forwardSearch) {
if (forwardSearch) {
java.util.function.Function<String, SearchMatch> function = textLine -> {
int index = StringUtils.indexOfIgnoreCase(textLine, searchString);
if (index == -1) {
return SearchMatch.NO_MATCH;
}
return new SearchMatch(index, index + searchString.length(), textLine);
};
return findNextTokenGoingForward(function, searchString, currentLocation);
}
java.util.function.Function<String, SearchMatch> function = textLine -> {
int index = StringUtils.lastIndexOfIgnoreCase(textLine, searchString);
if (index == -1) {
return SearchMatch.NO_MATCH;
}
return new SearchMatch(index, index + searchString.length(), textLine);
};
return findNextTokenGoingBackward(function, searchString, currentLocation);
}
private String getTextLineFromOffset(FieldLocation location, ClangTextField textField,
boolean forwardSearch) {
if (location == null) { // the cursor location is not on this line; use all of the text
return textField.getText();
}
if (textField.getText().isEmpty()) { // the cursor is on blank line
return "";
}
String partialText = textField.getText();
if (forwardSearch) {
int nextCol = location.getCol();
// protects against the location column being out of range (this can happen if we're
// searching forward and the cursor is past the last token)
if (nextCol >= partialText.length()) {
return "";
}
// skip a character to start the next search; this prevents matching the previous match
return partialText.substring(nextCol);
}
// backwards search
return partialText.substring(0, location.getCol());
}
private FieldNumberColumnPair getFieldIndexFromOffset(int screenOffset,
ClangTextField textField) {
RowColLocation rowColLocation = textField.textOffsetToScreenLocation(screenOffset);
// we use 0 here because currently there is only one field, which is the entire line
return new FieldNumberColumnPair(0, rowColLocation.col());
}
private static class SearchMatch {
private static SearchMatch NO_MATCH = new SearchMatch(-1, -1, null);
private int start;
private int end;
private String textLine;
SearchMatch(int start, int end, String textLine) {
this.start = start;
this.end = end;
this.textLine = textLine;
}
@Override
public String toString() {
if (this == NO_MATCH) {
return "NO MATCH";
}
return "[start=" + start + ",end=" + end + "]: " + textLine;
}
}
//==================================================================================================
// End Search Related Methods
//==================================================================================================
ClangToken getTokenForLocation(FieldLocation fieldLocation) {
int row = fieldLocation.getIndex().intValue();
ClangTextField field = (ClangTextField) fieldList[row];
return field.getToken(fieldLocation);
}
public void locationChanged(FieldLocation loc, Field field, Color locationColor,
Color parenColor) {
// Highlighting is now handled through the decompiler panel's highlight controller.
}
public boolean changePending() {
return false;
}
@Override
public void flushChanges() {
// nothing to do
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class FieldNumberColumnPair {
private final int fieldNumber;
private final int column;
FieldNumberColumnPair(int fieldNumber, int column) {
this.fieldNumber = fieldNumber;
this.column = column;
}
int getFieldNumber() {
return fieldNumber;
}
int getColumn() {
return column;
}
}
}

View File

@ -18,8 +18,8 @@ package ghidra.app.decompiler.component;
import java.util.List;
import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.FieldHighlightFactory;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.app.decompiler.ClangToken;
public class ClangTextField extends WrappingVerticalLayoutTextField {
@ -123,4 +123,12 @@ public class ClangTextField extends WrappingVerticalLayoutTextField {
public int getLineNumber() {
return lineNumber;
}
public ClangToken getFirstToken() {
return tokenList.get(0);
}
public ClangToken getLastToken() {
return tokenList.get(tokenList.size() - 1);
}
}

View File

@ -0,0 +1,219 @@
/* ###
* 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.decompiler.component;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.ListSelectionModel;
import docking.DockingWindowManager;
import docking.Tool;
import docking.widgets.FindDialog;
import docking.widgets.SearchLocation;
import docking.widgets.button.GButton;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.table.AbstractDynamicTableColumnStub;
import docking.widgets.table.TableColumnDescriptor;
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
import ghidra.app.plugin.core.decompile.actions.DecompilerSearcher;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.util.HelpTopics;
import ghidra.app.util.query.TableService;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.*;
import ghidra.util.task.TaskMonitor;
public class DecompilerFindDialog extends FindDialog {
private DecompilerPanel decompilerPanel;
public DecompilerFindDialog(DecompilerPanel decompilerPanel) {
super("Decompiler Find Text", new DecompilerSearcher(decompilerPanel));
this.decompilerPanel = decompilerPanel;
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
GButton showAllButton = new GButton("Search All");
showAllButton.addActionListener(e -> showAll());
// move this button to the end
removeButton(dismissButton);
addButton(showAllButton);
addButton(dismissButton);
}
private void showAll() {
close();
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
Tool tool = dwm.getTool();
TableService tableService = tool.getService(TableService.class);
if (tableService == null) {
Msg.error(this,
"Cannot use the Decompiler Search All action without having a TableService " +
"installed");
return;
}
List<SearchLocation> results = searcher.searchAll(getSearchText(), useRegex());
if (!results.isEmpty()) {
// save off searches that find results so users can reuse them later
storeSearchText(getSearchText());
}
Program program = decompilerPanel.getProgram();
DecompilerFindResultsModel model = new DecompilerFindResultsModel(tool, program, results);
String title = "Decompiler Search '%s'".formatted(getSearchText());
String type = "Decompiler Search ";
String subMenuName = "Search";
TableComponentProvider<DecompilerSearchLocation> provider =
tableService.showTable(title, type, model, subMenuName, null);
// The Decompiler does not support some of the table's basic actions, such as making
// selections for a given row, so remove them.
provider.removeAllActions();
provider.installRemoveItemsAction();
GhidraThreadedTablePanel<DecompilerSearchLocation> panel = provider.getThreadedTablePanel();
GhidraTable table = panel.getTable();
// add row listener to go to the field for that row
ListSelectionModel selectionModel = table.getSelectionModel();
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
selectionModel.addListSelectionListener(lse -> {
if (lse.getValueIsAdjusting()) {
return;
}
int row = table.getSelectedRow();
if (row == -1) {
searcher.highlightSearchResults(null);
return;
}
DecompilerSearchLocation location = model.getRowObject(row);
notifySearchHit(location);
});
// add listener to table closed to clear highlights
provider.setClosedCallback(() -> decompilerPanel.setSearchResults(null));
// set the tab text to the short and descriptive search term
provider.setTabText("'%s'".formatted(getSearchText()));
}
@Override
protected void dialogClosed() {
// clear the search results when the dialog is closed
decompilerPanel.setSearchResults(null);
}
//=================================================================================================
// Inner Classes
//=================================================================================================
private class DecompilerFindResultsModel
extends GhidraProgramTableModel<DecompilerSearchLocation> {
private List<DecompilerSearchLocation> searchLocations;
DecompilerFindResultsModel(ServiceProvider sp, Program program,
List<SearchLocation> searchLocations) {
super("Decompiler Search All Results", sp, program, null);
this.searchLocations = searchLocations.stream()
.map(l -> (DecompilerSearchLocation) l)
.collect(Collectors.toList());
}
@Override
protected TableColumnDescriptor<DecompilerSearchLocation> createTableColumnDescriptor() {
TableColumnDescriptor<DecompilerSearchLocation> descriptor =
new TableColumnDescriptor<>();
descriptor.addVisibleColumn(new LineNumberColumn(), 1, true);
descriptor.addVisibleColumn(new ContextColumn());
return descriptor;
}
@Override
protected void doLoad(Accumulator<DecompilerSearchLocation> accumulator,
TaskMonitor monitor)
throws CancelledException {
for (DecompilerSearchLocation location : searchLocations) {
accumulator.add(location);
}
}
@Override
public ProgramLocation getProgramLocation(int modelRow, int modelColumn) {
return null; // This doesn't really make sense for this model
}
@Override
public ProgramSelection getProgramSelection(int[] modelRows) {
return new ProgramSelection(); // This doesn't really make sense for this model
}
private class LineNumberColumn
extends AbstractDynamicTableColumnStub<DecompilerSearchLocation, Integer> {
@Override
public Integer getValue(DecompilerSearchLocation rowObject, Settings settings,
ServiceProvider sp) throws IllegalArgumentException {
FieldLocation fieldLocation = rowObject.getFieldLocation();
return fieldLocation.getIndex().intValue() + 1; // +1 for 1-based lines
}
@Override
public String getColumnName() {
return "Line";
}
@Override
public int getColumnPreferredWidth() {
return 75;
}
}
private class ContextColumn
extends AbstractDynamicTableColumnStub<DecompilerSearchLocation, String> {
@Override
public String getValue(DecompilerSearchLocation rowObject, Settings settings,
ServiceProvider sp) throws IllegalArgumentException {
return rowObject.getTextLine();
}
@Override
public String getColumnName() {
return "Context";
}
}
}
}

View File

@ -42,7 +42,7 @@ import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.hover.DecompilerHoverService;
import ghidra.app.decompiler.component.margin.*;
import ghidra.app.plugin.core.decompile.DecompilerClipboardProvider;
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
import ghidra.app.util.viewer.util.ScrollpaneAlignedHorizontalLayout;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Function;
@ -80,7 +80,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
private FieldHighlightFactory hlFactory;
private ClangHighlightController highlightController;
private Map<String, ClangDecompilerHighlighter> highlightersById = new HashMap<>();
private Map<String, DecompilerHighlighter> highlightersById = new HashMap<>();
private PendingHighlightUpdate pendingHighlightUpdate;
private SwingUpdateManager highlighCursorUpdater = new SwingUpdateManager(() -> {
if (pendingHighlightUpdate != null) {
@ -89,6 +89,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
});
private ActiveMiddleMouse activeMiddleMouse;
private int middleMouseHighlightButton;
private Color middleMouseHighlightColor;
private Color currentVariableHighlightColor;
@ -194,7 +195,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return highlightController.getHighlighterHighlights(highligter);
}
private Set<ClangDecompilerHighlighter> getSecondaryHighlihgtersByFunction(Function function) {
public TokenHighlights getMiddleMouseHighlights() {
if (activeMiddleMouse != null) {
return activeMiddleMouse.getHighlights();
}
return null;
}
private Set<DecompilerHighlighter> getSecondaryHighlihgtersByFunction(Function function) {
return highlightController.getSecondaryHighlighters(function);
}
@ -212,31 +220,50 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
public void addSecondaryHighlight(ClangToken token) {
ColorProvider cp = highlightController.getRandomColorProvider();
ColorProvider cp = highlightController.getGeneratedColorProvider();
addSecondaryHighlight(token.getText(), cp);
}
public void addSecondaryHighlight(ClangToken token, Color color) {
ColorProvider cp = t -> color;
ColorProvider cp = new DefaultColorProvider("User Secondary Highlight", color);
addSecondaryHighlight(token.getText(), cp);
}
private void addSecondaryHighlight(String tokenText, ColorProvider colorProvider) {
NameTokenMatcher matcher = new NameTokenMatcher(tokenText, colorProvider);
ClangDecompilerHighlighter highlighter = createHighlighter(matcher);
DecompilerHighlighter highlighter = createHighlighter(matcher);
applySecondaryHighlights(highlighter);
}
private void applySecondaryHighlights(ClangDecompilerHighlighter highlighter) {
private void applySecondaryHighlights(DecompilerHighlighter highlighter) {
Function function = decompileData.getFunction();
highlightController.addSecondaryHighlighter(function, highlighter);
highlighter.applyHighlights();
}
private void togglePrimaryHighlight(FieldLocation location, Field field, Color highlightColor) {
private void toggleMiddleMouseHighlight(FieldLocation location, Field field) {
ClangToken token = ((ClangTextField) field).getToken(location);
Supplier<List<ClangToken>> lazyTokens = () -> findTokensByName(token.getText());
highlightController.togglePrimaryHighlights(middleMouseHighlightColor, lazyTokens);
ColorProvider cp = new MiddleMouseColorProvider();
NameTokenMatcher matcher = new NameTokenMatcher(token.getText(), cp);
ActiveMiddleMouse previousMiddleMouse = activeMiddleMouse;
activeMiddleMouse = null;
if (previousMiddleMouse != null) {
// middle mousing always clears the last middle-mouse highlight
previousMiddleMouse.clear();
if (previousMiddleMouse.matches(token)) {
// middle mousing on the same token clears, but does not create a new highlight
return;
}
}
DecompilerHighlighter newMiddleMouseHighlighter = createHighlighter(matcher);
ActiveMiddleMouse newMiddleMouse = new ActiveMiddleMouse(token, newMiddleMouseHighlighter);
newMiddleMouse.apply();
activeMiddleMouse = newMiddleMouse;
}
void addHighlighterHighlights(ClangDecompilerHighlighter highlighter,
@ -248,14 +275,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
highlightController.removeHighlighterHighlights(highlighter);
}
public ClangDecompilerHighlighter createHighlighter(CTokenHighlightMatcher tm) {
public DecompilerHighlighter createHighlighter(CTokenHighlightMatcher tm) {
UUID uuId = UUID.randomUUID();
String id = uuId.toString();
return createHighlighter(id, tm);
}
public ClangDecompilerHighlighter createHighlighter(String id, CTokenHighlightMatcher tm) {
ClangDecompilerHighlighter currentHighlighter = highlightersById.get(id);
public DecompilerHighlighter createHighlighter(String id, CTokenHighlightMatcher tm) {
DecompilerHighlighter currentHighlighter = highlightersById.get(id);
if (currentHighlighter != null) {
currentHighlighter.dispose();
}
@ -271,7 +298,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
void removeHighlighter(String id) {
ClangDecompilerHighlighter highlighter = highlightersById.remove(id);
DecompilerHighlighter highlighter = highlightersById.remove(id);
highlightController.removeHighlighter(highlighter);
}
@ -339,11 +366,15 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
private void cloneGlobalHighlighters(DecompilerPanel sourcePanel) {
Set<ClangDecompilerHighlighter> globalHighlighters =
Set<DecompilerHighlighter> globalHighlighters =
sourcePanel.highlightController.getGlobalHighlighters();
for (ClangDecompilerHighlighter otherHighlighter : globalHighlighters) {
for (DecompilerHighlighter otherHighlighter : globalHighlighters) {
ClangDecompilerHighlighter newHighlighter = otherHighlighter.clone(this);
if (!(otherHighlighter instanceof ClangDecompilerHighlighter clangHighlighter)) {
continue;
}
DecompilerHighlighter newHighlighter = clangHighlighter.clone(this);
highlightersById.put(newHighlighter.getId(), newHighlighter);
TokenHighlights otherHighlighterTokens =
@ -374,15 +405,20 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
// clone will match the cloned decompiler.
//
Function function = decompileData.getFunction();
Set<ClangDecompilerHighlighter> secondaryHighlighters =
Set<DecompilerHighlighter> secondaryHighlighters =
sourcePanel.getSecondaryHighlihgtersByFunction(function);
//
// We do NOT clone the secondary highlighters. This allows the user the remove them
// from the primary provider without effecting the cloned provider and vice versa.
//
for (ClangDecompilerHighlighter highlighter : secondaryHighlighters) {
ClangDecompilerHighlighter newHighlighter = highlighter.copy(this);
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
if (!(highlighter instanceof ClangDecompilerHighlighter clangHighlighter)) {
continue;
}
DecompilerHighlighter newHighlighter = clangHighlighter.copy(this);
highlightersById.put(newHighlighter.getId(), newHighlighter);
applySecondaryHighlights(newHighlighter);
}
@ -455,9 +491,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return;
}
Set<ClangDecompilerHighlighter> globalHighlighters =
highlightController.getGlobalHighlighters();
for (ClangDecompilerHighlighter highlighter : globalHighlighters) {
Set<DecompilerHighlighter> globalHighlighters = highlightController.getGlobalHighlighters();
for (DecompilerHighlighter highlighter : globalHighlighters) {
highlighter.clearHighlights();
highlighter.applyHighlights();
}
@ -470,9 +505,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return;
}
Set<ClangDecompilerHighlighter> secondaryHighlighters =
Set<DecompilerHighlighter> secondaryHighlighters =
getSecondaryHighlihgtersByFunction(function);
for (ClangDecompilerHighlighter highlighter : secondaryHighlighters) {
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
highlighter.clearHighlights();
highlighter.applyHighlights();
}
@ -719,7 +754,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
if (buttonState == middleMouseHighlightButton && clickCount == 1) {
togglePrimaryHighlight(location, field, middleMouseHighlightColor);
toggleMiddleMouseHighlight(location, field);
}
}
@ -770,19 +805,6 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
controller.goToFunction(function, newWindow);
return;
}
// TODO no idea what this is supposed to be handling...someone doc this please
// String labelName = functionToken.getText();
// if (labelName.startsWith("func_0x")) {
// try {
// Address addr =
// decompileData.getFunction().getEntryPoint().getAddress(labelName.substring(7));
// controller.goToAddress(addr, newWindow);
// }
// catch (AddressFormatException e) {
// controller.goToLabel(labelName, newWindow);
// }
// }
}
private void tryGoToLabel(ClangLabelToken token, boolean newWindow) {
@ -966,33 +988,15 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
location.getIndex().intValue(), location.col);
}
//==================================================================================================
// Search Methods
//==================================================================================================
public SearchLocation searchText(String text, FieldLocation startLocation,
boolean forwardDirection) {
return layoutController.findNextTokenForSearch(text, startLocation, forwardDirection);
}
public SearchLocation searchTextRegex(String text, FieldLocation startLocation,
boolean forwardDirection) {
return layoutController.findNextTokenForSearchRegex(text, startLocation, forwardDirection);
}
public void setSearchResults(SearchLocation searchLocation) {
currentSearchLocation = searchLocation;
repaint();
}
public FieldBasedSearchLocation getSearchResults() {
return (FieldBasedSearchLocation) currentSearchLocation;
public DecompilerSearchLocation getSearchResults() {
return (DecompilerSearchLocation) currentSearchLocation;
}
//==================================================================================================
// End Search Methods
//==================================================================================================
public Color getCurrentVariableHighlightColor() {
return currentVariableHighlightColor;
}
@ -1257,7 +1261,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
int highlightLine = cField.getLineNumber();
FieldLocation searchCursorLocation =
((FieldBasedSearchLocation) currentSearchLocation).getFieldLocation();
((DecompilerSearchLocation) currentSearchLocation).getFieldLocation();
int searchLineNumber = searchCursorLocation.getIndex().intValue() + 1;
if (highlightLine != searchLineNumber) {
// only highlight the match on the actual line
@ -1377,4 +1381,57 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
}
}
private class MiddleMouseColorProvider implements ColorProvider {
@Override
public Color getColor(ClangToken token) {
return middleMouseHighlightColor;
}
@Override
public String toString() {
return "Middle Mouse Color Provider " + middleMouseHighlightColor;
}
}
/**
* A class to track the current middle moused token.
*/
private class ActiveMiddleMouse {
private ClangToken token;
private DecompilerHighlighter highlighter;
ActiveMiddleMouse(ClangToken token, DecompilerHighlighter highlighter) {
this.token = token;
this.highlighter = highlighter;
}
TokenHighlights getHighlights() {
return highlightController.getHighlighterHighlights(highlighter);
}
DecompilerHighlighter getHighlighter() {
return highlighter;
}
boolean matches(ClangToken other) {
return token.getText().equals(other.getText());
}
void clear() {
highlightController.removeHighlighter(highlighter);
}
void apply() {
applySecondaryHighlights(highlighter);
}
@Override
public String toString() {
return "Middle Mouse Token " + token;
}
}
}

View File

@ -33,9 +33,10 @@ import ghidra.program.model.pcode.*;
public class DecompilerUtils {
/**
* Gaither decompiler options from tool and program. If tool is null or does not provide
* Gather decompiler options from tool and program. If tool is null or does not provide
* a {@link OptionsService} provider only options stored within the program will be consumed.
* @param serviceProvider plugin tool or service provider providing access to {@link OptionsService}
* @param serviceProvider plugin tool or service provider providing access to
* {@link OptionsService}
* @param program program
* @return decompiler options
*/
@ -281,7 +282,7 @@ public class DecompilerUtils {
* @param function decompiled function
* @return true if {@code var} corresponds to existing auto {@code this} parameter, else false
*/
public static boolean testForAutoParameterThis(HighVariable var, Function function) {
public static boolean isThisParameter(HighVariable var, Function function) {
if (var instanceof HighParam) {
int slot = ((HighParam) var).getSlot();
Parameter parameter = function.getParameter(slot);
@ -626,7 +627,7 @@ public class DecompilerUtils {
String destinationStart = label.getText() + ':';
Address address = label.getMinAddress();
List<ClangToken> tokens = DecompilerUtils.getTokens(root, address);
List<ClangToken> tokens = getTokens(root, address);
for (ClangToken token : tokens) {
if (isGoToStatement(token)) {
continue; // ignore any goto statements
@ -791,7 +792,7 @@ public class DecompilerUtils {
* @param group is the token hierarchy
* @return the array of ClangLine objects
*/
public static ArrayList<ClangLine> toLines(ClangTokenGroup group) {
public static List<ClangLine> toLines(ClangTokenGroup group) {
List<ClangNode> alltoks = new ArrayList<>();
group.flatten(alltoks);

View File

@ -0,0 +1,49 @@
/* ###
* 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.decompiler.component;
import java.awt.Color;
import ghidra.app.decompiler.ClangToken;
/**
* A color provider that returns a specific color.
*/
public class DefaultColorProvider implements ColorProvider {
private Color color;
private String prefix;
/**
* Constructor
* @param prefix a descriptive prefix used in the {@link #toString()} method
* @param color the color
*/
DefaultColorProvider(String prefix, Color color) {
this.prefix = prefix;
this.color = color;
}
@Override
public Color getColor(ClangToken token) {
return color;
}
@Override
public String toString() {
return prefix + ' ' + color;
}
}

View File

@ -50,7 +50,7 @@ public class LocationClangHighlightController extends ClangHighlightController {
addPrimaryHighlight(tok, defaultHighlightColor);
if (tok instanceof ClangSyntaxToken) {
addPrimaryHighlightToTokensForParenthesis((ClangSyntaxToken) tok, defaultParenColor);
addBraceHighlight((ClangSyntaxToken) tok, defaultParenColor);
addPrimaryHighlightToTokensForBrace((ClangSyntaxToken) tok, defaultParenColor);
}
}
}

View File

@ -17,6 +17,7 @@ package ghidra.app.decompiler.component;
import java.awt.Color;
import generic.json.Json;
import ghidra.app.decompiler.CTokenHighlightMatcher;
import ghidra.app.decompiler.ClangToken;
@ -40,4 +41,9 @@ class NameTokenMatcher implements CTokenHighlightMatcher {
}
return null;
}
@Override
public String toString() {
return Json.toString(this);
}
}

View File

@ -35,11 +35,6 @@ public class NullClangHighlightController extends ClangHighlightController {
// stub
}
@Override
public String getPrimaryHighlightedText() {
return null;
}
@Override
public void addPrimaryHighlights(ClangNode parentNode, ColorProvider colorProvider) {
// stub
@ -51,7 +46,7 @@ public class NullClangHighlightController extends ClangHighlightController {
}
@Override
public void addBraceHighlight(ClangSyntaxToken token, Color highlightColor) {
public void addPrimaryHighlightToTokensForBrace(ClangSyntaxToken token, Color highlightColor) {
// stub
}

View File

@ -25,7 +25,7 @@ import generic.theme.Gui;
*/
public class TokenHighlightColors {
private Map<String, Color> colorsByName = new HashMap<>();
private Map<String, Color> colorsByText = new HashMap<>();
private List<Color> recentColors = new ArrayList<>();
private Color generateColor() {
@ -42,15 +42,27 @@ public class TokenHighlightColors {
}
public Color getColor(String text) {
return colorsByName.computeIfAbsent(text, t -> generateColor());
return colorsByText.computeIfAbsent(text, t -> generateColor());
}
public void setColor(String text, Color color) {
colorsByName.put(text, color);
colorsByText.put(text, color);
recentColors.add(color);
}
public List<Color> getRecentColors() {
return recentColors;
}
public String getAppliedColorsString() {
if (colorsByText.isEmpty()) {
return "No tokens highlighted";
}
return colorsByText.toString();
}
@Override
public String toString() {
return getAppliedColorsString();
}
}

View File

@ -0,0 +1,148 @@
/* ###
* 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.decompiler.component;
import java.awt.Color;
import java.util.*;
import org.apache.commons.collections4.map.LazyMap;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.DecompilerHighlighter;
import ghidra.program.model.listing.Function;
/**
* A class to manage and track Decompiler highlights created by the user via the UI or from a
* script. This class manages secondary and global highlights. For a description of these terms,
* see {@link ClangHighlightController}.
* <p>
* These highlights will remain until cleared explicitly by the user or a client API call.
* Contrastingly, context highlights are cleared as the user moves the cursor around the Decompiler
* display.
*/
public class UserHighlights {
private Map<Function, List<DecompilerHighlighter>> secondaryHighlightersByFunction =
LazyMap.lazyMap(new HashMap<>(), f -> new ArrayList<>());
// store the secondary highlighters here in addition to the map below so that we may discern
// between secondary highlights and highlight service highlights
private Set<DecompilerHighlighter> secondaryHighlighters = new HashSet<>();
// all highlighters, including secondary and global highlight service highlighters
private Map<DecompilerHighlighter, TokenHighlights> allHighlighterHighlights = new HashMap<>();
// color supplier for secondary highlights
private TokenHighlightColors secondaryHighlightColors = new TokenHighlightColors();
Color getSecondaryColor(String text) {
// Note: this call is used to generate colors for secondary highlighters that this API
// creates. Client highlighters will create their own colors.
return secondaryHighlightColors.getColor(text);
}
String getAppliedColorsString() {
return secondaryHighlightColors.getAppliedColorsString();
}
boolean hasSecondaryHighlights(Function function) {
return !secondaryHighlightersByFunction.get(function).isEmpty();
}
Color getSecondaryHighlight(ClangToken token) {
DecompilerHighlighter highlighter = getSecondaryHighlighter(token);
if (highlighter != null) {
TokenHighlights highlights = allHighlighterHighlights.get(highlighter);
HighlightToken hlToken = highlights.get(token);
return hlToken.getColor();
}
return null;
}
TokenHighlightColors getSecondaryHighlightColors() {
return secondaryHighlightColors;
}
Set<DecompilerHighlighter> getSecondaryHighlighters(Function function) {
return new HashSet<>(secondaryHighlightersByFunction.get(function));
}
Set<DecompilerHighlighter> getGlobalHighlighters() {
Set<DecompilerHighlighter> allHighlighters = allHighlighterHighlights.keySet();
Set<DecompilerHighlighter> results = new HashSet<>(allHighlighters);
results.removeAll(secondaryHighlighters);
return results;
}
List<DecompilerHighlighter> getSecondaryHighlightersByFunction(Function f) {
return secondaryHighlightersByFunction.get(f);
}
TokenHighlights getHighlights(DecompilerHighlighter highlighter) {
return allHighlighterHighlights.get(highlighter);
}
DecompilerHighlighter getSecondaryHighlighter(ClangToken token) {
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
TokenHighlights highlights = allHighlighterHighlights.get(highlighter);
HighlightToken hlToken = highlights.get(token);
if (hlToken != null) {
return highlighter;
}
}
return null;
}
void addSecondaryHighlighter(Function function, DecompilerHighlighter highlighter) {
// Note: this highlighter has likely already been added the the this class, but has not
// yet been bound to the given function.
secondaryHighlightersByFunction.get(function).add(highlighter);
secondaryHighlighters.add(highlighter);
allHighlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
}
// This adds the given highlighter. This is for global and secondary highlights. Secondary
// highlights will be later registered to this class for the function they apply to.
TokenHighlights add(DecompilerHighlighter highlighter) {
allHighlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
return allHighlighterHighlights.get(highlighter);
}
void remove(DecompilerHighlighter highlighter) {
allHighlighterHighlights.remove(highlighter);
secondaryHighlighters.remove(highlighter);
Collection<List<DecompilerHighlighter>> lists = secondaryHighlightersByFunction.values();
for (List<DecompilerHighlighter> highlighters : lists) {
if (highlighters.remove(highlighter)) {
break;
}
}
}
TokenHighlights get(DecompilerHighlighter highlighter) {
return allHighlighterHighlights.get(highlighter);
}
void dispose() {
secondaryHighlighters.clear();
secondaryHighlightersByFunction.clear();
allHighlighterHighlights.clear();
}
}

View File

@ -66,23 +66,24 @@ public class FillOutStructureCmd extends BackgroundCommand<Program> {
throw new AssertionError("program does not match location");
}
Function function =
program.getFunctionManager().getFunctionContaining(location.getAddress());
if (function == null) {
setStatusMsg("Function not found at " + location.getAddress());
return false;
}
FillOutStructureHelper fillStructureHelper =
new FillOutStructureHelper(program, monitor);
DecompInterface decompInterface = fillStructureHelper.setUpDecompiler(decompileOptions);
try {
Function function =
program.getFunctionManager().getFunctionContaining(location.getAddress());
if (function == null) {
setStatusMsg("Function not found at " + location.getAddress());
return false;
}
FillOutStructureHelper fillStructureHelper =
new FillOutStructureHelper(program, decompileOptions, monitor);
HighVariable var = null;
if (!(location instanceof DecompilerLocation dloc)) {
// if we don't have one, make one, and map variable to a varnode
Address storageAddr = computeStorageAddress(function);
var = fillStructureHelper.computeHighVariable(storageAddr, function);
var =
fillStructureHelper.computeHighVariable(storageAddr, function, decompInterface);
}
else {
@ -108,7 +109,8 @@ public class FillOutStructureCmd extends BackgroundCommand<Program> {
}
}
Structure structDT = fillStructureHelper.processStructure(var, function, false, true);
Structure structDT =
fillStructureHelper.processStructure(var, function, false, true, decompInterface);
if (structDT == null) {
setStatusMsg("Failed to fill-out structure");
return false;
@ -120,7 +122,7 @@ public class FillOutStructureCmd extends BackgroundCommand<Program> {
pointerDT = program.getDataTypeManager()
.addDataType(pointerDT, DataTypeConflictHandler.DEFAULT_HANDLER);
boolean isThisParam = DecompilerUtils.testForAutoParameterThis(var, function);
boolean isThisParam = DecompilerUtils.isThisParameter(var, function);
if (!isThisParam) {
commitVariable(var, pointerDT, isThisParam);
}
@ -131,6 +133,9 @@ public class FillOutStructureCmd extends BackgroundCommand<Program> {
Msg.showError(this, null, "Auto Create Structure Failed",
"Failed to create Structure variable", e);
}
finally {
decompInterface.dispose();
}
return false;
}

View File

@ -21,7 +21,6 @@ import java.util.Map.Entry;
import ghidra.app.cmd.label.RenameLabelCmd;
import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.DecompilerUtils;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.data.*;
@ -35,11 +34,16 @@ import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
/**
* Automatically creates a structure definition based on the references found by the decompiler.
*
* If the parameter is already a structure pointer, any new references found will be added
* to the structure, even if the structure must grow.
* Automatically create a Structure data-type based on references found by the decompiler to a
* root parameter or other variable.
*
* If the parameter is already a Structure pointer, any new references found can optionally be added
* to the existing Structure data-type.
* {@link #processStructure(HighVariable, Function, boolean, boolean, DecompInterface)} is the primary
* entry point to the helper, which computes the new or updated Structure based on an existing
* decompiled function. Decompilation, if not provided externally, can be performed by calling
* {@link #computeHighVariable(Address, Function, DecompInterface)}. A decompiler process,
* if not provided externally, can be started by calling {@link #setUpDecompiler(DecompileOptions)}.
*/
public class FillOutStructureHelper {
@ -61,7 +65,6 @@ public class FillOutStructureHelper {
private Program currentProgram;
private TaskMonitor monitor;
private DecompileOptions decompileOptions;
private static final int maxCallDepth = 1;
@ -75,36 +78,35 @@ public class FillOutStructureHelper {
* Constructor.
*
* @param program the current program
* @param decompileOptions decompiler options
* (see {@link DecompilerUtils#getDecompileOptions(ServiceProvider, Program)})
* @param monitor task monitor
*/
public FillOutStructureHelper(Program program, DecompileOptions decompileOptions,
TaskMonitor monitor) {
public FillOutStructureHelper(Program program, TaskMonitor monitor) {
this.currentProgram = program;
this.decompileOptions = decompileOptions;
this.monitor = monitor;
}
/**
* Method to create a structure data type for a variable in the given function.
* Unlike the applyTo() action, this method will not modify the function, its variables,
* or any existing data-types. A new structure is always created.
* @param var a parameter, local variable, or global variable used in the given function
* @param function the function to process
* @param createNewStructure if true a new structure with a unique name will always be generated,
* if false and variable corresponds to a structure pointer the existing structure will be
* Create or update a Structure data-type given a function and a root pointer variable.
* The function must already be decompiled, but if a decompiler interface is provided, this
* method will recursively follow variable references into CALLs, possibly triggering additional
* decompilation.
* @param var is the pointer variable
* @param function is the function to process
* @param createNewStructure if true a new Structure with a unique name will always be generated,
* if false and the variable corresponds to a Structure pointer, the existing Structure will be
* updated instead.
* @param createClassIfNeeded if true and variable corresponds to a <B>this</B> pointer without
* an assigned Ghidra Class (i.e., {@code void * this}), the function will be assigned to a
* new unique Ghidra Class namespace with a new identically named structure returned. If false,
* a new uniquely structure will be created.
* @return a filled-in structure or null if one could not be created
* new unique Ghidra Class namespace with a new identically named Structure returned. If false,
* a new unique Structure will be created.
* @param decomplib is the (optional) decompiler interface, which can be used to recursively
* decompile into CALLs.
* @return a filled-in Structure or null if one could not be created
*/
public Structure processStructure(HighVariable var, Function function,
boolean createNewStructure, boolean createClassIfNeeded) {
boolean createNewStructure, boolean createClassIfNeeded, DecompInterface decomplib) {
if (var == null || var.getSymbol() == null || var.getOffset() >= 0) {
if (var == null) {
return null;
}
@ -120,7 +122,9 @@ public class FillOutStructureHelper {
}
fillOutStructureDef(var);
pushIntoCalls();
if (decomplib != null) {
pushIntoCalls(decomplib);
}
long size = componentMap.getSize();
if (size == 0) {
@ -132,7 +136,7 @@ public class FillOutStructureHelper {
}
if (structDT == null) {
if (createClassIfNeeded && DecompilerUtils.testForAutoParameterThis(var, function)) {
if (createClassIfNeeded && DecompilerUtils.isThisParameter(var, function)) {
structDT = createUniqueClassNamespaceAndStructure(var, (int) size, function);
}
else {
@ -168,7 +172,7 @@ public class FillOutStructureHelper {
/**
* Retrieve the component map that was generated when structure was created using decompiler
* info. Results are not valid until
* {@link #processStructure(HighVariable, Function, boolean, boolean)} is invoked.
* {@link #processStructure(HighVariable, Function, boolean, boolean, DecompInterface)} is invoked.
* @return componentMap
*/
public NoisyStructureBuilder getComponentMap() {
@ -179,7 +183,7 @@ public class FillOutStructureHelper {
* Retrieve the offset/pcodeOp pairs that are used to store data into the variable
* used to fill-out structure.
* Results are not valid until
* {@link #processStructure(HighVariable, Function, boolean, boolean)} is invoked.
* {@link #processStructure(HighVariable, Function, boolean, boolean, DecompInterface)} is invoked.
* @return the pcodeOps doing the storing to the associated variable
*/
public List<OffsetPcodeOpPair> getStorePcodeOps() {
@ -190,7 +194,7 @@ public class FillOutStructureHelper {
* Retrieve the offset/pcodeOp pairs that are used to load data from the variable
* used to fill-out structure.
* Results are not valid until
* {@link #processStructure(HighVariable, Function, boolean, boolean)} is invoked.
* {@link #processStructure(HighVariable, Function, boolean, boolean, DecompInterface)} is invoked.
* @return the pcodeOps doing the loading from the associated variable
*/
public List<OffsetPcodeOpPair> getLoadPcodeOps() {
@ -237,8 +241,9 @@ public class FillOutStructureHelper {
/**
* Recursively visit calls that take the structure pointer as a parameter.
* Add any new references to the offsetToDataTypeMap.
* @param decomplib is the active interface for decompiling
*/
private void pushIntoCalls() {
private void pushIntoCalls(DecompInterface decomplib) {
AddressSet doneSet = new AddressSet();
while (addressToCallInputMap.size() > 0) {
@ -256,7 +261,7 @@ public class FillOutStructureHelper {
doneSet.addRange(addr, addr);
Function func = currentProgram.getFunctionManager().getFunctionAt(addr);
Address storageAddr = savedList.get(addr);
HighVariable paramHighVar = computeHighVariable(storageAddr, func);
HighVariable paramHighVar = computeHighVariable(storageAddr, func, decomplib);
if (paramHighVar != null) {
fillOutStructureDef(paramHighVar);
}
@ -268,77 +273,73 @@ public class FillOutStructureHelper {
* Decompile a function and return the resulting HighVariable associated with a storage address
* @param storageAddress the storage address of the variable
* @param function is the function
* @param decomplib is the active interface to use for decompiling
* @return the corresponding HighVariable or null
*/
public HighVariable computeHighVariable(Address storageAddress, Function function) {
public HighVariable computeHighVariable(Address storageAddress, Function function,
DecompInterface decomplib) {
if (storageAddress == null) {
return null;
}
DecompInterface decomplib = setUpDecompiler();
HighVariable highVar = null;
// call decompiler to get syntax tree
try {
if (!decomplib.openProgram(currentProgram)) {
return null;
}
DecompileResults results = decomplib.decompileFunction(function,
decomplib.getOptions().getDefaultTimeout(), monitor);
if (monitor.isCancelled()) {
return null;
}
HighFunction highFunc = results.getHighFunction();
// no decompile...
if (highFunc == null) {
return null;
}
// try to map the variable
HighSymbol sym =
highFunc.getMappedSymbol(storageAddress, function.getEntryPoint().subtractWrap(1L));
if (sym == null) {
sym = highFunc.getMappedSymbol(storageAddress, null);
}
if (sym == null) {
sym = highFunc.getMappedSymbol(storageAddress, function.getEntryPoint());
}
if (sym == null) {
sym = highFunc.getLocalSymbolMap()
.findLocal(storageAddress, function.getEntryPoint().subtractWrap(1L));
}
if (sym == null) {
sym = highFunc.getLocalSymbolMap().findLocal(storageAddress, null);
}
if (sym == null) {
sym = highFunc.getLocalSymbolMap()
.findLocal(storageAddress, function.getEntryPoint());
}
if (sym == null) {
return null;
}
highVar = sym.getHighVariable();
}
finally {
decomplib.dispose();
DecompileResults results = decomplib.decompileFunction(function,
decomplib.getOptions().getDefaultTimeout(), monitor);
if (monitor.isCancelled()) {
return null;
}
HighFunction highFunc = results.getHighFunction();
// no decompile...
if (highFunc == null) {
return null;
}
// try to map the variable
HighSymbol sym =
highFunc.getMappedSymbol(storageAddress, function.getEntryPoint().subtractWrap(1L));
if (sym == null) {
sym = highFunc.getMappedSymbol(storageAddress, null);
}
if (sym == null) {
sym = highFunc.getMappedSymbol(storageAddress, function.getEntryPoint());
}
if (sym == null) {
sym = highFunc.getLocalSymbolMap()
.findLocal(storageAddress, function.getEntryPoint().subtractWrap(1L));
}
if (sym == null) {
sym = highFunc.getLocalSymbolMap().findLocal(storageAddress, null);
}
if (sym == null) {
sym = highFunc.getLocalSymbolMap()
.findLocal(storageAddress, function.getEntryPoint());
}
if (sym == null) {
return null;
}
highVar = sym.getHighVariable();
return highVar;
}
/**
* Set up a decompiler interface for recovering data-flow
* Set up a decompiler interface and prepare for decompiling on the currentProgram.
* The interface can be used to pass to computeHighVariable or to processStructure.
* @param options are the options to pass to the decompiler
* @return the decompiler interface
*/
private DecompInterface setUpDecompiler() {
public DecompInterface setUpDecompiler(DecompileOptions options) {
DecompInterface decomplib = new DecompInterface();
decomplib.setOptions(decompileOptions);
decomplib.setOptions(options);
decomplib.toggleCCode(true);
decomplib.toggleSyntaxTree(true);
decomplib.setSimplificationStyle("decompile");
if (!decomplib.openProgram(currentProgram)) {
return null;
}
return decomplib;
}

View File

@ -1016,6 +1016,13 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
new RemoveAllSecondaryHighlightsAction();
setGroupInfo(removeAllSecondadryHighlightsAction, highlightGroup, subGroupPosition++);
PreviousHighlightedTokenAction previousHighlightedTokenAction =
new PreviousHighlightedTokenAction();
setGroupInfo(previousHighlightedTokenAction, highlightGroup, subGroupPosition++);
NextHighlightedTokenAction nextHighlightedTokenAction = new NextHighlightedTokenAction();
setGroupInfo(nextHighlightedTokenAction, highlightGroup, subGroupPosition++);
String convertGroup = "7 - Convert Group";
subGroupPosition = 0;
RemoveEquateAction removeEquateAction = new RemoveEquateAction();
@ -1122,6 +1129,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
addLocalAction(setSecondaryHighlightColorChooserAction);
addLocalAction(removeSecondaryHighlightAction);
addLocalAction(removeAllSecondadryHighlightsAction);
addLocalAction(nextHighlightedTokenAction);
addLocalAction(previousHighlightedTokenAction);
addLocalAction(convertBinaryAction);
addLocalAction(convertDecAction);
addLocalAction(convertFloatAction);
@ -1165,7 +1174,14 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
private void setGroupInfo(DockingAction action, String group, int subGroupPosition) {
MenuData popupMenuData = action.getPopupMenuData();
popupMenuData.setMenuGroup(group);
popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition));
// Some groups have numbers reach double-digits. These will not compare correctly unless
// padded. Ensure all string numbers are at least 2 digits.
String numberString = Integer.toString(subGroupPosition);
if (numberString.length() == 1) {
numberString = '0' + numberString;
}
popupMenuData.setMenuSubGroup(numberString);
}
private void graphServiceRemoved() {

View File

@ -19,21 +19,27 @@ import docking.widgets.CursorPosition;
import docking.widgets.SearchLocation;
import docking.widgets.fieldpanel.support.FieldLocation;
public class FieldBasedSearchLocation extends SearchLocation {
public class DecompilerSearchLocation extends SearchLocation {
private final FieldLocation fieldLocation;
private String textLine;
public FieldBasedSearchLocation(FieldLocation fieldLocation, int startIndexInclusive,
int endIndexInclusive, String searchText, boolean forwardDirection) {
public DecompilerSearchLocation(FieldLocation fieldLocation, int startIndexInclusive,
int endIndexInclusive, String searchText, boolean forwardDirection, String textLine) {
super(startIndexInclusive, endIndexInclusive, searchText, forwardDirection);
this.fieldLocation = fieldLocation;
this.textLine = textLine;
}
public FieldLocation getFieldLocation() {
return fieldLocation;
}
public String getTextLine() {
return textLine;
}
@Override
public CursorPosition getCursorPosition() {
return new DecompilerCursorPosition(fieldLocation);

View File

@ -15,14 +15,18 @@
*/
package ghidra.app.plugin.core.decompile.actions;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.function.Function;
import java.util.regex.*;
import docking.widgets.*;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.RowColLocation;
import ghidra.app.decompiler.component.ClangTextField;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.util.Msg;
import ghidra.util.UserSearchUtils;
/**
* A {@link FindDialogSearcher} for searching the text of the decompiler window.
@ -86,15 +90,14 @@ public class DecompilerSearcher implements FindDialogSearcher {
DecompilerCursorPosition decompilerCursorPosition = (DecompilerCursorPosition) position;
FieldLocation startLocation =
getNextSearchStartLocation(decompilerCursorPosition, searchForward);
return useRegex ? decompilerPanel.searchTextRegex(text, startLocation, searchForward)
: decompilerPanel.searchText(text, startLocation, searchForward);
return doFind(text, startLocation, searchForward, useRegex);
}
private FieldLocation getNextSearchStartLocation(
DecompilerCursorPosition decompilerCursorPosition, boolean searchForward) {
FieldLocation startLocation = decompilerCursorPosition.getFieldLocation();
FieldBasedSearchLocation currentSearchLocation = decompilerPanel.getSearchResults();
DecompilerSearchLocation currentSearchLocation = decompilerPanel.getSearchResults();
if (currentSearchLocation == null) {
return startLocation; // nothing to do; no prior search hit
}
@ -139,4 +142,229 @@ public class DecompilerSearcher implements FindDialogSearcher {
return startLocation;
}
//=================================================================================================
// Search Methods
//=================================================================================================
@Override
public List<SearchLocation> searchAll(String searchString, boolean isRegex) {
Pattern pattern = createPattern(searchString, isRegex);
Function<String, SearchMatch> function = createForwardMatchFunction(pattern);
FieldLocation start = new FieldLocation();
List<SearchLocation> results = new ArrayList<>();
DecompilerSearchLocation searchLocation = findNext(function, searchString, start);
while (searchLocation != null) {
results.add(searchLocation);
FieldLocation last = searchLocation.getFieldLocation();
int line = last.getIndex().intValue();
int field = 0; // there is only 1 field
int row = 0; // there is only 1 row
int col = last.getCol() + 1; // move over one char to handle sub-matches
start = new FieldLocation(line, field, row, col);
searchLocation = findNext(function, searchString, start);
}
return results;
}
private DecompilerSearchLocation doFind(String searchString, FieldLocation currentLocation,
boolean forwardSearch, boolean isRegex) {
Pattern pattern = createPattern(searchString, isRegex);
if (forwardSearch) {
Function<String, SearchMatch> function = createForwardMatchFunction(pattern);
return findNext(function, searchString, currentLocation);
}
Function<String, SearchMatch> reverse = createReverseMatchFunction(pattern);
return findPrevious(reverse, searchString, currentLocation);
}
private Pattern createPattern(String searchString, boolean isRegex) {
int options = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
if (isRegex) {
try {
return Pattern.compile(searchString, options);
}
catch (PatternSyntaxException e) {
Msg.showError(this, decompilerPanel, "Regular Expression Syntax Error",
e.getMessage());
return null;
}
}
return UserSearchUtils.createPattern(searchString, false, options);
}
private Function<String, SearchMatch> createForwardMatchFunction(Pattern pattern) {
return textLine -> {
Matcher matcher = pattern.matcher(textLine);
if (matcher.find()) {
int start = matcher.start();
int end = matcher.end();
return new SearchMatch(start, end, textLine);
}
return SearchMatch.NO_MATCH;
};
}
private Function<String, SearchMatch> createReverseMatchFunction(Pattern pattern) {
return textLine -> {
Matcher matcher = pattern.matcher(textLine);
if (!matcher.find()) {
return SearchMatch.NO_MATCH;
}
int start = matcher.start();
int end = matcher.end();
// Since the matcher can only match from the start to end of line, we need to find all
// matches and then take the last match
// Setting the region to one character past the previous match allows repeated matches
// within a match. The default behavior of the matcher is to start the match after
// the previous match found by find().
matcher.region(start + 1, textLine.length());
while (matcher.find()) {
start = matcher.start();
end = matcher.end();
matcher.region(start + 1, textLine.length());
}
return new SearchMatch(start, end, textLine);
};
}
private DecompilerSearchLocation findNext(Function<String, SearchMatch> matcher,
String searchString, FieldLocation currentLocation) {
List<Field> fields = decompilerPanel.getFields();
int line = currentLocation.getIndex().intValue();
for (int i = line; i < fields.size(); i++) {
ClangTextField field = (ClangTextField) fields.get(i);
String partialLine = substring(field, (i == line) ? currentLocation : null, true);
SearchMatch match = matcher.apply(partialLine);
if (match == SearchMatch.NO_MATCH) {
continue;
}
if (i == line) { // cursor is on this line
//
// The match start for all lines without the cursor will be relative to the start
// of the line, which is 0. However, when searching on the row with the cursor,
// the match start is relative to the cursor position. Update the start to
// compensate for the difference between the start of the line and the cursor.
//
String fullLine = field.getText();
int cursorOffset = fullLine.length() - partialLine.length();
match.start += cursorOffset;
match.end += cursorOffset;
}
FieldLineLocation lineInfo = getFieldIndexFromOffset(match.start, field);
FieldLocation fieldLocation =
new FieldLocation(i, lineInfo.fieldNumber(), 0, lineInfo.column());
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
searchString, true, field.getText());
}
return null;
}
private DecompilerSearchLocation findPrevious(Function<String, SearchMatch> matcher,
String searchString, FieldLocation currentLocation) {
List<Field> fields = decompilerPanel.getFields();
int line = currentLocation.getIndex().intValue();
for (int i = line; i >= 0; i--) {
ClangTextField field = (ClangTextField) fields.get(i);
String textLine = substring(field, (i == line) ? currentLocation : null, false);
SearchMatch match = matcher.apply(textLine);
if (match != SearchMatch.NO_MATCH) {
FieldLineLocation lineInfo = getFieldIndexFromOffset(match.start, field);
FieldLocation fieldLocation =
new FieldLocation(i, lineInfo.fieldNumber(), 0, lineInfo.column());
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
searchString, false, field.getText());
}
}
return null;
}
private String substring(ClangTextField textField, FieldLocation location,
boolean forwardSearch) {
if (location == null) { // the cursor location is not on this line; use all of the text
return textField.getText();
}
if (textField.getText().isEmpty()) { // the cursor is on blank line
return "";
}
String partialText = textField.getText();
if (forwardSearch) {
int nextCol = location.getCol();
// protects against the location column being out of range (this can happen if we're
// searching forward and the cursor is past the last token)
if (nextCol >= partialText.length()) {
return "";
}
// skip a character to start the next search; this prevents matching the previous match
return partialText.substring(nextCol);
}
// backwards search
return partialText.substring(0, location.getCol());
}
private FieldLineLocation getFieldIndexFromOffset(int screenOffset, ClangTextField textField) {
RowColLocation rowColLocation = textField.textOffsetToScreenLocation(screenOffset);
// we use 0 here because currently there is only one field, which is the entire line
return new FieldLineLocation(0, rowColLocation.col());
}
private static class SearchMatch {
private static SearchMatch NO_MATCH = new SearchMatch(-1, -1, null);
private int start;
private int end;
private String textLine;
SearchMatch(int start, int end, String textLine) {
this.start = start;
this.end = end;
this.textLine = textLine;
}
@Override
public String toString() {
if (this == NO_MATCH) {
return "NO MATCH";
}
return "[start=" + start + ",end=" + end + "]: " + textLine;
}
}
private record FieldLineLocation(int fieldNumber, int column) {
}
}

View File

@ -61,7 +61,7 @@ public class DecompilerStructureVariableAction extends CreateStructureVariableAc
HighVariable var = tokenAtCursor.getHighVariable();
if (var != null && !(var instanceof HighConstant)) {
dt = var.getDataType();
isThisParam = DecompilerUtils.testForAutoParameterThis(var, function);
isThisParam = DecompilerUtils.isThisParameter(var, function);
}
if (dt == null || dt.getLength() > maxPointerSize) {

View File

@ -23,13 +23,14 @@ import org.apache.commons.lang3.StringUtils;
import docking.action.KeyBindingData;
import docking.action.MenuData;
import docking.widgets.FindDialog;
import ghidra.app.decompiler.component.DecompilerFindDialog;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.util.HelpLocation;
public class FindAction extends AbstractDecompilerAction {
private FindDialog findDialog;
private DecompilerFindDialog findDialog;
public FindAction() {
super("Find");
@ -49,15 +50,7 @@ public class FindAction extends AbstractDecompilerAction {
protected FindDialog getFindDialog(DecompilerPanel decompilerPanel) {
if (findDialog == null) {
findDialog =
new FindDialog("Decompiler Find Text", new DecompilerSearcher(decompilerPanel)) {
@Override
protected void dialogClosed() {
// clear the search results when the dialog is closed
decompilerPanel.setSearchResults(null);
}
};
findDialog.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
findDialog = new DecompilerFindDialog(decompilerPanel);
}
return findDialog;
}

View File

@ -0,0 +1,99 @@
/* ###
* 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.plugin.core.decompile.actions;
import java.util.List;
import docking.action.KeyBindingData;
import docking.action.MenuData;
import docking.widgets.fieldpanel.field.Field;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.TokenIterator;
import ghidra.app.decompiler.component.*;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.util.HelpLocation;
/**
* An action to navigate to the next token highlighted by the user via the middle-mouse.
*/
public class NextHighlightedTokenAction extends AbstractDecompilerAction {
public NextHighlightedTokenAction() {
super("Next Highlihted Token");
setPopupMenuData(new MenuData(new String[] { "Next Highlight" }, "Decompile"));
setKeyBindingData(new KeyBindingData("Ctrl period"));
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "GoToMiddleMouseHighlight"));
}
@Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
if (!context.hasRealFunction()) {
return false;
}
DecompilerPanel panel = context.getDecompilerPanel();
TokenHighlights highlights = panel.getMiddleMouseHighlights();
if (highlights != null) {
return highlights.size() > 1;
}
return false;
}
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
DecompilerPanel panel = context.getDecompilerPanel();
TokenHighlights highlights = panel.getMiddleMouseHighlights();
ClangToken cursorToken = context.getTokenAtCursor();
TokenIterator it = new TokenIterator(cursorToken, true);
it.next(); // ignore the current token
if (goToNexToken(panel, it, highlights)) {
return; // found another token in the current direction
}
// this means there are no more occurrences in the current direction; wrap the search
ClangToken firstToken = getFirstToken(panel);
it = new TokenIterator(firstToken, true);
goToNexToken(panel, it, highlights);
}
private ClangToken getFirstToken(DecompilerPanel panel) {
List<Field> fields = panel.getFields();
Field line = fields.get(0);
ClangTextField tf = (ClangTextField) line;
return tf.getFirstToken();
}
private boolean goToNexToken(DecompilerPanel panel, TokenIterator it,
TokenHighlights highlights) {
while (it.hasNext()) {
ClangToken nextToken = it.next();
HighlightToken hlToken = highlights.get(nextToken);
if (hlToken == null) {
continue;
}
ClangToken token = hlToken.getToken();
panel.goToToken(token);
return true;
}
return false;
}
}

View File

@ -0,0 +1,100 @@
/* ###
* 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.plugin.core.decompile.actions;
import java.util.List;
import docking.action.KeyBindingData;
import docking.action.MenuData;
import docking.widgets.fieldpanel.field.Field;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.TokenIterator;
import ghidra.app.decompiler.component.*;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.util.HelpLocation;
/**
* An action to navigate to the previous token highlighted by the user via the middle-mouse.
*/
public class PreviousHighlightedTokenAction extends AbstractDecompilerAction {
public PreviousHighlightedTokenAction() {
super("Previous Highlighted Token");
setPopupMenuData(new MenuData(new String[] { "Previous Highlight" }, "Decompile"));
setKeyBindingData(new KeyBindingData("Ctrl comma"));
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "GoToMiddleMouseHighlight"));
}
@Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
if (!context.hasRealFunction()) {
return false;
}
DecompilerPanel panel = context.getDecompilerPanel();
TokenHighlights highlights = panel.getMiddleMouseHighlights();
if (highlights != null) {
return highlights.size() > 1;
}
return false;
}
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
DecompilerPanel panel = context.getDecompilerPanel();
TokenHighlights highlights = panel.getMiddleMouseHighlights();
ClangToken cursorToken = context.getTokenAtCursor();
TokenIterator it = new TokenIterator(cursorToken, false);
it.next(); // ignore the current token
if (goToNexToken(panel, it, highlights)) {
return; // found another token in the current direction
}
// this means there are no more occurrences in the current direction; wrap the search
ClangToken lastToken = getLastToken(panel);
it = new TokenIterator(lastToken, false);
goToNexToken(panel, it, highlights);
}
private ClangToken getLastToken(DecompilerPanel panel) {
List<Field> fields = panel.getFields();
int lastLine = fields.size();
Field line = fields.get(lastLine - 1);
ClangTextField tf = (ClangTextField) line;
return tf.getLastToken();
}
private boolean goToNexToken(DecompilerPanel panel, TokenIterator it,
TokenHighlights highlights) {
while (it.hasNext()) {
ClangToken nextToken = it.next();
HighlightToken hlToken = highlights.get(nextToken);
if (hlToken == null) {
continue;
}
ClangToken token = hlToken.getToken();
panel.goToToken(token);
return true;
}
return false;
}
}

View File

@ -70,4 +70,9 @@ public class SliceHighlightColorProvider implements ColorProvider {
}
return c;
}
@Override
public String toString() {
return "Slice Color Provider " + hlColor;
}
}

View File

@ -46,6 +46,7 @@ import ghidra.app.plugin.core.decompile.actions.*;
import ghidra.app.util.AddEditDialog;
import ghidra.framework.options.ToolOptions;
import ghidra.program.model.listing.CodeUnit;
import ghidra.util.Msg;
public class DecompilerClangTest extends AbstractDecompilerTest {
@ -383,29 +384,32 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
setDecompilerLocation(line, charPosition);
ClangToken token = getToken();
String text = token.getText();
assertEquals("_printf", text);
String printfText = token.getText();
assertEquals("_printf", printfText);
highlight();
highlight(); // "printf" is secondary highlighted
// 5:30 "a->name"
line = 5;
charPosition = 38;
setDecompilerLocation(line, charPosition);
ClangToken token2 = getToken();
String text2 = token2.getText();
assertEquals("name", text2);
String nameText = token2.getText();
assertEquals("name", nameText);
Color color2 = highlight();
Color color2 = highlight(); // "name" is secondary highlighted
// 5:2 "_printf"
line = 5;
charPosition = 2;
setDecompilerLocation(line, charPosition);
removeSecondaryHighlight();
assertNoFieldsSecondaryHighlighted(text);
assertAllFieldsHighlighted(text2, color2);
Msg.debug(this, "test - remove");
removeSecondaryHighlight(); // remove "printf" highlight
assertNoFieldsSecondaryHighlighted(printfText);
assertAllFieldsHighlighted(nameText, color2);
}
@Test
@ -812,7 +816,106 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
}
@Test
public void testSecondaryHighlighting_MiddleMouseDoesNotClearSecondaryHighlight() {
public void testSecondaryHighlighting_MiddleMouse_SecondaryHighlight() {
/*
The middle mouse is a secondary highlight so that it persists as the user clicks around.
Middle mousing on an already middle moused highlight should clear the highlight. Middle
mousing on a new token should clear the original highlight and highlight the new token.
Decomp of '_call_structure_A':
1|
2| void _call_structure_A(A *a)
3|
4| {
5| _printf("call_structure_A: %s\n",a->name);
6| _printf("call_structure_A: %s\n",(a->b).name);
7| _printf("call_structure_A: %s\n",(a->b).c.name);
8| _printf("call_structure_A: %s\n",(a->b).c.d.name);
9| _printf("call_structure_A: %s\n",(a->b).c.d.e.name);
10| _call_structure_B(&a->b);
11| return;
12| }
*/
decompile("100000d60"); // '_call_structure_A'
// 5:2 "_printf"
int line = 5;
int charPosition = 2;
setDecompilerLocation(line, charPosition);
ClangToken token = getToken();
String tokenText = token.getText();
assertEquals("_printf", tokenText);
middleMouse();
assertCombinedHighlightColor(token);
middleMouse();
assertNoFieldsSecondaryHighlighted(tokenText);
}
@Test
public void testSecondaryHighlighting_MiddleMouse_SecondaryHighlight_NewToken() {
/*
The middle mouse is a secondary highlight so that it persists as the user clicks around.
Middle mousing on an already middle moused highlight should clear the highlight. Middle
mousing on a new token should clear the original highlight and highlight the new token.
Decomp of '_call_structure_A':
1|
2| void _call_structure_A(A *a)
3|
4| {
5| _printf("call_structure_A: %s\n",a->name);
6| _printf("call_structure_A: %s\n",(a->b).name);
7| _printf("call_structure_A: %s\n",(a->b).c.name);
8| _printf("call_structure_A: %s\n",(a->b).c.d.name);
9| _printf("call_structure_A: %s\n",(a->b).c.d.e.name);
10| _call_structure_B(&a->b);
11| return;
12| }
*/
decompile("100000d60"); // '_call_structure_A'
// 5:2 "_printf"
int line = 5;
int charPosition = 2;
setDecompilerLocation(line, charPosition);
ClangToken token = getToken();
String tokenText = token.getText();
assertEquals("_printf", tokenText);
middleMouse();
assertCombinedHighlightColor(token);
// 5:30 "a->name"
line = 5;
charPosition = 38;
setDecompilerLocation(line, charPosition);
ClangToken token2 = getToken();
String text2 = token2.getText();
assertEquals("name", text2);
middleMouse();
assertNoFieldsSecondaryHighlighted(tokenText);
assertCombinedHighlightColor(token2);
}
@Test
public void testSecondaryHighlighting_MiddleMouseDoesNotClearSecondaryHighlight_ExistingHighlight() {
/*
@ -850,7 +953,9 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
assertCombinedHighlightColor(token);
middleMouse();
assertAllFieldsHighlighted(tokenText, color);
ClangToken cursorToken = getToken(provider);
Predicate<ClangToken> ignores = t -> t == cursorToken;
assertAllFieldsHighlighted(tokenText, color, ignores);
}
@Test

View File

@ -17,21 +17,28 @@ package ghidra.app.plugin.core.decompile;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import docking.action.DockingActionIf;
import docking.widgets.FindDialog;
import docking.widgets.dialogs.InputDialog;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.table.GTable;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.component.DecompilerFindDialog;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.program.model.listing.Program;
import ghidra.test.ClassicSampleX86ProgramBuilder;
import ghidra.util.table.GhidraProgramTableModel;
import ghidra.util.table.GhidraThreadedTablePanel;
public class DecompilerFindDialogTest extends AbstractDecompilerTest {
private FindDialog findDialog;
private DecompilerFindDialog findDialog;
@Override
@After
@ -243,10 +250,78 @@ public class DecompilerFindDialogTest extends AbstractDecompilerTest {
assertSearchHit(line, column, length);
}
@Test
public void testSearchAll() {
/*
bool FUN_01002239(int param_1)
{
undefined4 uVar1;
int iVar2;
undefined4 *puVar3;
bool bVar4;
undefined *puVar5;
undefined2 local_210;
undefined4 local_20e [129];
int local_8;
local_210 = 0;
puVar3 = local_20e;
...
...
...
*/
decompile("1002239");
String text = "puVar";
showFind(text);
searchAll();
GTable table = getResultsTable();
List<DecompilerSearchLocation> results = getResults(table);
assertEquals(10, results.size());
// click some rows and verify the cursor location
for (int i = 0; i < results.size(); i++) {
clickAndVerify(i, table, results);
}
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void clickAndVerify(int row, GTable table, List<DecompilerSearchLocation> results) {
runSwing(() -> table.selectRow(row));
DecompilerSearchLocation searchLocation = results.get(row);
FieldLocation fieldLocation = searchLocation.getFieldLocation();
ClangToken expectedToken = getToken(fieldLocation);
ClangToken cursorToken = getToken();
assertEquals(expectedToken, cursorToken);
}
private GTable getResultsTable() {
@SuppressWarnings("unchecked")
TableComponentProvider<DecompilerSearchLocation> tableProvider =
waitForComponentProvider(TableComponentProvider.class);
GhidraThreadedTablePanel<DecompilerSearchLocation> panel =
tableProvider.getThreadedTablePanel();
return panel.getTable();
}
private List<DecompilerSearchLocation> getResults(GTable table) {
@SuppressWarnings("unchecked")
GhidraProgramTableModel<DecompilerSearchLocation> model =
(GhidraProgramTableModel<DecompilerSearchLocation>) table.getModel();
waitForTableModel(model);
return model.getModelData();
}
private void next() {
runSwing(() -> findDialog.next());
}
@ -255,13 +330,17 @@ public class DecompilerFindDialogTest extends AbstractDecompilerTest {
runSwing(() -> findDialog.previous());
}
private void searchAll() {
pressButtonByText(findDialog, "Search All");
}
private void assertSearchHit(int line, int column, int length) {
waitForSwing();
assertCurrentLocation(line, column);
DecompilerPanel panel = getDecompilerPanel();
FieldBasedSearchLocation searchResults = panel.getSearchResults();
DecompilerSearchLocation searchResults = panel.getSearchResults();
FieldLocation searchCursorLocation = searchResults.getFieldLocation();
int searchLineNumber = searchCursorLocation.getIndex().intValue() + 1;
assertEquals("Search result is on the wrong line", line, searchLineNumber);
@ -285,7 +364,7 @@ public class DecompilerFindDialogTest extends AbstractDecompilerTest {
private void showFind(String text) {
DockingActionIf findAction = getAction(decompiler, "Find");
performAction(findAction, provider, true);
findDialog = waitForDialogComponent(FindDialog.class);
findDialog = waitForDialogComponent(DecompilerFindDialog.class);
runSwing(() -> findDialog.setSearchText(text));
}

View File

@ -162,22 +162,10 @@ public class MDMang {
* @throws MDException upon parsing error
*/
public MDDataType demangleType(boolean errorOnRemainingChars) throws MDException {
if (mangled == null) {
throw new MDException("MDMang: Mangled string is null.");
}
initState();
pushContext();
if (peek() != '.') {
throw new MDException("MDMang: Mangled string is not that of a type.");
}
increment();
MDDataType mdDataType = MDDataTypeParser.parseDataType(this, false);
MDDataType mdDataType = MDDataTypeParser.determineAndParseDataType(this, false);
item = mdDataType;
if (mdDataType != null) {
mdDataType.parse();
}
int numCharsRemaining = getNumCharsRemaining();
popContext();
if (errorOnRemainingChars && (numCharsRemaining > 0)) {
throw new MDException(
"MDMang: characters remain after demangling: " + numCharsRemaining + ".");

View File

@ -17,6 +17,7 @@ package mdemangler.datatype;
import mdemangler.MDException;
import mdemangler.MDMang;
import mdemangler.MDMang.ProcessingMode;
import mdemangler.datatype.complex.*;
import mdemangler.datatype.extended.*;
import mdemangler.datatype.modifier.*;
@ -31,6 +32,52 @@ import mdemangler.object.MDObjectCPP;
* by calling the appropriate parser at the appropriate place in the code.
*/
public class MDDataTypeParser {
/**
* This method is only to be used by MDMang itself for the highest level type parsing where
* there is not already a multi-retry. This method checks for the '.' starting character,
* determines the type by calling the {@link #parseDataType(MDMang, boolean)}, parses the type,
* and does the multi-mode retry if there is an exception on the first pass as
* MDMangObjectParser does for generic mangled objects
* @param dmang - the MDMang driver
* @param isHighest - boolean indicating whether something else modifies or names the data
* type to be parsed, which impacts when certain overloaded CV modifiers can be applied.
* @return - a type derived from MDDataType
* @throws MDException on parsing error
*/
public static MDDataType determineAndParseDataType(MDMang dmang, boolean isHighest)
throws MDException {
MDDataType dt = null;
if (dmang.peek() != '.') {
throw new MDException("MDMang: Mangled string is not that of a type.");
}
dmang.setProcessingMode(ProcessingMode.DEFAULT_STANDARD);
try {
dmang.pushContext();
dmang.increment(); // skip the '.'
dt = parseDataType(dmang, isHighest);
dt.parse();
dmang.popContext();
}
catch (MDException e1) {
dmang.resetState();
dmang.setProcessingMode(ProcessingMode.LLVM);
try {
dmang.pushContext();
dmang.increment(); // skip the '.'
dt = parseDataType(dmang, isHighest);
dt.parse();
dmang.popContext();
}
catch (MDException e2) {
throw new MDException(
"Reason1: " + e1.getMessage().trim() + "; Reason2: " + e2.getMessage().trim());
}
}
return dt;
}
/**
* This method parses all data types. Specifically, it parses void, data indirect types,
* function indirect types, and all types parsed by parsePrimaryDataType().

View File

@ -14992,6 +14992,19 @@ public class MDMangBaseTest extends AbstractGenericTest {
demangleAndTest();
}
// Note the suffix seems like an already or partially demangled name. Note that name0
// seems like a plain tag (no closing '@'), there is a regular namespace delimiter "::",
// the suffix "3@" is almost like a backreference tag with the '@' closing the full
// qualified name... except... we've seen numbers that are beyond the backref range as
// here, but also have seen numbers like 18.
@Ignore
public void testMangledTypeWithNamespaceSuffix() throws Exception {
mangled = ".?AT<unnamed-tag>@name0::3@";
msTruth = "";
mdTruth = msTruth;
demangleAndTest();
}
//=====================
@Test

View File

@ -80,4 +80,18 @@ public class MDMangExtraTest extends AbstractGenericTest {
assertEquals("k::j::i", qualifications.get(2).toString());
}
// Need to test the demangleType() method to make sure it does the retry with LLVM mode
@Test
public void testDemangleTypeWithRetry() throws Exception {
// Test string taken from MDMangBaseTest
String mangled = ".?AW4name0@?name1@name2@@YAX_N@Z@";
String truth = "enum `void __cdecl name2::name1(bool)'::name0";
MDMangGhidra demangler = new MDMangGhidra();
MDParsableItem item = demangler.demangleType(mangled, true); // note demangleType()
String demangled = item.toString();
assertEquals(truth, demangled);
}
}

View File

@ -216,7 +216,7 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel<IconValue, Obj
private class ThemeIconRenderer extends AbstractGColumnRenderer<ResolvedIcon> {
public ThemeIconRenderer() {
setFont(Gui.getFont("font.monospaced"));
setBaseFontId("font.monospaced");
}
@Override

View File

@ -32,7 +32,7 @@ public class FindDialog extends ReusableDialogComponentProvider {
private GhidraComboBox<String> comboBox;
private FindDialogSearcher searcher;
protected FindDialogSearcher searcher;
private JButton nextButton;
private JButton previousButton;
private JRadioButton stringRadioButton;
@ -140,6 +140,10 @@ public class FindDialog extends ReusableDialogComponentProvider {
doSearch(false);
}
protected boolean useRegex() {
return regexRadioButton.isSelected();
}
private void doSearch(boolean forward) {
if (!nextButton.isEnabled()) {
@ -190,7 +194,7 @@ public class FindDialog extends ReusableDialogComponentProvider {
notifyUser("Not found");
}
private void notifySearchHit(SearchLocation location) {
protected void notifySearchHit(SearchLocation location) {
searcher.setCursorPosition(location.getCursorPosition());
storeSearchText(location.getSearchText());
searcher.highlightSearchResults(location);
@ -234,7 +238,7 @@ public class FindDialog extends ReusableDialogComponentProvider {
history.forEach(comboBox::addToModel);
}
private void storeSearchText(String text) {
protected void storeSearchText(String text) {
MutableComboBoxModel<String> model = (MutableComboBoxModel<String>) comboBox.getModel();
model.insertElementAt(text, 0);

View File

@ -15,21 +15,72 @@
*/
package docking.widgets;
import java.util.List;
import javax.help.UnsupportedOperationException;
/**
* A simple interface for the {@link FindDialog} so that it can work for different search clients.
* <p>
* The {@link CursorPosition} object used by this interface is one that implementations can extend
* to add extra context to use when searching. The implementation is responsible for creating the
* locations and these locations will later be handed back to the searcher.
*/
public interface FindDialogSearcher {
/**
* The current cursor position. Used to search for the next item.
* @return the cursor position.
*/
public CursorPosition getCursorPosition();
/**
* Sets the cursor position after a successful search.
* @param position the cursor position.
*/
public void setCursorPosition(CursorPosition position);
/**
* Returns the start cursor position. This is used when a search is wrapped to start at the
* beginning of the search range.
* @return the start position.
*/
public CursorPosition getStart();
/**
* The end cursor position. This is used when a search is wrapped while searching backwards to
* start at the end position.
* @return the end position.
*/
public CursorPosition getEnd();
/**
* Called to signal the implementor should highlight the given search location.
* @param location the search result location.
*/
public void highlightSearchResults(SearchLocation location);
/**
* Perform a search for the next item in the given direction starting at the given cursor
* position.
* @param text the search text.
* @param cursorPosition the current cursor position.
* @param searchForward true if searching forward.
* @param useRegex useRegex true if the search text is a regular expression; false if the texts is
* literal text.
* @return the search result or null if no match was found.
*/
public SearchLocation search(String text, CursorPosition cursorPosition, boolean searchForward,
boolean useRegex);
/**
* Search for all matches.
* @param text the search text.
* @param useRegex true if the search text is a regular expression; false if the texts is
* literal text.
* @return all search results or an empty list.
*/
public default List<SearchLocation> searchAll(String text, boolean useRegex) {
throw new UnsupportedOperationException("Search All is not defined for this searcher");
}
}

View File

@ -21,7 +21,7 @@ import javax.swing.JButton;
import resources.ResourceManager;
/**
* A drop-in replacement for {@link JButton} that correctly installs a disable icon.
* A drop-in replacement for {@link JButton} that correctly installs a disabled icon.
*/
public class GButton extends JButton {

View File

@ -174,6 +174,9 @@ public class BytesPcodeExecutorStateSpace<B> {
* @return the bytes read
*/
public byte[] read(long offset, int size, Reason reason) {
if (size == 0) {
return new byte[0];
}
ULongSpanSet uninitialized = bytes.getUninitialized(offset, offset + size - 1);
if (uninitialized.isEmpty()) {
return readBytes(offset, size, reason);

View File

@ -742,7 +742,7 @@ public class DefaultProjectData implements ProjectData {
}
@Override
public void refresh(boolean force) throws IOException {
public void refresh(boolean force) {
try {
rootFolderData.refresh(true, true, projectDisposalMonitor);
}

Some files were not shown because too many files have changed in this diff Show More