From c79c9606bc536bd15f26e95a245e92dc76c01ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luke=20Sern=C3=A9?= Date: Sat, 25 May 2024 12:11:34 +0200 Subject: [PATCH 01/53] Decompiler: Simplify comparisons between `INT_OR` and zero. At -O1, gcc combines several values that all need to be compared against zero by combining them using `INT_OR` and only comparing the combined result against zero. With this rule, the decompiler is able to break these `INT_OR` chains apart and simplify the individual links. --- .../src/decompile/cpp/coreaction.cc | 1 + .../src/decompile/cpp/ruleaction.cc | 58 +++++++++++++++++++ .../src/decompile/cpp/ruleaction.hh | 11 ++++ 3 files changed, 70 insertions(+) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc index 5b0c4ab32c..45718760ea 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc @@ -5518,6 +5518,7 @@ void ActionDatabase::universalAction(Architecture *conf) actprop->addRule( new RuleOrMultiBool("analysis") ); actprop->addRule( new RuleXorSwap("analysis") ); actprop->addRule( new RuleLzcountShiftBool("analysis") ); + actprop->addRule( new RuleOrCompare("analysis") ); actprop->addRule( new RuleSubvarAnd("subvar") ); actprop->addRule( new RuleSubvarSubpiece("subvar") ); actprop->addRule( new RuleSplitFlow("subvar") ); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc index 74ec9ea4b7..51e3313ec9 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc @@ -10434,4 +10434,62 @@ int4 RuleLzcountShiftBool::applyOp(PcodeOp *op,Funcdata &data) return 0; } +/// \class RuleOrCompare +/// \brief Simplify INT_OR in comparisons with 0. +/// `(V | W) == 0` => '(V == 0) && (W == 0)' +/// `(V | W) != 0` => '(V != 0) || (W != 0)' +void RuleOrCompare::getOpList(vector &oplist) const + +{ + oplist.push_back(CPUI_INT_EQUAL); + oplist.push_back(CPUI_INT_NOTEQUAL); +} + +int4 RuleOrCompare::applyOp(PcodeOp *op,Funcdata &data) + +{ + // make sure the comparison is against 0 + if (! op->getIn(1)->constantMatch(0)) return 0; + + // make sure the other operand is an INT_OR + PcodeOp *or_op = op->getIn(0)->getDef(); + if (or_op == (PcodeOp *)0) return 0; + if (or_op->code() != CPUI_INT_OR) return 0; + + Varnode* V = or_op->getIn(0); + Varnode* W = or_op->getIn(1); + + // make sure V and W are in SSA form + if (V->isFree()) return 0; + if (W->isFree()) return 0; + + // construct the new segment: + // if the original condition was INT_EQUAL: BOOL_AND(INT_EQUAL(V, 0:|V|), INT_EQUAL(W, 0:|W|)) + // if the original condition was INT_NOTEQUAL: BOOL_OR(INT_NOTEQUAL(V, 0:|V|), INT_NOTEQUAL(W, 0:|W|)) + Varnode* zero_V = data.newConstant(V->getSize(), 0); + Varnode* zero_W = data.newConstant(W->getSize(), 0); + PcodeOp* eq_V = data.newOp(2, op->getAddr()); + data.opSetOpcode(eq_V, op->code()); + data.opSetInput(eq_V, V, 0); + data.opSetInput(eq_V, zero_V, 1); + PcodeOp* eq_W = data.newOp(2, op->getAddr()); + data.opSetOpcode(eq_W, op->code()); + data.opSetInput(eq_W, W, 0); + data.opSetInput(eq_W, zero_W, 1); + + Varnode* eq_V_out = data.newUniqueOut(1, eq_V); + Varnode* eq_W_out = data.newUniqueOut(1, eq_W); + + // make sure the comparisons' output is already defined + data.opInsertBefore(eq_V, op); + data.opInsertBefore(eq_W, op); + + // change the original INT_EQUAL into a BOOL_AND, and INT_NOTEQUAL becomes BOOL_OR + data.opSetOpcode(op, op->code() == CPUI_INT_EQUAL ? CPUI_BOOL_AND : CPUI_BOOL_OR); + data.opSetInput(op, eq_V_out, 0); + data.opSetInput(op, eq_W_out, 1); + + return 1; +} + } // End namespace ghidra diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh index 97a7576ef1..e8dec8cc7b 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh @@ -1643,5 +1643,16 @@ public: virtual int4 applyOp(PcodeOp *op,Funcdata &data); }; +class RuleOrCompare : public Rule { +public: + RuleOrCompare(const string &g) : Rule( g, 0, "ruleorcompare") {} ///< Constructor + virtual Rule *clone(const ActionGroupList &grouplist) const { + if (!grouplist.contains(getGroup())) return (Rule *)0; + return new RuleOrCompare(getGroup()); + } + virtual void getOpList(vector &oplist) const; + virtual int4 applyOp(PcodeOp *op,Funcdata &data); +}; + } // End namespace ghidra #endif From ee8e205172b36ff87c55cc5db2e66a0bf7da56cf Mon Sep 17 00:00:00 2001 From: emteere <47253321+emteere@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:55:57 -0400 Subject: [PATCH 02/53] GP-4702 PPC changes in evx to ensure destination of operation is assigned --- .../PowerPC/data/languages/SPE_APU.sinc | 16 +++--- .../PowerPC/data/languages/evx.sinc | 52 ++++++++++--------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/Ghidra/Processors/PowerPC/data/languages/SPE_APU.sinc b/Ghidra/Processors/PowerPC/data/languages/SPE_APU.sinc index 56536a053a..cc07249e2a 100644 --- a/Ghidra/Processors/PowerPC/data/languages/SPE_APU.sinc +++ b/Ghidra/Processors/PowerPC/data/languages/SPE_APU.sinc @@ -2654,8 +2654,8 @@ define pcodeop ROTL64; define pcodeop VectorSplatFractionalImmediate; :evsplatfi D,BU_SIMM is OP=4 & D & BU_SIMM & XOP_0_10=0x22B { # TODO definition -# RT0:31 􀁉 SI || 270 -# RT32:63 􀁉 SI || 270 +# RT0:31 = SI || 270 +# RT32:63 = SI || 270 # The value specified by SI is padded with trailing zeros # and placed in both elements of RT. The SI ends up in # bit positions RT0:4 and RT32:36. @@ -2687,9 +2687,9 @@ define pcodeop VectorSplatImmediate; define pcodeop VectorShiftRightWordImmediateSigned; :evsrwis D,A,EVUIMM is OP=4 & A & D & EVUIMM & XOP_0_10=0x223 { # TODO definition -# n 􀁉 UI -# RT0:31 􀁉 EXTS((RA)0:31-n) -# RT32:63 􀁉 EXTS((RA)32:63-n) +# n = UI +# RT0:31 = EXTS((RA)0:31-n) +# RT32:63 = EXTS((RA)32:63-n) # Both high and low elements of RA are shifted right by # the 5-bit UI value. Bits in the most significant positions # vacated by the shift are filled with a copy of the sign bit. @@ -2703,9 +2703,9 @@ define pcodeop VectorShiftRightWordImmediateSigned; define pcodeop VectorShiftRightWordImmediateUnsigned; :evsrwiu D,A,EVUIMM is OP=4 & A & D & EVUIMM & XOP_0_10=0x222 { # TODO definition -# n 􀁉 UI -# RT0:31 􀁉 EXTZ((RA)0:31-n) -# RT32:63 􀁉 EXTZ((RA)32:63-n) +# n = UI +# RT0:31 = EXTZ((RA)0:31-n) +# RT32:63 = EXTZ((RA)32:63-n) # Both high and low elements of RA are shifted right by # the 5-bit UI value; zeros are shifted into the most significant # position. diff --git a/Ghidra/Processors/PowerPC/data/languages/evx.sinc b/Ghidra/Processors/PowerPC/data/languages/evx.sinc index b47a5f1869..190b52513e 100644 --- a/Ghidra/Processors/PowerPC/data/languages/evx.sinc +++ b/Ghidra/Processors/PowerPC/data/languages/evx.sinc @@ -18,15 +18,19 @@ define pcodeop vectorShiftRightWordUnsigned; vrD_64_0 = vrA_64_0 ^ vrB_64_0; } +@if REGISTER_SIZE=="8" :evmergehi S,A,B is OP=4 & S & A & B & XOP_0_10=556 { - vectorMergeHigh(S,A,B); + S[32,32] = A[32,32]; + S[ 0,32] = B[ 0,32]; } :evmergelo S,A,B is OP=4 & S & A & B & XOP_0_10=557 { - vectorMergeLow(S,A,B); + S[32,32] = A[0,32]; + S[ 0,32] = B[0,32]; } +@endif :evldd RT,dUI16PlusRAOrZeroAddress is OP=4 & RT & dUI16PlusRAOrZeroAddress & XOP_0_10=769 @@ -41,19 +45,31 @@ define pcodeop vectorShiftRightWordUnsigned; RT = *:8 ($(EATRUNC)); } +@if REGISTER_SIZE=="8" @ifndef IS_ISA :evsrws S,A,B is OP=4 & S & A & B & XOP_0_10=545 { - vectorShiftRightWordSigned(S,A,B); + local low:4 = A[0,32]; + local high:4 = A[32,32]; + local low_shift:1 = B[0,5]; + local high_shift:1 = B[32,5]; + S[0,32] = low s>> zext(low_shift); + S[32,32] = high s>> zext(high_shift); } @endif @ifndef IS_ISA :evsrwu S,A,B is OP=4 & S & A & B & XOP_0_10=544 { - vectorShiftRightWordUnsigned(S,A,B); + local low:4 = A[0,32]; + local high:4 = A[32,32]; + local low_shift:1 = B[0,5]; + local high_shift:1 = B[32,5]; + S[0,32] = low >> zext(low_shift); + S[32,32] = high >> zext(high_shift); } @endif +@endif :evstdd RS,dUI16PlusRAOrZeroAddress is OP=4 & RS & dUI16PlusRAOrZeroAddress & XOP_0_10=801 { @@ -73,32 +89,20 @@ define pcodeop vectorShiftRightWordUnsigned; RT = RA; } +@if REGISTER_SIZE=="8" # evmergehilo rD,rA,rB 010 0010 1110 -define pcodeop VectorMergeHighLow; -:evmergehilo D,A,B is OP=4 & A & B & D & XOP_0_10=558 { - local lo = (A & 0x00000000FFFFFFFF); - local hi = ((A & 0xFFFFFFFF00000000) >> 32); - #local b_lo:$(REGISTER_SIZE) = (B & 0x00000000FFFFFFFF); - local b_hi:$(REGISTER_SIZE) = ((B & 0xFFFFFFFF00000000) >> 32); +:evmergehilo S,A,B is OP=4 & S & A & B & XOP_0_10=558 { + S[32,32] = A[32,32]; + S[ 0,32] = B[ 0,32]; - lo = lo; - hi = b_hi; - - D = ((hi << 32) | lo); } # evmergelohi rD,rA,rB 010 0010 1111 -:evmergelohi D,A,B is OP=4 & D & A & B & XOP_0_10=559 { - local lo = (A & 0x00000000FFFFFFFF); - local hi = ((A & 0xFFFFFFFF00000000) >> 32); - local b_lo:$(REGISTER_SIZE) = (B & 0x00000000FFFFFFFF); - #local b_hi:$(REGISTER_SIZE) = ((B & 0xFFFFFFFF00000000) >> 32); - - lo = lo; - hi = b_lo; - - D = ((hi << 32) | lo); +:evmergelohi S,A,B is OP=4 & S & A & B & XOP_0_10=559 { + S[32,32] = A[ 0,32]; + S[ 0,32] = B[32,32]; } +@endif # evstwwe rS,rA,UIMM 011 0011 1001 :evstwwe RS,dUI16PlusRAOrZeroAddress is OP=4 & RS & dUI16PlusRAOrZeroAddress & XOP_0_10=0x339 From 74b127eb2c8c81b9ae7700b730df3af5a508792a Mon Sep 17 00:00:00 2001 From: ghidorahrex Date: Tue, 13 Aug 2024 18:52:31 +0000 Subject: [PATCH 03/53] GP-4845: Fixed m68k fmovem.l register bug --- Ghidra/Processors/68000/data/languages/68000.sinc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ghidra/Processors/68000/data/languages/68000.sinc b/Ghidra/Processors/68000/data/languages/68000.sinc index 9d53e82209..775e7fe866 100644 --- a/Ghidra/Processors/68000/data/languages/68000.sinc +++ b/Ghidra/Processors/68000/data/languages/68000.sinc @@ -2687,7 +2687,7 @@ m2fpC2: FPCR is FPCR & f12=1 { FPCR = *movemptr; movemptr = movemptr + 1 m2fpC2: is f12=0 { } m2fpC1: m2fpC2" "FPSR is FPSR & f11=1 & m2fpC2 { FPSR = *movemptr; movemptr = movemptr + 12; } m2fpC1: m2fpC2 is f11=0 & m2fpC2 { } -m2fpC0: { m2fpC1" "FPCR } is FPCR & f10=1 & m2fpC1 { FPCR = *movemptr; movemptr = movemptr + 12; } +m2fpC0: { m2fpC1" "FPIAR } is FPIAR & f10=1 & m2fpC1 { FPIAR = *movemptr; movemptr = movemptr + 12; } m2fpC0: { m2fpC1 } is f10=0 & m2fpC1 { } # Floating point control register to Memory From 34adcff8308d2fce92424dcf817e5e3152dbce8c Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:21:07 +0000 Subject: [PATCH 04/53] GP-4782 Refactor RulePtrsubUndo --- .../src/decompile/cpp/coreaction.cc | 7 +- .../Decompiler/src/decompile/cpp/funcdata.hh | 5 +- .../src/decompile/cpp/ruleaction.cc | 266 ++++++++++++++++-- .../src/decompile/cpp/ruleaction.hh | 10 +- .../Decompiler/src/decompile/cpp/type.cc | 114 ++++++-- .../Decompiler/src/decompile/cpp/type.hh | 7 +- .../Decompiler/src/decompile/cpp/typeop.cc | 26 +- 7 files changed, 355 insertions(+), 80 deletions(-) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc index a79c3030c3..22a461bf85 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc @@ -4,9 +4,9 @@ * 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. @@ -2723,7 +2723,7 @@ int4 ActionSetCasts::apply(Funcdata &data) data.opUndoPtradd(op,true); } else if (opc == CPUI_PTRSUB) { // Check for PTRSUB that no longer fits pointer - if (!op->getIn(0)->getHighTypeReadFacing(op)->isPtrsubMatching(op->getIn(1)->getOffset())) { + if (!op->getIn(0)->getTypeReadFacing(op)->isPtrsubMatching(op->getIn(1)->getOffset(),0,1)) { if (op->getIn(1)->getOffset() == 0) { data.opRemoveInput(op, 1); data.opSetOpcode(op, CPUI_COPY); @@ -5232,6 +5232,7 @@ int4 ActionInferTypes::apply(Funcdata &data) if (localcount >= 7) { // This constant arrived at empirically if (localcount == 7) { data.warningHeader("Type propagation algorithm not settling"); + data.setTypeRecoveryExceeded(); localcount += 1; } return 0; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh index 23aafec62a..840c8c1978 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh @@ -68,7 +68,8 @@ class Funcdata { restart_pending = 0x400, ///< Analysis must be restarted (because of new override info) unimplemented_present = 0x800, ///< Set if function contains unimplemented instructions baddata_present = 0x1000, ///< Set if function flowed into bad data - double_precis_on = 0x2000 ///< Set if we are performing double precision recovery + double_precis_on = 0x2000, ///< Set if we are performing double precision recovery + typerecovery_exceeded= 0x4000 ///< Set if data-type propagation passes reached maximum }; uint4 flags; ///< Boolean properties associated with \b this function uint4 clean_up_index; ///< Creation index of first Varnode created after start of cleanup @@ -151,6 +152,7 @@ public: bool hasUnreachableBlocks(void) const { return ((flags&blocks_unreachable)!=0); } ///< Did this function exhibit unreachable code bool isTypeRecoveryOn(void) const { return ((flags&typerecovery_on)!=0); } ///< Will data-type analysis be performed bool hasTypeRecoveryStarted(void) const { return ((flags&typerecovery_start)!=0); } ///< Has data-type recovery processes started + bool isTypeRecoveryExceeded(void) const { return ((flags&typerecovery_exceeded)!=0); } ///< Has maximum propagation passes been reached bool hasNoCode(void) const { return ((flags & no_code)!=0); } ///< Return \b true if \b this function has no code body void setNoCode(bool val) { if (val) flags |= no_code; else flags &= ~no_code; } ///< Toggle whether \b this has a body void setLanedRegGenerated(void) { minLanedSize = 1000000; } ///< Mark that laned registers have been collected @@ -180,6 +182,7 @@ public: /// /// \param val is \b true if data-type analysis is enabled void setTypeRecovery(bool val) { flags = val ? (flags | typerecovery_on) : (flags & ~typerecovery_on); } + void setTypeRecoveryExceeded(void) { flags |= typerecovery_exceeded; } ///< Mark propagation passes have reached maximum void startCastPhase(void) { cast_phase_index = vbank.getCreateIndex(); } ///< Start the \b cast insertion phase uint4 getCastPhaseIndex(void) const { return cast_phase_index; } ///< Get creation index at the start of \b cast insertion uint4 getHighLevelIndex(void) const { return high_level_index; } ///< Get creation index at the start of HighVariable creation diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc index 658a3b45ca..e897fcf390 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc @@ -4,9 +4,9 @@ * 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. @@ -5920,30 +5920,30 @@ bool AddTreeState::spanAddTree(PcodeOp *op,uint8 treeCoeff) void AddTreeState::calcSubtype(void) { - if (size == 0 || nonmultsum < size) - offset = nonmultsum; + uint8 tmpoff = (multsum + nonmultsum) & ptrmask; + if (size == 0 || tmpoff < size) + offset = tmpoff; else { // For a sum that falls completely outside the data-type, there is presumably some // type of constant term added to an array index either at the current level or lower. // If we knew here whether an array of the baseType was possible we could make a slightly // better decision. - intb snonmult = sign_extend(nonmultsum,ptrsize*8-1); - snonmult = snonmult % size; - if (snonmult >= 0) + intb stmpoff = sign_extend(tmpoff,ptrsize*8-1); + stmpoff = stmpoff % size; + if (stmpoff >= 0) // We assume the sum is big enough it represents an array index at this level - offset = (uint8)snonmult; + offset = (uint8)stmpoff; else { // For a negative sum, if the baseType is a structure and there is array hints, // we assume the sum is an array index at a lower level - if (baseType->getMetatype() == TYPE_STRUCT && biggestNonMultCoeff != 0) - offset = nonmultsum; + if (baseType->getMetatype() == TYPE_STRUCT && biggestNonMultCoeff != 0 && multsum == 0) + offset = tmpoff; else - offset = (uint8)(snonmult + size); + offset = (uint8)(stmpoff + size); } } - correct = nonmultsum - offset; - nonmultsum = offset; - multsum = (multsum + correct) & ptrmask; // Some extra multiples of size + correct = nonmultsum; // Non-multiple constants are double counted, correct in final sum + multsum = (tmpoff - offset) & ptrmask; // Some extra multiples of size if (nonmult.empty()) { if ((multsum == 0) && multiple.empty()) { // Is there anything at all valid = false; @@ -5952,31 +5952,33 @@ void AddTreeState::calcSubtype(void) isSubtype = false; // There are no offsets INTO the pointer } else if (baseType->getMetatype() == TYPE_SPACEBASE) { - int8 nonmultbytes = AddrSpace::addressToByteInt(nonmultsum,ct->getWordSize()); // Convert to bytes + int8 offsetbytes = AddrSpace::addressToByteInt(offset,ct->getWordSize()); // Convert to bytes int8 extra; // Get offset into mapped variable - if (!hasMatchingSubType(nonmultbytes, biggestNonMultCoeff, &extra)) { + if (!hasMatchingSubType(offsetbytes, biggestNonMultCoeff, &extra)) { valid = false; // Cannot find mapped variable but nonmult is non-empty return; } extra = AddrSpace::byteToAddress(extra, ct->getWordSize()); // Convert back to address units - offset = (nonmultsum - extra) & ptrmask; + offset = (offset - extra) & ptrmask; + correct = (correct - extra) & ptrmask; isSubtype = true; } else if (baseType->getMetatype() == TYPE_STRUCT) { - intb snonmult = sign_extend(nonmultsum,ptrsize*8-1); - int8 nonmultbytes = AddrSpace::addressToByteInt(snonmult,ct->getWordSize()); // Convert to bytes + intb soffset = sign_extend(offset,ptrsize*8-1); + int8 offsetbytes = AddrSpace::addressToByteInt(soffset,ct->getWordSize()); // Convert to bytes int8 extra; // Get offset into field in structure - if (!hasMatchingSubType(nonmultbytes, biggestNonMultCoeff, &extra)) { - if (nonmultbytes < 0 || nonmultbytes >= baseType->getSize()) { // Compare as bytes! not address units + if (!hasMatchingSubType(offsetbytes, biggestNonMultCoeff, &extra)) { + if (offsetbytes < 0 || offsetbytes >= baseType->getSize()) { // Compare as bytes! not address units valid = false; // Out of structure's bounds return; } extra = 0; // No field, but pretend there is something there } extra = AddrSpace::byteToAddressInt(extra, ct->getWordSize()); // Convert back to address units - offset = (nonmultsum - extra) & ptrmask; + offset = (offset - extra) & ptrmask; + correct = (correct - extra) & ptrmask; if (pRelType != (TypePointerRel *)0 && offset == pRelType->getPointerOffset()) { // offset falls within basic ptrto if (!pRelType->evaluateThruParent(0)) { // If we are not representing offset 0 through parent @@ -5988,12 +5990,31 @@ void AddTreeState::calcSubtype(void) } else if (baseType->getMetatype() == TYPE_ARRAY) { isSubtype = true; + correct = (correct - offset) & ptrmask; offset = 0; } else { // No struct or array, but nonmult is non-empty valid = false; // There is substructure we don't know about } + if (pRelType != (const TypePointerRel *)0) { + int4 ptrOff = ((TypePointerRel *)ct)->getPointerOffset(); + offset = (offset - ptrOff) & ptrmask; + correct = (correct - ptrOff) & ptrmask; + } +} + +/// The data-type from the pointer input (of either a PTRSUB or PTRADD) is propagated to the +/// output of the PcodeOp. +/// \param op is the given PcodeOp +void AddTreeState::assignPropagatedType(PcodeOp *op) + +{ + Varnode *vn = op->getIn(0); + Datatype *inType = vn->getTypeReadFacing(op); + Datatype *newType = op->getOpcode()->propagateType(inType, op, vn, op->getOut(), 0, -1); + if (newType != (Datatype *)0) + op->getOut()->updateType(newType, false, false); } /// Construct part of the tree that sums to a multiple of the base data-type size. @@ -6037,7 +6058,6 @@ Varnode *AddTreeState::buildMultiples(void) Varnode *AddTreeState::buildExtra(void) { - correct = correct+offset; // Total correction that needs to be made Varnode *resNode = (Varnode *)0; for(int4 i=0;igetPointerOffset(); - offset -= ptrOff; - offset &= ptrmask; - } Varnode *multNode = buildMultiples(); Varnode *extraNode = buildExtra(); PcodeOp *newop = (PcodeOp *)0; @@ -6152,6 +6167,8 @@ void AddTreeState::buildTree(void) newop = data.newOpBefore(baseOp,CPUI_PTRADD,ptr,multNode,data.newConstant(ptrsize,size)); if (ptr->getType()->needsResolution()) data.inheritResolution(ptr->getType(),newop, 0, baseOp, baseSlot); + if (data.isTypeRecoveryExceeded()) + assignPropagatedType(newop); multNode = newop->getOut(); } else @@ -6162,6 +6179,8 @@ void AddTreeState::buildTree(void) newop = data.newOpBefore(baseOp,CPUI_PTRSUB,multNode,data.newConstant(ptrsize,offset)); if (multNode->getType()->needsResolution()) data.inheritResolution(multNode->getType(),newop, 0, baseOp, baseSlot); + if (data.isTypeRecoveryExceeded()) + assignPropagatedType(newop); if (size != 0) newop->setStopTypePropagation(); multNode = newop->getOut(); @@ -6555,6 +6574,8 @@ int4 RulePtraddUndo::applyOp(PcodeOp *op,Funcdata &data) return 1; } +const int4 RulePtrsubUndo::DEPTH_LIMIT = 8; + /// \class RulePtrsubUndo /// \brief Remove PTRSUB operations with mismatched data-type information /// @@ -6567,17 +6588,204 @@ void RulePtrsubUndo::getOpList(vector &oplist) const oplist.push_back(CPUI_PTRSUB); } +/// \brief Recursively search for additive constants and multiplicative constants +/// +/// Walking backward from the given Varnode, search for constants being added in and return +/// the sum of all the constants. Additionally pass back the biggest constant coefficient, for any term +/// formed with INT_MULT. +/// \param vn is the given root Varnode of the additive tree +/// \param multiplier will hold the biggest constant coefficient +/// \param maxLevel is the maximum depth to search in the tree +/// \return the sum of all constants in the additive expression +int8 RulePtrsubUndo::getConstOffsetBack(Varnode *vn,int8 &multiplier,int4 maxLevel) + +{ + multiplier = 1; + int8 submultiplier; + if (vn->isConstant()) + return vn->getOffset(); + if (!vn->isWritten()) + return 0; + maxLevel -= 1; + if (maxLevel < 0) + return 0; + PcodeOp *op = vn->getDef(); + OpCode opc = op->code(); + int8 retval = 0; + if (opc == CPUI_INT_ADD) { + retval += getConstOffsetBack(op->getIn(0),submultiplier,maxLevel); + if (submultiplier > multiplier) + multiplier = submultiplier; + retval += getConstOffsetBack(op->getIn(1), submultiplier, maxLevel); + if (submultiplier > multiplier) + multiplier = submultiplier; + } + else if (opc == CPUI_INT_MULT) { + Varnode *cvn = op->getIn(1); + if (!cvn->isConstant()) return 0; + multiplier = cvn->getOffset(); + getConstOffsetBack(op->getIn(0), submultiplier, maxLevel); + multiplier *= submultiplier; // Only contribute to the multiplier + } + return retval; +} + +/// \brief Collect constants and the biggest multiplier in the given PTRSUB expression. +/// +/// Walking the additive expression (INT_ADD, PTRADD, and other PTRSUBs) and calculate any additional +/// constant value being added to the PTRSUB. Additionally pass back the biggest constant coefficient of any +/// multiplicative term in the expression. +/// \param op is the given PTRSUB +/// \param multiplier will hold the biggest multiplicative coefficient +int8 RulePtrsubUndo::getExtraOffset(PcodeOp *op,int8 &multiplier) + +{ + int8 extra = 0; + multiplier = 1; + int8 submultiplier; + Varnode *outvn = op->getOut(); + op = outvn->loneDescend(); + while(op != (PcodeOp *)0) { + OpCode opc = op->code(); + if (opc == CPUI_INT_ADD) { + int4 slot = op->getSlot(outvn); + extra += getConstOffsetBack(op->getIn(1-slot),submultiplier,DEPTH_LIMIT); // Get any constants from other input + if (submultiplier > multiplier) + multiplier = submultiplier; + } + else if (opc == CPUI_PTRSUB) { + extra += op->getIn(1)->getOffset(); + } + else if (opc == CPUI_PTRADD) { + if (op->getIn(0) != outvn) break; + int8 ptraddmult = op->getIn(2)->getOffset(); + Varnode *invn = op->getIn(1); + if (invn->isConstant()) // Only contribute to the extra + extra += ptraddmult * (int8)invn->getOffset(); // if the index is constant + getConstOffsetBack(invn,submultiplier,DEPTH_LIMIT); // otherwise just contribute to multiplier + submultiplier *= ptraddmult; + if (submultiplier > multiplier) + multiplier = submultiplier; + } + else { + break; + } + outvn = op->getOut(); + op = outvn->loneDescend(); + } + extra = sign_extend(extra, 8*outvn->getSize()-1); + extra &= calc_mask(outvn->getSize()); + return extra; +} + +/// \brief Remove any constants in the additive expression rooted at the given PcodeOp +/// +/// Walking recursively through the expression, any INT_ADD with a constant input is converted to +/// a COPY. The INT_ADD must only contribute to the root expression. +/// \param op is the given root PcodeOp +/// \param slot is the input slot to walk back from +/// \param maxLevel is the maximum depth to recurse +/// \param data is the function containing the expression +/// \return the sum of all constants that are removed +int8 RulePtrsubUndo::removeLocalAddRecurse(PcodeOp *op,int4 slot,int4 maxLevel,Funcdata &data) + +{ + Varnode *vn = op->getIn(slot); + if (!vn->isWritten()) + return 0; + if (vn->loneDescend() != op) + return 0; // Varnode must not be used anywhere else + maxLevel -= 1; + if (maxLevel < 0) + return 0; + op = vn->getDef(); + int8 retval = 0; + if (op->code() == CPUI_INT_ADD) { + if (op->getIn(1)->isConstant()) { + retval += (int8)op->getIn(1)->getOffset(); + data.opRemoveInput(op, 1); + data.opSetOpcode(op, CPUI_COPY); + } + else { + retval += removeLocalAddRecurse(op, 0, maxLevel, data); + retval += removeLocalAddRecurse(op, 1, maxLevel, data); + } + } + return retval; +} + +/// \brief Remove constants in the additive expression involving the given Varnode +/// +/// Any additional PTRADD, PTRSUB, or INT_ADD that uses the Varnode and adds a constant is converted +/// to a COPY. Additionally any other INT_ADD involved in the expression that adds a constant is +/// also converted to COPY. +/// \param vn is the given Varnode +/// \param data is the function containing the expression +/// \return the sum of all constants that are removed +int8 RulePtrsubUndo::removeLocalAdds(Varnode *vn,Funcdata &data) + +{ + int8 extra = 0; + PcodeOp *op = vn->loneDescend(); + while(op != (PcodeOp *)0) { + OpCode opc = op->code(); + if (opc == CPUI_INT_ADD) { + int4 slot = op->getSlot(vn); + if (slot == 0 && op->getIn(1)->isConstant()) { + extra += (int8)op->getIn(1)->getOffset(); + data.opRemoveInput(op, 1); + data.opSetOpcode(op, CPUI_COPY); + } + else { + extra += removeLocalAddRecurse(op,1-slot,DEPTH_LIMIT, data); // Get any constants from other input + } + } + else if (opc == CPUI_PTRSUB) { + extra += op->getIn(1)->getOffset(); + op->clearStopTypePropagation(); + data.opRemoveInput(op, 1); + data.opSetOpcode(op, CPUI_COPY); + } + else if (opc == CPUI_PTRADD) { + if (op->getIn(0) != vn) break; + int8 ptraddmult = op->getIn(2)->getOffset(); + Varnode *invn = op->getIn(1); + if (invn->isConstant()) { + extra += ptraddmult * (int8)invn->getOffset(); + data.opRemoveInput(op,2); + data.opRemoveInput(op,1); + data.opSetOpcode(op, CPUI_COPY); + } + } + else { + break; + } + vn = op->getOut(); + op = vn->loneDescend(); + } + return extra; +} + int4 RulePtrsubUndo::applyOp(PcodeOp *op,Funcdata &data) { if (!data.hasTypeRecoveryStarted()) return 0; Varnode *basevn = op->getIn(0); - if (basevn->getTypeReadFacing(op)->isPtrsubMatching(op->getIn(1)->getOffset())) + Varnode *cvn = op->getIn(1); + int8 val = cvn->getOffset(); + int8 multiplier; + int8 extra = getExtraOffset(op,multiplier); + if (basevn->getTypeReadFacing(op)->isPtrsubMatching(val,extra,multiplier)) return 0; data.opSetOpcode(op,CPUI_INT_ADD); op->clearStopTypePropagation(); + extra = removeLocalAdds(op->getOut(),data); + if (extra != 0) { + val = val + extra; // Lump extra into additive offset + data.opSetInput(op,data.newConstant(cvn->getSize(), val & calc_mask(cvn->getSize())),1); + } return 1; } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh index b38af07623..97c299a04b 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh @@ -4,9 +4,9 @@ * 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. @@ -71,6 +71,7 @@ class AddTreeState { bool checkTerm(Varnode *vn,uint8 treeCoeff); ///< Accumulate details of given term and continue tree traversal bool spanAddTree(PcodeOp *op,uint8 treeCoeff); ///< Walk the given sub-tree accumulating details void calcSubtype(void); ///< Calculate final sub-type offset + void assignPropagatedType(PcodeOp *op); ///< Assign a data-type propagated through the given PcodeOp Varnode *buildMultiples(void); ///< Build part of tree that is multiple of base size Varnode *buildExtra(void); ///< Build part of tree not accounted for by multiples or \e offset bool buildDegenerate(void); ///< Transform ADD into degenerate PTRADD @@ -1079,6 +1080,11 @@ public: virtual int4 applyOp(PcodeOp *op,Funcdata &data); }; class RulePtrsubUndo : public Rule { + static const int4 DEPTH_LIMIT; ///< The maximum depth of the additive expression to check + static int8 getConstOffsetBack(Varnode *vn,int8 &multiplier,int4 maxLevel); + static int8 getExtraOffset(PcodeOp *op,int8 &multiplier); + static int8 removeLocalAdds(Varnode *vn,Funcdata &data); + static int8 removeLocalAddRecurse(PcodeOp *op,int4 slot,int4 maxLevel,Funcdata &data); public: RulePtrsubUndo(const string &g) : Rule(g, 0, "ptrsubundo") {} ///< Constructor virtual Rule *clone(const ActionGroupList &grouplist) const { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc index 7862028213..38376a0b26 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc @@ -4,9 +4,9 @@ * 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. @@ -528,8 +528,10 @@ void Datatype::calcAlignSize(void) /// A CPUI_PTRSUB must act on a pointer data-type where the given offset addresses a component. /// Perform this check. /// \param off is the given offset +/// \param extra is any additional constant being added to the pointer +/// \param multiplier is the size of any index multiplier being added to the pointer /// \return \b true if \b this is a suitable PTRSUB data-type -bool Datatype::isPtrsubMatching(uintb off) const +bool Datatype::isPtrsubMatching(int8 off,int8 extra,int8 multiplier) const { return false; @@ -897,14 +899,14 @@ void TypePointer::printRaw(ostream &s) const Datatype *TypePointer::getSubType(int8 off,int8 *newoff) const { - if (truncate == (TypePointer *)0) - return truncate; - int8 min = ((flags & truncate_bigendian) != 0) ? size - truncate->getSize() : 0; - if (off >= min && off < min + truncate->getSize()) { - *newoff = off - min; - return truncate; + if (truncate != (TypePointer *)0) { + int8 min = ((flags & truncate_bigendian) != 0) ? size - truncate->getSize() : 0; + if (off >= min && off < min + truncate->getSize()) { + *newoff = off - min; + return truncate; + } } - return (Datatype *)0; + return Datatype::getSubType(off, newoff); } int4 TypePointer::compare(const Datatype &op,int4 level) const @@ -960,6 +962,27 @@ void TypePointer::encode(Encoder &encoder) const encoder.closeElement(ELEM_TYPE); } +/// If the given data-type is an array, or has an arrayed component, return \b true. +/// \param dt is the given data-type to check +/// \param off is the out-of-bounds offset +/// \return \b true is an array is present +bool TypePointer::testForArraySlack(Datatype *dt,int8 off) + +{ + int8 newoff; + int8 elSize; + if (dt->getMetatype() == TYPE_ARRAY) + return true; + Datatype *compType; + if (off < 0) { + compType = dt->nearestArrayedComponentForward(off, &newoff, &elSize); + } + else { + compType = dt->nearestArrayedComponentBackward(off, &newoff, &elSize); + } + return (compType != (Datatype *)0); +} + /// Parse a \ element with a child describing the data-type being pointed to /// \param decoder is the stream decoder /// \param typegrp is the factory owning \b this data-type @@ -1070,19 +1093,49 @@ TypePointer *TypePointer::downChain(int8 &off,TypePointer *&par,int8 &parOff,boo return typegrp.getTypePointer(size,pt,wordsize); } -bool TypePointer::isPtrsubMatching(uintb off) const +bool TypePointer::isPtrsubMatching(int8 off,int8 extra,int8 multiplier) const { - if (ptrto->getMetatype()==TYPE_SPACEBASE) { + type_metatype meta = ptrto->getMetatype(); + if (meta==TYPE_SPACEBASE) { int8 newoff = AddrSpace::addressToByteInt(off,wordsize); - ptrto->getSubType(newoff,&newoff); - if (newoff != 0) + Datatype *subType = ptrto->getSubType(newoff,&newoff); + if (subType == (Datatype *)0 || newoff != 0) + return false; + extra = AddrSpace::addressToByteInt(extra,wordsize); + if (extra < 0 || extra >= subType->getSize()) { + if (!testForArraySlack(subType, extra)) + return false; + } + } + else if (meta == TYPE_ARRAY) { + if (off != 0) + return false; + multiplier = AddrSpace::addressToByteInt(multiplier,wordsize); + if (multiplier >= ptrto->getAlignSize()) return false; } - else if (ptrto->getMetatype() == TYPE_ARRAY || ptrto->getMetatype() == TYPE_STRUCT) { + else if (meta == TYPE_STRUCT) { int4 typesize = ptrto->getSize(); - if ((typesize <= AddrSpace::addressToByteInt(off,wordsize))&&(typesize!=0)) + multiplier = AddrSpace::addressToByteInt(multiplier,wordsize); + if (multiplier >= ptrto->getAlignSize()) return false; + int8 newoff = AddrSpace::addressToByteInt(off,wordsize); + extra = AddrSpace::addressToByteInt(extra, wordsize); + Datatype *subType = ptrto->getSubType(newoff,&newoff); + if (subType != (Datatype *)0) { + if (newoff != 0) + return false; + if (extra < 0 || extra >= subType->getSize()) { + if (!testForArraySlack(subType, extra)) + return false; + } + } + else { + extra += newoff; + if ((extra < 0 || extra >= typesize)&&(typesize!=0)) + return false; + } } else if (ptrto->getMetatype() == TYPE_UNION) { // A PTRSUB reaching here cannot be used for a union field resolution @@ -1154,6 +1207,8 @@ int4 TypeArray::compareDependency(const Datatype &op) const Datatype *TypeArray::getSubType(int8 off,int8 *newoff) const { // Go down exactly one level, to type of element + if (off >= size) + return Datatype::getSubType(off, newoff); *newoff = off % arrayof->getAlignSize(); return arrayof; } @@ -2465,13 +2520,14 @@ TypePointer *TypePointerRel::downChain(int8 &off,TypePointer *&par,int8 &parOff, return origPointer->downChain(off,par,parOff,allowArrayWrap,typegrp); } -bool TypePointerRel::isPtrsubMatching(uintb off) const +bool TypePointerRel::isPtrsubMatching(int8 off,int8 extra,int8 multiplier) const { if (stripped != (TypePointer *)0) - return TypePointer::isPtrsubMatching(off); + return TypePointer::isPtrsubMatching(off,extra,multiplier); int4 iOff = AddrSpace::addressToByteInt(off,wordsize); - iOff += offset; + extra = AddrSpace::addressToByteInt(extra, wordsize); + iOff += offset + extra; return (iOff >= 0 && iOff <= parent->getSize()); } @@ -3874,27 +3930,27 @@ TypePointer *TypeFactory::resizePointer(TypePointer *ptr,int4 newSize) Datatype *TypeFactory::getExactPiece(Datatype *ct,int4 offset,int4 size) { - if (offset + size > ct->getSize()) - return (Datatype *)0; Datatype *lastType = (Datatype *)0; int8 lastOff = 0; int8 curOff = offset; do { - if (ct->getSize() <= size) { - if (ct->getSize() == size) - return ct; // Perfect size match - break; + if (ct->getSize() < size + curOff) { // Range is beyond end of current data-type + break; // Construct partial around last data-type } - else if (ct->getMetatype() == TYPE_UNION) { + if (ct->getSize() == size) + return ct; // Perfect size match + if (ct->getMetatype() == TYPE_UNION) { return getTypePartialUnion((TypeUnion *)ct, curOff, size); } lastType = ct; lastOff = curOff; ct = ct->getSubType(curOff,&curOff); } while(ct != (Datatype *)0); - // If we reach here, lastType is bigger than size - if (lastType->getMetatype() == TYPE_STRUCT || lastType->getMetatype() == TYPE_ARRAY) - return getTypePartialStruct(lastType, lastOff, size); + if (lastType != (Datatype *)0) { + // If we reach here, lastType is bigger than size + if (lastType->getMetatype() == TYPE_STRUCT || lastType->getMetatype() == TYPE_ARRAY) + return getTypePartialStruct(lastType, lastOff, size); + } return (Datatype *)0; } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh index 5fa888190e..32cf96dbb0 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh @@ -268,7 +268,7 @@ public: virtual int4 compare(const Datatype &op,int4 level) const; ///< Order types for propagation virtual int4 compareDependency(const Datatype &op) const; ///< Compare for storage in tree structure virtual void encode(Encoder &encoder) const; ///< Encode the data-type to a stream - virtual bool isPtrsubMatching(uintb off) const; ///< Is this data-type suitable as input to a CPUI_PTRSUB op + virtual bool isPtrsubMatching(int8 off,int8 extra,int8 multiplier) const; ///< Is this data-type suitable as input to a CPUI_PTRSUB op virtual Datatype *getStripped(void) const; ///< Get a stripped version of \b this for formal use in formal declarations virtual Datatype *resolveInFlow(PcodeOp *op,int4 slot); ///< Tailor data-type propagation based on Varnode use virtual Datatype* findResolve(const PcodeOp *op,int4 slot); ///< Find a previously resolved sub-type @@ -393,6 +393,7 @@ protected: AddrSpace *spaceid; ///< If non-null, the address space \b this is intented to point into TypePointer *truncate; ///< Truncated form of the pointer (if not null) uint4 wordsize; ///< What size unit does the pointer address + static bool testForArraySlack(Datatype *dt,int8 off); ///< Test if an \e out-of-bounds offset makes sense as array slack void decode(Decoder &decoder,TypeFactory &typegrp); ///< Restore \b this pointer data-type from a stream void calcSubmeta(void); ///< Calculate specific submeta for \b this pointer void calcTruncate(TypeFactory &typegrp); // Assign a truncated pointer subcomponent if necessary @@ -420,7 +421,7 @@ public: virtual Datatype *clone(void) const { return new TypePointer(*this); } virtual void encode(Encoder &encoder) const; virtual TypePointer *downChain(int8 &off,TypePointer *&par,int8 &parOff,bool allowArrayWrap,TypeFactory &typegrp); - virtual bool isPtrsubMatching(uintb off) const; + virtual bool isPtrsubMatching(int8 off,int8 extra,int8 multiplier) const; virtual Datatype *resolveInFlow(PcodeOp *op,int4 slot); virtual Datatype* findResolve(const PcodeOp *op,int4 slot); }; @@ -633,7 +634,7 @@ public: virtual Datatype *clone(void) const { return new TypePointerRel(*this); } virtual void encode(Encoder &encoder) const; virtual TypePointer *downChain(int8 &off,TypePointer *&par,int8 &parOff,bool allowArrayWrap,TypeFactory &typegrp); - virtual bool isPtrsubMatching(uintb off) const; + virtual bool isPtrsubMatching(int8 off,int8 extra,int8 multiplier) const; virtual Datatype *getStripped(void) const { return stripped; } ///< Get the plain form of the pointer static Datatype *getPtrToFromParent(Datatype *base,int4 off,TypeFactory &typegrp); }; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc index 3fb7fa9bb6..3264c3fd7d 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc @@ -411,15 +411,15 @@ Datatype *TypeOpLoad::propagateType(Datatype *alttype,PcodeOp *op,Varnode *invn, Datatype *newtype; if (inslot == -1) { // Propagating output to input (value to ptr) AddrSpace *spc = op->getIn(0)->getSpaceFromConst(); - newtype = tlst->getTypePointerNoDepth(outvn->getTempType()->getSize(),alttype,spc->getWordSize()); + newtype = tlst->getTypePointerNoDepth(outvn->getSize(),alttype,spc->getWordSize()); } else if (alttype->getMetatype()==TYPE_PTR) { newtype = ((TypePointer *)alttype)->getPtrTo(); - if (newtype->getSize() != outvn->getTempType()->getSize() || newtype->isVariableLength()) // Size must be appropriate - newtype = outvn->getTempType(); + if (newtype->getSize() != outvn->getSize() || newtype->isVariableLength()) // Size must be appropriate + newtype = (Datatype *)0; } else - newtype = outvn->getTempType(); // Don't propagate anything + newtype = (Datatype *)0; // Don't propagate anything return newtype; } @@ -486,15 +486,15 @@ Datatype *TypeOpStore::propagateType(Datatype *alttype,PcodeOp *op,Varnode *invn Datatype *newtype; if (inslot==2) { // Propagating value to ptr AddrSpace *spc = op->getIn(0)->getSpaceFromConst(); - newtype = tlst->getTypePointerNoDepth(outvn->getTempType()->getSize(),alttype,spc->getWordSize()); + newtype = tlst->getTypePointerNoDepth(outvn->getSize(),alttype,spc->getWordSize()); } else if (alttype->getMetatype()==TYPE_PTR) { newtype = ((TypePointer *)alttype)->getPtrTo(); - if (newtype->getSize() != outvn->getTempType()->getSize() || newtype->isVariableLength()) - newtype = outvn->getTempType(); + if (newtype->getSize() != outvn->getSize() || newtype->isVariableLength()) + newtype = (Datatype *)0; } else - newtype = outvn->getTempType(); // Don't propagate anything + newtype = (Datatype *)0; // Don't propagate anything return newtype; } @@ -1106,7 +1106,7 @@ Datatype *TypeOpIntAdd::propagateType(Datatype *alttype,PcodeOp *op,Varnode *inv if (outvn->isConstant() && (alttype->getMetatype() != TYPE_PTR)) newtype = alttype; else if (inslot == -1) // Propagating output to input - newtype = op->getIn(outslot)->getTempType(); // Don't propagate pointer types this direction + newtype = (Datatype *)0; // Don't propagate pointer types this direction else newtype = propagateAddIn2Out(alttype,tlst,op,inslot); return newtype; @@ -1130,7 +1130,7 @@ Datatype *TypeOpIntAdd::propagateAddIn2Out(Datatype *alttype,TypeFactory *typegr TypePointer *pointer = (TypePointer *)alttype; uintb offset; int4 command = propagateAddPointer(offset,op,inslot,pointer->getPtrTo()->getAlignSize()); - if (command == 2) return op->getOut()->getTempType(); // Doesn't look like a good pointer add + if (command == 2) return (Datatype *)0; // Doesn't look like a good pointer add TypePointer *parent = (TypePointer *)0; int8 parentOff; if (command != 3) { @@ -1155,7 +1155,7 @@ Datatype *TypeOpIntAdd::propagateAddIn2Out(Datatype *alttype,TypeFactory *typegr if (pointer == (TypePointer *)0) { if (command == 0) return alttype; - return op->getOut()->getTempType(); + return (Datatype *)0; } if (op->getIn(inslot)->isSpacebase()) { if (pointer->getPtrTo()->getMetatype() == TYPE_SPACEBASE) @@ -2112,7 +2112,7 @@ Datatype *TypeOpPtradd::propagateType(Datatype *alttype,PcodeOp *op,Varnode *inv if (metain != TYPE_PTR) return (Datatype *)0; Datatype *newtype; if (inslot == -1) // Propagating output to input - newtype = op->getIn(outslot)->getTempType(); // Don't propagate pointer types this direction + newtype = (Datatype *)0; // Don't propagate pointer types this direction else newtype = TypeOpIntAdd::propagateAddIn2Out(alttype,tlst,op,inslot); return newtype; @@ -2192,7 +2192,7 @@ Datatype *TypeOpPtrsub::propagateType(Datatype *alttype,PcodeOp *op,Varnode *inv if (metain != TYPE_PTR) return (Datatype *)0; Datatype *newtype; if (inslot == -1) // Propagating output to input - newtype = op->getIn(outslot)->getTempType(); // Don't propagate pointer types this direction + newtype = (Datatype *)0; // Don't propagate pointer types this direction else newtype = TypeOpIntAdd::propagateAddIn2Out(alttype,tlst,op,inslot); return newtype; From 614f20cfa3feb44d418b55040f0f5aa65c253fe3 Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Mon, 22 Jul 2024 21:12:51 +0000 Subject: [PATCH 05/53] GP-4790 Collapse double precision COPY to address forced storage --- .../Decompiler/certification.manifest | 1 + .../src/decompile/cpp/coreaction.cc | 5 +- .../Decompiler/src/decompile/cpp/double.cc | 237 +++++++++++++++--- .../Decompiler/src/decompile/cpp/double.hh | 40 ++- .../Decompiler/src/decompile/cpp/funcdata.hh | 5 +- .../src/decompile/cpp/funcdata_varnode.cc | 76 +++++- .../src/decompile/datatests/doublemove.xml | 42 ++++ 7 files changed, 360 insertions(+), 46 deletions(-) create mode 100644 Ghidra/Features/Decompiler/src/decompile/datatests/doublemove.xml diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index fba3250745..44392d08d4 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -21,6 +21,7 @@ src/decompile/datatests/deindirect.xml||GHIDRA||||END| src/decompile/datatests/deindirect2.xml||GHIDRA||||END| src/decompile/datatests/displayformat.xml||GHIDRA||||END| src/decompile/datatests/divopt.xml||GHIDRA||||END| +src/decompile/datatests/doublemove.xml||GHIDRA||||END| src/decompile/datatests/dupptr.xml||GHIDRA||||END| src/decompile/datatests/elseif.xml||GHIDRA||||END| src/decompile/datatests/floatprint.xml||GHIDRA||||END| diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc index a79c3030c3..ca1fe51b72 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc @@ -4,9 +4,9 @@ * 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. @@ -5538,6 +5538,7 @@ void ActionDatabase::universalAction(Architecture *conf) actprop->addRule( new RuleDoubleLoad("doubleload") ); actprop->addRule( new RuleDoubleStore("doubleprecis") ); actprop->addRule( new RuleDoubleIn("doubleprecis") ); + actprop->addRule( new RuleDoubleOut("doubleprecis") ); for(iter=conf->extra_pool_rules.begin();iter!=conf->extra_pool_rules.end();++iter) actprop->addRule( *iter ); // Add CPU specific rules conf->extra_pool_rules.clear(); // Rules are now absorbed into universal diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/double.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/double.cc index 840ed38365..3537c3e5ef 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/double.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/double.cc @@ -4,9 +4,9 @@ * 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. @@ -1220,6 +1220,13 @@ int4 SplitVarnode::applyRuleIn(SplitVarnode &in,Funcdata &data) return 1; } break; + case CPUI_COPY: + if (workop->getOut()->isAddrForce()) { + CopyForceForm copyform; + if (copyform.applyRule(in, workop, workishi, data)) + return 1; + } + break; default: break; } @@ -1385,6 +1392,47 @@ void SplitVarnode::replaceIndirectOp(Funcdata &data,SplitVarnode &out,SplitVarno out.buildHiFromWhole(data); } +/// \brief Rewrite the double precision version of a COPY to an address forced Varnode +/// +/// This assumes that we have checked that the transformation is possible. The logical input must already +/// exist, and after this method is called, the logical output will also exist. The original COPY pieces +/// are explicitly destroyed. +/// \param data is the function owning the COPYs +/// \param addr is the storage address being COPYed +/// \param in is the input to the COPYs +/// \param copylo is the original least significant COPY +/// \param copyhi is the original most significant COPY +void SplitVarnode::replaceCopyForce(Funcdata &data,const Address &addr,SplitVarnode &in,PcodeOp *copylo,PcodeOp *copyhi) + +{ + Varnode *inVn = in.getWhole(); + bool returnForm = copyhi->stopsCopyPropagation(); + if (returnForm && inVn->getAddr() != addr) { + // Placeholder for global propagation past a RETURN needs an additional COPY + PcodeOp *otherPoint1 = copyhi->getIn(0)->getDef(); + PcodeOp *otherPoint2 = copylo->getIn(0)->getDef(); + // We know these are COPYs in the same basic block. Compute the later one. + if (otherPoint1->getSeqNum().getOrder() < otherPoint2->getSeqNum().getOrder()) + otherPoint1 = otherPoint2; + PcodeOp *otherCopy = data.newOp(1, otherPoint1->getAddr()); + data.opSetOpcode(otherCopy, CPUI_COPY); + Varnode *vn = data.newVarnodeOut(in.getSize(), addr, otherCopy); + data.opSetInput(otherCopy,inVn,0); + data.opInsertBefore(otherCopy, otherPoint1); + inVn = vn; + } + PcodeOp *wholeCopy = data.newOp(1, copyhi->getAddr()); + data.opSetOpcode(wholeCopy, CPUI_COPY); + Varnode *outVn = data.newVarnodeOut(in.getSize(), addr, wholeCopy); + outVn->setAddrForce(); + if (returnForm) + wholeCopy->setStopCopyPropagation(); + data.opSetInput(wholeCopy,inVn,0); + data.opInsertBefore(wholeCopy, copyhi); + data.opDestroy(copyhi); // Destroy the original COPYs. Outputs have no descendants. + data.opDestroy(copylo); +} + bool AddForm::checkForCarry(PcodeOp *op) { // If -op- matches a CARRY construction based on lo1 (i.e. CARRY(x,lo1) ) @@ -3151,6 +3199,73 @@ bool IndirectForm::applyRule(SplitVarnode &i,PcodeOp *ind,bool workishi,Funcdata return true; } +/// Starting with the input pieces, identify the matching COPYs and verify that they act as a single +/// address forced COPY with no descendants. +/// \param h is the most significant input piece +/// \param l is the least significant input piece +/// \param w is the preexisting logical whole +/// \param cpy is the COPY of the most significant piece +bool CopyForceForm::verify(Varnode *h,Varnode *l,Varnode *w,PcodeOp *cpy) + +{ + if (w == (Varnode *)0) + return false; + copyhi = cpy; + if (copyhi->getIn(0) != h) return false; + reshi = copyhi->getOut(); + if (!reshi->isAddrForce() || !reshi->hasNoDescend()) + return false; + list::const_iterator iter,enditer; + iter = l->beginDescend(); + enditer = l->endDescend(); + while(iter != enditer) { + copylo = *iter; + ++iter; + if (copylo->code() != CPUI_COPY || copylo->getParent() != copyhi->getParent()) + continue; + reslo = copylo->getOut(); + if (!reslo->isAddrForce() || !reslo->hasNoDescend()) + continue; + if (!SplitVarnode::isAddrTiedContiguous(reslo, reshi, addrOut)) // Output MUST be contiguous addresses + continue; + if (copyhi->stopsCopyPropagation()) { // Special form has additional requirements + if (h->loneDescend() == (PcodeOp *)0) + continue; + if (l->loneDescend() == (PcodeOp *)0) + continue; + if (w->getAddr() != addrOut) { // Input whole MUST also be the same address + // Unless there are addition COPYs from the same basic block + if (!h->isWritten() || !l->isWritten()) + continue; + PcodeOp *otherLo = l->getDef(); + PcodeOp *otherHi = h->getDef(); + if (otherLo->code() != CPUI_COPY || otherHi->code() != CPUI_COPY) + continue; + if (otherLo->getParent() != otherHi->getParent()) + continue; + } + } + return true; + } + return false; +} + +/// \param i is the putative input to the COPYs +/// \param cpy is a putative COPY +/// \param workishi is \b true if the COPY is of the most significant piece +/// \param data is the function +bool CopyForceForm::applyRule(SplitVarnode &i,PcodeOp *cpy,bool workishi,Funcdata &data) + +{ + if (!workishi) return false; + if (!i.hasBothPieces()) return false; + in = i; + if (!verify(in.getHi(),in.getLo(),in.getWhole(),cpy)) + return false; + SplitVarnode::replaceCopyForce(data, addrOut, in, copylo, copyhi); + return true; +} + void RuleDoubleIn::reset(Funcdata &data) { @@ -3168,10 +3283,10 @@ void RuleDoubleIn::getOpList(vector &oplist) const /// If the given Varnode looks like the most significant piece, there is another SUBPIECE that looks /// like the least significant piece, and the whole is from an operation that produces a logical whole, /// then mark the Varnode (and its companion) as double precision pieces and return 1. -/// \param data is the function owning the Varnode /// \param vn is the given Varnode /// \param subpieceOp is the SUBPIECE PcodeOp producing the Varnode -int4 RuleDoubleIn::attemptMarking(Funcdata &data,Varnode *vn,PcodeOp *subpieceOp) +/// \return 1 if the pieces are marked, 0 otherwise +int4 RuleDoubleIn::attemptMarking(Varnode *vn,PcodeOp *subpieceOp) { Varnode *whole = subpieceOp->getIn(0); @@ -3190,36 +3305,10 @@ int4 RuleDoubleIn::attemptMarking(Funcdata &data,Varnode *vn,PcodeOp *subpieceOp } else { // Categorize opcodes as "producing a logical whole" - switch(whole->getDef()->code()) { - case CPUI_INT_ADD: - // Its hard to tell if the bit operators are really being used to act on the "logical whole" -// case CPUI_INT_AND: -// case CPUI_INT_OR: -// case CPUI_INT_XOR: -// case CPUI_INT_NEGATE: - case CPUI_INT_MULT: - case CPUI_INT_DIV: - case CPUI_INT_SDIV: - case CPUI_INT_REM: - case CPUI_INT_SREM: - case CPUI_INT_2COMP: - case CPUI_FLOAT_ADD: - case CPUI_FLOAT_DIV: - case CPUI_FLOAT_MULT: - case CPUI_FLOAT_SUB: - case CPUI_FLOAT_NEG: - case CPUI_FLOAT_ABS: - case CPUI_FLOAT_SQRT: - case CPUI_FLOAT_INT2FLOAT: - case CPUI_FLOAT_FLOAT2FLOAT: - case CPUI_FLOAT_TRUNC: - case CPUI_FLOAT_CEIL: - case CPUI_FLOAT_FLOOR: - case CPUI_FLOAT_ROUND: - break; - default: - return 0; - } + // Its hard to tell if a logical op is really being used to act on the "logical whole" + TypeOp *typeop = whole->getDef()->getOpcode(); + if (!typeop->isArithmeticOp() && !typeop->isFloatingPointOp()) + return 0; } Varnode *vnLo = (Varnode *)0; list::const_iterator iter; @@ -3240,11 +3329,11 @@ int4 RuleDoubleIn::attemptMarking(Funcdata &data,Varnode *vn,PcodeOp *subpieceOp int4 RuleDoubleIn::applyOp(PcodeOp *op,Funcdata &data) -{ // Try to push double precision object "down" one level from input +{ Varnode *outvn = op->getOut(); if (!outvn->isPrecisLo()) { if (outvn->isPrecisHi()) return 0; - return attemptMarking(data, outvn, op); + return attemptMarking(outvn, op); } if (data.hasUnreachableBlocks()) return 0; @@ -3260,6 +3349,82 @@ int4 RuleDoubleIn::applyOp(PcodeOp *op,Funcdata &data) return 0; } +void RuleDoubleOut::getOpList(vector &oplist) const + +{ + oplist.push_back(CPUI_PIECE); +} + +/// \brief Determine if the given inputs to a PIECE should marked as double precision pieces +/// +/// If the concatenation of the pieces is used as a logical whole by other ops, the two pieces +/// are marked and 1 is returned. +/// \param vnhi is the most significant input to the PIECE +/// \param vnlo is the least significant input +/// \param pieceOp is the op reading the pieces +/// \return 1 if the pieces are marked, 0 otherwise +int4 RuleDoubleOut::attemptMarking(Varnode *vnhi,Varnode *vnlo,PcodeOp *pieceOp) + +{ + Varnode *whole = pieceOp->getOut(); + if (whole->isTypeLock()) { + if (!whole->getType()->isPrimitiveWhole()) + return 0; // Don't mark for double precision if not a primitive type + } + if (vnhi->getSize() != vnlo->getSize()) + return 0; + + SymbolEntry *entryhi = vnhi->getSymbolEntry(); + SymbolEntry *entrylo = vnlo->getSymbolEntry(); + if (entryhi != (SymbolEntry *)0 || entrylo != (SymbolEntry *)0) { + if (entryhi == (SymbolEntry *)0 || entrylo == (SymbolEntry *)0) + return 0; // One has a symbol, one doesn't + if (entryhi->getSymbol() != entrylo->getSymbol()) + return 0; // Not from the same symbol + } + + bool isWhole = false; + list::const_iterator iter; + for(iter=whole->beginDescend();iter!=whole->endDescend();++iter) { + TypeOp *typeop = (*iter)->getOpcode(); + // Categorize op as "reading a logical whole" + if (typeop->isArithmeticOp() || typeop->isFloatingPointOp()) { + isWhole = true; + break; + } + } + if (!isWhole) + return 0; + vnhi->setPrecisHi(); + vnlo->setPrecisLo(); + return 1; +} + +int4 RuleDoubleOut::applyOp(PcodeOp *op,Funcdata &data) + +{ + Varnode *vnhi= op->getIn(0); + Varnode *vnlo = op->getIn(1); + + // Currently this only implements collapsing input varnodes read by CPUI_PIECE + // So we put the test for this particular case early + if (!vnhi->isInput() || !vnlo->isInput()) + return 0; + if (!vnhi->isPersist() || !vnlo->isPersist()) + return 0; + + if (!vnhi->isPrecisHi() || !vnlo->isPrecisLo()) { + return attemptMarking(vnhi,vnlo,op); + } + if (data.hasUnreachableBlocks()) return 0; + + Address addr; + if (!SplitVarnode::isAddrTiedContiguous(vnlo, vnhi, addr)) + return 0; + data.combineInputVarnodes(vnhi,vnlo); + return 1; +} + /// \brief Scan for conflicts between two LOADs or STOREs that would prevent them from being combined /// /// The PcodeOps must be in the same basic block. Each PcodeOp that falls in between is examined diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/double.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/double.hh index 175f6024ae..0ff546dc05 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/double.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/double.hh @@ -4,9 +4,9 @@ * 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. @@ -95,6 +95,7 @@ public: PcodeOp *existop); static bool prepareIndirectOp(SplitVarnode &in,PcodeOp *affector); static void replaceIndirectOp(Funcdata &data,SplitVarnode &out,SplitVarnode &in,PcodeOp *affector); + static void replaceCopyForce(Funcdata &data,const Address &addr,SplitVarnode &in,PcodeOp *copylo,PcodeOp *copyhi); static int4 applyRuleIn(SplitVarnode &in,Funcdata &data); }; @@ -299,14 +300,30 @@ public: bool applyRule(SplitVarnode &i,PcodeOp *ind,bool workishi,Funcdata &data); }; -/// \brief Simply a double precision operation, starting from a marked double precision input. +/// \brief Collapse two COPYs into contiguous address forced Varnodes +/// +/// The inputs must be pieces of a logical whole and outputs must be address forced with no descendants. +/// Take into account special form of COPYs holding global variables upto/past a RETURN. +class CopyForceForm { + SplitVarnode in; ///< Incoming pieces to COPY + Varnode *reslo; ///< Least significant result of global COPY + Varnode *reshi; ///< Most significant result of global COPY + PcodeOp *copylo; ///< Partial COPY of least significant piece + PcodeOp *copyhi; ///< Partial COPY of most significant piece + Address addrOut; ///< Storage address +public: + bool verify(Varnode *h,Varnode *l,Varnode *w,PcodeOp *cpy); ///< Make sure the COPYs have the correct form + bool applyRule(SplitVarnode &i,PcodeOp *cpy,bool workishi,Funcdata &data); /// Verify and then collapse COPYs +}; + +/// \brief Simply a double precision operation, pushing down one level, starting from a marked double precision input. /// /// This rule starts by trying to find a pair of Varnodes that are SUBPIECE from a whole, /// are marked as double precision, and that are then used in some double precision operation. /// The various operation \e forms are overlayed on the data-flow until a matching one is found. The /// pieces of the double precision operation are then transformed into a single logical operation on the whole. class RuleDoubleIn : public Rule { - int4 attemptMarking(Funcdata &data,Varnode *vn,PcodeOp *subpieceOp); + int4 attemptMarking(Varnode *vn,PcodeOp *subpieceOp); public: RuleDoubleIn(const string &g) : Rule(g, 0, "doublein") {} ///< Constructor virtual Rule *clone(const ActionGroupList &grouplist) const { @@ -318,6 +335,20 @@ public: virtual int4 applyOp(PcodeOp *op,Funcdata &data); }; +/// \brief Simplify a double precision operation, pulling back one level, starting from inputs to a PIECE operation +class RuleDoubleOut : public Rule { + int4 attemptMarking(Varnode *vnhi,Varnode *vnlo,PcodeOp *pieceOp); +public: + RuleDoubleOut(const string &g) : Rule(g, 0, "doubleout") {} ///< Constructor + virtual Rule *clone(const ActionGroupList &grouplist) const { + if (!grouplist.contains(getGroup())) return (Rule *)0; + return new RuleDoubleOut(getGroup()); + } + virtual void getOpList(vector &oplist) const; + virtual int4 applyOp(PcodeOp *op,Funcdata &data); +}; + +/// \brief Collapse contiguous loads: `x = CONCAT44(*(ptr+4),*ptr) => x = *ptr` class RuleDoubleLoad : public Rule { public: RuleDoubleLoad(const string &g) : Rule( g, 0, "doubleload") {} @@ -330,6 +361,7 @@ public: static PcodeOp *noWriteConflict(PcodeOp *op1,PcodeOp *op2,AddrSpace *spc,vector *indirects); }; +/// \brief Collapse contiguous stores: `*ptr = SUB(x,0); *(ptr + 4) = SUB(x,4) => *ptr = x` class RuleDoubleStore : public Rule { public: RuleDoubleStore(const string &g) : Rule( g, 0, "doublestore") {} diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh index 23aafec62a..5acda5b585 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh @@ -4,9 +4,9 @@ * 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. @@ -288,6 +288,7 @@ public: Varnode *newUnique(int4 s,Datatype *ct=(Datatype *)0); ///< Create a new \e temporary Varnode Varnode *newCodeRef(const Address &m); ///< Create a code address \e annotation Varnode Varnode *setInputVarnode(Varnode *vn); ///< Mark a Varnode as an input to the function + void combineInputVarnodes(Varnode *vnHi,Varnode *vnLo); ///< Combine two contiguous input Varnodes into one Varnode *newExtendedConstant(int4 s,uint8 *val,PcodeOp *op); ///< Create extended precision constant void adjustInputVarnodes(const Address &addr,int4 sz); void deleteVarnode(Varnode *vn) { vbank.destroy(vn); } ///< Delete the given varnode diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc index 23671ba026..a7b1bc1097 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc @@ -4,9 +4,9 @@ * 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. @@ -372,6 +372,78 @@ Varnode *Funcdata::setInputVarnode(Varnode *vn) return vn; } +/// A new Varnode that covers both the original Varnodes is created and is itself marked as a function input. +/// Any CPUI_PIECE reading the original Varnodes is converted to a CPUI_COPY reading the new Varnode. If there +/// are other ops reading the original Varnodes, they are changed to read replacement Varnodes, which are +/// defined as SUBPIECEs of the new Varnode. The original Varnodes are destroyed. +/// \param vnHi is the most significant Varnode to combine +/// \param vnLo is the least significant Varnode +void Funcdata::combineInputVarnodes(Varnode *vnHi,Varnode *vnLo) + +{ + if (!vnHi->isInput() || !vnLo->isInput()) + throw LowlevelError("Varnodes being combined are not inputs"); + bool isContiguous; + Address addr = vnLo->getAddr(); + if (addr.isBigEndian()) { + addr = vnHi->getAddr(); + Address otheraddr = addr + vnHi->getSize(); + isContiguous = (otheraddr == vnLo->getAddr()); + } + else { + Address otheraddr = addr + vnLo->getSize(); + isContiguous = (otheraddr == vnHi->getAddr()); + } + if (!isContiguous) + throw LowlevelError("Input varnodes being combined are not contiguous"); + vector pieceList; + bool otherOps = false; + list::const_iterator iter; + for(iter=vnHi->beginDescend();iter!=vnHi->endDescend();++iter) { + PcodeOp *op = *iter; + if (op->code() == CPUI_PIECE && op->getIn(0) == vnHi && op->getIn(1) == vnLo) + pieceList.push_back(op); + else + otherOps = true; + } + for(int4 i=0;igetStart()); + opSetOpcode(subHi, CPUI_SUBPIECE); + opSetInput(subHi,newConstant(4, vnLo->getSize()),1); + Varnode *newHi = newVarnodeOut(vnHi->getSize(),vnHi->getAddr(),subHi); + opInsertBegin(subHi, bb); + subLo = newOp(2,bb->getStart()); + opSetOpcode(subLo, CPUI_SUBPIECE); + opSetInput(subLo,newConstant(4, 0),1); + Varnode *newLo = newVarnodeOut(vnLo->getSize(),vnLo->getAddr(),subLo); + opInsertBegin(subLo, bb); + totalReplace(vnHi, newHi); + totalReplace(vnLo, newLo); + } + int4 outSize = vnHi->getSize() + vnLo->getSize(); + vbank.destroy(vnHi); + vbank.destroy(vnLo); + Varnode *inVn = newVarnode(outSize, addr); + inVn = setInputVarnode(inVn); + for(int4 i=0;i + + + + 3c020042d4424318afa40000 +c4414318c440431cafa60008afa7000c +03e0000846201000 + + + 3c020042d7a20028afa40000 +c4414320c440432446201000e4414308 +e440430c03e0000846201000 + + + 401c000000000000 + + + + + +return glob1 \+ glob1; +r0x00424318:8\(i\) \+ r0x00424318:8\(i\) +r0x00424318:8\(0x.*\) = r0x00424318:8\(i\) +glob2 = in_stack_00000028 \+ 7\.0; +r0x00424308.* = f0_1.* +r0x00424308.* = r0x00424308.* + From 0e558caa3dd36a27d4b95e5b90cb684c8842e4cf Mon Sep 17 00:00:00 2001 From: Lee Chagolla-Christensen Date: Wed, 21 Aug 2024 15:06:25 -0700 Subject: [PATCH 06/53] Fix null exception in PropagateExternalParametersScript.java --- .../PropagateExternalParametersScript.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Ghidra/Features/Base/ghidra_scripts/PropagateExternalParametersScript.java b/Ghidra/Features/Base/ghidra_scripts/PropagateExternalParametersScript.java index 1eecf5918e..9ae737c9bf 100644 --- a/Ghidra/Features/Base/ghidra_scripts/PropagateExternalParametersScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/PropagateExternalParametersScript.java @@ -134,8 +134,8 @@ public class PropagateExternalParametersScript extends GhidraScript { for (Reference extRef : extRefs) { Address refAddr = extRef.getFromAddress(); - String refMnemonic = listing.getCodeUnitAt(refAddr).getMnemonicString(); + Function calledFromFunc = listing.getFunctionContaining(refAddr); if (calledFromFunc == null) { continue; @@ -147,8 +147,14 @@ public class PropagateExternalParametersScript extends GhidraScript { while (tempIter.hasNext()) { Reference thunkRef = tempIter.next(); Address thunkRefAddr = thunkRef.getFromAddress(); - String thunkRefMnemonic = - listing.getCodeUnitAt(thunkRefAddr).getMnemonicString(); + + CodeUnit cu = listing.getCodeUnitAt(thunkRefAddr); + if(cu == null) { + // println("Referenced CodeUnit is null: " + thunkRefAddr); + continue; + } + String thunkRefMnemonic = cu.getMnemonicString(); + Function thunkRefFunc = listing.getFunctionContaining(thunkRefAddr); if ((thunkRefMnemonic.equals(new String("CALL")) && (thunkRefFunc != null))) { CodeUnitIterator cuIt = @@ -297,7 +303,7 @@ public class PropagateExternalParametersScript extends GhidraScript { setEOLComment(cu.getMinAddress(), params[index].getDataType().getDisplayName() + " " + params[index].getName() + " for " + extFuncName); // add the following to the EOL comment to see the value of the optype - // +" " + toHexString(currentProgram.getListing().getInstructionAt(cu.getMinAddress()).getOperandType(0), false, true) + // + " | " + ghidra.program.model.lang.OperandType.toString(currentProgram.getListing().getInstructionAt(cu.getMinAddress()).getOperandType(0)) addResult(params[index].getName(), params[index].getDataType(), cu.getMinAddress(), extFuncName); index++; From 67851b9e0561f5810e89e9d6b3e6cb186a40b1d0 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Thu, 22 Aug 2024 13:55:17 -0400 Subject: [PATCH 07/53] GP-4860: Upgrading yajsw to 13.12 --- Ghidra/Features/GhidraServer/Module.manifest | 2 +- Ghidra/Features/GhidraServer/build.gradle | 6 +++--- Ghidra/RuntimeScripts/Common/server/server.conf | 6 ------ gradle/support/fetchDependencies.gradle | 10 +++++----- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Ghidra/Features/GhidraServer/Module.manifest b/Ghidra/Features/GhidraServer/Module.manifest index 119be8ab89..070fe00556 100644 --- a/Ghidra/Features/GhidraServer/Module.manifest +++ b/Ghidra/Features/GhidraServer/Module.manifest @@ -1,2 +1,2 @@ -DATA SEARCH IGNORE DIR: yajsw-stable-13.09 +DATA SEARCH IGNORE DIR: yajsw-stable-13.12 EXCLUDE FROM GHIDRA JAR: true diff --git a/Ghidra/Features/GhidraServer/build.gradle b/Ghidra/Features/GhidraServer/build.gradle index 731d198b9a..abfa67a369 100644 --- a/Ghidra/Features/GhidraServer/build.gradle +++ b/Ghidra/Features/GhidraServer/build.gradle @@ -4,9 +4,9 @@ * 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. @@ -21,7 +21,7 @@ apply plugin: 'eclipse' eclipse.project.name = 'Features GhidraServer' -def yajswRelease = "yajsw-stable-13.09" +def yajswRelease = "yajsw-stable-13.12" configurations { runGhidraServer diff --git a/Ghidra/RuntimeScripts/Common/server/server.conf b/Ghidra/RuntimeScripts/Common/server/server.conf index 5e44f2feed..82d675b3b9 100644 --- a/Ghidra/RuntimeScripts/Common/server/server.conf +++ b/Ghidra/RuntimeScripts/Common/server/server.conf @@ -214,12 +214,6 @@ wrapper.logfile.maxsize=10m # files are deleted. The default value of 0 implies no limit. wrapper.logfile.maxfiles=10 -#******************************************************************** -# Service Wrapper Linux Properties -#******************************************************************** -# Force initd (systemd had issues during testing on Ubuntu 21.04 with yajsw-13.00) -wrapper.daemon.system = initd - #******************************************************************** # Service Wrapper Windows Properties #******************************************************************** diff --git a/gradle/support/fetchDependencies.gradle b/gradle/support/fetchDependencies.gradle index 3b7d5f33e3..95a004a2b0 100644 --- a/gradle/support/fetchDependencies.gradle +++ b/gradle/support/fetchDependencies.gradle @@ -4,9 +4,9 @@ * 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. @@ -82,9 +82,9 @@ ext.deps = [ destination: FLAT_REPO_DIR ], [ - name: "yajsw-stable-13.09.zip", - url: "https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-13.09/yajsw-stable-13.09.zip", - sha256: "4dae732a535846ae5dfab753e82a4d5f93ad9a05a065e2172bb9774a1b15453a", + name: "yajsw-stable-13.12.zip", + url: "https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-13.12/yajsw-stable-13.12.zip", + sha256: "c6fc59815d3800d14ec977926a8afd3f606a0ebd74d2cfd60601677466edeaa2", destination: file("${DEPS_DIR}/GhidraServer") ], [ From de80c63e63c574a722e6b264281c1ae7b246d0b6 Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Fri, 23 Aug 2024 20:00:59 +0000 Subject: [PATCH 08/53] GP-4150 add option to ignore DWARF func param storage info Add option to DWARF analyzer to ignore storage locations specified for function parameters. (the info provided by DWARF info will often not be directly at the func entry point and requires future dev effort to walk the parameter info backwards to get it) Add option to DWARF analyzer to set the default calling convention of functions created via the analyzer. --- .../format/dwarf/DWARFFunctionImporter.java | 12 +++++- .../bin/format/dwarf/DWARFImportOptions.java | 38 ++++++++++++++++++- ...StorageVerificationDWARFFunctionFixup.java | 14 +++++-- .../golang/GolangDWARFFunctionFixup.java | 6 +-- .../Processors/ARM/data/languages/ARM.dwarf | 8 +++- .../ARM/data/languages/ARMneon.dwarf | 8 +++- 6 files changed, 74 insertions(+), 12 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunctionImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunctionImporter.java index beafdc1845..01685aef08 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunctionImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunctionImporter.java @@ -4,9 +4,9 @@ * 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. @@ -196,6 +196,14 @@ public class DWARFFunctionImporter { dfunc.runFixups(); + String defaultCC = prog.getImportOptions().getDefaultCC(); + if (defaultCC != null && defaultCC.isBlank()) { + defaultCC = null; + } + if (dfunc.callingConventionName == null && defaultCC != null) { + dfunc.callingConventionName = defaultCC; + } + decorateFunctionWithDWARFInfo(dfunc, origFuncDef); if (dfunc.signatureCommitMode != CommitMode.SKIP) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java index 6d32032fe9..215256cceb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java @@ -4,9 +4,9 @@ * 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. @@ -70,6 +70,14 @@ public class DWARFImportOptions { private static final String OPTION_IMPORT_LOCAL_VARS_DESC = "Import local variable information from DWARF and attempt to create Ghidra local variables."; + private static final String OPTION_IGNORE_PARAM_STORAGE = "Ignore Parameter Storage Info"; + private static final String OPTION_IGNORE_PARAM_STORAGE_DESC = + "Ignore any function parameter storage info specifed, allow automatic layout."; + + private static final String OPTION_DEFAULT_CC = "Default Calling Convention"; + private static final String OPTION_DEFAULT_CC_DESC = + "Name of default calling convention to assign to functions (e.g. __cdecl, __stdcall, etc), or leave blank."; + //================================================================================================== // Old Option Names - Should stick around for multiple major versions after 10.2 //================================================================================================== @@ -104,6 +112,8 @@ public class DWARFImportOptions { private boolean importLocalVariables = true; private boolean useBookmarks = true; private boolean outputSourceLineInfo = false; + private boolean ignoreParamStorage = false; + private String defaultCC = ""; /** * Create new instance @@ -374,6 +384,22 @@ public class DWARFImportOptions { this.outputSourceLineInfo = outputSourceLineInfo; } + public boolean isIgnoreParamStorage() { + return ignoreParamStorage; + } + + public void setIgnoreParamStorage(boolean ignoreParamStorage) { + this.ignoreParamStorage = ignoreParamStorage; + } + + public String getDefaultCC() { + return defaultCC; + } + + public void setDefaultCC(String defaultCC) { + this.defaultCC = defaultCC; + } + /** * See {@link Analyzer#registerOptions(Options, ghidra.program.model.listing.Program)} * @@ -409,6 +435,11 @@ public class DWARFImportOptions { options.registerOption(OPTION_SOURCE_LINEINFO, isOutputSourceLineInfo(), null, OPTION_SOURCE_LINEINFO_DESC); + + options.registerOption(OPTION_IGNORE_PARAM_STORAGE, isIgnoreParamStorage(), null, + OPTION_IGNORE_PARAM_STORAGE_DESC); + + options.registerOption(OPTION_DEFAULT_CC, getDefaultCC(), null, OPTION_DEFAULT_CC_DESC); } /** @@ -433,6 +464,9 @@ public class DWARFImportOptions { options.getBoolean(OPTION_IMPORT_LOCAL_VARS, isImportLocalVariables())); setOutputSourceLineInfo( options.getBoolean(OPTION_SOURCE_LINEINFO, isOutputSourceLineInfo())); + setIgnoreParamStorage( + options.getBoolean(OPTION_IGNORE_PARAM_STORAGE, isIgnoreParamStorage())); + setDefaultCC(options.getString(OPTION_DEFAULT_CC, getDefaultCC())); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/funcfixup/StorageVerificationDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/funcfixup/StorageVerificationDWARFFunctionFixup.java index c364be9782..ec281168fc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/funcfixup/StorageVerificationDWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/funcfixup/StorageVerificationDWARFFunctionFixup.java @@ -4,9 +4,9 @@ * 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. @@ -16,8 +16,8 @@ package ghidra.app.util.bin.format.dwarf.funcfixup; import ghidra.app.util.bin.format.dwarf.DWARFFunction; -import ghidra.app.util.bin.format.dwarf.DWARFVariable; import ghidra.app.util.bin.format.dwarf.DWARFFunction.CommitMode; +import ghidra.app.util.bin.format.dwarf.DWARFVariable; import ghidra.util.classfinder.ExtensionPointProperties; /** @@ -32,6 +32,14 @@ public class StorageVerificationDWARFFunctionFixup implements DWARFFunctionFixup @Override public void fixupDWARFFunction(DWARFFunction dfunc) { + boolean ignoreStorage = dfunc.getProgram().getImportOptions().isIgnoreParamStorage() || + dfunc.getProgram().getRegisterMappings().isUseFormalParameterStorage(); + boolean isEmptySignature = dfunc.params.isEmpty() && dfunc.retval.isVoidType(); + if (ignoreStorage || isEmptySignature) { + dfunc.signatureCommitMode = CommitMode.FORMAL; + return; + } + boolean storageIsGood = true; for (DWARFVariable param : dfunc.params) { if (param.isMissingStorage() && !param.isZeroByte()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java index d50f254488..b18afde3ff 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java @@ -4,9 +4,9 @@ * 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. @@ -68,7 +68,7 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup { return false; } // sanity check: gofuncs always have a void return type in dwarf - if (!dfunc.retval.type.isEquivalent(VoidDataType.dataType)) { + if (!dfunc.retval.isVoidType()) { return false; } return true; diff --git a/Ghidra/Processors/ARM/data/languages/ARM.dwarf b/Ghidra/Processors/ARM/data/languages/ARM.dwarf index 0444463e93..4d4a18228f 100644 --- a/Ghidra/Processors/ARM/data/languages/ARM.dwarf +++ b/Ghidra/Processors/ARM/data/languages/ARM.dwarf @@ -8,5 +8,11 @@ - + diff --git a/Ghidra/Processors/ARM/data/languages/ARMneon.dwarf b/Ghidra/Processors/ARM/data/languages/ARMneon.dwarf index fa2ff76207..a6cd403bff 100644 --- a/Ghidra/Processors/ARM/data/languages/ARMneon.dwarf +++ b/Ghidra/Processors/ARM/data/languages/ARMneon.dwarf @@ -10,5 +10,11 @@ - + From a31c4033a8d5224328d5196dcdb8a084cf81c2a9 Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Tue, 20 Aug 2024 20:16:26 +0000 Subject: [PATCH 09/53] GP-4859 RuleOrCompare --- .../Decompiler/certification.manifest | 1 + .../src/decompile/cpp/coreaction.cc | 1 - .../Decompiler/src/decompile/cpp/double.cc | 146 +++++------------- .../Decompiler/src/decompile/cpp/double.hh | 10 +- .../Decompiler/src/decompile/cpp/op.cc | 39 +++-- .../src/decompile/cpp/ruleaction.cc | 138 ++++++----------- .../src/decompile/cpp/ruleaction.hh | 11 -- .../src/decompile/datatests/orcompare.xml | 29 ++++ 8 files changed, 141 insertions(+), 234 deletions(-) create mode 100644 Ghidra/Features/Decompiler/src/decompile/datatests/orcompare.xml diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index 8d3f7a6ae8..710f82ea78 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -50,6 +50,7 @@ src/decompile/datatests/noforloop_alias.xml||GHIDRA||||END| src/decompile/datatests/noforloop_globcall.xml||GHIDRA||||END| src/decompile/datatests/noforloop_iterused.xml||GHIDRA||||END| src/decompile/datatests/offsetarray.xml||GHIDRA||||END| +src/decompile/datatests/orcompare.xml||GHIDRA||||END| src/decompile/datatests/packstructaccess.xml||GHIDRA||||END| src/decompile/datatests/partialmerge.xml||GHIDRA||||END| src/decompile/datatests/partialsplit.xml||GHIDRA||||END| diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc index 02faf8d6d1..76963accce 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc @@ -5518,7 +5518,6 @@ void ActionDatabase::universalAction(Architecture *conf) actprop->addRule( new RulePiece2Zext("analysis") ); actprop->addRule( new RulePiece2Sext("analysis") ); actprop->addRule( new RulePopcountBoolXor("analysis") ); - actprop->addRule( new RuleOrMultiBool("analysis") ); actprop->addRule( new RuleXorSwap("analysis") ); actprop->addRule( new RuleLzcountShiftBool("analysis") ); actprop->addRule( new RuleFloatSign("analysis") ); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/double.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/double.cc index 840ed38365..ab4257ed5b 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/double.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/double.cc @@ -4,9 +4,9 @@ * 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. @@ -1124,9 +1124,6 @@ int4 SplitVarnode::applyRuleIn(SplitVarnode &in,Funcdata &data) break; case CPUI_INT_OR: { - Equal2Form equal2form; - if (equal2form.applyRule(in,workop,workishi,data)) - return 1; LogicalForm logicalform; if (logicalform.applyRule(in,workop,workishi,data)) return 1; @@ -1134,9 +1131,6 @@ int4 SplitVarnode::applyRuleIn(SplitVarnode &in,Funcdata &data) break; case CPUI_INT_XOR: { - Equal2Form equal2form; - if (equal2form.applyRule(in,workop,workishi,data)) - return 1; LogicalForm logicalform; if (logicalform.applyRule(in,workop,workishi,data)) return 1; @@ -1151,6 +1145,9 @@ int4 SplitVarnode::applyRuleIn(SplitVarnode &in,Funcdata &data) Equal1Form equal1form; if (equal1form.applyRule(in,workop,workishi,data)) return 1; + Equal2Form equal2form; + if (equal2form.applyRule(in,workop,workishi,data)) + return 1; } break; case CPUI_INT_LESS: @@ -1870,95 +1867,30 @@ bool Equal1Form::applyRule(SplitVarnode &i,PcodeOp *hop,bool workishi,Funcdata & return false; } -bool Equal2Form::checkLoForm(void) - -{ // Assuming we have equal <- or <- xor <- hi1, verify if we have the full equal form - Varnode *orvnin = orop->getIn(1-orhislot); - if (orvnin == lo1) { // lo2 is an implied 0 - loxor = (PcodeOp *)0; - lo2 = (Varnode *)0; - return true; - } - if (!orvnin->isWritten()) return false; - loxor = orvnin->getDef(); - if (loxor->code() != CPUI_INT_XOR) return false; - if (loxor->getIn(0) == lo1) { - lo2 = loxor->getIn(1); - return true; - } - else if (loxor->getIn(1) == lo1) { - lo2 = loxor->getIn(0); - return true; - } - return false; -} - -bool Equal2Form::fillOutFromOr(Funcdata &data) - -{ // We have filled in either or <- xor <- hi1, OR, or <- hi1 - // Now try to fill in the rest of the form - Varnode *outvn = orop->getOut(); - list::const_iterator iter,enditer; - iter = outvn->beginDescend(); - enditer = outvn->endDescend(); - while(iter != enditer) { - equalop = *iter; - ++iter; - if ((equalop->code() != CPUI_INT_EQUAL)&&(equalop->code() != CPUI_INT_NOTEQUAL)) continue; - if (!equalop->getIn(1)->isConstant()) continue; - if (equalop->getIn(1)->getOffset() != 0) continue; - - if (!checkLoForm()) continue; - if (!replace(data)) continue; - return true; - } - return false; -} - bool Equal2Form::replace(Funcdata &data) { - if ((hi2==(Varnode *)0)&&(lo2==(Varnode *)0)) { - param2.initPartial(in.getSize(),0); // Double precis zero constant - return SplitVarnode::prepareBoolOp(in,param2,equalop); - } - if ((hi2==(Varnode *)0)&&(lo2->isConstant())) { - param2.initPartial(in.getSize(),lo2->getOffset()); - return SplitVarnode::prepareBoolOp(in,param2,equalop); - } - if ((lo2==(Varnode *)0)&&(hi2->isConstant())) { - param2.initPartial(in.getSize(),hi2->getOffset() << 8*lo1->getSize()); - return SplitVarnode::prepareBoolOp(in,param2,equalop); - } - if (lo2 == (Varnode *)0) { - // Equal to a zero extended and shifted var - return false; - } - if (hi2 == (Varnode *)0) { - // Equal to a zero extended var - return false; - } if (hi2->isConstant()&&lo2->isConstant()) { uintb val = hi2->getOffset(); val <<= 8*lo1->getSize(); val |= lo2->getOffset(); param2.initPartial(in.getSize(),val); - return SplitVarnode::prepareBoolOp(in,param2,equalop); + return SplitVarnode::prepareBoolOp(in,param2,boolAndOr); } if (hi2->isConstant()||lo2->isConstant()) { // Some kind of mixed form return false; } param2.initPartial(in.getSize(),lo2,hi2); - return SplitVarnode::prepareBoolOp(in,param2,equalop); + return SplitVarnode::prepareBoolOp(in,param2,boolAndOr); } // Given a known double precis input, look for double precision compares of the form // a == b, a != b // // We look for -// res = ((hi1 ^ hi2) | (lo1 ^ lo2) == 0) -// where hi2 or lo2 may be zero, and optimized out +// res = (hi1 == hi2) && (lo1 == lo2) or +// res = (hi1 != hi2) || (lo1 != lo2) bool Equal2Form::applyRule(SplitVarnode &i,PcodeOp *op,bool workishi,Funcdata &data) { @@ -1967,41 +1899,37 @@ bool Equal2Form::applyRule(SplitVarnode &i,PcodeOp *op,bool workishi,Funcdata &d in = i; hi1 = in.getHi(); lo1 = in.getLo(); - - if (op->code() == CPUI_INT_OR) { - orop = op; - orhislot = op->getSlot(hi1); - hixor = (PcodeOp *)0; - hi2 = (Varnode *)0; - if (fillOutFromOr(data)) { - if (!param2.exceedsConstPrecision()) { - SplitVarnode::replaceBoolOp(data,equalop,in,param2,equalop->code()); - return true; - } + OpCode eqCode = op->code(); + int4 hi1slot = op->getSlot(hi1); + hi2 = op->getIn(1-hi1slot); + Varnode *outvn = op->getOut(); + list::const_iterator iter,enditer; + iter = outvn->beginDescend(); + enditer = outvn->endDescend(); + while(iter != enditer) { + boolAndOr = *iter; + ++iter; + if (eqCode == CPUI_INT_EQUAL && boolAndOr->code() != CPUI_BOOL_AND) continue; + if (eqCode == CPUI_INT_NOTEQUAL && boolAndOr->code() != CPUI_BOOL_OR) continue; + int4 slot = boolAndOr->getSlot(outvn); + Varnode *othervn = boolAndOr->getIn(1-slot); + if (!othervn->isWritten()) continue; + PcodeOp *equalLo = othervn->getDef(); + if (equalLo->code() != eqCode) continue; + if (equalLo->getIn(0) == lo1) { + lo2 = equalLo->getIn(1); } - } - else { // We see an XOR - hixor = op; - xorhislot = hixor->getSlot(hi1); - hi2 = hixor->getIn(1-xorhislot); - Varnode *vn = op->getOut(); - list::const_iterator iter,enditer; - iter = vn->beginDescend(); - enditer = vn->endDescend(); - while(iter != enditer) { - orop = *iter; - ++iter; - if (orop->code() != CPUI_INT_OR) continue; - orhislot = orop->getSlot(vn); - if (fillOutFromOr(data)) { - if (!param2.exceedsConstPrecision()) { - SplitVarnode::replaceBoolOp(data,equalop,in,param2,equalop->code()); - return true; - } - } + else if (equalLo->getIn(1) == lo1) { + lo2 = equalLo->getIn(0); } + else { + continue; + } + if (!replace(data)) continue; + if (param2.exceedsConstPrecision()) continue; + SplitVarnode::replaceBoolOp(data,boolAndOr,in,param2,eqCode); + return true; } - return false; } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/double.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/double.hh index 175f6024ae..0b2e9e7b09 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/double.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/double.hh @@ -4,9 +4,9 @@ * 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. @@ -160,12 +160,8 @@ public: class Equal2Form { SplitVarnode in; Varnode *hi1,*hi2,*lo1,*lo2; - PcodeOp *equalop,*orop; - PcodeOp *hixor,*loxor; - int4 orhislot,xorhislot; + PcodeOp *boolAndOr; SplitVarnode param2; - bool checkLoForm(void); - bool fillOutFromOr(Funcdata &data); bool replace(Funcdata &data); public: bool applyRule(SplitVarnode &i,PcodeOp *op,bool workishi,Funcdata &data); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/op.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/op.cc index 74a62950c8..d76a799960 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/op.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/op.cc @@ -4,9 +4,9 @@ * 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. @@ -672,20 +672,33 @@ uintb PcodeOp::getNZMaskLocal(bool cliploop) const case CPUI_INT_MULT: val = getIn(0)->getNZMask(); resmask = getIn(1)->getNZMask(); - sz1 = (size > sizeof(uintb)) ? 8*size-1 : mostsigbit_set(val); - if (sz1 == -1) - resmask = 0; + if (size > sizeof(uintb)) { + resmask = fullmask; + } else { - sz2 = (size > sizeof(uintb)) ? 8*size-1 : mostsigbit_set(resmask); - if (sz2 == -1) + sz1 = mostsigbit_set(val); + sz2 = mostsigbit_set(resmask); + if (sz1 == -1 || sz2 == -1) { resmask = 0; + } else { - if (sz1 + sz2 < 8*size-2) - fullmask >>= (8*size-2-sz1-sz2); - sz1 = leastsigbit_set(val); - sz2 = leastsigbit_set(resmask); - resmask = (~((uintb)0))<<(sz1+sz2); - resmask &= fullmask; + int4 l1 = leastsigbit_set(val); + int4 l2 = leastsigbit_set(resmask); + sa = l1 + l2; + if (sa >= 8*size) { + resmask = 0; + } + else { + sz1 = sz1 - l1 + 1; + sz2 = sz2 - l2 + 1; + int4 total = sz1 + sz2; + if (sz1 == 1 || sz2 == 1) + total -= 1; + resmask = fullmask; + if (total < 8 * size) + resmask >>= (8*size - total); + resmask = (resmask << sa) & fullmask; + } } } break; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc index 35cad45220..aedfd8fa9c 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc @@ -10101,66 +10101,6 @@ Varnode *RulePopcountBoolXor::getBooleanResult(Varnode *vn,int4 bitPos,int4 &con } } -/// \class RuleOrMultiBool -/// \brief Simplify boolean expressions that are combined through INT_OR -/// -/// Convert expressions involving boolean values b1 and b2: -/// - `(b1 << 6) | (b2 << 2) != 0 => b1 || b2 -void RuleOrMultiBool::getOpList(vector &oplist) const - -{ - oplist.push_back(CPUI_INT_OR); -} - -int4 RuleOrMultiBool::applyOp(PcodeOp *op,Funcdata &data) - -{ - Varnode *outVn = op->getOut(); - list::const_iterator iter; - - if (popcount(outVn->getNZMask()) != 2) return 0; - for(iter=outVn->beginDescend();iter!=outVn->endDescend();++iter) { - PcodeOp *baseOp = *iter; - OpCode opc = baseOp->code(); - // Result of INT_OR must be compared with zero - if (opc != CPUI_INT_EQUAL && opc != CPUI_INT_NOTEQUAL) continue; - Varnode *zerovn = baseOp->getIn(1); - if (!zerovn->isConstant()) continue; - if (zerovn->getOffset() != 0) continue; - int4 pos0 = leastsigbit_set(outVn->getNZMask()); - int4 pos1 = mostsigbit_set(outVn->getNZMask()); - int4 constRes0,constRes1; - Varnode *b1 = RulePopcountBoolXor::getBooleanResult(outVn, pos0, constRes0); - if (b1 == (Varnode *)0 && constRes0 != 1) continue; - Varnode *b2 = RulePopcountBoolXor::getBooleanResult(outVn, pos1, constRes1); - if (b2 == (Varnode *)0 && constRes1 != 1) continue; - if (b1 == (Varnode *)0 && b2 == (Varnode *)0) continue; - - if (b1 == (Varnode *)0) - b1 = data.newConstant(1, 1); - if (b2 == (Varnode *)0) - b2 = data.newConstant(1, 1); - if (opc == CPUI_INT_EQUAL) { - PcodeOp *newOp = data.newOp(2,baseOp->getAddr()); - Varnode *notIn = data.newUniqueOut(1, newOp); - data.opSetOpcode(newOp, CPUI_BOOL_OR); - data.opSetInput(newOp, b1, 0); - data.opSetInput(newOp, b2, 1); - data.opInsertBefore(newOp, baseOp); - data.opRemoveInput(baseOp, 1); - data.opSetInput(baseOp, notIn, 0); - data.opSetOpcode(baseOp, CPUI_BOOL_NEGATE); - } - else { - data.opSetOpcode(baseOp, CPUI_BOOL_OR); - data.opSetInput(baseOp, b1, 0); - data.opSetInput(baseOp, b2, 1); - } - return 1; - } - return 0; -} - /// \brief Return \b true if concatenating with a SUBPIECE of the given Varnode is unusual /// /// \param vn is the given Varnode @@ -10550,53 +10490,65 @@ int4 RuleFloatSignCleanup::applyOp(PcodeOp *op,Funcdata &data) void RuleOrCompare::getOpList(vector &oplist) const { - oplist.push_back(CPUI_INT_EQUAL); - oplist.push_back(CPUI_INT_NOTEQUAL); + oplist.push_back(CPUI_INT_OR); } int4 RuleOrCompare::applyOp(PcodeOp *op,Funcdata &data) { - // make sure the comparison is against 0 - if (! op->getIn(1)->constantMatch(0)) return 0; + Varnode *outvn = op->getOut(); + list::const_iterator iter; + bool hasCompares = false; + for(iter=outvn->beginDescend();iter!=outvn->endDescend();++iter) { + PcodeOp *compOp = *iter; + OpCode opc = compOp->code(); + if (opc != CPUI_INT_EQUAL && opc != CPUI_INT_NOTEQUAL) + return 0; + if (!compOp->getIn(1)->constantMatch(0)) + return 0; + hasCompares = true; + } + if (!hasCompares) + return 0; - // make sure the other operand is an INT_OR - PcodeOp *or_op = op->getIn(0)->getDef(); - if (or_op == (PcodeOp *)0) return 0; - if (or_op->code() != CPUI_INT_OR) return 0; - - Varnode* V = or_op->getIn(0); - Varnode* W = or_op->getIn(1); + Varnode* V = op->getIn(0); + Varnode* W = op->getIn(1); // make sure V and W are in SSA form if (V->isFree()) return 0; if (W->isFree()) return 0; - // construct the new segment: - // if the original condition was INT_EQUAL: BOOL_AND(INT_EQUAL(V, 0:|V|), INT_EQUAL(W, 0:|W|)) - // if the original condition was INT_NOTEQUAL: BOOL_OR(INT_NOTEQUAL(V, 0:|V|), INT_NOTEQUAL(W, 0:|W|)) - Varnode* zero_V = data.newConstant(V->getSize(), 0); - Varnode* zero_W = data.newConstant(W->getSize(), 0); - PcodeOp* eq_V = data.newOp(2, op->getAddr()); - data.opSetOpcode(eq_V, op->code()); - data.opSetInput(eq_V, V, 0); - data.opSetInput(eq_V, zero_V, 1); - PcodeOp* eq_W = data.newOp(2, op->getAddr()); - data.opSetOpcode(eq_W, op->code()); - data.opSetInput(eq_W, W, 0); - data.opSetInput(eq_W, zero_W, 1); + iter = outvn->beginDescend(); + while(iter!=outvn->endDescend()) { + PcodeOp *equalOp = *iter; + OpCode opc = equalOp->code(); + ++iter; // Advance iterator immediately as equalOp gets modified + // construct the new segment: + // if the original condition was INT_EQUAL: BOOL_AND(INT_EQUAL(V, 0:|V|), INT_EQUAL(W, 0:|W|)) + // if the original condition was INT_NOTEQUAL: BOOL_OR(INT_NOTEQUAL(V, 0:|V|), INT_NOTEQUAL(W, 0:|W|)) + Varnode* zero_V = data.newConstant(V->getSize(), 0); + Varnode* zero_W = data.newConstant(W->getSize(), 0); + PcodeOp* eq_V = data.newOp(2, equalOp->getAddr()); + data.opSetOpcode(eq_V, opc); + data.opSetInput(eq_V, V, 0); + data.opSetInput(eq_V, zero_V, 1); + PcodeOp* eq_W = data.newOp(2, equalOp->getAddr()); + data.opSetOpcode(eq_W, opc); + data.opSetInput(eq_W, W, 0); + data.opSetInput(eq_W, zero_W, 1); - Varnode* eq_V_out = data.newUniqueOut(1, eq_V); - Varnode* eq_W_out = data.newUniqueOut(1, eq_W); + Varnode* eq_V_out = data.newUniqueOut(1, eq_V); + Varnode* eq_W_out = data.newUniqueOut(1, eq_W); - // make sure the comparisons' output is already defined - data.opInsertBefore(eq_V, op); - data.opInsertBefore(eq_W, op); + // make sure the comparisons' output is already defined + data.opInsertBefore(eq_V, equalOp); + data.opInsertBefore(eq_W, equalOp); - // change the original INT_EQUAL into a BOOL_AND, and INT_NOTEQUAL becomes BOOL_OR - data.opSetOpcode(op, op->code() == CPUI_INT_EQUAL ? CPUI_BOOL_AND : CPUI_BOOL_OR); - data.opSetInput(op, eq_V_out, 0); - data.opSetInput(op, eq_W_out, 1); + // change the original INT_EQUAL into a BOOL_AND, and INT_NOTEQUAL becomes BOOL_OR + data.opSetOpcode(equalOp, opc == CPUI_INT_EQUAL ? CPUI_BOOL_AND : CPUI_BOOL_OR); + data.opSetInput(equalOp, eq_V_out, 0); + data.opSetInput(equalOp, eq_W_out, 1); + } return 1; } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh index 20fd92145c..fc27699028 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh @@ -1598,17 +1598,6 @@ public: static Varnode *getBooleanResult(Varnode *vn,int4 bitPos,int4 &constRes); }; -class RuleOrMultiBool : public Rule { -public: - RuleOrMultiBool(const string &g) : Rule( g, 0, "ormultibool") {} ///< Constructor - virtual Rule *clone(const ActionGroupList &grouplist) const { - if (!grouplist.contains(getGroup())) return (Rule *)0; - return new RuleOrMultiBool(getGroup()); - } - virtual void getOpList(vector &oplist) const; - virtual int4 applyOp(PcodeOp *op,Funcdata &data); -}; - class RulePiecePathology : public Rule { static bool isPathology(Varnode *vn,Funcdata &data); static int4 tracePathologyForward(PcodeOp *op,Funcdata &data); diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/orcompare.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/orcompare.xml new file mode 100644 index 0000000000..8d28d7cd16 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/orcompare.xml @@ -0,0 +1,29 @@ + + + +f30f1efa83ff0a0f94c20fb6d201d283 +fe140f94c00fb6c0c1e007b901000000 +09c2740f833dd50f0000000f94c10fb6 +c901c989c8c3f30f1efa81fec8000000 +0f94c00fb6c0c1e00283ff640f94c10f +b6c909c881fa2c0100000f94c20fb6d2 +c1e20409d07510833d920f000007ba02 +0000000f44c2c3b801000000c3 + + + + + +if \(a == 10 \|\| b == 0x14\) +if \(\(y != 200 && x != 100\) && z != 300\) + From 36334430070151de1cdc07bfe5bd27ffc7455b59 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Mon, 26 Aug 2024 13:46:55 -0400 Subject: [PATCH 10/53] GP-4874: AskDialog now uses Integer.decode() and Long.decode() to parse better --- .../main/java/ghidra/app/script/AskDialog.java | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java index a89ce3495e..a82350b244 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java @@ -4,9 +4,9 @@ * 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. @@ -246,21 +246,12 @@ public class AskDialog extends DialogComponentProvider { protected Integer getValueAsInt() { String text = getValueAsString(); - if (text == null) { - return null; - } - if (text.startsWith("0x")) { - return (int) NumericUtilities.parseHexLong(text); - } - return (int) NumericUtilities.parseLong(text); + return text != null ? Integer.decode(text) : null; } protected Long getValueAsLong() { String text = getValueAsString(); - if (text == null) { - return null; - } - return NumericUtilities.parseLong(text); + return text != null ? Long.decode(text) : null; } protected Double getValueAsDouble() { From 78fb4e70778226aaf5466f00938131d3e5ee4ada Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:28:33 -0400 Subject: [PATCH 11/53] GP-4868: Re-write StaticMappingService. Fix tests. --- .../breakpoint/TraceBreakpointSet.java | 102 +- .../DebuggerStaticMappingServicePlugin.java | 897 ++++-------------- .../debug/service/modules/InfoPerProgram.java | 180 ++++ .../debug/service/modules/InfoPerTrace.java | 254 +++++ .../debug/service/modules/MappingEntry.java | 202 ++++ .../DebuggerTraceManagerServicePlugin.java | 12 +- .../gui/AbstractGhidraHeadedDebuggerTest.java | 7 +- .../DebuggerStaticMappingServiceTest.java | 81 +- .../database/module/DBTraceStaticMapping.java | 6 +- .../trace/util/OverlappingObjectIterator.java | 27 +- ...actDebuggerBreakpointMarkerPluginTest.java | 8 +- ...tDebuggerLogicalBreakpointServiceTest.java | 513 +++++----- ...tRecorderLogicalBreakpointServiceTest.java | 7 +- ...rRecorderLogicalBreakpointServiceTest.java | 28 +- ...buggerRmiLogicalBreakpointServiceTest.java | 30 +- 15 files changed, 1273 insertions(+), 1081 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerProgram.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerTrace.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/MappingEntry.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java index 5699a01209..31c2100629 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java @@ -4,9 +4,9 @@ * 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. @@ -65,7 +65,9 @@ class TraceBreakpointSet { @Override public String toString() { - return String.format("", address, trace.getName(), breakpoints); + synchronized (breakpoints) { + return String.format("", address, trace.getName(), breakpoints); + } } /** @@ -126,22 +128,24 @@ class TraceBreakpointSet { */ public TraceMode computeMode() { TraceMode mode = TraceMode.NONE; - if (getControlMode().useEmulatedBreakpoints()) { + synchronized (breakpoints) { + if (getControlMode().useEmulatedBreakpoints()) { + for (IDHashed bpt : breakpoints) { + mode = mode.combine(computeEmuMode(bpt.obj)); + if (mode == TraceMode.MISSING) { + return mode; + } + } + return mode; + } for (IDHashed bpt : breakpoints) { - mode = mode.combine(computeEmuMode(bpt.obj)); + mode = mode.combine(computeTargetMode(bpt.obj)); if (mode == TraceMode.MISSING) { return mode; } } return mode; } - for (IDHashed bpt : breakpoints) { - mode = mode.combine(computeTargetMode(bpt.obj)); - if (mode == TraceMode.MISSING) { - return mode; - } - } - return mode; } /** @@ -188,14 +192,16 @@ class TraceBreakpointSet { */ public String computeSleigh() { String sleigh = null; - for (IDHashed bpt : breakpoints) { - String s = bpt.obj.getEmuSleigh(); - if (sleigh != null && !sleigh.equals(s)) { - return null; + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + String s = bpt.obj.getEmuSleigh(); + if (sleigh != null && !sleigh.equals(s)) { + return null; + } + sleigh = s; } - sleigh = s; + return sleigh; } - return sleigh; } /** @@ -206,8 +212,10 @@ class TraceBreakpointSet { public void setEmuSleigh(String emuSleigh) { this.emuSleigh = emuSleigh; try (Transaction tx = trace.openTransaction("Set breakpoint Sleigh")) { - for (IDHashed bpt : breakpoints) { - bpt.obj.setEmuSleigh(emuSleigh); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + bpt.obj.setEmuSleigh(emuSleigh); + } } } } @@ -218,7 +226,9 @@ class TraceBreakpointSet { * @return true if empty, false otherwise */ public boolean isEmpty() { - return breakpoints.isEmpty(); + synchronized (breakpoints) { + return breakpoints.isEmpty(); + } } /** @@ -227,7 +237,9 @@ class TraceBreakpointSet { * @return the breakpoints */ public Set getBreakpoints() { - return breakpoints.stream().map(e -> e.obj).collect(Collectors.toUnmodifiableSet()); + synchronized (breakpoints) { + return breakpoints.stream().map(e -> e.obj).collect(Collectors.toUnmodifiableSet()); + } } /** @@ -246,7 +258,9 @@ class TraceBreakpointSet { bpt.setEmuSleigh(emuSleigh); } } - return breakpoints.add(new IDHashed<>(bpt)); + synchronized (breakpoints) { + return breakpoints.add(new IDHashed<>(bpt)); + } } /** @@ -275,7 +289,9 @@ class TraceBreakpointSet { * @return true if the set actually changes as a result */ public boolean remove(TraceBreakpoint bpt) { - return breakpoints.remove(new IDHashed<>(bpt)); + synchronized (breakpoints) { + return breakpoints.remove(new IDHashed<>(bpt)); + } } /** @@ -303,7 +319,7 @@ class TraceBreakpointSet { public void planEnable(BreakpointActionSet actions, long length, Collection kinds) { long snap = getSnap(); - if (breakpoints.isEmpty()) { + if (isEmpty()) { if (target == null || getControlMode().useEmulatedBreakpoints()) { planPlaceEmu(actions, snap, length, kinds); } @@ -339,14 +355,18 @@ class TraceBreakpointSet { } private void planEnableTarget(BreakpointActionSet actions) { - for (IDHashed bpt : breakpoints) { - actions.planEnableTarget(target, bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planEnableTarget(target, bpt.obj); + } } } private void planEnableEmu(BreakpointActionSet actions) { - for (IDHashed bpt : breakpoints) { - actions.planEnableEmu(bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planEnableEmu(bpt.obj); + } } } @@ -369,14 +389,18 @@ class TraceBreakpointSet { private void planDisableTarget(BreakpointActionSet actions, long length, Collection kinds) { - for (IDHashed bpt : breakpoints) { - actions.planDisableTarget(target, bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planDisableTarget(target, bpt.obj); + } } } private void planDisableEmu(BreakpointActionSet actions) { - for (IDHashed bpt : breakpoints) { - actions.planDisableEmu(bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planDisableEmu(bpt.obj); + } } } @@ -399,14 +423,18 @@ class TraceBreakpointSet { private void planDeleteTarget(BreakpointActionSet actions, long length, Set kinds) { - for (IDHashed bpt : breakpoints) { - actions.planDeleteTarget(target, bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planDeleteTarget(target, bpt.obj); + } } } private void planDeleteEmu(BreakpointActionSet actions) { - for (IDHashed bpt : breakpoints) { - actions.planDeleteEmu(bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planDeleteEmu(bpt.obj); + } } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java index 149cc31cd7..04b76e5db0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java @@ -18,11 +18,9 @@ package ghidra.app.plugin.core.debug.service.modules; import java.io.FileNotFoundException; import java.net.URL; import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.*; import java.util.stream.Collectors; - -import org.apache.commons.lang3.ArrayUtils; +import java.util.stream.Stream; import db.Transaction; import ghidra.app.events.ProgramClosedPluginEvent; @@ -35,8 +33,6 @@ import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingProposa import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; import ghidra.app.services.*; -import ghidra.async.AsyncDebouncer; -import ghidra.async.AsyncTimer; import ghidra.debug.api.modules.*; import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry; import ghidra.debug.api.modules.RegionMapProposal.RegionMapEntry; @@ -45,9 +41,8 @@ import ghidra.framework.model.*; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; -import ghidra.generic.util.datastruct.TreeValueSortedMap; -import ghidra.generic.util.datastruct.ValueSortedMap; -import ghidra.program.model.address.*; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.util.ProgramLocation; @@ -55,581 +50,64 @@ import ghidra.trace.model.*; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.modules.*; import ghidra.trace.model.program.TraceProgramView; -import ghidra.trace.util.TraceEvents; import ghidra.util.Msg; import ghidra.util.datastruct.ListenerSet; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -//@formatter:off @PluginInfo( shortDescription = "Debugger static mapping manager", description = "Track and manage static mappings (program-trace relocations)", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, - eventsConsumed = { ProgramOpenedPluginEvent.class, ProgramClosedPluginEvent.class, - TraceOpenedPluginEvent.class, TraceClosedPluginEvent.class, }, - servicesRequired = { ProgramManager.class, DebuggerTraceManagerService.class, }, - servicesProvided = { DebuggerStaticMappingService.class, }) -//@formatter:on + eventsConsumed = { + ProgramOpenedPluginEvent.class, + ProgramClosedPluginEvent.class, + TraceOpenedPluginEvent.class, + TraceClosedPluginEvent.class, }, + servicesRequired = { + ProgramManager.class, + DebuggerTraceManagerService.class, + }, + servicesProvided = { + DebuggerStaticMappingService.class, + }) public class DebuggerStaticMappingServicePlugin extends Plugin implements DebuggerStaticMappingService, DomainFolderChangeListener { - protected class MappingEntry { - private final TraceStaticMapping mapping; + record ChangeCollector(DebuggerStaticMappingServicePlugin plugin, Set traces, + Set programs) implements AutoCloseable { - private Program program; - private AddressRange staticRange; + static Set subtract(Set a, Set b) { + Set result = new HashSet<>(a); + result.removeAll(b); + return result; + } - public MappingEntry(TraceStaticMapping mapping) { - this.mapping = mapping; + public ChangeCollector(DebuggerStaticMappingServicePlugin plugin) { + this(plugin, new HashSet<>(), new HashSet<>()); + } + + public void traceAffected(Trace trace) { + this.traces.add(trace); + } + + public void programAffected(Program program) { + if (program != null) { + this.programs.add(program); + } } @Override - public boolean equals(Object o) { - if (!(o instanceof MappingEntry that)) { - return false; - } - // Yes, use identity, since it should be the same trace db records - if (this.mapping != that.mapping) { - return false; - } - if (this.program != that.program) { - return false; - } - if (!Objects.equals(this.staticRange, that.staticRange)) { - return false; - } - return true; - } - - public Trace getTrace() { - return mapping.getTrace(); - } - - public Address addOrMax(Address start, long length) { - Address result = start.addWrapSpace(length); - if (result.compareTo(start) < 0) { - Msg.warn(this, "Mapping entry caused overflow in static address space"); - return start.getAddressSpace().getMaxAddress(); - } - return result; - } - - public boolean programOpened(Program opened) { - if (mapping.getStaticProgramURL().equals(ProgramURLUtils.getUrlFromProgram(opened))) { - this.program = opened; - Address minAddr = opened.getAddressFactory().getAddress(mapping.getStaticAddress()); - Address maxAddr = addOrMax(minAddr, mapping.getLength() - 1); - this.staticRange = new AddressRangeImpl(minAddr, maxAddr); - return true; - } - return false; - } - - public boolean programClosed(Program closed) { - if (this.program == closed) { - this.program = null; - this.staticRange = null; - return true; - } - return false; - } - - public Address getTraceAddress() { - return mapping.getMinTraceAddress(); - } - - public Address getStaticAddress() { - if (staticRange == null) { - return null; - } - return staticRange.getMinAddress(); - } - - public TraceSpan getTraceSpan() { - return new DefaultTraceSpan(mapping.getTrace(), mapping.getLifespan()); - } - - public TraceAddressSnapRange getTraceAddressSnapRange() { - // NOTE: No need to capture shape since static mappings are immutable - return new ImmutableTraceAddressSnapRange(mapping.getTraceAddressRange(), - mapping.getLifespan()); - } - - public boolean isInTraceRange(Address address, Long snap) { - return mapping.getTraceAddressRange().contains(address) && - (snap == null || mapping.getLifespan().contains(snap)); - } - - public boolean isInTraceRange(AddressRange rng, Long snap) { - return mapping.getTraceAddressRange().intersects(rng) && - (snap == null || mapping.getLifespan().contains(snap)); - } - - public boolean isInTraceLifespan(long snap) { - return mapping.getLifespan().contains(snap); - } - - public boolean isInProgramRange(Address address) { - if (staticRange == null) { - return false; - } - return staticRange.contains(address); - } - - public boolean isInProgramRange(AddressRange rng) { - if (staticRange == null) { - return false; - } - return staticRange.intersects(rng); - } - - protected Address mapTraceAddressToProgram(Address address) { - assert isInTraceRange(address, null); - long offset = address.subtract(mapping.getMinTraceAddress()); - return staticRange.getMinAddress().addWrapSpace(offset); - } - - public ProgramLocation mapTraceAddressToProgramLocation(Address address) { - if (program == null) { - throw new IllegalStateException("Static program is not opened"); - } - return new ProgramLocation(program, mapTraceAddressToProgram(address)); - } - - public AddressRange mapTraceRangeToProgram(AddressRange rng) { - assert isInTraceRange(rng, null); - AddressRange part = rng.intersect(mapping.getTraceAddressRange()); - Address min = mapTraceAddressToProgram(part.getMinAddress()); - Address max = mapTraceAddressToProgram(part.getMaxAddress()); - return new AddressRangeImpl(min, max); - } - - protected Address mapProgramAddressToTrace(Address address) { - assert isInProgramRange(address); - long offset = address.subtract(staticRange.getMinAddress()); - return mapping.getMinTraceAddress().addWrapSpace(offset); - } - - protected TraceLocation mapProgramAddressToTraceLocation(Address address) { - return new DefaultTraceLocation(mapping.getTrace(), null, mapping.getLifespan(), - mapProgramAddressToTrace(address)); - } - - public AddressRange mapProgramRangeToTrace(AddressRange rng) { - assert (rng.intersects(staticRange)); - AddressRange part = rng.intersect(staticRange); - Address min = mapProgramAddressToTrace(part.getMinAddress()); - Address max = mapProgramAddressToTrace(part.getMaxAddress()); - return new AddressRangeImpl(min, max); - } - - public boolean isStaticProgramOpen() { - return program != null; - } - - public URL getStaticProgramURL() { - return mapping.getStaticProgramURL(); + public void close() { + plugin.changeListeners.getProxy().mappingsChanged(traces, programs); } } - protected class InfoPerTrace extends TraceDomainObjectListener { - private Trace trace; - private Map outbound = new HashMap<>(); - - public InfoPerTrace(Trace trace) { - this.trace = trace; - - listenForUntyped(DomainObjectEvent.RESTORED, e -> objectRestored()); - listenFor(TraceEvents.MAPPING_ADDED, this::staticMappingAdded); - listenFor(TraceEvents.MAPPING_DELETED, this::staticMappingDeleted); - - trace.addListener(this); - - loadOutboundEntries(); - } - - private void objectRestored() { - synchronized (lock) { - Set fromClosure = collectAffectedByTrace(trace); - var old = Map.copyOf(outbound); - clearOutboundEntries(); - loadOutboundEntries(); // Also places corresponding inbound entries - if (!old.equals(outbound)) { - if (!fromClosure.isEmpty()) { - traceAffected(trace); - } - programsAffected(fromClosure); - doAffectedByTraceOpened(trace); - } - } - } - - private void staticMappingAdded(TraceStaticMapping mapping) { - // Msg.debug(this, "Trace Mapping added: " + mapping); - synchronized (lock) { - MappingEntry me = new MappingEntry(mapping); - putOutboundAndInboundEntries(me); - if (me.program != null) { - traceAffected(trace); - programAffected(me.program); - } - } - } - - private void staticMappingDeleted(TraceStaticMapping mapping) { - synchronized (lock) { - MappingEntry me = - outbound.get(new ImmutableTraceAddressSnapRange(mapping.getTraceAddressRange(), - mapping.getLifespan())); - if (me == null) { - Msg.warn(this, "It appears I lost track of something that just got removed"); - return; - } - Program program = me.program; - removeOutboundAndInboundEntries(me); - if (program != null) { - traceAffected(trace); - programAffected(program); - } - } - } - - public void dispose() { - trace.removeListener(this); - } - - protected void putOutboundAndInboundEntries(MappingEntry me) { - outbound.put(me.getTraceAddressSnapRange(), me); - - InfoPerProgram destInfo = trackedProgramInfo.get(me.getStaticProgramURL()); - if (destInfo == null) { - return; // Not opened - } - me.programOpened(destInfo.program); - destInfo.inbound.put(me, me.getStaticAddress()); - } - - protected void removeInboundEntryFor(MappingEntry me) { - InfoPerProgram destInfo = trackedProgramInfo.get(me.getStaticProgramURL()); - if (destInfo == null) { - return; // Not opened - } - destInfo.inbound.remove(me); - } - - protected void removeOutboundAndInboundEntries(MappingEntry me) { - outbound.remove(me.getTraceAddressSnapRange()); - removeInboundEntryFor(me); - } - - protected void loadOutboundEntries() { - TraceStaticMappingManager manager = trace.getStaticMappingManager(); - for (TraceStaticMapping mapping : manager.getAllEntries()) { - putOutboundAndInboundEntries(new MappingEntry(mapping)); - } - } - - protected void clearOutboundEntries() { - for (MappingEntry me : outbound.values()) { - removeInboundEntryFor(me); - } - outbound.clear(); - } - - public boolean programOpened(Program other, InfoPerProgram otherInfo) { - boolean result = false; - for (MappingEntry me : outbound.values()) { - if (me.programOpened(other)) { - otherInfo.inbound.put(me, me.getStaticAddress()); - result = true; - } - } - return result; - } - - public boolean programClosed(Program other) { - boolean result = false; - for (MappingEntry me : outbound.values()) { - result |= me.programClosed(other); - } - return result; - } - - public Set getOpenMappedProgramsAtSnap(long snap) { - Set result = new HashSet<>(); - Set toClean = new HashSet<>(); - for (Entry out : outbound.entrySet()) { - MappingEntry me = out.getValue(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - if (!me.isStaticProgramOpen()) { - continue; - } - if (!out.getKey().getLifespan().contains(snap)) { - continue; - } - result.add(me.program); - } - outbound.values().removeAll(toClean); - mappingsAffected(toClean); - return result; - } - - public ProgramLocation getOpenMappedLocations(Address address, Lifespan span) { - TraceAddressSnapRange at = new ImmutableTraceAddressSnapRange(address, span); - Set toClean = new HashSet<>(); - for (Entry out : outbound.entrySet()) { - if (out.getKey().intersects(at)) { - MappingEntry me = out.getValue(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - if (me.isStaticProgramOpen()) { - outbound.values().removeAll(toClean); - mappingsAffected(toClean); - return me.mapTraceAddressToProgramLocation(address); - } - } - } - outbound.values().removeAll(toClean); - mappingsAffected(toClean); - return null; - } - - protected void collectOpenMappedPrograms(AddressRange rng, Lifespan span, - Map> result) { - TraceAddressSnapRange tatr = new ImmutableTraceAddressSnapRange(rng, span); - Set toClean = new HashSet<>(); - for (Entry out : outbound.entrySet()) { - MappingEntry me = out.getValue(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - if (me.program == null) { - continue; - } - if (!out.getKey().intersects(tatr)) { - continue; - } - AddressRange srcRng = out.getKey().getRange().intersect(rng); - AddressRange dstRng = me.mapTraceRangeToProgram(rng); - result.computeIfAbsent(me.program, p -> new TreeSet<>()) - .add(new MappedAddressRange(srcRng, dstRng)); - } - outbound.values().removeAll(toClean); - mappingsAffected(toClean); - } - - public Map> getOpenMappedViews(AddressSetView set, - Lifespan span) { - Map> result = new HashMap<>(); - for (AddressRange rng : set) { - collectOpenMappedPrograms(rng, span, result); - } - return Collections.unmodifiableMap(result); - } - - protected void collectMappedProgramURLsInView(AddressRange rng, Lifespan span, - Set result) { - Set toClean = new HashSet<>(); - TraceAddressSnapRange tatr = new ImmutableTraceAddressSnapRange(rng, span); - for (Entry out : outbound.entrySet()) { - MappingEntry me = out.getValue(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - if (!out.getKey().intersects(tatr)) { - continue; - } - result.add(me.getStaticProgramURL()); - } - outbound.values().removeAll(toClean); - mappingsAffected(toClean); - } - - public Set getMappedProgramURLsInView(AddressSetView set, Lifespan span) { - Set result = new HashSet<>(); - for (AddressRange rng : set) { - collectMappedProgramURLsInView(rng, span, result); - } - return Collections.unmodifiableSet(result); - } - } - - protected class InfoPerProgram implements DomainObjectListener { - private Program program; - - private ValueSortedMap inbound = - TreeValueSortedMap.createWithNaturalOrder(); - - public InfoPerProgram(Program program) { - this.program = program; - program.addListener(this); - loadInboundEntries(); - } - - @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (ev.contains(DomainObjectEvent.FILE_CHANGED)) { - // TODO: This seems like overkill - programClosed(program); - programOpened(program); - } - // TODO: Can I listen for when the program moves? - // TODO: Or when relevant blocks move? - } - - protected void loadInboundEntries() { - for (InfoPerTrace traceInfo : trackedTraceInfo.values()) { - for (MappingEntry out : traceInfo.outbound.values()) { - if (out.program == program) { - inbound.put(out, out.getStaticAddress()); - } - } - } - } - - public boolean isMappedInTrace(Trace trace) { - Set toClean = new HashSet<>(); - for (MappingEntry me : inbound.keySet()) { - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - if (Objects.equals(trace, me.getTrace())) { - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - return true; - } - } - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - return false; - } - - public boolean traceClosed(Trace trace) { - Set updates = new HashSet<>(); - for (Entry ent : inbound.entrySet()) { - MappingEntry me = ent.getKey(); - if (Objects.equals(trace, me.getTrace())) { - updates.add(me); - } - } - return inbound.keySet().removeAll(updates); - } - - public Set getOpenMappedTraceLocations(Address address) { - Set result = new HashSet<>(); - Set toClean = new HashSet<>(); - for (Entry inPreceding : inbound.headMapByValue(address, true) - .entrySet()) { - MappingEntry me = inPreceding.getKey(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - Address start = inPreceding.getValue(); - if (start == null) { - continue; - } - if (!me.isInProgramRange(address)) { - continue; - } - result.add(me.mapProgramAddressToTraceLocation(address)); - } - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - return result; - } - - public TraceLocation getOpenMappedTraceLocation(Trace trace, Address address, long snap) { - // TODO: Map by trace? - Set toClean = new HashSet<>(); - for (Entry inPreceding : inbound.headMapByValue(address, true) - .entrySet()) { - MappingEntry me = inPreceding.getKey(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - Address start = inPreceding.getValue(); - if (start == null) { - continue; - } - if (me.getTrace() != trace) { - continue; - } - if (!me.isInProgramRange(address)) { - continue; - } - if (!me.isInTraceLifespan(snap)) { - continue; - } - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - return me.mapProgramAddressToTraceLocation(address); - } - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - return null; - } - - protected void collectOpenMappedViews(AddressRange rng, - Map> result) { - Set toClean = new HashSet<>(); - for (Entry inPreceeding : inbound - .headMapByValue(rng.getMaxAddress(), true) - .entrySet()) { - MappingEntry me = inPreceeding.getKey(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - Address start = inPreceeding.getValue(); - if (start == null) { - continue; - } - if (!me.isInProgramRange(rng)) { - continue; - } - - AddressRange srcRange = me.staticRange.intersect(rng); - AddressRange dstRange = me.mapProgramRangeToTrace(rng); - result.computeIfAbsent(me.getTraceSpan(), p -> new TreeSet<>()) - .add(new MappedAddressRange(srcRange, dstRange)); - } - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - } - - public Map> getOpenMappedViews( - AddressSetView set) { - Map> result = new HashMap<>(); - for (AddressRange rng : set) { - collectOpenMappedViews(rng, result); - } - return Collections.unmodifiableMap(result); - } - } - - private final Map trackedTraceInfo = new HashMap<>(); - private final Map trackedProgramInfo = new HashMap<>(); + final Map traceInfoByTrace = new HashMap<>(); + final Map programInfoByProgram = new HashMap<>(); + final Map programInfoByUrl = new HashMap<>(); @AutoServiceConsumed private DebuggerTraceManagerService traceManager; @@ -638,14 +116,11 @@ public class DebuggerStaticMappingServicePlugin extends Plugin @SuppressWarnings("unused") private final AutoService.Wiring autoWiring; - private final Object lock = new Object(); + final Object lock = new Object(); - private final AsyncDebouncer changeDebouncer = - new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100); + final ExecutorService executor = Executors.newSingleThreadExecutor(); private final ListenerSet changeListeners = new ListenerSet<>(DebuggerStaticMappingChangeListener.class, true); - private Set affectedTraces = new HashSet<>(); - private Set affectedPrograms = new HashSet<>(); private final ProgramModuleIndexer programModuleIndexer; private final ModuleMapProposalGenerator moduleMapProposalGenerator; @@ -655,60 +130,15 @@ public class DebuggerStaticMappingServicePlugin extends Plugin this.autoWiring = AutoService.wireServicesProvidedAndConsumed(this); this.programModuleIndexer = new ProgramModuleIndexer(tool); this.moduleMapProposalGenerator = new ModuleMapProposalGenerator(programModuleIndexer); - - changeDebouncer.addListener(this::fireChangeListeners); - tool.getProject().getProjectData().addDomainFolderChangeListener(this); } @Override protected void dispose() { tool.getProject().getProjectData().removeDomainFolderChangeListener(this); + executor.close(); super.dispose(); } - private void fireChangeListeners(Void v) { - Set traces; - Set programs; - synchronized (affectedTraces) { - traces = Collections.unmodifiableSet(affectedTraces); - programs = Collections.unmodifiableSet(affectedPrograms); - affectedTraces = new HashSet<>(); - affectedPrograms = new HashSet<>(); - } - changeListeners.invoke().mappingsChanged(traces, programs); - } - - private void traceAffected(Trace trace) { - synchronized (affectedTraces) { - affectedTraces.add(trace); - changeDebouncer.contact(null); - } - } - - private void programAffected(Program program) { - synchronized (affectedTraces) { - affectedPrograms.add(program); - changeDebouncer.contact(null); - } - } - - private void programsAffected(Collection programs) { - synchronized (affectedTraces) { - affectedPrograms.addAll(programs); - changeDebouncer.contact(null); - } - } - - private void mappingsAffected(Collection entries) { - Set traces = entries.stream().map(e -> e.getTrace()).collect(Collectors.toSet()); - Set programs = entries.stream().map(e -> e.program).collect(Collectors.toSet()); - synchronized (affectedTraces) { - affectedTraces.addAll(traces); - affectedPrograms.addAll(programs); - changeDebouncer.contact(null); - } - } - @Override public void addChangeListener(DebuggerStaticMappingChangeListener l) { changeListeners.add(l); @@ -719,73 +149,134 @@ public class DebuggerStaticMappingServicePlugin extends Plugin changeListeners.remove(l); } + void checkAndClearProgram(ChangeCollector cc, MappingEntry me) { + InfoPerProgram info = programInfoByUrl.get(me.getStaticProgramUrl()); + if (info == null) { + return; + } + info.clearProgram(cc, me); + } + + void checkAndFillProgram(ChangeCollector cc, MappingEntry me) { + InfoPerProgram info = programInfoByUrl.get(me.getStaticProgramUrl()); + if (info == null) { + return; + } + info.fillProgram(cc, me); + } + @Override public CompletableFuture changesSettled() { - return changeDebouncer.stable(); + return CompletableFuture.runAsync(() -> { + }, executor); + } + + void programsChanged() { + try (ChangeCollector cc = new ChangeCollector(this)) { + // Invoke change callbacks without the lock! (try must surround sync) + synchronized (lock) { + programsChanged(cc); + } + } + } + + void programsChanged(ChangeCollector cc) { + Set curProgs = Stream.of(programManager.getAllOpenPrograms()) + .filter(p -> !p.isClosed()) // Double-check + .collect(Collectors.toSet()); + Set removed = programInfoByProgram.values() + .stream() + .filter(i -> !curProgs.contains(i.program) || !i.urlMatches()) + .collect(Collectors.toSet()); + processRemovedProgramInfos(cc, removed); + Set added = ChangeCollector.subtract(curProgs, programInfoByProgram.keySet()); + processAddedPrograms(cc, added); + } + + void processRemovedProgramInfos(ChangeCollector cc, Set removed) { + for (InfoPerProgram info : removed) { + processRemovedProgramInfo(cc, info); + } + } + + void processRemovedProgramInfo(ChangeCollector cc, InfoPerProgram info) { + programInfoByProgram.remove(info.program); + programInfoByUrl.remove(info.url); + info.clearEntries(cc); + } + + void processAddedPrograms(ChangeCollector cc, Set added) { + for (Program program : added) { + processAddedProgram(cc, program); + } + } + + void processAddedProgram(ChangeCollector cc, Program program) { + InfoPerProgram info = new InfoPerProgram(this, program); + programInfoByProgram.put(program, info); + programInfoByUrl.put(info.url, info); + info.fillEntries(cc); + } + + private void tracesChanged() { + try (ChangeCollector cc = new ChangeCollector(this)) { + // Invoke change callbacks without the lock! (try must surround sync) + synchronized (lock) { + tracesChanged(cc); + } + } + } + + void tracesChanged(ChangeCollector cc) { + Set curTraces = traceManager.getOpenTraces() + .stream() + .filter(t -> !t.isClosed()) // Double-check + .collect(Collectors.toSet()); + Set oldTraces = traceInfoByTrace.keySet(); + + Set removed = ChangeCollector.subtract(oldTraces, curTraces); + Set added = ChangeCollector.subtract(curTraces, oldTraces); + + processRemovedTraces(cc, removed); + processAddedTraces(cc, added); + } + + void processRemovedTraces(ChangeCollector cc, Set removed) { + for (Trace trace : removed) { + processRemovedTrace(cc, trace); + } + } + + void processRemovedTrace(ChangeCollector cc, Trace trace) { + InfoPerTrace info = traceInfoByTrace.remove(trace); + info.removeEntries(cc); + } + + void processAddedTraces(ChangeCollector cc, Set added) { + for (Trace trace : added) { + processAddedTrace(cc, trace); + } + } + + void processAddedTrace(ChangeCollector cc, Trace trace) { + InfoPerTrace info = new InfoPerTrace(this, trace); + traceInfoByTrace.put(trace, info); + info.resyncEntries(cc); } @Override public void processEvent(PluginEvent event) { if (event instanceof ProgramOpenedPluginEvent) { - ProgramOpenedPluginEvent openedEvt = (ProgramOpenedPluginEvent) event; - programOpened(openedEvt.getProgram()); + CompletableFuture.runAsync(this::programsChanged, executor); } else if (event instanceof ProgramClosedPluginEvent) { - ProgramClosedPluginEvent closedEvt = (ProgramClosedPluginEvent) event; - programClosed(closedEvt.getProgram()); + CompletableFuture.runAsync(this::programsChanged, executor); } else if (event instanceof TraceOpenedPluginEvent) { - TraceOpenedPluginEvent openedEvt = (TraceOpenedPluginEvent) event; - traceOpened(openedEvt.getTrace()); + CompletableFuture.runAsync(this::tracesChanged, executor); } else if (event instanceof TraceClosedPluginEvent) { - TraceClosedPluginEvent closedEvt = (TraceClosedPluginEvent) event; - traceClosed(closedEvt.getTrace()); - } - } - - private void programOpened(Program program) { - synchronized (lock) { - if (program instanceof TraceProgramView) { - return; // TODO: Allow this? - } - URL url = ProgramURLUtils.getUrlFromProgram(program); - if (url == null) { - // Not in a project. Nothing could refer to it anyway.... - // TODO: If the program is saved into a project, it could be.... - return; - } - InfoPerProgram newInfo = new InfoPerProgram(program); - InfoPerProgram mustBeNull = trackedProgramInfo.put(url, newInfo); - assert mustBeNull == null; - - for (InfoPerTrace info : trackedTraceInfo.values()) { - if (info.programOpened(program, newInfo)) { - programAffected(program); - traceAffected(info.trace); - } - } - } - } - - private void programClosed(Program program) { - synchronized (lock) { - if (program instanceof TraceProgramView) { - return; - } - // NB. The URL may have changed, so can't use that as key - for (Iterator it = trackedProgramInfo.values().iterator(); it - .hasNext();) { - InfoPerProgram info = it.next(); - if (info.program == program) { - it.remove(); - } - } - for (InfoPerTrace info : trackedTraceInfo.values()) { - if (info.programClosed(program)) { - traceAffected(info.trace); - } - } + CompletableFuture.runAsync(this::tracesChanged, executor); } } @@ -794,69 +285,15 @@ public class DebuggerStaticMappingServicePlugin extends Plugin // This get called when a domain object is saved into the active project // We essentially need to update the URL, which requires examining every entry // TODO: Could probably cut out a bit of the kruft, but this should do - if (object instanceof Program) { - Program program = (Program) object; + if (object instanceof Program program) { synchronized (lock) { - programClosed(program); - int i = ArrayUtils.indexOf(programManager.getAllOpenPrograms(), program); - if (i >= 0) { - programOpened(program); + if (programInfoByProgram.containsKey(program)) { + CompletableFuture.runAsync(this::programsChanged, executor); } } } } - private Set collectAffectedByTrace(Trace trace) { - Set set = new HashSet<>(); - for (InfoPerProgram info : trackedProgramInfo.values()) { - if (info.isMappedInTrace(trace)) { - set.add(info.program); - } - } - return set; - } - - private void doAffectedByTraceOpened(Trace trace) { - Set set = collectAffectedByTrace(trace); - if (!set.isEmpty()) { - traceAffected(trace); - programsAffected(set); - } - } - - private void traceOpened(Trace trace) { - synchronized (lock) { - if (trace.isClosed()) { - Msg.warn(this, "Got traceOpened for a close trace"); - return; - } - InfoPerTrace newInfo = new InfoPerTrace(trace); - InfoPerTrace mustBeNull = trackedTraceInfo.put(trace, newInfo); - assert mustBeNull == null; - doAffectedByTraceOpened(trace); - } - } - - private void doAffectedByTraceClosed(Trace trace) { - for (InfoPerProgram info : trackedProgramInfo.values()) { - if (info.traceClosed(trace)) { - programAffected(info.program); - } - } - } - - private void traceClosed(Trace trace) { - synchronized (lock) { - InfoPerTrace traceInfo = trackedTraceInfo.remove(trace); - if (traceInfo == null) { - Msg.warn(this, "Got traceClosed without/before traceOpened"); - return; - } - traceInfo.dispose(); - doAffectedByTraceClosed(trace); - } - } - @Override public void addMapping(TraceLocation from, ProgramLocation to, long length, boolean truncateExisting) throws TraceConflictedMappingException { @@ -960,7 +397,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } protected InfoPerTrace requireTrackedInfo(Trace trace) { - InfoPerTrace info = trackedTraceInfo.get(trace); + InfoPerTrace info = traceInfoByTrace.get(trace); if (info == null) { return noTraceInfo(); } @@ -968,11 +405,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } protected InfoPerProgram requireTrackedInfo(Program program) { - URL url = ProgramURLUtils.getUrlFromProgram(program); - if (url == null) { - return noProject(); - } - InfoPerProgram info = trackedProgramInfo.get(url); + InfoPerProgram info = programInfoByProgram.get(program); if (info == null) { return noProgramInfo(); } @@ -997,7 +430,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin if (info == null) { return null; } - return info.getOpenMappedLocations(loc.getAddress(), loc.getLifespan()); + return info.getOpenMappedProgramLocation(loc.getAddress(), loc.getLifespan()); } } @@ -1090,7 +523,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin if (info == null) { return null; } - urls = info.getMappedProgramURLsInView(set, Lifespan.at(snap)); + urls = info.getMappedProgramUrlsInView(set, Lifespan.at(snap)); } Set result = new HashSet<>(); for (URL url : urls) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerProgram.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerProgram.java new file mode 100644 index 0000000000..330f2d9cf5 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerProgram.java @@ -0,0 +1,180 @@ +/* ### + * 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.debug.service.modules; + +import java.net.URL; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin.ChangeCollector; +import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; +import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.framework.model.*; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.trace.model.*; +import ghidra.util.Msg; + +class InfoPerProgram implements DomainObjectListener { + + static class NavMultiMap { + private final TreeMap> map = new TreeMap<>(); + + public boolean put(K k, V v) { + return map.computeIfAbsent(k, __ -> new HashSet<>()).add(v); + } + + public boolean remove(K k, V v) { + Set set = map.get(k); + if (set == null) { + return false; + } + if (!set.remove(v)) { + return false; + } + if (set.isEmpty()) { + map.remove(k); + } + return true; + } + } + + private final DebuggerStaticMappingServicePlugin plugin; + final Program program; + final NavMultiMap inboundByStaticAddress = new NavMultiMap<>(); + + final URL url; + + InfoPerProgram(DebuggerStaticMappingServicePlugin plugin, Program program) { + this.plugin = plugin; + this.program = program; + this.url = ProgramURLUtils.getUrlFromProgram(program); + + program.addListener(this); + } + + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { + if (ev.contains(DomainObjectEvent.FILE_CHANGED) || ev.contains(DomainObjectEvent.RENAMED)) { + if (!urlMatches()) { + CompletableFuture.runAsync(plugin::programsChanged, plugin.executor); + } + } + } + + boolean urlMatches() { + return Objects.equals(url, ProgramURLUtils.getUrlFromProgram(program)); + } + + void clearProgram(ChangeCollector cc, MappingEntry me) { + assert me.program == program; + inboundByStaticAddress.remove(me.getStaticAddress(), me); + me.clearProgram(cc, program); + } + + void fillProgram(ChangeCollector cc, MappingEntry me) { + assert me.getStaticProgramUrl().equals(ProgramURLUtils.getUrlFromProgram(program)); + me.fillProgram(cc, program); + inboundByStaticAddress.put(me.getStaticAddress(), me); + } + + void clearEntries(ChangeCollector cc) { + if (url == null) { + return; + } + for (InfoPerTrace info : plugin.traceInfoByTrace.values()) { + info.clearEntriesForProgram(cc, this); + } + } + + void fillEntries(ChangeCollector cc) { + if (url == null) { + return; + } + for (InfoPerTrace info : plugin.traceInfoByTrace.values()) { + info.fillEntriesForProgram(cc, this); + } + } + + Set getOpenMappedTraceLocations(Address address) { + Set result = new HashSet<>(); + for (Set set : inboundByStaticAddress.map.headMap(address, true).values()) { + for (MappingEntry me : set) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (!me.isInProgramRange(address)) { + continue; + } + result.add(me.mapProgramAddressToTraceLocation(address)); + } + } + return result; + } + + TraceLocation getOpenMappedTraceLocation(Trace trace, Address address, long snap) { + // TODO: Map by trace? + for (Set set : inboundByStaticAddress.map.headMap(address, true).values()) { + for (MappingEntry me : set) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (me.getTrace() != trace) { + continue; + } + if (!me.isInProgramRange(address)) { + continue; + } + if (!me.isInTraceLifespan(snap)) { + continue; + } + return me.mapProgramAddressToTraceLocation(address); + } + } + return null; + } + + private void collectOpenMappedViews(Map> result, + AddressRange rng) { + for (Set set : inboundByStaticAddress.map.headMap(rng.getMaxAddress(), true) + .values()) { + for (MappingEntry me : set) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + // NB. No lifespan to consider + if (!me.isInProgramRange(rng)) { + continue; + } + AddressRange srcRange = me.getStaticRange().intersect(rng); + AddressRange dstRange = me.mapProgramRangeToTrace(rng); + result.computeIfAbsent(me.getTraceSpan(), p -> new TreeSet<>()) + .add(new MappedAddressRange(srcRange, dstRange)); + } + } + } + + Map> getOpenMappedViews(AddressSetView set) { + Map> result = new HashMap<>(); + for (AddressRange rng : set) { + collectOpenMappedViews(result, rng); + } + return Collections.unmodifiableMap(result); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerTrace.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerTrace.java new file mode 100644 index 0000000000..7f3378d2e9 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerTrace.java @@ -0,0 +1,254 @@ +/* ### + * 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.debug.service.modules; + +import java.net.URL; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.HashSetValuedHashMap; + +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin.ChangeCollector; +import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.framework.model.DomainObjectChangedEvent; +import ghidra.framework.model.DomainObjectEvent; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.*; +import ghidra.trace.model.modules.TraceStaticMapping; +import ghidra.trace.util.TraceEvents; +import ghidra.util.Msg; + +class InfoPerTrace extends TraceDomainObjectListener { + private final DebuggerStaticMappingServicePlugin plugin; + final Trace trace; + + final Map outboundByEntry = new HashMap<>(); + final NavigableMap outboundByRange = + new TreeMap<>(Comparator.comparing(TraceAddressSnapRange::getX1)); + final MultiValuedMap outboundByStaticUrl = new HashSetValuedHashMap<>(); + + private volatile boolean needsResync = false; + + InfoPerTrace(DebuggerStaticMappingServicePlugin plugin, Trace trace) { + this.plugin = plugin; + this.trace = trace; + + listenForUntyped(DomainObjectEvent.RESTORED, e -> objectRestored()); + listenFor(TraceEvents.MAPPING_ADDED, this::staticMappingAdded); + listenFor(TraceEvents.MAPPING_DELETED, this::staticMappingDeleted); + trace.addListener(this); + } + + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { + super.domainObjectChanged(ev); // Dispatch individual records + // Now do the actual processing + if (needsResync) { + needsResync = false; + CompletableFuture.runAsync(this::resyncEntries, plugin.executor); + } + } + + private void objectRestored() { + this.needsResync = true; + } + + private void staticMappingAdded(TraceStaticMapping mapping) { + this.needsResync = true; + } + + private void staticMappingDeleted(TraceStaticMapping mapping) { + this.needsResync = true; + } + + public void dispose() { + trace.removeListener(this); + } + + private void resyncEntries() { + try (ChangeCollector cc = new ChangeCollector(plugin)) { + // Invoke change callbacks without the lock! (try must surround sync) + synchronized (plugin.lock) { + resyncEntries(cc); + } + } + } + + void resyncEntries(ChangeCollector cc) { + Set oldEntries = outboundByEntry.keySet(); + Set curEntries = trace.getStaticMappingManager() + .getAllEntries() + .stream() + .filter(e -> !e.isDeleted()) // Double-check + .collect(Collectors.toSet()); + + Set removed = ChangeCollector.subtract(oldEntries, curEntries); + Set added = ChangeCollector.subtract(curEntries, oldEntries); + + processRemovedEntries(cc, removed); + processAddedEntries(cc, added); + } + + void removeEntries(ChangeCollector cc) { + processRemovedEntries(cc, Set.copyOf(outboundByEntry.keySet())); + } + + private void processRemovedEntries(ChangeCollector cc, Set removed) { + for (TraceStaticMapping entry : removed) { + processRemovedEntry(cc, entry); + } + } + + private void processRemovedEntry(ChangeCollector cc, TraceStaticMapping entry) { + MappingEntry me = outboundByEntry.remove(entry); + if (me == null) { + return; + } + outboundByRange.remove(me.getTraceAddressSnapRange()); + outboundByStaticUrl.removeMapping(me.getStaticProgramUrl(), me); + plugin.checkAndClearProgram(cc, me); + } + + private void processAddedEntries(ChangeCollector cc, Set added) { + for (TraceStaticMapping entry : added) { + processAddedEntry(cc, entry); + } + } + + private void processAddedEntry(ChangeCollector cc, TraceStaticMapping entry) { + MappingEntry me = new MappingEntry(entry); + outboundByEntry.put(entry, me); + outboundByRange.put(me.getTraceAddressSnapRange(), me); + outboundByStaticUrl.put(me.getStaticProgramUrl(), me); + plugin.checkAndFillProgram(cc, me); + } + + void clearEntriesForProgram(ChangeCollector cc, InfoPerProgram progInfo) { + for (MappingEntry me : outboundByStaticUrl.get(progInfo.url)) { + progInfo.clearProgram(cc, me); + } + } + + void fillEntriesForProgram(ChangeCollector cc, InfoPerProgram progInfo) { + for (MappingEntry me : outboundByStaticUrl.get(progInfo.url)) { + progInfo.fillProgram(cc, me); + } + } + + Set getOpenMappedProgramsAtSnap(long snap) { + Set result = new HashSet<>(); + for (Entry out : outboundByRange.entrySet()) { + MappingEntry me = out.getValue(); + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (!me.isStaticProgramOpen()) { + continue; + } + if (!out.getKey().getLifespan().contains(snap)) { + continue; + } + result.add(me.program); + } + return result; + } + + ProgramLocation getOpenMappedProgramLocation(Address address, Lifespan span) { + TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(address, span); + // max is tasr (single address) + for (MappingEntry me : outboundByRange.headMap(tasr, true).values()) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (!tasr.intersects(me.getTraceAddressSnapRange())) { + continue; + } + if (me.isStaticProgramOpen()) { + return me.mapTraceAddressToProgramLocation(address); + } + } + return null; + } + + private void collectOpenMappedViews(Map> result, + AddressRange rng, Lifespan span) { + TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(rng, span); + TraceAddressSnapRange max = new ImmutableTraceAddressSnapRange(rng.getMaxAddress(), span); + for (MappingEntry me : outboundByRange.headMap(max, true).values()) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (me.program == null) { + continue; + } + if (!tasr.intersects(me.getTraceAddressSnapRange())) { + continue; + } + AddressRange srcRng = me.getTraceRange().intersect(rng); + AddressRange dstRng = me.mapTraceRangeToProgram(rng); + result.computeIfAbsent(me.program, p -> new TreeSet<>()) + .add(new MappedAddressRange(srcRng, dstRng)); + } + } + + Map> getOpenMappedViews(AddressSetView set, + Lifespan span) { + /** + * NB. Cannot use the OverlappingObjectIterator here. Because of the snap dimension, objects + * may not be disjoint in the address dimension. + */ + Map> result = new HashMap<>(); + for (AddressRange rng : set) { + collectOpenMappedViews(result, rng, span); + } + return Collections.unmodifiableMap(result); + } + + private void collectMappedProgramUrlsInView(Set result, AddressRange rng, Lifespan span) { + TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(rng, span); + TraceAddressSnapRange max = new ImmutableTraceAddressSnapRange(rng.getMaxAddress(), span); + for (MappingEntry me : outboundByRange.headMap(max, true).values()) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (!tasr.intersects(me.getTraceAddressSnapRange())) { + continue; + } + result.add(me.getStaticProgramUrl()); + } + } + + Set getMappedProgramUrlsInView(AddressSetView set, Lifespan span) { + /** + * NB. Cannot use the OverlappingObjectIterator here. Because of the snap dimension, objects + * may not be disjoint in the address dimension. + */ + Set result = new HashSet<>(); + for (AddressRange rng : set) { + collectMappedProgramUrlsInView(result, rng, span); + } + return Collections.unmodifiableSet(result); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/MappingEntry.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/MappingEntry.java new file mode 100644 index 0000000000..d551c0b7cb --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/MappingEntry.java @@ -0,0 +1,202 @@ +/* ### + * 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.debug.service.modules; + +import java.net.URL; +import java.util.Objects; + +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin.ChangeCollector; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.*; +import ghidra.trace.model.modules.TraceStaticMapping; +import ghidra.util.Msg; + +class MappingEntry { + final TraceStaticMapping mapping; + final TraceAddressSnapRange tasr; + + Program program; + private AddressRange staticRange; + + public MappingEntry(TraceStaticMapping mapping) { + this.mapping = mapping; + // Yes, mapping range and lifespan are immutable + this.tasr = new ImmutableTraceAddressSnapRange(mapping.getTraceAddressRange(), + mapping.getLifespan()); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof MappingEntry that)) { + return false; + } + // Yes, use identity, since it should be the same trace db records + if (this.mapping != that.mapping) { + return false; + } + if (this.program != that.program) { + return false; + } + if (!Objects.equals(this.staticRange, that.staticRange)) { + return false; + } + return true; + } + + public Trace getTrace() { + return mapping.getTrace(); + } + + Address addrOrMin(Program program, String addr) { + AddressFactory factory = program.getAddressFactory(); + Address result = factory.getAddress(addr); + if (result == null) { + Msg.warn(this, "Mapping entry has invalid static address: " + addr); + result = factory.getDefaultAddressSpace().getMinAddress(); + } + return result; + } + + Address addrOrMax(Address start, long length) { + Address result = start.addWrapSpace(length); + if (result.compareTo(start) < 0) { + Msg.warn(this, "Mapping entry caused overflow in static address space"); + return start.getAddressSpace().getMaxAddress(); + } + return result; + } + + void clearProgram(ChangeCollector cc, Program program) { + this.program = null; + this.staticRange = null; + cc.traceAffected(getTrace()); + cc.programAffected(program); + } + + void fillProgram(ChangeCollector cc, Program program) { + this.program = program; + Address minAddr = addrOrMin(program, mapping.getStaticAddress()); + Address maxAddr = addrOrMax(minAddr, mapping.getLength() - 1); + this.staticRange = new AddressRangeImpl(minAddr, maxAddr); + cc.traceAffected(getTrace()); + cc.programAffected(program); + } + + public AddressRange getTraceRange() { + return mapping.getTraceAddressRange(); + } + + public Address getTraceAddress() { + return mapping.getMinTraceAddress(); + } + + public AddressRange getStaticRange() { + return staticRange; + } + + public Address getStaticAddress() { + if (staticRange == null) { + return null; + } + return staticRange.getMinAddress(); + } + + public TraceSpan getTraceSpan() { + return new DefaultTraceSpan(mapping.getTrace(), mapping.getLifespan()); + } + + public TraceAddressSnapRange getTraceAddressSnapRange() { + return tasr; + } + + public boolean isInTraceRange(Address address, Long snap) { + return mapping.getTraceAddressRange().contains(address) && + (snap == null || mapping.getLifespan().contains(snap)); + } + + public boolean isInTraceRange(AddressRange rng, Long snap) { + return mapping.getTraceAddressRange().intersects(rng) && + (snap == null || mapping.getLifespan().contains(snap)); + } + + public boolean isInTraceLifespan(long snap) { + return mapping.getLifespan().contains(snap); + } + + public boolean isInProgramRange(Address address) { + if (staticRange == null) { + return false; + } + return staticRange.contains(address); + } + + public boolean isInProgramRange(AddressRange rng) { + if (staticRange == null) { + return false; + } + return staticRange.intersects(rng); + } + + protected Address mapTraceAddressToProgram(Address address) { + assert isInTraceRange(address, null); + long offset = address.subtract(mapping.getMinTraceAddress()); + return staticRange.getMinAddress().addWrapSpace(offset); + } + + public ProgramLocation mapTraceAddressToProgramLocation(Address address) { + if (program == null) { + throw new IllegalStateException("Static program is not opened"); + } + return new ProgramLocation(program, mapTraceAddressToProgram(address)); + } + + public AddressRange mapTraceRangeToProgram(AddressRange rng) { + assert isInTraceRange(rng, null); + AddressRange part = rng.intersect(mapping.getTraceAddressRange()); + Address min = mapTraceAddressToProgram(part.getMinAddress()); + Address max = mapTraceAddressToProgram(part.getMaxAddress()); + return new AddressRangeImpl(min, max); + } + + protected Address mapProgramAddressToTrace(Address address) { + assert isInProgramRange(address); + long offset = address.subtract(staticRange.getMinAddress()); + return mapping.getMinTraceAddress().addWrapSpace(offset); + } + + protected TraceLocation mapProgramAddressToTraceLocation(Address address) { + return new DefaultTraceLocation(mapping.getTrace(), null, mapping.getLifespan(), + mapProgramAddressToTrace(address)); + } + + public AddressRange mapProgramRangeToTrace(AddressRange rng) { + assert (rng.intersects(staticRange)); + AddressRange part = rng.intersect(staticRange); + Address min = mapProgramAddressToTrace(part.getMinAddress()); + Address max = mapProgramAddressToTrace(part.getMaxAddress()); + return new AddressRangeImpl(min, max); + } + + public boolean isStaticProgramOpen() { + return program != null; + } + + public URL getStaticProgramUrl() { + return mapping.getStaticProgramURL(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 95c2ebefcc..821cd14e34 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -4,9 +4,9 @@ * 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. @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.debug.service.tracemgr; -import static ghidra.framework.main.DataTreeDialogType.*; +import static ghidra.framework.main.DataTreeDialogType.OPEN; import java.io.IOException; import java.lang.invoke.MethodHandles; @@ -656,7 +656,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin @Override public synchronized Collection getOpenTraces() { - return Set.copyOf(tracesView); + synchronized (listenersByTrace) { + return Set.copyOf(tracesView); + } } @Override @@ -998,8 +1000,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin protected void doCloseTraces(Collection traces, Collection targets) { for (Trace t : traces) { if (t.getConsumerList().contains(this)) { - firePluginEvent(new TraceClosedPluginEvent(getName(), t)); doTraceClosed(t); + firePluginEvent(new TraceClosedPluginEvent(getName(), t)); } } TargetActionTask.executeTask(tool, new DisconnectTask(tool, targets)); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java index e1641374c8..c4ca0f3b7a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java @@ -4,9 +4,9 @@ * 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. @@ -614,8 +614,7 @@ public abstract class AbstractGhidraHeadedDebuggerTest @BeforeClass public static void beforeClass() { - - // Note: we may decided to move this up to a framework-level base test class + // Note: we decided to move this up to a framework-level base test class TestDataStructureErrorHandlerInstaller.installConcurrentExceptionErrorHandler(); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java index 8bad648977..15f288504c 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -95,6 +95,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg try (ToyDBTraceBuilder r = new ToyDBTraceBuilder(saved)) { assertNotSame(tb.trace, r.trace); traceManager.openTrace(r.trace); + waitForDomainObject(r.trace); return r.trace; } } @@ -240,10 +241,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayBefore() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayBefore() throws Throwable { addMapping(); copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00000bad))); @@ -251,10 +253,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustBefore() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustBefore() throws Throwable { addMapping(); copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x001fffff))); @@ -262,10 +265,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtStart() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtStart() throws Throwable { addMapping(); Trace copy = copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200000))); @@ -281,10 +285,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceHitInMiddle() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceHitInMiddle() throws Throwable { addMapping(); Trace copy = copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200833))); @@ -298,10 +303,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtEnd() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtEnd() throws Throwable { addMapping(); Trace copy = copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200fff))); @@ -315,10 +321,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustAfter() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustAfter() throws Throwable { addMapping(); copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00201000))); @@ -326,10 +333,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayAfter() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayAfter() throws Throwable { addMapping(); copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0xbadbadbadL))); @@ -377,10 +385,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenTranslateStaticViewToTraceEmpty() throws Exception { + public void testAddMappingThenTranslateStaticViewToTraceEmpty() throws Throwable { addMapping(); copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Map> views = mappingService.getOpenMappedViews(program, new AddressSet()); @@ -388,10 +397,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenTranslateStaticViewToTraceReplete() throws Exception { + public void testAddMappingThenTranslateStaticViewToTraceReplete() throws Throwable { addMapping(); Trace copy = copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); AddressSet set = new AddressSet(); // Before @@ -438,10 +448,12 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCloseStaticAndOpenMappedMissWayBefore() throws Exception { + public void testAddMappingThenCloseStaticAndOpenMappedMissWayBefore() throws Throwable { // NOTE: Does not make sense to test program->trace, as program has no mapping records addMapping(); programManager.closeProgram(program, true); + waitForSwing(); + waitOn(mappingService.changesSettled()); AddressSet addrSet = new AddressSet(dynSpace.getAddress(0x00000bad)); Set programSet = @@ -451,9 +463,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCloseStaticAndOpenMappedHitInMiddle() throws Exception { + public void testAddMappingThenCloseStaticAndOpenMappedHitInMiddle() throws Throwable { addMapping(); programManager.closeProgram(program, true); + waitForSwing(); + waitOn(mappingService.changesSettled()); AddressSet addrSet = new AddressSet(dynSpace.getAddress(0x00100c0d)); Set programSet = @@ -465,9 +479,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCloseStaticAndOpenMappedMissWayAfter() throws Exception { + public void testAddMappingThenCloseStaticAndOpenMappedMissWayAfter() throws Throwable { addMapping(); programManager.closeProgram(program, true); + waitForSwing(); + waitOn(mappingService.changesSettled()); AddressSet addrSet = new AddressSet(dynSpace.getAddress(0xbadbadbadL)); Set programSet = @@ -478,14 +494,17 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg @Test public void testAddMappingThenCloseStaticAndTranslateTraceToStaticHitInMiddle() - throws Exception { + throws Throwable { addMapping(); + waitOn(mappingService.changesSettled()); // pre-check assertNotNull(mappingService.getOpenMappedLocation( new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), dynSpace.getAddress(0x00100c0d)))); programManager.closeProgram(program, true); + waitForSwing(); + waitOn(mappingService.changesSettled()); assertNull(mappingService.getOpenMappedLocation( new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), @@ -494,14 +513,16 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg @Test public void testAddMappingThenCloseTraceAndTranslateStaticToTraceHitInMiddle() - throws Exception { + throws Throwable { addMapping(); + waitOn(mappingService.changesSettled()); // pre-check assertEquals(1, mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).size()); traceManager.closeTrace(tb.trace); waitForSwing(); + waitOn(mappingService.changesSettled()); assertTrue(mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).isEmpty()); @@ -509,9 +530,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg @Test public void testAddMappingThenCloseAndReopenStaticAndTranslateTraceToStaticHitInMiddle() - throws Exception { + throws Throwable { addMapping(); programManager.closeProgram(program, true); + waitForSwing(); + waitOn(mappingService.changesSettled()); // pre-check assertNull(mappingService.getOpenMappedLocation( new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), @@ -519,6 +542,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg programManager.openProgram(program); waitForProgram(program); + waitOn(mappingService.changesSettled()); assertNotNull(mappingService.getOpenMappedLocation( new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), @@ -527,17 +551,19 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg @Test public void testAddMappingThenCloseAndReopenTraceAndTranslateStaticToTraceHitInMiddle() - throws Exception { + throws Throwable { addMapping(); traceManager.closeTrace(tb.trace); waitForSwing(); + waitOn(mappingService.changesSettled()); // pre-check assertTrue(mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).isEmpty()); traceManager.openTrace(tb.trace); - waitForSwing(); + waitForDomainObject(tb.trace); + waitOn(mappingService.changesSettled()); assertEquals(1, mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).size()); @@ -545,7 +571,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg @Test public void testAddMappingThenRemoveButAbortThenTranslateTraceToStaticHitInMiddle() - throws Exception { + throws Throwable { addMapping(); TraceLocation goodLoc = new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), @@ -553,19 +579,23 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg try (Transaction tx = tb.startTransaction()) { mappingManager.findContaining(dynSpace.getAddress(0x00100000), 0).delete(); waitForDomainObject(tb.trace); + waitOn(mappingService.changesSettled()); // pre-check assertNull(mappingService.getOpenMappedLocation(goodLoc)); tx.abort(); } waitForDomainObject(tb.trace); + waitOn(mappingService.changesSettled()); assertNotNull(mappingService.getOpenMappedLocation(goodLoc)); } @Test public void testAddCorrelationRemoveButUndoThenRequestMappingDynamicToStaticWithin() - throws Exception { + throws Throwable { addMapping(); + waitOn(mappingService.changesSettled()); + TraceLocation goodLoc = new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), dynSpace.getAddress(0x00100c0d)); @@ -577,11 +607,13 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg mappingManager.findContaining(dynSpace.getAddress(0x00100000), 0).delete(); } waitForDomainObject(tb.trace); + waitOn(mappingService.changesSettled()); // pre-check assertNull(mappingService.getOpenMappedLocation(goodLoc)); undo(tb.trace, true); + waitOn(mappingService.changesSettled()); assertNotNull(mappingService.getOpenMappedLocation(goodLoc)); } @@ -619,7 +651,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testMapFullSpace() throws Exception { + public void testMapFullSpace() throws Throwable { try (Transaction tx = tb.startTransaction()) { TraceLocation traceLoc = new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), tb.addr(0)); @@ -627,7 +659,10 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg // NB. 0 indicates 1 << 64 mappingService.addMapping(traceLoc, progLoc, 0, true); } - waitForPass(() -> assertMapsTwoWay(0L, 0L)); + waitForSwing(); + waitOn(mappingService.changesSettled()); + + assertMapsTwoWay(0L, 0L); assertMapsTwoWay(-1L, -1L); assertMapsTwoWay(Long.MAX_VALUE, Long.MAX_VALUE); assertMapsTwoWay(Long.MIN_VALUE, Long.MIN_VALUE); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMapping.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMapping.java index e7bf8120ff..e5a79b4e9c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMapping.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMapping.java @@ -4,9 +4,9 @@ * 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. @@ -33,7 +33,7 @@ import ghidra.util.database.*; import ghidra.util.database.annot.*; /** - * The implementation of a stack mapping, directly via a database object + * The implementation of a static mapping, directly via a database object * *

* Version history: diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java index b242035826..0529d29475 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java @@ -4,9 +4,9 @@ * 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. @@ -26,7 +26,30 @@ import ghidra.program.model.listing.CodeUnit; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.util.AbstractPeekableIterator; +/** + * An iterator of overlapping objects return from two given iterators. + * + *

+ * The given iterators, named left and right, must return objects each having a range attribute. + * Each iterator must return objects having disjoint ranges, i.e., no two objects from the + * same iterator may intersect. Each iterator must also return the objects sorted by min + * address. This iterator will then discover every case where an object from the left iterator + * overlaps an object from the right iterator, and return a pair for each such instance, in order of + * min address. + * + *

+ * WARNING: To avoid heap pollution, this iterator re-uses the same {@link Pair} on each call + * to {@link #next()}. If you need to save an overlapping pair, you must copy it. + * + * @param the type of objects returned by the left iterator + * @param the type of objects returned by the right iterator + */ public class OverlappingObjectIterator extends AbstractPeekableIterator> { + /** + * A means of obtaining the range attribute from each object + * + * @param the type of objects returned by an iterator + */ public interface Ranger { Address getMinAddress(T t); diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java index 19677ae117..fc76747e59 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java @@ -4,9 +4,9 @@ * 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. @@ -483,7 +483,7 @@ public abstract class AbstractDebuggerBreakpointMarkerPluginTest addMappedBreakpointOpenAndWait(); // wasteful, but whatever for (LogicalBreakpoint lb : List.copyOf(breakpointService.getAllBreakpoints())) { TraceBreakpoint brk = Unique.assertOne(lb.getTraceBreakpoints(tb.trace)); - lb.delete(); + waitOn(lb.delete()); handleDeleteBreakpointInvocation(brk); } waitForPass(() -> assertEquals(0, breakpointService.getAllBreakpoints().size())); @@ -515,7 +515,7 @@ public abstract class AbstractDebuggerBreakpointMarkerPluginTest addMappedBreakpointOpenAndWait(); // wasteful, but whatever for (LogicalBreakpoint lb : List.copyOf(breakpointService.getAllBreakpoints())) { TraceBreakpoint brk = Unique.assertOne(lb.getTraceBreakpoints(tb.trace)); - lb.delete(); + waitOn(lb.delete()); handleDeleteBreakpointInvocation(brk); } waitForPass(() -> assertEquals(0, breakpointService.getAllBreakpoints().size())); diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/AbstractDebuggerLogicalBreakpointServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/AbstractDebuggerLogicalBreakpointServiceTest.java index 9a54aec1ed..c980c80a7c 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/AbstractDebuggerLogicalBreakpointServiceTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/AbstractDebuggerLogicalBreakpointServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -46,8 +46,6 @@ import ghidra.trace.model.memory.TraceMemoryFlag; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.util.Msg; import ghidra.util.SystemUtilities; -import utility.function.ExceptionalCallback; -import utility.function.ExceptionalSupplier; public abstract class AbstractDebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDebuggerIntegrationTest { @@ -102,22 +100,6 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest } } - protected U expectMappingChange(ExceptionalSupplier supplier) - throws Throwable { - mappingChangeListener.ar.set(false, null); - U result = supplier.get(); - waitOn(mappingChangeListener.ar.waitValue(true)); - return result; - } - - protected void expectMappingChange(ExceptionalCallback runnable) - throws Throwable { - expectMappingChange(() -> { - runnable.call(); - return null; - }); - } - protected DebuggerStaticMappingService mappingService; protected DebuggerLogicalBreakpointService breakpointService; @@ -134,8 +116,6 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest protected NoDuplicatesBreakpointsChangeListener changeListener = new NoDuplicatesBreakpointsChangeListener(); - protected ForTimingMappingChangeListener mappingChangeListener = - new ForTimingMappingChangeListener(); protected abstract void createTarget1() throws Throwable; @@ -169,10 +149,12 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest protected abstract TraceBreakpoint findLoc(Set locs, int index); - protected abstract void handleToggleBreakpointInvocation(TraceBreakpoint expectedBreakpoint, + protected abstract void handleToggleBreakpointInvocation(T target, + TraceBreakpoint expectedBreakpoint, boolean expectedEnabled) throws Throwable; - protected abstract void handleDeleteBreakpointInvocation(TraceBreakpoint expectedBreakpoint) + protected abstract void handleDeleteBreakpointInvocation(T target, + TraceBreakpoint expectedBreakpoint) throws Throwable; @Before @@ -182,7 +164,6 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest mappingService = tool.getService(DebuggerStaticMappingService.class); breakpointService.addChangeListener(changeListener); - mappingService.addChangeListener(mappingChangeListener); } @After @@ -269,6 +250,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest .createInitializedBlock(".text", addr(p, 0x00400000), 0x1000, (byte) 0, monitor, false); } + waitForDomainObject(p); } protected void addProgramBreakpoints(Program p) throws Throwable { @@ -280,6 +262,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest .setBookmark(addr(p, 0x00400321), LogicalBreakpoint.DISABLED_BOOKMARK_TYPE, "SW_EXECUTE;1", ""); } + waitForDomainObject(p); } protected void refetchProgramBreakpoints(Program p) throws Throwable { @@ -297,6 +280,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest p.getBookmarkManager().removeBookmark(enBm); p.getBookmarkManager().removeBookmark(disBm); } + waitForDomainObject(p); } protected void assertLogicalBreakpointForLoneAccessBreakpoint(Trace trace) { @@ -546,8 +530,8 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @@ -564,9 +548,9 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); addProgramBreakpoints(program); - waitForSwing(); assertLogicalBreakpointsForMappedBookmarks(trace); } @@ -584,12 +568,12 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); addProgramBreakpoints(program); - waitForSwing(); changeListener.assertAgreesWithService(); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForMappedBookmarks(trace); } @@ -606,12 +590,12 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); + addTextMapping(target1, text, program); addTargetSoftwareBreakpoint(target1, text); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); - }); + assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); } @Test @@ -627,14 +611,14 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); addTargetSoftwareBreakpoint(target1, text); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - }); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); changeListener.assertAgreesWithService(); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); } @@ -660,8 +644,9 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest // NOTE: Extraneous mappings-changed events can cause timing issues here. // TODO: Better testing for static mapping listener events? MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForMappedBookmarks(trace); } @@ -680,12 +665,15 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTextMapping(target1, text, program); addProgramBreakpoints(program); - waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); - expectMappingChange(() -> programManager.openProgram(program)); - waitForSwing(); + programManager.openProgram(program); + waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForMappedBookmarks(trace); } @@ -704,13 +692,17 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTextMapping(target1, text, program); addTargetSoftwareBreakpoint(target1, text); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1)); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - expectMappingChange(() -> programManager.openProgram(program)); - waitForSwing(); + programManager.openProgram(program); + waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> assertLogicalBreakpointForMappedSoftwareBreakpoint(trace)); + assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); } @Test @@ -722,12 +714,16 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest createProgramFromTrace(trace); intoProject(program); programManager.openProgram(program); - waitForSwing(); + waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); programManager.closeProgram(program, true); waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @@ -741,13 +737,17 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest createProgramFromTrace(trace); intoProject(program); programManager.openProgram(program); - waitForSwing(); + waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); traceManager.closeTrace(trace); terminateTarget(target1); waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @@ -766,13 +766,14 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); addProgramBreakpoints(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForMappedBookmarks(trace); removeProgramBreakpoints(program); - waitForSwing(); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @@ -791,13 +792,15 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); addProgramBreakpoints(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForMappedBookmarks(trace); - expectMappingChange(() -> removeTextMapping(target1, program)); - waitForSwing(); + removeTextMapping(target1, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForUnmappedBookmarks(); assertTrue(breakpointService.getBreakpoints(trace).isEmpty()); @@ -818,19 +821,19 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest programManager.openProgram(program); addProgramTextBlock(program); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); assertServiceAgreesWithOpenProgramsAndTraces(); removeTargetSoftwareBreakpoint(target1); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - // NB. The bookmark remains - LogicalBreakpoint one = Unique.assertOne(breakpointService.getAllBreakpoints()); - assertTrue(one.getTraceBreakpoints().isEmpty()); - }); + // NB. The bookmark remains + LogicalBreakpoint one = Unique.assertOne(breakpointService.getAllBreakpoints()); + assertTrue(one.getTraceBreakpoints().isEmpty()); } @Test @@ -848,14 +851,16 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest programManager.openProgram(program); addProgramTextBlock(program); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); assertServiceAgreesWithOpenProgramsAndTraces(); - expectMappingChange(() -> removeTextMapping(target1, program)); - waitForSwing(); + removeTextMapping(target1, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // NB. Bookmark remains assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 2); @@ -881,23 +886,19 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addTextMapping(target1, text1, program); addTextMapping(target3, text3, program); - waitForSwing(); - waitForPass(() -> { - assertEquals(2, - mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); - }); - waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + + assertEquals(2, mappingService + .getOpenMappedLocations(new ProgramLocation(program, addr(program, 0x00400123))) + .size()); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text1); addTargetSoftwareBreakpoint(target3, text3); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - }); + assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); } @Test @@ -920,26 +921,24 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addTextMapping(target1, text1, program); addTextMapping(target3, text3, program); - waitForSwing(); - waitForPass(() -> { - assertEquals(2, - mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); - }); - waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + + assertEquals(2, mappingService + .getOpenMappedLocations(new ProgramLocation(program, addr(program, 0x00400123))) + .size()); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text1); addTargetSoftwareBreakpoint(target3, text3); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - }); + assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - expectMappingChange(() -> programManager.closeProgram(program, true)); + programManager.closeProgram(program, true); waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointForLoneSoftwareBreakpoint(trace1, 0x55550123, 2); assertLogicalBreakpointForLoneSoftwareBreakpoint(trace3, 0x55551123, 2); @@ -965,38 +964,34 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addTextMapping(target1, text1, program); addTextMapping(target3, text3, program); - waitForSwing(); - waitForPass(() -> { - assertEquals(2, - mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); - }); - waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + + assertEquals(2, mappingService + .getOpenMappedLocations(new ProgramLocation(program, addr(program, 0x00400123))) + .size()); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text1); addTargetSoftwareBreakpoint(target3, text3); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - }); + assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - expectMappingChange(() -> programManager.closeProgram(program, true)); + programManager.closeProgram(program, true); waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace1, 0x55550123, 2); - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace3, 0x55551123, 2); - }); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace1, 0x55550123, 2); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace3, 0x55551123, 2); - expectMappingChange(() -> programManager.openProgram(program)); - waitForSwing(); + programManager.openProgram(program); + waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - }); + assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); } @Test @@ -1019,36 +1014,34 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addTextMapping(target1, text1, program); addTextMapping(target3, text3, program); - waitForSwing(); - waitForPass(() -> { - assertEquals(2, - mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); - }); - waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + + assertEquals(2, mappingService + .getOpenMappedLocations(new ProgramLocation(program, addr(program, 0x00400123))) + .size()); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text1); addTargetSoftwareBreakpoint(target3, text3); - waitForSwing(); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - }); + assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); waitForLock(getTrace(target3)); - expectMappingChange(() -> { - // If I don't close the trace here, the test will fail. - terminateTarget(target3); - // NB. Auto-close on stop is the default - //traceManager.closeTrace(trace3); - }); + /** + * NB. Relying on terminate to auto-close is causing some timing issue I don't yet + * understand. So, I close it in the test code. + */ + terminateTarget(target3); + traceManager.closeTrace(trace3); + waitForPass(() -> !traceManager.getOpenTraces().contains(trace3)); waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // NB. Auto-close is possibly delayed because of auto-save - waitForPass(() -> assertLogicalBreakpointForMappedBookmarkAnd1TraceBreakpoint(trace1)); + assertLogicalBreakpointForMappedBookmarkAnd1TraceBreakpoint(trace1); } @Test // Mappings are not write-behind cached @@ -1064,10 +1057,10 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); addTargetSoftwareBreakpoint(target1, text); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - }); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); /** * NB. The target could still be mid transaction. If we open this transaction too soon, then * the breakpoint gets aborted as well. @@ -1077,19 +1070,21 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest changeListener.assertAgreesWithService(); try (Transaction tx = trace.openTransaction("Will abort")) { - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // Sanity assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); - expectMappingChange(() -> tx.abort()); + tx.abort(); } + waitForDomainObject(trace); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - // NB. The bookmark is left over, so total increases - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 2); - }); + // NB. The bookmark is left over, so total increases + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 2); } // @Test // Not gonna with write-behind cache @@ -1108,15 +1103,18 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest try (Transaction tx = trace.openTransaction("Will abort")) { addTargetSoftwareBreakpoint(target1, text); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // Sanity assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); - expectMappingChange(() -> tx.abort()); + tx.abort(); } - waitForDomainObject(trace); // Duplicative, but for form's sake.... + waitForDomainObject(trace); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // Left over, because it was bookmarked automatically in program // Still, there should be no trace breakpoint in it @@ -1136,18 +1134,19 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); try (Transaction tx = program.openTransaction("Will abort")) { addProgramBreakpoints(program); - waitForDomainObject(program); // Sanity assertLogicalBreakpointsForMappedBookmarks(trace); tx.abort(); } waitForDomainObject(program); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @@ -1167,18 +1166,16 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest try (Transaction tx = trace.openTransaction("Will undo")) { addTargetSoftwareBreakpoint(target1, text); - expectMappingChange(() -> addTextMapping(target1, text, program)); + addTextMapping(target1, text, program); } - waitForDomainObject(trace); - + waitForDomainObject(program); waitOn(mappingService.changesSettled()); waitOn(breakpointService.changesSettled()); // Sanity assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); - expectMappingChange(() -> undo(trace)); - + undo(trace); waitOn(mappingService.changesSettled()); waitOn(breakpointService.changesSettled()); @@ -1186,10 +1183,12 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest LogicalBreakpoint one = Unique.assertOne(breakpointService.getAllBreakpoints()); assertTrue(one.getTraceBreakpoints().isEmpty()); - expectMappingChange(() -> redo(trace)); + redo(trace); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // Mapping, breakpoint may be processed in whatever order - waitForPass(() -> assertLogicalBreakpointForMappedSoftwareBreakpoint(trace)); + assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); } @Test @@ -1204,22 +1203,27 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); try (Transaction tx = program.openTransaction("Will undo")) { addProgramBreakpoints(program); } waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // Sanity assertLogicalBreakpointsForMappedBookmarks(trace); undo(program); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); redo(program); + waitOn(breakpointService.changesSettled()); refetchProgramBreakpoints(program); assertLogicalBreakpointsForMappedBookmarks(trace); @@ -1234,18 +1238,20 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTargetSoftwareBreakpoint(target1, text); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - }); + waitOn(breakpointService.changesSettled()); + + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); CompletableFuture disable = lb.disable(); - handleToggleBreakpointInvocation(Unique.assertOne(lb.getTraceBreakpoints(trace)), false); + handleToggleBreakpointInvocation(target1, Unique.assertOne(lb.getTraceBreakpoints(trace)), + false); waitOn(disable); - waitForPass(() -> { - assertEquals(State.INCONSISTENT_DISABLED, lb.computeState()); - }); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); + + assertEquals(State.INCONSISTENT_DISABLED, lb.computeState()); // Simulate a step, which should also cause snap advance in target long oldSnap = getSnap(target1); @@ -1256,11 +1262,13 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest }); CompletableFuture enable = lb.enable(); - handleToggleBreakpointInvocation(Unique.assertOne(lb.getTraceBreakpoints(trace)), true); + handleToggleBreakpointInvocation(target1, Unique.assertOne(lb.getTraceBreakpoints(trace)), + true); waitOn(enable); - waitForPass(() -> { - assertEquals(State.INCONSISTENT_ENABLED, lb.computeState()); - }); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); + + assertEquals(State.INCONSISTENT_ENABLED, lb.computeState()); } @Test @@ -1270,22 +1278,20 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest traceManager.openTrace(trace); MR text = addTargetTextRegion(target1); - addTargetSoftwareBreakpoint(target1, text); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - }); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); CompletableFuture delete = lb.delete(); - handleDeleteBreakpointInvocation(Unique.assertOne(lb.getTraceBreakpoints(trace))); + handleDeleteBreakpointInvocation(target1, Unique.assertOne(lb.getTraceBreakpoints(trace))); waitOn(delete); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertTrue(breakpointService.getAllBreakpoints().isEmpty()); - }); + assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @Test @@ -1295,11 +1301,10 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest traceManager.openTrace(trace); MR text = addTargetTextRegion(target1); - addTargetSoftwareBreakpoint(target1, text); - waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1)); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); @@ -1307,12 +1312,12 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest simulateTargetStep(target1); CompletableFuture delete = lb.delete(); - handleDeleteBreakpointInvocation(Unique.assertOne(lb.getTraceBreakpoints(trace))); + handleDeleteBreakpointInvocation(target1, Unique.assertOne(lb.getTraceBreakpoints(trace))); waitOn(delete); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertEquals(0, breakpointService.getAllBreakpoints().size()); - }); + assertEquals(0, breakpointService.getAllBreakpoints().size()); } @Test @@ -1324,14 +1329,14 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTargetSoftwareBreakpoint(target1, text); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - }); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); // NOTE: Still recording in the background traceManager.closeTraceNoConfirm(trace); waitForSwing(); + waitOn(breakpointService.changesSettled()); assertEquals(0, breakpointService.getAllBreakpoints().size()); } @@ -1350,39 +1355,36 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTextMapping(target1, text, program); - waitForSwing(); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text); addTargetSoftwareBreakpoint(target1, text); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertEquals(2, breakpointService.getAllBreakpoints().size()); + assertEquals(2, breakpointService.getAllBreakpoints().size()); - LogicalBreakpoint lb = Unique.assertOne( - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); - assertEquals(program, lb.getProgram()); - assertEquals(Set.of(trace), lb.getMappedTraces()); + LogicalBreakpoint lb = Unique.assertOne( + breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); + assertEquals(program, lb.getProgram()); + assertEquals(Set.of(trace), lb.getMappedTraces()); - assertEquals(2, lb.getTraceBreakpoints().size()); - }); + assertEquals(2, lb.getTraceBreakpoints().size()); - LogicalBreakpoint lb = Unique - .assertOne(breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); Set locs = lb.getTraceBreakpoints(); TraceBreakpoint bpt0 = findLoc(locs, 0); TraceBreakpoint bpt1 = findLoc(locs, 1); CompletableFuture disable = breakpointService.disableLocs(Set.of(bpt0)); - handleToggleBreakpointInvocation(bpt0, false); + handleToggleBreakpointInvocation(target1, bpt0, false); waitOn(disable); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertEquals(State.INCONSISTENT_ENABLED, lb.computeState()); - assertEquals(State.INCONSISTENT_MIXED, lb.computeStateForTrace(trace)); - assertEquals(State.INCONSISTENT_DISABLED, lb.computeStateForLocation(bpt0)); - assertEquals(State.ENABLED, lb.computeStateForLocation(bpt1)); - }); + assertEquals(State.INCONSISTENT_ENABLED, lb.computeState()); + assertEquals(State.INCONSISTENT_MIXED, lb.computeStateForTrace(trace)); + assertEquals(State.INCONSISTENT_DISABLED, lb.computeStateForLocation(bpt0)); + assertEquals(State.ENABLED, lb.computeStateForLocation(bpt1)); } @Test @@ -1399,29 +1401,26 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTextMapping(target1, text, program); - waitForSwing(); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text); addTargetAccessBreakpoint(target1, text); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertEquals(3, breakpointService.getAllBreakpoints().size()); + assertEquals(3, breakpointService.getAllBreakpoints().size()); - Set lbs = - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)); - assertEquals(2, lbs.size()); - lbs.stream() - .filter(l -> l.getKinds().contains(TraceBreakpointKind.SW_EXECUTE)) - .findAny() - .orElseThrow(); - lbs.stream() - .filter(l -> l.getKinds().contains(TraceBreakpointKind.READ)) - .findAny() - .orElseThrow(); - }); Set lbs = breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)); + assertEquals(2, lbs.size()); + lbs.stream() + .filter(l -> l.getKinds().contains(TraceBreakpointKind.SW_EXECUTE)) + .findAny() + .orElseThrow(); + lbs.stream() + .filter(l -> l.getKinds().contains(TraceBreakpointKind.READ)) + .findAny() + .orElseThrow(); LogicalBreakpoint lbEx = lbs.stream() .filter(l -> l.getKinds().contains(TraceBreakpointKind.SW_EXECUTE)) .findAny() @@ -1431,12 +1430,14 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest .findAny() .orElseThrow(); CompletableFuture disable = lbEx.disable(); - handleToggleBreakpointInvocation(Unique.assertOne(lbEx.getTraceBreakpoints(trace)), false); + handleToggleBreakpointInvocation(target1, Unique.assertOne(lbEx.getTraceBreakpoints(trace)), + false); waitOn(disable); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); // TODO: This is more a test for the marker plugin, no? - waitForPass( - () -> assertEquals(State.MIXED, lbEx.computeState().sameAdddress(lbRw.computeState()))); + assertEquals(State.MIXED, lbEx.computeState().sameAdddress(lbRw.computeState())); } protected void addTextMappingDead(Program p, ToyDBTraceBuilder tb) throws Throwable { @@ -1451,6 +1452,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest textRegion.getMinAddress()), new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false); } + waitForDomainObject(tb.trace); } protected void addEnabledProgramBreakpointWithSleigh(Program p) { @@ -1459,6 +1461,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest .setBookmark(addr(p, 0x00400123), LogicalBreakpoint.ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1", "{sleigh: 'r0=0xbeef;'}"); } + waitForDomainObject(p); } @Test @@ -1471,16 +1474,18 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest programManager.openProgram(program); addTextMappingDead(program, tb); - waitForSwing(); addEnabledProgramBreakpointWithSleigh(program); - LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne( - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)))); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + LogicalBreakpoint lb = Unique.assertAtMostOne( + breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); assertEquals("r0=0xbeef;", lb.getEmuSleigh()); waitOn(lb.enable()); - waitForSwing(); + waitForDomainObject(program); + waitOn(breakpointService.changesSettled()); TraceBreakpoint bpt = Unique.assertOne( tb.trace.getBreakpointManager().getBreakpointsAt(0, tb.addr(0x55550123))); @@ -1497,21 +1502,22 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest programManager.openProgram(program); addEnabledProgramBreakpointWithSleigh(program); - LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne( - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)))); + waitOn(breakpointService.changesSettled()); + LogicalBreakpoint lb = Unique.assertAtMostOne( + breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); assertEquals("r0=0xbeef;", lb.getEmuSleigh()); addTextMappingDead(program, tb); - lb = waitForPass(() -> { - LogicalBreakpoint newLb = Unique.assertOne( - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); - assertTrue(newLb.getMappedTraces().contains(tb.trace)); - return newLb; - }); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + lb = Unique.assertOne( + breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); + assertTrue(lb.getMappedTraces().contains(tb.trace)); waitOn(lb.enable()); - waitForSwing(); + waitForDomainObject(program); + waitOn(breakpointService.changesSettled()); TraceBreakpoint bpt = Unique.assertOne( tb.trace.getBreakpointManager().getBreakpointsAt(0, tb.addr(0x55550123))); @@ -1541,21 +1547,25 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest false /* emuEnabled defaults to true */, ""); bpt.setEmuSleigh("r0=0xbeef;"); } - LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne( - breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550123)))); + waitForDomainObject(tb.trace); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + LogicalBreakpoint lb = Unique.assertAtMostOne( + breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550123))); assertEquals("r0=0xbeef;", lb.getEmuSleigh()); addTextMappingDead(program, tb); - lb = waitForPass(() -> { - LogicalBreakpoint newLb = Unique.assertOne( - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); - assertTrue(newLb.getMappedTraces().contains(tb.trace)); - return newLb; - }); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + + lb = Unique + .assertOne(breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); + assertTrue(lb.getMappedTraces().contains(tb.trace)); lb.enableForProgram(); - waitForSwing(); + waitForDomainObject(tb.trace); + waitOn(breakpointService.changesSettled()); assertEquals("{\"sleigh\":\"r0\\u003d0xbeef;\"}", lb.getProgramBookmark().getComment()); } @@ -1566,6 +1576,8 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest * *

* With the addition of the write-behind cache, this test is no longer sane. + * + * @throws Throwable who doesn't? */ // @Test public void testAbortAddBreakpointSetSleigh() throws Throwable { @@ -1595,6 +1607,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest tid.abort(); } waitForDomainObject(tb.trace); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java index 981859ef71..6684bb0235 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -17,6 +17,8 @@ package ghidra.app.plugin.core.debug.service.breakpoint; import java.io.IOException; +import org.junit.Ignore; + import db.Transaction; import ghidra.dbg.target.schema.SchemaContext; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; @@ -24,6 +26,7 @@ import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.trace.model.Trace; import ghidra.trace.model.target.TraceObjectKeyPath; +@Ignore("Deprecated") public class DebuggerObjectRecorderLogicalBreakpointServiceTest extends DebuggerRecorderLogicalBreakpointServiceTest { diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java index 6685d6efff..a3e2820e5a 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -19,9 +19,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.util.Set; -import java.util.concurrent.TimeUnit; import org.junit.Before; +import org.junit.Ignore; import db.Transaction; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; @@ -39,6 +39,7 @@ import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.modules.TraceStaticMapping; +@Ignore("Deprecated") public class DebuggerRecorderLogicalBreakpointServiceTest extends AbstractDebuggerLogicalBreakpointServiceTest { @@ -130,6 +131,7 @@ public class DebuggerRecorderLogicalBreakpointServiceTest extends new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false); } + waitForDomainObject(t); } @Override @@ -140,6 +142,7 @@ public class DebuggerRecorderLogicalBreakpointServiceTest extends t.getStaticMappingManager().findContaining(addr(t, 0x55550000), r.getSnap()); mapping.delete(); } + waitForDomainObject(t); } @Override @@ -168,7 +171,7 @@ public class DebuggerRecorderLogicalBreakpointServiceTest extends @Override protected void removeTargetSoftwareBreakpoint(TraceRecorder r) throws Throwable { TargetBreakpointSpecContainer cont = getBreakpointContainer(r); - cont.fetchElements().thenAccept(elements -> { + waitOn(cont.fetchElements().thenCompose(elements -> { for (TargetObject obj : elements.values()) { if (!(obj instanceof TargetBreakpointSpec) || !(obj instanceof TargetDeletable)) { @@ -179,11 +182,12 @@ public class DebuggerRecorderLogicalBreakpointServiceTest extends continue; } TargetDeletable del = (TargetDeletable) obj; - del.delete(); - return; + return del.delete(); } fail("No deletable software breakpoint spec found"); - }).get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + throw new AssertionError(); + })); + waitRecorder(r); } @Override @@ -200,14 +204,16 @@ public class DebuggerRecorderLogicalBreakpointServiceTest extends } @Override - protected void handleToggleBreakpointInvocation(TraceBreakpoint expectedBreakpoint, - boolean expectedEnabled) throws Throwable { + protected void handleToggleBreakpointInvocation(TraceRecorder target, + TraceBreakpoint expectedBreakpoint, boolean expectedEnabled) throws Throwable { // Logic is in the Test model + waitRecorder(target); } @Override - protected void handleDeleteBreakpointInvocation(TraceBreakpoint expectedBreakpoint) - throws Throwable { + protected void handleDeleteBreakpointInvocation(TraceRecorder target, + TraceBreakpoint expectedBreakpoint) throws Throwable { // Logic is in the Test model + waitRecorder(target); } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRmiLogicalBreakpointServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRmiLogicalBreakpointServiceTest.java index 0ec8b8c5b6..56ec694d37 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRmiLogicalBreakpointServiceTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRmiLogicalBreakpointServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -53,6 +53,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION); tb.createObjectsProcessAndThreads(); } + waitForDomainObject(tb.trace); target1 = rmiCx.publishTarget(tool, tb.trace); } @@ -64,6 +65,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends tb3.trace.getObjectManager().createRootObject(SCHEMA_SESSION); tb3.createObjectsProcessAndThreads(); } + waitForDomainObject(tb3.trace); target3 = rmiCx.publishTarget(tool, tb3.trace); } @@ -79,6 +81,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends try (Transaction tx = trace.openTransaction("Simulate step")) { snapshot = trace.getTimeManager().createSnapshot("Simulated step"); } + waitForDomainObject(trace); rmiCx.setLastSnapshot(trace, snapshot.getKey()); } @@ -96,12 +99,15 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends protected TraceObjectMemoryRegion addTargetTextRegion(TraceRmiTarget target, long offset) throws Throwable { Trace trace = target.getTrace(); + TraceObjectMemoryRegion result; try (Transaction tx = trace.openTransaction("Add .text")) { - return Objects.requireNonNull( + result = Objects.requireNonNull( addMemoryRegion(trace.getObjectManager(), Lifespan.nowOn(target.getSnap()), tb.range(offset, offset + 0x0fff), "bin:.text", "rx") .queryInterface(TraceObjectMemoryRegion.class)); } + waitForDomainObject(trace); + return result; } @Override @@ -113,12 +119,15 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends protected TraceObjectMemoryRegion addTargetDataRegion(TraceRmiTarget target) throws Throwable { Trace trace = target.getTrace(); long offset = 0x56550000; + TraceObjectMemoryRegion result; try (Transaction tx = trace.openTransaction("Add .data")) { - return Objects.requireNonNull( + result = Objects.requireNonNull( addMemoryRegion(trace.getObjectManager(), Lifespan.nowOn(target.getSnap()), tb.range(offset, offset + 0x0fff), "bin:.data", "rw") .queryInterface(TraceObjectMemoryRegion.class)); } + waitForDomainObject(trace); + return result; } @Override @@ -131,6 +140,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends new ProgramLocation(program, addr(program, 0x00400000)), 0x1000, false); } + waitForDomainObject(trace); } @Override @@ -141,6 +151,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends t.getStaticMappingManager().findContaining(addr(t, 0x55550000), target.getSnap()); mapping.delete(); } + waitForDomainObject(t); } @Override @@ -188,6 +199,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends spec.getObject().remove(nowOn); } } + waitForDomainObject(trace); } @Override @@ -204,8 +216,8 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends } @Override - protected void handleToggleBreakpointInvocation(TraceBreakpoint expectedBreakpoint, - boolean expectedEn) throws Throwable { + protected void handleToggleBreakpointInvocation(TraceRmiTarget target, + TraceBreakpoint expectedBreakpoint, boolean expectedEn) throws Throwable { if (!(expectedBreakpoint instanceof TraceObjectBreakpointLocation loc)) { throw new AssertionError("Unexpected trace breakpoint type: " + expectedBreakpoint); } @@ -213,6 +225,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends try (Transaction tx = tb.startTransaction()) { loc.setEnabled(Lifespan.nowOn(0), expectedEn); } + waitForDomainObject(tb.trace); rmiMethodToggleBreak.result(null); assertEquals(Map.ofEntries( Map.entry("breakpoint", loc.getSpecification().getObject()), @@ -220,8 +233,8 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends } @Override - protected void handleDeleteBreakpointInvocation(TraceBreakpoint expectedBreakpoint) - throws Throwable { + protected void handleDeleteBreakpointInvocation(TraceRmiTarget target, + TraceBreakpoint expectedBreakpoint) throws Throwable { if (!(expectedBreakpoint instanceof TraceObjectBreakpointLocation loc)) { throw new AssertionError("Unexpected trace breakpoint type: " + expectedBreakpoint); } @@ -229,6 +242,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends try (Transaction tx = tb.startTransaction()) { loc.getObject().remove(Lifespan.nowOn(0)); } + waitForDomainObject(tb.trace); rmiMethodDeleteBreak.result(null); assertEquals(Map.ofEntries( Map.entry("breakpoint", loc.getSpecification().getObject())), args); From 9a5e0f4f36abc4abb8e9d169d7c5121105b688e4 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:59:03 -0400 Subject: [PATCH 12/53] GP-0: Increment Debugger versions to 11.2. Req Pybag>=2.2.12 --- .../src/main/py/pyproject.toml | 6 ++-- .../src/main/py/pyproject.toml | 4 +-- .../src/main/py/pyproject.toml | 4 +-- .../service/tracermi/TraceRmiHandler.java | 6 ++-- .../src/main/py/pyproject.toml | 2 +- .../src/main/py/src/ghidratrace/client.py | 28 +++++++++---------- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/pyproject.toml b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/pyproject.toml index 60a9a995eb..61f8ad3197 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/pyproject.toml +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ghidradbg" -version = "11.1.2" +version = "11.2" authors = [ { name="Ghidra Development Team" }, ] @@ -17,8 +17,8 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ - "ghidratrace==11.1.2", - "pybag>=2.2.10" + "ghidratrace==11.2", + "pybag>=2.2.12" ] [project.urls] diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/pyproject.toml b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/pyproject.toml index 0926c8b6ba..3aeb175fb6 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/pyproject.toml +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ghidragdb" -version = "11.1.2" +version = "11.2" authors = [ { name="Ghidra Development Team" }, ] @@ -17,7 +17,7 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ - "ghidratrace==11.1.2", + "ghidratrace==11.2", ] [project.urls] diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/pyproject.toml b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/pyproject.toml index 88592a89e2..83b0b4ef24 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/pyproject.toml +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ghidralldb" -version = "11.1.2" +version = "11.2" authors = [ { name="Ghidra Development Team" }, ] @@ -17,7 +17,7 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ - "ghidratrace==11.1.2", + "ghidratrace==11.2", ] [project.urls] diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java index d1338420f5..6750bd9f1a 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java @@ -4,9 +4,9 @@ * 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. @@ -69,7 +69,7 @@ import ghidra.util.exception.CancelledException; import ghidra.util.exception.DuplicateFileException; public class TraceRmiHandler implements TraceRmiConnection { - public static final String VERSION = "11.1"; + public static final String VERSION = "11.2"; protected static class VersionMismatchError extends TraceRmiError { public VersionMismatchError(String remote) { diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/py/pyproject.toml b/Ghidra/Debug/Debugger-rmi-trace/src/main/py/pyproject.toml index 52df77e438..3731a44e99 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/py/pyproject.toml +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ghidratrace" -version = "11.1.2" +version = "11.2" authors = [ { name="Ghidra Development Team" }, ] diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py b/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py index 2d6a42f105..6f8539bdc9 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py @@ -1,17 +1,17 @@ ## ### -# 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. +# 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. ## from collections import deque, namedtuple from concurrent.futures import Future @@ -34,7 +34,7 @@ from .util import send_delimited, recv_delimited # Other places to change: # * every pyproject.toml file (incl. deps) # * TraceRmiHandler.VERSION -VERSION = '11.1' +VERSION = '11.2' class RemoteResult(Future): From 1cb3796bc28efcb0a6842084fbc2ac125ca3d6c6 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:31:27 -0400 Subject: [PATCH 13/53] GP-4875 - Updated how providers track user changes to the title --- .../src/main/java/docking/ComponentNode.java | 14 ++-- .../main/java/docking/ComponentProvider.java | 65 ++++++++++++++++++- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java index 6f54bc3046..89b415fb3c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java @@ -640,13 +640,13 @@ class ComponentNode extends Node { return; // cancelled } - // If the user changes the name, then we want to replace all of the - // parts of the title with that name. We skip the subtitle, as that - // doesn't make sense in that case. - provider.setTitle(newName); // title on window - provider.setSubTitle(""); // part after the title - provider.setTabText(newName); // text on the tab - placeholder.update(); + // If the user changes the name, then we want to replace all of the parts of the + // title with that name. We do not supply a custom subtitle, as that doesn't make + // sense in this case, but we clear it so the user's title is the only thing + // visible. This means that providers can still update the subtitle later. + provider.setCustomTitle(newName); // title on window + provider.setSubTitle(""); // part after the title + provider.setCustomTabText(newName); // text on the tab } } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java index 90d12575d6..2fe3ef2d13 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java @@ -4,9 +4,9 @@ * 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. @@ -88,9 +88,13 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext protected Tool dockingTool; private String name; private final String owner; + private String title; private String subTitle; private String tabText; + private String customTitle; + private String customTabText; + private String customSubTitle; private Set actionSet = new LinkedHashSet<>(); @@ -549,6 +553,10 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext * @param title the title string to use. */ public void setTitle(String title) { + if (customTitle != null) { + return; + } + this.title = title; if (isInTool()) { dockingTool.updateTitle(this); @@ -561,6 +569,10 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext * @param subTitle the sub-title string to use. */ public void setSubTitle(String subTitle) { + if (customSubTitle != null) { + return; + } + this.subTitle = subTitle; if (isInTool()) { dockingTool.updateTitle(this); @@ -572,7 +584,56 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext * @param tabText the tab text. */ public void setTabText(String tabText) { + if (customTabText != null) { + return; + } + this.tabText = tabText; + if (isInTool()) { + dockingTool.updateTitle(this); + } + } + + /** + * The new custom title. Setting the title here prevents future calls to + * {@link #setTitle(String)} from having any effect. This is done to preserve the custom + * title. + * @param title the title + */ + public void setCustomTitle(String title) { + this.customTitle = title; + this.title = title; + if (isInTool()) { + dockingTool.updateTitle(this); + } + } + + /** + * The new custom tab text. Setting the text here prevents future calls to + * {@link #setTabText(String)} from having any effect. This is done to preserve the custom + * tab text. + * @param tabText the text + */ + public void setCustomTabText(String tabText) { + this.customTabText = tabText; + this.tabText = tabText; + if (isInTool()) { + dockingTool.updateTitle(this); + } + } + + /** + * The new custom sub-title. Setting the sub-title here prevents future calls to + * {@link #setSubTitle(String)} from having any effect. This is done to preserve the custom + * sub-title. + * @param subTitle the sub-title + */ + public void setCustomSubTitle(String subTitle) { + this.customSubTitle = subTitle; + this.subTitle = subTitle; + if (isInTool()) { + dockingTool.updateTitle(this); + } } /** From 08004a3376bb5bba724821e474d0b5dd74a59e7b Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 27 Aug 2024 09:31:53 -0400 Subject: [PATCH 14/53] Test fixes --- .../java/ghidra/framework/options/OptionsTest.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Ghidra/Features/Base/src/test/java/ghidra/framework/options/OptionsTest.java b/Ghidra/Features/Base/src/test/java/ghidra/framework/options/OptionsTest.java index 276adad85d..75b9f4eb7a 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/framework/options/OptionsTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/framework/options/OptionsTest.java @@ -4,9 +4,9 @@ * 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. @@ -665,7 +665,7 @@ public class OptionsTest extends AbstractGuiTest { int value; @SuppressWarnings("unused") - MyCustomOption() { + public MyCustomOption() { // used by reflection } @@ -695,6 +695,11 @@ public class OptionsTest extends AbstractGuiTest { public int hashCode() { return 0; } + + @Override + public String toString() { + return "MyCustomOption[value=%s]".formatted(value); + } } static class MyOptionsEditor implements OptionsEditor { From 67a2f8880326aa0cba411f8c837e080b743642b7 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 27 Aug 2024 11:11:53 -0400 Subject: [PATCH 15/53] GP-3490: GhidraDev run config fixes --- .../utils/GhidraLaunchUtils.java | 21 +++++++++++++------ .../utils/GhidraModuleUtils.java | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraLaunchUtils.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraLaunchUtils.java index e70f7c7a60..73791e259e 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraLaunchUtils.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraLaunchUtils.java @@ -29,7 +29,7 @@ import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.launching.*; -import ghidra.GhidraLauncher; +import ghidra.Ghidra; /** * Utility methods for working with Ghidra launchers in Eclipse. @@ -134,10 +134,7 @@ public class GhidraLaunchUtils { /** * Sets the main type name attribute in the provided working copy. For Ghidra projects, this - * should be {@link GhidraLauncher}. - *

- * TODO: {@link GhidraLauncher#main(String[])} is deprecated. Fix in future version of - * GhidraDev when we are ready to break backwards compatibility with Ghidra. + * should be {@link Ghidra}. * * @param wc The launch configuration working copy to modify. * @return The modified working copy. @@ -145,7 +142,7 @@ public class GhidraLaunchUtils { public static ILaunchConfigurationWorkingCopy setMainTypeName( ILaunchConfigurationWorkingCopy wc) { wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, - GhidraLauncher.class.getName()); + Ghidra.class.getName()); return wc; } @@ -216,6 +213,18 @@ public class GhidraLaunchUtils { List newList = new ArrayList<>(); IJavaProject javaProject = JavaRuntime.getJavaProject(wc); if (javaProject != null) { + + // Add current project (might need to add dependent projects later) + newList.add(JavaRuntime.newProjectRuntimeClasspathEntry(javaProject).getMemento()); + + // Add JDK + newList.add(JavaRuntime + .newRuntimeContainerClasspathEntry( + JavaRuntime.newJREContainerPath(JavaRuntime.getVMInstall(javaProject)), + IRuntimeClasspathEntry.STANDARD_CLASSES) + .getMemento()); + + // Add Ghidra jar source for (IClasspathEntry entry : javaProject.getRawClasspath()) { IPath sourcePath = entry.getSourceAttachmentPath(); if (sourcePath != null) { diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java index 5a83376d9c..f4d93a2297 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java @@ -110,7 +110,7 @@ public class GhidraModuleUtils { sourceFolderInfos.add(new SourceFolderInfo(project.getFolder("ghidra_scripts"), project.getFolder("bin/scripts"))); for (SourceFolderInfo sourceFolderInfo : sourceFolderInfos) { - GhidraProjectUtils.createFolder(sourceFolderInfo.outputFolder(), monitor); + GhidraProjectUtils.createFolder(sourceFolderInfo.sourceFolder(), monitor); } // Put the source directories in the project's classpath From 62b96db10bfd415a2cd76ec80739d57213d86da1 Mon Sep 17 00:00:00 2001 From: foralost Date: Sun, 26 Feb 2023 22:28:51 +0100 Subject: [PATCH 16/53] getValueAsInt - Checking if parsed long is within Java Integer.INT range getValueAsInt - Using the int copy of NumerUtilities.parseLong AskDialog: Organize commits --- .../java/ghidra/app/script/AskDialog.java | 7 ++- .../java/ghidra/util/NumericUtilities.java | 48 ++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java index a82350b244..b0a8f05893 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java @@ -246,7 +246,12 @@ public class AskDialog extends DialogComponentProvider { protected Integer getValueAsInt() { String text = getValueAsString(); - return text != null ? Integer.decode(text) : null; + + if (text == null) { + return null; + } + + return NumericUtilities.parseInt(text); } protected Long getValueAsLong() { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java index de9f078886..7326ef75c2 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java @@ -98,7 +98,53 @@ public final class NumericUtilities { } /** - * Parses the given string as a numeric value, detecting whether or not it begins with a hex + * Parses the given string as a numeric value, detecting whether or not it begins with a Hex + * prefix, and if not, parses as a int value. + */ + public static int parseInt(String numStr) { + String origStr = numStr; + int value = 0; + int sign = 1; + + numStr = (numStr == null ? "" : numStr.trim()); + if (numStr.length() == 0) { + return value; + } + if (numStr.startsWith("-")) { + sign = -1; + numStr = numStr.substring(1); + } + int radix = 10; + + if (numStr.startsWith(HEX_PREFIX_x) || numStr.startsWith(HEX_PREFIX_X)) { + if (numStr.length() > 10) { + throw new NumberFormatException(numStr + " has too many digits."); + } + numStr = numStr.substring(2); + radix = 16; + } + if (numStr.length() == 0) { + return 0; + } + try { + BigInteger bi = new BigInteger(numStr, radix); + return bi.intValue() * sign; + } + catch (NumberFormatException e) { + // This is a little hacky, but the message should be complete and report about the + // original string + NumberFormatException e2 = + new NumberFormatException("Cannot parse int from " + origStr); + e2.setStackTrace(e.getStackTrace()); + throw e2; + } + catch (ArithmeticException e) { + throw new NumberFormatException(origStr + " is too big."); + } + } + + /** + * Parses the given string as a numeric value, detecting whether or not it begins with a Hex * prefix, and if not, parses as a long int value. * @param s the string to parse * @return the long value From 1e68becd7587b18ea10da6ab5016a9454a9e509e Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 27 Aug 2024 13:19:39 -0400 Subject: [PATCH 17/53] GP-4874: Cleanup --- .../java/ghidra/app/script/AskDialog.java | 9 +--- .../java/ghidra/util/NumericUtilities.java | 51 ++++++++++--------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java index b0a8f05893..ee24ff5d02 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/AskDialog.java @@ -246,17 +246,12 @@ public class AskDialog extends DialogComponentProvider { protected Integer getValueAsInt() { String text = getValueAsString(); - - if (text == null) { - return null; - } - - return NumericUtilities.parseInt(text); + return text != null ? NumericUtilities.parseInt(text) : null; } protected Long getValueAsLong() { String text = getValueAsString(); - return text != null ? Long.decode(text) : null; + return text != null ? NumericUtilities.parseLong(text) : null; } protected Double getValueAsDouble() { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java index 7326ef75c2..beafa27700 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java @@ -4,9 +4,9 @@ * 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. @@ -98,36 +98,39 @@ public final class NumericUtilities { } /** - * Parses the given string as a numeric value, detecting whether or not it begins with a Hex - * prefix, and if not, parses as a int value. + * Parses the given decimal/hex string as an {@code int} value. This method allows values with + * the top bit set to be implicitly parsed as negative values. + * + * @param s the string to parse + * @return the {@code int} value, or 0 if the string to parse is null or blank + * @throws NumberFormatException if the string does not represent a valid {@code int} value */ - public static int parseInt(String numStr) { - String origStr = numStr; - int value = 0; + public static int parseInt(String s) { + String origStr = s; int sign = 1; - numStr = (numStr == null ? "" : numStr.trim()); - if (numStr.length() == 0) { - return value; + s = (s == null ? "" : s.trim()); + if (s.length() == 0) { + return 0; } - if (numStr.startsWith("-")) { + if (s.startsWith("-")) { sign = -1; - numStr = numStr.substring(1); + s = s.substring(1); } int radix = 10; - if (numStr.startsWith(HEX_PREFIX_x) || numStr.startsWith(HEX_PREFIX_X)) { - if (numStr.length() > 10) { - throw new NumberFormatException(numStr + " has too many digits."); + if (s.startsWith(HEX_PREFIX_x) || s.startsWith(HEX_PREFIX_X)) { + if (s.length() > 10) { + throw new NumberFormatException(s + " has too many digits."); } - numStr = numStr.substring(2); + s = s.substring(2); radix = 16; } - if (numStr.length() == 0) { + if (s.length() == 0) { return 0; } try { - BigInteger bi = new BigInteger(numStr, radix); + BigInteger bi = new BigInteger(s, radix); return bi.intValue() * sign; } catch (NumberFormatException e) { @@ -144,20 +147,20 @@ public final class NumericUtilities { } /** - * Parses the given string as a numeric value, detecting whether or not it begins with a Hex - * prefix, and if not, parses as a long int value. + * Parses the given decimal/hex string as a {@code long} value. This method allows values with + * the top bit set to be implicitly parsed as negative values. + * * @param s the string to parse - * @return the long value - * @throws NumberFormatException if the string is blank or has too many digits + * @return the {@code long} value, or 0 if the string to parse is null or blank + * @throws NumberFormatException if the string does not represent a valid {@code long} value */ public static long parseLong(String s) { String origStr = s; - long value = 0; long sign = 1; s = (s == null ? "" : s.trim()); if (s.length() == 0) { - return value; + return 0; } if (s.startsWith("-")) { sign = -1; From e1cc67a3d2f048f3bf5d74f9235f45cf31550d81 Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Wed, 14 Aug 2024 21:09:15 +0000 Subject: [PATCH 18/53] GP-4849 Volatile attribute in tag. --- .../src/decompile/cpp/architecture.cc | 48 ++++++++++++++----- .../src/decompile/cpp/architecture.hh | 6 +-- .../Decompiler/src/decompile/cpp/transform.cc | 34 +++---------- .../Decompiler/src/decompile/cpp/transform.hh | 6 +-- .../data/languages/processor_spec.rxg | 5 ++ .../processors/sleigh/SleighLanguage.java | 35 +++++++------- .../PowerPC/data/languages/ppc_32.pspec | 8 ++-- .../data/languages/ppc_32_mpc8270.pspec | 8 ++-- .../PowerPC/data/languages/ppc_64.pspec | 8 ++-- 9 files changed, 85 insertions(+), 73 deletions(-) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc index 4cccca2ca5..841e9ec22d 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc @@ -4,9 +4,9 @@ * 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. @@ -924,23 +924,47 @@ void Architecture::decodeIncidentalCopy(Decoder &decoder) decoder.closeElement(elemId); } -/// Look for \ elements that have a \e vector_lane_size attribute. -/// Record these so that the decompiler can split large registers into appropriate lane size pieces. +/// Read \ elements to collect specific properties associated with the register storage. /// \param decoder is the stream decoder -void Architecture::decodeLaneSizes(Decoder &decoder) +void Architecture::decodeRegisterData(Decoder &decoder) { vector maskList; - LanedRegister lanedRegister; // Only allocate once uint4 elemId = decoder.openElement(ELEM_REGISTER_DATA); while(decoder.peekElement() != 0) { - if (lanedRegister.decode(decoder)) { - int4 sizeIndex = lanedRegister.getWholeSize(); - while (maskList.size() <= sizeIndex) - maskList.push_back(0); - maskList[sizeIndex] |= lanedRegister.getSizeBitMask(); + uint4 subId = decoder.openElement(ELEM_REGISTER); + bool isVolatile = false; + string laneSizes; + for(;;) { + uint4 attribId = decoder.getNextAttributeId(); + if (attribId == 0) break; + if (attribId == ATTRIB_VECTOR_LANE_SIZES) { + laneSizes = decoder.readString(); + } + else if (attribId == ATTRIB_VOLATILE) { + isVolatile = decoder.readBool(); + } } + if (!laneSizes.empty() || isVolatile) { + decoder.rewindAttributes(); + VarnodeData storage; + storage.space = (AddrSpace *)0; + storage.decodeFromAttributes(decoder); + if (!laneSizes.empty()) { + LanedRegister lanedRegister; + lanedRegister.parseSizes(storage.size,laneSizes); + int4 sizeIndex = lanedRegister.getWholeSize(); + while (maskList.size() <= sizeIndex) + maskList.push_back(0); + maskList[sizeIndex] |= lanedRegister.getSizeBitMask(); + } + if (isVolatile) { + Range range( storage.space, storage.offset, storage.offset+storage.size-1); + symboltab->setPropertyRange(Varnode::volatil,range); + } + } + decoder.closeElement(subId); } decoder.closeElement(elemId); lanerecords.clear(); @@ -1172,7 +1196,7 @@ void Architecture::parseProcessorConfig(DocumentStorage &store) else if (subId == ELEM_SEGMENTOP) userops.decodeSegmentOp(decoder,this); else if (subId == ELEM_REGISTER_DATA) { - decodeLaneSizes(decoder); + decodeRegisterData(decoder); } else if (subId == ELEM_DATA_SPACE) { uint4 elemId = decoder.openElement(); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh index 73dc41296b..ebd0e84343 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh @@ -4,9 +4,9 @@ * 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. @@ -368,7 +368,7 @@ protected: void decodeVolatile(Decoder &decoder); ///< Apply volatile region configuration void decodeReturnAddress(Decoder &decoder); ///< Apply return address configuration void decodeIncidentalCopy(Decoder &decoder); ///< Apply incidental copy configuration - void decodeLaneSizes(Decoder &decoder); ///< Apply lane size configuration + void decodeRegisterData(Decoder &decoder); ///< Read specific register properties void decodeStackPointer(Decoder &decoder); ///< Apply stack pointer configuration void decodeDeadcodeDelay(Decoder &decoder); ///< Apply dead-code delay configuration void decodeInferPtrBounds(Decoder &decoder); ///< Apply pointer inference bounds diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/transform.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/transform.cc index 64fc4ff9ae..888785af9c 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/transform.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/transform.cc @@ -4,9 +4,9 @@ * 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. @@ -294,32 +294,13 @@ void LanedRegister::LanedIterator::normalize(void) size = -1; // Indicate ending iterator } -/// Parse any vector lane sizes. -/// \param decoder is the stream decoder -/// \return \b true if the XML description provides lane sizes -bool LanedRegister::decode(Decoder &decoder) +/// Collect specific lane sizes in this object. +/// \param registerSize is the size of the laned register in bytes +/// \param laneSizes is a comma separated list of sizes + void LanedRegister::parseSizes(int4 registerSize,string laneSizes) { - uint4 elemId = decoder.openElement(ELEM_REGISTER); - string laneSizes; - for(;;) { - uint4 attribId = decoder.getNextAttributeId(); - if (attribId == 0) break; - if (attribId == ATTRIB_VECTOR_LANE_SIZES) { - laneSizes = decoder.readString(); - break; - } - } - if (laneSizes.empty()) { - decoder.closeElement(elemId); - return false; - } - decoder.rewindAttributes(); - VarnodeData storage; - storage.space = (AddrSpace *)0; - storage.decodeFromAttributes(decoder); - decoder.closeElement(elemId); - wholeSize = storage.size; + wholeSize = registerSize; sizeBitMask = 0; string::size_type pos = 0; while(pos != string::npos) { @@ -343,7 +324,6 @@ bool LanedRegister::decode(Decoder &decoder) throw LowlevelError("Bad lane size: " + value); addLaneSize(sz); } - return true; } TransformManager::~TransformManager(void) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/transform.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/transform.hh index 08462ed07c..58bdecb44c 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/transform.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/transform.hh @@ -4,9 +4,9 @@ * 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. @@ -115,7 +115,7 @@ private: public: LanedRegister(void) { wholeSize = 0; sizeBitMask = 0; } ///< Constructor for use with decode LanedRegister(int4 sz,uint4 mask) { wholeSize = sz; sizeBitMask = mask; } ///< Constructor - bool decode(Decoder &decoder); ///< Parse \ elements for lane sizes + void parseSizes(int4 registerSize,string laneSizes); ///< Parse a \e vector_lane_sizes attribute int4 getWholeSize(void) const { return wholeSize; } ///< Get the size in bytes of the whole laned register uint4 getSizeBitMask(void) const { return sizeBitMask; } ///< Get the bit mask of possible lane sizes void addLaneSize(int4 size) { sizeBitMask |= ((uint4)1 << size); } ///< Add a new \e size to the allowed list diff --git a/Ghidra/Framework/SoftwareModeling/data/languages/processor_spec.rxg b/Ghidra/Framework/SoftwareModeling/data/languages/processor_spec.rxg index b3e4f25b8b..12b9e86a1e 100644 --- a/Ghidra/Framework/SoftwareModeling/data/languages/processor_spec.rxg +++ b/Ghidra/Framework/SoftwareModeling/data/languages/processor_spec.rxg @@ -114,6 +114,11 @@ + + + + + diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java index c577072f3c..330b0a80ef 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java @@ -20,8 +20,8 @@ import static ghidra.pcode.utils.SlaFormat.*; import java.io.*; import java.math.BigInteger; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -82,7 +82,7 @@ public class SleighLanguage implements Language { */ private String segmentedspace = ""; private String segmentType = ""; - private AddressSet volatileAddresses; + private AddressSet volatileAddresses = new AddressSet(); private AddressSet volatileSymbolAddresses; private AddressSet nonVolatileSymbolAddresses; private ContextCache contextcache = null; @@ -155,9 +155,6 @@ public class SleighLanguage implements Language { } private void buildVolatileSymbolAddresses() { - if (volatileAddresses == null) { - volatileAddresses = new AddressSet(); - } if (volatileSymbolAddresses != null) { volatileAddresses.add(volatileSymbolAddresses); } @@ -378,10 +375,10 @@ public class SleighLanguage implements Language { // get existing proto and use it // if doesn't exist in map, cache info and store new proto res = instructProtoMap.computeIfAbsent(hashcode, h -> { - newProto.cacheInfo(buf, context, true); - return newProto; + newProto.cacheInfo(buf, context, true); + return newProto; }); - + if (inDelaySlot && res.hasDelaySlots()) { throw new NestedDelaySlotException(); } @@ -680,9 +677,6 @@ public class SleighLanguage implements Language { throw new SleighException("no support for volatile registers yet"); } Pair range = parseRange(next); - if (volatileAddresses == null) { - volatileAddresses = new AddressSet(); - } volatileAddresses.addRange(range.first, range.second); // skip the end tag parser.end(next); @@ -709,6 +703,7 @@ public class SleighLanguage implements Language { String registerAlias = reg.getAttribute("alias"); String groupName = reg.getAttribute("group"); boolean isHidden = SpecXmlUtils.decodeBoolean(reg.getAttribute("hidden")); + boolean isVolatile = SpecXmlUtils.decodeBoolean(reg.getAttribute("volatile")); if (registerRename != null) { if (!registerBuilder.renameRegister(registerName, registerRename)) { throw new SleighException( @@ -732,6 +727,11 @@ public class SleighLanguage implements Language { if (isHidden) { registerBuilder.setFlag(registerName, Register.TYPE_HIDDEN); } + if (isVolatile) { + Address first = register.getAddress(); + Address second = first.add(register.getNumBytes() - 1); + volatileAddresses.addRange(first, second); + } String sizes = reg.getAttribute("vector_lane_sizes"); if (sizes != null) { String[] lanes = sizes.split(","); @@ -753,7 +753,7 @@ public class SleighLanguage implements Language { else if (elName.equals("default_symbols")) { XmlElement subel = parser.start(); Address previousAddr = null; - int previousSize = 1; + int previousSize = 1; while (parser.peek().getName().equals("symbol")) { XmlElement symbol = parser.start(); String labelName = symbol.getAttribute("name"); @@ -765,9 +765,11 @@ public class SleighLanguage implements Language { Address startAddress = null; if (addressString.equalsIgnoreCase("next")) { if (previousAddr == null) { - Msg.error(this, "use of addr=\"next\" tag with no previous address for " + - labelName + " : " + description.getSpecFile()); - } else { + Msg.error(this, + "use of addr=\"next\" tag with no previous address for " + + labelName + " : " + description.getSpecFile()); + } + else { startAddress = previousAddr.add(previousSize); } } @@ -784,7 +786,8 @@ public class SleighLanguage implements Language { else { AddressLabelInfo info; try { - info = new AddressLabelInfo(startAddress, rangeSize, labelName, comment, false, + info = new AddressLabelInfo(startAddress, rangeSize, labelName, comment, + false, isEntry, type, isVolatile); } catch (AddressOverflowException e) { diff --git a/Ghidra/Processors/PowerPC/data/languages/ppc_32.pspec b/Ghidra/Processors/PowerPC/data/languages/ppc_32.pspec index 8c9194eb1a..260c1622b1 100644 --- a/Ghidra/Processors/PowerPC/data/languages/ppc_32.pspec +++ b/Ghidra/Processors/PowerPC/data/languages/ppc_32.pspec @@ -304,8 +304,8 @@ - - + + @@ -321,8 +321,8 @@ - - + + diff --git a/Ghidra/Processors/PowerPC/data/languages/ppc_32_mpc8270.pspec b/Ghidra/Processors/PowerPC/data/languages/ppc_32_mpc8270.pspec index 95fdbf330c..409b563b24 100644 --- a/Ghidra/Processors/PowerPC/data/languages/ppc_32_mpc8270.pspec +++ b/Ghidra/Processors/PowerPC/data/languages/ppc_32_mpc8270.pspec @@ -302,8 +302,8 @@ don't know about the DCRs though - - + + @@ -319,8 +319,8 @@ don't know about the DCRs though - - + + diff --git a/Ghidra/Processors/PowerPC/data/languages/ppc_64.pspec b/Ghidra/Processors/PowerPC/data/languages/ppc_64.pspec index 028581a0f2..d61b9f0b2c 100644 --- a/Ghidra/Processors/PowerPC/data/languages/ppc_64.pspec +++ b/Ghidra/Processors/PowerPC/data/languages/ppc_64.pspec @@ -301,8 +301,8 @@ - - + + @@ -318,8 +318,8 @@ - - + + From a963884d7ee2c00733f47641dc0d533fe3df5240 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 28 Aug 2024 08:11:38 -0400 Subject: [PATCH 19/53] GP-0: Fix guest-mapping tests (misplaced override) --- .../trace/database/guest/DBTraceGuestPlatform.java | 5 ----- .../trace/database/guest/DBTracePlatformManager.java | 9 +++++++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java index 0aa0d24862..91903664c9 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java @@ -230,11 +230,6 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject return languageEntry == null ? manager.baseLanguage : languageEntry.getLanguage(); } - @Override - public AddressFactory getAddressFactory() { - return manager.trace.getBaseAddressFactory(); - } - @Override public CompilerSpec getCompilerSpec() { return compilerSpec; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java index 6f93390b37..2d9a34826f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java @@ -4,9 +4,9 @@ * 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. @@ -92,6 +92,11 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana return trace.getBaseCompilerSpec(); } + @Override + public AddressFactory getAddressFactory() { + return trace.getBaseAddressFactory(); + } + @Override public AddressSetView getHostAddressSet() { return trace.getBaseAddressFactory().getAddressSet(); From d973740105da4a2eaa28683b34866ec91c3eb5f9 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 28 Aug 2024 08:36:25 -0400 Subject: [PATCH 20/53] GP-0: Fix breakpoint-marker tests (adjust waitOn's) --- ...ractDebuggerBreakpointMarkerPluginTest.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java index fc76747e59..af17c8d699 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java @@ -483,9 +483,13 @@ public abstract class AbstractDebuggerBreakpointMarkerPluginTest addMappedBreakpointOpenAndWait(); // wasteful, but whatever for (LogicalBreakpoint lb : List.copyOf(breakpointService.getAllBreakpoints())) { TraceBreakpoint brk = Unique.assertOne(lb.getTraceBreakpoints(tb.trace)); - waitOn(lb.delete()); + CompletableFuture delete = lb.delete(); handleDeleteBreakpointInvocation(brk); + waitOn(delete); } + waitForDomainObject(program); + waitForDomainObject(tb.trace); + waitOn(breakpointService.changesSettled()); waitForPass(() -> assertEquals(0, breakpointService.getAllBreakpoints().size())); try (Transaction tx = program.openTransaction("Disassemble")) { @@ -501,6 +505,9 @@ public abstract class AbstractDebuggerBreakpointMarkerPluginTest waitForDialogComponent(DebuggerPlaceBreakpointDialog.class); runSwing(() -> dialog.okCallback()); handleSetBreakpointInvocation(TraceBreakpointKindSet.SW_EXECUTE, 0x55550123); + waitForDomainObject(program); + waitForDomainObject(tb.trace); + waitOn(breakpointService.changesSettled()); waitForPass(() -> { LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); @@ -515,9 +522,13 @@ public abstract class AbstractDebuggerBreakpointMarkerPluginTest addMappedBreakpointOpenAndWait(); // wasteful, but whatever for (LogicalBreakpoint lb : List.copyOf(breakpointService.getAllBreakpoints())) { TraceBreakpoint brk = Unique.assertOne(lb.getTraceBreakpoints(tb.trace)); - waitOn(lb.delete()); + CompletableFuture delete = lb.delete(); handleDeleteBreakpointInvocation(brk); + waitOn(delete); } + waitForDomainObject(program); + waitForDomainObject(tb.trace); + waitOn(breakpointService.changesSettled()); waitForPass(() -> assertEquals(0, breakpointService.getAllBreakpoints().size())); try (Transaction tx = program.openTransaction("Disassemble")) { @@ -532,6 +543,9 @@ public abstract class AbstractDebuggerBreakpointMarkerPluginTest waitForDialogComponent(DebuggerPlaceBreakpointDialog.class); runSwing(() -> dialog.okCallback()); handleSetBreakpointInvocation(TraceBreakpointKindSet.ACCESS, 0x55550123); + waitForDomainObject(program); + waitForDomainObject(tb.trace); + waitOn(breakpointService.changesSettled()); waitForPass(() -> { LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); From 403619065cc76b08f64a17d4a60c6d3e8c0c5436 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Wed, 28 Aug 2024 09:29:22 -0400 Subject: [PATCH 21/53] GP-0: Adding option to disable download progress output in fetchDependencies.gradle --- gradle/support/fetchDependencies.gradle | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/gradle/support/fetchDependencies.gradle b/gradle/support/fetchDependencies.gradle index 95a004a2b0..f0f7fda81b 100644 --- a/gradle/support/fetchDependencies.gradle +++ b/gradle/support/fetchDependencies.gradle @@ -51,6 +51,7 @@ ext.DOWNLOADS_DIR = file("${DEPS_DIR}/downloads") ext.FID_DIR = file("${DEPS_DIR}/fidb") ext.FLAT_REPO_DIR = file("${DEPS_DIR}/flatRepo") ext.OFFLINE = System.properties["offline"] != null +ext.HIDE_DOWNLOAD_PROGRESS = System.properties["hideDownloadProgress"] != null ext.createdDirs = [] as Set file("${REPO_DIR}/Ghidra/application.properties").withReader { reader -> @@ -275,17 +276,22 @@ def download(url, file) { def dataBuffer = new byte[1024]; int bytesRead; int totalRead; + if (HIDE_DOWNLOAD_PROGRESS) { + print " Downloading..." + } while ((bytesRead = istream.read(dataBuffer, 0, 1024)) != -1) { ostream.write(dataBuffer, 0, bytesRead); totalRead += bytesRead - print "\r" - print " Downloading: " + totalRead + " of " + size - if (!size.equals("???")) { - int pctComplete = (totalRead / size) * 100 - print " (" + pctComplete + "%)" + if (!HIDE_DOWNLOAD_PROGRESS) { + print "\r" + print " Downloading: " + totalRead + " of " + size + if (!size.equals("???")) { + int pctComplete = (totalRead / size) * 100 + print " (" + pctComplete + "%)" + } + print " " // overwrite gradle timer output + System.out.flush() } - print " " // overwrite gradle timer output - System.out.flush() } println() istream.close(); From 42b73252f1522e283aee80409037690742745922 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:19:29 -0400 Subject: [PATCH 22/53] GP-4876: JShell launcher for Ghidra --- Ghidra/Features/Base/.launch/JShell.launch | 34 +++++++++++++ Ghidra/Features/Base/certification.manifest | 1 + .../Base/src/main/java/ghidra/JShellRun.java | 49 +++++++++++++++++++ Ghidra/RuntimeScripts/Linux/support/jshellRun | 19 +++++++ .../Windows/support/jshellRun.bat | 11 +++++ Ghidra/RuntimeScripts/certification.manifest | 2 + 6 files changed, 116 insertions(+) create mode 100644 Ghidra/Features/Base/.launch/JShell.launch create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/JShellRun.java create mode 100755 Ghidra/RuntimeScripts/Linux/support/jshellRun create mode 100644 Ghidra/RuntimeScripts/Windows/support/jshellRun.bat diff --git a/Ghidra/Features/Base/.launch/JShell.launch b/Ghidra/Features/Base/.launch/JShell.launch new file mode 100644 index 0000000000..c657097019 --- /dev/null +++ b/Ghidra/Features/Base/.launch/JShell.launch @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 8f6c07bb61..c053587a20 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -12,6 +12,7 @@ .launch/Ghidra Code Coverage.launch||GHIDRA||||END| .launch/Ghidra.launch||GHIDRA||||END| .launch/Headless.launch||GHIDRA||||END| +.launch/JShell.launch||GHIDRA||||END| Module.manifest||GHIDRA||||END| data/ElfFunctionsThatDoNotReturn||GHIDRA||||END| data/ExtensionPoint.manifest||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/java/ghidra/JShellRun.java b/Ghidra/Features/Base/src/main/java/ghidra/JShellRun.java new file mode 100644 index 0000000000..c8ccbf5afb --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/JShellRun.java @@ -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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Stream; + +import ghidra.framework.*; +import jdk.jshell.tool.JavaShellToolBuilder; + +public class JShellRun implements GhidraLaunchable { + @Override + public void launch(GhidraApplicationLayout layout, String[] args) throws Exception { + if (Stream.of(args).anyMatch(a -> a.startsWith("--execution"))) { + System.err.println("Ignoring --execution option. Overridden to local"); + } + + ArrayList fullArgs = new ArrayList<>(); + fullArgs.addAll(Arrays.asList(args)); + + ApplicationConfiguration configuration; + if (fullArgs.remove("--headless")) { + configuration = new HeadlessGhidraApplicationConfiguration(); + } + else { + GhidraApplicationConfiguration gac = new GhidraApplicationConfiguration(); + gac.setShowSplashScreen(false); + configuration = gac; + } + Application.initializeApplication(layout, configuration); + + fullArgs.add("--execution=local"); + JavaShellToolBuilder.builder().start(fullArgs.toArray(String[]::new)); + } +} diff --git a/Ghidra/RuntimeScripts/Linux/support/jshellRun b/Ghidra/RuntimeScripts/Linux/support/jshellRun new file mode 100755 index 0000000000..c9a5a1a3f8 --- /dev/null +++ b/Ghidra/RuntimeScripts/Linux/support/jshellRun @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +#---------------------------------------- +# Ghidra JShell launch +#---------------------------------------- + +# Maximum heap memory may be changed if default is inadequate. This will generally be up to 1/4 of +# the physical memory available to the OS. Uncomment MAXMEM setting if non-default value is needed. +#MAXMEM=2G + +# Resolve symbolic link if present and get the directory this script lives in. +# NOTE: "readlink -f" is best but works on Linux only, "readlink" will only work if your PWD +# contains the link you are calling (which is the best we can do on macOS), and the "echo" is the +# fallback, which doesn't attempt to do anything with links. +SCRIPT_FILE="$(readlink -f "$0" 2>/dev/null || readlink "$0" 2>/dev/null || echo "$0")" +SCRIPT_DIR="${SCRIPT_FILE%/*}" + +# Launch Ghidra +"${SCRIPT_DIR}"/launch.sh fg jdk Ghidra-JShell "${MAXMEM}" "" ghidra.JShellRun "$@" diff --git a/Ghidra/RuntimeScripts/Windows/support/jshellRun.bat b/Ghidra/RuntimeScripts/Windows/support/jshellRun.bat new file mode 100644 index 0000000000..37238450c0 --- /dev/null +++ b/Ghidra/RuntimeScripts/Windows/support/jshellRun.bat @@ -0,0 +1,11 @@ +:: Ghidra JShell launch + +@echo off +setlocal + +:: Maximum heap memory may be changed if default is inadequate. This will generally be up to 1/4 of +:: the physical memory available to the OS. Uncomment MAXMEM setting if non-default value is needed. +::set MAXMEM=2G + +call "%~dp0launch.bat" fg jdk Ghidra-JShell "%MAXMEM%" "" ghidra.JShellRun %* + diff --git a/Ghidra/RuntimeScripts/certification.manifest b/Ghidra/RuntimeScripts/certification.manifest index be6ade10e3..860d43bd8e 100644 --- a/Ghidra/RuntimeScripts/certification.manifest +++ b/Ghidra/RuntimeScripts/certification.manifest @@ -26,6 +26,7 @@ Linux/support/convertStorage||GHIDRA||||END| Linux/support/gdbGADPServerRun||GHIDRA||||END| Linux/support/ghidraClean||GHIDRA||||END| Linux/support/ghidraDebug||GHIDRA||||END| +Linux/support/jshellRun||GHIDRA||||END| Linux/support/pythonRun||GHIDRA||||END| Linux/support/sleigh||GHIDRA||||END| Windows/ghidraRun.bat||GHIDRA||||END| @@ -45,6 +46,7 @@ Windows/support/dbgmodelGADPServerRun.bat||GHIDRA||||END| Windows/support/ghidra.ico||GHIDRA||||END| Windows/support/ghidraClean.bat||GHIDRA||||END| Windows/support/ghidraDebug.bat||GHIDRA||||END| +Windows/support/jshellRun.bat||GHIDRA||||END| Windows/support/launch.bat||GHIDRA||||END| Windows/support/pythonRun.bat||GHIDRA||||END| Windows/support/sleigh.bat||GHIDRA||||END| From c941eb027c817ace7d1c0e214c0b62cc7f7c751f Mon Sep 17 00:00:00 2001 From: emteere <47253321+emteere@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:52:30 -0400 Subject: [PATCH 23/53] GP-4879 Initial M16C commit --- Ghidra/Processors/M16C/Module.manifest | 1 + Ghidra/Processors/M16C/build.gradle | 23 + Ghidra/Processors/M16C/certification.manifest | 12 + .../M16C/data/languages/M16C_60.cspec | 40 + .../M16C/data/languages/M16C_60.ldefs | 20 + .../M16C/data/languages/M16C_60.pspec | 169 + .../M16C/data/languages/M16C_60.slaspec | 3627 ++++++++++++ .../M16C/data/languages/M16C_80.cspec | 31 + .../M16C/data/languages/M16C_80.ldefs | 20 + .../M16C/data/languages/M16C_80.pspec | 296 + .../M16C/data/languages/M16C_80.slaspec | 5050 +++++++++++++++++ .../Processors/M16C/data/manuals/M16C_60.idx | 95 + .../Processors/M16C/data/manuals/M16C_80.idx | 110 + 13 files changed, 9494 insertions(+) create mode 100644 Ghidra/Processors/M16C/Module.manifest create mode 100644 Ghidra/Processors/M16C/build.gradle create mode 100644 Ghidra/Processors/M16C/certification.manifest create mode 100644 Ghidra/Processors/M16C/data/languages/M16C_60.cspec create mode 100644 Ghidra/Processors/M16C/data/languages/M16C_60.ldefs create mode 100644 Ghidra/Processors/M16C/data/languages/M16C_60.pspec create mode 100644 Ghidra/Processors/M16C/data/languages/M16C_60.slaspec create mode 100644 Ghidra/Processors/M16C/data/languages/M16C_80.cspec create mode 100644 Ghidra/Processors/M16C/data/languages/M16C_80.ldefs create mode 100644 Ghidra/Processors/M16C/data/languages/M16C_80.pspec create mode 100644 Ghidra/Processors/M16C/data/languages/M16C_80.slaspec create mode 100644 Ghidra/Processors/M16C/data/manuals/M16C_60.idx create mode 100644 Ghidra/Processors/M16C/data/manuals/M16C_80.idx diff --git a/Ghidra/Processors/M16C/Module.manifest b/Ghidra/Processors/M16C/Module.manifest new file mode 100644 index 0000000000..f394b55cb6 --- /dev/null +++ b/Ghidra/Processors/M16C/Module.manifest @@ -0,0 +1 @@ +MODULE DEPENDENCY: Ghidra/Framework/SoftwareModeling \ No newline at end of file diff --git a/Ghidra/Processors/M16C/build.gradle b/Ghidra/Processors/M16C/build.gradle new file mode 100644 index 0000000000..64451b65c1 --- /dev/null +++ b/Ghidra/Processors/M16C/build.gradle @@ -0,0 +1,23 @@ +/* ### + * 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. + */ +apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle" +apply from: "$rootProject.projectDir/gradle/processorProject.gradle" +apply plugin: 'eclipse' +eclipse.project.name = 'Processors M16C' + +sleighCompileOptions = [ + '-l' +] diff --git a/Ghidra/Processors/M16C/certification.manifest b/Ghidra/Processors/M16C/certification.manifest new file mode 100644 index 0000000000..06f5f2e280 --- /dev/null +++ b/Ghidra/Processors/M16C/certification.manifest @@ -0,0 +1,12 @@ +##VERSION: 2.0 +Module.manifest||GHIDRA||||END| +data/languages/M16C_60.cspec||GHIDRA||||END| +data/languages/M16C_60.ldefs||GHIDRA||||END| +data/languages/M16C_60.pspec||GHIDRA||||END| +data/languages/M16C_60.slaspec||GHIDRA||||END| +data/languages/M16C_80.cspec||GHIDRA||||END| +data/languages/M16C_80.ldefs||GHIDRA||||END| +data/languages/M16C_80.pspec||GHIDRA||||END| +data/languages/M16C_80.slaspec||GHIDRA||||END| +data/manuals/M16C_60.idx||GHIDRA||||END| +data/manuals/M16C_80.idx||GHIDRA||||END| diff --git a/Ghidra/Processors/M16C/data/languages/M16C_60.cspec b/Ghidra/Processors/M16C/data/languages/M16C_60.cspec new file mode 100644 index 0000000000..3949c2a7fc --- /dev/null +++ b/Ghidra/Processors/M16C/data/languages/M16C_60.cspec @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghidra/Processors/M16C/data/languages/M16C_60.ldefs b/Ghidra/Processors/M16C/data/languages/M16C_60.ldefs new file mode 100644 index 0000000000..18014c05e0 --- /dev/null +++ b/Ghidra/Processors/M16C/data/languages/M16C_60.ldefs @@ -0,0 +1,20 @@ + + + + + + Renesas M16C/60 16-Bit MicroComputer + + + + diff --git a/Ghidra/Processors/M16C/data/languages/M16C_60.pspec b/Ghidra/Processors/M16C/data/languages/M16C_60.pspec new file mode 100644 index 0000000000..a1e280dda3 --- /dev/null +++ b/Ghidra/Processors/M16C/data/languages/M16C_60.pspec @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ghidra/Processors/M16C/data/languages/M16C_60.slaspec b/Ghidra/Processors/M16C/data/languages/M16C_60.slaspec new file mode 100644 index 0000000000..e6046afac7 --- /dev/null +++ b/Ghidra/Processors/M16C/data/languages/M16C_60.slaspec @@ -0,0 +1,3627 @@ +# Renesas M16C/60 16-Bit MicroComputer + +# +# Memory Architecture +# +define endian=little; + +define alignment=1; + +define space RAM type=ram_space size=3 default; +define space register type=register_space size=2; + +# +# General Registers +# +define register offset=0x0000 size=2 [ + R1 R3 R0 R2 A0 A1 +]; + +define register offset=0x0000 size=1 [ + R1L R1H _ _ R0L R0H +]; + +define register offset=0x0000 size=4 [ + R3R1 R2R0 A1A0 +]; + +define register offset=0x1000 size=3 [ + PC # Program Counter +]; + +define register offset=0x2000 size=3 [ + INTB # Interrupt Table Register +]; + +define register offset=0x2000 size=2 [ + INTBL INTBH +]; + +define register offset=0x3000 size=2 [ + SP # Current Stack Pointer (Represents active stack pointer: ISP or USP) + FB # Frame Base Register + SB # Static Base Register + FLG # Flag Register + ISP # Interrupt Stack Pointer +]; + +# Flag Register Contents (FLG) +# +# b15 - Reserved area +# b14:b12 - Processor interrupt priority level +# b11:b8 - Reserved area +# b7 - (U) Stack pointer select flag +# b6 - (I) Interrupt enable flag +# b5 - (O) Overflow flag +# b4 - (B) Register bank select flag +# b3 - (S) Sign flag +# b2 - (Z) Zero flag +# b1 - (D) Debug flag +# b0 - (C) Carry flag +@define CARRY "FLG[0,1]" +@define DEBUG "FLG[1,1]" +@define ZERO "FLG[2,1]" +@define SIGN "FLG[3,1]" +@define REG_BANK "FLG[4,1]" +@define OVERFLOW "FLG[5,1]" +@define INTERRUPT "FLG[6,1]" +@define STACK_SEL "FLG[7,1]" +@define IPL "FLG[12,3]" + +# Define context bits +define register offset=0xA000 size=4 contextreg; + +define context contextreg + dstFollowsSrc = (0,1) # =1 destination add-on data follows 4-bit encoded source add-on data + # =2 destination add-on data follows 8-bit data +; + +define token b0(8) + b0_0007 = (0,7) +; + +define token b1(8) + b1_d2 = (0,1) + b1_d3 = (0,2) + b1_d3_2 = (2,2) + b1_2_reg8 = (2,2) + b1_2_regAx = (2,2) + b1_3_regAx = (3,3) + b1_3_reg8 = (3,3) + b1_size_0 = (0,0) + b1_0407 = (4,7) + b1_0307 = (3,7) + b1_0107 = (1,7) + b1_0007 = (0,7) + b1_0002 = (0,2) + b1_bit = (0,2) +; + +define token b2(8) + b2_d4_reg8 = (0,1) + b2_s4_reg8 = (4,5) + b2_d4_reg16 = (0,1) + b2_s4_reg16 = (4,5) + b2_d4_reg32 = (0,0) + b2_s4_reg32 = (4,4) + b2_reg32 = (4,4) + b2_d4_regAxSF = (0,1) # selects A0, A1, SB or FB + b2_s4_regAxSF = (4,5) # selects A0, A1, SB or FB + b2_d4_regAx = (0,0) + b2_s4_regAx = (4,4) + b2_reg16 = (4,6) + b2_creg16 = (4,6) + b2_d4 = (0,3) + b2_d4_3 = (3,3) + b2_d4_23 = (2,3) + b2_d4_13 = (1,3) + b2_s4 = (4,7) + b2_s4_23 = (6,7) + b2_s4_13 = (5,7) + b2_shiftSign_7 = (7,7) + b2_shiftSign_3 = (3,3) + b2_0707 = (7,7) + b2_0607 = (6,7) + b2_0507 = (5,7) + b2_0407 = (4,7) + b2_0406 = (4,6) + b2_0307 = (3,7) + b2_0303 = (3,3) + b2_0007 = (0,7) + b2_0003 = (0,3) + b2_0002 = (0,2) + b2_simm4_0407 = (4,7) signed + b2_simm4_0003 = (0,3) signed +; + +define token b3(8) + b3_0407 = (4,7) + b3_0007 = (0,7) + b3_0003 = (0,3) +; + +define token b4(8) + b4_0007 = (0,7) +; + +define token b5(8) + b5_0007 = (0,7) +; + +define token b6(8) + b6_0007 = (0,7) +; + +define token imm8(8) + simm8_dat = (0,7) signed + imm8_dat = (0,7) + imm8_base = (3,7) # bit,base byte displacement + imm8_bit = (0,2) # bit,base bit number + simm8_base = (3,7) signed # bit,base signed byte displacement + simm8_bit = (0,2) # bit,base signed bit number + imm6_dat = (0,5) # int number + cnd8_dat = (0,7) + imm8_0607 = (6,7) + imm8_0407 = (4,7) + imm8_0003 = (0,3) + regBit7 = (7,7) + regBit6 = (6,6) + regBit5 = (5,5) + regBit4 = (4,4) + regBit3 = (3,3) + regBit2 = (2,2) + regBit1 = (1,1) + regBit0 = (0,0) +; + +define token imm16(16) + simm16_dat = (0,15) signed + imm16_dat = (0,15) + imm16_base = (3,15) # bit,base byte displacement + imm16_bit = (0, 2) # bit,base bit number +; + +define token imm24(24) + simm24_dat = (0,23) signed + imm24_dat = (0,23) + simm20_dat = (0,19) + imm20_dat = (0,19) +; + +define token imm32(32) + simm32_dat = (0,31) signed + imm32_dat = (0,31) +; + +attach variables [ b2_s4_reg16 b2_d4_reg16 ] [ R0 R1 R2 R3 ]; +attach variables [ b2_s4_reg8 b2_d4_reg8 ] [ R0L R0H R1L R1H ]; +attach variables [ b1_2_reg8 b1_3_reg8 ] [ R0L R0H ]; +attach variables [ b2_s4_regAx b2_d4_regAx b1_3_regAx b1_2_regAx ] [ A0 A1 ]; +attach variables [ b2_s4_regAxSF b2_d4_regAxSF ] [ A0 A1 SB FB ]; +attach variables [ b2_reg16 ] [ R0 R1 R2 R3 A0 A1 _ _ ]; +attach variables [ b2_creg16 ] [ _ INTBL INTBH FLG ISP SP SB FB ]; +attach variables [ b2_reg32 b2_d4_reg32 ] [ R2R0 R3R1 ]; + +# +# PCode Op +# +define pcodeop Break; # BRK +define pcodeop DecimalAdd; # DADD +define pcodeop DecimalAddWithCarry; # DADC +define pcodeop DecimalSubtractWithBorrow; # DSBB +define pcodeop DecimalSubtract; # DSUB +define pcodeop Wait; # WAIT + +# +# FLAG MACROS... +# +# Set zero and sign flags from result +macro setResultFlags(result) { + $(SIGN) = (result s< 0x0); + $(ZERO) = (result == 0x0); +} + +# Set carry and overflow flags for addition +macro setAdd3Flags(v1, v2, v3) { + local add13 = v1 + v3; + $(CARRY) = carry(v1,v3) || carry(v2,add13); + $(OVERFLOW) = scarry(v1,v3) || scarry(v2,add13); +} + +# Set carry and overflow flags for addition +macro setAddFlags(v1, v2) { + $(CARRY) = carry(v1, v2); + $(OVERFLOW) = scarry(v1, v2); +} + +# Set overflow flags for subtraction of op3,op2 from op1 (op1-op2-op3) +macro setSubtract3Flags(v1, v2, v3) { + local add12 = v1 - v2; + $(CARRY) = (v1 >= v2) || (add12 >= v3); + $(OVERFLOW) = sborrow(v1, v2) || sborrow(add12, v3); +} + +# Set overflow flags for subtraction of op2 from op1 (op1-op2) +macro setSubtractFlags(v1, v2) { + $(CARRY) = (v1 s>= v2); + $(OVERFLOW) = sborrow(v1, v2); +} + +macro push1(val) { + SP = SP - 1; + ptr:3 = zext(SP); + *:1 ptr = val; +} + +macro push2(val) { + SP = SP - 2; + ptr:3 = zext(SP); + *:2 ptr = val; +} + +macro push3(val) { + SP = SP - 3; + ptr:3 = zext(SP); + *:3 ptr = val; +} + +macro push4(val) { + SP = SP - 4; + ptr:3 = zext(SP); + *:4 ptr = val; +} + +macro pop1(val) { + ptr:3 = zext(SP); + val = *:1 ptr; + SP = SP + 1; +} + +macro pop2(val) { + ptr:3 = zext(SP); + val = *:2 ptr; + SP = SP + 2; +} + +macro pop3(val) { + ptr:3 = zext(SP); + val = *:3 ptr; + SP = SP + 3; +} + +macro pop4(val) { + ptr:3 = zext(SP); + val = *:4 ptr; + SP = SP + 4; +} + +# +# Source operand location data +# +# Obtain base offset displacement for [AX | SB | FB] - AX and SB uses unsigned displacements, FB uses signed displacement +src4dsp8: imm8_dat^":8" is b1_0007; b2_s4; imm8_dat { export *[const]:2 imm8_dat; } +src4dsp8: simm8_dat^":8" is b1_0007; b2_s4=0xb; simm8_dat { export *[const]:2 simm8_dat; } +src4dsp16: imm16_dat^":16" is b1_0007; b2_s4; imm16_dat { export *[const]:2 imm16_dat; } + +# src4... Handle 4-bit encoded Source specified by b2_s4(4-bits) +# Variable length pattern starting at instruction byte b1 +# associated src4 add-on data immediately follows instruction byte b2 +# abs16 cases are broken out differently to facilitate export of constant addresses in certain cases +# 1-Byte source value/location specified by 4-bit encoding (b2_d4) +src4B: b2_s4_reg8 is b1_0007; b2_s4_23=0x0 & b2_s4_reg8 { export b2_s4_reg8; } # Rx +src4B: b2_s4_regAx is b1_0007; b2_s4_13=0x2 & b2_s4_regAx { tmp:1 = b2_s4_regAx:1; export tmp; } # Ax +src4B: [b2_s4_regAx] is b1_0007; b2_s4_13=0x3 & b2_s4_regAx { ptr:3 = zext(b2_s4_regAx); export *:1 ptr; } # [Ax] +src4B: src4dsp8^[b2_s4_regAxSF] is (b1_0007; b2_s4_23=0x2 & b2_s4_regAxSF) ... & src4dsp8 { ptr:3 = zext(b2_s4_regAxSF + src4dsp8); export *:1 ptr; } # dsp:8[Ax|SB|FB] +src4B: src4dsp16^[b2_s4_regAxSF] is (b1_0007; b2_s4_23=0x3 & b2_s4_regAxSF) ... & src4dsp16 { ptr:3 = zext(b2_s4_regAxSF + src4dsp16); export *:1 ptr; } # dsp:16[Ax|SB|FB] +src4B: imm16_dat is b1_0007; b2_s4=0xf; imm16_dat { export *:1 imm16_dat; } # abs16 (special constant address case) + +# 2-Byte source value/location specified by 2-bit encoding (b2_d4) +src4W: b2_s4_reg16 is b1_0007; b2_s4_23=0x0 & b2_s4_reg16 { export b2_s4_reg16; } # Rx +src4W: b2_s4_regAx is b1_0007; b2_s4_13=0x2 & b2_s4_regAx { export b2_s4_regAx; } # Ax +src4W: [b2_s4_regAx] is b1_0007; b2_s4_13=0x3 & b2_s4_regAx { ptr:3 = zext(b2_s4_regAx); export *:2 ptr; } # [Ax] +src4W: src4dsp8^[b2_s4_regAxSF] is (b1_0007; b2_s4_23=0x2 & b2_s4_regAxSF) ... & src4dsp8 { ptr:3 = zext(b2_s4_regAxSF + src4dsp8); export *:2 ptr; } # dsp:8[Ax|SB|FB] +src4W: src4dsp16^[b2_s4_regAxSF] is (b1_0007; b2_s4_23=0x3 & b2_s4_regAxSF) ... & src4dsp16 { ptr:3 = zext(b2_s4_regAxSF + src4dsp16); export *:2 ptr; } # dsp:16[Ax|SB|FB] +src4W: imm16_dat is b1_0007; b2_s4=0xf; imm16_dat { export *:2 imm16_dat; } # abs16 (special constant address case) + +# +# Destination operand location data (may also be used as a source in certain cases) +# +# Skip instruction and source add-on bytes which occur before destination add-on bytes +# Starting position is at b1 +skipBytesBeforeDst4: is b1_0007; b2_s4 { } +skipBytesBeforeDst4: is dstFollowsSrc=1 & b1_0007; b2_s4_23=0x2; imm8_dat { } # src4: dsp8 +skipBytesBeforeDst4: is dstFollowsSrc=1 & b1_0007; b2_s4_23=0x3; imm16_dat { } # src4: dsp16/abs16 +skipBytesBeforeDst4: is dstFollowsSrc=2 & b1_0007; b2_d4; imm8_dat { } # dsp8 + +# Obtain base offset displacement for [AX | SB | FB] - AX and SB uses unsigned displacements, FB uses signed displacement +dst4dsp8: imm8_dat^":8" is (skipBytesBeforeDst4; imm8_dat) { export *[const]:2 imm8_dat; } +dst4dsp8: simm8_dat^":8" is (b1_0007; b2_d4=0xb) ... & (skipBytesBeforeDst4; simm8_dat) { export *[const]:2 simm8_dat; } +dst4dsp16: imm16_dat^":16" is (skipBytesBeforeDst4; imm16_dat) { export *[const]:2 imm16_dat; } + +# dst4... Handle 4-bit encoded Destination specified by b2_d4(4-bits) +# Ax direct case is read-only! Instruction must use dst4Ax for write/update case +# Variable length pattern starting at instruction byte b1 +# abs16 cases are broken out differently to facilitate export of constant addresses in certain cases +# 1-Byte destination value/location specified by 4-bit encoding (b2_d4) +dst4B: b2_d4_reg8 is b1_0007; b2_d4_23=0x0 & b2_d4_reg8 { export b2_d4_reg8; } # Rx +dst4B: b2_d4_regAx is b1_0007; b2_d4_13=0x2 & b2_d4_regAx { tmp:1 = b2_d4_regAx:1; export tmp; } # Ax - read-only use ! +dst4B: [b2_d4_regAx] is b1_0007; b2_d4_13=0x3 & b2_d4_regAx { ptr:3 = zext(b2_d4_regAx); export *:1 ptr; } # [Ax] +dst4B: dst4dsp8^[b2_d4_regAxSF] is (b1_0007; b2_d4_23=0x2 & b2_d4_regAxSF) ... & dst4dsp8 { ptr:3 = zext(b2_d4_regAxSF + dst4dsp8); export *:1 ptr; } # dsp:8[Ax|SB|FB] +dst4B: dst4dsp16^[b2_d4_regAxSF] is (b1_0007; b2_d4_23=0x3 & b2_d4_regAxSF) ... & dst4dsp16 { ptr:3 = zext(b2_d4_regAxSF + dst4dsp16); export *:1 ptr; } # dsp:16[Ax|SB] +dst4B: imm16_dat is (b1_0007; b2_d4=0xf) ... & (skipBytesBeforeDst4; imm16_dat) { export *:1 imm16_dat; } # abs16 (special constant address case) + +# 2-Byte destination value/location specified by 4-bit encoding (b2_d4) +dst4W: b2_d4_reg16 is b1_0007; b2_d4_23=0x0 & b2_d4_reg16 { export b2_d4_reg16; } # Rx +dst4W: b2_d4_regAx is b1_0007; b2_d4_13=0x2 & b2_d4_regAx { export b2_d4_regAx; } # Ax +dst4W: [b2_d4_regAx] is b1_0007; b2_d4_13=0x3 & b2_d4_regAx { ptr:3 = zext(b2_d4_regAx); export *:2 ptr; } # [Ax] +dst4W: dst4dsp8^[b2_d4_regAxSF] is (b1_0007; b2_d4_23=0x2 & b2_d4_regAxSF) ... & dst4dsp8 { ptr:3 = zext(b2_d4_regAxSF + dst4dsp8); export *:2 ptr; } # dsp:8[Ax|SB|FB] +dst4W: dst4dsp16^[b2_d4_regAxSF] is (b1_0007; b2_d4_23=0x3 & b2_d4_regAxSF) ... & dst4dsp16 { ptr:3 = zext(b2_d4_regAxSF + dst4dsp16); export *:2 ptr; } # dsp:16[Ax|SB] +dst4W: imm16_dat is (b1_0007; b2_d4=0xf) ... & (skipBytesBeforeDst4; imm16_dat) { export *:2 imm16_dat; } # abs16 (special constant address case) + +# 4-Byte destination value/location specified by 4-bit encoding (b2_d4) +dst4L: b2_d4_reg32 is b1_0007; b2_d4_13=0x0 & b2_d4_reg32 { export b2_d4_reg32; } # Rx +dst4L: A1A0 is A1A0 & b1_0007; b2_d4=0x4 { export A1A0; } # A1A0 +dst4L: [b2_d4_regAx] is b1_0007; b2_d4_13=0x3 & b2_d4_regAx { ptr:3 = zext(b2_d4_regAx); export *:4 ptr; } # [Ax] +dst4L: dst4dsp8^[b2_d4_regAxSF] is (b1_0007; b2_d4_23=0x2 & b2_d4_regAxSF) ... & dst4dsp8 { ptr:3 = zext(b2_d4_regAxSF + dst4dsp8); export *:4 ptr; } # dsp:8[Ax|SB|FB] +dst4L: dst4dsp16^[b2_d4_regAxSF] is (b1_0007; b2_d4_23=0x3 & b2_d4_regAxSF) ... & dst4dsp16 { ptr:3 = zext(b2_d4_regAxSF + dst4dsp16); export *:4 ptr; } # dsp:16[Ax|SB] +dst4L: imm16_dat is (b1_0007; b2_d4=0xf) ... & (skipBytesBeforeDst4; imm16_dat) { export *:4 imm16_dat; } # abs16 (special constant address case) + +# 3-Byte destination value/location specified by 4-bit encoding (b2_d4) - use DST4L to constrain, and dst4L for register Ax/Rx non-memory cases +dst4T: [b2_d4_regAx] is b1_0007; b2_d4_13=0x3 & b2_d4_regAx { ptr:3 = zext(b2_d4_regAx); export *:3 ptr; } # [Ax] +dst4T: dst4dsp8^[b2_d4_regAxSF] is (b1_0007; b2_d4_23=0x2 & b2_d4_regAxSF) ... & dst4dsp8 { ptr:3 = zext(b2_d4_regAxSF + dst4dsp8); export *:3 ptr; } # dsp:8[Ax|SB|FB] +dst4T: dst4dsp16^[b2_d4_regAxSF] is (b1_0007; b2_d4_23=0x3 & b2_d4_regAxSF) ... & dst4dsp16 { ptr:3 = zext(b2_d4_regAxSF + dst4dsp16); export *:3 ptr; } # dsp:16[Ax|SB] +dst4T: imm16_dat is (b1_0007; b2_d4=0xf) ... & (skipBytesBeforeDst4; imm16_dat) { export *:3 imm16_dat; } # abs16 (special constant address case) + +# 3-Byte effective address specified by 4-bit encoding (b2_d4) +dst4A: dst4dsp8^[b2_d4_regAxSF] is (b1_0007; b2_d4_23=0x2 & b2_d4_regAxSF) ... & dst4dsp8 { ptr:3 = zext(b2_d4_regAxSF + dst4dsp8); export ptr; } # dsp:8[Ax|SB|FB] +dst4A: dst4dsp16^[b2_d4_regAxSF] is (b1_0007; b2_d4_23=0x3 & b2_d4_regAxSF) ... & dst4dsp16 { ptr:3 = zext(b2_d4_regAxSF + dst4dsp16); export ptr; } # dsp:16[Ax|SB] +dst4A: imm16_dat is (b1_0007; b2_d4=0xf) ... & (skipBytesBeforeDst4; imm16_dat) { export *[const]:3 imm16_dat; } # abs16 (special constant address case) + +# Ax destination specified by 4-bit encoding (b2_d4) +# NOTE! Ax destination is special case and must be handled separately by each instruction +# Starting position is at instruction b1 +dst4Ax: b2_d4_regAx is b1_0007; b2_d4_regAx { export b2_d4_regAx; } + +# 1/2-Byte destination value/location specified by 4-bit encoding (b2_d4) +# This handles the case for dst4B, dst4W and dst4L where 5-bit encoded Source (src4) add-on bytes may exist before Destination add-on bytes +# Variable length pattern starting at instruction byte b1 +dst4B_afterSrc4: dst4B is dst4B [ dstFollowsSrc=1; ] { export dst4B; } + +dst4W_afterSrc4: dst4W is dst4W [ dstFollowsSrc=1; ] { export dst4W; } + +dst4L_afterSrc4: dst4L is dst4L [ dstFollowsSrc=1; ] { export dst4L; } + +# +# The following macros are used to constrain bit patterns when using dst4 +# These should be used by constructor pattern matching instead of the corresponding dst4 sub-constructor +# +@define DST4AX "((b1_0007; b2_d4_13=0x2) & dst4Ax)" +@define DST4A "((b1_0007; b2_d4_3=1) ... & dst4A)" +@define DST4T "((b1_0007; (b2_d4_3=1 | b2_d4_13=3)) ... & dst4T)" + +# Skip instruction and source add-on bytes which occur before destination add-on bytes +# Starting position is at b1 +skipBytesBeforeDst2: is b1_d2 { } +skipBytesBeforeDst2: is dstFollowsSrc=2 & b1_d2; imm8_dat { } # dsp8 + +# +# destination value/location specified by 2/3-bit encoding, R0H/R0L choice controlled by destination-bit (b1_0002) +# +dst2B: R0L is (R0L & b1_d3=0x4) { export R0L; } +dst2B: R0H is (R0H & b1_d3=0x0) { export R0H; } +dst2B: imm8_dat^[SB] is (SB & b1_d2=0x1) ... & (skipBytesBeforeDst2; imm8_dat) { ptr:3 = zext(SB + imm8_dat); export *:1 ptr; } +dst2B: simm8_dat^[FB] is (FB & b1_d2=0x2) ... & (skipBytesBeforeDst2; simm8_dat) { ptr:3 = zext(FB + simm8_dat); export *:1 ptr; } +dst2B: imm16_dat is (b1_d2=0x3) ... & (skipBytesBeforeDst2; imm16_dat) { export *:1 imm16_dat; } + +# +# destination value/location specified by 3-bit encoding (must be constrained by DST3B or DST3B_AFTER_DSP8) +# +dst3B: R0L is (R0L & b1_d3=0x4) { export R0L; } +dst3B: R0H is (R0H & b1_d3=0x3) { export R0H; } +dst3B: imm8_dat^[SB] is (SB & b1_d3=0x5) ... & (skipBytesBeforeDst2; imm8_dat) { ptr:3 = zext(SB + imm8_dat); export *:1 ptr; } +dst3B: simm8_dat^[FB] is (FB & b1_d3=0x6) ... & (skipBytesBeforeDst2; simm8_dat) { ptr:3 = zext(FB + simm8_dat); export *:1 ptr; } +dst3B: imm16_dat is (b1_d3=0x7) ... & (skipBytesBeforeDst2; imm16_dat) { export *:1 imm16_dat; } + +# 1-Byte destination value/location specified by 3-bit encoding (b2_d3) +# This handles the case for dst3B where Dsp8 add-on bytes always exist before Destination add-on bytes +# Variable length pattern starting at instruction byte b1 +dst3B_afterDsp8: dst3B is dst3B [ dstFollowsSrc=2; ] { export dst3B; } + +# +# The following macros are used to constrain bit patterns when using dst2 for a 3-bit src/dest +# These should be used by constructor pattern matching instead of the corresponding dst4 sub-constructor +# +@define DST3B "((b1_d3=3 | b1_d3_2=1) ... & dst3B)" +@define DST3B_AFTER_DSP8 "((b1_d3=3 | b1_d3_2=1) ... & dst3B_afterDsp8)" + +# Special dsp8[SP] source/destination - starting point is on dsp8 data +dsp8spB: simm8_dat^":8"^[SP] is simm8_dat & SP { ptr:3 = zext(SP + simm8_dat); export *:1 ptr; } + +dsp8spW: simm8_dat^":8"^[SP] is simm8_dat & SP { ptr:3 = zext(SP + simm8_dat); export *:2 ptr; } + +# Special dsp20[A0] source/destination - starting point is on dsp20 data +dsp20A0B: simm20_dat^":20["^A0^"]" is A0 & simm20_dat { ptr:3 = zext(A0 + simm20_dat); export *:1 ptr; } + +dsp20A0W: simm20_dat^":20["^A0^"]" is A0 & simm20_dat { ptr:3 = zext(A0 + simm20_dat); export *:2 ptr; } + +# +# Bit base - associated add-on data immediately follows instruction byte b2 +# There are three cases which must be broken-out by instruction (regBase, memBaseAx, memBase) +# +# bit-base is bit,byte specified by [Ax] (constrain instruction pattern using b2_d4_13=0x3) - contexts of Ax are exported +memBaseAx: [b2_d4_regAx] is b1_0007; b2_d4_13=0x3 & b2_d4_regAx { export b2_d4_regAx; } # [Ax] (special case! bit operand does not appear) + +# bit-base is 16-bit register: Rx or Ax (constrain instruction pattern using b2_d4_3=0) +regBase: b2_d4_reg16 is b1_0007; b2_d4_23=0x0 & b2_d4_reg16 { export b2_d4_reg16; } # Rx +regBase: b2_d4_regAx is b1_0007; b2_d4_13=0x2 & b2_d4_regAx { export b2_d4_regAx; } # Ax + +# bit-base is byte location within memory +memBase: imm8_base^":8"^[b2_d4_regAxSF] is b1_0007; b2_d4_23=0x2 & b2_d4_regAxSF; imm8_base { ptr:3 = zext(b2_d4_regAxSF + imm8_base); export *:1 ptr; } # dsp:8[Ax|SB] +memBase: simm8_base^":8"^[FB] is b1_0007; b2_d4_23=0x2 & b2_d4=0xb & FB; simm8_base { ptr:3 = zext(FB + simm8_base); export *:1 ptr; } # dsp:8[FB] +memBase: imm16_base^":16"^[b2_d4_regAxSF] is b1_0007; b2_d4_23=0x3 & b2_d4_regAxSF; imm16_base { ptr:3 = zext(b2_d4_regAxSF + imm16_base); export *:1 ptr; } # dsp:16[Ax|SB] +memBase: imm16_base^":16" is b1_0007; b2_d4=0xf; imm16_base { export *:1 imm16_base; } # abs16 (special constant address case) + +memBase11: imm8_dat^":11"^[SB] is SB & b1_0007; imm8_dat { ptr:3 = zext(SB + imm8_dat); export *:1 ptr; } # dsp:11[SB] + +# Bit operand associated with regBase operand +# TODO: imm8_0407=0 constraint removed due to sleigh compiler issue +regBit: imm8_0003 is b1_0007; b2_d4; imm8_0003 { export *[const]:1 imm8_0003; } # Rx, Ax + +# Bit operand associated with memBase operand +memBit: imm8_bit is b1_0007; b2_d4; imm8_bit { export *[const]:1 imm8_bit; } # dsp:8[Ax|SB|FB] +memBit: imm16_bit is b1_0007; b2_d4_23=3; imm16_bit { export *[const]:1 imm16_bit; } # dsp:16[Ax|SB], base:16 + +# +# Immediate data operand +# Fixed length - current position is at start of immediate data +# +srcImm3: "#"^b2_0002 is b2_0002 { export *[const]:1 b2_0002; } +srcImm8: "#"^imm8_dat is imm8_dat { export *[const]:1 imm8_dat; } +srcImm16: "#"^imm16_dat is imm16_dat { export *[const]:2 imm16_dat; } + +srcSimm8: "#"^simm8_dat is simm8_dat { export *[const]:1 simm8_dat; } +srcSimm16: "#"^simm16_dat is simm16_dat { export *[const]:2 simm16_dat; } + +# Signed immediate data from 4-bit value: -8 <= value <= 7 +# NOTE! There are two different cases based upon the bits used from b2 +srcSimm4_0407: "#"^b2_simm4_0407 is b2_simm4_0407 { export *[const]:1 b2_simm4_0407; } +srcSimm4_0003: "#"^b2_simm4_0003 is b2_simm4_0003 { export *[const]:1 b2_simm4_0003; } + +# Signed immediate shift amount from 4-bit value: -8 <= value <= -1 || 1 <= value <= 8 +# NOTE! There are two different cases based upon the bits used from b2 +srcSimm4Shift_0407: "#"^val is b2_shiftSign_7=0 & b2_0406 [ val = b2_0406 + 1; ] { export *[const]:1 val; } +srcSimm4Shift_0407: "#"^val is b2_shiftSign_7=1 & b2_0406 [ val = -(b2_0406 + 1); ] { export *[const]:1 val; } +srcSimm4Shift_0003: "#"^val is b2_shiftSign_3=0 & b2_0002 [ val = b2_0002 + 1; ] { export *[const]:1 val; } +srcSimm4Shift_0003: "#"^val is b2_shiftSign_3=1 & b2_0002 [ val = -(b2_0002 + 1); ] { export *[const]:1 val; } + +srcZero8: "#0" is b1_0007 { export 0:1; } + +# special 6-bit immediate for INT number +srcIntNum: "#"^imm6_dat is imm6_dat { export *[const]:1 imm6_dat; } + +# +# Offset label operand +# +abs20offset: imm20_dat is imm20_dat { export *:1 imm20_dat; } + +abs20offsetW: imm20_dat is imm20_dat { export *:2 imm20_dat; } + +abs16offset: imm16_dat is imm16_dat { export *:1 imm16_dat; } + +# Relative address offsets +rel16offset1: offs is simm16_dat [ offs = inst_start + 1 + simm16_dat; ] { export *:1 offs; } + +rel8offset1: offs is simm8_dat [ offs = inst_start + 1 + simm8_dat; ] { export *:1 offs; } +rel8offset2: offs is simm8_dat [ offs = inst_start + 2 + simm8_dat; ] { export *:1 offs; } + +rel3offset2: offs is b1_0002 [ offs = inst_start + 2 + b1_0002; ] { export *:1 offs; } + +reloffset_dst4W: dst4W is dst4W { local reladdr = inst_start + dst4W; export *:3 reladdr; } + +reloffset_dst4L: dst4L is dst4L { local reladdr = inst_start + dst4L; export *:3 reladdr; } + +reloffset_dst4T: dst4T is $(DST4T) { local reladdr = inst_start + dst4T; export *:3 reladdr; } + +# +# Conditionals +# +cnd8: "GEU" is cnd8_dat=0x00 { tstCnd:1 = ($(CARRY) == 1); export tstCnd; } # Equal to or greater than (<=), C flag is 1 +cnd8: "GTU" is cnd8_dat=0x01 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 1); export tstCnd; } # Greater than (<) +cnd8: "EQ" is cnd8_dat=0x02 { tstCnd:1 = ($(ZERO) == 1); export tstCnd; } # Equal to (=), Z flag is 1 +cnd8: "N" is cnd8_dat=0x03 { tstCnd:1 = ($(SIGN) == 1); export tstCnd; } # Negative (0>) +cnd8: "LE" is cnd8_dat=0x04 { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 1); export tstCnd; } # Equal to or less than (signed value) (>=) +cnd8: "O" is cnd8_dat=0x05 { tstCnd:1 = ($(OVERFLOW) == 1); export tstCnd; } # O flag is 1 +cnd8: "GE" is cnd8_dat=0x06 { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 0); export tstCnd; } # Equal to or greater than (signed value) (<=) +cnd8: "LTU" is cnd8_dat=0xf8 { tstCnd:1 = ($(CARRY) == 0); export tstCnd; } # less than (>), C flag is 0 +cnd8: "LEU" is cnd8_dat=0xf9 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 0); export tstCnd; } # Equal to or less than (>=) +cnd8: "NE" is cnd8_dat=0xfa { tstCnd:1 = ($(ZERO) == 0); export tstCnd; } # Not Equal to (=), Z flag is 0 +cnd8: "PZ" is cnd8_dat=0xfb { tstCnd:1 = ($(SIGN) == 0); export tstCnd; } # Positive or zero (0<=) +cnd8: "GT" is cnd8_dat=0xfc { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 0); export tstCnd; } # Greater than (signed value) (<) +cnd8: "NO" is cnd8_dat=0xfd { tstCnd:1 = ($(OVERFLOW) == 0); export tstCnd; } # O flag is 0 +cnd8: "LT" is cnd8_dat=0xfe { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 1); export tstCnd; } # less than (signed value) (<=) + +b2cnd4: "GEU" is b2_0003=0x0 { tstCnd:1 = ($(CARRY) == 1); export tstCnd; } # Equal to or greater than (<=), C flag is 1 +b2cnd4: "GTU" is b2_0003=0x1 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 1); export tstCnd; } # Greater than (<) +b2cnd4: "EQ" is b2_0003=0x2 { tstCnd:1 = ($(ZERO) == 1); export tstCnd; } # Equal to (=), Z flag is 1 +b2cnd4: "N" is b2_0003=0x3 { tstCnd:1 = ($(SIGN) == 1); export tstCnd; } # Negative (0>) +b2cnd4: "LTU" is b2_0003=0x4 { tstCnd:1 = ($(CARRY) == 0); export tstCnd; } # less than (>), C flag is 0 +b2cnd4: "LEU" is b2_0003=0x5 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 0); export tstCnd; } # Equal to or less than (>=) +b2cnd4: "NE" is b2_0003=0x6 { tstCnd:1 = ($(ZERO) == 0); export tstCnd; } # Not Equal to (=), Z flag is 0 +b2cnd4: "PZ" is b2_0003=0x7 { tstCnd:1 = ($(SIGN) == 0); export tstCnd; } # Positive or zero (0<=) +b2cnd4: "LE" is b2_0003=0x8 { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 1); export tstCnd; } # Equal to or less than (signed value) (>=) +b2cnd4: "O" is b2_0003=0x9 { tstCnd:1 = ($(OVERFLOW) == 1); export tstCnd; } # O flag is 1 +b2cnd4: "GE" is b2_0003=0xa { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 0); export tstCnd; } # Equal to or greater than (signed value) (<=) +b2cnd4: "GT" is b2_0003=0xc { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 0); export tstCnd; } # Greater than (signed value) (<) +b2cnd4: "NO" is b2_0003=0xd { tstCnd:1 = ($(OVERFLOW) == 0); export tstCnd; } # O flag is 0 +b2cnd4: "LT" is b2_0003=0xe { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 1); export tstCnd; } # less than (signed value) (<=) + +# Special case of b2cnd4 where b2_0003=1 (see JCnd) +b2cnd3: "LE" is b2_0002=0x0 { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 1); export tstCnd; } # Equal to or less than (signed value) (>=) +b2cnd3: "O" is b2_0002=0x1 { tstCnd:1 = ($(OVERFLOW) == 1); export tstCnd; } # O flag is 1 +b2cnd3: "GE" is b2_0002=0x2 { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 0); export tstCnd; } # Equal to or greater than (signed value) (<=) +b2cnd3: "GT" is b2_0002=0x4 { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 0); export tstCnd; } # Greater than (signed value) (<) +b2cnd3: "NO" is b2_0002=0x5 { tstCnd:1 = ($(OVERFLOW) == 0); export tstCnd; } # O flag is 0 +b2cnd3: "LT" is b2_0002=0x6 { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 1); export tstCnd; } # less than (signed value) (<=) + +b1cnd3: "LTU" is b1_0002=4 { tstCnd:1 = ($(CARRY) == 0); export tstCnd; } # less than (>), C flag is 0 +b1cnd3: "LEU" is b1_0002=5 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 0); export tstCnd; } # Equal to or less than (>=) +b1cnd3: "NE" is b1_0002=6 { tstCnd:1 = ($(ZERO) == 0); export tstCnd; } # Not Equal to (=), Z flag is 0 +b1cnd3: "PZ" is b1_0002=7 { tstCnd:1 = ($(SIGN) == 0); export tstCnd; } # Positive or zero (0<=) +b1cnd3: "GEU" is b1_0002=0 { tstCnd:1 = ($(CARRY) == 1); export tstCnd; } # Equal to or greater than (<=), C flag is 1 +b1cnd3: "GTU" is b1_0002=1 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 1); export tstCnd; } # Greater than (<) +b1cnd3: "EQ" is b1_0002=2 { tstCnd:1 = ($(ZERO) == 1); export tstCnd; } # Equal to (=), Z flag is 1 +b1cnd3: "N" is b1_0002=3 { tstCnd:1 = ($(SIGN) == 1); export tstCnd; } # Negative (0>) + +# +# Flag bit operand +# +flagBit: "C" is b2_0406=0 { export 0:2; } +flagBit: "D" is b2_0406=1 { export 1:2; } +flagBit: "Z" is b2_0406=2 { export 2:2; } +flagBit: "S" is b2_0406=3 { export 3:2; } +flagBit: "B" is b2_0406=4 { export 4:2; } +flagBit: "O" is b2_0406=5 { export 5:2; } +flagBit: "I" is b2_0406=6 { export 6:2; } +flagBit: "U" is b2_0406=7 { export 7:2; } + +# +# Instruction Constructors +# +### ABS ### +:ABS.B dst4B is (b1_0107=0x3b & b1_size_0=0; b2_0407=0xf) ... & dst4B { + local tmp = dst4B; + $(OVERFLOW) = (tmp == 0x80); + local ztst = (tmp s< 0); + tmp = (zext(ztst) * -tmp) + (zext(!ztst) * tmp); + dst4B = tmp; + setResultFlags(tmp); +} + +# 0111 0110 1111 0100 ABS.B A0 +# 0111 0110 1111 0001 ABS.B R0H +:ABS.B dst4Ax is (b1_0107=0x3b & b1_size_0=0; b2_0407=0xf) & $(DST4AX) { + local tmp = dst4Ax:1; + $(OVERFLOW) = (tmp == 0x80); + local ztst = (tmp s< 0); + tmp = (zext(ztst) * -tmp) + (zext(!ztst) * tmp); + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +:ABS.W dst4W is (b1_0107=0x3b & b1_size_0=1; b2_0407=0xf) ... & dst4W { + local tmp = dst4W; + $(OVERFLOW) = (tmp == 0x8000); + local ztst = (tmp s< 0); + tmp = (zext(ztst) * -tmp) + (zext(!ztst) * tmp); + dst4W = tmp; + setResultFlags(tmp); +} + +### ADC ### + +# (1) ADC.B #simm, dst +:ADC.B srcSimm8, dst4B is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x6) ... & dst4B); srcSimm8 { + tmp:1 = dst4B; + c:1 = $(CARRY); + setAdd3Flags(tmp, srcSimm8, c); + tmp = tmp + srcSimm8 + c; + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) ADC.B #simm, Ax +:ADC.B srcSimm8, dst4Ax is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x6) & $(DST4AX)); srcSimm8 { + tmp:1 = dst4Ax:1; + c:1 = $(CARRY); + setAdd3Flags(tmp, srcSimm8, c); + tmp = tmp + srcSimm8 + c; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) ADC.W #simm, dst +:ADC.W srcSimm16, dst4W is ((b1_0107=0x3b & b1_size_0=1; b2_0407=0x6) ... & dst4W); srcSimm16 { + tmp:2 = dst4W; + c:2 = zext($(CARRY)); + setAdd3Flags(tmp, srcSimm16, c); + tmp = tmp + srcSimm16 + c; + dst4W = tmp; + setResultFlags(tmp); +} + +# (2) ADC.B src, dst +:ADC.B src4B, dst4B_afterSrc4 is (b1_0107=0x58 & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... { + tmp:1 = dst4B_afterSrc4; + src:1 = src4B; + c:1 = $(CARRY); + setAdd3Flags(tmp, src, c); + tmp = tmp + src + c; + dst4B_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (2) ADC.B src, Ax +:ADC.B src4B, dst4Ax is (b1_0107=0x58 & b1_size_0=0) ... & src4B & $(DST4AX) ... { + tmp:1 = dst4Ax:1; + src:1 = src4B; + c:1 = $(CARRY); + setAdd3Flags(tmp, src, c); + tmp = tmp + src + c; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) ADC.W src, dst +:ADC.W src4W, dst4W_afterSrc4 is (b1_0107=0x58 & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... { + tmp:2 = dst4W_afterSrc4; + src:2 = src4W; + c:2 = zext($(CARRY)); + setAdd3Flags(tmp, src, c); + tmp = tmp + src + c; + dst4W_afterSrc4 = tmp; + setResultFlags(tmp); +} + + +### ADCF ### + +:ADCF.B dst4B is (b1_0107=0x3b & b1_size_0=0; b2_0407=0xe) ... & dst4B { + tmp:1 = dst4B; + c:1 = $(CARRY); + setAddFlags(tmp, c); + tmp = tmp + c; + dst4B = tmp; + setResultFlags(tmp); +} + +:ADCF.B dst4Ax is (b1_0107=0x3b & b1_size_0=0; b2_0407=0xe) & $(DST4AX) { + tmp:1 = dst4Ax:1; + c:1 = $(CARRY); + setAddFlags(tmp, c); + tmp = tmp + c; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +:ADCF.W dst4W is (b1_0107=0x3b & b1_size_0=1; b2_0407=0xe) ... & dst4W { + tmp:2 = dst4W; + c:2 = zext($(CARRY)); + setAddFlags(tmp, c); + tmp = tmp + c; + dst4W = tmp; + setResultFlags(tmp); +} + +### ADD ### + +# (1) ADD.B:G #simm, dst +:ADD^".B:G" srcSimm8, dst4B is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x4) ... & dst4B); srcSimm8 { + tmp:1 = dst4B; + setAddFlags(tmp, srcSimm8); + tmp = tmp + srcSimm8; + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) ADD.B:G #simm, Ax +:ADD^".B:G" srcSimm8, dst4Ax is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x4) & $(DST4AX)); srcSimm8 { + tmp:1 = dst4Ax:1; + setAddFlags(tmp, srcSimm8); + tmp = tmp + srcSimm8; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) ADD.W:G #simm, dst +:ADD^".W:G" srcSimm16, dst4W is ((b1_0107=0x3b & b1_size_0=1; b2_0407=0x4) ... & dst4W); srcSimm16 { + tmp:2 = dst4W; + setAddFlags(tmp, srcSimm16); + tmp = tmp + srcSimm16; + dst4W = tmp; + setResultFlags(tmp); +} + +# (2) ADD.B:Q #simm4, dst +:ADD^".B:Q" srcSimm4_0407, dst4B is (b1_0107=0x64 & b1_size_0=0; srcSimm4_0407) ... & dst4B { + tmp:1 = dst4B; + setAddFlags(tmp, srcSimm4_0407); + tmp = tmp + srcSimm4_0407; + dst4B = tmp; + setResultFlags(tmp); +} + +# (2) ADD.B:Q #simm4, Ax +:ADD^".B:Q" srcSimm4_0407, dst4Ax is (b1_0107=0x64 & b1_size_0=0; srcSimm4_0407) & $(DST4AX) { + tmp:1 = dst4Ax:1; + setAddFlags(tmp, srcSimm4_0407); + tmp = tmp + srcSimm4_0407; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) ADD.W:Q #simm4, dst +:ADD^".W:Q" srcSimm4_0407, dst4W is (b1_0107=0x64 & b1_size_0=1; srcSimm4_0407) ... & dst4W { + tmp:2 = dst4W; + imm:2 = sext(srcSimm4_0407); + setAddFlags(tmp, imm); + tmp = tmp + imm; + dst4W = tmp; + setResultFlags(tmp); +} + +# (3) ADD.B:S #imm, dst +:ADD^".B:S" srcSimm8, dst3B_afterDsp8 is (b1_0307=0x10; srcSimm8) ... & $(DST3B_AFTER_DSP8) { + tmp:1 = dst3B_afterDsp8; + setAddFlags(tmp, srcSimm8); + tmp = tmp + srcSimm8; + dst3B_afterDsp8 = tmp; + setResultFlags(tmp); +} + +# (4) ADD.B:G src, dst +:ADD^".B:G" src4B, dst4B_afterSrc4 is (b1_0107=0x50 & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... { + tmp:1 = dst4B_afterSrc4; + src:1 = src4B; + setAddFlags(tmp, src); + tmp = tmp + src; + dst4B_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (4) ADD.B:G src, Ax +:ADD^".B:G" src4B, dst4Ax is (b1_0107=0x50 & b1_size_0=0) ... & src4B & $(DST4AX) ... { + tmp:1 = dst4Ax:1; + src:1 = src4B; + setAddFlags(tmp, src); + tmp = tmp + src; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (4) ADD.W:G src, dst +:ADD^".W:G" src4W, dst4W_afterSrc4 is (b1_0107=0x50 & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... { + tmp:2 = dst4W_afterSrc4; + src:2 = src4W; + setAddFlags(tmp, src); + tmp = tmp + src; + dst4W_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (5) ADD.B:S src, R0H/R0L +:ADD^".B:S" dst2B, b1_2_reg8 is (b1_0307=0x4 & b1_2_reg8) ... & dst2B { + src:1 = dst2B; + setAddFlags(b1_2_reg8, src); + b1_2_reg8 = b1_2_reg8 + src; + setResultFlags(b1_2_reg8); +} + +# (6) ADD.B:G #simm, SP +:ADD^".B:G" srcSimm8, SP is SP & b1_0107=0x3e & b1_size_0=0; b2_0007=0xeb; srcSimm8 { + imm:2 = sext(srcSimm8); + setAddFlags(SP, imm); + SP = SP + imm; + setResultFlags(SP); +} + +# (6) ADD.W:G #simm, SP +:ADD^".W:G" srcSimm16, SP is SP & b1_0107=0x3e & b1_size_0=1; b2_0007=0xeb; srcSimm16 { + setAddFlags(SP, srcSimm16); + SP = SP + srcSimm16; + setResultFlags(SP); +} + +# (7) ADD.W:Q #simm, SP +:ADD^".B:Q" srcSimm4_0003, SP is SP & b1_0007=0x7d; b2_0407=0xb & srcSimm4_0003 { + imm:2 = sext(srcSimm4_0003); + setAddFlags(SP, imm); + SP = SP + imm; + setResultFlags(SP); +} + +### ADJNZ ### + +:ADJNZ.B srcSimm4_0407, dst4B is ((b1_0107=0x7c & b1_size_0=0; srcSimm4_0407) ... & dst4B); rel8offset2 { + tmp:1 = dst4B + srcSimm4_0407; + dst4B = tmp; + if (tmp != 0) goto rel8offset2; +} + +:ADJNZ.B srcSimm4_0407, dst4Ax is ((b1_0107=0x7c & b1_size_0=0; srcSimm4_0407) & $(DST4AX)); rel8offset2 { + tmp:1 = dst4Ax:1 + srcSimm4_0407; + dst4Ax = zext(tmp); + if (tmp != 0) goto rel8offset2; +} + +:ADJNZ.W srcSimm4_0407, dst4W is ((b1_0107=0x7c & b1_size_0=1; srcSimm4_0407) ... & dst4W); rel8offset2 { + tmp:2 = dst4W + sext(srcSimm4_0407); + dst4W = tmp; + if (tmp != 0) goto rel8offset2; +} + +### AND ### + +# (1) AND.B:G #imm, dst +:AND^".B:G" srcImm8, dst4B is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x2) ... & dst4B); srcImm8 { + tmp:1 = dst4B & srcImm8; + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) AND.B:G #imm, Ax +:AND^".B:G" srcImm8, dst4Ax is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x2) & $(DST4AX)); srcImm8 { + tmp:1 = dst4Ax:1 & srcImm8; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) AND.W:G #imm, dst +:AND^".W:G" srcImm16, dst4W is ((b1_0107=0x3b & b1_size_0=1; b2_0407=0x2) ... & dst4W); srcImm16 { + tmp:2 = dst4W & srcImm16; + dst4W = tmp; + setResultFlags(tmp); +} + +# (2) AND.B:S #imm, dst +:AND^".B:S" srcImm8, dst3B_afterDsp8 is (b1_0307=0x12; srcImm8) ... & $(DST3B_AFTER_DSP8) { + tmp:1 = dst3B_afterDsp8 & srcImm8; + dst3B_afterDsp8 = tmp; + setResultFlags(tmp); +} + +# (3) AND.B:G src, dst +:AND^".B:G" src4B, dst4B_afterSrc4 is (b1_0107=0x48 & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... { + tmp:1 = dst4B_afterSrc4 & src4B; + dst4B_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (3) AND.B:G src, Ax +:AND^".B:G" src4B, dst4Ax is (b1_0107=0x48 & b1_size_0=0) ... & src4B & $(DST4AX) ... { + tmp:1 = dst4Ax:1 & src4B; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (3) AND.W:G src, dst +:AND^".W:G" src4W, dst4W_afterSrc4 is (b1_0107=0x48 & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... { + tmp:2 = dst4W_afterSrc4 & src4W; + dst4W_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (4) AND.B:S src, R0L/R0H +:AND^".B:S" dst2B, b1_2_reg8 is (b1_0307=0x2 & b1_2_reg8) ... & dst2B { + tmp:1 = dst2B & b1_2_reg8; + b1_2_reg8 = tmp; + setResultFlags(tmp); +} + +### BAND ### + +# BAND bit,Rx/Ax +:BAND regBit, regBase is (b1_0007=0x7e; b2_0407=0x4 & b2_d4_3=0) ... & regBase ... & regBit { + bitValue:2 = (regBase >> regBit) & 1; + $(CARRY) = $(CARRY) & bitValue:1; +} + +# BAND [Ax] +:BAND memBaseAx is (b1_0007=0x7e; b2_0407=0x4 & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + bitValue:1 = (val >> bit) & 1; + $(CARRY) = $(CARRY) & bitValue; +} + +# BAND bit,base +:BAND memBit, memBase is (b1_0007=0x7e; b2_0407=0x4) ... & memBase & memBit { + bitValue:1 = (memBase >> memBit) & 1; + $(CARRY) = $(CARRY) & bitValue; +} + + +### BCLR ### + +# (1) BCLR:G bit,Rx/Ax +:BCLR^":G" regBit, regBase is (b1_0007=0x7e; b2_0407=0x8 & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = ~(1 << regBit); + regBase = regBase & mask; +} + +# (1) BCLR:G [Ax] +:BCLR^":G" memBaseAx is (b1_0007=0x7e; b2_0407=0x8 & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = ~(1 << bit); + *:1 ptr = val & mask; +} + +# (1) BCLR:G bit,base +:BCLR^":G" memBit, memBase is (b1_0007=0x7e; b2_0407=0x8) ... & memBase & memBit { + mask:1 = ~(1 << memBit); + memBase = memBase & mask; +} + +# (2) BCLR:S bit,base:11[SB] +:BCLR^":S" b1_bit, memBase11 is (b1_0307=0x08 & b1_bit) ... & memBase11 { + mask:1 = ~(1 << b1_bit); + memBase11 = memBase11 & mask; +} + +### BMcnd ### + +# (1) BMcnd bit,Rx/Ax +:BM^cnd8 regBit, regBase is ((b1_0007=0x7e; b2_0407=0x2 & b2_d4_3=0) ... & regBase ... & regBit); cnd8 { + mask:2 = ~(1 << regBit); + regBase = ((zext(cnd8) << regBit) | (regBase & mask)); +} + +# (1) BMcnd [Ax] +:BM^cnd8 memBaseAx is ((b1_0007=0x7e; b2_0407=0x2 & b2_d4_13=0x3) & memBaseAx); cnd8 { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = ~(1 << bit); + *:1 ptr = ((cnd8 << bit) | (val & mask)); +} + +# (1) BMcnd bit,base +:BM^cnd8 memBit, memBase is ((b1_0007=0x7e; b2_0407=0x2) ... & memBase & memBit); cnd8 { + mask:1 = ~(1 << memBit); + memBase = ((cnd8 << memBit) | (memBase & mask)); +} + +# (2) BMcnd C +:BM^b2cnd4 "C" is b1_0007=0x7d; b2_0407=0xd & b2cnd4 { + $(CARRY) = b2cnd4; +} + +### BNAND ### + +# BNAND bit,Rx/Ax +:BNAND regBit, regBase is (b1_0007=0x7e; b2_0407=0x5 & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + bitValue:2 = (regBase & mask); + $(CARRY) = $(CARRY) && (bitValue == 0); +} + +# BNAND [Ax] +:BNAND memBaseAx is (b1_0007=0x7e; b2_0407=0x5 & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + bitValue:1 = (val & mask); + $(CARRY) = $(CARRY) && (bitValue == 0); +} + +# BNAND bit,base +:BNAND memBit, memBase is (b1_0007=0x7e; b2_0407=0x5) ... & memBase & memBit { + mask:1 = (1 << memBit); + bitValue:1 = (memBase & mask); + $(CARRY) = $(CARRY) && (bitValue == 0); +} + +### BNOR ### + +# BNOR bit,Rx/Ax +:BNOR regBit, regBase is (b1_0007=0x7e; b2_0407=0x7 & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + bitValue:2 = (regBase & mask); + $(CARRY) = $(CARRY) || (bitValue == 0); +} + +# BNOR [Ax] +:BNOR memBaseAx is (b1_0007=0x7e; b2_0407=0x7 & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + bitValue:1 = (val & mask); + $(CARRY) = $(CARRY) || (bitValue == 0); +} + +# BNOR bit,base +:BNOR memBit, memBase is (b1_0007=0x7e; b2_0407=0x7) ... & memBase & memBit { + mask:1 = (1 << memBit); + bitValue:1 = (memBase & mask); + $(CARRY) = $(CARRY) || (bitValue == 0); +} + +### BNOT ### + +# (1) BNOT:G bit,Rx/Ax +:BNOT^":G" regBit, regBase is (b1_0007=0x7e; b2_0407=0xa & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + bitValue:2 = (~regBase & mask); + regBase = (regBase & ~mask) | bitValue; +} + +# (1) BNOT:G [Ax] +:BNOT^":G" memBaseAx is (b1_0007=0x7e; b2_0407=0xa & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + bitValue:1 = (~val & mask); + *:1 ptr = (val & ~mask) | bitValue; +} + +# (1) BNOT:G bit,base +:BNOT^":G" memBit, memBase is (b1_0007=0x7e; b2_0407=0xa) ... & memBase & memBit { + mask:1 = (1 << memBit); + val:1 = memBase; + bitValue:1 = (~val & mask); + memBase = (val & ~mask) | bitValue; +} + +# (2) BNOT:S bit,base:11[SB] +:BNOT^":S" b1_bit, memBase11 is (b1_0307=0x0a & b1_bit) ... & memBase11 { + mask:1 = (1 << b1_bit); + val:1 = memBase11; + bitValue:1 = (~val & mask); + memBase11 = (val & ~mask) | bitValue; +} + +### BNTST ### + +# BNTST bit,Rx/Ax +:BNTST regBit, regBase is (b1_0007=0x7e; b2_0407=0x3 & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + bitValue:2 = (regBase & mask); + z:1 = (bitValue == 0); + $(CARRY) = z; + $(ZERO) = z; +} + +# BNTST [Ax] +:BNTST memBaseAx is (b1_0007=0x7e; b2_0407=0x3 & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + bitValue:1 = (val & mask); + z:1 = (bitValue == 0); + $(CARRY) = z; + $(ZERO) = z; +} + +# BNTST bit,base +:BNTST memBit, memBase is (b1_0007=0x7e; b2_0407=0x3) ... & memBase & memBit { + mask:1 = (1 << memBit); + bitValue:1 = (memBase & mask); + z:1 = (bitValue == 0); + $(CARRY) = z; + $(ZERO) = z; +} + +### BNXOR ### + +# BNXOR bit,Rx/Ax +:BNXOR regBit, regBase is (b1_0007=0x7e; b2_0407=0xd & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + bitValue:2 = (regBase & mask); + $(CARRY) = $(CARRY) ^ (bitValue == 0); +} + +# BNXOR [Ax] +:BNXOR memBaseAx is (b1_0007=0x7e; b2_0407=0xd & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + bitValue:1 = (val & mask); + $(CARRY) = $(CARRY) ^ (bitValue == 0); +} + +# BNXOR bit,base +:BNXOR memBit, memBase is (b1_0007=0x7e; b2_0407=0xd) ... & memBase & memBit { + mask:1 = (1 << memBit); + bitValue:1 = (memBase & mask); + $(CARRY) = $(CARRY) ^ (bitValue == 0); +} + +### BOR ### + +# BOR bit,Rx/Ax +:BOR regBit, regBase is (b1_0007=0x7e; b2_0407=0x6 & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + bitValue:2 = (regBase & mask); + $(CARRY) = $(CARRY) || (bitValue != 0); +} + +# BOR [Ax] +:BOR memBaseAx is (b1_0007=0x7e; b2_0407=0x6 & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + bitValue:1 = (val & mask); + $(CARRY) = $(CARRY) || (bitValue != 0); +} + +# BOR bit,base +:BOR memBit, memBase is (b1_0007=0x7e; b2_0407=0x6) ... & memBase & memBit { + mask:1 = (1 << memBit); + bitValue:1 = (memBase & mask); + $(CARRY) = $(CARRY) || (bitValue != 0); +} + +### BRK ### + +:BRK is b1_0007=0x0 { + # most likely not necessary to model break behavior + Break(); +} + +### BSET ### + +# (1) BSET:G bit,Rx/Ax +:BSET^":G" regBit, regBase is (b1_0007=0x7e; b2_0407=0x9 & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + regBase = regBase | mask; +} + +# (1) BSET:G [Ax] +:BSET^":G" memBaseAx is (b1_0007=0x7e; b2_0407=0x9 & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + *:1 ptr = val | mask; +} + +# (1) BSET:G bit,base +:BSET^":G" memBit, memBase is (b1_0007=0x7e; b2_0407=0x9) ... & memBase & memBit { + mask:1 = (1 << memBit); + memBase = memBase | mask; +} + +# (2) BSET:S bit,base:11[SB] +:BSET^":S" b1_bit, memBase11 is (b1_0307=0x09 & b1_bit) ... & memBase11 { + mask:1 = (1 << b1_bit); + memBase11 = memBase11 | mask; +} + +### BTST ### + +# (1) BTST:G bit,Rx/Ax +:BTST^":G" regBit, regBase is (b1_0007=0x7e; b2_0407=0xb & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + bitValue:2 = (regBase & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; +} + +# (1) BTST:G [Ax] +:BTST^":G" memBaseAx is (b1_0007=0x7e; b2_0407=0xb & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + bitValue:1 = (val & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; +} + +# (1) BTST:G bit,base +:BTST^":G" memBit, memBase is (b1_0007=0x7e; b2_0407=0xb) ... & memBase & memBit { + mask:1 = (1 << memBit); + bitValue:1 = (memBase & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; +} + +# (2) BTST:S bit,base:11[SB] +:BTST^":S" b1_bit, memBase11 is (b1_0307=0x0b & b1_bit) ... & memBase11 { + mask:1 = (1 << b1_bit); + bitValue:1 = (memBase11 & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; +} + +### BTSTC ### + +# BTSTC bit,Rx/Ax +:BTSTC regBit, regBase is (b1_0007=0x7e; b2_0407=0x0 & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + bitValue:2 = (regBase & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; + regBase = regBase & ~mask; +} + +# BTSTC [Ax] +:BTSTC memBaseAx is (b1_0007=0x7e; b2_0407=0x0 & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + bitValue:1 = (val & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; + *:1 ptr = val & ~mask; +} + +# BTSTC bit,base +:BTSTC memBit, memBase is (b1_0007=0x7e; b2_0407=0x0) ... & memBase & memBit { + mask:1 = (1 << memBit); + val:1 = memBase; + bitValue:1 = (val & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; + memBase = val & ~mask; +} + +### BTSTS ### + +# BTSTS bit,Rx/Ax +:BTSTS regBit, regBase is (b1_0007=0x7e; b2_0407=0x1 & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + bitValue:2 = (regBase & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; + regBase = regBase | mask; +} + +# BTSTS [Ax] +:BTSTS memBaseAx is (b1_0007=0x7e; b2_0407=0x1 & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + bitValue:1 = (val & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; + *:1 ptr = val | mask; +} + +# BTSTS bit,base +:BTSTS memBit, memBase is (b1_0007=0x7e; b2_0407=0x1) ... & memBase & memBit { + mask:1 = (1 << memBit); + val:1 = memBase; + bitValue:1 = (val & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; + memBase = val | mask; +} + +### BXOR ### + +# BXOR bit,Rx/Ax +:BXOR regBit, regBase is (b1_0007=0x7e; b2_0407=0xc & b2_d4_3=0) ... & regBase ... & regBit { + mask:2 = (1 << regBit); + bitValue:2 = (regBase & mask); + $(CARRY) = $(CARRY) ^ (bitValue != 0); +} + +# BXOR [Ax] +:BXOR memBaseAx is (b1_0007=0x7e; b2_0407=0xc & b2_d4_13=0x3) & memBaseAx { + ptr:3 = zext(memBaseAx >> 3); + bit:1 = memBaseAx:1 & 0x7; + val:1 = *:1 ptr; + mask:1 = (1 << bit); + bitValue:1 = (val & mask); + $(CARRY) = $(CARRY) ^ (bitValue != 0); +} + +# BXOR bit,base +:BXOR memBit, memBase is (b1_0007=0x7e; b2_0407=0xc) ... & memBase & memBit { + mask:1 = (1 << memBit); + bitValue:1 = (memBase & mask); + $(CARRY) = $(CARRY) ^ (bitValue != 0); +} + +### CMP ### + +# (1) CMP.B:G #simm, dst +:CMP^".B:G" srcSimm8, dst4B is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x8) ... & dst4B); srcSimm8 { + tmp:1 = dst4B; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + setResultFlags(tmp); +} + +# (1) CMP.B:G #simm, Ax +:CMP^".B:G" srcSimm8, dst4Ax is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x8) & $(DST4AX)); srcSimm8 { + tmp:1 = dst4Ax:1; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + setResultFlags(tmp); +} + +# (1) CMP.W:G #simm, dst +:CMP^".W:G" srcSimm16, dst4W is ((b1_0107=0x3b & b1_size_0=1; b2_0407=0x8) ... & dst4W); srcSimm16 { + tmp:2 = dst4W; + setSubtractFlags(tmp, srcSimm16); + tmp = tmp - srcSimm16; + setResultFlags(tmp); +} + +# (2) CMP.B:Q #simm4, dst +:CMP^".B:Q" srcSimm4_0407, dst4B is (b1_0107=0x68 & b1_size_0=0; srcSimm4_0407) ... & dst4B { + tmp:1 = dst4B; + setSubtractFlags(tmp, srcSimm4_0407); + tmp = tmp - srcSimm4_0407; + setResultFlags(tmp); +} + +# (2) CMP.B:Q #simm4, Ax +:CMP^".B:Q" srcSimm4_0407, dst4Ax is (b1_0107=0x68 & b1_size_0=0; srcSimm4_0407) & $(DST4AX) { + tmp:1 = dst4Ax:1; + setSubtractFlags(tmp, srcSimm4_0407); + tmp = tmp - srcSimm4_0407; + setResultFlags(tmp); +} + +# (2) CMP.W:Q #simm4, dst +:CMP^".W:Q" srcSimm4_0407, dst4W is (b1_0107=0x68 & b1_size_0=1; srcSimm4_0407) ... & dst4W { + tmp:2 = dst4W; + imm:2 = sext(srcSimm4_0407); + setSubtractFlags(tmp, imm); + tmp = tmp - imm; + setResultFlags(tmp); +} + +# (3) CMP.B:S #imm, dst +:CMP^".B:S" srcSimm8, dst3B_afterDsp8 is (b1_0307=0x1c; srcSimm8) ... & $(DST3B_AFTER_DSP8) { + tmp:1 = dst3B_afterDsp8; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + setResultFlags(tmp); +} + +# (4) CMP.B:G src, dst +:CMP^".B:G" src4B, dst4B_afterSrc4 is (b1_0107=0x60 & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... { + tmp:1 = dst4B_afterSrc4; + src:1 = src4B; + setSubtractFlags(tmp, src); + tmp = tmp - src; + setResultFlags(tmp); +} + +# (4) CMP.B:G src, Ax +:CMP^".B:G" src4B, dst4Ax is (b1_0107=0x60 & b1_size_0=0) ... & src4B & $(DST4AX) ... { + tmp:1 = dst4Ax:1; + src:1 = src4B; + setSubtractFlags(tmp, src); + tmp = tmp - src; + setResultFlags(tmp); +} + +# (4) CMP.W:G src, dst +:CMP^".W:G" src4W, dst4W_afterSrc4 is (b1_0107=0x60 & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... { + tmp:2 = dst4W_afterSrc4; + src:2 = src4W; + setSubtractFlags(tmp, src); + tmp = tmp - src; + setResultFlags(tmp); +} + +# (5) CMP.B:S src, R0H/R0L +:CMP^".B:S" dst2B, b1_2_reg8 is (b1_0307=0x7 & b1_2_reg8) ... & dst2B { + src:1 = dst2B; + setSubtractFlags(b1_2_reg8, src); + b1_2_reg8 = b1_2_reg8 - src; + setResultFlags(b1_2_reg8); +} + +### DADC ### + +# (1) DADC.B #imm, R0L +:DADC.B srcImm8, R0L is R0L & b1_0007=0x7c; b2_0007=0xee; srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(R0L); + tmp:2 = DecimalAddWithCarry(src, dst); + R0L = tmp:1; + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (2) DADC.W #imm, R0 +:DADC.W srcImm16, R0 is R0 & b1_0007=0x7d; b2_0007=0xee; srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(R0); + tmp:4 = DecimalAddWithCarry(src, dst); + R0 = tmp:2; + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +# (3) DADC.B R0H, R0L +:DADC.B R0H, R0L is R0H & R0L & b1_0007=0x7c; b2_0007=0xe6 { + src:2 = zext(R0H); + dst:2 = zext(R0L); + tmp:2 = DecimalAddWithCarry(src, dst); + R0L = tmp:1; + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (4) DADC.W R1, R0 +:DADC.W R1, R0 is R1 & R0 & b1_0007=0x7d; b2_0007=0xe6 { + src:4 = zext(R1); + dst:4 = zext(R0); + tmp:4 = DecimalAddWithCarry(src, dst); + R0 = tmp:2; + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +### DADD ### + +# (1) DADD.B #imm, R0L +:DADD.B srcImm8, R0L is R0L & b1_0007=0x7c; b2_0007=0xec; srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(R0L); + tmp:2 = DecimalAdd(src, dst); + R0L = tmp:1; + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (2) DADD.W #imm, R0 +:DADD.W srcImm16, R0 is R0 & b1_0007=0x7d; b2_0007=0xec; srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(R0); + tmp:4 = DecimalAdd(src, dst); + R0 = tmp:2; + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +# (3) DADD.B R0H, R0L +:DADD.B R0H, R0L is R0H & R0L & b1_0007=0x7c; b2_0007=0xe4 { + src:2 = zext(R0H); + dst:2 = zext(R0L); + tmp:2 = DecimalAdd(src, dst); + R0L = tmp:1; + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (4) DADD.W R1, R0 +:DADD.W R1, R0 is R1 & R0 & b1_0007=0x7d; b2_0007=0xe4 { + src:4 = zext(R1); + dst:4 = zext(R0); + tmp:4 = DecimalAdd(src, dst); + R0 = tmp:2; + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +### DEC ### + +# (1) DEC.B dst +:DEC.B dst3B is b1_0307=0x15 ... & $(DST3B) { + dst:1 = dst3B; + setSubtractFlags(dst, 1); + dst = dst - 1; + dst3B = dst; + setResultFlags(dst); +} + +# (2) DEC.W dst +:DEC.W b1_3_regAx is b1_0407=0xf & b1_0002=0x2 & b1_3_regAx { + dst:2 = b1_3_regAx; + setSubtractFlags(dst, 1); + dst = dst - 1; + b1_3_regAx = dst; + setResultFlags(dst); +} + +### DIV ### + +# (1) DIV.B #imm +:DIV.B srcSimm8 is b1_0107=0x3e & b1_size_0=0; b2_0007=0xe1; srcSimm8 { + d:2 = sext(srcSimm8); + q:2 = R0 s/ d; + r:2 = R0 s% d; # remainder has same sign as R0 (dividend) + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (1) DIV.W #imm +:DIV.W srcSimm16 is b1_0107=0x3e & b1_size_0=1; b2_0007=0xe1; srcSimm16 { + d:4 = sext(srcSimm16); + q:4 = R2R0 s/ d; + r:4 = R2R0 s% d; # remainder has same sign as R0 (dividend) + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIV.B src +:DIV.B dst4B is (b1_0107=0x3b & b1_size_0=0; b2_0407=0xd) ... & dst4B { + d:2 = sext(dst4B); + q:2 = R0 s/ d; + r:2 = R0 s% d; # remainder has same sign as R0 (dividend) + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIV.W src +:DIV.W dst4W is (b1_0107=0x3b & b1_size_0=1; b2_0407=0xd) ... & dst4W { + d:4 = sext(dst4W); + q:4 = R2R0 s/ d; + r:4 = R2R0 s% d; # remainder has same sign as R0 (dividend) + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +### DIVU ### + +# (1) DIVU.B #imm +:DIVU.B srcImm8 is b1_0107=0x3e & b1_size_0=0; b2_0007=0xe0; srcImm8 { + d:2 = zext(srcImm8); + q:2 = R0 / d; + r:2 = R0 % d; + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (1) DIVU.W #imm +:DIVU.W srcImm16 is b1_0107=0x3e & b1_size_0=1; b2_0007=0xe0; srcImm16 { + d:4 = zext(srcImm16); + q:4 = R2R0 / d; + r:4 = R2R0 % d; + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIVU.B src +:DIVU.B dst4B is (b1_0107=0x3b & b1_size_0=0; b2_0407=0xc) ... & dst4B { + d:2 = zext(dst4B); + q:2 = R0 / d; + r:2 = R0 % d; + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIVU.W src +:DIVU.W dst4W is (b1_0107=0x3b & b1_size_0=1; b2_0407=0xc) ... & dst4W { + d:4 = zext(dst4W); + q:4 = R2R0 / d; + r:4 = R2R0 % d; + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +### DIVX ### + +# (1) DIVX.B #imm +:DIVX.B srcSimm8 is b1_0107=0x3e & b1_size_0=0; b2_0007=0xe3; srcSimm8 { + d:2 = sext(srcSimm8); + q:2 = R0 s/ d; + r:2 = R0 s% d; + + #according to the manual the remainder has the same sign as the quotient + differ:1 = (r s< 0) != (d s< 0); + r = (zext(differ) * (-r)) + (zext(!differ) * r); + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (1) DIVX.W #imm +:DIVX.W srcSimm16 is b1_0107=0x3e & b1_size_0=1; b2_0007=0xe3; srcSimm16 { + d:4 = sext(srcSimm16); + q:4 = R2R0 s/ d; + r:4 = R2R0 s% d; + + #according to the manual the remainder has the same sign as the quotient + differ:1 = (r s< 0) != (d s< 0); + r = (zext(differ) * (-r)) + (zext(!differ) * r); + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIVX.B src +:DIVX.B dst4B is (b1_0107=0x3b & b1_size_0=0; b2_0407=0x9) ... & dst4B { + d:2 = sext(dst4B); + q:2 = R0 s/ d; + r:2 = R0 s% d; + + #according to the manual the remainder has the same sign as the quotient + differ:1 = (r s< 0) != (d s< 0); + r = (zext(differ) * (-r)) + (zext(!differ) * r); + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIVX.W src +:DIVX.W dst4W is (b1_0107=0x3b & b1_size_0=1; b2_0407=0x9) ... & dst4W { + d:4 = sext(dst4W); + q:4 = R2R0 s/ d; + r:4 = R2R0 s% d; + + #according to the manual the remainder has the same sign as the quotient + differ:1 = (r s< 0) != (d s< 0); + r = (zext(differ) * (-r)) + (zext(!differ) * r); + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +### DSBB ### + +# (1) DSBB.B #imm8, R0L +:DSBB.B srcImm8, R0L is R0L & b1_0007=0x7c; b2_0007=0xef; srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(R0L); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:2 = DecimalSubtractWithBorrow(dst, src); + R0L = tmp:1; + setResultFlags(tmp:1); +} + +# (2) DSBB.W #imm16, R0 +:DSBB.W srcImm16, R0 is R0 & b1_0007=0x7d; b2_0007=0xef; srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(R0); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:4 = DecimalSubtractWithBorrow(dst, src); + R0 = tmp:2; + setResultFlags(tmp:2); +} + +# (3) DSBB.B R0H, R0L +:DSBB.B R0H, R0L is R0H & R0L & b1_0007=0x7c; b2_0007=0xe7 { + src:2 = zext(R0H); + dst:2 = zext(R0L); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:2 = DecimalSubtractWithBorrow(dst, src); + R0L = tmp:1; + setResultFlags(tmp:1); +} + +# (4) DSBB.W R1, R0 +:DSBB.W R1, R0 is R0 & R1 & b1_0007=0x7d; b2_0007=0xe7 { + src:4 = zext(R1); + dst:4 = zext(R0); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:4 = DecimalSubtractWithBorrow(dst, src); + R0 = tmp:2; + setResultFlags(tmp:2); +} + +### DSUB ### + +# (1) DSUB.B #imm8, R0L +:DSUB.B srcImm8, R0L is R0L & b1_0007=0x7c; b2_0007=0xed; srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(R0L); + $(CARRY) = (dst >= src); + tmp:2 = DecimalSubtract(dst, src); + R0L = tmp:1; + setResultFlags(tmp:1); +} + +# (2) DSUB.W #imm16, R0 +:DSUB.W srcImm16, R0 is R0 & b1_0007=0x7d; b2_0007=0xed; srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(R0); + $(CARRY) = (dst >= src); + tmp:4 = DecimalSubtract(dst, src); + R0 = tmp:2; + setResultFlags(tmp:2); +} + +# (3) DSUB.B R0H, R0L +:DSUB.B R0H, R0L is R0H & R0L & b1_0007=0x7c; b2_0007=0xe5 { + src:2 = zext(R0H); + dst:2 = zext(R0L); + $(CARRY) = (dst >= src); + tmp:2 = DecimalSubtract(dst, src); + R0L = tmp:1; + setResultFlags(tmp:1); +} + +# (4) DSUB.W R1, R0 +:DSUB.W R1, R0 is R0 & R1 & b1_0007=0x7d; b2_0007=0xe5 { + src:4 = zext(R1); + dst:4 = zext(R0); + $(CARRY) = (dst >= src); + tmp:4 = DecimalSubtract(dst, src); + R0 = tmp:2; + setResultFlags(tmp:2); +} + +### ENTER ### + +:ENTER srcImm8 is b1_0007=0x7c; b2_0007=0xf2; srcImm8 { + push2(FB); + FB = SP; + SP = SP - zext(srcImm8); +} + +### EXITD ### + +:EXITD is b1_0007=0x7d; b2_0007=0xf2 { + SP = FB; + pop2(FB); + pc:3 = 0; + pop3(pc); + return [pc]; +} + +### EXTS ### + +# (1) EXTS.B dst +:EXTS.B dst4B is (b1_0007=0x7c; b2_0407=0x6) ... & dst4B & dst4W { + tmp:2 = sext(dst4B); + dst4W = tmp; + setResultFlags(tmp); +} + +# (1) EXTS.B Ax +:EXTS.B dst4Ax is (b1_0007=0x7c; b2_0407=0x6) & $(DST4AX) { + tmp:2 = sext(dst4Ax:1); + dst4Ax = tmp; + setResultFlags(tmp); +} + +# (2) EXTS.W R0 +:EXTS.W R0 is R0 & b1_0007=0x7c; b2_0007=0xf3 { + tmp:4 = sext(R0); + R2R0 = tmp; + setResultFlags(tmp); +} + +### FCLR ### + +:FCLR flagBit is b1_0007=0xeb; b2_0707=0 & flagBit & b2_0003=0x5 { + mask:2 = ~(1 << flagBit); + FLG = FLG & mask; +} + +### FSET ### + +:FSET flagBit is b1_0007=0xeb; b2_0707=0 & flagBit & b2_0003=0x4 { + mask:2 = (1 << flagBit); + FLG = FLG | mask; +} + +### INC ### + +# (1) INC.B dst +:INC.B dst3B is b1_0307=0x14 ... & $(DST3B) { + tmp:1 = dst3B + 1; + dst3B = tmp; + setResultFlags(tmp); +} + +# (2) INC.W dst +:INC.W b1_3_regAx is b1_0407=0xb & b1_0002=0x2 & b1_3_regAx { + tmp:2 = b1_3_regAx + 1; + b1_3_regAx = tmp; + setResultFlags(tmp); +} + +### INT ### + +:INT srcIntNum is b1_0007=0xeb; imm8_0607=3 & srcIntNum { + push1(FLG:1); + next:3 = inst_next; + push3(next); + ptr3:3 = (INTB + (zext(srcIntNum) * 0x4)); + pc:3 = *:3 ptr3; + $(STACK_SEL) = ((srcIntNum > 0x1f) * $(STACK_SEL)); + $(INTERRUPT) = 0x0; + $(DEBUG) = 0x0; + call [pc]; +} + +##### INTO ##### + +:INTO is b1_0007=0xf6 { + if ($(OVERFLOW) == 0) goto inst_next; + push1(FLG:1); + next:3 = inst_next; + push3(next); + $(STACK_SEL) = 0; + $(INTERRUPT) = 0x0; + $(DEBUG) = 0x0; + call 0x0fffe0; +} + +### JCnd ### + +# (1) JCnd3 dsp8 +:J^b1cnd3 rel8offset1 is b1_0307=0x0d & b1cnd3; rel8offset1 { + if (b1cnd3) goto rel8offset1; +} + +# (2) JCnd4 dsp8 +:J^b2cnd3 rel8offset2 is b1_0007=0x7d; b2_0407=0xc & b2_0303=1 & b2cnd3; rel8offset2 { + if (b2cnd3) goto rel8offset2; +} + +### JMP ### + +# (1) JMP.S dsp3 +:JMP.S rel3offset2 is b1_0307=0x0c & rel3offset2 { + goto rel3offset2; +} + +# (2) JMP.B dsp8 +:JMP.B rel8offset1 is b1_0007=0xfe; rel8offset1 { + goto rel8offset1; +} + +# (3) JMP.W dsp16 +:JMP.W rel16offset1 is b1_0007=0xf4; rel16offset1 { + goto rel16offset1; +} + +# (4) JMP.A abs20 +:JMP.A abs20offset is b1_0007=0xfc; abs20offset { + goto abs20offset; +} + +### JMPI ### + +# JMPI.W dst +:JMPI.W reloffset_dst4W is (b1_0007=0x7d; b2_0407=0x2) ... & reloffset_dst4W { + goto reloffset_dst4W; +} + +# JMPI.A dst (dst=register) +:JMPI.A reloffset_dst4L is (b1_0007=0x7d; b2_0407=0x0) ... & reloffset_dst4L { + goto reloffset_dst4L; +} + +# JMPI.A dst (dst=memory) +:JMPI.A reloffset_dst4T is (b1_0007=0x7d; b2_0407=0x0) ... & reloffset_dst4T { + goto reloffset_dst4T; +} + +### JMPS ### + +:JMPS srcImm8 is b1_0007=0xee; srcImm8 { + # 18 <= srcImm8 <= 255 (range restriction not enforced by pattern match) + ptr:3 = 0x0ffffe - (zext(srcImm8) << 1); + pc:3 = 0x0f0000 | zext(*:2 ptr); + goto [pc]; +} + +### JSR ### + +:JSR.W rel16offset1 is b1_0007=0xf5; rel16offset1 { + next:3 = inst_next; + push3(next); + call rel16offset1; +} + +:JSR.A abs20offset is b1_0007=0xfd; abs20offset { + next:3 = inst_next; + push3(next); + call abs20offset; +} + +### JSRI ### + +# JSRI.W dst +:JSRI.W reloffset_dst4W is (b1_0007=0x7d; b2_0407=0x3) ... & reloffset_dst4W { + next:3 = inst_next; + push3(next); + call reloffset_dst4W; +} + +# JSRI.A dst (dst=register) +:JSRI.A dst4L is (b1_0007=0x7d; b2_0407=0x1) ... & dst4L { + next:3 = inst_next; + push3(next); + pc:3 = dst4L:3; + call [pc]; +} + +# JSRI.A dst (dst=memory) +:JSRI.A dst4T is (b1_0007=0x7d; b2_0407=0x1) ... & $(DST4T) { + next:3 = inst_next; + push3(next); + pc:3 = dst4T; + call [pc]; +} + +### JSRS ### + +:JSRS srcImm8 is b1_0007=0xef; srcImm8 { + # 18 <= srcImm8 <= 255 (range restriction not enforced by pattern match) + next:3 = inst_next; + push3(next); + ptr:3 = 0x0ffffe - (zext(srcImm8) << 1); + pc:3 = 0x0f0000 | zext(*:2 ptr); + call [pc]; +} + +### LDC ### + +:LDC srcImm16, b2_creg16 is b1_0007=0xeb; b2_0707=0 & b2_creg16 & b2_0003=0x0; srcImm16 { + b2_creg16 = srcImm16; +} + +:LDC dst4W, b2_creg16 is (b1_0007=0x7a; b2_0707=1 & b2_creg16) ... & dst4W { + b2_creg16 = dst4W; +} + +### LDCTX ### + +:LDCTX abs16offset, abs20offset is b1_0007=0x7c; b2_0007=0xf0; abs16offset; imm20_dat & abs20offset { + + taskNum:1 = abs16offset; # load task number stored at abs16 + ptr:3 = imm20_dat + (zext(taskNum) * 2); # compute table entry address relative to abs20 + regInfo:1 = *:1 ptr; + ptr = ptr + 1; + spCorrect:1 = *:1 ptr; + + ptr = zext(SP); + + if ((regInfo & 1) == 0) goto ; + R0 = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + R1 = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + R2 = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + R3 = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + A0 = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + A1 = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + SB = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + FB = *:2 ptr; + ptr = ptr + 2; + + SP = SP + zext(spCorrect); +} + +### LDE ### + +# (1) LDE.B abs20, dst +:LDE.B abs20offset, dst4B is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0x8) ... & dst4B); abs20offset { + tmp:1 = abs20offset; + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) LDE.B abs20, Ax +:LDE.B abs20offset, dst4Ax is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0x8) & $(DST4AX)); abs20offset { + tmp:1 = abs20offset; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) LDE.W abs20, dst +:LDE.W abs20offsetW, dst4W is ((b1_0107=0x3a & b1_size_0=1; b2_0407=0x8) ... & dst4W); abs20offsetW { + tmp:2 = abs20offsetW; + dst4W = tmp; + setResultFlags(tmp); +} + +# (2) LDE.B dsp20, dst +:LDE.B dsp20A0B, dst4B is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0x9) ... & dst4B); dsp20A0B { + tmp:1 = dsp20A0B; + dst4B = tmp; + setResultFlags(tmp); +} + +# (2) LDE.B dsp20, Ax +:LDE.B dsp20A0B, dst4Ax is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0x9) & $(DST4AX)); dsp20A0B { + tmp:1 = dsp20A0B; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) LDE.W dsp20, dst +:LDE.W dsp20A0W, dst4W is ((b1_0107=0x3a & b1_size_0=1; b2_0407=0x9) ... & dst4W); dsp20A0W { + tmp:2 = dsp20A0W; + dst4W = tmp; + setResultFlags(tmp); +} + +# (3) LDE.B [A1A0], dst +:LDE.B [A1A0], dst4B is (A1A0 & b1_0107=0x3a & b1_size_0=0; b2_0407=0xa) ... & dst4B { + ptr:3 = A1A0:3; + tmp:1 = *:1 ptr; + dst4B = tmp; + setResultFlags(tmp); +} + +# (3) LDE.B [A1A0], Ax +:LDE.B [A1A0], dst4Ax is (A1A0 & b1_0107=0x3a & b1_size_0=0; b2_0407=0xa) & $(DST4AX) { + ptr:3 = A1A0:3; + tmp:1 = *:1 ptr; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (3) LDE.W [A1A0], dst +:LDE.W [A1A0], dst4W is (A1A0 & b1_0107=0x3a & b1_size_0=1; b2_0407=0xa) ... & dst4W { + ptr:3 = A1A0:3; + tmp:2 = *:2 ptr; + dst4W = tmp; + setResultFlags(tmp); +} + +### LDINTB ### +# LDINTB operand value +ldIntbVal: "#"^val is b1_0007; b2_0007; b3_0003; b4_0007; b5_0007; b6_0007; imm16_dat [ val = (b3_0003 << 16) + imm16_dat; ] { + export *[const]:3 val; +} + +# NOTE: Although this is documented as a macro for two LDE instructions, the encoding is different ?? +:LDINTB ldIntbVal is (b1_0007=0xeb; b2_0007=0x20; b3_0407=0x0; b4_0007=0x0; b5_0007=0xeb; b6_0007=0x10) ... & ldIntbVal { + INTB = ldIntbVal; +} + +### LDIPL ### + +:LDIPL srcImm3 is b1_0007=0x7d; b2_0307=0x14 & srcImm3 { + $(IPL) = srcImm3; +} + +### MOV ### + +# (1) MOV.B:G #imm, dst +:MOV^".B:G" srcImm8, dst4B is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0xc) ... & dst4B); srcImm8 { + val:1 = srcImm8; + dst4B = val; + setResultFlags(val); +} + +# (1) MOV.B:G #imm, Ax +:MOV^".B:G" srcImm8, dst4Ax is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0xc) & $(DST4AX)); srcImm8 { + val:1 = srcImm8; + dst4Ax = zext(val); + setResultFlags(val); +} + +# (1) MOV.W:G #imm, dst +:MOV^".W:G" srcImm16, dst4W is ((b1_0107=0x3a & b1_size_0=1; b2_0407=0xc) ... & dst4W); srcImm16 { + val:2 = srcImm16; + dst4W = val; + setResultFlags(val); +} + +# (2) MOV.B:Q #simm4, dst +:MOV^".B:Q" srcSimm4_0407, dst4B is (b1_0107=0x6c & b1_size_0=0; srcSimm4_0407) ... & dst4B { + val:1 = srcSimm4_0407; + dst4B = val; + setResultFlags(val); +} + +# (2) MOV.B:Q #simm4, Ax +:MOV^".B:Q" srcSimm4_0407, dst4Ax is (b1_0107=0x6c & b1_size_0=0; srcSimm4_0407) & $(DST4AX) { + val:1 = srcSimm4_0407; + dst4Ax = zext(val); + setResultFlags(val); +} + +# (2) MOV.W:Q #simm4, dst +:MOV^".W:Q" srcSimm4_0407, dst4W is (b1_0107=0x6c & b1_size_0=1; srcSimm4_0407) ... & dst4W { + val:2 = sext(srcSimm4_0407); + dst4W = val; + setResultFlags(val); +} + +# (3) MOV.B:S #imm, dst +:MOV^".B:S" srcImm8, dst3B_afterDsp8 is (b1_0307=0x18; srcImm8) ... & $(DST3B_AFTER_DSP8) { + val:1 = srcImm8; + dst3B_afterDsp8 = val; + setResultFlags(val); +} + +# (4) MOV.B:S #imm, dst +:MOV^".B:S" srcImm8, b1_3_regAx is b1_0407=0xe & b1_3_regAx & b1_0002=0x2; srcImm8 { + val:1 = srcImm8; + b1_3_regAx = zext(val); + setResultFlags(val); +} + +# (4) MOV.W:S #imm, Ax +:MOV^".W:S" srcImm16, b1_3_regAx is b1_0407=0xa & b1_3_regAx & b1_0002=0x2; srcImm16 { + val:2 = srcImm16; + b1_3_regAx = val; + setResultFlags(val); +} + +# (5) MOV.B:Z #0, dst +:MOV^".B:Z" srcZero8, dst3B is (srcZero8 & b1_0307=0x16) ... & $(DST3B) { + dst3B = 0; + $(SIGN) = 0; + $(ZERO) = 1; +} + +# (6) MOV.B:G src, dst +:MOV^".B:G" src4B, dst4B_afterSrc4 is (b1_0107=0x39 & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... { + val:1 = src4B; + dst4B_afterSrc4 = val; + setResultFlags(val); +} + +# (6) MOV.B:G src, Ax +:MOV^".B:G" src4B, dst4Ax is (b1_0107=0x39 & b1_size_0=0) ... & src4B & $(DST4AX) ... { + val:1 = src4B; + dst4Ax = zext(val); + setResultFlags(val); +} + +# (6) MOV.W:G src, dst +:MOV^".W:G" src4W, dst4W_afterSrc4 is (b1_0107=0x39 & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... { + val:2 = src4W; + dst4W_afterSrc4 = val; + setResultFlags(val); +} + +# (7) MOV.B:S src, Ax +:MOV^".B:S" dst2B, b1_2_regAx is (b1_0307=0x06 & b1_2_regAx) ... & dst2B { + val:1 = dst2B; + b1_2_regAx = zext(val); + setResultFlags(val); +} + +# (8) MOV.B:S R0H/R0L, dst +# TODO: Is it really necessary to exclude R0H/R0L as valid destination ?? +:MOV^".B:S" b1_2_reg8, dst2B is (b1_0307=0x0 & b1_2_reg8) ... & dst2B { + val:1 = b1_2_reg8; + dst2B = val; + setResultFlags(val); +} + +# (9) MOV.B:S src, R0H/R0L +:MOV^".B:S" dst2B, b1_2_reg8 is (b1_0307=0x1 & b1_2_reg8) ... & dst2B { + val:1 = dst2B; + b1_2_reg8 = val; + setResultFlags(val); +} + +# (10) MOV.B:G dsp:8[SP], dst +:MOV^".B:G" dsp8spB, dst4B is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0xb) ... & dst4B); dsp8spB { + val:1 = dsp8spB; + dst4B = val; + setResultFlags(val); +} + +# (10) MOV.B:G dsp:8[SP], Ax +:MOV^".B:G" dsp8spB, dst4Ax is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0xb) & $(DST4AX)); dsp8spB { + val:1 = dsp8spB; + dst4Ax = zext(val); + setResultFlags(val); +} + +# (10) MOV.W:G dsp:8[SP], dst +:MOV^".W:G" dsp8spW, dst4W is ((b1_0107=0x3a & b1_size_0=1; b2_0407=0xb) ... & dst4W); dsp8spW { + val:2 = dsp8spW; + dst4W = val; + setResultFlags(val); +} + +# (11) MOV.B:G src, dsp:8[SP] +:MOV^".B:G" dst4B, dsp8spB is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0x3) ... & dst4B); dsp8spB { + val:1 = dst4B; + dsp8spB = val; + setResultFlags(val); +} + +# (11) MOV.W:G src, dsp:8[SP] +:MOV^".W:G" dst4W, dsp8spW is ((b1_0107=0x3a & b1_size_0=1; b2_0407=0x3) ... & dst4W); dsp8spW { + val:2 = dst4W; + dsp8spW = val; + setResultFlags(val); +} + +### MOVA ### + +:MOVA dst4A, b2_reg16 is (b1_0007=0xeb; b2_0707=0 & b2_reg16) ... & $(DST4A) { + b2_reg16 = dst4A:2; +} + +### MOVDir ### + +# TODO: dst4B=Ax/R0L cases will parse but are not valid + +# (1) MOVDir R0L, dst +:MOVLL R0L, dst4B is (R0L & b1_0007=0x7c; b2_0407=0x8) ... & dst4B { + dst4B = (R0L & 0x0f) | (dst4B & 0xf0); +} +:MOVHL R0L, dst4B is (R0L & b1_0007=0x7c; b2_0407=0x9) ... & dst4B { + dst4B = ((R0L & 0xf0) >> 4) | (dst4B & 0xf0); +} +:MOVLH R0L, dst4B is (R0L & b1_0007=0x7c; b2_0407=0xa) ... & dst4B { + dst4B = ((R0L & 0x0f) << 4) | (dst4B & 0x0f); +} +:MOVHH R0L, dst4B is (R0L & b1_0007=0x7c; b2_0407=0xb) ... & dst4B { + dst4B = (R0L & 0xf0) | (dst4B & 0x0f); +} + +# (1) MOVDir dst, R0L +:MOVLL dst4B, R0L is (R0L & b1_0007=0x7c; b2_0407=0x0) ... & dst4B { + R0L = (dst4B & 0x0f) | (R0L & 0xf0); +} +:MOVHL dst4B, R0L is (R0L & b1_0007=0x7c; b2_0407=0x1) ... & dst4B { + R0L = ((dst4B & 0xf0) >> 4) | (R0L & 0xf0); +} +:MOVLH dst4B, R0L is (R0L & b1_0007=0x7c; b2_0407=0x2) ... & dst4B { + R0L = ((dst4B & 0x0f) << 4) | (R0L & 0x0f); +} +:MOVHH dst4B, R0L is (R0L & b1_0007=0x7c; b2_0407=0x3) ... & dst4B { + R0L = (dst4B & 0xf0) | (R0L & 0x0f); +} + +### MUL ### + +# TODO: Illegal MUL destination cases will parse but are not valid (e.g., R0H, R2, R1H, R3) + +# (1) MUL.B #imm, dst +:MUL.B srcSimm8, dst4B is ((b1_0107=0x3e & b1_size_0=0; b2_0407=0x5) ... & dst4B & dst4W); srcSimm8 { + dst4W = sext(srcSimm8) * sext(dst4B); +} + +# (1) MUL.W #imm, dst +:MUL.W srcSimm16, dst4W is ((b1_0107=0x3e & b1_size_0=1; b2_0407=0x5) ... & dst4W & dst4L); srcSimm16 { + dst4L = sext(srcSimm16) * sext(dst4W); +} + +# (2) MUL.B src, dst +:MUL.B src4B, dst4B_afterSrc4 is (b1_0107=0x3c & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... & dst4W_afterSrc4 ... { + dst4W_afterSrc4 = sext(src4B) * sext(dst4B_afterSrc4); +} + +# (2) MUL.W src, dst +:MUL.W src4W, dst4W_afterSrc4 is (b1_0107=0x3c & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... & dst4L_afterSrc4 ... { + dst4L_afterSrc4 = sext(src4W) * sext(dst4W_afterSrc4); +} + +### MULU ### + +# TODO: Illegal MULU destination cases will parse but are not valid (e.g., R0H, R2, R1H, R3) + +# (1) MULU.B #imm, dst +:MULU.B srcImm8, dst4B is ((b1_0107=0x3e & b1_size_0=0; b2_0407=0x4) ... & dst4B & dst4W); srcImm8 { + dst4W = zext(srcImm8) * zext(dst4B); +} + +# (1) MULU.W #imm, dst +:MULU.W srcImm16, dst4W is ((b1_0107=0x3e & b1_size_0=1; b2_0407=0x4) ... & dst4W & dst4L); srcImm16 { + dst4L = zext(srcImm16) * zext(dst4W); +} + +# (2) MULU.B src, dst +:MULU.B src4B, dst4B_afterSrc4 is (b1_0107=0x38 & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... & dst4W_afterSrc4 ... { + dst4W_afterSrc4 = zext(src4B) * zext(dst4B_afterSrc4); +} + +# (2) MULU.W src, dst +:MULU.W src4W, dst4W_afterSrc4 is (b1_0107=0x38 & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... & dst4L_afterSrc4 ... { + dst4L_afterSrc4 = zext(src4W) * zext(dst4W_afterSrc4); +} + +### NEG ### + +# (1) NEG.B dst +:NEG.B dst4B is (b1_0107=0x3a & b1_size_0=0; b2_0407=0x5) ... & dst4B { + tmp:1 = dst4B; + setSubtractFlags(0:1, tmp); + tmp = -tmp; + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) NEG.W dst +:NEG.W dst4W is (b1_0107=0x3a & b1_size_0=1; b2_0407=0x5) ... & dst4W { + tmp:2 = dst4W; + setSubtractFlags(0:2, tmp); + tmp = -tmp; + dst4W = tmp; + setResultFlags(tmp); +} + +### NOP ### + +:NOP is b1_0007=0x04 { +} + +### NOT ### + +# (1) NOT.B dst +:NOT.B dst4B is (b1_0107=0x3a & b1_size_0=0; b2_0407=0x7) ... & dst4B { + tmp:1 = ~dst4B; + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) NOT.W dst +:NOT.W dst4W is (b1_0107=0x3a & b1_size_0=1; b2_0407=0x7) ... & dst4W { + tmp:2 = ~dst4W; + dst4W = tmp; + setResultFlags(tmp); +} + +# (2) NOT.B:S dst +:NOT^".B:S" dst3B is (b1_0307=0x17) ... & $(DST3B) { + tmp:1 = ~dst3B; + dst3B = tmp; + setResultFlags(tmp); +} + +### OR ### + +# (1) OR.B:G #imm, dst +:OR^".B:G" srcImm8, dst4B is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x3) ... & dst4B); srcImm8 { + tmp:1 = dst4B | srcImm8; + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) OR.B:G #imm, Ax +:OR^".B:G" srcImm8, dst4Ax is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x3) & $(DST4AX)); srcImm8 { + tmp:1 = dst4Ax:1 | srcImm8; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) OR.W:G #imm, dst +:OR^".W:G" srcImm16, dst4W is ((b1_0107=0x3b & b1_size_0=1; b2_0407=0x3) ... & dst4W); srcImm16 { + tmp:2 = dst4W | srcImm16; + dst4W = tmp; + setResultFlags(tmp); +} + +# (2) OR.B:S #imm, dst +:OR^".B:S" srcImm8, dst3B_afterDsp8 is (b1_0307=0x13; srcImm8) ... & $(DST3B_AFTER_DSP8) { + tmp:1 = dst3B_afterDsp8 | srcImm8; + dst3B_afterDsp8 = tmp; + setResultFlags(tmp); +} + +# (3) OR.B:G src, dst +:OR^".B:G" src4B, dst4B_afterSrc4 is (b1_0107=0x4c & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... { + tmp:1 = dst4B_afterSrc4 | src4B; + dst4B_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (3) OR.B:G src, Ax +:OR^".B:G" src4B, dst4Ax is (b1_0107=0x4c & b1_size_0=0) ... & src4B & $(DST4AX) ... { + tmp:1 = dst4Ax:1 | src4B; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (3) OR.W:G src, dst +:OR^".W:G" src4W, dst4W_afterSrc4 is (b1_0107=0x4c & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... { + tmp:2 = dst4W_afterSrc4 | src4W; + dst4W_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (4) OR.B:S src, R0L/R0H +:OR^".B:S" dst2B, b1_2_reg8 is (b1_0307=0x3 & b1_2_reg8) ... & dst2B { + tmp:1 = dst2B | b1_2_reg8; + b1_2_reg8 = tmp; + setResultFlags(tmp); +} + +### POP ### + +# (1) POP.B:G dst +:POP^".B:G" dst4B is (b1_0107=0x3a & b1_size_0=0; b2_0407=0xd) ... & dst4B { + pop1(dst4B); +} + +# (1) POP.B:G Ax +:POP^".B:G" dst4Ax is (b1_0107=0x3a & b1_size_0=0; b2_0407=0xd) & $(DST4AX) { + val:1 = 0; + pop1(val); + dst4Ax = zext(val); +} + +# (1) POP.W:G dst +:POP^".W:G" dst4W is (b1_0107=0x3a & b1_size_0=1; b2_0407=0xd) ... & dst4W { + pop2(dst4W); +} + +# (2) POP.B:S R0L/R0H +:POP^".B:S" b1_3_reg8 is b1_0407=0x9 & b1_3_reg8 & b1_0002=0x2 { + pop1(b1_3_reg8); +} + +# (3) POP.W:S Ax +:POP^".W:S" b1_3_regAx is b1_0407=0xd & b1_3_regAx & b1_0002=0x2 { + pop2(b1_3_regAx); +} + +### POPC ### + +:POPC b2_creg16 is b1_0007=0xeb; b2_0707=0 & b2_creg16 & b2_0003=0x3 { + pop2(b2_creg16); +} + +### POPM ### +popRegFB: FB is regBit7=1 & FB { pop2(FB); } +popRegFB: is regBit7=0 { } + +popRegSB: SB popRegFB is regBit6=1 & popRegFB & SB { pop2(SB); build popRegFB; } +popRegSB: popRegFB is popRegFB { build popRegFB; } + +popRegA1: A1 popRegSB is regBit5=1 & popRegSB & A1 { pop2(A1); build popRegSB; } +popRegA1: popRegSB is popRegSB { build popRegSB; } +popRegA0: A0 popRegA1 is regBit4=1 & popRegA1 & A0 { pop2(A0); build popRegA1; } +popRegA0: popRegA1 is popRegA1 { build popRegA1; } + +popRegR3: R3 popRegA0 is regBit3=1 & popRegA0 & R3 { pop2(R3); build popRegA0; } +popRegR3: popRegA0 is popRegA0 { build popRegA0; } +popRegR2: R2 popRegR3 is regBit2=1 & popRegR3 & R2 { pop2(R2); build popRegR3; } +popRegR2: popRegR3 is popRegR3 { build popRegR3; } +popRegR1: R1 popRegR2 is regBit1=1 & popRegR2 & R1 { pop2(R1); build popRegR2; } +popRegR1: popRegR2 is popRegR2 { build popRegR2; } +popRegR0: R0 popRegR1 is regBit0=1 & popRegR1 & R0 { pop2(R0); build popRegR1; } +popRegR0: popRegR1 is popRegR1 { build popRegR1; } + +popRegList: "( "^popRegR0^")" is popRegR0 { build popRegR0; } + +:POPM popRegList is b1_0007=0xed; popRegList { + build popRegList; +} + +### PUSH ### + +# (1) PUSH.B:G #imm +:PUSH^".B:G" srcImm8 is b1_0107=0x3e & b1_size_0=0; b2_0007=0xe2; srcImm8 { + push1(srcImm8); +} + +# (1) PUSH.W:G #imm +:PUSH^".W:G" srcImm16 is b1_0107=0x3e & b1_size_0=1; b2_0007=0xe2; srcImm16 { + push2(srcImm16); +} + +# (2) PUSH.B:G src +:PUSH^".B:G" dst4B is (b1_0107=0x3a & b1_size_0=0; b2_0407=0x4) ... & dst4B { + push1(dst4B); +} + +# (2) PUSH.W:G src +:PUSH^".W:G" dst4W is (b1_0107=0x3a & b1_size_0=1; b2_0407=0x4) ... & dst4W { + push2(dst4W); +} + +# (3) PUSH.B:S R0H/R0L +:PUSH^".B:S" b1_3_reg8 is b1_0407=0x8 & b1_3_reg8 & b1_0002=0x2 { + push1(b1_3_reg8); +} + +# (4) PUSH.W:S Ax +:PUSH^".W:S" b1_3_regAx is b1_0407=0xc & b1_3_regAx & b1_0002=0x2 { + push2(b1_3_regAx); +} + +### PUSHA ### + +:PUSHA dst4A is (b1_0007=0x7d; b2_0407=0x9) ... & $(DST4A) { + push2(dst4A:2); +} + +### PUSHC ### + +:PUSHC b2_creg16 is b1_0007=0xeb; b2_0707=0 & b2_creg16 & b2_0003=0x2 { + push2(b2_creg16); +} + +### PUSHM ### +pushRegR0: R0 is regBit7=1 & R0 { push2(R0); } +pushRegR0: is regBit7=0 { } +pushRegR1: pushRegR0 R1 is regBit6=1 & pushRegR0 & R1 { push2(R1); build pushRegR0; } +pushRegR1: pushRegR0 is pushRegR0 { build pushRegR0; } +pushRegR2: pushRegR1 R2 is regBit5=1 & pushRegR1 & R2 { push2(R2); build pushRegR1; } +pushRegR2: pushRegR1 is pushRegR1 { build pushRegR1; } +pushRegR3: pushRegR2 R3 is regBit4=1 & pushRegR2 & R3 { push2(R3); build pushRegR2; } +pushRegR3: pushRegR2 is pushRegR2 { build pushRegR2; } + +pushRegA0: pushRegR3 A0 is regBit3=1 & pushRegR3 & A0 { push3(A0); build pushRegR3; } +pushRegA0: pushRegR3 is pushRegR3 { build pushRegR3; } +pushRegA1: pushRegA0 A1 is regBit2=1 & pushRegA0 & A1 { push3(A1); build pushRegA0; } +pushRegA1: pushRegA0 is pushRegA0 { build pushRegA0; } + +pushRegSB: pushRegA1 SB is regBit1=1 & pushRegA1 & SB { push3(SB); build pushRegA1; } +pushRegSB: pushRegA1 is pushRegA1 { build pushRegA1; } + +pushRegFB: pushRegSB FB is regBit0=1 & pushRegSB & FB { push3(FB); build pushRegSB; } +pushRegFB: pushRegSB is pushRegSB { build pushRegSB; } + +pushRegList: "("^pushRegFB^" )" is pushRegFB { build pushRegFB; } + +:PUSHM pushRegList is b1_0007=0xec; pushRegList { + build pushRegList; +} + +### REIT ### + +:REIT is b1_0007=0xfb { + pc:3 = 0; + pop3(pc); + f:1 = 0; + pop1(f); + FLG = zext(f); # TODO: Not sure what state upper FLG bits should be in ?? + return [pc]; +} + +### RMPA ### + +:RMPA.B is b1_0107=0x3e & b1_size_0=0; b2_0007=0xf1 { + if (R3 == 0) goto inst_next; + ptr0:3 = zext(A0); + ptr1:3 = zext(A1); + a:1 = *:1 ptr0; + b:1 = *:1 ptr1; + A0 = A0 + 1; + A1 = A1 + 1; + prod:2 = sext(a) * sext(b); + o:1 = scarry(R0, prod); + $(OVERFLOW) = o | $(OVERFLOW); + R0 = R0 + prod; + R3 = R3 - 1; + goto inst_start; +} + +:RMPA.W is b1_0107=0x3e & b1_size_0=1; b2_0007=0xf1 { + if (R3 == 0) goto inst_next; + ptr0:3 = zext(A0); + ptr1:3 = zext(A1); + a:2 = *:2 ptr0; + b:2 = *:2 ptr1; + A0 = A0 + 2; + A1 = A1 + 2; + prod:4 = sext(a) * sext(b); + o:1 = scarry(R2R0, prod); + $(OVERFLOW) = o | $(OVERFLOW); + R2R0 = R2R0 + prod; + R3 = R3 - 1; + goto inst_start; +} + +### ROLC ### + +:ROLC.B dst4B is (b1_0107=0x3b & b1_size_0=0; b2_0407=0xa) ... & dst4B { + c:1 = $(CARRY); + tmp:1 = dst4B; + $(CARRY) = tmp s< 0; + tmp = (tmp << 1) | c; + dst4B = tmp; + setResultFlags(tmp); +} + +:ROLC.B dst4Ax is (b1_0107=0x3b & b1_size_0=0; b2_0407=0xa) & $(DST4AX) { + c:1 = $(CARRY); + tmp:1 = dst4Ax:1; + $(CARRY) = tmp s< 0; + tmp = (tmp << 1) | c; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +:ROLC.W dst4W is (b1_0107=0x3b & b1_size_0=1; b2_0407=0xa) ... & dst4W { + c:2 = zext($(CARRY)); + tmp:2 = dst4W; + $(CARRY) = tmp s< 0; + tmp = (tmp << 1) | c; + dst4W = tmp; + setResultFlags(tmp); +} + +### RORC ### + +:RORC.B dst4B is (b1_0107=0x3b & b1_size_0=0; b2_0407=0xb) ... & dst4B { + c:1 = $(CARRY); + tmp:1 = dst4B; + $(CARRY) = (tmp & 1) == 1; + tmp = (tmp >> 1) | (c << 7); + dst4B = tmp; + setResultFlags(tmp); +} + +:RORC.B dst4Ax is (b1_0107=0x3b & b1_size_0=0; b2_0407=0xb) & $(DST4AX) { + c:1 = $(CARRY); + tmp:1 = dst4Ax:1; + $(CARRY) = (tmp & 1) == 1; + tmp = (tmp >> 1) | (c << 7); + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +:RORC.W dst4W is (b1_0107=0x3b & b1_size_0=1; b2_0407=0xb) ... & dst4W { + c:2 = zext($(CARRY)); + tmp:2 = dst4W; + $(CARRY) = (tmp & 1) == 1; + tmp = (tmp >> 1) | (c << 15); + dst4W = tmp; + setResultFlags(tmp); +} + +### ROT ### + +# (1) ROT.B #imm, dst (right) +:ROT.B srcSimm4Shift_0407, dst4B is (b1_0107=0x70 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=1) ... & dst4B { + rightShift:1 = -srcSimm4Shift_0407; + tmp:1 = dst4B; + $(CARRY) = (tmp >> (rightShift - 1)) & 1; + tmp = (tmp >> rightShift) | (tmp << (8 - rightShift)); + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) ROT.B #imm, Ax (right) +:ROT.B srcSimm4Shift_0407, dst4Ax is (b1_0107=0x70 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=1) & $(DST4AX) { + rightShift:1 = -srcSimm4Shift_0407; + tmp:1 = dst4Ax:1; + $(CARRY) = (tmp >> (rightShift - 1)) & 1; + tmp = (tmp >> rightShift) | (tmp << (8 - rightShift)); + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) ROT.W #imm, dst (right) +:ROT.W srcSimm4Shift_0407, dst4W is (b1_0107=0x70 & b1_size_0=1; srcSimm4Shift_0407 & b2_shiftSign_7=1) ... & dst4W { + rightShift:1 = -srcSimm4Shift_0407; + tmp:2 = dst4W; + c:2 = (tmp >> (rightShift - 1)); + $(CARRY) = c:1 & 1; + tmp = (tmp >> rightShift) | (tmp << (16 - rightShift)); + dst4W = tmp; + setResultFlags(tmp); +} + +# (1) ROT.B #imm, dst (left) +:ROT.B srcSimm4Shift_0407, dst4B is (b1_0107=0x70 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=0) ... & dst4B { + leftShift:1 = srcSimm4Shift_0407; + tmp:1 = dst4B; + $(CARRY) = (tmp >> (8 - leftShift)) & 1; + tmp = (tmp << leftShift) | (tmp >> (8 - leftShift)); + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) ROT.B #imm, Ax (left) +:ROT.B srcSimm4Shift_0407, dst4Ax is (b1_0107=0x70 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=0) & $(DST4AX) { + leftShift:1 = srcSimm4Shift_0407; + tmp:1 = dst4Ax:1; + $(CARRY) = (tmp >> (8 - leftShift)) & 1; + tmp = (tmp << leftShift) | (tmp >> (8 - leftShift)); + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) ROT.W #imm, dst (left) +:ROT.W srcSimm4Shift_0407, dst4W is (b1_0107=0x70 & b1_size_0=1; srcSimm4Shift_0407 & b2_shiftSign_7=0) ... & dst4W { + leftShift:1 = srcSimm4Shift_0407; + tmp:2 = dst4W; + c:2 = (tmp >> (16 - leftShift)); + $(CARRY) = c:1 & 1; + tmp = (tmp << leftShift) | (tmp >> (16 - leftShift)); + dst4W = tmp; + setResultFlags(tmp); +} + +# (2) ROT.B R1H, dst +:ROT.B R1H, dst4B is (R1H & b1_0107=0x3a & b1_size_0=0; b2_0407=0x6) ... & dst4B { + if (R1H == 0) goto inst_next; + shift:1 = R1H s% 8; + tmp:1 = dst4B; + if (shift s>= 0) goto ; + shift = -shift; + $(CARRY) = (tmp >> (shift - 1)) & 1; + tmp = (tmp >> shift) | (tmp << (8 - shift)); + goto ; + + $(CARRY) = (tmp >> (8 - shift)) & 1; + tmp = (tmp << shift) | (tmp >> (8 - shift)); + + dst4B = tmp; + setResultFlags(tmp); +} + +# (2) ROT.B R1H, Ax +:ROT.B R1H, dst4Ax is (R1H & b1_0107=0x3a & b1_size_0=0; b2_0407=0x6) & $(DST4AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H s% 8; + tmp:1 = dst4Ax:1; + if (shift s>= 0) goto ; + shift = -shift; + $(CARRY) = (tmp >> (shift - 1)) & 1; + tmp = (tmp >> shift) | (tmp << (8 - shift)); + goto ; + + $(CARRY) = (tmp >> (8 - shift)) & 1; + tmp = (tmp << shift) | (tmp >> (8 - shift)); + + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) ROT.W R1H, dst +:ROT.W R1H, dst4W is (R1H & b1_0107=0x3a & b1_size_0=1; b2_0407=0x6) ... & dst4W { + if (R1H == 0) goto inst_next; + shift:1 = R1H s% 16; + tmp:2 = dst4W; + if (shift s>= 0) goto ; + shift = -shift; + c:2 = (tmp >> (shift - 1)); + tmp = (tmp >> shift) | (tmp << (16 - shift)); + goto ; + + c = (tmp >> (16 - shift)); + tmp = (tmp << shift) | (tmp >> (16 - shift)); + + $(CARRY) = c:1 & 1; + dst4W = tmp; + setResultFlags(tmp); +} + +### RTS ### + +:RTS is b1_0007=0xf3 { + pc:3 = 0; + pop3(pc); + return [pc]; +} + +### SBB ### + +# (1) SBB.B #imm, dst +:SBB.B srcSimm8, dst4B is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x7) ... & dst4B); srcSimm8 { + tmp:1 = dst4B; + c:1 = $(CARRY); + setSubtract3Flags(tmp, srcSimm8, c); + tmp = tmp - srcSimm8 - c; + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) SBB.B #imm, Ax +:SBB.B srcSimm8, dst4Ax is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x7) & $(DST4AX)); srcSimm8 { + tmp:1 = dst4Ax:1; + c:1 = $(CARRY); + setSubtract3Flags(tmp, srcSimm8, c); + tmp = tmp - srcSimm8 - c; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) SBB.W #imm, dst +:SBB.W srcSimm16, dst4W is ((b1_0107=0x3b & b1_size_0=1; b2_0407=0x7) ... & dst4W); srcSimm16 { + tmp:2 = dst4W; + c:2 = zext($(CARRY)); + setSubtract3Flags(tmp, srcSimm16, c); + tmp = tmp - srcSimm16 - c; + dst4W = tmp; + setResultFlags(tmp); +} + +# (2) SBB.B src, dst +:SBB.B src4B, dst4B_afterSrc4 is (b1_0107=0x5c & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... { + tmp:1 = dst4B_afterSrc4; + s:1 = src4B; + c:1 = $(CARRY); + setSubtract3Flags(tmp, s, c); + tmp = tmp - s - c; + dst4B_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (2) SBB.B src, Ax +:SBB.B src4B, dst4Ax is (b1_0107=0x5c & b1_size_0=0) ... & src4B & $(DST4AX) ... { + tmp:1 = dst4Ax:1; + s:1 = src4B; + c:1 = $(CARRY); + setSubtract3Flags(tmp, s, c); + tmp = tmp - s - c; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) SBB.W src, dst +:SBB.W src4W, dst4W_afterSrc4 is (b1_0107=0x5c & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... { + tmp:2 = dst4W_afterSrc4; + s:2 = src4W; + c:2 = zext($(CARRY)); + setSubtract3Flags(tmp, s, c); + tmp = tmp - s - c; + dst4W_afterSrc4 = tmp; + setResultFlags(tmp); +} + +##### SBJNZ - PSUEDO-OP! SAME AS ADJNZ ##### +### SHA ### +macro SHAsetShiftRightFlags(val,shift,result) { + local c = (val >> (shift - 1)) & 1; + $(CARRY) = c:1; + local mask = ~(-(1 << shift)); + allOnes:1 = (mask & val) == mask; + allZeros:1 = (mask & val) == 0; + $(OVERFLOW) = (result s< 0 && allOnes) || (result s>= 0 && allZeros); + setResultFlags(result); +} + +macro SHAsetShiftLeftFlags(val,shift,result,sze) { + local c = (val >> (sze - shift)) & 1; + $(CARRY) = c:1; + local mask = -(1 << shift); + allOnes:1 = (mask & val) == mask; + allZeros:1 = (mask & val) == 0; + $(OVERFLOW) = (result s< 0 && allOnes) || (result s>= 0 && allZeros); + setResultFlags(result); +} + +# (1) SHA.B #imm4, dst (right) +:SHA.B srcSimm4Shift_0407, dst4B is (b1_0107=0x78 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=1) ... & dst4B { + val:1 = dst4B; + shift:1 = -srcSimm4Shift_0407; + tmp:1 = val s>> shift; + dst4B = tmp; + SHAsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHA.B #imm4, Ax (right) +:SHA.B srcSimm4Shift_0407, dst4Ax is (b1_0107=0x78 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=1) & $(DST4AX) { + val:1 = dst4Ax:1; + shift:1 = -srcSimm4Shift_0407; + tmp:1 = val s>> shift; + dst4Ax = zext(tmp); + SHAsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHA.W #imm4, dst (right) +:SHA.W srcSimm4Shift_0407, dst4W is (b1_0107=0x78 & b1_size_0=1; srcSimm4Shift_0407 & b2_shiftSign_7=1) ... & dst4W { + val:2 = dst4W; + shift:1 = -srcSimm4Shift_0407; + tmp:2 = val s>> shift; + dst4W = tmp; + SHAsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHA.B #imm4, dst (left) +:SHA.B srcSimm4Shift_0407, dst4B is (b1_0107=0x78 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=0) ... & dst4B { + val:1 = dst4B; + shift:1 = srcSimm4Shift_0407; + tmp:1 = val << shift; + dst4B = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (1) SHA.B #imm4, Ax (left) +:SHA.B srcSimm4Shift_0407, dst4Ax is (b1_0107=0x78 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=0) & $(DST4AX) { + val:1 = dst4Ax:1; + shift:1 = srcSimm4Shift_0407; + tmp:1 = val << shift; + dst4Ax = zext(tmp); + SHAsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (1) SHA.W #imm4, dst (left) +:SHA.W srcSimm4Shift_0407, dst4W is (b1_0107=0x78 & b1_size_0=1; srcSimm4Shift_0407 & b2_shiftSign_7=0) ... & dst4W { + val:2 = dst4W; + shift:1 = srcSimm4Shift_0407; + tmp:2 = val << shift; + dst4W = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (2) SHA.B R1H, dst +:SHA.B R1H, dst4B is (R1H & b1_0107=0x3a & b1_size_0=0; b2_0407=0xf) ... & dst4B { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:1 = dst4B; + if (shift s> 0) goto ; + shift = -shift; + tmp:1 = val s>> shift; + dst4B = tmp; + SHAsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst4B = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (2) SHA.B R1H, Ax +:SHA.B R1H, dst4Ax is (R1H & b1_0107=0x3a & b1_size_0=0; b2_0407=0xf) & $(DST4AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:1 = dst4Ax:1; + if (shift s> 0) goto ; + shift = -shift; + tmp:1 = val s>> shift; + dst4Ax = zext(tmp); + SHAsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst4Ax = zext(tmp); + SHAsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (2) SHA.W R1H, dst +:SHA.W R1H, dst4W is (R1H & b1_0107=0x3a & b1_size_0=1; b2_0407=0xf) ... & dst4W { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:2 = dst4W; + if (shift s> 0) goto ; + shift = -shift; + tmp:2 = val s>> shift; + dst4W = tmp; + SHAsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst4W = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (3) SHA.L #imm4, R2R0/R3R1 (right) +:SHA.L srcSimm4Shift_0003, b2_reg32 is b1_0007=0xeb; b2_0507=0x5 & b2_reg32 & srcSimm4Shift_0003 & b2_shiftSign_3=1 { + val:4 = b2_reg32; + shift:1 = -srcSimm4Shift_0003; + tmp:4 = val s>> shift; + b2_reg32 = tmp; + SHAsetShiftRightFlags(val, shift, tmp); +} + +# (3) SHA.L #imm4, R2R0/R3R1 (left) +:SHA.L srcSimm4Shift_0003, b2_reg32 is b1_0007=0xeb; b2_0507=0x5 & b2_reg32 & srcSimm4Shift_0003 & b2_shiftSign_3=0 { + val:4 = b2_reg32; + shift:1 = srcSimm4Shift_0003; + tmp:4 = val << shift; + b2_reg32 = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 32); +} + +# (4) SHA.L R1H, R2R0/R3R1 +:SHA.L R1H, b2_reg32 is R1H & b1_0007=0xeb; b2_0507=0x1 & b2_reg32 & b2_0003=0x1 { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:4 = b2_reg32; + if (shift s> 0) goto ; + shift = -shift; + tmp:4 = val s>> shift; + b2_reg32 = tmp; + SHAsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + b2_reg32 = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 32); +} + +### SHL ### +macro SHLsetShiftRightFlags(val,shift,result) { + local c = (val >> (shift - 1)) & 1; + $(CARRY) = c:1; + setResultFlags(result); +} + +macro SHLsetShiftLeftFlags(val,shift,result,sze) { + local c = (val >> (sze - shift)) & 1; + $(CARRY) = c:1; + setResultFlags(result); +} + +# (1) SHL.B #imm4, dst (right) +:SHL.B srcSimm4Shift_0407, dst4B is (b1_0107=0x74 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=1) ... & dst4B { + val:1 = dst4B; + shift:1 = -srcSimm4Shift_0407; + tmp:1 = val >> shift; + dst4B = tmp; + SHLsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHL.B #imm4, Ax (right) +:SHL.B srcSimm4Shift_0407, dst4Ax is (b1_0107=0x74 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=1) & $(DST4AX) { + val:1 = dst4Ax:1; + shift:1 = -srcSimm4Shift_0407; + tmp:1 = val >> shift; + dst4Ax = zext(tmp); + SHLsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHL.W #imm4, dst (right) +:SHL.W srcSimm4Shift_0407, dst4W is (b1_0107=0x74 & b1_size_0=1; srcSimm4Shift_0407 & b2_shiftSign_7=1) ... & dst4W { + val:2 = dst4W; + shift:1 = -srcSimm4Shift_0407; + tmp:2 = val >> shift; + dst4W = tmp; + SHLsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHL.B #imm4, dst (left) +:SHL.B srcSimm4Shift_0407, dst4B is (b1_0107=0x74 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=0) ... & dst4B { + val:1 = dst4B; + shift:1 = srcSimm4Shift_0407; + tmp:1 = val << shift; + dst4B = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (1) SHL.B #imm4, Ax (left) +:SHL.B srcSimm4Shift_0407, dst4Ax is (b1_0107=0x74 & b1_size_0=0; srcSimm4Shift_0407 & b2_shiftSign_7=0) & $(DST4AX) { + val:1 = dst4Ax:1; + shift:1 = srcSimm4Shift_0407; + tmp:1 = val << shift; + dst4Ax = zext(tmp); + SHLsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (1) SHL.W #imm4, dst (left) +:SHL.W srcSimm4Shift_0407, dst4W is (b1_0107=0x74 & b1_size_0=1; srcSimm4Shift_0407 & b2_shiftSign_7=0) ... & dst4W { + val:2 = dst4W; + shift:1 = srcSimm4Shift_0407; + tmp:2 = val << shift; + dst4W = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (2) SHL.B R1H, dst +:SHL.B R1H, dst4B is (R1H & b1_0107=0x3a & b1_size_0=0; b2_0407=0xe) ... & dst4B { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:1 = dst4B; + if (shift s> 0) goto ; + shift = -shift; + tmp:1 = val >> shift; + dst4B = tmp; + SHLsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst4B = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (2) SHL.B R1H, Ax +:SHL.B R1H, dst4Ax is (R1H & b1_0107=0x3a & b1_size_0=0; b2_0407=0xe) & $(DST4AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:1 = dst4Ax:1; + if (shift s> 0) goto ; + shift = -shift; + tmp:1 = val >> shift; + dst4Ax = zext(tmp); + SHLsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst4Ax = zext(tmp); + SHLsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (2) SHL.W R1H, dst +:SHL.W R1H, dst4W is (R1H & b1_0107=0x3a & b1_size_0=1; b2_0407=0xe) ... & dst4W { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:2 = dst4W; + if (shift s> 0) goto ; + shift = -shift; + tmp:2 = val >> shift; + dst4W = tmp; + SHLsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst4W = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (3) SHL.L #imm4, R2R0/R3R1 (right) +:SHL.L srcSimm4Shift_0003, b2_reg32 is b1_0007=0xeb; b2_0507=0x4 & b2_reg32 & srcSimm4Shift_0003 & b2_shiftSign_3=1 { + val:4 = b2_reg32; + shift:1 = -srcSimm4Shift_0003; + tmp:4 = val >> shift; + b2_reg32 = tmp; + SHLsetShiftRightFlags(val, shift, tmp); +} + +# (3) SHL.L #imm4, R2R0/R3R1 (left) +:SHL.L srcSimm4Shift_0003, b2_reg32 is b1_0007=0xeb; b2_0507=0x4 & b2_reg32 & srcSimm4Shift_0003 & b2_shiftSign_3=0 { + val:4 = b2_reg32; + shift:1 = srcSimm4Shift_0003; + tmp:4 = val << shift; + b2_reg32 = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 32); +} + +# (4) SHL.L R1H, R2R0/R3R1 +:SHL.L R1H, b2_reg32 is R1H & b1_0007=0xeb; b2_0507=0x0 & b2_reg32 & b2_0003=0x1 { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:4 = b2_reg32; + if (shift s> 0) goto ; + shift = -shift; + tmp:4 = val >> shift; + b2_reg32 = tmp; + SHLsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + b2_reg32 = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 32); +} + +### SMOVB ### + +:SMOVB.B is b1_0107=0x3e & b1_size_0=0; b2_0007=0xe9 { + if (R3 == 0) goto inst_next; + ptr0:3 = (zext(R1H) << 16) + zext(A0); + ptr1:3 = zext(A1); + *:1 ptr1 = *:1 ptr0; + A1 = A1 - 1; + ptr0 = ptr0 - 1; + A0 = ptr0:2; + R1H = ptr0(2); + R3 = R3 - 1; + goto inst_start; +} + +:SMOVB.W is b1_0107=0x3e & b1_size_0=1; b2_0007=0xe9 { + if (R3 == 0) goto inst_next; + ptr0:3 = (zext(R1H) << 16) + zext(A0); + ptr1:3 = zext(A1); + *:2 ptr1 = *:2 ptr0; + A1 = A1 - 2; + ptr0 = ptr0 - 2; + A0 = ptr0:2; + R1H = ptr0(2); + R3 = R3 - 1; + goto inst_start; +} + +### SMOVF ### + +:SMOVF.B is b1_0107=0x3e & b1_size_0=0; b2_0007=0xe8 { + if (R3 == 0) goto inst_next; + ptr0:3 = (zext(R1H) << 16) + zext(A0); + ptr1:3 = zext(A1); + *:1 ptr1 = *:1 ptr0; + A1 = A1 + 1; + ptr0 = ptr0 + 1; + A0 = ptr0:2; + R1H = ptr0(2); + R3 = R3 - 1; + goto inst_start; +} + +:SMOVF.W is b1_0107=0x3e & b1_size_0=1; b2_0007=0xe8 { + if (R3 == 0) goto inst_next; + ptr0:3 = (zext(R1H) << 16) + zext(A0); + ptr1:3 = zext(A1); + *:2 ptr1 = *:2 ptr0; + A1 = A1 + 2; + ptr0 = ptr0 + 2; + A0 = ptr0:2; + R1H = ptr0(2); + R3 = R3 - 1; + goto inst_start; +} + +### SSTR ### + +:SSTR.B is b1_0107=0x3e & b1_size_0=0; b2_0007=0xea { + if (R3 == 0) goto inst_next; + ptr1:3 = zext(A1); + *:1 ptr1 = R0L; + A1 = A1 + 1; + R3 = R3 - 1; + goto inst_start; +} + +:SSTR.W is b1_0107=0x3e & b1_size_0=1; b2_0007=0xea { + if (R3 == 0) goto inst_next; + ptr1:3 = zext(A1); + *:2 ptr1 = R0; + A1 = A1 + 2; + R3 = R3 - 1; + goto inst_start; +} + +### STC ### + +# (1) STC src, dst +:STC b2_creg16, dst4W is (b1_0007=0x7b; b2_0707=1 & b2_creg16) ... & dst4W { + dst4W = b2_creg16; +} + +# (2) STC PC, dst (dst=register) +:STC PC, dst4L is (PC & b1_0007=0x7c; b2_0407=0xc) ... & dst4L { + dst4L = zext(PC); +} + +# (2) STC PC, dst (dst=memory) +:STC PC, dst4T is (PC & b1_0007=0x7c; b2_0407=0xc) ... & $(DST4T) { + dst4T = inst_next; # PC value refers to next instruction address +} + +### STCTX ### + +:STCTX abs16offset, abs20offset is b1_0007=0xb6; b2_0007=0xd3; abs16offset; imm20_dat & abs20offset { + + taskNum:1 = abs16offset; # load task number stored at abs16 + ptr:3 = imm20_dat + (zext(taskNum) * 2); # compute table entry address relative to abs20 + regInfo:1 = *:1 ptr; + ptr = ptr + 1; + spCorrect:1 = *:1 ptr; + + ptr = zext(SP); + + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = FB; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = SB; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = A1; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = A0; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = R3; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = R2; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = R1; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = R0; + + SP = SP - zext(spCorrect); +} + +### STE ### + +# (1) STE.B src, abs20 +:STE.B dst4B, abs20offset is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0) ... & dst4B); abs20offset { + val:1 = dst4B; + abs20offset = val; + setResultFlags(val); +} + +# (1) STE.W src, abs20 +:STE.W dst4W, abs20offsetW is ((b1_0107=0x3a & b1_size_0=1; b2_0407=0) ... & dst4W); abs20offsetW { + val:2 = dst4W; + abs20offsetW = val; + setResultFlags(val); +} + +# (2) STE.B src, dsp:20[A0] +:STE.B dst4B, dsp20A0B is ((b1_0107=0x3a & b1_size_0=0; b2_0407=0x1) ... & dst4B); dsp20A0B { + val:1 = dst4B; + dsp20A0B = val; + setResultFlags(val); +} + +# (2) STE.W src, dsp:20[A0] +:STE.W dst4W, dsp20A0W is ((b1_0107=0x3a & b1_size_0=1; b2_0407=0x1) ... & dst4W); dsp20A0W { + val:2 = dst4W; + dsp20A0W = val; + setResultFlags(val); +} + +steA1A0B: "["^A1A0^"]" is A1A0 { ptr:3 = A1A0:3; export *:1 ptr; } + +steA1A0W: "["^A1A0^"]" is A1A0 { ptr:3 = A1A0:3; export *:2 ptr; } + +# (3) STE.B src, [A1A0] +:STE.B dst4B, steA1A0B is (steA1A0B & b1_0107=0x3a & b1_size_0=0; b2_0407=0x2) ... & dst4B { + val:1 = dst4B; + steA1A0B = val; + setResultFlags(val); +} + +# (3) STE.W src, [A1A0] +:STE.W dst4W, steA1A0W is (steA1A0W & b1_0107=0x3a & b1_size_0=1; b2_0407=0x2) ... & dst4W { + val:2 = dst4W; + steA1A0W = val; + setResultFlags(val); +} + +### STNZ ### + +:STNZ srcImm8, dst3B_afterDsp8 is (b1_0307=0x1a; srcImm8) ... & $(DST3B_AFTER_DSP8) { + if ($(ZERO) != 0) goto inst_next; + dst3B_afterDsp8 = srcImm8; +} + +### STZ ### + +:STZ srcImm8, dst3B_afterDsp8 is (b1_0307=0x19; srcImm8) ... & $(DST3B_AFTER_DSP8) { + if ($(ZERO) == 0) goto inst_next; + dst3B_afterDsp8 = srcImm8; +} + +### STZX ### +skipBytesBeforeImm82: is b1_0007; imm8_dat { } # imm81 +skipBytesBeforeImm82: is b1_d3=0x5; imm16_dat { } # imm81; dsp8 +skipBytesBeforeImm82: is b1_d3=0x6; imm16_dat { } # imm81; dsp8 +skipBytesBeforeImm82: is b1_d3=0x7; imm24_dat { } # imm81; abs16 + +stzxImm82: "#"^imm8_dat is skipBytesBeforeImm82; imm8_dat { export *[const]:1 imm8_dat; } + +:STZX srcImm8, stzxImm82, dst3B_afterDsp8 is (b1_0307=0x1b; srcImm8) ... & $(DST3B_AFTER_DSP8) ... & stzxImm82 { + z:1 = $(ZERO); + dst3B_afterDsp8 = (z * srcImm8) + (!z * stzxImm82); +} + +### SUB ### + +# (1) SUB.B:G #simm, dst +:SUB^".B:G" srcSimm8, dst4B is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x5) ... & dst4B); srcSimm8 { + tmp:1 = dst4B; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) SUB.B:G #simm, Ax +:SUB^".B:G" srcSimm8, dst4Ax is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x5) & $(DST4AX)); srcSimm8 { + tmp:1 = dst4Ax:1; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) SUB.W:G #simm, dst +:SUB^".W:G" srcSimm16, dst4W is ((b1_0107=0x3b & b1_size_0=1; b2_0407=0x5) ... & dst4W); srcSimm16 { + tmp:2 = dst4W; + setSubtractFlags(tmp, srcSimm16); + tmp = tmp - srcSimm16; + dst4W = tmp; + setResultFlags(tmp); +} + +# (2) SUB.B:S #simm, dst +:SUB^".B:S" srcSimm8, dst3B_afterDsp8 is (b1_0307=0x11; srcSimm8) ... & $(DST3B_AFTER_DSP8) { + tmp:1 = dst3B_afterDsp8; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + dst3B_afterDsp8 = tmp; + setResultFlags(tmp); +} + +# (3) SUB.B:G src, dst +:SUB^".B:G" src4B, dst4B_afterSrc4 is (b1_0107=0x54 & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... { + tmp:1 = dst4B_afterSrc4; + src:1 = src4B; + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst4B_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (3) SUB.B:G src, Ax +:SUB^".B:G" src4B, dst4Ax is (b1_0107=0x54 & b1_size_0=0) ... & src4B & $(DST4AX) ... { + tmp:1 = dst4Ax:1; + src:1 = src4B; + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (3) SUB.W:G src, dst +:SUB^".W:G" src4W, dst4W_afterSrc4 is (b1_0107=0x54 & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... { + tmp:2 = dst4W_afterSrc4; + src:2 = src4W; + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst4W_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (4) SUB.B:S src, R0H/R0L +:SUB^".B:S" dst2B, b1_2_reg8 is (b1_0307=0x5 & b1_2_reg8) ... & dst2B { + tmp:1 = b1_2_reg8; + src:1 = dst2B; + setSubtractFlags(tmp, src); + tmp = tmp - src; + b1_2_reg8 = tmp; + setResultFlags(tmp); +} + +### TST ### + +# (1) TST.B #imm, dst +:TST.B srcImm8, dst4B is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x0) ... & dst4B); srcImm8 { + tmp:1 = dst4B & srcImm8; + setResultFlags(tmp); +} + +# (1) TST.W #imm, dst +:TST.W srcImm16, dst4W is ((b1_0107=0x3b & b1_size_0=1; b2_0407=0x0) ... & dst4W); srcImm16 { + tmp:2 = dst4W & srcImm16; + setResultFlags(tmp); +} + +# (2) TST.B src, dst +:TST.B src4B, dst4B_afterSrc4 is (b1_0107=0x40 & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... { + tmp:1 = dst4B_afterSrc4 & src4B; + setResultFlags(tmp); +} + +# (2) TST.W src, dst +:TST.W src4W, dst4W_afterSrc4 is (b1_0107=0x40 & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... { + tmp:2 = dst4W_afterSrc4 & src4W; + setResultFlags(tmp); +} + +##### UND ##### +# Don't implement this "Undefined" instruction +# :UND is b1_0007=0xff + +### WAIT ### + +:WAIT is b1_0007=0x7d; b2_0007=0xf3 { + Wait(); +} + +### XCHG ### + +:XCHG.B b2_s4_reg8, dst4B is (b1_0107=0x3d & b1_size_0=0; b2_0607=0 & b2_s4_reg8) ... & dst4B { + tmp:1 = dst4B; + dst4B = b2_s4_reg8; + b2_s4_reg8 = tmp; +} + +:XCHG.B b2_s4_reg8, dst4Ax is (b1_0107=0x3d & b1_size_0=0; b2_0607=0 & b2_s4_reg8) & $(DST4AX) { + tmp:1 = dst4Ax:1; + dst4Ax = zext(b2_s4_reg8); + b2_s4_reg8 = tmp; +} + +:XCHG.W b2_s4_reg16, dst4W is (b1_0107=0x3d & b1_size_0=1; b2_0607=0 & b2_s4_reg16) ... & dst4W { + tmp:2 = dst4W; + dst4W = b2_s4_reg16; + b2_s4_reg16 = tmp; +} + +### XOR ### + +# (1) XOR.B:G #imm, dst +:XOR^".B:G" srcImm8, dst4B is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x1) ... & dst4B); srcImm8 { + tmp:1 = dst4B ^ srcImm8; + dst4B = tmp; + setResultFlags(tmp); +} + +# (1) XOR.B:G #imm, Ax +:XOR^".B:G" srcImm8, dst4Ax is ((b1_0107=0x3b & b1_size_0=0; b2_0407=0x1) & $(DST4AX)); srcImm8 { + tmp:1 = dst4Ax:1 ^ srcImm8; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) XOR.W:G #imm, dst +:XOR^".W:G" srcImm16, dst4W is ((b1_0107=0x3b & b1_size_0=1; b2_0407=0x1) ... & dst4W); srcImm16 { + tmp:2 = dst4W ^ srcImm16; + dst4W = tmp; + setResultFlags(tmp); +} + +# (2) XOR.B:G src, dst +:XOR^".B:G" src4B, dst4B_afterSrc4 is (b1_0107=0x44 & b1_size_0=0) ... & src4B ... & dst4B_afterSrc4 ... { + tmp:1 = dst4B_afterSrc4 ^ src4B; + dst4B_afterSrc4 = tmp; + setResultFlags(tmp); +} + +# (2) XOR.B:G src, Ax +:XOR^".B:G" src4B, dst4Ax is (b1_0107=0x44 & b1_size_0=0) ... & src4B & $(DST4AX) ... { + tmp:1 = dst4Ax:1 ^ src4B; + dst4Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) XOR.W:G src, dst +:XOR^".W:G" src4W, dst4W_afterSrc4 is (b1_0107=0x44 & b1_size_0=1) ... & src4W ... & dst4W_afterSrc4 ... { + tmp:2 = dst4W_afterSrc4 ^ src4W; + dst4W_afterSrc4 = tmp; + setResultFlags(tmp); +} diff --git a/Ghidra/Processors/M16C/data/languages/M16C_80.cspec b/Ghidra/Processors/M16C/data/languages/M16C_80.cspec new file mode 100644 index 0000000000..dda14808d1 --- /dev/null +++ b/Ghidra/Processors/M16C/data/languages/M16C_80.cspec @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghidra/Processors/M16C/data/languages/M16C_80.ldefs b/Ghidra/Processors/M16C/data/languages/M16C_80.ldefs new file mode 100644 index 0000000000..8781217449 --- /dev/null +++ b/Ghidra/Processors/M16C/data/languages/M16C_80.ldefs @@ -0,0 +1,20 @@ + + + + + + Renesas M16C/80 16-Bit MicroComputer + + + + diff --git a/Ghidra/Processors/M16C/data/languages/M16C_80.pspec b/Ghidra/Processors/M16C/data/languages/M16C_80.pspec new file mode 100644 index 0000000000..012579c0eb --- /dev/null +++ b/Ghidra/Processors/M16C/data/languages/M16C_80.pspec @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ghidra/Processors/M16C/data/languages/M16C_80.slaspec b/Ghidra/Processors/M16C/data/languages/M16C_80.slaspec new file mode 100644 index 0000000000..9acb2b71c4 --- /dev/null +++ b/Ghidra/Processors/M16C/data/languages/M16C_80.slaspec @@ -0,0 +1,5050 @@ +# +# Renesas M16C/80 16-Bit MicroComputer +# + +# +# Memory Architecture +# +define endian=little; + +define alignment=1; + +define space RAM type=ram_space size=3 default; +define space register type=register_space size=2; + +# +# General Registers +# +define register offset=0x0000 size=2 [ + R0 R2 R1 R3 +]; + +define register offset=0x0000 size=1 [ + R0L R0H _ _ R1L R1H _ _ +]; + +define register offset=0x0000 size=4 [ + R2R0 R3R1 +]; + +define register offset=0x0000 size=6 [ + R1R2R0 +]; + +define register offset=0x2000 size=3 [ + A0 A1 +]; + +define register offset=0x3000 size=3 [ + PC # Program Counter + SVP # Save PC Register + VCT # Vector Register + byteIndexOffset # Byte offset for memory (see useByteIndex) + bitIndex # Index offset for bit operations (see useBitIndex) +]; + +define register offset=0x4000 size=3 [ + INTB +]; + +define register offset=0x4000 size=2 [ + INTBL INTBH +]; + +define register offset=0x5000 size=3 [ + SP # Stack Pointer (Represents active stack pointer: ISP or USP) + FB # Frame Base Register + SB # Static Base Register + ISP # Interrupt Stack Pointer +]; + +define register offset=0x6000 size=2 [ + FLG # Flag Register + SVF # Save Flag Register +]; + +@define CARRY "FLG[0,1]" +@define DEBUG "FLG[1,1]" +@define ZERO "FLG[2,1]" +@define SIGN "FLG[3,1]" +@define REG_BANK "FLG[4,1]" +@define OVERFLOW "FLG[5,1]" +@define INTERRUPT "FLG[6,1]" +@define STACK_SEL "FLG[7,1]" +@define IPL "FLG[12,3]" + +define register offset=0x7000 size=2 [ + # These are really 1-Byte registers + DMD0 # DMA mode register + DMD1 # DMA mode register +]; + +define register offset=0x8000 size=2 [ + DCT0 # DMA transfer count register + DCT1 # DMA transfer count register + DRC0 # DMA transfer count reload register + DRC1 # DMA transfer count reload register +]; + +define register offset=0x9000 size=3 [ + DMA0 # DMA memory address register + DMA1 # DMA memory address register + DSA0 # DMA SFR address register + DSA1 # DMA SFR address register + DRA0 # DMA memory address reload register + DRA1 # DMA memory address reload register +]; + +# Define context bits +define register offset=0xA000 size=4 contextreg; + +define context contextreg + useBitIndex = (0, 0) noflow # =1 use bitIndex instead of bit specified by instruction + useByteIndexOffset = (1, 2) noflow + useSrcByteIndexOffset = (1, 1) noflow + useDstByteIndexOffset = (2, 2) noflow + + # transient context: + phase = (3, 4) # guard for saving off modes before starting instructions + indDst = (5, 5) # =1 indirect destination + indSrc = (6, 6) # =1 indirect source + dstFollowsSrc = (7, 8) # =1 destination add-on data follows 5-bit encoded source add-on data + # =2 destination add-on data follows 8-bit data +; + +define token b0(8) + b0_0007 = (0,7) +; + +define token b1(8) + b1_s5 = (4,6) + b1_s5_4 = (6,6) + b1_d5 = (1,3) + b1_d5_4 = (3,3) + b1_d2 = (4,5) + b1_d1_regAx = (0,0) + b1_size_5 = (5,5) + b1_size_4 = (4,4) + b1_size_0 = (0,0) + b1_0707 = (7,7) + b1_0607 = (6,7) + b1_0507 = (5,7) + b1_0505 = (5,5) + b1_0407 = (4,7) + b1_0406 = (4,6) + b1_0405 = (4,5) + b1_0104 = (1,4) + b1_0103 = (1,3) + b1_0007 = (0,7) + b1_0000 = (0,0) +; + +define token b2(8) + b2_d5_reg8 = (6,7) + b2_s5_reg8 = (4,5) + b2_d5_reg16 = (6,7) + b2_s5_reg16 = (4,5) + b2_d5_reg32 = (6,6) # only d0 used to select double register + b2_s5_reg32 = (4,4) # only s0 used to select double register + b2_d5_regAxSF = (6,7) # selects A0, A1, SB or FB + b2_s5_regAxSF = (4,5) # selects A0, A1, SB or FB + b2_d5_regAx = (6,6) + b2_s5_regAx = (4,4) + b2_d5 = (6,7) + b2_s5 = (4,5) + b2_d5_1 = (7,7) + b2_d5_0 = (6,6) + b2_s5_1 = (5,5) + b2_s5_0 = (4,4) + b2_0707 = (7,7) + b2_0606 = (6,6) + b2_0405 = (4,5) + b2_0307 = (3,7) + b2_0305 = (3,5) + b2_0105 = (1,5) + b2_0102 = (1,2) + b2_0101 = (1,1) + b2_0007 = (0,7) + b2_0005 = (0,5) + b2_0003 = (0,3) + b2_0002 = (0,2) + b2_simm4 = (0,3) signed + b2_shiftSign = (3,3) + b2_bit = (0,2) + b2_reg8 = (0,2) + b2_reg16 = (0,2) + b2_creg16 = (0,2) + b2_creg24 = (0,2) + b2_dreg24 = (0,2) + b2_reg32 = (0,0) + b2_regAx = (0,0) +; + +define token imm8(8) + simm8_dat = (0,7) signed + imm8_dat = (0,7) + imm6_dat = (2,7) + cnd_dat = (0,3) + imm8_0001 = (0,1) + regBit7 = (7,7) + regBit6 = (6,6) + regBit5 = (5,5) + regBit4 = (4,4) + regBit3 = (3,3) + regBit2 = (2,2) + regBit1 = (1,1) + regBit0 = (0,0) +; + +define token imm16(16) + simm16_dat = (0,15) signed + imm16_dat = (0,15) +; + +define token imm24(24) + simm24_dat = (0,23) signed + imm24_dat = (0,23) +; + +define token imm32(32) + simm32_dat = (0,31) signed + imm32_dat = (0,31) +; + +attach variables [ b2_s5_reg32 b2_d5_reg32 ] [ R2R0 R3R1 ]; +attach variables [ b2_s5_reg16 b2_d5_reg16 ] [ R2 R3 R0 R1 ]; +attach variables [ b2_s5_reg8 b2_d5_reg8 ] [ R0H R1H R0L R1L ]; +attach variables [ b2_s5_regAx b2_d5_regAx b1_d1_regAx b2_regAx ] [ A0 A1 ]; +attach variables [ b2_s5_regAxSF b2_d5_regAxSF ] [ A0 A1 SB FB ]; +attach variables [ b2_creg16 ] [ DCT0 DCT1 FLG SVF DRC0 DRC1 DMD0 DMD1 ]; +attach variables [ b2_creg24 ] [ INTB SP SB FB SVP VCT _ ISP ]; +attach variables [ b2_dreg24 ] [ _ _ DMA0 DMA1 DRA0 DRA1 DSA0 DSA1 ]; +attach variables [ b2_reg32 ] [ R2R0 R3R1 ]; + +# XCHG register attach +attach variables [ b2_reg8 ] [ R0L R1L _ _ R0H R1H _ _ ]; +attach variables [ b2_reg16 ] [ R0 R1 _ _ R2 R3 _ _ ]; + +# +# PCode Op +# +define pcodeop Break; # BRK +define pcodeop Break2; # BRK2 +define pcodeop DecimalAdd; # DADD +define pcodeop DecimalAddWithCarry; # DADC +define pcodeop DecimalSubtractWithBorrow; # DSBB +define pcodeop DecimalSubtract; # DSUB +define pcodeop Wait; # WAIT + +# +# FLAG MACROS... +# +# Set zero and sign flags from result +macro setResultFlags(result) { + $(SIGN) = (result s< 0x0); + $(ZERO) = (result == 0x0); +} + +# Set carry and overflow flags for addition +macro setAdd3Flags(v1, v2, v3) { + local add13 = v1 + v3; + $(CARRY) = carry(v1,v3) || carry(v2,add13); + $(OVERFLOW) = scarry(v1,v3) || scarry(v2,add13); +} + +# Set carry and overflow flags for addition +macro setAddFlags(v1, v2) { + $(CARRY) = carry(v1, v2); + $(OVERFLOW) = scarry(v1, v2); +} + +# Set overflow flags for subtraction of op3,op2 from op1 (op1-op2-op3) +macro setSubtract3Flags(v1, v2, v3) { + local add12 = v1 - v2; + $(CARRY) = (v1 >= v2) || (add12 >= v3); + $(OVERFLOW) = sborrow(v1, v2) || sborrow(add12, v3); +} + +# Set overflow flags for subtraction of op2 from op1 (op1-op2) +macro setSubtractFlags(v1, v2) { + $(CARRY) = (v1 s>= v2); + $(OVERFLOW) = sborrow(v1, v2); +} + +macro push1(val) { + SP = SP - 2; + *:1 SP = val; +} + +macro push2(val) { + SP = SP - 2; + *:2 SP = val; +} + +macro push3(val) { + SP = SP - 4; + *:3 SP = val; +} + +macro push4(val) { + SP = SP - 4; + *:4 SP = val; +} + +macro pop1(val) { + val = *:1 SP; + SP = SP + 2; +} + +macro pop2(val) { + val = *:2 SP; + SP = SP + 2; +} + +macro pop3(val) { + val = *:3 SP; + SP = SP + 4; +} + +macro pop4(val) { + val = *:4 SP; + SP = SP + 4; +} + +:^instruction is phase=0 & b0_0007 & instruction [ phase=1; ] {} +:^instruction is phase=0 & b0_0007=0x09; instruction [ indDst=1; phase=1; ] {} # indirect destination prefix +:^instruction is phase=0 & b0_0007=0x41; instruction [ indSrc=1; phase=1; ] {} # indirect source prefix +:^instruction is phase=0 & b0_0007=0x49; instruction [ indDst=1; indSrc=1; phase=1; ] {} # indirect source and destination prefix + +# +# Source operand location data +# +# Obtain additional source byte offset as a result of an INDEX instruction (flagged by useSrcByteIndexOffset context bit) +srcIndexOffset: is useSrcByteIndexOffset=0 { export 0:3; } +srcIndexOffset: is useSrcByteIndexOffset=1 { export byteIndexOffset; } + +# Obtain base offset displacement for [AX | SB | FB] - AX and SB uses unsigned displacements, FB uses signed displacement +src5dsp8: imm8_dat^":8" is b1_s5; b2_s5; imm8_dat { export *[const]:3 imm8_dat; } +src5dsp8: simm8_dat^":8" is b1_s5; b2_s5=0x3; simm8_dat { export *[const]:3 simm8_dat; } + +src5dsp16: imm16_dat^":16" is b1_s5; b2_s5; imm16_dat { export *[const]:3 imm16_dat; } +src5dsp16: simm16_dat^":16" is b1_s5; b2_s5=0x3; simm16_dat { export *[const]:3 simm16_dat; } + +src5dsp24: imm24_dat^":24" is b1_s5; b2_s5; imm24_dat { export *[const]:3 imm24_dat; } +src5dsp24: simm24_dat^":24" is b1_s5; b2_s5=0x3; simm24_dat { export *[const]:3 simm24_dat; } + +# src5... Handle 5-bit encoded Source specified by b1_s(3-bits) and b2_s(2-bits) +# Variable length pattern starting at instruction byte b1 +# associated src5 add-on data immediately follows instruction byte b2 +# abs16 and abs24 cases are broken out differently to facilitate export of constant addresses in certain cases +# 1-Byte source value/location specified by 5-bit encoding (b1_d5/b2_d5) - supports indirect prefix and byteIndexOffset +src5B: b2_s5_reg8 is b1_s5=0x4; b2_s5_reg8 { export b2_s5_reg8; } # Rx +src5B: b2_s5_regAx is b1_s5=0x0; b2_s5_1=1 & b2_s5_regAx { tmp:1 = b2_s5_regAx:1; export tmp; } # Ax +src5B: [b2_s5_regAx] is indSrc=1 & b1_s5=0x0; b2_s5_1=1 & b2_s5_regAx { ptr:3 = b2_s5_regAx; export *:1 ptr; } # [Ax] - w/ indirect prefix +src5B: [b2_s5_regAx] is srcIndexOffset & b1_s5=0x0; b2_s5_1=0 & b2_s5_regAx { ptr:3 = b2_s5_regAx + srcIndexOffset; export *:1 ptr; } # [Ax] +src5B: [[b2_s5_regAx]] is indSrc=1 & srcIndexOffset & b1_s5=0x0; b2_s5_1=0 & b2_s5_regAx { ptr:3 = b2_s5_regAx + srcIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [[Ax]] +src5B: src5dsp8^[b2_s5_regAxSF] is (srcIndexOffset & b1_s5=0x1; b2_s5_regAxSF) ... & src5dsp8 { ptr:3 = b2_s5_regAxSF + src5dsp8 + srcIndexOffset; export *:1 ptr; } # dsp:8[Ax|SB|FB] +src5B: [src5dsp8^[b2_s5_regAxSF]] is (indSrc=1 & srcIndexOffset & b1_s5=0x1; b2_s5_regAxSF) ... & src5dsp8 { ptr:3 = b2_s5_regAxSF + src5dsp8 + srcIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [dsp:8[Ax|SB|FB]] +src5B: src5dsp16^[b2_s5_regAxSF] is (srcIndexOffset & b1_s5=0x2; b2_s5_regAxSF) ... & src5dsp16 { ptr:3 = b2_s5_regAxSF + src5dsp16 + srcIndexOffset; export *:1 ptr; } # dsp:16[Ax|SB|FB] +src5B: [src5dsp16^[b2_s5_regAxSF]] is (indSrc=1 & srcIndexOffset & b1_s5=0x2; b2_s5_regAxSF) ... & src5dsp16 { ptr:3 = b2_s5_regAxSF + src5dsp16 + srcIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [dsp:16[Ax|SB|FB]] +src5B: src5dsp24^[b2_s5_regAx] is (srcIndexOffset & b1_s5=0x3; b2_s5_1=0 & b2_s5_regAx) ... & src5dsp24 { ptr:3 = b2_s5_regAx + src5dsp24 + srcIndexOffset; export *:1 ptr; } # dsp:24[Ax] +src5B: [src5dsp24^[b2_s5_regAx]] is (indSrc=1 & srcIndexOffset & b1_s5=0x3; b2_s5_1=0 & b2_s5_regAx) ... & src5dsp24 { ptr:3 = b2_s5_regAx + src5dsp24 + srcIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [dsp:24[Ax]] +src5B: imm16_dat is indSrc=0 & useSrcByteIndexOffset=1 & b1_s5=0x3; b2_s5=0x3; imm16_dat { ptr:3 = imm16_dat + byteIndexOffset; export *:1 ptr; } # abs16 (+byteIndexOffset) +src5B: imm16_dat is indSrc=0 & b1_s5=0x3; b2_s5=0x3; imm16_dat { export *:1 imm16_dat; } # abs16 (special constant address case) +src5B: [imm16_dat] is indSrc=1 & srcIndexOffset & b1_s5=0x3; b2_s5=0x3; imm16_dat { ptr:3 = imm16_dat + srcIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [abs16] +src5B: imm24_dat is indSrc=0 & useSrcByteIndexOffset=1 & b1_s5=0x3; b2_s5=0x2; imm24_dat { ptr:3 = imm24_dat + byteIndexOffset; export *:1 ptr; } # abs24 (+byteIndexOffset) +src5B: imm24_dat is indSrc=0 & b1_s5=0x3; b2_s5=0x2; imm24_dat { export *:1 imm24_dat; } # abs24 (special constant address case) +src5B: [imm24_dat] is indSrc=1 & srcIndexOffset & b1_s5=0x3; b2_s5=0x2; imm24_dat { ptr:3 = imm24_dat + srcIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [abs24] + +# 2-Byte source value/location specified by 5-bit encoding (b1_d5/b2_d5) - supports indirect prefix and byteIndexOffset +src5W: b2_s5_reg16 is b1_s5=0x4; b2_s5_reg16 { export b2_s5_reg16; } # Rx +src5W: b2_s5_regAx is b1_s5=0x0; b2_s5_1=1 & b2_s5_regAx { tmp:2 = b2_s5_regAx:2; export tmp; } # Ax +src5W: [b2_s5_regAx] is indSrc=1 & b1_s5=0x0; b2_s5_1=1 & b2_s5_regAx { ptr:3 = b2_s5_regAx; export *:2 ptr; } # [Ax] - w/ indirect prefix +src5W: [b2_s5_regAx] is srcIndexOffset & b1_s5=0x0; b2_s5_1=0 & b2_s5_regAx { ptr:3 = b2_s5_regAx + srcIndexOffset; export *:2 ptr; } # [Ax] +src5W: [[b2_s5_regAx]] is indSrc=1 & srcIndexOffset & b1_s5=0x0; b2_s5_1=0 & b2_s5_regAx { ptr:3 = b2_s5_regAx + srcIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [[Ax]] +src5W: src5dsp8^[b2_s5_regAxSF] is (srcIndexOffset & b1_s5=0x1; b2_s5_regAxSF) ... & src5dsp8 { ptr:3 = b2_s5_regAxSF + src5dsp8 + srcIndexOffset; export *:2 ptr; } # dsp:8[Ax|SB|FB] +src5W: [src5dsp8^[b2_s5_regAxSF]] is (indSrc=1 & srcIndexOffset & b1_s5=0x1; b2_s5_regAxSF) ... & src5dsp8 { ptr:3 = b2_s5_regAxSF + src5dsp8 + srcIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [dsp:8[Ax|SB|FB]] +src5W: src5dsp16^[b2_s5_regAxSF] is (srcIndexOffset & b1_s5=0x2; b2_s5_regAxSF) ... & src5dsp16 { ptr:3 = b2_s5_regAxSF + src5dsp16 + srcIndexOffset; export *:2 ptr; } # dsp:16[Ax|SB|FB] +src5W: [src5dsp16^[b2_s5_regAxSF]] is (indSrc=1 & srcIndexOffset & b1_s5=0x2; b2_s5_regAxSF) ... & src5dsp16 { ptr:3 = b2_s5_regAxSF + src5dsp16 + srcIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [dsp:16[Ax|SB|FB]] +src5W: src5dsp24^[b2_s5_regAx] is (srcIndexOffset & b1_s5=0x3; b2_s5_1=0 & b2_s5_regAx) ... & src5dsp24 { ptr:3 = b2_s5_regAx + src5dsp24 + srcIndexOffset; export *:2 ptr; } # dsp:24[Ax] +src5W: [src5dsp24^[b2_s5_regAx]] is (indSrc=1 & srcIndexOffset & b1_s5=0x3; b2_s5_1=0 & b2_s5_regAx) ... & src5dsp24 { ptr:3 = b2_s5_regAx + src5dsp24 + srcIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [dsp:24[Ax]] +src5W: imm16_dat is indSrc=0 & useSrcByteIndexOffset=1 & b1_s5=0x3; b2_s5=0x3; imm16_dat { ptr:3 = imm16_dat + byteIndexOffset; export *:2 ptr; } # abs16 (+byteIndexOffset) +src5W: imm16_dat is indSrc=0 & b1_s5=0x3; b2_s5=0x3; imm16_dat { export *:2 imm16_dat; } # abs16 (special constant address case) +src5W: [imm16_dat] is indSrc=1 & srcIndexOffset & b1_s5=0x3; b2_s5=0x3; imm16_dat { ptr:3 = imm16_dat + srcIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [abs16] +src5W: imm24_dat is indSrc=0 & useSrcByteIndexOffset=1 & b1_s5=0x3; b2_s5=0x2; imm24_dat { ptr:3 = imm24_dat + byteIndexOffset; export *:2 ptr; } # abs24 (+byteIndexOffset) +src5W: imm24_dat is indSrc=0 & b1_s5=0x3; b2_s5=0x2; imm24_dat { export *:2 imm24_dat; } # abs24 (special constant address case) +src5W: [imm24_dat] is indSrc=1 & srcIndexOffset & b1_s5=0x3; b2_s5=0x2; imm24_dat { ptr:3 = imm24_dat + srcIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [abs24] + +# 4-Byte source value/location specified by 5-bit encoding (b1_d5/b2_d5) - supports indirect prefix and byteIndexOffset +src5L: b2_s5_reg32 is b1_s5=0x4; b2_s5_1=1 & b2_s5_reg32 { export b2_s5_reg32; } # Rx +src5L: b2_s5_regAx is b1_s5=0x0; b2_s5_1=1 & b2_s5_regAx { tmp:4 = zext(b2_s5_regAx); export tmp; } # Ax +src5L: [b2_s5_regAx] is indSrc=1 & b1_s5=0x0; b2_s5_1=1 & b2_s5_regAx { ptr:3 = b2_s5_regAx; export *:4 ptr; } # [Ax] - w/ indirect prefix +src5L: [b2_s5_regAx] is srcIndexOffset & b1_s5=0x0; b2_s5_1=0 & b2_s5_regAx { ptr:3 = b2_s5_regAx + srcIndexOffset; export *:4 ptr; } # [Ax] +src5L: [[b2_s5_regAx]] is indSrc=1 & srcIndexOffset & b1_s5=0x0; b2_s5_1=0 & b2_s5_regAx { ptr:3 = b2_s5_regAx + srcIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [[Ax]] +src5L: src5dsp8^[b2_s5_regAxSF] is (srcIndexOffset & b1_s5=0x1; b2_s5_regAxSF) ... & src5dsp8 { ptr:3 = b2_s5_regAxSF + src5dsp8 + srcIndexOffset; export *:4 ptr; } # dsp:8[Ax|SB|FB] +src5L: [src5dsp8^[b2_s5_regAxSF]] is (indSrc=1 & srcIndexOffset & b1_s5=0x1; b2_s5_regAxSF) ... & src5dsp8 { ptr:3 = b2_s5_regAxSF + src5dsp8 + srcIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [dsp:8[Ax|SB|FB]] +src5L: src5dsp16^[b2_s5_regAxSF] is (srcIndexOffset & b1_s5=0x2; b2_s5_regAxSF) ... & src5dsp16 { ptr:3 = b2_s5_regAxSF + src5dsp16 + srcIndexOffset; export *:4 ptr; } # dsp:16[Ax|SB|FB] +src5L: [src5dsp16^[b2_s5_regAxSF]] is (indSrc=1 & srcIndexOffset & b1_s5=0x2; b2_s5_regAxSF) ... & src5dsp16 { ptr:3 = b2_s5_regAxSF + src5dsp16 + srcIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [dsp:16[Ax|SB|FB]] +src5L: src5dsp24^[b2_s5_regAx] is (srcIndexOffset & b1_s5=0x3; b2_s5_1=0 & b2_s5_regAx) ... & src5dsp24 { ptr:3 = b2_s5_regAx + src5dsp24 + srcIndexOffset; export *:4 ptr; } # dsp:24[Ax] +src5L: [src5dsp24^[b2_s5_regAx]] is (indSrc=1 & srcIndexOffset & b1_s5=0x3; b2_s5_1=0 & b2_s5_regAx) ... & src5dsp24 { ptr:3 = b2_s5_regAx + src5dsp24 + srcIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [dsp:24[Ax]] +src5L: imm16_dat is indSrc=0 & useSrcByteIndexOffset=1 & b1_s5=0x3; b2_s5=0x3; imm16_dat { ptr:3 = imm16_dat + byteIndexOffset; export *:4 ptr; } # abs16 (+byteIndexOffset) +src5L: imm16_dat is indSrc=0 & b1_s5=0x3; b2_s5=0x3; imm16_dat { export *:4 imm16_dat; } # abs16 (special constant address case) +src5L: [imm16_dat] is indSrc=1 & srcIndexOffset & b1_s5=0x3; b2_s5=0x3; imm16_dat { ptr:3 = imm16_dat + srcIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [abs16] +src5L: imm24_dat is indSrc=0 & useSrcByteIndexOffset=1 & b1_s5=0x3; b2_s5=0x2; imm24_dat { ptr:3 = imm24_dat + byteIndexOffset; export *:4 ptr; } # abs24 (+byteIndexOffset) +src5L: imm24_dat is indSrc=0 & b1_s5=0x3; b2_s5=0x2; imm24_dat { export *:4 imm24_dat; } # abs24 (special constant address case) +src5L: [imm24_dat] is indSrc=1 & srcIndexOffset & b1_s5=0x3; b2_s5=0x2; imm24_dat { ptr:3 = imm24_dat + srcIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [abs24] + +# +# The following macros are used to elliminate illegal bit patterns when using src5 +# These should be used by constructor pattern matching instead of the corresponding src5 subconstructor +# +@define SRC5B "((b1_s5=4 | b1_s5_4=0) ... & src5B)" +@define SRC5W "((b1_s5=4 | b1_s5_4=0) ... & src5W)" +@define SRC5L "((b1_s5=4 | b1_s5_4=0) ... & src5L)" + +# +# Destination operand location data (may also be used as a source in certain cases) +# +# Skip instruction and source add-on bytes which occur before destination add-on bytes +# Starting position is at b1 +skipBytesBeforeDst5: is b1_s5; b2_s5 { } +skipBytesBeforeDst5: is dstFollowsSrc=1 & b1_s5=1; b2_s5; imm8_dat { } # src5: dsp8 +skipBytesBeforeDst5: is dstFollowsSrc=1 & b1_s5=2; b2_s5; imm16_dat { } # src5: dsp16 +skipBytesBeforeDst5: is dstFollowsSrc=1 & b1_s5=3; b2_s5; imm24_dat { } # src5: dsp24/abs24 +skipBytesBeforeDst5: is dstFollowsSrc=1 & b1_s5=3; b2_s5=3; imm16_dat { } # src5: abs16 +skipBytesBeforeDst5: is dstFollowsSrc=2 & b1_d5; b2_d5; imm8_dat { } # dsp8 + +# Obtain additional destination byte offset as a result of an INDEX instruction (flagged by useDstByteIndexOffset context bit) +dstIndexOffset: is useDstByteIndexOffset=0 { export 0:3; } +dstIndexOffset: is useDstByteIndexOffset=1 { export byteIndexOffset; } + +# Obtain base offset displacement for [AX | SB | FB] - AX and SB uses unsigned displacements, FB uses signed displacement +dst5dsp8: imm8_dat^":8" is (skipBytesBeforeDst5; imm8_dat) { export *[const]:3 imm8_dat; } +dst5dsp8: simm8_dat^":8" is (b1_d5; b2_d5=0x3) ... & (skipBytesBeforeDst5; simm8_dat) { export *[const]:3 simm8_dat; } +dst5dsp16: imm16_dat^":16" is (skipBytesBeforeDst5; imm16_dat) { export *[const]:3 imm16_dat; } +dst5dsp16: simm16_dat^":16" is (b1_d5; b2_d5=0x3) ... & (skipBytesBeforeDst5; simm16_dat) { export *[const]:3 simm16_dat; } +dst5dsp24: imm24_dat^":24" is (skipBytesBeforeDst5; imm24_dat) { export *[const]:3 imm24_dat; } + +# dst5... Handle 5-bit encoded Destination specified by b1_d5(3-bits) and b2_d5(2-bits) +# Ax direct case is read-only! Instruction must use dst5Ax for write/update case +# Variable length pattern starting at instruction byte b1 +# abs16 and abs24 cases are broken out differently to facilitate export of constant addresses in certain cases +# 1-Byte destination value/location specified by 5-bit encoding (b1_d5/b2_d5) - supports indirect prefix and byteIndexOffset +dst5B: b2_d5_reg8 is b1_d5=0x4; b2_d5_reg8 { export b2_d5_reg8; } # Rx +dst5B: b2_d5_regAx is b1_d5=0x0; b2_d5_1=1 & b2_d5_regAx { tmp:1 = b2_d5_regAx:1; export tmp; } # Ax - read-only use ! +dst5B: [b2_d5_regAx] is indDst=1 & b1_d5=0x0; b2_d5_1=1 & b2_d5_regAx { ptr:3 = b2_d5_regAx; export *:1 ptr; } # [Ax] - w/ indirect prefix +dst5B: [b2_d5_regAx] is dstIndexOffset & b1_d5=0x0; b2_d5_1=0 & b2_d5_regAx { ptr:3 = b2_d5_regAx + dstIndexOffset; export *:1 ptr; } # [Ax] +dst5B: [[b2_d5_regAx]] is indDst=1 & dstIndexOffset & b1_d5=0x0; b2_d5_1=0 & b2_d5_regAx { ptr:3 = b2_d5_regAx + dstIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [[Ax]] +dst5B: dst5dsp8^[b2_d5_regAxSF] is (dstIndexOffset & b1_d5=0x1; b2_d5_regAxSF) ... & dst5dsp8 { ptr:3 = b2_d5_regAxSF + dst5dsp8 + dstIndexOffset; export *:1 ptr; } # dsp:8[Ax|SB|FB] +dst5B: [dst5dsp8^[b2_d5_regAxSF]] is (indDst=1 & dstIndexOffset & b1_d5=0x1; b2_d5_regAxSF) ... & dst5dsp8 { ptr:3 = b2_d5_regAxSF + dst5dsp8 + dstIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [dsp:8[Ax|SB|FB]] +dst5B: dst5dsp16^[b2_d5_regAxSF] is (dstIndexOffset & b1_d5=0x2; b2_d5_regAxSF) ... & dst5dsp16 { ptr:3 = b2_d5_regAxSF + dst5dsp16 + dstIndexOffset; export *:1 ptr; } # dsp:16[Ax|SB|FB] +dst5B: [dst5dsp16^[b2_d5_regAxSF]] is (indDst=1 & dstIndexOffset & b1_d5=0x2; b2_d5_regAxSF) ... & dst5dsp16 { ptr:3 = b2_d5_regAxSF + dst5dsp16 + dstIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [dsp:16[Ax|SB|FB]] +dst5B: dst5dsp24^[b2_d5_regAx] is (dstIndexOffset & b1_d5=0x3; b2_d5_1=0 & b2_d5_regAx) ... & dst5dsp24 { ptr:3 = b2_d5_regAx + dst5dsp24 + dstIndexOffset; export *:1 ptr; } # dsp:24[Ax] +dst5B: [dst5dsp24^[b2_d5_regAx]] is (indDst=1 & dstIndexOffset & b1_d5=0x3; b2_d5_1=0 & b2_d5_regAx) ... & dst5dsp24 { ptr:3 = b2_d5_regAx + dst5dsp24 + dstIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [dsp:24[Ax]] +dst5B: imm16_dat is (indDst=0 & useDstByteIndexOffset=1 & b1_d5=0x3; b2_d5=0x3) ... & (skipBytesBeforeDst5; imm16_dat) { ptr:3 = imm16_dat + byteIndexOffset; export *:1 ptr; } # abs16 (+byteIndexOffset) +dst5B: imm16_dat is (indDst=0 & b1_d5=0x3; b2_d5=0x3) ... & (skipBytesBeforeDst5; imm16_dat) { export *:1 imm16_dat; } # abs16 (special constant address case) +dst5B: [imm16_dat] is (indDst=1 & dstIndexOffset & b1_d5=0x3; b2_d5=0x3) ... & (skipBytesBeforeDst5; imm16_dat) { ptr:3 = imm16_dat + dstIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [abs16] +dst5B: imm24_dat is (indDst=0 & useDstByteIndexOffset=1 & b1_d5=0x3; b2_d5=0x2) ... & (skipBytesBeforeDst5; imm24_dat) { ptr:3 = imm24_dat + byteIndexOffset; export *:1 ptr; } # abs24 +dst5B: imm24_dat is (indDst=0 & b1_d5=0x3; b2_d5=0x2) ... & (skipBytesBeforeDst5; imm24_dat) { export *:1 imm24_dat; } # abs24 (special constant address case) +dst5B: [imm24_dat] is (indDst=1 & dstIndexOffset & b1_d5=0x3; b2_d5=0x2) ... & (skipBytesBeforeDst5; imm24_dat) { ptr:3 = imm24_dat + dstIndexOffset; ptr = *:3 ptr; export *:1 ptr; } # [abs24] + +# 2-Byte destination value/location specified by 5-bit encoding (b1_d5/b2_d5) - supports indirect prefix and byteIndexOffset +dst5W: b2_d5_reg16 is b1_d5=0x4; b2_d5_reg16 { export b2_d5_reg16; } # Rx +dst5W: b2_d5_regAx is b1_d5=0x0; b2_d5_1=1 & b2_d5_regAx { tmp:2 = b2_d5_regAx:2; export tmp; } # Ax - read-only use ! +dst5W: [b2_d5_regAx] is indDst=1 & b1_d5=0x0; b2_d5_1=1 & b2_d5_regAx { ptr:3 = b2_d5_regAx; export *:2 ptr; } # [Ax] - w/ indirect prefix +dst5W: [b2_d5_regAx] is dstIndexOffset & b1_d5=0x0; b2_d5_1=0 & b2_d5_regAx { ptr:3 = b2_d5_regAx + dstIndexOffset; export *:2 ptr; } # [Ax] +dst5W: [[b2_d5_regAx]] is indDst=1 & dstIndexOffset & b1_d5=0x0; b2_d5_1=0 & b2_d5_regAx { ptr:3 = b2_d5_regAx + dstIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [[Ax]] +dst5W: dst5dsp8^[b2_d5_regAxSF] is (dstIndexOffset & b1_d5=0x1; b2_d5_regAxSF) ... & dst5dsp8 { ptr:3 = b2_d5_regAxSF + dst5dsp8 + dstIndexOffset; export *:2 ptr; } # dsp:8[Ax|SB|FB] +dst5W: [dst5dsp8^[b2_d5_regAxSF]] is (indDst=1 & dstIndexOffset & b1_d5=0x1; b2_d5_regAxSF) ... & dst5dsp8 { ptr:3 = b2_d5_regAxSF + dst5dsp8 + dstIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [dsp:8[Ax|SB|FB]] +dst5W: dst5dsp16^[b2_d5_regAxSF] is (dstIndexOffset & b1_d5=0x2; b2_d5_regAxSF) ... & dst5dsp16 { ptr:3 = b2_d5_regAxSF + dst5dsp16 + dstIndexOffset; export *:2 ptr; } # dsp:16[Ax|SB|FB] +dst5W: [dst5dsp16^[b2_d5_regAxSF]] is (indDst=1 & dstIndexOffset & b1_d5=0x2; b2_d5_regAxSF) ... & dst5dsp16 { ptr:3 = b2_d5_regAxSF + dst5dsp16 + dstIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [dsp:16[Ax|SB|FB]] +dst5W: dst5dsp24^[b2_d5_regAx] is (dstIndexOffset & b1_d5=0x3; b2_d5_1=0 & b2_d5_regAx) ... & dst5dsp24 { ptr:3 = b2_d5_regAx + dst5dsp24 + dstIndexOffset; export *:2 ptr; } # dsp:24[Ax] +dst5W: [dst5dsp24^[b2_d5_regAx]] is (indDst=1 & dstIndexOffset & b1_d5=0x3; b2_d5_1=0 & b2_d5_regAx) ... & dst5dsp24 { ptr:3 = b2_d5_regAx + dst5dsp24 + dstIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [dsp:24[Ax]] +dst5W: imm16_dat is (indDst=0 & useDstByteIndexOffset=1 & b1_d5=0x3; b2_d5=0x3) ... & (skipBytesBeforeDst5; imm16_dat) { ptr:3 = imm16_dat + byteIndexOffset; export *:2 ptr; } # abs16 (+byteIndexOffset) +dst5W: imm16_dat is (indDst=0 & b1_d5=0x3; b2_d5=0x3) ... & (skipBytesBeforeDst5; imm16_dat) { export *:2 imm16_dat; } # abs16 (special constant address case) +dst5W: [imm16_dat] is (indDst=1 & dstIndexOffset & b1_d5=0x3; b2_d5=0x3) ... & (skipBytesBeforeDst5; imm16_dat) { ptr:3 = imm16_dat + dstIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [abs16] +dst5W: imm24_dat is (indDst=0 & useDstByteIndexOffset=1 & b1_d5=0x3; b2_d5=0x2) ... & (skipBytesBeforeDst5; imm24_dat) { ptr:3 = imm24_dat + byteIndexOffset; export *:2 ptr; } # abs24 +dst5W: imm24_dat is (indDst=0 & b1_d5=0x3; b2_d5=0x2) ... & (skipBytesBeforeDst5; imm24_dat) { export *:2 imm24_dat; } # abs24 (special constant address case) +dst5W: [imm24_dat] is (indDst=1 & dstIndexOffset & b1_d5=0x3; b2_d5=0x2) ... & (skipBytesBeforeDst5; imm24_dat) { ptr:3 = imm24_dat + dstIndexOffset; ptr = *:3 ptr; export *:2 ptr; } # [abs24] + +# 4-Byte destination value/location specified by 5-bit encoding (b1_d5/b2_d5) - supports indirect prefix and byteIndexOffset +dst5L: b2_d5_reg32 is b1_d5=0x4; b2_d5_1=1 & b2_d5_reg32 { export b2_d5_reg32; } # Rx +dst5L: b2_d5_regAx is b1_d5=0x0; b2_d5_1=1 & b2_d5_regAx { tmp:4 = zext(b2_d5_regAx); export tmp; } # Ax - read-only use ! +dst5L: [b2_d5_regAx] is indDst=1 & b1_d5=0x0; b2_d5_1=1 & b2_d5_regAx { ptr:3 = b2_d5_regAx; export *:4 ptr; } # [Ax] - w/ indirect prefix +dst5L: [b2_d5_regAx] is dstIndexOffset & b1_d5=0x0; b2_d5_1=0 & b2_d5_regAx { ptr:3 = b2_d5_regAx + dstIndexOffset; export *:4 ptr; } # [Ax] +dst5L: [[b2_d5_regAx]] is indDst=1 & dstIndexOffset & b1_d5=0x0; b2_d5_1=0 & b2_d5_regAx { ptr:3 = b2_d5_regAx + dstIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [[Ax]] +dst5L: dst5dsp8^[b2_d5_regAxSF] is (dstIndexOffset & b1_d5=0x1; b2_d5_regAxSF) ... & dst5dsp8 { ptr:3 = b2_d5_regAxSF + dst5dsp8 + dstIndexOffset; export *:4 ptr; } # dsp:8[Ax|SB|FB] +dst5L: [dst5dsp8^[b2_d5_regAxSF]] is (indDst=1 & dstIndexOffset & b1_d5=0x1; b2_d5_regAxSF) ... & dst5dsp8 { ptr:3 = b2_d5_regAxSF + dst5dsp8 + dstIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [dsp:8[Ax|SB|FB]] +dst5L: dst5dsp16^[b2_d5_regAxSF] is (dstIndexOffset & b1_d5=0x2; b2_d5_regAxSF) ... & dst5dsp16 { ptr:3 = b2_d5_regAxSF + dst5dsp16 + dstIndexOffset; export *:4 ptr; } # dsp:16[Ax|SB|FB] +dst5L: [dst5dsp16^[b2_d5_regAxSF]] is (indDst=1 & dstIndexOffset & b1_d5=0x2; b2_d5_regAxSF) ... & dst5dsp16 { ptr:3 = b2_d5_regAxSF + dst5dsp16 + dstIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [dsp:16[Ax|SB|FB]] +dst5L: dst5dsp24^[b2_d5_regAx] is (dstIndexOffset & b1_d5=0x3; b2_d5_1=0 & b2_d5_regAx) ... & dst5dsp24 { ptr:3 = b2_d5_regAx + dst5dsp24 + dstIndexOffset; export *:4 ptr; } # dsp:24[Ax] +dst5L: [dst5dsp24^[b2_d5_regAx]] is (indDst=1 & dstIndexOffset & b1_d5=0x3; b2_d5_1=0 & b2_d5_regAx) ... & dst5dsp24 { ptr:3 = b2_d5_regAx + dst5dsp24 + dstIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [dsp:24[Ax]] +dst5L: imm16_dat is (indDst=0 & useDstByteIndexOffset=1 & b1_d5=0x3; b2_d5=0x3) ... & (skipBytesBeforeDst5; imm16_dat) { ptr:3 = imm16_dat + byteIndexOffset; export *:4 ptr; } # abs16 (+byteIndexOffset) +dst5L: imm16_dat is (indDst=0 & b1_d5=0x3; b2_d5=0x3) ... & (skipBytesBeforeDst5; imm16_dat) { export *:4 imm16_dat; } # abs16 (special constant address case) +dst5L: [imm16_dat] is (indDst=1 & dstIndexOffset & b1_d5=0x3; b2_d5=0x3) ... & (skipBytesBeforeDst5; imm16_dat) { ptr:3 = imm16_dat + dstIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [abs16] +dst5L: imm24_dat is (indDst=0 & useDstByteIndexOffset=1 & b1_d5=0x3; b2_d5=0x2) ... & (skipBytesBeforeDst5; imm24_dat) { ptr:3 = imm24_dat + byteIndexOffset; export *:4 ptr; } # abs24 +dst5L: imm24_dat is (indDst=0 & b1_d5=0x3; b2_d5=0x2) ... & (skipBytesBeforeDst5; imm24_dat) { export *:4 imm24_dat; } # abs24 (special constant address case) +dst5L: [imm24_dat] is (indDst=1 & dstIndexOffset & b1_d5=0x3; b2_d5=0x2) ... & (skipBytesBeforeDst5; imm24_dat) { ptr:3 = imm24_dat + dstIndexOffset; ptr = *:3 ptr; export *:4 ptr; } # [abs24] + +# 3-Byte destination effective address specified by 5-bit encoding (b1_d5/b2_d5) +dst5A: dst5dsp8^[b2_d5_regAxSF] is (b1_d5=0x1; b2_d5_regAxSF) ... & dst5dsp8 { ptr:3 = b2_d5_regAxSF + dst5dsp8; export ptr; } # dsp:8[Ax|SB|FB] +dst5A: dst5dsp16^[b2_d5_regAxSF] is (b1_d5=0x2; b2_d5_regAxSF) ... & dst5dsp16 { ptr:3 = b2_d5_regAxSF + dst5dsp16; export ptr; } # dsp:16[Ax|SB|FB] +dst5A: dst5dsp24^[b2_d5_regAx] is (b1_d5=0x3; b2_d5_1=0 & b2_d5_regAx) ... & dst5dsp24 { ptr:3 = b2_d5_regAx + dst5dsp24; export ptr; } # dsp:24[Ax] +dst5A: imm16_dat is (b1_d5=0x3; b2_d5=0x3) ... & (skipBytesBeforeDst5; imm16_dat) { export *[const]:3 imm16_dat; } # abs16 (special constant address case) +dst5A: imm24_dat is (b1_d5=0x3; b2_d5=0x2) ... & (skipBytesBeforeDst5; imm24_dat) { export *[const]:3 imm24_dat; } # abs24 (special constant address case) + +# Ax destination specified by 5-bit encoding (b1_d5/b2_d5) +# NOTE! Ax destination is special case and must be handled seperately by each instruction +# Starting position is at instruction b1 +dst5Ax: b2_d5_regAx is b1_d5; b2_d5_regAx { export b2_d5_regAx; } + +# 1/2/4-Byte destination value/location specified by 5-bit encoding (b1_d5/b2_d5) +# This handles the case for dst5B, dst5W and dst5L where 5-bit encoded Source (src5) add-on bytes may exist before Destination add-on bytes +# Variable length pattern starting at instruction byte b1 +dst5B_afterSrc5: dst5B is dst5B [ dstFollowsSrc=1; ] { export dst5B; } + +dst5W_afterSrc5: dst5W is dst5W [ dstFollowsSrc=1; ] { export dst5W; } + +dst5L_afterSrc5: dst5L is dst5L [ dstFollowsSrc=1; ] { export dst5L; } + +# 1/2/4-Byte destination value/location specified by 5-bit encoding (b1_d5/b2_d5) +# This handles the case for dst5B, dst5W and dst5L where Dsp8 add-on bytes always exist before Destination add-on bytes +# Variable length pattern starting at instruction byte b1 +dst5B_afterDsp8: dst5B is dst5B [ dstFollowsSrc=2; ] { export dst5B; } + +dst5W_afterDsp8: dst5W is dst5W [ dstFollowsSrc=2; ] { export dst5W; } + +# +# The following macros are used to elliminate illegal bit patterns when using dst5 +# These should be used by constructor pattern matching instead of the corresponding dst5 subconstructor +# +@define DST5B "((b1_d5=4 | b1_d5_4=0) ... & dst5B)" +@define DST5W "((b1_d5=4 | b1_d5_4=0) ... & dst5W)" +@define DST5L "((b1_d5=4 | b1_d5_4=0) ... & dst5L)" +@define DST5A "((b1_d5_4=0) ... & dst5A)" +@define DST5AX "((b1_d5=0x0; b2_d5_1=1) & dst5Ax)" +@define DST5B_AFTER_SRC5 "((b1_d5=4 | b1_d5_4=0) ... & dst5B_afterSrc5)" +@define DST5W_AFTER_SRC5 "((b1_d5=4 | b1_d5_4=0) ... & dst5W_afterSrc5)" +@define DST5L_AFTER_SRC5 "((b1_d5=4 | b1_d5_4=0) ... & dst5L_afterSrc5)" +@define DST5B_AFTER_DSP8 "((b1_d5=4 | b1_d5_4=0) ... & dst5B_afterDsp8)" +@define DST5W_AFTER_DSP8 "((b1_d5=4 | b1_d5_4=0) ... & dst5W_afterDsp8)" +@define DST5L_AFTER_DSP8 "((b1_d5=4 | b1_d5_4=0) ... & dst5L_afterDsp8)" + +# dst2... Handle 2-bit encoded Destination specified by b1_d2 +# Variable length pattern starting at instruction byte b1 +# TODO? Certain uses of dst2 should exclude the R0 case (b1_d2=0) +# 1-Byte destination value/location specified by 2-bit encoding (b1_d2) +dst2B: R0L is b1_d2=0 & R0L { export R0L; } +dst2B: imm16_dat is b1_d2=1; imm16_dat { export *:1 imm16_dat; } +dst2B: [imm16_dat] is indDst=1 & b1_d2=1; imm16_dat { ptr:3 = imm16_dat; ptr = *:3 ptr; export *:1 ptr; } +dst2B: imm8_dat^":8"^[SB] is b1_d2=2 & SB; imm8_dat { ptr:3 = SB + imm8_dat; export *:1 ptr; } +dst2B: [imm8_dat^":8"^[SB]] is indDst=1 & b1_d2=2 & SB; imm8_dat { ptr:3 = SB + imm8_dat; ptr = *:3 ptr; export *:1 ptr; } +dst2B: simm8_dat^":8"^[FB] is b1_d2=3 & FB; simm8_dat { ptr:3 = FB + simm8_dat; export *:1 ptr; } +dst2B: [simm8_dat^":8"^[FB]] is indDst=1 & b1_d2=3 & FB; simm8_dat { ptr:3 = FB + simm8_dat; ptr = *:3 ptr; export *:1 ptr; } + +# 2-Byte destination value/location specified by 2-bit encoding (b1_d2) +dst2W: R0 is b1_d2=0 & R0 { export R0; } +dst2W: imm16_dat is b1_d2=1; imm16_dat { export *:2 imm16_dat; } +dst2W: [imm16_dat] is indDst=1 & b1_d2=1; imm16_dat { ptr:3 = imm16_dat; ptr = *:3 ptr; export *:2 ptr; } +dst2W: imm8_dat^":8"^[SB] is b1_d2=2 & SB; imm8_dat { ptr:3 = SB + imm8_dat; export *:2 ptr; } +dst2W: [imm8_dat^":8"^[SB]] is indDst=1 & b1_d2=2 & SB; imm8_dat { ptr:3 = SB + imm8_dat; ptr = *:3 ptr; export *:2 ptr; } +dst2W: simm8_dat^":8"^[FB] is b1_d2=3 & FB; simm8_dat { ptr:3 = FB + simm8_dat; export *:2 ptr; } +dst2W: [simm8_dat^":8"^[FB]] is indDst=1 & b1_d2=3 & FB; simm8_dat { ptr:3 = FB + simm8_dat; ptr = *:3 ptr; export *:2 ptr; } + +# 4-Byte destination value/location specified by 2-bit encoding (b1_d2) +dst2L: R2R0 is b1_d2=0 & R2R0 { export R2R0; } +dst2L: imm16_dat is b1_d2=1; imm16_dat { export *:4 imm16_dat; } +dst2L: [imm16_dat] is indDst=1 & b1_d2=1; imm16_dat { ptr:3 = imm16_dat; ptr = *:3 ptr; export *:4 ptr; } +dst2L: imm8_dat^":8"^[SB] is b1_d2=2 & SB; imm8_dat { ptr:3 = SB + imm8_dat; export *:4 ptr; } +dst2L: [imm8_dat^":8"^[SB]] is indDst=1 & b1_d2=2 & SB; imm8_dat { ptr:3 = SB + imm8_dat; ptr = *:3 ptr; export *:4 ptr; } +dst2L: simm8_dat^":8"^[FB] is b1_d2=3 & FB; simm8_dat { ptr:3 = FB + simm8_dat; export *:4 ptr; } +dst2L: [simm8_dat^":8"^[FB]] is indDst=1 & b1_d2=3 & FB; simm8_dat { ptr:3 = FB + simm8_dat; ptr = *:3 ptr; export *:4 ptr; } + +dsp8spB: simm8_dat^":8"^[SP] is simm8_dat & SP { ptr:3 = SP + simm8_dat; export *:1 ptr; } + +dsp8spW: simm8_dat^":8"^[SP] is simm8_dat & SP { ptr:3 = SP + simm8_dat; export *:2 ptr; } + +# +# Bit base - associated add-on data immediately follows instruction byte b2 +# (Ax destination case must be handled seperately) +# +# Obtain bitbase offset displacement for [AX | SB | FB] - AX and SB uses unsigned displacements, FB uses signed displacement +bitbaseDsp8: imm8_dat^":11" is b1_d5; b2_d5; imm8_dat { export *[const]:3 imm8_dat; } +bitbaseDsp8: simm8_dat^":11" is b1_d5; b2_d5=0x3; simm8_dat { export *[const]:3 simm8_dat; } + +bitbaseDsp16: imm16_dat^":19" is b1_d5; b2_d5; imm16_dat { export *[const]:3 imm16_dat; } +bitbaseDsp16: simm16_dat^":19" is b1_d5; b2_d5=0x3; simm16_dat { export *[const]:3 simm16_dat; } + +bitbaseDsp24: imm24_dat^":27" is b1_d5; b2_d5; imm24_dat { export *[const]:3 imm24_dat; } +bitbaseDsp24: simm24_dat^":27" is b1_d5; b2_d5=0x3; simm24_dat { export *[const]:3 simm24_dat; } + +bitbase: b2_d5_reg8 is useBitIndex=0 & b1_d5=0x4; b2_d5_reg8 { export b2_d5_reg8; } # Rx +bitbase: b2_d5_regAx is useBitIndex=0 & b1_d5=0x0; b2_d5_1=1 & b2_d5_regAx { tmp:1 = b2_d5_regAx:1; export tmp; } # Ax - read-only case +bitbase: [b2_d5_regAx] is useBitIndex=0 & b1_d5=0x0; b2_d5_1=0 & b2_d5_regAx { ptr:3 = b2_d5_regAx; export *:1 ptr; } # [Ax] +bitbase: bitbaseDsp8^[b2_d5_regAxSF] is (useBitIndex=0 & b1_d5=0x1; b2_d5_regAxSF) ... & bitbaseDsp8 { ptr:3 = b2_d5_regAxSF + bitbaseDsp8; export *:1 ptr; } # base:11[Ax|SB|FB] +bitbase: bitbaseDsp16^[b2_d5_regAxSF] is (useBitIndex=0 & b1_d5=0x2; b2_d5_regAxSF) ... & bitbaseDsp16 { ptr:3 = b2_d5_regAxSF + bitbaseDsp16; export *:1 ptr; } # base:19[Ax|SB|FB] +bitbase: bitbaseDsp24^[b2_d5_regAx] is (useBitIndex=0 & b1_d5=0x3; b2_d5_1=0 & b2_d5_regAx) ... & bitbaseDsp24 { ptr:3 = b2_d5_regAx + bitbaseDsp24; export *:1 ptr; } # base:27[Ax] +bitbase: imm16_dat^":19" is useBitIndex=0 & b1_d5=0x3; b2_d5=0x3; imm16_dat { export *:1 imm16_dat; } # base:19 +bitbase: imm24_dat^":27" is useBitIndex=0 & b1_d5=0x3; b2_d5=0x2; imm24_dat { export *:1 imm24_dat; } # base:27 + +bitbase: [b2_d5_regAx] is useBitIndex=1 & b1_d5=0x0; b2_d5_1=0 & b2_d5_regAx { ptr:3 = b2_d5_regAx + (bitIndex / 8); export *:1 ptr; } # [Ax] w/bitIndex +bitbase: bitbaseDsp8^[b2_d5_regAxSF] is (useBitIndex=1 & b1_d5=0x1; b2_d5_regAxSF) ... & bitbaseDsp8 { ptr:3 = b2_d5_regAxSF + bitbaseDsp8 + (bitIndex / 8); export *:1 ptr; } # base:11[Ax|SB|FB] w/bitIndex +bitbase: bitbaseDsp16^[b2_d5_regAxSF] is (useBitIndex=1 & b1_d5=0x2; b2_d5_regAxSF) ... & bitbaseDsp16 { ptr:3 = b2_d5_regAxSF + bitbaseDsp16 + (bitIndex / 8); export *:1 ptr; } # base:19[Ax|SB|FB] w/bitIndex +bitbase: bitbaseDsp24^[b2_d5_regAx] is (useBitIndex=1 & b1_d5=0x3; b2_d5_1=0 & b2_d5_regAx) ... & bitbaseDsp24 { ptr:3 = b2_d5_regAx + bitbaseDsp24 + (bitIndex / 8); export *:1 ptr; } # base:27[Ax] w/bitIndex +bitbase: imm16_dat^":19" is useBitIndex=1 & b1_d5=0x3; b2_d5=0x3; imm16_dat { ptr:3 = imm16_dat + (bitIndex / 8); export *:1 ptr; } # base:19 w/bitIndex +bitbase: imm24_dat^":27" is useBitIndex=1 & b1_d5=0x3; b2_d5=0x2; imm24_dat { ptr:3 = imm24_dat + (bitIndex / 8); export *:1 ptr; } # base:27 w/bitIndex + +# Ax bitbase destination specified by 5-bit encoding (b1_d5/b2_d5) +# NOTE! Ax destination is special case and must be handled seperately by each instruction +# Starting position is at instruction b1 +bitbaseAx: b2_d5_regAx is b1_d5; b2_d5_regAx { export b2_d5_regAx; } + +bitbaseAbs16: imm16_dat is imm16_dat { export *:1 imm16_dat; } + +# +# The following macros are used to elliminate illegal bit patterns when using dst5 +# These should be used by constructor pattern matching instead of the corresponding dst5 subconstructor +# +@define BITBASE "((b1_d5=4 | b1_d5_4=0) ... & bitbase)" +@define BITBASE_AX "((b1_d5=0x0; b2_d5_1=1) & bitbaseAx)" + +# Bit identifier (may be overriden if useBitIndex has been set by BINDEX instruction +bit: b2_bit is useBitIndex=0 & b2_bit { export *[const]:1 b2_bit; } +bit: [bitIndex] is useBitIndex=1 & bitIndex { val:3 = bitIndex % 8; b:1 = val:1; export b; } + +# +# Immediate data operand +# Fixed length - current position is at start of immediate data +# +srcImm3: "#"^b2_0002 is b2_0002 { export *[const]:1 b2_0002; } +srcImm8: "#"^imm8_dat is imm8_dat { export *[const]:1 imm8_dat; } + +srcImm8a: "#"^imm8_dat is imm8_dat { export *[const]:1 imm8_dat; } # used when two imm8 are needed + +srcImm16: "#"^imm16_dat is imm16_dat { export *[const]:2 imm16_dat; } + +srcImm16a: "#"^imm16_dat is imm16_dat { export *[const]:2 imm16_dat; } # used when two imm16 are needed + +srcImm24: "#"^imm24_dat is imm24_dat { export *[const]:3 imm24_dat; } +srcImm32: "#"^imm32_dat is imm32_dat { export *[const]:4 imm32_dat; } + +# Unsigned immediate data from 1-bit value: 1 <= value <= 2 (1 added to unsigned bit value) +srcImm1p: "#"^val is b1_0505 [ val = b1_0505 + 1; ] { export *[const]:1 val; } + +# Unsigned immediate data from 2-bit value: 1 <= value <= 8 (1 added to unsigned bit value) +srcImm3p: "#"^val is b1_0405 & b1_0000 [ val = (b1_0405 << 1) + b1_0000 + 1; ] { export *[const]:1 val; } + +srcSimm8: "#"^simm8_dat is simm8_dat { export *[const]:1 simm8_dat; } +srcSimm16: "#"^simm16_dat is simm16_dat { export *[const]:2 simm16_dat; } +srcSimm32: "#"^simm32_dat is simm32_dat { export *[const]:4 simm32_dat; } + +# Signed immediate data from signed 4-bit value: -8 <= value <= 7 +srcSimm4: "#"^b2_simm4 is b2_simm4 { export *[const]:1 b2_simm4; } + +srcSimm8a: srcSimm8 is srcSimm8 { export srcSimm8; } + +srcSimm16a: srcSimm16 is srcSimm16 { export srcSimm16; } + +# Signed immediate shift amount from 4-bit value: -8 <= value <= -1 || 1 <= value <= 8 +srcSimm4Shift: "#"^val is b2_shiftSign=0 & b2_0002 [ val = b2_0002 + 1; ] { export *[const]:1 val; } +srcSimm4Shift: "#"^val is b2_shiftSign=1 & b2_0002 [ val = -(b2_0002 + 1); ] { export *[const]:1 val; } + +srcZero8: "#0" is b1_0007 { export 0:1; } +srcZero16: "#0" is b1_0007 { export 0:2; } + +# special 6-bit immediate for INT number +srcIntNum: "#"^imm6_dat is imm6_dat { export *[const]:1 imm6_dat; } + +# +# Offset label operand +# +abs24offset: imm24_dat is imm24_dat { export *:1 imm24_dat; } + +abs16offset: imm16_dat is imm16_dat { export *:1 imm16_dat; } + +# Relative address offsets +rel16offset1: offs is simm16_dat [ offs = inst_start + 1 + simm16_dat; ] { export *:1 offs; } + +rel8offset1: offs is simm8_dat [ offs = inst_start + 1 + simm8_dat; ] { export *:1 offs; } +rel8offset2: offs is simm8_dat [ offs = inst_start + 2 + simm8_dat; ] { export *:1 offs; } + +rel3offset2: offs is b1_0405 & b1_0000 [ offs = inst_start + 2 + ((b1_0405 << 1) + b1_0000); ] { export *:1 offs; } + +reloffset_dst5W: dst5W is $(DST5W) { local reladdr = inst_start + dst5W; export *:3 reladdr; } + +reloffset_dst5L: dst5L is $(DST5L) { local reladdr = inst_start + dst5L; export *:3 reladdr; } + +reloffset_dst5Ax: dst5Ax is $(DST5AX) { local reladdr = inst_start + dst5Ax; export *:3 reladdr; } + +# +# Conditionals (see BMcnd) +# +# TODO!! Need to verify conditional logic pulled from old slaspec +# TODO: the 'cnd' subconstructor should really constrain the bits 4-7 to 0x0, however this exposes a sleigh compiler problem +cnd: "LTU" is cnd_dat=0x0 { tstCnd:1 = ($(CARRY) == 0); export tstCnd; } # less than (>), C flag is 0 +cnd: "LEU" is cnd_dat=0x1 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 0); export tstCnd; } # Equal to or less than (>=) +cnd: "NE" is cnd_dat=0x2 { tstCnd:1 = ($(ZERO) == 0); export tstCnd; } # Not Equal to (=), Z flag is 0 +cnd: "PZ" is cnd_dat=0x3 { tstCnd:1 = ($(SIGN) == 0); export tstCnd; } # Positive or zero (0<=) +cnd: "NO" is cnd_dat=0x4 { tstCnd:1 = ($(OVERFLOW) == 0); export tstCnd; } # O flag is 0 +cnd: "GT" is cnd_dat=0x5 { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 0); export tstCnd; } # Greater than (signed value) (<) +cnd: "GE" is cnd_dat=0x6 { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 0); export tstCnd; } # Equal to or greater than (signed value) (<=) +cnd: "GEU" is cnd_dat=0x8 { tstCnd:1 = ($(CARRY) == 1); export tstCnd; } # Equal to or greater than (<=), C flag is 1 +cnd: "GTU" is cnd_dat=0x9 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 1); export tstCnd; } # Greater than (<) +cnd: "EQ" is cnd_dat=0xa { tstCnd:1 = ($(ZERO) == 1); export tstCnd; } # Equal to (=), Z flag is 1 +cnd: "N" is cnd_dat=0xb { tstCnd:1 = ($(SIGN) == 1); export tstCnd; } # Negative (0>) +cnd: "O" is cnd_dat=0xc { tstCnd:1 = ($(OVERFLOW) == 1); export tstCnd; } # O flag is 1 +cnd: "LE" is cnd_dat=0xd { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 1); export tstCnd; } # Equal to or less than (signed value) (>=) +cnd: "LT" is cnd_dat=0xe { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 1); export tstCnd; } # less than (signed value) (<=) + +b2cnd: "LTU" is b2_0606=0 & b2_0002=0 { tstCnd:1 = ($(CARRY) == 0); export tstCnd; } # less than (>), C flag is 0 +b2cnd: "LEU" is b2_0606=0 & b2_0002=1 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 0); export tstCnd; } # Equal to or less than (>=) +b2cnd: "NE" is b2_0606=0 & b2_0002=2 { tstCnd:1 = ($(ZERO) == 0); export tstCnd; } # Not Equal to (=), Z flag is 0 +b2cnd: "PZ" is b2_0606=0 & b2_0002=3 { tstCnd:1 = ($(SIGN) == 0); export tstCnd; } # Positive or zero (0<=) +b2cnd: "NO" is b2_0606=0 & b2_0002=4 { tstCnd:1 = ($(OVERFLOW) == 0); export tstCnd; } # O flag is 0 +b2cnd: "GT" is b2_0606=0 & b2_0002=5 { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 0); export tstCnd; } # Greater than (signed value) (<) +b2cnd: "GE" is b2_0606=0 & b2_0002=6 { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 0); export tstCnd; } # Equal to or greater than (signed value) (<=) +b2cnd: "GEU" is b2_0606=1 & b2_0002=0 { tstCnd:1 = ($(CARRY) == 1); export tstCnd; } # Equal to or greater than (<=), C flag is 1 +b2cnd: "GTU" is b2_0606=1 & b2_0002=1 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 1); export tstCnd; } # Greater than (<) +b2cnd: "EQ" is b2_0606=1 & b2_0002=2 { tstCnd:1 = ($(ZERO) == 1); export tstCnd; } # Equal to (=), Z flag is 1 +b2cnd: "N" is b2_0606=1 & b2_0002=3 { tstCnd:1 = ($(SIGN) == 1); export tstCnd; } # Negative (0>) +b2cnd: "O" is b2_0606=1 & b2_0002=4 { tstCnd:1 = ($(OVERFLOW) == 1); export tstCnd; } # O flag is 1 +b2cnd: "LE" is b2_0606=1 & b2_0002=5 { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 1); export tstCnd; } # Equal to or less than (signed value) (>=) +b2cnd: "LT" is b2_0606=1 & b2_0002=6 { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 1); export tstCnd; } # less than (signed value) (<=) + +b1cnd: "LTU" is b1_0406=0 & b1_0000=0 { tstCnd:1 = ($(CARRY) == 0); export tstCnd; } # less than (>), C flag is 0 +b1cnd: "LEU" is b1_0406=0 & b1_0000=1 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 0); export tstCnd; } # Equal to or less than (>=) +b1cnd: "NE" is b1_0406=1 & b1_0000=0 { tstCnd:1 = ($(ZERO) == 0); export tstCnd; } # Not Equal to (=), Z flag is 0 +b1cnd: "PZ" is b1_0406=1 & b1_0000=1 { tstCnd:1 = ($(SIGN) == 0); export tstCnd; } # Positive or zero (0<=) +b1cnd: "NO" is b1_0406=2 & b1_0000=0 { tstCnd:1 = ($(OVERFLOW) == 0); export tstCnd; } # O flag is 0 +b1cnd: "GT" is b1_0406=2 & b1_0000=1 { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 0); export tstCnd; } # Greater than (signed value) (<) +b1cnd: "GE" is b1_0406=3 & b1_0000=0 { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 0); export tstCnd; } # Equal to or greater than (signed value) (<=) +b1cnd: "GEU" is b1_0406=4 & b1_0000=0 { tstCnd:1 = ($(CARRY) == 1); export tstCnd; } # Equal to or greater than (<=), C flag is 1 +b1cnd: "GTU" is b1_0406=4 & b1_0000=1 { tstCnd:1 = (($(CARRY) & (!$(ZERO))) == 1); export tstCnd; } # Greater than (<) +b1cnd: "EQ" is b1_0406=5 & b1_0000=0 { tstCnd:1 = ($(ZERO) == 1); export tstCnd; } # Equal to (=), Z flag is 1 +b1cnd: "N" is b1_0406=5 & b1_0000=1 { tstCnd:1 = ($(SIGN) == 1); export tstCnd; } # Negative (0>) +b1cnd: "O" is b1_0406=6 & b1_0000=0 { tstCnd:1 = ($(OVERFLOW) == 1); export tstCnd; } # O flag is 1 +b1cnd: "LE" is b1_0406=6 & b1_0000=1 { tstCnd:1 = ((($(SIGN) ^ $(OVERFLOW)) | $(ZERO)) == 1); export tstCnd; } # Equal to or less than (signed value) (>=) +b1cnd: "LT" is b1_0406=7 & b1_0000=0 { tstCnd:1 = (($(SIGN) ^ $(OVERFLOW)) == 1); export tstCnd; } # less than (signed value) (<=) + +# +# Flag bit operand +# +flagBit: "C" is b2_0002=0 { export 0:2; } +flagBit: "D" is b2_0002=1 { export 1:2; } +flagBit: "Z" is b2_0002=2 { export 2:2; } +flagBit: "S" is b2_0002=3 { export 3:2; } +flagBit: "B" is b2_0002=4 { export 4:2; } +flagBit: "O" is b2_0002=5 { export 5:2; } +flagBit: "I" is b2_0002=6 { export 6:2; } +flagBit: "U" is b2_0002=7 { export 7:2; } + +with: phase=1 { +# +# Instruction Constructors +# +##### ABS ##### +# (1) ABS.B dst +# 1010 0100 1001 1111 0011 0100 0001 0010 ABS.B 0x1234:16[SB] +# 0000 1001 1010 0100 1001 1111 0011 0100 0001 0010 ABS.B [0x1234:16[SB]] +:ABS.B dst5B is (b1_0407=0xa & b1_size_0=0; b2_0005=0x1f) ... & $(DST5B) ... { + tmp:1 = dst5B; + $(OVERFLOW) = (tmp == 0x80); + if (tmp s>= 0) goto ; + tmp = -tmp; + dst5B = tmp; + + setResultFlags(tmp); +} + +# (1) ABS.B Ax +:ABS.B dst5Ax is (b1_0407=0xa & b1_size_0=0; b2_0005=0x1f) ... & $(DST5AX) ... { + tmp:1 = dst5Ax:1; + $(OVERFLOW) = (tmp == 0x80); + if (tmp s>= 0) goto ; + tmp = -tmp; + dst5Ax = zext(tmp); + + setResultFlags(tmp); +} + +# (1) ABS.W dst +# 1010 0101 1001 1111 0011 0100 0001 0010 ABS.W 0x1234:16[SB] +# 0000 1001 1010 0101 1001 1111 0011 0100 0001 0010 ABS.W [0x1234:16[SB]] +:ABS.W dst5W is (b1_0407=0xa & b1_size_0=1; b2_0005=0x1f) ... & $(DST5W) ... { + tmp:2 = dst5W; + $(OVERFLOW) = (tmp == 0x8000); + if (tmp s>= 0) goto ; + tmp = -tmp; + dst5W = tmp; + + setResultFlags(tmp); +} + +# (1) ABS.W Ax +:ABS.W dst5Ax is (b1_0407=0xa & b1_size_0=1; b2_0005=0x1f) ... & $(DST5AX) ... { + tmp:2 = dst5Ax:2; + $(OVERFLOW) = (tmp == 0x8000); + if (tmp s>= 0) goto ; + tmp = -tmp; + dst5Ax = zext(tmp); + + setResultFlags(tmp); +} + +##### ADC ##### + +# (1) ADC.B #simm, dst +# 0000 0001 1000 0100 1010 1110 0011 0100 0001 0010 0101 0110 ADC.B 0x56, 0x1234:16[SB] +# 0000 1001 0000 0001 1000 0100 1010 1110 0011 0100 0001 0010 0101 0110 ABS.B 0x56, [0x1234:16[SB]] +:ADC.B srcSimm8, dst5B is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x2e) ... & $(DST5B)); srcSimm8 { + tmp:1 = dst5B; + c:1 = $(CARRY); + setAdd3Flags(tmp, srcSimm8, c); + tmp = tmp + srcSimm8 + c; + dst5B = tmp; + setResultFlags(tmp); +} + +# (1) ADC.B #simm, Ax +:ADC.B srcSimm8, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x2e) & $(DST5AX)); srcSimm8 { + tmp:1 = dst5Ax:1; + c:1 = $(CARRY); + setAdd3Flags(tmp, srcSimm8, c); + tmp = tmp + srcSimm8 + c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) ADC.W #simm, dst +:ADC.W srcSimm16, dst5W is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x2e) ... & $(DST5W)); srcSimm16 { + tmp:2 = dst5W; + c:2 = zext($(CARRY)); + setAdd3Flags(tmp, srcSimm16, c); + tmp = tmp + srcSimm16 + c; + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) ADC.B #simm, Ax +:ADC.W srcSimm16, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x2e) & $(DST5AX)); srcSimm16 { + tmp:2 = dst5Ax:2; + c:2 = zext($(CARRY)); + setAdd3Flags(tmp, srcSimm16, c); + tmp = tmp + srcSimm16 + c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) ADC.B src5, dst5 +:ADC.B src5B, dst5B_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x4) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ...) { + tmp:1 = dst5B_afterSrc5; + s:1 = src5B; + c:1 = $(CARRY); + setAdd3Flags(tmp, s, c); + tmp = tmp + s + c; + dst5B_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (2) ADC.B src5, Ax +:ADC.B src5B, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x4) ... & $(SRC5B) & $(DST5AX) ...) { + tmp:1 = dst5Ax:1; + s:1 = src5B; + c:1 = $(CARRY); + setAdd3Flags(tmp, s, c); + tmp = tmp + s + c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) ADC.W src5, dst5 +:ADC.W src5W, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x4) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ...) { + tmp:2 = dst5W_afterSrc5; + s:2 = src5W; + c:2 = zext($(CARRY)); + setAdd3Flags(tmp, s, c); + tmp = tmp + s + c; + dst5W_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (2) ADC.W src5, Ax +:ADC.W src5W, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x4) ... & $(SRC5W) & $(DST5AX) ...) { + tmp:2 = dst5Ax:2; + s:2 = src5W; + c:2 = zext($(CARRY)); + setAdd3Flags(tmp, s, c); + tmp = tmp + s + c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### ADCF ##### + +# (1) ADCF.B dst +:ADCF.B dst5B is (b1_0407=0xb & b1_size_0=0; b2_0005=0x1e) ... & $(DST5B) { + tmp:1 = dst5B; + c:1 = $(CARRY); + setAddFlags(tmp, c); + tmp = tmp + c; + dst5B = tmp; + setResultFlags(tmp); +} + +# (1) ADCF.B Ax +:ADCF.B dst5Ax is (b1_0407=0xb & b1_size_0=0; b2_0005=0x1e) & $(DST5AX) { + tmp:1 = dst5Ax:1; + c:1 = $(CARRY); + setAddFlags(tmp, c); + tmp = tmp + c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) ADCF.W dst +:ADCF.W dst5W is (b1_0407=0xb & b1_size_0=1; b2_0005=0x1e) ... & $(DST5W) { + tmp:2 = dst5W; + c:2 = zext($(CARRY)); + setAddFlags(tmp, c); + tmp = tmp + c; + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) ADCF.B Ax +:ADCF.W dst5Ax is (b1_0407=0xb & b1_size_0=1; b2_0005=0x1e) & $(DST5AX) { + tmp:2 = dst5Ax:2; + c:2 = zext($(CARRY)); + setAddFlags(tmp, c); + tmp = tmp + c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### ADD ##### + +# (1) ADD.B:G #simm, dst +:ADD^".B:G" srcSimm8, dst5B is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x2e) ... & $(DST5B)); srcSimm8 { + tmp:1 = dst5B; + setAddFlags(tmp, srcSimm8); + tmp = tmp + srcSimm8; + dst5B = tmp; + setResultFlags(tmp); +} + +# (1) ADD.B:G #simm, Ax +:ADD^".B:G" srcSimm8, dst5Ax is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x2e) & $(DST5AX)); srcSimm8 { + tmp:1 = dst5Ax:1; + setAddFlags(tmp, srcSimm8); + tmp = tmp + srcSimm8; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) ADD.W:G #simm, dst +:ADD^".W:G" srcSimm16, dst5W is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x2e) ... & $(DST5W)); srcSimm16 { + tmp:2 = dst5W; + setAddFlags(tmp, srcSimm16); + tmp = tmp + srcSimm16; + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) ADD.W:G #simm, Ax +:ADD^".W:G" srcSimm16, dst5Ax is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x2e) & $(DST5AX)); srcSimm16 { + tmp:2 = dst5Ax:2; + setAddFlags(tmp, srcSimm16); + tmp = tmp + srcSimm16; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) ADD.L:G #simm, dst +:ADD^".L:G" srcSimm32, dst5L is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x31) ... & $(DST5L)); srcSimm32 { + tmp:4 = dst5L; + setAddFlags(tmp, srcSimm32); + tmp = tmp + srcSimm32; + dst5L = tmp; + setResultFlags(tmp); +} + +# (2) ADD.L:G #simm, Ax +:ADD^".L:G" srcSimm32, dst5Ax is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x31) & $(DST5AX)); srcSimm32 { + tmp:4 = zext(dst5Ax); + setAddFlags(tmp, srcSimm32); + tmp = tmp + srcSimm32; + dst5Ax = tmp:3; + setResultFlags(tmp); +} + +# (3) ADD.B:G #simm4, dst +:ADD^".B:G" srcSimm4, dst5B is (b1_0507=0x7 & b1_size_4=0 & b1_size_0=0; b2_0405=3 & srcSimm4) ... & $(DST5B) { + tmp:1 = dst5B; + setAddFlags(tmp, srcSimm4); + tmp = tmp + srcSimm4; + dst5B = tmp; + setResultFlags(tmp); +} + +# (3) ADD.B:G #simm4, Ax +:ADD^".B:G" srcSimm4, dst5Ax is (b1_0507=0x7 & b1_d5=0x0 & b1_size_4=0 & b1_size_0=0; b2_0405=3 & srcSimm4) & $(DST5AX) { + tmp:1 = dst5Ax:1; + setAddFlags(tmp, srcSimm4); + tmp = tmp + srcSimm4; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (3) ADD.W:Q #simm4, dst +:ADD^".W:Q" srcSimm4, dst5W is (b1_0507=0x7 & b1_size_4=0 & b1_size_0=1; b2_0405=3 & srcSimm4) ... & $(DST5W) { + tmp:2 = dst5W; + imm:2 = sext(srcSimm4); + setAddFlags(tmp, imm); + tmp = tmp + imm; + dst5W = tmp; + setResultFlags(tmp); +} + +# (3) ADD.W:Q #simm4, Ax +:ADD^".W:Q" srcSimm4, dst5Ax is (b1_0507=0x7 & b1_d5=0x0 & b1_size_4=0 & b1_size_0=1; b2_0405=3 & srcSimm4) & $(DST5AX) { + tmp:2 = dst5Ax:2; + imm:2 = sext(srcSimm4); + setAddFlags(tmp, imm); + tmp = tmp + imm; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (3) ADD.L:Q #simm4, dst +:ADD^".L:Q" srcSimm4, dst5L is (b1_0507=0x7 & b1_size_4=1 & b1_size_0=0; b2_0405=3 & srcSimm4) ... & $(DST5L) { + tmp:4 = dst5L; + imm:4 = sext(srcSimm4); + setAddFlags(tmp, imm); + tmp = tmp + imm; + dst5L = tmp; + setResultFlags(tmp); +} + +# (3) ADD.L:Q #simm4, Ax +:ADD^".L:Q" srcSimm4, dst5Ax is (b1_0507=0x7 & b1_d5=0x0 & b1_size_4=1 & b1_size_0=0; b2_0405=3 & srcSimm4) & $(DST5AX) { + tmp:4 = sext(dst5Ax); + imm:4 = sext(srcSimm4); + setAddFlags(tmp, imm); + tmp = tmp + imm; + dst5Ax = tmp:3; + setResultFlags(tmp); +} + +# (4) ADD.B:S #simm, dst +:ADD^".B:S" srcSimm8, dst2B is ((b1_0607=0 & b1_0103=3 & b1_size_0=0) ... & dst2B); srcSimm8 { + tmp:1 = dst2B; + setAddFlags(tmp, srcSimm8); + tmp = tmp + srcSimm8; + dst2B = tmp; + setResultFlags(tmp); +} + +# (4) ADD.W:S #simm, dst +# 0010 0111 0101 0110 0011 0100 0001 0010 ADD.W:S #0x1234, 0x56:8[SB] +:ADD^".W:S" srcSimm16, dst2W is ((b1_0607=0 & b1_0103=3 & b1_size_0=1) ... & dst2W); srcSimm16 { + tmp:2 = dst2W; + setAddFlags(tmp, srcSimm16); + tmp = tmp + srcSimm16; + dst2W = tmp; + setResultFlags(tmp); +} + +# (5) ADD.L:S #imm1, Ax +:ADD^".L:S" srcImm1p, b1_d1_regAx is b1_0607=2 & srcImm1p & b1_0104=0x6 & b1_d1_regAx { + tmp:4 = sext(b1_d1_regAx); + imm:4 = zext(srcImm1p); + setAddFlags(tmp, imm); + tmp = tmp + imm; + b1_d1_regAx = tmp:3; + setResultFlags(tmp); +} + +# (6) ADD.B:G src, dst +# 1011 0110 0001 1000 0101 0110 0011 0100 0001 0010 0011 0011 0010 0010 0001 0001 ADD.B:G 0x123456:24[A0], 112233[A1] +# 1100 0101 1111 1000 0011 0100 0001 0010 ADD.W:G R1, 0x1234:16[FB] +:ADD^".B:G" src5B, dst5B_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0x8) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ... { + tmp:1 = dst5B_afterSrc5; + src:1 = src5B; + setAddFlags(tmp, src); + tmp = tmp + src; + dst5B_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (6) ADD.B:G src, Ax - Ax destination case +:ADD^".B:G" src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0x8) ... & $(SRC5B) & $(DST5AX) ... { + tmp:1 = dst5Ax:1; + src:1 = src5B; + setAddFlags(tmp, src); + tmp = tmp + src; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (6) ADD.W:G src, dst +:ADD^".W:G" src5W, dst5W_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0x8) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ... { + tmp:2 = dst5W_afterSrc5; + src:2 = src5W; + setAddFlags(tmp, src); + tmp = tmp + src; + dst5W_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (6) ADD.W:G src, Ax - Ax destination case +:ADD^".W:G" src5W, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0x8) ... & $(SRC5W) & $(DST5AX) ... { + tmp:2 = dst5Ax:2; + src:2 = src5W; + setAddFlags(tmp, src); + tmp = tmp + src; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (7) ADD.L:G src, dst +:ADD^".L:G" src5L, dst5L_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0x2) ... & $(SRC5L) ... & $(DST5L_AFTER_SRC5) ... { + tmp:4 = dst5L_afterSrc5; + src:4 = src5L; + setAddFlags(tmp, src); + tmp = tmp + src; + dst5L_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (7) ADD.L:G src, Ax - Ax destination case +:ADD^".L:G" src5L, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0x2) ... & $(SRC5L) & $(DST5AX) ... { + tmp:4 = zext(dst5Ax); + src:4 = src5L; + setAddFlags(tmp, src); + tmp = tmp + src; + dst5Ax = tmp:3; + setResultFlags(tmp); +} + +# (8) ADD.l:G #simm16, SP +:ADD^".L:G" srcSimm16, SP is b1_0007=0xb6 & SP; b2_0007=0x13; srcSimm16 { + # not done as 32-bit calculation to simplify stack analysis + imm:3 = sext(srcSimm16); + setAddFlags(SP, imm); + SP = SP + imm; + setResultFlags(SP); +} + +# (9) ADD.L:Q #imm3, SP +:ADD^".L:Q" srcImm3p, SP is b1_0607=1 & srcImm3p & b1_0103=1 & SP { + # not done as 32-bit calculation to simplify stack analysis + imm:3 = zext(srcImm3p); + setAddFlags(SP, imm); + SP = SP + imm; + setResultFlags(SP); +} + +# (10) ADD.L:S #simm8, SP +:ADD^".L:S" srcSimm8, SP is b1_0007=0xb6 & SP; b2_0007=0x03; srcSimm8 { + # not done as 32-bit calculation to simplify stack analysis + imm:3 = sext(srcSimm8); + setAddFlags(SP, imm); + SP = SP + imm; + setResultFlags(SP); +} + +##### ADDX ##### + +# (1) ADDX #simm, dst5 +:ADDX srcSimm8, dst5L is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x11) ... & $(DST5L)); srcSimm8 { + tmp:4 = dst5L; + src:4 = sext(srcSimm8); + setAddFlags(tmp, src); + tmp = tmp + src; + dst5L = tmp; + setResultFlags(tmp); +} + +# (1) ADDX #simm, Ax +:ADDX srcSimm8, dst5Ax is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x11) & $(DST5AX)); srcSimm8 { + tmp:4 = zext(dst5Ax); + src:4 = sext(srcSimm8); + setAddFlags(tmp, src); + tmp = tmp + src; + dst5Ax = tmp:3; + setResultFlags(tmp); +} + +# (2) ADDX src5, dst5 +:ADDX src5B, dst5L_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0x2) ... & $(SRC5B) ... & $(DST5L_AFTER_SRC5) ... { + tmp:4 = dst5L_afterSrc5; + src:4 = sext(src5B); + setAddFlags(tmp, src); + tmp = tmp + src; + dst5L_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (2) ADDX src5, Ax +:ADDX src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0x2) ... & $(SRC5B) & $(DST5AX) ... { + tmp:4 = zext(dst5Ax); + src:4 = sext(src5B); + setAddFlags(tmp, src); + tmp = tmp + src; + dst5Ax = tmp:3; + setResultFlags(tmp); +} + +##### ADJNZ ##### + +# ADJNZ.B #simm4, dst, rel8offset2 +# 1111 1000 1001 1111 0000 0110 ADJNZ #-0x1,R0L, +:ADJNZ.B srcSimm4, dst5B, rel8offset2 is ((b1_0407=0xf & b1_size_0=0; b2_0405=1 & srcSimm4) ... & $(DST5B)); rel8offset2 { + tmp:1 = dst5B + srcSimm4; + dst5B = tmp; + if (tmp != 0) goto rel8offset2; +} + +# ADJNZ.B #simm4, Ax, , rel8offset2 +:ADJNZ.B srcSimm4, dst5Ax, rel8offset2 is ((b1_0407=0xf & b1_size_0=0; b2_0405=1 & srcSimm4) & $(DST5AX)); rel8offset2 { + tmp:1 = dst5Ax:1 + srcSimm4; + dst5Ax = zext(tmp); + if (tmp != 0) goto rel8offset2; +} + +# ADJNZ.W #simm4, dst, rel8offset2 +:ADJNZ.W srcSimm4, dst5W, rel8offset2 is ((b1_0407=0xf & b1_size_0=1; b2_0405=1 & srcSimm4) ... & $(DST5W)); rel8offset2 { + tmp:2 = dst5W + sext(srcSimm4); + dst5W = tmp; + if (tmp != 0) goto rel8offset2; +} + +# ADJNZ.W #simm4, Ax, rel8offset2 +:ADJNZ.W srcSimm4, dst5Ax, rel8offset2 is ((b1_0407=0xf & b1_size_0=1; b2_0405=1 & srcSimm4) & $(DST5AX)); rel8offset2 { + tmp:2 = dst5Ax:2 + sext(srcSimm4); + dst5Ax = zext(tmp); + if (tmp != 0) goto rel8offset2; +} + +##### AND ##### + +# (1) AND.B:G #imm, dst +:AND^".B:G" srcImm8, dst5B is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x3f) ... & $(DST5B)); srcImm8 { + tmp:1 = dst5B & srcImm8; + dst5B = tmp; + setResultFlags(tmp); +} + +# (1) AND.B:G #imm, Ax +:AND^".B:G" srcImm8, dst5Ax is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x3f) & $(DST5AX)); srcImm8 { + tmp:1 = dst5Ax:1 & srcImm8; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) AND.W:G #imm, dst +:AND^".W:G" srcImm16, dst5W is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x3f) ... & $(DST5W)); srcImm16 { + tmp:2 = dst5W & srcImm16; + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) AND.W:G #imm, Ax +:AND^".W:G" srcImm16, dst5Ax is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x3f) & $(DST5AX)); srcImm16 { + tmp:2 = dst5Ax:2 & srcImm16; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) AND.B:S #imm, dst +:AND^".B:S" srcImm8, dst2B is ((b1_0607=1 & b1_0103=6 & b1_size_0=0) ... & dst2B); srcImm8 { + tmp:1 = dst2B & srcImm8; + dst2B = tmp; + setResultFlags(tmp); +} + +# (2) AND.W:S #imm, dst +:AND^".W:S" srcImm16, dst2W is ((b1_0607=1 & b1_0103=6 & b1_size_0=1) ... & dst2W); srcImm16 { + tmp:2 = dst2W & srcImm16; + dst2W = tmp; + setResultFlags(tmp); +} + +# (3) AND.B:G src5, dst5 +:AND^".B:G" src5B, dst5B_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0xd) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ... { + tmp:1 = dst5B_afterSrc5 & src5B; + dst5B_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (3) AND.B:G src5, Ax +:AND^".B:G" src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0xd) ... & $(SRC5B) & $(DST5AX) ... { + tmp:1 = dst5Ax:1 & src5B; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (3) AND.W:G src5, dst5 +:AND^".W:G" src5W, dst5W_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0xd) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ... { + tmp:2 = dst5W_afterSrc5 & src5W; + dst5W_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (3) AND.W:G src5, Ax +:AND^".W:G" src5W, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0xd) ... & $(SRC5W) & $(DST5AX) ... { + tmp:2 = dst5Ax:2 & src5W; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### BAND ##### + +# BAND bit,bitbase +# 0000 0001 1101 0110 0000 1011 0101 0110 0011 0100 0001 0010 BAND 0x3,0x123456[A0] +:BAND bit, bitbase is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x1 & bit) ... & $(BITBASE)) { + bitValue:1 = (bitbase >> bit) & 1; + $(CARRY) = $(CARRY) & bitValue; +} + +##### BCLR ##### + +# BCLR bit,bitbase +# 1101 0110 0011 0011 0101 0110 0011 0100 0001 0010 BCLR 0x3,0x123456[A0] +:BCLR bit, bitbase is (b1_0407=0xd & b1_size_0=0; b2_0305=0x6 & bit) ... & $(BITBASE) { + mask:1 = ~(1 << bit); + bitbase = bitbase & mask; +} + +# BCLR bit,Ax +:BCLR b2_bit, bitbaseAx is (b1_0407=0xd & b1_size_0=0; b2_0305=0x6 & b2_bit) & $(BITBASE_AX) { + mask:3 = ~(1 << b2_bit); + bitbaseAx = bitbaseAx & mask; +} + +##### BITINDEX ##### + +# BITINDEX.B src -- dst5B used as source +# 1100 1000 1010 1110 BINDEX.B R0L +:BITINDEX.B dst5B is (b1_0407=0xc & b1_size_0=0; b2_0005=0x2e) ... & $(DST5B) + [ useBitIndex=1; globalset(inst_next,useBitIndex); useBitIndex=0; ] { + bitIndex = zext(dst5B); +} + +# BITINDEX.W src -- dst5W used as source +:BITINDEX.W dst5W is (b1_0407=0xc & b1_size_0=1; b2_0005=0x2e) ... & $(DST5W) + [ useBitIndex=1; globalset(inst_next,useBitIndex); useBitIndex=0; ] { + bitIndex = zext(dst5W); +} + +##### BMCnd ##### + +# (1) BMcnd bit, bitbase +:BM^cnd bit, bitbase is ((b1_0407=0xd & b1_size_0=0; b2_0305=0x2 & bit) ... & $(BITBASE)); cnd { + mask:1 = ~(1 << bit); + bitbase = ((cnd << bit) | (bitbase & mask)); +} + +# (1) BMcnd bit, Ax +:BM^cnd b2_bit, bitbaseAx is ((b1_0407=0xd & b1_size_0=0; b2_0305=0x2 & b2_bit) & $(BITBASE_AX)); cnd { + mask:3 = ~(1 << b2_bit); + bitbaseAx = ((zext(cnd) << b2_bit) | (bitbaseAx & mask)); +} + +# (2) BMcnd C +:BM^b2cnd "C" is b1_0007=0xd9; b2_0707=0 & b2_0305=5 & b2cnd { + $(CARRY) = b2cnd; +} + +##### BNAND ##### + +:BNAND bit, bitbase is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x3 & bit) ... & $(BITBASE)) { + mask:1 = (1 << bit); + bitValue:1 = (bitbase & mask); + $(CARRY) = $(CARRY) && (bitValue == 0); +} + +:BNAND b2_bit, bitbaseAx is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x3 & b2_bit) & $(BITBASE_AX)) { + mask:3 = (1 << b2_bit); + bitValue:3 = (bitbaseAx & mask); + $(CARRY) = $(CARRY) && (bitValue == 0); +} + +##### BNOR ##### + +:BNOR bit, bitbase is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x6 & bit) ... & $(BITBASE)) { + mask:1 = (1 << bit); + bitValue:1 = (bitbase & mask); + $(CARRY) = $(CARRY) || (bitValue == 0); +} + +:BNOR b2_bit, bitbaseAx is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x6 & b2_bit) & $(BITBASE_AX)) { + mask:3 = (1 << b2_bit); + bitValue:3 = (bitbaseAx & mask); + $(CARRY) = $(CARRY) || (bitValue == 0); +} + +##### BNOT ##### + +# BNOT bit,bitbase +:BNOT bit, bitbase is (b1_0407=0xd & b1_size_0=0; b2_0305=0x3 & bit) ... & $(BITBASE) { + mask:1 = (1 << bit); + val:1 = bitbase; + bitValue:1 = (~val & mask); + bitbase = (val & ~mask) | bitValue; +} + +# BNOT bit,Ax +:BNOT b2_bit, bitbaseAx is (b1_0407=0xd & b1_size_0=0; b2_0305=0x3 & b2_bit) & $(BITBASE_AX) { + mask:3 = (1 << b2_bit); + bitValue:3 = (~bitbaseAx & mask); + bitbaseAx = (bitbaseAx & ~mask) | bitValue; +} + +##### BNTST ##### + +:BNTST bit, bitbase is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x0 & bit) ... & $(BITBASE)) { + mask:1 = (1 << bit); + bitValue:1 = (bitbase & mask); + z:1 = (bitValue == 0); + $(CARRY) = z; + $(ZERO) = z; +} + +:BNTST b2_bit, bitbaseAx is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x0 & b2_bit) & $(BITBASE_AX)) { + mask:3 = (1 << b2_bit); + bitValue:3 = (bitbaseAx & mask); + z:1 = (bitValue == 0); + $(CARRY) = z; + $(ZERO) = z; +} + +##### BNXOR ##### + +:BNXOR bit, bitbase is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x7 & bit) ... & $(BITBASE)) { + mask:1 = (1 << bit); + bitValue:1 = (bitbase & mask); + $(CARRY) = $(CARRY) ^ (bitValue == 0); +} + +:BNXOR b2_bit, bitbaseAx is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x7 & b2_bit) & $(BITBASE_AX)) { + mask:3 = (1 << b2_bit); + bitValue:3 = (bitbaseAx & mask); + $(CARRY) = $(CARRY) ^ (bitValue == 0); +} + +##### BOR ##### + +:BOR bit, bitbase is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x4 & bit) ... & $(BITBASE)) { + mask:1 = (1 << bit); + bitValue:1 = (bitbase & mask); + $(CARRY) = $(CARRY) || (bitValue != 0); +} + +:BOR b2_bit, bitbaseAx is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x4 & b2_bit) & $(BITBASE_AX)) { + mask:3 = (1 << b2_bit); + bitValue:3 = (bitbaseAx & mask); + $(CARRY) = $(CARRY) || (bitValue != 0); +} + +##### BRK ##### + +:BRK is b1_0007=0x0 { + # I don't think it is necessary to model break behavior + Break(); +} + +##### BRK2 ##### + +:BRK2 is b1_0007=0x8 { + # I don't think it is necessary to model break behavior + Break2(); +} + +##### BSET ##### + +:BSET bit, bitbase is (b1_0407=0xd & b1_size_0=0; b2_0305=0x7 & bit) ... & $(BITBASE) { + mask:1 = (1 << bit); + bitbase = bitbase | mask; +} + +:BSET b2_bit, bitbaseAx is (b1_0407=0xd & b1_size_0=0; b2_0305=0x7 & b2_bit) & $(BITBASE_AX) { + mask:3 = (1 << b2_bit); + bitbaseAx = bitbaseAx | mask; +} + +##### BTST ##### + +# (1) BTST bit, bitbase +:BTST bit, bitbase is (b1_0407=0xd & b1_size_0=0; b2_0305=0x0 & bit) ... & $(BITBASE) { + mask:1 = (1 << bit); + bitValue:1 = (bitbase & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; +} + +# (1) BTST bit, Ax +:BTST b2_bit, bitbaseAx is (b1_0407=0xd & b1_size_0=0; b2_0305=0x0 & b2_bit) & $(BITBASE_AX) { + mask:3 = (1 << b2_bit); + bitValue:3 = (bitbaseAx & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; +} + +# (2) BTST bit, bitbase +:BTST b, bitbaseAbs16 is b1_0607=0 & b1_0405 & b1_0103=5 & b1_0000; bitbaseAbs16 [ b = (b1_0405 << 1) + b1_0000; ] { + mask:1 = (1 << b); + bitValue:1 = (bitbaseAbs16 & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; +} + +##### BTSTC ##### + +# (1) BTSTC bit, bitbase +:BTSTC bit, bitbase is (b1_0407=0xd & b1_size_0=0; b2_0305=0x4 & bit) ... & $(BITBASE) { + mask:1 = (1 << bit); + val:1 = bitbase; + bitValue:1 = (val & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; + bitbase = val & ~mask; +} + +# (1) BTSTC bit, Ax +:BTSTC b2_bit, bitbaseAx is (b1_0407=0xd & b1_size_0=0; b2_0305=0x4 & b2_bit) & $(BITBASE_AX) { + mask:3 = (1 << b2_bit); + bitValue:3 = (bitbaseAx & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; + bitbaseAx = bitbaseAx & ~mask; +} + +##### BTSTS ##### + +# (1) BTSTS bit, bitbase +:BTSTS bit, bitbase is (b1_0407=0xd & b1_size_0=0; b2_0305=0x5 & bit) ... & $(BITBASE) { + mask:1 = (1 << bit); + val:1 = bitbase; + bitValue:1 = (val & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; + bitbase = val | mask; +} + +# (1) BTSTS bit, Ax +:BTSTS b2_bit, bitbaseAx is (b1_0407=0xd & b1_size_0=0; b2_0305=0x5 & b2_bit) & $(BITBASE_AX) { + mask:3 = (1 << b2_bit); + bitValue:3 = (bitbaseAx & mask); + z:1 = (bitValue == 0); + $(CARRY) = !z; + $(ZERO) = z; + bitbaseAx = bitbaseAx | mask; +} + +##### BXOR ##### + +:BXOR bit, bitbase is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x5 & bit) ... & $(BITBASE)) { + mask:1 = (1 << bit); + bitValue:1 = (bitbase & mask); + $(CARRY) = $(CARRY) ^ (bitValue != 0); +} + +:BXOR b2_bit, bitbaseAx is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=0; b2_0305=0x5 & b2_bit) & $(BITBASE_AX)) { + mask:3 = (1 << b2_bit); + bitValue:3 = (bitbaseAx & mask); + $(CARRY) = $(CARRY) ^ (bitValue != 0); +} + +##### CLIP ##### + +# CLIP.B #simm, #simm, dst5 +:CLIP.B srcSimm8, srcSimm8a, dst5B is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x3e) ... & $(DST5B)); srcSimm8; srcSimm8a { + val:1 = dst5B; + cmp1:1 = srcSimm8 s> val; + cmp2:1 = srcSimm8a s< val; + dst5B = (cmp1 * srcSimm8) + (cmp2 * srcSimm8a) + ((!cmp1 * !cmp2) * val); +} + +# CLIP.B #simm, #simm, Ax +:CLIP.B srcSimm8, srcSimm8a, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x3e) & $(DST5AX)); srcSimm8; srcSimm8a { + val:1 = dst5Ax:1; + cmp1:1 = srcSimm8 s> val; + cmp2:1 = srcSimm8a s< val; + dst5Ax = zext((cmp1 * srcSimm8) + (cmp2 * srcSimm8a) + ((!cmp1 * !cmp2) * val)); +} + +# CLIP.W #simm, #simm, dst5 +:CLIP.W srcSimm16, srcSimm16a, dst5W is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x3e) ... & $(DST5W)); srcSimm16; srcSimm16a { + val:2 = dst5W; + cmp1:1 = srcSimm16 s> val; + cmp2:1 = srcSimm16a s< val; + dst5W = (zext(cmp1) * srcSimm16) + (zext(cmp2) * srcSimm16a) + (zext(!cmp1 * !cmp2) * val); +} + +# CLIP.W #simm, #simm, Ax +:CLIP.W srcSimm16, srcSimm16a, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x3e) & $(DST5AX)); srcSimm16; srcSimm16a { + val:2 = dst5Ax:2; + cmp1:1 = srcSimm16 s> val; + cmp2:1 = srcSimm16a s< val; + dst5Ax = zext((zext(cmp1) * srcSimm16) + (zext(cmp2) * srcSimm16a) + (zext(!cmp1 * !cmp2) * val)); +} + +##### CMP ##### + +# (1) CMP.B:G #simm, dst5 +:CMP^".B:G" srcSimm8, dst5B is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x2e) ... & $(DST5B)); srcSimm8 { + tmp:1 = dst5B; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + setResultFlags(tmp); +} + +# (1) CMP.B:G #simm, Ax +:CMP^".B:G" srcSimm8, dst5Ax is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x2e) & $(DST5AX)); srcSimm8 { + tmp:1 = dst5Ax:1; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + setResultFlags(tmp); +} + +# (1) CMP.W:G #simm, dst5 +:CMP^".W:G" srcSimm16, dst5W is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x2e) ... & $(DST5W)); srcSimm16 { + tmp:2 = dst5W; + setSubtractFlags(tmp, srcSimm16); + tmp = tmp - srcSimm16; + setResultFlags(tmp); +} + +# (1) CMP.W:G #simm, Ax +:CMP^".W:G" srcSimm16, dst5Ax is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x2e) & $(DST5AX)); srcSimm16 { + tmp:2 = dst5Ax:2; + setSubtractFlags(tmp, srcSimm16); + tmp = tmp - srcSimm16; + setResultFlags(tmp); +} + +# (2) CMP.L:G #simm, dst5 +:CMP^".L:G" srcSimm32, dst5L is ((b1_0407=0xa & b1_size_0=0; b2_0005=0x31) ... & $(DST5L)); srcSimm32 { + tmp:4 = dst5L; + setSubtractFlags(tmp, srcSimm32); + tmp = tmp - srcSimm32; + setResultFlags(tmp); +} + +# (2) CMP.L:G #simm, Ax +:CMP^".L:G" srcSimm32, dst5Ax is ((b1_0407=0xa & b1_size_0=0; b2_0005=0x31) & $(DST5AX)); srcSimm32 { + tmp:4 = zext(dst5Ax); + setSubtractFlags(tmp, srcSimm32); + tmp = tmp - srcSimm32; + setResultFlags(tmp); +} + +# (3) CMP.B:Q #simm4, dst5 +:CMP^".B:Q" srcSimm4, dst5B is (b1_0407=0xe & b1_size_0=0; b2_0405=1 & srcSimm4) ... & $(DST5B) { + tmp:1 = dst5B; + setSubtractFlags(tmp, srcSimm4); + tmp = tmp - srcSimm4; + setResultFlags(tmp); +} + +# (3) CMP.B:Q #simm4, Ax +:CMP^".B:Q" srcSimm4, dst5Ax is (b1_0407=0xe & b1_size_0=0; b2_0405=1 & srcSimm4) & $(DST5AX) { + tmp:1 = dst5Ax:1; + setSubtractFlags(tmp, srcSimm4); + tmp = tmp - srcSimm4; + setResultFlags(tmp); +} + +# (3) CMP.W:Q #simm4, dst5 +:CMP^".W:Q" srcSimm4, dst5W is (b1_0407=0xe & b1_size_0=1; b2_0405=1 & srcSimm4) ... & $(DST5W) { + tmp:2 = dst5W; + imm:2 = sext(srcSimm4); + setSubtractFlags(tmp, imm); + tmp = tmp - imm; + setResultFlags(tmp); +} + +# (3) CMP.W:Q #simm4, Ax +:CMP^".W:Q" srcSimm4, dst5Ax is (b1_0407=0xe & b1_size_0=1; b2_0405=1 & srcSimm4) & $(DST5AX) { + tmp:2 = dst5Ax:2; + imm:2 = sext(srcSimm4); + setSubtractFlags(tmp, imm); + tmp = tmp - imm; + setResultFlags(tmp); +} + +# (4) CMP.B:S #simm, dst2 +:CMP^".B:S" srcSimm8, dst2B is ((b1_0607=1 & b1_0103=3 & b1_size_0=0) ... & dst2B); srcSimm8 { + tmp:1 = dst2B; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + setResultFlags(tmp); +} + +# (4) CMP.W:S #simm, dst2 +:CMP^".W:S" srcSimm16, dst2W is ((b1_0607=1 & b1_0103=3 & b1_size_0=1) ... & dst2W); srcSimm16 { + tmp:2 = dst2W; + setSubtractFlags(tmp, srcSimm16); + tmp = tmp - srcSimm16; + setResultFlags(tmp); +} + +# (5) CMP.B:G src5, dst5 +:CMP^".B:G" src5B, dst5B_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0x6) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) { + tmp:1 = dst5B_afterSrc5; + src:1 = src5B; + setSubtractFlags(tmp, src); + tmp = tmp - src; + setResultFlags(tmp); +} + +# (5) CMP.B:G src5, Ax +:CMP^".B:G" src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0x6) ... & $(SRC5B) & $(DST5AX) ... { + tmp:1 = dst5Ax:1; + src:1 = src5B; + setSubtractFlags(tmp, src); + tmp = tmp - src; + setResultFlags(tmp); +} + +# (5) CMP.W:G src5, dst5 +:CMP^".W:G" src5W, dst5W_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0x6) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) { + tmp:2 = dst5W_afterSrc5; + src:2 = src5W; + setSubtractFlags(tmp, src); + tmp = tmp - src; + setResultFlags(tmp); +} + +# (5) CMP.W:G src5, Ax +:CMP^".W:G" src5W, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0x6) ... & $(SRC5W) & $(DST5AX) ... { + tmp:2 = dst5Ax:2; + src:2 = src5W; + setSubtractFlags(tmp, src); + tmp = tmp - src; + setResultFlags(tmp); +} + +# (6) CMP.L:G src5, dst5 +:CMP^".L:G" src5L, dst5L_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=1) ... & $(SRC5L) ... & $(DST5L_AFTER_SRC5) ... { + tmp:4 = dst5L_afterSrc5; + src:4 = src5L; + setSubtractFlags(tmp, src); + tmp = tmp - src; + setResultFlags(tmp); +} + +# (6) CMP.L:G src5, Ax +:CMP^".L:G" src5L, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=1) ... & $(SRC5L) & $(DST5AX) ... { + tmp:4 = zext(dst5Ax); + src:4 = src5L; + setSubtractFlags(tmp, src); + tmp = tmp - src; + setResultFlags(tmp); +} + +# (7) CMP.B:S src2, R0L +:CMP^".B:S" dst2B, R0L is (b1_0607=1 & b1_0103=0 & b1_size_0=0 & R0L) ... & dst2B { + tmp:1 = dst2B; + setSubtractFlags(R0L, tmp); + tmp = tmp - R0L; + setResultFlags(tmp); +} + +# (7) CMP.W:S src2, R0 +:CMP^".W:S" dst2W, R0 is (b1_0607=1 & b1_0103=0 & b1_size_0=1 & R0) ... & dst2W { + tmp:2 = dst2W; + setSubtractFlags(R0, tmp); + tmp = tmp - R0; + setResultFlags(tmp); +} + +##### CMPX ##### + +# CMPX #simm, dst5 +:CMPX srcSimm8, dst5L is ((b1_0407=0xa & b1_size_0=0; b2_0005=0x11) ... & $(DST5L)); srcSimm8 { + tmp:4 = dst5L; + imm:4 = sext(srcSimm8); + setSubtractFlags(tmp, imm); + tmp = tmp - imm; + setResultFlags(tmp); +} + +# CMPX #simm, Ax +:CMPX srcSimm8, dst5Ax is ((b1_0407=0xa & b1_size_0=0; b2_0005=0x11) & $(DST5AX)); srcSimm8 { + tmp:4 = zext(dst5Ax); + imm:4 = sext(srcSimm8); + setSubtractFlags(tmp, imm); + tmp = tmp - imm; + setResultFlags(tmp); +} + +##### DADC ##### + +# (1) DADC.B #imm, dst5 +:DADC.B srcImm8, dst5B is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x0e) ... & $(DST5B)); srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(dst5B); + tmp:2 = DecimalAddWithCarry(src, dst); + dst5B = tmp:1; + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (1) DADC.B #imm, Ax +:DADC.B srcImm8, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x0e) & $(DST5AX)); srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(dst5Ax:1); + tmp:2 = DecimalAddWithCarry(src, dst); + dst5Ax = zext(tmp:1); + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (1) DADC.W #imm, dst5 +:DADC.W srcImm16, dst5W is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x0e) ... & $(DST5W)); srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(dst5W); + tmp:4 = DecimalAddWithCarry(src, dst); + dst5W = tmp:2; + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +# (1) DADC.W #imm, Ax +:DADC.W srcImm16, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x0e) & $(DST5AX)); srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(dst5Ax:2); + tmp:4 = DecimalAddWithCarry(src, dst); + dst5Ax = zext(tmp:2); + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +# (2) DADC.B src5, dst5 +:DADC.B src5B, dst5B_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x8) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ...) { + src:2 = zext(src5B); + dst:2 = zext(dst5B_afterSrc5); + tmp:2 = DecimalAddWithCarry(src, dst); + dst5B_afterSrc5 = tmp:1; + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (2) DADC.B src5, Ax +:DADC.B src5B, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x8) ... & $(SRC5B) & $(DST5AX) ...) { + src:2 = zext(src5B); + dst:2 = zext(dst5Ax:1); + tmp:2 = DecimalAddWithCarry(src, dst); + dst5Ax = zext(tmp:1); + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (2) DADC.W src5, dst5 +:DADC.W src5W, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x8) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ...) { + src:4 = zext(src5W); + dst:4 = zext(dst5W_afterSrc5); + tmp:4 = DecimalAddWithCarry(src, dst); + dst5W_afterSrc5 = tmp:2; + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +# (2) DADC.W src5, Ax +:DADC.W src5W, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x8) ... & $(SRC5W) & $(DST5AX) ...) { + src:4 = zext(src5W); + dst:4 = zext(dst5Ax:2); + tmp:4 = DecimalAddWithCarry(src, dst); + dst5Ax = zext(tmp:2); + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +##### DADD ##### + +# (1) DADD.B #imm, dst5 +:DADD.B srcImm8, dst5B is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x1e) ... & $(DST5B)); srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(dst5B); + tmp:2 = DecimalAdd(src, dst); + dst5B = tmp:1; + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (1) DADD.B #imm, Ax +:DADD.B srcImm8, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x1e) & $(DST5AX)); srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(dst5Ax:1); + tmp:2 = DecimalAdd(src, dst); + dst5Ax = zext(tmp:1); + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (1) DADD.W #imm, dst5 +:DADD.W srcImm16, dst5W is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x1e) ... & $(DST5W)); srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(dst5W); + tmp:4 = DecimalAdd(src, dst); + dst5W = tmp:2; + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +# (1) DADD.W #imm, Ax +:DADD.W srcImm16, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x1e) & $(DST5AX)); srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(dst5Ax:2); + tmp:4 = DecimalAdd(src, dst); + dst5Ax = zext(tmp:2); + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +# (2) DADD.B src5, dst5 +:DADD.B src5B, dst5B_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x0) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ...) { + src:2 = zext(src5B); + dst:2 = zext(dst5B_afterSrc5); + tmp:2 = DecimalAdd(src, dst); + dst5B_afterSrc5 = tmp:1; + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (2) DADD.B src5, Ax +:DADD.B src5B, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x0) ... & $(SRC5B) & $(DST5AX) ...) { + src:2 = zext(src5B); + dst:2 = zext(dst5Ax:1); + tmp:2 = DecimalAdd(src, dst); + dst5Ax = zext(tmp:1); + $(CARRY) = (tmp > 0x99); + setResultFlags(tmp:1); +} + +# (2) DADD.W src5, dst5 +:DADD.W src5W, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x0) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ...) { + src:4 = zext(src5W); + dst:4 = zext(dst5W_afterSrc5); + tmp:4 = DecimalAdd(src, dst); + dst5W_afterSrc5 = tmp:2; + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +# (2) DADD.W src5, Ax +:DADD.W src5W, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x0) ... & $(SRC5W) & $(DST5AX) ...) { + src:4 = zext(src5W); + dst:4 = zext(dst5Ax:2); + tmp:4 = DecimalAdd(src, dst); + dst5Ax = zext(tmp:2); + $(CARRY) = (tmp > 0x9999); + setResultFlags(tmp:2); +} + +##### DEC ##### + +# DEC.B dst5 +:DEC.B dst5B is (b1_0407=0xb & b1_size_0=0; b2_0005=0x0e) ... & $(DST5B) { + tmp:1 = dst5B - 1; + dst5B = tmp; + setResultFlags(tmp); +} + +# DEC.B Ax +:DEC.B dst5Ax is (b1_0407=0xb & b1_size_0=0; b2_0005=0x0e) & $(DST5AX) { + tmp:1 = dst5Ax:1 - 1; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# DEC.W dst5 +:DEC.W dst5W is (b1_0407=0xb & b1_size_0=1; b2_0005=0x0e) ... & $(DST5W) { + tmp:2 = dst5W - 1; + dst5W = tmp; + setResultFlags(tmp); +} + +# DEC.W Ax +:DEC.W dst5Ax is (b1_0407=0xb & b1_size_0=1; b2_0005=0x0e) & $(DST5AX) { + tmp:2 = dst5Ax:2 - 1; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### DIV ##### + +# (1) DIV.B #imm +:DIV.B srcSimm8 is b1_0007=0xb0; b2_0007=0x43; srcSimm8 { + d:2 = sext(srcSimm8); + q:2 = R0 s/ d; + r:2 = R0 s% d; # remainder has same sign as R0 (dividend) + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (1) DIV.W #imm +:DIV.W srcSimm16 is b1_0007=0xb0; b2_0007=0x53; srcSimm16 { + d:4 = sext(srcSimm16); + q:4 = R2R0 s/ d; + r:4 = R2R0 s% d; # remainder has same sign as R0 (dividend) + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIV.B src5 +:DIV.B dst5B is (b1_0407=0x8 & b1_size_0=0; b2_0005=0x1e) ... & $(DST5B) { + d:2 = sext(dst5B); + q:2 = R0 s/ d; + r:2 = R0 s% d; # remainder has same sign as R0 (dividend) + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIV.W src5 +:DIV.W dst5W is (b1_0407=0x8 & b1_size_0=1; b2_0005=0x1e) ... & $(DST5W) { + d:4 = sext(dst5W); + q:4 = R2R0 s/ d; + r:4 = R2R0 s% d; # remainder has same sign as R0 (dividend) + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +##### DIVU ##### + +# (1) DIVU.B #imm +:DIVU.B srcImm8 is b1_0007=0xb0; b2_0007=0x03; srcImm8 { + d:2 = zext(srcImm8); + q:2 = R0 / d; + r:2 = R0 % d; + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (1) DIVU.W #imm +:DIVU.W srcImm16 is b1_0007=0xb0; b2_0007=0x13; srcImm16 { + d:4 = zext(srcImm16); + q:4 = R2R0 / d; + r:4 = R2R0 % d; + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIVU.B src5 +:DIVU.B dst5B is (b1_0407=0x8 & b1_size_0=0; b2_0005=0x0e) ... & $(DST5B) { + d:2 = zext(dst5B); + q:2 = R0 / d; + r:2 = R0 % d; + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIVU.W src5 +:DIVU.W dst5W is (b1_0407=0x8 & b1_size_0=1; b2_0005=0x0e) ... & $(DST5W) { + d:4 = zext(dst5W); + q:4 = R2R0 / d; + r:4 = R2R0 % d; + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +##### DIVX ##### + +# (1) DIVX.B #imm +:DIVX.B srcSimm8 is b1_0007=0xb2; b2_0007=0x43; srcSimm8 { + d:2 = sext(srcSimm8); + q:2 = R0 s/ d; + r:2 = R0 s% d; + + #according to the manual the remainder has the same sign as the quotient + differ:1 = (r s< 0) != (d s< 0); + r = (zext(differ) * (-r)) + (zext(!differ) * r); + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (1) DIVX.W #imm +:DIVX.W srcSimm16 is b1_0007=0xb2; b2_0007=0x53; srcSimm16 { + d:4 = sext(srcSimm16); + q:4 = R2R0 s/ d; + r:4 = R2R0 s% d; + + #according to the manual the remainder has the same sign as the quotient + differ:1 = (r s< 0) != (d s< 0); + r = (zext(differ) * (-r)) + (zext(!differ) * r); + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIVX.B src5 +:DIVX.B dst5B is (b1_0407=0x9 & b1_size_0=0; b2_0005=0x1e) ... & $(DST5B) { + d:2 = sext(dst5B); + q:2 = R0 s/ d; + r:2 = R0 s% d; + + #according to the manual the remainder has the same sign as the quotient + differ:1 = (r s< 0) != (d s< 0); + r = (zext(differ) * (-r)) + (zext(!differ) * r); + R0L = q:1; + R0H = r:1; + q = q s>> 8; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +# (2) DIVX.W src5 +:DIVX.W dst5W is (b1_0407=0x9 & b1_size_0=1; b2_0005=0x1e) ... & $(DST5W) { + d:4 = sext(dst5W); + q:4 = R2R0 s/ d; + r:4 = R2R0 s% d; + + #according to the manual the remainder has the same sign as the quotient + R0 = q:2; + R2 = r:2; + q = q s>> 16; + $(OVERFLOW) = (d == 0) || ((q != 0) && (q != -1)); +} + +##### DSBB ##### + +# (1) DSBB.B #imm, dst5 +:DSBB.B srcImm8, dst5B is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x0e) ... & $(DST5B)); srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(dst5B); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:2 = DecimalSubtractWithBorrow(dst, src); + dst5B = tmp:1; + setResultFlags(tmp:1); +} + +# (1) DSBB.B #imm, Ax +:DSBB.B srcImm8, dst5Ax is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x0e) & $(DST5AX)); srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(dst5Ax:1); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:2 = DecimalSubtractWithBorrow(dst, src); + dst5Ax = zext(tmp:1); + setResultFlags(tmp:1); +} + +# (1) DSBB.W #imm, dst5 +:DSBB.W srcImm16, dst5W is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x0e) ... & $(DST5W)); srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(dst5W); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:4 = DecimalSubtractWithBorrow(dst, src); + dst5W = tmp:2; + setResultFlags(tmp:2); +} + +# (1) DSBB.W #imm, Ax +:DSBB.W srcImm16, dst5Ax is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x0e) & $(DST5AX)); srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(dst5Ax:2); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:4 = DecimalSubtractWithBorrow(dst, src); + dst5Ax = zext(tmp:2); + setResultFlags(tmp:2); +} + +# (2) DSBB.B src5, dst5 +:DSBB.B src5B, dst5B_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0xa) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ...) { + src:2 = zext(src5B); + dst:2 = zext(dst5B_afterSrc5); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:2 = DecimalSubtractWithBorrow(dst, src); + dst5B_afterSrc5 = tmp:1; + setResultFlags(tmp:1); +} + +# (2) DSBB.B src5, Ax +:DSBB.B src5B, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0xa) ... & $(SRC5B) & $(DST5AX) ...) { + src:2 = zext(src5B); + dst:2 = zext(dst5Ax:1); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:2 = DecimalSubtractWithBorrow(dst, src); + dst5Ax = zext(tmp:1); + setResultFlags(tmp:1); +} + +# (2) DSBB.W src5, dst5 +:DSBB.W src5W, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0xa) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ...) { + src:4 = zext(src5W); + dst:4 = zext(dst5W_afterSrc5); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:4 = DecimalSubtractWithBorrow(dst, src); + dst5W_afterSrc5 = tmp:2; + setResultFlags(tmp:2); +} + +# (2) DSBB.W src5, Ax +:DSBB.W src5W, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0xa) ... & $(SRC5W) & $(DST5AX) ...) { + src:4 = zext(src5W); + dst:4 = zext(dst5Ax:2); + c:1 = $(CARRY); + $(CARRY) = (c && (dst > src)) || (!c && (dst >= src)); + tmp:4 = DecimalSubtractWithBorrow(dst, src); + dst5Ax = zext(tmp:2); + setResultFlags(tmp:2); +} + +##### DSUB ##### + +# (1) DSUB.B #imm, dst5 +:DSUB.B srcImm8, dst5B is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x1e) ... & $(DST5B)); srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(dst5B); + $(CARRY) = (dst >= src); + tmp:2 = DecimalSubtract(dst, src); + dst5B = tmp:1; + setResultFlags(tmp:1); +} + +# (1) DSUB.B #imm, Ax +:DSUB.B srcImm8, dst5Ax is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x1e) & $(DST5AX)); srcImm8 { + src:2 = zext(srcImm8); + dst:2 = zext(dst5Ax:1); + $(CARRY) = (dst >= src); + tmp:2 = DecimalSubtract(dst, src); + dst5Ax = zext(tmp:1); + setResultFlags(tmp:1); +} + +# (1) DSUB.W #imm, dst5 +:DSUB.W srcImm16, dst5W is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x1e) ... & $(DST5W)); srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(dst5W); + $(CARRY) = (dst >= src); + tmp:4 = DecimalSubtract(dst, src); + dst5W = tmp:2; + setResultFlags(tmp:2); +} + +# (1) DSUB.W #imm, Ax +:DSUB.W srcImm16, dst5Ax is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x1e) & $(DST5AX)); srcImm16 { + src:4 = zext(srcImm16); + dst:4 = zext(dst5Ax:2); + $(CARRY) = (dst >= src); + tmp:4 = DecimalSubtract(dst, src); + dst5Ax = zext(tmp:2); + setResultFlags(tmp:2); +} + +# (2) DSUB.B src5, dst5 +:DSUB.B src5B, dst5B_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x2) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ...) { + src:2 = zext(src5B); + dst:2 = zext(dst5B_afterSrc5); + $(CARRY) = (dst >= src); + tmp:2 = DecimalSubtract(dst, src); + dst5B_afterSrc5 = tmp:1; + setResultFlags(tmp:1); +} + +# (2) DSUB.B src5, Ax +:DSUB.B src5B, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x2) ... & $(SRC5B) & $(DST5AX) ...) { + src:2 = zext(src5B); + dst:2 = zext(dst5Ax:1); + $(CARRY) = (dst >= src); + tmp:2 = DecimalSubtract(dst, src); + dst5Ax = zext(tmp:1); + setResultFlags(tmp:1); +} + +# (2) DSUB.W src5, dst5 +:DSUB.W src5W, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x2) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ...) { + src:4 = zext(src5W); + dst:4 = zext(dst5W_afterSrc5); + $(CARRY) = (dst >= src); + tmp:4 = DecimalSubtract(dst, src); + dst5W_afterSrc5 = tmp:2; + setResultFlags(tmp:2); +} + +# (2) DSUB.W src5, Ax +:DSUB.W src5W, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x2) ... & $(SRC5W) & $(DST5AX) ...) { + src:4 = zext(src5W); + dst:4 = zext(dst5Ax:2); + $(CARRY) = (dst >= src); + tmp:4 = DecimalSubtract(dst, src); + dst5Ax = zext(tmp:2); + setResultFlags(tmp:2); +} + +##### ENTER ##### + +:ENTER srcImm8 is b1_0007=0xec; srcImm8 { + push3(FB); + FB = SP; + SP = SP - zext(srcImm8); +} + +##### EXITD ##### + +:EXITD is b1_0007=0xfc { + SP = FB; + pop3(FB); + pc:3 = 0; + pop3(pc); + return [pc]; +} + +##### EXTS ##### + +# (1) EXTS.B dst5 +:EXTS.B dst5B is (b1_0407=0xc & b1_size_0=0; b2_0005=0x1e) ... & $(DST5B) & $(DST5W) { + tmp:2 = sext(dst5B); + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) EXTS.B Ax +:EXTS.B dst5Ax is (b1_0407=0xc & b1_size_0=0; b2_0005=0x1e) & $(DST5AX) { + tmp:2 = sext(dst5Ax:1); + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) EXTS.W dst5 +:EXTS.W dst5W is (b1_0407=0xc & b1_size_0=1; b2_0005=0x1e) ... & $(DST5W) & $(DST5L) { + tmp:4 = sext(dst5W); + dst5L = tmp; + setResultFlags(tmp); +} + +# (1) EXTS.W Ax +:EXTS.W dst5Ax is (b1_0407=0xc & b1_size_0=1; b2_0005=0x1e) & $(DST5AX) { + tmp:4 = sext(dst5Ax:2); + dst5Ax = tmp:3; + setResultFlags(tmp); +} + +# (2) EXTS.B src5, dst5 +:EXTS.B src5B, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x7) ... & $(SRC5B) ... & $(DST5W_AFTER_SRC5) ...) { + tmp:2 = sext(src5B); + dst5W_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (2) EXTS.B src5, Ax +:EXTS.B src5B, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x7) ... & $(SRC5B) & $(DST5AX) ...) { + tmp:2 = sext(src5B); + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### EXTZ ##### + +# (1) EXTZ.B src5, dst5 +:EXTZ.B src5B, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0xb) ... & $(SRC5B) ... & $(DST5W_AFTER_SRC5) ...) { + tmp:2 = zext(src5B); + dst5W_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (1) EXTZ.B src5, Ax +:EXTZ.B src5B, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0xb) ... & $(SRC5B) & $(DST5AX) ...) { + tmp:2 = zext(src5B); + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### FCLR ##### + +:FCLR flagBit is b1_0007=0xd3; b2_0307=0x1d & flagBit { + mask:2 = ~(1 << flagBit); + FLG = FLG & mask; +} + +##### FREIT ##### + +:FREIT is b1_0007=0x9f { + FLG = SVF; + return [SVP]; +} + +##### FSET ##### + +:FSET flagBit is b1_0007=0xd1; b2_0307=0x1d & flagBit { + mask:2 = (1 << flagBit); + FLG = FLG | mask; +} + +##### INC ##### + +# INC.B dst5 +:INC.B dst5B is (b1_0407=0xa & b1_size_0=0; b2_0005=0x0e) ... & $(DST5B) { + tmp:1 = dst5B + 1; + dst5B = tmp; + setResultFlags(tmp); +} + +# INC.B Ax +:INC.B dst5Ax is (b1_0407=0xa & b1_size_0=0; b2_0005=0x0e) & $(DST5AX) { + tmp:1 = dst5Ax:1 + 1; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# INC.W dst5 +:INC.W dst5W is (b1_0407=0xa & b1_size_0=1; b2_0005=0x0e) ... & $(DST5W) { + tmp:2 = dst5W + 1; + dst5W = tmp; + setResultFlags(tmp); +} + +# INC.W Ax +:INC.W dst5Ax is (b1_0407=0xa & b1_size_0=1; b2_0005=0x0e) & $(DST5AX) { + tmp:2 = dst5Ax:2 + 1; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### INDEXB ##### + +# 1000 1000 0100 0011 INDEXB.B R1H +:INDEXB.B dst5B is (b1_0407=0x8 & b1_size_0=0; b2_0005=0x03) ... & $(DST5B) + [ useByteIndexOffset=3; globalset(inst_next,useByteIndexOffset); useByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5B); +} + +:INDEXB.W dst5W is (b1_0407=0x8 & b1_size_0=0; b2_0005=0x13) ... & $(DST5W) + [ useByteIndexOffset=3; globalset(inst_next,useByteIndexOffset); useByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5W); +} + +##### INDEXBD ##### + +:INDEXBD.B dst5B is (b1_0407=0xa & b1_size_0=0; b2_0005=0x03) ... & $(DST5B) + [ useDstByteIndexOffset=3; globalset(inst_next,useDstByteIndexOffset); useDstByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5B); +} + +:INDEXBD.W dst5W is (b1_0407=0xa & b1_size_0=0; b2_0005=0x13) ... & $(DST5W) + [ useDstByteIndexOffset=3; globalset(inst_next,useDstByteIndexOffset); useDstByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5W); +} + +##### INDEXBS ##### + +:INDEXBS.B dst5B is (b1_0407=0xc & b1_size_0=0; b2_0005=0x03) ... & $(DST5B) + [ useSrcByteIndexOffset=3; globalset(inst_next,useSrcByteIndexOffset); useSrcByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5B); +} + +:INDEXBS.W dst5W is (b1_0407=0xc & b1_size_0=0; b2_0005=0x13) ... & $(DST5W) + [ useSrcByteIndexOffset=3; globalset(inst_next,useSrcByteIndexOffset); useSrcByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5W); +} + +##### INDEXL ##### + +:INDEXL.B dst5B is (b1_0407=0x9 & b1_size_0=0; b2_0005=0x23) ... & $(DST5B) + [ useByteIndexOffset=3; globalset(inst_next,useByteIndexOffset); useByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5B) * 4; +} + +:INDEXL.W dst5W is (b1_0407=0x9 & b1_size_0=0; b2_0005=0x33) ... & $(DST5W) + [ useByteIndexOffset=3; globalset(inst_next,useByteIndexOffset); useByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5W) * 4; +} + +##### INDEXLD ##### + +:INDEXLD.B dst5B is (b1_0407=0xb & b1_size_0=0; b2_0005=0x23) ... & $(DST5B) + [ useDstByteIndexOffset=3; globalset(inst_next,useDstByteIndexOffset); useDstByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5B) * 4; +} + +:INDEXLD.W dst5W is (b1_0407=0xb & b1_size_0=0; b2_0005=0x33) ... & $(DST5W) + [ useDstByteIndexOffset=3; globalset(inst_next,useDstByteIndexOffset); useDstByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5W) * 4; +} + +##### INDEXLS ##### + +:INDEXLS.B dst5B is (b1_0407=0x9 & b1_size_0=0; b2_0005=0x03) ... & $(DST5B) + [ useSrcByteIndexOffset=3; globalset(inst_next,useSrcByteIndexOffset); useSrcByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5B) * 4; +} + +:INDEXLS.W dst5W is (b1_0407=0x9 & b1_size_0=0; b2_0005=0x13) ... & $(DST5W) + [ useSrcByteIndexOffset=3; globalset(inst_next,useSrcByteIndexOffset); useSrcByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5W) * 4; +} + +##### INDEXW ##### + +:INDEXW.B dst5B is (b1_0407=0x8 & b1_size_0=0; b2_0005=0x23) ... & $(DST5B) + [ useByteIndexOffset=3; globalset(inst_next,useByteIndexOffset); useByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5B) * 2; +} + +:INDEXW.W dst5W is (b1_0407=0x8 & b1_size_0=0; b2_0005=0x33) ... & $(DST5W) + [ useByteIndexOffset=3; globalset(inst_next,useByteIndexOffset); useByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5W) * 2; +} + +##### INDEXWD ##### + +:INDEXWD.B dst5B is (b1_0407=0xa & b1_size_0=0; b2_0005=0x23) ... & $(DST5B) + [ useDstByteIndexOffset=3; globalset(inst_next,useDstByteIndexOffset); useDstByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5B) * 2; +} + +:INDEXWD.W dst5W is (b1_0407=0xa & b1_size_0=0; b2_0005=0x33) ... & $(DST5W) + [ useDstByteIndexOffset=3; globalset(inst_next,useDstByteIndexOffset); useDstByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5W) * 2; +} + +##### INDEXWS ##### + +:INDEXWS.B dst5B is (b1_0407=0xc & b1_size_0=0; b2_0005=0x23) ... & $(DST5B) + [ useSrcByteIndexOffset=3; globalset(inst_next,useSrcByteIndexOffset); useSrcByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5B) * 2; +} + +:INDEXWS.W dst5W is (b1_0407=0xc & b1_size_0=0; b2_0005=0x33) ... & $(DST5W) + [ useSrcByteIndexOffset=3; globalset(inst_next,useSrcByteIndexOffset); useSrcByteIndexOffset=0; ] { + byteIndexOffset = zext(dst5W) * 2; +} + +##### INT ##### + +:INT srcIntNum is b1_0007=0xbe; imm8_0001=0 & srcIntNum { + push2(FLG); + next:3 = inst_next; + push3(next); + ptr3:3 = (INTB + (zext(srcIntNum) * 0x4)); + pc:3 = *:3 ptr3; + $(STACK_SEL) = ((srcIntNum > 0x1f) * $(STACK_SEL)); + $(INTERRUPT) = 0x0; + $(DEBUG) = 0x0; + call [pc]; +} + +##### INTO ##### + +:INTO is b1_0007=0xbf { + if ($(OVERFLOW) == 0) goto inst_next; + push2(FLG); + next:3 = inst_next; + push3(next); + $(STACK_SEL) = 0; + $(INTERRUPT) = 0x0; + $(DEBUG) = 0x0; + call 0x0ffffe0; +} + +##### JCnd ##### + +:J^b1cnd rel8offset1 is b1_0707=1 & b1_0103=5 & b1cnd; rel8offset1 { + if (b1cnd) goto rel8offset1; +} + +##### JMP ##### + +:JMP.S rel3offset2 is b1_0607=1 & b1_0103=5 & rel3offset2 { + goto rel3offset2; +} + +:JMP.B rel8offset1 is b1_0007=0xbb; rel8offset1 { + goto rel8offset1; +} + +:JMP.W rel16offset1 is b1_0007=0xce; rel16offset1 { + goto rel16offset1; +} + +:JMP.A abs24offset is b1_0007=0xcc; abs24offset { + goto abs24offset; +} + +##### JMPI ##### +:JMPI.W reloffset_dst5W is (b1_0407=0xc & b1_size_0=1; b2_0005=0x0f) ... & reloffset_dst5W { + goto reloffset_dst5W; +} + +:JMPI.A reloffset_dst5L is (b1_0407=0x8 & b1_size_0=0; b2_0005=0x01) ... & reloffset_dst5L { + goto reloffset_dst5L; +} + +:JMPI.A reloffset_dst5Ax is (b1_0407=0x8 & b1_size_0=0; b2_0005=0x01) & reloffset_dst5Ax { + goto reloffset_dst5Ax; +} + +##### JMPS ##### + +:JMPS srcImm8 is b1_0007=0xdc; srcImm8 { + # 18 <= srcImm8 <= 255 (range restriction not enforced by pattern match) + ptr:3 = 0x0fffe - (zext(srcImm8) << 1); + pc:3 = 0xff0000 | zext(*:2 ptr); + goto [pc]; +} + +##### JSR ##### + +:JSR.W rel16offset1 is b1_0007=0xcf; rel16offset1 { + next:3 = inst_next; + push3(next); + call rel16offset1; +} + +:JSR.A abs24offset is b1_0007=0xcd; abs24offset { + next:3 = inst_next; + push3(next); + call abs24offset; +} + +##### JSRI ##### + +:JSRI.W reloffset_dst5W is (b1_0407=0xc & b1_size_0=1; b2_0005=0x1f) ... & reloffset_dst5W { + next:3 = inst_next; + push3(next); + call reloffset_dst5W; +} + +:JSRI.A dst5L is (b1_0407=0x9 & b1_size_0=0; b2_0005=0x01) ... & $(DST5L) { + next:3 = inst_next; + push3(next); + pc:3 = dst5L:3; + call [pc]; +} + +:JSRI.A dst5Ax is (b1_0407=0x9 & b1_size_0=0; b2_0005=0x01) & $(DST5AX) { + next:3 = inst_next; + push3(next); + call [dst5Ax]; +} + +##### JSRS ##### + +:JSRS srcImm8 is b1_0007=0xdd; srcImm8 { + # 18 <= srcImm8 <= 255 (range restriction not enforced by pattern match) + next:3 = inst_next; + push3(next); + ptr:3 = 0x0fffe - (zext(srcImm8) << 1); + pc:3 = 0xff0000 | zext(*:2 ptr); + call [pc]; +} + +##### LDC ##### + +# (1) LDC #imm16, b2_creg16 +:LDC srcImm16, b2_creg16 is b1_0007=0xd5; b2_0307=0x15 & b2_creg16; srcImm16 { + b2_creg16 = srcImm16; +} + +# (2) LDC #imm24, b2_creg24 +:LDC srcImm24, b2_creg24 is b1_0007=0xd5; b2_0307=0x05 & b2_creg24; srcImm24 { + b2_creg24 = srcImm24; +} + +# (3) LDC #imm24, b2_dreg24 +:LDC srcImm24, b2_dreg24 is b1_0007=0xd5; b2_0307=0x0d & b2_dreg24; srcImm24 { + b2_dreg24 = srcImm24; +} + +# (4) LDC dst5, b2_creg16 +:LDC dst5W, b2_creg16 is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=1; b2_0305=1 & b2_creg16) ... & $(DST5W)) { + b2_creg16 = dst5W; +} + +# (5) LDC dst5, b2_creg24 +:LDC dst5L, b2_creg24 is (b1_0407=0xd & b1_size_0=1; b2_0305=0 & b2_creg24) ... & $(DST5L) { + b2_creg24 = dst5L:3; +} + +# (6) LDC dst5, b2_dreg24 +:LDC dst5L, b2_dreg24 is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=1; b2_0305=0 & b2_dreg24) ... & $(DST5L)) { + b2_dreg24 = dst5L:3; +} + +##### LDCTX ##### + +:LDCTX abs16offset, abs24offset is b1_0007=0xb6; b2_0007=0xc3; abs16offset; imm24_dat & abs24offset { + + taskNum:1 = abs16offset; # load task number stored at abs16 + ptr:3 = imm24_dat + (zext(taskNum) * 2); # compute table entry address relative to abs24 + regInfo:1 = *:1 ptr; + ptr = ptr + 1; + spCorrect:1 = *:1 ptr; + + ptr = SP; + + if ((regInfo & 1) == 0) goto ; + R0 = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + R1 = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + R2 = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + R3 = *:2 ptr; + ptr = ptr + 2; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + tmp:4 = *:4 ptr; + A0 = tmp:3; + ptr = ptr + 4; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + tmp = *:4 ptr; + A1 = tmp:3; + ptr = ptr + 4; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + tmp = *:4 ptr; + SB = tmp:3; + ptr = ptr + 4; + + regInfo = regInfo >> 1; + if ((regInfo & 1) == 0) goto ; + tmp = *:4 ptr; + FB = tmp:3; + ptr = ptr + 4; + + SP = SP + zext(spCorrect); +} + +##### LDIPL ##### + +:LDIPL srcImm3 is b1_0007=0xd5; b2_0307=0x1d & srcImm3 { + $(IPL) = srcImm3; +} + +##### MAX ##### + +# (1) MAX.B #imm, dst5 +:MAX.B srcSimm8, dst5B is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x3f) ... & $(DST5B)); srcSimm8 { + if (srcSimm8 s<= dst5B) goto inst_next; + dst5B = srcSimm8; +} + +# (1) MAX.B #imm, Ax +:MAX.B srcSimm8, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x3f) & $(DST5AX)); srcSimm8 { + if (srcSimm8 s<= dst5Ax:1) goto inst_next; + dst5Ax = zext(srcSimm8); +} + +# (1) MAX.W #imm, dst5 +:MAX.W srcSimm16, dst5W is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x3f) ... & $(DST5W)); srcSimm16 { + if (srcSimm16 s<= dst5W) goto inst_next; + dst5W = srcSimm16; +} + +# (1) MAX.W #imm, Ax +:MAX.W srcSimm16, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x3f) & $(DST5AX)); srcSimm16 { + if (srcSimm16 s<= dst5Ax:2) goto inst_next; + dst5Ax = zext(srcSimm16); +} + +# (2) MAX.B src5, dst5 +:MAX.B src5B, dst5B_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0xd) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ...) { + val:1 = src5B; + if (val s<= dst5B_afterSrc5) goto inst_next; + dst5B_afterSrc5 = val; +} + +# (2) MAX.B src5, Ax +:MAX.B src5B, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0xd) ... & $(SRC5B) & $(DST5AX) ...) { + val:1 = src5B; + if (val s<= dst5Ax:1) goto inst_next; + dst5Ax = zext(val); +} + +# (2) MAX.W src5, dst5 +:MAX.W src5W, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0xd) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ...) { + val:2 = src5W; + if (val s<= dst5W_afterSrc5) goto inst_next; + dst5W_afterSrc5 = val; +} + +# (2) MAX.W src5, Ax +:MAX.B src5W, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0xd) ... & $(SRC5W) & $(DST5AX) ...) { + val:2 = src5W; + if (val s<= dst5Ax:2) goto inst_next; + dst5Ax = zext(val); +} + +##### MIN ##### + +# (1) MIN.B #imm, dst5 +:MIN.B srcSimm8, dst5B is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x2f) ... & $(DST5B)); srcSimm8 { + if (srcSimm8 s>= dst5B) goto inst_next; + dst5B = srcSimm8; +} + +# (1) MIN.B #imm, Ax +:MIN.B srcSimm8, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x2f) & $(DST5AX)); srcSimm8 { + if (srcSimm8 s>= dst5Ax:1) goto inst_next; + dst5Ax = zext(srcSimm8); +} + +# (1) MIN.W #imm, dst5 +:MIN.W srcSimm16, dst5W is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x2f) ... & $(DST5W)); srcSimm16 { + if (srcSimm16 s>= dst5W) goto inst_next; + dst5W = srcSimm16; +} + +# (1) MIN.W #imm, Ax +:MIN.W srcSimm16, dst5Ax is b0_0007=0x1; ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x2f) & $(DST5AX)); srcSimm16 { + if (srcSimm16 s>= dst5Ax:2) goto inst_next; + dst5Ax = zext(srcSimm16); +} + +# (2) MIN.B src5, dst5 +:MIN.B src5B, dst5B_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0xc) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ...) { + val:1 = src5B; + if (val s>= dst5B_afterSrc5) goto inst_next; + dst5B_afterSrc5 = val; +} + +# (2) MIN.B src5, Ax +:MIN.B src5B, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0xc) ... & $(SRC5B) & $(DST5AX) ...) { + val:1 = src5B; + if (val s>= dst5Ax:1) goto inst_next; + dst5Ax = zext(val); +} + +# (2) MIN.W src5, dst5 +:MIN.W src5W, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0xc) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ...) { + val:2 = src5W; + if (val s>= dst5W_afterSrc5) goto inst_next; + dst5W_afterSrc5 = val; +} + +# (2) MIN.W src5, Ax +:MIN.B src5W, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0xc) ... & $(SRC5W) & $(DST5AX) ...) { + val:2 = src5W; + if (val s>= dst5Ax:2) goto inst_next; + dst5Ax = zext(val); +} + +##### MOV ##### + +# (1) MOV.B:G #imm, dst5 +:MOV^".B:G" srcImm8, dst5B is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x2f) ... & $(DST5B)); srcImm8 { + val:1 = srcImm8; + dst5B = val; + setResultFlags(val); +} + +# (1) MOV.B:G #imm, Ax +:MOV^".B:G" srcImm8, dst5Ax is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x2f) & $(DST5AX)); srcImm8 { + val:1 = srcImm8; + dst5Ax = zext(val); + setResultFlags(val); +} + +# (1) MOV.W:G #imm, dst5 +:MOV^".W:G" srcImm16, dst5W is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x2f) ... & $(DST5W)); srcImm16 { + val:2 = srcImm16; + dst5W = val; + setResultFlags(val); +} + +# (1) MOV.W:G #imm, Ax +:MOV^".W:G" srcImm16, dst5Ax is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x2f) & $(DST5AX)); srcImm16 { + val:2 = srcImm16; + dst5Ax = zext(val); + setResultFlags(val); +} + +# (2) MOV.L:G #imm, dst5 +:MOV^".L:G" srcImm32, dst5L is ((b1_0407=0xb & b1_size_0=0; b2_0005=0x31) ... & $(DST5L)); srcImm32 { + val:4 = srcImm32; + dst5L = val; + setResultFlags(val); +} + +# (2) MOV.L:G #imm, Ax +:MOV^".L:G" srcImm32, dst5Ax is ((b1_0407=0xb & b1_size_0=0; b2_0005=0x31) & $(DST5AX)); srcImm32 { + val:4 = srcImm32; + dst5Ax = val:3; + setResultFlags(val); +} + +# (3) MOV.B:Q #imm4, dst5 +:MOV^".B:Q" srcSimm4, dst5B is (b1_0407=0xf & b1_size_0=0; b2_0405=2 & srcSimm4) ... & $(DST5B) { + val:1 = srcSimm4; + dst5B = val; + setResultFlags(val); +} + +# (3) MOV.B:Q #imm4, Ax +:MOV^".B:Q" srcSimm4, dst5Ax is (b1_0407=0xf & b1_size_0=0; b2_0405=2 & srcSimm4) & $(DST5AX) { + val:1 = srcSimm4; + dst5Ax = zext(val); + setResultFlags(val); +} + +# (3) MOV.W:Q #imm4, dst5 +:MOV^".W:Q" srcSimm4, dst5W is (b1_0407=0xf & b1_size_0=1; b2_0405=2 & srcSimm4) ... & $(DST5W) { + val:2 = sext(srcSimm4); + dst5W = val; + setResultFlags(val); +} + +# (3) MOV.W:Q #imm4, Ax +:MOV^".W:Q" srcSimm4, dst5Ax is (b1_0407=0xf & b1_size_0=1; b2_0405=2 & srcSimm4) & $(DST5AX) { + val:2 = sext(srcSimm4); + dst5Ax = zext(val); + setResultFlags(val); +} + +# (4) MOV.B:S #imm, dst2 +:MOV^".B:S" srcImm8, dst2B is ((b1_0607=0 & b1_0103=2 & b1_size_0=0) ... & dst2B); srcImm8 { + val:1 = srcImm8; + dst2B = val; + setResultFlags(val); +} + +# (4) MOV.W:S #imm, dst2 +:MOV^".W:S" srcImm16, dst2W is ((b1_0607=0 & b1_0103=2 & b1_size_0=1) ... & dst2W); srcImm16 { + val:2 = srcImm16; + dst2W = val; + setResultFlags(val); +} + +# (5) MOV.W:S #imm16, Ax +:MOV^".W:S" srcImm16, b1_d1_regAx is b1_0607=2 & b1_size_5=0 & b1_0104=0xe & b1_d1_regAx; srcImm16 { + val:2 = srcImm16; + b1_d1_regAx = zext(val); + setResultFlags(val); +} + +# (5) MOV.L:S #imm24, Ax +:MOV^".L:S" srcImm24, b1_d1_regAx is b1_0607=2 & b1_size_5=1 & b1_0104=0xe & b1_d1_regAx; srcImm24 { + val:3 = srcImm24; + b1_d1_regAx = val; + setResultFlags(val); +} + +# (6) MOV.B:Z #0, dst2 +:MOV^".B:Z" srcZero8, dst2B is (b1_0607=0 & b1_0103=1 & b1_size_0=0 & srcZero8) ... & dst2B { + dst2B = 0; + $(SIGN) = 0; + $(ZERO) = 1; +} + +# (6) MOV.W:Z #0, dst2 +:MOV^".W:Z" srcZero16, dst2W is (b1_0607=0 & b1_0103=1 & b1_size_0=1 & srcZero16) ... & dst2W { + dst2W = 0; + $(SIGN) = 0; + $(ZERO) = 1; +} + +# (7) MOV.B:G src5, dst5 +:MOV^".B:G" src5B, dst5B_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0xb) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ... { + val:1 = src5B; + dst5B_afterSrc5 = val; + setResultFlags(val); +} + +# (7) MOV.B:G src5, Ax +:MOV^".B:G" src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0xb) ... & $(SRC5B) & $(DST5AX) ... { + val:1 = src5B; + dst5Ax = zext(val); + setResultFlags(val); +} + +# (7) MOV.W:G src5, dst5 +:MOV^".W:G" src5W, dst5W_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0xb) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ... { + val:2 = src5W; + dst5W_afterSrc5 = val; + setResultFlags(val); +} + +# (7) MOV.W:G src5, Ax +:MOV^".W:G" src5W, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0xb) ... & $(SRC5W) & $(DST5AX) ... { + val:2 = src5W; + dst5Ax = zext(val); + setResultFlags(val); +} + +# (8) MOV.L:G src5, dst5 +:MOV^".L:G" src5L, dst5L_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0x3) ... & $(SRC5L) ... & $(DST5L_AFTER_SRC5) ... { + val:4 = src5L; + dst5L_afterSrc5 = val; + setResultFlags(val); +} + +# (8) MOV.L:G src5, Ax +:MOV^".L:G" src5L, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0x3) ... & $(SRC5L) & $(DST5AX) ... { + val:4 = src5L; + dst5Ax = val:3; + setResultFlags(val); +} + +# (9) MOV.B:S src2, R0L +:MOV^".B:S" dst2B, R0L is (R0L & b1_0607=0 & b1_0103=4 & b1_size_0=0) ... & dst2B { + val:1 = dst2B; + R0L = val; + setResultFlags(val); +} + +# (9) MOV.W:S src2, R0 +:MOV^".W:S" dst2W, R0 is (R0 & b1_0607=0 & b1_0103=4 & b1_size_0=1) ... & dst2W { + val:2 = dst2W; + R0 = val; + setResultFlags(val); +} + +# (10) MOV.B:S src2, R1L +:MOV^".B:S" dst2B, R1L is (R1L & b1_0607=1 & b1_0103=7 & b1_size_0=0) ... & dst2B { + val:1 = dst2B; + R1L = val; + setResultFlags(val); +} + +# (10) MOV.W:S src2, R1 +:MOV^".W:S" dst2W, R1 is (R1 & b1_0607=1 & b1_0103=7 & b1_size_0=1) ... & dst2W { + val:2 = dst2W; + R1 = val; + setResultFlags(val); +} + +# (11) MOV.B:S R0L, dst2 +:MOV^".B:S" R0L, dst2B is (R0L & b1_0607=0 & b1_0103=0 & b1_size_0=0) ... & dst2B { + val:1 = R0L; + dst2B = val; + setResultFlags(val); +} + +# (11) MOV.W:S R0, dst2 +:MOV^".W:S" R0, dst2W is (R0 & b1_0607=0 & b1_0103=0 & b1_size_0=1) ... & dst2W { + val:2 = R0; + dst2W = val; + setResultFlags(val); +} + +# (12) MOV.L:S src2L, Ax +:MOV^".L:S" dst2L, b1_d1_regAx is (b1_0607=1 & b1_0103=4 & b1_d1_regAx) ... & dst2L { + val:4 = dst2L; + b1_d1_regAx = val:3; + setResultFlags(val); +} + +# (13) MOV.B:G dsp:8[SP], dst5 +# 1011 0110 1000 1111 0001 0010 1110 1111 1100 1101 1010 1011 MOV.G:G 0x12(SP),0xabcdef +:MOV^".B:G" dsp8spB, dst5B_afterDsp8 is (b1_0407=0xb & b1_size_0=0; b2_0005=0x0f; dsp8spB) ... & $(DST5B_AFTER_DSP8) { + val:1 = dsp8spB; + dst5B_afterDsp8 = val; + setResultFlags(val); +} + +# (13) MOV.B:G dsp:8[SP], Ax +:MOV^".B:G" dsp8spB, dst5Ax is (b1_0407=0xb & b1_size_0=0; b2_0005=0x0f; dsp8spB) & $(DST5AX) ... { + val:1 = dsp8spB; + dst5Ax = zext(val); + setResultFlags(val); +} + +# (13) MOV.W:G dsp:8[SP], dst5 +:MOV^".W:G" dsp8spW, dst5W_afterDsp8 is (b1_0407=0xb & b1_size_0=1; b2_0005=0x0f; dsp8spW) ... & $(DST5W_AFTER_DSP8) { + val:2 = dsp8spW; + dst5W_afterDsp8 = val; + setResultFlags(val); +} + +# (13) MOV.W:G dsp:8[SP], Ax +:MOV^".W:G" dsp8spW, dst5Ax is (b1_0407=0xb & b1_size_0=1; b2_0005=0x0f; dsp8spW) & $(DST5AX) ... { + val:2 = dsp8spW; + dst5Ax = zext(val); + setResultFlags(val); +} + +# (14) MOV.B:G src5, dsp:8[SP] +:MOV^".B:G" dst5B, dsp8spB is ((b1_0407=0xa & b1_size_0=0; b2_0005=0x0f) ... & $(DST5B)); dsp8spB { + val:1 = dst5B; + dsp8spB = val; + setResultFlags(val); +} + +# (14) MOV.W:G src5, dsp:8[SP] +:MOV^".W:G" dst5W, dsp8spW is ((b1_0407=0xa & b1_size_0=1; b2_0005=0x0f) ... & $(DST5W)); dsp8spW { + val:2 = dst5W; + dsp8spW = val; + setResultFlags(val); +} + +##### MOVA ##### + +# MOVA dst5A, RxRx +:MOVA dst5A, b2_reg32 is (b1_0407=0xd & b1_size_0=1; b2_0105=0xc & b2_reg32) ... & $(DST5A) { + b2_reg32 = zext(dst5A); +} + +# MOVA dst5A, Ax +:MOVA dst5A, b2_regAx is (b1_0407=0xd & b1_size_0=1; b2_0105=0xd & b2_regAx) ... & $(DST5A) { + b2_regAx = dst5A; +} + +##### MOVDir ##### + +# TODO: dst5B=Ax case will parse but is not valid + +# (1) MOVDir R0L, dst +:MOVLL R0L, dst5B is R0L & b0_0007=0x1; ((b1_0407=0xb & b1_size_0=0; b2_0005=0x0e) ... & $(DST5B)) { + dst5B = (R0L & 0x0f) | (dst5B & 0xf0); +} +:MOVHL R0L, dst5B is R0L & b0_0007=0x1; ((b1_0407=0xb & b1_size_0=0; b2_0005=0x1e) ... & $(DST5B)) { + dst5B = ((R0L & 0xf0) >> 4) | (dst5B & 0xf0); +} +:MOVLH R0L, dst5B is R0L & b0_0007=0x1; ((b1_0407=0xb & b1_size_0=0; b2_0005=0x2e) ... & $(DST5B)) { + dst5B = ((R0L & 0x0f) << 4) | (dst5B & 0x0f); +} +:MOVHH R0L, dst5B is R0L & b0_0007=0x1; ((b1_0407=0xb & b1_size_0=0; b2_0005=0x3e) ... & $(DST5B)) { + dst5B = (R0L & 0xf0) | (dst5B & 0x0f); +} + +# (2) MOVDir dst, R0L +:MOVLL dst5B, R0L is R0L & b0_0007=0x1; ((b1_0407=0xa & b1_size_0=0; b2_0005=0x0e) ... & $(DST5B)) { + R0L = (dst5B & 0x0f) | (R0L & 0xf0); +} +:MOVHL dst5B, R0L is R0L & b0_0007=0x1; ((b1_0407=0xa & b1_size_0=0; b2_0005=0x1e) ... & $(DST5B)) { + R0L = ((dst5B & 0xf0) >> 4) | (R0L & 0xf0); +} +:MOVLH dst5B, R0L is R0L & b0_0007=0x1; ((b1_0407=0xa & b1_size_0=0; b2_0005=0x2e) ... & $(DST5B)) { + R0L = ((dst5B & 0x0f) << 4) | (R0L & 0x0f); +} +:MOVHH dst5B, R0L is R0L & b0_0007=0x1; ((b1_0407=0xa & b1_size_0=0; b2_0005=0x3e) ... & $(DST5B)) { + R0L = (dst5B & 0xf0) | (R0L & 0x0f); +} + +##### MOVX ##### + +:MOVX srcSimm8, dst5L is ((b1_0407=0xb & b1_size_0=0; b2_0005=0x11) ... & $(DST5L)); srcSimm8 { + val:4 = sext(srcSimm8); + dst5L = val; + setResultFlags(val); +} + +:MOVX srcSimm8, dst5Ax is ((b1_0407=0xb & b1_size_0=0; b2_0005=0x11) & $(DST5AX)); srcSimm8 { + val:3 = sext(srcSimm8); + dst5Ax = val; + setResultFlags(val); +} + +##### MUL ##### + +# TODO: Illegal MUL destination cases will parse but are not valid (e.g., R0H/R2, R1H/R3) + +# (1) MUL.B #imm, dst5 +:MUL.B srcSimm8, dst5B is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x1f) ... & $(DST5W) & $(DST5B)); srcSimm8 { + dst5W = sext(srcSimm8) * sext(dst5B); +} + +# (1) MUL.B #imm, Ax +:MUL.B srcSimm8, dst5Ax is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x1f) & $(DST5AX)); srcSimm8 { + val:2 = sext(srcSimm8) * sext(dst5Ax:1); + dst5Ax = zext(val); +} + +# (1) MUL.W #imm, dst5 +:MUL.W srcSimm16, dst5W is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x1f) ... & $(DST5L) & $(DST5W)); srcSimm16 { + dst5L = sext(srcSimm16) * sext(dst5W); +} + +# (1) MUL.W #imm, Ax +:MUL.W srcSimm16, dst5Ax is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x1f) & $(DST5AX)); srcSimm16 { + val:4 = sext(srcSimm16) * sext(dst5Ax:2); + dst5Ax = val:3; +} + +# (2) MUL.B src5, dst5 +:MUL.B src5B, dst5B_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0xc) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ... & $(DST5W_AFTER_SRC5) ... { + dst5W_afterSrc5 = sext(src5B) * sext(dst5B_afterSrc5); +} + +# (2) MUL.B src5, Ax +:MUL.B src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0xc) ... & $(SRC5B) & $(DST5AX) ... { + val:2 = sext(src5B) * sext(dst5Ax:1); + dst5Ax = zext(val); +} + +# (2) MUL.W src5, dst5 +:MUL.W src5W, dst5W_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0xc) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ... & $(DST5L_AFTER_SRC5) ... { + dst5L_afterSrc5 = sext(src5W) * sext(dst5W_afterSrc5); +} + +# (2) MUL.W src5, Ax +:MUL.W src5W, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0xc) ... & $(SRC5W) & $(DST5AX) ... { + val:4 = sext(src5W) * sext(dst5Ax:2); + dst5Ax = val:3; +} + +##### MULEX ##### + +:MULEX dst5W is (b1_0407=0xc & b1_size_0=1; b2_0005=0x3e) ... & $(DST5W) { + R1R2R0 = sext(R2R0) * sext(dst5W); +} + +##### MULU ##### + +# TODO: Illegal MULU destination cases will parse but are not valid (e.g., R0H/R2, R1H/R3) + +# (1) MULU.B #imm, dst5 +:MULU.B srcImm8, dst5B is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x0f) ... & $(DST5B) & $(DST5W)); srcImm8 { + dst5W = zext(srcImm8) * zext(dst5B); +} + +# (1) MULU.B #imm, Ax +:MULU.B srcImm8, dst5Ax is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x0f) & $(DST5AX)); srcImm8 { + val:2 = zext(srcImm8) * zext(dst5Ax:1); + dst5Ax = zext(val); +} + +# (1) MULU.W #imm, dst5 +:MULU.W srcImm16, dst5W is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x0f) ... & $(DST5W) & $(DST5L)); srcImm16 { + dst5L = zext(srcImm16) * zext(dst5W); +} + +# (1) MULU.W #imm, Ax +:MULU.W srcImm16, dst5Ax is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x0f) & $(DST5AX)); srcImm16 { + val:4 = zext(srcImm16) * zext(dst5Ax:2); + dst5Ax = val:3; +} + +# (2) MULU.B src5, dst5 +:MULU.B src5B, dst5B_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0x4) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ... & $(DST5W_AFTER_SRC5) ... { + dst5W_afterSrc5 = zext(src5B) * zext(dst5B_afterSrc5); +} + +# (2) MULU.B src5, Ax +:MULU.B src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0x4) ... & $(SRC5B) & $(DST5AX) ... { + val:2 = zext(src5B) * zext(dst5Ax:1); + dst5Ax = zext(val); +} + +# (2) MULU.W src5, dst5 +:MULU.W src5W, dst5W_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0x4) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ... & $(DST5L_AFTER_SRC5) ... { + dst5L_afterSrc5 = zext(src5W) * zext(dst5W_afterSrc5); +} + +# (2) MULU.W src5, Ax +:MULU.W src5W, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0x4) ... & $(SRC5W) & $(DST5AX) ... { + val:4 = zext(src5W) * zext(dst5Ax:2); + dst5Ax = val:3; +} + +##### NEG ##### + +# NEG.B dst5 +:NEG.B dst5B is (b1_0407=0xa & b1_size_0=0; b2_0005=0x2f) ... & $(DST5B) { + tmp:1 = dst5B; + setSubtractFlags(0:1, tmp); + tmp = -tmp; + dst5B = tmp; + setResultFlags(tmp); +} + +# NEG.B Ax +:NEG.B dst5Ax is (b1_0407=0xa & b1_size_0=0; b2_0005=0x2f) & $(DST5AX) { + tmp:1 = dst5Ax:1; + setSubtractFlags(0:1, tmp); + tmp = -tmp; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# NEG.W dst5 +:NEG.W dst5W is (b1_0407=0xa & b1_size_0=1; b2_0005=0x2f) ... & $(DST5W) { + tmp:2 = dst5W; + setSubtractFlags(0:2, tmp); + tmp = -tmp; + dst5W = tmp; + setResultFlags(tmp); +} + +# NEG.W Ax +:NEG.W dst5Ax is (b1_0407=0xa & b1_size_0=1; b2_0005=0x2f) & $(DST5AX) { + tmp:2 = dst5Ax:2; + setSubtractFlags(0:2, tmp); + tmp = -tmp; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### NOP ##### + +:NOP is b1_0007=0xde { +} + +##### NOT ##### + +# NOT.B dst5 +:NOT.B dst5B is (b1_0407=0xa & b1_size_0=0; b2_0005=0x1e) ... & $(DST5B) { + tmp:1 = ~dst5B; + dst5B = tmp; + setResultFlags(tmp); +} + +# NOT.B Ax +:NOT.B dst5Ax is (b1_0407=0xa & b1_size_0=0; b2_0005=0x1e) & $(DST5AX) { + tmp:1 = ~dst5Ax:1; + tmp = tmp; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# NOT.W dst5 +:NOT.W dst5W is (b1_0407=0xa & b1_size_0=1; b2_0005=0x1e) ... & $(DST5W) { + tmp:2 = ~dst5W; + dst5W = tmp; + setResultFlags(tmp); +} + +# NOT.W Ax +:NOT.W dst5Ax is (b1_0407=0xa & b1_size_0=1; b2_0005=0x1e) & $(DST5AX) { + tmp:2 = ~dst5Ax:2; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### OR ##### + +# (1) OR.B:G #imm, dst +:OR^".B:G" srcImm8, dst5B is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x2f) ... & $(DST5B)); srcImm8 { + tmp:1 = dst5B & srcImm8; + dst5B = tmp; + setResultFlags(tmp); +} + +# (1) OR.B:G #imm, Ax +:OR^".B:G" srcImm8, dst5Ax is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x2f) & $(DST5AX)); srcImm8 { + tmp:1 = dst5Ax:1 & srcImm8; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) OR.W:G #imm, dst +:OR^".W:G" srcImm16, dst5W is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x2f) ... & $(DST5W)); srcImm16 { + tmp:2 = dst5W & srcImm16; + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) OR.W:G #imm, Ax +:OR^".W:G" srcImm16, dst5Ax is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x2f) & $(DST5AX)); srcImm16 { + tmp:2 = dst5Ax:2 & srcImm16; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) OR.B:S #imm, dst +:OR^".B:S" srcImm8, dst2B is ((b1_0607=1 & b1_0103=2 & b1_size_0=0) ... & dst2B); srcImm8 { + tmp:1 = dst2B & srcImm8; + dst2B = tmp; + setResultFlags(tmp); +} + +# (2) OR.W:S #imm, dst +:OR^".W:S" srcImm16, dst2W is ((b1_0607=1 & b1_0103=2 & b1_size_0=1) ... & dst2W); srcImm16 { + tmp:2 = dst2W & srcImm16; + dst2W = tmp; + setResultFlags(tmp); +} + +# (3) OR.B:G src5, dst5 +:OR^".B:G" src5B, dst5B_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0x5) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ... { + tmp:1 = dst5B_afterSrc5 & src5B; + dst5B_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (3) OR.B:G src5, Ax +:OR^".B:G" src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0x5) ... & $(SRC5B) & $(DST5AX) ... { + tmp:1 = dst5Ax:1 & src5B; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (3) OR.W:G src5, dst5 +:OR^".W:G" src5W, dst5W_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0x5) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ... { + tmp:2 = dst5W_afterSrc5 & src5W; + dst5W_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (3) OR.W:G src5, Ax +:OR^".W:G" src5W, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0x5) ... & $(SRC5W) & $(DST5AX) ... { + tmp:2 = dst5Ax:2 & src5W; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### POP ##### + +# POP.B dst5 +:POP.B dst5B is (b1_0407=0xb & b1_size_0=0; b2_0005=0x2f) ... & $(DST5B) { + pop1(dst5B); +} + +# POP.B Ax +:POP.B dst5Ax is (b1_0407=0xb & b1_size_0=0; b2_0005=0x2f) & $(DST5AX) { + val:1 = 0; + pop1(val); + dst5Ax = zext(val); +} + +# POP.W dst5 +:POP.W dst5W is (b1_0407=0xb & b1_size_0=1; b2_0005=0x2f) ... & $(DST5W) { + pop2(dst5W); +} + +# POP.W Ax +:POP.W dst5Ax is (b1_0407=0xb & b1_size_0=1; b2_0005=0x2f) & $(DST5AX) { + val:2 = 0; + pop2(val); + dst5Ax = zext(val); +} + +##### POPC ##### + +# (1) POPC reg16 +:POPC b2_creg16 is b1_0007=0xd3; b2_0307=0x15 & b2_creg16 { + pop2(b2_creg16); +} + +# (2) POPC reg24 +:POPC b2_creg24 is b1_0007=0xd3; b2_0307=0x05 & b2_creg24 { + pop3(b2_creg24); +} + +##### POPM ##### +popRegFB: FB is regBit7=1 & FB { pop3(FB); } +popRegFB: is regBit7=0 { } + +popRegSB: SB popRegFB is regBit6=1 & popRegFB & SB { pop3(SB); build popRegFB; } +popRegSB: popRegFB is popRegFB { build popRegFB; } + +popRegA1: A1 popRegSB is regBit5=1 & popRegSB & A1 { pop3(A1); build popRegSB; } +popRegA1: popRegSB is popRegSB { build popRegSB; } +popRegA0: A0 popRegA1 is regBit4=1 & popRegA1 & A0 { pop3(A0); build popRegA1; } +popRegA0: popRegA1 is popRegA1 { build popRegA1; } + +popRegR3: R3 popRegA0 is regBit3=1 & popRegA0 & R3 { pop2(R3); build popRegA0; } +popRegR3: popRegA0 is popRegA0 { build popRegA0; } +popRegR2: R2 popRegR3 is regBit2=1 & popRegR3 & R2 { pop2(R2); build popRegR3; } +popRegR2: popRegR3 is popRegR3 { build popRegR3; } +popRegR1: R1 popRegR2 is regBit1=1 & popRegR2 & R1 { pop2(R1); build popRegR2; } +popRegR1: popRegR2 is popRegR2 { build popRegR2; } +popRegR0: R0 popRegR1 is regBit0=1 & popRegR1 & R0 { pop2(R0); build popRegR1; } +popRegR0: popRegR1 is popRegR1 { build popRegR1; } + +popRegList: "( "^popRegR0^")" is popRegR0 { build popRegR0; } + +:POPM popRegList is b1_0007=0x8e; popRegList { + build popRegList; +} + +##### PUSH ##### + +# (1) PUSH.B #imm +:PUSH.B srcImm8 is b1_0007=0xae; srcImm8 { + push1(srcImm8); +#tmp:2 = zext(srcImm8); # This differs from what really happens - decompiler tries to resolve source of unknown byte on stack +#push2(tmp); +} + +# (1) PUSH.W #imm +:PUSH.B srcImm16 is b1_0007=0xaf; srcImm16 { + push2(srcImm16); +} + +# (2) PUSH.B src5 +:PUSH.B dst5B is (b1_0407=0xc & b1_size_0=0; b2_0005=0x0e) ... & $(DST5B) { + push1(dst5B); +#tmp:2 = zext(dst5B); # This differs from what really happens - decompiler tries to resolve source of unknown byte on stack +#push2(tmp); +} + +# (2) PUSH.W src5 +:PUSH.W dst5W is (b1_0407=0xc & b1_size_0=1; b2_0005=0x0e) ... & $(DST5W) { + push2(dst5W); +} + +# (3) PUSH.L #imm +:PUSH.L srcImm32 is b1_0007=0xb6; b2_0007=0x53; srcImm32 { + push4(srcImm32); +} + +# (4) PUSH.L src5 +:PUSH.L dst5L is (b1_0407=0xa & b1_size_0=0; b2_0005=0x01) ... & $(DST5L) { + push4(dst5L); +} + +##### PUSHA ##### + +:PUSHA dst5A is (b1_0407=0xb & b1_size_0=0; b2_0005=0x01) ... & $(DST5A) { + push3(dst5A); +#tmp:4 = zext(dst5A); # This differs from what really happens - decompiler tries to resolve source of unknown byte on stack +#push4(tmp); +} + +##### PUSHC ##### + +# (1) PUSHC reg16 +:PUSHC b2_creg16 is b1_0007=0xd1; b2_0307=0x15 & b2_creg16 { + push2(b2_creg16); +} + +# (2) PUSHC reg24 +:PUSHC b2_creg24 is b1_0007=0xd1; b2_0307=0x05 & b2_creg24 { + push3(b2_creg24); +#tmp:4 = zext(b2_creg24); # This differs from what really happens - decompiler tries to resolve source of unknown byte on stack +#push4(tmp); +} + +##### PUSHM ##### +pushRegR0: R0 is regBit7=1 & R0 { push2(R0); } +pushRegR0: is regBit7=0 { } +pushRegR1: pushRegR0 R1 is regBit6=1 & pushRegR0 & R1 { push2(R1); build pushRegR0; } +pushRegR1: pushRegR0 is pushRegR0 { build pushRegR0; } +pushRegR2: pushRegR1 R2 is regBit5=1 & pushRegR1 & R2 { push2(R2); build pushRegR1; } +pushRegR2: pushRegR1 is pushRegR1 { build pushRegR1; } +pushRegR3: pushRegR2 R3 is regBit4=1 & pushRegR2 & R3 { push2(R3); build pushRegR2; } +pushRegR3: pushRegR2 is pushRegR2 { build pushRegR2; } + +pushRegA0: pushRegR3 A0 is regBit3=1 & pushRegR3 & A0 { push3(A0); build pushRegR3; } +pushRegA0: pushRegR3 is pushRegR3 { build pushRegR3; } +pushRegA1: pushRegA0 A1 is regBit2=1 & pushRegA0 & A1 { push3(A1); build pushRegA0; } +pushRegA1: pushRegA0 is pushRegA0 { build pushRegA0; } + +pushRegSB: pushRegA1 SB is regBit1=1 & pushRegA1 & SB { push3(SB); build pushRegA1; } +pushRegSB: pushRegA1 is pushRegA1 { build pushRegA1; } + +pushRegFB: pushRegSB FB is regBit0=1 & pushRegSB & FB { push3(FB); build pushRegSB; } +pushRegFB: pushRegSB is pushRegSB { build pushRegSB; } + +pushRegList: "("^pushRegFB^" )" is pushRegFB { build pushRegFB; } + +:PUSHM pushRegList is b1_0007=0x8f; pushRegList { + build pushRegList; +} + +##### REIT ##### + +:REIT is b1_0007=0x9e { + pc:3 = 0; + pop3(pc); + pop2(FLG); + return [pc]; +} + +##### RMPA ##### + +:RMPA.B is b1_0007=0xb8; b2_0007=0x43 { + if (R3 == 0) goto inst_next; + a:1 = *:1 A0; + b:1 = *:1 A1; + A0 = A0 + 1; + A1 = A1 + 1; + prod:6 = sext(a) * sext(b); + o:1 = scarry(R1R2R0, prod); + $(OVERFLOW) = o | $(OVERFLOW); + R1R2R0 = R1R2R0 + prod; + R3 = R3 - 1; + goto inst_start; +} + +:RMPA.W is b1_0007=0xb8; b2_0007=0x53 { + if (R3 == 0) goto inst_next; + a:2 = *:2 A0; + b:2 = *:2 A1; + A0 = A0 + 2; + A1 = A1 + 2; + prod:6 = sext(a) * sext(b); + o:1 = scarry(R1R2R0, prod); + $(OVERFLOW) = o | $(OVERFLOW); + R1R2R0 = R1R2R0 + prod; + R3 = R3 - 1; + goto inst_start; +} + +##### ROLC ##### + +:ROLC.B dst5B is (b1_0407=0xb & b1_size_0=0; b2_0005=0x2e) ... & $(DST5B) { + c:1 = $(CARRY); + tmp:1 = dst5B; + $(CARRY) = tmp s< 0; + tmp = (tmp << 1) | c; + dst5B = tmp; + setResultFlags(tmp); +} + +:ROLC.B dst5Ax is (b1_0407=0xb & b1_size_0=0; b2_0005=0x2e) & $(DST5AX) { + c:1 = $(CARRY); + tmp:1 = dst5Ax:1; + $(CARRY) = tmp s< 0; + tmp = (tmp << 1) | c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +:ROLC.W dst5W is (b1_0407=0xb & b1_size_0=1; b2_0005=0x2e) ... & $(DST5W) { + c:2 = zext($(CARRY)); + tmp:2 = dst5W; + $(CARRY) = tmp s< 0; + tmp = (tmp << 1) | c; + dst5W = tmp; + setResultFlags(tmp); +} + +:ROLC.W dst5Ax is (b1_0407=0xb & b1_size_0=1; b2_0005=0x2e) & $(DST5AX) { + c:2 = zext($(CARRY)); + tmp:2 = dst5Ax:2; + $(CARRY) = tmp s< 0; + tmp = (tmp << 1) | c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### RORC ##### + +:RORC.B dst5B is (b1_0407=0xa & b1_size_0=0; b2_0005=0x2e) ... & $(DST5B) { + c:1 = $(CARRY); + tmp:1 = dst5B; + $(CARRY) = (tmp & 1) == 1; + tmp = (tmp >> 1) | (c << 7); + dst5B = tmp; + setResultFlags(tmp); +} + +:RORC.B dst5Ax is (b1_0407=0xa & b1_size_0=0; b2_0005=0x2e) & $(DST5AX) { + c:1 = $(CARRY); + tmp:1 = dst5Ax:1; + $(CARRY) = (tmp & 1) == 1; + tmp = (tmp >> 1) | (c << 7); + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +:RORC.W dst5W is (b1_0407=0xa & b1_size_0=1; b2_0005=0x2e) ... & $(DST5W) { + c:2 = zext($(CARRY)); + tmp:2 = dst5W; + $(CARRY) = (tmp & 1) == 1; + tmp = (tmp >> 1) | (c << 15); + dst5W = tmp; + setResultFlags(tmp); +} + +:RORC.W dst5Ax is (b1_0407=0xa & b1_size_0=1; b2_0005=0x2e) & $(DST5AX) { + c:2 = zext($(CARRY)); + tmp:2 = dst5Ax:2; + $(CARRY) = (tmp & 1) == 1; + tmp = (tmp >> 1) | (c << 15); + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### ROT ##### + +# (1) ROT.B #imm4, dst5 (right) +:ROT.B srcSimm4Shift, dst5B is (b1_0407=0xe & b1_size_0=0; b2_0405=2 & b2_shiftSign=1 & srcSimm4Shift) ... & $(DST5B) { + rightShift:1 = -srcSimm4Shift; + tmp:1 = dst5B; + $(CARRY) = (tmp >> (rightShift - 1)) & 1; + tmp = (tmp >> rightShift) | (tmp << (8 - rightShift)); + dst5B = tmp; + setResultFlags(tmp); +} + +# (1) ROT.B #imm4, Ax (right) +:ROT.B srcSimm4Shift, dst5Ax is (b1_0407=0xe & b1_size_0=0; b2_0405=2 & b2_shiftSign=1 & srcSimm4Shift) & $(DST5AX) { + rightShift:1 = -srcSimm4Shift; + tmp:1 = dst5Ax:1; + $(CARRY) = (tmp >> (rightShift - 1)) & 1; + tmp = (tmp >> rightShift) | (tmp << (8 - rightShift)); + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) ROT.W #imm4, dst5 (right) +:ROT.W srcSimm4Shift, dst5W is (b1_0407=0xe & b1_size_0=1; b2_0405=2 & b2_shiftSign=1 & srcSimm4Shift) ... & $(DST5W) { + rightShift:1 = -srcSimm4Shift; + tmp:2 = dst5W; + c:2 = (tmp >> (rightShift - 1)); + $(CARRY) = c:1 & 1; + tmp = (tmp >> rightShift) | (tmp << (16 - rightShift)); + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) ROT.W #imm4, Ax (right) +:ROT.W srcSimm4Shift, dst5Ax is (b1_0407=0xe & b1_size_0=1; b2_0405=2 & b2_shiftSign=1 & srcSimm4Shift) & $(DST5AX) { + rightShift:1 = -srcSimm4Shift; + tmp:2 = dst5Ax:2; + c:2 = (tmp >> (rightShift - 1)); + $(CARRY) = c:1 & 1; + tmp = (tmp >> rightShift) | (tmp << (16 - rightShift)); + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) ROT.B #imm4, dst5 (left) +:ROT.B srcSimm4Shift, dst5B is (b1_0407=0xe & b1_size_0=0; b2_0405=2 & b2_shiftSign=0 & srcSimm4Shift) ... & $(DST5B) { + leftShift:1 = srcSimm4Shift; + tmp:1 = dst5B; + $(CARRY) = (tmp >> (8 - leftShift)) & 1; + tmp = (tmp << leftShift) | (tmp >> (8 - leftShift)); + dst5B = tmp; + setResultFlags(tmp); +} + +# (1) ROT.B #imm4, Ax (left) +:ROT.B srcSimm4Shift, dst5Ax is (b1_0407=0xe & b1_size_0=0; b2_0405=2 & b2_shiftSign=0 & srcSimm4Shift) & $(DST5AX) { + leftShift:1 = srcSimm4Shift; + tmp:1 = dst5Ax:1; + $(CARRY) = (tmp >> (8 - leftShift)) & 1; + tmp = (tmp << leftShift) | (tmp >> (8 - leftShift)); + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) ROT.W #imm4, dst5 (left) +:ROT.W srcSimm4Shift, dst5W is (b1_0407=0xe & b1_size_0=1; b2_0405=2 & b2_shiftSign=0 & srcSimm4Shift) ... & $(DST5W) { + leftShift:1 = srcSimm4Shift; + tmp:2 = dst5W; + c:2 = (tmp >> (16 - leftShift)); + $(CARRY) = c:1 & 1; + tmp = (tmp << leftShift) | (tmp >> (16 - leftShift)); + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) ROT.W #imm4, Ax (left) +:ROT.W srcSimm4Shift, dst5Ax is (b1_0407=0xe & b1_size_0=1; b2_0405=2 & b2_shiftSign=0 & srcSimm4Shift) & $(DST5AX) { + leftShift:1 = srcSimm4Shift; + tmp:2 = dst5Ax:2; + c:2 = (tmp >> (16 - leftShift)); + $(CARRY) = c:1 & 1; + tmp = (tmp << leftShift) | (tmp >> (16 - leftShift)); + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) ROT.B R1H, dst5 +:ROT.B R1H, dst5B is (R1H & b1_0407=0xa & b1_size_0=0; b2_0005=0x3f) ... & $(DST5B) { + if (R1H == 0) goto inst_next; + shift:1 = R1H s% 8; + tmp:1 = dst5B; + if (shift s>= 0) goto ; + shift = -shift; + $(CARRY) = (tmp >> (shift - 1)) & 1; + tmp = (tmp >> shift) | (tmp << (8 - shift)); + goto ; + + $(CARRY) = (tmp >> (8 - shift)) & 1; + tmp = (tmp << shift) | (tmp >> (8 - shift)); + + dst5B = tmp; + setResultFlags(tmp); +} + +# (2) ROT.B R1H, Ax +:ROT.B R1H, dst5Ax is (R1H & b1_0407=0xa & b1_size_0=0; b2_0005=0x3f) & $(DST5AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H s% 8; + tmp:1 = dst5Ax:1; + if (shift s>= 0) goto ; + shift = -shift; + $(CARRY) = (tmp >> (shift - 1)) & 1; + tmp = (tmp >> shift) | (tmp << (8 - shift)); + goto ; + + $(CARRY) = (tmp >> (8 - shift)) & 1; + tmp = (tmp << shift) | (tmp >> (8 - shift)); + + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) ROT.W R1H, dst5 +:ROT.W R1H, dst5W is (R1H & b1_0407=0xa & b1_size_0=1; b2_0005=0x3f) ... & $(DST5W) { + if (R1H == 0) goto inst_next; + shift:1 = R1H s% 16; + tmp:2 = dst5W; + if (shift s>= 0) goto ; + shift = -shift; + c:2 = (tmp >> (shift - 1)); + tmp = (tmp >> shift) | (tmp << (16 - shift)); + goto ; + + c = (tmp >> (16 - shift)); + tmp = (tmp << shift) | (tmp >> (16 - shift)); + + $(CARRY) = c:1 & 1; + dst5W = tmp; + setResultFlags(tmp); +} + +# (2) ROT.W R1H, Ax +:ROT.W R1H, dst5Ax is (R1H & b1_0407=0xa & b1_size_0=1; b2_0005=0x3f) & $(DST5AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H s% 16; + tmp:2 = dst5Ax:2; + if (shift s>= 0) goto ; + shift = -shift; + c:2 = (tmp >> (shift - 1)); + tmp = (tmp >> shift) | (tmp << (16 - shift)); + goto ; + + c = (tmp >> (16 - shift)); + tmp = (tmp << shift) | (tmp >> (16 - shift)); + + $(CARRY) = c:1 & 1; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### RTS ##### + +:RTS is b1_0007=0xdf { + pc:3 = 0; + pop3(pc); + return [pc]; +} + +##### SBB ##### + +# (1) SBB.B #simm, dst +:SBB.B srcSimm8, dst5B is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x2e) ... & $(DST5B)); srcSimm8 { + tmp:1 = dst5B; + c:1 = $(CARRY); + setSubtract3Flags(tmp, srcSimm8, c); + tmp = tmp - srcSimm8 - c; + dst5B = tmp; + setResultFlags(tmp); +} + +# (1) SBB.B #simm, Ax +:SBB.B srcSimm8, dst5Ax is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x2e) & $(DST5AX)); srcSimm8 { + tmp:1 = dst5Ax:1; + c:1 = $(CARRY); + setSubtract3Flags(tmp, srcSimm8, c); + tmp = tmp - srcSimm8 - c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) SBB.W #simm, dst +:SBB.W srcSimm16, dst5W is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x2e) ... & $(DST5W)); srcSimm16 { + tmp:2 = dst5W; + c:2 = zext($(CARRY)); + setSubtract3Flags(tmp, srcSimm16, c); + tmp = tmp - srcSimm16 - c; + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) SBB.B #simm, Ax +:SBB.W srcSimm16, dst5Ax is b0_0007=0x1; ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x2e) & $(DST5AX)); srcSimm16 { + tmp:2 = dst5Ax:2; + c:2 = zext($(CARRY)); + setSubtract3Flags(tmp, srcSimm16, c); + tmp = tmp - srcSimm16 - c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) SBB.B src5, dst5 +:SBB.B src5B, dst5B_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x6) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ...) { + tmp:1 = dst5B_afterSrc5; + s:1 = src5B; + c:1 = $(CARRY); + setSubtract3Flags(tmp, s, c); + tmp = tmp - s - c; + dst5B_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (2) SBB.B src5, Ax +:SBB.B src5B, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x6) ... & $(SRC5B) & $(DST5AX) ...) { + tmp:1 = dst5Ax:1; + s:1 = src5B; + c:1 = $(CARRY); + setSubtract3Flags(tmp, s, c); + tmp = tmp - s - c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) SBB.W src5, dst5 +:SBB.W src5W, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x6) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ...) { + tmp:2 = dst5W_afterSrc5; + s:2 = src5W; + c:2 = zext($(CARRY)); + setSubtract3Flags(tmp, s, c); + tmp = tmp - s - c; + dst5W_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (2) SBB.W src5, Ax +:SBB.W src5W, dst5Ax is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x6) ... & $(SRC5W) & $(DST5AX) ...) { + tmp:2 = dst5Ax:2; + s:2 = src5W; + c:2 = zext($(CARRY)); + setSubtract3Flags(tmp, s, c); + tmp = tmp - s - c; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +##### SBJNZ - PSUEDO-OP! SAME AS ADJNZ ##### + +##### SCCnd ##### + +:SC^b2cnd dst5W is (b1_0407=0xd & b1_size_0=1; b2_0405=3 & b2cnd) ... & $(DST5W) { + dst5W = zext(b2cnd); +} + +:SC^b2cnd dst5Ax is (b1_0407=0xd & b1_size_0=1; b2_0405=3 & b2cnd) & $(DST5AX) { + dst5Ax = zext(b2cnd); +} + +##### SCMPU ##### + +:SCMPU.B is b1_0007=0xb8; b2_0007=0xc3 { + tmp0:1 = *:1 A0; + tmp2:1 = *:1 A1; + setSubtractFlags(tmp0, tmp2); + tmp:1 = tmp0 - tmp2; + setResultFlags(tmp); + A0 = A0 + 1; + A1 = A1 + 1; + if ((tmp0 != 0) && (tmp0 == tmp2)) goto inst_start; +} + +:SCMPU.W is b1_0007=0xb8; b2_0007=0xd3 { + # TODO: The symantic description looks suspicious - manual may be incorrect ?? + tmp0:1 = *:1 A0; + tmp2:1 = *:1 A1; + setSubtractFlags(tmp0, tmp2); + setResultFlags(tmp0 - tmp2); + A0 = A0 + 1; + A1 = A1 + 1; + tmp1:1 = *:1 A0; + tmp3:1 = *:1 A1; + A0 = A0 + 1; + A1 = A1 + 1; + if (tmp0 == 0 || tmp0 != tmp2) goto ; + setSubtractFlags(tmp1, tmp3); + setResultFlags(tmp1 - tmp3); + + if ((tmp0 != 0) && (tmp1 != 0) && (tmp0 == tmp2) && (tmp1 == tmp3)) goto inst_start; +} + +##### SHA ##### +macro SHAsetShiftRightFlags(val,shift,result) { + local c = (val >> (shift - 1)) & 1; + $(CARRY) = c:1; + local mask = ~(-(1 << shift)); + allOnes:1 = (mask & val) == mask; + allZeros:1 = (mask & val) == 0; + $(OVERFLOW) = (result s< 0 && allOnes) || (result s>= 0 && allZeros); + setResultFlags(result); +} + +macro SHAsetShiftLeftFlags(val,shift,result,sze) { + local c = (val >> (sze - shift)) & 1; + $(CARRY) = c:1; + local mask = -(1 << shift); + allOnes:1 = (mask & val) == mask; + allZeros:1 = (mask & val) == 0; + $(OVERFLOW) = (result s< 0 && allOnes) || (result s>= 0 && allZeros); + setResultFlags(result); +} + +# (1) SHA.B #imm4, dst5 (right) +:SHA.B srcSimm4Shift, dst5B is (b1_0407=0xf & b1_size_0=0; b2_0405=0 & b2_shiftSign=1 & srcSimm4Shift) ... & $(DST5B) { + val:1 = dst5B; + shift:1 = -srcSimm4Shift; + tmp:1 = val s>> shift; + dst5B = tmp; + SHAsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHA.B #imm4, Ax (right) +:SHA.B srcSimm4Shift, dst5Ax is (b1_0407=0xf & b1_size_0=0; b2_0405=0 & b2_shiftSign=1 & srcSimm4Shift) & $(DST5AX) { + val:1 = dst5Ax:1; + shift:1 = -srcSimm4Shift; + tmp:1 = val s>> shift; + dst5Ax = zext(tmp); + SHAsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHA.W #imm4, dst5 (right) +:SHA.W srcSimm4Shift, dst5W is (b1_0407=0xf & b1_size_0=1; b2_0405=0 & b2_shiftSign=1 & srcSimm4Shift) ... & $(DST5W) { + val:2 = dst5W; + shift:1 = -srcSimm4Shift; + tmp:2 = val s>> shift; + dst5W = tmp; + SHAsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHA.W #imm4, Ax (right) +:SHA.W srcSimm4Shift, dst5Ax is (b1_0407=0xf & b1_size_0=1; b2_0405=0 & b2_shiftSign=1 & srcSimm4Shift) & $(DST5AX) { + val:2 = dst5Ax:2; + shift:1 = -srcSimm4Shift; + tmp:2 = val s>> shift; + dst5Ax = zext(tmp); + SHAsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHA.B #imm4, dst5 (left) +:SHA.B srcSimm4Shift, dst5B is (b1_0407=0xf & b1_size_0=0; b2_0405=0 & b2_shiftSign=0 & srcSimm4Shift) ... & $(DST5B) { + val:1 = dst5B; + shift:1 = srcSimm4Shift; + tmp:1 = val << shift; + dst5B = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (1) SHA.B #imm4, Ax (left) +:SHA.B srcSimm4Shift, dst5Ax is (b1_0407=0xf & b1_size_0=0; b2_0405=0 & b2_shiftSign=0 & srcSimm4Shift) & $(DST5AX) { + val:1 = dst5Ax:1; + shift:1 = srcSimm4Shift; + tmp:1 = val << shift; + dst5Ax = zext(tmp); + SHAsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (1) SHA.W #imm4, dst5 (left) +:SHA.W srcSimm4Shift, dst5W is (b1_0407=0xf & b1_size_0=1; b2_0405=0 & b2_shiftSign=0 & srcSimm4Shift) ... & $(DST5W) { + val:2 = dst5W; + shift:1 = srcSimm4Shift; + tmp:2 = val << shift; + dst5W = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (1) SHA.W #imm4, Ax (left) +:SHA.W srcSimm4Shift, dst5Ax is (b1_0407=0xf & b1_size_0=1; b2_0405=0 & b2_shiftSign=0 & srcSimm4Shift) & $(DST5AX) { + val:2 = dst5Ax:2; + shift:1 = srcSimm4Shift; + tmp:2 = val << shift; + dst5Ax = zext(tmp); + SHAsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (2) SHA.L #imm, dst5 +:SHA.L srcSimm8, dst5L is ((b1_0407=0xa & b1_size_0=0; b2_0005=0x21) ... & $(DST5L)); srcSimm8 { + # Unable to pattern match on sign bit due to interior ellipses + shift:1 = srcSimm8; + val:4 = dst5L; + if (shift s> 0) goto ; + shift = -shift; + tmp:4 = val s>> shift; + dst5L = tmp; + SHAsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5L = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 32); +} + +# (2) SHA.L #imm, Ax +:SHA.L srcSimm8, dst5Ax is ((b1_0407=0xa & b1_size_0=0; b2_0005=0x21) & $(DST5AX)); srcSimm8 { + # Unable to pattern match on sign bit due to interior ellipses + shift:1 = srcSimm8; + val:4 = zext(dst5Ax); + if (shift s> 0) goto ; + shift = -shift; + tmp:4 = val s>> shift; + dst5Ax = tmp:3; + goto inst_next; + + tmp = val << shift; + dst5Ax = tmp:3; +# No flags set +} + +# (3) SHA.B R1H, dst5 +:SHA.B R1H, dst5B is (R1H & b1_0407=0xb & b1_size_0=0; b2_0005=0x3e) ... & $(DST5B) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:1 = dst5B; + if (shift s> 0) goto ; + shift = -shift; + tmp:1 = val s>> shift; + dst5B = tmp; + SHAsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5B = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (3) SHA.B R1H, Ax +:SHA.B R1H, dst5Ax is (R1H & b1_0407=0xb & b1_size_0=0; b2_0005=0x3e) & $(DST5AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:1 = dst5Ax:1; + if (shift s> 0) goto ; + shift = -shift; + tmp:1 = val s>> shift; + dst5Ax = zext(tmp); + SHAsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5Ax = zext(tmp); + SHAsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (3) SHA.W R1H, dst5 +:SHA.W R1H, dst5W is (R1H & b1_0407=0xb & b1_size_0=1; b2_0005=0x3e) ... & $(DST5W) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:2 = dst5W; + if (shift s> 0) goto ; + shift = -shift; + tmp:2 = val s>> shift; + dst5W = tmp; + SHAsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5W = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (3) SHA.W R1H, Ax +:SHA.W R1H, dst5Ax is (R1H & b1_0407=0xb & b1_size_0=1; b2_0005=0x3e) & $(DST5AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:2 = dst5Ax:2; + if (shift s> 0) goto ; + shift = -shift; + tmp:2 = val s>> shift; + dst5Ax = zext(tmp); + SHAsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5Ax = zext(tmp); + SHAsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (4) SHA.L R1H, dst5 +:SHA.L R1H, dst5L is (R1H & b1_0407=0xc & b1_size_0=0; b2_0005=0x11) ... & $(DST5L) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:4 = dst5L; + if (shift s> 0) goto ; + shift = -shift; + tmp:4 = val s>> shift; + dst5L = tmp; + SHAsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5L = tmp; + SHAsetShiftLeftFlags(val, shift, tmp, 32); +} + +# (4) SHA.L R1H, Ax +:SHA.L R1H, dst5Ax is (R1H & b1_0407=0xc & b1_size_0=0; b2_0005=0x11) & $(DST5AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:4 = zext(dst5Ax); + if (shift s> 0) goto ; + shift = -shift; + tmp:4 = val s>> shift; + dst5Ax = tmp:3; + goto inst_next; + + tmp = val << shift; + dst5Ax = tmp:3; +# No flags set +} + +##### SHL ##### +macro SHLsetShiftRightFlags(val,shift,result) { + local c = (val >> (shift - 1)) & 1; + $(CARRY) = c:1; + setResultFlags(result); +} + +macro SHLsetShiftLeftFlags(val,shift,result,sze) { + local c = (val >> (sze - shift)) & 1; + $(CARRY) = c:1; + setResultFlags(result); +} + +# (1) SHL.B #imm4, dst5 (right) +:SHL.B srcSimm4Shift, dst5B is (b1_0407=0xe & b1_size_0=0; b2_0405=0 & b2_shiftSign=1 & srcSimm4Shift) ... & $(DST5B) { + val:1 = dst5B; + shift:1 = -srcSimm4Shift; + tmp:1 = val >> shift; + dst5B = tmp; + SHLsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHL.B #imm4, Ax (right) +:SHL.B srcSimm4Shift, dst5Ax is (b1_0407=0xe & b1_size_0=0; b2_0405=0 & b2_shiftSign=1 & srcSimm4Shift) & $(DST5AX) { + val:1 = dst5Ax:1; + shift:1 = -srcSimm4Shift; + tmp:1 = val >> shift; + dst5Ax = zext(tmp); + SHLsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHL.W #imm4, dst5 (right) +:SHL.W srcSimm4Shift, dst5W is (b1_0407=0xe & b1_size_0=1; b2_0405=0 & b2_shiftSign=1 & srcSimm4Shift) ... & $(DST5W) { + val:2 = dst5W; + shift:1 = -srcSimm4Shift; + tmp:2 = val >> shift; + dst5W = tmp; + SHLsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHL.W #imm4, Ax (right) +:SHL.W srcSimm4Shift, dst5Ax is (b1_0407=0xe & b1_size_0=1; b2_0405=0 & b2_shiftSign=1 & srcSimm4Shift) & $(DST5AX) { + val:2 = dst5Ax:2; + shift:1 = -srcSimm4Shift; + tmp:2 = val >> shift; + dst5Ax = zext(tmp); + SHLsetShiftRightFlags(val, shift, tmp); +} + +# (1) SHL.B #imm4, dst5 (left) +:SHL.B srcSimm4Shift, dst5B is (b1_0407=0xe & b1_size_0=0; b2_0405=0 & b2_shiftSign=0 & srcSimm4Shift) ... & $(DST5B) { + val:1 = dst5B; + shift:1 = srcSimm4Shift; + tmp:1 = val << shift; + dst5B = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (1) SHL.B #imm4, Ax (left) +:SHL.B srcSimm4Shift, dst5Ax is (b1_0407=0xe & b1_size_0=0; b2_0405=0 & b2_shiftSign=0 & srcSimm4Shift) & $(DST5AX) { + val:1 = dst5Ax:1; + shift:1 = srcSimm4Shift; + tmp:1 = val << shift; + dst5Ax = zext(tmp); + SHLsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (1) SHL.W #imm4, dst5 (left) +:SHL.W srcSimm4Shift, dst5W is (b1_0407=0xe & b1_size_0=1; b2_0405=0 & b2_shiftSign=0 & srcSimm4Shift) ... & $(DST5W) { + val:2 = dst5W; + shift:1 = srcSimm4Shift; + tmp:2 = val << shift; + dst5W = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (1) SHL.W #imm4, Ax (left) +:SHL.W srcSimm4Shift, dst5Ax is (b1_0407=0xe & b1_size_0=1; b2_0405=0 & b2_shiftSign=0 & srcSimm4Shift) & $(DST5AX) { + val:2 = dst5Ax:2; + shift:1 = srcSimm4Shift; + tmp:2 = val << shift; + dst5Ax = zext(tmp); + SHLsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (2) SHL.L #imm, dst5 +:SHL.L srcSimm8, dst5L is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x21) ... & $(DST5L)); srcSimm8 { + # Unable to pattern match on sign bit due to interior ellipses + shift:1 = srcSimm8; + val:4 = dst5L; + if (shift s> 0) goto ; + shift = -shift; + tmp:4 = val >> shift; + dst5L = tmp; + SHLsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5L = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 32); +} + +# (2) SHL.L #imm, Ax +:SHL.L srcSimm8, dst5Ax is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x21) & $(DST5AX)); srcSimm8 { + # Unable to pattern match on sign bit due to interior ellipses + shift:1 = srcSimm8; + val:4 = zext(dst5Ax); + if (shift s> 0) goto ; + shift = -shift; + tmp:4 = val >> shift; + dst5Ax = tmp:3; + goto inst_next; + + tmp = val << shift; + dst5Ax = tmp:3; +# No flags set +} + +# (3) SHL.B R1H, dst5 +:SHL.B R1H, dst5B is (R1H & b1_0407=0xa & b1_size_0=0; b2_0005=0x3e) ... & $(DST5B) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:1 = dst5B; + if (shift s> 0) goto ; + shift = -shift; + tmp:1 = val >> shift; + dst5B = tmp; + SHLsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5B = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (3) SHL.B R1H, Ax +:SHL.B R1H, dst5Ax is (R1H & b1_0407=0xa & b1_size_0=0; b2_0005=0x3e) & $(DST5AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:1 = dst5Ax:1; + if (shift s> 0) goto ; + shift = -shift; + tmp:1 = val >> shift; + dst5Ax = zext(tmp); + SHLsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5Ax = zext(tmp); + SHLsetShiftLeftFlags(val, shift, tmp, 8); +} + +# (3) SHL.W R1H, dst5 +:SHL.W R1H, dst5W is (R1H & b1_0407=0xa & b1_size_0=1; b2_0005=0x3e) ... & $(DST5W) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:2 = dst5W; + if (shift s> 0) goto ; + shift = -shift; + tmp:2 = val >> shift; + dst5W = tmp; + SHLsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5W = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (3) SHL.W R1H, Ax +:SHL.W R1H, dst5Ax is (R1H & b1_0407=0xa & b1_size_0=1; b2_0005=0x3e) & $(DST5AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:2 = dst5Ax:2; + if (shift s> 0) goto ; + shift = -shift; + tmp:2 = val >> shift; + dst5Ax = zext(tmp); + SHLsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5Ax = zext(tmp); + SHLsetShiftLeftFlags(val, shift, tmp, 16); +} + +# (4) SHL.L R1H, dst5 +:SHL.L R1H, dst5L is (R1H & b1_0407=0xc & b1_size_0=0; b2_0005=0x01) ... & $(DST5L) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:4 = dst5L; + if (shift s> 0) goto ; + shift = -shift; + tmp:4 = val >> shift; + dst5L = tmp; + SHLsetShiftRightFlags(val, shift, tmp); + goto inst_next; + + tmp = val << shift; + dst5L = tmp; + SHLsetShiftLeftFlags(val, shift, tmp, 32); +} + +# (4) SHL.L R1H, Ax +:SHL.L R1H, dst5Ax is (R1H & b1_0407=0xc & b1_size_0=0; b2_0005=0x01) & $(DST5AX) { + if (R1H == 0) goto inst_next; + shift:1 = R1H; + val:4 = zext(dst5Ax); + if (shift s> 0) goto ; + shift = -shift; + tmp:4 = val >> shift; + dst5Ax = tmp:3; + goto inst_next; + + tmp = val << shift; + dst5Ax = tmp:3; +# No flags set +} + +##### SIN ##### + +:SIN.B is b1_0007=0xb2; b2_0007=0x83 { + if (R3 == 0) goto inst_next; + *:1 A1 = *:1 A0; + A1 = A1 + 1; + R3 = R3 - 1; + goto inst_start; +} + +:SIN.W is b1_0007=0xb2; b2_0007=0x93 { + if (R3 == 0) goto inst_next; + *:2 A1 = *:2 A0; + A1 = A1 + 2; + R3 = R3 - 1; + goto inst_start; +} + +##### SMOVB ##### + +:SMOVB.B is b1_0007=0xb6; b2_0007=0x83 { + if (R3 == 0) goto inst_next; + *:1 A1 = *:1 A0; + A1 = A1 - 1; + A0 = A0 - 1; + R3 = R3 - 1; + goto inst_start; +} + +:SMOVB.W is b1_0007=0xb6; b2_0007=0x93 { + if (R3 == 0) goto inst_next; + *:2 A1 = *:2 A0; + A1 = A1 - 2; + A0 = A0 - 2; + R3 = R3 - 1; + goto inst_start; +} + +##### SMOVF ##### + +:SMOVF.B is b1_0007=0xb0; b2_0007=0x83 { + if (R3 == 0) goto inst_next; + *:1 A1 = *:1 A0; + A1 = A1 + 1; + A0 = A0 + 1; + R3 = R3 - 1; + goto inst_start; +} + +:SMOVF.W is b1_0007=0xb0; b2_0007=0x93 { + if (R3 == 0) goto inst_next; + *:2 A1 = *:2 A0; + A1 = A1 + 2; + A0 = A0 + 2; + R3 = R3 - 1; + goto inst_start; +} + +##### SMOVU ##### + +:SMOVU.B is b1_0007=0xb8; b2_0007=0x83 { + local tmp:1 = *:1 A0; + *:1 A1 = tmp; + A0 = A0 + 1; + A1 = A1 + 1; + if (tmp != 0) goto inst_start; +} + +:SMOVU.W is b1_0007=0xb8; b2_0007=0x93 { + local tmp:2 = *:2 A0; + *:2 A1 = tmp; + A0 = A0 + 2; + A1 = A1 + 2; + local tmp0:2 = tmp & 0xff; + local tmp1:2 = tmp & 0xff00; + if ((tmp0 != 0) && (tmp1 != 0)) goto inst_start; +} + +##### SOUT ##### + +:SOUT.B is b1_0007=0xb4; b2_0007=0x83 { + if (R3 == 0) goto inst_next; + *:1 A1 = *:1 A0; + A0 = A0 + 1; + R3 = R3 - 1; + goto inst_start; +} + +:SOUT.W is b1_0007=0xb4; b2_0007=0x93 { + if (R3 == 0) goto inst_next; + *:2 A1 = *:2 A0; + A0 = A0 + 2; + R3 = R3 - 1; + goto inst_start; +} + +##### SSTR ##### + +:SSTR.B is b1_0007=0xb8; b2_0007=0x03 { + if (R3 == 0) goto inst_next; + *:1 A1 = R0L; + A1 = A1 + 1; + R3 = R3 - 1; + goto inst_start; +} + +:SSTR.W is b1_0007=0xb8; b2_0007=0x13 { + if (R3 == 0) goto inst_next; + *:2 A1 = R0; + A1 = A1 + 2; + R3 = R3 - 1; + goto inst_start; +} + +##### STC ##### + +# (1) STC dreg24, dst5 +:STC b2_dreg24, dst5L is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=1; b2_0305=0x2 & b2_dreg24) ... & $(DST5L)) { + dst5L = zext(b2_dreg24); +} + +# (1) STC dreg24, Ax +:STC b2_dreg24, dst5Ax is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=1; b2_0305=0x2 & b2_dreg24) & $(DST5AX)) { + dst5Ax = b2_dreg24; +} + +# (2) STC reg16, dst5 +:STC b2_creg16, dst5W is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=1; b2_0305=0x3 & b2_creg16) ... & $(DST5W)) { + dst5W = b2_creg16; +} + +# (2) STC reg16, Ax +:STC b2_creg16, dst5Ax is b0_0007=0x1; ((b1_0407=0xd & b1_size_0=1; b2_0305=0x3 & b2_creg16) & $(DST5AX)) { + dst5Ax = zext(b2_creg16); +} + +# (3) STC reg24, dst5L +:STC b2_creg24, dst5L is (b1_0407=0xd & b1_size_0=1; b2_0305=0x2 & b2_creg24) ... & $(DST5L) { + dst5L = zext(b2_creg24); +} + +# (3) STC reg24, Ax +:STC b2_creg24, dst5Ax is (b1_0407=0xd & b1_size_0=1; b2_0305=0x2 & b2_creg24) & $(DST5AX) { + dst5Ax = b2_creg24; +} + +##### STCTX ##### + +:STCTX abs16offset, abs24offset is b1_0007=0xb6; b2_0007=0xd3; abs16offset; imm24_dat & abs24offset { + + taskNum:1 = abs16offset; # load task number stored at abs16 + ptr:3 = imm24_dat + (zext(taskNum) * 2); # compute table entry address relative to abs24 + regInfo:1 = *:1 ptr; + ptr = ptr + 1; + spCorrect:1 = *:1 ptr; + + ptr = SP; + + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 4; + *:4 ptr = FB; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 4; + *:4 ptr = SB; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 4; + *:4 ptr = A1; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 4; + *:4 ptr = A0; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = R3; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = R2; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = R1; + + regInfo = regInfo << 1; + if ((regInfo & 0x80) == 0) goto ; + ptr = ptr - 2; + *:2 ptr = R0; + + SP = SP - zext(spCorrect); +} + +##### STNZ ##### + +# (1) STNZ.B #imm, dst5 +:STNZ.B srcImm8, dst5B is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x1f) ... & $(DST5B)); srcImm8 { + if ($(ZERO) != 0) goto inst_next; + dst5B = srcImm8; +} + +# (1) STNZ.B #imm, Ax +:STNZ.B srcImm8, dst5Ax is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x1f) & $(DST5AX)); srcImm8 { + if ($(ZERO) != 0) goto inst_next; + dst5Ax = zext(srcImm8); +} + +# (1) STNZ.W #imm, dst5 +:STNZ.W srcImm16, dst5W is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x1f) ... & $(DST5W)); srcImm16 { + if ($(ZERO) != 0) goto inst_next; + dst5W = srcImm16; +} + +# (1) STNZ.W #imm, Ax +:STNZ.W srcImm16, dst5Ax is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x1f) & $(DST5AX)); srcImm16 { + if ($(ZERO) != 0) goto inst_next; + dst5Ax = zext(srcImm16); +} + +##### STZ ##### + +# (1) STZ.B #imm, dst5 +:STZ.B srcImm8, dst5B is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x0f) ... & $(DST5B)); srcImm8 { + if ($(ZERO) == 0) goto inst_next; + dst5B = srcImm8; +} + +# (1) STZ.B #imm, Ax +:STZ.B srcImm8, dst5Ax is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x0f) & $(DST5AX)); srcImm8 { + if ($(ZERO) == 0) goto inst_next; + dst5Ax = zext(srcImm8); +} + +# (1) STZ.W #imm, dst5 +:STZ.W srcImm16, dst5W is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x0f) ... & $(DST5W)); srcImm16 { + if ($(ZERO) == 0) goto inst_next; + dst5W = srcImm16; +} + +# (1) STZ.W #imm, Ax +:STZ.W srcImm16, dst5Ax is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x0f) & $(DST5AX)); srcImm16 { + if ($(ZERO) == 0) goto inst_next; + dst5Ax = zext(srcImm16); +} + +##### STZX ##### + +# STZX.B #imm, #imm, dst5 +:STZX.B srcImm8, srcImm8a, dst5B is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x3f) ... & $(DST5B)); srcImm8; srcImm8a { + z:1 = $(ZERO); + dst5B = (z * srcImm8) + (!z * srcImm8a); +} + +# STZX.B #imm, #imm, Ax +:STZX.B srcImm8, srcImm8a, dst5Ax is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x3f) & $(DST5AX)); srcImm8; srcImm8a { + z:1 = $(ZERO); + dst5Ax = zext((z * srcImm8) + (!z * srcImm8a)); +} + +# STZX.W #imm, #imm, dst5 +:STZX.W srcImm16, srcImm16a, dst5W is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x3f) ... & $(DST5W)); srcImm16; srcImm16a { + z:1 = $(ZERO); + dst5W = (zext(z) * srcImm16) + (zext(!z) * srcImm16a); +} + +# STZX.W #imm, #imm, Ax +:STZX.W srcImm16, srcImm16a, dst5Ax is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x3f) & $(DST5AX)); srcImm16; srcImm16a { + z:1 = $(ZERO); + dst5Ax = zext((zext(z) * srcImm16) + (zext(!z) * srcImm16a)); +} + +##### SUB ##### + +# (1) SUB.B:G #simm, dst +:SUB^".B:G" srcSimm8, dst5B is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x3e) ... & $(DST5B)); srcSimm8 { + tmp:1 = dst5B; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + dst5B = tmp; + setResultFlags(tmp); +} + +# (1) SUB.B:G #simm, Ax +:SUB^".B:G" srcSimm8, dst5Ax is ((b1_0407=0x8 & b1_size_0=0; b2_0005=0x3e) & $(DST5AX)); srcSimm8 { + tmp:1 = dst5Ax:1; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) SUB.W:G #simm, dst +:SUB^".W:G" srcSimm16, dst5W is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x3e) ... & $(DST5W)); srcSimm16 { + tmp:2 = dst5W; + setSubtractFlags(tmp, srcSimm16); + tmp = tmp - srcSimm16; + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) SUB.W:G #simm, Ax +:SUB^".W:G" srcSimm16, dst5Ax is ((b1_0407=0x8 & b1_size_0=1; b2_0005=0x3e) & $(DST5AX)); srcSimm16 { + tmp:2 = dst5Ax:2; + setSubtractFlags(tmp, srcSimm16); + tmp = tmp - srcSimm16; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) SUB.L:G #simm, dst +:SUB^".L:G" srcSimm32, dst5L is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x31) ... & $(DST5L)); srcSimm32 { + tmp:4 = dst5L; + setSubtractFlags(tmp, srcSimm32); + tmp = tmp - srcSimm32; + dst5L = tmp; + setResultFlags(tmp); +} + +# (2) SUB.L:G #simm, Ax +:SUB^".L:G" srcSimm32, dst5Ax is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x31) & $(DST5AX)); srcSimm32 { + tmp:4 = zext(dst5Ax); + setSubtractFlags(tmp, srcSimm32); + tmp = tmp - srcSimm32; + dst5Ax = tmp:3; + setResultFlags(tmp); +} + +# (3) SUB.B:S #simm, dst +:SUB^".B:S" srcSimm8, dst2B is ((b1_0607=0 & b1_0103=7 & b1_size_0=0) ... & dst2B); srcSimm8 { + tmp:1 = dst2B; + setSubtractFlags(tmp, srcSimm8); + tmp = tmp - srcSimm8; + dst2B = tmp; + setResultFlags(tmp); +} + +# (3) SUB.W:S #simm, dst +:SUB^".W:S" srcSimm16, dst2W is ((b1_0607=0 & b1_0103=7 & b1_size_0=1) ... & dst2W); srcSimm16 { + tmp:2 = dst2W; + setSubtractFlags(tmp, srcSimm16); + tmp = tmp - srcSimm16; + dst2W = tmp; + setResultFlags(tmp); +} + +# (4) SUB.B:G src, dst +:SUB^".B:G" src5B, dst5B_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0xa) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ... { + tmp:1 = dst5B_afterSrc5; + src:1 = src5B; + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst5B_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (4) SUB.B:G src, Ax - Ax destination case +:SUB^".B:G" src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0xa) ... & $(SRC5B) & $(DST5AX) ... { + tmp:1 = dst5Ax:1; + src:1 = src5B; + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (4) SUB.W:G src, dst +:SUB^".W:G" src5W, dst5W_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0xa) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ... { + tmp:2 = dst5W_afterSrc5; + src:2 = src5W; + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst5W_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (4) SUB.W:G src, Ax - Ax destination case +:SUB^".W:G" src5W, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0xa) ... & $(SRC5W) & $(DST5AX) ... { + tmp:2 = dst5Ax:2; + src:2 = src5W; + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (5) SUB.L:G src, dst +:SUB^".L:G" src5L, dst5L_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0x0) ... & $(SRC5L) ... & $(DST5L_AFTER_SRC5) ... { + tmp:4 = dst5L_afterSrc5; + src:4 = src5L; + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst5L_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (5) SUB.L:G src, Ax - Ax destination case +:SUB^".L:G" src5L, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0x0) ... & $(SRC5L) & $(DST5AX) ... { + tmp:4 = zext(dst5Ax); + src:4 = src5L; + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst5Ax = tmp:3; + setResultFlags(tmp); +} + +##### SUBX ##### + +# (1) SUBX #simm, dst5 +:SUBX srcSimm8, dst5L is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x11) ... & $(DST5L)); srcSimm8 { + tmp:4 = dst5L; + src:4 = sext(srcSimm8); + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst5L = tmp; + setResultFlags(tmp); +} + +# (1) SUBX #simm, Ax +:SUBX srcSimm8, dst5Ax is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x11) & $(DST5AX)); srcSimm8 { + tmp:4 = zext(dst5Ax); + src:4 = sext(srcSimm8); + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst5Ax = tmp:3; + setResultFlags(tmp); +} + +# (2) SUBX src5, dst5 +:SUBX src5B, dst5L_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0x0) ... & $(SRC5B) ... & $(DST5L_AFTER_SRC5) ... { + tmp:4 = dst5L_afterSrc5; + src:4 = sext(src5B); + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst5L_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (2) SUBX src5, Ax +:SUBX src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0x0) ... & $(SRC5B) & $(DST5AX) ... { + tmp:4 = zext(dst5Ax); + src:4 = sext(src5B); + setSubtractFlags(tmp, src); + tmp = tmp - src; + dst5Ax = tmp:3; + setResultFlags(tmp); +} + +##### TST ##### + +# (1) TST.B:G #imm, dst +:TST^".B:G" srcImm8, dst5B is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x3e) ... & $(DST5B)); srcImm8 { + tmp:1 = dst5B & srcImm8; + setResultFlags(tmp); +} + +# (1) TST.W:G #imm, dst +:TST^".W:G" srcImm16, dst5W is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x3e) ... & $(DST5W)); srcImm16 { + tmp:2 = dst5W & srcImm16; + setResultFlags(tmp); +} + +# (2) TST.B:S #imm, dst +:TST^".B:S" srcImm8, dst2B is ((b1_0607=0 & b1_0103=6 & b1_size_0=0) ... & dst2B); srcImm8 { + tmp:1 = dst2B & srcImm8; + setResultFlags(tmp); +} + +# (2) TST.W:S #imm, dst +:TST^".W:S" srcImm16, dst2W is ((b1_0607=0 & b1_0103=6 & b1_size_0=1) ... & dst2W); srcImm16 { + tmp:2 = dst2W & srcImm16; + setResultFlags(tmp); +} + +# (3) TST.B:G src5, dst5 +:TST^".B:G" src5B, dst5B_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=0; b2_0003=0x9) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ...) { + tmp:1 = dst5B_afterSrc5 & src5B; + setResultFlags(tmp); +} + +# (3) TST.W:G src5, dst5 +:TST^".W:G" src5W, dst5W_afterSrc5 is b0_0007=0x1; ((b1_0707=1 & b1_size_0=1; b2_0003=0x9) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ...) { + tmp:2 = dst5W_afterSrc5 & src5W; + setResultFlags(tmp); +} + +##### UND ##### +# Don't implement this "Undefined" instruction +# :UND is b1_0007=0xff + +##### WAIT ##### + +:WAIT is b1_0007=0xb2; b2_0007=0x03 { + Wait(); +} + +##### XCHG ##### + +# XCHG.B reg8, dst5 +:XCHG.B b2_reg8, dst5B is (b1_0407=0xd & b1_size_0=0; b2_0305=1 & b2_0101=0 & b2_reg8) ... & $(DST5B) { + tmp:1 = dst5B; + dst5B = b2_reg8; + b2_reg8 = tmp; +} + +# XCHG.B Ax, dst5 +:XCHG.B b2_regAx, dst5B is (b1_0407=0xd & b1_size_0=0; b2_0305=1 & b2_0102=1 & b2_regAx) ... & $(DST5B) { + tmp:1 = dst5B; + dst5B = b2_regAx:1; + b2_regAx = zext(tmp); +} + +# XCHG.B reg8, Ax +:XCHG.B b2_reg8, dst5Ax is (b1_0407=0xd & b1_size_0=0; b2_0305=1 & b2_0101=0 & b2_reg8) & $(DST5AX) { + tmp:1 = dst5Ax:1; + dst5Ax = zext(b2_reg8); + b2_reg8 = tmp; +} + +# XCHG.B Ax, Ax +:XCHG.B b2_regAx, dst5Ax is (b1_0407=0xd & b1_size_0=0; b2_0305=1 & b2_0102=1 & b2_regAx) & $(DST5AX) { + tmp:1 = dst5Ax:1; + dst5Ax = zext(b2_regAx:1); + b2_regAx = zext(tmp); +} + +# XCHG.W reg16, dst5 +:XCHG.W b2_reg16, dst5W is (b1_0407=0xd & b1_size_0=1; b2_0305=1 & b2_0101=0 & b2_reg16) ... & $(DST5W) { + tmp:2 = dst5W; + dst5W = b2_reg16; + b2_reg16 = tmp; +} + +# XCHG.W Ax, dst5 +:XCHG.W b2_regAx, dst5W is (b1_0407=0xd & b1_size_0=1; b2_0305=1 & b2_0102=1 & b2_regAx) ... & $(DST5W) { + tmp:2 = dst5W; + dst5W = b2_regAx:2; + b2_regAx = zext(tmp); +} + +# XCHG.W reg16, Ax +:XCHG.W b2_reg16, dst5Ax is (b1_0407=0xd & b1_size_0=1; b2_0305=1 & b2_0101=0 & b2_reg16) & $(DST5AX) { + tmp:2 = dst5Ax:2; + dst5Ax = zext(b2_reg16); + b2_reg16 = tmp; +} + +# XCHG.W Ax, Ax +:XCHG.W b2_regAx, dst5Ax is (b1_0407=0xd & b1_size_0=1; b2_0305=1 & b2_0102=1 & b2_regAx) & $(DST5AX) { + tmp:3 = dst5Ax; + dst5Ax = zext(b2_regAx:2); # dest Ax recieves low 16-bits of src Ax zero extended + b2_regAx = tmp; # src Ax recieves all 24-bits of dest Ax +} + +##### XOR ##### + +# (1) XOR.B #imm, dst +:XOR^".B:G" srcImm8, dst5B is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x0e) ... & $(DST5B)); srcImm8 { + tmp:1 = dst5B ^ srcImm8; + dst5B = tmp; + setResultFlags(tmp); +} + +# (1) XOR.B #imm, Ax +:XOR^".B:G" srcImm8, dst5Ax is ((b1_0407=0x9 & b1_size_0=0; b2_0005=0x0e) & $(DST5AX)); srcImm8 { + tmp:1 = dst5Ax:1 ^ srcImm8; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (1) XOR.W #imm, dst +:XOR^".W:G" srcImm16, dst5W is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x0e) ... & $(DST5W)); srcImm16 { + tmp:2 = dst5W ^ srcImm16; + dst5W = tmp; + setResultFlags(tmp); +} + +# (1) XOR.W #imm, Ax +:XOR^".W:G" srcImm16, dst5Ax is ((b1_0407=0x9 & b1_size_0=1; b2_0005=0x0e) & $(DST5AX)); srcImm16 { + tmp:2 = dst5Ax:2 ^ srcImm16; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) XOR.B src5, dst5 +:XOR^".B:G" src5B, dst5B_afterSrc5 is (b1_0707=1 & b1_size_0=0; b2_0003=0x9) ... & $(SRC5B) ... & $(DST5B_AFTER_SRC5) ... { + tmp:1 = dst5B_afterSrc5 ^ src5B; + dst5B_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (2) XOR.B src5, Ax +:XOR^".B:G" src5B, dst5Ax is (b1_0707=1 & b1_size_0=0; b2_0003=0x9) ... & $(SRC5B) & $(DST5AX) ... { + tmp:1 = dst5Ax:1 ^ src5B; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +# (2) XOR.W src5, dst5 +:XOR^".W:G" src5W, dst5W_afterSrc5 is (b1_0707=1 & b1_size_0=1; b2_0003=0x9) ... & $(SRC5W) ... & $(DST5W_AFTER_SRC5) ... { + tmp:2 = dst5W_afterSrc5 ^ src5W; + dst5W_afterSrc5 = tmp; + setResultFlags(tmp); +} + +# (2) XOR.W src5, Ax +:XOR^".W:G" src5W, dst5Ax is (b1_0707=1 & b1_size_0=1; b2_0003=0x9) ... & $(SRC5W) & $(DST5AX) ... { + tmp:2 = dst5Ax:2 ^ src5W; + dst5Ax = zext(tmp); + setResultFlags(tmp); +} + +} # end phase=1 + diff --git a/Ghidra/Processors/M16C/data/manuals/M16C_60.idx b/Ghidra/Processors/M16C/data/manuals/M16C_60.idx new file mode 100644 index 0000000000..a57d66928f --- /dev/null +++ b/Ghidra/Processors/M16C/data/manuals/M16C_60.idx @@ -0,0 +1,95 @@ +@m16csm.pdf +ABS, 55 +ADC, 56 +ADCF, 57 +ADD, 58 +ADJNZ, 60 +AND, 61 +BAND, 63 +BCLR, 64 +BM, 65 +BNAND, 66 +BNOR, 67 +BNOT, 68 +BNTST, 69 +BNXOR, 70 +BOR, 71 +BRK, 72 +BSET, 73 +BTST, 74 +BTSTC, 75 +BTSTS, 76 +BXOR, 77 +CMP, 78 +DADC, 80 +DADD, 81 +DEC, 82 +DIV, 83 +DIVU, 84 +DIVX, 85 +DSBB, 86 +DSUB, 87 +ENTER, 88 +EXITD, 89 +EXTS, 90 +FCLR, 91 +FSET, 92 +INC, 93 +INT, 94 +INTO, 95 +J, 96 +JMP, 97 +JMPI, 98 +JMPS, 99 +JSR, 100 +JSRI, 101 +JSRS, 102 +LDC, 103 +LDCTX, 104 +LDE, 105 +LDINTB, 106 +LDIPL, 107 +MOV, 108 +MOVA, 110 +MOVHH, 111 +MOVHL, 111 +MOVLH, 111 +MOVLL, 111 +MUL, 112 +MULU, 113 +NEG, 114 +NOP, 115 +NOT, 116 +OR, 117 +POP, 119 +POPC, 120 +POPM, 121 +PUSH, 122 +PUSHA, 123 +PUSHC, 124 +PUSHM, 125 +REIT, 126 +RMPA, 127 +ROLC, 128 +RORC, 129 +ROT, 130 +RTS, 131 +SBB, 132 +SBJNZ, 133 +SHA, 134 +SHL, 135 +SMOVB, 136 +SMOVF, 137 +SSTR, 138 +STC, 139 +STCTX, 140 +STE, 141 +STNZ, 142 +STZ, 143 +STZX, 144 +SUB, 145 +TST, 147 +UND, 148 +WAIT, 149 +XCHG, 150 +XOR, 151 diff --git a/Ghidra/Processors/M16C/data/manuals/M16C_80.idx b/Ghidra/Processors/M16C/data/manuals/M16C_80.idx new file mode 100644 index 0000000000..cd2c20f43f --- /dev/null +++ b/Ghidra/Processors/M16C/data/manuals/M16C_80.idx @@ -0,0 +1,110 @@ +@m16c80.pdf +ABS, 60 +ADC, 61 +ADCF, 62 +ADD, 63 +ADDX, 65 +ADJNZ, 66 +AND, 67 +BAND, 69 +BCLR, 70 +BITINDEX, 71 +BM, 72 +BNAND, 73 +BNOR, 74 +BNOT, 75 +BNTST, 76 +BNXOR, 77 +BOR, 78 +BRK, 79 +BRK2, 80 +BSET, 81 +BTST, 82 +BTSTC, 83 +BTSTS, 84 +BXOR, 85 +CLIP, 86 +CMP, 87 +CMPX, 89 +DADC, 90 +DADD, 91 +DEC, 92 +DIV, 93 +DIVU, 94 +DIVX, 95 +DSBB, 96 +DSUB, 97 +ENTER, 98 +EXITD, 99 +EXTS, 100 +EXTZ, 101 +FLCR, 102 +FREIT, 103 +FSET, 104 +INC, 105 +INDEX, 175 +INT, 107 +INTO, 108 +J, 109 +JMP, 110 +JMPI, 111 +JMPS, 112 +JSR, 113 +JSRI, 114 +JSRS, 115 +LDC, 116 +LDCTX, 117 +LDIPL, 118 +MAX, 119 +MIN, 120 +MOV, 121 +MOVA, 123 +MOVHH, 124 +MOVHL, 124 +MOVLH, 124 +MOVLL, 124 +MOVX, 125 +MUL, 126 +MULEX, 127 +MULU, 128 +NEG, 129 +NOP, 130 +NOT, 131 +OR, 132 +POP, 134 +POPC, 135 +POPM, 136 +PUSH, 137 +PUSHA, 138 +PUSHC, 139 +PUSHM, 140 +REIT, 141 +RMPA, 142 +ROLC, 143 +RORC, 144 +ROT, 145 +RTS, 146 +SBB, 147 +SBJNZ, 148 +SC, 149 +SCMPU, 150 +SHA, 151 +SHL, 153 +SIN, 155 +SMOVB, 156 +SMOVF, 157 +SMOVU, 158 +SOUT, 159 +SSTR, 160 +STC, 161 +STCTX, 162 +STNZ, 163 +STZ, 164 +STZX, 165 +SUB, 166 +SUBX, 168 +TST, 169 +UND, 171 +WAIT, 172 +XCHG, 173 +XOR, 174 From 249d91f0a1545114db20722bdc35b1ae8f7af7a7 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Wed, 28 Aug 2024 15:46:47 -0400 Subject: [PATCH 24/53] GP-4867 Added BSim Server connection toggle for H2 and Postgres. Fixed various related bugs. --- Ghidra/Features/BSim/certification.manifest | 2 + .../Features/BSim/data/bsim.theme.properties | 3 + .../AddProgramToH2BSimDatabaseScript.java | 39 +++-- .../CreateH2BSimDatabaseScript.java | 55 ++++--- .../topics/BSimSearchPlugin/BSimSearch.html | 16 +- .../images/ManageServersDialog.png | Bin 16072 -> 20021 bytes .../features/bsim/gui/BSimSearchPlugin.java | 6 +- .../dialog => }/BSimServerManager.java | 108 +++++++++---- .../dialog/AbstractBSimSearchDialog.java | 5 +- .../gui/search/dialog/BSimOverviewDialog.java | 5 +- .../gui/search/dialog/BSimSearchDialog.java | 5 +- .../gui/search/dialog/BSimServerDialog.java | 145 +++++++++++++----- .../search/dialog/BSimServerTableModel.java | 144 +++++++++++------ .../search/dialog/ConnectionPoolStatus.java | 44 ++++++ .../dialog/CreateBsimServerInfoDialog.java | 7 +- .../bsim/query/BSimClientFactory.java | 9 +- .../bsim/query/BSimJDBCDataSource.java | 15 +- .../BSimPostgresDBConnectionManager.java | 26 ++-- .../features/bsim/query/FunctionDatabase.java | 23 ++- .../file/BSimH2FileDBConnectionManager.java | 60 +++++--- .../src/main/resources/images/connect.png | Bin 0 -> 748 bytes .../src/main/resources/images/disconnect.png | Bin 0 -> 796 bytes .../BSimSearchPluginScreenShots.java | 7 +- .../bsim/gui/BSimSearchPluginTestHelper.java | 5 +- .../features/bsim/gui/QueryFilterTest.java | 9 +- .../gui/overview/BSimOverviewTestHelper.java | 7 +- .../search/dialog/BSimFilterPanelTest.java | 10 +- .../dialog/BSimSearchDialogTestHelper.java | 7 +- .../file}/BSimH2DatabaseManagerTest.java | 11 +- .../bsim}/query/test/BSimServerTest.java | 6 +- .../bsim}/query/test/BSimServerTestUtil.java | 6 +- 31 files changed, 530 insertions(+), 255 deletions(-) rename Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/{search/dialog => }/BSimServerManager.java (65%) create mode 100644 Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/ConnectionPoolStatus.java create mode 100644 Ghidra/Features/BSim/src/main/resources/images/connect.png create mode 100644 Ghidra/Features/BSim/src/main/resources/images/disconnect.png rename Ghidra/Features/BSim/src/test.slow/java/ghidra/{query/inmemory => features/bsim/query/file}/BSimH2DatabaseManagerTest.java (98%) rename Ghidra/Features/BSim/src/test.slow/java/ghidra/{ => features/bsim}/query/test/BSimServerTest.java (99%) rename Ghidra/Features/BSim/src/test.slow/java/ghidra/{ => features/bsim}/query/test/BSimServerTestUtil.java (99%) diff --git a/Ghidra/Features/BSim/certification.manifest b/Ghidra/Features/BSim/certification.manifest index 12c5480cf9..137ac8dcbf 100755 --- a/Ghidra/Features/BSim/certification.manifest +++ b/Ghidra/Features/BSim/certification.manifest @@ -42,6 +42,8 @@ src/main/help/help/topics/BSimSearchPlugin/images/BSimSearchDialog.png||GHIDRA|| src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png||GHIDRA||||END| src/main/resources/bsim.log4j.xml||GHIDRA||||END| src/main/resources/images/checkmark_yellow.gif||GHIDRA||||END| +src/main/resources/images/connect.png||FAMFAMFAM Icons - CC 2.5||||END| +src/main/resources/images/disconnect.png||FAMFAMFAM Icons - CC 2.5||||END| src/main/resources/images/flag_green.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/resources/images/preferences-desktop-user-password.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/preferences-web-browser-shortcuts-32.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| diff --git a/Ghidra/Features/BSim/data/bsim.theme.properties b/Ghidra/Features/BSim/data/bsim.theme.properties index c0efedbea0..497af941d8 100644 --- a/Ghidra/Features/BSim/data/bsim.theme.properties +++ b/Ghidra/Features/BSim/data/bsim.theme.properties @@ -14,4 +14,7 @@ icon.bsim.results.status.ignored = checkmark_yellow.gif icon.bsim.functions.table = FunctionScope.gif +icon.bsim.connected = connect.png +icon.bsim.disconnected = disconnect.png + [Dark Defaults] diff --git a/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java b/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java index 648768ce7c..adbe50e621 100644 --- a/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java +++ b/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java @@ -4,9 +4,9 @@ * 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. @@ -71,22 +71,17 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript { askValues("Select Database File", null, values); File h2DbFile = values.getFile(DATABASE); + BSimServerInfo serverInfo = + new BSimServerInfo(DBType.file, null, 0, h2DbFile.getAbsolutePath()); - FunctionDatabase h2Database = null; - try { - BSimServerInfo serverInfo = - new BSimServerInfo(DBType.file, null, 0, h2DbFile.getAbsolutePath()); - h2Database = BSimClientFactory.buildClient(serverInfo, false); - BSimH2FileDataSource bds = - BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); - if (bds == null) { - popup(h2DbFile.getAbsolutePath() + " is not an H2 database file"); - return; - } - if (bds.getActiveConnections() > 0) { - popup("There is an existing connection to the database."); - return; - } + BSimH2FileDataSource existingBDS = + BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); + if (existingBDS != null && existingBDS.getActiveConnections() > 0) { + popup("There is an existing connection to the database."); + return; + } + + try (FunctionDatabase h2Database = BSimClientFactory.buildClient(serverInfo, false)) { h2Database.initialize(); DatabaseInformation dbInfo = h2Database.getInfo(); @@ -169,11 +164,13 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript { } finally { - if (h2Database != null) { - h2Database.close(); + if (existingBDS == null) { + // Dispose database source if it did not previously exist BSimH2FileDataSource bds = - BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); - bds.dispose(); + BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); + if (bds != null) { + bds.dispose(); + } } } } diff --git a/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java b/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java index ef332f6d64..f30b1c65d5 100644 --- a/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java +++ b/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java @@ -4,9 +4,9 @@ * 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. @@ -31,7 +31,6 @@ import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; import ghidra.features.bsim.query.protocol.*; import ghidra.util.MessageType; -import ghidra.util.Msg; public class CreateH2BSimDatabaseScript extends GhidraScript { private static final String NAME = "Database Name"; @@ -80,31 +79,27 @@ public class CreateH2BSimDatabaseScript extends GhidraScript { askValues("Enter Database Parameters", "Enter values required to create a new BSim H2 database.", values); - FunctionDatabase h2Database = null; - try { - String databaseName = values.getString(NAME); - File dbDir = values.getFile(DIRECTORY); - String template = values.getChoice(DATABASE_TEMPLATE); - String functionTagsCSV = values.getString(FUNCTION_TAGS); - List tags = parseCSV(functionTagsCSV); + String databaseName = values.getString(NAME); + File dbDir = values.getFile(DIRECTORY); + String template = values.getChoice(DATABASE_TEMPLATE); + String functionTagsCSV = values.getString(FUNCTION_TAGS); + List tags = parseCSV(functionTagsCSV); - String exeCatCSV = values.getString(EXECUTABLE_CATEGORIES); - List cats = parseCSV(exeCatCSV); + String exeCatCSV = values.getString(EXECUTABLE_CATEGORIES); + List cats = parseCSV(exeCatCSV); - File dbFile = new File(dbDir, databaseName); + File dbFile = new File(dbDir, databaseName); + BSimServerInfo serverInfo = + new BSimServerInfo(DBType.file, null, 0, dbFile.getAbsolutePath()); - BSimServerInfo serverInfo = - new BSimServerInfo(DBType.file, null, 0, dbFile.getAbsolutePath()); - h2Database = BSimClientFactory.buildClient(serverInfo, false); - BSimH2FileDataSource bds = - BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); - if (bds.getActiveConnections() > 0) { - //if this happens, there is a connection to the database but the - //database file was deleted - Msg.showError(this, null, "Connection Error", - "There is an existing connection to the database!"); - return; - } + BSimH2FileDataSource existingBDS = + BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); + if (existingBDS != null && existingBDS.getActiveConnections() > 0) { + popup("There is an existing connection to the database."); + return; + } + + try (FunctionDatabase h2Database = BSimClientFactory.buildClient(serverInfo, false)) { CreateDatabase command = new CreateDatabase(); command.info = new DatabaseInformation(); @@ -140,11 +135,13 @@ public class CreateH2BSimDatabaseScript extends GhidraScript { popup("Database " + values.getString(NAME) + " created successfully!"); } finally { - if (h2Database != null) { - h2Database.close(); + if (existingBDS == null) { + // Dispose database source if it did not previously exist BSimH2FileDataSource bds = - BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo()); - bds.dispose(); + BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); + if (bds != null) { + bds.dispose(); + } } } diff --git a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html index bdbfd102af..ffecfeb75d 100644 --- a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html +++ b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html @@ -57,18 +57,24 @@ entry shows a name for the BSim database, its type (postgres, elastic, or file), a host ip and port (if applicable), and finally the number of active connections.

-

There are three primary actions for this dialog:

+

There are four primary actions for this dialog:

    -
  •  Add a new database/server definition - a - Define Server Dialog will be shown.
  • +
  •  Add a new BSim database/server definition - an + Add BSim Server Dialog will be shown.
  •  Delete a database/server definition - The - selected entry will be deleted.
  • + selected entry will be deleted. This action will force an immediate disconnect for an + active/idle connection and should be used with care. + +
  •   + Connect or disconnect an inactive database/server connection. This action is not supported + by Elastic database servers.
  •  Change password - A change password - dialog will appear for the selected entry
  • + dialog will appear for the selected database server. Action is disabled for databases + which do not use a password (e.g., Local H2 File database).

Defining a new BSim server/database

diff --git a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png index abe6af84947c4d99306483fc57bb2542981e0b53..59f713dc8cd3d62ac6a1f261f92a67b83a88668e 100644 GIT binary patch literal 20021 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 searchResultsProviders = new HashSet<>(); private Set overviewProviders = new HashSet<>(); - private BSimServerManager serverManager = new BSimServerManager(); + private BSimServerManager serverManager = BSimServerManager.getBSimServerManager(); private BSimSearchService searchService; private BSimServerCache lastUsedServerCache = null; diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/BSimServerManager.java similarity index 65% rename from Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerManager.java rename to Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/BSimServerManager.java index 9e1559af07..df21cbbbfb 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerManager.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/BSimServerManager.java @@ -4,25 +4,25 @@ * 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.features.bsim.gui.search.dialog; +package ghidra.features.bsim.gui; import java.io.File; import java.io.IOException; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; -import ghidra.features.bsim.query.BSimPostgresDBConnectionManager; +import ghidra.features.bsim.gui.search.dialog.BSimServerManagerListener; +import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.BSimPostgresDBConnectionManager.BSimPostgresDataSource; -import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; @@ -36,12 +36,24 @@ import ghidra.util.Swing; * Managers BSim database server definitions and connections */ public class BSimServerManager { - // TODO: Do not allow removal of active server. Dispose data source when removed. + + private static BSimServerManager instance; + + /** + * Get static singleton instance for BSimServerManager + * @return BSimServerManager instance + */ + static synchronized BSimServerManager getBSimServerManager() { + if (instance == null) { + instance = new BSimServerManager(); + } + return instance; + } private Set serverInfos = new HashSet<>(); private List listeners = new CopyOnWriteArrayList<>(); - public BSimServerManager() { + private BSimServerManager() { List files = Application.getUserSettingsFiles("bsim", ".server.properties"); for (File file : files) { BSimServerInfo info = readBsimServerInfoFile(file); @@ -51,6 +63,10 @@ public class BSimServerManager { } } + /** + * Get list of defined servers. Method must be invoked from swing thread only. + * @return list of defined servers + */ public Set getServerInfos() { return new HashSet<>(serverInfos); } @@ -108,6 +124,10 @@ public class BSimServerManager { return serverFile.delete(); } + /** + * Add server to list. Method must be invoked from swing thread only. + * @param newServerInfo new BSim DB server + */ public void addServer(BSimServerInfo newServerInfo) { if (saveBSimServerInfo(newServerInfo)) { serverInfos.add(newServerInfo); @@ -115,28 +135,42 @@ public class BSimServerManager { } } - public boolean removeServer(BSimServerInfo info, boolean force) { + private static boolean disposeServer(BSimServerInfo info, boolean force) { DBType dbType = info.getDBType(); if (dbType == DBType.file) { - BSimH2FileDataSource ds = BSimH2FileDBConnectionManager.getDataSource(info); - int active = ds.getActiveConnections(); - if (active != 0) { - if (!force) { + BSimH2FileDataSource ds = BSimH2FileDBConnectionManager.getDataSourceIfExists(info); + if (ds != null) { + int active = ds.getActiveConnections(); + if (active != 0 && !force) { return false; } ds.dispose(); } } else if (dbType == DBType.postgres) { - BSimPostgresDataSource ds = BSimPostgresDBConnectionManager.getDataSource(info); - int active = ds.getActiveConnections(); - if (active != 0) { - if (!force) { + BSimPostgresDataSource ds = BSimPostgresDBConnectionManager.getDataSourceIfExists(info); + if (ds != null) { + int active = ds.getActiveConnections(); + if (active != 0 && !force) { return false; } ds.dispose(); } } + return true; + } + + /** + * Remove BSim DB server from list. Method must be invoked from swing thread only. + * Specified server datasource will be dispose unless it is active or force is true. + * @param info BSim DB server to be removed + * @param force true if server datasource should be disposed even when active. + * @return true if server disposed and removed from list + */ + public boolean removeServer(BSimServerInfo info, boolean force) { + if (!disposeServer(info, force)) { + return false; + } if (serverInfos.remove(info)) { removeServerFileFromSettings(info); notifyServerListChanged(); @@ -160,26 +194,38 @@ public class BSimServerManager { }); } - public static int getActiveConnections(BSimServerInfo serverInfo) { + /** + * Convenience method to get existing BSim JDBC datasource + * @param serverInfo BSim DB server info + * @return BSim DB datasource or null if not instantiated or server does not support a + * {@link BSimJDBCDataSource}. + */ + public static BSimJDBCDataSource getDataSourceIfExists(BSimServerInfo serverInfo) { switch (serverInfo.getDBType()) { case postgres: - BSimPostgresDataSource postgresDs = - BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo); - if (postgresDs != null) { - return postgresDs.getActiveConnections(); - } - break; + return BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo); case file: - BSimH2FileDataSource h2FileDs = - BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); - if (h2FileDs != null) { - return h2FileDs.getActiveConnections(); - } - break; + return BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); default: - break; + return null; + } + } + + /** + * Convenience method to get a new or existing BSim JDBC datasource + * @param serverInfo BSim DB server info + * @return BSim DB datasource or null if server does not support a + * {@link BSimJDBCDataSource}. + */ + public static BSimJDBCDataSource getDataSource(BSimServerInfo serverInfo) { + switch (serverInfo.getDBType()) { + case postgres: + return BSimPostgresDBConnectionManager.getDataSource(serverInfo); + case file: + return BSimH2FileDBConnectionManager.getDataSource(serverInfo); + default: + return null; } - return -1; } } diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/AbstractBSimSearchDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/AbstractBSimSearchDialog.java index ff999ee822..9ed6829018 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/AbstractBSimSearchDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/AbstractBSimSearchDialog.java @@ -4,9 +4,9 @@ * 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. @@ -30,6 +30,7 @@ import docking.widgets.EmptyBorderButton; import docking.widgets.combobox.GComboBox; import docking.widgets.textfield.FloatingPointTextField; import generic.theme.Gui; +import ghidra.features.bsim.gui.BSimServerManager; import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.description.DatabaseInformation; import ghidra.features.bsim.query.facade.QueryDatabaseException; diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimOverviewDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimOverviewDialog.java index 2b44b19bbb..a4afa85649 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimOverviewDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimOverviewDialog.java @@ -4,9 +4,9 @@ * 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. @@ -16,6 +16,7 @@ package ghidra.features.bsim.gui.search.dialog; import ghidra.features.bsim.gui.BSimSearchPlugin; +import ghidra.features.bsim.gui.BSimServerManager; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java index d0df4f8bba..9b9318638e 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java @@ -4,9 +4,9 @@ * 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. @@ -30,6 +30,7 @@ import docking.widgets.textfield.IntegerTextField; import generic.theme.GIcon; import ghidra.app.services.GoToService; import ghidra.features.bsim.gui.BSimSearchPlugin; +import ghidra.features.bsim.gui.BSimServerManager; import ghidra.features.bsim.gui.filters.BSimFilterType; import ghidra.features.bsim.query.description.DatabaseInformation; import ghidra.framework.plugintool.PluginTool; diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java index a2c90f1c67..bec9aeee2e 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java @@ -4,9 +4,9 @@ * 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. @@ -16,21 +16,25 @@ package ghidra.features.bsim.gui.search.dialog; import java.awt.BorderLayout; +import java.sql.Connection; +import java.sql.SQLException; import javax.swing.*; import org.bouncycastle.util.Arrays; -import docking.DialogComponentProvider; -import docking.DockingWindowManager; -import docking.action.DockingAction; +import docking.*; +import docking.action.*; import docking.action.builder.ActionBuilder; +import docking.action.builder.ToggleActionBuilder; import docking.widgets.OptionDialog; import docking.widgets.PasswordChangeDialog; import docking.widgets.table.GFilterTable; import docking.widgets.table.GTable; import generic.theme.GIcon; +import ghidra.features.bsim.gui.BSimServerManager; import ghidra.features.bsim.query.*; +import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.FunctionDatabase.Error; import ghidra.features.bsim.query.FunctionDatabase.ErrorCategory; import ghidra.framework.plugintool.PluginTool; @@ -42,15 +46,14 @@ import resources.Icons; */ public class BSimServerDialog extends DialogComponentProvider { - // TODO: Add connected status indicator (not sure how this relates to elastic case which will likely have a Session concept) - // TODO: Add "Disconnect" action (only works when active connections is 0; does not apply to elastic) - private PluginTool tool; private BSimServerManager serverManager; private BSimServerTableModel serverTableModel; - private GFilterTable filterTable; + private GFilterTable serverTable; private BSimServerInfo lastAdded = null; + private ToggleDockingAction dbConnectionAction; + public BSimServerDialog(PluginTool tool, BSimServerManager serverManager) { super("BSim Server Manager"); this.tool = tool; @@ -60,7 +63,7 @@ public class BSimServerDialog extends DialogComponentProvider { addDismissButton(); setPreferredSize(600, 400); notifyContextChanged(); // kick actions to initialized enabled state - setHelpLocation(new HelpLocation("BSimSearchPlugin","BSim_Servers_Dialog" )); + setHelpLocation(new HelpLocation("BSimSearchPlugin", "BSim_Servers_Dialog")); } @Override @@ -70,34 +73,101 @@ public class BSimServerDialog extends DialogComponentProvider { } private void createToolbarActions() { - HelpLocation help = new HelpLocation("BSimSearchPlugin","Manage_Servers_Actions" ); - + HelpLocation help = new HelpLocation("BSimSearchPlugin", "Manage_Servers_Actions"); + DockingAction addServerAction = - new ActionBuilder("Add Server", "Dialog").toolBarIcon(Icons.ADD_ICON) - .helpLocation(help) - .onAction(e -> defineBsimServer()) - .build(); + new ActionBuilder("Add BSim Database", "Dialog").toolBarIcon(Icons.ADD_ICON) + .helpLocation(help) + .onAction(e -> defineBsimServer()) + .build(); addAction(addServerAction); DockingAction removeServerAction = - new ActionBuilder("Delete Server", "Dialog").toolBarIcon(Icons.DELETE_ICON) - .helpLocation(help) - .onAction(e -> deleteBsimServer()) - .enabledWhen(c -> hasSelection()) - .build(); + new ActionBuilder("Delete BSim Database", "Dialog").toolBarIcon(Icons.DELETE_ICON) + .helpLocation(help) + .onAction(e -> deleteBsimServer()) + .enabledWhen(c -> hasSelection()) + .build(); addAction(removeServerAction); - DockingAction changePasswordAction = new ActionBuilder("Change User Password", "Dialog") - .helpLocation(help) - .toolBarIcon(new GIcon("icon.bsim.change.password")) - .onAction(e -> changePassword()) - .enabledWhen(c -> hasSelection()) - .build(); + dbConnectionAction = + new ToggleActionBuilder("Toggle Database Connection", "Dialog").helpLocation(help) + .toolBarIcon(new GIcon("icon.bsim.disconnected")) + .onAction(e -> toggleSelectedJDBCDataSourceConnection()) + .enabledWhen(c -> isNonActiveJDBCDataSourceSelected(c)) + .build(); + addAction(dbConnectionAction); + + DockingAction changePasswordAction = + new ActionBuilder("Change User Password", "Dialog").helpLocation(help) + .toolBarIcon(new GIcon("icon.bsim.change.password")) + .onAction(e -> changePassword()) + .enabledWhen(c -> canChangePassword()) + .build(); addAction(changePasswordAction); } + private void toggleSelectedJDBCDataSourceConnection() { + + BSimServerInfo serverInfo = serverTable.getSelectedRowObject(); + if (serverInfo == null || serverInfo.getDBType() == DBType.elastic) { + return; + } + + BSimJDBCDataSource dataSource = BSimServerManager.getDataSourceIfExists(serverInfo); + if (dataSource == null) { + // connect + dataSource = BSimServerManager.getDataSource(serverInfo); + try (Connection connection = dataSource.getConnection()) { + // do nothing + } + catch (SQLException e) { + Msg.showError(this, rootPanel, "BSim Connection Failure", e.getMessage()); + } + } + else { + dataSource.dispose(); + } + serverTableModel.fireTableDataChanged(); + notifyContextChanged(); + } + + private boolean isNonActiveJDBCDataSourceSelected(ActionContext c) { + BSimServerInfo serverInfo = serverTable.getSelectedRowObject(); + if (serverInfo == null) { + return false; + } + + // TODO: May need connection listener on dataSource to facilitate GUI update, + // although modal dialog avoids the issue somewhat + + dbConnectionAction.setDescription(dbConnectionAction.getName()); + + ConnectionPoolStatus status = serverTableModel.getConnectionPoolStatus(serverInfo); + if (status.isActive) { + + // Show connected icon + dbConnectionAction + .setToolBarData(new ToolBarData(new GIcon("icon.bsim.connected"), null)); + dbConnectionAction.setSelected(true); + dbConnectionAction.setDescription("Disconnect idle BSim Database connection"); + + // disconnect permitted when no active connections + return status.activeCount == 0; + } + + // Show disconnected icon (elastic always shown as disconnected) + dbConnectionAction + .setToolBarData(new ToolBarData(new GIcon("icon.bsim.disconnected"), null)); + dbConnectionAction.setSelected(false); + dbConnectionAction.setDescription("Connect BSim Database"); + + // Action never enabled for elastic DB (i.e., does not use pooled JDBC data source) + return serverInfo.getDBType() != DBType.elastic; + } + private void changePassword() { - BSimServerInfo serverInfo = filterTable.getSelectedRowObject(); + BSimServerInfo serverInfo = serverTable.getSelectedRowObject(); if (serverInfo == null) { return; } @@ -141,8 +211,13 @@ public class BSimServerDialog extends DialogComponentProvider { } } + private boolean canChangePassword() { + BSimServerInfo serverInfo = serverTable.getSelectedRowObject(); + return serverInfo != null && serverInfo.getDBType() != DBType.file; + } + private void deleteBsimServer() { - BSimServerInfo selected = filterTable.getSelectedRowObject(); + BSimServerInfo selected = serverTable.getSelectedRowObject(); if (selected != null) { int answer = OptionDialog.showYesNoDialog(getComponent(), "Delete Server Configuration?", @@ -152,7 +227,7 @@ public class BSimServerDialog extends DialogComponentProvider { answer = OptionDialog.showOptionDialogWithCancelAsDefaultButton(getComponent(), "Active Server Configuration!", "Database connections are still active!\n" + - "Are you sure you want to delete server?", + "Are you sure you want to terminate connections and delete server?", "Yes", OptionDialog.WARNING_MESSAGE); if (answer == OptionDialog.YES_OPTION) { serverManager.removeServer(selected, true); @@ -169,7 +244,7 @@ public class BSimServerDialog extends DialogComponentProvider { if (newServerInfo != null) { serverManager.addServer(newServerInfo); lastAdded = newServerInfo; - Swing.runLater(() -> filterTable.setSelectedRowObject(newServerInfo)); + Swing.runLater(() -> serverTable.setSelectedRowObject(newServerInfo)); } } @@ -178,11 +253,11 @@ public class BSimServerDialog extends DialogComponentProvider { panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); serverTableModel = new BSimServerTableModel(serverManager); - filterTable = new GFilterTable<>(serverTableModel); - GTable table = filterTable.getTable(); + serverTable = new GFilterTable<>(serverTableModel); + GTable table = serverTable.getTable(); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.getSelectionModel().addListSelectionListener(e -> notifyContextChanged()); - panel.add(filterTable, BorderLayout.CENTER); + panel.add(serverTable, BorderLayout.CENTER); if (serverTableModel.getRowCount() > 0) { table.setRowSelectionInterval(0, 0); @@ -192,7 +267,7 @@ public class BSimServerDialog extends DialogComponentProvider { } private boolean hasSelection() { - return filterTable.getSelectedRowObject() != null; + return serverTable.getSelectedRowObject() != null; } public BSimServerInfo getLastAdded() { diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java index b26aeea431..8bed9cc704 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java @@ -4,9 +4,9 @@ * 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. @@ -16,27 +16,33 @@ package ghidra.features.bsim.gui.search.dialog; import java.awt.Component; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import javax.swing.Icon; import javax.swing.JLabel; import docking.widgets.table.*; import ghidra.docking.settings.Settings; +import ghidra.features.bsim.gui.BSimServerManager; import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProviderStub; -import ghidra.program.model.listing.Program; import ghidra.util.table.column.AbstractGColumnRenderer; import ghidra.util.table.column.GColumnRenderer; -import ghidra.util.table.field.AbstractProgramBasedDynamicTableColumn; /** - * Table model for BSim database server definitions + * Table model for BSim database server definitions. + * + * NOTE: This implementation assumes modal dialog use and non-changing connection state + * while instance is in-use. This was done to avoid adding a conection listener which could + * introduce excessive overhead into the connection pool use. */ public class BSimServerTableModel extends GDynamicColumnTableModel { + private List servers; + private Map statusCache = new HashMap<>(); + private BSimServerManager serverManager; private BSimServerManagerListener listener = new BSimServerManagerListener() { @Override @@ -63,8 +69,18 @@ public class BSimServerTableModel extends GDynamicColumnTableModel new ConnectionPoolStatus(s)); } @Override @@ -74,7 +90,7 @@ public class BSimServerTableModel extends GDynamicColumnTableModel { + extends AbstractDynamicTableColumn { private GColumnRenderer renderer = new AbstractGColumnRenderer<>() { @Override public Component getTableCellRendererComponent(GTableCellRenderingData data) { @@ -112,11 +128,8 @@ public class BSimServerTableModel extends GDynamicColumnTableModel { + private static class TypeColumn + extends AbstractDynamicTableColumn { + + @Override + public String getColumnName() { + return "Type"; + } + + @Override + public String getValue(BSimServerInfo serverInfo, Settings settings, Object data, + ServiceProvider provider) throws IllegalArgumentException { + + return serverInfo.getDBType().toString(); + } + + @Override + public int getColumnPreferredWidth() { + return 80; + } + } + + private static class HostColumn + extends AbstractDynamicTableColumn { @Override public String getColumnName() { @@ -140,8 +174,8 @@ public class BSimServerTableModel extends GDynamicColumnTableModel { + private static class PortColumn + extends AbstractDynamicTableColumn { @Override public String getColumnName() { @@ -161,64 +195,78 @@ public class BSimServerTableModel extends GDynamicColumnTableModel { + private static class ConnectionStatusColumnRenderer + extends AbstractGColumnRenderer { + + private static final ConnectionStatusColumnRenderer INSTANCE = + new ConnectionStatusColumnRenderer(); @Override - public String getColumnName() { - return "Active Connections"; - } + public Component getTableCellRendererComponent(GTableCellRenderingData data) { - @Override - public Integer getValue(BSimServerInfo serverInfo, Settings settings, Program data, - ServiceProvider provider) throws IllegalArgumentException { - int activeConnections = BSimServerManager.getActiveConnections(serverInfo); - if (activeConnections < 0) { - return null; + JLabel c = (JLabel) super.getTableCellRendererComponent(data); + + ConnectionPoolStatus status = (ConnectionPoolStatus) data.getValue(); + + // NOTE: Custom column renderer has neem established with future use of + // status icon in mind (e.g., H2 mixed-mode server enabled) + + Icon icon = null; // NOTE: may need default filler icon + String text = null; + if (status.isActive) { + text = Integer.toString(status.activeCount) + " / " + + Integer.toString(status.idleCount); } - return activeConnections; + c.setText(text); + c.setIcon(icon); + return c; } @Override - public int getColumnPreferredWidth() { - return 80; + public String getFilterString(ConnectionPoolStatus t, Settings settings) { + return null; // Filtering not supported } + } - private class TypeColumn - extends AbstractProgramBasedDynamicTableColumn { + private class ConnectionStatusColumn + extends AbstractDynamicTableColumn { @Override public String getColumnName() { - return "Type"; + return "Active/Idle Connections"; } @Override - public String getValue(BSimServerInfo serverInfo, Settings settings, Program data, - ServiceProvider provider) throws IllegalArgumentException { - - return serverInfo.getDBType().toString(); + public ConnectionPoolStatus getValue(BSimServerInfo serverInfo, Settings settings, + Object data, ServiceProvider provider) throws IllegalArgumentException { + return getConnectionPoolStatus(serverInfo); } @Override public int getColumnPreferredWidth() { - return 80; + return 150; + } + + @Override + public GColumnRenderer getColumnRenderer() { + return ConnectionStatusColumnRenderer.INSTANCE; } } diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/ConnectionPoolStatus.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/ConnectionPoolStatus.java new file mode 100644 index 0000000000..dc9d7b2dfe --- /dev/null +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/ConnectionPoolStatus.java @@ -0,0 +1,44 @@ +/* ### + * 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.features.bsim.gui.search.dialog; + +import ghidra.features.bsim.gui.BSimServerManager; +import ghidra.features.bsim.query.BSimJDBCDataSource; +import ghidra.features.bsim.query.BSimServerInfo; + +class ConnectionPoolStatus { + BSimServerInfo serverInfo; + + final boolean isActive; + final int activeCount; + final int idleCount; + + ConnectionPoolStatus(BSimServerInfo serverInfo) { + this.serverInfo = serverInfo; + + BSimJDBCDataSource dataSource = BSimServerManager.getDataSourceIfExists(serverInfo); + if (dataSource == null) { + isActive = false; + activeCount = 0; + idleCount = 0; + } + else { + isActive = true; + activeCount = dataSource.getActiveConnections(); + idleCount = dataSource.getIdleConnections(); + } + } +} diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java index badb9638b2..f31c59cd4d 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java @@ -4,9 +4,9 @@ * 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. @@ -91,8 +91,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider { public boolean acceptServer(BSimServerInfo serverInfo) { // FIXME: Use task to correct dialog parenting issue caused by password prompt String errorMessage = null; - try { - FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true); + try (FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true)) { if (database.initialize()) { return true; } diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimClientFactory.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimClientFactory.java index 7b207d34ee..291e47316c 100755 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimClientFactory.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimClientFactory.java @@ -4,9 +4,9 @@ * 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. @@ -118,7 +118,10 @@ public class BSimClientFactory { } /** - * Given the URL for a BSim server construct the appropriate BSim client object (implementing FunctionDatabase) + * Given the URL for a BSim server construct the appropriate BSim client object + * (implementing FunctionDatabase). Returned instance must be + * {@link FunctionDatabase#close() closed} when done using it to prevent depletion + * of database connections. * @param bsimServerInfo BSim server details * @param async true if database commits should be asynchronous * @return the database client diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimJDBCDataSource.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimJDBCDataSource.java index c0ca147904..5e82082b90 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimJDBCDataSource.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimJDBCDataSource.java @@ -4,9 +4,9 @@ * 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. @@ -48,4 +48,15 @@ public interface BSimJDBCDataSource { */ int getActiveConnections(); + /** + * Get the number of idle connections in the associated connection pool + * @return number of idle connections + */ + int getIdleConnections(); + + /** + * Dispose pooled datasource. + */ + void dispose(); + } diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java index c0dafe4370..70aea9aa2b 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java @@ -4,9 +4,9 @@ * 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. @@ -41,7 +41,8 @@ public class BSimPostgresDBConnectionManager { private static HashMap dataSourceMap = new HashMap<>(); - public static BSimPostgresDataSource getDataSource(BSimServerInfo postgresServerInfo) { + public static synchronized BSimPostgresDataSource getDataSource( + BSimServerInfo postgresServerInfo) { if (postgresServerInfo.getDBType() != DBType.postgres) { throw new IllegalArgumentException("expected postgres server info"); } @@ -54,19 +55,20 @@ public class BSimPostgresDBConnectionManager { return getDataSource(new BSimServerInfo(postgresUrl)); } - public static BSimPostgresDataSource getDataSourceIfExists(BSimServerInfo serverInfo) { + public static synchronized BSimPostgresDataSource getDataSourceIfExists( + BSimServerInfo serverInfo) { return dataSourceMap.get(serverInfo); } - private static synchronized void remove(BSimServerInfo serverInfo) { + private static synchronized void remove(BSimServerInfo serverInfo, boolean force) { BSimPostgresDataSource ds = dataSourceMap.get(serverInfo); if (ds == null) { return; } int n = ds.bds.getNumActive(); - if (n != 0) { - System.out - .println("Unable to remove data source which has " + n + " active connections"); + if (n != 0 && !force) { + Msg.error(BSimPostgresDBConnectionManager.class, + "Unable to remove data source which has " + n + " active connections"); return; } ds.close(); @@ -113,8 +115,9 @@ public class BSimPostgresDBConnectionManager { bds.setUsername(userName); } + @Override public void dispose() { - remove(serverInfo); + remove(serverInfo, true); } private void close() { @@ -143,6 +146,11 @@ public class BSimPostgresDBConnectionManager { return bds.getNumActive(); } + @Override + public int getIdleConnections() { + return bds.getNumIdle(); + } + /** * Update password on {@link BasicDataSource} for use with future connect attempts. * Has no affect if username does not match username on data source. diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java index 4a85e5e54d..6998da5447 100755 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java @@ -4,9 +4,9 @@ * 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. @@ -33,7 +33,9 @@ import ghidra.features.bsim.query.facade.SFOverviewInfo; import ghidra.features.bsim.query.facade.SFQueryInfo; import ghidra.features.bsim.query.protocol.*; import ghidra.framework.Application; +import ghidra.program.model.data.DataUtilities; import ghidra.util.Msg; +import ghidra.util.StringUtilities; public interface FunctionDatabase extends AutoCloseable { @@ -239,7 +241,15 @@ public interface FunctionDatabase extends AutoCloseable { if (res == 3) { throw new LSHException("Query signature data has no setting information"); } - throw new LSHException("Query signature data does not match database"); + throw new LSHException("Query signature data " + + getFormattedVersion(manage.getMajorVersion(), manage.getMinorVersion(), + manage.getSettings()) + + " does not match database " + + getFormattedVersion(info.major, info.minor, info.settings)); + } + + private static String getFormattedVersion(int maj, int min, int settings) { + return String.format("%d.%d:0x%02x", maj, min, settings); } public static boolean checkSettingsForInsert(DescriptionManager manage, @@ -262,8 +272,11 @@ public interface FunctionDatabase extends AutoCloseable { if (res == 3) { throw new LSHException("Trying to insert signature data with no setting information"); } - throw new LSHException( - "Trying to insert signature data with settings that don't match database"); + throw new LSHException("Trying to insert signature data " + + getFormattedVersion(manage.getMajorVersion(), manage.getMinorVersion(), + manage.getSettings()) + + " with settings that don't match database " + + getFormattedVersion(info.major, info.minor, info.settings)); } public static String constructFatalError(int flags, ExecutableRecord newrec, diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/file/BSimH2FileDBConnectionManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/file/BSimH2FileDBConnectionManager.java index a5d13af20f..b7ad1d0629 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/file/BSimH2FileDBConnectionManager.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/file/BSimH2FileDBConnectionManager.java @@ -4,9 +4,9 @@ * 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. @@ -28,6 +28,7 @@ import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.FunctionDatabase.ConnectionType; import ghidra.features.bsim.query.FunctionDatabase.Status; +import ghidra.util.Msg; public class BSimH2FileDBConnectionManager { @@ -44,7 +45,7 @@ public class BSimH2FileDBConnectionManager { * Get all H2 File DB data sorces which exist in the JVM. * @return all H2 File DB data sorces */ - public static Collection getAllDataSources() { + public static synchronized Collection getAllDataSources() { // Create copy to avoid potential concurrent modification return Collections.unmodifiableCollection(new ArrayList<>(dataSourceMap.values())); } @@ -57,7 +58,7 @@ public class BSimH2FileDBConnectionManager { * @throws IllegalArgumentException if {@code fileServerInfo} does not specify an * H2 File DB type. */ - public static BSimH2FileDataSource getDataSource(BSimServerInfo fileServerInfo) { + public static synchronized BSimH2FileDataSource getDataSource(BSimServerInfo fileServerInfo) { if (fileServerInfo.getDBType() != DBType.file) { throw new IllegalArgumentException("expected file info"); } @@ -79,26 +80,26 @@ public class BSimH2FileDBConnectionManager { * @return existing H2 File data source or null if server info does not correspond to an * H2 File or has not be established as an H2 File data source. */ - public static BSimH2FileDataSource getDataSourceIfExists(BSimServerInfo serverInfo) { + public static synchronized BSimH2FileDataSource getDataSourceIfExists( + BSimServerInfo serverInfo) { return dataSourceMap.get(serverInfo); } - private static synchronized void remove(BSimServerInfo serverInfo, boolean force) { + private static synchronized boolean remove(BSimServerInfo serverInfo, boolean force) { BSimH2FileDataSource ds = dataSourceMap.get(serverInfo); if (ds == null) { - return; + return true; } int n = ds.bds.getNumActive(); - if (n != 0) { - System.out - .println("Unable to remove data source which has " + n + " active connections"); - if (!force) { - return; - } + if (n != 0 && !force) { + Msg.error(BSimH2FileDBConnectionManager.class, + "Unable to remove data source which has " + n + " active connections"); + return false; } ds.close(); dataSourceMap.remove(serverInfo); BSimVectorStoreManager.remove(serverInfo); + return true; } /** @@ -123,20 +124,31 @@ public class BSimH2FileDBConnectionManager { return serverInfo; } + @Override public void dispose() { BSimH2FileDBConnectionManager.remove(serverInfo, true); } /** - * Delete the database files associated with this H2 File DB. When complete - * this data source will no longer be valid and should no tbe used. + * Delete the database files associated with this H2 File DB. This will fail immediately + * if active connections exist. Otherwise removal will be attempted and this data source + * will no longer be valid. + * @return true if DB sucessfully removed */ - public void delete() { - dispose(); + public synchronized boolean delete() { File dbf = new File(serverInfo.getDBName()); - // TODO: Should we check for lock on database - could be another process + if (getActiveConnections() != 0) { + Msg.error(this, "Failed to delete active database: " + dbf); + return false; + } + + dispose(); + + if (dbf.isFile()) { + return true; + } String name = dbf.getName(); int ix = name.lastIndexOf(BSimServerInfo.H2_FILE_EXTENSION); @@ -145,6 +157,13 @@ public class BSimH2FileDBConnectionManager { } DeleteDbFiles.execute(dbf.getParent(), name, true); + + if (!dbf.isFile()) { + return true; + } + + Msg.error(this, "Failed to delete database: " + dbf); + return false; } /** @@ -181,6 +200,11 @@ public class BSimH2FileDBConnectionManager { return bds.getNumActive(); } + @Override + public int getIdleConnections() { + return bds.getNumIdle(); + } + private String getH2FileUrl() { // Remove H2 db file extension if present diff --git a/Ghidra/Features/BSim/src/main/resources/images/connect.png b/Ghidra/Features/BSim/src/main/resources/images/connect.png new file mode 100644 index 0000000000000000000000000000000000000000..024138eb33b9124af6db8149747adbb41c1b8cfc GIT binary patch literal 748 zcmVzR>QH2KN`fNj zBHaWZEgiB#>49kYe(borqx+hj`JS_1d)R}7LjHa+tu+qg(TC+eO4&q(D;ZL8C8o8; zLEfZxiIlQ~iE1b1qBZ2=VhsA0V`*@y@M|Sc2@Wtadv3g0hOc*O(ADVFjPTWAYz(JXS z8MBaa%aCO{h8lvp*ONKIh5Bg|-DNo^;jg=q=uDZ@vodOJXfu;GK`ze`H-VrWK!nUg zje)ufRUJ&ouB2nYB2_pI=gji&GcuBL=+DM-wBZ)fbc7&a9AP1Ztk5)S4Ah03bqXOt zxx}VdK|CIVljyc=oqO1G{;Qew7T~%?tSw{^mi$E(2^Td6>M8+m4H!qrBtj~%w(Y~Q zVrXkVOEN#2&@(U(cX3H&S97B(;-}{)?<>?0)RjVZqrJ&ONChgCgE9%PC}CSBp!+d1 z`bkAqacXYz!94aLsJZ!K=3b*?T#ge1nL>bsZNf4&8mt(EkXYZ?MK0YwH2ZOI9{(VN z&%WPH*v6AY+yG?)mZ`CxE@5ZK2lW}4Pr-ctMRE2V`yf8$PurT4&^p3q*2kt>M8OM& zl@MbQ=U&8BS_$dSjo(q&2Pyd!8`}{~Xr#A_DDL|G({Ha$;Xe@?&|@q4QbvV}D#ixB ey}zEqA^Zgf(1+rQ>#k7%0000h5&w{Y-QlBkdy7eSyz8|k(w=syt3MbOGZFmTy2f|dnI3kj6Sz)H!` z%1hM3FiovS8#Qs98Rxs5^PSs#9S4da6*};44(Iv3@B5rb3BwQ$Is?0$0ilY#Q^EELL=6SfS*Ly93GR<|lPYvfPXoM^)HN1)!-B@N5Zi(e|Ge`rl{XLurIiz-D}wH selectedFunctions = new HashSet<>(); private BSimFilterPanel filterPanel; + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -57,6 +56,7 @@ public class BSimFilterPanelTest extends AbstractBSimPluginTest { filterPanel = BSimSearchDialogTestHelper.getFilterPanel(searchDialog); } + @Override @After public void tearDown() throws Exception { close(searchDialog); diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialogTestHelper.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialogTestHelper.java index 4792197296..ac1cb0ef01 100644 --- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialogTestHelper.java +++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialogTestHelper.java @@ -4,9 +4,9 @@ * 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. @@ -17,8 +17,7 @@ package ghidra.features.bsim.gui.search.dialog; import java.util.Set; -import ghidra.features.bsim.gui.BSimSearchPlugin; -import ghidra.features.bsim.gui.BSimSearchPluginTestHelper; +import ghidra.features.bsim.gui.*; import ghidra.features.bsim.query.BSimServerInfo; import ghidra.features.bsim.query.FunctionDatabase; import ghidra.features.bsim.query.facade.TestBSimServerInfo; diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/inmemory/BSimH2DatabaseManagerTest.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/file/BSimH2DatabaseManagerTest.java similarity index 98% rename from Ghidra/Features/BSim/src/test.slow/java/ghidra/query/inmemory/BSimH2DatabaseManagerTest.java rename to Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/file/BSimH2DatabaseManagerTest.java index 3878d4caa6..f3dd96c173 100644 --- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/inmemory/BSimH2DatabaseManagerTest.java +++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/file/BSimH2DatabaseManagerTest.java @@ -4,16 +4,16 @@ * 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.query.inmemory; +package ghidra.features.bsim.query.file; import static org.junit.Assert.*; @@ -26,7 +26,6 @@ import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.FunctionDatabase.Error; import ghidra.features.bsim.query.description.DatabaseInformation; -import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; import ghidra.features.bsim.query.protocol.CreateDatabase; import ghidra.features.bsim.query.protocol.ResponseInfo; @@ -50,7 +49,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe @After public void tearDown() { - //cleanup(); + cleanup(); } private File getTempDbDir() { @@ -77,7 +76,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe } private BSimServerInfo createDatabase(String databaseName, List tags, - List execats, String expectedError) { + List execats, String expectedError) { BSimServerInfo h2DbInfo = getBsimServerInfo(databaseName); Msg.debug(this, "Creating H2 File DB: " + h2DbInfo); diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTest.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTest.java similarity index 99% rename from Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTest.java rename to Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTest.java index b1073152bf..07154c507b 100755 --- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTest.java +++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTest.java @@ -4,16 +4,16 @@ * 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.query.test; +package ghidra.features.bsim.query.test; import static org.junit.Assert.*; diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTestUtil.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTestUtil.java similarity index 99% rename from Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTestUtil.java rename to Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTestUtil.java index 7a1e2f1728..d1ffa67c49 100755 --- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/query/test/BSimServerTestUtil.java +++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTestUtil.java @@ -4,16 +4,16 @@ * 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.query.test; +package ghidra.features.bsim.query.test; import java.io.*; From 27c162ee8fb7a3095a8555e8bdda8880529cb2c1 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Thu, 29 Aug 2024 07:00:13 -0400 Subject: [PATCH 25/53] GP-0: Skeleton's build.grade was interfering with building natives in a release --- Ghidra/Features/Base/ghidra_scripts/VSCodeProjectScript.java | 4 ++++ .../ghidraprojectcreator/utils/GhidraModuleUtils.java | 5 +++++ GhidraBuild/Skeleton/build.gradle | 5 ++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Ghidra/Features/Base/ghidra_scripts/VSCodeProjectScript.java b/Ghidra/Features/Base/ghidra_scripts/VSCodeProjectScript.java index 5295544354..3ee4ab24e9 100644 --- a/Ghidra/Features/Base/ghidra_scripts/VSCodeProjectScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/VSCodeProjectScript.java @@ -268,7 +268,11 @@ public class VSCodeProjectScript extends GhidraScript { } // Fix Ghidra installation directory path in build.gradle + File buildTemplateGradleFile = new File(projectDir, "buildTemplate.gradle"); File buildGradleFile = new File(projectDir, "build.gradle"); + if (!buildTemplateGradleFile.renameTo(buildGradleFile)) { + throw new IOException("Failed to rename: " + buildTemplateGradleFile); + } String fileData = FileUtils.readFileToString(buildGradleFile, StandardCharsets.UTF_8); fileData = fileData.replaceAll("", FilenameUtils.separatorsToUnix(installDir.getPath())); diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java index f4d93a2297..fefabd83db 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraModuleUtils.java @@ -177,6 +177,11 @@ public class GhidraModuleUtils { return excludeRegexes.stream().map(r -> Pattern.compile(r)).noneMatch( p -> p.matcher(f.getName()).matches()); }, null); + File buildTemplateGradleFile = new File(projectDir, "buildTemplate.gradle"); + File buildGradleFile = new File(projectDir, "build.gradle"); + if (!buildTemplateGradleFile.renameTo(buildGradleFile)) { + throw new IOException("Failed to rename: " + buildTemplateGradleFile); + } } catch (CancelledException | IOException e) { throw new IOException("Failed to copy skeleton directory: " + projectDir); diff --git a/GhidraBuild/Skeleton/build.gradle b/GhidraBuild/Skeleton/build.gradle index fffa507422..34d2737c3b 100644 --- a/GhidraBuild/Skeleton/build.gradle +++ b/GhidraBuild/Skeleton/build.gradle @@ -4,9 +4,9 @@ * 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. @@ -36,7 +36,6 @@ rootProject.assembleDistribution { exclude '.classpath' exclude '.project' exclude 'build.gradle' - rename "buildTemplate.gradle", "build.gradle" rename "gitignore", ".gitignore" into "Extensions/Ghidra/Skeleton" } From bc46e577b7607e8a9bddbb006c30b889def7abd5 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Thu, 29 Aug 2024 13:47:35 -0400 Subject: [PATCH 26/53] GP-4722: More OMF header markup --- .../omf/omf/OmfComdatExternalSymbol.java | 25 +++- .../bin/format/omf/omf/OmfComdefRecord.java | 117 ++++++++++++++---- .../bin/format/omf/omf/OmfExternalSymbol.java | 17 ++- .../bin/format/omf/omf/OmfFileHeader.java | 18 +-- .../ghidra/app/util/opinion/OmfLoader.java | 9 +- 5 files changed, 139 insertions(+), 47 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfComdatExternalSymbol.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfComdatExternalSymbol.java index 9090283020..e717b9811f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfComdatExternalSymbol.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfComdatExternalSymbol.java @@ -4,9 +4,9 @@ * 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. @@ -21,12 +21,17 @@ import java.util.List; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.format.omf.*; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; public class OmfComdatExternalSymbol extends OmfExternalSymbol { public record ExternalLookup(int nameIndex, int type) {} protected List externalLookups = new ArrayList<>(); + private record Reference(OmfIndex nameIndex, OmfIndex typeIndex) {} + private List refs = new ArrayList<>(); + public OmfComdatExternalSymbol(BinaryReader reader) throws IOException { super(reader, false); @@ -37,6 +42,7 @@ public class OmfComdatExternalSymbol extends OmfExternalSymbol { while (dataReader.getPointerIndex() < dataEnd) { OmfIndex nameIndex = OmfUtils.readIndex(dataReader); OmfIndex type = OmfUtils.readIndex(dataReader); + refs.add(new Reference(nameIndex, type)); externalLookups.add(new ExternalLookup(nameIndex.value(), type.value())); } } @@ -47,4 +53,19 @@ public class OmfComdatExternalSymbol extends OmfExternalSymbol { symbols.add(new OmfSymbol(name, ext.type, 0, 0, 0)); } } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + StructureDataType struct = new StructureDataType(OmfRecordTypes.getName(recordType), 0); + struct.add(BYTE, "type", null); + struct.add(WORD, "length", null); + for (Reference ref : refs) { + struct.add(ref.nameIndex.toDataType(), "logical_name_index", null); + struct.add(ref.typeIndex.toDataType(), "type_index", null); + } + struct.add(BYTE, "checksum", null); + + struct.setCategoryPath(new CategoryPath(OmfUtils.CATEGORY_PATH)); + return struct; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfComdefRecord.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfComdefRecord.java index f7b0367296..b117b6d78f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfComdefRecord.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfComdefRecord.java @@ -4,9 +4,9 @@ * 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. @@ -16,12 +16,21 @@ package ghidra.app.util.bin.format.omf.omf; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverter; import ghidra.app.util.bin.format.omf.*; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; public class OmfComdefRecord extends OmfExternalSymbol { + private record Reference(OmfString name, OmfIndex typeIndex, OmfCommunalLength communalLength1, + OmfCommunalLength communalLength2) {} + private List refs = new ArrayList<>(); + public OmfComdefRecord(BinaryReader reader, boolean isStatic) throws IOException { super(reader, isStatic); } @@ -34,38 +43,96 @@ public class OmfComdefRecord extends OmfExternalSymbol { byte dataType = dataReader.readNextByte(); int byteLength = 0; if (dataType == 0x61) { // FAR data, reads numElements and elSize - int numElements = readCommunalLength(dataReader); - int elSize = readCommunalLength(dataReader); - byteLength = numElements * elSize; + OmfCommunalLength numElements = new OmfCommunalLength(dataReader); + OmfCommunalLength elSize = new OmfCommunalLength(dataReader); + byteLength = numElements.value * elSize.value; + refs.add(new Reference(name, typeIndex, numElements, elSize)); } else { // Values 1 thru 5f plus 61, read the byte length - byteLength = readCommunalLength(dataReader); + OmfCommunalLength communalLength = new OmfCommunalLength(dataReader); + byteLength = communalLength.value; + refs.add(new Reference(name, typeIndex, communalLength, null)); } symbols.add(new OmfSymbol(name.str(), typeIndex.value(), 0, dataType, byteLength)); } } - private static int readCommunalLength(BinaryReader reader) throws OmfException, IOException { - int val = reader.readNextByte() & 0xff; - if (val <= 128) { - return val; + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + StructureDataType struct = new StructureDataType(OmfRecordTypes.getName(recordType), 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.typeIndex.toDataType(), "type_index", null); + struct.add(BYTE, "data_type", null); + struct.add(ref.communalLength1.toDataType(), "communal_length", null); + if (ref.communalLength2 != null) { + struct.add(ref.communalLength2.toDataType(), "communal_length", null); + } } - if (val == 0x81) { - val = reader.readNextShort() & 0xffff; - } - else if (val == 0x84) { - val = reader.readNextShort() & 0xffff; - int hithird = reader.readNextByte() & 0xff; - val += (hithird << 16); - } - else if (val == 0x88) { - val = reader.readNextInt(); - } - else { - throw new OmfException("Illegal communal length encoding"); - } - return val; + struct.add(BYTE, "checksum", null); + + struct.setCategoryPath(new CategoryPath(OmfUtils.CATEGORY_PATH)); + return struct; } + /** + * A OMF COMDEF "communal length" + */ + private static class OmfCommunalLength implements StructConverter { + + private int numBytes; + private int value; + + public OmfCommunalLength(BinaryReader reader) throws OmfException, IOException { + long origIndex = reader.getPointerIndex(); + int b = reader.readNextUnsignedByte(); + if (b <= 128) { + value = b; + } + else if (b == 0x81) { + value = reader.readNextUnsignedShort(); + } + else if (b == 0x84) { + value = reader.readNextUnsignedShort(); + int hithird = reader.readNextUnsignedByte(); + value += (hithird << 16); + } + else if (b == 0x88) { + value = reader.readNextInt(); + } + else { + throw new OmfException("Illegal communal length encoding"); + } + numBytes = (int)(reader.getPointerIndex() - origIndex); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + StructureDataType struct = + new StructureDataType(OmfCommunalLength.class.getSimpleName(), 0); + switch (numBytes) { + case 1: + struct.add(BYTE, "value", null); + break; + case 3: + struct.add(BYTE, "type", null); + struct.add(WORD, "value", null); + break; + case 4: + struct.add(BYTE, "type", null); + struct.add(Integer3DataType.dataType, "value", null); + break; + case 5: + struct.add(BYTE, "type", null); + struct.add(DWORD, "value", null); + break; + + } + struct.setCategoryPath(new CategoryPath(OmfUtils.CATEGORY_PATH)); + return struct; + } + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfExternalSymbol.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfExternalSymbol.java index 08102f6ae5..76c6ec711b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfExternalSymbol.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfExternalSymbol.java @@ -4,9 +4,9 @@ * 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. @@ -29,8 +29,7 @@ public class OmfExternalSymbol extends OmfRecord { private boolean isStatic; protected List symbols = new ArrayList<>(); - private record Reference(OmfString name, OmfIndex type) {} - + private record Reference(OmfString name, OmfIndex typeIndex) {} private List refs = new ArrayList<>(); public OmfExternalSymbol(BinaryReader reader, boolean isStatic) throws IOException { @@ -42,9 +41,9 @@ public class OmfExternalSymbol extends OmfRecord { public void parseData() throws IOException, OmfException { while (dataReader.getPointerIndex() < dataEnd) { OmfString name = OmfUtils.readString(dataReader); - OmfIndex type = OmfUtils.readIndex(dataReader); - refs.add(new Reference(name, type)); - symbols.add(new OmfSymbol(name.str(), type.value(), 0, 0, 0)); + OmfIndex typeIndex = OmfUtils.readIndex(dataReader); + refs.add(new Reference(name, typeIndex)); + symbols.add(new OmfSymbol(name.str(), typeIndex.value(), 0, 0, 0)); } } @@ -62,8 +61,8 @@ public class OmfExternalSymbol extends OmfRecord { 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(ref.name.toDataType(), "external_name", null); + struct.add(ref.typeIndex.toDataType(), "type_index", null); } struct.add(BYTE, "checksum", null); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfFileHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfFileHeader.java index efbdef641d..e12532d272 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfFileHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfFileHeader.java @@ -4,9 +4,9 @@ * 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. @@ -324,19 +324,22 @@ public class OmfFileHeader extends OmfRecord { * @throws IOException for problems reading data * @throws OmfException for malformed records */ - public static OmfFileHeader parse(AbstractOmfRecordFactory factory, TaskMonitor monitor, MessageLog log) - throws IOException, OmfException { + public static OmfFileHeader parse(AbstractOmfRecordFactory factory, TaskMonitor monitor, + MessageLog log) throws IOException, OmfException { OmfRecord record = factory.readNextRecord(); if (!(record instanceof OmfFileHeader header)) { throw new OmfException("Object file does not start with proper header"); } + if (!record.validCheckSum()) { + logRecord("Invalid checksum", record, log); + } header.records.add(header); OmfData lastDataBlock = null; while (true) { record = factory.readNextRecord(); if (!record.validCheckSum()) { - throw new OmfException("Invalid checksum!"); + logRecord("Invalid checksum", record, log); } header.records.add(record); @@ -358,7 +361,7 @@ public class OmfFileHeader extends OmfRecord { } else if (record instanceof OmfComdefRecord comdef) { header.evaluateComdef(comdef); - header.externsymbols.add((OmfExternalSymbol) record); + header.externsymbols.add(comdef); } else if (record instanceof OmfComdatExternalSymbol comdat) { comdat.loadNames(header.nameList); @@ -494,7 +497,8 @@ public class OmfFileHeader extends OmfRecord { } private static void logRecord(String description, OmfRecord record, MessageLog log) { - log.appendMsg(description + " (" + record + ")"); + log.appendMsg("%s (0x%x - %s @ 0x%x)".formatted(description, record.getRecordType(), + OmfRecordTypes.getName(record.getRecordType()), record.getRecordOffset())); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/OmfLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/OmfLoader.java index 47bf7775c9..e1d1f6049f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/OmfLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/OmfLoader.java @@ -4,9 +4,9 @@ * 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. @@ -68,6 +68,7 @@ public class OmfLoader extends AbstractProgramWrapperLoader { case String s when s.startsWith("CodeGear") -> "codegearcpp"; case String s when s.equals("MS C") -> "windows"; case String s when s.startsWith("Watcom") -> "watcom"; + case null -> null; default -> null; }; } @@ -120,9 +121,9 @@ public class OmfLoader extends AbstractProgramWrapperLoader { header.sortSegmentDataBlocks(); OmfFileHeader.doLinking(IMAGE_BASE, header.getSegments(), header.getGroups()); } - catch (OmfException ex) { + catch (OmfException e) { if (header == null) { - throw new IOException("OMF File header was corrupted"); + throw new IOException("OMF File header was corrupted. " + e.getMessage()); } log.appendMsg("File was corrupted - leaving partial program " + provider.getName()); } From 86c126b7f697d3f51e33591686bb4a9a9fd831e3 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Thu, 29 Aug 2024 18:56:49 -0400 Subject: [PATCH 27/53] GP-4849 corrected typo in pspec --- Ghidra/Processors/PowerPC/data/languages/ppc_32_mpc8270.pspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ghidra/Processors/PowerPC/data/languages/ppc_32_mpc8270.pspec b/Ghidra/Processors/PowerPC/data/languages/ppc_32_mpc8270.pspec index 409b563b24..6f223ab6d6 100644 --- a/Ghidra/Processors/PowerPC/data/languages/ppc_32_mpc8270.pspec +++ b/Ghidra/Processors/PowerPC/data/languages/ppc_32_mpc8270.pspec @@ -303,7 +303,7 @@ don't know about the DCRs though - + From 7f7559df567c4a3ebbe8ac928a08f429b925abdf Mon Sep 17 00:00:00 2001 From: ghidragon <106987263+ghidragon@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:50:20 -0400 Subject: [PATCH 28/53] GP-4559 Creating new Memory Search Feature that include dynamic change detection --- .../api/tracemgr/DebuggerCoordinates.java | 14 +- .../core/debug/gui/DebuggerByteSource.java | 112 ++ .../gui/DebuggerSearchRegionFactory.java | 130 +++ .../gui/listing/DebuggerListingProvider.java | 17 +- .../memory/DebuggerMemoryBytesProvider.java | 17 +- ...bstractDBTraceMemoryManagerMemoryTest.java | 35 +- .../database/spatial/RStarTreeMapTest.java | 21 +- Ghidra/Features/Base/certification.manifest | 14 +- .../Base/data/base.icons.theme.properties | 5 + .../Search/Instruction_Mnemonic_Search.htm | 112 ++ .../topics/Search/Regular_Expressions.htm | 12 +- .../help/topics/Search/Search_Formats.htm | 1019 +++++++++++++++++ .../help/help/topics/Search/Search_Memory.htm | 906 ++++++++------- .../Search/images/MemorySearchProvider.png | Bin 0 -> 12965 bytes .../MemorySearchProviderWithOptionsOn.png | Bin 0 -> 24494 bytes .../MemorySearchProviderWithScanPanelOn.png | Bin 0 -> 15611 bytes .../Search/images/MultipleSelectionError.png | Bin 13196 -> 7482 bytes .../Search/images/SearchMemoryBinary.png | Bin 30935 -> 0 bytes .../Search/images/SearchMemoryDecimal.png | Bin 31941 -> 0 bytes .../topics/Search/images/SearchMemoryHex.png | Bin 30396 -> 0 bytes .../Search/images/SearchMemoryRegex.png | Bin 31361 -> 12815 bytes .../Search/images/SearchMemoryString.png | Bin 33994 -> 0 bytes .../main/java/ghidra/app/nav/Navigatable.java | 19 +- .../core/searchmem/MemSearchDialog.java | 5 +- .../core/searchmem/MemSearchPlugin.java | 51 +- .../app/services/MemorySearchService.java | 41 +- .../viewer/field/ArrayValuesFieldFactory.java | 38 +- .../bytesequence/AddressableByteSequence.java | 125 ++ .../memsearch/bytesequence/ByteSequence.java | 53 + .../bytesequence/ExtendedByteSequence.java | 98 ++ .../bytesource/AddressableByteSource.java | 59 + .../memsearch/bytesource/EmptyByteSource.java | 42 + .../bytesource/ProgramByteSource.java | 56 + .../bytesource/ProgramSearchRegion.java | 78 ++ .../memsearch/bytesource/SearchRegion.java | 52 + .../base/memsearch/combiner/Combiner.java | 123 ++ .../memsearch/format/BinarySearchFormat.java | 188 +++ .../memsearch/format/DecimalSearchFormat.java | 280 +++++ .../memsearch/format/FloatSearchFormat.java | 200 ++++ .../memsearch/format/HexSearchFormat.java | 244 ++++ .../memsearch/format/NumberParseResult.java | 24 + .../memsearch/format/RegExSearchFormat.java | 66 ++ .../base/memsearch/format/SearchFormat.java | 159 +++ .../memsearch/format/StringSearchFormat.java | 145 +++ .../gui/CombinedMatchTableLoader.java | 75 ++ .../gui/EmptyMemoryMatchTableLoader.java | 51 + .../memsearch/gui/FindOnceTableLoader.java | 94 ++ .../memsearch/gui/MemoryMatchHighlighter.java | 242 ++++ .../memsearch/gui/MemoryMatchTableLoader.java | 60 + .../memsearch/gui/MemoryMatchTableModel.java | 287 +++++ .../MemoryMatchToAddressTableRowMapper.java | 36 + ...yMatchToProgramLocationTableRowMapper.java | 37 + .../MemoryMatchtToFunctionTableRowMapper.java | 37 + .../memsearch/gui/MemoryScanControlPanel.java | 81 ++ .../gui/MemorySearchControlPanel.java | 452 ++++++++ .../memsearch/gui/MemorySearchOptions.java | 139 +++ .../gui/MemorySearchOptionsPanel.java | 317 +++++ .../memsearch/gui/MemorySearchPlugin.java | 252 ++++ .../memsearch/gui/MemorySearchProvider.java | 655 +++++++++++ .../gui/MemorySearchResultsPanel.java | 294 +++++ .../memsearch/gui/NewSearchTableLoader.java | 67 ++ .../gui/RefreshResultsTableLoader.java | 63 + .../base/memsearch/gui/SearchGuiModel.java | 287 +++++ .../base/memsearch/gui/SearchHistory.java | 79 ++ .../base/memsearch/gui/SearchMarkers.java | 113 ++ .../base/memsearch/gui/SearchSettings.java | 277 +++++ .../base/memsearch/matcher/ByteMatcher.java | 126 ++ .../memsearch/matcher/InvalidByteMatcher.java | 83 ++ .../MaskedByteSequenceByteMatcher.java | 179 +++ .../memsearch/matcher/RegExByteMatcher.java | 140 +++ .../memsearch/mnemonic}/MaskGenerator.java | 6 +- .../base/memsearch/mnemonic}/MaskValue.java | 6 +- .../mnemonic}/MnemonicSearchPlugin.java | 16 +- .../memsearch/mnemonic}/SLMaskControl.java | 7 +- .../features/base/memsearch/scan/Scanner.java | 71 ++ .../memsearch/searcher/AlignmentFilter.java | 36 + .../memsearch/searcher/CodeUnitFilter.java | 69 ++ .../base/memsearch/searcher/MemoryMatch.java | 114 ++ .../memsearch/searcher/MemorySearcher.java | 337 ++++++ .../util/search/memory/MemSearchResult.java | 14 + .../src/main/resources/images/view_bottom.png | Bin 0 -> 946 bytes .../main/resources/images/view_left_right.png | Bin 0 -> 822 bytes .../main/resources/images/view_top_bottom.png | Bin 0 -> 767 bytes .../src/main/resources/images/viewmag+.png | Bin 0 -> 1127 bytes .../src/main/resources/images/viewmag.png | Bin 0 -> 813 bytes .../core/searchmem/AbstractMemSearchTest.java | 307 ----- .../core/searchmem/MemSearchAsciiTest.java | 433 ------- .../core/searchmem/MemSearchBinaryTest.java | 559 --------- .../core/searchmem/MemSearchDecimal2Test.java | 668 ----------- .../base/memsearch/AbstractMemSearchTest.java | 289 +++++ .../base/memsearch/MemSearchAsciiTest.java | 342 ++++++ .../base/memsearch/MemSearchBinaryTest.java | 258 +++++ .../base/memsearch}/MemSearchHexTest.java | 347 ++---- .../base/memsearch/MemSearchNumbersTest.java | 473 ++++++++ .../base/memsearch}/MemSearchRegExTest.java | 150 +-- .../base/memsearch/MemSearchScanTest.java} | 131 ++- .../memsearch}/MnemonicSearchPluginTest.java | 53 +- .../bytesequence/ByteArrayByteSequence.java | 51 + .../ByteArrayByteSequenceTest.java | 134 +++ .../MaskedBytesSequenceByteMatcherTest.java | 116 ++ .../bytesequence/RegExByteMatcherTest.java | 86 ++ .../combiner/MemoryMatchCombinerTest.java | 345 ++++++ .../format/AbstractSearchFormatTest.java | 98 ++ .../format/BinarySearchFormatTest.java | 115 ++ .../format/DoubleSearchFormatTest.java | 204 ++++ .../format/FloatSearchFormatTest.java | 201 ++++ .../memsearch/format/HexSearchFormatTest.java | 200 ++++ .../format/Int1SearchFormatTest.java | 191 +++ .../format/Int2SearchFormatTest.java | 190 +++ .../format/Int4SearchFormatTest.java | 188 +++ .../format/Int8SearchFormatTest.java | 197 ++++ .../format/RegExSearchFormatTest.java | 86 ++ .../format/StringSearchFormatTest.java | 165 +++ .../format/UInt1SearchFormatTest.java | 189 +++ .../format/UInt2SearchFormatTest.java | 186 +++ .../format/UInt4SearchFormatTest.java | 187 +++ .../format/UInt8SearchFormatTest.java | 192 ++++ .../memsearch/searcher/MemSearcherTest.java | 337 ++++++ .../ghidra/feature/fid/plugin/IngestTask.java | 6 +- .../core/scl/SourceCodeLookupPlugin.java | 12 +- .../src/main/java/docking/ActionContext.java | 12 +- .../java/docking/DefaultActionContext.java | 10 +- .../src/main/java/docking/DockableHeader.java | 14 +- .../main/java/docking/menu/ButtonState.java | 73 ++ .../java/docking/menu/MultiStateButton.java | 414 +++++++ .../java/docking/widgets/PopupWindow.java | 252 ++-- .../widgets/combobox/GhidraComboBox.java | 28 +- .../table/threaded/IncrementalLoadJob.java | 9 +- .../table/threaded/ThreadedTableModel.java | 46 +- .../src/main/java/ghidra/util/task/Task.java | 6 +- .../model/address/AddressRangeSplitter.java | 85 ++ .../program/model/address/AddressSetView.java | 21 + .../address/AddressRangeSplitterTest.java | 128 +++ .../screenshot/MemorySearchScreenShots.java | 142 +-- 134 files changed, 15580 insertions(+), 3105 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerByteSource.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerSearchRegionFactory.java create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Search/Instruction_Mnemonic_Search.htm create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Formats.htm create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProvider.png create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithOptionsOn.png create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png delete mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryBinary.png delete mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryDecimal.png delete mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryHex.png delete mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryString.png create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/AddressableByteSequence.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/ByteSequence.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/ExtendedByteSequence.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/AddressableByteSource.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/EmptyByteSource.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramSearchRegion.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/SearchRegion.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/combiner/Combiner.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/BinarySearchFormat.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/DecimalSearchFormat.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/FloatSearchFormat.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/HexSearchFormat.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/NumberParseResult.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/RegExSearchFormat.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/SearchFormat.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/StringSearchFormat.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/CombinedMatchTableLoader.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/EmptyMemoryMatchTableLoader.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/FindOnceTableLoader.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchHighlighter.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableLoader.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchToAddressTableRowMapper.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchToProgramLocationTableRowMapper.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchtToFunctionTableRowMapper.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptions.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchPlugin.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/NewSearchTableLoader.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/RefreshResultsTableLoader.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchGuiModel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchHistory.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchMarkers.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchSettings.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/ByteMatcher.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/InvalidByteMatcher.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/MaskedByteSequenceByteMatcher.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java rename Ghidra/Features/Base/src/main/java/ghidra/{app/plugin/core/searchmem/mask => features/base/memsearch/mnemonic}/MaskGenerator.java (99%) rename Ghidra/Features/Base/src/main/java/ghidra/{app/plugin/core/searchmem/mask => features/base/memsearch/mnemonic}/MaskValue.java (97%) rename Ghidra/Features/Base/src/main/java/ghidra/{app/plugin/core/searchmem/mask => features/base/memsearch/mnemonic}/MnemonicSearchPlugin.java (95%) rename Ghidra/Features/Base/src/main/java/ghidra/{app/plugin/core/searchmem/mask => features/base/memsearch/mnemonic}/SLMaskControl.java (93%) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/scan/Scanner.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/AlignmentFilter.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/CodeUnitFilter.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemoryMatch.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java create mode 100644 Ghidra/Features/Base/src/main/resources/images/view_bottom.png create mode 100644 Ghidra/Features/Base/src/main/resources/images/view_left_right.png create mode 100644 Ghidra/Features/Base/src/main/resources/images/view_top_bottom.png create mode 100644 Ghidra/Features/Base/src/main/resources/images/viewmag+.png create mode 100644 Ghidra/Features/Base/src/main/resources/images/viewmag.png delete mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/AbstractMemSearchTest.java delete mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchAsciiTest.java delete mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchBinaryTest.java delete mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchDecimal2Test.java create mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/AbstractMemSearchTest.java create mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchAsciiTest.java create mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchBinaryTest.java rename Ghidra/Features/Base/src/test.slow/java/ghidra/{app/plugin/core/searchmem => features/base/memsearch}/MemSearchHexTest.java (60%) create mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchNumbersTest.java rename Ghidra/Features/Base/src/test.slow/java/ghidra/{app/plugin/core/searchmem => features/base/memsearch}/MemSearchRegExTest.java (62%) rename Ghidra/Features/Base/src/test.slow/java/ghidra/{app/plugin/core/searchmem/MemSearchDecimal1Test.java => features/base/memsearch/MemSearchScanTest.java} (51%) rename Ghidra/Features/Base/src/test.slow/java/ghidra/{app/plugin/core/searchmem => features/base/memsearch}/MnemonicSearchPluginTest.java (80%) create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequence.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequenceTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/combiner/MemoryMatchCombinerTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/AbstractSearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/BinarySearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/DoubleSearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/FloatSearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/HexSearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int1SearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int2SearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int4SearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int8SearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/RegExSearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/StringSearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt1SearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt2SearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt4SearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt8SearchFormatTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/searcher/MemSearcherTest.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/menu/ButtonState.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateButton.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressRangeSplitter.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/address/AddressRangeSplitterTest.java diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java index afb0ce07a5..1f63cff305 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java @@ -4,9 +4,9 @@ * 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. @@ -778,10 +778,18 @@ public class DebuggerCoordinates { return coords; } - public boolean isAlive() { + public static boolean isAlive(Target target) { return target != null && target.isValid(); } + public boolean isAlive() { + return isAlive(target); + } + + public static boolean isAliveAndPresent(TraceProgramView view, Target target) { + return isAlive(target) && target.getSnap() == view.getSnap(); + } + protected boolean isPresent() { TraceSchedule defaultedTime = getTime(); return target.getSnap() == defaultedTime.getSnap() && defaultedTime.isSnapOnly(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerByteSource.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerByteSource.java new file mode 100644 index 0000000000..3080d36a0b --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerByteSource.java @@ -0,0 +1,112 @@ +/* ### + * 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.debug.gui; + +import java.util.List; +import java.util.concurrent.*; +import java.util.stream.Stream; + +import db.Transaction; +import ghidra.app.nav.Navigatable; +import ghidra.app.plugin.core.debug.gui.action.DebuggerReadsMemoryTrait; +import ghidra.debug.api.target.Target; +import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.features.base.memsearch.bytesource.AddressableByteSource; +import ghidra.features.base.memsearch.bytesource.SearchRegion; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.trace.model.memory.*; +import ghidra.trace.model.program.TraceProgramView; + +/** + * A byte source for searching the memory of a possibly-live target in the debugger. + * + *

+ * Because we'd like the search to preserve its state over the lifetime of the target, and the + * target "changes" by navigating snapshots, we need to allow the view to move without requiring a + * new byte source to be constructed. We cannot, however, just blindly follow the + * {@link Navigatable} wherever it goes. This is roughly the equivalent of a {@link Program}, but + * with knowledge of the target to cause a refresh of actual target memory when necessary. + */ +public class DebuggerByteSource implements AddressableByteSource { + + private final PluginTool tool; + private final TraceProgramView view; + private final Target target; + private final DebuggerReadsMemoryTrait readsMem; + + public DebuggerByteSource(PluginTool tool, TraceProgramView view, Target target, + DebuggerReadsMemoryTrait readsMem) { + this.tool = tool; + this.view = view; + this.target = target; + this.readsMem = readsMem; + } + + @Override + public int getBytes(Address address, byte[] bytes, int length) { + AddressSet set = new AddressSet(address, address.add(length - 1)); + try { + readsMem.getAutoSpec() + .readMemory(tool, DebuggerCoordinates.NOWHERE.view(view).target(target), set) + .get(Target.TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + return view.getMemory().getBytes(address, bytes, 0, length); + } + catch (AddressOutOfBoundsException | MemoryAccessException | InterruptedException + | ExecutionException | TimeoutException e) { + return 0; + } + } + + @Override + public List getSearchableRegions() { + AddressFactory factory = view.getTrace().getBaseAddressFactory(); + List spaces = Stream.of(factory.getPhysicalSpaces()) + .filter(s -> s.getType() != AddressSpace.TYPE_OTHER) + .toList(); + if (spaces.size() == 1) { + return DebuggerSearchRegionFactory.ALL.stream() + .map(f -> f.createRegion(null)) + .toList(); + } + + Stream concat = + Stream.concat(Stream.of((AddressSpace) null), spaces.stream()); + return concat + .flatMap(s -> DebuggerSearchRegionFactory.ALL.stream().map(f -> f.createRegion(s))) + .toList(); + } + + @Override + public void invalidate() { + try (Transaction tx = view.getTrace().openTransaction("Invalidate memory")) { + TraceMemoryManager mm = view.getTrace().getMemoryManager(); + for (AddressSpace space : view.getTrace().getBaseAddressFactory().getAddressSpaces()) { + if (!space.isMemorySpace()) { + continue; + } + TraceMemorySpace ms = mm.getMemorySpace(space, false); + if (ms == null) { + continue; + } + ms.setState(view.getSnap(), space.getMinAddress(), space.getMaxAddress(), + TraceMemoryState.UNKNOWN); + } + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerSearchRegionFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerSearchRegionFactory.java new file mode 100644 index 0000000000..10e94f360e --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerSearchRegionFactory.java @@ -0,0 +1,130 @@ +/* ### + * 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.debug.gui; + +import java.util.List; + +import ghidra.features.base.memsearch.bytesource.SearchRegion; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; + +public enum DebuggerSearchRegionFactory { + FULL_SPACE("All Addresses", """ + Searches all memory in the space, regardless of known validity.""") { + + @Override + AddressSetView getAddresses(AddressSpace space, Program program) { + AddressSet set = new AddressSet(); + if (space != null) { + set.add(space.getMinAddress(), space.getMaxAddress()); + return set; + } + for (AddressSpace s : program.getAddressFactory().getAddressSpaces()) { + set.add(s.getMinAddress(), s.getMaxAddress()); + } + return set; + } + }, + VALID("Valid Addresses", """ + Searches listed memory regions in the space.""") { + + @Override + AddressSetView getAddresses(AddressSpace space, Program program) { + AddressSet set = new AddressSet(); + for (MemoryBlock block : program.getMemory().getBlocks()) { + if (space == null || space == block.getStart().getAddressSpace()) { + set.add(block.getAddressRange()); + } + } + return set; + } + + @Override + boolean isDefault(AddressSpace space) { + return space == null; + } + }, + WRITABLE("Writable Addresses", """ + Searches listed regions marked as writable in the space.""") { + + @Override + AddressSetView getAddresses(AddressSpace space, Program program) { + AddressSet set = new AddressSet(); + for (MemoryBlock block : program.getMemory().getBlocks()) { + if (block.isWrite() && + (space == null || space == block.getStart().getAddressSpace())) { + set.add(block.getAddressRange()); + } + } + return set; + } + }; + + public static final List ALL = List.of(values()); + + record DebuggerSearchRegion(DebuggerSearchRegionFactory factory, AddressSpace spaces) + implements SearchRegion { + @Override + public String getName() { + return factory.getName(spaces); + } + + @Override + public String getDescription() { + return factory.getDescription(spaces); + } + + @Override + public AddressSetView getAddresses(Program program) { + return factory.getAddresses(spaces, program); + } + + @Override + public boolean isDefault() { + return factory.isDefault(spaces); + } + } + + private final String namePrefix; + private final String description; + + private DebuggerSearchRegionFactory(String namePrefix, String description) { + this.namePrefix = namePrefix; + this.description = description; + } + + public SearchRegion createRegion(AddressSpace space) { + return new DebuggerSearchRegion(this, space); + } + + String getName(AddressSpace space) { + if (space == null) { + return namePrefix; + } + return "%s (%s)".formatted(namePrefix, space.getName()); + } + + String getDescription(AddressSpace spaces) { + return description; + } + + abstract AddressSetView getAddresses(AddressSpace space, Program program); + + boolean isDefault(AddressSpace space) { + return false; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java index f8a96384b7..211b88326f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java @@ -4,9 +4,9 @@ * 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. @@ -51,8 +51,7 @@ import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel; import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand; import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand.Reqs; import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin; -import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel; -import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.OpenProgramAction; import ghidra.app.plugin.core.debug.gui.action.*; @@ -76,6 +75,8 @@ import ghidra.debug.api.listing.MultiBlendedListingBackgroundColorModel; import ghidra.debug.api.modules.DebuggerMissingModuleActionContext; import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener; import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.features.base.memsearch.bytesource.AddressableByteSource; +import ghidra.features.base.memsearch.bytesource.EmptyByteSource; import ghidra.framework.model.DomainFile; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; @@ -1358,4 +1359,12 @@ public class DebuggerListingProvider extends CodeViewerProvider { .setViewerPosition(vp.getIndex(), vp.getXOffset(), vp.getYOffset()); }); } + + @Override + public AddressableByteSource getByteSource() { + if (current == DebuggerCoordinates.NOWHERE) { + return EmptyByteSource.INSTANCE; + } + return new DebuggerByteSource(tool, current.getView(), current.getTarget(), readsMemTrait); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java index 3931bce10e..1dddf5485c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java @@ -4,9 +4,9 @@ * 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. @@ -34,8 +34,7 @@ import docking.menu.MultiStateDockingAction; import docking.widgets.fieldpanel.support.ViewerPosition; import generic.theme.GThemeDefaults.Colors; import ghidra.app.plugin.core.byteviewer.*; -import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel; -import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction; import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; @@ -46,6 +45,8 @@ import ghidra.app.services.DebuggerTraceManagerService; import ghidra.debug.api.action.GoToInput; import ghidra.debug.api.action.LocationTrackingSpec; import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.features.base.memsearch.bytesource.AddressableByteSource; +import ghidra.features.base.memsearch.bytesource.EmptyByteSource; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoConfigStateField; @@ -666,4 +667,12 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi newProvider.panel.setViewerPosition(vp); }); } + + @Override + public AddressableByteSource getByteSource() { + if (current == DebuggerCoordinates.NOWHERE) { + return EmptyByteSource.INSTANCE; + } + return new DebuggerByteSource(tool, current.getView(), current.getTarget(), readsMemTrait); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java index 4af570456a..98f8bc8380 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java @@ -4,9 +4,9 @@ * 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. @@ -960,4 +960,35 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); } } + + @Test + public void testReplicateNpeScenario() throws Exception { + ByteBuffer buf4k = ByteBuffer.allocate(0x1000); + AddressSetView set = b.set( + b.range(0x00400000, 0x00404fff), + b.range(0x00605000, 0x00606fff), + b.range(0x7ffff7a2c000L, 0x7ffff7a33fffL)); + Random random = new Random(); + for (int i = 0; i < 30; i++) { + try (Transaction tx = b.startTransaction()) { + for (int j = 0; j < 3; j++) { + for (AddressRange r : set) { + for (AddressRange rc : new AddressRangeChunker(r, 0x1000)) { + if (random.nextInt(100) < 20) { + memory.setState(0, rc, TraceMemoryState.ERROR); + continue; + } + buf4k.position(0); + buf4k.limit(0x1000); + memory.putBytes(0, rc.getMinAddress(), buf4k); + } + } + } + } + + try (Transaction tx = b.startTransaction()) { + memory.setState(0, b.range(0, -1), TraceMemoryState.UNKNOWN); + } + } + } } diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/util/database/spatial/RStarTreeMapTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/util/database/spatial/RStarTreeMapTest.java index 94b92afbb9..0b832ef1a2 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/util/database/spatial/RStarTreeMapTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/util/database/spatial/RStarTreeMapTest.java @@ -4,9 +4,9 @@ * 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. @@ -1097,6 +1097,23 @@ public class RStarTreeMapTest { assertTrue(obj.map.isEmpty()); } + @Test + public void testAddThenRemove1() { + assertTrue(obj.map.isEmpty()); + + try (Transaction tx = obj.openTransaction("AddPoint")) { + obj.map.put(new ImmutableIntRect(0, 0, 0, 10), "Test"); + } + + assertFalse(obj.map.isEmpty()); + + try (Transaction tx = obj.openTransaction("RemovePoint")) { + assertTrue(obj.map.remove(new ImmutableIntRect(0, 0, 0, 10), "Test")); + } + + assertTrue(obj.map.isEmpty()); + } + @Test public void testClear() { List> points = generatePoints(rect(1, 12, 1, 12)); diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index c053587a20..0b966dd28b 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -508,8 +508,10 @@ src/main/help/help/topics/RuntimeInfoPlugin/RuntimeInfo.htm||GHIDRA||||END| src/main/help/help/topics/ScalarSearchPlugin/The_Scalar_Table.htm||GHIDRA||||END| src/main/help/help/topics/ScalarSearchPlugin/images/ScalarWindow.png||GHIDRA||||END| src/main/help/help/topics/ScalarSearchPlugin/images/SearchAllScalarsDialog.png||GHIDRA||||END| +src/main/help/help/topics/Search/Instruction_Mnemonic_Search.htm||GHIDRA||||END| src/main/help/help/topics/Search/Query_Results_Dialog.htm||GHIDRA||||END| src/main/help/help/topics/Search/Regular_Expressions.htm||GHIDRA||||END| +src/main/help/help/topics/Search/Search_Formats.htm||GHIDRA||||END| src/main/help/help/topics/Search/Search_Instruction_Patterns.htm||GHIDRA||||END| src/main/help/help/topics/Search/Search_Memory.htm||GHIDRA||||END| src/main/help/help/topics/Search/Search_Program_Text.htm||GHIDRA||||END| @@ -520,6 +522,9 @@ src/main/help/help/topics/Search/Searching.htm||GHIDRA||||END| src/main/help/help/topics/Search/images/DirectReferences.png||GHIDRA||||END| src/main/help/help/topics/Search/images/EncodedStringsDialog_advancedoptions.png||GHIDRA||||END| src/main/help/help/topics/Search/images/EncodedStringsDialog_initial.png||GHIDRA||||END| +src/main/help/help/topics/Search/images/MemorySearchProvider.png||GHIDRA||||END| +src/main/help/help/topics/Search/images/MemorySearchProviderWithOptionsOn.png||GHIDRA||||END| +src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png||GHIDRA||||END| src/main/help/help/topics/Search/images/MultipleSelectionError.png||GHIDRA||||END| src/main/help/help/topics/Search/images/QueryResultsSearch.png||GHIDRA||||END| src/main/help/help/topics/Search/images/SearchForAddressTables.png||GHIDRA||||END| @@ -536,11 +541,7 @@ src/main/help/help/topics/Search/images/SearchInstructionsIncludeOperands.png||G src/main/help/help/topics/Search/images/SearchInstructionsIncludeOperandsNoConsts.png||GHIDRA||||END| src/main/help/help/topics/Search/images/SearchInstructionsManualSearchDialog.png||GHIDRA||||END| src/main/help/help/topics/Search/images/SearchLimitExceeded.png||GHIDRA||||END| -src/main/help/help/topics/Search/images/SearchMemoryBinary.png||GHIDRA||||END| -src/main/help/help/topics/Search/images/SearchMemoryDecimal.png||GHIDRA||||END| -src/main/help/help/topics/Search/images/SearchMemoryHex.png||GHIDRA||||END| src/main/help/help/topics/Search/images/SearchMemoryRegex.png||GHIDRA||||END| -src/main/help/help/topics/Search/images/SearchMemoryString.png||GHIDRA||||END| src/main/help/help/topics/Search/images/SearchText.png||GHIDRA||||END| src/main/help/help/topics/Search/images/StringSearchDialog.png||GHIDRA||||END| src/main/help/help/topics/Search/images/StringSearchResults.png||GHIDRA||||END| @@ -919,6 +920,11 @@ src/main/resources/images/unlock.gif||GHIDRA||||END| src/main/resources/images/verticalSplit.png||GHIDRA||||END| src/main/resources/images/view-sort-ascending.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/view-sort-descending.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| +src/main/resources/images/view_bottom.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| +src/main/resources/images/view_left_right.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| +src/main/resources/images/view_top_bottom.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| +src/main/resources/images/viewmag+.png||Crystal Clear Icons - LGPL 2.1||||END| +src/main/resources/images/viewmag.png||GHIDRA||||END| src/main/resources/images/window.png||GHIDRA||||END| src/main/resources/images/wizard.png||Nuvola Icons - LGPL 2.1|||nuvola|END| src/main/resources/images/x-office-document-template.png||Tango Icons - Public Domain|||tango icon set|END| diff --git a/Ghidra/Features/Base/data/base.icons.theme.properties b/Ghidra/Features/Base/data/base.icons.theme.properties index 648f2fdb88..05628777bb 100644 --- a/Ghidra/Features/Base/data/base.icons.theme.properties +++ b/Ghidra/Features/Base/data/base.icons.theme.properties @@ -395,6 +395,11 @@ icon.base.util.xml.functions.bookmark = imported_bookmark.gif icon.base.util.datatree.version.control.archive.dt.checkout.undo = vcUndoCheckOut.png +icon.base.mem.search.panel.options = view_left_right.png +icon.base.mem.search.panel.scan = view_bottom.png +icon.base.mem.search.panel.search = view_top_bottom.png + + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/Instruction_Mnemonic_Search.htm b/Ghidra/Features/Base/src/main/help/help/topics/Search/Instruction_Mnemonic_Search.htm new file mode 100644 index 0000000000..7a99d6113a --- /dev/null +++ b/Ghidra/Features/Base/src/main/help/help/topics/Search/Instruction_Mnemonic_Search.htm @@ -0,0 +1,112 @@ + + + + + + + Search Memory + + + + + + +

+ +

Search for Matching Instructions

+ +
+

This action works only on a selection of code. It uses the selected instructions to build + a combined mask/value bit pattern that is then used to populate the search field in a + Memory Search Window. This enables searching through memory + for a particular ordering of + instructions. There are three options available: 

+ +
    +
  • Include Operands - All bits that make up the instruction and all bits that make + up the operands will be included in the search pattern.
  • + +
  • Exclude Operands - All bits that make up the instruction are included in the + search pattern but the bits that make up the operands will be masked off to enable wild + carding for those bits.
  • + +
  • Include Operands (except constants) - All bits that make up the instruction are + included in the search pattern and all bits that make up the operands, except constant + operands, which will be masked off to enable wild carding for those bits.
  • +
+ +
+

Example:

+ +

A user first selects the following lines of code. Then, from the Search menu they choose + Search for Matching Instructions and one of the following options:

+ +

+ Option 1: + +
+

If the Include Operands action is chosen then the search will find all + instances of the following instructions and operands.

+ +

+

+ +

All of the bytes that make up the selected code will be searched for exactly, with no + wild carding. The bit pattern 10000101 11000000 01010110 01101010 00010100 + 01011110 which equates to the byte pattern 85 c0 56 6a 14 5e is searched + for.
+
+

+
Option 2: + +
+

If the Exclude Operands option is chosen then the search will find all + instances of the following instructions only.

+ +

+

+ +

Only the parts of the byte pattern that make up the instructions will be searched for + with the remaining bits used as wildcards. The bit pattern 10000101 11...... 01010... + 01101010 ........ 01011... is searched for where the .'s indicate the wild carded + values.
+
+

+
Option 3: + +
+

If the Include Operands (except constants) option is chosen then the search + will find all instances of the instruction and all operands except the 0x14 which is a + constant.

+ +

+ +

The bit pattern 10000101 11000000 01010110 01101010 ........ 01011110 which + equates to the byte pattern 85 c0 56 6a xx 5e is searched for where xx can be any + number N between 0x0 and 0xff.
+
+

+
+
+ +

NoteThe previous operations can only work on a + single selected region. If multiple regions are selected, the following error dialog + will be shown and the operation will be cancelled.

+ +

+
+
+ +

Provided by: Mnemonic Search Plugin

+ +

Related Topics:

+ +
+
+
+ + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/Regular_Expressions.htm b/Ghidra/Features/Base/src/main/help/help/topics/Search/Regular_Expressions.htm index 34cbbacdbf..d3f0a4e88c 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Search/Regular_Expressions.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/Search/Regular_Expressions.htm @@ -19,17 +19,21 @@ same as a regular expression for any standard Java application. Because of restrictions on how regular expressions are processed, regular expression searches can only be performed in the forward direction. Unlike standard string searches, case sensitivity and unicode options do not - apply. The Search Memory dialog below shows a sample regular expression entered in the + apply. The Search Memory window below shows a sample regular expression entered in the Value field.

- + + +
+
+
- +
- +

Examples

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Formats.htm b/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Formats.htm new file mode 100644 index 0000000000..566d267613 --- /dev/null +++ b/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Formats.htm @@ -0,0 +1,1019 @@ + + + + + + + Search Memory + + + + + + +
+

Search Formats

+ +

The selected format determines how the user input is used to generate a search byte + sequence (and possibly mask byte sequence). They are also used to format bytes back into + "values" to be displayed in the table, if applicable.

+ +

Hex:

+ +
+

The hex format allows the user to specify the search bytes as hex values.

+ +

Notes:

+
+ +
    +
  • The input string is interpreted as a sequence of hex numbers, separated by spaces.
  • + +
  • Wildcard characters can be used to match any single hex digit (i.e. any 4 bit + value).
  • + +
  • Either the '.' or '?' character can be used for the wildcard character.
  • + +
  • Each hex number group (groups are separated by spaces) will produce a sequence of bytes + that may be reversed depending on the byte order. To avoid byte ordering effects, separate + each two digit hex value with a space.
  • + +
  • The byte search pattern is formed by concatenating the bytes from each hex number + group.
  • + +
  • The Hex format generates no "values" in the values table column (it would just be a + repeat of the bytes column).
  • + +
  • As a convenience, if a user enters a single wildcard value within the search text, then + the search string will be interpreted as if 2 consecutive wildcard characters were entered, + meaning to match any byte value.
  • + +
  • Similarly, if the search string contains an odd number of characters, then a 0 is + prepended to the search string, based on the assumption that a single hex digit implies a + leading 0 value.
  • +
+ +
+

Examples: (Little Endian)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
1212FF
12 A412 A4FF FF
12A4A4 12FF FF
12 345612 56 34FF FF FF
5 E1205 12 0EFF FF FF
5.50F0
.5050F
12.404 120F FF
+
+ +

Examples: (Big Endian)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
1212FF
12 A412 A4FF FF
12A412 A4FF FF
12 345612 34 56FF FF FF
5 E1205 0E 12FF FF FF
5.50F0
.5050F
12.412 04FF 0F
+
+
+ +

Binary:

+ +
+

The Binary format allows the user to specify the search bytes as binary + values.

+ +
+ +
    +
  • The input string is interpreted as a sequence of binary numbers.
  • + +
  • Binary values must always be specified in groups of up to 8 "0" or "1" digits.
  • + +
  • Since a group can have at most 8 characters, a group always represents 1 byte.
  • + +
  • Since a group can be only 1 byte, byte ordering doesn't affect the binary format.
  • + +
  • Wildcard characters can be used to match any single binary digit (i.e. any 1 bit + value).
  • + +
  • Either the '.' or '?' character can be used for the wildcard character.
  • + +
  • The byte search pattern is formed by concatenating the bytes from each binary number + group.
  • + +
  • The binary format generates no "values" in the values table column
  • + +
  • As a convenience, if a user enters less than 8 binary digits, it is assumed that the + leading bits are 0.
  • +
+ +
+

Examples:

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
1000000181FF
1103FF
0 1 000 01 00FF FF FF
0 1000 02FF FF
111.00.0E0ED
1 . 001 00 00FF 00 FF
+
+
+ +

String:

+ +
+

The String format allows the user to search to specify the search bytes as a string.

+ +
+ +
    +
  • The input string is converted to bytes using the chosen character encoding.
  • + +
  • If the Case Sensitive option is on, the bytes are translated exactly to the + string values and the masks bytes are all 0xFF.
  • + +
  • If the Case Sensitive option is off, the bytes are translated to the string values in + upper case and the masks are all 0xEF, meaning matches + will be found regardless of the case of the input string or the bytes in memory.
  • + +
  • If the Escape Sequences option is on, values such as "\n" or "\t" are translated to + their single byte equivalent escape value.
  • + +
  • If the Escape Sequences option is off, values such as "\n" or "\t" are + translated literally to those characters (e.g. the "\" char followed by the "n" char.
  • + +
  • The String format generates strings for the table's Match Value column.
  • + +
  • Wild cards are not supported by the String format.
  • +
+ +
+

Examples: (Encoding is Ascii, Case Sensitive is on, Escape Sequences is off)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
Hey048 65 79 30FF FF FF FF
Hey\n49 65 79 5c 6eFF FF FF FF FF
+
+ +

Examples: (Encoding is Ascii, Case Sensitive is off, Escape Sequences is off)

+ +
+ DF DF DF FF + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
Hey048 45 59 30
Hey\n49 65 79 5c 6eDF DF DF DF FF DF
+
+ +

Examples: (Encoding is Ascii, Case Sensitive is on, Escape Sequences is on)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
Hey048 65 79 30FF FF FF FF
Hey\n49 65 79 0AFF FF FF FF
+
+

Examples: (Encoding is UTF-16, Case Sensitive is on, Escape Sequences is off, Little Endian)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
Hey48 00 65 00 70 00 79 00FF FF FF FF FF FF
a\n61 00 5c 00 6e 00FF FF FF FF FF FF
+
+ +

Examples: (Encoding is UTF-16, Case Sensitive is on, Escape Sequences is off, Big Endian)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
Hey00 48 00 65 00 70 00 79FF FF FF FF FF FF
a\n00 61 00 5c 00 6eFF FF FF FF FF FF
+
+
+ +

Reg Ex:

+ +
+

The Reg Ex format allows the user to search memory for strings using Java regular + expressions.

+ +
+ +
    +
  • The Reg Ex format treats consecutive bytes in memory as if it was a string and uses the + input string as the specification for the regular expression.
  • + +
  • The Reg Ex format generates no bytes and masks, instead using Java's regular expression + engine to try and find matches.
  • + +
  • The Reg Ex format generates strings for the table's Match Value column.
  • + +
  • For more information on supported regular expression syntax, see the page on + Regular Expressions +
+ +

Decimal:

+ +
+

The Decimal format allows the user to search for a sequence of decimal values.

+ +
+ +
    +
  • The input string can have one or more decimal values, separated by + spaces.
  • + +
  • Each decimal value in the input text, are converted to a sequence of bytes. The number + of bytes for each value is determined by the size as specified in the decimal options. + +
  • If the Unsigned option is on, negative number can't be entered.
  • + +
  • If the Unsigned option is on, input numbers can be as big as the largest + unsigned value that can be represented by the selected byte size. For example, if the + byte size is 1, the largest unsigned value you can enter is 255.
  • + +
  • If the Unsigned option is off, input numbers can be from the lowest negative + number to the highest positive number for values that can be represented by the selected + byte size. For example, if the byte size is 1, the entered values can be from -128 to 127. + +
  • The endian setting affects the order of the bytes generated for decimal values.
  • + +
  • The Decimal format does not support wildcards.
  • + +
  • The byte search pattern is formed by concatenating the bytes from each decimal + number entered in the input.
  • + +
  • The Decimal format displays decimal values in the table's Match Value column.
  • +
+ +
+

Examples: (Size = 1 byte, Signed Values)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
-1 0 127FF 0 7FFF FF FF
+
+ +

Examples: (Size = 2 byte, Signed Values, Little Endian)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
-1 0 32767FF FF 00 00 FF 7FFF FF FF FF FF FF
+
+ +

Examples: (Size = 2 byte, Signed Values, Big Endian)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
-1 0 32767FF FF 00 00 7F FFFF FF FF FF FF FF
+
+

Examples: (Size = 4 byte, Signed Values, Little Endian)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
-1 5FF FF FF FF 05 00 00 00FF FF FF FF FF FF FF FF
+
+ +

Examples: (Size = 4 byte, Signed Values, Big Endian)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
-1 5FF FF FF FF 00 00 00 05FF FF FF FF FF FF
+
+ +

Examples: (Size = 8 byte, Signed Values, Little Endian)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
-1FF FF FF FF FF FF FF FFFF FF FF FF FF FF FF FF
505 00 00 00 00 00 00 00FF FF FF FF FF FF FF FF
+
+ +

Examples: (Size = 8 byte, Signed Values, Big Endian)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
-1FF FF FF FF FF FF FF FFFF FF FF FF FF FF FF FF
500 00 00 00 00 00 00 05FF FF FF FF FF FF FF FF
+
+ +

Examples: (Size = 1 byte, Unsigned Values)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
0 2560 FFFF FF
+
+

Examples: (Size = 2 byte, Unsigned Values, Little Endian)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
505 00FF FF
65535FF FFFF FF
+
+ +

Examples: (Size = 2 byte, Unsigned Values, Big Endian)

+ +
+ + + + + + + + + + + + + + + + +
50 05FF FF
65535FF FFFF FF
+
+ +

Examples: (Size = 4 byte, Unsigned Values, Little Endian)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
505 00 00 00FF FF FF FF
+
+ +

Examples: (Size = 4 byte, Unsigned Values, Big Endian)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
500 00 00 05FF FF FF FF FF
+
+
+ + +
+

Examples: (Size = 8 byte, Unsigned Values, Little Endian)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
505 00 00 00 00 00 00 00FF FF FF FF FF FF FF FF
+
+ +

Examples: (Size = 8 byte, Unsigned Values, Big Endian)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
500 00 00 00 00 00 00 05FF FF FF FF FF FF FF FF
+
+
+ + +

Float:

+ +
+

The Float format allows the user to enter floating point numbers of size 4 bytes.

+ +
+ +
    +
  • The input string is interpreted as a sequence of floating point numbers.
  • + +
  • Each floating point number is converted to 4 bytes.
  • + +
  • The Endian setting affects the order of the bytes.
  • + +
  • Wildcard characters are not supported.
  • + +
  • The byte search pattern is formed by concatenating the bytes from each floating point + number.
  • + +
  • The Float format generates floating point numbers in the values table column
  • +
+ +
+

Examples: (Little Endian)

+ +
+ + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
3.14C3 F5 48 40FF FF FF FF
+
+

Examples: (Big Endian)

+
+ + + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
3.1440 48 F5 C3FF FF FF FF
+
+ +
+

Double:

+ +
+

The Double format allows the user to enter floating point numbers of size 8 bytes.

+ +
+ +
    +
  • The input string is interpreted as a sequence of floating point numbers.
  • + +
  • Each floating point number is converted to 8 bytes.
  • + +
  • The Endian setting affects the order of the bytes.
  • + +
  • Wildcard characters are not supported.
  • + +
  • The byte search pattern is formed by concatenating the bytes from each floating point + number.
  • + +
  • The Double format generates floating point numbers in the values table column
  • +
+ +
+

Examples: (Little Endian)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
3.141F 85 EB 51 B8 1E 09 40FF FF FF FF FF FF FF FF
+
+

Examples: (Big Endian)

+ +
+ + + + + + + + + + + + + + + + +
Input StringByte SequenceMask Bytes
3.1440 09 1E B8 51 EB 85 1FFF FF FF FF FF FF FF FF
+
+
+ +
+
+
+ + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Memory.htm b/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Memory.htm index 5e53b35fc7..33cb787bc1 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Memory.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Memory.htm @@ -12,467 +12,525 @@ -

Search Memory

- -

Search Memory locates sequences of bytes in program memory.  The search is based on a - value entered as hex numbers, decimal numbers or strings.  The byte sequence may contain - "wildcards" that will match any byte (or possibly nibble). String searching also allows for the - use of regular expression searches.

- -

To Search Memory:

-
-
    -
  1. From the Tool, select Search - Memory
  2. + -
  3. Enter a Hex String in the Value field
    - This will create a Hex Sequence for searching.
  4. +

    Search Memory

    -
  5. Choose "Next" to find the next occurrence
    -                               - - or -
    - Choose "Previous" to find the previous occurrence
    -                               - - or -
    - Choose "Search All" to find all occurrences.
  6. -
-
+

The memory search feature locates sequences of bytes in program memory. The search is + based on user entered values which can be specified in a variety of formats such as hex, + binary, string, and decimal values. The hex and binary formats support "wildcards" which can + match any bit or nibbles depending on the format. String search also supports the use of regular expression searches.

- -

Search Formats

- -
- -
- -

 

- -

Search Options

- -
-

Search

+

To create a new memory search window, select Search Memory from the main tool menu or use the default keybinding + "S".

+

By default, search windows and their + tabs display "Search Memory:" followed by the search string and the program name. This + can be changed by right-clicking on the title or table to change its name. (This is true + for all transient windows.)

+

Contents

-

Search Value

-
- -
    -
  • The value to search.  The values entered will be interpreted based on the - Format options.
  • -
- -
-

Hex Sequence

-
- -
    -
  • As the search value is entered, this field will display the exact hex byte sequence - that will be searched for in memory.
  • -
- -

Format

- -
-

Hex:

-
- -
    -
  • Value is interpreted as a sequence of hex numbers, separated by spaces.  Wildcard - characters can be used to match any single hex digit (i.e. any 4 bit value).  Either - the '.' or '?' character can be used for the wildcard character.
  • - -
  • Each hex number (separated by spaces) will produce a sequence of bytes that may be - reversed depending on the Byte Order.
  • - -
  • The byte search pattern is formed by concatenating the bytes from each hex number.
  • -
- -
-
-

Example:

- - - - - - - - - - - - - - - - - - - - - -
-

Value:   

-
-

 "1234 567 89ab"

-
-

Little Endian - Hex Sequence   

-
-

34 12 67 05 ab 89

-
-

Big Endian Hex - Sequence   

-
-

12 34 05 67 89 ab

-
- -

- As a convenience, if a user enters a single wildcard value within the search text, then - the search string will be interpreted as if 2 consecutive wildcard characters were - entered, meaning to match any byte value. -

-

- Similarly, if the search string contains an odd number of characters, then a 0 is prepended - to the search string, based on the assumption that a single hex digit implies a leading - 0 value. -

- - -
-
- -

 

- -
-

String:

- -
-

Value is interpreted as the specified character encoding. The center panel of the - Search Memory dialog shows the Format Options, described below.

- -

- -
    -
  • Encoding - Interprets strings by the specified encoding.  Note that - byte ordering determines if the high order byte comes first or last.
  • - -
  • Case Sensitive - Turning off this option will search for the string - regardless of case using the specified character encoding. Only applicable for English - characters.
  • - -
  • Escape Sequences - Enabling this option allows escape sequences in the - search value (i.e., allows \n to be searched for).
  • -
-
- -

Decimal:

- -
-

Value is interpreted as a sequence of decimal numbers, separated by spaces. The center - panel of the Search Memory dialog shows the Decimal Options, described below.

-
- -

- +
-

Binary:

+

Memory Search Window

-

Value is interpreted as a sequence of binary numbers, separated by spaces.  - Wildcard characters ('x' or '?' or '.') can be used to match any bit.

-
+

The Memory Search Window provides controls and options for entering search values and + and a table for displaying the search results. It supports both bulk searching and + incremental searching forwards and backwards. Also, users can perform additional searches + and combine those results with the existing results using set operations. The window also + has a "scan" feature which will re-examine the bytes of the current results set, looking + for memory changes at those locations (this is most useful when debugging). Scanning has an + option for reducing the results to those that either changed, didn't change, were + incremented, or were decremented.

-

+

 

-
    -
  • Only binary digits (0 or 1) or wildcard characters (*?.) are allowed to be - entered.
  • +

    Memory Search Window

    + -
  • The byte search pattern is formed by concatenating the bytes from each  - number.
  • - -
  • An additional Mask byte which is not shown, is generated for each search byte to handle - the wildcards.
  • -
- -

Regular Expression:

- -
-

Value is interpreted as a Java Regular Expression - that is matched against memory as if all memory was a string. Help on how to form regular - expressions is available on the Regular Expression - Help page.

-
- -

- -
    -
  • Regular Expressions can only be used to search forward in memory.
  • - -
  • No Hex Sequence is displayed for regular expressions.
  • -
-
- -

Memory Block Types

- -
    -
  • Selects which initialized memory blocks are searched. Ghidra now stores external - information from the program's file header in special memory blocks. These blocks do not live - in the program's address space, but instead are stored in the "OTHER" address space. Memory - blocks which would be found in an actual running version of the program are referred to as - "Loaded Memory Blocks."
  • - -
  • -
      -
    • Loaded Blocks - will search only "loaded" memory blocks (memory blocks that would - appear in an actual running instance of the program) and not "Other" information memory - blocks.
    • - -
    • All Blocks - will search all memory blocks including "Other" blocks.
    • -
    -
  • -
- -

 

- -

Selection Scope

- -
    -
  • Search All - If this option is selected, the search will search all memory in the - tool.
  • - -
  • Search Selection - If this option is selected, the search will be restricted to - the current selection in the tool. This option is only enabled if there is a current - selection in the tool.
  • -
- -

 

- -

Code Unit Scope

- -
-

Filters the matches based upon the code unit containing a given address.

- -
    -
  • Instructions - includes instruction code units in the search.
  • - -
  • Defined Data - includes defined data in the search.
  • - -
  • Undefined Data - includes undefined data in the search.
  • -
-
- -

Byte Order

- -
-

Sets the byte ordering for multi-byte values.  Has no effect on non-Unicode Ascii - values, Binary, or regular expressions.

- -

Little Endian - places low-order bytes first.
- For example, the hex number "1234" will generate the bytes "34" , "12".

- -

Big Endian - places high-order bytes first.
- For example, the hex number "1234" will generate the bytes "12", "34".

-
- -

Alignment

- -
    -
  • Generally the alignment defaults to 1, but can be set to any number greater than 0. The - search results will be limited to those that begin on the specified byte alignment. In other - words, an alignment of 1 will get all matching results regardless of the address where each - begins. An alignment of 2 will only return matching results that begin on a word aligned - address.
  • -
- -

 

- -

Searching

- -
    -
  • Next / Previous - Finds the next/previous occurrence of the byte pattern from the current - cursor location; if you mouse click in the Code Browser to move focus there, you can choose - Search Repeat Memory Search to go to the next/previous match - found.
  • - -
  • Search All - Finds all occurrences of the byte pattern in a Query Results display.
  • -
- -
-

For very large Programs that may take a - while to search, you can cancel the search at any time. For these situations, a progress bar - is displayed, along with a Cancel button. Click on the Cancel button to stop the search. 

- -

Dismissing the search dialog - automatically cancels the search operation.

-
- -

 

- -

Highlight Search Option

- -
-

You can specify that the bytes found in the search be highlighted in the Code Browser by - selecting the Highlight Search Results checkbox on the Search Options panel. To view - the Search Options, select Edit - Tool Options... from the tool menu, then select the Search node in the Options - tree in the Options dialog. You can also change the highlight color. Click on the color bar - next to Highlight Color to bring up a color chooser. Choose the new color, click on - the OK button. Apply your changes by clicking on the OK or Apply button - on the Options dialog. 

- -
-

Highlights are displayed for the last - search that you did. For example, if you bring up the Search Program Text dialog and search - for text, that string now becomes the new highlight string. Similarly, if you invoke cursor text - highlighting, that becomes the new highlight string.

-
- -

Highlights are dropped when you close the search dialog, or close the query results window - for your most recent search.

- -

- -
- -

Search for Matching Instructions

- -
-

This action works only on a selection of code. It uses the selected instructions to build - a combined mask/value bit pattern that is then used to populate the search field in the - Memory Search Dialog. This enables searching through memory for a particular ordering of - instructions. There are three options available: 

- -
    -
  • Include Operands - All bits that make up the instruction and all bits that make - up the operands will be included in the search pattern.
  • - -
  • Exclude Operands - All bits that make up the instruction are included in the - search pattern but the bits that make up the operands will be masked off to enable wild - carding for those bits.
  • - -
  • Include Operands (except constants) - All bits that make up the instruction are - included in the search pattern and all bits that make up the operands, except constant - operands, which will be masked off to enable wild carding for those bits.
  • -
- -
-

Example:

- -

A user first selects the following lines of code. Then, from the Search menu they choose - Search for Matching Instructions and one of the following options:

- -

- Option 1: +

Search Controls

-

If the Include Operands action is chosen then the search will find all - instances of the following instructions and operands.

+

At the top of the window as shown above, there are several GUI elements for initializing and + executing a memory byte search. These controls can be closed from the view after a search + to give more space to view results using the toolbar button.

-

-

+

Search Format Field:

-

All of the bytes that make up the selected code will be searched for exactly, with no - wild carding. The bit pattern 10000101 11000000 01010110 01101010 00010100 - 01011110 which equates to the byte pattern 85 c0 56 6a 14 5e is searched - for.
-
-

-
Option 2: +
+

This is a drop-down list of formats whose selected format determines how to + interpret the text in the Search Input Field. The format will convert the user's + input into a sequence of bytes (and possibly masks). Details on each format are + described in the Search Formats section.

+
+ +

Search Input Field:

+ +
+

Next to the Search Format drop-down, there is a text field where users can + enter one or more values to be searched. This field performs validation depending on + the active format. For example, when the format is Hex, users can only enter + hexadecimal values.

+
+ +

Previous Search Drop Down:

+ +
+

At the end of the input field, there is a drop-down list of previous searches. + Selecting one of these will populate the input field with that previous search input, + as well as the relevant settings that were used in that search such as the search + format.

+
+ +

Search Button:

+ +
+

Pressing the search button will initiate a search. When the results table is empty, + the only choice is to do an initial search. If there are current results showing in the + table, a drop-down will appear at the back of the button, allowing the user to combine + new search results with the existing results using set operations. See the + Combining Searches section + below for more details.

+
+ +

Byte Sequence Field:

+ +
+

This field is used to show the user the bytes sequence that will be search for based + on the format and the user input entered. Hovering on this field will also display the + masks that will be used (if applicable).

+
+ +

Selection Only Checkbox:

+ +
+

If there is a current selection, then this checkbox will be enabled and provide the + user with the option to restrict the search to only the selected addresses. Note that + there is an action that controls whether this option will be selected automatically if + a selection exists.

+
+
+ +

Scan Controls

-

If the Exclude Operands option is chosen then the search will find all - instances of the following instructions only.

+

The scan controls are used to re-examine search results, looking for values that have + changed since the search was initiated. This is primary useful when debugging. The + scan controls are not showing by default. Pressing the toolbar button will show them along the right side of the + window

-

-

+

 

-

Only the parts of the byte pattern that make up the instructions will be searched for - with the remaining bits used as wildcards. The bit pattern 10000101 11...... 01010... - 01101010 ........ 01011... is searched for where the .'s indicate the wild carded - values.
-
-

-
Option 3: +

Memory Search Window With Scan Controls Showing

+ +

Scan Values Button:

+ +
+

This button initiates a scan of the byte values in all the matches in the results + table. Depending on the selected scan option, the set of matches in the table may be + reduced based on the values that changed.

+
+ +

Scan Option Radio Buttons

+ +
+

One of the following buttons can be selected and they determine how the set of + current matches should be reduced based on changed values.

+ +
    +
  • Equals This option will keep all matches whose values haven't changed and + remove any matches whose bytes have changed.
  • + +
  • Not Equals This option will keep all matches whose values have changed and + will remove any matches whose bytes have not changed.
  • + +
  • Increased This option will keep all matches whose values have increased + and will remove any matches whose values decreased or stayed the same.
  • + +
  • Decreased This option will keep all matches whose values have decreased + and will remove any matches whose values increased or stayed the same.
  • +
+ +

The Increased or + Decreased options really only make sense for matches that represent numerical + values such as integers or floats. In other cases it makes the determination based on + the first byte in the sequence that changed, as if they were a sequence of 1 byte + unsigned values.

+ +

Another way to see changed bytes is + to use the Refresh toolbar action. This will + update the bytes for each search result and show them in red without reducing the set + of results.

+
+
+ +

Results Table

-

If the Include Operands (except constants) option is chosen then the search - will find all instances of the instruction and all operands except the 0x14 which is a - constant.

- -

- -

The bit pattern 10000101 11000000 01010110 01101010 ........ 01011110 which - equates to the byte pattern 85 c0 56 6a xx 5e is searched for where xx can be any - number N between 0x0 and 0xff.
-
-

+

The bottom part of the window is the search results table. Each row in the table + represents one search match. The table can contain combined results from multiple + searches. At the bottom of the results table, all the standard table filters are + available. The table has the following default columns, but additional columns can be + added by right-clicking on any column header.

+ +
    +
  • Location: Displays the address of the first byte in the matching + sequence.
  • + +
  • Match Bytes: Displays the bytes of the matching sequence. (Note: if you + refresh or scan, the bytes can change. Changed bytes will be displayed in red.)
  • + +
  • Match Value: Displays the matching bytes as a value where the value is + determined by the Search Format. Note that not all formats have a meaningful + value, in which case the column value will be empty.
  • + +
  • Label: Displays any labels that are present at the match address.
  • + +
  • Code Unit: Displays the instruction or data that the match address.
  • +
+ +

Options

+ +
+

The options panel is not displayed by default. Pressing the toolbar button will show them along the right side of the + window.

+
+ +

+  

+ +

Memory Search Window With Options Open

+ +

Byte Options

+
+

These are general options that affect most searches.

+
    +
  • Endianess: This chooses the byte ordering for values that are larger than + one byte. Big Endian orders the bytes most significant first. Little Endian orders the + bytes least significant first. +
  • Alignment: The alignment requires that matches must start on an address that + has an offset that is a multiple of the specified integer field. For example, an + alignment of two would require that the address have an even value.
  • +
+ +

Decimal Options

+ +
+

These options apply when parsing input as decimal values.

+ +
    +
  • Size: The size (in bytes) of the decimal values.
  • + +
  • Unsigned: If checked, the values will be interpreted as unsigned values + and the input field won't accept negative values.
  • +
+
+ +

String Options

+ +
+

These options apply when parsing input as string data.

+ +
    +
  • Encoding: Specified the char set used to convert between strings and + bytes. (ASCII, UTF8, or UTF 16)
  • + +
  • Case Sensitive: If unselected, causes mask bytes to be generated such that + the search will not be case sensitive. Otherwise, the bytes must match exactly the + input the user entered.
  • + +
  • Escape Sequences: Determines if standard escape sequences are interpreted + literally or not. For example, if checked, and the user enters "\n", the search will + look for an end of line character. If unchecked, the search will look for a "\" + followed by an "n". Supported escape sequences include "\n", "\r", "\b", "\f", "\0", + "\x##", "\u####", "\U#########".
  • +
+
+ +

Code Type Filters

+ +
+

These are filters that can be applied to choose what type(s) of code units to + include in the results. By default, they are all selected. The types are:

+ +
    +
  • Instructions: Include matches that start at or in an instruction.
  • + +
  • Defined Data: Include matches that start at or in a define data.
  • + +
  • Undefined Data: Include matches that start at or in undefined data.
  • +
+
+ +

Memory Regions

+ +
+

Choose one or more memory regions to search. The available regions can vary depending + on the context, but the default regions are:

+ +
    +
  • Loaded Blocks: These include all the memory blocks defined that are actually + part of a loaded executable binary. On by default.
  • + +
  • Other Blocks: These are other than loaded blocks, typically representing + file header data. Off by default.
  • +
+
+ + + + + +
+ +

Search Formats

+ +
+

The selected format determines how the user input is used to generate a search byte + sequence (and possibly mask byte sequence). They are also used to format bytes back into + "values" to be displayed in the table, if applicable.

+ +

See the page on Search Formats for full details on each + format.

+
+ +

Actions

+ +
+ + +

  Incremental Search Forward

+ +
+

This action searches forward in memory, starting at the address just after the current + cursor location. It will continue until a match is found or the highest address in the + search space is reached. It does not "wrap". If a match is found, it it is added to the + current table of results.

+
+ +

  Incremental Search Backwards

+ +
+

This action searches backwards in memory, starting at the address just before the + current cursor location. It will continue until a match is found or the lowest address in + the search space is reached. It does not "wrap". If a match is found, it it is added to + the current table of results.

+
+ +

  Refresh

+ +
+

This action will read the bytes again from memory for every match in the results + table, looking to see if any of the bytes have changed. If so, the Match Bytes and + Match Value columns will display the changed values in red.

+
+ +

  Toggle + Search Controls

+ +
+

This action toggles the search controls on or off.

+
+ +

  Toggle Scan + Controls

+ +
+

This action toggles the scan controls on or off.

+
+ +

  Toggle + Options Panel

+ +
+

This action toggles the options display on or off.

+
+ +

  Make Selection

+ +
+

This action will create a selection in the associated view from all the currently + selected match table rows.

+
+ +

  Toggle Single Click + Navigation

+ +
+

This action toggles on or off whether a single row selection change triggers + navigation in the associated view. If this option is off, the user must double-click on a + row to navigate in the associated view.

+
+ +

  Delete Selected + Table Rows

+ +
+

This action deletes all selected rows in the results match table.

+
+
+ +

Combining Searches

+ +
+

Results from multiple searches can be combined in various ways. These options are only + available once you have results in the table. Once results are present, the Search + Button changes to a button that has a drop down menu that allows you do decide how you + want additional searches to interact with the current results showing in the results table. + The options are as follows:

+ +

New Search

+ +
+ This option causes all existing result matches to be replaced with the results of the new + search. When this option is selected, the button text will show "New Search". +

This does not create a new + search memory window, but re-uses this window. To create a new + search window, you must go back to the search memory action from the main menu.

+
+ +

A union B

+ +
+ This option adds the results from the new search to all the existing result matches. When + this option is selected, the button text will show "Add To Search". +
+ +

A intersect B

+ +
+ This option will combine the results of the new search with the existing search, but only + keep results that are in both the existing result set and the new search result set. When + this option is selected, the button text will show "Intersect Search". +
+ +

A xor B

+ +
+ This option will combine the results of the new search with the existing search, but only + keep results that are in either the new or existing results, but not both. + When this option is selected, the button text will show "Xor Search". +
+ +

A - B

+ +
+ Subtracts the new results from the existing results. When + this option is selected, the button text will show "A-B Search". +
+ +

B - A

+ +
+ Subtracts the existing results from the new results. When this option is + selected, the button text will show "B-A Search". +
+ +

Many of these set operations only make + sense if you do advanced searches using wildcards. For example, if you do a search for + integer values of 5, it would make no sense to intersect that with a search for integer + values of 3. The sets are mutually exclusive, so the intersection would be empty. + Explaining how to take advantage of these options is beyond the scope of this document.

+
+ +

Search Forward/Backwards Using Global Actions

+ +
+

Once at least one search has been executed using the Memory Search Window, the search can be repeated in an + incremental fashion outside a search window using global actions in the main tool menu or + their assigned default keybindings.

+ + +

Search Memory Forwards:

+ +

This action will use the input data and settings from the last memory search and begin + searching forwards in memory starting at the cursor location in the associated Listing + display. If a match is found, the cursor in the Listing will be placed on the found match + location. To execute this action, select Search Search Memory Forwards from the main tool menu or press + F3 (the default keybinding.)

+ + +

Search Memory Backwards:

+ +

This action will use the input data and settings from the last memory search and begin + searching backwards in memory starting at the cursor location in the associated Listing + display. If a match is found, the cursor in the Listing will be placed on the found match + location. To execute this action, select Search Search Memory Backwards from the main tool menu or press + <Shift>F3 (the default keybinding.)

+
+ +

Highlight Search Options

+ +
+

You can control how the bytes found in the search be highlighted in the Code Browser by + selecting the Highlight Search Results checkbox on the Search Options panel. To view + the Search Options, select Edit + Tool Options... from the tool menu, then select the Search node in the + Options tree in the Options dialog. You can also change the highlight color. Click on the + color bar next to Highlight Color to bring up a color chooser. Choose the new color, + click on the OK button. Apply your changes by clicking on the OK or + Apply button on the Options dialog. 

+ +
+

Highlights are displayed for the + last search that you did. For example, if you bring up the Search Program Text dialog and + search for text, that string now becomes the new highlight string. Similarly, if you + invoke cursor + text highlighting, that becomes the new highlight string.

+
+ +

Highlights are removed when you close the search window.

+
+
-

NoteThe previous operations can only work on a - single selected region. If multiple regions are selected, the following error dialog - will be shown and the operation will be cancelled.

+

Provided by: Memory Search Plugin

-

-
+

Related Topics:

+ +

- -

Provided by: the MemSearchPlugin  

-   - -

Related Topics:

- - -
-
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProvider.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProvider.png new file mode 100644 index 0000000000000000000000000000000000000000..86aa4931eb372f2813082482f2cb461e9f7aecb7 GIT binary patch literal 12965 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

>rtwW{wu2s7E05kpPvGgErnWlvcvgJm>A-K5 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 literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png new file mode 100644 index 0000000000000000000000000000000000000000..94a13ce3baa43ae7ab149e61fbf5664d213c74b0 GIT binary patch literal 15611 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 literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MultipleSelectionError.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MultipleSelectionError.png index daf1fa2a52883204ac9770ffe9040d86600ec5c5..8d48fee05ac6d3e005f4d3f1ac89c00750ddfd32 100644 GIT binary patch literal 7482 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 literal 13196 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

aPo4$Rv0T+AgRTUv<9ezJFD0~hNh_u4 zx2-_m++WuYTW+plM3R)!iy)%uHO6~&r`6I zdISrk055|ei;ULygdIbG{Yl2@LM~PL3hXWVw7WOX!9(OPZyy)Mq1J*SQ3Yc#E=7tc&gyw? zoDFUUv-aUs2c?n2@JVE!7$z-|SHbTSpLbP+h{jMV0^S_TizX1m=xmM#H}DJgP;J2zYJ};9ZmaLysfh z)$Y6YkyL$rd;}gXxb06B!-)NzouWY8zlC}-4<$|9%w*?{h}HVm5X?LMTR#U$mQ!2~ TKN;A+Y+gx=D|{&zHT3^q!jv9e diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryBinary.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryBinary.png deleted file mode 100644 index def1ebe504f352a75318b513a7920fe2bd53f072..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30935 zcmZ^~cOcdM-#<^-vg77E9%V`LUWBo&cl@0}2iy+v~D9f>-2+1u}V z)OCG6_kI7q{z->(yvOVPe9YHV4K)Rl)AXltaBxVJ6lJw=a8CH(;NX@Jo`6?wj+h?c z;CLn~$x7*XjxHt;8Bi;r4to@w&)4I(P>YPabgG(Yo}5;;Tt_u;aL8Y~GB1SC(b0LP zd3pM+ZT-t+DM2nC87jh5SLqkIm=dZWb)cARLc6WXzt!ProX;SH`oG^H-FL^E--z8%D z`*%S>!IkmKwMhT{wEX=1=v1z-)NCV)`qR}XK1lLCvOl?hxcxnH-xOZvnjay;zG-!B zO$GbkFt64VE)kC=*|6WICKltEutjU9DVY;ckdWP zoPvUb32cK->b=F+z7 zOm@g-(f(#G-nk3!!)`n9pz}&gOI2d|aZ87a%p(T~iSpSfJT7xUT&b~@Z4r?!ojBC6?MI+K$_vY53{$&qv~R#<6jOH zn(l4NXZpmhH-~+;@8Q0oepTz0Q|8Z^3{E*#K~wA5EIEBzIXwq7ChX_*P6)dO+Xwcv zS4f6e=Ur#0+ui3fqJ6jpx!X;nD(98GYK@{~m*SKrS&el#dwZ1T>S7Pm#fF@eBffMh zJdK=+zASEG~GFoBLwaS1C<`4laGUk&=7*+`tl>I&U@?J6T*l=`$S`{qkkZyBN)v z+x)-kdDZXMZTH!#-m4aUkgtu|8mWBfzv0!ksT4C2fG({bZw`-NTD(|Ntp|Wapmp7Rq*pfOvdTS_cZ880}f!~i}Q@xFd zu)5s~Pc~;V&6^+BtvR*SZl;m?9#p2)bPW!RYOZW~J1#A41)*0XQju7Mhq>FM$D_4}hleMMPoS>Qe|lh%{L#5cfDaaN>2%Efe1dY#Cf*oZ z)oO36lW?Z-sq<3Y;>He(DU~Z#%s0rlRgszPYN+?kU%u|Fh1-pc*ooA-e=LhHKeBxC z8+GHT?8xWznciAAW_ytp4ceQn@-NRCdamLo(W`R3+JTX zjNf>i%`Y_WmdIzcIuKF1n7>jz6j`=cFS10swDa|7>U8wZK-c2pI{LM{`)2%ed5+BV zyZ7$3CA2uZ%*6MHd0DBkF0DDzCj_RRiPnq>JM*}3v^H2$-_cwqIbKe4BegEW@4;rp zLMHX5^n;J5+H5$?cTXx65l;q%JbB{Ju(^XOc6auP`Bi>;t$)Re{l%W@uawtTH2(K* zEI5BkRB8P(;jyD+$4&ECerru6DgWLD1%g z&G06TjO7#8knfwjyYB^3zNDNIZguKPs;ly&LX)T-#w$nhAdYrncz15@beCcQ76Uod!y=DKKIT#(Qi9*vEfTZ%Acbd zbo#Hmf0`|QwsuQltl;n}-$Czb<0@ZPB|ClIRRNBB*+NQYdv^`_?j$;oeYyE6dmu5| zS^Rz3!G)PjTN}CQoYZcIj|??@Hw6#18OBJfi+UNjI$Y>7^0=(p6n4$i1K=E{7g)pu zv>GHG&9hu=4&XbMgw%NxrAeP+hi_k0h_vMAlNj5+Oi=D0wBXevEP7XhiIj5Yp3*Qn zE`3aVMitW*Gxk}`<%*ctH^H{za2^W!i>Cz2%f^1)orxM)DN4L`pzDC}YR@A6xx6#c z+H=o!YRljD(^f3$NQ5b40DY0~^IVTb6M~cS$(ApZUKEa}OAPWC&Aw`r9y3fN`eZI} zRMRy#O}6RBWZpwyTotIe|23e9SC~m6)aXk~uMk)6>8r}x+S{+6RX!LHr)$K{mbY)- z&{9yOZhj&YhO?UNoD5OjM}&=X$_$?jycU-0-LltRTvldUvn6dHYdy13;=fB~Z>;7U zA4B`G;a>jIyt;Hzn^_(5ewTsq80kUZs{qEfQT|&~9%8Fr^-01__eTz2l$OSaFgc#n zy^x4mR48$1`y#d)>&(V3P3m{K)qOoPkK4}N|AWi(J#B_)!F#`-#QWQ}cc~!v1TjqH&1#K zMR~%kkBJd4Za7+6b0gjPix0{+zaV{MPrb!%^?U8k{eiyyk%hA%6S8K#`U>xotZNQl z?hP6`uMn##RX+6DpjDOLWe8s%t^M_MJp&0ML-Q+nO8sD^_f-xO5L?#$MJ%7HLc~uTp$> z_R+6w;^bL0yro)T*6{TAtSxb+&2h=wQ3oS0+Zi=lF)Lzt30f%?+3nno7rF&(tdz zeK0H4;`@VC?MK>CH{Y&8V4#zdk`l058U7dOi*QZYkQWmq{QYLyUvBF=(j-;1*{jW@ z9Ch>OTc#5~V!Gw-usmLwr(Y!QHs4>u(x)O&v;Nh4W$wza-@khbMw^=0+1OIO@CVtE z%%YwyJFBxB3qwcied?=%(Xp}RuHRc{J7X2(>f1*;iKnP&Ql${%pE^a# zEbgC-f~~OEFx0p@=ba}RHlHj z%%^OV>jx!@N{$?L=gyr_*K)_PO-v8_e)zTf2oluGmoK~GMbwp*y)vzHqhH$D*&S^< z9|e2rcZ__v+ZirUYp`@;ff#jNX=`OtT~QGoFg{-Sz~=KC*4|TR88LlX>i_4DB=>^)odK9k)@UpX3^Q-U+O$rdtbiLpg2`KOD&Lq6jw&eI$K-iyIGS-N^H9Q2D_1NtV6lFye3+qNF^nr zkF6~&)HF2TFH0P(Yrpj0Tr{+|UpPg2K6^IrbsDF6fmqm15Wk#e&~n}3hCo39>PDpf zeeaQ;9`f;ot9MT~s&Z2X#S1&qv!L7-^i)(-UOwFZ^$m?iH#RmZMR7zhOPEbFhgNuP z%!h`Ce)w>8ak$K0-`Uw2Ijdh}_S#{z+*!ir%R65ELY$zFA3vhHu6wO#7Z7DE4 zIz6|z=pgo58ND=+7h6(NqWFFFq*P*}%&)C23D;>^CYkcmQmVXf-@d)VZQK>8+oyQ? zy)82S&70=d*6FMTbV}g*FUc$B1&m^|*t@N%8&LQ-R~*TzuHNmwG|DO}=CRDEmzI{+ zbL8denar%^3OP^Q4|M)IOe?v0~A^Hakj5BOdibFpHO)JTGQpUvm_C0lU zs3=y4-Zt}1#_$=3d6zgjIfd<0-U_oVqes`=pFQn<^s9*!6k1q#I7vPgdgA?^Kt%z= z53-W_zscr_Q5vv*JyyQ*^%fTwU(XH*I8l7l`D=BtRU2E!Wc9<6{bxKsU_E8|V25po z3r2jbJQkFmdXhLF;m`T`urD`n-lU&PkqJ5BFdd$-zrQ~|ZsPCn?-23ysgjynqUgh` zY?oVTo)#nQ?C7YdF1+BbJ#m!`MHXNrJJwsJ7OaDCc9yu)7zC@%w938AX~M`WGtZmw zL~C!<4^+_dbEaD^wnzIvC0-`pc);;oZst2hTVvyNR+1kIMmDiPwaf2mxq}_VZ1z7( zg+J)I{;rz7qt7#h^iW@4UvqPFe?OI}sMnJ3-sV#u$+Ng_aEDRuOYk^$caz4!;$=y# zth|BR{^&RsHuSbM85UwFBXSbQ8rDohBaal#?8Exg71R_te7geU1*cE@F7j zH@>^^U~zFVkqPO)GfTthnWtCaLRE3XcCxnSy4ca~P+ff^zbR^cdBWJ|^pnq@Ki|H# zxVy2?cy9vbcAaeB5&?&e`2@q%^3O2k)tL*`8K|pyW+4AJ z>L2#$H(MO!gFQ(r^>pkq;0rM@cV1fTES{l>$usxCaf$wpoI8IW_(E0+D*^onw2EO? z0)NJ3@PhMfq=(-@r`KeodZ9}U>t9?E23%=Kd$-zl$Q?Q8_t3txvR8YO9EBYlRJwAQ zL=`V2f5wN4b3HjL%)YB#2|}E!dV4eHYc{PsN13F{zb1G718?Ow{HC8rKS{b!vldr7 zYZ^H-tI#)ew6l^pIey47o7MY)nQ#IXyjS|8ca^p z`)t+ECiz;em>n!I$xTzBx=rZH{Z4q0uiOWCCXSUyF;=Wf4E3&X{5(qcSNe2B13 zQk;tn<@WEd)byVtC!Da!kiZ%|s%Ak>6H;4DKn!w;RA};pmP?-S`=5emIg|T$3weqP z21tr#5I+{n092^{{(F4-au|v~*CECDBF4+htAjHl#C>_3j+$DxDKk1cn(=>N>I2dV z<8mjan@HP+0Q~dk&*S6cv#re!3}N=8Wnxl21z3)lN0)v0;I-BzGJAv1 zsC2l_-=8Gj($O*E)lh-))_h*k=-8OV7J8{KtNR(9AgsyYG+6qX?c)`$-x-{%zs=9j zPfb}^TE2SyT8+eK_g26mDT9(&d4bTZAzVwpwk;~ZlIOBRoB%Clg zDi^JKj9z$hD36upg@y2Ua6*R%dsUT{M1fXq&qZGyeysh=sjQOL0}&^<`4?>LKU*?a;|mEJ^L^>gq{1-FP0^AP`}z z8_|z^VNRNgVazJ;}hbE%%MOQ z+l`jm-MDcBu5KBCMqJ2QrmMojXGuu#d+P;T6h1zG{(K4@&8=Op!>wIhN=}cfLPSJ_ zQ$WSBIX}=jGGdsX@a`S3leDZCv1VR5FajR@_>4P@qhoAr{E7BTc6qsnre@DTp8iJk z)XIuK9L&5FjZvMSn4gHZw>M*BfpPhT_@)rj?zckrzor_Jd^hQsu*M*|Y<{9@>r5Pu z3ho33W6RFYPD&d2>(?*1HL~@eK7Fz*+FAQ)Y+%5$D=S?fl$e;fG+KuDx;x=kmY$K1 zkLXogIU>#7l)S+Uk{*pgM2((+T0qjGpA{EX>w2Bd&3%;|F6rN!uEfGWy$CbpBmsf4 zu&v%?bp@0cMMpK z(*Rg4y=qC~5x3{&oHNk6_7ur`Kmm!YJUq6}GTl^cWo2bu<(RvXjgZ0K-|1zMvy_W% z0)dx}oSynH0`15ZGUp_g0ehwY4?!sTljjIrq6<{D$#QQJm(U-|!_QBnqwq4v{y8 zmFJn5ni_v};I7ruy;NXc=Xdt(+0dtWczE7h%Y0HpU%#GDKG6ik;mHduwitx6A?0c_ zGVW?nT)m-FasXpb3yjKkyN~wO zce!9JrsSPAl}$~PkC0XamymoYfa}VYEBEf*>$U2Hwa;R{P@J8Wl?Ct0$;o;01ixdl z+&Rg}u6=!Tli9M@NS;?kwHSE-lq@lzhq~#LdnwQ~nOY7)_6SLUt{%)<`&edUdvw z?JHJL5l#?X;KK;JxGvts%Bpg5nJt)M5g2OoW2J?5Y(_hp94oMYlu0V~8+abrkU0pM ztt)VS27=@;gG|Ud$Nx-L&5d)BFkRt?a>oPr&zyxzi5zUtly)?mP*Tu&GuVU+vB1hEJcKa+c>j{E(3`+y0Wh;l_gpYvbeNqw@rLKcj-2 zWPyZc82zDt*6Pl>oM8r&4t&Ec#&*c?;5 zz`;S!z~DSun)9(b!%JKVBYYMEYUtMW>!p2|vLUl3>X>$gAHgIH_ddUQtoq0{bJv=;!iNnu}d3ip3_|(+mvUNKd&(o}mB8*~^LFQ}?5XQ!sJl`+4_O=KQarpa%^E-}}xpw17|)6;)RYsJZFo%h0*F5Mn7wvccf zy>{6dmQ+kkjL`tybkwRu7&-k!g)2KN>z#E>4+WCizGt*Nr!wx_nS!fG>YwV5!db*= zQ^^ZGUQkj}22yfYk{3=F)6>v!e(74D>&x&hzG**r>7`kzEjfQqGf|)oEdztSP<=~F z#)F?<5L>#})8yE7g_6f!9B2^UQpOERfA#9sy{@+)s#W_#!_vvlwc_I9qVSc`1OAsN zIc}5f-11mn94<~%tlORrPwneRFqjtV?Z5vxeO^v|8DQ-ZF5aUnQ|Dq;%bg|=1Gxhr z(2EB~M(AR-QOh(;OeC=YboD}pE-o&>q+dlv<;_R+GRpz1J*%myDRUCY8%W(aHIP2O z7$-CIV$7?yw)XlJa4RgSX_O5%*4Fw^_lJy3t39vq&&Z8-cB;%TK4-iUz_W|cv9nPS z>NVyUDtFfK^W2!%?Jx7)bAJe;Tjb@#>1Lt{0+ugd!816Iq4&Iml~&1e^c8X%y&&&_ zh9dB%b-E6IjSO&2n6=8i&dHgB*Iy9y1{s4%@cnzF#GsqRH`ae~vf!h7qPVZovNHv8 z0MWB*3q4SjSA@ddVAB2eGq7&Uj{R1>Jx9sFQ*{9jM4)6uX%4SCW?8pk<-$K zzM!hW-%nTJsw|h4lWVx>s4dO4gCg(xFKZJe3-@Zp2s!LI7tYuq9t zBD}ozFz?@!Gr!Xnku_v#hJaJ^PH!+RZ)6#I5gk%342 zUraqcJwXm;=qFv`=U%2jXNmdC7^DW~vWnZmB1zi6JE-vj5`mJCw zcBk5s5BxD1eOZEb=LwV$e;UCVS{D9Kc^<~ygM;VnOws_;@O+~@LoQyr^trJSYkjb7 zCj;7GLL^yGJV7n*?O>U;MlfU)pVde!D|3T|SFecl(IU*#O|HHZ%m|l;OQB!fYaIjn z)XRuHpzx*q^}v#QUCN4zWIGl2r8yKY86qAM7ZNH&&<}tc0NmoE{g9fdZ}uzojw7z> znwmS0dU3pMY;1^7VQ`V%PMeA;tE;!^KJ1D-JsV16yejl+6&^l0aXF0eZ3)@PbJW!M zk_fs`wbRDMMa$VTUZ?YWi)RqB%=RW|-9A_+A!q1P0k!i0fnX-=R@#gj# zVQy^TnT8<<(TOjXXkCcSign^wWUEXmY93-aNM{LLeDjJ${>X=?RtmDZ{fz>O;C27oBfS?!z zH_(vFm(gQm-gWYd7SwT~V#lAMTX5ypty|*a;-aFWvg{Y$^K{9n3)x|Q{P;m|>eSx$ zwh0Q!eeq(3BK!z4<>~F~$L}H-G%zp-T`e-L3if|+|Gx4yY3Yuc*eX?etSE(HA|)l& z($a!;{?;)s-#C#SN6!{Gf{C1Pf`q0ALMOlrDt2{s#fYwat=a1os2d0C8JkaA{CZYl ztf!ZGOL9&yHxylPuLpQ%sO>jOMAQLlQ{>yW8##bv`f2I|6aS;x_KSvw&ngAdFDbmTQ&_|(Q02~3gN)+n6ckY`vZxE9DUxlU1S5wIw zr*r3vaRca{fmwvT2xSErPQj~JXM9vuRDi?c5t1u(tli1new)jNoW1F@{oV)1rC=ae zTHoI^SaPd!>zz@slcc1ip=~mJ~)y$0~$BpapR3Y5v^YT-Dvi+)|=@VJ|b-2y3dV0|Eo(B9~@nPEI*a){61) zDBJ52qu}XUg$X_gile!uMK^~(dU1OCgH8AOIRZ}Qy~UDlVaL%@`yu|ti&FXshM;Dc z{|N~Rq>R^Dma_FMD4+2ARe{Y&d&2Y$w;3hwAF-ARQ(6Vxk_i%Uc4o$50yO|fWPSc7 zvVC~?`n79R{I*U`W;H5&aA~rG2`0c(tnye{nz!wRRfz$@gojQ} z{EhZRs8xDbS;A8Et2Bkk>5yJ?>;b>4cfm9okfUn6w-Q;kVQ!p6f`1tC?Ab5M@6V={ zPm#Z0ul7CM-vKoO36SFZ?<`=cqlHeQW(g+EzI1dLmE0rAm2?Pvz77G^p(eEiJ7xkNH1am|Voj%gN!Q;4-4T zSy_O~I1Lpt71j;AlA)+Jd?aZOE=b>=pvfU~wyjab7o?R~P!tgs*6S{&J!>la2|oU_ z5;+Q(vHegXwNUVrC*-J(j`nuRYX6^Km=2ns;2!@4X_=6-zH=F|PU97-iyC;V#N*_vrD`oNoRE}OEyefwEloS}t9@@0c!OZ-)a@8tuFi`cn2pqnc%akO7T z?^>)hoGq7RZ#`OK6M3$j>?Y`kiIx_*=4hk5`zrOM1gatF@0ygvT*-MTKI-i+Y43enS5m&R=9nyxqPG}5X#`}dw{*D*bnw9#QLSQw6vI|r5f{N6iGafoqrR# zgKrWNn7}3gJ1mtPTS4lkPcgpIW9H)WJ~A?rHab_4{R$)X)ncXFI+9owh|x2$x^suM zqt1Pa{tzZHHyl4VC4s01tnL3F*r9c!`-3PfUFnL)@1JLNa&T}kF=4#f4a4PdwNt>d z|L!ra1xN+XI)R)J`#B;la^b>Bay}nv#~4UPX&4nttXxeFLy!aL&%UW1i z*p|q{!I4RiEscaIQxhrr6T&2ccF zjz4E6?vKcXL66l3%@`@ZY#p8ny?lcnaLlCw2|$}lVO1nJ z8TGaBz;(v($z1L_d;(ZNa2B2lSi-@QH21=AvrkSmHcE4HW@Tq*uS$r&0*C$x@Cfa= z3{lwRB=aVLv_4gU(|AkE-R?v&t@p@#e7UWvN-aQKz22(=4i3bl&qG6Ts*87z zeX;bheOQDYmxv3cRz+B8F8Ywdw=6`S?MOR< z?Ck81ALqq=40trmxZj2yGSZy3jVH`lr&7esm#nX?2{NKC>s@N@>nj1-2^6lEt`}8v z#s^$I(^{U8A1pjc`%J%o_^{1aIW`aP}c0*5H#22n>UThC2t@vf>?7M`{?L(`T~-d z`{o}w^E#i~M4ewy5D^j45-W#7zvjv#HsvOffcOrN;unRaKFUxEEOe>?u4D!p8k_B< zyR^KkTH|H}XGI)_!D|)x$^4c?UYXft{&C@QgwhS!B9qDwkVIa9BIdy9=rMLO>7)H( z`TLmpbBY)}t~g6umfhY2Up|2i&B= zfE1Y$#i<@9a_>Sl_+a_wb>aYWVYloFC-(P!B=vXw0Odk+1`ADjdE|bnVg|D@&z?Pl zLM7yd3KFh&a0F@Xmtc6EJ>uf$m#6>I^Wm)5lBVzPpPfB{pD8)p%`O z6%@2Ok_Iw{7s5{)JWNh>&`izB@oXR&xNb~hV(o*UYA*W3&R}+YEwdj2zZsTqxS75e zcbOOZ30bw*!-v%8&T(*Zf&)J%O`}MFgw(Xh!hk2ZG02HJOn%?%dus%|IhUL!-XfXy z2Uudz&Uy3ptr75aLqqT_)MvgU2ycLOL(zi}N)GQ^nVPz6QIDgbpm65n zBCgjcxZ84>kh!une*JKlIJHPMUihg(Wss9lI6oIxy&UEHT-p^Z$Hx(DnQ)Z@36-(^ z!PC;xvfvq)uq@!BeVccHSO}i_1W2(1>|7(C zVA;lF8GQ>P>xjt6?v4%~KE9f~W9ShcIBE2=J4wQK_u7dRSUk6G+?b==i$qkQse#$x zm-IBf!Moj7Q>g9VLYz#3mF%1O!W&+mv4O?Vw$}EOfT;%8H*8-R`vXeB- zd9?KC(Id!9c6N5SYs2J(=>H10b!)G)SOJIiI|0~Y@9JZHP2h$p*pE^?e(~?G9{-Bd zT_LURkYh;A4n8~4*8d>gE7%Dx959*k^71!D)GOh%$k>;o~|eFjXgb>4>l(z zUc4ZaEroW3e$_$*@GFM!p1!^ZRq}wP;vUN;Mn*;(XFqp#60^FI_%jvEJ^QOdb?^e5 zw6rJ`cfewq_a8lj_l8^TqV?Uou*i#s%>5@uM^SK(T3cH|$>g>^hOPtFRXr}PpfeJU zF>F}8x;Dcn08cDA%DHwYvvhuX+S1y(6dVn&9k=5L8`Q2&)06+!N5K88t*orI?230snan2;d;0d$J@e1FbgxzjLAhPip;M$NBAVt_2g zz)--z&QG6!m*>e^T0_JzFg8||os9w0991ogf+5`*D-anG!6fQ=+2s~I0SiM#b-!B~ zvekwe#eF~)mA-%P0x1tHNKPd1EbP)CVM6)&`31|+v<6EK4GfflSzUhzi7bVBOGtb zgpd#7OQYG_6Z2zi8!C{)zs9S$^(8kZf4CUT0n$^W|7fH z2dsUUJ~2N2=VlJxLgIdb2c92N8*rlR2lH?EY`cJC{&=sk0)`3!J@a)JmTT8^jNmz* ztn;rsG(EG=QIeiMGhA#1o~LPzSK0KMtaN#%8dRrTVFZIKfZ^mn1Dr<;%?GnciRp^v zNiwcVdQT!Uunh6$VB%l-Kz$!+n! z!UF2icEg|>dU{-es{nPvjFe=pZ`v+NmjgaP&i_getj=3*^XdLHJ0fw{-Fr9}Iz~t7 zO?RP8W9GY|3*q5SgPjsuM!es=YW4pmS@4nMmBRLeXRa?!REe0Vir>4Cd>zrBeU8rv z2@xTznCToBOG`FPXw8&0=<`ePRkXWw7l>neS63I1kDiZ?#=rTgvDx){lh@;AFo?;W z(=#&>Ewv;kQTHS01PPeR*ofbXI2T?&kLQ~&oDxn5#(!RYu4Q%jZ7k!>oxmIiMN|_k zw?J|P$HuTJe;Eq>0$u|46P@ne<8Z+muVslH4Gk#?@~ZW^EXR4wBE_&6qaA`9CC?Vx!v7^_&3HS_3hR4 zUOqhBe+XV|%eTN}7-XbOH?NtbL3QRKwB6YJgXBXI6ciNlk>~lO^na_U;HB*KstA-h zPx^g%cLP*2?u>(@V}_3_4H+xhs33#{8XDtwqdWihpu&74&08Wx2hN_R#@_HxpXPwp zOS%Eu+3*oJGAml696IXvBNS^GN={A|q-TAs+w!vlJG52Lp-_|`Acvts>s}J%;2?;< zAj-blgfKBN0VkY-C9XZzNr2bS&hR1zf+VSQ&;ldHRtkKWn7m1|7^6@YYS$&`GpBFb z`p)FIYovl{c}}V4@+#hm6YGpRzFp~^N{d+6kDdfscD1Ro5$82@z~)E8BQIo}${<7( z&;R`CvAb^fu=b>BivV9)$09^n;E1H_vM|#QKFO=9x)tyxSGwHfLHYRT=;-h;?VaLj zg+#2&;#|B|h4YJVKXYC79=f)fuJY-4+^i3!$r5 zDN@Y9Hc2mbgW`*yUCzawrSbb+p0f1@e9|=1!(sOrCHzc&v#XZ{_U3Z&@saFYzb13` zk3wWak`#~|AbL_;Kwq@5e}*pE$>iy%_W$j0>*?uD7u=hHB#b5h%$YOre3Y->#@^7% zul`5A+S=NSx`jK^CtD%$hw0edlDo_r6cWOIc+LumGXq5W+4JZ8`n&Dg-BJyn!|(63 zH?O=$4DgtQhi>Y7X#fgi5!m<%Juc7X*5Tm_Se?leF#I2L!3#OiQe>Hh*dx7L0G?~( zG6cR3p52!(Um&WfD~--ePR4mBvpAfgLX%AXBFgN%@JVa|5zHbi-}+9hw7R~IjosLb zsY$*<+}p0dk6r5)c8-pGez=g%anl#B13^8O(B&OMsn$WyQ0Ca!Z^7?^p_W$a-i?eG z@H83WgD0!MS7MZ`<8z;z=012LRGm8TSzE~`CtSC=%(oP>|NWfUM2BqpPC_}Xa0a`0 z3^O}FKg!iwYfFaS*jrw{I=Q!b>ip*9CgJh!p9x64VKpU=UlaQoCn+Mn*EXBAVsEhL zIi(PJYAM^`MLo||*gYevbWAbUtzopv;bpHy9*hoUoG6uXM+ktuK>MQVzkbaALYKYp zUDajn@d=;_kVV5|o|2N%TZJI_GNJf~{HPM5{3a)s>Xu%mn`a5(12$!~CLa?i}7qv&w*Xg8^C z<&N&?NkLj(>8+*mN&X?9jk^vG)pmUxiE5}zRXkYh!4!Xl+4K6Zv`MS+8%dMi>zGd$ z&L~>whIxRD6+?G*bX*k@qFz%|d>|5&*S!+@nN=qEO0I{;YJEsV>o^u zR4fZ@$uA!kgsgo}yh)6`EtkK~K{MBn?%-$!Eym!{Muyp3p75@Jg!^a~f{yenMYCw# zcaxg0{?;GA&-G=6uY=8CRE8+?STRc{7hzYD%4zzJ!esNvf_Wsmm`P=d9x`VNGv_#c&$ zRnlvFiuqvj89TM}=WW@28PSpj!!fjwHDVVf5Qf&(AUV~BgN(TL;r;tJLiT#n($bZ8 z%3~cJ9i_CPDFFCg>NuuXt&%}J0jws)g)HHK7)~XE9L2J%a_cZoXWf2e;*_3!NsHhu zndH39xl;i#cbbA(^`vUwa6A72()KwhqplM@UB9TPruGFUR?k7$gvZk8T`Md0UNHP2 zOOumu_(OEdQqzkxEg}cp>1#Ge?R!OQgGFAW3%c*HxE?kKJ>Q|{9?b`a(f)#U(FCRu zLKcm0c;x#tinC&SNN7fU^KF?#RneMQPZ|9Q(=v4lNgR9N?q z`pdG}fF(g0{bIQ$+@FCfX!ItRG^9pC^FTb zN!s5$^&9_(@%c($;R81R`}&j^aiXaGeb5xb&R-o^kYIbT^s2;MB1cDrg`E%rf_^4w z8O#6+f56$7M}KklL#`^e&W|-vM{_iHt!<|^C%p;}4<6fUP(RUb=K<=NJT;ZKbv#DFagqlpX{QD|JV;CnuVlWewOY zh~@!nW#1TM7sMX{ywqoO4IS)gI00)v3c)z_k9 z^{-BPEX2j)&;>&Oujmu;D6-*C%9{IIO5}fQ=R~#aaM&dSds-hEP8{)Hym%2&)QyEH za+J>ZKqo~-LblPptrZD9a@Z^7k$oB4RbQe{fQ){ytz<9(+3uo5LfOdLzh~!{_#=P= zi^XinJQJjrRxgl1zy05BZC?0zZ|kDqq@w%8AVyVW7beyH-t@6DvW6sEK&hXRAOSC*0l7ptW?e8c|=S&jeU<{qG# z1n)h&CUPHv$o)5DgeEBORc$RTi$}Mo9zq9jSbru@_5G!g<^Qp>!7kXk;OW~98JaS% z3MRX{RP)b66&CzdNQ6`0L9yyfl0+Sh8K6hddkmZ9Js&-NsVgi_D318;@%=-rV+(No zfu~51!>W7&Q#4_}&$lNu;(LpNFG*)^#|x7rkAkL#MQ~D+3%DmEJt(0!7gh&23}R%q z{sq-24pn*yziMliq-?{yJA;FRd)H2w%l!bG{8>bV=Giwk5C}4ncg%q!nOp0#vkL1I zc)hWaQLb_Av=sN3-uyR)QI*NC@DWt#*-)LJLU9%76*i^t{!s0I!hadcO@bghuH1Cr z>8pGR=B;Ej7zrTN05t;3+QIJ4KH0+~rT-7^;_b+G8IdfEl>D@(=Yxh+hr;!6gL4hn zD6}{_3`=caQU#_au;s_b7I|vS@|0ZTy*Fr$y^pE%_{fEOi+4>~U%hmCGhjkU+iWW7 zrN$@oH#oH1gp=sKqp+W3WnVj z?{^zcF>^`NI0dyf{VZLjCRO(9mh_0ZSMd35H@@nzsakj~sgU1Qg6POby2n`&6Q{24 z9Ur=$>sz(ttWXPs{Fvg5oRHK3SccpIcDw z4sa(R;-V7Angk5FrzarzbOV>1lk-%)H!Rz9h96v~9@~L_v9p_lo-EYLcW!cFa$d>C13yy6%%N16 zvKL}OLj`+tQXsn|A_%)vcn(S4>AozUuAkYvJwKp&ytgBFk4ZWU+)CeoN#&Ff` z!RzCpnp-SMV|}odpq@B2Z9eoOjP>Z=Qup$LkNS&Ymw$}AY46h$q+CuDs3+I&L#J>m z8cyxidk9)eljVZ<5PpX@$WvxTan`k-E*K+!s(d#m~kE zUUc@RB>RQtcc+GR#HQyFuqzDq`Hfe32z(LY_fLQH;VbNmygodF6`gtbrjLZv7R|G! z{g%pi4$}jV*2e12`|d1}p376t_%-B$0pS1~fiYTZ@6mLE$Jd9zm#Do3TZ&kzI-f>H zDrABOd|CL{=7;KstG9PVI1uHrv%;!pQPOmDbW`>k8fZF*baSU4j3XAn4f-$dn&3ep zO@ni~_oZXmCKR$HF(4%AttM6q^!E5~G-83gyW_AC{PX z-`g#4n7DA5&<+F1|Kv7II|wg@F!Oea(;^)IQTVupsU$$#;lMF^S*{h|*{XcmEwot8?Rq_L% z>v+eG0=giuICcZ`r##Ioq@tws>GS9O?+{2I_qk(k+5hiZkEwr*hgIzeAD?~bJ(D;m zj*3>i3JVJh{u5Qfgfyvg@9OO2y?(tSSR&IX13-OD2omWnATGVVy}*I7RdL~4_IBc? zrqg~DhuuM`lP}PMNlvl+rg{ZN7Yn?+y*Jcy3QPkQo_f2vcDHkgPeDCnH)UaFW*zp$MaRU1ZU2veK|#&T z>?=?whU^VXvMi|qD%~wKzO7)#p&QTgkYD_Y`ps>Lx`TzD z*vUhS;#{56{&}Y+ps5hL4to>$~@nms>(2NvvN`j=S3F-yO|5s$Y^>b6 z9J>1+`M-zUhDEL+%ish*J^+ z5bg3lDRkNio6XiW;zmyTZkaw!02TXK$~>Y!InTDwASIl=p@<+|=?2swz)@qUMYt z?+QXMXGEtjh<|(3P44iMv%hD4&p(0^okw6wMxKx;dhO)TbxCqp8}LU?oH$XsO+!un zC=(VlmMO!jLcBV49LE6N0zE70I(@Pi7$H~|C3el|mhNjvj0QLC#u96{ z(?I*~EtS7iynQ?RtB}7t`gM@DI3&lL%5KTfuiTDD)|eREXcXG~zH0sYt*Vd^MmgyW z$@rmh(d!#Sf_|G3{41M>{`MpNeY0O1NX28<}X-_N5P>T^y~De!_V$|H@97pTtFPee3E2;3OF_Y!*o@2UUR2c+Be`~k zps;h6MX=NmJCtfSdLj6`MuyVPyshcLlM6u-cGHCBEk9h?*(O%UP;G-XnioM=C@Y_? z63zMEwDDozJv?zz!*8CN1le7+-1%bFLuliA?)0s9uZ4|!ey{giRvc+W5TgDc`1D8} zf_@p75wMzBP8|f#2qH8<#ySn;>O$2X+sXb)@vc5sGfkeg4w$%$poB!-ywveccU#Gt zj>OlbtbbOhdnnllz3ZKAZ*1ZKlBdO63(#!)L48spp$oMmgn>uv3ZMMX?&n|s2-f#g zTZ$0m{MZF9*iUh?~cD9$qu@1NIjYvJ^7 zk*B;2JGMfITFwM7IlPIr!o92MtcPivXR}NYU4w3-Vr8_;5)l*sUvu9bPj&zQohw-x zS=mI$$jT;L5lLlc7TKe4WRnqPMI3~zY*CWrAdx*Ho9xxGv*+P{9j@Q^dtLYAzW=!Y zyRU~o&UHGU^BL#!e!pJt=lbZ-N3!(cAnYt?RgDT*MMMr9)o$E)G1PnYkjQpM(X@5VroODKkKzjdmPc5KmDCyCfJkGZ` zWO7-X+&nf`o!^0=PRe(cK>Ixvxp$|b?J1H)v-w+7E9k>~8Wo4V?1ovL zRlr0&0eUvG>1Z-c-~&A!UEZ%&utQ=wRANY9h+qq0M=QdaHCXspo+}MiZUtbC%24*v z=+zC!4`3jrPU4YE2Jfo2=gyx8rDGZdReTv+;3%O)p=z_JeNn)@|LfR%(eq-41vq$p zW@$%28*N^d?{97<^!t6ZX?nyF;#l6oV24yJ1EW6}4FP}%@X^)ngL&b0bl;l?H^A{T zpkAp^gf{~2ilC{H3b#~nUR9pMP^V7=f9Tw<{(BPd%K$kZXy!mG8@)UPN~QYj40+u5 zxu{Q{5HQX`HB2oRJ-PB^wvCRFz*glVFVkAZoIH)y7WDR$HdO@$c2LAXy-czyBMPH{ z-;b{A@zGB*<5J57F?m0};C(FE)j@^;?KvnCkco*PqOKS3<9SwucD(HlcmQ~R6$G4& z-xC;*<5#NCWaAlMi~yP$97D*cs5G7D2G_v7DEK{+;3ky$+)X@^adFQLYeXb^g&35^ zT6fY;3>ux>7k!Z#boFB-Ax|3s6Qt7#``J%0;@mEL3yc)WvInvfNMMD8JGZGws5&b8 zTBCw@G_l6h&t|7jra_QbpwsQr@-ef90PxZGvKOA9)1n7UJv_Db%2_0WMko!Wq7|aF z>*+mD-!h|!M0=|o%a{6spk!a=YghBp{3*X$*=id6F8D2!&QOxh!mRv5B>?4Oo-UVA zvfWZXjincMa#V`*O0V#+rvG}_TZh5Ih9f9lBcow36*ANpREBD}y1F{FnA4m?iE2}B zr(P#9&cER^fjN-Jf@~mQjvu=&r+LhvWzdTt^0Md5v5SEdmj;!z^d9RmzIf7<|5x23 zDumwiadUgU;5_ZM@~uy#3fkQ#%8k8|1fcTUqZ{7|3SnZ%;i~;7$1Kx z7#M#>x65tWnZ}2b-g0ZPz)Fkfv#TpG-yMV^y$c!QV`FnkaCgF=JhbX}$NahH9^maJ z1p)!Egt9%oy)ZY8VFH!w_Qu9lze8^arct2KAz?yG$y~(dzpP#E=U!&lm)|#gNf37E zkZ+)~_*)S>4+Miun3C8qB{;n%{0#yxwYueK!4;-_Ry5}32B*coGKc50;dffr`}r9{ z4?le2`rozyh5-6#85qEy`0$wIQ?(Zr7w>;V8`=m>7_N_{L!B7oH80QSmwwcZ#gO6o$j*JGdiY%03s2DvU?KplNcz#B zNoAIF&ZJ5T4h{xz7TXB{Daq#YuZ6~)jTazlQX>0u5(V`MxAzBzfl%73geG2uX{Hoi`jD?0wGy*&qQG&^^^yypGL7D~8^B zUP4U#-lR&3jqR_Ayu?cvg5H1%7s;#l%^7N8y*ZtzZS0rXK$ezAh?Aen*6oSu^&>eC zD>t#_TcTLCPN1DPxoMo8iCry7Es}>J0|)DZRIO`lH0Sjc&r_mejEIc9E*pUNSO`|E zAgTaEf^7pzWB$b0*cky*V)aUO4p$f3c1lrDbIpxvg9&PGx;dt>un-`CtOFWT?>pnE zZ;X}xC^_+vo3UX7XOa`oa!aL4w8C0}#zrPZ@IftZ^yo>hjO(vcDvH-YpQQ@OJ7(-~ zX)Lqi!pS{6RJO_Kd*HU~Q1zl>t-#4^u)m+iCvM{@2`e|2MT(Mjo+en_w`Jd}fbVUM z;D`)W#3N;t*vb2mB2!@b?iV|`Z5lq@VaA{BUson+*`+j*5fc7fvaNY!zqY*{|NC^= z^N^F0TYFuI_pws2V>{+H7Zlz8>o6ujG#|>Ji4HQL>X{;Z6;V;)1?dS0hEsvSJ$L*> zZ}yw2NJ1CMft8+h`mhz!=zA6(H6MN#8AGoL6degJzeT*$ow}9}pGkiYrjAT~47LV` z+8u!p`NDkN3(6*hiwh-&Dc~jCxo2>3t(`=8c!<1qcXpc8b%k~R<9%$YkSv{jK~Ik1 zdQu9WLM5s*426YhqLXt_hf7KdOyy!56DJ?!XJ9Y!`7@hd0nBKqq--v*!ZS>@t$R)m zgA4BRk>5%R5k@3`p-`!|`cfl8rmxHoZ}C&~?mx`^Pd#n{l~hX$y|^QzJ#b?T(QJDa zfZcuoElV?nz=Or{@u##LC(zN|Lm(%Aioe(|1Nr7;D~qVPF*+_b7BnSqTb0^aS7v(> zFWE!OU0+V#v6ad6;HhT?VvcPj?0Z!us=+gERori7+O(MZe;`Ihn4BQ7Wr!2&6bAiPlY& z>4j$dn%uJk_-T?76G>?W<)vVXGz9YXA4GD)GmAt7?h0rL7MivxFtMdF^|nyI{Gj_~xieV6e#fmK`4Qb7Sn zy25iq3Dz{&!mW`^l{^SR8FNmo*Rbc|t9d)H2vpw7>d(H2crTUqV>Nsr-%Ldhl2pg1 zr10-#BDp7ny(5%4-LtoPH2}TsX-6_}88{axj-yu33)A|LCYXUTpp_yz>~ay7#zf6ZF27}qUo>nd=3l@ z#0FRa)~EWmQNZwX8?db(2S3@wEH{m^`8B`d0LJtGq$j<@Xl_*e4Q4jL#J^GK5t?bs z(zd0@ROEM3v;AimT4-I@+@*MtzfQ@ILBVaQrqW{^WY~SW2O2c^E;V2I{k*!DW#3-T~sL#%7f@xw^WVmNyuyr^Ql9B8UR19`d2mLd-n$8&wimrF z`T>*0R%QK@t*&vBQ;bMROpITT#oVayn?ZTiP4G0o1+2_hzrV9^{`N1xhwd=Qm^h%R zJq{sn7G?~dE=X7S3uS8_UO=si6_jEn3}mQYTH6W`MrFo2{ziTI(!+=fH4mpmMVv-K z_vr8MKRPWq`eut}V_ooD;D?3c+oV@bE3{%bSzsTZgPA;duE*)k*% z1khIL6zD#6#O6YjJ<>$$)^M+{tpVH;5D)-4C!mAaLC+~?V|})eF`S@NTDa!lnJ>a+ zP^xmAB=vD!TKOlB2WQh8l8LqK`oue{0{>OKa9xV8yI7xKDuW^F+b>zQQ zu%#toIbrwK9kZiJVK$_(Gc>r*q^x<9!`8SvKT_oV+Sj+Aj1qWm!W&u z+{F1F!WbK1r*jU@3&75{7YAKH{%M#QW@&9NdE_;pmqT>+Z2P=svV?z)Bm|Psn7AuQ z4OW1f34T}5h}*$j)zzE?(p^fe2GHieUBzN+YHFh%zuy4X30EHjw~AOVyX+Z=Nb=b1 z)#KAV80aO@nlo^?KZswsS<)dJW(lPdL_Sh}3myGwGAXiFxa;w-pcZ+7@%rS@TsgLU$rE2AV5K1qYjRa5IAIVtSVn+i8*96BxGn{ekN|hxdAS9% z#z6Uf8t#!b{`1Nt2Bgea^*!TvcH9@sf22r*bjzgM@ZiS9Xly7P=y@K7A$}4^@b!Oi z9FZa*o5J}kh9_Nz@yw*aAy!Hgeq+V;&$vR97c8kKFKBX|qd%OE(R{2O;uLTOhVqk@ z(FjY4%9x?)+1c|{RE!WoQXVZm7o3ME6#KAvT9!#7vMw4&H&U$xInHs}d4PZj7TqOP zzhe1WN!X1SFWSc!eW4s7_Npdmzl%-Z0=GMT1YXGcqzO-+(5y&;jJj8Y_^A77&DF@s zq>jsed}q%_GKlkwii&b`-&jQAVzW6^RUhx*7phLh7fEbCyTP0tJZV_~#)loU2-bAa z<==cGwF%be?4<`^fN4>)^jxXhR=#)H+QTSZtn2nL7;rcv-MUDc<8>K&`tVD-Tf5XE znPoz^%=UQqp5{!>zmFKnMiAN%%uQ-J?9W*gbi2UO7Jlw-y`6VAFIn(GfQ|VoiB%V& z&}#L-zFC!b{lj9KU{CXYPD6z3-2KiZ$0ky<8dV4)EPwPp4T zKcN736H$dlE7nX%q^dNl(?)g0-hsoTtNqk&dbFo!y?G18TG(zIIJ9>=331J9&iSLn zPYf6z2YVgV;-fs{i*}07VsDZ-nhd5LwogNuXF&}KO6Qvl&_^@jA(!(Tjc_%8lB~8{ z#WgOP)g&h~!sun%OuDrol-h7P=zDJQ4Vt%SU8J#J;%bU)+#vl5ck;`G%P@`gZ}dXN zXQT>k!RjE9Mf&H~?}uc0{c_a*foP}i=?fXk^qOuW{}lgYX5430d-#;X++9>A`3;)X zUck$ZD&316r!sI7(~AcuBYnsdvk(9ULN6gUK;LcA-H>1c#;Wb5Uo~a;NprR6i2q7oBwogJqp<+BpwLEQDI;f58KxSnp@mX-D z%^{?my7Udsg=*Zo6^yJmA>&mMhJ;6LXqF$-ii*vph##I4|Kh#t?9foJo)OV2bQP>P zAO!jS`?r#q#M2m~ufU#x5p;HDMrM21rFH>KF)C9~FBnxO32qv#h{PnT;NL zn#Q#vj)JX^1q|7CB}3nAS%82HLQdr+&?Ob&nl&U%3Qi7Mr5b;@?~h28%Tk|v)xcq_ zihks`&+y6C^=p_~We}cQzfOV@fsetDp29xEwLo&xwClXaZGXOEwhSk<3tWugBa??u z)CGILtA5y%`>0=M?;KupnKRPj`uvXdfEWH#(koh=^BB!c*#5~UNgY<@vx}HA1W&KsM(b~G@SL9TGsF3b?qzV*~N^0@L!d*zq zh(8M;pXy^!z|rJ;KSkmERn~8atVD)!2~2yBa$ti`%aUvp5}AnRs!PU-^nnP%bOd3R zIzg5?m$BlUvy}>$ajlc;9EDo^W2;6dRqUG-@CPGkFPfiq)4!^VW4h=i?5(E)u^H6E z@R`hLHdG`SHhJY4&(9ikPA&d6D(KwtCg&7kbNC}T*#&DXKfGWKf`13k=YKzl-NVWL z|L247?=bUe#7B4uL>liJF63f0E=Qd`Rb` zSR^aiLA%>~Cirypd(DKmDzX(bqmM(~xUpx@g`makm43`Nt>vXO6F-D#;LQ|1Zt=*5Xx_ZzhUs^*?h@a=sy0F1_Gr zcEf?B7O!%OU$3mhsr^vv8GIlGjqIThPL4N;-0N743nHpZ`KrRchx z6N$fK4<(nWDAj!U)31*zct5cQo)sD-xe56`FKNAus6FL4{V-0ygU5kOHpphO89M*cGxe6H;%}mAvAaHX1C;tNLvr5V zTz-Mn(}|SKCR%vm7Hy{(c;!%Ag=IeS8_vQXla{BqNGomWbtK>Mdu1D++&Xy*Klh#M z>Uhaxe9JjFM>%6_j71$9($fA!=seqcmq(^{7mqXeD&oTQ#P<}|kqcL~=TX%|wY-96 zwbZdnERwUc>GG#zF`iX)A`iWGrf9UuG1?P8`F-PKI4;&2!++8i-{gP5*G`wopFd6c zf#Rq~bYjYvi2k!-{zfP>l~{J$rCpD5v5%3mJ?9TGeLM#Syol998^2H6ItxO1drn;f zO?-d<_G`>Xq)cuc4-YJq(*Eqs{JMVs2acZjz583Tu~jjq&PWg32rjW6vZEi5k!2mW zKikTF6TKcj`)pjLb=ZLGpi4aM-O|LwMw4sSlapP_GTOr!QPJw3K&FjO@r56?Zt@ zawvI3)n4gUi8Jxyqm?qs>Y*Q%fwf{4i~gP7wBNs+$0W)%MQW911bC=@mx_Jw^krXm z-rl%Yb7@m4Pz~QvbSXGDh<*ko#^&8ESM-)FJH2&};?aE3+WOM=B`?!L-b_aNRJO9e z2VWCK$G*(kqiPkBbg#N#TGvtIxdpm6Q@tKV1RnAMAyT*kyAO}L>w9&Q_VTRrmcI^b zEOWFBjk0|4`@?&^uezs_CS4F9{4xVLln2}}FZ_?|j*h~M4X0nPn zqGU@kYd+{|cgk}$jrXEu+I^$=t)0V1eLh39m2W?HNO_r-5?8ATgfi;NnZ$BPPLL@d zPkh;d>N&!rWTt=Uc7>TW&XzvY+b$cKreFGVCGYx(TTIRKDmlgSXJQ8AaoO$sx;c(p zo!1rJd)t3hcxSB9h8748M9i6i^l%wAxdj~^?KBM`_9NK(PpH0XrAFUM33Qfi> zmLp`9PR2jm$d<)?^7{P5??)c*WTyQ`sD#LpbUdxuW**FO<=!2A;xI8c$rE@1$&?cK zCsQnRpivNC$7(U}p3ZGIJ-70kb5n!6XT-}5n7hs*qjK3CldZ=GEpReOb<>J?fizbR)| zgxs9uV)B*!cN0znl5;;NZjGrE84+j<=$Q=-X06!?;E^2rkNwaV3!(6$Bqqx)Nt zTbngE{26sGb>5c0@b|p|-@_M8CdtR*+_%&8Sn<=jmw}oW|L4BhfsD1T>NdWGSo=E9iOp{ZL*BpXY7-lBu+o1*hTcE1|OHPAdzz?SLCFs9hW}~DFW7i{O@r! zJo)gyizBum9@o@!iUd$T;pK6${QD)gv|_&o-i7)JFOMtl$xA4&@b1ax|J(*!YyaNn zWb=P)bMo^4%Uw>V95)w=^9+H|cEFk0oHLS`xdgB1@&0k9$1neVp|s@x?-xcbUP5`o zP44&MZhSa(wX3oAPgkdB6UF6tubygOU?sh@zF1xYs&H(K0?;~^^J71QF4 zYrEUkM;JJh;po=^K^OSiF0Bu4tlgNSsIW=(GylfJ&9j=YYFTo&c!I4?E3>}X+ws@u z2rnV^sJQIdjdUT-tR8PCynlU&L3~d~Wyo1+!|VI&pT0L0e&6sD zE1VPD3Un+VliD_3X77sA3w4NRjy~VGRt~BU>4Ss!AZmaJ1(22)RC&5W5HeGd11Q{J zg^ya07R}76StE?_*_jA|Wndz-z1S=na~CPsc@c{iDnkQ+=qKMp)Kl}&`7$3JH{R4z zk~@DulT8D{e_5ZQIi({1#=`HrnTQu7b?sk%?w2xD#f>aJQ!(Et;+NpWrK6H9m z>28GBJM*^jYozb;?GCq_U5+5PSw_VE>kYhfPQs2-?&by$CYXE=;mKl2=p>2@jU<7UCYb$qgNxF zaG}TNMpo{fkSF%9OPl<4JXFS5Vy_OBkCY5NR$`r|v0P4`EB&TWf zeAD1z__IAvBAxrmeqH){uf2Ajp2@)LilF_X8Y|t-w;nmSl)iCyFQ`6Z#6I`+`dgk0 zV$Pj@VR%AsWqXv_Iet_#cey0qEE4&&K^o!VUbtRi`wBlZOGtG)J%kxJBb5i^-~0i^ zfk7|GQ4Iext!}#OF7x$_x8cxkx!D6T1 zS_C8ygI|3raz)fdUj+}(Bc#A31MG~Jmh5_)aMMjtDrH%;b8&HX+LoI7?s6n={8V8P z)tL(bmo?;N-XM$(PVQw5!fQGJvJRYCAlk)ZJj8O0iOR~#?+nX+G$19M9^Xa9ItEgR z-5ay<#ROZ)d+&@>9~xgO=hb$wWK@da$uvhly*8=is!x;pMCsQIhOujfI;$uvH@wNW zzwjB`leMGINj}$$)*_fObUeT0{BAI%O#gCnuNeMFx?a?4ml_>c!EbzXNSj9o0t*I2 z&9`NM`&>*HvINH`pzC1+ z0TGNe%1ywIx=8tfCIT!rHk}fUzi^qK{}8CT6=ZiSAj3g!PTCJV3x8fQzYi{cAT&S_ zGX(A_Ko{80CT2kxk2MT}rf&e7@|D*3w?be8*mB6?A-K8KRx|3sYEC%ha+h#kHYQGj zVGhV*f;}?|pmtbiv#-;Fb;a6qu zBjJ@km7L%!7i8J(t}Ptc5v3BiDihgi*DwX{2`I8!wvgZaOEH96S@Te(b+y81zC{*Of6?4isOL{2$B4d^VNMdV*np( zzZH>iu8R_B%NYLw$JPAsBb+3}EpQ`SAhd>&L~sTY5()-?6y)X-sDo5Q7e#}oS$%Z4 zFO`Xn#)1ccu&#Ka6N?6<7W;%`sKX4~6KwXeBP{A#OTxW;J^}s=4bYizLBhpAM@Pp; zvL?P!0+m}_F#C6cTQxCCRAz26p`*Q>Bt*c{@S!U~dW{m940{Xcm7l+OLH>Ibn{&>1 z*&w@qUM*Qdg4)XEa6sl2sRJ;8a`mvNU@F$^io|x7y zDz1WS1elS7838|`DnT0*8Q}7iulit!KIz4(kvAcqFJUzO-1+mtlF5SsX52P!Knu{= zjn$5k&Y8W8OGrR3FUx|B402dTmAcO8i*azAk#=H??1?Lec-U$v0fphXxVSxWK#jZg zJ88JVbANQR!th{TxEKL>4g?F$?d=ZvT|Sd9F5P@-kjeK^D(EJ|O-Q9>WmQ0#59T8Z zi|Sqk#&l&N7^qT1Hls^4%zhMVz9Ek)GCe|Oyql9~FwR5nE6?xoH*`l42ESGw|L~G8 zzUx!-DzT=Mp65QI1IeQ^v29LrYbAYVc0aswKPTSqS1Yh9v^Rc1&hI@2+P`8(AZE|} z6?j4%KaZD<`G#f5!qaZ$5F|2OD)&aTp8rQ_*M@ zT4E)P0~UT@uj4v8+^#O;F)mK~BIOv5;N{`DEeW|Bdigb){1 zbCW&6An%A&L%J5;$vBv2^l54{G>^O1{^r@R+-S~NOMXs=kwZFRLu6Y;huNLC1j`u& zw+I-IWLj!8O$k#x3D1m$ZyV$W2Be~L8BonSeYGn!d>O?kjJ3!U$L)8)oH?s%dc(cg z2M{?qE&39aC%2)I3?5)-j$&_y_45|WmY!9e^h zz|mTH+OH5ozk(}&;&m%@9}(KLbzuOslRY|cv6fF>57K?fC}}2Y4yxz+0-#=@Zvwwa z0@6T3G|qPL;|$2S?;JuBOx=slUuramoz^1R{q*%q+GfEc4>Pfmt&2mYXB#P_mNn%t z56iFVhD23$m7*vvk={~^?{c_(H3W^x=~e#r$uLEVW7>B$u?N|uwX(>^UsRN65>~HO zO?ZhqotxkB3YqJNn{yF*J}kU(+@-SL-Jau_7-u)f%3241T*<{SC!TW?@LNjLOdi$j zc2F^(FF{Pa0Cx_I{)TMgR2ti zmDhIH2OJ4K-{Ngc>77d}?QL{WU}_hX#yJbo3w66koEMDQaW9hOHCzX#_&Vd(Jx7>2 zAHM(ReZMPT)uCOrt-anD9NDZ_+4$yTaq+f!maL}a`bZUD%i2wh@o?T?6V!sXuk%gk zfn+q_Ju6I2O|9$qWHuw(YvDT2&Fc`PYrkpg$|oFb|Gas@;$mqbw$Y84xAK4%W~r82 zsR?Er(UTcx_x+*g`%4@1mq=KN-0f3XUtPHJ^pixKW{!8eN%z#UuLCPI!+lz^z{iOD ze}B}{I7c0`8G57lWkx6QThH$+3^`=9%j5Cpi^JvJiv`w%6ak+EQ`@Y%Tj5q;v;?7o zQSibDCwinoFT|$&b&ykx1tqcn6^bk9db`M(bQ~6xL yMLKLc+KO%0-Iz1O68h$Q8Q) diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryDecimal.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryDecimal.png deleted file mode 100644 index 42fcf5ee7abdbb26741340acb86b6bdc5d6a16c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31941 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 KX}<7l_x}Z6k>bJt diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryHex.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryHex.png deleted file mode 100644 index 5ff166e5a2dc1a8a62b95e20f4c42123df63109c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30396 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 diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryRegex.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryRegex.png index 34ae7c69f255619127be94c3b53f557543918709..0b77e07997cc275a8866dc8e7329b1e73fa533ad 100644 GIT binary patch literal 12815 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 literal 31361 zcmaI8cOcdO+dq!X>=jX26*414_R7r2=1_!?eUP0Uva*uQY#|{WSs5uA*;|P^_Q;;! z>*)P?-}n9d-rt{pIyvk0dXDRQtfz3zJGV*CGMvT2!Xi~xlGnzu5U~ z+Qe24dJk&Sn^ZR*;)~E{OH*8lpn8u*_5MENd#xJo(Chx{rQPy^`X_w(mHndm2Tmh0 zQ#<658(mYS> zXz1vY(;HS zSFhf@IikUK`E7)n+;-HL2>0lH^dNCO_emcyF_nRWl_ch?h19TX6srf^1euxbjt_6O z+!lZ5JvXepZ4ldZO`v=}$uu`F&lH6u%B=T0@~TwCeEdj`|8(Q=GMD#B;Utb%J%!lY zX^)_C<^bIX=KVYoMS_;M{f?3mx86I9pxjP=``5=AvbhB_8LyXmzmSZqd|F{T#S%Vh zG%C`tzO^6eKO^<{B}dV5(Naojw#)0+Si}CKBZvF@vu0-g%fp{v;)M^hvr(ts%Y5&s zsEGfa0eMDf zd?F{gEbNm9%K=Oe3`Qzio2fqnrre_CQflk+lHN+yWV__9!r5tl`eI z7+Zm_%EW3Y*|SS}@0qiH@Cr9GCuiH5`I8&k*i|n-8t5O)Y1(rydnC$uu(^6=PjR=( zZnz=pbL{H#B+VpG9s$t<8%39ysLG{@%2vV$dEsw940H&*UVBH%fN(auvFb#xBJciK zKHEXA?_=}_oKf!DgjejASMisI9+aY;~kKIWJ!W#3c9sQuct zg1S@houhnqf-@^zY3!cugIL^acdhoM8=9j{J_fvoN?bo8;;8igBNrh=jQGmkBRTlXkmL znz(u%RHOTq)U95LyFRm6+c6Za_5btu(=VH(<-_j-6R(0JQ|*^8SJeAQeT>e^;Pbbf zx~2VV=8TMCqodM7RZm-9S4Nmr*BA9o1536{#z1r0)P#fT*RJ-tY6gD4-mB}msg0WX zX3KiJ^zqI#O?y^9GOLRVYdD^0cGw(3n`o`>c-t>6ZR38s#qmY@yk#NB&p8&$9%pu` zpp_hD%TBwVnMvhse$DaW;bDDdJ!ugYHcv9R>E*p#%f?mwc5K59iaJV{iq*5Ys~m{8nCg;KU#EIx6%f z_ADuu5l0gp#+NJ~H{s6i)<(ZW{7GIdWx_)S#UGdU<-8mF?YCdK{r#O$cO3t69=e$FN!ll~A|v1S<7*oG1v$QGp3g1ju-I2Ss!qNS z<>TJH%;WJ+nEV~uMxuR3z82=R>;(g5{p0=73yj8LLCr+d7v3k@biWcMi~d9{(6BSr zux69gWpY@tbkE6af5#!R{bs_I8!BW*4=fhlMYW32kzrHm6+3;7!msHFEvsO5&t)`4QnJv!>X!IziIj^4dVqS&dnx4NX{ItdL&Y)5?BQ0dIp-D1<}3ZWR5 zSro({!=qS5t*X*i#|8t`y-&H1IoalV$Si&>5gi`SVBZ^*S{G^AZB3{QCMFYpZ>$kY z+-cFN*dyw6ormXGFzh;UCl$jLJl=}($u-N}{=wC<{a44=#IEq5`|(`253PRkd&QmX zG}?XMNhTkSHlzt=DD#ajs$MlXiyN9{9>kh=Wo2F2jPIbN%VvgR z+wY?O5I*C0kka8$U|FZ|MX9GoKH2jOG`iU5bBj1PRrX(r+Gdcz?iz4cS5hK8dsh44 z0U@Tuug#fn~SN8j&tt^o*(GzIUn5TxcKRmT>yzwd>X>uW1$^q@Ucnx>)s zsBd+HHg)r0Y_1B||9z8H=y=q?Z2$P}?BJfxG3pxYy@#n>U+KNhQN${sqNo35{;o}h^&(kxfZ8LKp<*-|-P9yK*?c`Vyz&zC#ny)2#AJ-Nni<^u@}qc!##7Lo&kyHk zCZlBvrM1-i^HpEj#pY~|l|HG4+p6y)mN$$7$r|nc4E=a#p-kdByl30At*1ke5w`|%73YcCB#3OpcD)<>IyhfOrxk;bx zviy==M+2P&U+IC>>q)auJBNK#r-*|}VX(X*=IkfD2alhmQX!D_mlC{Gn0$-xBW83DXHv__vGDLe&T6UFtvU@796gC^#YE|-89TfYt9sfg?%U6*c#qm)ws z^>P^A^{mzZ@2g+=5lm{IzQ$H9m=}YLC{d@+ptb+oKUu2qgcj*tQ9OK}V266m_AY;y zA0I=AlSAu#=y%Iv_$2rr>}@XeSX-=y zHJdFv=qzzbj@u#o7F)`P)#s0~n)9_wUo6v4^ zdxZjH_aS+WH-dBO3exTzsz&}}Rgc*Wlk4dv;G^hojtyv>_&s+&_*@vw&Uj;eWpu(YU=D%t+o{rseb$R?H5`D{obwh_0OH1f@?RXKX|&9 zsl~;`_2o@WOhhq?@EX78W@VLio`x-=^;+?wl;4E3H~L3QSTdiPX`|m!jMOnl&+w!O z7nfbr6YQIu=2b>yD%Uf5b2*AG$%^pvJE42C`t#IV8kM+KTv%5=+m>A*MRL*48yOoL z8yc1vS3C6OD7(A6yWvcdG748ZPMco(ke8c#o{{lWYwHbebg5C*mv7&`HT5s8t~%P< zitVT5)Y4T{RNQh~z&-0Q-7q;ZaR{@GI*2|mm?yivvpVevUpL+0+guwMh{I`SKmGNq z+MDA=vy-CIAnmX0NY02N$6it*BAI6g%>+#1euwlYtWmFQgB%_|ehlxVrlz(_P@5AF z5E9xV_uoAu%f)HknZ(1_uxp-6{Duq7dV}evQ|1q!h0%(9hvQeGu6Y_6ynO^@7asoz zp+nkxZK9p`H*H#$e@KvD5`P*KGaOht)^PKN!Hhrw>OTY1AVZg+@FGC+?&hnfM7N*O zYuWeK14)U$s%~OpQn_(gTlIqhnmHO@E$D5PnKZ3h8+PT$Z?w%IfOd zslCoE^!KS*2RQHMF)PU$^w)Vfin771OqE-F!ajqqbnqo)p*@O`y<~fFn7lmMDAxrY zeL>U(hz@JL>+d`AT3%$>_Ai9&^txo|tPAAA$U;Iw`od)xK5fkBXG9FT{_Zw3HkR_) z)g;d_djE)Xa*XHNHJlW43yZ2prk-=O7vB8XmPG+Z5xII5zp)l~sfdXNR-{hQlNBIR zOeG~KQC(F1()aIJD|gbx&;Q6vAo{4DCPqs!iE=B>&CTr)_t|xVA3wk2U?OT$-!0v6 ze{`CDnQ7Ex!T?J5rM}$A{Csw=?PC2h72)Nvs#FQ@?L;npELk8LlU5fbyaq-_ZVC!g z+t7p{_4Ja`Rj#13xjbB4sBBEqnf3Zz2^$(3*zde5_(oSa_4p@gxT5T*7$%YH*TbWu z+gJM=e7)z1ux|z*UbkpJoY(Mw9;;_!LP8S~8mdxv@&hJ*qm!?yd#BQ-UtC#;Y66*)PVYwIg2T=VawT|UOfV&!qi-9xV8t7@ zZDsHH6BRTlCINK^w`FN*DK=JMb*etHlUQGW_N>#Zy&WQ zAR+UWHTRzN{pu){mph}Cu>~^D&+_{+;4YM zPDH9V9`AGMRhb%3S6KHv!9Jr|7qD&HJJ?tj71gyd*U2RQSaT~#yYrvDdGWe=D|}F{ zHA+eUw;-aHVm*@@w&`Ped0eWE;g_vE)4#e>BfTr0>eKA8yVHELy^`rY_l0)p_(vGi z%ajz_bLSWgI5X?+x8TQ_et2B|q;!U5V9Wz$(f(|GYb!A$z1HHT4Dr=z2zt=-oePE9$@ z(V+B9S5>w1$rG#qwRPIqfZh;_0oJjkDK~)}?OV6%oPV~@{rh;>)%RdKd~kWZ z`lkoQ{kdo71)aXLG9f2woL>h^6D5U?6wBz_o82I4eD5=T?NWldBxbw%2Jr?vsPEs= zUv9sDB`fmb=hoKNfdQI}9Aak35LTvGIq6AQ#JmU;bAqVrFA@R!M~|fCU0q!_2kgfx zDa(XxWJFk4utm{o-z+`Qz1ZAuD3FvLk+)uH->FSZ7jrvneE&=*Y+IFHUGd=23hVK) zv7sACc>gVr74E+C=g$`$!NN6ngQD<|$M0N@!qQZuzrRV|8`FBDi7tvB@+PT--eP^e z^~nTeznm8~J!Mf$j7IG?haoEdnPA^LOVz^Kwh=*z9i4;lTq+Dfo4k*U~Ee~xE&ocdr$Vwz}Rt~Ohtf; z`&~@!tVXe)X2c!_rEF27_OG;?md^6W>ZnW8$;+#mx$-_wOxzkWc4YZ7Y*FF1IFy;0 zsX~(;A1`Rrm&2uB`fyJFKn8Yoj9HR+7|gNbuRf(8a*V8 zj0u@G{*m%I1cQ4RRs=l#<66Q`CN~D_F9z(NOK$Qe;60hW=)1Zwy>g8zxwphQclwZW zLg7Q_-#4ILzJEC}Rjo@-*m-??MRfJ{=AJ0tekOdf;(uPUsg00lM%t4Pj|7BuYH zPWGrUkd`PZDYe@2XlQ9K-g18g6cQSVq}#%O-kA%P&aq)HPdwkS!s=jWwaonPo01Z( z4V2sBRrQw-tgUbV{YHlKY$s z6#e}9Gi*RNH#b`RPtg|{#gGs0-_QCG%qZe;!=|sbtxchrcd{>YU|`_e>)UJ<9;;KT z$-D~Q^2bni;XgcmuqA?{(q)&HmqlQ2`Rr{#gCL{l{|@kmRf>nl9_sn8U%&G5@;J;i zh(`01RI@7$c6 zusql`(n<1EQX+Cm`#C_8l$5-biASv9*OM-h_Ue`V*tc)%A#|p_cRN)1>IX^@b+B#C zYF+T8tIEscp4B1eXXfV(H8q{K7IY=9?C$TU-*gTHxEGOadkzIq^Cd6!Jl~W?_a;JCAkAAd!VA>-x*SB6puBzGv%#85A~uBChWPh zre^F1WiPUb)4Nz8LBY!T9-o|?ykbiDROT-3JGY}Poo#(EN3Iy!4BE4_N<`T2R&oX$)prn+$ywPzXI^#wXe z(EXiZ;d^M)dM~%ZBHe$sxb{E)o)RP2#Q)R-U5Q>_S&U|GPKl}_KzK0%P5kFcowPQK zzi;|~)5d=a3KSL$E8RgIwJd-9D0@t{g@;;MSs8zTLe5k9n-p#9k{?kZ2CXxEe|`VH z_AAwKWxTqKYBDJ-?DoBT%o+}Cn-9Q6`2PJ~ji&F~=TM>Ag+Js7F0SHq3GX|ec|SkJ z49hM$PSj9RQ@2zo4H`>*?dYHvc%U>~ib&=$es4QiG*$1tvpi0>O?%A=+BKjM@!F`a zcpe@e*bijgDXFQyXJ>WvMGQnUE+ZG&kj~$9I!l;@YzKh*d7dluS`2giu&xMG_3quf zR$D(mNwFHhwY}wqE)sjo)zgZsEG*3qJkJ;WkE0-=^GN+>_VYt!p z^()EnLud}t-4V20YHDhlIJ~31e(9=34SV;ktgQb0>ay-h=jk!2_o|AE`-Ty)&j>qA z+=K;wgM=0!GuJsnL3Z|hml!;I;|3p3D47H#^vq-Ud;9y@g&d@NP);{v@d*%*;|dEo z#qV#-eg?eroMg&tu|&vj2<@e+q7qS3!s7xJ``7YviS8Q~NzRkkO6uwtCA@C*5k6f{ zz9OSvey+sVld6~i<$qj1%PVmIWAGmaW%9mIvWsGm*S>2;nfmR`0hx_FOg8l*nmpHi zW54S8bE-Gl1cvV)86uIJO#x@V9Xw-_x^(FYmjDeH;sG83!Ov`xst@}M#T}o!x)k;V zt-dzR-)=DfQ-Ggy7O_3(Dn%p9*qaO&p_A^pKBMp;`@**;+sos(#KcVDHlc_^ap&aa z9pa&|TCLwPt^L$jinxW$(=RoVaeQcD!M1^YZ!@Ucza}c^8V85w0e0b+z57+jW-sy4 zwl=(BwHj_NZtjU%SLj7w*Xopkd_wWk~57|EXOJxRDik%cA@rzWXt0fPzDBKfp2$dBS6x3hd z*!vztE+18(boT!JrQg5vjWcl(N1p^>;^P`EK1IX0Ki!PvYv2%5FM0oMuBeT?m}^CA za~3u`tPpbgGY-z9NLV^aJjN@Nb#%tikeozw7yUqya3X>_WD<{uk1uRD#LdZBm@Ub9 zqy>God8EJp?&9`4BOW7v&8RP3wii&L6GMNtx7)*&)zoN;YcxMD0Gh8Xov6~I<1q>* z_Ke$JTZI6kUHx|yUpxV)Ii1Q$Z<27O6A-ScNV=>uqPvenp6D?}V7 z3J6g6mpb99D5WEBI7MdWc>eiCe2%;KY-?fB!Tqj5j!DyYg~YMn0D2Ecc&FsYNSj08m%}|AqQ>0ZCHEVv6tH!tFpU!9lA12F;F-> zy_S|1u5Y72$FqhhB!4U{sG#3f*irZy7#KW#dyuGa8fWb=_2IlP$Ba=4!`-`gbB%p$ zdxh2vW_xLIUQqI9*?JiT1@Ydn8+vm|!i)LG>S|0ZO%2{*jw(lOwYuRFb;65TBcS<%9FeMD6q3 zr0OQe@@tnkX6NSS{p@r~HNKp1e0SX5-Uia}-M7rN{&I%?n~>fT3v@&W)?SHG)kjMm z(4$(fc+NR2j*g5lN7+>Z6?*MRwfZ(VI2Z;Gxiq4M3nGOv6mvp({gAix!ccuN^ytPA zwr%D6_bX#nT=e158fWiUolOw=8fy_JM>qF$>~{18PoRWeMrQ&`;}mZO30ikQi;1Dz zt8tONs>E$*>q%Tn2rACEzdWhZeq2x7w;&dCz2XuQ_n-7(d7pOD2pHt5vj_B>tTswL>}8(x;cCTSwC09ndkPuR+B&{Z93*rsqDcrRRR#!; z-O#=FVbI0zLW843(I!PkMy{;k(PR|^ni;vGnzY7)1gHWXxVL1Gf%Bh-SCI3f@OtR5 zS*n=UiH6?m?TLqK&|Sy?g+ECISUyk13Gaw0Gi{QSM0or|zZpupo{o_b(T{pr)`yM*4hA0GN7 z_%DwM9j2zGA#y0Gw68W!*bnq}b#-n1`9n-d$idC+vx)u&J+%!PbZUL$o_Vn@j_Kg& zg)&O%C}^HhQ3}630o`TzAMXYH^~Z687-k%lmBUxCa)op#vy4Ay2L%RFeEY~gaP^?= zDt(ihvxkSr&Y#~sovweWL-Wd$w2N7)LbB-gPoG||)p@K+YEpu={gP5f^~aANDDo{< zHnys()%yK_Kz)6DV0{nJa^HoX6mSc4QN-8Fh^+UNC`%PJwbt_}kXNYd1F^s6)W*&A zrCrGb&T(^niELz)5~X7if)d_;M3>mqA1Sk>uQite7&cJnI;8vk-dUcN_sl)Z#!FCOx44Z zZH?RTdBNF`S44=YC1aB0X2LSDtIF;jG)rdb^<9z3f0?!T`?tQfwmBj4KK{=LSvo%} zJG*h{st%JVA~2`#V8BjsaWQ|>++c5(9N_m+UkVfrJdV6q4y2jY&d6wtH|6a$`@j_Q z2Ze;PggMlkusUo1(zI6k3U0z;JTSR;XA`z>j6}M5% z;eC<^OBL#=!pjr2)ZWUyiDURI){q^1>X2L58tdgRS*D{ln=_m*v*_GJJ(9PZg$q6zeZIN^c6BC+6 zS;q@SaY(!;RPP^+x@^oUx1t>w!VfisWUYb62dxv?+2Jecy_eln`>FfxR zs-z9|uToNKt-2U=0!HvJu0n?Z`4PE@^GrfW%zOKdPGD;%&_@<=ojX|cARL>~(YELUnR<$K^q{9zTKeNZxClF!{D zgKeKqj&5&nXYlhLT{CV-e8PK{;Ob6Cj(5l0klWmsL{RB53R75L4*BiDOvX!>8UP5! z2tAk6uWo85MjR@_PR2|Ne<=l$jg=J(!NGBeVTiY0F) zQs=2irD;y!`Db=weXw7LT)9K#=(Ge>;w4?RgB=tV+o8_!*^&?uu^oVUoD`A8pLK9!1doPE!YfD71fnJ8FN}GS&R&Yo=mTD1bg@!Av)9?#U%!4m zJUR+#20b%wnxe793L_u{mzHYZ*ata%;Ay1#M@61}6SjWf1eG-*y`Hc!y1Tns^9GC( zjdA>*It`T~4nV<#&TeQziY{FI-W=pb552Aygv^9rGc#25^aUL{;Mo99avXh!Co`6( zp4$4_L{=Z;6rjAeuDvayUuA_{j%H$5S#51?gorkVho%dnS@Qq|(x~Y^bB3gh?%^G( zv?8CH+b|5<9H9e=n##(`wF&_=*;hgsBnZ;)vQc09ZY|5qWp%5mXJ3=;a_cLCk=Eff z_$+AHZz$hl8&VnqzCTAtH{DMNE+cj7O#vB;uu@GgFR@()3JNDZ$Tt=3UijNr2KjJ9 zo*IK|(1K|XgS1HPo-^RVV48@RfJE47Gr=^HFX5Tfmqwhv zBwekC<@BY5h=tlf2U*#`#>U3{{NH|2m}Hcv*Xbp48Cig|14hcjhYw>CW)V(QtCJ2yk*;fDV;sdSV~41{y*-W-*kijQK(m$DF6XGxuT2P22P`r1Em#OQ;#p7MUmR8g4i%I%wD{^|-OisLsYtElI80WH z2!AW74kBfnuU>{YXlhch8tn#;am$TB%i>&#=Bfce+yymyYhe&oA9evFsw!ZN>7!Yv}`anB}$%!mzVcbI_I@(MO3{d z4|9V{Wfa(gAvf^Y{K-9hi^;`8SegqT9}g60K}WTYY3h#d&1IlPe$&Xy&zItt=D+Ks zPWwR|xMtz6=4sG>xjgP_I=k;j$R@+`>xD+o#mRXc7sVe`UiwqmrVlq+XX7g^m)?sP zFDhZFgB)W{W#R380EVGjhUCSRnwplP`1p8!MK55uoymNwBjrEAW&pvHc>{ibJLVGU z0hE0{1d68l#sM9S?}8|j@-D1kMjXToPW((N6Mt)i*a+4thFzI zHOu}i&SAK8a%r@p%%s-b&dv__;GEpk9GPk0^G_O<%%F}I{q9wu1Hn*2E|XZ0o@-+R zS_jyElE7z^&HOk`?Zz7Y8$nd#r^rBBJ$QhX%z^T9>16qzU8g&K#Xkxrd^5nk0DSb`ejNI#s|kf2(llGaBV%3$2{LFP;_3hj26 zEn$eXCrt6lK;HPixXAUJ=_RS63X{`9KMc&A#;cnw8_w7Ym@m^k$%^z75ESgE zKz}4@_sS$r6Zh2qaGmW(NkPFwFgTz*Xipd_pnuKHVWawXR3my7)V7WfRwGp?eW+2o z*Tf_wjsOtDFb!&$&^M8^8`Xt{hJKVfOH3RW>I#~m$wVHOvrNB=oc_H7&}K)#M|*xn z)oXjn#>%Q4l7P7dUStSHTq(iYG`Gk8H1|wz4Y7^%8d!!M7qHa$X zaqh#-ehsNYO1jkGsF{`Ta|}_iltPd1k~DLDN@Zs9>I6<@(%@sbu3V(^w6{5kV2htB zt`F2z=!%i)!uWnZpYr;VO2^&YbWg`O5vhaXL`6n+e*MbL%iECuHw>$bXY{)#L&|sm22K{N9>By4=MQ3v8hdF~ zG&Bgz&fws5+Xmz6xn=wuivF$(#moO_Yxv@Y3j?9Pd9d023>@>f<(8h-ToD!)Hi5=! zv9)0OaE=Qx$NM31DVS`?Sz~t?z(HI?Gr@^ByLmgw4cJJ z{I8dE7RPx>%AYcaK@YTKRyy0@btLdnfHD#i5^X+F7>0G&g1Hi?;1w~tf=HkvIA_k( zfB4Xvse{K5{-Yo^T;2Ro<8Oy%MHbH{0QT_n^TWo*1_dJqB#6V^@~3tG^uxa3$Sp%4 zFoObPf~Uk%VUQ4cO2wdlNiUa*VA8|fico9p8Nl@K{Pc-kNT|w%;Mb2I0kNov#^@oW zB7nPZWBy+Uh>Z|*iE}A7Ha2(j)UJa}B_y;wSyzq8X+R?3c^_di z9iJ&l>i?ppe#FNQ!Y75&!3Y)(SZAk2lZgtY;~TaDp`A;TJ`YdJk6h*=8am=+g%g!W)+qcOt+@z?1$j%{P#%Tya=&(P6N^s%A zg;yCFQlN$EoB1aGdz&8Ue`@^*1sVlTg#8%J&1cV^-4GCPadicShLnWl5qM78+Rr-% ze0_@XCIPubY=SvONgC<#946ZK(W5t2um9?U|Ls2h;UV`Q5h5`m2N@ZNw5(P6pB$gb zm4IGNEyRUec733L;efIzhvqzm9n!;O&v(+|%5 zcMnm|CtV4xH^|xF-w!eY6ZY_?+pQ_FML$KDEx??f}@t{U-j5 z{;MZY+b}ivUMBsovuIWq`@S3^rx9?|YhL<%Rg!jYeM$)wS%*Oud(d=pzsR8m2*2V3GL!=@J1Q+t0>^8QHdz1l96(Hvqky z9?o1x0sS`y_sz`%Ki0V~N0@0WipcEF&dg+w`%T7(50aft0UP#}h~uRV9#GFGCrQN% z6mb#F2$P>rN6DU)hcU3Q{1R`hBtr+%{?aa2g!#>yKMCF)M+#V>Ro*pxU~5?~Wx z;k6+YYhm{_O+u8QlVDT#u;cD>rluCOQ2?KOW(_1d)i(W#J-=V)x`|0SPiicO^-}wg z5)$4lhGwGc6Rip|*Wkmf;#(|>(3G(eZ33{0-YS0-WR-dY{Jp)cjRpI8xX(N&0m(_U zfh$n+;5V_CS?_J70LngEwC)}H8PTZtM=+1_GuS?1&0`Du#OSl zM!+Bqnq&ZWrWX#mn;PT(Jn)W2)V&4dIS(`hSEw|nFvR7UHL(V zr0-R=7qP$$Qc^_SmkhYLxYqRqHRlV|xcXM-1Ae(VRNT|&;_a&eF zJ&3a1yNQ%Bd9!dy_TfX08OQ621kzIc@{=;K2YtD46K=tRZk!K z*ZzAYv;~xzI2O;MqF{NP9gDgkO7)Z~6DQuN$`(%>@_8EjhkgD16;QRMg<^lr&R&+$ zUsqMF1+s(J&Emc^Q7hO>*rCiv&WJM^8%se<90}2Yi1m=mV56nJc##=55`n)uUac|9 z~oDzfLzd!CVOMp^eN5#!Js7|t2oJr?C}e{a2x zy;LiVSQE^Qqp5(;+vG(6_R^- zyO(Mc6BBcD=hl9-fPP}{;K1u3-}&1iXJoYeuMELM{TCpFhJ~igTwr*zGVa)jDDoul z{A;9&ry&j9b>Y$}QD-;)C9LtGM7qHaR#ti7slV`$eOXtoI1K+3i_Ls0x4KGz?7T?6$iDSF{*3mI)wl|TS1s@ASv-IJh z_W2*Ke?rn=E*zM82TpNN!qf59mfHsSKAZj+q_+ZWpzr59 zEu6P&Pz%IElt8@Rtec?zLR##Bl?nrQ4SP~JhhZSzmb7~N+77V3d&}$ zn=TdG3wbS94d;0JkUCz=G8Sgq_@Ax*;Qr6UaH5NUZfI9}o*eBEkTSG(V`wTqE+Td- z=2p67b6X>?jHTRh*F?tP_-jqk<$@;xeAVh_1711&!4lV zw|;KKl@S@Qu+|`Y1uhTYhqXYZpr?Sr);BlhMbW{Ou0*Lb)ATbpZ4 zM|OwPgR&!Hl+4NNl5KGCi$7SYwt+RF1@HrzXYcK$`@b>N@Jt}DbgJn_#@+J9&C$bq zADd5(wxnVj54Q;-b=$Oe?U<3YQb&*1+vvhb8S}Pq^%v9M9;#A6fdOY4I0LH3&MlIv zH!30`u%6IgRmcJhhtLk%Gcor42?}A59s^Z$fsf zc;;^3S)s&#NMA8E>bqU?oF{wZk&Jl0cPG#95Ot|ercFIOlx${NO3EngUef;_T!LDk znFr!|KySVKGQ$#R=$spkyEDXZB8Gjh4d|TGC9nt68FDp3_Z<(aen%`V&P#dTPI5== zNg2sKK$hYB| z755FtTbS_~vbX8kSXSSuM}&c;4VPtX>Z*pkRQbp49K36>OmZgWb)17F^R;YU1f-SN z#C0bU($h=plr^+~;n^yjpZa%HY@=SSeHZ<6;j|YP9b^8PXZ;U9O_$mE>ammLCH3~! zTEQwo%;|aDjZI8HM)$=NO~RQ2lc6-re5Qv6#@n&}P22AS>z3^RhmFJRzbyN2&eHh7Rd)6ktdyodz+1}8I`mWT8Ioa2AL+kM zwvXhq5w*}_7_TjWB8+uuAOWWCU++>u^e+)G1*q_s3ea}yLtxnOC9Pnw0NsthE%=!*?&L}V}zCScHbZ`U5OyL*E!2k)h$X-}G zIQT6La2CD-?)Q2Dc#TGiKrY-hgaq6n)<2*UQ^|ATB9uYo0O9I6 z$t<;yozNSgh-EgF55KW;GX3B-H^l8%QcB^lhSN*hBoXeQd)OqFJ=V;Stc2GrfE<{fhGB}gK z74#ZrWD<+M_Pu#wm{^+$r!H(MdJ}sDa;lV83l>r=mG+m+!7T@D2a9!1xG*#G9>~zp z6J$J`K|iwHQ<0sL(l7T6URllAV%6>0k{a`Uv2f9Fzio@D-7g_jx5X?+42%d+6rCGX zGrFRd=H{+cSzo0@#wfhja@+P)0ZYB=%E~?eRg)R74J&`CpvW0jl0>opg(o zN30#{o=WlHMq)4nlIxrJ;egT3A%hUoP3rtR-v@yd*S=Vm;$W7_$>@K-#DgR?1*&PvGXD1TCZk_JSV~|40 z`c8npY2NzOs_Ug2q*3qZHZckS;X2q_{Qdp=_n8@1L|UQxTW)p=jEFm4?MOh=R;RO{ zMeO)+L+#$R7o=Yg4i1Phxz*SAXQ;5}zJDZ*BAC6$)|UByU-MZ6#ZK<>$sO+9+}SC) z;+=|C!CPmth@$|^c-i0GITHck_1<7>EAAxSetX}v|COw5q2QS?dRRbx!&e>#vGfkw zrmu)x<yRwhS*BVsEZ}QbSEA&LZ+*M#KLH3_Em=spXQ*vt^zyNS zsK#qsacms78a*W?B??OG5Hc0#BrCZ*htl5Daw$ZE4t(Q3vA#*fd+bK!^`zcvrDtIo zX@)Ebl^X{85ah2J@3WKS9N++l-*oxj6!5*ErXks^rr4J>YnP>+KMG>RGe3V)^3W3^ zJpUjYugf@w^FL~TcK%`d<2r@)TM6xWlYK_7*6x9(1G%&jz~|i}y#6^)-gWCHYTd+S zO(6J#hj=1fKP#1Q?mu6#*Bl&laV@9P9XBFwIOf>6S?xc0JpV0|RCJ@eT2<%9hxqAZ=7uJpfACGl#CgI=Nbbhk zz%I#MVA6yYA2G)~0*Y5SeMXZL`b%p5c2qc&8sY!2;gtU;py4COD0yc|NG^bs*O8Ez zc;)>yKWQy3bWtMgeI_s-L8$^F0gP)ttkmJpK*M!x=iKDt!AC%ZPVZDe=nKK8xBK6- zM#txWMOMs-B()jH*#=oCWgzGo8H0S7lMa2=+Q>PsfrR4%Au;3kkHXbdRaGG!1t+&` z(q}|4#0@+a45Yy>%_cqVN>KCj+KuS(wR`kgbh+~Zz(!mt_O*B(8_P@@W9B-Y1!l{w zTl*}7HJP16F%BnAb#SiK zqymr~G$f1ZxB5QxxNGG}=I}x9Z(HIK%mSv8)kfD$Ges z3yYsn1rxnZTe%xyk-$wF`&m!^FFfs{%ufBzRD51S8Si{R>5FyK4u~FgbUXx%;}roq zC?=JSIUdL%Tm8Hj{*cGm@oH;}X3t*yCvAB+X?y+{E_#cn2-8Aqq9Y)R97 zv|GoUg)=>2-1dsjKF%O!` z@^Z?(mKzZunZ(2}iMr&pNP!b1-hr=Sn^0M@ZV#F@IEZ>(8ze{E zzWwPOj}iPyoygt)fqbsHfQJArNX(+0W5I zW-N-1W)!)Tk)TfdQtORlj*Y2)A(Sn zv~j74A-A}*g%cMco?or53{7__%a$g04F!kc;EdWnPz_ztpwI%Nslhjz6@`k#lfGuj zO?`7SN2>k@!^;_$NXKH?9Jm+5z}813^CGU5fFVXhKXgPrQt#~n!f)J7um*RNU%V)2 zlLBL877j4`e-{MuU21|}NGdakHp-oo-I?^5P8{)^?VzOJ98w}hMF^A9qHavW?G4I zAO!$tMy>jvWJPA!tbK3R1{~-ed$%m}0Qdo09MrISJ5$%*#NfMAAU|!ZRAsKMIqjw* z>rD{Wnx!uvpMNLYmB+w{{C}{}0$+hjmpK(&DafJ*+BeOUQi`+;~ zZf^6N)1wtHX*gM3!GMO&aXaN7#c$w6^^}(#v}xO3n3iC=h~&{(hb%qH4a%(Skdb02 z0Y=BcccVAK2M4M?`#GLAHKGW^r9lB4h&F4QoJ91j;;)_;ct9usC!}7ydi9l8V`0~+ zp1W}}`?p9!hL^&b$v>qB0y&2Wb&2SrT{7$1E2vt6et~~K29XX1%U}L;#$iD{SJ2*hszq* z+Zj1D14T-o$qGH6zSi6Tz!kC#_(ia)jH_&~T*?m$M4w+bUQFkIdmh1P6xg6lB6!t< zxGLfv`TuF|yW^>D|NkpeWRx5V5m^~W%E}(uva-TSsB9U>NJeE9i84d><`_jdW@Saz z$;i&iL1y;I?{##4?)(0HACKSn@9*Q$A17zM-`9Cx*Y$cm*L(VrCJhY@xKQ*KG@Bo@ z7x0mXp#cZZ`KC*AJPVCRS9xt+#v1Ku}j8b@uAVpyVW2j{qNOp6hkLE`AXZ;8& z4{jP|E3lhP>&f0qM9(59&>fWvt>J;2VFTg`&N)KHeaoxe=H>f>XoDPSAHx%!m}s-} zPME;X&ZWwWfDhUj{MzKnjy~Z%?7XULlk6BfKHn@6n(nov>}jjhmU&BlfSj9ZAl zMMDt7Aa+>a({72}AF0=SwW&&PpmO`wxE?j~MV)jq{p9II9hEnkMhdA@*pB(436PEe z?`UOj&*v#Zl#wL(mxq`*5|NE0}Z?n46eL z8{MeSF%Z-eUov;3YbOHCLKB!gl~;x%u~Axq8TupwQibSP8?9 zD=4%MXidgLrE*S|24C|5pB@tZSjTQYD1&?2F*M7dI{H~P#_GXfw&wfg7E0yhG zLSamWRX%T|aI&}5)*zi7pIg19k3<)uDg)2`gNOwN1%dDhRv56k9R6ff=74EtA;orr z?%(m|dH!n{)-GpDOUs66P#4oxGuUy1S1z-K3Iq&@D1ZVC=iN`s-4Dt3238t`p^nx8 zNej?M{QKB)nX%Y5@Yp;qJ%g8q!b_5>(-n=bAKgm&y~=qNt)@3($U}z(L815d>g!8p zM}A(pas}wUkl>O9a<#W5CH1$)DV&Ayz*n;{F7{#uL0dHyfG<4G4ldkqoK#JqVE8! zqt%=PB{Zz(OyKQ&x|nkopXj~?lVB&z2cIisTy}WdR$#~%lqa4($?=JaHPGrq6$8b4 zO)(6mg1g^Gn5uVeY{)#H&3P)|k4S%)#9k7@(_7y$GZTQElFdyIj9fRjsp7teqSBi_>eOMe#XZ&Syss&Bc-02w(BiU`GLM2O{zqhZ`B4XHgiz+tG=md`fAGgZ(-E7igb9eX8Z9 z_ReJVhM<C@e^nCmL9Hz$ieDMT zPpcdNZo1Wh$R1Je=l`f?fvuKK|3}XQW#?bdP`?zHNGr#n*dv9>ElDTR!VMe6n#{-A5TwKe$6?K%3YSDJM3?y z$+@_AJF^^oraN1|rt{{OKz=d=z%4g@nhF4LE>2FQl$6nmrI^+1hl~YN% zMA))D0T_iOG1puU%6>;pWmVND$80DPc}L%9K)Vr2gSD)w?A!2bT1j-MQ`+w%a6{qY z8}1-2EdUAI8pwHidGM(!uxk;*ar7Z^VEEbC@!@Tf;oO~+(o&)J9XO0XPH%&Af>wrY7Xl z3JqPzmM!VJQW*v9?4_I{X82;V(l zpZ(>DEI%FA$9fmrADw+p;Sc(cxc^8WN}V0Ctm|d0kjZ_6ljz}Q`uf* zrl)@!7%(pd3=-G5WYZW-C@2V&HGgtXTaFOb7U+>=9iLZg)#&fXMnwe>N(zOV!b1KR zM=0>n6pkF&S0QLlkam08j(CMJ%Hlbu;fM^@a2{407CKh>ZIVUV1CZx*P zfb)pLX9`?pw>MzJqCcARw^3ybNBvNYP*^CulVAhoP?^xEMzb^HjK)QI}L> z!6V_(uR20FrS(6A4=)#2x#wDLmG3o5ULc5AdY(AY4X=VqNJ&Y_f=9d(yCx4cprbq3 zM$364E+bINU8GLYkH_Gm3JM5dk|X@ST#fS-nW2M)MVbfi1;ytl`@ELqw?Z8V zDwpG^`Syj@=6%k^pY07J7-E8A$1#+Dyj7>o_Bvt9jT#gc7k8iSCVf_3E^4Q+$l?0* zNg7;6%}Skzkbid~Ds6^kD&TIM?JH!)c0pku?4FbEpn-XistAWV93QL}&HEZmb)}`Q zPEN&<`yhKD+kN*gkIIKjgIx}!On`0LAk*>w*xnPC!~J|?YjJ}R@?dt!Zi6YbXc6-gzo?NU z#U~Oi%1t&iTDxCoDdxVA5LfIFaV`5XQ)hQKxW_YA2nWn64Vf@yc}HuDKSNeyBGnKp zlg&ug){~WPbDDYQq|9zJ2hgEIVr3q(d3Cs}_hGJ{U(0-AJq zTK+JF$=VStHp#|NY(&U3!Tz@fC3V!!9YPnYnIr|peKUxgzImm&mi7GC1DxR%Rg!6k*$j7B;n2{tL_EkM1~5 zJ6=A%hKf@i>*?pr9(fW1Q-)I|9!smV;5okS=qDk_u5pP83J`f{%MUx(#~0(D%2e!**PJVkR@zO1ChkNVJ6yndX- zSFkxNNC)gfcEanpID#HU%m`|D3k!>XG0csS`v9eRD?qp@@t^{IwJnPdfjJ}N1AXnu zyTJ_IHjUWpPk4EGp;|r5%v?xVf%qXD=H9nc4Q}{O*&)GbH)3;eQ$F*2Y?Ghr7|Wt7 z_XCLdZ~Z+f%-91Z%Xho+^zUE5aHbjd_s>C(295%=qr-xvFa<>zT-@yNYh>kCjBUWq zO`KG6Sz#zV>t!=>M|TaDV2d0`Zz`W{^8-N*;+Jbw+9~nM?e9U3xlf-SZ_spgB`gGa z1RfoB4jNzrf!TuPs4F|8M}`tzc=AJ_JcR8BR>9YQ z?3R|6448P+V5jymWat#SlRfX50oZ+8l5{Ox{lKe1P(=OtWBy2SaoEA}L8Zhf`M`nq z2l`R!8K6k5S2sblXmsn;@g)DPs&7L>RdDH=>C3!TT(h-53i5To{teca!7_?FKDi%F<|Ux z*m|@LKXPhX8f2Os9|CpNZ!l;;yg(qaU4uX);shYzu&Co9U8v4_nI)mHu<#5|AVH={ z&{7DYdBnPfd1VQkRIt}ptf$R|hZlVRVM>iGVCrlts@4A>cA|cr#`H#9=Xy5}S;o~84 z0s9eFs-WF#MYEPa9tekhQL0EEGZv?ZfeP3Pv#(W8j81b&U6Lr z_tJ8MGPSE^q9-OMx~K@P+vDIOQWEoaE`>noe7%7_01@S} zMBDLjfVLJVCT<{ADeMASGNSSTwzDo!**5cgv;dwET3*8hLjHEVvHm(I>&T%vrF|D?_*p_jU*}UCErU9 z$a;I0m}D83Hl-d5_v_&?)GArecC|X^QhVnE2gl)vGk3N}rhQlP^Q+efZ1?TfEBo3x zhPY1)%3Ki?UNHOoiXBQcQ%IG7Q*vZC(8dY*p6OMgt3AJ68J2IfS6FdznC$El0J=cs z04gQqwYl`x@^YlZS|l>Fh>%ccS#K5C$RRE;9n#qnUaBCG+OjNj$IWo|sf1oIzs-#?V;+B&~^{1yR_26EyUx|6_La?BD~D(HyQ zmN2AWEUk=K$v@O@J*73IqV1A(v+cqHug531e00+31tApp%Uk z>DX)xQ~1;wQ}CIj(V{Ms^Lm{By^9ww!irSBi?1z|eOUD-ljxV-%%}TZna|B`VC&W0 zbe*^Z&;WmUf%DBJJ>;I1-P_Y@wa43Ju<#{sBp(W=i-JK4+~t0a!2j7n-E!+3AaJ%I zOvzSyC@n3WVB#1Y$@GAZCzXT`Xh}|cb_oea%Yu(;0ieX|^{r3RrJjLcO8B%k#+lT! z^b8wCU>iD4)kH+hp(mGDPFAnE3n_UY1UT&H0J00;fXQ30kf?QDaJPZZ^m{I46REzs zh6dOi&c}_dXxKzVMwXa;3IvAau2|xoGut?!{yGox&G|Z4wgU3btK1Y~A6WBW-OBj!LLN)`y=r#jYStYZ&hr zD=fEJHtzGC;CVqWluOV!0u*P-E(^IzU_i$F4$@CwQ8O6Y`N&3fLw>nP518TIpvG`; zaeB(heT2-XwZTu0V9Sa80-ifDZCW0BQ@XTJG#7*(gtry!tMm z@DTrOD6@QB@RA5EA#|gb3ES}HTUVD8lwG^V9jaWY-PN|*YLmtrA2!F#14W$1uPt*b zp6rm2tbaf&r+WSRn>2Y-EG|+z-)dLD-zR>tW{1+q`<0n;^=mt=fitQR1y=mt3A_$y zaPM}f!jhk3)Ze@&`O9s|+9)p(2S&%xy9uzAr#d7{9)s z<_%EYmm2Y**5)+l1d|3(Xc|J!Il^xo2R9(N}M65k}G{zy5$4Z^dTK zYVp5#3^P|gPt*uGCq*0JT#wRM7jsJfy*UwP7ze&~4aZdwkgtAZ)?Ueu>PtLn$M z)?G6b;cfN4J46!#b>-P2(a31?@fw>R?);Dk>%ctyXn$kdP5QqrJ$4Bf{8it`653o&q+Mu#-Ig0?STE;|Hk!pr{d!j^}psDZ|4SG_%J!z zY>x2zEI@fr!0Ah$c4J{+hUOtdL2?)RSVp34{&^lY%$X}0YRxyI3N{rQ6tJ83kAZ`z zDvjvqvNToE(|7VZBRE9wI3XRw=O4MzJV=oCwFU>nUP!`KNq_0nO(W z)2cI`=x`#fFiG4Wpn4~1GJo!>^PO5snx*$Yud70uIHqVL#*%j%LeI}8G|B` z7u{#5<&OV(2TLRp9Wzx{ZGoxP^-qAl>75t|GW@h%i<5u|LcW>W1-gZ z?)TZ8ZI1F)tjbtsYA=%VQnVsEddGRPMZB5&;CP$U8YR)C^Q%E)rv+cC#AV+>rPI)z z(Z3eV;T%Y^Wf#9%$1##YI6dVxvrXKf8EIVa6(M_c7j`qs%(~=(V0h1%+J5rE`Cnx5 z`Z;{>8P$a>wUiIGt!#k}DQa&NTiD*x)B48X*x?Y21GN5Y!f{O z*N63c1(dH|la1{0ThKl@hsfjw->jL*hu1kq?Nhz0vv}eWX3Nt@#4-Y_6OE1hl5g3b zn{v4RWuZtSHMOx*P&-~{?a-g`k<|6xe;(^$a4n70oi%uT!QxZi))@%PD2(>Kh>@LV zvOO>1g5|FDh_m2oNmR}(_}&|obTFi;LAHg16pefRmt^_LN8*A>CLBaBN>1k186ig~ znno}8H%f2ID+SD1RC4zfS&_@`)xPzdN&9?}`+V7---(?o&9`tP#&~VCJX9uSgn?@m zN83U^T42kFn62}v6zE@@D^|&+VtUYcK*Lv*~ooywO)lv3nZ13DN&oJ8ia?@msiaOp)%`W(+&;2(z+!d!4qAs+T z6T8;yPv#E%7Vzb8Fzlb9g zTI&lX1GW1#VugFN)I-A=8ZoI%UU6oW7&3;R(vG!ioxhN!;xs)w3aO`)92n-FW5=cO z$+|N}wf8%pdWB_|P0fa1Zqzc`M33e#z0mRq*txdfgUiUgP_rrYo5NY{;HS`eQuWE# zCJcK@bQ>$Ip5#N9Bpk=<+M-THYCm)v~;ci zYI%^0vL=m9j!mU>ZECh`laO=Ar3xi)|A>&VY+Vl#h5gighqAh^{?kXp9FlDG1|Ej} zzI2Vfn`^^{?c=+q>ATNmrK`+qn-tHXsw6A-+i)3o10v;zjVGi3nqD{OT1WWzn|2WP z9@Tb=;-+jby$5YU-Lf)}^m9B#`>6DL`g5yzc==JD} z-7SHiLV9OcM8uWLI?VKHPp>39=(Ji^#c_X9qO;FilX#80k?XE=w9<8l?5Cp;zm+n0CFss2T$9C_|*O!~L2AI6@>6G4$dBt0gG$sR>;=(CAOMbueQEi#B9y$yPp9}g^K#jaZGY~)em9@13S+HB$lswqbK)#7BsJ$=_YwPZp*Yao z;{MXn(Q)DZq1V@>?AOx%+7UHL|7#Z{Y;l**{kVVXU+?)p{FORW2j>&M zqR4}yARKlgnOnljX*hGuWB=Q@L?Um<{5=i_AMxMAXxOlDx|V%!@7|YS=_HinVt>F! zNuiKXIC1(B(qggK0WoAJ$jCri76b0Vn-j9s;bd&Y*2Q8;fa2n30Z_LVVq!a-BO@2v zq54WwHOA0kXm#|K;f`eoVuQpBE$y(kS03hCD8z?;D7iVYL)0gUuUPRJ8W2|waozI2 z?Tp?9?PQsagz}SS5}Y|o%1KTFQfHu!>qOL=GO5ULho4Lj*vcp0N=H9yU~{PL@jiPe zZ7meJK((9wL*es+?UhIV@Mmk3lt8gK2#Z|vsHmTm7{+L-Uvo4{XIhn&3#a|@?($>= zj-B?a>+*1{-o{Qr_2`B?D*wUB9W$qj9}re$5z;D<$Am_$X1Q*CX>V`jQOFC6q~+O! zbRsf-gbw-kc^2gpziysgQJJ9*v5s3w?&H3=Fd=l$HS|RB1Z%xQ|tJ*JtZ`Hh@=5Dk=lV!W2ZL zmZJ%;Aaq_lk?T-+Q!)tKLEI}dX~Lw+dS5W!NT6#@|9Z4=Xm1G7KeGpKMY-7D5# z;Hl|3!$0OOev|U#TJ&5mhHzttkJrDYM`VQ`9IWBEUpun|zGTvjvuZZg#p#3o*bioo zyo{%+E#WrZvTgD^d_}2wmNzt%+IHPJjl;AmcRbI^__IspbIr8Ji`@T{L#+XkOF(n` zC8d{vJ@w~6Y2K_V-85QFGZ{4N5SJD!r19t#nuA}5^vzPyo_Q!T{R!6~ugTu0 zpXcu6CezN-4burZl*adkqHZe~EUYE{3Zr)z96LK-{{G~4uOC_D0wf4r+X8lGAHqCy zKg>^MaSQ{obL?0U+!VxFQYqeA^-Bf#^a()_)zxbdpiVSX19Tpa5NHiEfsHY?kJ8)v-F#4P%(3*0%gXK`r2vTH z+E`kc)JK0;5b*=wSphn*v5b!pMM+>w?1~#eOR@>(%_y-C!4N?3#?T3HUL4VNkN9*8 zpmuP~LVTt*UI5moCPYUEzRTi*cw*$ux5UOD&J;Cpvkb!*KoT>*QAu-h4Vcn^LMkXI zkd>7M<<-fk*d!q^yl3c?+Vt3r)_AABg{vHpDo78o+9eo9;RXbzwLXwL1+EKl32tX5 zTrztCrW!zDGF&MD;bk3RcFm)QpT(fI?jn7r3c1ql49Zlw2G~>(VNc;QeWC)p+ceT~*r0 z4DIo=-Q~o>g~PUw4QFY)Thcy1;`m&9)GaA(HJ69m@0ib|mRgtN&wb(7KH3Z8H+?q^ zvhB9lh04V2CE}?ZUPX@DtNIP?oi2m;K%9ugB{zj@E%pIQMaGWeFwTM-ixr0gI-PFD5_dC|r2PKo z+k4KtZbY`%(K*quHEoK=+4Yg}FeR5Vs-nlc{fe`Ho04J4#A4*nn*HqHNMt)l`4>%P zWy4Y%gape_1>6><>pVaIePD=yID0G(yjv&vI|0FXLF-o@ZuB>9sbs%GvuNbIf+lRn ziLVM_j=YYI<^T9K^*Ul(-9Tc-@2gm5mibyf@4Q)HYa~D`aQhk=jtG{Z?RjwcLh>W$ zn+0%ZvbC5iOiNY=s!H*818D2u6^PW3lb3I(-T;V|i-v5XB=%?h9d=j-^bdOl+S0Sx(pO>QWOH|gCazYoFYd^sI(KcFhaD;S?};>_;YmtO1rmIM!v9PX%L8Nj-t&nzZk zPS=_BW^1Q$G~5>Hx(5pi*dr;>kaAC-w9pU?eskW5E5iUIy^ciEmt(P%JzZt@O;-WS z)kS6OG&0=Bo0cj+B)$kS%n+mR1OaHGMd_XFZ*b-mpqy z)5CT@pp^XP<=83iq2_HZ+GZG>2dMq-mxxF7<{! z^0(#kXw1)>`oVnZcW&RKa}eh|!vB*xbU2Pu$zqVQ<4*QCcOkmrMqBnN5g%9rxE*Bu z0KAh6-_BFxffR--LPD;vd0KfvUL|NeEaX!uIC`@o5ZKem$jID0l?|ny98d*RRMZNr(5%-m6`utqJ5M#Y*LFwBf6i#dKjRKR&2#i-l(a1}Dw2}~HImBQtN zOG4b_MgjklpN_sb%>Bs4H?*Vv)zX{IX5)DOA#sx)`;66I@9#+QhY$6#0>&{LvA==` z=6^EY(Qr6?IYT_!vnG!3My@1D)-#d<3`a{OlU3l`=^_h_Tp1+;X4g@EJ1LDlZzUt{ zcW6KepSpO<$;qjzlKm*f_1!ib2~;(#pA3%eOLehw1u4u@cI*$b3xwz!a3q2?793$& zY{M{(ufM*`e)Z~ut(7Ugsr3~og`CFWUIvup?>T|)81BT2B`FT)iakEE?;ksjXa|}UUJvuqA<(#w7i!2yXuN?IbZ(`s=9OJ zHH>7WG!cDsVybiRmUPTAw#y_CJgAWJ+%yTXVwH|aWX4X(R$(H2pNOEteH(e2C8I0y zY_fL_S?BEHFr=n0%pwZ;@_lrF_Q_mM^2e1j)qbFwN)rq&rdRM<&g1H?;~H7ERJ8Uw zX1q0Z1_Bo}Hzlcis0bYaG6}$Kkxd)ZC;sR8zF6LE=FCvz%^VdknNb4w@NR zqu7p?gPQhJTA!80?-mo}qmXr7mD-piYe=DDj(4eg4LbhB--tu`;;jJ}!PSCG$ zWy62ow{V7O4l*Tu$)flL9mM}=RuACe|4*~}r@__f-}Lol5SPvQC^-bTmOiASpmD80 I?)H=a2Z{5u3IG5A diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryString.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryString.png deleted file mode 100644 index dea2d8a8fe122f52c9ff4397575ca061f8287524..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33994 zcmaI82RPMz{5Ot}ErhI$>>VK#*)p<6IEaiS^B8622uYH?SIFM`*jq-9viHof_g>HE z=)Ujg`906||95p=$vNli`~8gf`?WrT)SoF3;!)$Fp`j5zRg~34L&NYyLqjja!2sV8 z8-rY+p}j|YDl4V!KE9rWt52>lzSXS2M(H3$qWz?urI3nJ`?l{Lq3SQnZA;Yms6)g! z?NY1Tk?3x!4`!{=$OGjp6ft28Bwvf=z7`Ws6o<5Qeq*kQ_Y@-w2|HVkbRBnwCs@u* zU;QT5)@y`7rXX3~2d-{9uND^2fL*G~L2}L%RgV6gKdY|*KLi`|E@M%J}REnk{ zc5ZHNmg_eTHesONVMhA*Rc+EtN#l+xduQ1X2h@{!*?3Vls zDYU$gZQnhZH6u9q4%Zs##fm3h9BnR`oG(0k=0|&P-0OmKgq*)b30Wo~6P^0saZzG!ewG5CO8qKp<9Q)| z)gyz?rW#*vGt#WpDJf+do;ekg?=y1d*wUY`fBq`tgOqOD^26hav%;l4qRTB95~6Fb zR%$eL>p7T)1C|gBk|jE5fYuqcYWP!?=*SP~&_u#l;AT53>=_@C$k^KlpI^!+ zy`?GDr;FF^lKYN%S6AMyF77#dG>hy0dgRuvKBCT+5{@{Zj7tH@Ft=(I%Ee6Wg2kyI zIh&_(ivCt9Nzviq(W;q#vf&&do+L{@P1Fyx<4D}Z>@}Py6HFN@w+_}u?5E)Eo|Ep$ zv5mp1{I@2Kl8W-r6bQ`Q=%~d54DX;ya+*Iq}UuC6%W^deKKfTmz+F2)R2qWf7? zzOE-l-e;9Xg=gi&ClVr!vnk6Goy+}0O9)6R>_Z2zdCFjG!C=5Uo)i=Ws%7LmC})q=)SZsX;- z7N=Z3Zt+53!VdQK%s*NnLbN{7-vBG<%vgZxd@4Ivp?gPZvLjZPr$ou;=T;@$9t%eve#dksg(O#mU-E(J#}l zI~#LB(ZSFB8GpUGkj)FC@w^uya2a>uG}OVdPvok)CtljrJ>Qx@RU>j$Nr67o_3|9g z-gxqhgTwgR365Kdz>MpkJ)Y==B=fN%p^nyWvyy}n+&h8agDi7a9sW#UI8^WBv@_u^ zF7`}N)rij2yh*UG8RJ^0IQQIgbE5lozWdD1&a&&BvT`1sReHp4fzmQ(vcg&F#1q!~ z)oRCS9Ft>VKfIek%v#iJ+SARYQnsthCu_ky)1&!sT zr7*c~j*F1IctJ7~;8o)CZOJ2r?IN%H(Z$&@dz&{I)aIg%JY~A;L@#%(I|x5?%013t z+$D8qThGn7&@!{5qa*8zYdPU}56@BEx$imGMxGp*B!SWer6gsJh-8((47HOuvM4&b z(=VK4Ve3J&)9;zaS{Dig;nRy4#pv8G4al+>1>zjBZar?=xytX|vliarCOYUM#@|yk z$giI3cjN_sJ#Fs}iVnmeImZ@{_liH}3Cbi0*`G3AE(R7qM9dSp@m))6}JZsPQ(vf2C5YqUfWD1(xaaEruZC9I#7^ygsDw2WsYjz?JIigW9*VmstsTRja!p4?ON(bTS=?-eI2|PS z8k^FN==E=4%Fi`%OK@mCR4$@V@`+TBv7p$4L})e|pD82@j&aalh?Q^>Q$(|o9N)WX zqW5O^VNkkJNY*zgrlBX)RA!&5<0>4|^kW!pVZNhO|SV4$k-e8W6{WA{?{o@j5g=QsbUEXJyNd;fQK z@zyVPN=)RHQa5CSh1fjpg=%;G z=p*Cq*uqikIX?YuAslEj#*l=|RXFvQ=boFiWEsCzSXCGU>C7VasX?7-w6Ubv6+2@J zUs>$36WhH$t1I`6Z8;rp*q404s@P&t%*MftTxU^|$gUpgcw#4_H&C_jFz~#x)UW21 zj&^>QmuS$_-Uu6wWX(A~K2A+#IB33qE2wzCfpY#4+nJi)-xuYXgu)tX&c}9xC=x5_ zj-IJ^uz5PaTF5)bGE@tkk}~R!v7$AR_uBq3H?=4EkRmeLwX@X@CeU=z@vVQu@bH%{ zY^uGeQO0OH=!lZm(;@=;<^*$skL=gUm}2!M$7jDSZMhC33G4eWHinM(V|ErcWVG5! z_s0iL6mTW#yXP-{>nw+pF2UDA&(9xoYs3W>g$-Jnur)*nHl_1x9JguaFofA;)+7K-Cd?o>%fLIfnJ9m&-w=sH;qRO@oKCBrwj@ccAa(MB@nWB(^ z3QJhL{!FJ_Iy#k?VFiOvihkze$b2}Ny^o%kuNuW@nRlr{?S}}m##+dfoyem~HClVh zNW~>z+1$7XngAs|Z6NidV^jGsoN^h@iyfq`^@=${kvZIxWombCfB$l|gARV1S+g%Q z3F&W;)=MuoAkI|NE9Xo#)SUnlG=Rm8{Lfdh98@e~=X` zjQ1L1VM4CIfddC94P5M>e@UQEQUAAz>*kBDxCKq{FWtI)g3nWqkXG)2ND!U&HzU}b zqWfEXY7)2EIrrFLcE8!zHq|xk?4H21lwUpvR*kHel!qK2y*o71GbZn$tl ze8__^#Nmm@FZ{okHb62tm*=Eh8OFVB{3TxVOKN4y_?f#b;*9`OxCLoOO-=ovjT$`7 zP?3#+uV3w_wb14%aIcI=((A_>e2=(TdNt(yr!Uz*`)V)P$J!Oa1OD*w+5f~9W`w3Z z_BcF0aX#ReiUA`Mj|VsLIv5%)G2hX%jlsu_O5mkPz3k`7P0PZ6UsGGlXH<>&`Sa(* zL@i_21GBb|p%SNxEwe5D_^W}gj*dZ}KIKASs#sIXF}!lJvfK8(I}4qHf`Tk|hsc3+ z$EBgx>j|(SMPg%N#l^?}j1#=+LqajRWM-B*?D*G9`=LkA6C=!6~uEzTt6IGHPhuqh(Q3~Y>7C_jDrN?kywr|a|Q&yp7_YTIr#v5;EV`h#J^if3O_mBAXy zFnB^4Sy5GW(~+dZZKfTQ**Nb=%fzY`smfzM&7?(7ztf;_pv+9jScZ&#? z-3XlGcA4&r4+E1r2{}2|Gt*Av`gb)oHRP0( zfq{X}!tVn^Li+j=M9`)2@$nCA->ZP37Znx7xySLr+1Z(es9Oq5M8lUaU${U2d3tqu z!SbxRzkh4KJ(v&=QXceWd$159P6W4pQhzkt%nA$5OnBEX7LDuISn21;g zWVj-;ri_fnj84 zH#;T&1UibtJXh8HMEMSzd~ckdp2B9F8Hp9C=5+FO>n{!`II%fzMg`baWFZ_7{yvQ# z*&bNmfr7>Kl|@ZW?a2dHR_y1}%F+bzv9U4!!^1<%ofuw73n%rhzsqwhc~jZ_rB!PM z%;&kf`s~t@f45iHSH@Q3mF_66Vo@;asZxH}>?~c9mF5l(#cf=pt%1aDThkDJV${Mv zJ))mfnD62V&Oc0rA;`u3Sv|8rE&a$%NH1~TUv!(AniCsh>A5xin8HL~|KrfwND&!o ztL8|hiQ7$9Pu03PZ$U{4``zH4EX>TQ&yKdY-_r-6U6rvOc2&s)60MKrM?^$0T#bI| zH7vk_+E3R7O-WuJn*;6|9!?r}6AMa5ORMj`)Ez|thh-tk5q-VAveD-k7do4X3{Wji zO;gW}aUqJW<>kPk&z}jdOQR4wl;&`4>ElNndU|^KhyK#&E$tm0Opj~^+24m8jQS=- zrKE(uqc>cbp9fPh<8?IkGf5oZTk*3F85!9_8-loe%C9O&X$PCRH_kI+KSZ;mqJ|cK zLfJZmaA_I zTK)XcM=W{%-Z^mO3+WRC;K>=-{=ArhfooY@!V>a4X=|&ixdjCU>FMUy)*lTn!3N~M z`_)f|&~$%wC=V>4hK7cZ<;BHlBnSKZhg&lr2%~)ki^cRsNGiU< z-LrY-nphj+)s2mif@0sL079CFNW5fNNJvO#riGRirC#ggB<=m`@hP}Vt@p*d%;P}q zm6a7Vo_LaK@zwxBzXZdl^qQdAXtv5!X?A?nuJEZNu)2MjVyZURcT)y@^`|6o?F+&PLc4&lu2?!S6=qMvPa3%uPKOLPhI0Z zWTg#iUyXe(R^~MS=StpELW$@6>AR;~ z=eRE3-X*thj^AQVnJ}cP()LAAExO_@36zEyPJjJCgvxaJm{S4%ypWry zcAa4M_zMpFZ%ZeE;{NUF$nbxf`akVY7Qq6(GSLIo%hgr*`hy5^|H8aHE}viSNKib0 z_TbX1^y*bkZS5K8V;k%1b@lZVIH-McxG|w;blz#r0`~S}m({^=7G)zNBhXveVxMVf zghfRBhGF4LoFdcrmix=PfL`SF2wZd-S$sGds7fm=q9 z2W*1U5zPzf4t+J#Z~?mGS}ZwekQsF@Ys0U8e}De`c?A|SIwmIc&V3-!_-$<*Y|Z#= zPS$uFZMF}^ir6Q=V|x4cEm(bmA|jIR+q0k`i6(%evw}o^_R2Kx{PJU_%3$d13nicN z1AhKg69zDbNMUqndV2aq#p|0@MJ^_GBYUUj};XMU+Bc&KRz}D#SLpt-_z3aPyX|={r&w}AttY&+r?X8m~99ll9G*J z>=)Wj5cO~OSPO4#2k|Ffa%g?eX8V<{5DHp(PG^sA;b;TQYRW;J37b;O&~T!w%Nq2i zeR1Y6MN9|>^2^=Fr{>AC8BU1y?_*_Fph<5`*H5&yeUM8Qb5T-O=3-{Xba}vO-K=7$rV?z@=GEk{xUV$T;3dw%ft$KC+1%iDCMG6UT3lc6g=&1Hz6nDGh9W>gav*wA zQCvLxdG0m*-$tsOfo5Y|=U(E9==GW3$kYB=0=W1L*O;oU*5 zzD-b2&_`LGepgYi)A9$<_EJm&mE=cpRP+xWo|u@V2zE4_8$C_bR&AF$+NhebL?=g@ zfjyAJz)7}6bI;K&pqmb6YgB)>4(poK z+s>zfD&S;EIxQ-mVu5`Ov)iEO^LYCPd$X2h&_ee zZLzShU^yqp#|+^fXNEken?ri<4=D5ku+<7099laMQTH`ju7P(ORi@B4O>u>CZ5Qr3qLn9TEYbBwXnbt;1kmzS3?go}fN z=?jKHDLrmw7{a21mN}OBS*|Oh9rvymOF8M+<4$@X0^(%I1dm~*L!0!%0)ShbLHo!7 zKacx~01!^iDddi(TJW295F8NTY;w5|6T$ z&#J8YlO4t%J$^jnzCdVVViNUW?nbk}g^3==?N3B>Vr?BApE89VW}m!%{rb2K9UcAR z^k5+se}-h5QGaQDoggOOBa18cBLnn#T~N?dQ&WZ(4GoQ@r6n^CG|^T2`%6z^=p!N{ z+du;op^S-nOwgnwy(dkVO(SCWKv>vPEmR5#z+uUnuC8teK??xbPw~A|ng8#>Yayc( zv7B5}lu)hq}1bj6?t+FH_h3T0>7y@6DSxbaZqwGBO$c2lsW<2*d2`?C8W@ zWtnq5+&|Gm>+9=5&z_|-(AT#eE%x;*G-WbFs6KmUIpzK}zo9|m7H{-F&G|xFL_)&5 zMz{o7@ZbjR*~ML&{n4MI(V%IpwyDCU?t5#C{}>uV=S8B&ij2{5N92q4F5VDy-; zJ8=;Bx&MBFLNv^zQZd(jgBiw~OmPd(bSY4NvnARN{S1;T3T8f)bVLZk)=M7hK?5iU7)^! zzVX;~U7shnzhVt6euuO%BuI~0zvn{T79O$%c?#+Fi_#% z;w3shQb7G?ZHr2K}G&G>;NwO;)vNVdKVq(n6 zg}TNoUN0;x1QOB&j9Y;{!j5ho#`wH2jGUAdCQtQc_ow(4!}ruww3Yq@5#lQP9oX2t z3WlNVv60>$MzY=a@b&evvhyx5F84@RiU|M@pPHV&>Ef6g*7;)tqf)0BNKkMoYh2cQq^VEVsH^e;p&-MvrJM6EB+m2cAF18`cLsN=i!q zu!B#S=i0@>+}tYltm+Vpwp|WsAvF-!plxwALvAH!n6Py{QmvVbiKzIi6g^LfB%xk z#>RIrT4-j!#qQz{?#sWNlly$w(7r@Ti^X1haW#{6IA-Rae)!qJnuzR(w1>8?dcxY;06}n2*vGRa8Ed0OH@hU{R8u zeV#h5egCxsviog504e@75#ix2x&!JhxkW`qN<9z@m-bKO)?H=WHO|Y`-J88}f)Cr! zGII;o6?yi&m9KxeIdwMM?6=w;8ugV?CV;>r zlS{8~5Uz51e7xO5a}xsYH`0nzG=y-ft4ne64xreT?=H{Z=F2N6jC?Bv98kKTx%)#z z1esI}XDx*Wk+PD~tZO5n>E~TZ;%?uF{7v$l+5i*XBuTjgwMfRNhpV6o(z4RVD;Wg) z@s1fUCsTeohZ#oVsOI!I4x~-8Sdc8kbq0)#qW=Mg4w4Ed|7imEoAPnU{HLmS^M?pb>lO9s(bLBm# zx=s^aT+9y|=#3jU{&;@Hf%g^DV)$m~&Qi2~2iPCX?Q(}$6A9q^K;vps)b>oo&kJXe zG2VRq@#deU)man?~I7x`a_&WbbDId~;R zMdR8dz-^V$ev=Ol3PNYlA<$>>FBsMH8e|N@d=2G2QPQA#{TxC5JDSHZfSA7UZjq(| z9v)tX$H$FPgTQGENOBhQHgae1)XAef#+A>?SsLbY$csAZ-D?(iScS+zhZing83Elh{wOx&;@HG~~% z_4wOqYLb%rfbK{}0?eCgDiZD6baVxmntZ~-x)x3z_vs|P&w=D3(2v!LH^=e#G33ve zD!VB&!KWka_Lx(gnHd?gL>I>kp;6)BajI*=PBc?kzI&Y=R_}aZf$>VkID3(jk!kc_ zZnqLgz0nEgePjaNSt`BbGsbyzG_;%Cxku&W(a?1{GWO}TlIL_Ja=vGFQ=$EzipVhdaC?^clAep zKDUI4pn!m+XvlNLwdNd=8itmUhiD+8nuZ5*}mW{0~6JulE zWsw_#cSdf**{!0Zqd|ca1`wL%9uLn(pU4c<$;k}c+8AixVAgMS;J^!X0G0}{`rVNx zKC*F?yA-_a+~6)Tav_SrZApaBZvLmvz%t28;$X2X8VB+)7>Gx-&j+T(+Q@WkRQbvF zxv`|&fjj^lC|z>^wr;qe9d3Y)r}13J7c?I?h>=mT3*e}@xVS=M6q?gzR{dZPRo_fh z+}P`voGCF6EGrW{oKNjpB9xv55uc`x~swhpb2Z-ELmG;r*mHRl=|C}Z7`JTetd?N1clh%s!p0*oSd);U$Gc^ zj_9Aklzg6^o`5@kPGNpTKDe;1MLZZ478ceUKwp^iz2?$xkG44TPM=lVhmXVUFHmrvz#6j8pbqu&}mf zpZtxtRBMwFzzMn7DV@FX#?%||9a7v%2of6`yW6LZ`?%6*en;RFMymMBuCFhOJ}`CJ zswY^#e(kn8xB|cpCO-`5U~3Y|n2mR{YtWW=s9ZeH#>sI4VgaEmaA~$K2`UYOb*MptxYzg!-baI&khRRp988pXM14b zJJ=gac(weiSFccO)z2{`m4=MWZ{|^a@2W>Lqp+ah+IR)mxCwY%UL}qHh>VPk8^Cfv z{O7@f1EdWMA{l%giN)E0)s+=08k!rK{V9@GVP-49=|L&W<>is|Kj7;llu({$rAz)! zIg?HW-JSLEny*e)g7Y|Z<_boP?UJ&MrYBFou}D)MFdL~H5a-O=XL~AQ3EmMY&!VBH z=X2!UO~dD-%5j`Z#7s*_N~*W$p>S;jEge=^Tv}Q>tMkb7SpD?ojT^De)$#~``4DO$ z;IVzw|I+L;@y6>sn`J)q|BVJ_e;a6NBQp9UibUY-8Lm#&23m7X`GC@W|ar7<&V3GUBmT)Zu!y@gS7zRAd$$GHV9=@z>k1r#7k z$`NY#t;7*PA<4|V=8tz{qe z%W$r8d<9+}IjUCPMyo2U8Je|0{ zGWM=+@LR8neiN`NxgsILsVP2}&p%g>w%NAT^{YOj6LpgHH)Q_>3hqRi74aU2j_yhH zd-_L-z%%nX-{}Mr#LEKoAoHY0Z#7|IB_$<9L_~_JY3i6*Shxa1?d=v5m2YZYH((I~ zGq*jWlo2{Z^&Uqpt*tu|WczL)k;k9i)1y~jghihLa0D$B^y@DiOPiZNnG`F67^w6IUf1wuoD{?X-k6Y8gY3Gxy77!Bc+nB0nFdmX!wh71X9eacdo0D9EWC1W zW7z(@Vf(E=Ns*9n3IKlU{1yuu4 zu7KvQ>r zU7zq1A|k*lI#dKm5Foa1VYuUB&^WM()dw)Kv5j@{(DU&cI$6S~=XL~Af`P*YWL}T< zJhgcpc?_#Cqb!ey75WDU2Nn{vlziymj_24ID(zdPD4zj!QnczFC{2=AC;gnl+h!+4 zuZFOQM}epSNZL&kTwaOs2WBhh%aT_ELqm{aQz<{`s+r_&@7gQ<%QM3z{}0`lwR!V9 zBlprczt3z+_U=blnj)Rl+~%rMcRG`07~T(^I*C0kK!-DH?gjFmF&k&*nY?_-%=Gy2 z!j*io;T00n(b9^bd0HTksCU~+j*g}{xbPMg(VxpF&PJV}GW}1ddnPhdN^xmz;vELAjoO zW56k_aOcZNGz(>3L#Egf#Dc!F+j&RlMcXzZl(NG8+Q!ig4&)8Cs#oNSG$)DV5^|Vr z0;lqYMV0smxVWX1${(vLD?uKC2nPo_Qk3F}lm=1~a;EVLpqwZ1K&OYjHK=aW`vW(o~0TOifNU-Kln*EeE<0%8jII zlD7ep98gBs&2|7{1kjNX9b%V_=K#qR;i>EI?d_#@86uB_iUFanx^6OK{2<#dA&;Vd zpkP%*hKBaTUDhmrCcb+8 zI&Zfg8WI^FkGEOrh^#!zE}e>xQTCuXR-`&cF8xZTOfSiB+Pu8Fh2m?ZIfPPG4cgk; zw*m7dAxRUcc1#HGSXBzq3C9;KE*LD%S#Mm=ach$S+G&)`$Wg7r>$+t9B=^HV)0Ec< z=aob7^IZlmZXN-ypZ@I|FuuXk=Bs@ADps$dQYHKL=ati#<==Gl5crO{OiZ5qdmuyG znVB(B8#Z4PKFtj&M1f-Og=0ZK8O!TD`A<~vMw{y}H0vFNDtN-RkJI7d;Y{Hf4nb`J zg0@nK_8^A;N$|X!)NIlbSn8CuS7N4WNQwlAlf{SVxl%HOT{Fq_^BP8oovA1 zN8bVE+r<0CKLq~yI*iS4T{#KzIa%SqixK|!xa5r_++MI&iyJs>Zf@dJ@|KsCX{E3z zsfURAJOo$!<}@0MP*qZro+qZ4xSe2ulVIA^r<(T-?9;DbzXB0@JB#sCNJznewkBtku;V5zoUaf~W4@%(z%Rx+uCDS-6klHGU=U zJaT~^JDIwj$f^=s{PG8BE=hLjJ!JU!ZIqD@k{rohw|Y&vyOinaQ}C8-y+AX~o05@g z9$R^h2jLVY#DL_H^#^$F28O=gYd5z;dkFaYNiFKa%|nBOvJs!?Yk;K>OeY{_(@;^B zj1A`(6oA;opPxy=iVrqd1+IsdNyre{lEux%6>$R+iz}8&3k71<`o@O2nHi_C4~w3@ z{_)9jO8Pj|)%9?1&#ztuSt<*b+tK=%^I{jt&*yNDBie+tH$0qT|t>BDv33qwDl&yD_V|08RRx4=P+tt-&dfz$~m6A#j;d}fz z$%~uctPKYOfq;OJ@FOqCA7VDtPPfE|XgdOo4pP6d&H7(sfKgQMxX=N_cRmQbP2#l} z(0qWF6W8pRSz1vmc1g*r{gvNF zuya4AyzKq~^(WKSL3!<@%*bIhc#PQ$lidL0l3)5(BnZDT+qCyT{%v~rY(zDuCx*{C$f1sakkDe! z99E&o52S_1QW8*6NTavcHJKrWzCLt++uiK5N6)9`$Q$Wh*NZFH+}o?7tc-70$N&}X z)_t&fK8I3nx?iMAi7+xs0TbsA{%-hVV{9TYq37r4$LBJY&5F8xN-}yEI|B6IMC_+0 z>pes;fd6TRySp)mljbdveVZP>ii`Sbj5;%KB%gaF&XF5BUJm;`O?hdib1K6b^$T0` zoxA%W9sJn{*LENb1pkZ9r(F+NTAy=wTA3r`hVdvnGy5paY zO8VT#`iLJtObsWW-wOn~8nb4ZKkpsv84~nY^8%D)9cBnnCO~U=htF!3q03S`8< z>HzsIlNu+i3f?0Pn~hZb=C_Z57Xl1F8=G9PdBmW;FP{J=J7xz`%j0f|8OZSoWVivv~l7w4bbIJZ6nj{;&dKK%}2PWG&9l7QfxI94rTUE|`jo zqoZ$NJy?4P1OiCp#d=OhCUNIoE$ADq3SKe><<|-`O>nuFA;3JYY1> zO$`O77)*(A*0KS{^FAE2a#6cUUXeP{Hv%B^Xz?hEQbkhuZ(Y_$*+nOJ|aw;hOyi9I|#f=aFw`zYZmeKt0Yr}6(T<(Ei-W;d|t zl)(WU6t>e{Q`hA(otT-KnVOm!8PVI;;?@lI1tC*Z|IA_5`oZ`&hyrXnP%XnUb3grk z1=SmwIRn8xUB7az@%{6uJaMKRmydrzL?0bIDnmDeHNeiv$?3_H_X!C!2}L9zm>cdr zn$Z!s37(Vb-aW9vq4KhztHch}r=q$5OGTf6ZvX$6B-z$+1;XWoaG=tC`tqd#U>ZQ6 z>2q*Ved+oNir@mw|F15YhwDOuIyr=kJ^3NH?>R6}4g|&pbAF@+GX*vpz0#%>cC>`S zuVya+OaSh?APA%ZS1U=vOAKhJ(>)3Q1#1S`TyiQv!3~VIQx(w=1?H#$Hq_70Z*D;1 z?XGOTYb!X26Vi@cU4^UVfYlGFeqa`;&Jq{) z%z#i#xWQdi#^*^lIG?2fclAT>#mpj=&ioDJxHv(pZx{l?!o$CQjhC1!&~Z@%y^?ZC zl?YlB*RK7*>SyBJ$M3RxlRN$>=J3fWC{(>3dA6;f5gO0`0>gkn8%T~Bx)_k^VvvP* zYS@KQea1?wg|2AYxd8SK;<}&_ya~^fLA(({fI};&){?UY!~jCt&50^Cev1cqU{-)I zBmQn52{wphV5<#qDhUY)s-+g2ZNU~}9_wBF>tuH5-n(~C*pCAo|H^+3n&bk}>9ogI$y=SwJ(VR8dbKe_o}Qh>#KiRY z^7dlRq~tuI6u4Ho!?NEC2nuRgAc5_TL=xQLf0Fra&L%sGORvRpw#gS%6L6Qp4<4+8 zq%AO^m~)?jF{+XlJ)$!3hP{zRf)2vS$S5Z#rApGkRl%oOWA-?ZxhpFvsR34^HwnOjq@7Ej9Hpdu zb8=Sd-tKLSO9z}Jpm|j9_PTWr05^D43u&{b0+L?g@ucwS>1nkTi6~S}_U8OX1o7}V z6Pb2T&(BxM$9wV;B(f$aC)ImC1P12qk27@<%{LyEzR4*QKgP z!81g#sae+2UZ-hH)~h+&^_Kt@>~w&6d3xRo=n0lxT#idsMz9D`K|#G-lA-KyN(~lv z2b#cpnP$%Sa=DESK*asmx!evI6Zw&?GVm=XTMQS3-oPP30*iGJ4}x)|h&p5YyS~>n z`hk9kpjbzwwU%1+#Hy_T-%vJyfJ$IJs*SYw=T9Jn$^5{?X+NBg>HR0Kx?U}ih7b1Y zo74Rj9AG}D6B?vHA_GynjPJg7AZ&aN&e=rYm;#~N61Yk+o@vp~O4o*(I54G+)Of3Kn8vU3XPX!5SAc)951j$>h z0}!)wa!UC670n!!+>-jX)I4va6GXcj?$1I{=!`ag{rVOECoXj&|JmZD-x&)Fi*`x5 zHxjssS(%wXz&bTG;(=<|sfY0xlq=?gyIM~VHIUKEzn-z0WqROV%~Zaj@#ynqT>vFX z!`3u3oQ#q(CO-ZY@IR2=jvF&+@T8iQZV%k;_3{pd1(LQWUw~DXOF%+KLNd3o(8{Ug zFYWu%+}ynVkQ7+&jwc-qyt-IEtfM_mO=xWu6%|Va8DIkyOQjjc6wdMgQ8KR(bDWn| zRK%$QJ;wd~*l`8&^6-akYT+{Glq4Wxo+HJ;d@o(H01MJX~12G&f62AtwV^jRSMmp_?EaF&7~T3YrF%?M2AiIbDSD$%;X z^mpJ_?>@Kmuj}kVEt9IDCjg=`-n(~0-;0q6<#Xj07|w;~?jz(by5isYE0uJ4W)5nx zv$M~N?PcQx0hbrYbtr07S^jm=mebK$>8x9uu~Nm4|lRCn_rHVr!7Amy|J#J~cmYhDQD+KjkAhn`9(H17HE` zheGIx!=N_Mt07S!!zT9m`6Gi6ApV$3(~5ps;Uq|O`bN`6?PAxte*K!cB~>+4vtsn$2+vpybW6(bV$kFIn;QQuDvUq z{-L#=;gAa49N_QFR6E|t-u#srEPFKYi8U#udL`@7Zw$~QRaMfIY0ucFf z-HW!fvoqyHZLPix<%rR1+!^d!sFWc);xPO)+(QE|iy{xitR*BQPDn}@B{4p z-A5eGu_AyPkEuy?_fhPc_sX9?!vH6wQY$*6>~X_4R%-xU`8sdY0b3XM0fy6LXRA|c z-mjB{2>lV~X4GR~DFZ3n6$>mJQTyuuefgdM-k8#NBindOdUC5t%%It~zth&67^4P9 zkFkc}h6Sd}_CbZqu%t&rYM%z^vO@QV_@wt%r2dg@hS8>k3!??3N&W?wJjQtb-{d}f z4REI>Bqq|{x;6XfkE_wOWXUOiZFeiP)VzsK#P+oVK?!wrgg-vzXsUL*QpsHQtsvC` z??TdRpqd%3!jg4A)exM9*xI7Mb4OoOb9a4A&`v_=|MIJOqh7-7lEQ)39iVWw_=Buu zt`v0;&^fyX4oAV8>u{OFOzw}w+Ax5K0shSp#+-_bjLg$3x&&tNuU73AG)ve_7%}yO zuD_u%;Ghow{%ubD?ZjCmJi9dgA1IxNNhvHU`uyiMf}bo=<_6+o){mGoa^?lFfi^}< z{Os7spXcU+&@ZW!-V+en1c?x*RD40ZSam5Rh;#vQr1&jcbqx(y+p?SNB1o0GxVQjd z)|awB(A@k2Xu;bFK*oTZwc~9**=oGH1Y%frJ`kwDpygyrk4#LQp}bLWMc^k=LIDml zT?S`g#;TLfF9*!_#FIfSVP?(%+0)v_y`SQV_xWwDtSHIJQQ^WIEjHmreLK6t%1T## z{X0?QU{4GtrX?6`FAV!Uy6b_gn*I{yHAsOvh;AxhRm3Ab0vLt_6icB86Z%iSsOi_( zSRBn>JTavCgids0tSk%MeBut!EcK5;srV;#)Uus0$D$<#xQJpOEga3JaVkniB8S#B z)v06>DjcRc}nz2zvZfLFuV| zRPcH_e=noi!`~mTSjn#f&``;SvjqEw8r!kb@g$Ojheey3Pjht++3UQsIJJN&1?|IEm2SuPuDylQbPf4KuNstFN$&Wo)<&D6h*VKfb z)V#FFw6r+@)<95;bMdipB{+ei`hT22;fWH10;$=zO$wT8f+F%DUhq-e?8-;$lMj#2 zJ`@pcl;8cKP=AE@ArCOx1Bzho!dla(2WtRpkeI)G+2+DH_YStWv=l-wd3T6!VhWh7 zlaq=5po)N)mj63_5Z7vYk8bwVv0lS8bm-a^OG@a}^VS8eagPxw0ypwtm%Jzv$Deof zyW_%~OpJn)#k$O*CmQ&uT1!AT4l}p~l^T|$cR#_V^dWE7k-6-XOcrn2xqq*O7Wv8L z0&NIw@Qiq~v$GRuNP6rFdqP4&Nh0>f@7{@dkace1!fYzRi7?uS8yg$oOj~~ogV|rO z{gr6-ZAr#M6PyHKX#Ust41ec?a~V!x@g!`ZRW0Xe;uMP#7w0g3X~u8{I?VH6+|Z00 z=4MjukOmG-%;E@U4Bv!oSB$`=`|atA=T94yy|3sRw@vnkvEVAHNri=*fM#1rk2E*? z`h#f250oQ*!)9}=Oo0^&2;qyZm6ad!z~BzmIjm6&DOExt6gnuMNdvc((U)3&vPmx> z0cBfQUPpe(jJd!vTeSM`NanYe-|(~^7b@z~t*VB#Jv)nwb#lWO<#RsT0bNK4NYRg` zv$$O~dqr~DReP9{9-1NKniC$q74THGvRVcfh~pOYg{7HUONXhcy$?%x%P_F@z_*1Y zB_-L|tbS!Y?RtOuUUA4W3Lpg`aa8nNhY0$D6?-aEIYu}TA4@V7$c`{UXqW*D^u*7c zVEjz`$d9`|>h1Z>p4KQo%^<=rj5p!H<^io^b0EDFghp%nsX=c2mc6NIQ-`FwZ(&tc z6%ajv$>Q(detqzZm5r^Hl!%LTaAd?v?LA94M(oZ4Mx_fBicNgC_rNAc18gK9zxfrW zq@;i{rUJ^b2Vb^ZT=uP$5OY-MhLUd+#0+vtLC&2bB{>zSVM;o!?y0FHIlMz-^(_GnMhTjm<69uq z0CE{<^>FA4%;0k-KR_YCaRqUpgxJ_@tB!xEc5JsC-lmXz4#*d8qX@nFL4E)NF$!JHIVUh{PrjT?Em^4kioIf)szT2I*qS8>pc3o!?|S|eq8 zaiAlV-Tr?#G0~*iap$(c*q%dkJ;-~J;Icc-#sn0OreTSro>~8&XGWnyxetRBX?d6K z&+Ct1`Z2hoWsYdyz`a><01o?dlnYYhB*(7|>YhUbp!^^-hAz36IwEfg0o8I6S9A?FtbYeN$=Lh5xmVd;x7W+^_C7t}+ zG+*$-Mh6K@`~XT8@-H;q!aCq3BQ0}mx&pc|2N%~wwWB#WTROUh;>)9LcRNrt^IRV! z4Q5+F0*oPFo&Bqmt!@6OG03Zy1E8w*`OvP>`=VMV_~7W4itM_$X7f+RzjFZ|>-Bp( zDJgX7kzQr2}Kl67(O1sYV<7WUsf!jfrH<`;KbUf=_Al9MJbW^hYLNL zoT;7?vsUF}U&jlejlFz{`MDWuF#rcGp)-VL1o)(2kGX7&M-d`gfBY*1-&9a21n06? zOJWid3;|Ig!37y+F(*KUwishS-hpEEYxDbCPFxQQAwsSKEA$X(Y=22%zUxB3r4*g^ z7QI-mni#W5pN$IB_u&VWP#Ky3$^U4MbQ;>;p*E6wN{C;S{IO3eCC#lTnLC`rwb=iw zxwj09s%`&;MJc6~5Ckca6lqXOQo0*KL6GiI8WaH$L^=c{6r@wip`-)}K^mn?I)?6j z4&Kl6-v4(Wdw<>@d>CZb%$hYzR z`*l`{4UKk;WSSc7;fG1m>(^_Yre)cz&IVgSU7fGH`xo@W;OOWshG15TrxVpt0Xb+W z_Q<4^u5CEoiF9XAck|D2Fh?*mGnX%}uG(8$x79k5T(gy9^4@RU#g!c}=d^@c5b9ze zz&$)Y#k~)-&Kd+llDlvYQ zxkL!B>k}HdM)D@F{XsW+?~=J!;RH??3K7cH7y6@EuU?2^rAKOZbpv=Aed_jZdtKAw zH<&0L>o6Ly@W_kg>LctqAJ#tjh~cW0%5D)(7xU&~Z7=k1V8K}J%f3=~+kfJb6x#+r zRqcJQ5>6Khut}$rV;G%qNU^m~gr%5J9(-TB{2AYAloPa|i0wt~6Sq-QHcn0fe*P5z zKypHWMDti+qTiu;c&p@ox3}`LeBoIQ+AJSnxBR{!EKC*PHpe=4X;~!3%)}L!F)nJI z1q|0c%t&52@H4|EI@r;HuLtV0&vb|{^?_PbHr!re3TC5!{BHg&szlY&Y15e8y~!R?wGO{s<<%aa;{jZW2x_(*UVfhosX zmLAGfrc^utvvA*-5E)_JN4^6b`MJkTYXtCi(?9CtS6H~~c^Jc+=p^|4RzU*cv9tUx zE)Hj+2FAn41(#wtubwz@udtzs`(?>^3Fy56cYeo-l#a9d_ZN%nk6ZB6#m^yw78nVQ ze44TTVBSJOiK#$M!jJ9yW8WhJ6Je7es&%Ls%RxV1JGbn|05J6xM$R*A2ZWbi`cN^z z*jSfxOur@#qAa58B0~cMI%#T-`MkyGi*@a5!re&XB?9#o#E;gO)blxJsx@KXp(u_Jyb-WmT&Rb6m{xjg{`|$s$;q!dsdXXH z(nzexzKpucj&`~{{`O{4hY99ix|p1rx4cJDnzgEer-93xJw-iLy3oe>N)*l$Y=62=|l;veXrAby}e zXO6XCmNAV0tu;sQxx7SP@55q;2N=+L>5wf>>MzX<;zusmA8C4r)*->$3zasFs0Zop zwNBa}X|~S%s7F$EMgMTx7{BsRf5tA>5svJeEq*^eUfB;(92esn(fjtL4koA?3z&Ft z6kb0~-2=5W6vnK2iSkMX9KLrg-VIORM;*ooi5k!SF1<6)0|Ma}g)d&bh(qsserrxP zw}9LN7QZv;PA`0Ewp@R1sxuM^pnGovQAC)xQ9rk|)3MXM_^He3AB#pB)bPvM1WDKa z9Bq6RQyP}^G ztaH_Fk2Z#$$3quq6c7|px%4*s(T|lb5rTZ4%i{*ew%CBTFl*7hZJar=CjngrsH9?L$Ag z#HNVWi`_IaF@Z7>(6f$uvQl*G2QMd4A#mT;--OPF@1=8S&*UeUf--o!8_diujnje; z#7SX=MwC6*l78KTB^9@y6zapMxJKFMoZ0zO6_L``T;Fd`A@b!q)2Jbd zg@EuZdIs$zID7CPpE{5hmjQ4;^YWsVJ7_!MnU3gR5B{P#Xy>dQn+;V&FgyVagR*}1 zjBjc<(x4@ai;mW^fnqlH{rk~Y9H2qa7Q>2UcANkIO8H*MiQ&c#e?zcViTE5<9xcD_ zeA%8|Svl0#R|;JYl!$=Ii2z%yrlv-7`_qsF$PIQ`pMtIf@EIr@&|e*#pkYc3zhFwi zT_^F-JqL{q2VA+1Ob|hK0rc#g3t}3$pxd~kP!glAB#F3}fKU2@ge!Cup#Fb<$K?}H zcc6!ajvLC0oSNIVu@BL^9tdl4A(l?)5;4Wt$_Q^*@pM2I?Kz{SEh~mI@9#)7YMbfe zd}H3Q-hQFh16SLY$Xp3?hXF>MU^}S zbRaP=xSfDiE8c_p57wMV4$w6}r*5hH>%hxvXx~DiBs+i;DuR>=J2SIVFy6#|0{cAT zU~xf?TI)QL_!)RWOiZY4{u&2iDSgsnQ0F6x3QP|dePAJR?VnT8PW#u|;^S8{N83d+ zu@@gvN!}gs>ercqGci%n!Oss$LJprd8bCa%C@4^g`-rrNKevXD2uA-Z*R>~ERiGj{ zYBgyLeDM*;vd2~pyD=&cMQL8YRQS&##lp`1_{8ad1iLhml9I-sX-9~LrK(dwQ&p@D z<%qHISiEqg!9hNA1QjjvoSn|T%bNc~oK-KL?U~b)oulRq2imLl9MdD?za-H5Xb!K5 zuNdCm$CqZ~bbz|#0`NF$48mEO0N;_8mIg6vnZvjkKR?+9t+EoM$o?`T5@NhN$QJb#Ce zo10Sj8Hasla`JT>T0Uxoy3iBW{tAbOki?>2s1;T-EVl?GYGWwi2LBI)l2QjRL;gV%)tW;urNmOTnkwNsq{ zdB(N??h?O+AQ~+yB(P)W{s3}C{5Cjv5vV_K5s^G|cE0Ri5U-3E$jR9YUh>L##`|yS zKIu#Bz9bU6M+YXY!-49_)b@)NUSKoRV(0h!@bRPiSmlnh_ozfI)zsrwD)Yp?fzrK`a5wj|F^+iG_)$Ces*R4cWl#2kBJ>^(2tBZT_o}_kj61 zSPEb)G_@mix8QkhZf?_4sRc?=4?YGcV}Y8?YMW*gD~;O^GrUnie>#9il{N0V9Y()D zZmmG7Rqr5_+gbjoK1wL~2qk4L(LGD_mfLbobCjrLOJ{C_mV-!_P=QH62zw9si5o(3 zU_!-oR&#JPm&FhM>xQ=-udM+#Fjx?WxyTy4ctPOAqX>RiSm>~l${zoANTNR_bE@Th z;l)R?{cXN!RwgDd+>VU@lXUZ53lL==!o3A<9Nq3uU!-;&3PXJFIO;puLtvEo*bSBM zjg2pX(WJ1cWUoZa3qr%fXa#Jq7W@EH2iSMFK^;IZK{#efDZ|q2tL5hnusB4bJhx_M z1~~j~@!&!#owe)(o9%1Z0w1lVRMZ5EO}_0lOD^9lx<7gE9SbKXAraA^IOe9Wm+kQO zlM6sx{YWQ*?ovig%`xl_YW-7-#i}UG!fT86d_pkY!E1THnq_kZ0sq2 zFmI&Im(5#tsxa=pcU+t6i_e;Q6y)9bJx;H@A1}E{eI_m?)>tb+^$zHf2im#9EpzMn zqfhxVN=vo!_`@Db)bMTf_eKb1DNXVX4@LwiIBiUj?DnU}D}N5OXHxY_M^|BD55N^w zj08c_UMdI%@R6nuYtuf(i~EWxnxK8cKR&lR?JVn;-X{(U32A)$AU}9V7_BlNs$I|5 zKZ1Sor-Ers`cH-PQJ9AEmeenN@A0>`)MiYvDHmTTPRgyTXnYGhrqAvuT1ct4a`Ot( zRA0ug^8AD1uM#ksn+tv$I@C0a73$v6F%QwcmCCM1I9wy8NEkW{hbhE~K3l>fXj-j; zpb+bJ)qqyPq{Yh}4?Xw;Q^zKtX6=#{N=Nq!{8zBtYTN-C$&c?cIRjmk(3IajSM zWCH6mF3jsgRKCGHJT|=#7A|t%e@w%ogJG??~I1tn5X_{4lBHA}*H++SU zxN6O`J4RnEBP}=CE(X6@@U-ofeLyRPO}}JCbv5jKXgV># z`>%i4kuHY0F-b{Dsfgmx?*Tp%Kx)Sp0Ql@|ZTDWw(c-d#lT(L9{H_Jj*WtHZMdjsy zwleSbsSqNtJ_ln?R!?<9X%2+qa!&?cm9*dB3Tn1ph=iKvUEZ_Swekx_Bl?iMwNq}K zrUn%XJpk1z!&i9K>ei0J=x5_-t>%Nx4Mi{X+>dzZ14XLF(hGYXo!HNxT|7LHP?Oi` z<&q=wkP=hS`}h3(iBY3hWlW6xO5_&Cri72AQlO-w`jH|o&dMs!L;{W)z&i1T8@T&{ zR@(Q&UcUx42iyHVSLig7wQpFS0Yv@^lqc+eI&qB!lg{&?w{Gf$&|2Bn&`aoEQUO5&oofGN)?5n>pMw?cComYED2kA$uObA24 zrxmMyJE#KLrb=3g82cJsY=7>4%$ddRh^(~VS1229Q|Y)~`$$RY&4g}3f2^p)h5ExE zr&mkJvC3qJ$vd7Os9PJHeg4Gf_Kg>>GgrTWosIAGH)DLi2W3on>hCB0-`a`L^|GBU zWT)jp{F01U8+*KVfaK5E7EW*ytJ_r{m0E(3Sc_<&)TUwqxNS$x-dtBBLinL zI*8psSL0C2fx^1&Z5#bRW%=2Y&MI_3MB({;-%^(jM?dl#tLCYQc)G0NaiG(=5kKWa zFB+XWib13<$i3TACZ-zw^g;iFFsWVLTj-~N=nlD+4tP|I6#8Wj(n5l5iw_Gg>}Y8_ zE^2Zzf{LMAXnPc7?88E+ZTklX25bVvk1njp#Td2UdPSgqd9O+{4TkS8PQ>KBpnk;B z0U?@pLn3{)`*pXUdSt4m0E_8D6B>~LWjDmcgohK`YyCC|L*8%P<|jlf=@Gu*c6Hqa z#`o9ou)C+HU=wTm?Tgvj*(@*EUNUdGW=hJyl~A8Q@Y$X3thv3%KiaM8LwmHr%YnPT zBpiRSR`(j8WjF9LiDZZfrYl#Xz-eTf3#j@*lOsfXV2>i>q<}XV*@Z|9l|+HgwzeFr zv??G}2?#;~*g(sSZuqU*7))=^bk_LxA+n~)t3(Oq4*S;*AD$MrJkE1Ye1tt6dw!Hi zY%f*vPQ@E=p(NJDrv6S!Gi-6bN<%XW34`@_e4&nA|562312ePq!on`!z%wBjE|N@# zIhkO8hG@KDE~EwG52&LrW8^veV1sP@3KCk7GtI}r>B@V&2U`8LEidqfI=Hy1X&H#H z-1j7j)qiSXZiGr&nrF)d$LNb`Mnx)UZeCXv`^+Qy>1K<@o!A4`(507&bLv|;j_TEZ zI4QI0s*B&5cUw4y-)=@y2JWsM>Zk1I)$IJT891^iuJv4@+<~}waIb_o?-hcgiY+Dw zz8BD5T@C)hJWz8Q7}U$u(Cu~egT$jS!wBItzKx90D6?9wHa$HZnn+QPE#d%m zYahs%d5J^xt*J@wdUMiHov&}lbC4$!;o@=`>*p$3(rR)UT)m6P+AE2f5(z3NH_1ml5=f_rhGl^8%(lCc!c`3oKZ0bwot>XA9Mc6i5z*Ks9?> zuJCnf=mKTM7#SPK#*C4dBo{Q0{yqJ*c@(>or&h)TA%_#ClyPw&ynT5`@!&auqJ}NR zyAkd)DaNMy;PIQ*8dg2ae`5aq3^xAEJ5r-p zW}!mte{VhMunZh%563I};_F}-xF+W1R8`}q z`rWN^;fv5LwRCnWS6s+S)ne1^+gkZeu~NN1ml(~Np~y1&%F_Eqvu{I;J#Se4u;+eA z3g=VbT_J(aJ%)Z5KU{-(24xDiJ{!#xLrq_NRa6K`x1ogjuztKdPO(WtP74zi_RT)j zJTUH#eIR+{Hx$qs4o65hJC^_(cQ$&aD2oq%3ipF0&6F(Qu7RhCO&!L^lvoc{q z{=tN)My<1f(pQqY5%EYG`KK%8{v>ag2ycyFRu&5cYQeCuyK0;Lr|;sz;z_RT*QVTp zk1EJo#3jnj6Z3g!xpBU^i!bsI>dq`N zVfi?g!NP}OnfLAxQalWwGt)JkCU)2Y{IB`dF05Ktp8zn{FYJ#`AbAUqit?gtdX?9{ zbJ7bdpRc!TRhV>!;F24T*H5LNoEX>ab!YBmcz$UUWP0w*m9M8ueEmA-9BVXt49FWv zE?;J-`30@~JEghBMYJjy`lHu{#d&(g4Zt4(N!U9AQk4vM;$Bk(g0BtIxr*CJDY6K( z2UaexZ){M4vl}v9%Ra%_5k|}t=S<$?TUv$8Rw_jt$zp&=+>EoR5-q9wi?k$7oSe9s zs|=`L0VEXLKIQN~?Gfr)e!T+#lc+BvA&{Va;hyuCV_W zVMERvO!Rz-F1YwIQZRwJjMM0?R8M&NZ#suUa2OuI7u4%UywCxOnp0)$Rp_{mv-XjfJsFr$!+A zgoLi%K7z2MXijEf$%V^r8xz9q&F#yKGUxlUdqG~D!!iox_DHq6=E%(Ro>{Z|EJh7Z zdQv_49r@)jZa%5JXF~zB8?ky_9F*VSyi<7kN$K4OPYVZiF;Ly;>JFx+rY0qU#Agly zLHKM2Y@t#4@OI4e&63@EzwX1B_Svl0pD~?u#kAy9R0On<=N00jXwncDTTDU~2Kx-{ zDzqzw&)yxzhDju_-8@4sOR|kJT0&R|& znHe+7Gzial2z8T22<*X2b;+rfvd}*C>RhSMUD?t0rrGhsN&uP z1rhJhw#VTuP$63?)38~M6yns7{(a&L2~N2yAl;1DKqd(Trk|szyC(XLb@YIK6x8l^ zW7Y0p58mvjd#Ox{B1-H_M}Rn%h+3!!+Mayt@cNsVC|xu%({0A`%O|VnIum^%Mi4Y& zAPKWf3+}10oea&QUIE!mQv{(OK31%-GmiQc_Y*t~H2+5x^f&5s@5D zBeq80{*mKZCd|6GP0-4MprZAu+aAy&jx{mgCnP#t%aA zl)gcrZ6Rd0o(M{LH*;!rA1ga3C%ZB7Nu9FQx^0+J2l3Xz7H^#Jvg4%A`NU1}IA#^T zY;GbTj<-Ky6ycFIImj+N9Rey@r_lHq0wX26`ZpqBnXLohk~ya|ax_cHM%4^<&>jb6 zXX*jG1vHxpI`kA_S9|EiXPv)nHI8kcR~S6sBMO$W6O`}ais1C943<{!;T6uFr$p0& zlDz4SFfXt%G0}-^km?Oo^=>b&LItQy>U!c-Bk`&9}h zXVUlnSOohx!Df1hIB{oOSXRPu??|+LKEe%hAHd-Nl^6}TssBa{e#UREj1Z<}=yBP4 zA8SdO*Mw@#$y&;$v!9evue6)-Y}l+v+dq!t96N>$ek2bw>(k+EEp*C|X_~lkF~~f_ zMnkY8YBlBb$YLQ@YKF)6S6ENJYY#-@G4koZyZ(`iZOJa3M^Mu90|ElTz!q7_`|ims zWPgJbR47+oNvWy&X3Xx=AW_I;>VBvP^DS68IHY~*{jffWu(RjFiRvkNy1D7iRJgr1 z&!_^V3JMEdS!7mV8*-@*j1Fiqioq;ewKoCSJIZsdhS_Mj^T&^5(89oajt+`mnCgV) zv)Jsx7iT6m$bT<~8ytP^5=#WLJ=BL{P{t@LPZd4_s{^g~R>W!mZ4@$%)u@IHaritl z85o2dNOXmT?hk5O{Nc-kAumIMf#IH4;~(Be*lQxwBmeHA=r#l(?SeN5b%K~}0n^3M z?$Y$%fNp?!PtFM#1!$lRVt`s?WMFufV+Ah6SA;aucNW2d_1L+dl+}CIh&t$UpOS(? zWHQ9B;PVbPD^kEH=dYVmT?|6FI6Fh_phLOv0j_9y5rmQ85=~>AkGhnmorE<-a|%j- zu5amo?n3t3OpWa@k5eQwDJ}twsS2UoMxpu_5Hvkf<$613a!VzL7X}GbDdhS+SdgE*@BY2 zF<_T?1l}-(jE05kJ{N*k)Yt!9dcLDfQP zyVX~prl(cf{TAS$&g~)i9_PV+b@AoEta)|>B*pYmv_JfbV;ijM%RX$U5Eq^h&Uuo< z?(43ahcaJQ2Zklc2@zT=x;cGZD}ZZhbACn_l$w#^ zEc)Q5>HmRn{^vSyZrS`bWcb_l;RqS;g=sUVv_lBbyZJP+hbjG62D{Jl2c8}o{!yY2 zos}dB^b3A7GiIOy>uiJN)lVYBq5ttMKV^i}$iC8Uk`XoV8H>xy9PhVnIG2i(zp?o@ zW);X&e8jVSkps>V@hLNjz}3I z7>y~=!IJWZiUX!_YAxaxPFINX1{N;5Gk7Y8+%p90=5OdwL$6Vw*OD?q!8KV+WDl4q zM_?^BvEhj0XMgNYLrU70t9v`?%VR1qp$RW4#-(03f4*r-n3?&$myH2+&?N|6g3X|( zj;r8d-F4L$&eWW>bjpj-2yiFeiZ%Zh8geP?)6%)&ckTkR?y{m=H%z~=Le0{H}vX$=xwuU@BfL(h@Br) z{LrS6)4sIQ2~<Q6vPQ6)`N^VkiNS)*<$x3#uDw{<+v{KX5V4UA;)Db4+MsJm5N3%#aRvVa}c z4@M{Kn__Eyk@gVusZgXR9RTqJ^FB86g*8fxflq8;8m=@#@6;_M(A=i?nIwo(HMlnj zQcK{*5%&+0#i2%wL^2u4%|qO-zs`1n>)Do9RnR~sIj`xOQSi4ed=5Kvaf7heUXf$Y z7(eH;G`~HcDo(vclNBFAgd;Fmq&Q#!_eE)QE3R*m<)!$5m_E7=rQ zBS~v57&YqW|Ybi523|KYw395^6DcC0u&+8(sX@6X67i;SK)vMEE-l zqDJ$_tNrSVr?|eKJ04?3?SFSMbC<-9pf1}I_@Hq1d{m{=uF;CfvBZ#}xq5G#>608y zS((7^Z=%)7`9I=sEQ_qv%2X?&pBf(67^G?+@b-3yPiEf7yE3{WJns_jiX{{GlcXHM z+7Qi2KSN^jc&#+?h|U@r-L`)9PU!cUbV8$dr1H}Ie8+@m=ibWrKx4Hm#Y5b*&0EAk zNcqb(?)})=#Qa?EaMPeSt-_fjXT>V6yZ`3IZ#(8uJKhDAL{v-FqF087<-xm8qI3Rl zSp~G}3)d{3@nj0b*caW^u-i^snL&l$B|7`ixIgdl&xfsFbd|Q=9#~m_weaIglxLGA zJ3ISst1f}IWo+g7hFm4h{y6IrBBGDXe%U{S2qrAfzCp{0p64wYmY17cFNe;)Z+{WVi1kA9 z!ZCc074pkGw~VDup16OrL@kyzsH`q0mePb6tu^s$xPQ&+ZF^!=Tp3-34Y@HtbykBU zGgDuz{?yF4o6~I}a@T@k`ugVEhR>nzA7re*w!Z6@IJ7kMfvC;scOBvTc2NWQS&WZA zQfWMK1P$L^pz_rdDv?_lr0HVNbOl$pS{Pt3-=a$|ouUp({dmY;a5BB5f>*W)}gf^Di^J*zGqY8DI zF`YuHVZoLbj#7=Gj)wU{4UxsyK0hr(U<#a z;YqadM!e63;{SaAdB4RIGmcje+qO~u9qofD@zt7}d3LrnQM-(5mp5z6@@u+2W6jP= zg+=?eU8~U0a#^Zv(wPfV>ZMS$OK26n{5jn9ZPEk#rj~1ng}s8B9>tm2#X=;VXd$sfi=grwlH5QB|+1S_;vp zmrnTDovYW!;nUk2re_m^7=?StS?3;x)Jc{FT~uf_8;@+H<`TC6PX><=+4nSeaXA$- zZjxF1i~6mepH9NE=6dY7{h3jO7_SLmc!yhku%1J0MZO|K7KVQbXnXK7d^slhgWBp( z@|BQkmpJ!lg!ZkK-+YYi1fdi6{}Rb6v# zOovkJzyy!mW#hqY49=WnhKf2bzbC^bZBZR@9m`50qmXt6k zi9$Hvk1r=h;U!IdUU`3kzB<@CZiQOJcJ@bbI0;c$@L?`lx-gw?3_^O2;RTTMR0 za?GnPvg3&S24%JiT`ZyIQv!GG@l$8_oy$BtVYtz`*sHe-H#;cfq8UDVb8Q8SmAMl7 zwAGYN_u9aTzEAe%@X(2%I9sSC`O<%v_pxr5^mez0iBhTXto=Yu{3*S(qWyxWPTDNm zNJmf5si>qpfR1csEO0&jE-E~(2>a<`#hSXNSnaiKsTofJ9_DurM5K}*P~Cj$ z^=XaNa=J4#I7c*9k6imowJU{1H^We<7w$ZTmLl)p_xzS!T^{m&zae&2UTBfEi*Rnm zJ6_|qjZ9BY=bf|bMoh2}`2MNfmvp&Wy_0xnc@I8e)>qMYx4Lp|k1<+kBD>-&B7@s{ zqZZk*lN_(c7aAoa6qYEJmtF>3Hq|5fio(DA&$#_SKN@a2Jh53)PthNyL@uY?^ti{p zDfXWw{CcrP-a+&)d=U%b`6PnKR(vy_+c6A*L>GoK`)b-x)z4N+5!0`grn#KWI>!p_ zny0=Z9%ej@R)GbI-2a&xLHveYSWRRG%}K?YE@5RuN z`n&7G?)cx`Z<5ac-@l@F&42y%znds}cKE-KIXnFCF@F#L_c3RO|3CSb;U2fq)r7y6;B3z=M);(1p^uuG#Njh* z@c!SfOB#amJo>o*(`9VpD5w3xW49UVqj)mUw`QNY@7hj#%|?=#_!4H*@+0GFL~(lh z3|KtSo+GO*I$T_=^L|BK$!{zgLzpuZl?3g_Vl2cCKXye{Nr~0PAyLMQaf>bAV*3g` z=HqrF>ng`B7Hays>=`-cny>hN#UZ5L1N7PFcw2Xjh(_Wb3o)^Oz=oN~HDeK0BU7Fz z!Lj8U+!EH^(7U6GC)FDn_ixvUoX2gQ2^H)Jf#}o?i;e}QE0~xjCL)wwffT9=aq&up zSgr@Rss!ghd%hIY&LrE#@iB;S5RpI5!O@vHyj3;6G%el{@~2Du)N7+hM#U_Fjt&=Z z`n#JzwVMXA$?VsYk+mu%h28Zb2ltPPQvo421W3p?ryz4d<%cPfj)QkkTeY zC|H<{P2P1NEQUVS{7>VmdR}#|G75TH$sV`M&8&stwzLONIwPNb9C()eY^bD5BPP0N zKG|)hhq1xbSOolO>dkeh(D`%$Mh& zBqB-ya04ve=u}``Bp^O;D~rOq6Qp^DVbn&SurxTnaDW$61LJeMS&9DV zC;HD?``0V^g_EN>x!+AxTyNrJ(PU<2M3F1L6-yfsC}*qNDW)C2!lcs4kU*3K=gBEy z)=vDT@Y6cXbsDOqOOrVTH<)^@cFQm!J$yqQErzx*#+PSj0 zxVW^mG&z~(PVfaoZZ{*&I8p#2oIuBAXmEwtG4^XPkr0erf>Jbc9+ z?z#iKeX1mc?}IujT{hW!M_(ym>MOuF>TZzFYYU*|S4?oPNIS^5+uAOJ7ERg*8a`F= z<5EQDt-E)ry8OhVLF|dQ072N0RDur2G?+z_T+mA?v-_=+p#kzz6M%m{sIm9qQkepW zusSh83?dY(wu&(iya=mNWEmolJXa^*`Ps9Rh0K(RbjW7s(^~KYy;2!O_g_loo7p0y z*Sa@@CQ`wR4e=MC$~4`Z?7Kln_XwgyVr-lIUPb|QZ9`gDY`$Wl>)^b8X;al;YM6CY zrNEZ|3oetwc+eB?Kd)<4d}tp0#<$|#40c!}B3td0F}a8OBcMD(|M;}3a|@3r!>~K3 zF0E*#YB1?Y7%78q$D3bjG?#C^!rP#+)YJSyIa>>?WFgsDcc8V+cV(q_ve_;vJVKYHH!F zQq0{%zc^S}_IB$|jUIB|4g*p2ShIe>oJA;HM)WN0?5ugZqywroAQEoiSqJ=TX|Vu& zJ?JX{2pM;UoZNwo_1&MPsH_H?u!pA~Zb$_WA0KHyRS|;QHi6H|8FY>Ly6LXCujgxk zwqvn{doRKdsEMND;$HE}l;&L{uL;$2bObKNK6Gl+SwC0bJfDZwx71R@EnuJU!R2Hp z#Z{^a(rzrKwofEt-gER4$F*-GLutJYLH+6UPeh&pIv60;WB3sMiV!zl|eT_#$ zQ>C*CDHB@iMnqe?t{-Mxt7>4UK=HY{u1>t;#k&KD-8Cgtke4SRQ%PbWqS8@!`~%#J zt(_g*KmfqMPiia>LL7Wa5A|%ZPu>RA*45Rb4tKAN*)qCnn=Inq6NG?!UV&j$jt~;kwSkUj;4MgWPrU#*k8?LsK|r?&C)>Ny3>j`V8}xHeFPz6vK&G}z*|(v zZcwWaI6u*EckQ=&BI7X_DZffNH|opE<8ynpq@;XfwLtO&Rj#O7>Knw;+X&@fXja!q1S!b3IoEM-3u$EhPoTm< z&<8q3`>|^Muyi~}TkxlGy`DxisWm}H(3R`g$23AA5GYLz#F~Sx7D%{w^c6GvxymP75sS zr$qa__Y1lPPiD=fKBEzKo>BwWbLTJv>*QyT0A(1iq{07O#gA-e~}{STlqe2yav^lXBOG`D4!f&Y##soS5sHgmd_ zDZUU>Af)DX*FF->^@lWoHQHAXBi3Aq(mK4S#`{p<&0z!H?W-O#22!nB3sjV^$SIT1 zgzHD4jM#k@cM;`&)@*}F_rNbZ#s`j@`H9L}%G?oOnmQ?%P@iaKls{HKyz|tJw2k>%2pM0y>jpH@Mt_RtnxWV zR$?Ac)L($Lx$Rsv)O|-O;lSggS+h|j85QxMNLoKYp!1Y4pjXL9*2-+e8|Wv+Ui$zbc&DuUBy<5`BuQ&zvJ}4$6E_yA@ z%wT?5^s&v>MZ%WSi#&&CP-;nEa4L9ifr0L-6)wLqI?<)sDrFVDMkxJz8boa0cA3Fj0PTl~ znZGTJxl?1DER7u&%KMrw2$v2j++K!?VH>x;RXaRN)o-5@rqJcZ#`S}Hu{t+t4R5Lz zx{MaO*Rr(UVODnVt^8$aJ6Y$Z`Oc>Ue0Y}0yTvm!sTJx?}Tzh*>FGvRJc z9-*WHB^2!O6kU+13KY!p$d^sfttI^+#`HgrA)$8_&U3Oks)uQ=$m7uAwPs=V!Fbu? zg8@0s%vY5C&pn~sH+{wh`XDz@d;>iWt3rW6*(); + valueComboBox.setAutoCompleteEnabled(false); // we do our own completion with validation valueComboBox.setEditable(true); valueComboBox.setToolTipText(currentFormat.getToolTip()); valueComboBox.setDocument(new RestrictedInputDocument()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java index 86911f59b8..493b2689b6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java @@ -4,9 +4,9 @@ * 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. @@ -16,14 +16,14 @@ package ghidra.app.plugin.core.searchmem; import java.awt.Color; -import java.awt.event.KeyEvent; import java.util.*; import javax.swing.Icon; import javax.swing.JComponent; import docking.*; -import docking.action.*; +import docking.action.DockingAction; +import docking.action.MenuData; import docking.tool.ToolConstants; import docking.widgets.fieldpanel.support.Highlight; import docking.widgets.table.threaded.*; @@ -64,7 +64,7 @@ import ghidra.util.task.*; */ //@formatter:off @PluginInfo( - status = PluginStatus.RELEASED, + status = PluginStatus.DEPRECATED, packageName = CorePluginPackage.NAME, category = PluginCategoryNames.SEARCH, shortDescription = "Search bytes in memory", @@ -73,12 +73,12 @@ import ghidra.util.task.*; " The value may contain \"wildcards\" or regular expressions" + " that will match any byte or nibble.", servicesRequired = { ProgramManager.class, GoToService.class, TableService.class, CodeViewerService.class }, - servicesProvided = { MemorySearchService.class }, +// servicesProvided = { MemorySearchService.class }, eventsConsumed = { ProgramSelectionPluginEvent.class } ) //@formatter:on public class MemSearchPlugin extends Plugin implements OptionsChangeListener, - DockingContextListener, NavigatableRemovalListener, MemorySearchService { + DockingContextListener, NavigatableRemovalListener { /** Constant for read/writeConfig() for dialog options */ private static final String SHOW_ADVANCED_OPTIONS = "Show Advanced Options"; @@ -243,12 +243,12 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, } - @Override - public void setIsMnemonic(boolean isMnemonic) { - // provides the dialog with the knowledge of whether or not - // the action being performed is a MnemonicSearchPlugin - this.isMnemonic = isMnemonic; - } +// @Override +// public void setIsMnemonic(boolean isMnemonic) { +// // provides the dialog with the knowledge of whether or not +// // the action being performed is a MnemonicSearchPlugin +// this.isMnemonic = isMnemonic; +// } private void setNavigatable(Navigatable newNavigatable) { if (newNavigatable == navigatable) { @@ -329,16 +329,16 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, return new BytesFieldLocation(program, address); } - @Override - public void search(byte[] bytes, NavigatableActionContext context) { - setNavigatable(context.getNavigatable()); - invokeSearchDialog(context); - } - - @Override - public void setSearchText(String maskedString) { - searchDialog.setSearchText(maskedString); - } +// @Override +// public void search(byte[] bytes, NavigatableActionContext context) { +// setNavigatable(context.getNavigatable()); +// invokeSearchDialog(context); +// } +// +// @Override +// public void setSearchText(String maskedString) { +// searchDialog.setSearchText(maskedString); +// } private void createActions() { searchAction = new NavigatableContextAction("Search Memory", getName(), false) { @@ -349,9 +349,8 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, } }; searchAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, searchAction.getName())); - String[] menuPath = new String[] { "&Search", "&Memory..." }; + String[] menuPath = new String[] { "&Search", "Memory (Deprecated)..." }; searchAction.setMenuBarData(new MenuData(menuPath, "search")); - searchAction.setKeyBindingData(new KeyBindingData('S', 0)); searchAction.setDescription("Search Memory for byte sequence"); searchAction.addToWindowWhen(NavigatableActionContext.class); tool.addAction(searchAction); @@ -372,10 +371,10 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, .setHelpLocation(new HelpLocation(HelpTopics.SEARCH, searchAgainAction.getName())); menuPath = new String[] { "&Search", "Repeat Memory Search" }; searchAgainAction.setMenuBarData(new MenuData(menuPath, "search")); - searchAgainAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_F3, 0)); searchAgainAction.setDescription("Search Memory for byte sequence"); searchAgainAction.addToWindowWhen(NavigatableActionContext.class); tool.addAction(searchAgainAction); + } private void initializeOptionListeners() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/MemorySearchService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/MemorySearchService.java index 379fa84118..d52c9e6c60 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/MemorySearchService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/MemorySearchService.java @@ -4,9 +4,9 @@ * 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. @@ -15,23 +15,36 @@ */ package ghidra.app.services; -import ghidra.app.context.NavigatableActionContext; +import ghidra.app.nav.Navigatable; +import ghidra.features.base.memsearch.gui.MemorySearchProvider; +import ghidra.features.base.memsearch.gui.SearchSettings; +/** + * Service for invoking the {@link MemorySearchProvider} + * @deprecated This is not a generally useful service, may go away at some point + */ +@Deprecated(since = "11.2") public interface MemorySearchService { - /* - * sets up MemSearchDialog based on given bytes + /** + * Creates a new memory search provider window + * @param navigatable the navigatable used to get bytes to search + * @param input the input string to search for + * @param settings the settings that determine how to interpret the input string + * @param useSelection true if the provider should automatically restrict to a selection if + * a selection exists in the navigatable */ - public void search(byte[] bytes, NavigatableActionContext context); + public void createMemorySearchProvider(Navigatable navigatable, String input, + SearchSettings settings, boolean useSelection); - /* - * sets the search value field to the masked bit string - */ - public void setSearchText(String maskedString); +// These method were removed because they didn't work correctly and were specific to the needs of +// one outlier plugin. The functionality has been replaced by the above method, which is also +// unlikely to be useful. - /* - * determines whether the dialog was called by a mnemonic or not - */ - public void setIsMnemonic(boolean isMnemonic); +// public void search(byte[] bytes, NavigatableActionContext context); +// +// public void setSearchText(String maskedString); +// +// public void setIsMnemonic(boolean isMnemonic); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayValuesFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayValuesFieldFactory.java index a467b26514..c8ec329427 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayValuesFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayValuesFieldFactory.java @@ -4,9 +4,9 @@ * 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. @@ -25,8 +25,10 @@ import ghidra.app.util.viewer.format.FieldFormatModel; import ghidra.app.util.viewer.format.FormatManager; import ghidra.app.util.viewer.proxy.ProxyObj; import ghidra.framework.options.*; +import ghidra.program.model.address.Address; import ghidra.program.model.data.DataType; import ghidra.program.model.listing.*; +import ghidra.program.util.AddressFieldLocation; import ghidra.program.util.ProgramLocation; import ghidra.util.HelpLocation; import ghidra.util.exception.AssertException; @@ -128,16 +130,34 @@ public class ArrayValuesFieldFactory extends FieldFactory { @Override public FieldLocation getFieldLocation(ListingField lf, BigInteger index, int fieldNum, ProgramLocation location) { - if (!(location instanceof ArrayElementFieldLocation)) { + + // Unless the location is specifically targeting the address field, then we should + // process the location because the arrays display is different from all other format + // models in that one line actually represents more than one array data element. So + // this field has the best chance of representing the location for array data elements. + + if (location instanceof AddressFieldLocation) { return null; } - ArrayElementFieldLocation loc = (ArrayElementFieldLocation) location; - ListingTextField btf = (ListingTextField) lf; - Data firstDataOnLine = (Data) btf.getProxy().getObject(); - int elementIndex = loc.getElementIndexOnLine(firstDataOnLine); - RowColLocation rcl = btf.dataToScreenLocation(elementIndex, loc.getCharOffset()); - return new FieldLocation(index, fieldNum, rcl.row(), rcl.col()); + RowColLocation rcl = getDataRowColumnLocation(location, lf); + return new FieldLocation(index, fieldNum, rcl.row(), rcl.col()); + } + + private RowColLocation getDataRowColumnLocation(ProgramLocation location, ListingField field) { + ListingTextField ltf = (ListingTextField) field; + Data firstDataOnLine = (Data) field.getProxy().getObject(); + + if (location instanceof ArrayElementFieldLocation loc) { + int elementIndex = loc.getElementIndexOnLine(firstDataOnLine); + return ltf.dataToScreenLocation(elementIndex, loc.getCharOffset()); + } + + Address byteAddress = location.getByteAddress(); + int byteOffset = (int) byteAddress.subtract(firstDataOnLine.getAddress()); + int componentSize = firstDataOnLine.getLength(); + int elementOffset = byteOffset / componentSize; + return ltf.dataToScreenLocation(elementOffset, 0); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/AddressableByteSequence.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/AddressableByteSequence.java new file mode 100644 index 0000000000..28987810eb --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/AddressableByteSequence.java @@ -0,0 +1,125 @@ +/* ### + * 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.features.base.memsearch.bytesequence; + +import ghidra.features.base.memsearch.bytesource.AddressableByteSource; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; + +/** + * This class provides a {@link ByteSequence} view into an {@link AddressableByteSource}. By + * specifying an address and length, this class provides a view into the byte source + * as a indexable sequence of bytes. It is mutable and can be reused by setting a new + * address range for this sequence. This was to avoid constantly allocating large byte arrays. + */ +public class AddressableByteSequence implements ByteSequence { + + private final AddressableByteSource byteSource; + private final byte[] bytes; + private final int capacity; + + private Address startAddress; + private int length; + + /** + * Constructor + * @param byteSource the source of the underlying bytes that is a buffer into + * @param capacity the maximum size range that this object will buffer + */ + public AddressableByteSequence(AddressableByteSource byteSource, int capacity) { + this.byteSource = byteSource; + this.capacity = capacity; + this.length = 0; + this.bytes = new byte[capacity]; + } + + /** + * Sets this view to an empty byte sequence + */ + public void clear() { + startAddress = null; + length = 0; + } + + /** + * Sets the range of bytes that this object will buffer. This immediately will read the bytes + * from the byte source into it's internal byte array buffer. + * @param range the range of bytes to buffer + */ + public void setRange(AddressRange range) { + // Note that this will throw an exception if the range length is larger then Integer.MAX + // which is unsupported by the ByteSequence interface + try { + setRange(range.getMinAddress(), range.getBigLength().intValueExact()); + } + catch (ArithmeticException e) { + throw new IllegalArgumentException("Length exceeds capacity"); + } + } + + /** + * Returns the address of the byte represented by the given index into this buffer. + * @param index the index into the buffer to get its associated address + * @return the Address for the given index + */ + public Address getAddress(int index) { + if (index < 0 || index >= length) { + throw new IndexOutOfBoundsException(); + } + if (index == 0) { + return startAddress; + } + return startAddress.add(index); + } + + @Override + public int getLength() { + return length; + } + + @Override + public byte getByte(int index) { + if (index < 0 || index >= length) { + throw new IndexOutOfBoundsException(); + } + return bytes[index]; + } + + @Override + public byte[] getBytes(int index, int size) { + if (index < 0 || index + size > length) { + throw new IndexOutOfBoundsException(); + } + byte[] results = new byte[size]; + System.arraycopy(bytes, index, results, 0, size); + return results; + } + + @Override + public boolean hasAvailableBytes(int index, int length) { + return index >= 0 && index + length <= getLength(); + } + + private void setRange(Address start, int length) { + if (length > capacity) { + throw new IllegalArgumentException("Length exceeds capacity"); + } + this.startAddress = start; + this.length = length; + byteSource.getBytes(start, bytes, length); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/ByteSequence.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/ByteSequence.java new file mode 100644 index 0000000000..a4818d754a --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/ByteSequence.java @@ -0,0 +1,53 @@ +/* ### + * 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.features.base.memsearch.bytesequence; + +/** + * An interface for accessing bytes from a byte source. + */ +public interface ByteSequence { + + /** + * Returns the length of available bytes. + * @return the length of the sequence of bytes + */ + public int getLength(); + + /** + * Returns the byte at the given index. The index must between 0 and the extended length. + * @param index the index in the byte sequence to retrieve a byte value + * @return the byte at the given index + */ + public byte getByte(int index); + + /** + * A convenience method for checking if this sequence can provide a range of bytes from some + * offset. + * @param index the index of the start of the range to check for available bytes + * @param length the length of the range to check for available bytes + * @return true if bytes are available for the given range + */ + public boolean hasAvailableBytes(int index, int length); + + /** + * Returns a byte array containing the bytes from the given range. + * @param start the start index of the range to get bytes + * @param length the number of bytes to get + * @return a byte array containing the bytes from the given range + */ + public byte[] getBytes(int start, int length); + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/ExtendedByteSequence.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/ExtendedByteSequence.java new file mode 100644 index 0000000000..46d324751e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesequence/ExtendedByteSequence.java @@ -0,0 +1,98 @@ +/* ### + * 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.features.base.memsearch.bytesequence; + +/** + * A class for accessing a contiguous sequence of bytes from some underlying byte source to + * be used for searching for a byte pattern within the byte source. This sequence of bytes + * consists of two parts; the primary sequence and an extended sequence. Search matches + * must begin in the primary sequence, but may extend into the extended sequence. + *

+ * Searching large ranges of memory can be partitioned into searching smaller chunks. But + * to handle search sequences that span chunks, two chunks are presented at a time, with the second + * chunk being the extended bytes. On the next iteration of the search loop, the extended chunk + * will become the primary chunk, with the next chunk after that becoming the extended sequence + * and so on. + */ +public class ExtendedByteSequence implements ByteSequence { + + private ByteSequence main; + private ByteSequence extended; + private int extendedLength; + + /** + * Constructs an extended byte sequence from two {@link ByteSequence}s. + * @param main the byte sequence where search matches may start + * @param extended the byte sequence where search matches may extend into + * @param extendedLimit specifies how much of the extended byte sequence to allow search + * matches to extend into. (The extended buffer will be the primary buffer next time, so + * it is a full size buffer, but we only need to use a portion of it to support overlap. + */ + public ExtendedByteSequence(ByteSequence main, ByteSequence extended, int extendedLimit) { + this.main = main; + this.extended = extended; + this.extendedLength = main.getLength() + Math.min(extendedLimit, extended.getLength()); + } + + @Override + public int getLength() { + return main.getLength(); + } + + /** + * Returns the overall length of sequence of available bytes. This will be the length of + * the primary sequence as returned by {@link #getLength()} plus the length of the available + * extended bytes, if any. + * @return the + */ + public int getExtendedLength() { + return extendedLength; + } + + @Override + public byte getByte(int i) { + int mainLength = main.getLength(); + if (i >= mainLength) { + return extended.getByte(i - mainLength); + } + return main.getByte(i); + } + + @Override + public byte[] getBytes(int index, int size) { + if (index < 0 || index + size > extendedLength) { + throw new IndexOutOfBoundsException(); + } + int length = main.getLength(); + if (index + size < length) { + return main.getBytes(index, size); + } + if (index >= length) { + return extended.getBytes(index - length, size); + } + // otherwise it spans + byte[] results = new byte[size]; + for (int i = 0; i < size; i++) { + results[i] = getByte(index + i); + } + return results; + } + + @Override + public boolean hasAvailableBytes(int index, int length) { + return index >= 0 && index + length <= getExtendedLength(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/AddressableByteSource.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/AddressableByteSource.java new file mode 100644 index 0000000000..9bbb081eed --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/AddressableByteSource.java @@ -0,0 +1,59 @@ +/* ### + * 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.features.base.memsearch.bytesource; + +import java.util.List; + +import ghidra.program.model.address.Address; + +/** + * Interface for reading bytes from a program. This provides a level of indirection for reading the + * bytes of a program so that the provider of the bytes can possibly do more than just reading the + * bytes from the static program. For example, a debugger would have the opportunity to refresh the + * bytes first. + *

+ * This interface also provides methods for determining what regions of memory can be queried and + * what addresses sets are associated with those regions. This would allow client to present choices + * about what areas of memory they are interested in AND are valid to be examined. + */ +public interface AddressableByteSource { + + /** + * Retrieves the byte values for an address range. + * + * @param address The address of the first byte in the range + * @param bytes the byte array to store the retrieved byte values + * @param length the number of bytes to retrieve + * @return the number of bytes actually retrieved + */ + public int getBytes(Address address, byte[] bytes, int length); + + /** + * Returns a list of memory regions where each region has an associated address set of valid + * addresses that can be read. + * + * @return a list of readable regions + */ + public List getSearchableRegions(); + + /** + * Invalidates any caching of byte values. This intended to provide a hint in debugging scenario + * that we are about to issue a sequence of byte value requests where we are re-acquiring + * previous requested byte values to look for changes. + */ + public void invalidate(); + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/EmptyByteSource.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/EmptyByteSource.java new file mode 100644 index 0000000000..a9e2f1ff33 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/EmptyByteSource.java @@ -0,0 +1,42 @@ +/* ### + * 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.features.base.memsearch.bytesource; + +import java.util.List; + +import ghidra.program.model.address.Address; + +/** + * Implementation for an empty {@link AddressableByteSource} + */ +public enum EmptyByteSource implements AddressableByteSource { + INSTANCE; + + @Override + public int getBytes(Address address, byte[] bytes, int length) { + return 0; + } + + @Override + public List getSearchableRegions() { + return List.of(); + } + + @Override + public void invalidate() { + // nothing to do + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java new file mode 100644 index 0000000000..0506245e7b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java @@ -0,0 +1,56 @@ +/* ### + * 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.features.base.memsearch.bytesource; + +import java.util.List; + +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryAccessException; + +/** + * {@link AddressableByteSource} implementation for a Ghidra {@link Program} + */ +public class ProgramByteSource implements AddressableByteSource { + + private Memory memory; + + public ProgramByteSource(Program program) { + memory = program.getMemory(); + } + + @Override + public int getBytes(Address address, byte[] bytes, int length) { + try { + return memory.getBytes(address, bytes, 0, length); + } + catch (MemoryAccessException e) { + return 0; + } + } + + @Override + public List getSearchableRegions() { + return ProgramSearchRegion.ALL; + } + + @Override + public void invalidate() { + // nothing to do in the static case + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramSearchRegion.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramSearchRegion.java new file mode 100644 index 0000000000..c2bbc3fd2f --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramSearchRegion.java @@ -0,0 +1,78 @@ +/* ### + * 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.features.base.memsearch.bytesource; + +import java.util.List; + +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; + +/** + * An enum specifying the selectable regions within a {@link Program} that users can select for + * searching memory. + */ +public enum ProgramSearchRegion implements SearchRegion { + LOADED("Loaded Blocks", + "Searches all memory blocks that represent loaded program instructions and data") { + + @Override + public boolean isDefault() { + return true; + } + + @Override + public AddressSetView getAddresses(Program program) { + Memory memory = program.getMemory(); + return memory.getLoadedAndInitializedAddressSet(); + } + }, + OTHER("All Other Blocks", "Searches non-loaded initialized blocks") { + + @Override + public boolean isDefault() { + return false; + } + + @Override + public AddressSetView getAddresses(Program program) { + Memory memory = program.getMemory(); + AddressSetView all = memory.getAllInitializedAddressSet(); + AddressSetView loaded = memory.getLoadedAndInitializedAddressSet(); + return all.subtract(loaded); + } + }; + + public static final List ALL = List.of(values()); + + private String name; + private String description; + + ProgramSearchRegion(String name, String description) { + this.name = name; + this.description = description; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/SearchRegion.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/SearchRegion.java new file mode 100644 index 0000000000..e6ca49a812 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/SearchRegion.java @@ -0,0 +1,52 @@ +/* ### + * 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.features.base.memsearch.bytesource; + +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; + +/** + * Interface to specify a named region within a byte source (Program) that users can select to + * specify {@link AddressSetView}s that can be searched. + */ +public interface SearchRegion { + + /** + * The name of the region. + * @return the name of the region + */ + public String getName(); + + /** + * Returns a description of the region. + * @return a description of the region + */ + public String getDescription(); + + /** + * Returns the set of addresses from a specific program that is associated with this region. + * @param program the program that determines the specific addresses for a named region + * @return the set of addresses for this region as applied to the given program + */ + public AddressSetView getAddresses(Program program); + + /** + * Returns true if this region should be included in the default selection of which regions to + * search. + * @return true if this region should be selected by default + */ + public boolean isDefault(); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/combiner/Combiner.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/combiner/Combiner.java new file mode 100644 index 0000000000..934a6007f2 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/combiner/Combiner.java @@ -0,0 +1,123 @@ +/* ### + * 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.features.base.memsearch.combiner; + +import java.util.*; +import java.util.function.BiFunction; + +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.program.model.address.Address; + +/** + * An enum of search results "combiners". Each combiner determines how to combine two sets of + * memory search results. The current or existing results is represented as the "A" set and the + * new search is represented as the "B" set. + */ +public enum Combiner { + REPLACE("New", Combiner::replace), + UNION("Add To", Combiner::union), + INTERSECT("Intersect", Combiner::intersect), + XOR("Xor", Combiner::xor), + A_MINUS_B("A-B", Combiner::subtract), + B_MINUS_A("B-A", Combiner::reverseSubtract); + + private String name; + private BiFunction, List, Collection> function; + + private Combiner(String name, + BiFunction, List, Collection> function) { + this.name = name; + this.function = function; + } + + public String getName() { + return name; + } + + public Collection combine(List matches1, List matches2) { + return function.apply(matches1, matches2); + } + + private static Collection replace(List matches1, + List matches2) { + + return matches2; + } + + private static Collection union(List matches1, + List matches2) { + + Map matches1Map = createMap(matches1); + for (MemoryMatch match2 : matches2) { + Address address = match2.getAddress(); + MemoryMatch match1 = matches1Map.get(address); + if (match1 == null || match2.getLength() > match1.getLength()) { + matches1Map.put(address, match2); + } + } + return matches1Map.values(); + } + + private static Collection intersect(List matches1, + List matches2) { + + List intersection = new ArrayList<>(); + Map matches1Map = createMap(matches1); + + for (MemoryMatch match2 : matches2) { + Address address = match2.getAddress(); + MemoryMatch match1 = matches1Map.get(address); + if (match1 != null) { + MemoryMatch best = match2.getLength() > match1.getLength() ? match2 : match1; + intersection.add(best); + } + } + return intersection; + } + + private static List xor(List matches1, List matches2) { + List results = new ArrayList<>(); + results.addAll(subtract(matches1, matches2)); + results.addAll(subtract(matches2, matches1)); + return results; + } + + private static Collection subtract(List matches1, + List matches2) { + + Map matches1Map = createMap(matches1); + + for (MemoryMatch match2 : matches2) { + Address address = match2.getAddress(); + matches1Map.remove(address); + } + return matches1Map.values(); + } + + private static Collection reverseSubtract(List matches1, + List matches2) { + return subtract(matches2, matches1); + } + + private static Map createMap(List matches) { + Map map = new HashMap<>(); + for (MemoryMatch result : matches) { + map.put(result.getAddress(), result); + } + return map; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/BinarySearchFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/BinarySearchFormat.java new file mode 100644 index 0000000000..c1d425dcd3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/BinarySearchFormat.java @@ -0,0 +1,188 @@ +/* ### + * 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.features.base.memsearch.format; + +import java.util.*; + +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.matcher.*; +import ghidra.util.HTMLUtilities; + +/** + * {@link SearchFormat} for parsing and display bytes in a binary format. This format only + * accepts 0s or 1s or wild card characters. + */ +class BinarySearchFormat extends SearchFormat { + private static final String VALID_CHARS = "01x?."; + private static final int MAX_GROUP_SIZE = 8; + + BinarySearchFormat() { + super("Binary"); + } + + @Override + public ByteMatcher parse(String input, SearchSettings settings) { + input = input.trim(); + if (input.isBlank()) { + return new InvalidByteMatcher(""); + } + + List byteGroups = getByteGroups(input); + + if (hasInvalidChars(byteGroups)) { + return new InvalidByteMatcher("Invalid character"); + } + + if (checkGroupSize(byteGroups)) { + return new InvalidByteMatcher("Max group size exceeded. Enter to add more."); + } + + byte[] bytes = getBytes(byteGroups); + byte[] masks = getMask(byteGroups); + return new MaskedByteSequenceByteMatcher(input, bytes, masks, settings); + } + + @Override + public String getToolTip() { + return HTMLUtilities.toHTML( + "Interpret value as a sequence of binary digits.\n" + + "Spaces will start the next byte. Bit sequences less\n" + + "than 8 bits are padded with 0's to the left. \n" + + "Enter 'x', '.' or '?' for a wildcard bit"); + } + + private boolean checkGroupSize(List byteGroups) { + for (String byteGroup : byteGroups) { + if (byteGroup.length() > MAX_GROUP_SIZE) { + return true; + } + } + return false; + } + + private List getByteGroups(String input) { + List list = new ArrayList(); + StringTokenizer st = new StringTokenizer(input); + while (st.hasMoreTokens()) { + list.add(st.nextToken()); + } + return list; + } + + private boolean hasInvalidChars(List byteGroups) { + for (String byteGroup : byteGroups) { + if (hasInvalidChars(byteGroup)) { + return true; + } + } + return false; + } + + private boolean hasInvalidChars(String string) { + for (int i = 0; i < string.length(); i++) { + if (VALID_CHARS.indexOf(string.charAt(i)) < 0) { + return true; + } + } + return false; + } + + private byte getByte(String token) { + byte b = 0; + for (int i = 0; i < token.length(); i++) { + b <<= 1; + char c = token.charAt(i); + if (c == '1') { + b |= 1; + } + } + return b; + } + + /** + * Return a mask byte that has a bit set to 1 for each bit that is not a wildcard. Any bits + * that aren't specified (i.e. token.lenght < 8) are treated as valid test bits. + * @param token the string of bits to determine a mask for. + */ + private byte getMask(String token) { + byte b = 0; + for (int i = 0; i < 8; i++) { + b <<= 1; + if (i < token.length()) { + char c = token.charAt(i); + if (c == '1' || c == '0') { + b |= 1; + } + } + else { + b |= 1; + } + + } + + return b; + } + + private byte[] getBytes(List byteGroups) { + byte[] bytes = new byte[byteGroups.size()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = getByte(byteGroups.get(i)); + } + return bytes; + } + + private byte[] getMask(List byteGroups) { + byte[] masks = new byte[byteGroups.size()]; + for (int i = 0; i < masks.length; i++) { + masks[i] = getMask(byteGroups.get(i)); + } + return masks; + } + + @Override + public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) { + SearchFormat oldFormat = oldSettings.getSearchFormat(); + if (oldFormat.getFormatType() != SearchFormatType.STRING_TYPE) { + ByteMatcher byteMatcher = oldFormat.parse(text, oldSettings); + if ((byteMatcher instanceof MaskedByteSequenceByteMatcher matcher)) { + byte[] bytes = matcher.getBytes(); + byte[] mask = matcher.getMask(); + return getMaskedInputString(bytes, mask); + } + } + + return isValidText(text, newSettings) ? text : ""; + } + + private String getMaskedInputString(byte[] bytes, byte[] masks) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + for (int shift = 7; shift >= 0; shift--) { + int bit = bytes[i] >> shift & 0x1; + int maskBit = masks[i] >> shift & 0x1; + builder.append(maskBit == 0 ? '.' : Integer.toString(bit)); + } + builder.append(" "); + } + + return builder.toString().trim(); + } + + @Override + public SearchFormatType getFormatType() { + return SearchFormatType.BYTE; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/DecimalSearchFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/DecimalSearchFormat.java new file mode 100644 index 0000000000..ff5ae7d709 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/DecimalSearchFormat.java @@ -0,0 +1,280 @@ +/* ### + * 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.features.base.memsearch.format; + +import java.math.BigInteger; +import java.util.StringTokenizer; + +import org.bouncycastle.util.Arrays; + +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.matcher.*; +import ghidra.util.HTMLUtilities; + +/** + * {@link SearchFormat} for parsing and display bytes in a decimal format. It supports sizes of + * 2,4,8,16 and can be either signed or unsigned. + */ +class DecimalSearchFormat extends SearchFormat { + + DecimalSearchFormat() { + super("Decimal"); + } + + @Override + public ByteMatcher parse(String input, SearchSettings settings) { + input = input.trim(); + if (input.isBlank()) { + return new InvalidByteMatcher(""); + } + int byteSize = settings.getDecimalByteSize(); + StringTokenizer tokenizer = new StringTokenizer(input); + int tokenCount = tokenizer.countTokens(); + byte[] bytes = new byte[tokenCount * byteSize]; + int bytesPosition = 0; + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + NumberParseResult result = parseNumber(token, settings); + if (result.errorMessage() != null) { + return new InvalidByteMatcher(result.errorMessage(), result.validInput()); + } + System.arraycopy(result.bytes(), 0, bytes, bytesPosition, byteSize); + bytesPosition += byteSize; + } + return new MaskedByteSequenceByteMatcher(input, bytes, settings); + } + + private NumberParseResult parseNumber(String tok, SearchSettings settings) { + BigInteger min = getMin(settings); + BigInteger max = getMax(settings); + try { + if (tok.equals("-")) { + if (settings.isDecimalUnsigned()) { + return new NumberParseResult(null, + "Negative numbers not allowed for unsigned values", false); + } + return new NumberParseResult(null, "Incomplete negative number", true); + } + BigInteger value = new BigInteger(tok); + if (value.compareTo(min) < 0 || value.compareTo(max) > 0) { + return new NumberParseResult(null, + "Number must be in the range [" + min + ", " + max + "]", false); + } + long longValue = value.longValue(); + return createBytesResult(longValue, settings); + } + catch (NumberFormatException e) { + return new NumberParseResult(null, "Number parse error: " + e.getMessage(), false); + } + } + + private BigInteger getMax(SearchSettings settings) { + boolean unsigned = settings.isDecimalUnsigned(); + int size = settings.getDecimalByteSize(); + int shift = unsigned ? 8 * size : 8 * size - 1; + return BigInteger.ONE.shiftLeft(shift).subtract(BigInteger.ONE); + } + + private BigInteger getMin(SearchSettings settings) { + boolean unsigned = settings.isDecimalUnsigned(); + int size = settings.getDecimalByteSize(); + if (unsigned) { + return BigInteger.ZERO; + } + return BigInteger.ONE.shiftLeft(8 * size - 1).negate(); + } + + private NumberParseResult createBytesResult(long value, SearchSettings settings) { + int byteSize = settings.getDecimalByteSize(); + byte[] bytes = new byte[byteSize]; + for (int i = 0; i < byteSize; i++) { + byte b = (byte) value; + bytes[i] = b; + value >>= 8; + } + if (settings.isBigEndian()) { + reverse(bytes); + } + return new NumberParseResult(bytes, null, true); + } + + @Override + public String getToolTip() { + return HTMLUtilities.toHTML( + "Interpret values as a sequence of decimal numbers, separated by spaces"); + } + + @Override + public int compareValues(byte[] bytes1, byte[] bytes2, SearchSettings settings) { + int byteSize = settings.getDecimalByteSize(); + // check each value one at a time, and return the first one different + for (int i = 0; i < bytes1.length / byteSize; i++) { + long value1 = getValue(bytes1, i * byteSize, settings); + long value2 = getValue(bytes2, i * byteSize, settings); + if (value1 != value2) { + if (byteSize == 8 && settings.isDecimalUnsigned()) { + return Long.compareUnsigned(value1, value2); + } + return Long.compare(value1, value2); + } + } + return 0; + } + + public long getValue(byte[] bytes, int index, SearchSettings settings) { + boolean isBigEndian = settings.isBigEndian(); + int byteSize = settings.getDecimalByteSize(); + boolean isUnsigned = settings.isDecimalUnsigned(); + + byte[] bigEndianBytes = getBigEndianBytes(bytes, index, isBigEndian, byteSize); + long value = isUnsigned ? bigEndianBytes[0] & 0xff : bigEndianBytes[0]; + for (int i = 1; i < byteSize; i++) { + value = (value << 8) | (bigEndianBytes[i] & 0xff); + } + return value; + } + + private byte[] getBigEndianBytes(byte[] bytes, int index, boolean isBigEndian, int byteSize) { + byte[] bigEndianBytes = new byte[byteSize]; + System.arraycopy(bytes, index * byteSize, bigEndianBytes, 0, byteSize); + if (!isBigEndian) { + reverse(bigEndianBytes); + } + return bigEndianBytes; + } + + @Override + public String getValueString(byte[] bytes, SearchSettings settings) { + return getValueString(bytes, settings, false); + } + + protected String getValueString(byte[] bytes, SearchSettings settings, boolean padNegative) { + int byteSize = settings.getDecimalByteSize(); + boolean isBigEndian = settings.isBigEndian(); + boolean isUnsigned = settings.isDecimalUnsigned(); + StringBuilder buffer = new StringBuilder(); + int numValues = bytes.length / byteSize; + for (int i = 0; i < numValues; i++) { + long value = getValue(bytes, i, settings); + String text = isUnsigned ? Long.toUnsignedString(value) : Long.toString(value); + buffer.append(text); + if (i != numValues - 1) { + buffer.append(", "); + } + } + int remainder = bytes.length - numValues * byteSize; + if (remainder > 0) { + byte[] remainderBytes = new byte[remainder]; + System.arraycopy(bytes, numValues * byteSize, remainderBytes, 0, remainder); + byte[] padded = padToByteSize(remainderBytes, byteSize, isBigEndian, padNegative); + long value = getValue(padded, 0, settings); + String text = isUnsigned ? Long.toUnsignedString(value) : Long.toString(value); + if (!buffer.isEmpty()) { + buffer.append(", "); + } + buffer.append(text); + } + + return buffer.toString(); + } + + @Override + public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) { + SearchFormat oldFormat = oldSettings.getSearchFormat(); + switch (oldFormat.getFormatType()) { + case BYTE: + return getTextFromBytes(text, oldSettings, newSettings); + case INTEGER: + return convertFromDifferentNumberFormat(text, oldSettings, newSettings); + + case STRING_TYPE: + case FLOATING_POINT: + default: + return isValidText(text, newSettings) ? text : ""; + + } + } + + private String convertFromDifferentNumberFormat(String text, SearchSettings oldSettings, + SearchSettings newSettings) { + int oldSize = oldSettings.getDecimalByteSize(); + int newSize = newSettings.getDecimalByteSize(); + boolean oldUnsigned = oldSettings.isDecimalUnsigned(); + boolean newUnsigned = newSettings.isDecimalUnsigned(); + + if (oldSize == newSize && oldUnsigned == newUnsigned) { + return text; + } + // if the new format is smaller, first try re-parsing to avoid unnecessary 0's + if (oldSize > newSize) { + if (isValidText(text, newSettings)) { + return text; + } + } + return getTextFromBytes(text, oldSettings, newSettings); + } + + private String getTextFromBytes(String text, SearchSettings oldSettings, + SearchSettings newSettings) { + byte[] bytes = getBytes(oldSettings.getSearchFormat(), text, oldSettings); + if (bytes == null) { + return ""; + } + boolean padNegative = shouldPadNegative(text); + String valueString = getValueString(bytes, newSettings, padNegative); + return valueString.replaceAll(",", ""); + } + + private boolean shouldPadNegative(String text) { + if (text.isBlank()) { + return false; + } + int lastIndexOf = text.trim().lastIndexOf(" "); + if (lastIndexOf < 0) { + // only pad negative if there is only one word in the text and it begins with '-' + return text.charAt(0) == '-'; + } + return false; + } + + private byte[] getBytes(SearchFormat oldFormat, String text, SearchSettings settings) { + ByteMatcher byteMatcher = oldFormat.parse(text, settings); + if (byteMatcher instanceof MaskedByteSequenceByteMatcher matcher) { + return matcher.getBytes(); + } + return null; + } + + private byte[] padToByteSize(byte[] bytes, int byteSize, boolean isBigEndian, + boolean padNegative) { + if (bytes.length >= byteSize) { + return bytes; + } + byte[] newBytes = new byte[byteSize]; + if (padNegative) { + Arrays.fill(newBytes, (byte) -1); + } + int startIndex = isBigEndian ? byteSize - bytes.length : 0; + System.arraycopy(bytes, 0, newBytes, startIndex, bytes.length); + + return newBytes; + } + + @Override + public SearchFormatType getFormatType() { + return SearchFormatType.INTEGER; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/FloatSearchFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/FloatSearchFormat.java new file mode 100644 index 0000000000..f212147e7f --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/FloatSearchFormat.java @@ -0,0 +1,200 @@ +/* ### + * 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.features.base.memsearch.format; + +import java.util.StringTokenizer; + +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.matcher.*; +import ghidra.util.HTMLUtilities; + +/** + * {@link SearchFormat} for parsing and display bytes in a float or double format. + */ +class FloatSearchFormat extends SearchFormat { + private String longName; + private int byteSize; + + FloatSearchFormat(String name, String longName, int size) { + super(name); + if (size != 8 && size != 4) { + throw new IllegalArgumentException("Only supports 4 or 8 byte floating point numbers"); + } + this.longName = longName; + this.byteSize = size; + } + + @Override + public ByteMatcher parse(String input, SearchSettings settings) { + input = input.trim(); + if (input.isBlank()) { + return new InvalidByteMatcher(""); + } + + StringTokenizer tokenizer = new StringTokenizer(input); + int tokenCount = tokenizer.countTokens(); + byte[] bytes = new byte[tokenCount * byteSize]; + int bytesPosition = 0; + while (tokenizer.hasMoreTokens()) { + String tok = tokenizer.nextToken(); + NumberParseResult result = parseNumber(tok, settings); + if (result.errorMessage() != null) { + return new InvalidByteMatcher(result.errorMessage(), result.validInput()); + } + System.arraycopy(result.bytes(), 0, bytes, bytesPosition, byteSize); + bytesPosition += byteSize; + } + return new MaskedByteSequenceByteMatcher(input, bytes, settings); + } + + private NumberParseResult parseNumber(String tok, SearchSettings settings) { + if (tok.equals("-") || tok.equals("-.")) { + return new NumberParseResult(null, "Incomplete negative floating point number", true); + } + if (tok.equals(".")) { + return new NumberParseResult(null, "Incomplete floating point number", true); + } + if (tok.endsWith("E") || tok.endsWith("e") || tok.endsWith("E-") || tok.endsWith("e-")) { + return new NumberParseResult(null, "Incomplete floating point number", true); + } + try { + long value = getValue(tok); + return new NumberParseResult(getBytes(value, settings), null, true); + + } + catch (NumberFormatException e) { + return new NumberParseResult(null, "Floating point parse error: " + e.getMessage(), + false); + } + } + + private long getValue(String tok) { + switch (byteSize) { + case 4: + float floatValue = Float.parseFloat(tok); + return Float.floatToIntBits(floatValue); + case 8: + default: + double dvalue = Double.parseDouble(tok); + return Double.doubleToLongBits(dvalue); + } + } + + private byte[] getBytes(long value, SearchSettings settings) { + byte[] bytes = new byte[byteSize]; + for (int i = 0; i < byteSize; i++) { + byte b = (byte) value; + bytes[i] = b; + value >>= 8; + } + if (settings.isBigEndian()) { + reverse(bytes); + } + return bytes; + } + + @Override + public String getToolTip() { + return HTMLUtilities.toHTML( + "Interpret values as a sequence of\n" + longName + " numbers, separated by spaces"); + } + + @Override + public int compareValues(byte[] bytes1, byte[] bytes2, SearchSettings settings) { + boolean isBigEndian = settings.isBigEndian(); + // check each value one at a time, and return the first one different + for (int i = 0; i < bytes1.length / byteSize; i++) { + double value1 = getValue(bytes1, i, isBigEndian); + double value2 = getValue(bytes2, i, isBigEndian); + if (value1 != value2) { + return Double.compare(value1, value2); + } + } + return 0; + } + + public Double getValue(byte[] bytes, int index, boolean isBigEndian) { + long bits = fromBytes(bytes, index, isBigEndian); + switch (byteSize) { + case 4: + float f = Float.intBitsToFloat((int) bits); + return (double) f; + case 8: + default: + return Double.longBitsToDouble(bits); + } + } + + private long fromBytes(byte[] bytes, int index, boolean isBigEndian) { + byte[] bigEndianBytes = new byte[byteSize]; + System.arraycopy(bytes, index * byteSize, bigEndianBytes, 0, byteSize); + if (!isBigEndian) { + reverse(bigEndianBytes); + } + + long value = 0; + for (int i = 0; i < bigEndianBytes.length; i++) { + value = (value << 8) | (bigEndianBytes[i] & 0xff); + } + return value; + } + + @Override + public String getValueString(byte[] bytes, SearchSettings settings) { + StringBuilder buffer = new StringBuilder(); + int numValues = bytes.length / byteSize; + for (int i = 0; i < numValues; i++) { + double value = getValue(bytes, i, settings.isBigEndian()); + buffer.append(Double.toString(value)); + if (i != numValues - 1) { + buffer.append(", "); + } + } + return buffer.toString(); + } + + @Override + public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) { + SearchFormat oldFormat = oldSettings.getSearchFormat(); + switch (oldFormat.getFormatType()) { + case BYTE: + return getTextFromBytes(text, oldFormat, oldSettings); + case FLOATING_POINT: + case STRING_TYPE: + case INTEGER: + default: + return isValidText(text, newSettings) ? text : ""; + + } + } + + private String getTextFromBytes(String text, SearchFormat oldFormat, SearchSettings settings) { + ByteMatcher byteMatcher = oldFormat.parse(text, settings); + if ((byteMatcher instanceof MaskedByteSequenceByteMatcher matcher)) { + byte[] bytes = matcher.getBytes(); + if (bytes.length >= byteSize) { + String valueString = getValueString(bytes, settings); + return valueString.replaceAll(",", ""); + } + } + return isValidText(text, settings) ? text : ""; + } + + @Override + public SearchFormatType getFormatType() { + return SearchFormatType.FLOATING_POINT; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/HexSearchFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/HexSearchFormat.java new file mode 100644 index 0000000000..9c56ba244b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/HexSearchFormat.java @@ -0,0 +1,244 @@ +/* ### + * 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.features.base.memsearch.format; + +import java.util.*; + +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.matcher.*; +import ghidra.util.HTMLUtilities; + +/** + * {@link SearchFormat} for parsing and display bytes in a hex format. This format only + * accepts hex digits or wild card characters. + */ +class HexSearchFormat extends SearchFormat { + + private static final String WILD_CARDS = ".?"; + private static final String VALID_CHARS = "0123456789abcdefABCDEF" + WILD_CARDS; + private static final int MAX_GROUP_SIZE = 16; + + HexSearchFormat() { + super("Hex"); + } + + @Override + public ByteMatcher parse(String input, SearchSettings settings) { + input = input.trim(); + if (input.isBlank()) { + return new InvalidByteMatcher(""); + } + + List byteGroups = getByteGroups(input); + + if (hasInvalidChars(byteGroups)) { + return new InvalidByteMatcher("Invalid character"); + } + + if (checkGroupSize(byteGroups)) { + return new InvalidByteMatcher("Max group size exceeded. Enter to add more."); + } + + List byteList = getByteList(byteGroups, settings); + byte[] bytes = getBytes(byteList); + byte[] masks = getMask(byteList); + return new MaskedByteSequenceByteMatcher(input, bytes, masks, settings); + } + + @Override + public String getToolTip() { + return HTMLUtilities.toHTML("Interpret value as a sequence of\n" + + "hex numbers, separated by spaces.\n" + "Enter '.' or '?' for a wildcard match"); + } + + private byte[] getBytes(List byteList) { + byte[] bytes = new byte[byteList.size()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = getByte(byteList.get(i)); + } + return bytes; + } + + private byte[] getMask(List byteList) { + byte[] masks = new byte[byteList.size()]; + for (int i = 0; i < masks.length; i++) { + masks[i] = getMask(byteList.get(i)); + } + return masks; + } + + /** + * Returns the search mask for the given hex byte string. Normal hex digits result + * in a "1111" mask and wildcard digits result in a "0000" mask. + */ + private byte getMask(String tok) { + char c1 = tok.charAt(0); + char c2 = tok.charAt(1); + int index1 = WILD_CARDS.indexOf(c1); + int index2 = WILD_CARDS.indexOf(c2); + if (index1 >= 0 && index2 >= 0) { + return (byte) 0x00; + } + if (index1 >= 0 && index2 < 0) { + return (byte) 0x0F; + } + if (index1 < 0 && index2 >= 0) { + return (byte) 0xF0; + } + return (byte) 0xFF; + } + + /** + * Returns the byte value to be used for the given hex bytes. Handles wildcard characters by + * return treating them as 0s. + */ + private byte getByte(String tok) { + char c1 = tok.charAt(0); + char c2 = tok.charAt(1); + // note: the hexValueOf() method will turn wildcard chars into 0s + return (byte) (hexValueOf(c1) * 16 + hexValueOf(c2)); + } + + private List getByteList(List byteGroups, SearchSettings settings) { + List byteList = new ArrayList<>(); + for (String byteGroup : byteGroups) { + List byteStrings = getByteStrings(byteGroup); + if (!settings.isBigEndian()) { + Collections.reverse(byteStrings); + } + byteList.addAll(byteStrings); + } + return byteList; + } + + private List getByteStrings(String token) { + + if (isSingleWildCardChar(token)) { + // normally, a wildcard character represents a nibble. For convenience, if the there + // is a single wild card character surrounded by whitespace, treat it + // as if the entire byte is wild + token += token; + } + else if (token.length() % 2 != 0) { + // pad an odd number of nibbles with 0; assuming users leave off leading 0 + token = "0" + token; + } + + int n = token.length() / 2; + List list = new ArrayList(n); + for (int i = 0; i < n; i++) { + list.add(token.substring(i * 2, i * 2 + 2)); + } + return list; + } + + private boolean isSingleWildCardChar(String token) { + if (token.length() == 1) { + char c = token.charAt(0); + return WILD_CARDS.indexOf(c) >= 0; + } + return false; + } + + private boolean hasInvalidChars(List byteGroups) { + for (String byteGroup : byteGroups) { + if (hasInvalidChars(byteGroup)) { + return true; + } + } + return false; + } + + private boolean checkGroupSize(List byteGroups) { + for (String byteGroup : byteGroups) { + if (byteGroup.length() > MAX_GROUP_SIZE) { + return true; + } + } + return false; + } + + private List getByteGroups(String input) { + List list = new ArrayList(); + StringTokenizer st = new StringTokenizer(input); + while (st.hasMoreTokens()) { + list.add(st.nextToken()); + } + return list; + } + + private boolean hasInvalidChars(String string) { + for (int i = 0; i < string.length(); i++) { + if (VALID_CHARS.indexOf(string.charAt(i)) < 0) { + return true; + } + } + return false; + } + + /** + * Returns the value of the given hex digit character. + */ + private int hexValueOf(char c) { + if ((c >= '0') && (c <= '9')) { + return c - '0'; + } + else if ((c >= 'a') && (c <= 'f')) { + return c - 'a' + 10; + } + else if ((c >= 'A') && (c <= 'F')) { + return c - 'A' + 10; + } + else { + return 0; + } + } + + @Override + public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) { + SearchFormat oldFormat = oldSettings.getSearchFormat(); + if (oldFormat.getClass() == getClass()) { + return text; + } + if (oldFormat.getFormatType() != SearchFormatType.STRING_TYPE) { + ByteMatcher byteMatcher = oldFormat.parse(text, oldSettings); + if ((byteMatcher instanceof MaskedByteSequenceByteMatcher matcher)) { + byte[] bytes = matcher.getBytes(); + byte[] mask = matcher.getMask(); + return getMaskedInputString(bytes, mask); + } + } + + return isValidText(text, newSettings) ? text : ""; + } + + private String getMaskedInputString(byte[] bytes, byte[] mask) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + String s = String.format("%02x", bytes[i]); + builder.append((mask[i] & 0xf0) == 0 ? "." : s.charAt(0)); + builder.append((mask[i] & 0x0f) == 0 ? "." : s.charAt(1)); + builder.append(" "); + } + + return builder.toString().trim(); + } + + @Override + public SearchFormatType getFormatType() { + return SearchFormatType.BYTE; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/NumberParseResult.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/NumberParseResult.java new file mode 100644 index 0000000000..632e6e6da3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/NumberParseResult.java @@ -0,0 +1,24 @@ +/* ### + * 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.features.base.memsearch.format; + +/** + * Used by the NumberSearchFormat and the FloatSearchFormat for intermediate parsing results. + * @param bytes The bytes that match the parsed number sequence + * @param errorMessage an optional parsing error message + * @param validInput boolean if the input was valid + */ +record NumberParseResult(byte[] bytes, String errorMessage, boolean validInput) {} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/RegExSearchFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/RegExSearchFormat.java new file mode 100644 index 0000000000..6b4f9a6045 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/RegExSearchFormat.java @@ -0,0 +1,66 @@ +/* ### + * 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.features.base.memsearch.format; + +import java.util.regex.PatternSyntaxException; + +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.matcher.*; + +/** + * {@link SearchFormat} for parsing input as a regular expression. This format can't generate + * bytes or parse results. + */ +class RegExSearchFormat extends SearchFormat { + RegExSearchFormat() { + super("Reg Ex"); + } + + @Override + public ByteMatcher parse(String input, SearchSettings settings) { + input = input.trim(); + if (input.isBlank()) { + return new InvalidByteMatcher(""); + } + + try { + return new RegExByteMatcher(input, settings); + } + catch (PatternSyntaxException e) { + return new InvalidByteMatcher("RegEx Pattern Error: " + e.getDescription(), true); + } + } + + @Override + public String getToolTip() { + return "Interpret value as a regular expression."; + } + + @Override + public String getValueString(byte[] bytes, SearchSettings settings) { + return new String(bytes); + } + + @Override + public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) { + return isValidText(text, newSettings) ? text : ""; + } + + @Override + public SearchFormatType getFormatType() { + return SearchFormatType.STRING_TYPE; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/SearchFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/SearchFormat.java new file mode 100644 index 0000000000..5dc29b7e5f --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/SearchFormat.java @@ -0,0 +1,159 @@ +/* ### + * 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.features.base.memsearch.format; + +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +/** + * SearchFormats are responsible for parsing user input data into a {@link ByteMatcher} that + * can be used for searching memory. It also can convert search matches back into string data and + * can convert string data from other formats into string data for this format. + */ + +public abstract class SearchFormat { + //@formatter:off + public static SearchFormat HEX = new HexSearchFormat(); + public static SearchFormat BINARY = new BinarySearchFormat(); + public static SearchFormat DECIMAL = new DecimalSearchFormat(); + + public static SearchFormat STRING = new StringSearchFormat(); + public static SearchFormat REG_EX = new RegExSearchFormat(); + + public static SearchFormat FLOAT = new FloatSearchFormat("Float", "Floating Point", 4); + public static SearchFormat DOUBLE = new FloatSearchFormat("Double", "Floating Point (8)", 8); + //@formatter:on + + public static SearchFormat[] ALL = + { HEX, BINARY, DECIMAL, STRING, REG_EX, FLOAT, DOUBLE }; + + // SearchFormats fall into one of 4 types + public enum SearchFormatType { + BYTE, INTEGER, FLOATING_POINT, STRING_TYPE + } + + private final String name; + + protected SearchFormat(String name) { + this.name = name; + } + + /** + * Parse the given input and settings into a {@link ByteMatcher} + * @param input the user input string + * @param settings the current search/parse settings + * @return a ByteMatcher that can be used for searching bytes (or an error version of a matcher) + */ + public abstract ByteMatcher parse(String input, SearchSettings settings); + + /** + * Returns a tool tip describing this search format + * @return a tool tip describing this search format + */ + public abstract String getToolTip(); + + /** + * Returns the name of the search format. + * @return the name of the search format + */ + public String getName() { + return name; + } + + @Override + public String toString() { + return getName(); + } + + /** + * Reverse parses the bytes back into input value strings. Note that this is only used by + * numerical and string type formats. Byte oriented formats just return an empty string. + * @param bytes the to convert back into input value strings + * @param settings The search settings used to parse the input into bytes + * @return the string of the reversed parsed byte values + */ + public String getValueString(byte[] bytes, SearchSettings settings) { + return ""; + } + + /** + * Returns a new search input string, doing its best to convert an input string that + * was parsed by a previous {@link SearchFormat}. When it makes sense to do so, it will + * re-interpret the parsed bytes from the old format and reconstruct the input from those + * bytes. This allows the user to do conversions, for example, from numbers to hex or binary and + * vise-versa. If the byte conversion doesn't make sense based on the old and new formats, it + * will use the original input if that input can be parsed by the new input. Finally, if all + * else fails, the new input will be the empty string. + * + * @param text the old input that is parsable by the old format + * @param oldSettings the search settings used to parse the old text + * @param newSettings the search settings to used for the new text + * @return the "best" text to change the user search input to + */ + public abstract String convertText(String text, SearchSettings oldSettings, + SearchSettings newSettings); + + /** + * Returns the {@link SearchFormatType} for this format. This is used to help with the + * {@link #convertText(String, SearchSettings, SearchSettings)} method. + * @return the type for this format + */ + public abstract SearchFormatType getFormatType(); + + /** + * Compares bytes from search results based on how this format interprets the bytes. + * By default, formats just compare the bytes one by one as if they were unsigned values. + * SearchFormats whose bytes represent numerical values will override this method and + * compare the bytes after interpreting them as numerical values. + * + * @param bytes1 the first array of bytes to compare + * @param bytes2 the second array of bytes to compare + * @param settings the search settings used to generate the bytes. + * + * @return a negative integer, zero, or a positive integer as the first byte array + * is less than, equal to, or greater than the second byte array + * + */ + public int compareValues(byte[] bytes1, byte[] bytes2, SearchSettings settings) { + return compareBytesUnsigned(bytes1, bytes2); + } + + protected void reverse(byte[] bytes) { + for (int i = 0; i < bytes.length / 2; i++) { + int swapIndex = bytes.length - 1 - i; + byte tmp = bytes[i]; + bytes[i] = bytes[swapIndex]; + bytes[swapIndex] = tmp; + } + } + + private int compareBytesUnsigned(byte[] oldBytes, byte[] newBytes) { + for (int i = 0; i < oldBytes.length; i++) { + int value1 = oldBytes[i] & 0xff; + int value2 = newBytes[i] & 0xff; + if (value1 != value2) { + return value1 - value2; + } + } + return 0; + } + + protected boolean isValidText(String text, SearchSettings settings) { + ByteMatcher byteMatcher = parse(text, settings); + return byteMatcher.isValidSearch(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/StringSearchFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/StringSearchFormat.java new file mode 100644 index 0000000000..2f653af8da --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/format/StringSearchFormat.java @@ -0,0 +1,145 @@ +/* ### + * 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.features.base.memsearch.format; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.matcher.*; +import ghidra.util.StringUtilities; + +/** + * {@link SearchFormat} for parsing and display bytes in a string format. This format uses + * several values from SearchSettings included character encoding, case sensitive, and escape + * sequences. + */ +class StringSearchFormat extends SearchFormat { + private final byte CASE_INSENSITIVE_MASK = (byte) 0xdf; + + StringSearchFormat() { + super("String"); + } + + @Override + public ByteMatcher parse(String input, SearchSettings settings) { + input = input.trim(); + if (input.isBlank()) { + return new InvalidByteMatcher(""); + } + + boolean isBigEndian = settings.isBigEndian(); + int inputLength = input.length(); + Charset charset = settings.getStringCharset(); + if (charset == StandardCharsets.UTF_16) { + charset = isBigEndian ? StandardCharsets.UTF_16BE : StandardCharsets.UTF_16LE; + } + + // Escape sequences in the "input" are 2 Characters long. + if (settings.useEscapeSequences() && inputLength >= 2) { + input = StringUtilities.convertEscapeSequences(input); + } + byte[] bytes = input.getBytes(charset); + byte[] maskArray = new byte[bytes.length]; + Arrays.fill(maskArray, (byte) 0xff); + + if (!settings.isCaseSensitive()) { + createCaseInsensitiveBytesAndMasks(charset, bytes, maskArray); + } + + return new MaskedByteSequenceByteMatcher(input, bytes, maskArray, settings); + } + + private void createCaseInsensitiveBytesAndMasks(Charset encodingCharSet, byte[] bytes, + byte[] masks) { + int i = 0; + while (i < bytes.length) { + if (encodingCharSet == StandardCharsets.US_ASCII && + Character.isLetter(bytes[i])) { + masks[i] = CASE_INSENSITIVE_MASK; + bytes[i] = (byte) (bytes[i] & CASE_INSENSITIVE_MASK); + i++; + } + else if (encodingCharSet == StandardCharsets.UTF_8) { + int numBytes = bytesPerCharUTF8(bytes[i]); + if (numBytes == 1 && Character.isLetter(bytes[i])) { + masks[i] = CASE_INSENSITIVE_MASK; + bytes[i] = (byte) (bytes[i] & CASE_INSENSITIVE_MASK); + } + i += numBytes; + } + // Assumes UTF-16 will return 2 Bytes for each character. + // 4-byte UTF-16 will never satisfy the below checks because + // none of their bytes can ever be 0. + else if (encodingCharSet == StandardCharsets.UTF_16BE) { + if (bytes[i] == (byte) 0x0 && Character.isLetter(bytes[i + 1])) { // Checks if ascii character. + masks[i + 1] = CASE_INSENSITIVE_MASK; + bytes[i + 1] = (byte) (bytes[i + 1] & CASE_INSENSITIVE_MASK); + } + i += 2; + } + else if (encodingCharSet == StandardCharsets.UTF_16LE) { + if (bytes[i + 1] == (byte) 0x0 && Character.isLetter(bytes[i])) { // Checks if ascii character. + masks[i] = CASE_INSENSITIVE_MASK; + bytes[i] = (byte) (bytes[i] & CASE_INSENSITIVE_MASK); + } + i += 2; + } + else { + i++; + } + } + } + + private int bytesPerCharUTF8(byte zByte) { + // This method is intended for UTF-8 encoding. + // The first byte in a sequence of UTF-8 bytes can tell + // us how many bytes make up a char. + int offset = 1; + // If the char is ascii, this loop will be skipped. + while ((zByte & 0x80) != 0x00) { + zByte <<= 1; + offset++; + } + return offset; + } + + @Override + public String getToolTip() { + return "Interpret value as a sequence of characters."; + } + + @Override + public String getValueString(byte[] bytes, SearchSettings settings) { + boolean isBigEndian = settings.isBigEndian(); + Charset charset = settings.getStringCharset(); + if (charset == StandardCharsets.UTF_16) { + charset = isBigEndian ? StandardCharsets.UTF_16BE : StandardCharsets.UTF_16LE; + } + return new String(bytes, charset); + } + + @Override + public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) { + return isValidText(text, newSettings) ? text : ""; + } + + @Override + public SearchFormatType getFormatType() { + return SearchFormatType.STRING_TYPE; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/CombinedMatchTableLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/CombinedMatchTableLoader.java new file mode 100644 index 0000000000..07950fbca2 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/CombinedMatchTableLoader.java @@ -0,0 +1,75 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.util.Collection; +import java.util.List; + +import ghidra.features.base.memsearch.combiner.Combiner; +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.features.base.memsearch.searcher.MemorySearcher; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.datastruct.ListAccumulator; +import ghidra.util.task.TaskMonitor; + +/** + * Table loader that performs a search and then combines the new results with existing results. + */ +public class CombinedMatchTableLoader implements MemoryMatchTableLoader { + private MemorySearcher memSearcher; + private List previousResults; + private Combiner combiner; + private boolean completedSearch; + private MemoryMatch firstMatch; + + public CombinedMatchTableLoader(MemorySearcher memSearcher, + List previousResults, Combiner combiner) { + this.memSearcher = memSearcher; + this.previousResults = previousResults; + this.combiner = combiner; + } + + @Override + public void loadResults(Accumulator accumulator, TaskMonitor monitor) { + ListAccumulator listAccumulator = new ListAccumulator<>(); + completedSearch = memSearcher.findAll(listAccumulator, monitor); + List followOnResults = listAccumulator.asList(); + firstMatch = followOnResults.isEmpty() ? null : followOnResults.get(0); + Collection results = combiner.combine(previousResults, followOnResults); + accumulator.addAll(results); + } + + @Override + public boolean didTerminateEarly() { + return !completedSearch; + } + + @Override + public void dispose() { + previousResults = null; + } + + @Override + public MemoryMatch getFirstMatch() { + return firstMatch; + } + + @Override + public boolean hasResults() { + return firstMatch != null; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/EmptyMemoryMatchTableLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/EmptyMemoryMatchTableLoader.java new file mode 100644 index 0000000000..3c58ee5eab --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/EmptyMemoryMatchTableLoader.java @@ -0,0 +1,51 @@ +/* ### + * 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.features.base.memsearch.gui; + +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.task.TaskMonitor; + +/** + * Table loader for clearing the existing results + */ +public class EmptyMemoryMatchTableLoader implements MemoryMatchTableLoader { + + @Override + public void loadResults(Accumulator accumulator, TaskMonitor monitor) { + return; + } + + @Override + public void dispose() { + // nothing to do + } + + @Override + public boolean didTerminateEarly() { + return false; + } + + @Override + public MemoryMatch getFirstMatch() { + return null; + } + + @Override + public boolean hasResults() { + return false; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/FindOnceTableLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/FindOnceTableLoader.java new file mode 100644 index 0000000000..a6602e7287 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/FindOnceTableLoader.java @@ -0,0 +1,94 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.util.List; + +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.features.base.memsearch.searcher.MemorySearcher; +import ghidra.program.model.address.Address; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.task.TaskMonitor; + +/** + * Table loader for executing an incremental search forwards or backwards and adding that result + * to the table. + */ +public class FindOnceTableLoader implements MemoryMatchTableLoader { + + private MemorySearcher searcher; + private Address address; + private List previousResults; + private MemorySearchResultsPanel panel; + private MemoryMatch match; + private boolean forward; + + public FindOnceTableLoader(MemorySearcher searcher, Address address, + List previousResults, MemorySearchResultsPanel panel, boolean forward) { + this.searcher = searcher; + this.address = address; + this.previousResults = previousResults; + this.panel = panel; + this.forward = forward; + } + + @Override + public void loadResults(Accumulator accumulator, TaskMonitor monitor) { + accumulator.addAll(previousResults); + + match = searcher.findOnce(address, forward, monitor); + + if (match != null) { + MemoryMatch existing = findExisingMatch(match.getAddress()); + if (existing != null) { + existing.updateBytes(match.getBytes()); + } + else { + accumulator.add(match); + } + } + } + + private MemoryMatch findExisingMatch(Address newMatchAddress) { + for (MemoryMatch memoryMatch : previousResults) { + if (newMatchAddress.equals(memoryMatch.getAddress())) { + return memoryMatch; + } + } + return null; + } + + @Override + public boolean didTerminateEarly() { + return false; + } + + @Override + public MemoryMatch getFirstMatch() { + return match; + } + + @Override + public void dispose() { + previousResults = null; + } + + @Override + public boolean hasResults() { + return match != null; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchHighlighter.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchHighlighter.java new file mode 100644 index 0000000000..d36df87fb3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchHighlighter.java @@ -0,0 +1,242 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.awt.Color; +import java.util.*; + +import org.apache.commons.lang3.ArrayUtils; + +import docking.widgets.fieldpanel.support.Highlight; +import docking.widgets.table.threaded.ThreadedTableModelListener; +import ghidra.app.nav.Navigatable; +import ghidra.app.util.ListingHighlightProvider; +import ghidra.app.util.SearchConstants; +import ghidra.app.util.viewer.field.*; +import ghidra.app.util.viewer.proxy.ProxyObj; +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.listing.Program; + +/** + * Listing highlight provider to highlight memory search results. + */ +public class MemoryMatchHighlighter implements ListingHighlightProvider { + private Navigatable navigatable; + private Program program; + private List sortedResults; + private MemoryMatchTableModel model; + private MemorySearchOptions options; + private MemoryMatch selectedMatch; + + public MemoryMatchHighlighter(Navigatable navigatable, MemoryMatchTableModel model, + MemorySearchOptions options) { + this.model = model; + this.options = options; + this.navigatable = navigatable; + this.program = navigatable.getProgram(); + + model.addThreadedTableModelListener(new ThreadedTableModelListener() { + @Override + public void loadingStarted() { + clearCache(); + } + + @Override + public void loadingFinished(boolean wasCancelled) { + // stub + } + + @Override + public void loadPending() { + clearCache(); + } + + }); + } + + @Override + public Highlight[] createHighlights(String text, ListingField field, int cursorTextOffset) { + if (!options.isShowHighlights()) { + return NO_HIGHLIGHTS; + } + + if (program != navigatable.getProgram()) { + return NO_HIGHLIGHTS; + } + + Class fieldFactoryClass = field.getFieldFactory().getClass(); + if (fieldFactoryClass != BytesFieldFactory.class) { + return NO_HIGHLIGHTS; + } + + ProxyObj proxy = field.getProxy(); + Object obj = proxy.getObject(); + if (!(obj instanceof CodeUnit cu)) { + return NO_HIGHLIGHTS; + } + + Address minAddr = cu.getMinAddress(); + Address maxAddr = cu.getMaxAddress(); + List results = getMatchesInRange(minAddr, maxAddr); + if (results.isEmpty()) { + return NO_HIGHLIGHTS; + } + + return getHighlights(text, minAddr, results); + } + + private Highlight[] getHighlights(String text, Address minAddr, List results) { + + Highlight[] highlights = new Highlight[results.size()]; + int selectedMatchIndex = -1; + + for (int i = 0; i < highlights.length; i++) { + MemoryMatch match = results.get(i); + Color highlightColor = SearchConstants.SEARCH_HIGHLIGHT_COLOR; + if (match == selectedMatch) { + selectedMatchIndex = i; + highlightColor = SearchConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR; + } + highlights[i] = createHighlight(match, minAddr, text, highlightColor); + } + + // move the selected match to the end so that it gets painted last and doesn't get + // painted over by the non-active highlights + if (selectedMatchIndex >= 0) { + ArrayUtils.swap(highlights, selectedMatchIndex, highlights.length - 1); + } + + return highlights; + } + + private Highlight createHighlight(MemoryMatch match, Address start, String text, Color color) { + int highlightLength = match.getLength(); + Address address = match.getAddress(); + int startByteOffset = (int) address.subtract(start); + int endByteOffset = startByteOffset + highlightLength - 1; + startByteOffset = Math.max(startByteOffset, 0); + return getHighlight(text, startByteOffset, endByteOffset, color); + } + + private Highlight getHighlight(String text, int start, int end, Color color) { + int charStart = getCharPosition(text, start); + int charEnd = getCharPosition(text, end) + 1; + return new Highlight(charStart, charEnd, color); + + } + + private int getCharPosition(String text, int byteOffset) { + int byteGroupSize = options.getByteGroupSize(); + int byteDelimiterLength = options.getByteDelimiter().length(); + + int groupSize = byteGroupSize * 2 + byteDelimiterLength; + int groupIndex = byteOffset / byteGroupSize; + int groupOffset = byteOffset % byteGroupSize; + + int pos = groupIndex * groupSize + 2 * groupOffset; + return Math.min(text.length() - 1, pos); + } + + List getMatches() { + + if (sortedResults != null) { + return sortedResults; + } + + if (model.isBusy()) { + return Collections.emptyList(); + } + + List modelData = model.getModelData(); + if (model.isSortedOnAddress()) { + return modelData; + } + + sortedResults = new ArrayList<>(modelData); + Collections.sort(sortedResults); + + return sortedResults; + } + + private List getMatchesInRange(Address start, Address end) { + List matches = getMatches(); + int startIndex = findFirstIndex(matches, start, end); + if (startIndex < 0) { + return Collections.emptyList(); + } + + int endIndex = findIndexAtOrGreater(matches, end); + if (endIndex < matches.size() && (matches.get(endIndex).getAddress().equals(end))) { + endIndex++; // end index is non-inclusive and we want to include direct hit + } + + List resultList = matches.subList(startIndex, endIndex); + return resultList; + } + + private int findFirstIndex(List matches, Address start, Address end) { + + int startIndex = findIndexAtOrGreater(matches, start); + if (startIndex > 0) { // see if address before extends into this range. + MemoryMatch resultBefore = matches.get(startIndex - 1); + Address beforeAddr = resultBefore.getAddress(); + int length = resultBefore.getLength(); + if (start.hasSameAddressSpace(beforeAddr) && start.subtract(beforeAddr) < length) { + return startIndex - 1; + } + } + + if (startIndex == matches.size()) { + return -1; + } + + MemoryMatch result = matches.get(startIndex); + Address addr = result.getAddress(); + if (end.compareTo(addr) >= 0) { + return startIndex; + } + return -1; + } + + private int findIndexAtOrGreater(List matches, Address address) { + + MemoryMatch key = new MemoryMatch(address); + int index = Collections.binarySearch(matches, key); + if (index < 0) { + index = -index - 1; + } + return index; + } + + private void clearCache() { + if (sortedResults != null) { + sortedResults.clear(); + sortedResults = null; + } + } + + void dispose() { + navigatable.removeHighlightProvider(this, program); + clearCache(); + } + + void setSelectedMatch(MemoryMatch selectedMatch) { + this.selectedMatch = selectedMatch; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableLoader.java new file mode 100644 index 0000000000..6756a3cf5e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableLoader.java @@ -0,0 +1,60 @@ +/* ### + * 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.features.base.memsearch.gui; + +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.task.TaskMonitor; + +/** + * Interface for loading the memory search results table. Various implementations handle the + * different cases such as a search all, or a search next, or combining results with a previous + * search, etc. + */ +public interface MemoryMatchTableLoader { + + /** + * Called by the table model to initiate searching and loading using the threaded table models + * threading infrastructure. + * @param accumulator the accumulator to store results that will appear in the results table + * @param monitor the task monitor + */ + public void loadResults(Accumulator accumulator, TaskMonitor monitor); + + /** + * Returns true if the search/loading did not fully complete. (Search limit reached, cancelled + * by user, etc.) + * @return true if the search/loading did not fully complete + */ + public boolean didTerminateEarly(); + + /** + * Cleans up resources + */ + public void dispose(); + + /** + * Returns the first match found. Typically used to navigate the associated navigatable. + * @return the first match found + */ + public MemoryMatch getFirstMatch(); + + /** + * Returns true if at least one match was found. + * @return true if at least one match was found + */ + public boolean hasResults(); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java new file mode 100644 index 0000000000..55e9adf56e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java @@ -0,0 +1,287 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.awt.*; + +import docking.widgets.table.*; +import generic.theme.GThemeDefaults.Colors.Tables; +import ghidra.docking.settings.Settings; +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.framework.plugintool.ServiceProvider; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.util.*; +import ghidra.util.HTMLUtilities; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.exception.CancelledException; +import ghidra.util.table.AddressBasedTableModel; +import ghidra.util.table.column.AbstractGColumnRenderer; +import ghidra.util.table.column.GColumnRenderer; +import ghidra.util.table.field.*; +import ghidra.util.task.TaskMonitor; + +/** + * Table model for memory search results. + */ +public class MemoryMatchTableModel extends AddressBasedTableModel { + private Color CHANGED_COLOR = Tables.ERROR_UNSELECTED; + private Color CHANGED_SELECTED_COLOR = Tables.ERROR_SELECTED; + + private MemoryMatchTableLoader loader; + + MemoryMatchTableModel(ServiceProvider serviceProvider, Program program) { + super("Memory Search", serviceProvider, program, null, true); + } + + @Override + protected TableColumnDescriptor createTableColumnDescriptor() { + TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); + + descriptor.addVisibleColumn( + DiscoverableTableUtils.adaptColumForModel(this, new AddressTableColumn()), 1, true); + descriptor.addVisibleColumn(new MatchBytesColumn()); + descriptor.addVisibleColumn(new MatchValueColumn()); + descriptor.addVisibleColumn( + DiscoverableTableUtils.adaptColumForModel(this, new LabelTableColumn())); + descriptor.addVisibleColumn( + DiscoverableTableUtils.adaptColumForModel(this, new CodeUnitTableColumn())); + + return descriptor; + } + + @Override + protected void doLoad(Accumulator accumulator, TaskMonitor monitor) + throws CancelledException { + if (loader == null) { + return; + } + loader.loadResults(accumulator, monitor); + loader = null; + } + + void setLoader(MemoryMatchTableLoader loader) { + this.loader = loader; + reload(); + } + + public boolean isSortedOnAddress() { + TableSortState sortState = getTableSortState(); + if (sortState.isUnsorted()) { + return false; + } + + ColumnSortState primaryState = sortState.getAllSortStates().get(0); + DynamicTableColumn column = + getColumn(primaryState.getColumnModelIndex()); + String name = column.getColumnName(); + if (AddressTableColumn.NAME.equals(name)) { + return true; + } + return false; + } + + @Override + public ProgramLocation getProgramLocation(int modelRow, int modelColumn) { + Program p = getProgram(); + if (p == null) { + return null; // we've been disposed + } + + DynamicTableColumn column = getColumn(modelColumn); + Class columnClass = column.getClass(); + if (column instanceof MappedTableColumn mappedColumn) { + columnClass = mappedColumn.getMappedColumnClass(); + } + if (columnClass == AddressTableColumn.class || columnClass == MatchBytesColumn.class || + columnClass == MatchValueColumn.class) { + return new BytesFieldLocation(p, getAddress(modelRow)); + } + + return super.getProgramLocation(modelRow, modelColumn); + } + + @Override + public Address getAddress(int row) { + MemoryMatch result = getRowObject(row); + return result.getAddress(); + } + + @Override + public ProgramSelection getProgramSelection(int[] rows) { + AddressSet addressSet = new AddressSet(); + for (int row : rows) { + MemoryMatch result = getRowObject(row); + int addOn = result.getLength() - 1; + Address minAddr = getAddress(row); + Address maxAddr = minAddr; + try { + maxAddr = minAddr.addNoWrap(addOn); + addressSet.addRange(minAddr, maxAddr); + } + catch (AddressOverflowException e) { + // I guess we don't care--not sure why this is undocumented :( + } + } + return new ProgramSelection(addressSet); + } + + public class MatchBytesColumn + extends DynamicTableColumnExtensionPoint { + + private ByteArrayRenderer renderer = new ByteArrayRenderer(); + + @Override + public String getColumnName() { + return "Match Bytes"; + } + + @Override + public String getValue(MemoryMatch match, Settings settings, Program pgm, + ServiceProvider service) throws IllegalArgumentException { + + return getByteString(match.getBytes()); + } + + private String getByteString(byte[] bytes) { + StringBuilder b = new StringBuilder(); + int max = bytes.length - 1; + for (int i = 0;; i++) { + b.append(String.format("%02x", bytes[i])); + if (i == max) { + break; + } + b.append(" "); + } + return b.toString(); + } + + @Override + public int getColumnPreferredWidth() { + return 200; + } + + @Override + public GColumnRenderer getColumnRenderer() { + return renderer; + } + } + + public class MatchValueColumn + extends DynamicTableColumnExtensionPoint { + + private ValueRenderer renderer = new ValueRenderer(); + + @Override + public String getColumnName() { + return "Match Value"; + } + + @Override + public String getValue(MemoryMatch match, Settings settings, Program pgm, + ServiceProvider service) throws IllegalArgumentException { + + ByteMatcher byteMatcher = match.getByteMatcher(); + SearchSettings searchSettings = byteMatcher.getSettings(); + SearchFormat format = searchSettings.getSearchFormat(); + return format.getValueString(match.getBytes(), searchSettings); + } + + @Override + public int getColumnPreferredWidth() { + return 200; + } + + @Override + public GColumnRenderer getColumnRenderer() { + return renderer; + } + } + + private class ByteArrayRenderer extends AbstractGColumnRenderer { + public ByteArrayRenderer() { + setHTMLRenderingEnabled(true); + } + + @Override + protected Font getDefaultFont() { + return fixedWidthFont; + } + + @Override + public Component getTableCellRendererComponent(GTableCellRenderingData data) { + super.getTableCellRendererComponent(data); + MemoryMatch match = (MemoryMatch) data.getRowObject(); + String text = data.getValue().toString(); + if (match.isChanged()) { + text = getHtmlColoredString(match, data.isSelected()); + } + setText(text); + return this; + } + + private String getHtmlColoredString(MemoryMatch match, boolean isSelected) { + Color color = isSelected ? Tables.ERROR_SELECTED : Tables.ERROR_UNSELECTED; + + StringBuilder b = new StringBuilder(); + b.append(""); + byte[] bytes = match.getBytes(); + byte[] previousBytes = match.getPreviousBytes(); + int max = bytes.length - 1; + for (int i = 0;; i++) { + String byteString = String.format("%02x", bytes[i]); + if (bytes[i] != previousBytes[i]) { + byteString = HTMLUtilities.colorString(color, byteString); + } + b.append(byteString); + if (i == max) + break; + b.append(" "); + } + + return b.toString(); + } + + @Override + public String getFilterString(String t, Settings settings) { + // This returns the formatted string without the formatted markup + return t; + } + } + + private class ValueRenderer extends AbstractGColumnRenderer { + + @Override + public Component getTableCellRendererComponent(GTableCellRenderingData data) { + super.getTableCellRendererComponent(data); + setText((String) data.getValue()); + + MemoryMatch match = (MemoryMatch) data.getRowObject(); + if (match.isChanged()) { + setForeground(data.isSelected() ? CHANGED_SELECTED_COLOR : CHANGED_COLOR); + } + return this; + } + + @Override + public String getFilterString(String t, Settings settings) { + return t; + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchToAddressTableRowMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchToAddressTableRowMapper.java new file mode 100644 index 0000000000..8430b703f8 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchToAddressTableRowMapper.java @@ -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.features.base.memsearch.gui; + +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.framework.plugintool.ServiceProvider; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.util.table.ProgramLocationTableRowMapper; + +/** + * Maps {@link MemoryMatch} objects (search result) to an address to pick up address based + * table columns. + */ +public class MemoryMatchToAddressTableRowMapper + extends ProgramLocationTableRowMapper { + + @Override + public Address map(MemoryMatch rowObject, Program data, ServiceProvider serviceProvider) { + return rowObject.getAddress(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchToProgramLocationTableRowMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchToProgramLocationTableRowMapper.java new file mode 100644 index 0000000000..4959d8a7cb --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchToProgramLocationTableRowMapper.java @@ -0,0 +1,37 @@ +/* ### + * 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.features.base.memsearch.gui; + +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.framework.plugintool.ServiceProvider; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.util.table.ProgramLocationTableRowMapper; + +/** + * Maps {@link MemoryMatch} objects (search result) to program locations to pick up + * program location based table columns. + */ +public class MemoryMatchToProgramLocationTableRowMapper + extends ProgramLocationTableRowMapper { + + @Override + public ProgramLocation map(MemoryMatch rowObject, Program program, + ServiceProvider serviceProvider) { + return new ProgramLocation(program, rowObject.getAddress()); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchtToFunctionTableRowMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchtToFunctionTableRowMapper.java new file mode 100644 index 0000000000..92b7bf8257 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchtToFunctionTableRowMapper.java @@ -0,0 +1,37 @@ +/* ### + * 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.features.base.memsearch.gui; + +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.framework.plugintool.ServiceProvider; +import ghidra.program.model.listing.*; +import ghidra.util.table.ProgramLocationTableRowMapper; + +/** + * Maps {@link MemoryMatch} objects (search result) to functions to pick up function based + * table columns. + */ +public class MemoryMatchtToFunctionTableRowMapper + extends ProgramLocationTableRowMapper { + + @Override + public Function map(MemoryMatch rowObject, Program program, + ServiceProvider serviceProvider) { + FunctionManager functionManager = program.getFunctionManager(); + return functionManager.getFunctionContaining(rowObject.getAddress()); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java new file mode 100644 index 0000000000..82e8522eb6 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java @@ -0,0 +1,81 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; + +import javax.swing.*; + +import docking.widgets.button.GRadioButton; +import ghidra.app.util.HelpTopics; +import ghidra.features.base.memsearch.scan.Scanner; +import ghidra.util.HelpLocation; +import help.Help; +import help.HelpService; + +/** + * Internal panel of the memory search window that manages the controls for the scan feature. This + * panel can be added or removed via a toolbar action. Not showing by default. + */ +public class MemoryScanControlPanel extends JPanel { + private Scanner selectedScanner = Scanner.NOT_EQUALS; + private boolean hasResults; + private boolean isBusy; + private JButton scanButton; + + MemoryScanControlPanel(MemorySearchProvider provider) { + super(new BorderLayout()); + setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); + add(buildButtonPanel(), BorderLayout.CENTER); + scanButton = new JButton("Scan Values"); + scanButton.setToolTipText("Refreshes byte values of current results and eliminates " + + "those that don't meet the selected change criteria"); + HelpService helpService = Help.getHelpService(); + helpService.registerHelp(this, new HelpLocation(HelpTopics.SEARCH, "Scan_Controls")); + add(scanButton, BorderLayout.WEST); + scanButton.addActionListener(e -> provider.scan(selectedScanner)); + } + + private JComponent buildButtonPanel() { + JPanel panel = new JPanel(new FlowLayout()); + ButtonGroup buttonGroup = new ButtonGroup(); + for (Scanner scanner : Scanner.values()) { + GRadioButton button = new GRadioButton(scanner.getName()); + buttonGroup.add(button); + panel.add(button); + button.setSelected(scanner == selectedScanner); + button.addActionListener(e -> selectedScanner = scanner); + button.setToolTipText(scanner.getDescription()); + } + return panel; + } + + public void setSearchStatus(boolean hasResults, boolean isBusy) { + this.hasResults = hasResults; + this.isBusy = isBusy; + updateScanButton(); + } + + private void updateScanButton() { + scanButton.setEnabled(canScan()); + } + + private boolean canScan() { + return hasResults && !isBusy; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java new file mode 100644 index 0000000000..389a6ff228 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java @@ -0,0 +1,452 @@ +/* ### + * 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.features.base.memsearch.gui; + +import static ghidra.features.base.memsearch.combiner.Combiner.*; + +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.text.*; + +import docking.DockingUtils; +import docking.menu.ButtonState; +import docking.menu.MultiStateButton; +import docking.widgets.PopupWindow; +import docking.widgets.combobox.GhidraComboBox; +import docking.widgets.label.GDLabel; +import docking.widgets.list.GComboBoxCellRenderer; +import generic.theme.GThemeDefaults.Colors.Messages; +import ghidra.features.base.memsearch.combiner.Combiner; +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.features.base.memsearch.matcher.InvalidByteMatcher; +import ghidra.util.HTMLUtilities; +import ghidra.util.Swing; +import ghidra.util.layout.PairLayout; +import ghidra.util.layout.VerticalLayout; +import ghidra.util.timer.GTimer; + +/** + * Internal panel of the memory search window that manages the controls for the search feature. This + * panel can be added or removed via a toolbar action. This panel is showing by default. + */ +class MemorySearchControlPanel extends JPanel { + private MultiStateButton searchButton; + private GhidraComboBox searchInputField; + private GDLabel hexSearchSequenceField; + private boolean hasResults; + private ByteMatcher currentMatcher = new InvalidByteMatcher(""); + private SearchHistory searchHistory; + private SearchGuiModel model; + private JCheckBox selectionCheckbox; + private boolean isBusy; + private MemorySearchProvider provider; + private List> initialSearchButtonStates; + private List> combinerSearchButtonStates; + private JComboBox formatComboBox; + private PopupWindow popup; + private String errorMessage; + + MemorySearchControlPanel(MemorySearchProvider provider, SearchGuiModel model, + SearchHistory history) { + super(new BorderLayout()); + this.provider = provider; + this.searchHistory = history; + this.model = model; + model.addChangeCallback(this::guiModelChanged); + initialSearchButtonStates = createButtonStatesForInitialSearch(); + combinerSearchButtonStates = createButtonStatesForAdditionSearches(); + + setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); + add(buildLeftSearchInputPanel(), BorderLayout.CENTER); + add(buildRightSearchInputPanel(), BorderLayout.EAST); + } + + private JComponent buildRightSearchInputPanel() { + JPanel panel = new JPanel(new VerticalLayout(5)); + panel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); + searchButton = new MultiStateButton(initialSearchButtonStates); + searchButton + .setStateChangedListener(state -> model.setMatchCombiner(state.getClientData())); + searchButton.addActionListener(e -> search()); + panel.add(searchButton, BorderLayout.WEST); + selectionCheckbox = new JCheckBox("Selection Only"); + selectionCheckbox.setSelected(model.isSearchSelectionOnly()); + selectionCheckbox.setEnabled(model.hasSelection()); + selectionCheckbox + .setToolTipText("If selected, search will be restricted to selected addresses"); + selectionCheckbox.addActionListener( + e -> model.setSearchSelectionOnly(selectionCheckbox.isSelected())); + panel.add(selectionCheckbox); + searchButton.setEnabled(false); + return panel; + } + + private List> createButtonStatesForAdditionSearches() { + List> states = new ArrayList<>(); + states.add(new ButtonState("New Search", "New Search", + "Replaces the current results with the new search results", REPLACE)); + states.add(new ButtonState("Add To Search", "A union B", + "Adds the results of the new search to the existing results", UNION)); + states.add(new ButtonState("Intersect Search", "A intersect B", + "Keep results that in both the existing and new results", INTERSECT)); + states.add(new ButtonState("Xor Search", "A xor B", + "Keep results that are in either existig or results, but not both", XOR)); + states.add(new ButtonState("A-B Search", "A - B", + "Subtracts the new results from the existing results", A_MINUS_B)); + states.add(new ButtonState("B-A Search", "B - A", + "Subtracts the existing results from the new results.", B_MINUS_A)); + return states; + } + + private List> createButtonStatesForInitialSearch() { + List> states = new ArrayList<>(); + states.add(new ButtonState("Search", "", + "Perform a search for the entered values.", null)); + return states; + } + + private void guiModelChanged(SearchSettings oldSettings) { + SearchFormat searchFormat = model.getSearchFormat(); + if (!formatComboBox.getSelectedItem().equals(searchFormat)) { + formatComboBox.setSelectedItem(searchFormat); + } + selectionCheckbox.setSelected(model.isSearchSelectionOnly()); + selectionCheckbox.setEnabled(model.hasSelection()); + searchInputField.setToolTipText(searchFormat.getToolTip()); + + String text = searchInputField.getText(); + String convertedText = searchFormat.convertText(text, oldSettings, model.getSettings()); + searchInputField.setText(convertedText); + ByteMatcher byteMatcher = searchFormat.parse(convertedText, model.getSettings()); + setByteMatcher(byteMatcher); + } + + private JComponent buildLeftSearchInputPanel() { + createSearchInputField(); + hexSearchSequenceField = new GDLabel(); + hexSearchSequenceField.setName("HexSequenceField"); + Border outerBorder = BorderFactory.createLoweredBevelBorder(); + Border innerBorder = BorderFactory.createEmptyBorder(0, 4, 0, 4); + Border border = BorderFactory.createCompoundBorder(outerBorder, innerBorder); + hexSearchSequenceField.setBorder(border); + + JPanel panel = new JPanel(new PairLayout(2, 10)); + panel.add(buildSearchFormatCombo()); + panel.add(searchInputField); + JLabel byteSequenceLabel = new JLabel("Byte Sequence:", SwingConstants.RIGHT); + byteSequenceLabel.setToolTipText( + "This field shows the byte sequence that will be search (if applicable)"); + + panel.add(byteSequenceLabel); + panel.add(hexSearchSequenceField); + return panel; + } + + private void createSearchInputField() { + searchInputField = new GhidraComboBox<>() { + @Override + public void setSelectedItem(Object obj) { + if (obj instanceof String) { + // this can happen when a user types a string and presses enter + // our data model is ByteMatcher, not strings + return; + } + ByteMatcher matcher = (ByteMatcher) obj; + model.setSettings(matcher.getSettings()); + super.setSelectedItem(obj); + } + }; + updateCombo(); + searchInputField.setAutoCompleteEnabled(false); // this interferes with validation + searchInputField.setEditable(true); + searchInputField.setToolTipText(model.getSearchFormat().getToolTip()); + searchInputField.setDocument(new RestrictedInputDocument()); + searchInputField.addActionListener(ev -> search()); + JTextField searchTextField = searchInputField.getTextField(); + + // add escape key listener to dismiss any error popup windows + searchTextField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(java.awt.event.KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + clearInputError(); + e.consume(); + } + } + }); + + // add focus lost listener to dismiss any error popup windows + searchTextField.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + clearInputError(); + } + }); + searchInputField.setRenderer(new SearchHistoryRenderer()); + } + + private boolean canSearch() { + return !isBusy && currentMatcher.isValidSearch(); + } + + private void search() { + if (canSearch()) { + provider.search(); + searchHistory.addSearch(currentMatcher); + updateCombo(); + } + } + + private JComponent buildSearchFormatCombo() { + formatComboBox = new JComboBox<>(SearchFormat.ALL); + formatComboBox.setSelectedItem(model.getSearchFormat()); + formatComboBox.addItemListener(this::formatComboChanged); + formatComboBox.setToolTipText("The selected format will determine how to " + + "interpret text typed into the input field"); + + return formatComboBox; + } + + private void formatComboChanged(ItemEvent e) { + if (e.getStateChange() != ItemEvent.SELECTED) { + return; + } + SearchFormat newFormat = (SearchFormat) e.getItem(); + SearchSettings oldSettings = model.getSettings(); + SearchSettings newSettings = oldSettings.withSearchFormat(newFormat); + String newText = convertInput(oldSettings, newSettings); + model.setSearchFormat(newFormat); + searchInputField.setText(newText); + } + + String convertInput(SearchSettings oldSettings, SearchSettings newSettings) { + String text = searchInputField.getText(); + SearchFormat newFormat = newSettings.getSearchFormat(); + return newFormat.convertText(text, oldSettings, newSettings); + } + + private void setByteMatcher(ByteMatcher byteMatcher) { + clearInputError(); + currentMatcher = byteMatcher; + String text = currentMatcher.getDescription(); + hexSearchSequenceField.setText(text); + hexSearchSequenceField.setToolTipText(currentMatcher.getToolTip()); + updateSearchButton(); + provider.setByteMatcher(byteMatcher); + } + + void setSearchStatus(boolean hasResults, boolean isBusy) { + this.hasResults = hasResults; + this.isBusy = isBusy; + updateSearchButton(); + } + + private void updateSearchButton() { + searchButton.setEnabled(canSearch()); + if (!hasResults) { + searchButton.setButtonStates(initialSearchButtonStates); + return; + } + Combiner combiner = model.getMatchCombiner(); + searchButton.setButtonStates(combinerSearchButtonStates); + searchButton.setSelectedStateByClientData(combiner); + } + + private void adjustLocationForCaretPosition(Point location) { + JTextField textField = searchInputField.getTextField(); + Caret caret = textField.getCaret(); + Point p = caret.getMagicCaretPosition(); + if (p != null) { + location.x += p.x; + } + } + + private void reportInputError(String message) { + this.errorMessage = message; + + // Sometimes when user input is being processed we will get multiple events, with initial + // events putting our model in a bad state, but with follow-up events correcting the state. + // By showing the error message later, we give the follow-up events a change to fix the + // state and clear the error message which prevents the temporary bad state from actually + // displaying an error message to the user. + + Swing.runLater(this::popupErrorMessage); + } + + private void popupErrorMessage() { + if (errorMessage == null) { + return; + } + errorMessage = null; + DockingUtils.setTipWindowEnabled(false); + + Point location = searchInputField.getLocation(); + adjustLocationForCaretPosition(location); + location.y += searchInputField.getHeight() + 5; + + JToolTip tip = new JToolTip(); + tip.setTipText(errorMessage); + + if (popup != null) { + popup.dispose(); + } + popup = new PopupWindow(tip); + popup.showPopup(searchInputField, location, true); + GTimer.scheduleRunnable(1500, this::clearInputError); + Toolkit.getDefaultToolkit().beep(); + } + + private void clearInputError() { + errorMessage = null; + DockingUtils.setTipWindowEnabled(true); + PopupWindow.hideAllWindows(); + if (popup != null) { + popup.dispose(); + } + } + + private void updateCombo() { + ByteMatcher[] historyArray = searchHistory.getHistoryAsArray(); + + searchInputField.setModel(new DefaultComboBoxModel<>(historyArray)); + } + + /** + * Custom Document that validates user input on the fly. + */ + public class RestrictedInputDocument extends DefaultStyledDocument { + + /** + * Called before new user input is inserted into the entry text field. The super + * method is called if the input is accepted. + */ + @Override + public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { + // allow pasting numbers in forms like 0xABC or ABCh + str = removeNumberBasePrefixAndSuffix(str); + + String currentText = getText(0, getLength()); + String beforeOffset = currentText.substring(0, offs); + String afterOffset = currentText.substring(offs, currentText.length()); + String proposedText = beforeOffset + str + afterOffset; + + ByteMatcher byteMatcher = model.parse(proposedText); + if (!byteMatcher.isValidInput()) { + reportInputError(byteMatcher.getDescription()); + return; + } + super.insertString(offs, str, a); + + setByteMatcher(byteMatcher); + } + + /** + * Called before the user deletes some text. If the result is valid, the super + * method is called. + */ + @Override + public void remove(int offs, int len) throws BadLocationException { + clearInputError(); + + String currentText = getText(0, getLength()); + String beforeOffset = currentText.substring(0, offs); + String afterOffset = currentText.substring(len + offs, currentText.length()); + String proposedResult = beforeOffset + afterOffset; + + if (proposedResult.length() == 0) { + super.remove(offs, len); + setByteMatcher(new InvalidByteMatcher("")); + return; + } + + ByteMatcher byteMatcher = model.parse(proposedResult); + if (!byteMatcher.isValidInput()) { + reportInputError(byteMatcher.getDescription()); + return; + } + super.remove(offs, len); + setByteMatcher(byteMatcher); + } + + private String removeNumberBasePrefixAndSuffix(String str) { + SearchFormat format = model.getSearchFormat(); + if (!(format == SearchFormat.HEX || format == SearchFormat.BINARY)) { + return str; + } + + String numMaybe = str.strip(); + String lowercase = numMaybe.toLowerCase(); + if (format == SearchFormat.HEX) { + if (lowercase.startsWith("0x")) { + numMaybe = numMaybe.substring(2); + } + else if (lowercase.startsWith("$")) { + numMaybe = numMaybe.substring(1); + } + else if (lowercase.endsWith("h")) { + numMaybe = numMaybe.substring(0, numMaybe.length() - 1); + } + } + else { + if (lowercase.startsWith("0b")) { + numMaybe = numMaybe.substring(2); + } + } + + // check if the resultant number looks valid for insertion (i.e. not empty) + if (!numMaybe.isEmpty()) { + return numMaybe; + } + return str; + } + } + + void setSearchInput(String initialInput) { + searchInputField.setText(initialInput); + } + + private class SearchHistoryRenderer extends GComboBoxCellRenderer { + { + setHTMLRenderingEnabled(true); + } + + @Override + public Component getListCellRendererComponent(JList list, + ByteMatcher matcher, int index, + boolean isSelected, boolean cellHasFocus) { + + super.getListCellRendererComponent(list, matcher, index, isSelected, cellHasFocus); + + Font font = getFont(); + int formatSize = Math.max(font.getSize() - 3, 6); + SearchFormat format = matcher.getSettings().getSearchFormat(); + String formatHint = HTMLUtilities.setFontSize(format.getName(), formatSize); + if (!isSelected) { + formatHint = HTMLUtilities.colorString(Messages.HINT, formatHint); + } + + setText("" + matcher.getInput() + " " + formatHint + ""); + return this; + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptions.java new file mode 100644 index 0000000000..1f8f61194d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptions.java @@ -0,0 +1,139 @@ +/* ### + * 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.features.base.memsearch.gui; + +import static ghidra.GhidraOptions.*; +import static ghidra.app.util.SearchConstants.*; + +import ghidra.GhidraOptions; +import ghidra.app.util.SearchConstants; +import ghidra.app.util.viewer.field.BytesFieldFactory; +import ghidra.framework.options.OptionsChangeListener; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.util.bean.opteditor.OptionsVetoException; + +/** + * Class for managing search tool options. + */ +public class MemorySearchOptions { + private static final String PRE_POPULATE_MEM_SEARCH = "Pre-populate Memory Search"; + private static final String AUTO_RESTRICT_SELECTION = "Auto Restrict Search on Selection"; + + private OptionsChangeListener searchOptionsListener; + private OptionsChangeListener browserOptionsListener; + + private boolean prepopulateSearch = true; + private int searchLimit = DEFAULT_SEARCH_LIMIT; + private boolean highlightMatches = true; + private boolean autoRestrictSelection = true; + private int byteGroupSize; + private String byteDelimiter; + + public MemorySearchOptions(PluginTool tool) { + registerSearchOptions(tool); + registerBrowserOptionsListener(tool); + } + + public MemorySearchOptions() { + } + + public int getByteGroupSize() { + return byteGroupSize; + } + + public String getByteDelimiter() { + return byteDelimiter; + } + + public boolean isShowHighlights() { + return highlightMatches; + } + + public int getSearchLimit() { + return searchLimit; + } + + public boolean isAutoRestrictSelection() { + return autoRestrictSelection; + } + + private void registerSearchOptions(PluginTool tool) { + ToolOptions options = tool.getOptions(SearchConstants.SEARCH_OPTION_NAME); + + options.registerOption(PRE_POPULATE_MEM_SEARCH, prepopulateSearch, null, + "Initializes memory search byte sequence from " + + "the current selection provided the selection is less than 10 bytes."); + options.registerOption(AUTO_RESTRICT_SELECTION, autoRestrictSelection, null, + "Automactically restricts searches to the to the current selection," + + " if a selection exists"); + options.registerOption(SearchConstants.SEARCH_HIGHLIGHT_NAME, highlightMatches, null, + "Toggles highlight search results"); + + options.registerThemeColorBinding(SearchConstants.SEARCH_HIGHLIGHT_COLOR_OPTION_NAME, + SearchConstants.SEARCH_HIGHLIGHT_COLOR.getId(), null, + "The search result highlight color"); + options.registerThemeColorBinding( + SearchConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_OPTION_NAME, + SearchConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR.getId(), null, + "The search result highlight color for the currently selected match"); + + loadSearchOptions(options); + + searchOptionsListener = this::searchOptionsChanged; + options.addOptionsChangeListener(searchOptionsListener); + } + + private void registerBrowserOptionsListener(PluginTool tool) { + ToolOptions options = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); + loadBrowserOptions(options); + browserOptionsListener = this::browserOptionsChanged; + options.addOptionsChangeListener(browserOptionsListener); + + } + + private void loadBrowserOptions(ToolOptions options) { + byteGroupSize = options.getInt(BytesFieldFactory.BYTE_GROUP_SIZE_MSG, 1); + byteDelimiter = options.getString(BytesFieldFactory.DELIMITER_MSG, " "); + } + + private void searchOptionsChanged(ToolOptions options, String optionName, Object oldValue, + Object newValue) { + + if (optionName.equals(OPTION_SEARCH_LIMIT)) { + int limit = (int) newValue; + if (limit <= 0) { + throw new OptionsVetoException("Search limit must be greater than 0"); + } + } + + loadSearchOptions(options); + + } + + private void loadSearchOptions(ToolOptions options) { + searchLimit = options.getInt(OPTION_SEARCH_LIMIT, DEFAULT_SEARCH_LIMIT); + highlightMatches = options.getBoolean(SEARCH_HIGHLIGHT_NAME, true); + autoRestrictSelection = options.getBoolean(AUTO_RESTRICT_SELECTION, true); + prepopulateSearch = options.getBoolean(PRE_POPULATE_MEM_SEARCH, true); + } + + private void browserOptionsChanged(ToolOptions options, String optionName, Object oldValue, + Object newValue) { + + loadBrowserOptions(options); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java new file mode 100644 index 0000000000..64d90ff0a0 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java @@ -0,0 +1,317 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ItemEvent; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.TitledBorder; +import javax.swing.text.*; + +import docking.widgets.checkbox.GCheckBox; +import docking.widgets.combobox.GComboBox; +import ghidra.app.util.HelpTopics; +import ghidra.docking.util.LookAndFeelUtils; +import ghidra.features.base.memsearch.bytesource.SearchRegion; +import ghidra.program.model.lang.Endian; +import ghidra.util.HelpLocation; +import ghidra.util.layout.PairLayout; +import ghidra.util.layout.VerticalLayout; +import help.Help; +import help.HelpService; + +/** + * Internal panel of the memory search window that manages the controls for the search settings. + * This panel can be added or removed via a toolbar action. Not showing by default. + */ +class MemorySearchOptionsPanel extends JPanel { + private SearchGuiModel model; + private GCheckBox caseSensitiveCheckbox; + private GCheckBox escapeSequencesCheckbox; + private GCheckBox decimalUnsignedCheckbox; + private GComboBox decimalByteSizeCombo; + private GComboBox charsetCombo; + private GComboBox endianessCombo; + private boolean isNimbus; + + MemorySearchOptionsPanel(SearchGuiModel model) { + super(new BorderLayout()); + this.model = model; + + // if the look and feel is Nimbus, the spaceing it too big, so we use less spacing + // between elements. + isNimbus = LookAndFeelUtils.isUsingNimbusUI(); + + JPanel scrolledPanel = new JPanel(new VerticalLayout(isNimbus ? 8 : 16)); + scrolledPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5)); + + scrolledPanel.add(buildByteOptionsPanel()); + scrolledPanel.add(buildDecimalOptions()); + scrolledPanel.add(buildStringOptions()); + scrolledPanel.add(buildCodeUnitScopePanel()); + scrolledPanel.add(buildMemorySearchRegionsPanel()); + + JScrollPane scroll = new JScrollPane(scrolledPanel); + scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + + add(scroll, BorderLayout.CENTER); + + model.addChangeCallback(this::guiModelChanged); + HelpService helpService = Help.getHelpService(); + helpService.registerHelp(this, new HelpLocation(HelpTopics.SEARCH, "Options")); + + } + + @Override + public Dimension getPreferredSize() { + Dimension size = super.getPreferredSize(); + size.width += 20; // reserve space for the optional vertical scroll bar + return size; + } + + private JComponent buildMemorySearchRegionsPanel() { + JPanel panel = new JPanel(new VerticalLayout(3)); + panel.setBorder(createBorder("Search Region Filter")); + + List choices = model.getMemoryRegionChoices(); + for (SearchRegion region : choices) { + GCheckBox checkbox = new GCheckBox(region.getName()); + checkbox.setToolTipText(region.getDescription()); + checkbox.setSelected(model.isSelectedRegion(region)); + checkbox.addItemListener(e -> model.selectRegion(region, checkbox.isSelected())); + panel.add(checkbox); + } + return panel; + } + + private JComponent buildDecimalOptions() { + JPanel panel = new JPanel(new VerticalLayout(3)); + panel.setBorder(createBorder("Decimal Options")); + + JPanel innerPanel = new JPanel(new PairLayout(5, 5)); + JLabel label = new JLabel("Size:"); + label.setToolTipText("Size of decimal values in bytes"); + innerPanel.add(label); + + Integer[] decimalSizes = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 16 }; + decimalByteSizeCombo = new GComboBox<>(decimalSizes); + decimalByteSizeCombo.setSelectedItem(4); + decimalByteSizeCombo.addItemListener(this::byteSizeComboChanged); + decimalByteSizeCombo.setToolTipText("Size of decimal values in bytes"); + innerPanel.add(decimalByteSizeCombo); + panel.add(innerPanel); + + decimalUnsignedCheckbox = new GCheckBox("Unsigned"); + decimalUnsignedCheckbox.setToolTipText( + "Sets whether decimal values should be interpreted as unsigned values"); + decimalUnsignedCheckbox.addActionListener( + e -> model.setDecimalUnsigned(decimalUnsignedCheckbox.isSelected())); + + panel.add(decimalUnsignedCheckbox); + return panel; + } + + private void byteSizeComboChanged(ItemEvent e) { + if (e.getStateChange() != ItemEvent.SELECTED) { + return; + } + int byteSize = (Integer) e.getItem(); + model.setDecimalByteSize(byteSize); + } + + private JComponent buildCodeUnitScopePanel() { + JPanel panel = new JPanel(new VerticalLayout(5)); + panel.setBorder(createBorder("Code Type Filter")); + GCheckBox instructionsCheckBox = new GCheckBox("Instructions"); + GCheckBox definedDataCheckBox = new GCheckBox("Defined Data"); + GCheckBox undefinedDataCheckBox = new GCheckBox("Undefined Data"); + instructionsCheckBox.setToolTipText( + "If selected, include matches found in instructions"); + definedDataCheckBox.setToolTipText( + "If selected, include matches found in defined data"); + undefinedDataCheckBox.setToolTipText( + "If selected, include matches found in undefined data"); + instructionsCheckBox.setSelected(model.includeInstructions()); + definedDataCheckBox.setSelected(model.includeDefinedData()); + undefinedDataCheckBox.setSelected(model.includeUndefinedData()); + instructionsCheckBox.addActionListener( + e -> model.setIncludeInstructions(instructionsCheckBox.isSelected())); + definedDataCheckBox.addActionListener( + e -> model.setIncludeDefinedData(definedDataCheckBox.isSelected())); + undefinedDataCheckBox.addActionListener( + e -> model.setIncludeUndefinedData(undefinedDataCheckBox.isSelected())); + panel.add(instructionsCheckBox); + panel.add(definedDataCheckBox); + panel.add(undefinedDataCheckBox); + return panel; + } + + private JComponent buildByteOptionsPanel() { + JPanel panel = new JPanel(new PairLayout(3, 2)); + panel.setBorder(createBorder("Byte Options")); + + String[] endianess = new String[] { "Big", "Little" }; + endianessCombo = new GComboBox<>(endianess); + endianessCombo.setSelectedIndex(model.isBigEndian() ? 0 : 1); + endianessCombo.addItemListener(this::endianessComboChanged); + endianessCombo.setToolTipText("Selects the endianess"); + + JTextField alignField = new JTextField(5); + alignField.setDocument(new RestrictedInputDocument()); + alignField.setName("Alignment"); + alignField.setText(Integer.toString(model.getAlignment())); + alignField.setToolTipText( + "Filters out matches whose address is not divisible by the alignment value"); + + panel.add(new JLabel("Endianess:")); + panel.add(endianessCombo); + panel.add(new JLabel("Alignment:")); + panel.add(alignField); + + return panel; + } + + private void endianessComboChanged(ItemEvent e) { + if (e.getStateChange() != ItemEvent.SELECTED) { + return; + } + String endianString = (String) e.getItem(); + Endian endian = Endian.toEndian(endianString); + model.setBigEndian(endian.isBigEndian()); + } + + private JComponent buildStringOptions() { + JPanel panel = new JPanel(new VerticalLayout(3)); + Charset[] supportedCharsets = + { StandardCharsets.US_ASCII, StandardCharsets.UTF_8, StandardCharsets.UTF_16 }; + + charsetCombo = new GComboBox<>(supportedCharsets); + charsetCombo.setName("Encoding Options"); + charsetCombo.setSelectedIndex(0); + charsetCombo.addItemListener(this::encodingComboChanged); + charsetCombo.setToolTipText("Character encoding for translating strings to bytes"); + + JPanel innerPanel = new JPanel(new PairLayout(5, 5)); + JLabel label = new JLabel("Encoding:"); + label.setToolTipText("Character encoding for translating strings to bytes"); + innerPanel.add(label); + innerPanel.add(charsetCombo); + panel.add(innerPanel); + + caseSensitiveCheckbox = new GCheckBox("Case Sensitive"); + caseSensitiveCheckbox.setSelected(model.isCaseSensitive()); + caseSensitiveCheckbox.setToolTipText("Allows for case sensitive searching."); + caseSensitiveCheckbox.addActionListener( + e -> model.setCaseSensitive(caseSensitiveCheckbox.isSelected())); + + escapeSequencesCheckbox = new GCheckBox("Escape Sequences"); + escapeSequencesCheckbox.setSelected(model.useEscapeSequences()); + escapeSequencesCheckbox.setToolTipText( + "Allows specifying control characters using escape sequences " + + "(i.e., allows \\n to be searched for as a single line feed character)."); + escapeSequencesCheckbox.addActionListener( + e -> model.setUseEscapeSequences(escapeSequencesCheckbox.isSelected())); + + panel.setBorder(createBorder("String Options")); + panel.add(caseSensitiveCheckbox); + panel.add(escapeSequencesCheckbox); + return panel; + } + + private void encodingComboChanged(ItemEvent e) { + if (e.getStateChange() != ItemEvent.SELECTED) { + return; + } + Charset charSet = (Charset) e.getItem(); + model.setStringCharset(charSet); + } + + private void guiModelChanged(SearchSettings oldSettings) { + endianessCombo.setSelectedItem(model.isBigEndian() ? "Big" : "Little"); + caseSensitiveCheckbox.setSelected(model.isCaseSensitive()); + escapeSequencesCheckbox.setSelected(model.useEscapeSequences()); + decimalByteSizeCombo.setSelectedItem(model.getDecimalByteSize()); + decimalUnsignedCheckbox.setSelected(model.isDecimalUnsigned()); + charsetCombo.setSelectedItem(model.getStringCharset()); + } + + private Border createBorder(String name) { + TitledBorder outerBorder = BorderFactory.createTitledBorder(name); + if (isNimbus) { + return outerBorder; + } + Border innerBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5); + return BorderFactory.createCompoundBorder(outerBorder, innerBorder); + } + + /** + * Custom Document that validates user input on the fly. + */ + private class RestrictedInputDocument extends DefaultStyledDocument { + + /** + * Called before new user input is inserted into the entry text field. The super + * method is called if the input is accepted. + */ + @Override + public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { + + String currentText = getText(0, getLength()); + String beforeOffset = currentText.substring(0, offs); + String afterOffset = currentText.substring(offs, currentText.length()); + String proposedText = beforeOffset + str + afterOffset; + int alignment = getValue(proposedText); + if (alignment > 0) { + super.insertString(offs, str, a); + model.setAlignment(alignment); + } + + } + + @Override + public void remove(int offs, int len) throws BadLocationException { + + String currentText = getText(0, getLength()); + String beforeOffset = currentText.substring(0, offs); + String afterOffset = currentText.substring(len + offs, currentText.length()); + String proposedResult = beforeOffset + afterOffset; + int alignment = getValue(proposedResult); + if (alignment > 0) { + super.remove(offs, len); + model.setAlignment(alignment); + } + } + + private int getValue(String proposedText) { + if (proposedText.isBlank()) { + return 1; + } + try { + return Integer.parseInt(proposedText); + } + catch (NumberFormatException e) { + return -1; + } + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchPlugin.java new file mode 100644 index 0000000000..078deac018 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchPlugin.java @@ -0,0 +1,252 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import javax.swing.KeyStroke; + +import docking.action.builder.ActionBuilder; +import ghidra.app.CorePluginPackage; +import ghidra.app.context.NavigatableActionContext; +import ghidra.app.events.ProgramSelectionPluginEvent; +import ghidra.app.nav.Navigatable; +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.services.*; +import ghidra.app.util.HelpTopics; +import ghidra.app.util.query.TableService; +import ghidra.features.base.memsearch.bytesource.AddressableByteSource; +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.features.base.memsearch.searcher.MemorySearcher; +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.listing.Program; +import ghidra.program.util.*; +import ghidra.util.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.*; + +/** + * Plugin for searching program memory. + */ +//@formatter:off +@PluginInfo( + status = PluginStatus.RELEASED, + packageName = CorePluginPackage.NAME, + category = PluginCategoryNames.SEARCH, + shortDescription = "Search bytes in memory", + description = "This plugin searches bytes in memory. The search " + + "is based on a value entered as hex or decimal numbers, or strings." + + " The value may contain \"wildcards\" or regular expressions" + + " that will match any byte or nibble.", + servicesRequired = { ProgramManager.class, GoToService.class, TableService.class, CodeViewerService.class }, + servicesProvided = { MemorySearchService.class }, + eventsConsumed = { ProgramSelectionPluginEvent.class } +) +//@formatter:on +public class MemorySearchPlugin extends Plugin implements MemorySearchService { + private static final int MAX_HISTORY = 10; + private static final String SHOW_OPTIONS_PANEL = "Show Options Panel"; + private static final String SHOW_SCAN_PANEL = "Show Scan Panel"; + + private ByteMatcher lastByteMatcher; + private MemorySearchOptions options; + private SearchHistory searchHistory = new SearchHistory(MAX_HISTORY); + private Address lastSearchAddress; + + private boolean showScanPanel; + + private boolean showOptionsPanel; + + public MemorySearchPlugin(PluginTool tool) { + super(tool); + createActions(); + options = new MemorySearchOptions(tool); + } + + private void createActions() { + new ActionBuilder("Memory Search", getName()) + .menuPath("&Search", "&Memory...") + .menuGroup("search", "a") + .keyBinding("s") + .description("Search Memory for byte sequence") + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Memory Search")) + .withContext(NavigatableActionContext.class, true) + .onAction(this::showSearchMemoryProvider) + .buildAndInstall(tool); + + new ActionBuilder("Repeat Memory Search Forwards", getName()) + .menuPath("&Search", "Repeat Search &Forwards") + .menuGroup("search", "b") + .keyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0)) + .description("Repeat last memory search fowards once") + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Repeat Search Forwards")) + .withContext(NavigatableActionContext.class, true) + .enabledWhen(c -> lastByteMatcher != null && c.getAddress() != null) + .onAction(c -> searchOnce(c, true)) + .buildAndInstall(tool); + + new ActionBuilder("Repeat Memory Search Backwards", getName()) + .menuPath("&Search", "Repeat Search &Backwards") + .menuGroup("search", "c") + .keyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.SHIFT_DOWN_MASK)) + .description("Repeat last memory search backwards once") + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Repeat Search Backwards")) + .withContext(NavigatableActionContext.class, true) + .enabledWhen(c -> lastByteMatcher != null && c.getAddress() != null) + .onAction(c -> searchOnce(c, false)) + .buildAndInstall(tool); + + } + + private void showSearchMemoryProvider(NavigatableActionContext c) { + SearchSettings settings = lastByteMatcher != null ? lastByteMatcher.getSettings() : null; + SearchHistory copy = new SearchHistory(searchHistory); + MemorySearchProvider provider = + new MemorySearchProvider(this, c.getNavigatable(), settings, options, copy); + + provider.showOptions(showOptionsPanel); + provider.showScanPanel(showScanPanel); + } + + private void searchOnce(NavigatableActionContext c, boolean forward) { + SearchOnceTask task = new SearchOnceTask(c.getNavigatable(), forward); + TaskLauncher.launch(task); + } + + void updateByteMatcher(ByteMatcher matcher) { + lastByteMatcher = matcher; + searchHistory.addSearch(matcher); + } + + @Override + public void readConfigState(SaveState saveState) { + showOptionsPanel = saveState.getBoolean(SHOW_OPTIONS_PANEL, false); + showScanPanel = saveState.getBoolean(SHOW_SCAN_PANEL, false); + } + + @Override + public void writeConfigState(SaveState saveState) { + saveState.putBoolean(SHOW_OPTIONS_PANEL, showOptionsPanel); + saveState.putBoolean(SHOW_SCAN_PANEL, showOptionsPanel); + } +//================================================================================================== +// MemorySearchService methods +//================================================================================================== + + @Override + public void createMemorySearchProvider(Navigatable navigatable, String input, + SearchSettings settings, boolean useSelection) { + + SearchHistory copy = new SearchHistory(searchHistory); + MemorySearchProvider provider = + new MemorySearchProvider(this, navigatable, settings, options, copy); + provider.setSearchInput(input); + provider.setSearchSelectionOnly(false); + + // Custom providers may use input and settings that are fairly unique and not chosen + // by the user directly. We therefore don't want those settings to be reported back for + // adding to the default settings state and history and thereby affecting future normal + // memory searches. + provider.setPrivate(); + } + + private class SearchOnceTask extends Task { + + private Navigatable navigatable; + private boolean forward; + + public SearchOnceTask(Navigatable navigatable, boolean forward) { + super("Search Next", true, true, true); + this.navigatable = navigatable; + this.forward = forward; + } + + private AddressSet getSearchAddresses() { + SearchSettings settings = lastByteMatcher.getSettings(); + AddressSet searchAddresses = settings.getSearchAddresses(navigatable.getProgram()); + ProgramSelection selection = navigatable.getSelection(); + if (selection != null && !selection.isEmpty()) { + searchAddresses = searchAddresses.intersect(navigatable.getSelection()); + } + return searchAddresses; + } + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + AddressableByteSource source = navigatable.getByteSource(); + AddressSet addresses = getSearchAddresses(); + if (addresses.isEmpty()) { + Msg.showWarn(this, null, "Search Failed!", "Addresses to search is empty!"); + return; + } + + Address start = getSearchStartAddress(); + if (start == null) { + Msg.showWarn(this, null, "Search Failed!", "No valid start address!"); + return; + } + MemorySearcher searcher = new MemorySearcher(source, lastByteMatcher, addresses, 1); + + MemoryMatch match = searcher.findOnce(start, forward, monitor); + + Swing.runLater(() -> navigateToMatch(match)); + } + + private Address getSearchStartAddress() { + ProgramLocation location = navigatable.getLocation(); + if (location == null) { + return null; + } + Address start = navigatable.getLocation().getByteAddress(); + if (lastSearchAddress != null) { + CodeUnit cu = navigatable.getProgram().getListing().getCodeUnitContaining(start); + if (cu != null && cu.contains(lastSearchAddress)) { + start = lastSearchAddress; + } + } + return forward ? start.next() : start.previous(); + } + + private void navigateToMatch(MemoryMatch match) { + if (match != null) { + lastSearchAddress = match.getAddress(); + Program program = navigatable.getProgram(); + navigatable.goTo(program, new BytesFieldLocation(program, match.getAddress())); + } + else { + Msg.showWarn(this, null, "Match Not Found", + "No match found going forward for " + lastByteMatcher.getInput()); + } + } + } + + public void setShowOptionsPanel(boolean show) { + showOptionsPanel = show; + + } + + public void setShowScanPanel(boolean show) { + showScanPanel = show; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java new file mode 100644 index 0000000000..4cf79e60df --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java @@ -0,0 +1,655 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.awt.*; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import javax.swing.*; + +import docking.ActionContext; +import docking.DockingContextListener; +import docking.action.DockingAction; +import docking.action.ToggleDockingAction; +import docking.action.builder.ActionBuilder; +import docking.action.builder.ToggleActionBuilder; +import generic.theme.GIcon; +import ghidra.app.context.NavigatableActionContext; +import ghidra.app.nav.Navigatable; +import ghidra.app.nav.NavigatableRemovalListener; +import ghidra.app.util.HelpTopics; +import ghidra.features.base.memsearch.bytesource.AddressableByteSource; +import ghidra.features.base.memsearch.bytesource.SearchRegion; +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.features.base.memsearch.scan.Scanner; +import ghidra.features.base.memsearch.searcher.*; +import ghidra.framework.model.DomainObject; +import ghidra.framework.model.DomainObjectClosedListener; +import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.listing.Program; +import ghidra.program.util.*; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; +import ghidra.util.layout.VerticalLayout; +import ghidra.util.table.GhidraTable; +import ghidra.util.table.SelectionNavigationAction; +import ghidra.util.table.actions.DeleteTableRowAction; +import ghidra.util.table.actions.MakeProgramSelectionAction; +import resources.Icons; + +/** + * ComponentProvider used to search memory and display search results. + */ +public class MemorySearchProvider extends ComponentProviderAdapter + implements DockingContextListener, NavigatableRemovalListener, DomainObjectClosedListener { + + // @formatter:off + private static final Icon SHOW_SEARCH_PANEL_ICON = new GIcon("icon.base.mem.search.panel.search"); + private static final Icon SHOW_SCAN_PANEL_ICON = new GIcon("icon.base.mem.search.panel.scan"); + private static final Icon SHOW_OPTIONS_ICON = new GIcon("icon.base.mem.search.panel.options"); + // @formatter:on + + private static Set USED_IDS = new HashSet<>(); + + private final int id = getId(); + + private Navigatable navigatable; + private Program program; + private AddressableByteSource byteSource; + + private JComponent mainComponent; + private JPanel controlPanel; + private MemorySearchControlPanel searchPanel; + private MemoryScanControlPanel scanPanel; + private MemorySearchOptionsPanel optionsPanel; + private MemorySearchResultsPanel resultsPanel; + + private ToggleDockingAction toggleOptionsPanelAction; + private ToggleDockingAction toggleScanPanelAction; + private ToggleDockingAction toggleSearchPanelAction; + private DockingAction previousAction; + private DockingAction nextAction; + private DockingAction refreshAction; + + private ByteMatcher byteMatcher; + private Address lastMatchingAddress; + + private boolean isBusy; + private MemoryMatchHighlighter matchHighlighter; + private MemorySearchPlugin plugin; + private MemorySearchOptions options; + private SearchGuiModel model; + private boolean isPrivate = false; + + public MemorySearchProvider(MemorySearchPlugin plugin, Navigatable navigatable, + SearchSettings settings, MemorySearchOptions options, SearchHistory history) { + super(plugin.getTool(), "Memory Search", plugin.getName()); + this.plugin = plugin; + this.navigatable = navigatable; + this.options = options; + this.program = navigatable.getProgram(); + this.byteSource = navigatable.getByteSource(); + + // always initially use the byte ordering of the program, regardless of previous searches + if (settings == null) { + settings = new SearchSettings(); + } + settings = settings.withBigEndian(program.getMemory().isBigEndian()); + + this.model = new SearchGuiModel(settings, byteSource.getSearchableRegions()); + model.setHasSelection(hasSelection(navigatable.getSelection())); + model.setAutoRestrictSelection(options.isAutoRestrictSelection()); + setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Memory_Search")); + + SearchMarkers markers = new SearchMarkers(tool, getTitle(), program); + searchPanel = new MemorySearchControlPanel(this, model, history); + scanPanel = new MemoryScanControlPanel(this); + optionsPanel = new MemorySearchOptionsPanel(model); + resultsPanel = new MemorySearchResultsPanel(this, markers); + mainComponent = buildMainComponent(); + matchHighlighter = + new MemoryMatchHighlighter(navigatable, resultsPanel.getTableModel(), options); + + setTransient(); + addToTool(); + setVisible(true); + + createActions(plugin.getName()); + + tool.addContextListener(this); + navigatable.addNavigatableListener(this); + program.addCloseListener(this); + updateTitle(); + + } + + public void setSearchInput(String input) { + searchPanel.setSearchInput(input); + } + + public String getSearchInput() { + return byteMatcher == null ? "" : byteMatcher.getInput(); + } + + public void setSearchSelectionOnly(boolean b) { + model.setSearchSelectionOnly(b); + } + + void setPrivate() { + this.isPrivate = true; + } + + private void updateTitle() { + StringBuilder builder = new StringBuilder(); + String searchInput = getSearchInput(); + builder.append("Search Memory: "); + if (!searchInput.isBlank()) { + builder.append("\""); + builder.append(searchInput); + builder.append("\""); + } + builder.append(" ("); + builder.append(getProgramName()); + builder.append(")"); + setTitle(builder.toString()); + } + + @Override + public JComponent getComponent() { + return mainComponent; + } + + void setByteMatcher(ByteMatcher byteMatcher) { + this.byteMatcher = byteMatcher; + tool.contextChanged(this); + } + + /* + * This method will disable "search" actions immediately upon initiating any search or + * scan action. Normally, these actions would enable and disable via context as usual, but + * context changes are issued in a delayed fashion. + */ + void disableActionsFast() { + nextAction.setEnabled(false); + previousAction.setEnabled(false); + refreshAction.setEnabled(false); + } + + boolean canProcessResults() { + return !isBusy && resultsPanel.hasResults(); + } + + private void searchOnce(boolean forward) { + if (hasInvalidSearchSettings()) { + return; + } + updateTitle(); + + Address start = getSearchStartAddress(forward); + AddressSet addresses = getSearchAddresses(); + MemorySearcher searcher = new MemorySearcher(byteSource, byteMatcher, addresses, 1); + searcher.setMatchFilter(createFilter()); + + setBusy(true); + resultsPanel.searchOnce(searcher, start, forward); + + // Only update future memory search settings if this is a standard memory search provider + // because we don't want potentially highly specialized inputs and settings to be in + // the history for standard memory search operations. + if (!isPrivate) { + plugin.updateByteMatcher(byteMatcher); + } + } + + // public so can be called by tests + public void search() { + if (hasInvalidSearchSettings()) { + return; + } + updateTitle(); + int limit = options.getSearchLimit(); + AddressSet addresses = getSearchAddresses(); + MemorySearcher searcher = new MemorySearcher(byteSource, byteMatcher, addresses, limit); + searcher.setMatchFilter(createFilter()); + + setBusy(true); + searchPanel.setSearchStatus(resultsPanel.hasResults(), true); + resultsPanel.search(searcher, model.getMatchCombiner()); + + // Only update future memory search settings if this is a standard memory search provider + // because we don't want potentially highly specialized inputs and settings to be in + // the history for standard memory search operations. + if (!isPrivate) { + plugin.updateByteMatcher(byteMatcher); + } + } + + private boolean hasInvalidSearchSettings() { + Set selectedMemoryRegions = model.getSelectedMemoryRegions(); + if (selectedMemoryRegions.isEmpty()) { + Msg.showInfo(getClass(), resultsPanel, "No Memory Regions Selected!", + "You must select one or more memory regions to perform a search!"); + return true; + } + + if (!(model.includeInstructions() || + model.includeDefinedData() || + model.includeUndefinedData())) { + + Msg.showInfo(getClass(), resultsPanel, "No Code Types Selected!", + "You must select at least one of \"Instructions\"," + + " \"Defined Data\" or \"Undefined Data\" to perform a search!"); + return true; + + } + + return false; + } + + /** + * Performs a scan on the current results, keeping only the results that match the type of scan. + * Note: this method is public to facilitate testing. + * + * @param scanner the scanner to use to reduce the results. + */ + public void scan(Scanner scanner) { + setBusy(true); + resultsPanel.refreshAndMaybeScanForChanges(byteSource, scanner); + } + + private AddressSet getSearchAddresses() { + AddressSet set = model.getSettings().getSearchAddresses(program); + + if (model.isSearchSelectionOnly()) { + set = set.intersect(navigatable.getSelection()); + } + return set; + + } + + private void refreshResults() { + setBusy(true); + resultsPanel.refreshAndMaybeScanForChanges(byteSource, null); + } + + private void setBusy(boolean isBusy) { + this.isBusy = isBusy; + boolean hasResults = resultsPanel.hasResults(); + searchPanel.setSearchStatus(hasResults, isBusy); + scanPanel.setSearchStatus(hasResults, isBusy); + if (isBusy) { + disableActionsFast(); + } + tool.contextChanged(this); + } + + private Predicate createFilter() { + AlignmentFilter alignmentFilter = new AlignmentFilter(model.getAlignment()); + CodeUnitFilter codeUnitFilter = + new CodeUnitFilter(program, model.includeInstructions(), + model.includeDefinedData(), model.includeUndefinedData()); + return alignmentFilter.and(codeUnitFilter); + } + + private Address getSearchStartAddress(boolean forward) { + ProgramLocation location = navigatable.getLocation(); + Address startAddress = location == null ? null : location.getByteAddress(); + if (startAddress == null) { + startAddress = forward ? program.getMinAddress() : program.getMaxAddress(); + } + + /* + Finding the correct starting address is tricky. Ideally, we would just use the + current cursor location's address and begin searching. However, this doesn't work + for subsequent searches for two reasons. + + The first reason is simply that subsequent searches need to start one address past the + current address or else you will just find the same location again. + + The second reason is caused by the way the listing handles arrays. Since arrays don't + have a bytes field, a previous search may have found a hit inside an array, but because + there is no place in the listing to represent that, the cursor is actually placed at + address that is before (possibly several addresses before) the actual hit. So going + forward in the next search, even after incrementing the address, will result in finding + that same hit. + + To solve this, the provider keeps track of a last match address. Subsequent searches + will use this address as long as that address and the cursor address are in the same + code unit. If they are not in the same code unit, we assume the user manually moved the + cursor and want to start searching from that new location. + */ + + if (lastMatchingAddress == null) { + return startAddress; + } + CodeUnit cu = program.getListing().getCodeUnitContaining(startAddress); + if (cu.contains(lastMatchingAddress)) { + startAddress = forward ? lastMatchingAddress.next() : lastMatchingAddress.previous(); + } + if (startAddress == null) { + startAddress = program.getMinAddress(); + } + return startAddress; + } + + void searchAllCompleted(boolean foundResults, boolean cancelled, boolean terminatedEarly) { + setBusy(false); + updateSubTitle(); + if (!cancelled && terminatedEarly) { + Msg.showInfo(getClass(), resultsPanel, "Search Limit Exceeded!", + "Stopped search after finding " + options.getSearchLimit() + " matches.\n" + + "The search limit can be changed at Edit->Tool Options, under Search."); + + } + else if (!foundResults) { + showAlert("No matches found!"); + } + } + + void searchOnceCompleted(MemoryMatch match, boolean cancelled) { + setBusy(false); + updateSubTitle(); + if (match != null) { + lastMatchingAddress = match.getAddress(); + navigatable.goTo(program, new BytesFieldLocation(program, match.getAddress())); + } + else { + showAlert("No Match Found!"); + } + } + + void refreshAndScanCompleted(MemoryMatch match) { + setBusy(false); + updateSubTitle(); + if (match != null) { + lastMatchingAddress = match.getAddress(); + navigatable.goTo(program, new BytesFieldLocation(program, match.getAddress())); + } + } + + @Override + public void componentActivated() { + resultsPanel.providerActivated(); + navigatable.setHighlightProvider(matchHighlighter, program); + } + + private void updateSubTitle() { + StringBuilder builder = new StringBuilder(); + builder.append(" "); + int matchCount = resultsPanel.getMatchCount(); + if (matchCount > 0) { + builder.append("("); + builder.append(matchCount); + builder.append(matchCount == 1 ? " entry)" : " entries)"); + } + setSubTitle(builder.toString()); + } + + private String getProgramName() { + return program.getDomainFile().getName(); + } + + private void updateControlPanel() { + controlPanel.removeAll(); + boolean showSearchPanel = toggleSearchPanelAction.isSelected(); + boolean showScanPanel = toggleScanPanelAction.isSelected(); + + if (showSearchPanel) { + controlPanel.add(searchPanel); + } + if (showSearchPanel && showScanPanel) { + controlPanel.add(new JSeparator()); + } + if (showScanPanel) { + controlPanel.add(scanPanel); + } + controlPanel.revalidate(); + } + + private void toggleShowScanPanel() { + plugin.setShowScanPanel(toggleScanPanelAction.isSelected()); + updateControlPanel(); + } + + private void toggleShowSearchPanel() { + updateControlPanel(); + } + + private void toggleShowOptions() { + plugin.setShowOptionsPanel(toggleOptionsPanelAction.isSelected()); + if (toggleOptionsPanelAction.isSelected()) { + mainComponent.add(optionsPanel, BorderLayout.EAST); + } + else { + mainComponent.remove(optionsPanel); + } + mainComponent.validate(); + } + + private boolean canSearch() { + return !isBusy && byteMatcher != null && byteMatcher.isValidSearch(); + } + + private JComponent buildMainComponent() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setPreferredSize(new Dimension(900, 650)); + panel.add(buildCenterPanel(), BorderLayout.CENTER); + return panel; + } + + private JComponent buildCenterPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.add(buildControlPanel(), BorderLayout.NORTH); + panel.add(resultsPanel, BorderLayout.CENTER); + return panel; + } + + private JComponent buildControlPanel() { + controlPanel = new JPanel(new VerticalLayout(0)); + controlPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + controlPanel.add(searchPanel); + return controlPanel; + } + + private void createActions(String owner) { + + nextAction = new ActionBuilder("Search Next", owner) + .toolBarIcon(Icons.DOWN_ICON) + .toolBarGroup("A") + .description("Search forward for 1 result") + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search_Next")) + .enabledWhen(c -> canSearch()) + .onAction(c -> searchOnce(true)) + .buildAndInstallLocal(this); + previousAction = new ActionBuilder("Search Previous", owner) + .toolBarIcon(Icons.UP_ICON) + .toolBarGroup("A") + .description("Search backward for 1 result") + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search_Previous")) + .enabledWhen(c -> canSearch()) + .onAction(c -> searchOnce(false)) + .buildAndInstallLocal(this); + + refreshAction = new ActionBuilder("Refresh Results", owner) + .toolBarIcon(Icons.REFRESH_ICON) + .toolBarGroup("A") + .description( + "Reload bytes from memory for each search result and show changes in red") + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Refresh_Values")) + .enabledWhen(c -> canProcessResults()) + .onAction(c -> refreshResults()) + .buildAndInstallLocal(this); + + toggleSearchPanelAction = new ToggleActionBuilder("Show Memory Search Controls", owner) + .toolBarIcon(SHOW_SEARCH_PANEL_ICON) + .toolBarGroup("Z") + .description("Toggles showing the search controls") + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Toggle_Search")) + .selected(true) + .onAction(c -> toggleShowSearchPanel()) + .buildAndInstallLocal(this); + + toggleScanPanelAction = new ToggleActionBuilder("Show Memory Scan Controls", owner) + .toolBarIcon(SHOW_SCAN_PANEL_ICON) + .toolBarGroup("Z") + .description("Toggles showing the scan controls") + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Toggle_Scan")) + .onAction(c -> toggleShowScanPanel()) + .buildAndInstallLocal(this); + + toggleOptionsPanelAction = new ToggleActionBuilder("Show Options", owner) + .toolBarIcon(SHOW_OPTIONS_ICON) + .toolBarGroup("Z") + .description("Toggles showing the search options panel") + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Toggle_Options")) + .onAction(c -> toggleShowOptions()) + .buildAndInstallLocal(this); + + // add standard table actions + GhidraTable table = resultsPanel.getTable(); + addLocalAction(new MakeProgramSelectionAction(navigatable, owner, table)); + addLocalAction(new SelectionNavigationAction(owner, table)); + addLocalAction(new DeleteTableRowAction(table, owner) { + @Override + public void actionPerformed(ActionContext context) { + super.actionPerformed(context); + updateSubTitle(); + } + }); + + } + + @Override + public void removeFromTool() { + dispose(); + super.removeFromTool(); + } + + private void dispose() { + matchHighlighter.dispose(); + USED_IDS.remove(id); + if (navigatable != null) { + navigatable.removeNavigatableListener(this); + } + resultsPanel.dispose(); + tool.removeContextListener(this); + program.removeCloseListener(this); + } + + @Override + public void contextChanged(ActionContext context) { + model.setHasSelection(hasSelection(navigatable.getSelection())); + } + + private boolean hasSelection(ProgramSelection selection) { + if (selection == null) { + return false; + } + return !selection.isEmpty(); + } + + @Override + public void navigatableRemoved(Navigatable nav) { + closeComponent(); + } + + @Override + public void domainObjectClosed(DomainObject dobj) { + closeComponent(); + } + + Navigatable getNavigatable() { + return navigatable; + } + + private static int getId() { + for (int i = 0; i < Integer.MAX_VALUE; i++) { + if (!USED_IDS.contains(i)) { + USED_IDS.add(i); + return i; + } + } + return 0; + } + + void tableSelectionChanged() { + MemoryMatch selectedMatch = resultsPanel.getSelectedMatch(); + matchHighlighter.setSelectedMatch(selectedMatch); + if (selectedMatch != null) { + lastMatchingAddress = selectedMatch.getAddress(); + } + tool.contextChanged(this); + } + + public void showOptions(boolean b) { + toggleOptionsPanelAction.setSelected(b); + toggleShowOptions(); + } + + public void showScanPanel(boolean b) { + toggleScanPanelAction.setSelected(b); + updateControlPanel(); + } + + public void showSearchPanel(boolean b) { + toggleSearchPanelAction.setSelected(b); + updateControlPanel(); + } + + // testing + public boolean isBusy() { + return isBusy; + } + + public List getSearchResults() { + return resultsPanel.getTableModel().getModelData(); + } + + public void setSettings(SearchSettings settings) { + String converted = searchPanel.convertInput(model.getSettings(), settings); + model.setSettings(settings); + searchPanel.setSearchInput(converted); + } + + public boolean isSearchSelection() { + return model.isSearchSelectionOnly(); + } + + public String getByteString() { + return byteMatcher.getDescription(); + } + + void showAlert(String alertMessage) { + // replace with water mark concept + Toolkit.getDefaultToolkit().beep(); + Msg.showInfo(this, null, "Search Results", alertMessage); + } + + @Override + protected ActionContext createContext(Component sourceComponent, Object contextObject) { + ActionContext context = new NavigatableActionContext(this, navigatable); + context.setContextObject(contextObject); + context.setSourceComponent(sourceComponent); + return context; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java new file mode 100644 index 0000000000..7e85963a35 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java @@ -0,0 +1,294 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.awt.BorderLayout; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.TableModelEvent; + +import ghidra.app.nav.Navigatable; +import ghidra.features.base.memsearch.bytesource.AddressableByteSource; +import ghidra.features.base.memsearch.combiner.Combiner; +import ghidra.features.base.memsearch.scan.Scanner; +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.features.base.memsearch.searcher.MemorySearcher; +import ghidra.program.model.address.Address; +import ghidra.util.Msg; +import ghidra.util.Swing; +import ghidra.util.exception.CancelledException; +import ghidra.util.table.*; +import ghidra.util.task.*; + +/** + * Internal panel of the memory search window that manages the display of the search results + * in a table. This panel also includes most of the search logic as it has direct access to the + * table for showing the results. + */ +class MemorySearchResultsPanel extends JPanel { + private GhidraThreadedTablePanel threadedTablePanel; + private GhidraTableFilterPanel tableFilterPanel; + private GhidraTable table; + private MemoryMatchTableModel tableModel; + private MemorySearchProvider provider; + private SearchMarkers markers; + + MemorySearchResultsPanel(MemorySearchProvider provider, SearchMarkers markers) { + super(new BorderLayout()); + this.provider = provider; + this.markers = markers; + + Navigatable navigatable = provider.getNavigatable(); + tableModel = new MemoryMatchTableModel(provider.getTool(), navigatable.getProgram()); + threadedTablePanel = new GhidraThreadedTablePanel<>(tableModel); + table = threadedTablePanel.getTable(); + + table.setActionsEnabled(true); + table.installNavigation(provider.getTool(), navigatable); + tableModel.addTableModelListener(this::tableChanged); + + add(threadedTablePanel, BorderLayout.CENTER); + add(createFilterFieldPanel(), BorderLayout.SOUTH); + ListSelectionModel selectionModel = threadedTablePanel.getTable().getSelectionModel(); + selectionModel.addListSelectionListener(this::selectionChanged); + } + + private void tableChanged(TableModelEvent event) { + markers.loadMarkers(provider.getTitle(), tableModel.getModelData()); + } + + void providerActivated() { + markers.makeActiveMarkerSet(); + } + + private void selectionChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + provider.tableSelectionChanged(); + } + + private JComponent createFilterFieldPanel() { + tableFilterPanel = new GhidraTableFilterPanel<>(table, tableModel); + tableFilterPanel.setToolTipText("Filter search results"); + return tableFilterPanel; + } + + public void search(MemorySearcher searcher, Combiner combiner) { + MemoryMatchTableLoader loader = createLoader(searcher, combiner); + tableModel.addInitialLoadListener( + cancelled -> provider.searchAllCompleted(loader.hasResults(), cancelled, + loader.didTerminateEarly())); + tableModel.setLoader(loader); + } + + public void searchOnce(MemorySearcher searcher, Address address, boolean forward) { + SearchOnceTask task = new SearchOnceTask(forward, searcher, address); + TaskLauncher.launch(task); + } + + public void refreshAndMaybeScanForChanges(AddressableByteSource byteSource, Scanner scanner) { + RefreshAndScanTask task = new RefreshAndScanTask(byteSource, scanner); + TaskLauncher.launch(task); + } + + private MemoryMatchTableLoader createLoader(MemorySearcher searcher, Combiner combiner) { + if (hasResults()) { + + // If we have existing results, the combiner determines how the new search results get + // combined with the existing results. + // + // However, if the combiner is the "Replace" combiner, the results are not combined + // and only the new results are kept. In this case, it is preferred to use the same + // loader as if doing an initial search because you get incremental loading and also + // don't need to copy the existing results to feed to a combiner. + if (combiner != Combiner.REPLACE) { + List previousResults = tableModel.getModelData(); + return new CombinedMatchTableLoader(searcher, previousResults, combiner); + } + } + return new NewSearchTableLoader(searcher); + } + + public boolean hasResults() { + return tableModel.getRowCount() > 0; + } + + public void clearResults() { + tableModel.addInitialLoadListener(b -> provider.searchAllCompleted(true, false, false)); + tableModel.setLoader(new EmptyMemoryMatchTableLoader()); + } + + public int getMatchCount() { + return tableModel.getRowCount(); + } + + void select(MemoryMatch match) { + int rowIndex = tableModel.getRowIndex(match); + if (rowIndex >= 0) { + threadedTablePanel.getTable().selectRow(rowIndex); + } + } + + GhidraTable getTable() { + return table; + } + + public MemoryMatch getSelectedMatch() { + int row = table.getSelectedRow(); + return row < 0 ? null : tableModel.getRowObject(row); + } + + public void dispose() { + markers.dispose(); + tableFilterPanel.dispose(); + } + + MemoryMatchTableModel getTableModel() { + return tableModel; + } + + private class SearchOnceTask extends Task { + + private boolean forward; + private MemorySearcher searcher; + private Address start; + + public SearchOnceTask(boolean forward, MemorySearcher searcher, Address start) { + super(forward ? "Search Next" : "Search Previous", true, true, true); + this.forward = forward; + this.searcher = searcher; + this.start = start; + } + + private void tableLoadComplete(MemoryMatch match, boolean wasCancelled) { + int rowIndex = tableModel.getRowIndex(match); + if (rowIndex >= 0) { + table.selectRow(rowIndex); + table.scrollToSelectedRow(); + provider.searchOnceCompleted(match, wasCancelled); + } + } + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + try { + MemoryMatch match = searcher.findOnce(start, forward, monitor); + if (match != null) { + tableModel.addInitialLoadListener(b -> tableLoadComplete(match, b)); + tableModel.addObject(match); + return; + } + } + catch (Throwable t) { + // Catch any runtime errors so that we exit task gracefully and don't leave + // the provider in a stuck "busy" state. + Msg.showError(this, null, "Unexpected error refreshing bytes", t); + } + Swing.runLater(() -> provider.searchOnceCompleted(null, monitor.isCancelled())); + } + } + + private class RefreshAndScanTask extends Task { + + private AddressableByteSource byteSource; + private Scanner scanner; + + public RefreshAndScanTask(AddressableByteSource byteSource, Scanner scanner) { + super("Refreshing", true, true, true); + this.byteSource = byteSource; + this.scanner = scanner; + } + + private void tableLoadComplete(MemoryMatch match) { + if (match == null) { + provider.refreshAndScanCompleted(null); + } + int rowIndex = tableModel.getRowIndex(match); + if (rowIndex >= 0) { + table.selectRow(rowIndex); + table.scrollToSelectedRow(); + } + provider.refreshAndScanCompleted(match); + } + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + List matches = tableModel.getModelData(); + + if (refreshByteValues(monitor, matches) && scanner != null) { + performScanFiltering(monitor, matches); + } + else { + tableModel.fireTableDataChanged(); // some data bytes may have changed, repaint + provider.refreshAndScanCompleted(null); + } + + } + + private boolean refreshByteValues(TaskMonitor monitor, List matches) { + try { + byteSource.invalidate(); // clear any caches before refreshing byte values + monitor.initialize(matches.size(), "Refreshing..."); + for (MemoryMatch match : matches) { + byte[] bytes = new byte[match.getLength()]; + byteSource.getBytes(match.getAddress(), bytes, bytes.length); + match.updateBytes(bytes); + monitor.incrementProgress(); + if (monitor.isCancelled()) { + return false; + } + } + return true; + } + catch (Throwable t) { + // Catch any runtime errors so that we exit task gracefully and don't leave + // the provider in a stuck "busy" state. + Msg.showError(this, null, "Unexpected error refreshing bytes", t); + } + return false; + } + + private void performScanFiltering(TaskMonitor monitor, List matches) { + monitor.initialize(matches.size(), "Scanning for changes..."); + List scanResults = new ArrayList<>(); + for (MemoryMatch match : matches) { + if (scanner.accept(match)) { + scanResults.add(match); + } + if (monitor.isCancelled()) { + break; + } + } + + MemoryMatch firstIfReduced = getFirstMatchIfReduced(matches, scanResults); + tableModel.addInitialLoadListener(b -> tableLoadComplete(firstIfReduced)); + tableModel.setLoader(new RefreshResultsTableLoader(scanResults)); + } + + private MemoryMatch getFirstMatchIfReduced(List matches, + List scanResults) { + MemoryMatch firstIfReduced = null; + if (!scanResults.isEmpty() && scanResults.size() != matches.size()) { + firstIfReduced = scanResults.isEmpty() ? null : scanResults.getFirst(); + } + return firstIfReduced; + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/NewSearchTableLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/NewSearchTableLoader.java new file mode 100644 index 0000000000..164eda3b2c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/NewSearchTableLoader.java @@ -0,0 +1,67 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.util.Iterator; + +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.features.base.memsearch.searcher.MemorySearcher; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.task.TaskMonitor; + +/** + * Table loader that performs a search and displays the results in the table. + */ +public class NewSearchTableLoader implements MemoryMatchTableLoader { + + private MemorySearcher memSearcher; + private boolean completedSearch; + private MemoryMatch firstMatch; + + NewSearchTableLoader(MemorySearcher memSearcher) { + this.memSearcher = memSearcher; + } + + @Override + public void loadResults(Accumulator accumulator, TaskMonitor monitor) { + completedSearch = memSearcher.findAll(accumulator, monitor); + Iterator iterator = accumulator.iterator(); + if (iterator.hasNext()) { + firstMatch = iterator.next(); + } + } + + @Override + public boolean didTerminateEarly() { + return !completedSearch; + } + + @Override + public void dispose() { + // nothing to do + } + + @Override + public MemoryMatch getFirstMatch() { + return firstMatch; + } + + @Override + public boolean hasResults() { + return firstMatch != null; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/RefreshResultsTableLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/RefreshResultsTableLoader.java new file mode 100644 index 0000000000..fcc5ffce7b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/RefreshResultsTableLoader.java @@ -0,0 +1,63 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.util.List; + +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.task.TaskMonitor; + +/** + * Table loader that reloads the table with existing results after refreshing the byte values in + * those results. + */ +public class RefreshResultsTableLoader implements MemoryMatchTableLoader { + + private List matches; + private boolean hasResults; + + public RefreshResultsTableLoader(List matches) { + this.matches = matches; + } + + @Override + public void loadResults(Accumulator accumulator, TaskMonitor monitor) { + accumulator.addAll(matches); + hasResults = !accumulator.isEmpty(); + } + + @Override + public void dispose() { + matches.clear(); + matches = null; + } + + @Override + public boolean didTerminateEarly() { + return false; + } + + @Override + public MemoryMatch getFirstMatch() { + return null; + } + + @Override + public boolean hasResults() { + return hasResults; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchGuiModel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchGuiModel.java new file mode 100644 index 0000000000..a89be401ad --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchGuiModel.java @@ -0,0 +1,287 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.nio.charset.Charset; +import java.util.*; +import java.util.function.Consumer; + +import ghidra.features.base.memsearch.bytesource.SearchRegion; +import ghidra.features.base.memsearch.combiner.Combiner; +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +/** + * Maintains the state of all the settings and controls for the memory search window. + */ +public class SearchGuiModel { + + private SearchSettings settings; + private List> changeCallbacks = new ArrayList<>(); + private boolean hasSelection; + private List regionChoices; + private boolean autoResrictSelection = false; + private boolean isSearchSelectionOnly; + private Combiner combiner; + + public SearchGuiModel(SearchSettings settings, List regionChoices) { + this.regionChoices = regionChoices; + // make a copy of settings so they don't change out from under us + this.settings = settings != null ? settings : new SearchSettings(); + if (!isValidRegionSettings() || this.settings.getSelectedMemoryRegions().isEmpty()) { + installDefaultRegions(); + } + } + + private void installDefaultRegions() { + Set defaultRegions = new HashSet<>(); + for (SearchRegion region : regionChoices) { + if (region.isDefault()) { + defaultRegions.add(region); + } + } + settings = settings.withSelectedRegions(defaultRegions); + } + + private boolean isValidRegionSettings() { + for (SearchRegion region : settings.getSelectedMemoryRegions()) { + if (!regionChoices.contains(region)) { + return false; + } + } + return true; + } + + public void setAutoRestrictSelection() { + autoResrictSelection = true; + } + + public void addChangeCallback(Consumer changeCallback) { + changeCallbacks.add(changeCallback); + } + + private void notifySettingsChanged(SearchSettings oldSettings) { + for (Consumer callback : changeCallbacks) { + callback.accept(oldSettings); + } + } + + public boolean isSearchSelectionOnly() { + return isSearchSelectionOnly; + } + + public boolean hasSelection() { + return hasSelection; + } + + public void setHasSelection(boolean b) { + if (hasSelection == b) { + return; + } + hasSelection = b; + if (b) { + if (autoResrictSelection) { + // autoRestrictSelection means to auto turn on restrict search to selection when + // a selection happens + isSearchSelectionOnly = true; + } + } + else { + // if no selection, then we can't search in a selection! + isSearchSelectionOnly = false; + } + + notifySettingsChanged(settings); + } + + public void setSearchSelectionOnly(boolean b) { + if (isSearchSelectionOnly == b) { + return; + } + // can only set it if there is a current selection + isSearchSelectionOnly = b && hasSelection; + notifySettingsChanged(settings); + } + + public SearchFormat getSearchFormat() { + return settings.getSearchFormat(); + } + + public SearchSettings getSettings() { + return settings; + } + + public void setSearchFormat(SearchFormat searchFormat) { + if (searchFormat == settings.getSearchFormat()) { + return; + } + SearchSettings oldSettings = settings; + settings = settings.withSearchFormat(searchFormat); + notifySettingsChanged(oldSettings); + } + + public ByteMatcher parse(String proposedText) { + return settings.getSearchFormat().parse(proposedText, settings); + } + + public int getAlignment() { + return settings.getAlignment(); + } + + public void setAlignment(int alignment) { + settings = settings.withAlignment(alignment); + // this setting doesn't affect any other gui state, so no need to notify change + } + + public Set getSelectedMemoryRegions() { + return settings.getSelectedMemoryRegions(); + } + + public boolean includeInstructions() { + return settings.includeInstructions(); + } + + public boolean includeDefinedData() { + return settings.includeDefinedData(); + } + + public boolean includeUndefinedData() { + return settings.includeUndefinedData(); + } + + public void setIncludeInstructions(boolean selected) { + settings = settings.withIncludeInstructions(selected); + } + + public void setIncludeDefinedData(boolean selected) { + settings = settings.withIncludeDefinedData(selected); + } + + public void setIncludeUndefinedData(boolean selected) { + settings = settings.withIncludeUndefinedData(selected); + } + + public boolean isBigEndian() { + return settings.isBigEndian(); + } + + public void setBigEndian(boolean b) { + if (settings.isBigEndian() == b) { + return; + } + SearchSettings oldSettings = settings; + settings = settings.withBigEndian(b); + notifySettingsChanged(oldSettings); + } + + public boolean isCaseSensitive() { + return settings.isCaseSensitive(); + } + + public void setCaseSensitive(boolean selected) { + if (settings.isCaseSensitive() == selected) { + return; + } + SearchSettings oldSettings = settings; + settings = settings.withCaseSensitive(selected); + notifySettingsChanged(oldSettings); + } + + public boolean useEscapeSequences() { + return settings.useEscapeSequences(); + } + + public void setUseEscapeSequences(boolean selected) { + if (settings.useEscapeSequences() == selected) { + return; + } + SearchSettings oldSettings = settings; + settings = settings.withUseEscapeSequence(selected); + notifySettingsChanged(oldSettings); + } + + public void setDecimalUnsigned(boolean selected) { + if (settings.isDecimalUnsigned() == selected) { + return; + } + SearchSettings oldSettings = settings; + settings = settings.withDecimalUnsigned(selected); + notifySettingsChanged(oldSettings); + } + + public boolean isDecimalUnsigned() { + return settings.isDecimalUnsigned(); + } + + public void setDecimalByteSize(int byteSize) { + if (settings.getDecimalByteSize() == byteSize) { + return; + } + SearchSettings oldSettings = settings; + settings = settings.withDecimalByteSize(byteSize); + notifySettingsChanged(oldSettings); + } + + public int getDecimalByteSize() { + return settings.getDecimalByteSize(); + } + + public void setStringCharset(Charset charset) { + if (settings.getStringCharset() == charset) { + return; + } + SearchSettings oldSettings = settings; + settings = settings.withStringCharset(charset); + notifySettingsChanged(oldSettings); + } + + public Charset getStringCharset() { + return settings.getStringCharset(); + } + + public List getMemoryRegionChoices() { + return regionChoices; + } + + public void setMatchCombiner(Combiner combiner) { + this.combiner = combiner; + } + + public Combiner getMatchCombiner() { + return combiner; + } + + public void setAutoRestrictSelection(boolean autoRestrictSelection) { + this.autoResrictSelection = autoRestrictSelection; + } + + public void selectRegion(SearchRegion region, boolean selected) { + settings.withSelectedRegion(region, selected); + } + + public boolean isSelectedRegion(SearchRegion region) { + return settings.isSelectedRegion(region); + } + + public void setSettings(SearchSettings newSettings) { + SearchSettings oldSettings = settings; + settings = newSettings; + if (!isValidRegionSettings() || this.settings.getSelectedMemoryRegions().isEmpty()) { + installDefaultRegions(); + } + notifySettingsChanged(oldSettings); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchHistory.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchHistory.java new file mode 100644 index 0000000000..5341dbc958 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchHistory.java @@ -0,0 +1,79 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.features.base.memsearch.gui; + +import java.util.*; + +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +/** + * Class for managing memory search history. It maintains a list of previously used ByteMatchers to + * do memory searching. Each ByteMatcher records the input search text and the search settings used + * to create it. + */ +public class SearchHistory { + private List history = new LinkedList<>(); + private int maxHistory; + + public SearchHistory(int maxHistory) { + this.maxHistory = maxHistory; + } + + public SearchHistory(SearchHistory other) { + this.history = new LinkedList<>(other.history); + this.maxHistory = other.maxHistory; + } + + public void addSearch(ByteMatcher matcher) { + removeSimilarMatchers(matcher); + history.addFirst(matcher); + truncateHistoryAsNeeded(); + } + + private void removeSimilarMatchers(ByteMatcher matcher) { + Iterator it = history.iterator(); + String newInput = matcher.getInput(); + SearchFormat newFormat = matcher.getSettings().getSearchFormat(); + while (it.hasNext()) { + ByteMatcher historyMatch = it.next(); + SearchFormat historyFormat = historyMatch.getSettings().getSearchFormat(); + String historyInput = historyMatch.getInput(); + if (historyFormat.equals(newFormat) && historyInput.equals(newInput)) { + it.remove(); + } + } + } + + private void truncateHistoryAsNeeded() { + int historySize = history.size(); + + if (historySize > maxHistory) { + int numToRemove = historySize - maxHistory; + + for (int i = 0; i < numToRemove; i++) { + history.remove(history.size() - 1); + } + } + } + + public ByteMatcher[] getHistoryAsArray() { + ByteMatcher[] historyArray = new ByteMatcher[history.size()]; + history.toArray(historyArray); + return historyArray; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchMarkers.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchMarkers.java new file mode 100644 index 0000000000..9fec961756 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchMarkers.java @@ -0,0 +1,113 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.util.List; + +import javax.swing.Icon; + +import generic.theme.GIcon; +import ghidra.app.services.*; +import ghidra.app.util.SearchConstants; +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.program.util.*; + +/** + * Manages the {@link MarkerSet} for a given {@link MemorySearchProvider} window. + */ +public class SearchMarkers { + private static final Icon SEARCH_MARKER_ICON = new GIcon("icon.base.search.marker"); + private MarkerService service; + private MarkerSet markerSet; + private Program program; + + public SearchMarkers(PluginTool tool, String title, Program program) { + this.program = program; + service = tool.getService(MarkerService.class); + if (service == null) { + return; + } + } + + private MarkerSet createMarkerSet(String title) { + MarkerSet markers = service.createPointMarker(title, "Search", program, + MarkerService.SEARCH_PRIORITY, true, true, false, + SearchConstants.SEARCH_HIGHLIGHT_COLOR, SEARCH_MARKER_ICON); + + markers.setMarkerDescriptor(new MarkerDescriptor() { + @Override + public ProgramLocation getProgramLocation(MarkerLocation loc) { + return new BytesFieldLocation(program, loc.getAddr()); + } + }); + + // remove it; we will add it later to a group + service.removeMarker(markers, program); + return markers; + } + + void makeActiveMarkerSet() { + if (service == null || markerSet == null) { + return; + } + service.setMarkerForGroup(MarkerService.HIGHLIGHT_GROUP, markerSet, program); + } + + void loadMarkers(String title, List matches) { + if (service == null) { + return; + } + + if (matches.isEmpty()) { + deleteMarkerSet(); + return; + } + + // If the title of the provider changes, we need to re-create the marker set as the + // provider's title is what is used as the marker set's name. The name is what shows up in + // the marker set gui for turning markers on and off - if they don't match the provider's + // title, it isn't obvious what provider the markers represent. (And currently, there is + // no way to change a marker set's name once it is created.) + if (markerSet != null && !markerSet.getName().equals(title)) { + deleteMarkerSet(); + } + + if (markerSet == null) { + markerSet = createMarkerSet(title); + } + + markerSet.clearAll(); + for (MemoryMatch match : matches) { + markerSet.add(match.getAddress()); + } + service.setMarkerForGroup(MarkerService.HIGHLIGHT_GROUP, markerSet, program); + } + + private void deleteMarkerSet() { + if (markerSet != null) { + markerSet.clearAll(); + service.removeMarker(markerSet, program); + markerSet = null; + } + } + + public void dispose() { + deleteMarkerSet(); + program = null; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchSettings.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchSettings.java new file mode 100644 index 0000000000..4e8083b1e9 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/SearchSettings.java @@ -0,0 +1,277 @@ +/* ### + * 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.features.base.memsearch.gui; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import ghidra.features.base.memsearch.bytesource.SearchRegion; +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.listing.Program; + +/** + * Immutable container for all the relevant search settings. + */ +public class SearchSettings { + private final SearchFormat searchFormat; + private final Set selectedRegions; + private final int alignment; + private final boolean bigEndian; + private final boolean caseSensitive; + private final boolean useEscapeSequences; + private final boolean includeInstructions; + private final boolean includeDefinedData; + private final boolean includeUndefinedData; + private final boolean isDecimalUnsigned; + private final int decimalByteSize; + private final Charset charset; + + public SearchSettings() { + this(SearchFormat.HEX, false, false, false, true, true, true, false, 4, 1, new HashSet<>(), + StandardCharsets.US_ASCII); + } + + //@formatter:off + private SearchSettings( + SearchFormat format, + boolean bigEndian, + boolean caseSensitive, + boolean useEscapeSequences, + boolean includeInstructions, + boolean includeDefinedData, + boolean includeUndefinedData, + boolean isDecimalUnsigned, + int decimalByteSize, + int alignment, + Set selectedRegions, + Charset charset) { + + this.searchFormat = format; + this.bigEndian = bigEndian; + this.caseSensitive = caseSensitive; + this.useEscapeSequences = useEscapeSequences; + this.includeInstructions = includeInstructions; + this.includeDefinedData = includeDefinedData; + this.includeUndefinedData = includeUndefinedData; + this.alignment = alignment; + this.decimalByteSize = decimalByteSize; + this.isDecimalUnsigned = isDecimalUnsigned; + this.selectedRegions = Collections.unmodifiableSet(new HashSet<>(selectedRegions)); + this.charset = charset; + + } + //@formatter:on + + /** + * Returns the {@link SearchFormat} to be used to parse the input text. + * @return the search format to be used to parse the input text + */ + public SearchFormat getSearchFormat() { + return searchFormat; + } + + /** + * Creates a copy of this settings object, but using the given search format. + * @param format the new search format + * @return a new search settings that is the same as this settings except for the format + */ + public SearchSettings withSearchFormat(SearchFormat format) { + if (this.searchFormat == format) { + return this; + } + return new SearchSettings(format, bigEndian, caseSensitive, + useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData, + isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset); + } + + public boolean isBigEndian() { + return bigEndian; + } + + public SearchSettings withBigEndian(boolean isBigEndian) { + if (this.bigEndian == isBigEndian) { + return this; + } + return new SearchSettings(searchFormat, isBigEndian, caseSensitive, + useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData, + isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset); + } + + public SearchSettings withStringCharset(Charset stringCharset) { + if (this.charset == stringCharset) { + return this; + } + return new SearchSettings(searchFormat, bigEndian, caseSensitive, + useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData, + isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, stringCharset); + } + + public Charset getStringCharset() { + return charset; + } + + public boolean useEscapeSequences() { + return useEscapeSequences; + } + + public SearchSettings withUseEscapeSequence(boolean b) { + if (this.useEscapeSequences == b) { + return this; + } + return new SearchSettings(searchFormat, bigEndian, caseSensitive, + b, includeInstructions, includeDefinedData, includeUndefinedData, + isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset); + } + + public boolean isCaseSensitive() { + return caseSensitive; + } + + public SearchSettings withCaseSensitive(boolean b) { + if (this.caseSensitive == b) { + return this; + } + return new SearchSettings(searchFormat, bigEndian, b, + useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData, + isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset); + } + + public boolean isDecimalUnsigned() { + return isDecimalUnsigned; + } + + public SearchSettings withDecimalUnsigned(boolean b) { + if (this.isDecimalUnsigned == b) { + return this; + } + return new SearchSettings(searchFormat, bigEndian, caseSensitive, + useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData, + b, decimalByteSize, alignment, selectedRegions, charset); + } + + public int getDecimalByteSize() { + return decimalByteSize; + } + + public SearchSettings withDecimalByteSize(int byteSize) { + if (this.decimalByteSize == byteSize) { + return this; + } + return new SearchSettings(searchFormat, bigEndian, caseSensitive, + useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData, + isDecimalUnsigned, byteSize, alignment, selectedRegions, charset); + } + + public boolean includeInstructions() { + return includeInstructions; + } + + public SearchSettings withIncludeInstructions(boolean b) { + if (this.includeInstructions == b) { + return this; + } + return new SearchSettings(searchFormat, bigEndian, caseSensitive, + useEscapeSequences, b, includeDefinedData, includeUndefinedData, + isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset); + } + + public boolean includeDefinedData() { + return includeDefinedData; + } + + public SearchSettings withIncludeDefinedData(boolean b) { + if (this.includeDefinedData == b) { + return this; + } + return new SearchSettings(searchFormat, bigEndian, caseSensitive, + useEscapeSequences, includeInstructions, b, includeUndefinedData, + isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset); + } + + public boolean includeUndefinedData() { + return includeUndefinedData; + } + + public SearchSettings withIncludeUndefinedData(boolean b) { + if (this.includeUndefinedData == b) { + return this; + } + + return new SearchSettings(searchFormat, bigEndian, caseSensitive, + useEscapeSequences, includeInstructions, includeDefinedData, b, + isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset); + } + + public int getAlignment() { + return alignment; + } + + public SearchSettings withAlignment(int newAlignment) { + if (this.alignment == newAlignment) { + return this; + } + return new SearchSettings(searchFormat, bigEndian, caseSensitive, + useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData, + isDecimalUnsigned, decimalByteSize, newAlignment, selectedRegions, charset); + } + + public Set getSelectedMemoryRegions() { + return selectedRegions; + } + + public SearchSettings withSelectedRegions(Set regions) { + if (this.selectedRegions.equals(regions)) { + return this; + } + return new SearchSettings(searchFormat, bigEndian, caseSensitive, + useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData, + isDecimalUnsigned, decimalByteSize, alignment, regions, charset); + } + + public boolean isSelectedRegion(SearchRegion region) { + return selectedRegions.contains(region); + } + + public SearchSettings withSelectedRegion(SearchRegion region, boolean select) { + return new SearchSettings(searchFormat, bigEndian, caseSensitive, + useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData, + isDecimalUnsigned, decimalByteSize, alignment, + createRegionSet(selectedRegions, region, select), charset); + } + + public AddressSet getSearchAddresses(Program program) { + AddressSet set = new AddressSet(); + for (SearchRegion memoryRegion : selectedRegions) { + set.add(memoryRegion.getAddresses(program)); + } + return set; + } + + private static Set createRegionSet(Set regions, + SearchRegion region, boolean select) { + Set set = new HashSet<>(regions); + if (select) { + set.add(region); + } + else { + set.remove(region); + } + return set; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/ByteMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/ByteMatcher.java new file mode 100644 index 0000000000..8f167f7504 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/ByteMatcher.java @@ -0,0 +1,126 @@ +/* ### + * 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.features.base.memsearch.matcher; + +import java.util.Objects; + +import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence; +import ghidra.features.base.memsearch.gui.SearchSettings; + +/** + * ByteMatcher is the base class for an object that be used to scan bytes looking for sequences + * that match some criteria. As a convenience, it also stores the input string and settings that + * were used to generated this ByteMatcher. + */ +public abstract class ByteMatcher { + + private final String input; + private final SearchSettings settings; + + protected ByteMatcher(String input, SearchSettings settings) { + this.input = input; + this.settings = settings; + } + + /** + * Returns the original input text that generated this ByteMatacher. + * @return the original input text that generated this BytesMatcher + */ + public final String getInput() { + return input == null ? "" : input; + } + + /** + * Returns the settings used to generate this ByteMatcher. + * @return the settings used to generate this ByteMatcher + */ + public SearchSettings getSettings() { + return settings; + } + + /** + * Returns an {@link Iterable} for returning matches within the given byte sequence. + * @param bytes the byte sequence to search + * @return an iterable for return matches in the given sequence + */ + public abstract Iterable match(ExtendedByteSequence bytes); + + /** + * Returns a description of what this byte matcher matches. (Typically a sequence of bytes) + * @return a description of what this byte matcher matches + */ + public abstract String getDescription(); + + /** + * Returns additional information about this byte matcher. (Typically the mask bytes) + * @return additional information about this byte matcher + */ + public abstract String getToolTip(); + + /** + * Returns true if this byte matcher is valid and can be used to perform a search. If false, + * the the description will return a an error message explaining why this byte matcher is + * invalid. + * @return true if this byte matcher is valid and can be used to perform a search. + */ + public boolean isValidSearch() { + return true; + } + + /** + * Returns true if this byte matcher has valid (but possibly incomplete) input text. For + * example, when entering decimal values, the input could be just "-" as the user starts + * to enter a negative number. In this case the input is valid, but the {@link #isValidSearch()} + * would return false. + * @return true if this byte matcher has valid text + */ + public boolean isValidInput() { + return true; + } + + @Override + public String toString() { + return input; + } + + @Override + public int hashCode() { + return input.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + ByteMatcher other = (ByteMatcher) obj; + return Objects.equals(input, other.input) && + settings.getSearchFormat() == other.settings.getSearchFormat(); + } + + /** + * Record class to contain a match specification. + */ + public record ByteMatch(int start, int length) {} + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/InvalidByteMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/InvalidByteMatcher.java new file mode 100644 index 0000000000..ecea33ae32 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/InvalidByteMatcher.java @@ -0,0 +1,83 @@ +/* ### + * 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.features.base.memsearch.matcher; + +import org.apache.commons.collections4.iterators.EmptyIterator; + +import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence; +import ghidra.features.base.memsearch.format.SearchFormat; +import util.CollectionUtils; + +/** + * Objects of this class are the result of {@link SearchFormat}s not being able to fully parse + * input text. There are two cases. The first is the user type an illegal character for the + * selected search format. In that case this matcher is both an invalid search and an invalid + * input and the description will explain the error. The second case is the input is valid text, + * but not complete so that a fully valid byte matcher could not be created. In this case, the + * search is still invalid, but the input is valid. The description will reflect this situation. + */ +public class InvalidByteMatcher extends ByteMatcher { + + private final String errorMessage; + private final boolean isValidInput; + + /** + * Construct an invalid matcher from invalid input text. + * @param errorMessage the message describing the invalid input + */ + public InvalidByteMatcher(String errorMessage) { + this(errorMessage, false); + } + + /** + * Construct an invalid matcher from invalid input text or partial input text. + * @param errorMessage the message describing why this matcher is invalid + * @param isValidInput return true if the reason this is invalid is simply that the input + * text is not complete. For example, the user types "-" as they are starting to input + * a negative number. + */ + public InvalidByteMatcher(String errorMessage, boolean isValidInput) { + super(null, null); + this.errorMessage = errorMessage; + this.isValidInput = isValidInput; + } + + @Override + public Iterable match(ExtendedByteSequence bytes) { + return CollectionUtils.asIterable(EmptyIterator.emptyIterator()); + } + + @Override + public String getDescription() { + return errorMessage; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isValidInput() { + return isValidInput; + } + + @Override + public boolean isValidSearch() { + return false; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/MaskedByteSequenceByteMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/MaskedByteSequenceByteMatcher.java new file mode 100644 index 0000000000..0d587af996 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/MaskedByteSequenceByteMatcher.java @@ -0,0 +1,179 @@ +/* ### + * 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.features.base.memsearch.matcher; + +import java.util.Iterator; + +import org.bouncycastle.util.Arrays; + +import ghidra.features.base.memsearch.bytesequence.ByteSequence; +import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence; +import ghidra.features.base.memsearch.gui.SearchSettings; + +/** + * {@link ByteMatcher} where the user search input has been parsed into a sequence of bytes and + * masks to be used for searching a byte sequence. + */ +public class MaskedByteSequenceByteMatcher extends ByteMatcher { + + private final byte[] searchBytes; + private final byte[] masks; + + /** + * Constructor where no masking will be required. The bytes must match exactly. + * @param input the input text used to create this matcher + * @param bytes the sequence of bytes to use for searching + * @param settings the {@link SearchSettings} used to parse the input text + */ + public MaskedByteSequenceByteMatcher(String input, byte[] bytes, SearchSettings settings) { + this(input, bytes, null, settings); + } + + /** + * Constructor that includes a mask byte for each search byte. + * @param input the input text used to create this matcher + * @param bytes the sequence of bytes to use for searching + * @param masks the sequence of mask bytes to use for search. Each mask byte will be applied + * to the bytes being search before comparing them to the target bytes. + * @param settings the {@link SearchSettings} used to parse the input text + */ + public MaskedByteSequenceByteMatcher(String input, byte[] bytes, byte[] masks, + SearchSettings settings) { + super(input, settings); + + if (masks == null) { + masks = new byte[bytes.length]; + Arrays.fill(masks, (byte) 0xff); + } + + if (bytes.length != masks.length) { + throw new IllegalArgumentException("Search bytes and mask bytes must be same length!"); + } + + this.searchBytes = bytes; + this.masks = masks; + } + + @Override + public Iterable match(ExtendedByteSequence byteSequence) { + return new MatchIterator(byteSequence); + } + + @Override + public String getDescription() { + return getByteString(searchBytes); + } + + @Override + public String getToolTip() { + return "Mask = " + getByteString(masks); + } + + private String getByteString(byte[] bytes) { + StringBuilder buf = new StringBuilder(); + for (byte b : bytes) { + String hexString = Integer.toHexString(b & 0xff); + if (hexString.length() == 1) { + buf.append("0"); + } + buf.append(hexString); + buf.append(" "); + } + return buf.toString().trim(); + } + +//================================================================================================== +// Methods to facilitate testing +//================================================================================================== + public byte[] getBytes() { + return searchBytes; + } + + public byte[] getMask() { + return masks; + } + +//================================================================================================== +// Inner classes +//================================================================================================== + private class MatchIterator implements Iterator, Iterable { + + private ByteSequence byteSequence; + private int startIndex = 0; + private ByteMatch nextMatch; + + public MatchIterator(ByteSequence byteSequence) { + this.byteSequence = byteSequence; + nextMatch = findNextMatch(); + } + + @Override + public Iterator iterator() { + return this; + } + + @Override + public boolean hasNext() { + return nextMatch != null; + } + + @Override + public ByteMatch next() { + if (nextMatch == null) { + return null; + } + ByteMatch returnValue = nextMatch; + nextMatch = findNextMatch(); + return returnValue; + } + + private ByteMatch findNextMatch() { + int nextPossibleStart = findNextPossibleStart(startIndex); + while (nextPossibleStart >= 0) { + startIndex = nextPossibleStart + 1; + if (isValidMatch(nextPossibleStart)) { + return new ByteMatch(nextPossibleStart, searchBytes.length); + } + nextPossibleStart = findNextPossibleStart(startIndex); + } + return null; + } + + private boolean isValidMatch(int possibleStart) { + if (!byteSequence.hasAvailableBytes(possibleStart, searchBytes.length)) { + return false; + } + // we know 1st byte matches, check others + for (int i = 1; i < searchBytes.length; i++) { + if (searchBytes[i] != (byteSequence.getByte(possibleStart + i) & masks[i])) { + return false; + } + } + return true; + } + + private int findNextPossibleStart(int start) { + for (int i = start; i < byteSequence.getLength(); i++) { + if (searchBytes[0] == (byteSequence.getByte(i) & masks[0])) { + return i; + } + } + return -1; + } + + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java new file mode 100644 index 0000000000..99f850e03f --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java @@ -0,0 +1,140 @@ +/* ### + * 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.features.base.memsearch.matcher; + +import java.util.Iterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.help.UnsupportedOperationException; + +import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence; +import ghidra.features.base.memsearch.gui.SearchSettings; + +/** + * {@link ByteMatcher} where the user search input has been parsed as a regular expression. + */ +public class RegExByteMatcher extends ByteMatcher { + + private final Pattern pattern; + + public RegExByteMatcher(String input, SearchSettings settings) { + super(input, settings); + // without DOTALL mode, bytes that match line terminator characters will cause + // the regular expression pattern to not match. + this.pattern = Pattern.compile(input, Pattern.DOTALL); + } + + @Override + public Iterable match(ExtendedByteSequence byteSequence) { + return new PatternMatchIterator(byteSequence); + } + + @Override + public String getDescription() { + return "Reg Ex"; + } + + @Override + public String getToolTip() { + return null; + } + +//================================================================================================== +// Inner classes +//================================================================================================== + + /** + * Class for converting byte sequences into a {@link CharSequence} that can be used by + * the java regular expression engine + */ + private class ByteCharSequence implements CharSequence { + + private ExtendedByteSequence byteSequence; + + ByteCharSequence(ExtendedByteSequence byteSequence) { + this.byteSequence = byteSequence; + } + + @Override + public int length() { + return byteSequence.getExtendedLength(); + } + + @Override + public char charAt(int index) { + byte b = byteSequence.getByte(index); + return (char) (b & 0xff); + } + + @Override + public CharSequence subSequence(int start, int end) { + throw new UnsupportedOperationException(); + } + + } + + /** + * Adapter class for converting java {@link Pattern} matching into an iterator of + * {@link ByteMatch}s. + */ + private class PatternMatchIterator implements Iterable, Iterator { + + private Matcher matcher; + private ByteMatch nextMatch; + private ExtendedByteSequence byteSequence; + + public PatternMatchIterator(ExtendedByteSequence byteSequence) { + this.byteSequence = byteSequence; + matcher = pattern.matcher(new ByteCharSequence(byteSequence)); + nextMatch = findNextMatch(); + } + + @Override + public boolean hasNext() { + return nextMatch != null; + } + + @Override + public ByteMatch next() { + if (nextMatch == null) { + return null; + } + ByteMatch returnValue = nextMatch; + nextMatch = findNextMatch(); + return returnValue; + + } + + @Override + public Iterator iterator() { + return this; + } + + private ByteMatch findNextMatch() { + if (!matcher.find()) { + return null; + } + int start = matcher.start(); + int end = matcher.end(); + if (start >= byteSequence.getLength()) { + return null; + } + return new ByteMatch(start, end - start); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/MaskGenerator.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/MaskGenerator.java similarity index 99% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/MaskGenerator.java rename to Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/MaskGenerator.java index 780a3801d7..334b765eb4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/MaskGenerator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/MaskGenerator.java @@ -4,16 +4,16 @@ * 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.searchmem.mask; +package ghidra.features.base.memsearch.mnemonic; import java.util.*; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/MaskValue.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/MaskValue.java similarity index 97% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/MaskValue.java rename to Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/MaskValue.java index 570593cd53..f92693a349 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/MaskValue.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/MaskValue.java @@ -4,16 +4,16 @@ * 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.searchmem.mask; +package ghidra.features.base.memsearch.mnemonic; import java.util.Arrays; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/MnemonicSearchPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/MnemonicSearchPlugin.java similarity index 95% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/MnemonicSearchPlugin.java rename to Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/MnemonicSearchPlugin.java index 485fcef049..9bbe78bdc7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/MnemonicSearchPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/MnemonicSearchPlugin.java @@ -4,16 +4,16 @@ * 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.searchmem.mask; +package ghidra.features.base.memsearch.mnemonic; import docking.action.MenuData; import ghidra.app.CorePluginPackage; @@ -23,6 +23,8 @@ import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.services.MemorySearchService; import ghidra.app.util.HelpTopics; +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.features.base.memsearch.gui.SearchSettings; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.listing.Program; @@ -110,13 +112,13 @@ public class MnemonicSearchPlugin extends Plugin { // dialog with the proper search string. if (mask != null) { maskedBitString = createMaskedBitString(mask.getValue(), mask.getMask()); - byte[] maskedBytes = maskedBitString.getBytes(); MemorySearchService memorySearchService = tool.getService(MemorySearchService.class); - memorySearchService.setIsMnemonic(true); - memorySearchService.search(maskedBytes, newContext); - memorySearchService.setSearchText(maskedBitString); + SearchSettings settings = new SearchSettings().withSearchFormat(SearchFormat.BINARY); + memorySearchService.createMemorySearchProvider(context.getNavigatable(), + maskedBitString, settings, false); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/SLMaskControl.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/SLMaskControl.java similarity index 93% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/SLMaskControl.java rename to Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/SLMaskControl.java index 73870f9662..15c156c479 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/mask/SLMaskControl.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/mnemonic/SLMaskControl.java @@ -1,20 +1,19 @@ /* ### * 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. * 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.searchmem.mask; +package ghidra.features.base.memsearch.mnemonic; /** * Represents a filter for a single instruction. This defines what portions of the instruction will diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/scan/Scanner.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/scan/Scanner.java new file mode 100644 index 0000000000..fdb823fcaa --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/scan/Scanner.java @@ -0,0 +1,71 @@ +/* ### + * 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.features.base.memsearch.scan; + +import java.util.function.Predicate; + +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.features.base.memsearch.searcher.MemoryMatch; + +/** + * Scan algorithms that examine the byte values of existing search results and look for changes. + * The specific scanner algorithm determines which results to keep and which to discard. + */ +public enum Scanner { + // keep unchanged results + EQUALS("Equals", mm -> compareBytes(mm) == 0, "Keep results whose values didn't change"), + // keep changed results + NOT_EQUALS("Not Equals", mm -> compareBytes(mm) != 0, "Keep results whose values changed"), + // keep results whose values increased + INCREASED("Increased", mm -> compareBytes(mm) > 0, "Keep results whose values increased"), + // keep results whose values decreased + DECREASED("Decreased", mm -> compareBytes(mm) < 0, "Keep results whose values decreased"); + + private final String name; + private final Predicate acceptCondition; + private final String description; + + private Scanner(String name, Predicate condition, String description) { + this.name = name; + this.acceptCondition = condition; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public boolean accept(MemoryMatch match) { + return acceptCondition.test(match); + } + + private static int compareBytes(MemoryMatch match) { + byte[] bytes = match.getBytes(); + byte[] originalBytes = match.getPreviousBytes(); + + ByteMatcher matcher = match.getByteMatcher(); + SearchSettings settings = matcher.getSettings(); + SearchFormat searchFormat = settings.getSearchFormat(); + return searchFormat.compareValues(bytes, originalBytes, settings); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/AlignmentFilter.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/AlignmentFilter.java new file mode 100644 index 0000000000..cc0fa05804 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/AlignmentFilter.java @@ -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.features.base.memsearch.searcher; + +import java.util.function.Predicate; + +/** + * Search filter that can test a search result and determine if that result is at an address + * whose offset matches the given alignment (i.e. its offset is a multiple of the alignment value) + */ +public class AlignmentFilter implements Predicate { + + private int alignment; + + public AlignmentFilter(int alignment) { + this.alignment = alignment; + } + + @Override + public boolean test(MemoryMatch match) { + return match.getAddress().getOffset() % alignment == 0; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/CodeUnitFilter.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/CodeUnitFilter.java new file mode 100644 index 0000000000..62e5eb84cf --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/CodeUnitFilter.java @@ -0,0 +1,69 @@ +/* ### + * 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.features.base.memsearch.searcher; + +import java.util.function.Predicate; + +import ghidra.program.model.listing.*; + +/** + * Search filter that can test a search result and determine if that result starts at or inside + * a code unit that matches one of the selected types. + */ +public class CodeUnitFilter implements Predicate { + + private boolean includeInstructions; + private boolean includeUndefinedData; + private boolean includeDefinedData; + private boolean includeAll; + private Listing listing; + + /** + * Constructor + * @param program the program to get code units from for testing its type + * @param includeInstructions if true, accept matches that are in an instruction + * @param includeDefinedData if true, accept matches that are in defined data + * @param includeUndefinedData if true, accept matches that are in undefined data + */ + public CodeUnitFilter(Program program, boolean includeInstructions, boolean includeDefinedData, + boolean includeUndefinedData) { + this.listing = program.getListing(); + this.includeInstructions = includeInstructions; + this.includeDefinedData = includeDefinedData; + this.includeUndefinedData = includeUndefinedData; + this.includeAll = includeInstructions && includeDefinedData && includeUndefinedData; + } + + @Override + public boolean test(MemoryMatch match) { + if (includeAll) { + return true; + } + CodeUnit codeUnit = listing.getCodeUnitContaining(match.getAddress()); + if (codeUnit instanceof Instruction) { + return includeInstructions; + } + else if (codeUnit instanceof Data) { + Data data = (Data) codeUnit; + if (data.isDefined()) { + return includeDefinedData; + } + return includeUndefinedData; + } + return false; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemoryMatch.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemoryMatch.java new file mode 100644 index 0000000000..366ed32cda --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemoryMatch.java @@ -0,0 +1,114 @@ +/* ### + * 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.features.base.memsearch.searcher; + +import java.util.Arrays; +import java.util.Objects; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.program.model.address.Address; + +/** + * A class that represents a memory search hit at an address. Matches can also be updated with + * new byte values (from a scan or refresh action). The original bytes that matched the original + * search are maintained in addition to the "refreshed" bytes. + */ +public class MemoryMatch implements Comparable { + + private final Address address; + private byte[] bytes; + private byte[] previousBytes; + private final ByteMatcher matcher; + + public MemoryMatch(Address address, byte[] bytes, ByteMatcher matcher) { + if (bytes == null || bytes.length < 1) { + throw new IllegalArgumentException("Must provide at least 1 byte"); + } + this.address = Objects.requireNonNull(address); + this.bytes = bytes; + this.previousBytes = bytes; + this.matcher = matcher; + } + + public MemoryMatch(Address address) { + this.address = address; + this.matcher = null; + } + + public void updateBytes(byte[] newBytes) { + previousBytes = bytes; + if (!Arrays.equals(bytes, newBytes)) { + bytes = newBytes; + } + } + + public Address getAddress() { + return address; + } + + public int getLength() { + return bytes.length; + } + + public byte[] getBytes() { + return bytes; + } + + public byte[] getPreviousBytes() { + return previousBytes; + } + + public ByteMatcher getByteMatcher() { + return matcher; + } + + @Override + public int compareTo(MemoryMatch o) { + return address.compareTo(o.address); + } + + @Override + public int hashCode() { + return address.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + MemoryMatch other = (MemoryMatch) obj; + // just compare addresses. The bytes are mutable and we want matches to be equal even + // if the bytes are different + return Objects.equals(address, other.address); + } + + @Override + public String toString() { + return address.toString(); + } + + public boolean isChanged() { + return !bytes.equals(previousBytes); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java new file mode 100644 index 0000000000..3985d83687 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java @@ -0,0 +1,337 @@ +/* ### + * 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.features.base.memsearch.searcher; + +import java.util.function.Predicate; + +import ghidra.features.base.memsearch.bytesequence.*; +import ghidra.features.base.memsearch.bytesource.AddressableByteSource; +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch; +import ghidra.program.model.address.*; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.task.TaskMonitor; + +/** + * Class for searching bytes from a byteSource (memory) using a {@link ByteMatcher}. It handles + * breaking the search down into a series of searches, handling gaps in the address set and + * breaking large address ranges down into manageable sizes. + *

+ * It is created with a specific byte source, matcher, address set, and search limit. Clients can + * then either call the {@link #findAll(Accumulator, TaskMonitor)} method or use it to incrementally + * search using {@link #findNext(Address, TaskMonitor)}, + * {@link #findPrevious(Address, TaskMonitor)}, or {@link #findOnce(Address, boolean, TaskMonitor)}. + */ + +public class MemorySearcher { + private static final int DEFAULT_CHUNK_SIZE = 16 * 1024; + private static final int OVERLAP_SIZE = 100; + private final AddressableByteSequence bytes1; + private final AddressableByteSequence bytes2; + private final ByteMatcher matcher; + private final int chunkSize; + + private Predicate filter = r -> true; + private final int searchLimit; + private final AddressSetView searchSet; + + /** + * Constructor + * @param byteSource the source of the bytes to be searched + * @param matcher the matcher that can find matches in a byte sequence + * @param addresses the address in the byte source to search + * @param searchLimit the max number of hits before stopping + */ + public MemorySearcher(AddressableByteSource byteSource, ByteMatcher matcher, + AddressSet addresses, int searchLimit) { + this(byteSource, matcher, addresses, searchLimit, DEFAULT_CHUNK_SIZE); + } + + /** + * Constructor + * @param byteSource the source of the bytes to be searched + * @param matcher the matcher that can find matches in a byte sequence + * @param addresses the address in the byte source to search + * @param searchLimit the max number of hits before stopping + * @param chunkSize the maximum number of bytes to feed to the matcher at any one time. + */ + public MemorySearcher(AddressableByteSource byteSource, ByteMatcher matcher, + AddressSet addresses, int searchLimit, int chunkSize) { + this.matcher = matcher; + this.searchSet = addresses; + this.searchLimit = searchLimit; + this.chunkSize = chunkSize; + + bytes1 = new AddressableByteSequence(byteSource, chunkSize); + bytes2 = new AddressableByteSequence(byteSource, chunkSize); + } + + /** + * Sets any match filters. The filter can be used to exclude matches that don't meet some + * criteria that is not captured in the byte matcher such as alignment and code unit type. + * @param filter the predicate to use to filter search results + */ + public void setMatchFilter(Predicate filter) { + this.filter = filter; + } + + /** + * Searches all the addresses in this search's {@link AddressSetView} using the byte matcher to + * find matches. As each match is found (and passes any filters), the match is given to the + * accumulator. The search continues until either the entire address set has been search or + * the search limit has been reached. + * @param accumulator the accumulator for found matches + * @param monitor the task monitor + * @return true if the search completed searching through the entire address set. + */ + public boolean findAll(Accumulator accumulator, TaskMonitor monitor) { + monitor.initialize(searchSet.getNumAddresses(), "Searching..."); + + for (AddressRange range : searchSet.getAddressRanges()) { + if (!findAll(accumulator, range, monitor)) { + return false; + } + } + return true; + } + + /** + * Searches forwards or backwards starting at the given address until a match is found or + * the start or end of the address set is reached. It does not currently wrap the search. + * @param start the address to start searching + * @param forward if true, search forward, otherwise, search backwards. + * @param monitor the task monitor + * @return the first match found or null if no match found. + */ + public MemoryMatch findOnce(Address start, boolean forward, TaskMonitor monitor) { + if (forward) { + return findNext(start, monitor); + } + return findPrevious(start, monitor); + } + + /** + * Searches forwards starting at the given address until a match is found or + * the end of the address set is reached. It does not currently wrap the search. + * @param start the address to start searching + * @param monitor the task monitor + * @return the first match found or null if no match found. + */ + public MemoryMatch findNext(Address start, TaskMonitor monitor) { + + long numAddresses = searchSet.getNumAddresses() - searchSet.getAddressCountBefore(start); + monitor.initialize(numAddresses, "Searching...."); + + for (AddressRange range : searchSet.getAddressRanges(start, true)) { + range = range.intersectRange(start, range.getMaxAddress()); + MemoryMatch match = findFirst(range, monitor); + if (match != null) { + return match; + } + if (monitor.isCancelled()) { + break; + } + } + return null; + } + + /** + * Searches backwards starting at the given address until a match is found or + * the beginning of the address set is reached. It does not currently wrap the search. + * @param start the address to start searching + * @param monitor the task monitor + * @return the first match found or null if no match found. + */ + public MemoryMatch findPrevious(Address start, TaskMonitor monitor) { + + monitor.initialize(searchSet.getAddressCountBefore(start) + 1, "Searching...."); + + for (AddressRange range : searchSet.getAddressRanges(start, false)) { + MemoryMatch match = findLast(range, start, monitor); + if (match != null) { + return match; + } + if (monitor.isCancelled()) { + break; + } + } + return null; + } + + private MemoryMatch findFirst(AddressRange range, TaskMonitor monitor) { + AddressableByteSequence searchBytes = bytes1; + AddressableByteSequence extra = bytes2; + + AddressRangeIterator it = new AddressRangeSplitter(range, chunkSize, true); + AddressRange first = it.next(); + + searchBytes.setRange(first); + while (it.hasNext()) { + AddressRange next = it.next(); + extra.setRange(next); + + MemoryMatch match = findFirst(searchBytes, extra, monitor); + if (match != null) { + return match; + } + if (monitor.isCancelled()) { + break; + } + + // Flip flop the byte buffers, making the extended buffer become primary and preparing + // the primary buffer to be used to read the next chunk. See the + // ExtendedByteSequence class for an explanation of this approach. + searchBytes = extra; + extra = searchBytes == bytes1 ? bytes2 : bytes1; + } + // last segment, no extra bytes to overlap, so just search the primary buffer + extra.clear(); + return findFirst(searchBytes, extra, monitor); + } + + private MemoryMatch findLast(AddressRange range, Address start, TaskMonitor monitor) { + AddressableByteSequence searchBytes = bytes1; + AddressableByteSequence extra = bytes2; + extra.clear(); + + if (range.contains(start)) { + Address min = range.getMinAddress(); + Address max = range.getMaxAddress(); + range = new AddressRangeImpl(min, start); + AddressRange remaining = new AddressRangeImpl(start.next(), max); + AddressRange extraRange = new AddressRangeSplitter(remaining, chunkSize, true).next(); + extra.setRange(extraRange); + } + + AddressRangeIterator it = new AddressRangeSplitter(range, chunkSize, false); + + while (it.hasNext()) { + AddressRange next = it.next(); + searchBytes.setRange(next); + MemoryMatch match = findLast(searchBytes, extra, monitor); + if (match != null) { + return match; + } + if (monitor.isCancelled()) { + break; + } + + // Flip flop the byte buffers, making the primary buffer the new extended buffer + // and refilling the primary buffer with new data going backwards. + extra = searchBytes; + searchBytes = extra == bytes1 ? bytes2 : bytes1; + } + return null; + } + + private MemoryMatch findFirst(AddressableByteSequence searchBytes, ByteSequence extra, + TaskMonitor monitor) { + + ExtendedByteSequence searchSequence = + new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE); + + for (ByteMatch byteMatch : matcher.match(searchSequence)) { + Address address = searchBytes.getAddress(byteMatch.start()); + byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length()); + MemoryMatch match = new MemoryMatch(address, bytes, matcher); + if (filter.test(match)) { + return match; + } + if (monitor.isCancelled()) { + break; + } + } + monitor.incrementProgress(searchBytes.getLength()); + return null; + } + + private MemoryMatch findLast(AddressableByteSequence searchBytes, ByteSequence extra, + TaskMonitor monitor) { + + MemoryMatch last = null; + + ExtendedByteSequence searchSequence = + new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE); + + for (ByteMatch byteMatch : matcher.match(searchSequence)) { + Address address = searchBytes.getAddress(byteMatch.start()); + byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length()); + MemoryMatch match = new MemoryMatch(address, bytes, matcher); + if (filter.test(match)) { + last = match; + } + if (monitor.isCancelled()) { + return null; + } + } + monitor.incrementProgress(searchBytes.getLength()); + return last; + } + + private boolean findAll(Accumulator accumulator, AddressRange range, + TaskMonitor monitor) { + AddressableByteSequence searchBytes = bytes1; + AddressableByteSequence extra = bytes2; + + AddressRangeIterator it = new AddressRangeSplitter(range, chunkSize, true); + AddressRange first = it.next(); + + searchBytes.setRange(first); + while (it.hasNext()) { + AddressRange next = it.next(); + extra.setRange(next); + if (!findAll(accumulator, searchBytes, extra, monitor)) { + return false; + } + searchBytes = extra; + extra = searchBytes == bytes1 ? bytes2 : bytes1; + } + extra.clear(); + return findAll(accumulator, searchBytes, extra, monitor); + } + + private boolean findAll(Accumulator accumulator, + AddressableByteSequence searchBytes, ByteSequence extra, TaskMonitor monitor) { + + if (monitor.isCancelled()) { + return false; + } + + ExtendedByteSequence searchSequence = + new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE); + + for (ByteMatch byteMatch : matcher.match(searchSequence)) { + Address address = searchBytes.getAddress(byteMatch.start()); + byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length()); + MemoryMatch match = new MemoryMatch(address, bytes, matcher); + if (filter.test(match)) { + if (accumulator.size() >= searchLimit) { + return false; + } + accumulator.add(match); + } + if (monitor.isCancelled()) { + return false; + } + + } + // Reset the monitor message, since clients may change the message (such as the + // incremental table loader) + monitor.setMessage("Searching..."); + monitor.incrementProgress(searchBytes.getLength()); + return true; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/search/memory/MemSearchResult.java b/Ghidra/Features/Base/src/main/java/ghidra/util/search/memory/MemSearchResult.java index fafd536d2f..2294301292 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/search/memory/MemSearchResult.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/search/memory/MemSearchResult.java @@ -27,6 +27,7 @@ public class MemSearchResult implements Comparable { private Address address; private int length; + private byte[] bytes; public MemSearchResult(Address address, int length) { this.address = Objects.requireNonNull(address); @@ -37,6 +38,15 @@ public class MemSearchResult implements Comparable { this.length = length; } + public MemSearchResult(Address address, byte[] bytes) { + if (bytes == null || bytes.length < 1) { + throw new IllegalArgumentException("Must provide at least 1 byte"); + } + this.address = Objects.requireNonNull(address); + this.bytes = bytes; + this.length = bytes.length; + } + public Address getAddress() { return address; } @@ -45,6 +55,10 @@ public class MemSearchResult implements Comparable { return length; } + public byte[] getBytes() { + return bytes; + } + @Override public int compareTo(MemSearchResult o) { return address.compareTo(o.address); diff --git a/Ghidra/Features/Base/src/main/resources/images/view_bottom.png b/Ghidra/Features/Base/src/main/resources/images/view_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..e883ed1206a63cb33eec10a9588edc776a859e12 GIT binary patch literal 946 zcmV;j15NyiP)+?a4EUhF?Ghzy3h!-$-mEaYjxC2Ieyt7+zhvs0I)~EN?#i`El{YTeiR7 ze={&KF);iG+VY83=-m23@;uW zXV?k!0zd$<{QSqrV5BX^V5iQ3)&GCKd}Mg@a|HwAzo!hpSs8(r3Ne_7FK3XoKFV-$ zzuRws00IZ%ccA})?g6^|9~l1w*$hlo{}`DVzJ2_~@cg|b1J9464Ez#57+AQzFnsy= ziec_SFNT2cR|Ek9h~>|JCI)6kNQePl1jPR#X_t+e;oJL<3}24VWDwy2h6x)NgP5lQ zkbchaV$&Cflz@W_%1U<<0Ro7HnVp^G-s}Gi7w>-uCRJek0t4|sFo^#F4gU2Lm}p)} zG5mY+kikm!2QVFf1zPikf$`fPhVxfc7`{II{uUsBSbqHc@qhM#_Y9M^{es3dBs2Z} z%gFGTfra76Z&!wR(H#s{g6|kE&Ogb(!S$1YON5Ex#cxT56-O230t67t!<+YW9^HPV z2#o4K4Cv|UKihA<&(HWA-S)(9n)dgis49~or-(4;)kW_P6`Ra8d;iboB0vDK0Igc^ z9~j4A%^=K#5(f+%|GqK2Ja}=*lWUBxwAnbP{(H|a^7NEY($5bcCAb-W-2@0Q03n-z UhRYY+&j0`b07*qoM6N<$g6QnE+5i9m literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/src/main/resources/images/view_left_right.png b/Ghidra/Features/Base/src/main/resources/images/view_left_right.png new file mode 100644 index 0000000000000000000000000000000000000000..a5748383f3d984d98d9ee70242b6ee9bf15ef527 GIT binary patch literal 822 zcmV-61Ihe}P)Dbf*ROy70|XGu$IpKm;C; z;QAO;Zp`u!KI8EgR11^O0*L7^P!T)<02z#ou($>iz(4|d;Xlw7|Np_l0ciW5 zKj44`B{+Zp9FxHg03ZlLNp02o|GyQBIyv7s$b_I?fd{}JkdVKJhNT8Eo!C;rb^b~9 zKzlE&R?-`;0Ak_f;bhu>pNZl2oA?HP_%(l z6$2z-z(EAe6ikc^-(Efg2q4D)|8d13aR4B|0Czrax+2O1p#T5?07*qoM6N<$f_15H Awg3PC literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/src/main/resources/images/view_top_bottom.png b/Ghidra/Features/Base/src/main/resources/images/view_top_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..cdd2d2c967a65b9f39af968afd27b431eb4745a2 GIT binary patch literal 767 zcmVS#@)vN0mNeCVJPEJ z6w31O8AQV`82*F6KcV>dABfzK-wX_F+zbqiD>sS&1Q5%+Pyhby-}9E?)B7I`%*+f7 z|A0392igEc5DX;#{YQwg00~|o{gs)6;p^AG{{aGs<>TkS4DnhF3=Uet42%p=0B3-h zj8LNxQjCoM8Ma^k&5*wN9|J%DvHWIaV&LRpWf0`!V893d1D*B{=#+mznw9N813NpA z57Y|~KrDa%Gl2sJ8vtqe4|MxqpaKy7_YZ zFHrH{zd#yj0FY*5_{DJUtpLy<76yO-VqsurWqA1fGsBmcpMeenYWfS)_!q3{Kal?S z8^rztbT*I%I-T|JH--lc(m)q5F#rS*gJ$Tak2@}W`A-=91ESymfB*UkWPks^>HN3< z98Nnv0t65^48DB-1M%Yj|L~Ch`xg{=f1v^Rm*Ef4=0ATRG&k#Ch7a$7K?w{DfB*s- zz>E^_AVVPdA0+Dj{)Za^!~Yn8-uVX%SfJ$q0XQauBmiIt1}eGq|KCNsT%06hY@$Jd zEz;V6D0;tb&axxa;7U5dd`SrO=dU*I{Lk_E+aI9I8NoU29~6TP0QwIY x5lEQ>l)^xn0+>j?y?h4{K#U;&QUU-13;^jivpYYu_xb<;002ovPDHLkV1m$HRlooM literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/src/main/resources/images/viewmag+.png b/Ghidra/Features/Base/src/main/resources/images/viewmag+.png new file mode 100644 index 0000000000000000000000000000000000000000..902b292a0a6a9c851b8aa60ca9fd55eea787d765 GIT binary patch literal 1127 zcmV-t1ep7YP)|$iZ zGyougSbqLx^nN3t+bE>3!>BCxkAa&5sQCv2!}|}v8QA!^8Q3MwWnO>%IPJsbi+2B+ z7@sjPpc(=YKun*Qc=Om*)EO0||1+qGGco81Ffs5jGcd?Y1HHlfpFvcDnL*M@NBK7s zli&BR-xz-U_=$o60*K`so1n3{7&ikK$3F%kpcC1D{byihWn>WMVr2Ni!^ptK_@9AK zLz>|nD~tQb51(f+v$3JL03d)^{;=_~bFuwn`0(*B!?_>-7`V8Y7^L`^7=RQ57f=D) ze+C8?W+sMTKYsFj{Pcx^l^xXpfB<6o{r>q6MpkCdzrX%6`~&LZ;sm+^=mjRAMq!Wv zK>X|PKZY+KK7RQ0`6~m<&p!-|Kv%&4Kmakly>;p6w|AcyelsvL{QS+p@DCVOKYu`Vp*WKt{y(e@aW#t&EJ8V|9t~ z+2F1n9UjUV5&>g43&K(>2;BhG*6*{~?FL@#^#Abf^Z)GHrNB{=y+b8Ab^)2f31G@SA z0QLd^K>)u00|Q+En+5*@NNEp38Ohk#66hJ=GwToAzytCG_zdwM03Z?X01WHM+S=Rh z3l}0CI4M9l01WWjCg0)pDC+eCyaE700KWeN8CMOmX&OQmAPy7&D;gUS=I`z=?CtlN z@EGRs1^Nd7Cj=J&3-#Cg=Ox|002ovPDHLkV1lJ79X|j7 literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/src/main/resources/images/viewmag.png b/Ghidra/Features/Base/src/main/resources/images/viewmag.png new file mode 100644 index 0000000000000000000000000000000000000000..6dd1931589b453f6cc9772fd1851d8705d6b6631 GIT binary patch literal 813 zcmV+|1JeA7P)lXvaa)1B=8vtU6i;3jw7+Lm8D5$Z@i10EnF)=bcefw4K z$^E-CUfjQG`svfBbfD6|R#sLFj6lo3FG$G6@;ee>>~6i|H^5OY8c00W#fi<*1vzh{0&qu3dB`V0{{XDY`}(1n>S9M(9!zw#!&`A z#;*+0{6GVMSeWGp!`o{o8NPge#o%jgs54WT$qnQYpsyGNpauX05F;oJfZ`Pdw6!$j zynTH0RMa&!n3$Pa9zVGEY{&L(3qnPBl_DG6<9Xk0`Fl3`LiD4@pH{vDsxJWQ1qdJp zEJ>M}osCtQlY?CgW>F{;gVK#SmgoOZnEn5J%EtIXaWQXAO>Rdf)WFR;gt~f zXaA3z{{L{q=l48y!32 valueComboBox; - protected Container pane; - protected JLabel hexLabel; - protected Memory memory; - protected Listing listing; - protected TableServicePlugin tableServicePlugin; - protected MarkerService markerService; - - protected MemSearchDialog dialog; - - /* - * Note that this setup function does not have the @Before annotation - this is because - * sub-classes often need to override this and if we have the annotation here, the test - * runner will only invoke this base class implementation. - */ - public void setUp() throws Exception { - - // this builds the program and launches the tool - initialize(); - - memSearchPlugin = env.getPlugin(MemSearchPlugin.class); - - listing = program.getListing(); - memory = program.getMemory(); - - searchAction = getAction(memSearchPlugin, "Search Memory"); - - cb = codeBrowser; // TODO delete after 7.3 release; just use the parent's CodeBrowser - - provider = cb.getProvider(); - markerService = tool.getService(MarkerService.class); - - tableServicePlugin = env.getPlugin(TableServicePlugin.class); - - showMemSearchDialog(); - setToggleButtonSelected(pane, MemSearchDialog.ADVANCED_BUTTON_NAME, true); - selectRadioButton("Binary"); - } - - @Override - protected Program getProgram() throws Exception { - return buildProgram(); - } - - protected abstract Program buildProgram() throws Exception; - - protected void waitForSearch(String panelName, int expectedResults) { - - waitForCondition(() -> { - return !memSearchPlugin.isSearching(); - }, "Timed-out waiting for search results"); - - Window window = AbstractDockingTest.waitForWindowByTitleContaining(panelName); - GhidraTable gTable = findComponent(window, GhidraTable.class, true); - waitForSwing(); - assertEquals(expectedResults, gTable.getRowCount()); - } - - protected void waitForSearchTask() { - waitForSwing(); - Thread t = dialog.getTaskScheduler().getCurrentThread(); - if (t == null) { - return; - } - - try { - t.join(); - } - catch (InterruptedException e) { - Msg.debug(this, "Interrupted waiting for the search task thread to finish"); - } - waitForSwing(); - } - - protected void showMemSearchDialog() { - performAction(searchAction, provider, true); - // dig up the components of the dialog - dialog = waitForDialogComponent(MemSearchDialog.class); - pane = dialog.getComponent(); - - statusLabel = (JLabel) findComponentByName(pane, "statusLabel"); - valueComboBox = findComponent(pane, JComboBox.class); - valueField = (JTextField) valueComboBox.getEditor().getEditorComponent(); - hexLabel = (JLabel) findComponentByName(pane, "HexSequenceField"); - } - - protected void selectRadioButton(String text) { - setToggleButtonSelected(pane, text, true); - } - - protected void selectCheckBox(String text, boolean state) { - setToggleButtonSelected(pane, text, state); - } - - @SuppressWarnings("unchecked") - private List

getHighlightAddresses() { - CodeViewerService service = tool.getService(CodeViewerService.class); - Object codeViewerProvider = getInstanceField("connectedProvider", service); - Map highlighterMap = - (Map) getInstanceField("programHighlighterMap", - codeViewerProvider); - ListingHighlightProvider highlightProvider = highlighterMap.get(program); - - assertEquals("The inner-class has been renamed", "SearchTableHighlightHandler", - highlightProvider.getClass().getSimpleName()); - - MemSearchTableModel model = - (MemSearchTableModel) getInstanceField("model", highlightProvider); - List data = model.getModelData(); - return data.stream().map(result -> result.getAddress()).collect(Collectors.toList()); - } - - protected void checkMarkerSet(List
expected) { - - TableComponentProvider[] providers = tableServicePlugin.getManagedComponents(); - TableComponentProvider tableProvider = providers[0]; - assertTrue(tool.isVisible(tableProvider)); - - List
highlights = getHighlightAddresses(); - assertListEqualUnordered("Search highlights not correctly generated", expected, highlights); - - MarkerSet markers = - runSwing(() -> markerService.getMarkerSet(tableProvider.getName(), program)); - assertNotNull(markers); - - AddressSet addressSet = runSwing(() -> markers.getAddressSet()); - AddressIterator it = addressSet.getAddresses(true); - List
list = IteratorUtils.toList(it); - - assertListEqualUnordered("Search markers not correctly generated", expected, list); - } - - protected void pressSearchAllButton() { - runSwing(() -> invokeInstanceMethod("allCallback", dialog)); - } - - protected void pressSearchButton(String text) throws Exception { - pressButtonByText(pane, text); - waitForSearchTask(); - } - - protected void performSearchTest(List
expected, String buttonText) throws Exception { - - for (Address addr : expected) { - pressSearchButton(buttonText); - assertEquals("Found", getStatusText()); - cb.updateNow(); - assertEquals(addr, cb.getCurrentLocation().getAddress()); - } - - pressSearchButton(buttonText); - assertEquals("Not Found", getStatusText()); - } - - protected String getStatusText() { - AtomicReference ref = new AtomicReference<>(); - runSwing(() -> ref.set(statusLabel.getText())); - return ref.get(); - } - - protected void setValueText(String s) { - setText(valueField, s); - } - - protected void myTypeText(String text) { - // Note: we do not use setFocusedComponent(valueField), as that method will fail if the - // focus change doesn't work. Here, we will keep on going if the focus change - // doesn't work. - runSwing(() -> valueField.requestFocus()); - triggerText(valueField, text); - } - - protected ListingHighlightProvider getHighlightProvider() { - CodeViewerService service = tool.getService(CodeViewerService.class); - FormatManager fm = (FormatManager) getInstanceField("formatMgr", service); - return (ListingHighlightProvider) getInstanceField("highlightProvider", fm); - } - - protected void repeatSearch() { - DockingActionIf action = getAction(memSearchPlugin, "Repeat Memory Search"); - assertTrue(action.isEnabled()); - performAction(action, provider, true); - waitForSearchTask(); - } - - protected Address currentAddress() { - cb.updateNow(); - Address addr = cb.getCurrentLocation().getAddress(); - return addr; - } - - protected CodeUnit currentCodeUnit() { - CodeUnit cu = program.getListing().getCodeUnitContaining(currentAddress()); - return cu; - } - - protected CodeUnit codeUnitContaining(Address addr) { - CodeUnit cu = program.getListing().getCodeUnitContaining(addr); - return cu; - } - - protected void assertSearchSelectionSelected() { - - AbstractButton b = findAbstractButtonByText(pane, "Search Selection"); - assertTrue(isEnabled(b)); - assertTrue(isSelected(b)); - } - - protected void assertButtonState(String text, boolean isEnabled, boolean isSelected) { - - AbstractButton b = findAbstractButtonByText(pane, text); - assertEquals(isEnabled, isEnabled(b)); - assertEquals(isSelected, isSelected(b)); - } - - protected void assertEnabled(String text, boolean isEnabled) { - // Note: we do not use the findAbstractButtonByText() here as there are two buttons with - // the same text. Only one of the buttons is actually a JButton, so this call works. - // Ideally, all buttons would have a name set so that wouldn't have to rely on the - // button text. - JButton b = findButtonByText(pane, text); - assertEquals(isEnabled, isEnabled(b)); - } - - protected void setAlignment(String alignment) { - JTextField alignmentField = - (JTextField) findComponentByName(dialog.getComponent(), "Alignment"); - setText(alignmentField, alignment); - } - - protected Highlight[] getByteHighlights(Address address, String bytes) { - goTo(address); - CodeUnit cu = codeUnitContaining(address); - ListingHighlightProvider hlProvider = getHighlightProvider(); - ListingField field = getField(address, BytesFieldFactory.FIELD_NAME); - return hlProvider.createHighlights(bytes, field, -1); - } - - protected void setEndianness(String text) { - // we use this method because the given button may be disabled, which means we cannot - // click it, but we can select it - AbstractButton button = findAbstractButtonByText(pane, text); - runSwing(() -> button.setSelected(true)); - } - -} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchAsciiTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchAsciiTest.java deleted file mode 100644 index ef2300365a..0000000000 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchAsciiTest.java +++ /dev/null @@ -1,433 +0,0 @@ -/* ### - * 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.searchmem; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.List; - -import javax.swing.JComboBox; - -import org.junit.Before; -import org.junit.Test; - -import ghidra.program.database.ProgramBuilder; -import ghidra.program.model.address.Address; -import ghidra.program.model.listing.Program; -import ghidra.test.ToyProgramBuilder; - -/** - * Tests for searching memory for ascii. - */ -public class MemSearchAsciiTest extends AbstractMemSearchTest { - - @Override - @Before - public void setUp() throws Exception { - super.setUp(); - selectRadioButton("String"); - } - - @Override - protected Program buildProgram() throws Exception { - ToyProgramBuilder builder = new ToyProgramBuilder("Test", false, ProgramBuilder._TOY); - - builder.createMemory(".text", "0x1001000", 0x6600); - builder.createMemory(".data", "0x1008000", 0x600); - builder.createMemory(".rsrc", "0x100a000", 0x5400); - //create some strings - builder.createEncodedString("0x010016ec", "something", StandardCharsets.UTF_16LE, true); - builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16LE, true); - builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16LE, - true); - builder.createEncodedString("0x01001840", "\u039d\u03bf\u03c4\u03b5\u03c0\u03b1\u03bd", - StandardCharsets.UTF_16LE, true); - builder.createEncodedString("0x0100186a", - "\u03c1\u03b8\u03c4\u03b5\u03c0\u03b1\u03bd\u03c2\u03b2", StandardCharsets.UTF_16LE, - true); - builder.createEncodedString("0x0100196a", - "\u03c1\u03b8\u03c4\u03b5\u03c0\u03b1\u03bd\u03c2\u03b2", StandardCharsets.UTF_8, true); - builder.createEncodedString("0x0100189d", "\"Hello world!\"\n\t-new programmer", - StandardCharsets.US_ASCII, true); - builder.createEncodedString("0x0100198e", "\"Hello world!\"\n\t-new programmer", - StandardCharsets.UTF_16LE, true); - builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, false); - builder.createEncodedString("0x010013e0", "notepad.exe", StandardCharsets.US_ASCII, false); - builder.createEncodedString("0x1006c6a", "GetLocaleInfoW", StandardCharsets.US_ASCII, - false); - builder.createEncodedString("0x1006f26", "GetCPInfo", StandardCharsets.US_ASCII, false); - builder.createEncodedString("0x0100dde0", "NOTEPAD.EXE", StandardCharsets.UTF_16LE, true); - builder.createEncodedString("0x0100eb90", - "This string contains notepad twice. Here is the second NotePad.", - StandardCharsets.UTF_16LE, true); - builder.createEncodedString("0x0100ed00", "Another string", StandardCharsets.UTF_16LE, - true); - - return builder.getProgram(); - } - - @Test - public void testStringFormatSelected() throws Exception { - // verify that String options are showing: case sensitive, unicode, - // regular expression check boxes. - assertButtonState("Case Sensitive", true, false); - - @SuppressWarnings("unchecked") - JComboBox comboBox = - (JComboBox) findComponentByName(pane, "Encoding Options"); - assertNotNull(comboBox); - } - - @Test - public void testCaseSensitiveOff() throws Exception { - - selectCheckBox("Case Sensitive", false); - - setValueText("notepad"); - - List
addrs = addrs(0x010013cc, 0x010013e0); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testCaseSensitiveOn() throws Exception { - - selectCheckBox("Case Sensitive", true); - - setValueText("NOTEpad"); - - pressSearchButton("Next"); - - assertEquals("Not Found", statusLabel.getText()); - } - - @Test - public void testUnicodeNotCaseSensitive() throws Exception { - - selectCheckBox("Case Sensitive", false); - - setEncoding(StandardCharsets.UTF_16); - setValueText("NOTEpad"); - - List
addrs = addrs(0x01001708, 0x0100dde0, 0x0100eb90, 0x0100eb90); // this code unit contains two notepads in one string - - performSearchTest(addrs, "Next"); - } - - @Test - public void testGreekUnicodeSearch() throws Exception { - selectCheckBox("Case Sensitive", false); - - setEncoding(StandardCharsets.UTF_16); - setValueText("\u03c4\u03b5\u03c0\u03b1\u03bd"); - - List
addrs = addrs(0x01001840, 0x0100186a); - - performSearchTest(addrs, "Next"); - - addrs.add(addr(0x0100196a)); - - setEncoding(StandardCharsets.UTF_8); - pressSearchButton("Next"); - assertEquals("Found", statusLabel.getText()); - assertEquals(addrs.get(2), cb.getCurrentLocation().getAddress()); - - pressSearchButton("Next"); - assertEquals("Not Found", statusLabel.getText()); - } - - @Test - public void testRepeatUnicodeNotCaseSensitive() throws Exception { - selectCheckBox("Case Sensitive", false); - - setEncoding(StandardCharsets.UTF_16); - setValueText("NOTEpad"); - - List
startList = addrs(0x01001708, 0x0100dde0, 0x0100eb90, 0x0100eb90); // this code unit contains two notepads in one string - - for (int i = 0; i < startList.size(); i++) { - Address start = startList.get(i); - if (i == 0) { - pressSearchButton("Next"); - } - else { - repeatSearch(); - } - assertEquals(start, cb.getCurrentLocation().getAddress()); - assertEquals("Found", statusLabel.getText()); - } - pressSearchButton("Next"); - assertEquals("Not Found", statusLabel.getText()); - } - - @Test - public void testUnicodeCaseSensitive() throws Exception { - selectCheckBox("Case Sensitive", true); - - setEncoding(StandardCharsets.UTF_16); - setValueText("Notepad"); - - performSearchTest(addrs(0x01001708), "Next"); - } - - @Test - public void testUnicodeBigEndian() throws Exception { - - // with Big Endian selected, unicode bytes should be reversed - setEndianness("Big Endian"); - - setEncoding(StandardCharsets.UTF_16); - setValueText("start"); - - assertEquals("00 73 00 74 00 61 00 72 00 74 ", hexLabel.getText()); - - selectRadioButton("Little Endian"); - assertEquals("73 00 74 00 61 00 72 00 74 00 ", hexLabel.getText()); - } - - @Test - public void testSearchAllUnicodeNotCaseSensitive() throws Exception { - // test for markers - - // QueryResults should get displayed - // test the marker stuff - selectCheckBox("Case Sensitive", false); - - setEncoding(StandardCharsets.UTF_16); - setValueText("NOTEpad"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 4); - - List
addrs = addrs(0x01001708, 0x0100dde0, 0x0100ebba, 0x0100ebfe); - - checkMarkerSet(addrs); - } - - @Test - public void testSearchAllUnicodeCaseSensitive() throws Exception { - // test for markers - - // QueryResults should get displayed - // test the marker stuff - selectCheckBox("Case Sensitive", true); - - setEncoding(StandardCharsets.UTF_16); - setValueText("Notepad"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 1); - - checkMarkerSet(addrs(0x01001708)); - } - - @Test - public void testSearchAllNotCaseSensitive() throws Exception { - // QueryResults should get displayed - // test the marker stuff - selectCheckBox("Case Sensitive", false); - - setValueText("NOTEpad"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 2); - - List
addrs = addrs(0x010013cc, 0x010013e0); - - checkMarkerSet(addrs); - } - - @Test - public void testSearchAllCaseSensitive() throws Exception { - // QueryResults should get displayed - // test the marker stuff - // create an set of ascii bytes to do this test - byte[] b = new byte[] { 'N', 'O', 'T', 'E', 'p', 'a', 'd' }; - - int transactionID = program.startTransaction("test"); - memory.setBytes(addr(0x0100b451), b); - program.endTransaction(transactionID, true); - - selectCheckBox("Case Sensitive", true); - - setValueText("NOTEpad"); - pressSearchAllButton(); - - waitForSearch("Search Memory - ", 1); - - checkMarkerSet(addrs(0x0100b451)); - } - - @Test - public void testSearchAllCaseSensitiveAlign8() throws Exception { - // QueryResults should get displayed - // test the marker stuff - // create an set of ascii bytes to do this test - - setAlignment("8"); - - selectCheckBox("Case Sensitive", true); - - setValueText("notepad"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 1); - - checkMarkerSet(addrs(0x010013e0)); - } - - @Test - public void testSearchSelection() throws Exception { - - makeSelection(tool, program, addr(0x01006c73), addr(0x01006f02)); - - assertSearchSelectionSelected(); - - selectCheckBox("Case Sensitive", false); - - setValueText("Info"); - - performSearchTest(addrs(0x01006c6a), "Next"); - } - - @Test - public void testSearchNonContiguousSelection() throws Exception { - - makeSelection(tool, program, range(0x01006c70, 0x01006c80), range(0x01006f2b, 0x01006f37)); - - assertSearchSelectionSelected(); - - selectCheckBox("Case Sensitive", false); - - setValueText("Info"); - - List
addrs = addrs(0x01006c6a, 0x01006f26); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testSearchBackward() throws Exception { - - goTo(tool, program, addr(0x1006f56)); - - selectCheckBox("Case Sensitive", true); - - setValueText("Info"); - - List
addrs = addrs(0x01006f26, 0x01006c6a); - - performSearchTest(addrs, "Previous"); - } - - @Test - public void testSearchBackwardInSelection() throws Exception { - - goTo(tool, program, addr(0x01006f02)); - - makeSelection(tool, program, addr(0x01006c73), addr(0x01006f02)); - - assertSearchSelectionSelected(); - - selectCheckBox("Case Sensitive", false); - - setValueText("Info"); - - List
addrs = addrs(0x01006c6a); - - performSearchTest(addrs, "Previous"); - } - - @Test - public void testSearchBackwardAlign4() throws Exception { - - goTo(tool, program, addr(0x1006f56)); - - selectCheckBox("Case Sensitive", true); - - setAlignment("8"); - - setValueText("notepad"); - - List
addrs = addrs(0x010013e0); - - performSearchTest(addrs, "Previous"); - } - - @Test - public void testSearchBackwardAlign4NoneFound() throws Exception { - - goTo(tool, program, addr(0x1006f56)); - - selectCheckBox("Case Sensitive", true); - - setAlignment("8"); - - setValueText("Info"); - - pressSearchButton("Previous"); - assertEquals("Not Found", statusLabel.getText()); - } - - @Test - public void testSearchEscapeSequences() throws Exception { - selectCheckBox("Case Sensitive", true); - selectCheckBox("Escape Sequences", true); - - setEncoding(StandardCharsets.US_ASCII); - setValueText("\"Hello world!\"\\n\\t-new programmer"); - - List
addrs = addrs(0x0100189d, 0x0100198e); - - pressSearchButton("Next"); - assertEquals(addrs.get(0), cb.getCurrentLocation().getAddress()); - assertEquals("Found", statusLabel.getText()); - - pressSearchButton("Next"); - assertEquals("Not Found", statusLabel.getText()); - - setEncoding(StandardCharsets.UTF_16LE); - pressSearchButton("Next"); - assertEquals("Found", statusLabel.getText()); - - pressSearchButton("Next"); - assertEquals("Not Found", statusLabel.getText()); - } - -//================================================================================================== -// Private Methods -//================================================================================================== - - @SuppressWarnings("unchecked") - private void setEncoding(Charset encoding) throws Exception { - JComboBox encodingOptions = - (JComboBox) findComponentByName(pane, "Encoding Options", false); - - // Makes encoding UTF_16 in case encoding is UTF_16BE or UTF_16LE - // BE and LE are not choices in the combo box. - if (encoding == StandardCharsets.UTF_16BE || encoding == StandardCharsets.UTF_16LE) { - encoding = StandardCharsets.UTF_16; - } - - for (int i = 0; i < encodingOptions.getItemCount(); i++) { - if (encodingOptions.getItemAt(i) == encoding) { - int index = i; - runSwing(() -> encodingOptions.setSelectedIndex(index)); - break; - } - } - } -} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchBinaryTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchBinaryTest.java deleted file mode 100644 index 34bfd496cb..0000000000 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchBinaryTest.java +++ /dev/null @@ -1,559 +0,0 @@ -/* ### - * 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.searchmem; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.nio.charset.StandardCharsets; -import java.util.List; - -import javax.swing.DefaultComboBoxModel; - -import org.junit.Before; -import org.junit.Test; - -import docking.widgets.fieldpanel.support.Highlight; -import ghidra.app.events.ProgramLocationPluginEvent; -import ghidra.program.database.ProgramBuilder; -import ghidra.program.model.address.Address; -import ghidra.program.model.data.Pointer32DataType; -import ghidra.program.model.listing.*; -import ghidra.program.util.ProgramLocation; - -/** - * Tests for the Binary format in searching memory. - */ -public class MemSearchBinaryTest extends AbstractMemSearchTest { - - public MemSearchBinaryTest() { - super(); - } - - @Override - @Before - public void setUp() throws Exception { - super.setUp(); - selectRadioButton("Binary"); - } - - @Override - protected Program buildProgram() throws Exception { - ProgramBuilder builder = new ProgramBuilder("TestX86", ProgramBuilder._X86); - builder.createMemory(".text", Long.toHexString(0x1001000), 0x6600); - builder.createMemory(".data", Long.toHexString(0x1008000), 0x600); - builder.createMemory(".rsrc", Long.toHexString(0x100A000), 0x5400); - builder.createMemory(".bound_import_table", Long.toHexString(0xF0000248), 0xA8); - builder.createMemory(".debug_data", Long.toHexString(0xF0001300), 0x1C); - - //create and disassemble a function - builder.setBytes( - "0x01002cf5", - "55 8b ec 83 7d 14 00 56 8b 35 e0 10 00 01 57 74 09 ff 75 14 ff d6 8b f8 eb 02 33 " + - "ff ff 75 10 ff d6 03 c7 8d 44 00 02 50 6a 40 ff 15 dc 10 00 01 8b f0 85 f6 74 27 " + - "56 ff 75 14 ff 75 10 e8 5c ff ff ff ff 75 18 ff 75 0c 56 ff 75 08 ff 15 04 12 00 " + - "01 56 8b f8 ff 15 c0 10 00 01 eb 14 ff 75 18 ff 75 0c ff 75 10 ff 75 08 ff 15 04 " + - "12 00 01 8b f8 8b c7 5f 5e 5d c2 14"); - builder.disassemble("0x01002cf5", 0x121, true); - builder.createFunction("0x01002cf5"); - - //create some data - - builder.setBytes("0x1001004", "85 4f dc 77"); - builder.applyDataType("0x1001004", new Pointer32DataType(), 1); - builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true); - builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE, true); - builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, true); - - //create some undefined data - builder.setBytes("0x1001500", "4e 00 65 00 77 00"); - builder.setBytes("0x1003000", "55 00"); - - return builder.getProgram(); - } - - @Test - public void testBinaryInvalidEntry() { - // enter a non-binary digit; the search field should not accept it - setValueText("2"); - - assertEquals("", valueField.getText()); - } - - @Test - public void testBinaryMoreThan8Chars() throws Exception { - // try entering more than 8 binary digits (no spaces); the dialog - // should not accept the 9th digit. - myTypeText("010101010"); - assertEquals("01010101", valueField.getText()); - } - - @Test - public void testBinaryEnterSpaces() { - // verify that more than 8 digits are allowed if spaces are entered - myTypeText("01110000 01110000"); - assertEquals("01110000 01110000", valueField.getText()); - } - - @Test - public void testBinaryPasteNumberWithPrefix() { - // paste a number with a binary prefix; - // the prefix should be removed before the insertion - setValueText("0b00101010"); - assertEquals("00101010", valueField.getText()); - - setValueText("0B1010 10"); - assertEquals("1010 10", valueField.getText()); - } - - @Test - public void testBinarySearch() throws Exception { - - goTo(0x01001000); - - setValueText("00010100 11111111"); - - pressButtonByText(pane, "Next"); - - waitForSearchTask(); - - Address currentAddress = currentAddress(); - CodeUnit cu = codeUnitContaining(addr(0x01002d08)); - assertEquals(cu.getMinAddress(), currentAddress); - assertEquals("Found", statusLabel.getText()); - } - - @Test - public void testBinarySearchNext() throws Exception { - - goTo(0x01001000); - - setValueText("01110101"); - - //@formatter:off - List
addrs = addrs(0x01002d06, - 0x01002d11, - 0x01002d2c, - 0x01002d2f, - 0x01002d37, - 0x01002d3a, - 0x01002d3e, - 0x01002d52, - 0x01002d55, - 0x01002d58, - 0x01002d5b); - //@formatter:on - - for (int i = 0; i < addrs.size(); i++) { - Address start = addrs.get(i); - pressSearchButton("Next"); - CodeUnit cu = listing.getCodeUnitContaining(start); - assertEquals(cu.getMinAddress(), cb.getCurrentLocation().getAddress()); - assertEquals("Found", statusLabel.getText()); - } - pressSearchButton("Next"); - assertEquals("Not Found", statusLabel.getText()); - - } - - @Test - public void testBinarySearchNextAlign4() throws Exception { - // hit the enter key in the values field; - // should go to next match found - - Address addr = addr(0x01001000); - tool.firePluginEvent(new ProgramLocationPluginEvent("test", new ProgramLocation(program, - addr), program)); - waitForSwing(); - - // enter a Binary value and hit the search button - setValueText("01110101"); - - setAlignment("4"); - - //the bytes are at the right alignment value but the code units are not - List
addrs = addrs(0x01002d2f, 0x01002d37, 0x01002d5b); - - for (int i = 0; i < addrs.size(); i++) { - Address start = addrs.get(i); - pressSearchButton("Next"); - CodeUnit cu = listing.getCodeUnitContaining(start); - assertEquals(cu.getMinAddress(), cb.getCurrentLocation().getAddress()); - assertEquals("Found", statusLabel.getText()); - } - pressSearchButton("Next"); - assertEquals("Not Found", statusLabel.getText()); - } - - @Test - public void testBinaryContiguousSelection() throws Exception { - - goTo(0x01001070); - - makeSelection(tool, program, range(0x01002cf5, 0x01002d6d)); - - assertSearchSelectionSelected(); - - setValueText("11110110"); - - // the bytes are at the right alignment value but the code units are not - performSearchTest(addrs(0x01002d27), "Next"); - } - - @Test - public void testBinaryNonContiguousSelection() throws Exception { - - makeSelection(tool, program, range(0x01002cf5, 0x01002d0e), range(0x01002d47, 0x01002d51)); - - assertSearchSelectionSelected(); - - setValueText("01010110"); - - // the bytes are at the right alignment value but the code units are not - List
addrs = addrs(0x01002cfc, 0x01002d47); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testBinarySelectionNotOn() throws Exception { - - goTo(0x01002cf5); - - // make a selection but turn off the Selection checkbox; - // the search should go outside the selection - - makeSelection(tool, program, range(0x01002cf5, 0x01002d0d), range(0x01002d37, 0x01002d47)); - - // select Search All option to turn off searching only in selection - assertButtonState("Search All", true, false); - - // Note: this is 'Search All' for the search type, not the JButton on the button panel - pressButtonByText(pane, "Search All"); - - setValueText("11110110"); - - pressButtonByText(pane, "Next"); - waitForSearchTask(); - - Address resultAddr = addr(0x1002d27); - - // verify the code browser goes to resulting address - CodeUnit cu = codeUnitContaining(resultAddr); - assertEquals(cu.getMinAddress(), currentAddress()); - assertEquals("Found", statusLabel.getText()); - } - - @Test - public void testBinarySearchAll() throws Exception { - // QueryResults should get displayed - // test the marker stuff - setValueText("11110110"); - - pressSearchAllButton(); - - waitForSearch("Search Memory - ", 1); - - checkMarkerSet(addrs(0x1002d28)); - } - - @Test - public void testBinarySearchAll2() throws Exception { - // enter search string for multiple byte match - // ff d6 - setValueText("11111111 11010110"); - - pressSearchAllButton(); - - waitForSearch("Search Memory - ", 2); - - List
addrs = addrs(0x1002d09, 0x1002d14); - - checkMarkerSet(addrs); - } - - @Test - public void testBinarySearchAllAlign4() throws Exception { - // QueryResults should get displayed - // test the marker stuff - setValueText("11111111 01110101"); - - setAlignment("4"); - - pressSearchAllButton(); - waitForSearch("Search Memory - ", 2); - - List
startList = addrs(0x1002d2c, 0x1002d58); - - checkMarkerSet(startList); - } - - @Test - public void testBinaryHighlight() throws Exception { - - setValueText("00010000 00000000 00000001"); - - pressSearchAllButton(); - - waitForSearch("Search Memory - ", 3); - - Highlight[] h = getByteHighlights(addr(0x1002cfd), "8b 35 e0 10 00 01"); - assertEquals(1, h.length); - assertEquals(9, h[0].getStart()); - assertEquals(16, h[0].getEnd()); - } - - @Test - public void testBinarySearchSelection() throws Exception { - - goTo(0x01001074); - - makeSelection(tool, program, range(0x01002cf5, 0x01002d6d)); - - assertSearchSelectionSelected(); - - setValueText("11110110"); - - performSearchTest(addrs(0x01002d27), "Next"); - } - - @Test - public void testBinarySearchPreviousNotFound() throws Exception { - - goTo(0x01001000); - - setValueText("00000111"); - pressButtonByText(pane, "Previous"); - waitForSearchTask(); - - assertEquals("Not Found", statusLabel.getText()); - } - - @Test - public void testCodeUnitScope_Instructions() throws Exception { - // - // Turn on Instructions scope and make sure only that scope yields matches - // - goTo(0x1002cf5); - - selectCheckBox("Instructions", true); - selectCheckBox("Defined Data", false); - selectCheckBox("Undefined Data", false); - - setValueText("01010101"); - pressSearchButton("Next"); - - Address expectedSearchAddressHit = addr(0x1002cf5); - assertEquals( - "Did not find a hit at the next matching Instruction when we are searching Instructions", - expectedSearchAddressHit, currentAddress()); - - // Turn off Instructions scope and make sure we have no match at the expected address - goTo(0x1002cf5); - - selectCheckBox("Instructions", false); - selectCheckBox("Defined Data", true); - selectCheckBox("Undefined Data", true); - pressSearchButton("Next"); - - assertTrue( - "Found a search match at an Instruction, even though no Instruction should be searched", - !expectedSearchAddressHit.equals(currentAddress())); - - CodeUnit codeUnit = currentCodeUnit(); - assertTrue("Did not find a data match when searching instructions is disabled", - codeUnit instanceof Data); - } - - @Test - public void testCodeUnitScope_DefinedData() throws Exception { - // - // Turn on Defined Data scope and make sure only that scope yields matches - // - goTo(0x1001000);// start of program; pointer data - - selectCheckBox("Instructions", false); - selectCheckBox("Defined Data", true); - selectCheckBox("Undefined Data", false); - - setValueText("10000101"); - pressSearchButton("Next"); - Address expectedSearchAddressHit = addr(0x1001004); - - assertEquals( - "Did not find a hit at the next matching Defined Data when we are searching Defined Data", - expectedSearchAddressHit, currentAddress()); - - // Turn off Defined Data scope and make sure we have no match at the expected address - goTo(0x1001000);// start of program; pointer data - - selectCheckBox("Instructions", true); - selectCheckBox("Defined Data", false); - selectCheckBox("Undefined Data", true); - pressSearchButton("Next"); - assertTrue( - "Found a search match at a Defined Data, even though no Defined Data should be searched", - !expectedSearchAddressHit.equals(currentAddress())); - - CodeUnit codeUnit = currentCodeUnit(); - assertTrue("Did not find a instruction match when searching defined data is disabled", - codeUnit instanceof Instruction); - - // try backwards - goTo(0x1002000); - assertEquals( - "Did not find a hit at the next matching Defined Data when we are searching Defined Data", - addr(0x1002000), currentAddress()); - - selectCheckBox("Instructions", false); - selectCheckBox("Defined Data", true); - selectCheckBox("Undefined Data", false); - - pressSearchButton("Previous"); - expectedSearchAddressHit = addr(0x01001004); - assertEquals( - "Did not find a hit at the previous matching Defined Data when we are searching Defined Data", - expectedSearchAddressHit, currentAddress()); - } - - @Test - public void testCodeUnitScope_UndefinedData() throws Exception { - // - // Turn on Undefined Data scope and make sure only that scope yields matches - // - goTo(0x1001000); - - selectCheckBox("Instructions", false); - selectCheckBox("Defined Data", false); - selectCheckBox("Undefined Data", true); - - setValueText("01100101"); - pressSearchButton("Next"); - - Address expectedSearchAddressHit = addr(0x1001502); - assertEquals( - "Did not find a hit at the next matching Undefined Data when we are searching Undefined Data", - expectedSearchAddressHit, currentAddress()); - - // Turn off Undefined Data scope and make sure we have no match at the expected address - goTo(0x1001000); - - selectCheckBox("Instructions", true); - selectCheckBox("Defined Data", true); - selectCheckBox("Undefined Data", false); - pressSearchButton("Next"); - assertTrue( - "Found a search match at an Undefined Data, even though no Undefined Data should be searched", - !expectedSearchAddressHit.equals(currentAddress())); - - CodeUnit codeUnit = listing.getCodeUnitAt(cb.getCurrentLocation().getAddress()); - assertTrue("Did not find a instruction match when searching defined data is disabled", - codeUnit instanceof Data); - - // try backwards - - goTo(0x1003000); - - selectCheckBox("Instructions", false); - selectCheckBox("Defined Data", false); - selectCheckBox("Undefined Data", true); - - pressSearchButton("Previous"); - expectedSearchAddressHit = addr(0x1001502); - assertEquals( - "Did not find a hit at the previous matching Undefined Data when we are searching Undefined Data", - expectedSearchAddressHit, currentAddress()); - } - - @Test - public void testBinarySearchPrevious() throws Exception { - // enter search string for multiple byte match - // ff 15 - - // start at 01002d6b - goTo(0x01002d6b); - - setValueText("11111111 00010101"); - - List
addrs = addrs(0x01002d5e, 0x01002d4a, 0x01002d41, 0x01002d1f); - - performSearchTest(addrs, "Previous"); - } - - @Test - public void testBinarySearchPreviousAlign4() throws Exception { - // enter search string for multiple byte match - // ff 15 - - goTo(0x1002d6d); - - setValueText("11111111 01110101"); - - setAlignment("4"); - - List
addrs = addrs(0x1002d58, 0x1002d2c); - - performSearchTest(addrs, "Previous"); - } - - @Test - public void testBinaryWildcardSearch() throws Exception { - goTo(0x01001000); - - setValueText("010101xx 10001011"); - - List
addrs = addrs(0x01002cf5, 0x01002cfc, 0x01002d47); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testBinaryWildcardSearchAll() throws Exception { - - setValueText("10001011 1111xxxx"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 4); - - List
addrs = addrs(0x1002d0b, 0x1002d25, 0x1002d48, 0x1002d64); - - checkMarkerSet(addrs); - } - - @SuppressWarnings("rawtypes") - @Test - public void testValueComboBox() throws Exception { - setValueText("1x1xx1x1"); - - pressSearchButton("Next"); - setValueText(""); - - setValueText("00000"); - pressSearchButton("Next"); - setValueText(""); - - setValueText("111"); - pressSearchButton("Next"); - setValueText(""); - - // the combo box should list most recently entered values - DefaultComboBoxModel cbModel = (DefaultComboBoxModel) valueComboBox.getModel(); - assertEquals(3, cbModel.getSize()); - assertEquals("111", cbModel.getElementAt(0)); - assertEquals("00000", cbModel.getElementAt(1)); - assertEquals("1x1xx1x1", cbModel.getElementAt(2)); - } - -} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchDecimal2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchDecimal2Test.java deleted file mode 100644 index 2ca2d9e490..0000000000 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchDecimal2Test.java +++ /dev/null @@ -1,668 +0,0 @@ -/* ### - * 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.searchmem; - -import static org.junit.Assert.*; - -import java.awt.Container; -import java.nio.charset.StandardCharsets; -import java.util.List; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.TitledBorder; - -import org.junit.Before; -import org.junit.Test; - -import ghidra.program.database.ProgramBuilder; -import ghidra.program.model.address.Address; -import ghidra.program.model.data.Pointer32DataType; -import ghidra.program.model.listing.Program; - -/** - * Tests for searching for decimal values in memory. - */ -public class MemSearchDecimal2Test extends AbstractMemSearchTest { - - public MemSearchDecimal2Test() { - super(); - } - - @Override - @Before - public void setUp() throws Exception { - super.setUp(); - selectRadioButton("Decimal"); - } - - @Override - protected Program buildProgram() throws Exception { - ProgramBuilder builder = new ProgramBuilder("TestX86", ProgramBuilder._X86); - builder.createMemory(".text", Long.toHexString(0x1001000), 0x6600); - builder.createMemory(".data", Long.toHexString(0x1008000), 0x600); - builder.createMemory(".rsrc", Long.toHexString(0x100A000), 0x5400); - builder.createMemory(".bound_import_table", Long.toHexString(0xF0000248), 0xA8); - builder.createMemory(".debug_data", Long.toHexString(0xF0001300), 0x1C); - - //create and disassemble a function - builder.setBytes( - "0x01002cf5", - "55 8b ec 83 7d 14 00 56 8b 35 e0 10 00 01 57 74 09 ff 75 14 ff d6 8b f8 eb 02 " + - "33 ff ff 75 10 ff d6 03 c7 8d 44 00 02 50 6a 40 ff 15 dc 10 00 01 8b f0 85 f6 " + - "74 27 56 ff 75 14 ff 75 10 e8 5c ff ff ff ff 75 18 ff 75 0c 56 ff 75 08 ff 15 " + - "04 12 00 01 56 8b f8 ff 15 c0 10 00 01 eb 14 ff 75 18 ff 75 0c ff 75 10 ff 75 " + - "08 ff 15 04 12 00 01 8b f8 8b c7 5f 5e 5d c2 14"); - builder.disassemble("0x01002cf5", 0x121, true); - builder.createFunction("0x01002cf5"); - - //create some data - - builder.setBytes("0x1001004", "85 4f dc 77"); - builder.applyDataType("0x1001004", new Pointer32DataType(), 1); - builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true); - builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE, true); - builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, false); - - //create some undefined data - builder.setBytes("0x1001500", "4e 00 65 00 77 00"); - builder.setBytes("0x1003000", "55 00"); - builder.setBytes("0x1004100", "64 00 00 00");//100 dec - builder.setBytes("0x1004120", "50 ff 75 08");//7.4027124e-34 float - builder.setBytes("0x1004135", "64 00 00 00");//100 dec - builder.setBytes("0x1004200", "50 ff 75 08 e8 8d 3c 00");//1.588386874245921e-307 - builder.setBytes("0x1004247", "50 ff 75 08");//7.4027124e-34 float - builder.setBytes("0x1004270", "65 00 6e 00 64 00 69 00");//29555302058557541 qword - - return builder.getProgram(); - } - - @Test - public void testDecimalOptionsShowing() throws Exception { - // select the Decimal option; verify radio buttons for decimal types - // are showing in the Decimal Options panel. - JRadioButton rb = (JRadioButton) findAbstractButtonByText(pane, "Byte"); - assertNotNull(rb); - JPanel p = findTitledJPanel(rb, "Format Options"); - assertNotNull(p); - assertTrue(p.isVisible()); - - assertTrue(!rb.isSelected()); - assertTrue(rb.isVisible()); - - rb = (JRadioButton) findAbstractButtonByText(pane, "Word"); - assertNotNull(rb); - assertTrue(rb.isSelected()); - assertTrue(rb.isVisible()); - - rb = (JRadioButton) findAbstractButtonByText(pane, "DWord"); - assertNotNull(rb); - assertTrue(!rb.isSelected()); - assertTrue(rb.isVisible()); - - rb = (JRadioButton) findAbstractButtonByText(pane, "QWord"); - assertNotNull(rb); - assertTrue(!rb.isSelected()); - assertTrue(rb.isVisible()); - - rb = (JRadioButton) findAbstractButtonByText(pane, "Float"); - assertNotNull(rb); - assertTrue(!rb.isSelected()); - assertTrue(rb.isVisible()); - - rb = (JRadioButton) findAbstractButtonByText(pane, "Double"); - assertNotNull(rb); - assertTrue(!rb.isSelected()); - assertTrue(rb.isVisible()); - } - - @Test - public void testInvalidEntry() throws Exception { - // enter non-numeric value - setValueText("z"); - assertEquals("", valueField.getText()); - assertEquals("", hexLabel.getText()); - } - - @Test - public void testValueTooLarge() throws Exception { - // select "Byte" and enter 260; should not accept "0" - selectRadioButton("Byte"); - - myTypeText("260"); - assertEquals("26", valueField.getText()); - assertEquals(statusLabel.getText(), "Number must be in the range [-128,255]"); - } - - @Test - public void testValueTooLarge2() throws Exception { - // select "Word" and enter 2698990; should not accept "26989" - selectRadioButton("Word"); - - myTypeText("2698990"); - assertEquals(statusLabel.getText(), "Number must be in the range [-32768,65535]"); - assertEquals("26989", valueField.getText()); - } - - @Test - public void testNegativeValueEntered() throws Exception { - // enter a negative value; the hexLabel should show the correct - // byte sequence - - setValueText("-1234"); - assertEquals("2e fb ", hexLabel.getText()); - - selectRadioButton("Byte"); - assertEquals("", valueField.getText()); - assertEquals("", hexLabel.getText()); - setValueText("-55"); - assertEquals("c9 ", hexLabel.getText()); - - selectRadioButton("DWord"); - assertEquals("c9 ff ff ff ", hexLabel.getText()); - - selectRadioButton("QWord"); - assertEquals("c9 ff ff ff ff ff ff ff ", hexLabel.getText()); - - selectRadioButton("Float"); - assertEquals("00 00 5c c2 ", hexLabel.getText()); - - selectRadioButton("Double"); - assertEquals("00 00 00 00 00 80 4b c0 ", hexLabel.getText()); - } - - @Test - public void testMulipleValuesEntered() throws Exception { - // enter values separated by a space; values should be accepted - selectRadioButton("Byte"); - setValueText("12 34 56 78"); - assertEquals("0c 22 38 4e ", hexLabel.getText()); - - selectRadioButton("Word"); - assertEquals("0c 00 22 00 38 00 4e 00 ", hexLabel.getText()); - - selectRadioButton("DWord"); - assertEquals("0c 00 00 00 22 00 00 00 38 00 00 00 4e 00 00 00 ", hexLabel.getText()); - - selectRadioButton("QWord"); - assertEquals("0c 00 00 00 00 00 00 00 22 00 00 00 00 00 00 00 " - + "38 00 00 00 00 00 00 00 4e 00 00 00 00 00 00 00 ", hexLabel.getText()); - - selectRadioButton("Float"); - assertEquals("00 00 40 41 00 00 08 42 00 00 60 42 00 00 9c 42 ", hexLabel.getText()); - - selectRadioButton("Double"); - assertEquals("00 00 00 00 00 00 28 40 00 00 00 00 00 00 41 40 " - + "00 00 00 00 00 00 4c 40 00 00 00 00 00 80 53 40 ", hexLabel.getText()); - } - - @Test - public void testByteOrder() throws Exception { - setValueText("12 34 56 78"); - selectRadioButton("Byte"); - selectRadioButton("Big Endian"); - // should be unaffected - assertEquals("0c 22 38 4e ", hexLabel.getText()); - - selectRadioButton("Word"); - assertEquals("00 0c 00 22 00 38 00 4e ", hexLabel.getText()); - - selectRadioButton("DWord"); - assertEquals("00 00 00 0c 00 00 00 22 00 00 00 38 00 00 00 4e ", hexLabel.getText()); - - selectRadioButton("QWord"); - assertEquals("00 00 00 00 00 00 00 0c 00 00 00 00 00 00 00 22 " - + "00 00 00 00 00 00 00 38 00 00 00 00 00 00 00 4e ", hexLabel.getText()); - - selectRadioButton("Float"); - assertEquals("41 40 00 00 42 08 00 00 42 60 00 00 42 9c 00 00 ", hexLabel.getText()); - - selectRadioButton("Double"); - assertEquals("40 28 00 00 00 00 00 00 40 41 00 00 00 00 00 00 " - + "40 4c 00 00 00 00 00 00 40 53 80 00 00 00 00 00 ", hexLabel.getText()); - } - - @Test - public void testFloatDoubleFormat() throws Exception { - selectRadioButton("Float"); - - setValueText("12.345"); - assertEquals("12.345", valueField.getText()); - assertEquals("1f 85 45 41 ", hexLabel.getText()); - - selectRadioButton("Double"); - assertEquals("71 3d 0a d7 a3 b0 28 40 ", hexLabel.getText()); - } - - @Test - public void testSearchByte() throws Exception { - goTo(program.getMinAddress()); - - List
addrs = addrs(0x1002d3e, 0x1002d5b, 0x1004123, 0x1004203, 0x100424a); - - selectRadioButton("Byte"); - setValueText("8"); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testSearchWord() throws Exception { - - goTo(program.getMinAddress()); - - selectRadioButton("Word"); - - setValueText("20"); - - List
addrs = addrs(0x1002cf8, 0x1002d6b); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testSearchWordBackward() throws Exception { - - goTo(0x01002d6e); - - selectRadioButton("Word"); - - setValueText("20"); - - List
addrs = addrs(0x1002d6b, 0x1002cf8); - - performSearchTest(addrs, "Previous"); - } - - @Test - public void testSearchDWord() throws Exception { - goTo(program.getMinAddress()); - - selectRadioButton("DWord"); - - setValueText("100"); - - List
addrs = addrs(0x1001708, 0x1004100, 0x1004135); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testSearchDWordBackward() throws Exception { - goTo(0x01005000); - - selectRadioButton("DWord"); - - setValueText("100"); - - List
addrs = addrs(0x1004135, 0x1004100, 0x1001708); - - performSearchTest(addrs, "Previous"); - } - - @Test - public void testSearchQWord() throws Exception { - goTo(program.getMinAddress()); - - selectRadioButton("QWord"); - - setValueText("29555302058557541"); - - performSearchTest(addrs(0x1004270), "Next"); - } - - @Test - public void testSearchQWordBackward() throws Exception { - - goTo(program.getMaxAddress()); - - selectRadioButton("QWord"); - - setValueText("29555302058557541"); - - performSearchTest(addrs(0x1004270), "Previous"); - } - - @Test - public void testSearchFloat() throws Exception { - - goTo(program.getMinAddress()); - - selectRadioButton("Float"); - - setValueText("7.4027124e-34"); - - List
addrs = addrs(0x1004120, 0x1004200, 0x1004247); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testSearchFloatBackward() throws Exception { - - goTo(0x01005000); - - selectRadioButton("Float"); - - setValueText("7.4027124e-34"); - - List
addrs = addrs(0x1004247, 0x1004200, 0x1004120); - - performSearchTest(addrs, "Previous"); - } - - @Test - public void testSearchFloatBackwardAlign8() throws Exception { - - goTo(program.getMaxAddress()); - - JTextField alignment = (JTextField) findComponentByName(dialog.getComponent(), "Alignment"); - setText(alignment, "8"); - - selectRadioButton("Float"); - - setValueText("7.4027124e-34"); - - List
addrs = addrs(0x1004200, 0x1004120); - - performSearchTest(addrs, "Previous"); - } - - @Test - public void testSearchDouble() throws Exception { - - goTo(program.getMinAddress()); - - selectRadioButton("Double"); - - setValueText("1.588386874245921e-307"); - - List
addrs = addrs(0x1004200); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testSearchDoubleBackward() throws Exception { - - goTo(program.getMaxAddress()); - - selectRadioButton("Double"); - - setValueText("1.588386874245921e-307"); - - List
addrs = addrs(0x1004200); - - performSearchTest(addrs, "Previous"); - } - - @Test - public void testSearchAllByte() throws Exception { - - selectRadioButton("Byte"); - - setValueText("8"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 5); - - List
addrs = addrs(0x1002d40, 0x1002d5d, 0x1004123, 0x1004203, 0x100424a); - - checkMarkerSet(addrs); - } - - @Test - public void testSearchAllWord() throws Exception { - - selectRadioButton("Word"); - - setValueText("20"); - - pressSearchAllButton(); - waitForSearch("Search Memory - ", 2); - - List
addrs = addrs(0x1002cfa, 0x1002d6c); - - checkMarkerSet(addrs); - } - - @Test - public void testSearchAllWordAlign4() throws Exception { - - JTextField alignment = (JTextField) findComponentByName(dialog.getComponent(), "Alignment"); - setText(alignment, "4"); - - selectRadioButton("Word"); - - setValueText("20"); - - pressSearchAllButton(); - waitForSearch("Search Memory - ", 1); - - checkMarkerSet(addrs(0x1002d6c)); - } - - @Test - public void testSearchAllDWord() throws Exception { - - selectRadioButton("DWord"); - - setValueText("100"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 3); - - List
addrs = addrs(0x1001715, 0x1004100, 0x1004135); - - checkMarkerSet(addrs); - } - - @Test - public void testSearchAllQWord() throws Exception { - - selectRadioButton("QWord"); - - setValueText("29555302058557541"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 1); - - checkMarkerSet(addrs(0x1004270)); - } - - @Test - public void testSearchAllFloat() throws Exception { - - selectRadioButton("Float"); - - setValueText("7.4027124e-34"); - - pressSearchAllButton(); - waitForSearch("Search Memory - ", 3); - - List
addrs = addrs(0x1004120, 0x1004200, 0x1004247); - - checkMarkerSet(addrs); - } - - @Test - public void testSearchAllDouble() throws Exception { - - selectRadioButton("Double"); - - setValueText("1.588386874245921e-307"); - - pressSearchAllButton(); - waitForSearch("Search Memory - ", 1); - - checkMarkerSet(addrs(0x1004200)); - } - - @Test - public void testSearchSelectionByte() throws Exception { - - makeSelection(tool, program, range(0x01004000, 0x01005000)); - - assertSearchSelectionSelected(); - - selectRadioButton("Byte"); - - setValueText("8"); - - List
addrs = addrs(0x1004123, 0x1004203, 0x100424a); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testSearchSelectionWord() throws Exception { - - makeSelection(tool, program, range(0x01002c00, 0x01002d00)); - - assertSearchSelectionSelected(); - - selectRadioButton("Word"); - - setValueText("20"); - - performSearchTest(addrs(0x1002cf8), "Next"); - } - - @Test - public void testSearchSelectionDWord() throws Exception { - - makeSelection(tool, program, range(0x01004000, 0x01005000)); - - assertSearchSelectionSelected(); - - selectRadioButton("DWord"); - - setValueText("100"); - - List
addrs = addrs(0x1004100, 0x1004135); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testSearchSelectionQWord() throws Exception { - - makeSelection(tool, program, range(0x01004000, 0x01005000)); - - assertSearchSelectionSelected(); - - selectRadioButton("QWord"); - - setValueText("29555302058557541"); - - performSearchTest(addrs(0x1004270), "Next"); - - } - - @Test - public void testSearchSelectionFloat() throws Exception { - - makeSelection(tool, program, range(0x01004200, 0x01004300)); - - assertSearchSelectionSelected(); - - selectRadioButton("Float"); - - setValueText("7.4027124e-34"); - - List
addrs = addrs(0x1004200, 0x1004247); - - performSearchTest(addrs, "Next"); - } - - @Test - public void testSearchSelectionDouble() throws Exception { - - makeSelection(tool, program, range(0x01004000, 0x01005000)); - - assertSearchSelectionSelected(); - - selectRadioButton("Double"); - - setValueText("1.588386874245921e-307"); - - performSearchTest(addrs(0x1004200), "Next"); - - } - - @Test - public void testSearchAllInSelection() throws Exception { - - makeSelection(tool, program, range(0x01002cf5, 0x01002d6d)); - - assertSearchSelectionSelected(); - - selectRadioButton("Byte"); - - setValueText("8"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 2); - - List
addrs = addrs(0x1002d40, 0x1002d5d); - - checkMarkerSet(addrs); - } - - @Test - public void testSearchBackwardsInSelection() throws Exception { - - goTo(program.getMaxAddress()); - - makeSelection(tool, program, range(0x01004000, 0x01005000)); - - assertSearchSelectionSelected(); - - selectRadioButton("Double"); - - setValueText("1.588386874245921e-307"); - - performSearchTest(addrs(0x1004200), "Previous"); - } - -//================================================================================================== -// Private Methods -//================================================================================================== - - @Override - protected void showMemSearchDialog() { - super.showMemSearchDialog(); - selectRadioButton("Decimal"); - } - - private JPanel findTitledJPanel(Container container, String title) { - if (container instanceof JPanel) { - JPanel p = (JPanel) container; - Border b = p.getBorder(); - if ((b instanceof TitledBorder) && ((TitledBorder) b).getTitle().equals(title)) { - return p; - } - } - Container parent = container.getParent(); - while (parent != null) { - if (parent instanceof JPanel) { - JPanel p = findTitledJPanel(parent, title); - if (p != null) { - return p; - } - } - parent = parent.getParent(); - } - return null; - } - -} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/AbstractMemSearchTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/AbstractMemSearchTest.java new file mode 100644 index 0000000000..2237c273bc --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/AbstractMemSearchTest.java @@ -0,0 +1,289 @@ +/* ### + * 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.features.base.memsearch; + +import static org.junit.Assert.*; + +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.IteratorUtils; + +import docking.action.DockingActionIf; +import docking.widgets.fieldpanel.support.Highlight; +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; +import ghidra.app.services.*; +import ghidra.app.util.ListingHighlightProvider; +import ghidra.app.util.viewer.field.BytesFieldFactory; +import ghidra.app.util.viewer.field.ListingField; +import ghidra.app.util.viewer.format.FormatManager; +import ghidra.features.base.memsearch.bytesource.SearchRegion; +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.features.base.memsearch.gui.*; +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.Memory; +import ghidra.test.AbstractProgramBasedTest; +import ghidra.util.Swing; + +/** + * Base class for memory search tests. + */ +public abstract class AbstractMemSearchTest extends AbstractProgramBasedTest { + + protected MemorySearchPlugin memorySearchPlugin; + protected DockingActionIf searchAction; + protected CodeViewerProvider provider; + protected Memory memory; + protected Listing listing; + protected MarkerService markerService; + + protected MemorySearchProvider searchProvider; + private SearchSettings settings = new SearchSettings(); + + /* + * Note that this setup function does not have the @Before annotation - this is because + * sub-classes often need to override this and if we have the annotation here, the test + * runner will only invoke this base class implementation. + */ + public void setUp() throws Exception { + + // this builds the program and launches the tool + initialize(); + + memorySearchPlugin = env.getPlugin(MemorySearchPlugin.class); + + listing = program.getListing(); + memory = program.getMemory(); + + searchAction = getAction(memorySearchPlugin, "Memory Search"); + + provider = codeBrowser.getProvider(); + markerService = tool.getService(MarkerService.class); + + showMemorySearchProvider(); + } + + protected void setInput(String input) { + Swing.runNow(() -> searchProvider.setSearchInput(input)); + } + + @Override + protected Program getProgram() throws Exception { + return buildProgram(); + } + + protected abstract Program buildProgram() throws Exception; + + protected void waitForSearch(int expectedResults) { + + waitForCondition(() -> { + return runSwing(() -> !searchProvider.isBusy()); + }, "Timed-out waiting for search results"); + assertEquals(expectedResults, searchProvider.getSearchResults().size()); + } + + protected void waitForSearchTask() { + waitForSwing(); + waitForTasks(); + waitForSwing(); + } + + protected void showMemorySearchProvider() { + performAction(searchAction, provider, true); + searchProvider = waitForComponentProvider(MemorySearchProvider.class); + } + + @SuppressWarnings("unchecked") + private List
getHighlightAddresses() { + CodeViewerService service = tool.getService(CodeViewerService.class); + Object codeViewerProvider = getInstanceField("connectedProvider", service); + Map highlighterMap = + (Map) getInstanceField("programHighlighterMap", + codeViewerProvider); + ListingHighlightProvider highlightProvider = highlighterMap.get(program); + + assertEquals("The inner-class has been renamed", "MemoryMatchHighlighter", + highlightProvider.getClass().getSimpleName()); + + List data = searchProvider.getSearchResults(); + return data.stream().map(result -> result.getAddress()).collect(Collectors.toList()); + } + + protected void checkMarkerSet(List
expected) { + List
highlights = getHighlightAddresses(); + assertListEqualUnordered("Search highlights not correctly generated", expected, highlights); + + MarkerSet markers = + runSwing(() -> markerService.getMarkerSet(searchProvider.getTitle(), program)); + assertNotNull(markers); + + AddressSet addressSet = runSwing(() -> markers.getAddressSet()); + AddressIterator it = addressSet.getAddresses(true); + List
list = IteratorUtils.toList(it); + + assertListEqualUnordered("Search markers not correctly generated", expected, list); + } + + protected void performSearchNext(Address expected) throws Exception { + DockingActionIf action = getAction(tool, "MemorySearchPlugin", "Search Next"); + performAction(action); + waitForSearchTask(); + codeBrowser.updateNow(); + assertEquals(expected, codeBrowser.getCurrentAddress()); + } + + protected void performSearchNext(List
expected) throws Exception { + DockingActionIf action = getAction(tool, "MemorySearchPlugin", "Search Next"); + performSearchNextPrevious(expected, action); + } + + protected void performSearchPrevious(List
expected) throws Exception { + DockingActionIf action = getAction(tool, "MemorySearchPlugin", "Search Previous"); + performSearchNextPrevious(expected, action); + } + + protected void performSearchNextPrevious(List
expected, DockingActionIf action) + throws Exception { + + for (Address addr : expected) { + performAction(action); + waitForSearchTask(); + codeBrowser.updateNow(); + assertEquals(addr, codeBrowser.getCurrentAddress()); + } + + Address addr = codeBrowser.getCurrentAddress(); + performAction(action); + waitForSearchTask(); + codeBrowser.updateNow(); + assertEquals(addr, codeBrowser.getCurrentAddress()); + } + + protected void performSearchAll() { + runSwing(() -> searchProvider.search()); + } + + protected ListingHighlightProvider getHighlightProvider() { + CodeViewerService service = tool.getService(CodeViewerService.class); + FormatManager fm = (FormatManager) getInstanceField("formatMgr", service); + return (ListingHighlightProvider) getInstanceField("highlightProvider", fm); + } + + protected void repeatSearchForward() { + DockingActionIf action = getAction(memorySearchPlugin, "Repeat Memory Search Forwards"); + assertTrue(action.isEnabled()); + performAction(action, provider, true); + waitForSearchTask(); + } + + protected void repeatSearchBackward() { + DockingActionIf action = getAction(memorySearchPlugin, "Repeat Memory Search Backwards"); + assertTrue(action.isEnabled()); + performAction(action, provider, true); + waitForSearchTask(); + } + + protected Address currentAddress() { + codeBrowser.updateNow(); + Address addr = codeBrowser.getCurrentLocation().getAddress(); + return addr; + } + + protected CodeUnit currentCodeUnit() { + CodeUnit cu = program.getListing().getCodeUnitContaining(currentAddress()); + return cu; + } + + protected CodeUnit codeUnitContaining(Address addr) { + CodeUnit cu = program.getListing().getCodeUnitContaining(addr); + return cu; + } + + protected void assertSearchSelectionSelected() { + waitForSwing(); + assertTrue(Swing.runNow(() -> searchProvider.isSearchSelection())); + } + + protected Highlight[] getByteHighlights(Address address, String bytes) { + goTo(address); + ListingHighlightProvider hlProvider = getHighlightProvider(); + ListingField field = getField(address, BytesFieldFactory.FIELD_NAME); + return hlProvider.createHighlights(bytes, field, -1); + } + + protected String getInput() { + return Swing.runNow(() -> searchProvider.getSearchInput()); + } + + protected String getByteString() { + return Swing.runNow(() -> searchProvider.getByteString()); + } + + protected void setSearchFormat(SearchFormat format) { + settings = settings.withSearchFormat(format); + runSwing(() -> searchProvider.setSettings(settings)); + } + + protected void setDecimalSize(int size) { + settings = settings.withDecimalByteSize(size); + runSwing(() -> searchProvider.setSettings(settings)); + } + + protected void setAlignment(int alignment) { + settings = settings.withAlignment(alignment); + runSwing(() -> searchProvider.setSettings(settings)); + } + + protected void setSearchSelectionOnly(boolean b) { + runSwing(() -> searchProvider.setSearchSelectionOnly(b)); + } + + protected void setBigEndian(boolean b) { + settings = settings.withBigEndian(b); + runSwing(() -> searchProvider.setSettings(settings)); + } + + protected void setCaseSensitive(boolean b) { + settings = settings.withCaseSensitive(b); + runSwing(() -> searchProvider.setSettings(settings)); + } + + protected void setCharset(Charset charset) { + settings = settings.withStringCharset(charset); + runSwing(() -> searchProvider.setSettings(settings)); + } + + protected void setEscapeSequences(boolean b) { + settings = settings.withUseEscapeSequence(b); + runSwing(() -> searchProvider.setSettings(settings)); + } + + protected void addSearchRegion(SearchRegion region, boolean b) { + settings = settings.withSelectedRegion(region, b); + runSwing(() -> searchProvider.setSettings(settings)); + } + + protected void setCodeTypeFilters(boolean instructions, boolean data, boolean undefinedData) { + settings = settings.withIncludeInstructions(instructions); + settings = settings.withIncludeDefinedData(data); + settings = settings.withIncludeUndefinedData(undefinedData); + runSwing(() -> searchProvider.setSettings(settings)); + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchAsciiTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchAsciiTest.java new file mode 100644 index 0000000000..0d64b42f3d --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchAsciiTest.java @@ -0,0 +1,342 @@ +/* ### + * 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.features.base.memsearch; + +import static org.junit.Assert.*; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.test.ToyProgramBuilder; + +/** + * Tests for searching memory for ascii. + */ +public class MemSearchAsciiTest extends AbstractMemSearchTest { + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + setSearchFormat(SearchFormat.STRING); + } + + @Override + protected Program buildProgram() throws Exception { + ToyProgramBuilder builder = new ToyProgramBuilder("Test", false, ProgramBuilder._TOY); + + builder.createMemory(".text", "0x1001000", 0x6600); + builder.createMemory(".data", "0x1008000", 0x600); + builder.createMemory(".rsrc", "0x100a000", 0x5400); + //create some strings + builder.createEncodedString("0x010016ec", "something", StandardCharsets.UTF_16LE, true); + builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16LE, true); + builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16LE, + true); + builder.createEncodedString("0x01001840", "\u039d\u03bf\u03c4\u03b5\u03c0\u03b1\u03bd", + StandardCharsets.UTF_16LE, true); + builder.createEncodedString("0x0100186a", + "\u03c1\u03b8\u03c4\u03b5\u03c0\u03b1\u03bd\u03c2\u03b2", StandardCharsets.UTF_16LE, + true); + builder.createEncodedString("0x0100196a", + "\u03c1\u03b8\u03c4\u03b5\u03c0\u03b1\u03bd\u03c2\u03b2", StandardCharsets.UTF_8, true); + builder.createEncodedString("0x0100189d", "\"Hello world!\"\n\t-new programmer", + StandardCharsets.US_ASCII, true); + builder.createEncodedString("0x0100198e", "\"Hello world!\"\n\t-new programmer", + StandardCharsets.UTF_16LE, true); + builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, false); + builder.createEncodedString("0x010013e0", "notepad.exe", StandardCharsets.US_ASCII, false); + builder.createEncodedString("0x1006c6a", "GetLocaleInfoW", StandardCharsets.US_ASCII, + false); + builder.createEncodedString("0x1006f26", "GetCPInfo", StandardCharsets.US_ASCII, false); + builder.createEncodedString("0x0100dde0", "NOTEPAD.EXE", StandardCharsets.UTF_16LE, true); + builder.createEncodedString("0x0100eb90", + "This string contains notepad twice. Here is the second NotePad.", + StandardCharsets.UTF_16LE, true); + builder.createEncodedString("0x0100ed00", "Another string", StandardCharsets.UTF_16LE, + true); + + return builder.getProgram(); + } + + @Test + public void testCaseSensitiveOff() throws Exception { + + setCaseSensitive(false); + + setInput("notepad"); + + List
addrs = addrs(0x010013cc, 0x010013e0); + + performSearchNext(addrs); + } + + @Test + public void testCaseSensitiveOn() throws Exception { + + setCaseSensitive(true); + + setInput("NOTEpad"); + + performSearchNext(Collections.emptyList()); + } + + @Test + public void testUnicodeNotCaseSensitive() throws Exception { + + setCaseSensitive(false); + setCharset(StandardCharsets.UTF_16); + setInput("NOTEpad"); + + List
addrs = addrs(0x01001708, 0x0100dde0, 0x0100eb90, 0x0100eb90); // this code unit contains two notepads in one string + + performSearchNext(addrs); + } + + @Test + public void testGreekUnicodeSearch() throws Exception { + setCaseSensitive(false); + setCharset(StandardCharsets.UTF_16); + setInput("\u03c4\u03b5\u03c0\u03b1\u03bd"); + + List
addrs = addrs(0x01001840, 0x0100186a); + + performSearchNext(addrs); + + setCharset(StandardCharsets.UTF_8); + + addrs = addrs(0x0100196a); + + performSearchNext(addrs); + } + + @Test + public void testRepeatUnicodeNotCaseSensitive() throws Exception { + setCaseSensitive(false); + setCharset(StandardCharsets.UTF_16); + setInput("NOTEpad"); + + performSearchNext(addr(0x01001708)); + + // this code unit contains two notepads in one string + List
addrs = addrs(0x0100dde0, 0x0100eb90, 0x0100eb90); + + for (Address address : addrs) { + repeatSearchForward(); + assertEquals(address, codeBrowser.getCurrentLocation().getAddress()); + + } + repeatSearchForward(); + assertEquals(addrs.get(2), codeBrowser.getCurrentLocation().getAddress()); + } + + @Test + public void testUnicodeCaseSensitive() throws Exception { + setCaseSensitive(true); + setCharset(StandardCharsets.UTF_16); + setInput("Notepad"); + + performSearchNext(addrs(0x01001708)); + } + + @Test + public void testSearchAllUnicodeNotCaseSensitive() throws Exception { + setCaseSensitive(false); + + setCharset(StandardCharsets.UTF_16); + setInput("NOTEpad"); + + performSearchAll(); + waitForSearch(4); + + List
addrs = addrs(0x01001708, 0x0100dde0, 0x0100ebba, 0x0100ebfe); + + checkMarkerSet(addrs); + } + + @Test + public void testSearchAllUnicodeCaseSensitive() throws Exception { + setCaseSensitive(true); + setCharset(StandardCharsets.UTF_16); + setInput("Notepad"); + + performSearchAll(); + waitForSearch(1); + + checkMarkerSet(addrs(0x01001708)); + } + + @Test + public void testSearchAllNotCaseSensitive() throws Exception { + setCaseSensitive(false); + + setInput("NOTEpad"); + performSearchAll(); + waitForSearch(2); + + List
addrs = addrs(0x010013cc, 0x010013e0); + + checkMarkerSet(addrs); + } + + @Test + public void testSearchAllCaseSensitive() throws Exception { + byte[] b = new byte[] { 'N', 'O', 'T', 'E', 'p', 'a', 'd' }; + + int transactionID = program.startTransaction("test"); + memory.setBytes(addr(0x0100b451), b); + program.endTransaction(transactionID, true); + + setCaseSensitive(true); + setInput("NOTEpad"); + performSearchAll(); + + waitForSearch(1); + + checkMarkerSet(addrs(0x0100b451)); + } + + @Test + public void testSearchAllCaseSensitiveAlign8() throws Exception { + setAlignment(8); + + setCaseSensitive(true); + + setInput("notepad"); + performSearchAll(); + waitForSearch(1); + + checkMarkerSet(addrs(0x010013e0)); + } + + @Test + public void testSearchSelection() throws Exception { + + makeSelection(tool, program, addr(0x01006c73), addr(0x01006f02)); + + assertSearchSelectionSelected(); + + setCaseSensitive(false); + + setInput("Info"); + + performSearchNext(addrs(0x01006c6a)); + } + + @Test + public void testSearchNonContiguousSelection() throws Exception { + + makeSelection(tool, program, range(0x01006c70, 0x01006c80), range(0x01006f2b, 0x01006f37)); + + assertSearchSelectionSelected(); + + setCaseSensitive(false); + + setInput("Info"); + + List
addrs = addrs(0x01006c6a, 0x01006f26); + + performSearchNext(addrs); + } + + @Test + public void testSearchBackward() throws Exception { + + goTo(tool, program, addr(0x1006f56)); + + setCaseSensitive(true); + + setInput("Info"); + + List
addrs = addrs(0x01006f26, 0x01006c6a); + + performSearchPrevious(addrs); + } + + @Test + public void testSearchBackwardInSelection() throws Exception { + + goTo(tool, program, addr(0x01006f02)); + + makeSelection(tool, program, addr(0x01006c73), addr(0x01006f02)); + + assertSearchSelectionSelected(); + + setCaseSensitive(false); + + setInput("Info"); + + List
addrs = addrs(0x01006c6a); + + performSearchPrevious(addrs); + } + + @Test + public void testSearchBackwardAlign4() throws Exception { + + goTo(tool, program, addr(0x1006f56)); + + setCaseSensitive(true); + + setAlignment(8); + + setInput("notepad"); + + List
addrs = addrs(0x010013e0); + + performSearchPrevious(addrs); + } + + @Test + public void testSearchBackwardAlign4NoneFound() throws Exception { + + goTo(tool, program, addr(0x1006f56)); + + setCaseSensitive(true); + + setAlignment(8); + + setInput("Info"); + + performSearchPrevious(Collections.emptyList()); + } + + @Test + public void testSearchEscapeSequences() throws Exception { + setCaseSensitive(true); + setEscapeSequences(true); + + setCharset(StandardCharsets.US_ASCII); + setInput("\"Hello world!\"\\n\\t-new programmer"); + + List
addrs = addrs(0x0100189d); + performSearchNext(addrs); + + setBigEndian(false); + setCharset(StandardCharsets.UTF_16); + + addrs = addrs(0x0100198e); + performSearchNext(addrs); + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchBinaryTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchBinaryTest.java new file mode 100644 index 0000000000..5a1993e7ba --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchBinaryTest.java @@ -0,0 +1,258 @@ +/* ### + * 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.features.base.memsearch; + +import static org.junit.Assert.*; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.Pointer32DataType; +import ghidra.program.model.listing.Program; + +/** + * Tests for the Binary format in searching memory. + */ +public class MemSearchBinaryTest extends AbstractMemSearchTest { + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + setSearchFormat(SearchFormat.BINARY); + } + + @Override + protected Program buildProgram() throws Exception { + ProgramBuilder builder = new ProgramBuilder("TestX86", ProgramBuilder._X86); + builder.createMemory(".text", Long.toHexString(0x1001000), 0x6600); + builder.createMemory(".data", Long.toHexString(0x1008000), 0x600); + builder.createMemory(".rsrc", Long.toHexString(0x100A000), 0x5400); + builder.createMemory(".bound_import_table", Long.toHexString(0xF0000248), 0xA8); + builder.createMemory(".debug_data", Long.toHexString(0xF0001300), 0x1C); + + //create and disassemble a function + builder.setBytes( + "0x01002cf5", + "55 8b ec 83 7d 14 00 56 8b 35 e0 10 00 01 57 74 09 ff 75 14 ff d6 8b f8 eb 02 33 " + + "ff ff 75 10 ff d6 03 c7 8d 44 00 02 50 6a 40 ff 15 dc 10 00 01 8b f0 85 f6 74 27 " + + "56 ff 75 14 ff 75 10 e8 5c ff ff ff ff 75 18 ff 75 0c 56 ff 75 08 ff 15 04 12 00 " + + "01 56 8b f8 ff 15 c0 10 00 01 eb 14 ff 75 18 ff 75 0c ff 75 10 ff 75 08 ff 15 04 " + + "12 00 01 8b f8 8b c7 5f 5e 5d c2 14"); + builder.disassemble("0x01002cf5", 0x121, true); + builder.createFunction("0x01002cf5"); + + //create some data + + builder.setBytes("0x1001004", "85 4f dc 77"); + builder.applyDataType("0x1001004", new Pointer32DataType(), 1); + builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true); + builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE, + true); + builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, true); + + //create some undefined data + builder.setBytes("0x1001500", "4e 00 65 00 77 00"); + builder.setBytes("0x1003000", "55 00"); + + return builder.getProgram(); + } + + @Test + public void testBinaryInvalidEntry() { + // enter a non-binary digit; the search field should not accept it + setInput("2"); + + assertEquals("", getInput()); + } + + @Test + public void testBinaryEnterSpaces() { + // verify that more than 8 digits are allowed if spaces are entered + setInput("01110000 01110000"); + assertEquals("01110000 01110000", getInput()); + } + + @Test + public void testBinaryPasteNumberWithPrefix() { + // paste a number with a binary prefix; + // the prefix should be removed before the insertion + setInput("0b00101010"); + assertEquals("00101010", getInput()); + + setInput("0B1010 10"); + assertEquals("1010 10", getInput()); + } + + @Test + public void testBinarySearch() throws Exception { + + goTo(0x01001000); + + setInput("00010100 11111111"); + + performSearchNext(addr(0x01002d06)); + + } + + @Test + public void testBinarySearchNext() throws Exception { + + goTo(0x01001000); + + setInput("01110101"); + + //@formatter:off + List
addrs = addrs(0x01002d06, + 0x01002d11, + 0x01002d2c, + 0x01002d2f, + 0x01002d37, + 0x01002d3a, + 0x01002d3e, + 0x01002d52, + 0x01002d55, + 0x01002d58, + 0x01002d5b); + //@formatter:on + + performSearchNext(addrs); + } + + @Test + public void testBinarySearchNextAlign4() throws Exception { + goTo(0x01001000); + setInput("01110101"); + + setAlignment(4); + + //the bytes are at the right alignment value but the code units are not + List
addrs = addrs(0x01002d2f, 0x01002d37, 0x01002d5b); + performSearchNext(addrs); + } + + @Test + public void testBinarySearchAll() throws Exception { + setInput("11110110"); + + performSearchAll(); + waitForSearch(1); + + checkMarkerSet(addrs(0x1002d28)); + } + + @Test + public void testBinarySearchAllAlign4() throws Exception { + setInput("11111111 01110101"); + + setAlignment(4); + + performSearchAll(); + waitForSearch(2); + + List
startList = addrs(0x1002d2c, 0x1002d58); + + checkMarkerSet(startList); + } + + @Test + public void testCodeUnitScope_DefinedData() throws Exception { + // + // Turn on Defined Data scope and make sure only that scope yields matches + // + goTo(0x1001000);// start of program; pointer data + + setCodeTypeFilters(false, true, false); + + setInput("10000101"); + performSearchNext(addr(0x1001004)); + + // Turn off Defined Data scope and make sure we have no match at the expected address + goTo(0x1001000);// start of program; pointer data + setCodeTypeFilters(true, false, true); + performSearchNext(addr(0x1002d27)); // this is in an instruction past the data match + + // try backwards + goTo(0x1002000); + setCodeTypeFilters(false, true, false); + + performSearchPrevious(addrs(0x1001004)); + } + + @Test + public void testCodeUnitScope_UndefinedData() throws Exception { + // + // Turn on Undefined Data scope and make sure only that scope yields matches + // + goTo(0x1001000); + setCodeTypeFilters(false, false, true); + + setInput("01100101"); + performSearchNext(addr(0x1001502)); + + // Turn off Undefined Data scope and make sure we have no match at the expected address + goTo(0x1001500); + setCodeTypeFilters(true, true, false); + performSearchNext(addr(0x1001708)); + // try backwards + + goTo(0x1003000); + setCodeTypeFilters(false, false, true); + + performSearchPrevious(addrs(0x1001502)); + } + + @Test + public void testBinarySearchPrevious() throws Exception { + goTo(0x01002d6b); + + setInput("11111111 00010101"); + + List
addrs = addrs(0x01002d5e, 0x01002d4a, 0x01002d41, 0x01002d1f); + + performSearchPrevious(addrs); + } + + @Test + public void testBinaryWildcardSearch() throws Exception { + goTo(0x01001000); + + setInput("010101xx 10001011"); + + List
addrs = addrs(0x01002cf5, 0x01002cfc, 0x01002d47); + + performSearchNext(addrs); + } + + @Test + public void testBinaryWildcardSearchAll() throws Exception { + + setInput("10001011 1111xxxx"); + performSearchAll(); + waitForSearch(4); + + List
addrs = addrs(0x1002d0b, 0x1002d25, 0x1002d48, 0x1002d64); + + checkMarkerSet(addrs); + } + +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchHexTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchHexTest.java similarity index 60% rename from Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchHexTest.java rename to Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchHexTest.java index c944729927..16806af7b9 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchHexTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchHexTest.java @@ -4,36 +4,32 @@ * 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.searchmem; +package ghidra.features.base.memsearch; import static org.junit.Assert.*; -import java.awt.Component; -import java.awt.Container; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.List; -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.TitledBorder; - import org.junit.Before; import org.junit.Test; import docking.widgets.fieldpanel.support.Highlight; import ghidra.GhidraOptions; -import ghidra.app.plugin.core.table.TableComponentProvider; import ghidra.app.services.MarkerSet; import ghidra.app.util.viewer.field.BytesFieldFactory; +import ghidra.features.base.memsearch.bytesource.ProgramSearchRegion; +import ghidra.features.base.memsearch.format.SearchFormat; import ghidra.framework.options.Options; import ghidra.program.database.ProgramBuilder; import ghidra.program.model.address.Address; @@ -50,11 +46,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest { super(); } - @Override @Before + @Override public void setUp() throws Exception { super.setUp(); - selectRadioButton("Hex"); + setSearchFormat(SearchFormat.HEX); } @Override @@ -115,98 +111,43 @@ public class MemSearchHexTest extends AbstractMemSearchTest { @Test public void testDisplayDialog() throws Exception { assertTrue(searchAction.isEnabled()); - - // dig up the components of the dialog - dialog = waitForDialogComponent(MemSearchDialog.class); - assertNotNull(dialog); - assertNotNull(valueComboBox); - assertNotNull(hexLabel); - - assertButtonState("Hex", true, true); - assertButtonState("String", true, false); - assertButtonState("Decimal", true, false); - assertButtonState("Binary", true, false); - assertButtonState("Regular Expression", true, false); - assertButtonState("Little Endian", true, true); - assertButtonState("Big Endian", true, false); - assertButtonState("Search Selection", false, false); - - assertEnabled("Next", false); - assertEnabled("Previous", false); - assertEnabled("Search All", false); - assertEnabled("Dismiss", true); - - JPanel p = findTitledJPanel(pane, "Format Options"); - assertNotNull(p); - assertTrue(p.isVisible()); + assertTrue(searchProvider.isVisible()); } @Test public void testHexInvalidEntry() { // enter a non-hex digit; the search field should not accept it - setValueText("z"); + setInput("z"); - assertEquals("", valueField.getText()); + assertEquals("", getInput()); } @Test public void testHexEnterSpaces() { // verify that more than 16 digits are allowed if spaces are entered - setValueText("01 23 45 67 89 a b c d e f 1 2 3"); - assertEquals("01 23 45 67 89 a b c d e f 1 2 3", valueField.getText()); + setInput("01 23 45 67 89 a b c d e f 1 2 3"); + assertEquals("01 23 45 67 89 a b c d e f 1 2 3", getInput()); } - @Test - public void testHexNoSpaces() { - // enter a hex sequence (no spaces) more than 2 digits; - // the hex label should display the bytes reversed - setValueText("012345678"); - assertEquals("78 56 34 12 00 ", hexLabel.getText()); - } - - @Test - public void testHexBigLittleEndian() throws Exception { - // switch between little and big endian; - // verify the hex label - setValueText("012345678"); - - pressButtonByText(pane, "Big Endian", true); - - waitForSwing(); - assertEquals("00 12 34 56 78 ", hexLabel.getText()); - } - - @Test - public void testHexSpaceBetweenBytes() throws Exception { - // enter a hex sequence where each byte is separated by a space; - // ensure that the byte order setting has no effect on the sequence - setValueText("01 23 45 67 89"); - assertEquals("01 23 45 67 89", valueField.getText()); - - pressButtonByText(pane, "Big Endian", true); - - assertEquals("01 23 45 67 89", valueField.getText()); - } - @Test public void testHexPasteNumberWithPrefixAndSuffix() { // paste a number with a hex prefix; // the prefix should be removed before the insertion - setValueText("0xabcdef"); - assertEquals("abcdef", valueField.getText()); - - setValueText("$68000"); - assertEquals("68000", valueField.getText()); - + setInput("0xabcdef"); + assertEquals("abcdef", getInput()); + + setInput("$68000"); + assertEquals("68000", getInput()); + // same for 'h' the suffix - setValueText("ABCDEFh"); - assertEquals("ABCDEF", valueField.getText()); - + setInput("ABCDEFh"); + assertEquals("ABCDEF", getInput()); + // should also somehow work with leading and trailing white spaces - setValueText(" 0X321 "); - assertEquals("321", valueField.getText().strip()); - setValueText(" 123H "); - assertEquals("123", valueField.getText().strip()); + setInput(" 0X321 "); + assertEquals("321", getInput().strip()); + setInput(" 123H "); + assertEquals("123", getInput().strip()); } @Test @@ -216,9 +157,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest { List
addrs = addrs(0x1002d06, 0x1002d2c, 0x1002d50); - setValueText("14 ff"); + setInput("14 ff"); - performSearchTest(addrs, "Next"); + performSearchNext(addrs); } @Test @@ -248,22 +189,21 @@ public class MemSearchHexTest extends AbstractMemSearchTest { ); //@formatter:on - setValueText("75"); + setInput("75"); - performSearchTest(addrs, "Next"); + performSearchNext(addrs); } @Test public void testHexContiguousSelection() throws Exception { - makeSelection(tool, program, range(0x01002cf5, 0x01002d6d)); assertSearchSelectionSelected(); - setValueText("50"); + setInput("50"); - performSearchTest(addrs(0x01002d1c), "Next"); + performSearchNext(addrs(0x01002d1c)); } @Test @@ -273,11 +213,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest { assertSearchSelectionSelected(); - setValueText("50"); + setInput("50"); List
addrs = addrs(0x01002d1c, 0x01004120, 0x01004200, 0x01004247); - performSearchTest(addrs, "Next"); + performSearchNext(addrs); } @Test @@ -290,17 +230,14 @@ public class MemSearchHexTest extends AbstractMemSearchTest { makeSelection(tool, program, range(0x01002cf5, 0x01002d6d), range(0x01004100, 0x010041ff)); - // select Search All option to turn off searching only in selection - assertButtonState("Search All", true, false); - - // Note: this is 'Search All' for the search type, not the JButton on the button panel - pressButtonByText(pane, "Search All"); + assertSearchSelectionSelected(); + setSearchSelectionOnly(false); List
addrs = addrs(0x01002d1c, 0x01004120, 0x01004200, 0x01004247); - setValueText("50"); + setInput("50"); - performSearchTest(addrs, "Next"); + performSearchNext(addrs); } @Test @@ -309,10 +246,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest { // test the marker stuff goTo(0x1004180); - setValueText("50"); - pressSearchAllButton(); + setInput("50"); - waitForSearch("Search Memory - ", 4); + performSearchAll(); + + waitForSearch(4); List
addrs = addrs(0x01002d1c, 0x01004120, 0x01004200, 0x01004247); @@ -322,11 +260,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest { @Test public void testHexSearchAll2() throws Exception { // enter search string for multiple byte match - // ff 15 - setValueText("ff 15"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 5); + setInput("ff 15"); + performSearchAll(); + + waitForSearch(5); List
addrs = addrs(0x01002d1f, 0x01002d41, 0x01002d4a, 0x01002d5e, 0x010029bd); @@ -335,16 +273,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest { @Test public void testHexSearchAllAlign8() throws Exception { - // QueryResults should get displayed - // test the marker stuff + setAlignment(8); + setInput("8b"); + performSearchAll(); - JTextField alignment = (JTextField) findComponentByName(dialog.getComponent(), "Alignment"); - setText(alignment, "8"); - - setValueText("8b"); - pressSearchAllButton(); - - waitForSearch("Search Memory - ", 1); + waitForSearch(1); checkMarkerSet(addrs(0x01002d48)); } @@ -352,11 +285,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest { @Test public void testHexHighlight() throws Exception { - setValueText("80 00 01"); + setInput("80 00 01"); - pressSearchAllButton(); + performSearchAll(); - waitForSearch("Search Memory - ", 1); + waitForSearch(1); Highlight[] h = getByteHighlights(addr(0x10040d9), "8b 0d 58 80 00 01"); assertEquals(1, h.length); @@ -366,10 +299,10 @@ public class MemSearchHexTest extends AbstractMemSearchTest { @Test public void testHexHighlight2() throws Exception { - setValueText("01 8b"); - pressSearchAllButton(); + setInput("01 8b"); + performSearchAll(); - waitForSearch("Search Memory - ", 3); + waitForSearch(3); Highlight[] h = getByteHighlights(addr(0x10029bd), "ff 15 d4 10 00 01"); assertEquals(1, h.length); @@ -379,10 +312,10 @@ public class MemSearchHexTest extends AbstractMemSearchTest { @Test public void testHexHighlight3() throws Exception { - setValueText("d8 33 f6 3b"); - pressSearchAllButton(); + setInput("d8 33 f6 3b"); + performSearchAll(); - waitForSearch("Search Memory - ", 1); + waitForSearch(1); Highlight[] h = getByteHighlights(addr(0x10029c3), "8b d8"); assertEquals(1, h.length); @@ -393,10 +326,10 @@ public class MemSearchHexTest extends AbstractMemSearchTest { @Test public void testHexHighlight4() throws Exception { - setValueText("fd ff ff"); - pressSearchAllButton(); + setInput("fd ff ff"); + performSearchAll(); - waitForSearch("Search Memory - ", 1); + waitForSearch(1); Highlight[] h = getByteHighlights(addr(0x10035f8), "b9 30 fd ff ff"); assertEquals(1, h.length); @@ -410,10 +343,10 @@ public class MemSearchHexTest extends AbstractMemSearchTest { opt.setInt(BytesFieldFactory.BYTE_GROUP_SIZE_MSG, 3); opt.setString(BytesFieldFactory.DELIMITER_MSG, "#@#"); - setValueText("fd ff ff"); - pressSearchAllButton(); + setInput("fd ff ff"); + performSearchAll(); - waitForSearch("Search Memory - ", 1); + waitForSearch(1); Highlight[] h = getByteHighlights(addr(0x10035f8), "b930fd#@#ffff"); assertEquals(1, h.length); @@ -424,22 +357,19 @@ public class MemSearchHexTest extends AbstractMemSearchTest { @Test public void testMarkersRemoved() throws Exception { - setValueText("ff 15"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 5); + setInput("ff 15"); + performSearchAll(); + waitForSearch(5); - List
startList = addrs(0x01002d1f, 0x01002d41, 0x01002d4a, 0x01002d5e, 0x010029bd); + String title = searchProvider.getTitle(); - checkMarkerSet(startList); - - TableComponentProvider[] providers = tableServicePlugin.getManagedComponents(); - assertEquals(1, providers.length); - assertTrue(tool.isVisible(providers[0])); + MarkerSet markerSet = markerService.getMarkerSet(title, program); + assertNotNull(markerSet); //close it - runSwing(() -> providers[0].closeComponent()); + runSwing(() -> searchProvider.closeComponent()); - MarkerSet markerSet = markerService.getMarkerSet("Memory Search Results", program); + markerSet = markerService.getMarkerSet("Memory Search Results", program); assertNull(markerSet); } @@ -448,11 +378,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest { goTo(0x01001000); - setValueText("75"); - pressButtonByText(pane, "Previous"); - waitForSearchTask(); + setInput("75"); - assertEquals("Not Found", getStatusText()); + performSearchPrevious(Collections.emptyList()); } @Test @@ -463,11 +391,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest { //start at 1002d6d and search backwards goTo(0x1002d6d); - setValueText("ff 15"); + setInput("ff 15"); List
addrs = addrs(0x01002d5e, 0x01002d4a, 0x01002d41, 0x01002d1f, 0x010029bd); - performSearchTest(addrs, "Previous"); + performSearchPrevious(addrs); } @Test @@ -477,13 +405,13 @@ public class MemSearchHexTest extends AbstractMemSearchTest { goTo(0x1002d6d); - setAlignment("2"); + setAlignment(2); - setValueText("ff 15"); + setInput("ff 15"); List
addrs = addrs(0x01002d5e, 0x01002d4a); - performSearchTest(addrs, "Previous"); + performSearchPrevious(addrs); } @Test @@ -495,9 +423,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest { assertSearchSelectionSelected(); - setValueText("50"); + setInput("50"); - performSearchTest(addrs(0x01002d1c), "Previous"); + performSearchPrevious(addrs(0x01002d1c)); } @Test @@ -511,9 +439,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest { List
addrs = addrs(0x01004247, 0x01004200, 0x01004120, 0x01002d1c); - setValueText("50"); + setInput("50"); - performSearchTest(addrs, "Previous"); + performSearchPrevious(addrs); } @Test @@ -522,9 +450,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest { List
addrs = addrs(0x01002d0b, 0x01002d25, 0x01002d48, 0x01002d64); - setValueText("8b f?"); + setInput("8b f?"); - performSearchTest(addrs, "Next"); + performSearchNext(addrs); } @Test @@ -539,9 +467,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest { List
addrs = addrs(0x01001004, 0x01002d27); - setValueText("85 ?"); + setInput("85 ?"); - performSearchTest(addrs, "Next"); + performSearchNext(addrs); } @Test @@ -556,9 +484,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest { List
addrs = addrs(0x01001004, 0x01002d27); - setValueText("85 ."); + setInput("85 ."); - performSearchTest(addrs, "Next"); + performSearchNext(addrs); } @Test @@ -568,9 +496,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest { List
addrs = addrs(0x01002d64, 0x01002d48, 0x01002d25, 0x01002d0b); - setValueText("8b f?"); + setInput("8b f?"); - performSearchTest(addrs, "Previous"); + performSearchPrevious(addrs); } @Test @@ -578,9 +506,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest { // QueryResults should get displayed // test the marker stuff - setValueText("8b f?"); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 4); + setInput("8b f?"); + performSearchAll(); + waitForSearch(4); List
addrs = addrs(0x01002d64, 0x01002d48, 0x01002d25, 0x01002d0b); @@ -589,96 +517,39 @@ public class MemSearchHexTest extends AbstractMemSearchTest { @Test public void testHexByteOrder() throws Exception { - selectRadioButton("Big Endian"); + setBigEndian(true); goTo(0x01001000); - setValueText("8bec"); + setInput("8bec"); - performSearchTest(addrs(0x01002cf6), "Next"); + performSearchNext(addrs(0x01002cf6)); } @Test public void testSearchInOtherSpace() throws Exception { goTo(0x01001000); - setValueText("01 02 03 04 05 06 07 08 09"); + setInput("01 02 03 04 05 06 07 08 09"); - selectRadioButton("All Blocks"); + addSearchRegion(ProgramSearchRegion.OTHER, true); List
addrs = addrs(program.getAddressFactory().getAddress("otherOverlay:1")); - performSearchTest(addrs, "Next"); + performSearchNext(addrs); } @Test - public void testValueComboBox() throws Exception { - setValueText("00 65"); + public void testRepeatSearchForwardThenBackwards() throws Exception { - pressSearchButton("Next"); - setValueText(""); + setInput("8b f8"); + performSearchNext(addr(0x01002d0b)); - setValueText("d1 e1"); - pressSearchButton("Next"); - setValueText(""); - - setValueText("0123456"); - pressSearchButton("Next"); - setValueText(""); - - // the combo box should list most recently entered values - DefaultComboBoxModel cbModel = (DefaultComboBoxModel) valueComboBox.getModel(); - assertEquals(3, cbModel.getSize()); - assertEquals("0123456", cbModel.getElementAt(0)); - assertEquals("d1 e1", cbModel.getElementAt(1)); - assertEquals("00 65", cbModel.getElementAt(2)); - } - - @Test - public void testRepeatSearchAction() throws Exception { - - setValueText("8b f8"); - pressSearchButton("Next"); - - assertEquals(addr(0x01002d0b), currentAddress()); - - repeatSearch(); + repeatSearchForward(); assertEquals(addr(0x01002d48), currentAddress()); + + repeatSearchBackward(); + + assertEquals(addr(0x01002d0b), currentAddress()); } - - @Test - public void testSearchBackwardsWhenAtFirstAddressWithCurrentMatch() throws Exception { - setValueText("00"); - - pressSearchButton("Next"); - pressSearchButton("Previous"); - pressSearchButton("Previous"); - - assertEquals("Not Found", getStatusText()); - } - -//================================================================================================== -// Private Methods -//================================================================================================== - - private JPanel findTitledJPanel(Container container, String title) { - if (container instanceof JPanel) { - JPanel p = (JPanel) container; - Border b = p.getBorder(); - if ((b instanceof TitledBorder) && ((TitledBorder) b).getTitle().equals(title)) { - return p; - } - } - Component[] comps = container.getComponents(); - for (Component element : comps) { - if (element instanceof Container) { - JPanel p = findTitledJPanel((Container) element, title); - if (p != null) { - return p; - } - } - } - return null; - } - } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchNumbersTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchNumbersTest.java new file mode 100644 index 0000000000..202af6a4b2 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchNumbersTest.java @@ -0,0 +1,473 @@ +/* ### + * 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.features.base.memsearch; + +import static org.junit.Assert.*; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.Pointer32DataType; +import ghidra.program.model.listing.Program; + +/** + * Tests for searching for decimal values in memory. + */ +public class MemSearchNumbersTest extends AbstractMemSearchTest { + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + setSearchFormat(SearchFormat.DECIMAL); + setDecimalSize(4); + } + + @Override + protected Program buildProgram() throws Exception { + ProgramBuilder builder = new ProgramBuilder("TestX86", ProgramBuilder._X86); + builder.createMemory(".text", Long.toHexString(0x1001000), 0x6600); + builder.createMemory(".data", Long.toHexString(0x1008000), 0x600); + builder.createMemory(".rsrc", Long.toHexString(0x100A000), 0x5400); + builder.createMemory(".bound_import_table", Long.toHexString(0xF0000248), 0xA8); + builder.createMemory(".debug_data", Long.toHexString(0xF0001300), 0x1C); + + //create and disassemble a function + builder.setBytes( + "0x01002cf5", + "55 8b ec 83 7d 14 00 56 8b 35 e0 10 00 01 57 74 09 ff 75 14 ff d6 8b f8 eb 02 " + + "33 ff ff 75 10 ff d6 03 c7 8d 44 00 02 50 6a 40 ff 15 dc 10 00 01 8b f0 85 f6 " + + "74 27 56 ff 75 14 ff 75 10 e8 5c ff ff ff ff 75 18 ff 75 0c 56 ff 75 08 ff 15 " + + "04 12 00 01 56 8b f8 ff 15 c0 10 00 01 eb 14 ff 75 18 ff 75 0c ff 75 10 ff 75 " + + "08 ff 15 04 12 00 01 8b f8 8b c7 5f 5e 5d c2 14"); + builder.disassemble("0x01002cf5", 0x121, true); + builder.createFunction("0x01002cf5"); + + //create some data + + builder.setBytes("0x1001004", "85 4f dc 77"); + builder.applyDataType("0x1001004", new Pointer32DataType(), 1); + builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true); + builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE, + true); + builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, false); + + //create some undefined data + builder.setBytes("0x1001500", "4e 00 65 00 77 00"); + builder.setBytes("0x1003000", "55 00"); + builder.setBytes("0x1004100", "64 00 00 00");//100 dec + builder.setBytes("0x1004120", "50 ff 75 08");//7.4027124e-34 float + builder.setBytes("0x1004135", "64 00 00 00");//100 dec + builder.setBytes("0x1004200", "50 ff 75 08 e8 8d 3c 00");//1.588386874245921e-307 + builder.setBytes("0x1004247", "50 ff 75 08");//7.4027124e-34 float + builder.setBytes("0x1004270", "65 00 6e 00 64 00 69 00");//29555302058557541 qword + + return builder.getProgram(); + } + + @Test + public void testInvalidEntry() throws Exception { + // enter non-numeric value + setInput("z"); + assertEquals("", getInput()); + } + + @Test + public void testValueTooLarge() throws Exception { + setDecimalSize(1); + + setInput("262"); + assertEquals("", getInput()); + } + + @Test + public void testNegativeValueEntered() throws Exception { + // enter a negative value; the hexLabel should show the correct + // byte sequence + setSearchFormat(SearchFormat.DECIMAL); + setDecimalSize(2); + setInput("-1234"); + assertEquals("2e fb", getByteString()); + + setDecimalSize(1); + assertEquals("46 -5", getInput()); + + setInput("-55"); + assertEquals("c9", getByteString()); + + setDecimalSize(4); + assertEquals("-55", getInput()); + assertEquals("c9 ff ff ff", getByteString()); + + setDecimalSize(8); + assertEquals("-55", getInput()); + assertEquals("c9 ff ff ff ff ff ff ff", getByteString()); + + setSearchFormat(SearchFormat.FLOAT); + assertEquals("00 00 5c c2", getByteString()); + + setSearchFormat(SearchFormat.DOUBLE); + assertEquals("00 00 00 00 00 80 4b c0", getByteString()); + } + + @Test + public void testMulipleValuesEntered() throws Exception { + // enter values separated by a space; values should be accepted + setDecimalSize(1); + setInput("12 34 56 78"); + assertEquals("0c 22 38 4e", getByteString()); + + setDecimalSize(2); + assertEquals("8716 20024", getInput()); + assertEquals("0c 22 38 4e", getByteString()); + + setDecimalSize(4); + assertEquals("1312301580", getInput()); + assertEquals("0c 22 38 4e", getByteString()); + + setDecimalSize(8); + assertEquals("1312301580", getInput()); + assertEquals("0c 22 38 4e 00 00 00 00", getByteString()); + + setSearchFormat(SearchFormat.FLOAT); + assertEquals("1312301580", getInput()); + assertEquals("44 70 9c 4e", getByteString()); + + setSearchFormat(SearchFormat.DOUBLE); + assertEquals("1312301580", getInput()); + assertEquals("00 00 00 83 08 8e d3 41", getByteString()); + + } + + @Test + public void testByteOrder() throws Exception { + setBigEndian(true); + setInput("12 34 56 78"); + setDecimalSize(1); + setBigEndian(true); + // should be unaffected + assertEquals("0c 22 38 4e", getByteString()); + + setDecimalSize(2); + assertEquals("3106 14414", getInput()); + assertEquals("0c 22 38 4e", getByteString()); + + setDecimalSize(4); + assertEquals("203569230", getInput()); + assertEquals("0c 22 38 4e", getByteString()); + + setDecimalSize(8); + assertEquals("203569230", getInput()); + assertEquals("00 00 00 00 0c 22 38 4e", getByteString()); + + setSearchFormat(SearchFormat.FLOAT); + assertEquals("203569230", getInput()); + assertEquals("4d 42 23 85", getByteString()); + + setSearchFormat(SearchFormat.DOUBLE); + assertEquals("203569230", getInput()); + assertEquals("41 a8 44 70 9c 00 00 00", getByteString()); + } + + @Test + public void testFloatDoubleFormat() throws Exception { + setSearchFormat(SearchFormat.FLOAT); + + setInput("12.345"); + assertEquals("12.345", getInput()); + assertEquals("1f 85 45 41", getByteString()); + + setSearchFormat(SearchFormat.DOUBLE); + assertEquals("71 3d 0a d7 a3 b0 28 40", getByteString()); + } + + @Test + public void testSearchByte() throws Exception { + goTo(program.getMinAddress()); + + List
addrs = addrs(0x1002d3e, 0x1002d5b, 0x1004123, 0x1004203, 0x100424a); + + setDecimalSize(1); + setInput("8"); + + performSearchNext(addrs); + } + + @Test + public void testSearchByteBackward() throws Exception { + + goTo(0x01002d6d); + + setDecimalSize(1); + + setInput("8"); + + List
addrs = addrs(0x1002d5b, 0x1002d3e); + + performSearchPrevious(addrs); + } + + @Test + public void testSearchWord() throws Exception { + + goTo(program.getMinAddress()); + + setDecimalSize(2); + + setInput("20"); + + List
addrs = addrs(0x1002cf8, 0x1002d6b); + + performSearchNext(addrs); + } + + @Test + public void testSearchWordBackward() throws Exception { + + goTo(0x01002d6e); + + setDecimalSize(2); + + setInput("20"); + + List
addrs = addrs(0x1002d6b, 0x1002cf8); + + performSearchPrevious(addrs); + } + + @Test + public void testSearchDWord() throws Exception { + goTo(program.getMinAddress()); + + setDecimalSize(4); + + setInput("100"); + + List
addrs = addrs(0x1001708, 0x1004100, 0x1004135); + + performSearchNext(addrs); + } + + @Test + public void testSearchDWordBackward() throws Exception { + goTo(0x01005000); + + setDecimalSize(4); + + setInput("100"); + + List
addrs = addrs(0x1004135, 0x1004100, 0x1001708); + + performSearchPrevious(addrs); + } + + @Test + public void testSearchQWord() throws Exception { + goTo(program.getMinAddress()); + + setDecimalSize(8); + + setInput("29555302058557541"); + + performSearchNext(addrs(0x1004270)); + } + + @Test + public void testSearchQWordBackward() throws Exception { + + goTo(program.getMaxAddress()); + + setDecimalSize(8); + + setInput("29555302058557541"); + + performSearchPrevious(addrs(0x1004270)); + } + + @Test + public void testSearchFloat() throws Exception { + + goTo(program.getMinAddress()); + + setSearchFormat(SearchFormat.FLOAT); + + setInput("7.4027124e-34"); + + List
addrs = addrs(0x1004120, 0x1004200, 0x1004247); + + performSearchNext(addrs); + } + + @Test + public void testSearchFloatBackward() throws Exception { + + goTo(0x01005000); + + setSearchFormat(SearchFormat.FLOAT); + + setInput("7.4027124e-34"); + + List
addrs = addrs(0x1004247, 0x1004200, 0x1004120); + + performSearchPrevious(addrs); + } + + @Test + public void testSearchFloatBackwardAlign8() throws Exception { + + goTo(program.getMaxAddress()); + + setAlignment(8); + setSearchFormat(SearchFormat.FLOAT); + + setInput("7.4027124e-34"); + + List
addrs = addrs(0x1004200, 0x1004120); + + performSearchPrevious(addrs); + } + + @Test + public void testSearchDouble() throws Exception { + + goTo(program.getMinAddress()); + + setSearchFormat(SearchFormat.DOUBLE); + + setInput("1.588386874245921e-307"); + + List
addrs = addrs(0x1004200); + + performSearchNext(addrs); + } + + @Test + public void testSearchDoubleBackward() throws Exception { + + goTo(program.getMaxAddress()); + + setSearchFormat(SearchFormat.DOUBLE); + + setInput("1.588386874245921e-307"); + + List
addrs = addrs(0x1004200); + + performSearchPrevious(addrs); + } + + @Test + public void testSearchAllByte() throws Exception { + + setDecimalSize(1); + + setInput("8"); + performSearchAll(); + waitForSearch(5); + + List
addrs = addrs(0x1002d40, 0x1002d5d, 0x1004123, 0x1004203, 0x100424a); + + checkMarkerSet(addrs); + } + + @Test + public void testSearchAllWord() throws Exception { + + setDecimalSize(2); + + setInput("20"); + + performSearchAll(); + waitForSearch(2); + + List
addrs = addrs(0x1002cfa, 0x1002d6c); + + checkMarkerSet(addrs); + } + + @Test + public void testSearchAllWordAlign4() throws Exception { + setAlignment(4); + + setDecimalSize(2); + + setInput("20"); + + performSearchAll(); + waitForSearch(1); + + checkMarkerSet(addrs(0x1002d6c)); + } + + @Test + public void testSearchAllDWord() throws Exception { + + setDecimalSize(4); + + setInput("100"); + performSearchAll(); + waitForSearch(3); + + List
addrs = addrs(0x1001715, 0x1004100, 0x1004135); + + checkMarkerSet(addrs); + } + + @Test + public void testSearchAllQWord() throws Exception { + + setDecimalSize(8); + + setInput("29555302058557541"); + performSearchAll(); + waitForSearch(1); + + checkMarkerSet(addrs(0x1004270)); + } + + @Test + public void testSearchAllFloat() throws Exception { + + setSearchFormat(SearchFormat.FLOAT); + + setInput("7.4027124e-34"); + + performSearchAll(); + waitForSearch(3); + + List
addrs = addrs(0x1004120, 0x1004200, 0x1004247); + + checkMarkerSet(addrs); + } + + @Test + public void testSearchAllDouble() throws Exception { + + setSearchFormat(SearchFormat.DOUBLE); + + setInput("1.588386874245921e-307"); + + performSearchAll(); + waitForSearch(1); + + checkMarkerSet(addrs(0x1004200)); + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchRegExTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchRegExTest.java similarity index 62% rename from Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchRegExTest.java rename to Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchRegExTest.java index 8c55fcda1e..a0133b678b 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchRegExTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchRegExTest.java @@ -4,16 +4,16 @@ * 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.searchmem; +package ghidra.features.base.memsearch; import static org.junit.Assert.*; @@ -24,10 +24,11 @@ import org.junit.Before; import org.junit.Test; import docking.widgets.fieldpanel.support.Highlight; +import ghidra.features.base.memsearch.format.SearchFormat; import ghidra.program.database.ProgramBuilder; import ghidra.program.model.address.Address; import ghidra.program.model.data.Pointer32DataType; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Program; /** * Tests for searching memory for hex reg expression. @@ -38,7 +39,7 @@ public class MemSearchRegExTest extends AbstractMemSearchTest { @Before public void setUp() throws Exception { super.setUp(); - selectRadioButton("Regular Expression"); + setSearchFormat(SearchFormat.REG_EX); } @Override @@ -112,13 +113,11 @@ public class MemSearchRegExTest extends AbstractMemSearchTest { // NOTE: the following regular expression searches for 0x8b followed // by 0-10 occurrences of any character, followed by 0x56. - setValueText("\\x8b.{0,10}\\x56"); - - assertEquals("", hexLabel.getText()); + setInput("\\x8b.{0,10}\\x56"); List
addrs = addrs(0x01002cf6, 0x01002d25); - performSearchTest(addrs, "Next"); + performSearchNext(addrs); } @Test @@ -126,33 +125,18 @@ public class MemSearchRegExTest extends AbstractMemSearchTest { // // Turn on Instructions scope and make sure only that scope yields matches // - goTo(0x1002cf5); // 'ghidra' function address + goTo(0x1002cf4); // just before instruction hit + setCodeTypeFilters(true, false, false); // only accept instruction matches - selectCheckBox("Instructions", true); - selectCheckBox("Defined Data", false); - selectCheckBox("Undefined Data", false); - - setValueText("\\x55"); - pressSearchButton("Next"); - Address expectedSearchAddressHit = addr(0x1002cf5); - assertEquals( - "Did not find a hit at the next matching Instruction when we are searching Instructions", - expectedSearchAddressHit, cb.getCurrentLocation().getAddress()); + setInput("\\x55"); + performSearchNext(addr(0x1002cf5)); // Turn off Instructions scope and make sure we have no match at the expected address - goTo(0x1002cf5); // 'ghidra' function address + goTo(0x1002cf4); - selectCheckBox("Instructions", false); - selectCheckBox("Defined Data", true); - selectCheckBox("Undefined Data", true); - pressSearchButton("Next"); - assertTrue( - "Found a search match at an Instruction, even though no Instruction should be searched", - !expectedSearchAddressHit.equals(currentAddress())); + setCodeTypeFilters(false, true, true); // all but instruction matches + performSearchNext(addr(0x1003000)); // this is in undefined data - CodeUnit codeUnit = currentCodeUnit(); - assertTrue("Did not find a data match when searching instructions is disabled", - codeUnit instanceof Data); } @Test @@ -160,33 +144,18 @@ public class MemSearchRegExTest extends AbstractMemSearchTest { // // Turn on Defined Data scope and make sure only that scope yields matches // - goTo(0x1001004);// start of program; pointer data + goTo(0x1001000);// start of program + setCodeTypeFilters(false, true, false); // only accept defined data matches + setInput("\\x85"); - selectCheckBox("Instructions", false); - selectCheckBox("Defined Data", true); - selectCheckBox("Undefined Data", false); - - setValueText("\\x85"); - pressSearchButton("Next"); - Address expectedSearchAddressHit = addr(0x1001004); - assertEquals( - "Did not find a hit at the next matching Defined Data when we are searching Defined Data", - expectedSearchAddressHit, cb.getCurrentLocation().getAddress()); + performSearchNext(addr(0x1001004)); // Turn off Defined Data scope and make sure we have no match at the expected address - goTo(0x1001004);// start of program; pointer data + goTo(0x1001000);// start of program - selectCheckBox("Instructions", true); - selectCheckBox("Defined Data", false); - selectCheckBox("Undefined Data", true); - pressSearchButton("Next"); - assertTrue( - "Found a search match at a Defined Data, even though no Defined Data should be searched", - !expectedSearchAddressHit.equals(currentAddress())); + setCodeTypeFilters(true, false, true); // don't accept defined data + performSearchNext(addr(0x1002d27)); - CodeUnit codeUnit = currentCodeUnit(); - assertTrue("Did not find a instruction match when searching defined data is disabled", - codeUnit instanceof Instruction); } @Test @@ -194,33 +163,19 @@ public class MemSearchRegExTest extends AbstractMemSearchTest { // // Turn on Undefined Data scope and make sure only that scope yields matches // - goTo(0x1004270); + goTo(0x1004269); - selectCheckBox("Instructions", false); - selectCheckBox("Defined Data", false); - selectCheckBox("Undefined Data", true); + setCodeTypeFilters(false, false, true); // only accept undefined data matches + setInput("\\x65"); - setValueText("\\x65"); - pressSearchButton("Next"); - Address expectedSearchAddressHit = addr(0x1004270); - assertEquals( - "Did not find a hit at the next matching Undefined Data when we are searching Undefined Data", - expectedSearchAddressHit, cb.getCurrentLocation().getAddress()); + performSearchNext(addr(0x1004270)); // Turn off Undefined Data scope and make sure we have no match at the expected address - goTo(0x1004270); + setCodeTypeFilters(true, true, false); // don't accept undefined data + goTo(0x1004260); - selectCheckBox("Instructions", true); - selectCheckBox("Defined Data", true); - selectCheckBox("Undefined Data", false); - pressSearchButton("Next"); - assertTrue( - "Found a search match at an Undefined Data, even though no Undefined Data should be searched", - !expectedSearchAddressHit.equals(currentAddress())); + performSearchNext(addr(0x1004300)); // this is defined data past where we undefined match - CodeUnit codeUnit = currentCodeUnit(); - assertTrue("Did not find a data match when searching undefined data is disabled", - codeUnit instanceof Data); } @Test @@ -228,13 +183,11 @@ public class MemSearchRegExTest extends AbstractMemSearchTest { // NOTE: the following regular expression searches for 0x56 followed // by 0-10 occurrences of any character, followed by 0x10. - setValueText("\\x56.{0,10}\\x10"); + setInput("\\x56.{0,10}\\x10"); - assertEquals("", hexLabel.getText()); + setAlignment(4); - setAlignment("4"); - - performSearchTest(addrs(0x01002cfc), "Next"); + performSearchNext(addrs(0x01002cfc)); } @Test @@ -242,14 +195,12 @@ public class MemSearchRegExTest extends AbstractMemSearchTest { // Note: the following regular expression searches for 0x56 followed // by 0-10 occurrences of any character, followed by 0x10. - setValueText("\\x56.{0,10}\\x10"); - - assertEquals("", hexLabel.getText()); + setInput("\\x56.{0,10}\\x10"); List
addrs = addrs(0x01002cfc, 0x01002d2b, 0x01002d47); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 3); + performSearchAll(); + waitForSearch(3); checkMarkerSet(addrs); } @@ -259,13 +210,12 @@ public class MemSearchRegExTest extends AbstractMemSearchTest { // NOTE: the following regular expression searches for 0xf4 followed // by 0x77. - setValueText("\\xf4\\x77"); - assertEquals("", hexLabel.getText()); + setInput("\\xf4\\x77"); List
addrs = addrs(0x01001042, 0x01001046); - pressSearchAllButton(); - waitForSearch("Search Memory - ", 2); + performSearchAll(); + waitForSearch(2); checkMarkerSet(addrs); } @@ -275,24 +225,21 @@ public class MemSearchRegExTest extends AbstractMemSearchTest { // NOTE: the following regular expression searches for 0x56 followed // by 0-10 occurrences of any character, followed by 0x10. - setValueText("\\x56.{0,10}\\x10"); + setInput("\\x56.{0,10}\\x10"); - assertEquals("", hexLabel.getText()); + setAlignment(4); - setAlignment("4"); - - pressSearchAllButton(); - waitForSearch("Search Memory - ", 1); + performSearchAll(); + waitForSearch(1); checkMarkerSet(addrs(0x01002cfc)); } @Test public void testRegExpHighlight() throws Exception { - setValueText("\\x6a\\x01"); - pressSearchAllButton(); - - waitForSearch("Search Memory - ", 3); + setInput("\\x6a\\x01"); + performSearchAll(); + waitForSearch(3); Highlight[] h = getByteHighlights(addr(0x10029cb), "6a 01"); assertEquals(1, h.length); @@ -302,10 +249,9 @@ public class MemSearchRegExTest extends AbstractMemSearchTest { @Test public void testRegExpHighlight2() throws Exception { - setValueText("\\x6a\\x01"); - pressSearchAllButton(); - - waitForSearch("Search Memory - ", 3); + setInput("\\x6a\\x01"); + performSearchAll(); + waitForSearch(3); Highlight[] h = getByteHighlights(addr(0x1002826), "6a 01"); assertEquals(1, h.length); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchDecimal1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchScanTest.java similarity index 51% rename from Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchDecimal1Test.java rename to Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchScanTest.java index 3c116e4c58..b1a38149b0 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MemSearchDecimal1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MemSearchScanTest.java @@ -4,16 +4,16 @@ * 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.searchmem; +package ghidra.features.base.memsearch; import java.nio.charset.StandardCharsets; import java.util.List; @@ -21,25 +21,28 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.features.base.memsearch.scan.Scanner; import ghidra.program.database.ProgramBuilder; import ghidra.program.model.address.Address; import ghidra.program.model.data.Pointer32DataType; import ghidra.program.model.listing.Program; /** - * Tests for searching for decimal values in memory. + * Tests for the search results "scan" feature */ -public class MemSearchDecimal1Test extends AbstractMemSearchTest { +public class MemSearchScanTest extends AbstractMemSearchTest { - public MemSearchDecimal1Test() { + public MemSearchScanTest() { super(); } - @Override @Before + @Override public void setUp() throws Exception { super.setUp(); - selectRadioButton("Decimal"); + setSearchFormat(SearchFormat.DECIMAL); + setDecimalSize(4); } @Override @@ -67,7 +70,8 @@ public class MemSearchDecimal1Test extends AbstractMemSearchTest { builder.setBytes("0x1001004", "85 4f dc 77"); builder.applyDataType("0x1001004", new Pointer32DataType(), 1); builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true); - builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE, true); + builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE, + true); builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, false); //create some undefined data @@ -84,26 +88,109 @@ public class MemSearchDecimal1Test extends AbstractMemSearchTest { } @Test - public void testSearchByteBackward() throws Exception { + public void testScanEquals() throws Exception { - goTo(0x01002d6d); + setInput("100"); + performSearchAll(); + waitForSearch(3); - selectRadioButton("Byte"); + List
addrs = addrs(0x1001715, 0x1004100, 0x1004135); + checkMarkerSet(addrs); - setValueText("8"); + setValue(addr(0x1004100), 101); - List
addrs = addrs(0x1002d5b, 0x1002d3e); + // only keep values that don't change + scan(Scanner.EQUALS); + waitForSearch(2); - performSearchTest(addrs, "Previous"); + // the address we changed should now be removed from the results + addrs = addrs(0x1001715, 0x1004135); + checkMarkerSet(addrs); } -//================================================================================================== -// Private Methods -//================================================================================================== + @Test + public void testScanNotEquals() throws Exception { - @Override - protected void showMemSearchDialog() { - super.showMemSearchDialog(); - selectRadioButton("Decimal"); + setInput("100"); + performSearchAll(); + waitForSearch(3); + + List
addrs = addrs(0x1001715, 0x1004100, 0x1004135); + checkMarkerSet(addrs); + + setValue(addr(0x1004100), 101); + + // only keep values that don't change + scan(Scanner.NOT_EQUALS); + waitForSearch(1); + + // the address we changed should now be removed from the results + addrs = addrs(0x1004100); + checkMarkerSet(addrs); + } + + @Test + public void testScanIncrement() throws Exception { + + setInput("100"); + performSearchAll(); + waitForSearch(3); + + List
addrs = addrs(0x1001715, 0x1004100, 0x1004135); + checkMarkerSet(addrs); + + setValue(addr(0x1004100), 101); + setValue(addr(0x1004135), 99); + + // only keep values that don't change + scan(Scanner.INCREASED); + waitForSearch(1); + + // the address we changed should now be removed from the results + addrs = addrs(0x1004100); + checkMarkerSet(addrs); + } + + @Test + public void testScanDecrement() throws Exception { + + setInput("100"); + performSearchAll(); + waitForSearch(3); + + List
addrs = addrs(0x1001715, 0x1004100, 0x1004135); + checkMarkerSet(addrs); + + setValue(addr(0x1004100), 101); + setValue(addr(0x1004135), 99); + + // only keep values that don't change + scan(Scanner.DECREASED); + waitForSearch(1); + + // the address we changed should now be removed from the results + addrs = addrs(0x1004135); + checkMarkerSet(addrs); + } + + private void scan(Scanner scanner) { + runSwing(() -> searchProvider.scan(scanner)); + } + + private void setValue(Address address, int value) throws Exception { + byte[] bytes = getBytes(value); + + int transactionID = program.startTransaction("test"); + memory.setBytes(address, bytes); + program.endTransaction(transactionID, true); + } + + private byte[] getBytes(int value) { + byte[] bytes = new byte[4]; + bytes[0] = (byte) (value & 0xff); + bytes[1] = (byte) ((value >> 8) & 0xff); + bytes[2] = (byte) ((value >> 16) & 0xff); + bytes[3] = (byte) ((value >> 24) & 0xff); + return bytes; } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MnemonicSearchPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MnemonicSearchPluginTest.java similarity index 80% rename from Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MnemonicSearchPluginTest.java rename to Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MnemonicSearchPluginTest.java index 4d39a84f11..aa954ad0a3 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchmem/MnemonicSearchPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/memsearch/MnemonicSearchPluginTest.java @@ -4,25 +4,21 @@ * 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.searchmem; +package ghidra.features.base.memsearch; import static org.junit.Assert.*; -import java.awt.Container; import java.awt.Window; -import javax.swing.JComboBox; -import javax.swing.JTextField; - import org.junit.*; import docking.action.DockingActionIf; @@ -30,8 +26,10 @@ import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.marker.MarkerManagerPlugin; import ghidra.app.plugin.core.programtree.ProgramTreePlugin; -import ghidra.app.plugin.core.searchmem.mask.MnemonicSearchPlugin; +import ghidra.app.plugin.core.searchmem.MemSearchPlugin; import ghidra.app.services.ProgramManager; +import ghidra.features.base.memsearch.gui.MemorySearchProvider; +import ghidra.features.base.memsearch.mnemonic.MnemonicSearchPlugin; import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; @@ -40,6 +38,7 @@ import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramSelection; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; +import ghidra.util.Swing; public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTest { private TestEnv env; @@ -50,6 +49,7 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes private DockingActionIf searchMnemonicNoOperandsNoConstAction; private DockingActionIf searchMnemonicOperandsConstAction; private CodeBrowserPlugin cb; + private MemorySearchProvider searchProvider; @Before public void setUp() throws Exception { @@ -97,18 +97,12 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", sel, program)); performAction(searchMnemonicOperandsNoConstAction, cb.getProvider(), true); + searchProvider = waitForComponentProvider(MemorySearchProvider.class); - MemSearchDialog dialog = waitForDialogComponent(MemSearchDialog.class); - assertNotNull(dialog); - Container component = dialog.getComponent(); - - @SuppressWarnings("unchecked") - JComboBox comboBox = findComponent(component, JComboBox.class); - JTextField valueField = (JTextField) comboBox.getEditor().getEditorComponent(); - + assertNotNull(searchProvider); assertEquals( "01010101 10001011 11101100 10000001 11101100 ........ ........ ........ ........", - valueField.getText().strip()); + getInput()); } @@ -119,17 +113,12 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes performAction(searchMnemonicNoOperandsNoConstAction, cb.getProvider(), true); - MemSearchDialog dialog = waitForDialogComponent(MemSearchDialog.class); - assertNotNull(dialog); - Container component = dialog.getComponent(); - - @SuppressWarnings("unchecked") - JComboBox comboBox = findComponent(component, JComboBox.class); - JTextField valueField = (JTextField) comboBox.getEditor().getEditorComponent(); + searchProvider = waitForComponentProvider(MemorySearchProvider.class); + assertNotNull(searchProvider); assertEquals( "01010... 10001011 11...... 10000001 11101... ........ ........ ........ ........", - valueField.getText().strip()); + getInput()); } @@ -140,17 +129,14 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes performAction(searchMnemonicOperandsConstAction, cb.getProvider(), true); - MemSearchDialog dialog = waitForDialogComponent(MemSearchDialog.class); - assertNotNull(dialog); - Container component = dialog.getComponent(); + performAction(searchMnemonicOperandsConstAction, cb.getProvider(), true); - @SuppressWarnings("unchecked") - JComboBox comboBox = findComponent(component, JComboBox.class); - JTextField valueField = (JTextField) comboBox.getEditor().getEditorComponent(); + searchProvider = waitForComponentProvider(MemorySearchProvider.class); + assertNotNull(searchProvider); assertEquals( "01010101 10001011 11101100 10000001 11101100 00000100 00000001 00000000 00000000", - valueField.getText().strip()); + getInput()); } /** @@ -186,4 +172,7 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes return program.getMinAddress().getNewAddress(offset); } + protected String getInput() { + return Swing.runNow(() -> searchProvider.getSearchInput()); + } } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequence.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequence.java new file mode 100644 index 0000000000..e04bc5af86 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequence.java @@ -0,0 +1,51 @@ +/* ### + * 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.features.base.memsearch.bytesequence; + +public class ByteArrayByteSequence implements ByteSequence { + + private final byte[] bytes; + + public ByteArrayByteSequence(byte... bytes) { + this.bytes = bytes; + } + + @Override + public int getLength() { + return bytes.length; + } + + @Override + public byte getByte(int i) { + return bytes[i]; + } + + @Override + public byte[] getBytes(int index, int size) { + if (index < 0 || index + size > bytes.length) { + throw new IndexOutOfBoundsException(); + } + byte[] results = new byte[size]; + System.arraycopy(bytes, index, results, 0, size); + return results; + } + + @Override + public boolean hasAvailableBytes(int index, int length) { + return index >= 0 && index + length <= getLength(); + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequenceTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequenceTest.java new file mode 100644 index 0000000000..b4385acb52 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/ByteArrayByteSequenceTest.java @@ -0,0 +1,134 @@ +/* ### + * 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.features.base.memsearch.bytesequence; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class ByteArrayByteSequenceTest { + private ByteSequence main = new ByteArrayByteSequence((byte) 0, (byte) 1, (byte) 2, (byte) 3); + private ByteSequence extra = new ByteArrayByteSequence((byte) 4, (byte) 5); + private ByteSequence extended = new ExtendedByteSequence(main, extra, 100); + + @Test + public void testSimpleByteSeqeunce() { + assertEquals(4, main.getLength()); + assertEquals(0, main.getByte(0)); + assertEquals(1, main.getByte(1)); + assertEquals(2, main.getByte(2)); + assertEquals(3, main.getByte(3)); + try { + main.getByte(4); + fail("Expected index out of bounds exception"); + } + catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testSimpleGetAvailableBytes() { + assertTrue(main.hasAvailableBytes(0, 1)); + assertTrue(main.hasAvailableBytes(0, 2)); + assertTrue(main.hasAvailableBytes(0, 3)); + assertTrue(main.hasAvailableBytes(0, 4)); + assertFalse(main.hasAvailableBytes(0, 5)); + + assertTrue(main.hasAvailableBytes(1, 1)); + assertTrue(main.hasAvailableBytes(1, 2)); + assertTrue(main.hasAvailableBytes(1, 3)); + assertFalse(main.hasAvailableBytes(1, 4)); + assertFalse(main.hasAvailableBytes(1, 5)); + + assertTrue(main.hasAvailableBytes(2, 1)); + assertTrue(main.hasAvailableBytes(2, 2)); + assertFalse(main.hasAvailableBytes(2, 3)); + assertFalse(main.hasAvailableBytes(2, 4)); + + assertTrue(main.hasAvailableBytes(3, 1)); + assertFalse(main.hasAvailableBytes(3, 2)); + assertFalse(main.hasAvailableBytes(3, 3)); + + assertFalse(main.hasAvailableBytes(4, 1)); + assertFalse(main.hasAvailableBytes(4, 2)); + + } + + @Test + public void testExtendedByteSeqeunce() { + assertEquals(4, extended.getLength()); + assertEquals(0, extended.getByte(0)); + assertEquals(1, extended.getByte(1)); + assertEquals(2, extended.getByte(2)); + assertEquals(3, extended.getByte(3)); + assertEquals(4, extended.getByte(4)); + assertEquals(5, extended.getByte(5)); + try { + extended.getByte(6); + fail("Expected index out of bounds exception"); + } + catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testExtendedGetAvailableBytes() { + + assertTrue(extended.hasAvailableBytes(0, 1)); + assertTrue(extended.hasAvailableBytes(0, 2)); + assertTrue(extended.hasAvailableBytes(0, 3)); + assertTrue(extended.hasAvailableBytes(0, 4)); + assertTrue(extended.hasAvailableBytes(0, 5)); + assertTrue(extended.hasAvailableBytes(0, 6)); + assertFalse(extended.hasAvailableBytes(0, 7)); + + assertTrue(extended.hasAvailableBytes(1, 1)); + assertTrue(extended.hasAvailableBytes(1, 2)); + assertTrue(extended.hasAvailableBytes(1, 3)); + assertTrue(extended.hasAvailableBytes(1, 4)); + assertTrue(extended.hasAvailableBytes(1, 5)); + assertFalse(extended.hasAvailableBytes(1, 6)); + assertFalse(extended.hasAvailableBytes(1, 7)); + + assertTrue(extended.hasAvailableBytes(2, 1)); + assertTrue(extended.hasAvailableBytes(2, 2)); + assertTrue(extended.hasAvailableBytes(2, 3)); + assertTrue(extended.hasAvailableBytes(2, 4)); + assertFalse(extended.hasAvailableBytes(2, 5)); + assertFalse(extended.hasAvailableBytes(2, 6)); + + assertTrue(extended.hasAvailableBytes(3, 1)); + assertTrue(extended.hasAvailableBytes(3, 2)); + assertTrue(extended.hasAvailableBytes(3, 3)); + assertFalse(extended.hasAvailableBytes(3, 4)); + assertFalse(extended.hasAvailableBytes(3, 5)); + + assertTrue(extended.hasAvailableBytes(4, 1)); + assertTrue(extended.hasAvailableBytes(4, 2)); + assertFalse(extended.hasAvailableBytes(4, 3)); + assertFalse(extended.hasAvailableBytes(4, 4)); + + assertTrue(extended.hasAvailableBytes(5, 1)); + assertFalse(extended.hasAvailableBytes(5, 2)); + assertFalse(extended.hasAvailableBytes(5, 3)); + + assertFalse(extended.hasAvailableBytes(6, 1)); + + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java new file mode 100644 index 0000000000..aaec032521 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java @@ -0,0 +1,116 @@ +/* ### + * 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.features.base.memsearch.bytesequence; + +import static org.junit.Assert.*; + +import java.util.Iterator; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch; +import ghidra.features.base.memsearch.matcher.MaskedByteSequenceByteMatcher; + +public class MaskedBytesSequenceByteMatcherTest { + + private ExtendedByteSequence byteSequence; + + @Before + public void setUp() { + + ByteSequence main = new ByteArrayByteSequence(makeBytes(1, 2, 3, 2, 4, 5, 2, 6, 2, 3, 2)); + ByteSequence extra = new ByteArrayByteSequence(makeBytes(4, 1, 1, 3, 2, 4)); + + byteSequence = new ExtendedByteSequence(main, extra, 100); + + } + + @Test + public void testSimplePatterWithOneMatchCrossingBoundary() { + + byte[] searchBytes = makeBytes(3, 2, 4); + ByteMatcher byteMatcher = new MaskedByteSequenceByteMatcher("", searchBytes, null); + + Iterator it = byteMatcher.match(byteSequence).iterator(); + + assertTrue(it.hasNext()); + assertEquals(new ByteMatch(2, 3), it.next()); + + assertTrue(it.hasNext()); + assertEquals(new ByteMatch(9, 3), it.next()); + + assertFalse(it.hasNext()); + + } + + @Test + public void testSimplePatterWithOneMatchCrossingBoundaryNoHasNextCalls() { + + byte[] searchBytes = makeBytes(3, 2, 4); + ByteMatcher byteMatcher = new MaskedByteSequenceByteMatcher("", searchBytes, null); + + Iterator it = byteMatcher.match(byteSequence).iterator(); + + assertEquals(new ByteMatch(2, 3), it.next()); + assertEquals(new ByteMatch(9, 3), it.next()); + assertNull(it.next()); + } + + @Test + public void testMaskPattern() { + + byte[] searchBytes = makeBytes(2, 0, 2); + byte[] masks = makeBytes(0xff, 0x00, 0xff); + ByteMatcher byteMatcher = + new MaskedByteSequenceByteMatcher("", searchBytes, masks, null); + + Iterator it = byteMatcher.match(byteSequence).iterator(); + + assertEquals(new ByteMatch(1, 3), it.next()); + assertEquals(new ByteMatch(6, 3), it.next()); + assertEquals(new ByteMatch(8, 3), it.next()); + assertNull(it.next()); + } + + @Test + public void testPatternStartButNotEnoughExtraBytes() { + byte[] searchBytes = makeBytes(6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + byte[] masks = makeBytes(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + ByteMatcher byteMatcher = + new MaskedByteSequenceByteMatcher("", searchBytes, masks, null); + + Iterator it = byteMatcher.match(byteSequence).iterator(); + assertFalse(it.hasNext()); + } + + @Test + public void testGetDescription() { + byte[] searchBytes = makeBytes(1, 2, 3, 0xaa); + ByteMatcher byteMatcher = new MaskedByteSequenceByteMatcher("", searchBytes, null); + + assertEquals("01 02 03 aa", byteMatcher.getDescription()); + } + + private static byte[] makeBytes(int... byteValues) { + byte[] bytes = new byte[byteValues.length]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) byteValues[i]; + } + return bytes; + } +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java new file mode 100644 index 0000000000..ff027deffc --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java @@ -0,0 +1,86 @@ +/* ### + * 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.features.base.memsearch.bytesequence; + +import static org.junit.Assert.*; + +import java.util.Iterator; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch; +import ghidra.features.base.memsearch.matcher.RegExByteMatcher; + +public class RegExByteMatcherTest { + private ExtendedByteSequence byteSequence; + + @Before + public void setUp() { + ByteSequence main = new ByteArrayByteSequence(makeBytes("one two three tw")); + ByteSequence extra = new ByteArrayByteSequence(makeBytes("o four two five")); + byteSequence = new ExtendedByteSequence(main, extra, 100); + + } + + @Test + public void testSimplePatternWithOneMatchCrossingBoundary() { + + ByteMatcher byteMatcher = new RegExByteMatcher("two", null); + + Iterator it = byteMatcher.match(byteSequence).iterator(); + + assertTrue(it.hasNext()); + assertEquals(new ByteMatch(4, 3), it.next()); + + assertTrue(it.hasNext()); + assertEquals(new ByteMatch(14, 3), it.next()); + + assertFalse(it.hasNext()); + + } + + @Test + public void testSimplePatternWithOneMatchCrossingBoundaryNoHasNextCalls() { + + ByteMatcher byteMatcher = new RegExByteMatcher("two", null); + + Iterator it = byteMatcher.match(byteSequence).iterator(); + + assertEquals(new ByteMatch(4, 3), it.next()); + assertEquals(new ByteMatch(14, 3), it.next()); + assertNull(it.next()); + } + + @Test + public void testNoMatch() { + + ByteMatcher byteMatcher = new RegExByteMatcher("apple", null); + + Iterator it = byteMatcher.match(byteSequence).iterator(); + assertFalse(it.hasNext()); + } + + private byte[] makeBytes(String string) { + byte[] bytes = new byte[string.length()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) string.charAt(i); + } + return bytes; + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/combiner/MemoryMatchCombinerTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/combiner/MemoryMatchCombinerTest.java new file mode 100644 index 0000000000..7c20715e4b --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/combiner/MemoryMatchCombinerTest.java @@ -0,0 +1,345 @@ +/* ### + * 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.features.base.memsearch.combiner; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.features.base.memsearch.searcher.MemoryMatch; +import ghidra.program.model.address.*; + +public class MemoryMatchCombinerTest { + + private GenericAddressSpace space; + + private MemoryMatch m1; + private MemoryMatch m2; + private MemoryMatch m3; + private MemoryMatch m4; + private MemoryMatch m5; + private MemoryMatch m6; + private MemoryMatch m7; + private MemoryMatch m8; + + List list1; + List list2; + List result; + + @Before + public void setUp() { + space = new GenericAddressSpace("test", 64, AddressSpace.TYPE_RAM, 0); + + m1 = createMatch(1, 4); + m2 = createMatch(2, 4); + m3 = createMatch(3, 4); + m4 = createMatch(4, 4); + m5 = createMatch(5, 4); + m6 = createMatch(6, 4); + m7 = createMatch(7, 4); + m8 = createMatch(8, 4); + } + + @Test + public void testUnionAllUnique() { + list1 = list(m1, m8); + list2 = list(m2, m3, m4); + result = union(list1, list2); + assertEquals(list(m1, m2, m3, m4, m8), result); + } + + @Test + public void testUnionWithEmptyList() { + list1 = list(m1, m8); + list2 = list(); + result = union(list1, list2); + assertEquals(list(m1, m8), result); + + list1 = list(); + list2 = list(m5, m7); + result = union(list1, list2); + assertEquals(list(m5, m7), result); + + } + + @Test + public void testUnionWithDups() { + list1 = list(m1, m2, m3); + list2 = list(m3, m4, m5); + result = union(list1, list2); + assertEquals(list(m1, m2, m3, m4, m5), result); + } + + @Test + public void testUnionWithDupsKeepsLonger() { + MemoryMatch m3_short = createMatch(3, 2); + MemoryMatch m3_long = createMatch(3, 8); + + list1 = list(m1, m2, m3); + list2 = list(m3_short, m4, m5); + result = union(list1, list2); + assertEquals(list(m1, m2, m3, m4, m5), result); + + list2 = list(m3_long, m4, m5); + result = union(list1, list2); + assertEquals(list(m1, m2, m3_long, m4, m5), result); + } + + @Test + public void testIntersectionAllUnique() { + list1 = list(m1, m8); + list2 = list(m2, m3, m4); + result = intersect(list1, list2); + assertEquals(list(), result); + } + + @Test + public void testIntersectionAllSame() { + list1 = list(m1, m2); + list2 = list(m1, m2); + result = intersect(list1, list2); + assertEquals(list(m1, m2), result); + } + + @Test + public void testIntersectionSomeSameSomeUnique() { + list1 = list(m1, m2, m3); + list2 = list(m2, m3, m4); + result = intersect(list1, list2); + assertEquals(list(m2, m3), result); + } + + @Test + public void testIntersectionKeepsLonger() { + MemoryMatch m4_long = createMatch(4, 8); + MemoryMatch m3_long = createMatch(3, 8); + list1 = list(m1, m2, m3, m4_long); + list2 = list(m1, m2, m3_long, m4); + result = intersect(list1, list2); + assertEquals(list(m1, m2, m3_long, m4_long), result); + } + + @Test + public void testXor() { + list1 = list(m1, m2, m3, m4); + list2 = list(m3, m4, m5, m6); + result = xor(list1, list2); + assertEquals(list(m1, m2, m5, m6), result); + } + + @Test + public void testXorNothingInCommon() { + list1 = list(m1, m2); + list2 = list(m3, m4); + result = xor(list1, list2); + assertEquals(list(m1, m2, m3, m4), result); + } + + @Test + public void testXorAllInCommon() { + list1 = list(m1, m2); + list2 = list(m1, m2); + result = xor(list1, list2); + assertEquals(list(), result); + } + + @Test + public void testXorWithEmpty() { + list1 = list(m1, m2); + list2 = list(); + result = xor(list1, list2); + assertEquals(list(m1, m2), result); + + list1 = list(); + list2 = list(m1, m2); + result = xor(list1, list2); + assertEquals(list(m1, m2), result); + } + + @Test + public void testXorLengthDontMatter() { + MemoryMatch m4_long = createMatch(4, 8); + MemoryMatch m3_short = createMatch(3, 2); + + list1 = list(m1, m2, m3, m4); + list2 = list(m3_short, m4_long, m5); + result = xor(list1, list2); + assertEquals(list(m1, m2, m5), result); + + list1 = list(m1, m2, m3_short, m4_long); + list2 = list(m3, m4, m5); + result = xor(list1, list2); + assertEquals(list(m1, m2, m5), result); + } + + @Test + public void testAMinusB() { + list1 = list(m1, m2, m3, m4); + list2 = list(m2, m3); + result = aMinusB(list1, list2); + assertEquals(list(m1, m4), result); + } + + @Test + public void testAMinusBSameSet() { + list1 = list(m1, m2, m3, m4); + list2 = list(m1, m2, m3, m4); + result = aMinusB(list1, list2); + assertEquals(list(), result); + } + + @Test + public void testAMinusBNothingInCommon() { + list1 = list(m1, m2, m3, m4); + list2 = list(m5, m6, m7, m8); + result = aMinusB(list1, list2); + assertEquals(list(m1, m2, m3, m4), result); + } + + @Test + public void testAMinusBEmptyList() { + list1 = list(); + list2 = list(m5, m6, m7, m8); + result = aMinusB(list1, list2); + assertEquals(list(), result); + + list1 = list(m5, m6, m7, m8); + list2 = list(); + result = aMinusB(list1, list2); + assertEquals(list(m5, m6, m7, m8), result); + } + + @Test + public void testAMinusBLengthDontMatter() { + MemoryMatch m4_long = createMatch(4, 8); + MemoryMatch m3_short = createMatch(3, 2); + + list1 = list(m1, m2, m3, m4); + list2 = list(m3_short, m4_long, m5); + result = aMinusB(list1, list2); + assertEquals(list(m1, m2), result); + + list1 = list(m1, m2, m3_short, m4_long); + list2 = list(m3, m4, m5); + result = aMinusB(list1, list2); + assertEquals(list(m1, m2), result); + } + + @Test + public void testBMinusA() { + list1 = list(m1, m2, m3, m4); + list2 = list(m2, m3, m4, m5, m6); + result = BMinusA(list1, list2); + assertEquals(list(m5, m6), result); + } + + @Test + public void testBMinusASameSet() { + list1 = list(m1, m2, m3, m4); + list2 = list(m1, m2, m3, m4); + result = BMinusA(list1, list2); + assertEquals(list(), result); + } + + @Test + public void testBMinusANothingInCommon() { + list1 = list(m1, m2, m3, m4); + list2 = list(m5, m6, m7, m8); + result = BMinusA(list1, list2); + assertEquals(list(m5, m6, m7, m8), result); + } + + @Test + public void testBMinusAEmptyList() { + list1 = list(); + list2 = list(m5, m6, m7, m8); + result = BMinusA(list1, list2); + assertEquals(list(m5, m6, m7, m8), result); + + list1 = list(m5, m6, m7, m8); + list2 = list(); + result = BMinusA(list1, list2); + assertEquals(list(), result); + } + + @Test + public void testBMinusALengthDontMatter() { + MemoryMatch m4_long = createMatch(4, 8); + MemoryMatch m3_short = createMatch(3, 2); + + list1 = list(m1, m2, m3, m4); + list2 = list(m3_short, m4_long, m5); + result = BMinusA(list1, list2); + assertEquals(list(m5), result); + + list1 = list(m1, m2, m3_short, m4_long); + list2 = list(m3, m4, m5); + result = BMinusA(list1, list2); + assertEquals(list(m5), result); + } + + private List xor(List matches1, List matches2) { + Combiner combiner = Combiner.XOR; + List results = new ArrayList<>(combiner.combine(matches1, matches2)); + Collections.sort(results); + return results; + } + + private List union(List matches1, List matches2) { + Combiner combiner = Combiner.UNION; + List results = new ArrayList<>(combiner.combine(matches1, matches2)); + Collections.sort(results); + return results; + } + + private List intersect(List matches1, List matches2) { + Combiner combiner = Combiner.INTERSECT; + List results = new ArrayList<>(combiner.combine(matches1, matches2)); + Collections.sort(results); + return results; + } + + private List aMinusB(List matches1, List matches2) { + Combiner combiner = Combiner.A_MINUS_B; + List results = new ArrayList<>(combiner.combine(matches1, matches2)); + Collections.sort(results); + return results; + } + + private List BMinusA(List matches1, List matches2) { + Combiner combiner = Combiner.B_MINUS_A; + List results = new ArrayList<>(combiner.combine(matches1, matches2)); + Collections.sort(results); + return results; + } + + private List list(MemoryMatch... matches) { + return Arrays.asList(matches); + } + + private Address addr(long offset) { + return space.getAddress(offset); + } + + private MemoryMatch createMatch(int offset, int length) { + Address address = addr(offset); + byte[] bytes = new byte[length]; + return new MemoryMatch(address, bytes, null); + } +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/AbstractSearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/AbstractSearchFormatTest.java new file mode 100644 index 0000000000..7a40692afe --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/AbstractSearchFormatTest.java @@ -0,0 +1,98 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.features.base.memsearch.matcher.MaskedByteSequenceByteMatcher; + +public class AbstractSearchFormatTest { + protected SearchFormat format; + protected MaskedByteSequenceByteMatcher matcher; + protected SearchSettings settings = new SearchSettings().withBigEndian(true); + + protected SearchSettings hexSettings = settings.withSearchFormat(SearchFormat.HEX); + protected SearchSettings binarySettings = settings.withSearchFormat(SearchFormat.BINARY); + protected SearchSettings decimalSettings = settings.withSearchFormat(SearchFormat.DECIMAL); + protected SearchSettings int1Settings = decimalSettings.withDecimalByteSize(1); + protected SearchSettings int2Settings = decimalSettings.withDecimalByteSize(2); + protected SearchSettings int4Settings = decimalSettings.withDecimalByteSize(4); + protected SearchSettings int8Settings = decimalSettings.withDecimalByteSize(8); + protected SearchSettings uint1Settings = int1Settings.withDecimalUnsigned(true); + protected SearchSettings uint2Settings = int2Settings.withDecimalUnsigned(true); + protected SearchSettings uint4Settings = int4Settings.withDecimalUnsigned(true); + protected SearchSettings uint8Settings = int8Settings.withDecimalUnsigned(true); + protected SearchSettings floatSettings = settings.withSearchFormat(SearchFormat.FLOAT); + protected SearchSettings doubleSettings = settings.withSearchFormat(SearchFormat.DOUBLE); + protected SearchSettings stringSettings = settings.withSearchFormat(SearchFormat.STRING); + protected SearchSettings regExSettings = settings.withSearchFormat(SearchFormat.REG_EX); + + protected AbstractSearchFormatTest(SearchFormat format) { + this.format = format; + this.settings = settings.withSearchFormat(format); + } + + protected MaskedByteSequenceByteMatcher parse(String string) { + ByteMatcher byteMatcher = format.parse(string, settings); + if (byteMatcher instanceof MaskedByteSequenceByteMatcher m) { + return m; + } + fail("Expected MaskedByteSequenceByteMatcher, but got " + byteMatcher); + return null; + } + + protected static byte[] bytes(int... byteValues) { + byte[] bytes = new byte[byteValues.length]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) byteValues[i]; + } + return bytes; + } + + protected void assertBytes(int... expectedValues) { + byte[] bytes = matcher.getBytes(); + byte[] expectedBytes = bytes(expectedValues); + assertArrayEquals(expectedBytes, bytes); + } + + protected void assertMask(int... expectedValues) { + byte[] bytes = matcher.getMask(); + byte[] expectedBytes = bytes(expectedValues); + assertArrayEquals(expectedBytes, bytes); + } + + protected int compareBytes(String input1, String input2) { + byte[] bytes1 = getBytes(input1); + byte[] bytes2 = getBytes(input2); + return format.compareValues(bytes1, bytes2, settings); + } + + protected byte[] getBytes(String input) { + matcher = parse(input); + return matcher.getBytes(); + } + + protected String str(long value) { + return Long.toString(value); + } + + protected String convertText(SearchSettings oldSettings, String text) { + return format.convertText(text, oldSettings, settings); + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/BinarySearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/BinarySearchFormatTest.java new file mode 100644 index 0000000000..5650259a7d --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/BinarySearchFormatTest.java @@ -0,0 +1,115 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class BinarySearchFormatTest extends AbstractSearchFormatTest { + public BinarySearchFormatTest() { + super(SearchFormat.BINARY); + } + + @Test + public void testSimpleCase() { + matcher = parse("0 1 10001000 11111111"); + + assertBytes(0, 1, 0x88, 0xff); + assertMask(0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testWildCards() { + matcher = parse("111.111?"); + assertBytes(0xee); + assertMask(0xee); + + matcher = parse("....0001"); + assertBytes(0x01); + assertMask(0x0f); + } + + @Test + public void testGroupTooBig() { + ByteMatcher bad = format.parse("111111111", settings); + assertFalse(bad.isValidInput()); + assertEquals("Max group size exceeded. Enter to add more.", bad.getDescription()); + } + + @Test + public void testInvalidChars() { + ByteMatcher bad = format.parse("012", settings); + assertFalse(bad.isValidInput()); + assertEquals("Invalid character", bad.getDescription()); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("10001000 11111111", convertText(hexSettings, "88 ff")); + assertEquals("00000001 00000000", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromSignedInts() { + + assertEquals("00010110 11111111 00000000", convertText(int1Settings, "22 -1 0")); + assertEquals("00000000 00000001", convertText(int2Settings, "1")); + assertEquals("11111111 11111111", convertText(int2Settings, "-1")); + assertEquals("00000000 00000000 00000000 00000001", convertText(int4Settings, "1")); + assertEquals("11111111 11111111 11111111 11111111", convertText(int4Settings, "-1")); + assertEquals("00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001", + convertText(int8Settings, "1")); + assertEquals("11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111", + convertText(int8Settings, "-1")); + } + + @Test + public void testConvertTextFromUnsignedInts() { + + assertEquals("00010110 11111111 00000000", convertText(uint1Settings, "22 255 0")); + assertEquals("00000000 00000001", convertText(uint2Settings, "1")); + assertEquals("11111111 11111111", convertText(uint2Settings, "65535")); + assertEquals("00000000 00000000 00000000 00000001", convertText(uint4Settings, "1")); + assertEquals("11111111 11111111 11111111 11111111", + convertText(uint4Settings, "4294967295")); + assertEquals("00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001", + convertText(uint8Settings, "1")); + assertEquals("11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111", + convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromFloats() { + assertEquals("00111111 10011101 11110011 10110110", + convertText(floatSettings, "1.234")); + assertEquals("00111111 11110011 10111110 01110110 11001000 10110100 00111001 01011000", + convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("", convertText(stringSettings, "56 ab")); + assertEquals("0001", convertText(stringSettings, "0001")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("", convertText(regExSettings, "56 ab")); + assertEquals("0001", convertText(regExSettings, "0001")); + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/DoubleSearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/DoubleSearchFormatTest.java new file mode 100644 index 0000000000..8855a6dca1 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/DoubleSearchFormatTest.java @@ -0,0 +1,204 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class DoubleSearchFormatTest extends AbstractSearchFormatTest { + public DoubleSearchFormatTest() { + super(SearchFormat.DOUBLE); + settings = settings.withBigEndian(true); + } + + @Test + public void testSimpleCaseBigEndian() { + matcher = parse("1.2 1.2"); + assertBytes(63, -13, 51, 51, 51, 51, 51, 51, 63, -13, 51, 51, 51, 51, 51, 51); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff); + } + + @Test + public void testSimpleCaseLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("1.2 1.2"); + assertBytes(51, 51, 51, 51, 51, 51, -13, 63, 51, 51, 51, 51, 51, 51, -13, 63); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff); + } + + @Test + public void testECase() { + matcher = parse("1.2e10"); + assertBytes(66, 6, 90, 11, -64, 0, 0, 0); + } + + @Test + public void testNegativeECase() { + matcher = parse("1.2e-10"); + assertBytes(61, -32, 126, 31, -23, 27, 11, 112); + } + + @Test + public void testDotOnly() { + ByteMatcher byteMatcher = format.parse(".", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete floating point number", byteMatcher.getDescription()); + } + + @Test + public void testEndE() { + ByteMatcher byteMatcher = format.parse("2.1e", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete floating point number", byteMatcher.getDescription()); + } + + @Test + public void testEndNegativeE() { + ByteMatcher byteMatcher = format.parse("2.1-e", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete floating point number", byteMatcher.getDescription()); + } + + @Test + public void testNegativeSignOnly() { + ByteMatcher byteMatcher = format.parse("-", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete negative floating point number", byteMatcher.getDescription()); + } + + @Test + public void testNegativeDotSignOnly() { + ByteMatcher byteMatcher = format.parse("-.", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete negative floating point number", byteMatcher.getDescription()); + } + + @Test + public void testBadChars() { + ByteMatcher byteMatcher = format.parse("12.z", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Floating point parse error: For input string: \"12.z\"", + byteMatcher.getDescription()); + } + + @Test + public void testGetValueBigEndian() { + settings = settings.withBigEndian(true); + assertRoundTrip(-1.1234, "-1.1234"); + assertRoundTrip(1.1234, "1.1234"); + assertRoundTrip(0.0, "0.0"); + } + + @Test + public void testGetValueLittleEndian() { + settings = settings.withBigEndian(false); + assertRoundTrip(-1.1234, "-1.1234"); + assertRoundTrip(1.1234, "1.1234"); + assertRoundTrip(0.0, "0.0"); + } + + @Test + public void testCompareValuesBigEndian() { + settings = settings.withBigEndian(true); + assertEquals(0, compareBytes("10.0", "10.0")); + assertEquals(-1, compareBytes("9.0", "10.0")); + assertEquals(1, compareBytes("11.0", "10.0")); + + assertEquals(0, compareBytes("-10.1", "-10.1")); + assertEquals(1, compareBytes("-9.1", "-10.1")); + assertEquals(-1, compareBytes("-11.1", "-10.1")); + + } + + @Test + public void testCompareValuesLittleEndian() { + settings = settings.withBigEndian(false); + + assertEquals(0, compareBytes("10.0", "10.0")); + assertEquals(-1, compareBytes("9.0", "10.0")); + assertEquals(1, compareBytes("11.0", "10.0")); + + assertEquals(0, compareBytes("-10.1", "-10.1")); + assertEquals(1, compareBytes("-9.1", "-10.1")); + assertEquals(-1, compareBytes("-11.1", "-10.1")); + } + + private void assertRoundTrip(double expected, String input) { + matcher = parse(input); + byte[] bytes = matcher.getBytes(); + double value = getFloatFormat().getValue(bytes, 0, settings.isBigEndian()); + assertEquals(expected, value, 0.0000001); + } + + private FloatSearchFormat getFloatFormat() { + return (FloatSearchFormat) format; + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("10001000 11111111", convertText(binarySettings, "10001000 11111111")); + assertEquals("1 0", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("56 12", convertText(hexSettings, "56 12")); + assertEquals("1 0", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromSignedInts() { + assertEquals("0 22 -1 -14", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int4Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int8Settings, "0 22 -1 -14")); + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("0 22 255", convertText(uint1Settings, "0 22 255")); + assertEquals("0 22 65535", convertText(uint2Settings, "0 22 65535")); + assertEquals("22 4294967295", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("18446744073709551615", + convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromFloats() { + assertEquals("1.234", convertText(floatSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("1.23", convertText(stringSettings, "1.23")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("1.23", convertText(regExSettings, "1.23")); + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/FloatSearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/FloatSearchFormatTest.java new file mode 100644 index 0000000000..70d8c7bc2b --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/FloatSearchFormatTest.java @@ -0,0 +1,201 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class FloatSearchFormatTest extends AbstractSearchFormatTest { + public FloatSearchFormatTest() { + super(SearchFormat.FLOAT); + settings = settings.withBigEndian(true); + } + + @Test + public void testSimpleCaseBigEndian() { + matcher = parse("1.2 1.2"); + assertBytes(63, -103, -103, -102, 63, -103, -103, -102); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testSimpleCaseLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("1.2 1.2"); + assertBytes(-102, -103, -103, 63, -102, -103, -103, 63); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testECase() { + matcher = parse("1.2e10"); + assertBytes(80, 50, -48, 94); + } + + @Test + public void testNegativeECase() { + matcher = parse("1.2e-10"); + assertBytes(47, 3, -16, -1); + } + + @Test + public void testDotOnly() { + ByteMatcher byteMatcher = format.parse(".", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete floating point number", byteMatcher.getDescription()); + } + + @Test + public void testEndE() { + ByteMatcher byteMatcher = format.parse("2.1e", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete floating point number", byteMatcher.getDescription()); + } + + @Test + public void testEndNegativeE() { + ByteMatcher byteMatcher = format.parse("2.1-e", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete floating point number", byteMatcher.getDescription()); + } + + @Test + public void testNegativeSignOnly() { + ByteMatcher byteMatcher = format.parse("-", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete negative floating point number", byteMatcher.getDescription()); + } + + @Test + public void testNegativeDotSignOnly() { + ByteMatcher byteMatcher = format.parse("-.", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete negative floating point number", byteMatcher.getDescription()); + } + + @Test + public void testBadChars() { + ByteMatcher byteMatcher = format.parse("12.z", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Floating point parse error: For input string: \"12.z\"", + byteMatcher.getDescription()); + } + + @Test + public void testGetValueBigEndian() { + settings = settings.withBigEndian(true); + assertRoundTrip(-1.1234, "-1.1234"); + assertRoundTrip(1.1234, "1.1234"); + assertRoundTrip(0.0, "0.0"); + } + + @Test + public void testGetValueLittleEndian() { + settings = settings.withBigEndian(false); + assertRoundTrip(-1.1234, "-1.1234"); + assertRoundTrip(1.1234, "1.1234"); + assertRoundTrip(0.0, "0.0"); + } + + @Test + public void testCompareValuesBigEndian() { + settings = settings.withBigEndian(true); + assertEquals(0, compareBytes("10.0", "10.0")); + assertEquals(-1, compareBytes("9.0", "10.0")); + assertEquals(1, compareBytes("11.0", "10.0")); + + assertEquals(0, compareBytes("-10.1", "-10.1")); + assertEquals(1, compareBytes("-9.1", "-10.1")); + assertEquals(-1, compareBytes("-11.1", "-10.1")); + + } + + @Test + public void testCompareValuesLittleEndian() { + settings = settings.withBigEndian(false); + + assertEquals(0, compareBytes("10.0", "10.0")); + assertEquals(-1, compareBytes("9.0", "10.0")); + assertEquals(1, compareBytes("11.0", "10.0")); + + assertEquals(0, compareBytes("-10.1", "-10.1")); + assertEquals(1, compareBytes("-9.1", "-10.1")); + assertEquals(-1, compareBytes("-11.1", "-10.1")); + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("10001000 11111111", convertText(binarySettings, "10001000 11111111")); + assertEquals("1 0", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("56 12", convertText(hexSettings, "56 12")); + assertEquals("1 0", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromSignedInts() { + assertEquals("0 22 -1 -14", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int4Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int8Settings, "0 22 -1 -14")); + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("0 22 255", convertText(uint1Settings, "0 22 255")); + assertEquals("0 22 65535", convertText(uint2Settings, "0 22 65535")); + assertEquals("22 4294967295", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("18446744073709551615", convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromDoubles() { + assertEquals("1.234", convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("1.23", convertText(stringSettings, "1.23")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("1.23", convertText(regExSettings, "1.23")); + } + + private void assertRoundTrip(double expected, String input) { + matcher = parse(input); + byte[] bytes = matcher.getBytes(); + double value = getFloatFormat().getValue(bytes, 0, settings.isBigEndian()); + assertEquals(expected, value, 0.0000001); + } + + private FloatSearchFormat getFloatFormat() { + return (FloatSearchFormat) format; + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/HexSearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/HexSearchFormatTest.java new file mode 100644 index 0000000000..544ddef6cc --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/HexSearchFormatTest.java @@ -0,0 +1,200 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class HexSearchFormatTest extends AbstractSearchFormatTest { + + public HexSearchFormatTest() { + super(SearchFormat.HEX); + } + + @Test + public void testSimpleLowerCase() { + matcher = parse("1 02 3 aa"); + + assertBytes(1, 2, 3, 0xaa); + assertMask(0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testSimpleUpperCase() { + matcher = parse("1 02 3 AA"); + + assertBytes(1, 2, 3, 0xaa); + assertMask(0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testSimpleWildCardOneDigit() { + matcher = parse("1 . 3"); + assertBytes(1, 0, 3); + assertMask(0xff, 0, 0xff); + + matcher = parse("1 ? 3"); + assertBytes(1, 0, 3); + assertMask(0xff, 0, 0xff); + } + + @Test + public void testSimpleWildCardTwoDigit() { + matcher = parse("1 .. 3"); + assertBytes(1, 0, 3); + assertMask(0xff, 0, 0xff); + + matcher = parse("1 ?? 3"); + assertBytes(1, 0, 3); + assertMask(0xff, 0, 0xff); + + matcher = parse("1 ?. 3"); + assertBytes(1, 0, 3); + assertMask(0xff, 0, 0xff); + } + + @Test + public void testGroupBigEndian() { + settings = settings.withBigEndian(true); + + matcher = parse("1234"); + assertBytes(0x12, 0x34); + assertMask(0xff, 0xff); + + matcher = parse("12345678"); + assertBytes(0x12, 0x34, 0x56, 0x78); + assertMask(0xff, 0xff, 0xff, 0xff); + + matcher = parse("123456789abcdef0"); + assertBytes(0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testOddGroupSizeBigEndian() { + settings = settings.withBigEndian(true); + + matcher = parse("123456"); + assertBytes(0x12, 0x34, 0x56); + assertMask(0xff, 0xff, 0xff); + + matcher = parse("123456789abcde"); + assertBytes(0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testGroupLittleEndian() { + settings = settings.withBigEndian(false); + + matcher = parse("1234"); + assertBytes(0x34, 0x12); + assertMask(0xff, 0xff); + + matcher = parse("12345678"); + assertBytes(0x78, 0x56, 0x34, 0x12); + assertMask(0xff, 0xff, 0xff, 0xff); + + matcher = parse("123456789abcdef0"); + assertBytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testOddGroupSizeLittleEndian() { + settings = settings.withBigEndian(false); + + matcher = parse("123456"); + assertBytes(0x56, 0x34, 0x12); + assertMask(0xff, 0xff, 0xff); + + matcher = parse("123456789abcde"); + assertBytes(0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testGroupsWithWildsBigEndian() { + settings = settings.withBigEndian(true); + matcher = parse("12.45."); + assertBytes(0x12, 0x04, 0x50); + assertMask(0xff, 0x0f, 0xf0); + } + + @Test + public void testGroupsWithWildsLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("12.45."); + assertBytes(0x50, 0x04, 0x12); + assertMask(0xf0, 0x0f, 0xff); + } + + @Test + public void testGroupTooBig() { + ByteMatcher bad = format.parse("0123456789abcdef0", settings); + assertFalse(bad.isValidInput()); + assertEquals("Max group size exceeded. Enter to add more.", bad.getDescription()); + } + + @Test + public void testInvalidChars() { + ByteMatcher bad = format.parse("01z3", settings); + assertFalse(bad.isValidInput()); + assertEquals("Invalid character", bad.getDescription()); + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("88 ff", convertText(binarySettings, "10001000 11111111")); + assertEquals("01 00", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromSignedInts() { + + assertEquals("00 16 ff f2", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("00 00 00 16 ff ff ff f2", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("00 00 00 16 ff ff ff f2", convertText(int4Settings, "22 -14")); + assertEquals("00 00 00 00 00 00 00 16", convertText(int8Settings, "22")); + assertEquals("ff ff ff ff ff ff ff f2", convertText(int8Settings, "-14")); + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("00 16 ff", convertText(uint1Settings, "0 22 255")); + assertEquals("00 00 00 16 ff ff", convertText(uint2Settings, "0 22 65535")); + assertEquals("00 00 00 16 ff ff ff ff", convertText(uint4Settings, "22 4294967295")); + assertEquals("00 00 00 00 00 00 00 16", convertText(uint8Settings, "22")); + assertEquals("ff ff ff ff ff ff ff ff", convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromFloats() { + assertEquals("3f 9d f3 b6", convertText(floatSettings, "1.234")); + assertEquals("3f f3 be 76 c8 b4 39 58", convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("56 ab", convertText(stringSettings, "56 ab")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("56 ab", convertText(regExSettings, "56 ab")); + } +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int1SearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int1SearchFormatTest.java new file mode 100644 index 0000000000..c47c287c2f --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int1SearchFormatTest.java @@ -0,0 +1,191 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class Int1SearchFormatTest extends AbstractSearchFormatTest { + public Int1SearchFormatTest() { + super(SearchFormat.DECIMAL); + settings = settings.withDecimalByteSize(1); + } + + @Test + public void testSimpleCaseBigEndian() { + matcher = parse("1 2 -1"); + assertBytes(1, 2, -1); + assertMask(0xff, 0xff, 0xff); + } + + @Test + public void testSimpleCaseLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("1 2 -1"); + assertBytes(1, 2, -1); + assertMask(0xff, 0xff, 0xff); + } + + @Test + public void testMinimum() { + long value = Byte.MIN_VALUE; + matcher = parse(Long.toString(value)); + assertBytes(0x80); + + value -= 1; + ByteMatcher byteMatcher = format.parse(Long.toString(value), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [-128, 127]", + byteMatcher.getDescription()); + } + + @Test + public void testMaximum() { + long value = Byte.MAX_VALUE; + matcher = parse(Long.toString(value)); + assertBytes(0x7f); + + value += 1; + ByteMatcher byteMatcher = format.parse(Long.toString(value), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [-128, 127]", + byteMatcher.getDescription()); + } + + @Test + public void testNegativeSignOnly() { + ByteMatcher byteMatcher = format.parse("-", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete negative number", byteMatcher.getDescription()); + } + + @Test + public void testBadChars() { + ByteMatcher byteMatcher = format.parse("12z", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Number parse error: For input string: \"12z\"", byteMatcher.getDescription()); + } + + @Test + public void testGetValueBigEndian() { + settings = settings.withBigEndian(true); + assertRoundTrip(-1, "-1"); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(Byte.MAX_VALUE, Integer.toString(Byte.MAX_VALUE)); + assertRoundTrip(Byte.MIN_VALUE, Integer.toString(Byte.MIN_VALUE)); + } + + @Test + public void testGetValueLittleEndian() { + settings = settings.withBigEndian(false); + assertRoundTrip(-1, "-1"); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(Byte.MAX_VALUE, Integer.toString(Byte.MAX_VALUE)); + assertRoundTrip(Byte.MIN_VALUE, Integer.toString(Byte.MIN_VALUE)); + } + + @Test + public void testCompareValuesBigEndian() { + settings = settings.withBigEndian(true); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes("-10", "-10")); + assertEquals(1, compareBytes("-9", "-10")); + assertEquals(-1, compareBytes("-11", "-10")); + + } + + @Test + public void testCompareValuesLittleEndian() { + settings = settings.withBigEndian(false); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes("-10", "-10")); + assertEquals(1, compareBytes("-9", "-10")); + assertEquals(-1, compareBytes("-11", "-10")); + + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("-120 -1", convertText(binarySettings, "10001000 11111111")); + assertEquals("1 0", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromhexSettings() { + assertEquals("86 18", convertText(hexSettings, "56 12")); + assertEquals("1 0", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromOtherSizedInts() { + assertEquals("0 22 -1 -14", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int4Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int8Settings, "0 22 -1 -14")); + + assertEquals("78 32", convertText(int2Settings, "20000")); + assertEquals("1 49 45 0", convertText(int4Settings, "20000000")); + assertEquals("0 0 1 -47 -87 74 32 0", convertText(int8Settings, "2000000000000")); + + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("0 22 -1", convertText(uint1Settings, "0 22 255")); + assertEquals("0 0 0 22 -1 -1", convertText(uint2Settings, "0 22 65535")); + assertEquals("0 0 0 22 -1 -1 -1 -1", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("-1 -1 -1 -1 -1 -1 -1 -1", + convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromdoubleSettingss() { + assertEquals("", convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("123", convertText(stringSettings, "123")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("123", convertText(regExSettings, "123")); + } + + private void assertRoundTrip(long expected, String input) { + matcher = parse(input); + byte[] bytes = matcher.getBytes(); + long value = getDecimalFormat().getValue(bytes, 0, settings); + assertEquals(expected, value); + } + + private DecimalSearchFormat getDecimalFormat() { + return (DecimalSearchFormat) format; + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int2SearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int2SearchFormatTest.java new file mode 100644 index 0000000000..757b59658a --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int2SearchFormatTest.java @@ -0,0 +1,190 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class Int2SearchFormatTest extends AbstractSearchFormatTest { + public Int2SearchFormatTest() { + super(SearchFormat.DECIMAL); + settings = settings.withDecimalByteSize(2); + } + + @Test + public void testSimpleCaseBigEndian() { + matcher = parse("1 2 -1"); + assertBytes(0, 1, 0, 2, -1, -1); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testSimpleCaseLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("1 2 -1"); + assertBytes(1, 0, 2, 0, -1, -1); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testMinimum() { + long value = Short.MIN_VALUE; + matcher = parse(Long.toString(value)); + assertBytes(0x80, 0); + + value -= 1; + ByteMatcher byteMatcher = format.parse(Long.toString(value), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [-32768, 32767]", + byteMatcher.getDescription()); + } + + @Test + public void testMaximum() { + long value = Short.MAX_VALUE; + matcher = parse(Long.toString(value)); + assertBytes(0x7f, 0xff); + + value += 1; + ByteMatcher byteMatcher = format.parse(Long.toString(value), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [-32768, 32767]", + byteMatcher.getDescription()); + } + + @Test + public void testNegativeSignOnly() { + ByteMatcher byteMatcher = format.parse("-", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete negative number", byteMatcher.getDescription()); + } + + @Test + public void testBadChars() { + ByteMatcher byteMatcher = format.parse("12z", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Number parse error: For input string: \"12z\"", byteMatcher.getDescription()); + } + + @Test + public void testGetValueBigEndian() { + settings = settings.withBigEndian(true); + assertRoundTrip(-1, "-1"); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(Short.MAX_VALUE, Integer.toString(Short.MAX_VALUE)); + assertRoundTrip(Short.MIN_VALUE, Integer.toString(Short.MIN_VALUE)); + } + + @Test + public void testGetValueLittleEndian() { + settings = settings.withBigEndian(false); + assertRoundTrip(-1, "-1"); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(Short.MAX_VALUE, Integer.toString(Short.MAX_VALUE)); + assertRoundTrip(Short.MIN_VALUE, Integer.toString(Short.MIN_VALUE)); + } + + @Test + public void testCompareValuesBigEndian() { + settings = settings.withBigEndian(true); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes("-10", "-10")); + assertEquals(1, compareBytes("-9", "-10")); + assertEquals(-1, compareBytes("-11", "-10")); + + } + + @Test + public void testCompareValuesLittleEndian() { + settings = settings.withBigEndian(false); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes("-10", "-10")); + assertEquals(1, compareBytes("-9", "-10")); + assertEquals(-1, compareBytes("-11", "-10")); + + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("-30465", convertText(binarySettings, "10001000 11111111")); + assertEquals("256", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("22034", convertText(hexSettings, "56 12")); + assertEquals("256", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromOtherSizedInts() { + assertEquals("22 -14", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int4Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int8Settings, "0 22 -1 -14")); + + assertEquals("305 11520", convertText(int4Settings, "20000000")); + assertEquals("0 465 -22198 8192", convertText(int8Settings, "2000000000000")); + + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("22 255", convertText(uint1Settings, "0 22 255")); + assertEquals("0 22 -1", convertText(uint2Settings, "0 22 65535")); + assertEquals("0 22 -1 -1", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("-1 -1 -1 -1", + convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromDoubles() { + assertEquals("", convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("123", convertText(stringSettings, "123")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("123", convertText(regExSettings, "123")); + } + + private void assertRoundTrip(long expected, String input) { + matcher = parse(input); + byte[] bytes = matcher.getBytes(); + long value = getNumberFormat().getValue(bytes, 0, settings); + assertEquals(expected, value); + } + + private DecimalSearchFormat getNumberFormat() { + return (DecimalSearchFormat) format; + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int4SearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int4SearchFormatTest.java new file mode 100644 index 0000000000..8f7e1711ae --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int4SearchFormatTest.java @@ -0,0 +1,188 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class Int4SearchFormatTest extends AbstractSearchFormatTest { + public Int4SearchFormatTest() { + super(SearchFormat.DECIMAL); + settings = settings.withDecimalByteSize(4); + } + + @Test + public void testSimpleCaseBigEndian() { + matcher = parse("1 2 -1"); + assertBytes(0, 0, 0, 1, 0, 0, 0, 2, -1, -1, -1, -1); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testSimpleCaseLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("1 2 -1"); + assertBytes(1, 0, 0, 0, 2, 0, 0, 0, -1, -1, -1, -1); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testMinimum() { + long value = Integer.MIN_VALUE; + matcher = parse(Long.toString(value)); + assertBytes(0x80, 0, 0, 0); + + value -= 1; + ByteMatcher byteMatcher = format.parse(Long.toString(value), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [-2147483648, 2147483647]", + byteMatcher.getDescription()); + } + + @Test + public void testMaximum() { + long value = Integer.MAX_VALUE; + matcher = parse(Long.toString(value)); + assertBytes(0x7f, 0xff, 0xff, 0xff); + + value += 1; + ByteMatcher byteMatcher = format.parse(Long.toString(value), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [-2147483648, 2147483647]", + byteMatcher.getDescription()); + } + + @Test + public void testNegativeSignOnly() { + ByteMatcher byteMatcher = format.parse("-", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete negative number", byteMatcher.getDescription()); + } + + @Test + public void testBadChars() { + ByteMatcher byteMatcher = format.parse("12z", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Number parse error: For input string: \"12z\"", byteMatcher.getDescription()); + } + + @Test + public void testGetValueBigEndian() { + settings = settings.withBigEndian(true); + assertRoundTrip(-1, "-1"); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(Integer.MAX_VALUE, Integer.toString(Integer.MAX_VALUE)); + assertRoundTrip(Integer.MIN_VALUE, Integer.toString(Integer.MIN_VALUE)); + } + + @Test + public void testGetValueLittleEndian() { + settings = settings.withBigEndian(false); + assertRoundTrip(-1, "-1"); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(Integer.MAX_VALUE, Integer.toString(Integer.MAX_VALUE)); + assertRoundTrip(Integer.MIN_VALUE, Integer.toString(Integer.MIN_VALUE)); + } + + @Test + public void testCompareValuesBigEndian() { + settings = settings.withBigEndian(true); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes("-10", "-10")); + assertEquals(1, compareBytes("-9", "-10")); + assertEquals(-1, compareBytes("-11", "-10")); + + } + + @Test + public void testCompareValuesLittleEndian() { + settings = settings.withBigEndian(false); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes("-10", "-10")); + assertEquals(1, compareBytes("-9", "-10")); + assertEquals(-1, compareBytes("-11", "-10")); + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("35071", convertText(binarySettings, "10001000 11111111")); + assertEquals("256", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("22034", convertText(hexSettings, "56 12")); + assertEquals("256", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromOtherSizedInts() { + assertEquals("1507314", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("22 -14", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int8Settings, "0 22 -1 -14")); + + assertEquals("20000000", convertText(int4Settings, "20000000")); + assertEquals("465 -1454759936", convertText(int8Settings, "2000000000000")); + + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("5887", convertText(uint1Settings, "0 22 255")); + assertEquals("22 65535", convertText(uint2Settings, "0 22 65535")); + assertEquals("22 -1", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("-1 -1", + convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromDoubles() { + assertEquals("", convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("123", convertText(stringSettings, "123")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("123", convertText(regExSettings, "123")); + } + + private void assertRoundTrip(long expected, String input) { + matcher = parse(input); + byte[] bytes = matcher.getBytes(); + long value = getDecimalFormat().getValue(bytes, 0, settings); + assertEquals(expected, value); + } + + private DecimalSearchFormat getDecimalFormat() { + return (DecimalSearchFormat) format; + } +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int8SearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int8SearchFormatTest.java new file mode 100644 index 0000000000..982e7bea90 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/Int8SearchFormatTest.java @@ -0,0 +1,197 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import java.math.BigInteger; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class Int8SearchFormatTest extends AbstractSearchFormatTest { + public Int8SearchFormatTest() { + super(SearchFormat.DECIMAL); + settings = settings.withDecimalByteSize(8); + + } + + @Test + public void testSimpleCaseBigEndian() { + matcher = parse("1 2 -1"); + assertBytes(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, -1, -1, -1, -1, -1, -1, -1, -1); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testSimpleCaseLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("1 2 -1"); + assertBytes(1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testMinimum() { + long value = Long.MIN_VALUE; + matcher = parse(Long.toString(value)); + assertBytes(0x80, 0, 0, 0, 0, 0, 0, 0); + + BigInteger bigValue = BigInteger.valueOf(value).subtract(BigInteger.ONE); + ByteMatcher byteMatcher = format.parse(bigValue.toString(), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [-9223372036854775808, 9223372036854775807]", + byteMatcher.getDescription()); + } + + @Test + public void testMaximum() { + long value = Long.MAX_VALUE; + matcher = parse(Long.toString(value)); + assertBytes(0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + + BigInteger bigValue = BigInteger.valueOf(value).add(BigInteger.ONE); + ByteMatcher byteMatcher = format.parse(bigValue.toString(), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [-9223372036854775808, 9223372036854775807]", + byteMatcher.getDescription()); + } + + @Test + public void testNegativeSignOnly() { + ByteMatcher byteMatcher = format.parse("-", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Incomplete negative number", byteMatcher.getDescription()); + } + + @Test + public void testBadChars() { + ByteMatcher byteMatcher = format.parse("12z", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Number parse error: For input string: \"12z\"", byteMatcher.getDescription()); + } + + @Test + public void testGetValueBigEndian() { + settings = settings.withBigEndian(true); + assertRoundTrip(-1, "-1"); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(Long.MAX_VALUE, Long.toString(Long.MAX_VALUE)); + assertRoundTrip(Long.MIN_VALUE, Long.toString(Long.MIN_VALUE)); + } + + @Test + public void testGetValueLittleEndian() { + settings = settings.withBigEndian(false); + assertRoundTrip(-1, "-1"); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(Long.MAX_VALUE, Long.toString(Long.MAX_VALUE)); + assertRoundTrip(Long.MIN_VALUE, Long.toString(Long.MIN_VALUE)); + } + + @Test + public void testCompareValuesBigEndian() { + settings = settings.withBigEndian(true); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes("-10", "-10")); + assertEquals(1, compareBytes("-9", "-10")); + assertEquals(-1, compareBytes("-11", "-10")); + + assertEquals(0, compareBytes(str(Long.MAX_VALUE), str(Long.MAX_VALUE))); + assertEquals(0, compareBytes(str(Long.MIN_VALUE), str(Long.MIN_VALUE))); + assertEquals(-1, compareBytes(str(Long.MIN_VALUE), str(Long.MAX_VALUE))); + assertEquals(1, compareBytes(str(Long.MAX_VALUE), str(Long.MIN_VALUE))); + } + + @Test + public void testCompareValuesLittleEndian() { + settings = settings.withBigEndian(false); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes("-10", "-10")); + assertEquals(1, compareBytes("-9", "-10")); + assertEquals(-1, compareBytes("-11", "-10")); + + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("35071", convertText(binarySettings, "10001000 11111111")); + assertEquals("256", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("22034", convertText(hexSettings, "56 12")); + assertEquals("256", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromOtherSizedInts() { + assertEquals("1507314", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("98784247794", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("22 -14", convertText(int4Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int8Settings, "0 22 -1 -14")); + + assertEquals("20000000", convertText(int4Settings, "20000000")); + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("5887", convertText(uint1Settings, "0 22 255")); + assertEquals("1507327", convertText(uint2Settings, "0 22 65535")); + assertEquals("98784247807", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("-1", convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromDoubles() { + assertEquals("", convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("123", convertText(stringSettings, "123")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("123", convertText(regExSettings, "123")); + } + + private void assertRoundTrip(long expected, String input) { + matcher = parse(input); + byte[] bytes = matcher.getBytes(); + long value = getNumberFormat().getValue(bytes, 0, settings); + assertEquals(expected, value); + } + + private DecimalSearchFormat getNumberFormat() { + return (DecimalSearchFormat) format; + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/RegExSearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/RegExSearchFormatTest.java new file mode 100644 index 0000000000..7f2af90a3f --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/RegExSearchFormatTest.java @@ -0,0 +1,86 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class RegExSearchFormatTest extends AbstractSearchFormatTest { + private ByteMatcher byteMatcher; + + public RegExSearchFormatTest() { + super(SearchFormat.REG_EX); + } + + @Test + public void testSimpleCase() { + byteMatcher = format.parse("a|b", settings); + + assertTrue(byteMatcher.isValidInput()); + assertTrue(byteMatcher.isValidSearch()); + assertEquals("Reg Ex", byteMatcher.getDescription()); + } + + @Test + public void testIncompleteCase() { + byteMatcher = format.parse("a(", settings); + assertTrue(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("RegEx Pattern Error: Unclosed group", byteMatcher.getDescription()); + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("10001000 11111111", convertText(binarySettings, "10001000 11111111")); + assertEquals("1 0", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("56 12", convertText(hexSettings, "56 12")); + assertEquals("1 0", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromInts() { + assertEquals("0 22 -1 -14", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int8Settings, "0 22 -1 -14")); + + assertEquals("20000000", convertText(int4Settings, "20000000")); + assertEquals("2000000000000", convertText(int8Settings, "2000000000000")); + + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("0 22 255", convertText(uint1Settings, "0 22 255")); + assertEquals("0 22 65535", convertText(uint2Settings, "0 22 65535")); + assertEquals("22 4294967295", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("18446744073709551615", + convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromDoubles() { + assertEquals("1.234", convertText(doubleSettings, "1.234")); + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/StringSearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/StringSearchFormatTest.java new file mode 100644 index 0000000000..dfc94da9b6 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/StringSearchFormatTest.java @@ -0,0 +1,165 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import java.nio.charset.StandardCharsets; + +import org.junit.Test; + +public class StringSearchFormatTest extends AbstractSearchFormatTest { + + public StringSearchFormatTest() { + super(SearchFormat.STRING); + settings = settings.withCaseSensitive(true); + settings = settings.withUseEscapeSequence(false); + } + + @Test + public void testCaseSensitive() { + matcher = parse("aBc12"); + + assertBytes(0x61, 0x42, 0x63, 0x31, 0x32); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testCaseInSensitriveUTF8() { + settings = settings.withCaseSensitive(false); + matcher = parse("aBc12"); + + assertBytes(0x41, 0x42, 0x43, 0x31, 0x32); + assertMask(0xdf, 0xdf, 0xdf, 0xff, 0xff); + } + + @Test + public void testCaseSensitiveUTF8() { + settings = settings.withStringCharset(StandardCharsets.UTF_8); + matcher = parse("aBc12"); + + assertBytes(0x61, 0x42, 0x63, 0x31, 0x32); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testCaseInSensitrive() { + settings = settings.withCaseSensitive(false); + matcher = parse("aBc12"); + + assertBytes(0x41, 0x42, 0x43, 0x31, 0x32); + assertMask(0xdf, 0xdf, 0xdf, 0xff, 0xff); + } + + @Test + public void testEscapeSequence() { + settings = settings.withUseEscapeSequence(false); + matcher = parse("a\\n"); + assertBytes(0x61, 0x5c, 0x6e); + + settings = settings.withUseEscapeSequence(true); + matcher = parse("a\\n"); + assertBytes(0x61, 0x0a); + + } + + @Test + public void testUTF16CaseSensitiveLittleEndian() { + settings = settings.withBigEndian(false); + settings = settings.withStringCharset(StandardCharsets.UTF_16); + matcher = parse("aBc12"); + + assertBytes(0x61, 0x0, 0x42, 0x0, 0x63, 0x0, 0x31, 0x0, 0x32, 0x0); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testUTF16CaseSensitiveBigEndian() { + settings = settings.withBigEndian(true); + settings = settings.withStringCharset(StandardCharsets.UTF_16); + matcher = parse("aBc12"); + + assertBytes(0x00, 0x61, 0x0, 0x42, 0x0, 0x63, 0x0, 0x31, 0x0, 0x32); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testUTF16CaseInSensitiveLittleEndian() { + settings = settings.withCaseSensitive(false); + settings = settings.withBigEndian(false); + settings = settings.withStringCharset(StandardCharsets.UTF_16); + matcher = parse("aBc12"); + + assertBytes(0x41, 0x0, 0x42, 0x0, 0x43, 0x0, 0x31, 0x0, 0x32, 0x0); + assertMask(0xdf, 0xff, 0xdf, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testUTF16CaseInSensitiveBigEndian() { + settings = settings.withCaseSensitive(false); + settings = settings.withBigEndian(true); + settings = settings.withStringCharset(StandardCharsets.UTF_16); + matcher = parse("aBc12"); + + assertBytes(0x00, 0x41, 0x0, 0x42, 0x0, 0x43, 0x0, 0x31, 0x0, 0x32); + assertMask(0xff, 0xdf, 0xff, 0xdf, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("10001000 11111111", convertText(binarySettings, "10001000 11111111")); + assertEquals("1 0", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("56 12", convertText(hexSettings, "56 12")); + assertEquals("1 0", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromInts() { + assertEquals("0 22 -1 -14", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("0 22 -1 -14", convertText(int8Settings, "0 22 -1 -14")); + + assertEquals("20000000", convertText(int4Settings, "20000000")); + assertEquals("2000000000000", convertText(int8Settings, "2000000000000")); + + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("0 22 255", convertText(uint1Settings, "0 22 255")); + assertEquals("0 22 65535", convertText(uint2Settings, "0 22 65535")); + assertEquals("22 4294967295", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("18446744073709551615", + convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromDoubles() { + assertEquals("1.234", convertText(doubleSettings, "1.234")); + } + + protected void assertStringBytes(String string) { + byte[] bytes = matcher.getBytes(); + byte[] expectedBytes = string.getBytes(); + assertArrayEquals(expectedBytes, bytes); + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt1SearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt1SearchFormatTest.java new file mode 100644 index 0000000000..6dfa0d87d9 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt1SearchFormatTest.java @@ -0,0 +1,189 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class UInt1SearchFormatTest extends AbstractSearchFormatTest { + public UInt1SearchFormatTest() { + super(SearchFormat.DECIMAL); + settings = settings.withDecimalByteSize(1); + settings = settings.withDecimalUnsigned(true); + } + + @Test + public void testSimpleCaseBigEndian() { + matcher = parse("1 2 3"); + assertBytes(1, 2, 3); + assertMask(0xff, 0xff, 0xff); + } + + @Test + public void testSimpleCaseLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("1 2 3"); + assertBytes(1, 2, 3); + assertMask(0xff, 0xff, 0xff); + } + + @Test + public void testMinimum() { + matcher = parse("0"); + assertBytes(0); + + ByteMatcher byteMatcher = format.parse("-1", settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [0, 255]", + byteMatcher.getDescription()); + } + + @Test + public void testMaximum() { + long value = 0xffL; + matcher = parse(Long.toString(value)); + assertBytes(0xff); + + value += 1; + ByteMatcher byteMatcher = format.parse(Long.toString(value), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [0, 255]", + byteMatcher.getDescription()); + } + + @Test + public void testNegativeSignOnly() { + ByteMatcher byteMatcher = format.parse("-", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Negative numbers not allowed for unsigned values", + byteMatcher.getDescription()); + } + + @Test + public void testBadChars() { + ByteMatcher byteMatcher = format.parse("12z", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Number parse error: For input string: \"12z\"", byteMatcher.getDescription()); + } + + @Test + public void testGetValueBigEndian() { + settings = settings.withBigEndian(true); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(0xffL, Long.toString(0xffL)); + } + + @Test + public void testGetValueLittleEndian() { + settings = settings.withBigEndian(false); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(0xffL, Long.toString(0xffL)); + } + + @Test + public void testCompareValuesBigEndian() { + settings = settings.withBigEndian(true); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes(str(0xffl), str(0xffL))); + assertEquals(1, compareBytes(str(0xffl), str(0xfeL))); + assertEquals(-1, compareBytes(str(0xfeL), str(0xffL))); + + } + + @Test + public void testCompareValuesLittleEndian() { + settings = settings.withBigEndian(false); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes(str(0xffL), str(0xffL))); + assertEquals(1, compareBytes(str(0xffL), str(0xfeL))); + assertEquals(-1, compareBytes(str(0xfeL), str(0xffL))); + + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("136 255", convertText(binarySettings, "10001000 11111111")); + assertEquals("1 0", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("86 18", convertText(hexSettings, "56 12")); + assertEquals("1 0", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromOtherSizedInts() { + assertEquals("0 0 0 22 255 255 255 242", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("0 0 0 0 0 0 0 22 255 255 255 255", + convertText(int4Settings, "0 22 -1")); + assertEquals("0", convertText(int8Settings, "0")); + assertEquals("255 255 255 255 255 255 255 255", convertText(int8Settings, "-1")); + + assertEquals("78 32", convertText(int2Settings, "20000")); + assertEquals("1 49 45 0", convertText(int4Settings, "20000000")); + assertEquals("0 0 1 209 169 74 32 0", convertText(int8Settings, "2000000000000")); + + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("0 22 255", convertText(uint1Settings, "0 22 255")); + assertEquals("0 0 0 22 255 255", convertText(uint2Settings, "0 22 65535")); + assertEquals("0 0 0 22 255 255 255 255", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("255 255 255 255 255 255 255 255", + convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromDoubles() { + assertEquals("", convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("123", convertText(stringSettings, "123")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("123", convertText(regExSettings, "123")); + } + + private void assertRoundTrip(long expected, String input) { + matcher = parse(input); + byte[] bytes = matcher.getBytes(); + long value = getNumberFormat().getValue(bytes, 0, settings); + assertEquals(expected, value); + } + + private DecimalSearchFormat getNumberFormat() { + return (DecimalSearchFormat) format; + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt2SearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt2SearchFormatTest.java new file mode 100644 index 0000000000..0ed64f5d82 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt2SearchFormatTest.java @@ -0,0 +1,186 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class UInt2SearchFormatTest extends AbstractSearchFormatTest { + public UInt2SearchFormatTest() { + super(SearchFormat.DECIMAL); + settings = settings.withDecimalByteSize(2); + settings = settings.withDecimalUnsigned(true); + } + + @Test + public void testSimpleCaseBigEndian() { + matcher = parse("1 2 3"); + assertBytes(0, 1, 0, 2, 0, 3); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testSimpleCaseLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("1 2 3"); + assertBytes(1, 0, 2, 0, 3, 0); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testMinimum() { + matcher = parse("0"); + assertBytes(0, 0); + + ByteMatcher byteMatcher = format.parse("-1", settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [0, 65535]", + byteMatcher.getDescription()); + } + + @Test + public void testMaximum() { + long value = 0xffffL; + matcher = parse(Long.toString(value)); + assertBytes(0xff, 0xff); + + value += 1; + ByteMatcher byteMatcher = format.parse(Long.toString(value), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [0, 65535]", + byteMatcher.getDescription()); + } + + @Test + public void testNegativeSignOnly() { + ByteMatcher byteMatcher = format.parse("-", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Negative numbers not allowed for unsigned values", + byteMatcher.getDescription()); + } + + @Test + public void testBadChars() { + ByteMatcher byteMatcher = format.parse("12z", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Number parse error: For input string: \"12z\"", byteMatcher.getDescription()); + } + + @Test + public void testGetValueBigEndian() { + settings = settings.withBigEndian(true); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(0xffffL, Long.toString(0xffffL)); + } + + @Test + public void testGetValueLittleEndian() { + settings = settings.withBigEndian(false); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(0xffffL, Long.toString(0xffffL)); + } + + @Test + public void testCompareValuesBigEndian() { + settings = settings.withBigEndian(true); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes(str(0xffffl), str(0xffffL))); + assertEquals(1, compareBytes(str(0xffffl), str(0xfffeL))); + assertEquals(-1, compareBytes(str(0xfffeL), str(0xffffL))); + + } + + @Test + public void testCompareValuesLittleEndian() { + settings = settings.withBigEndian(false); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes(str(0xffffL), str(0xffffL))); + assertEquals(1, compareBytes(str(0xffffL), str(0xfffeL))); + assertEquals(-1, compareBytes(str(0xfffeL), str(0xffffL))); + + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("35071", convertText(binarySettings, "10001000 11111111")); + assertEquals("256", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("22034", convertText(hexSettings, "56 12")); + assertEquals("256", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromOtherSizedInts() { + assertEquals("22 65522", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("0 22 65535 65535", convertText(int4Settings, "22 -1")); + assertEquals("0 0 0 22 65535 65535 65535 65535", convertText(int8Settings, "22 -1")); + + assertEquals("305 11520", convertText(int4Settings, "20000000")); + assertEquals("0 465 43338 8192", convertText(int8Settings, "2000000000000")); + + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("22 255", convertText(uint1Settings, "0 22 255")); + assertEquals("0 22 65535", convertText(uint2Settings, "0 22 65535")); + assertEquals("0 22 65535 65535", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("65535 65535 65535 65535", + convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromDoubles() { + assertEquals("", convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("123", convertText(stringSettings, "123")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("123", convertText(regExSettings, "123")); + } + + private void assertRoundTrip(long expected, String input) { + matcher = parse(input); + byte[] bytes = matcher.getBytes(); + long value = getNumberFormat().getValue(bytes, 0, settings); + assertEquals(expected, value); + } + + private DecimalSearchFormat getNumberFormat() { + return (DecimalSearchFormat) format; + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt4SearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt4SearchFormatTest.java new file mode 100644 index 0000000000..34051186f8 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt4SearchFormatTest.java @@ -0,0 +1,187 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class UInt4SearchFormatTest extends AbstractSearchFormatTest { + public UInt4SearchFormatTest() { + super(SearchFormat.DECIMAL); + settings = settings.withDecimalByteSize(4); + settings = settings.withDecimalUnsigned(true); + } + + @Test + public void testSimpleCaseBigEndian() { + matcher = parse("1 2 3"); + assertBytes(0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testSimpleCaseLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("1 2 3"); + assertBytes(1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testMinimum() { + matcher = parse("0"); + assertBytes(0, 0, 0, 0); + + ByteMatcher byteMatcher = format.parse("-1", settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [0, 4294967295]", + byteMatcher.getDescription()); + } + + @Test + public void testMaximum() { + long value = 0xffffffffL; + matcher = parse(Long.toString(value)); + assertBytes(0xff, 0xff, 0xff, 0xff); + + value += 1; + ByteMatcher byteMatcher = format.parse(Long.toString(value), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [0, 4294967295]", + byteMatcher.getDescription()); + } + + @Test + public void testNegativeSignOnly() { + ByteMatcher byteMatcher = format.parse("-", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Negative numbers not allowed for unsigned values", + byteMatcher.getDescription()); + } + + @Test + public void testBadChars() { + ByteMatcher byteMatcher = format.parse("12z", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Number parse error: For input string: \"12z\"", byteMatcher.getDescription()); + } + + @Test + public void testGetValueBigEndian() { + settings = settings.withBigEndian(true); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(0xffffffffL, Long.toString(0xffffffffL)); + } + + @Test + public void testGetValueLittleEndian() { + settings = settings.withBigEndian(false); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(0xffffffffL, Long.toString(0xffffffffL)); + } + + @Test + public void testCompareValuesBigEndian() { + settings = settings.withBigEndian(true); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes(str(0xffffffffl), str(0xffffffffL))); + assertEquals(1, compareBytes(str(0xffffffffl), str(0xfffffffeL))); + assertEquals(-1, compareBytes(str(0xfffffffeL), str(0xffffffffL))); + + } + + @Test + public void testCompareValuesLittleEndian() { + settings = settings.withBigEndian(false); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + assertEquals(0, compareBytes(str(0xffffffffL), str(0xffffffffL))); + assertEquals(1, compareBytes(str(0xffffffffL), str(0xfffffffeL))); + assertEquals(-1, compareBytes(str(0xfffffffeL), str(0xffffffffL))); + + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("35071", convertText(binarySettings, "10001000 11111111")); + assertEquals("256", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("22034", convertText(hexSettings, "56 12")); + assertEquals("256", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromOtherSizedInts() { + assertEquals("1507314", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("22 4294967282", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("0 22 4294967295 4294967282", convertText(int4Settings, "0 22 -1 -14")); + assertEquals("0 22 4294967295 4294967295", convertText(int8Settings, "22 -1")); + + assertEquals("20000000", convertText(int4Settings, "20000000")); + assertEquals("465 2840207360", convertText(int8Settings, "2000000000000")); + + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("5887", convertText(uint1Settings, "0 22 255")); + assertEquals("22 65535", convertText(uint2Settings, "0 22 65535")); + assertEquals("22 4294967295", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + assertEquals("4294967295 4294967295", + convertText(uint8Settings, "18446744073709551615")); + } + + @Test + public void testConvertTextFromDoubles() { + assertEquals("", convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("123", convertText(stringSettings, "123")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("123", convertText(regExSettings, "123")); + } + + private void assertRoundTrip(long expected, String input) { + matcher = parse(input); + byte[] bytes = matcher.getBytes(); + long value = getNumberFormat().getValue(bytes, 0, settings); + assertEquals(expected, value); + } + + private DecimalSearchFormat getNumberFormat() { + return (DecimalSearchFormat) format; + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt8SearchFormatTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt8SearchFormatTest.java new file mode 100644 index 0000000000..2ba5142353 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/format/UInt8SearchFormatTest.java @@ -0,0 +1,192 @@ +/* ### + * 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.features.base.memsearch.format; + +import static org.junit.Assert.*; + +import java.math.BigInteger; + +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.ByteMatcher; + +public class UInt8SearchFormatTest extends AbstractSearchFormatTest { + public UInt8SearchFormatTest() { + super(SearchFormat.DECIMAL); + settings = settings.withDecimalByteSize(8); + settings = settings.withDecimalUnsigned(true); + } + + @Test + public void testSimpleCaseBigEndian() { + matcher = parse("1 2 3"); + assertBytes(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testSimpleCaseLittleEndian() { + settings = settings.withBigEndian(false); + matcher = parse("1 2 3"); + assertBytes(1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0); + assertMask(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + } + + @Test + public void testMinimum() { + matcher = parse("0"); + assertBytes(0, 0, 0, 0, 0, 0, 0, 0); + + ByteMatcher byteMatcher = format.parse("-1", settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [0, 18446744073709551615]", + byteMatcher.getDescription()); + } + + @Test + public void testMaximum() { + BigInteger value = new BigInteger("ffffffffffffffff", 16); + matcher = parse(value.toString()); + assertBytes(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + + BigInteger bigValue = value.add(BigInteger.ONE); + ByteMatcher byteMatcher = format.parse(bigValue.toString(), settings); + assertFalse(byteMatcher.isValidInput()); + assertEquals("Number must be in the range [0, 18446744073709551615]", + byteMatcher.getDescription()); + + } + + @Test + public void testNegativeSignOnly() { + ByteMatcher byteMatcher = format.parse("-", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Negative numbers not allowed for unsigned values", + byteMatcher.getDescription()); + } + + @Test + public void testBadChars() { + ByteMatcher byteMatcher = format.parse("12z", settings); + assertFalse(byteMatcher.isValidInput()); + assertFalse(byteMatcher.isValidSearch()); + assertEquals("Number parse error: For input string: \"12z\"", byteMatcher.getDescription()); + } + + @Test + public void testGetValueBigEndian() { + settings = settings.withBigEndian(true); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(-1, new BigInteger("ffffffffffffffff", 16).toString()); + } + + @Test + public void testGetValueLittleEndian() { + settings = settings.withBigEndian(false); + assertRoundTrip(1, "1"); + assertRoundTrip(0, "0"); + assertRoundTrip(-1, new BigInteger("ffffffffffffffff", 16).toString()); + } + + @Test + public void testCompareValuesBigEndian() { + settings = settings.withBigEndian(true); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + BigInteger maxValue = new BigInteger("ffffffffffffffff", 16); + BigInteger maxValueMinus1 = maxValue.subtract(BigInteger.valueOf(1)); + assertEquals(0, compareBytes(maxValue.toString(), maxValue.toString())); + assertEquals(1, compareBytes(maxValue.toString(), maxValueMinus1.toString())); + assertEquals(-1, compareBytes(maxValueMinus1.toString(), maxValue.toString())); + + } + + @Test + public void testCompareValuesLittleEndian() { + settings = settings.withBigEndian(false); + assertEquals(0, compareBytes("10", "10")); + assertEquals(-1, compareBytes("9", "10")); + assertEquals(1, compareBytes("11", "10")); + + BigInteger maxValue = new BigInteger("ffffffffffffffff", 16); + BigInteger maxValueMinus1 = maxValue.subtract(BigInteger.valueOf(1)); + assertEquals(0, compareBytes(maxValue.toString(), maxValue.toString())); + assertEquals(1, compareBytes(maxValue.toString(), maxValueMinus1.toString())); + assertEquals(-1, compareBytes(maxValueMinus1.toString(), maxValue.toString())); + + } + + @Test + public void testConvertTextFromBinary() { + assertEquals("35071", convertText(binarySettings, "10001000 11111111")); + assertEquals("256", convertText(binarySettings, "1 0")); + } + + @Test + public void testConvertTextFromHex() { + assertEquals("22034", convertText(hexSettings, "56 12")); + assertEquals("256", convertText(hexSettings, "1 0")); + } + + @Test + public void testConvertTextFromOtherSizedInts() { + assertEquals("1507314", convertText(int1Settings, "0 22 -1 -14")); + assertEquals("98784247794", convertText(int2Settings, "0 22 -1 -14")); + assertEquals("22 18446744073709551615", convertText(int4Settings, "0 22 -1 -1")); + assertEquals("0 22 18446744073709551615", convertText(int8Settings, "0 22 -1")); + + assertEquals("20000000", convertText(int4Settings, "20000000")); + } + + @Test + public void testConvertTextFromUnsignedInts() { + assertEquals("5887", convertText(uint1Settings, "0 22 255")); + assertEquals("1507327", convertText(uint2Settings, "0 22 65535")); + assertEquals("98784247807", convertText(uint4Settings, "22 4294967295")); + assertEquals("22", convertText(uint8Settings, "22")); + } + + @Test + public void testConvertTextFromDoubles() { + assertEquals("", convertText(doubleSettings, "1.234")); + } + + @Test + public void testConvertTextFromString() { + assertEquals("", convertText(stringSettings, "Hey")); + assertEquals("123", convertText(stringSettings, "123")); + assertEquals("", convertText(regExSettings, "B*B")); + assertEquals("123", convertText(regExSettings, "123")); + } + + private void assertRoundTrip(long expected, String input) { + matcher = parse(input); + byte[] bytes = matcher.getBytes(); + long value = getNumberFormat().getValue(bytes, 0, settings); + assertEquals(expected, value); + } + + private DecimalSearchFormat getNumberFormat() { + return (DecimalSearchFormat) format; + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/searcher/MemSearcherTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/searcher/MemSearcherTest.java new file mode 100644 index 0000000000..1c94a0fb1c --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/searcher/MemSearcherTest.java @@ -0,0 +1,337 @@ +/* ### + * 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.features.base.memsearch.searcher; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.features.base.memsearch.bytesource.AddressableByteSource; +import ghidra.features.base.memsearch.bytesource.SearchRegion; +import ghidra.features.base.memsearch.matcher.ByteMatcher; +import ghidra.features.base.memsearch.matcher.RegExByteMatcher; +import ghidra.program.model.address.*; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.datastruct.ListAccumulator; +import ghidra.util.task.TaskMonitor; + +public class MemSearcherTest { + private static final int SEARCH_LIMIT = 10; + private static final int TINY_CHUNK_SIZE = 4; + private TestByteSource bytes; + private AddressSpace space; + private TaskMonitor monitor = TaskMonitor.DUMMY; + private ByteMatcher bobMatcher = new RegExByteMatcher("bob", null); + private Accumulator accumulator = new ListAccumulator<>(); + + @Before + public void setUp() { + space = new GenericAddressSpace("test", 64, AddressSpace.TYPE_RAM, 0); + } + + @Test + public void testFindNext() { + bytes = new TestByteSource(addr(0), "xxbobxxx"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + + MemoryMatch match = searcher.findNext(addr(0), monitor); + assertMatch(2, "bob", match); + } + + @Test + public void testFindNextStartingAtMatch() { + bytes = new TestByteSource(addr(0), "xxbobxxx"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + + MemoryMatch match = searcher.findNext(addr(2), monitor); + assertMatch(2, "bob", match); + } + + @Test + public void testFindNextNoMatch() { + bytes = new TestByteSource(addr(0), "xxjoexxx"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + + MemoryMatch match = searcher.findNext(addr(0), monitor); + assertNull(match); + } + + @Test + public void testFindNextInSecondChunk() { + bytes = new TestByteSource(addr(0), "xxxx xbob x"); // spaces are removed by bytes call + AddressSet addresses = bytes.getAddressSet(); + MemorySearcher searcher = + new MemorySearcher(bytes, bobMatcher, addresses, SEARCH_LIMIT, TINY_CHUNK_SIZE); + + MemoryMatch match = searcher.findNext(addr(0), monitor); + assertMatch(5, "bob", match); + } + + @Test + public void testFindNextInLaterChunk() { + bytes = new TestByteSource(addr(0), "xxxx xxxx xxxx xxxx xbob x"); + MemorySearcher searcher = + new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT, TINY_CHUNK_SIZE); + + MemoryMatch match = searcher.findNext(addr(0), monitor); + assertMatch(17, "bob", match); + } + + @Test + public void testFindNextMatchSpansChunks() { + bytes = new TestByteSource(addr(0), "xxxb obxx"); + MemorySearcher searcher = + new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT, TINY_CHUNK_SIZE); + + MemoryMatch match = searcher.findNext(addr(0), monitor); + assertMatch(3, "bob", match); + } + + @Test + public void testFindNextMultipleRanges() { + bytes = new TestByteSource(addr(0), "xxxxx"); + bytes.addBytes(addr(100), "xxxxxboxxbxx"); + bytes.addBytes(addr(200), "xxxbobxxxxbobxxxx"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + + MemoryMatch match = searcher.findNext(addr(0), monitor); + assertMatch(203, "bob", match); + } + + @Test + public void testFindPrevious() { + bytes = new TestByteSource(addr(0), "xxbobxxx"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + + MemoryMatch match = searcher.findPrevious(addr(100), monitor); + assertMatch(2, "bob", match); + } + + @Test + public void testFindPreviousNoMatch() { + bytes = new TestByteSource(addr(0), "xxjoexxx"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + + MemoryMatch match = searcher.findPrevious(addr(100), monitor); + assertNull(match); + } + + @Test + public void testFindPreviousStartingAtMatch() { + bytes = new TestByteSource(addr(0), "xxbobxxx"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + + MemoryMatch match = searcher.findPrevious(addr(2), monitor); + assertMatch(2, "bob", match); + } + + @Test + public void testFindPreviousInFirstChunk() { + bytes = new TestByteSource(addr(0), "xxxx xbob"); + MemorySearcher searcher = + new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT, TINY_CHUNK_SIZE); + + MemoryMatch match = searcher.findPrevious(addr(100), monitor); + assertMatch(5, "bob", match); + } + + @Test + public void testFindPreviousInSecondChunk() { + bytes = new TestByteSource(addr(0), "xbob xxxx"); + MemorySearcher searcher = + new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT, TINY_CHUNK_SIZE); + + MemoryMatch match = searcher.findPrevious(addr(100), monitor); + assertMatch(1, "bob", match); + } + + @Test + public void testFindPreviousInLaterChunk() { + bytes = new TestByteSource(addr(0), "xbob xxxx xxxx xxxx xxxx xxxx x"); + MemorySearcher searcher = + new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT, TINY_CHUNK_SIZE); + + MemoryMatch match = searcher.findPrevious(addr(100), monitor); + assertMatch(1, "bob", match); + } + + @Test + public void testFindPreviousSpansChunk() { + bytes = new TestByteSource(addr(0), "xxbo bxxx"); + MemorySearcher searcher = + new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT, TINY_CHUNK_SIZE); + + MemoryMatch match = searcher.findPrevious(addr(100), monitor); + assertMatch(2, "bob", match); + } + + @Test + public void testFindPrevioustMultipleRanges() { + bytes = new TestByteSource(addr(0), "xxbobxxx"); + bytes.addBytes(addr(100), "xxxxxboxxbxx"); + bytes.addBytes(addr(200), "xxxxxxxbbxxxx"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + + MemoryMatch match = searcher.findNext(addr(0), monitor); + assertMatch(2, "bob", match); + } + + @Test + public void testFindAll() { + bytes = new TestByteSource(addr(0), "xbob xxxb obxx xxxx xxbo b"); + MemorySearcher searcher = + new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT, TINY_CHUNK_SIZE); + searcher.findAll(accumulator, monitor); + assertEquals(3, accumulator.size()); + Iterator it = accumulator.iterator(); + assertMatch(1, "bob", it.next()); + assertMatch(7, "bob", it.next()); + assertMatch(18, "bob", it.next()); + } + + @Test + public void testFindAllMultipleRanges() { + bytes = new TestByteSource(addr(0), "xbobxxxx"); + bytes.addBytes(addr(100), "bobxxxxxx"); + bytes.addBytes(addr(200), "xxxxxx"); + bytes.addBytes(addr(300), "xxxx xxbo bxxx bob"); + MemorySearcher searcher = + new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT, TINY_CHUNK_SIZE); + searcher.findAll(accumulator, monitor); + assertEquals(4, accumulator.size()); + Iterator it = accumulator.iterator(); + assertMatch(1, "bob", it.next()); + assertMatch(100, "bob", it.next()); + assertMatch(306, "bob", it.next()); + assertMatch(312, "bob", it.next()); + } + + @Test + public void testNextWithFilter() { + bytes = new TestByteSource(addr(0), "xxbobxxxbob"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + searcher.setMatchFilter(r -> r.getAddress().getOffset() != 2); + + MemoryMatch match = searcher.findNext(addr(0), monitor); + assertMatch(8, "bob", match); + + } + + @Test + public void testPreviousWithFilter() { + bytes = new TestByteSource(addr(0), "xxbobxxxbob"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + searcher.setMatchFilter(r -> r.getAddress().getOffset() != 8); + + MemoryMatch match = searcher.findNext(addr(0), monitor); + assertMatch(2, "bob", match); + + } + + @Test + public void testAllWithFilter() { + bytes = new TestByteSource(addr(0), "bobx xxbo bxxx xxxx xbob xxxx bobx"); + MemorySearcher searcher = new MemorySearcher(bytes, bobMatcher, addrs(), SEARCH_LIMIT); + searcher.setMatchFilter(r -> r.getAddress().getOffset() % 2 == 0); // only even addresses + + searcher.findAll(accumulator, monitor); + + assertEquals(3, accumulator.size()); + Iterator it = accumulator.iterator(); + assertMatch(0, "bob", it.next()); + assertMatch(6, "bob", it.next()); + assertMatch(24, "bob", it.next()); + } + + private AddressSet addrs() { + return bytes.getAddressSet(); + } + + private void assertMatch(int address, String matchString, MemoryMatch match) { + assertNotNull(match); + assertEquals(addr(address), match.getAddress()); + assertEquals(matchString.length(), match.getLength()); + assertEqualBytes(bytes(matchString), match.getBytes()); + } + + private void assertEqualBytes(byte[] bytes1, byte[] bytes2) { + assertEquals(bytes1.length, bytes2.length); + for (int i = 0; i < bytes1.length; i++) { + assertEquals(bytes1[i], bytes2[i]); + } + } + + private byte[] bytes(String string) { + // remove spaces as they are there for formatting purposes + string = string.replaceAll(" ", ""); + return string.getBytes(); + } + + private class TestByteSource implements AddressableByteSource { + private AddressSet set = new AddressSet(); + private Map map = new HashMap<>(); + + TestByteSource(Address address, String data) { + addBytes(address, data); + } + + public AddressSet getAddressSet() { + return set; + } + + void addBytes(Address address, String data) { + byte[] dataBytes = bytes(data); + Address end = address.add(dataBytes.length - 1); + int beforeNumAddressRanges = set.getNumAddressRanges(); + set.addRange(address, end); + int afterNumAddressRanges = set.getNumAddressRanges(); + // this simplistic test implementation can't handle ranges that coalesce so make + // sure our address set has an addition range in case we mess up writing a test + assertEquals(beforeNumAddressRanges + 1, afterNumAddressRanges); + map.put(address, dataBytes); + } + + @Override + public int getBytes(Address address, byte[] byteData, int length) { + AddressRange range = set.getRangeContaining(address); + if (range == null) { + return 0; + } + Address minAddress = range.getMinAddress(); + int index = (int) address.subtract(minAddress); + byte[] sourceBytes = map.get(minAddress); + System.arraycopy(sourceBytes, index, byteData, 0, length); + return length; + } + + @Override + public List getSearchableRegions() { + return null; + } + + @Override + public void invalidate() { + // ignore + } + } + + private Address addr(long offset) { + return space.getAddress(offset); + } +} diff --git a/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/plugin/IngestTask.java b/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/plugin/IngestTask.java index a3c69c4a00..c3a0eeb9c7 100644 --- a/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/plugin/IngestTask.java +++ b/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/plugin/IngestTask.java @@ -4,9 +4,9 @@ * 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. @@ -50,7 +50,7 @@ public class IngestTask extends Task { DomainFolder folder, String libraryFamilyName, String libraryVersion, String libraryVariant, String languageId, File commonSymbolsFile, FidService fidService, FidPopulateResultReporter reporter) { - super(title); + super(title, true, false, false, false); this.fidFile = fidFile; this.libraryRecord = libraryRecord; this.folder = folder; diff --git a/Ghidra/Features/SourceCodeLookup/src/main/java/ghidra/app/plugin/core/scl/SourceCodeLookupPlugin.java b/Ghidra/Features/SourceCodeLookup/src/main/java/ghidra/app/plugin/core/scl/SourceCodeLookupPlugin.java index f42dbf2209..25cc3c8cdb 100644 --- a/Ghidra/Features/SourceCodeLookup/src/main/java/ghidra/app/plugin/core/scl/SourceCodeLookupPlugin.java +++ b/Ghidra/Features/SourceCodeLookup/src/main/java/ghidra/app/plugin/core/scl/SourceCodeLookupPlugin.java @@ -4,9 +4,9 @@ * 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. @@ -15,12 +15,12 @@ */ package ghidra.app.plugin.core.scl; -import java.awt.event.KeyEvent; import java.io.*; import java.net.Socket; import docking.ActionContext; -import docking.action.*; +import docking.action.DockingAction; +import docking.action.MenuData; import docking.tool.ToolConstants; import ghidra.app.CorePluginPackage; import ghidra.app.context.ProgramLocationActionContext; @@ -94,7 +94,9 @@ public class SourceCodeLookupPlugin extends ProgramPlugin { // how to define the group/menu position. For now, just put the menu in the main menu bar. // lookupSourceCodeAction.setPopupMenuData(new MenuData(POPUP_PATH, null, "Label", // MenuData.NO_MNEMONIC, "z")); - lookupSourceCodeAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_F3, 0)); + + // F3 is conflicting with memory search again. + // lookupSourceCodeAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_F3, 0)); lookupSourceCodeAction.setHelpLocation( new HelpLocation("SourceCodeLookupPlugin", "Source_Code_Lookup_Plugin")); tool.addAction(lookupSourceCodeAction); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java b/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java index 35846dce37..519a74f5f7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java @@ -4,9 +4,9 @@ * 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. @@ -179,4 +179,12 @@ public interface ActionContext { */ public Component getSourceComponent(); + /** + * Sets the source component for this ActionContext. + * + * @param sourceComponent the source component + * @return this context + */ + public ActionContext setSourceComponent(Component sourceComponent); + } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DefaultActionContext.java b/Ghidra/Framework/Docking/src/main/java/docking/DefaultActionContext.java index 2e45bb4052..1e5c7e6c66 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DefaultActionContext.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DefaultActionContext.java @@ -4,9 +4,9 @@ * 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. @@ -164,6 +164,12 @@ public class DefaultActionContext implements ActionContext { return sourceComponent; } + @Override + public ActionContext setSourceComponent(Component sourceComponent) { + this.sourceComponent = sourceComponent; + return this; + } + @Override public String toString() { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockableHeader.java b/Ghidra/Framework/Docking/src/main/java/docking/DockableHeader.java index aed16982ec..d5d22b99a0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockableHeader.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockableHeader.java @@ -4,9 +4,9 @@ * 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. @@ -235,10 +235,14 @@ public class DockableHeader extends GenericHeader Component firstComponent = policy.getFirstComponent(dockComp); if (firstComponent == null) { ComponentPlaceholder info = dockComp.getComponentWindowingPlaceholder(); + String title = ""; + if (info != null) { + title = ": Title: " + info.getTitle() + ""; + } Msg.debug(this, - "Found a ComponentProvider that does not contain a " + "focusable component: " + - info.getTitle() + ". ComponentProviders are " + - "required to have at least one focusable component!"); + "Found a Component Provider that does not contain a focusable component" + + title + + ". Component Providers are required to have at least one focusable component!"); setSelected(false); // can't select it can't take focus } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/ButtonState.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/ButtonState.java new file mode 100644 index 0000000000..2746abbcee --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/ButtonState.java @@ -0,0 +1,73 @@ +/* ### + * 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 docking.menu; + +/** + * Defines one "state" for a {@link MultiStateButton}. Each button state represents one choice from + * a drop-down list of choices on the button. Each state provides information on what the button + * text should be when it is the active state, the text in the drop-down for picking the state, text + * for a tooltip description, and finally client data that the client can use to store info for + * processing the action when that state is active. + * + * @param the type of the client data object. + */ +public class ButtonState { + private String buttonText; + private String menuText; + private String description; + private T clientData; + + /** + * Constructor + * @param buttonText the text to display as both the drop-down choice and the active button text + * @param description the tooltip for this state + * @param clientData the client data for this state + */ + public ButtonState(String buttonText, String description, T clientData) { + this(buttonText, buttonText, description, clientData); + } + + /** + * Constructor + * @param buttonText the text to display in the button when this state is active + * @param menuText the text to display in the drop-down list + * @param description the tooltip for this state + * @param clientData the client data for this state + */ + public ButtonState(String buttonText, String menuText, String description, T clientData) { + this.buttonText = buttonText; + this.menuText = menuText; + this.description = description; + this.clientData = clientData; + } + + public String getButtonText() { + return buttonText; + } + + public String getMenuText() { + return menuText; + } + + public String getDescription() { + return description; + } + + public T getClientData() { + return clientData; + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateButton.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateButton.java new file mode 100644 index 0000000000..a3c34907f2 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateButton.java @@ -0,0 +1,414 @@ +/* ### + * 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 docking.menu; + +import java.awt.*; +import java.awt.event.*; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + +import docking.*; +import generic.theme.GThemeDefaults.Colors; +import generic.theme.GThemeDefaults.Colors.Messages; +import ghidra.util.Swing; +import resources.Icons; +import resources.ResourceManager; +import utility.function.Dummy; + +/** + * A button that has a drop-down list of choosable {@link ButtonState}s. When a state is selected, + * it changes the behavior of the action associated with the button. This code is based on code + * for the {@link MultipleActionDockingToolbarButton}. + * + * @param The type of the user data associated with the {@link ButtonState}s + */ +public class MultiStateButton extends JButton { + + private Icon arrowIcon; + private Icon disabledArrowIcon; + + private static int ARROW_WIDTH = 6; + private static int ARROW_HEIGHT = 3; + private static int ARROW_ICON_WIDTH = 20; + private static int ARROW_ICON_HEIGHT = 15; + + private PopupMouseListener popupListener; + private JPopupMenu popupMenu; + private Rectangle arrowButtonRegion; + private long popupLastClosedTime; + private List> buttonStates; + private ButtonState currentButtonState; + + private Consumer> stateChangedConsumer = Dummy.consumer(); + + public MultiStateButton(List> buttonStates) { + setButtonStates(buttonStates); + installMouseListeners(); + + arrowButtonRegion = createArrowRegion(); + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + super.componentResized(e); + arrowButtonRegion = createArrowRegion(); + repaint(); + } + }); + addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_DOWN) { + Swing.runLater(() -> popupMenu = showPopup()); + e.consume(); + } + } + }); + } + + public void setButtonStates(List> buttonStates) { + this.buttonStates = buttonStates; + if (buttonStates.size() == 1) { + arrowIcon = Icons.EMPTY_ICON; + disabledArrowIcon = Icons.EMPTY_ICON; + setHorizontalAlignment(SwingConstants.CENTER); // center text if no drop-down menu + } + else { + arrowIcon = new ArrowIcon(); + disabledArrowIcon = ResourceManager.getDisabledIcon(arrowIcon); + setHorizontalAlignment(SwingConstants.LEFT); // align left if we have drop-down menu + } + setCurrentButtonState(buttonStates.get(0)); + arrowButtonRegion = createArrowRegion(); + } + + /** + * Sets a consumer to be called when the user changes the active {@link ButtonState}. + * @param consumer the consumer to be called when the button state changes + */ + public void setStateChangedListener(Consumer> consumer) { + this.stateChangedConsumer = consumer; + } + + /** + * Sets the active button state for this button. + * @param buttonState the button state to be made active + */ + public void setCurrentButtonState(ButtonState buttonState) { + if (!buttonStates.contains(buttonState)) { + throw new IllegalArgumentException("Attempted to set button state to unknown state"); + } + this.currentButtonState = buttonState; + setText(buttonState.getButtonText()); + String tooltip = buttonState.getDescription(); + + setToolTipText(tooltip); + getAccessibleContext().setAccessibleDescription(tooltip); + stateChangedConsumer.accept(buttonState); + } + + /** + * Sets the active button state to the state that is associated with the given client data. + * @param clientData the client data to make its associated button state the active state + */ + public void setSelectedStateByClientData(T clientData) { + for (ButtonState buttonState : buttonStates) { + if (Objects.equals(clientData, buttonState.getClientData())) { + setCurrentButtonState(buttonState); + } + } + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + int y = (getHeight() - arrowIcon.getIconHeight()) / 2; + Icon icon = isEnabled() ? arrowIcon : disabledArrowIcon; + icon.paintIcon(this, g, arrowButtonRegion.x, y); + } + + @Override + public Dimension getPreferredSize() { + Dimension d = super.getPreferredSize(); + d.width = d.width + arrowIcon.getIconWidth(); + d.height = Math.max(d.height, arrowIcon.getIconHeight()); + return d; + } + + private Rectangle createArrowRegion() { + if (buttonStates.size() == 1) { + return new Rectangle(0, 0, 0, 0); + } + Dimension size = getSize(); + Border border = getBorder(); + + // Depending on the theme, the button may have thick borders to compensate for the extra + // space we requested in the preferred size method. Some themes do this via a compound + // border and using the outside border's right inset works very well to move the icon + // inside the border. + // Otherwise, we just use 3 as a decent compromise. Nimbus looks best with 3, but flat + // themes look best with 2 as they have a thinner "outside" border + int rightMargin = 3; + if (border instanceof CompoundBorder compoundBorder) { + Border outsideBorder = compoundBorder.getOutsideBorder(); + Insets borderInsets = outsideBorder.getBorderInsets(this); + rightMargin = borderInsets.right; + } + int w = arrowIcon.getIconWidth() + rightMargin; + int h = size.height; + return new Rectangle(size.width - w, 0, w, h); + } + + @Override + public void updateUI() { + + removeMouseListener(popupListener); + + super.updateUI(); + + installMouseListeners(); + } + + private void installMouseListeners() { + MouseListener[] mouseListeners = getMouseListeners(); + for (MouseListener mouseListener : mouseListeners) { + removeMouseListener(mouseListener); + } + + popupListener = new PopupMouseListener(mouseListeners); + addMouseListener(popupListener); + } + + protected ActionContext getActionContext() { + ComponentProvider provider = getComponentProvider(); + ActionContext context = provider == null ? null : provider.getActionContext(null); + final ActionContext actionContext = context == null ? new DefaultActionContext() : context; + return actionContext; + } + + private ComponentProvider getComponentProvider() { + DockingWindowManager manager = DockingWindowManager.getActiveInstance(); + if (manager == null) { + return null; + } + return manager.getActiveComponentProvider(); + } + + /** + * Show a popup containing all the actions below the button + * + * @return the popup menu that was shown + */ + protected JPopupMenu showPopup() { + + if (popupIsShowing()) { + popupMenu.setVisible(false); + return null; + } + + // + // showPopup() will handled 2 cases when this action's button is clicked: + // 1) show a popup if it was not showing + // 2) hide the popup if it was showing + // + // Case 2 requires timestamps. Java will close the popup as the button is clicked. This + // means that when we are told to show the popup as the result of a click, the popup will + // never be showing. To work around this, we track the elapsed time since last click. If + // the period is too short, then we assume Java closed the popup when the click happened + // and thus we should ignore it. + // + long elapsedTime = System.currentTimeMillis() - popupLastClosedTime; + if (elapsedTime < 500) { // somewhat arbitrary time window + return null; + } + + JPopupMenu menu = doCreateMenu(); + + menu.addPopupMenuListener(popupListener); + Point p = getPopupPoint(); + menu.show(this, p.x, p.y); + return menu; + } + + protected JPopupMenu doCreateMenu() { + + JPopupMenu menu = new JPopupMenu(); + ButtonGroup buttonGroup = new ButtonGroup(); + for (ButtonState state : buttonStates) { + + JCheckBoxMenuItem item = new JCheckBoxMenuItem(state.getMenuText()); + item.setToolTipText(state.getDescription()); + item.getAccessibleContext().setAccessibleDescription(state.getDescription()); + item.setSelected(state == currentButtonState); + buttonGroup.add(item); + + // a UI that handles alignment issues and allows for tabulating presentation + item.setUI(DockingMenuItemUI.createUI(item)); + item.addActionListener(e -> { + setCurrentButtonState(state); + }); + + menu.add(item); + } + return menu; + } + + public Point getPopupPoint() { + Rectangle bounds = getBounds(); + return new Point(bounds.width - arrowIcon.getIconWidth(), bounds.y + bounds.height); + } + + private boolean popupIsShowing() { + return (popupMenu != null) && popupMenu.isVisible(); + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + private class ArrowIcon implements Icon { + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(Messages.HINT); + g.drawLine(x, y, x, y + ARROW_ICON_HEIGHT); + g.setColor(Colors.FOREGROUND); + + int arrowMiddleX = x + ARROW_ICON_WIDTH / 2; + int arrowStartX = arrowMiddleX - ARROW_WIDTH / 2; + int arrowEndX = arrowStartX + ARROW_WIDTH; + + int arrowStartY = y + ARROW_ICON_HEIGHT / 2 - ARROW_HEIGHT / 2; + int arrowEndY = arrowStartY; + int arrowMiddleY = arrowStartY + ARROW_HEIGHT; + + int[] xPoints = { arrowStartX, arrowEndX, arrowMiddleX }; + int[] yPoints = { arrowStartY, arrowEndY, arrowMiddleY }; + + Graphics2D graphics2D = (Graphics2D) g; + graphics2D.drawPolygon(xPoints, yPoints, 3); + graphics2D.fillPolygon(xPoints, yPoints, 3); + } + + @Override + public int getIconWidth() { + return ARROW_ICON_WIDTH; + } + + @Override + public int getIconHeight() { + return ARROW_ICON_HEIGHT; + } + } + + private class PopupMouseListener extends MouseAdapter implements PopupMenuListener { + private final MouseListener[] parentListeners; + + public PopupMouseListener(MouseListener[] parentListeners) { + this.parentListeners = parentListeners; + } + + @Override + public void mousePressed(MouseEvent e) { + + Point clickPoint = e.getPoint(); + if (isEnabled() && arrowButtonRegion.contains(clickPoint)) { + + // Unusual Code Alert: we need to put this call in an invoke later, since Java + // will update the focused window after we click. We need the focus to be + // correct before we show, since our menu is built with actions based upon the + // focused component. + Swing.runLater(() -> popupMenu = showPopup()); + + e.consume(); + model.setPressed(false); + model.setArmed(false); + model.setRollover(false); + return; + } + + for (MouseListener listener : parentListeners) { + listener.mousePressed(e); + } + } + + @Override + public void mouseClicked(MouseEvent e) { + if (popupIsShowing()) { + e.consume(); + return; + } + + for (MouseListener listener : parentListeners) { + listener.mouseClicked(e); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (popupIsShowing()) { + e.consume(); + return; + } + + for (MouseListener listener : parentListeners) { + listener.mouseReleased(e); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + if (popupIsShowing()) { + return; + } + + for (MouseListener listener : parentListeners) { + listener.mouseEntered(e); + } + } + + @Override + public void mouseExited(MouseEvent e) { + if (popupIsShowing()) { + return; + } + for (MouseListener listener : parentListeners) { + listener.mouseExited(e); + } + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + // no-op + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + popupLastClosedTime = System.currentTimeMillis(); + } + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + // no-op + } + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java index 68f2f4aa69..667bfa7545 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java @@ -18,8 +18,7 @@ package docking.widgets; import java.awt.*; import java.awt.event.*; import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Iterator; +import java.util.*; import java.util.List; import javax.swing.*; @@ -31,6 +30,7 @@ import generic.theme.GThemeDefaults.Colors.Palette; import generic.util.WindowUtilities; import ghidra.util.bean.GGlassPane; import ghidra.util.bean.GGlassPanePainter; +import util.CollectionUtils; /** * A generic window intended to be used as a temporary window to show information. This window is @@ -52,8 +52,7 @@ public class PopupWindow { } private static final PopupWindowPlacer DEFAULT_WINDOW_PLACER = - new PopupWindowPlacerBuilder() - .rightEdge(Location.BOTTOM) + new PopupWindowPlacerBuilder().rightEdge(Location.BOTTOM) .leftEdge(Location.BOTTOM) .bottomEdge(Location.RIGHT) .topEdge(Location.CENTER) @@ -61,7 +60,10 @@ public class PopupWindow { .throwsAssertException() .build(); - /** Area where user can mouse without hiding the window (in screen coordinates) */ + /** + * Area where user can mouse without hiding the window (in screen coordinates). A.K.A., the + * mouse neutral zone. + */ private Rectangle mouseMovementArea; private JWindow popup; private Component sourceComponent; @@ -237,9 +239,39 @@ public class PopupWindow { popupWindowPlacer == null ? DEFAULT_WINDOW_PLACER : popupWindowPlacer; } - public void showOffsetPopup(MouseEvent e, Rectangle keepVisibleSize, boolean forceShow) { + public void showOffsetPopup(MouseEvent e, Rectangle keepVisibleArea, boolean forceShow) { if (forceShow || DockingUtils.isTipWindowEnabled()) { - doShowPopup(e, keepVisibleSize, popupWindowPlacer); + PopupSource popupSource = new PopupSource(e, keepVisibleArea); + doShowPopup(popupSource); + } + } + + /** + * Shows this popup window unless popups are disabled as reported by + * {@link DockingUtils#isTipWindowEnabled()}. If {@code forceShow} is true, then the popup + * will be shown regardless of the state returned by {@link DockingUtils#isTipWindowEnabled()}. + * @param e the event + * @param forceShow true to show the popup even popups are disabled application-wide + */ + public void showPopup(MouseEvent e, boolean forceShow) { + if (forceShow || DockingUtils.isTipWindowEnabled()) { + PopupSource popupSource = new PopupSource(e); + doShowPopup(popupSource); + } + } + + /** + * Shows this popup window unless popups are disabled as reported by + * {@link DockingUtils#isTipWindowEnabled()}. If {@code forceShow} is true, then the popup + * will be shown regardless of the state returned by {@link DockingUtils#isTipWindowEnabled()}. + * @param component the component for the popup + * @param location the location to show the popup + * @param forceShow true to show the popup even popups are disabled application-wide + */ + public void showPopup(Component component, Point location, boolean forceShow) { + if (forceShow || DockingUtils.isTipWindowEnabled()) { + PopupSource popupSource = new PopupSource(component, location, null); + doShowPopup(popupSource); } } @@ -253,34 +285,32 @@ public class PopupWindow { } /** - * Shows this popup window unless popups are disabled as reported by - * {@link DockingUtils#isTipWindowEnabled()}. If {@code forceShow} is true, then the popup - * will be shown regardless of the state returned by {@link DockingUtils#isTipWindowEnabled()}. - * @param e the event - * @param forceShow true to show the popup even popups are disabled application-wide + * Shows the popup window. This will hide any existing popup windows, adjusts the new popup + * to avoid covering the keep visible area and then shows the popup. + * + * @param popupSource the popup source that contains info about the source of the popup, such + * as the component, a mouse event and any area to keep visible. */ - public void showPopup(MouseEvent e, boolean forceShow) { - if (forceShow || DockingUtils.isTipWindowEnabled()) { - doShowPopup(e, null, popupWindowPlacer); - } - } + private void doShowPopup(PopupSource popupSource) { - private void doShowPopup(MouseEvent e, Rectangle keepVisibleSize, PopupWindowPlacer placer) { hideAllWindows(); - sourceComponent = e.getComponent(); + sourceComponent = popupSource.getSource(); sourceComponent.addMouseListener(sourceMouseListener); sourceComponent.addMouseMotionListener(sourceMouseMotionListener); - Dimension popupDimension = popup.getSize(); - ensureSize(popupDimension); + Dimension popupSize = popup.getSize(); + ensureSize(popupSize); - Rectangle keepVisibleArea = createKeepVisibleArea(e, keepVisibleSize); + // + // Creates a rectangle that contains both given rectangles entirely and includes padding. + // The padding allows users to mouse over the edge of the hovered area without triggering + // the popup to close. + // + Rectangle visibleArea = popupSource.getScreenKeepVisibleArea(); Rectangle screenBounds = WindowUtilities.getVisibleScreenBounds().getBounds(); - Rectangle placement = placer.getPlacement(popupDimension, keepVisibleArea, screenBounds); - mouseMovementArea = createMovementArea(placement, keepVisibleArea); - - installDebugPainter(e); + Rectangle placement = popupWindowPlacer.getPlacement(popupSize, visibleArea, screenBounds); + mouseMovementArea = placement.union(visibleArea); popup.setBounds(placement); popup.setVisible(true); @@ -290,26 +320,7 @@ public class PopupWindow { VISIBLE_POPUPS.add(new WeakReference<>(this)); } - private Rectangle createKeepVisibleArea(MouseEvent e, Rectangle keepVisibleAea) { - - Rectangle newArea; - if (keepVisibleAea == null) { - Point point = new Point(e.getPoint()); - newArea = new Rectangle(point); - newArea.grow(X_PADDING, Y_PADDING); // pad to avoid placing the popup too close - } - else { - newArea = new Rectangle(keepVisibleAea); - } - - Point point = newArea.getLocation(); - SwingUtilities.convertPointToScreen(point, sourceComponent); - newArea.setLocation(point); - - return newArea; - } - - private void ensureSize(Dimension popupDimension) { + private static void ensureSize(Dimension popupDimension) { Dimension screenDimension = WindowUtilities.getVisibleScreenBounds().getBounds().getSize(); if (screenDimension.width < popupDimension.width) { @@ -321,67 +332,120 @@ public class PopupWindow { } } - /** - * Creates a rectangle that contains both given rectangles entirely and includes padding. - * The padding allows users to mouse over the edge of the hovered area without triggering the - * popup to close. - */ - private Rectangle createMovementArea(Rectangle popupBounds, Rectangle hoverRectangle) { - Rectangle result = popupBounds.union(hoverRectangle); - return result; - } - - private void installDebugPainter(MouseEvent e) { -// GGlassPane glassPane = GGlassPane.getGlassPane(sourceComponent); -// ShapeDebugPainter painter = new ShapeDebugPainter(e, null, neutralMotionZone); -// painters.forEach(p -> glassPane.removePainter(p)); -// -// glassPane.addPainter(painter); -// painters.add(painter); - } - //================================================================================================== // Inner Classes //================================================================================================== - // for debug -// private static List painters = new ArrayList<>(); + /** + * A class that holds info related to the source of a hover request. This is used to position + * the popup window that will be shown. + */ + private class PopupSource { - /** Paints shapes used by this class (useful for debugging) */ - @SuppressWarnings("unused") - // enabled as needed - private class ShapeDebugPainter implements GGlassPanePainter { + private Component source; + private Rectangle screenKeepVisibleArea; + private Point location; - private MouseEvent sourceEvent; - private Rectangle bounds; - - ShapeDebugPainter(MouseEvent sourceEvent, Rectangle bounds) { - this.sourceEvent = sourceEvent; - this.bounds = bounds; + PopupSource(MouseEvent e) { + this(e, null); } - @Override - public void paint(GGlassPane glassPane, Graphics g) { + PopupSource(MouseEvent e, Rectangle keepVisibleArea) { + this(e.getComponent(), e.getPoint(), keepVisibleArea); + } - // bounds of the popup and the mouse neutral zone - if (bounds != null) { - Rectangle r = bounds; - Point p = new Point(r.getLocation()); - SwingUtilities.convertPointFromScreen(p, glassPane); + PopupSource(Component source, Point location, Rectangle keepVisibleArea) { - Color c = Palette.LAVENDER; - g.setColor(c); - g.fillRect(p.x, p.y, r.width, r.height); + if (CollectionUtils.isAllNull(location, keepVisibleArea)) { + throw new NullPointerException("Both location and keepVisibleArea cannot be null"); + } + if (keepVisibleArea == null) { + keepVisibleArea = new Rectangle(location, new Dimension(0, 0)); + } + this.location = location; + this.source = source; + this.screenKeepVisibleArea = createScreenKeepVisibleArea(location, keepVisibleArea); + + installDebugPainter(keepVisibleArea); + } + + Component getSource() { + return source; + } + + Rectangle getScreenKeepVisibleArea() { + return screenKeepVisibleArea; + } + + private Rectangle createScreenKeepVisibleArea(Point p, Rectangle keepVisibleAea) { + + Rectangle newArea = keepVisibleAea; + if (keepVisibleAea == null) { + Point point = new Point(p); + newArea = new Rectangle(point); + newArea.grow(X_PADDING, Y_PADDING); // pad to avoid placing the popup too close } - // show where the user hovered - if (sourceEvent != null) { - Point p = sourceEvent.getPoint(); - p = SwingUtilities.convertPoint(sourceEvent.getComponent(), p.x, p.y, glassPane); - g.setColor(Palette.RED); - int offset = 10; - g.fillRect(p.x - offset, p.y - offset, (offset * 2), (offset * 2)); + return createScreenKeepVisibleArea(newArea); + } + + private Rectangle createScreenKeepVisibleArea(Rectangle keepVisibleAea) { + + Objects.requireNonNull(keepVisibleAea); + + Rectangle newArea = new Rectangle(keepVisibleAea); + Point point = newArea.getLocation(); + SwingUtilities.convertPointToScreen(point, source.getParent()); + newArea.setLocation(point); + return newArea; + } + + // for debug + private void installDebugPainter(Rectangle keepVisibleArea) { +// +// GGlassPane glassPane = GGlassPane.getGlassPane(source); +// for (GGlassPanePainter p : painters) { +// glassPane.removePainter(p); +// } +// ShapeDebugPainter painter = new ShapeDebugPainter(); +// +// glassPane.addPainter(painter); +// painters.add(painter); + } + + @SuppressWarnings("unused") + private static List painters = new ArrayList<>(); + + /** Paints shapes used by this class (useful for debugging) */ + @SuppressWarnings("unused") // enabled as needed + private class ShapeDebugPainter implements GGlassPanePainter { + + @Override + public void paint(GGlassPane glassPane, Graphics g) { + + int alpha = 150; + + // bounds of the popup and the mouse neutral zone + if (mouseMovementArea != null) { + Rectangle r = mouseMovementArea; + Point p = new Point(r.getLocation()); + SwingUtilities.convertPointFromScreen(p, glassPane); + + Color c = Palette.LAVENDER.withAlpha(alpha); + g.setColor(c); + g.fillRect(p.x, p.y, r.width, r.height); + } + + // show where the user hovered + if (location != null) { + Point p = new Point(location); + p = SwingUtilities.convertPoint(source.getParent(), p.x, p.y, glassPane); + g.setColor(Palette.RED.withAlpha(alpha)); + int offset = 10; + g.fillRect(p.x - offset, p.y - offset, (offset * 2), (offset * 2)); + } } } } + } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java index 5c9ff98269..c7d995207e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java @@ -4,9 +4,9 @@ * 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. @@ -38,7 +38,7 @@ import ghidra.util.exception.AssertException; * *

* 2) Adds the auto-completion feature. As a user types in the field, the combo box suggest the - * nearest matching entry in the combo box model. + * nearest matching entry in the combo box model. This is enabled by default. * *

* It also fixes the following bug: @@ -70,6 +70,7 @@ public class GhidraComboBox extends JComboBox implements GComponent { private PassThroughActionListener passThroughActionListener; private PassThroughKeyListener passThroughKeyListener; private PassThroughDocumentListener passThroughDocumentListener; + private DocumentListener documentListener; /** * Default constructor. @@ -344,6 +345,21 @@ public class GhidraComboBox extends JComboBox implements GComponent { else { super.requestFocus(); } + + } + + /** + * This enables or disables auto completion. When on, the combobox will attempt to auto-fill + * the input text box with drop-down items that start with the text entered. This behavior + * may not be desirable when the drop-down list is more than just a list of previously typed + * strings. Auto completion is on by default. + * @param enable if true, auto completion is on, otherwise it is off. + */ + public void setAutoCompleteEnabled(boolean enable) { + removeDocumentListener(documentListener); + if (enable) { + addDocumentListener(documentListener); + } } private String matchHistory(String input) { @@ -397,8 +413,8 @@ public class GhidraComboBox extends JComboBox implements GComponent { if (getRenderer() instanceof JComponent) { GComponent.setHTMLRenderingFlag((JComponent) getRenderer(), false); } - // add our internal listener to with all the others that the pass through listener will call - addDocumentListener(new MatchingItemsDocumentListener()); + documentListener = new MatchingItemsDocumentListener(); + addDocumentListener(documentListener); } @@ -422,7 +438,7 @@ public class GhidraComboBox extends JComboBox implements GComponent { textField.getDocument().addDocumentListener(passThroughDocumentListener); } - private JTextField getTextField() { + public JTextField getTextField() { Object object = getEditor().getEditorComponent(); if (object instanceof JTextField textField) { return textField; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/IncrementalLoadJob.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/IncrementalLoadJob.java index d872eb70f4..371dfc829e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/IncrementalLoadJob.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/IncrementalLoadJob.java @@ -4,9 +4,9 @@ * 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. @@ -58,7 +58,7 @@ public class IncrementalLoadJob extends Job implements ThreadedTable this.incrementalAccumulator = new IncrementalUpdatingAccumulator(); notifyStarted(monitor); - + boolean error = false; try { doExecute(monitor); } @@ -70,13 +70,14 @@ public class IncrementalLoadJob extends Job implements ThreadedTable // console. Plus, handling it here gives us a chance to notify that the process is // complete. String name = threadedModel.getName(); + error = true; Msg.showError(this, null, "Unexpected Exception", "Unexpected exception loading table model \"" + name + "\"", e); } } boolean interrupted = Thread.currentThread().isInterrupted(); - notifyCompleted(hasBeenCancelled(monitor) || interrupted); + notifyCompleted(hasBeenCancelled(monitor) || interrupted || error); // all data should have been posted at this point; clean up any data left in the accumulator incrementalAccumulator.clear(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java index 8e226fb3fc..61756b8eff 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java @@ -4,9 +4,9 @@ * 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. @@ -18,6 +18,7 @@ package docking.widgets.table.threaded; import static docking.widgets.table.AddRemoveListItem.Type.*; import java.util.*; +import java.util.function.Consumer; import javax.swing.SwingUtilities; import javax.swing.event.TableModelEvent; @@ -860,6 +861,17 @@ public abstract class ThreadedTableModel listeners.add(new OneTimeListenerWrapper(listener)); } + /** + * Adds a consumer that will be notified when the model finishes loading. The consumer + * is passed a boolean that indicates is true if the loading was cancelled. After the + * table completes loading, the listener is removed. + * + * @param completedLoadingConsumer the consumer to be notified when the table is done loading + */ + public void addInitialLoadListener(Consumer completedLoadingConsumer) { + listeners.add(new OneTimeCompletedLoadingAdapter(completedLoadingConsumer)); + } + /** * This is a way to know about updates from the table. * @@ -1016,4 +1028,34 @@ public abstract class ThreadedTableModel } } + /** + * Class to adapt a {@link ThreadedTableModelListener} to a single use Consumer that gets + * notified once when the table is done loading and then removes the threaded table model + * listener. + */ + private class OneTimeCompletedLoadingAdapter implements ThreadedTableModelListener { + private Consumer completedLoadingConsumer; + + OneTimeCompletedLoadingAdapter(Consumer completedLoadingConsumer) { + this.completedLoadingConsumer = completedLoadingConsumer; + } + + @Override + public void loadPending() { + // do nothing + } + + @Override + public void loadingStarted() { + // do nothing + } + + @Override + public void loadingFinished(boolean wasCancelled) { + removeThreadedTableModelListener(this); + completedLoadingConsumer.accept(wasCancelled); + } + + } + } diff --git a/Ghidra/Framework/Gui/src/main/java/ghidra/util/task/Task.java b/Ghidra/Framework/Gui/src/main/java/ghidra/util/task/Task.java index 5331e73fef..fed6ddd599 100644 --- a/Ghidra/Framework/Gui/src/main/java/ghidra/util/task/Task.java +++ b/Ghidra/Framework/Gui/src/main/java/ghidra/util/task/Task.java @@ -4,9 +4,9 @@ * 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. @@ -44,7 +44,7 @@ public abstract class Task implements MonitoredRunnable { * @param title the title associated with the task */ public Task(String title) { - this(title, true, false, false, false); + this(title, true, false, true, false); } /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressRangeSplitter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressRangeSplitter.java new file mode 100644 index 0000000000..7c46182aed --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressRangeSplitter.java @@ -0,0 +1,85 @@ +/* ### + * 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.program.model.address; + +/** + * {@link AddressRangeIterator} that takes a single address range and breaks it down into smaller + * address ranges of a specified maximum size. This is useful for clients that want to break + * down the processing of large address ranges into manageable chunks. For example, searching the + * bytes in memory can be broken so that chunks can be read into reasonably sized buffers. + */ +public class AddressRangeSplitter implements AddressRangeIterator { + private AddressRange remainingRange; + private int splitSize; + private boolean forward; + + /** + * Constructor + * @param range the address range to split apart + * @param splitSize the max size of each sub range + * @param forward if true, the sub ranges will be returned in address order; otherwise they + * will be returned in reverse address order. + */ + public AddressRangeSplitter(AddressRange range, int splitSize, boolean forward) { + remainingRange = range; + this.splitSize = splitSize; + this.forward = forward; + } + + @Override + public boolean hasNext() { + return remainingRange != null; + } + + @Override + public AddressRange next() { + if (remainingRange == null) { + return null; + } + if (isRangeSmallEnough()) { + AddressRange returnValue = remainingRange; + remainingRange = null; + return returnValue; + } + return forward ? extractChunkFromStart() : extractChunkFromEnd(); + } + + private AddressRange extractChunkFromStart() { + Address start = remainingRange.getMinAddress(); + Address end = start.add(splitSize - 1); + remainingRange = new AddressRangeImpl(end.next(), remainingRange.getMaxAddress()); + return new AddressRangeImpl(start, end); + } + + private AddressRange extractChunkFromEnd() { + Address end = remainingRange.getMaxAddress(); + Address start = end.subtract(splitSize - 1); + + remainingRange = new AddressRangeImpl(remainingRange.getMinAddress(), start.previous()); + return new AddressRangeImpl(start, end); + } + + private boolean isRangeSmallEnough() { + try { + int size = remainingRange.getBigLength().intValueExact(); + return size <= splitSize; + } + catch (ArithmeticException e) { + return false; + } + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSetView.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSetView.java index 218ae9ed91..a4a6f08270 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSetView.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSetView.java @@ -253,6 +253,26 @@ public interface AddressSetView extends Iterable { */ public Address findFirstAddressInCommon(AddressSetView set); + /** + * Returns the number of address in this address set before the given address. + * @param address the address after the last address to be counted + * @return the number of address in this address set before the given address + */ + public default long getAddressCountBefore(Address address) { + long count = 0; + for (AddressRange range : getAddressRanges()) { + if (range.getMinAddress().compareTo(address) > 0) { + return count; + } + else if (range.contains(address)) { + count += address.subtract(range.getMinAddress()); + return count; + } + count += range.getLength(); + } + return count; + } + /** * Trim address set removing all addresses less-than-or-equal to specified * address based upon {@link Address} comparison. @@ -305,4 +325,5 @@ public interface AddressSetView extends Iterable { } return trimmedSet; } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/address/AddressRangeSplitterTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/address/AddressRangeSplitterTest.java new file mode 100644 index 0000000000..5329b48ec4 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/address/AddressRangeSplitterTest.java @@ -0,0 +1,128 @@ +/* ### + * 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.program.model.address; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +public class AddressRangeSplitterTest { + private AddressSpace space; + + @Before + public void setUp() { + space = new GenericAddressSpace("test", 64, AddressSpace.TYPE_RAM, 0); + } + + @Test + public void testRangeDoesntNeedSplitting() { + AddressRange range = range(0, 100); + AddressRangeSplitter splitter = new AddressRangeSplitter(range, 1000, true); + assertTrue(splitter.hasNext()); + assertEquals(range(0, 100), splitter.next()); + assertFalse(splitter.hasNext()); + assertNull(splitter.next()); + } + + @Test + public void testRangeSplitting() { + AddressRange range = range(0, 500); + AddressRangeSplitter splitter = new AddressRangeSplitter(range, 100, true); + + assertTrue(splitter.hasNext()); + assertEquals(range(0, 99), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(100, 199), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(200, 299), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(300, 399), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(400, 499), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(500, 500), splitter.next()); + + assertFalse(splitter.hasNext()); + assertNull(splitter.next()); + } + + @Test + public void testSplittingRangeWhoseLengthIsLong() { + AddressRange range = range(0, 0xffffffffffffffffL); + AddressRangeSplitter splitter = new AddressRangeSplitter(range, 100, true); + + assertTrue(splitter.hasNext()); + assertEquals(range(0, 99), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(100, 199), splitter.next()); + assertTrue(splitter.hasNext()); + + } + + @Test + public void testReverseRangeDoesntNeedSplitting() { + AddressRange range = range(0, 100); + AddressRangeSplitter splitter = new AddressRangeSplitter(range, 1000, true); + assertTrue(splitter.hasNext()); + assertEquals(range(0, 100), splitter.next()); + assertFalse(splitter.hasNext()); + assertNull(splitter.next()); + } + + @Test + public void testReverseRangeSplitting() { + AddressRange range = range(0, 500); + AddressRangeSplitter splitter = new AddressRangeSplitter(range, 100, false); + + assertTrue(splitter.hasNext()); + assertEquals(range(401, 500), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(301, 400), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(201, 300), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(101, 200), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(1, 100), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(0, 0), splitter.next()); + + assertFalse(splitter.hasNext()); + assertNull(splitter.next()); + } + + @Test + public void testReverseSplittingRangeWhoseLengthIsLong() { + AddressRange range = range(0, 0xffffffffffffffffL); + AddressRangeSplitter splitter = new AddressRangeSplitter(range, 0x100, false); + + assertTrue(splitter.hasNext()); + assertEquals(range(0xffffffffffffff00L, 0xffffffffffffffffL), splitter.next()); + assertTrue(splitter.hasNext()); + assertEquals(range(0xfffffffffffffe00L, 0xfffffffffffffeffL), splitter.next()); + assertTrue(splitter.hasNext()); + + } + + private AddressRange range(long start, long end) { + return new AddressRangeImpl(addr(start), addr(end)); + } + + private Address addr(long offset) { + return space.getAddress(offset); + } +} diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/MemorySearchScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/MemorySearchScreenShots.java index c38c7cdf42..03cd066596 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/MemorySearchScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/MemorySearchScreenShots.java @@ -4,9 +4,9 @@ * 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. @@ -17,17 +17,17 @@ package help.screenshot; import java.awt.*; -import javax.swing.*; - import org.junit.Before; import org.junit.Test; -import docking.DialogComponentProvider; import docking.action.DockingActionIf; import generic.theme.GThemeDefaults.Colors.Palette; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; -import ghidra.app.plugin.core.searchmem.mask.MnemonicSearchPlugin; +import ghidra.features.base.memsearch.format.SearchFormat; +import ghidra.features.base.memsearch.gui.MemorySearchProvider; +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.mnemonic.MnemonicSearchPlugin; import ghidra.program.model.address.*; /** @@ -51,119 +51,46 @@ public class MemorySearchScreenShots extends AbstractSearchScreenShots { } @Test - public void testSearchMemoryHex() { - - moveTool(500, 500); - - performAction("Search Memory", "MemSearchPlugin", false); + public void testMemorySearchProvider() { + performAction("Memory Search", "MemorySearchPlugin", false); waitForSwing(); - DialogComponentProvider dialog = getDialog(); - JTextField textField = (JTextField) getInstanceField("valueField", dialog); - setText(textField, "12 34"); + MemorySearchProvider provider = getComponentProvider(MemorySearchProvider.class); - JToggleButton button = (JToggleButton) getInstanceField("advancedButton", dialog); - pressButton(button); + runSwing(() -> provider.setSearchInput("12 34")); - waitForSwing(); + captureIsolatedProvider(provider, 700, 400); - captureDialog(DialogComponentProvider.class); } @Test - public void testSearchMemoryRegex() { - - moveTool(500, 500); - - performAction("Search Memory", "MemSearchPlugin", false); + public void testMemorySearchProviderWithOptionsOn() { + performAction("Memory Search", "MemorySearchPlugin", false); waitForSwing(); - DialogComponentProvider dialog = getDialog(); - JRadioButton regexRadioButton = - (JRadioButton) findAbstractButtonByText(dialog.getComponent(), "Regular Expression"); - pressButton(regexRadioButton); + MemorySearchProvider provider = getComponentProvider(MemorySearchProvider.class); - JTextField textField = (JTextField) getInstanceField("valueField", dialog); - setText(textField, "\\x50.{0,10}\\x55"); + runSwing(() -> { + provider.setSearchInput("12 34"); + provider.showOptions(true); + }); - JToggleButton button = (JToggleButton) getInstanceField("advancedButton", dialog); - pressButton(button); - - waitForSwing(); - - captureDialog(DialogComponentProvider.class); + captureIsolatedProvider(provider, 700, 650); } @Test - public void testSearchMemoryBinary() { - - moveTool(500, 500); - - performAction("Search Memory", "MemSearchPlugin", false); + public void testMemorySearchProviderWithScanPanelOn() { + performAction("Memory Search", "MemorySearchPlugin", false); waitForSwing(); - DialogComponentProvider dialog = getDialog(); - JRadioButton binaryRadioButton = - (JRadioButton) findAbstractButtonByText(dialog.getComponent(), "Binary"); - pressButton(binaryRadioButton); + MemorySearchProvider provider = getComponentProvider(MemorySearchProvider.class); - JTextField textField = (JTextField) getInstanceField("valueField", dialog); - setText(textField, "10xx0011"); + runSwing(() -> { + provider.setSearchInput("12 34"); + provider.showScanPanel(true); + }); - JToggleButton button = (JToggleButton) getInstanceField("advancedButton", dialog); - pressButton(button); - - waitForSwing(); - - captureDialog(DialogComponentProvider.class); - } - - @Test - public void testSearchMemoryDecimal() { - - moveTool(500, 500); - - performAction("Search Memory", "MemSearchPlugin", false); - waitForSwing(); - - DialogComponentProvider dialog = getDialog(); - JRadioButton decimalRadioButton = - (JRadioButton) findAbstractButtonByText(dialog.getComponent(), "Decimal"); - pressButton(decimalRadioButton); - - JTextField textField = (JTextField) getInstanceField("valueField", dialog); - setText(textField, "1234"); - - JToggleButton button = (JToggleButton) getInstanceField("advancedButton", dialog); - pressButton(button); - - waitForSwing(); - - captureDialog(DialogComponentProvider.class); - } - - @Test - public void testSearchMemoryString() { - - moveTool(500, 500); - - performAction("Search Memory", "MemSearchPlugin", false); - waitForSwing(); - - DialogComponentProvider dialog = getDialog(); - JRadioButton stringRadioButton = - (JRadioButton) findAbstractButtonByText(dialog.getComponent(), "String"); - pressButton(stringRadioButton); - - JTextField textField = (JTextField) getInstanceField("valueField", dialog); - setText(textField, "Hello"); - - JToggleButton button = (JToggleButton) getInstanceField("advancedButton", dialog); - pressButton(button); - - waitForSwing(); - - captureDialog(DialogComponentProvider.class); + captureIsolatedProvider(provider, 700, 500); } @Test @@ -208,6 +135,21 @@ public class MemorySearchScreenShots extends AbstractSearchScreenShots { image = tf.getImage(); } + @Test + public void testSearchMemoryRegex() { + performAction("Memory Search", "MemorySearchPlugin", false); + waitForSwing(); + + MemorySearchProvider provider = getComponentProvider(MemorySearchProvider.class); + + runSwing(() -> { + provider.setSettings(new SearchSettings().withSearchFormat(SearchFormat.REG_EX)); + provider.setSearchInput("\\x50.{0,10}\\x55"); + }); + + captureIsolatedProvider(provider, 700, 300); + } + @Test public void testSearchInstructionsExcludeOperands() { Font font = new Font("Monospaced", Font.PLAIN, 14); From 129109ea3b98748629db52061fb3d263749c0380 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Fri, 30 Aug 2024 06:17:33 -0400 Subject: [PATCH 29/53] GP-4884: Fixing IndexOutOfBoundsException in OMF --- .../bin/format/omf/omf/OmfFileHeader.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfFileHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfFileHeader.java index e12532d272..aca6600ad0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfFileHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/omf/omf/OmfFileHeader.java @@ -37,7 +37,8 @@ public class OmfFileHeader extends OmfRecord { private List segments = new ArrayList<>(); private List groups = new ArrayList<>(); private List externsymbols = new ArrayList<>(); - private List symbols = new ArrayList<>(); + private List publicSymbols = new ArrayList<>(); + private List localSymbols = new ArrayList<>(); private List fixup = new ArrayList<>(); private List extraSeg = null; // Holds implied segments that don't have official header record // private OmfModuleEnd endModule = null; @@ -128,10 +129,17 @@ public class OmfFileHeader extends OmfRecord { } /** - * @return the list of symbols exported by this file + * @return the list of public symbols exported by this file */ public List getPublicSymbols() { - return symbols; + return publicSymbols; + } + + /** + * @return the list of local symbols in this file + */ + public List getLocalSymbols() { + return localSymbols; } /** @@ -371,7 +379,12 @@ public class OmfFileHeader extends OmfRecord { header.externsymbols.add(external); } else if (record instanceof OmfSymbolRecord symbol) { - header.symbols.add(symbol); + if (symbol.isStatic()) { + header.localSymbols.add(symbol); + } + else { + header.publicSymbols.add(symbol); + } } else if (record instanceof OmfNamesRecord names) { names.appendNames(header.nameList); // Keep names, otherwise don't save record From 184180d54dbdf3db3c030ead3772f6f8f46f9d72 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Fri, 30 Aug 2024 08:46:03 -0400 Subject: [PATCH 30/53] GP-0: Safeguarding Mach-O sections living outside of their segment during Program Tree creation (Closes #6865) --- .../ghidra/app/util/opinion/MachoProgramBuilder.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java index 4f53b6c195..0b3dfb1526 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java @@ -4,9 +4,9 @@ * 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. @@ -433,6 +433,11 @@ public class MachoProgramBuilder { } Address sectionStart = segmentSpace.getAddress(section.getAddress()); Address sectionEnd = sectionStart.add(section.getSize() - 1); + if (!memory.contains(sectionStart)) { + log.appendMsg("Warning: Section %s.%s is not contained within its segment" + .formatted(section.getSegmentName(), section.getSectionName())); + continue; + } if (!memory.contains(sectionEnd)) { sectionEnd = memory.getBlock(sectionStart).getEnd(); } From 7825f8fd4d96b4812833d5c9111ae9fbfcfb52cd Mon Sep 17 00:00:00 2001 From: ghidra007 Date: Fri, 30 Aug 2024 14:47:36 +0000 Subject: [PATCH 31/53] GP-4872 update to all add function to namespaces methods to commit the function so as to not overwrite decomipler when making other changes via listing. Also added check for existing equivalent listing constructor return type before setting it to class structure pointer. --- .../classrecovery/DecompilerScriptUtils.java | 35 +++++- .../classrecovery/RecoveredClassHelper.java | 101 +++++++++--------- 2 files changed, 79 insertions(+), 57 deletions(-) diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/DecompilerScriptUtils.java b/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/DecompilerScriptUtils.java index 2cc9ca3d51..ebc15b5ed8 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/DecompilerScriptUtils.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/DecompilerScriptUtils.java @@ -4,9 +4,9 @@ * 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. @@ -16,10 +16,8 @@ //DO NOT RUN. THIS IS NOT A SCRIPT! THIS IS A CLASS THAT IS USED BY SCRIPTS. package classrecovery; -import docking.options.OptionsService; import ghidra.app.decompiler.*; import ghidra.app.decompiler.component.DecompilerUtils; -import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.address.Address; import ghidra.program.model.data.DataType; @@ -27,7 +25,10 @@ import ghidra.program.model.data.ParameterDefinition; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.*; -import ghidra.util.exception.CancelledException; +import ghidra.program.model.pcode.HighFunctionDBUtil.ReturnCommitOption; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.Msg; +import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; public class DecompilerScriptUtils { @@ -109,6 +110,30 @@ public class DecompilerScriptUtils { return decompRes.getHighFunction().getFunctionPrototype().getReturnType(); } + public void commitFunction(Function function) { + DecompileResults decompRes = decompInterface.decompileFunction(function, + decompInterface.getOptions().getDefaultTimeout(), monitor); + + if (decompRes == null || decompRes.getHighFunction() == null || + decompRes.getHighFunction().getFunctionPrototype() == null) { + Msg.debug(this, "Couldn't commit params - null high function"); + return; + } + + try { + HighFunctionDBUtil.commitParamsToDatabase(decompRes.getHighFunction(), true, + ReturnCommitOption.COMMIT, SourceType.ANALYSIS); + } + catch (DuplicateNameException e) { + Msg.debug(this, "Couldn't commit params " + e); + return; + } + catch (InvalidInputException e) { + Msg.debug(this, "Couldn't commit params " + e); + return; + } + } + /** * Method to retrieve the function signature string from the decompiler function prototype. NOTE: * if there is a this param, it will not be included. diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassHelper.java b/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassHelper.java index e62e551dae..7c798d5694 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassHelper.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassHelper.java @@ -479,7 +479,8 @@ public class RecoveredClassHelper { return functionToLoadPcodeOps.get(function); } - public Set getAllVfunctions(List

vftableAddresses) throws CancelledException { + public Set getAllVfunctions(List
vftableAddresses) + throws CancelledException { if (vftableAddresses.isEmpty()) { return Collections.emptySet(); } @@ -3294,6 +3295,14 @@ public class RecoveredClassHelper { public void addConstructorsToClassNamespace(RecoveredClass recoveredClass, Structure classStruct) throws Exception { + DataType undefinedDT = null; + if (defaultPointerSize == 4) { + undefinedDT = new Undefined4DataType(); + } + if (defaultPointerSize == 8) { + undefinedDT = new Undefined8DataType(); + } + Namespace classNamespace = recoveredClass.getClassNamespace(); String className = recoveredClass.getName(); @@ -3310,70 +3319,53 @@ public class RecoveredClassHelper { true); } - // if current decompiler function return type is a pointer then set the return type - // to a pointer to the class structure, otherwise if it is a void, make it a void so the - // listing has void too, otherwise, leave it as is, probably a void - String returnType = getReturnTypeFromDecompiler(constructorFunction); + // commit what the decompiler knows first so that retyping will not + // completely overwrite decompiler with listing signature + decompilerUtils.commitFunction(constructorFunction); - // Set error bookmark, add error message, and get the listing return type if the - // decompiler return type is null - if (returnType == null) { + HighFunction highFunction = decompilerUtils.getHighFunction(constructorFunction); + if (highFunction == null) { + String msg = + "Decompiler Error: Failed to decompile function possibly due to the addition of class structure. "; - String msg1 = "Decompiler Error: Failed to decompile function"; - String msg2 = ", possibly due to the addition of class structure."; - - Msg.debug(this, msg1 + " at " + constructorFunction.getEntryPoint() + msg2); + Msg.debug(this, msg + constructorFunction.getEntryPoint()); program.getBookmarkManager() .setBookmark(constructorFunction.getEntryPoint(), BookmarkType.ERROR, - "Decompiler Error", msg1 + msg2); - - // get the return type from the listing and in some cases it will - // indicate the correct type to help determine the below type to add - returnType = constructorFunction.getReturnType().getDisplayName(); + "Decompiler Error", msg); + continue; } - if (returnType.equals("void")) { - constructorFunction.setReturnType(VoidDataType.dataType, SourceType.ANALYSIS); + DataType returnType = highFunction.getFunctionPrototype().getReturnType(); + if (returnType == null) { + Msg.debug(this, + "ERROR: Return type is null " + constructorFunction.getEntryPoint()); + continue; } - else if (returnType.contains("*")) { - DataType classPointerDataType = dataTypeManager.getPointer(classStruct); - constructorFunction.setReturnType(classPointerDataType, SourceType.ANALYSIS); - } - // if neither and it is a FID function change it to undefined so the decompiler will + + // if a FID function and isn't void or * change it to undefined so the decompiler will // recompute it - else if (isFidFunction(constructorFunction)) { - DataType undefinedDT = null; - if (defaultPointerSize == 4) { - undefinedDT = new Undefined4DataType(); - } - if (defaultPointerSize == 8) { - undefinedDT = new Undefined8DataType(); - } + String returnTypeString = returnType.getDisplayName(); + if (isFidFunction(constructorFunction) && returnTypeString != "void" && + !returnTypeString.contains("*")) { + if (undefinedDT != null) { constructorFunction.setReturnType(undefinedDT, SourceType.ANALYSIS); } } + // if return type is a pointer then make sure it is the class structure + if (returnType.getDisplayName().contains("*")) { + DataType classPointerDataType = dataTypeManager.getPointer(classStruct); + if (!returnType.isEquivalent(classPointerDataType)) { + constructorFunction.setReturnType(classPointerDataType, + SourceType.ANALYSIS); + } + } + } } - /** - * Get the return value from the decompiler signature for the given function - * @param function the given function - * @return the decompiler return value for the given function - */ - private String getReturnTypeFromDecompiler(Function function) { - - DataType decompilerReturnType = decompilerUtils.getDecompilerReturnType(function); - - if (decompilerReturnType == null) { - return null; - } - - return decompilerReturnType.getDisplayName(); - } - /** * Method to name class destructors and add them to class namespace * @param recoveredClass current class @@ -3399,8 +3391,7 @@ public class RecoveredClassHelper { createNewSymbolAtFunction(destructorFunction, destructorName, classNamespace, true, true); } - - destructorFunction.setReturnType(VoidDataType.dataType, SourceType.ANALYSIS); + decompilerUtils.commitFunction(destructorFunction); } } @@ -3426,6 +3417,7 @@ public class RecoveredClassHelper { createNewSymbolAtFunction(destructorFunction, destructorName, classNamespace, false, false); + decompilerUtils.commitFunction(destructorFunction); } } @@ -3450,8 +3442,7 @@ public class RecoveredClassHelper { createNewSymbolAtFunction(vbaseDestructorFunction, destructorName, classNamespace, true, true); } - - vbaseDestructorFunction.setReturnType(VoidDataType.dataType, SourceType.ANALYSIS); + decompilerUtils.commitFunction(vbaseDestructorFunction); } } @@ -3606,6 +3597,7 @@ public class RecoveredClassHelper { function.getEntryPoint().toString()); } return; + } symbol = lcmd.getSymbol(); @@ -4674,6 +4666,7 @@ public class RecoveredClassHelper { createNewSymbolAtFunction(vfunction, vfunctionName, classNamespace, setPrimary, removeBadFID); } + decompilerUtils.commitFunction(vfunction); } } } @@ -5040,6 +5033,10 @@ public class RecoveredClassHelper { if (nameVfunctions) { createNewSymbolAtFunction(indeterminateFunction, className + "_Constructor_or_Destructor", classNamespace, false, false); + // in this case since indeterminate, only commit if script names it + // if name flag is not set then it will have correct name from debug and be handled + // in other methods (ie addConst, addDest) + decompilerUtils.commitFunction(indeterminateFunction); } } From ecf5fca01562d2d76b9bf9a8a51897daf1fc8f2d Mon Sep 17 00:00:00 2001 From: ghidra007 Date: Fri, 30 Aug 2024 15:38:32 +0000 Subject: [PATCH 32/53] GP-4883 fix null exception in PropagateExternalParamsScript --- .../PropagateExternalParametersScript.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Ghidra/Features/Base/ghidra_scripts/PropagateExternalParametersScript.java b/Ghidra/Features/Base/ghidra_scripts/PropagateExternalParametersScript.java index 9ae737c9bf..2d97e2f76e 100644 --- a/Ghidra/Features/Base/ghidra_scripts/PropagateExternalParametersScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/PropagateExternalParametersScript.java @@ -4,9 +4,9 @@ * 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. @@ -31,6 +31,8 @@ import ghidra.program.model.symbol.*; public class PropagateExternalParametersScript extends GhidraScript { private List results = new ArrayList<>(); + private static final boolean PRINT_OPTYPE = false; + @Override public void run() throws Exception { Listing listing = currentProgram.getListing(); @@ -147,9 +149,9 @@ public class PropagateExternalParametersScript extends GhidraScript { while (tempIter.hasNext()) { Reference thunkRef = tempIter.next(); Address thunkRefAddr = thunkRef.getFromAddress(); - + CodeUnit cu = listing.getCodeUnitAt(thunkRefAddr); - if(cu == null) { + if (cu == null) { // println("Referenced CodeUnit is null: " + thunkRefAddr); continue; } @@ -300,10 +302,18 @@ public class PropagateExternalParametersScript extends GhidraScript { numSkips--; } else { + + // if option is true add the value of the optype to the EOL comment + String opType = ""; + if (PRINT_OPTYPE) { + opType = " " + toHexString(currentProgram.getListing() + .getInstructionAt(cu.getMinAddress()) + .getOperandType(0), + false, true); + } setEOLComment(cu.getMinAddress(), params[index].getDataType().getDisplayName() + - " " + params[index].getName() + " for " + extFuncName); - // add the following to the EOL comment to see the value of the optype - // + " | " + ghidra.program.model.lang.OperandType.toString(currentProgram.getListing().getInstructionAt(cu.getMinAddress()).getOperandType(0)) + " " + params[index].getName() + " for " + extFuncName + opType); + addResult(params[index].getName(), params[index].getDataType(), cu.getMinAddress(), extFuncName); index++; From 8ef42de6d834ba875f233e1ac9ef192b55ff78ad Mon Sep 17 00:00:00 2001 From: ghidragon <106987263+ghidragon@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:49:39 +0000 Subject: [PATCH 33/53] added ip for crystal icons to base module --- Ghidra/Features/Base/certification.manifest | 1 + 1 file changed, 1 insertion(+) diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 0b966dd28b..908a52c4d6 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -8,6 +8,7 @@ ##MODULE IP: Nuvola Icons - LGPL 2.1 ##MODULE IP: Oxygen Icons - LGPL 3.0 ##MODULE IP: Tango Icons - Public Domain +##MODULE IP: Crystal Clear Icons - LGPL 2.1 .gitignore||GHIDRA||||END| .launch/Ghidra Code Coverage.launch||GHIDRA||||END| .launch/Ghidra.launch||GHIDRA||||END| From 0b36b540cb4945b1c1e4802d520fc0bc072d59df Mon Sep 17 00:00:00 2001 From: emteere <47253321+emteere@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:19:25 -0400 Subject: [PATCH 34/53] GP-4776 Added more semantics for AVX instructions --- Ghidra/Processors/x86/data/languages/avx.sinc | 273 +++++++++++------- .../Processors/x86/data/languages/avx2.sinc | 155 ++++++++-- Ghidra/Processors/x86/data/languages/ia.sinc | 4 + 3 files changed, 304 insertions(+), 128 deletions(-) diff --git a/Ghidra/Processors/x86/data/languages/avx.sinc b/Ghidra/Processors/x86/data/languages/avx.sinc index 9b34ec7a71..08badb2c7e 100644 --- a/Ghidra/Processors/x86/data/languages/avx.sinc +++ b/Ghidra/Processors/x86/data/languages/avx.sinc @@ -3,18 +3,23 @@ # INFO Command line arguments: ['--sinc', '--skip-sinc', '../../../../../../../ghidra/Ghidra/Processors/x86/data/languages/avx_manual.sinc', '../../../../../../../ghidra/Ghidra/Processors/x86/data/languages/ia.sinc'] # ADDPD 3-33 PAGE 603 LINE 33405 -define pcodeop vaddpd_avx ; :VADDPD XmmReg1, vexVVVV_XmmReg, XmmReg2_m128 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x58; (XmmReg1 & ZmmReg1) ... & XmmReg2_m128 { - local tmp:16 = vaddpd_avx( vexVVVV_XmmReg, XmmReg2_m128 ); - ZmmReg1 = zext(tmp); + local m:16 = XmmReg2_m128; + XmmReg1[0,64] = vexVVVV_XmmReg[0,64] f+ m[0,64]; + XmmReg1[64,64] = vexVVVV_XmmReg[64,64] f+ m[64,64]; + ZmmReg1 = zext(XmmReg1); } # ADDPD 3-33 PAGE 603 LINE 33408 :VADDPD YmmReg1, vexVVVV_YmmReg, YmmReg2_m256 is $(VEX_NDS) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_YmmReg; byte=0x58; (YmmReg1 & ZmmReg1) ... & YmmReg2_m256 { - local tmp:32 = vaddpd_avx( vexVVVV_YmmReg, YmmReg2_m256 ); - ZmmReg1 = zext(tmp); + local m:32 = YmmReg2_m256; + YmmReg1[0,64] = vexVVVV_YmmReg[0,64] f+ m[0,64]; + YmmReg1[64,64] = vexVVVV_YmmReg[64,64] f+ m[64,64]; + YmmReg1[128,64] = vexVVVV_YmmReg[128,64] f+ m[128,64]; + YmmReg1[192,64] = vexVVVV_YmmReg[192,64] f+ m[192,64]; + ZmmReg1 = zext(YmmReg1); } # ADDPS 3-36 PAGE 606 LINE 33558 @@ -686,88 +691,103 @@ define pcodeop vcvtps2pd_avx ; @endif # CVTSS2SD 3-261 PAGE 831 LINE 44744 -define pcodeop vcvtss2sd_avx ; -:VCVTSS2SD XmmReg1, vexVVVV_XmmReg, XmmReg2_m32 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x5A; (XmmReg1 & ZmmReg1) ... & XmmReg2_m32 +:VCVTSS2SD XmmReg1, vexVVVV_XmmReg, XmmReg2_m32 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x5A; (XmmReg1 & ZmmReg1) ... & XmmReg2_m32 { - local tmp:16 = vcvtss2sd_avx( vexVVVV_XmmReg, XmmReg2_m32 ); - ZmmReg1 = zext(tmp); + local tmp:8 = float2float( XmmReg2_m32[0,32] ); + XmmReg1[0,64] = tmp; + XmmReg1[64,64] = vexVVVV_XmmReg[64,64]; + ZmmReg1 = zext(XmmReg1); } # CVTSS2SI 3-263 PAGE 833 LINE 44835 -define pcodeop vcvtss2si_avx ; -:VCVTSS2SI Reg32, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_W0); byte=0x2D; Reg32 ... & XmmReg2_m32 +:VCVTSS2SI Reg32, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_W0); byte=0x2D; Reg32 ... & XmmReg2_m32 { - Reg32 = vcvtss2si_avx( XmmReg2_m32 ); - # TODO Reg64 = zext(Reg32) + Reg32 = trunc(round(XmmReg2_m32[0,32])); } # CVTSS2SI 3-263 PAGE 833 LINE 44837 @ifdef IA64 -:VCVTSS2SI Reg64, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_W1); byte=0x2D; Reg64 ... & XmmReg2_m32 +:VCVTSS2SI Reg64, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_W1); byte=0x2D; Reg64 ... & XmmReg2_m32 { - Reg64 = vcvtss2si_avx( XmmReg2_m32 ); + Reg64 = trunc(round(XmmReg2_m32[0,32])); } + @endif # CVTTPD2DQ 3-265 PAGE 835 LINE 44930 -define pcodeop vcvttpd2dq_avx ; -:VCVTTPD2DQ XmmReg1, XmmReg2_m128 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG); byte=0xE6; (XmmReg1 & ZmmReg1) ... & XmmReg2_m128 +:VCVTTPD2DQ XmmReg1, XmmReg2_m128 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG); byte=0xE6; (XmmReg1 & ZmmReg1) ... & XmmReg2_m128 { - local tmp:16 = vcvttpd2dq_avx( XmmReg2_m128 ); - ZmmReg1 = zext(tmp); + local tmp:16 = XmmReg2_m128; + XmmReg1[0,32] = trunc(tmp[0,64]); + XmmReg1[32,32] = trunc(tmp[64,64]); + XmmReg1[64,32] = 0; + XmmReg1[96,32] = 0; + ZmmReg1 = zext(XmmReg1); } # CVTTPD2DQ 3-265 PAGE 835 LINE 44933 -:VCVTTPD2DQ XmmReg1, YmmReg2_m256 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG); byte=0xE6; (XmmReg1 & ZmmReg1) ... & YmmReg2_m256 +:VCVTTPD2DQ XmmReg1, YmmReg2_m256 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG); byte=0xE6; (XmmReg1 & ZmmReg1) ... & YmmReg2_m256 { - local tmp:16 = vcvttpd2dq_avx( YmmReg2_m256 ); - ZmmReg1 = zext(tmp); + local tmp:32 = YmmReg2_m256; + XmmReg1[0,32] = trunc(tmp[0,64]); + XmmReg1[32,32] = trunc(tmp[64,64]); + XmmReg1[64,32] = trunc(tmp[128,64]); + XmmReg1[96,32] = trunc(tmp[192,64]); + ZmmReg1 = zext(XmmReg1); } # CVTTPS2DQ 3-270 PAGE 840 LINE 45163 -define pcodeop vcvttps2dq_avx ; -:VCVTTPS2DQ XmmReg1, XmmReg2_m128 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG); byte=0x5B; (XmmReg1 & ZmmReg1) ... & XmmReg2_m128 +:VCVTTPS2DQ XmmReg1, XmmReg2_m128 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG); byte=0x5B; (XmmReg1 & ZmmReg1) ... & XmmReg2_m128 { - local tmp:16 = vcvttps2dq_avx( XmmReg2_m128 ); - ZmmReg1 = zext(tmp); + local tmp:16 = XmmReg2_m128; + XmmReg1[0,32] = trunc(tmp[0,32]); + XmmReg1[32,32] = trunc(tmp[32,32]); + XmmReg1[64,32] = trunc(tmp[64,32]); + XmmReg1[96,32] = trunc(tmp[96,32]); + ZmmReg1 = zext(XmmReg1); } # CVTTPS2DQ 3-270 PAGE 840 LINE 45166 -:VCVTTPS2DQ YmmReg1, YmmReg2_m256 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG); byte=0x5B; (YmmReg1 & ZmmReg1) ... & YmmReg2_m256 +:VCVTTPS2DQ YmmReg1, YmmReg2_m256 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG); byte=0x5B; (YmmReg1 & ZmmReg1) ... & YmmReg2_m256 { - local tmp:32 = vcvttps2dq_avx( YmmReg2_m256 ); - ZmmReg1 = zext(tmp); + local tmp:32 = YmmReg2_m256; + YmmReg1[0,32] = trunc(tmp[0,32]); + YmmReg1[32,32] = trunc(tmp[32,32]); + YmmReg1[64,32] = trunc(tmp[64,32]); + YmmReg1[96,32] = trunc(tmp[96,32]); + YmmReg1[128,32] = trunc(tmp[128,32]); + YmmReg1[160,32] = trunc(tmp[160,32]); + YmmReg1[192,32] = trunc(tmp[192,32]); + YmmReg1[224,32] = trunc(tmp[224,32]); + ZmmReg1 = zext(YmmReg1); } # CVTTSD2SI 3-274 PAGE 844 LINE 45379 -define pcodeop vcvttsd2si_avx ; -:VCVTTSD2SI Reg32, XmmReg2_m64 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F2) & $(VEX_0F) & $(VEX_W0); byte=0x2C; Reg32 ... & XmmReg2_m64 +:VCVTTSD2SI Reg32, XmmReg2_m64 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F2) & $(VEX_0F) & $(VEX_W0); byte=0x2C; Reg32 ... & XmmReg2_m64 { - Reg32 = vcvttsd2si_avx( XmmReg2_m64 ); - # TODO Reg64 = zext(Reg32) + Reg32 = trunc(XmmReg2_m64[0,64]); } # CVTTSD2SI 3-274 PAGE 844 LINE 45382 @ifdef IA64 -:VCVTTSD2SI Reg64, XmmReg2_m64 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F2) & $(VEX_0F) & $(VEX_W1); byte=0x2C; Reg64 ... & XmmReg2_m64 +:VCVTTSD2SI Reg64, XmmReg2_m64 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F2) & $(VEX_0F) & $(VEX_W1); byte=0x2C; Reg64 ... & XmmReg2_m64 { - Reg64 = vcvttsd2si_avx( XmmReg2_m64 ); + Reg64 = trunc(XmmReg2_m64[0,64]); } + @endif # CVTTSS2SI 3-276 PAGE 846 LINE 45473 -define pcodeop vcvttss2si_avx ; -:VCVTTSS2SI Reg32, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_W0); byte=0x2C; Reg32 ... & XmmReg2_m32 +:VCVTTSS2SI Reg32, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_W0); byte=0x2C; Reg32 ... & XmmReg2_m32 { - Reg32 = vcvttss2si_avx( XmmReg2_m32 ); - # TODO Reg64 = zext(Reg32) + Reg32 = trunc(XmmReg2_m32[0,32]); } # CVTTSS2SI 3-276 PAGE 846 LINE 45476 @ifdef IA64 -:VCVTTSS2SI Reg64, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_W1); byte=0x2C; Reg64 ... & XmmReg2_m32 +:VCVTTSS2SI Reg64, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_W1); byte=0x2C; Reg64 ... & XmmReg2_m32 { - Reg64 = vcvttss2si_avx( XmmReg2_m32 ); + Reg64 = trunc(XmmReg2_m32[0,32]); } @endif @@ -802,19 +822,19 @@ define pcodeop vdivps_avx ; } # DIVSD 3-294 PAGE 864 LINE 46312 -define pcodeop vdivsd_avx ; -:VDIVSD XmmReg1, vexVVVV_XmmReg, XmmReg2_m64 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_F2) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x5E; (XmmReg1 & ZmmReg1) ... & XmmReg2_m64 +:VDIVSD XmmReg1, vexVVVV_XmmReg, XmmReg2_m64 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_F2) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x5E; (XmmReg1 & ZmmReg1) ... & XmmReg2_m64 { - local tmp:16 = vdivsd_avx( vexVVVV_XmmReg, XmmReg2_m64 ); - ZmmReg1 = zext(tmp); + XmmReg1[0,64] = vexVVVV_XmmReg[0,64] f/ XmmReg2_m64[0,64]; + XmmReg1[64,64] = vexVVVV_XmmReg[64,64]; + ZmmReg1 = zext(XmmReg1); } # DIVSS 3-296 PAGE 866 LINE 46410 -define pcodeop vdivss_avx ; -:VDIVSS XmmReg1, vexVVVV_XmmReg, XmmReg2_m32 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x5E; (XmmReg1 & ZmmReg1) ... & XmmReg2_m32 +:VDIVSS XmmReg1, vexVVVV_XmmReg, XmmReg2_m32 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x5E; (XmmReg1 & ZmmReg1) ... & XmmReg2_m32 { - local tmp:16 = vdivss_avx( vexVVVV_XmmReg, XmmReg2_m32 ); - ZmmReg1 = zext(tmp); + XmmReg1[0,32] = vexVVVV_XmmReg[0,32] f/ XmmReg2_m32[0,32]; + XmmReg1[32,96] = vexVVVV_XmmReg[32,96]; + ZmmReg1 = zext(XmmReg1); } # DPPD 3-298 PAGE 868 LINE 46509 @@ -848,18 +868,23 @@ define pcodeop vextractps_avx ; } # HADDPD 3-427 PAGE 997 LINE 52447 -define pcodeop vhaddpd_avx ; :VHADDPD XmmReg1, vexVVVV_XmmReg, XmmReg2_m128 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x7C; (XmmReg1 & ZmmReg1) ... & XmmReg2_m128 { - local tmp:16 = vhaddpd_avx( vexVVVV_XmmReg, XmmReg2_m128 ); - ZmmReg1 = zext(tmp); + local m:16 = XmmReg2_m128; + XmmReg1[0,64] = vexVVVV_XmmReg[0,64] f+ vexVVVV_XmmReg[64,64]; + XmmReg1[64,64] = m[0,64] f+ m[64,64]; + ZmmReg1 = zext(XmmReg1); } # HADDPD 3-427 PAGE 997 LINE 52450 :VHADDPD YmmReg1, vexVVVV_YmmReg, YmmReg2_m256 is $(VEX_NDS) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_YmmReg; byte=0x7C; (YmmReg1 & ZmmReg1) ... & YmmReg2_m256 { - local tmp:32 = vhaddpd_avx( vexVVVV_YmmReg, YmmReg2_m256 ); - ZmmReg1 = zext(tmp); + local m:32 = YmmReg2_m256; + YmmReg1[0,64] = vexVVVV_YmmReg[0,64] f+ vexVVVV_YmmReg[64,64]; + YmmReg1[64,64] = m[0,64] f+ m[64,64]; + YmmReg1[128,64] = vexVVVV_YmmReg[128,64] f+ vexVVVV_YmmReg[192,64]; + YmmReg1[192,64] = m[128,64] f+ m[192,64]; + ZmmReg1 = zext(YmmReg1); } # HADDPS 3-430 PAGE 1000 LINE 52586 @@ -1096,17 +1121,12 @@ define pcodeop vminss_avx ; } # MOVDQU,VMOVDQU8/16/32/64 4-67 PAGE 1187 LINE 61932 -:VMOVDQU m128, XmmReg1 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG); byte=0x7F; XmmReg1 ... & m128 +:VMOVDQU XmmReg2_m128, XmmReg1 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG); byte=0x7F; (XmmReg1 & XmmReg2_m128_extend) ... & XmmReg2_m128 { - m128 = XmmReg1; + XmmReg2_m128 = XmmReg1; + build XmmReg2_m128_extend; } -:VMOVDQU XmmReg2, XmmReg1 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG); byte=0x7F; XmmReg1 & (mod=3 & XmmReg2 & ZmmReg2) -{ - ZmmReg2 = zext( XmmReg1 ); -} - - # MOVDQU,VMOVDQU8/16/32/64 4-67 PAGE 1187 LINE 61934 :VMOVDQU YmmReg1, YmmReg2_m256 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_F3) & $(VEX_0F) & $(VEX_WIG); byte=0x6F; (YmmReg1 & ZmmReg1) ... & YmmReg2_m256 { @@ -1358,14 +1378,10 @@ define pcodeop vmovsldup_avx ; } # MOVUPD 4-126 PAGE 1246 LINE 64689 -:VMOVUPD m128, XmmReg1 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG); byte=0x11; XmmReg1 ... & m128 +:VMOVUPD XmmReg2_m128, XmmReg1 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG); byte=0x11; (XmmReg1 & XmmReg2_m128_extend) ... & XmmReg2_m128 { - m128 = XmmReg1; -} - -:VMOVUPD XmmReg2, XmmReg1 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG); byte=0x11; XmmReg1 & ( mod=3 & XmmReg2 & ZmmReg2 ) -{ - ZmmReg2 = zext( XmmReg1 ); + XmmReg2_m128 = XmmReg1; + build XmmReg2_m128_extend; } # MOVUPD 4-126 PAGE 1246 LINE 64691 @@ -1396,39 +1412,54 @@ define pcodeop vmpsadbw_avx ; } # MULPD 4-146 PAGE 1266 LINE 65682 -define pcodeop vmulpd_avx ; :VMULPD XmmReg1, vexVVVV_XmmReg, XmmReg2_m128 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x59; (XmmReg1 & ZmmReg1) ... & XmmReg2_m128 { - local tmp:16 = vmulpd_avx( vexVVVV_XmmReg, XmmReg2_m128 ); - ZmmReg1 = zext(tmp); + local m:16 = XmmReg2_m128; + XmmReg1[0,64] = vexVVVV_XmmReg[0,64] f* m[0,64]; + XmmReg1[64,64] = vexVVVV_XmmReg[64,64] f* m[64,64]; + ZmmReg1 = zext(XmmReg1); } # MULPD 4-146 PAGE 1266 LINE 65684 :VMULPD YmmReg1, vexVVVV_YmmReg, YmmReg2_m256 is $(VEX_NDS) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_YmmReg; byte=0x59; (YmmReg1 & ZmmReg1) ... & YmmReg2_m256 { - local tmp:32 = vmulpd_avx( vexVVVV_YmmReg, YmmReg2_m256 ); - ZmmReg1 = zext(tmp); + local m:32 = YmmReg2_m256; + YmmReg1[0,64] = vexVVVV_YmmReg[0,64] f* m[0,64]; + YmmReg1[64,64] = vexVVVV_YmmReg[64,64] f* m[64,64]; + YmmReg1[128,64] = vexVVVV_YmmReg[128,64] f* m[128,64]; + YmmReg1[192,64] = vexVVVV_YmmReg[192,64] f* m[192,64]; + ZmmReg1 = zext(YmmReg1); } # MULPS 4-149 PAGE 1269 LINE 65813 -define pcodeop vmulps_avx ; :VMULPS XmmReg1, vexVVVV_XmmReg, XmmReg2_m128 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_NONE) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x59; (XmmReg1 & ZmmReg1) ... & XmmReg2_m128 { - local tmp:16 = vmulps_avx( vexVVVV_XmmReg, XmmReg2_m128 ); - ZmmReg1 = zext(tmp); + local m:16 = XmmReg2_m128; + XmmReg1[0,32] = vexVVVV_XmmReg[0,32] f* m[0,32]; + XmmReg1[32,32] = vexVVVV_XmmReg[32,32] f* m[32,32]; + XmmReg1[64,32] = vexVVVV_XmmReg[64,32] f* m[64,32]; + XmmReg1[96,32] = vexVVVV_XmmReg[96,32] f* m[96,32]; + ZmmReg1 = zext(XmmReg1); } # MULPS 4-149 PAGE 1269 LINE 65815 :VMULPS YmmReg1, vexVVVV_YmmReg, YmmReg2_m256 is $(VEX_NDS) & $(VEX_L256) & $(VEX_PRE_NONE) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_YmmReg; byte=0x59; (YmmReg1 & ZmmReg1) ... & YmmReg2_m256 { - local tmp:32 = vmulps_avx( vexVVVV_YmmReg, YmmReg2_m256 ); - ZmmReg1 = zext(tmp); + local m:32 = YmmReg2_m256; + YmmReg1[0,32] = vexVVVV_YmmReg[0,32] f* m[0,32]; + YmmReg1[32,32] = vexVVVV_YmmReg[32,32] f* m[32,32]; + YmmReg1[64,32] = vexVVVV_YmmReg[64,32] f* m[64,32]; + YmmReg1[96,32] = vexVVVV_YmmReg[96,32] f* m[96,32]; + YmmReg1[128,32] = vexVVVV_YmmReg[128,32] f* m[128,32]; + YmmReg1[160,32] = vexVVVV_YmmReg[160,32] f* m[160,32]; + YmmReg1[192,32] = vexVVVV_YmmReg[192,32] f* m[192,32]; + ZmmReg1 = zext(YmmReg1); } # MULSD 4-152 PAGE 1272 LINE 65956 :VMULSD XmmReg1, vexVVVV_XmmReg, XmmReg2_m64 is $(VEX_NDS) & $(VEX_L128) & $(VEX_PRE_F2) & $(VEX_0F) & $(VEX_WIG) & vexVVVV_XmmReg; byte=0x59; (XmmReg1 & ZmmReg1) ... & XmmReg2_m64 { - local tmp:8 = vexVVVV_XmmReg[0,64] f* XmmReg2_m64[0,64]; + local tmp:8 = vexVVVV_XmmReg[0,64] f* XmmReg2_m64[0,64]; ZmmReg1 = zext(tmp); } @@ -2770,49 +2801,88 @@ define pcodeop vunpcklps_avx ; } # VBROADCAST 5-12 PAGE 1836 LINE 94909 -define pcodeop vbroadcastss_avx ; -:VBROADCASTSS XmmReg1, m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x18; (XmmReg1 & ZmmReg1) ... & m32 +:VBROADCASTSS XmmReg1, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x18; (XmmReg1 & ZmmReg1) ... & XmmReg2_m32 { - local tmp:16 = vbroadcastss_avx( m32 ); - ZmmReg1 = zext(tmp); + local val:4 = XmmReg2_m32[0,32]; + XmmReg1[0,32] = val; + XmmReg1[32,32] = val; + XmmReg1[64,32] = val; + XmmReg1[96,32] = val; + ZmmReg1 = zext(XmmReg1); } # VBROADCAST 5-12 PAGE 1836 LINE 94911 -:VBROADCASTSS YmmReg1, m32 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x18; (YmmReg1 & ZmmReg1) ... & m32 +:VBROADCASTSS YmmReg1, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x18; (YmmReg1 & ZmmReg1) ... & XmmReg2_m32 { - local tmp:32 = vbroadcastss_avx( m32 ); - ZmmReg1 = zext(tmp); + local val:4 = XmmReg2_m32[0,32]; + YmmReg1[0,32] = val; + YmmReg1[32,32] = val; + YmmReg1[64,32] = val; + YmmReg1[96,32] = val; + YmmReg1[128,32] = val; + YmmReg1[160,32] = val; + YmmReg1[192,32] = val; + YmmReg1[224,32] = val; + ZmmReg1 = zext(YmmReg1); } # VBROADCAST 5-12 PAGE 1836 LINE 94913 define pcodeop vbroadcastsd_avx ; -:VBROADCASTSD YmmReg1, m64 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x19; (YmmReg1 & ZmmReg1) ... & m64 +:VBROADCASTSD YmmReg1, XmmReg2_m64 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x19; (YmmReg1 & ZmmReg1) ... & XmmReg2_m64 { - local tmp:32 = vbroadcastsd_avx( m64 ); - ZmmReg1 = zext(tmp); + local val:8 = XmmReg2_m64[0,64]; + YmmReg1[0,64] = val; + YmmReg1[64,64] = val; + YmmReg1[128,64] = val; + YmmReg1[192,64] = val; + ZmmReg1 = zext(YmmReg1); } # VBROADCAST 5-12 PAGE 1836 LINE 94915 define pcodeop vbroadcastf128_avx ; -:VBROADCASTF128 YmmReg1, m128 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x1A; (YmmReg1 & ZmmReg1) ... & m128 +:VBROADCASTF128 YmmReg1, XmmReg2_m128 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x1A; (YmmReg1 & ZmmReg1) ... & XmmReg2_m128 { - local tmp:32 = vbroadcastf128_avx( m128 ); - ZmmReg1 = zext(tmp); + local val:16 = XmmReg2_m128; + YmmReg1[0,128] = val; + YmmReg1[128,128] = val; + ZmmReg1 = zext(YmmReg1); } # VEXTRACTF128/VEXTRACTF32x4/VEXTRACTF64x2/VEXTRACTF32x8/VEXTRACTF64x4 5-99 PAGE 1923 LINE 99102 -define pcodeop vextractf128_avx ; -:VEXTRACTF128 XmmReg2_m128, YmmReg1, imm8 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F3A) & $(VEX_W0); byte=0x19; YmmReg1 ... & XmmReg2_m128; imm8 +:VEXTRACTF128 XmmReg2_m128, YmmReg1, imm8 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F3A) & $(VEX_W0); byte=0x19; (YmmReg1 & XmmReg2_m128_extend) ... & XmmReg2_m128; imm8 { - XmmReg2_m128 = vextractf128_avx( YmmReg1, imm8:1 ); + local ext:1 = (imm8:1 & 1) == 1; + + local val:16 = YmmReg1[0,128]; + + if (ext == 0) goto ; + + val = YmmReg1[128,128]; + + + XmmReg2_m128 = val; + build XmmReg2_m128_extend; } # VINSERTF128/VINSERTF32x4/VINSERTF64x2/VINSERTF32x8/VINSERTF64x4 5-310 PAGE 2134 LINE 109703 -define pcodeop vinsertf128_avx ; -:VINSERTF128 YmmReg1, vexVVVV_YmmReg, XmmReg2_m128, imm8 is $(VEX_NDS) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F3A) & $(VEX_W0) & vexVVVV_YmmReg; byte=0x18; (YmmReg1 & ZmmReg1) ... & XmmReg2_m128; imm8 +:VINSERTF128 YmmReg1, vexVVVV_YmmReg, XmmReg2_m128, imm8 is $(VEX_NDS) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F3A) & $(VEX_W0) & vexVVVV_YmmReg; byte=0x18; (YmmReg1 & ZmmReg1) ... & XmmReg2_m128; imm8 { - local tmp:32 = vinsertf128_avx( vexVVVV_YmmReg, XmmReg2_m128, imm8:1 ); - ZmmReg1 = zext(tmp); + local ext:1 = (imm8:1 & 1) == 1; + local src1_0 = vexVVVV_YmmReg[0, 128]; + local src1_1 = vexVVVV_YmmReg[128, 128]; + + src2:16 = XmmReg2_m128; + + YmmReg1[0,128] = src2; + YmmReg1[128,128] = src1_1; + + if (ext == 0) goto ; + + YmmReg1[0,128] = src1_0; + YmmReg1[128,128] = src2; + + + ZmmReg1 = zext(YmmReg1); } # VMASKMOV 5-318 PAGE 2142 LINE 110151 @@ -3038,9 +3108,10 @@ define pcodeop vcvtps2ph_f16c ; } # VCVTPS2PH 5-37 PAGE 1861 LINE 96113 -:VCVTPS2PH XmmReg2_m128, YmmReg1, imm8 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F3A) & $(VEX_W0); byte=0x1D; YmmReg1 ... & XmmReg2_m128; imm8 +:VCVTPS2PH XmmReg2_m128, YmmReg1, imm8 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F3A) & $(VEX_W0); byte=0x1D; (YmmReg1 & XmmReg2_m128_extend) ... & XmmReg2_m128; imm8 { XmmReg2_m128 = vcvtps2ph_f16c( YmmReg1, imm8:1 ); + build XmmReg2_m128_extend; } diff --git a/Ghidra/Processors/x86/data/languages/avx2.sinc b/Ghidra/Processors/x86/data/languages/avx2.sinc index 416a1761d0..5bba08c71d 100644 --- a/Ghidra/Processors/x86/data/languages/avx2.sinc +++ b/Ghidra/Processors/x86/data/languages/avx2.sinc @@ -896,10 +896,19 @@ define pcodeop vpunpcklqdq_avx2 ; } # VEXTRACTI128/VEXTRACTI32x4/VEXTRACTI64x2/VEXTRACTI32x8/VEXTRACTI64x4 5-106 PAGE 1930 LINE 99432 -:VEXTRACTI128 XmmReg2_m128, YmmReg1, imm8 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F3A) & $(VEX_W0); byte=0x39; YmmReg1 ... & XmmReg2_m128; imm8 +:VEXTRACTI128 XmmReg2_m128, YmmReg1, imm8 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F3A) & $(VEX_W0); byte=0x39; (YmmReg1 & XmmReg2_m128_extend) ... & XmmReg2_m128; imm8 { - local ext:1 = imm8:1 == 1; - XmmReg2_m128 = (YmmReg1[0,128] * zext(ext==0)) | (YmmReg1[128,128] * zext(ext==1)); + local ext:1 = (imm8:1 & 1) == 1; + + local val:16 = YmmReg1[0,128]; + + if (ext == 0) goto ; + + val = YmmReg1[128,128]; + + + XmmReg2_m128 = val; + build XmmReg2_m128_extend; } # VPBLENDD 5-321 PAGE 2145 LINE 110309 @@ -918,71 +927,163 @@ define pcodeop vpblendd_avx2 ; } # VPBROADCAST 5-331 PAGE 2155 LINE 110776 -define pcodeop vpbroadcastb_avx2 ; :VPBROADCASTB XmmReg1, XmmReg2_m8 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x78; (XmmReg1 & ZmmReg1) ... & XmmReg2_m8 { - local tmp:16 = vpbroadcastb_avx2( XmmReg2_m8 ); - ZmmReg1 = zext(tmp); + local val:1 = XmmReg2_m8[0,8]; + XmmReg1[0,8] = val; + XmmReg1[8,8] = val; + XmmReg1[16,8] = val; + XmmReg1[24,8] = val; + XmmReg1[32,8] = val; + XmmReg1[40,8] = val; + XmmReg1[48,8] = val; + XmmReg1[56,8] = val; + XmmReg1[64,8] = val; + XmmReg1[72,8] = val; + XmmReg1[80,8] = val; + XmmReg1[88,8] = val; + XmmReg1[96,8] = val; + XmmReg1[104,8] = val; + XmmReg1[112,8] = val; + XmmReg1[120,8] = val; + ZmmReg1 = zext(XmmReg1); } # VPBROADCAST 5-331 PAGE 2155 LINE 110778 :VPBROADCASTB YmmReg1, XmmReg2_m8 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x78; (YmmReg1 & ZmmReg1) ... & XmmReg2_m8 { - local tmp:32 = vpbroadcastb_avx2( XmmReg2_m8 ); - ZmmReg1 = zext(tmp); + local val:1 = XmmReg2_m8[0,8]; + YmmReg1[0,8] = val; + YmmReg1[8,8] = val; + YmmReg1[16,8] = val; + YmmReg1[24,8] = val; + YmmReg1[32,8] = val; + YmmReg1[40,8] = val; + YmmReg1[48,8] = val; + YmmReg1[56,8] = val; + YmmReg1[64,8] = val; + YmmReg1[72,8] = val; + YmmReg1[80,8] = val; + YmmReg1[88,8] = val; + YmmReg1[96,8] = val; + YmmReg1[104,8] = val; + YmmReg1[112,8] = val; + YmmReg1[120,8] = val; + + YmmReg1[128,8] = val; + YmmReg1[136,8] = val; + YmmReg1[144,8] = val; + YmmReg1[152,8] = val; + YmmReg1[160,8] = val; + YmmReg1[168,8] = val; + YmmReg1[176,8] = val; + YmmReg1[184,8] = val; + YmmReg1[192,8] = val; + YmmReg1[200,8] = val; + YmmReg1[208,8] = val; + YmmReg1[216,8] = val; + YmmReg1[224,8] = val; + YmmReg1[232,8] = val; + YmmReg1[240,8] = val; + YmmReg1[248,8] = val; + + ZmmReg1 = zext(YmmReg1); } # VPBROADCAST 5-331 PAGE 2155 LINE 110787 -define pcodeop vpbroadcastw_avx2 ; :VPBROADCASTW XmmReg1, XmmReg2_m16 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x79; (XmmReg1 & ZmmReg1) ... & XmmReg2_m16 { - local tmp:16 = vpbroadcastw_avx2( XmmReg2_m16 ); - ZmmReg1 = zext(tmp); + local val:2 = XmmReg2_m16[0,16]; + XmmReg1[0,16] = val; + XmmReg1[16,16] = val; + XmmReg1[32,16] = val; + XmmReg1[48,16] = val; + XmmReg1[64,16] = val; + XmmReg1[80,16] = val; + XmmReg1[96,16] = val; + XmmReg1[112,16] = val; + + ZmmReg1 = zext(XmmReg1); } # VPBROADCAST 5-331 PAGE 2155 LINE 110789 :VPBROADCASTW YmmReg1, XmmReg2_m16 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x79; (YmmReg1 & ZmmReg1) ... & XmmReg2_m16 { - local tmp:32 = vpbroadcastw_avx2( XmmReg2_m16 ); - ZmmReg1 = zext(tmp); + local val:2 = XmmReg2_m16[0,16]; + YmmReg1[0,16] = val; + YmmReg1[16,16] = val; + YmmReg1[32,16] = val; + YmmReg1[48,16] = val; + YmmReg1[64,16] = val; + YmmReg1[80,16] = val; + YmmReg1[96,16] = val; + YmmReg1[112,16] = val; + + YmmReg1[128,16] = val; + YmmReg1[144,16] = val; + YmmReg1[160,16] = val; + YmmReg1[176,16] = val; + YmmReg1[192,16] = val; + YmmReg1[208,16] = val; + YmmReg1[224,16] = val; + YmmReg1[240,16] = val; + + ZmmReg1 = zext(YmmReg1); } # VPBROADCAST 5-331 PAGE 2155 LINE 110800 -define pcodeop vpbroadcastd_avx2 ; :VPBROADCASTD XmmReg1, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x58; (XmmReg1 & ZmmReg1) ... & XmmReg2_m32 { - local tmp:16 = vpbroadcastd_avx2( XmmReg2_m32 ); - ZmmReg1 = zext(tmp); + local val:4 = XmmReg2_m32[0,32]; + XmmReg1[0,32] = val; + XmmReg1[32,32] = val; + XmmReg1[64,32] = val; + XmmReg1[96,32] = val; + ZmmReg1 = zext(XmmReg1); } # VPBROADCAST 5-331 PAGE 2155 LINE 110802 :VPBROADCASTD YmmReg1, XmmReg2_m32 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x58; (YmmReg1 & ZmmReg1) ... & XmmReg2_m32 { - local tmp:32 = vpbroadcastd_avx2( XmmReg2_m32 ); - ZmmReg1 = zext(tmp); + local val:4 = XmmReg2_m32[0,32]; + YmmReg1[0,32] = val; + YmmReg1[32,32] = val; + YmmReg1[64,32] = val; + YmmReg1[96,32] = val; + YmmReg1[128,32] = val; + YmmReg1[160,32] = val; + YmmReg1[192,32] = val; + YmmReg1[224,32] = val; + ZmmReg1 = zext(YmmReg1); } # VPBROADCAST 5-331 PAGE 2155 LINE 110813 -define pcodeop vpbroadcastq_avx2 ; :VPBROADCASTQ XmmReg1, XmmReg2_m64 is $(VEX_NONE) & $(VEX_L128) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x59; (XmmReg1 & ZmmReg1) ... & XmmReg2_m64 { - local tmp:16 = vpbroadcastq_avx2( XmmReg2_m64 ); - ZmmReg1 = zext(tmp); + local val:8 = XmmReg2_m64[0,64]; + XmmReg1[0,64] = val; + XmmReg1[64,64] = val; + ZmmReg1 = zext(XmmReg1); } # VPBROADCAST 5-331 PAGE 2155 LINE 110815 :VPBROADCASTQ YmmReg1, XmmReg2_m64 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x59; (YmmReg1 & ZmmReg1) ... & XmmReg2_m64 { - local tmp:32 = vpbroadcastq_avx2( XmmReg2_m64 ); - ZmmReg1 = zext(tmp); + local val:8 = XmmReg2_m64[0,64]; + YmmReg1[0,64] = val; + YmmReg1[64,64] = val; + YmmReg1[128,64] = val; + YmmReg1[192,64] = val; + ZmmReg1 = zext(YmmReg1); } # VPBROADCAST 5-332 PAGE 2156 LINE 110843 -define pcodeop vbroadcasti128_avx2 ; -:VBROADCASTI128 YmmReg1, m128 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x5A; (YmmReg1 & ZmmReg1) ... & m128 +:VBROADCASTI128 YmmReg1, XmmReg2_m128 is $(VEX_NONE) & $(VEX_L256) & $(VEX_PRE_66) & $(VEX_0F38) & $(VEX_W0); byte=0x5A; (YmmReg1 & ZmmReg1) ... & XmmReg2_m128 { - local tmp:32 = vbroadcasti128_avx2( m128 ); - ZmmReg1 = zext(tmp); + local val:16 = XmmReg2_m128; + YmmReg1[0,128] = val; + YmmReg1[128,128] = val; + ZmmReg1 = zext(YmmReg1); } # VPERM2I128 5-360 PAGE 2184 LINE 112312 diff --git a/Ghidra/Processors/x86/data/languages/ia.sinc b/Ghidra/Processors/x86/data/languages/ia.sinc index b5ec7a8a05..8d671933d9 100644 --- a/Ghidra/Processors/x86/data/languages/ia.sinc +++ b/Ghidra/Processors/x86/data/languages/ia.sinc @@ -1212,6 +1212,10 @@ YmmReg2_m256: m256 is m256 { export m256; } ZmmReg2_m512: ZmmReg2 is mod=3 & ZmmReg2 { export ZmmReg2; } ZmmReg2_m512: m512 is m512 { export m512; } +# used to extend ZmmReg2 if not assigning to m128 +XmmReg2_m128_extend: XmmReg2 is mod=3 & XmmReg2 & ZmmReg2 { ZmmReg2 = zext(XmmReg2); } +XmmReg2_m128_extend: XmmReg2 is mod & XmmReg2 { } + m32bcst64: m32 is m32 { local tmp:4 = m32; BCST8[0,32] = tmp; BCST8[32,32] = tmp; export BCST8; } m32bcst128: m32 is m32 { local tmp:4 = m32; BCST16[0,32] = tmp; BCST16[32,32] = tmp; BCST16[64,32] = tmp; BCST16[96,32] = tmp; export BCST16; } m32bcst256: m32 is m32 { From 5dd8a6a79a291949639d2038a0526e8994acff5e Mon Sep 17 00:00:00 2001 From: emteere <47253321+emteere@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:45:42 -0400 Subject: [PATCH 35/53] GP-4826 Fixed incorrect .sla file reference --- Ghidra/Processors/PowerPC/data/languages/ppc.ldefs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ghidra/Processors/PowerPC/data/languages/ppc.ldefs b/Ghidra/Processors/PowerPC/data/languages/ppc.ldefs index a3765cf0ed..4c006eac13 100644 --- a/Ghidra/Processors/PowerPC/data/languages/ppc.ldefs +++ b/Ghidra/Processors/PowerPC/data/languages/ppc.ldefs @@ -219,7 +219,7 @@ size="32" variant="PowerQUICC-III-e500mc" version="1.6" - slafile="ppc_32_be.sla" + slafile="ppc_32_e500mc_be.sla" processorspec="ppc_32.pspec" manualindexfile="../manuals/PowerPC.idx" id="PowerPC:BE:32:e500mc"> @@ -235,7 +235,7 @@ size="32" variant="PowerQUICC-III-e500mc" version="1.6" - slafile="ppc_32_le.sla" + slafile="ppc_32_e500mc_le.sla" processorspec="ppc_32.pspec" manualindexfile="../manuals/PowerPC.idx" id="PowerPC:LE:32:e500mc"> From fb16e960db6ce9af812e542189ec33b47526d1c7 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Fri, 30 Aug 2024 13:29:44 -0400 Subject: [PATCH 36/53] GP-0: Fixing a case issue with headless command line argument parsing --- .../app/util/headless/AnalyzeHeadless.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java index 5cd2db6daf..d091481c30 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java @@ -4,9 +4,9 @@ * 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. @@ -19,7 +19,6 @@ import java.io.File; import java.io.IOException; import java.net.*; import java.util.*; -import java.util.stream.Collectors; import generic.stl.Pair; import ghidra.*; @@ -105,8 +104,6 @@ public class AnalyzeHeadless implements GhidraLaunchable { } private static final int EXIT_CODE_ERROR = 1; - private static final Set 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 @@ -256,7 +253,7 @@ public class AnalyzeHeadless implements GhidraLaunchable { loaderName = args[++argi]; } else if (checkArgument(Arg.LOADER_ARGS, args, argi)) { - if (ALL_ARG_NAMES.contains(args[argi + 1])) { + if (isExistingArg(args[argi + 1])) { throw new InvalidInputException(args[argi] + " expects value to follow."); } loaderArgs.add(new Pair<>(arg, args[++argi])); @@ -269,13 +266,13 @@ public class AnalyzeHeadless implements GhidraLaunchable { } else if (checkArgument(Arg.PRE_SCRIPT, args, argi)) { String scriptName = args[++argi]; - String[] scriptArgs = getSubArguments(args, argi, ALL_ARG_NAMES); + String[] scriptArgs = getSubArguments(args, argi); argi += scriptArgs.length; preScripts.add(new Pair<>(scriptName, scriptArgs)); } else if (checkArgument(Arg.POST_SCRIPT, args, argi)) { String scriptName = args[++argi]; - String[] scriptArgs = getSubArguments(args, argi, ALL_ARG_NAMES); + String[] scriptArgs = getSubArguments(args, argi); argi += scriptArgs.length; postScripts.add(new Pair<>(scriptName, scriptArgs)); } @@ -311,7 +308,7 @@ public class AnalyzeHeadless implements GhidraLaunchable { nextArg = args[++argi]; // Check if next argument is a parameter - if (ALL_ARG_NAMES.contains(nextArg)) { + if (isExistingArg(nextArg)) { argi--; break; } @@ -330,7 +327,7 @@ public class AnalyzeHeadless implements GhidraLaunchable { else if (checkArgument(Arg.CONNECT, args, argi)) { if ((argi + 1) < args.length) { arg = args[argi + 1]; - if (!ALL_ARG_NAMES.contains(arg)) { + if (!isExistingArg(arg)) { // serverUID is optional argument after -connect userId = arg; ++argi; @@ -341,7 +338,7 @@ public class AnalyzeHeadless implements GhidraLaunchable { String comment = null; if ((argi + 1) < args.length) { arg = args[argi + 1]; - if (!ALL_ARG_NAMES.contains(arg)) { + if (!isExistingArg(arg)) { // commit is optional argument after -commit comment = arg; ++argi; @@ -371,7 +368,7 @@ public class AnalyzeHeadless implements GhidraLaunchable { String processBinary = null; if ((argi + 1) < args.length) { arg = args[argi + 1]; - if (!ALL_ARG_NAMES.contains(arg)) { + if (!isExistingArg(arg)) { // processBinary is optional argument after -process processBinary = arg; ++argi; @@ -383,7 +380,7 @@ public class AnalyzeHeadless implements GhidraLaunchable { Integer depth = null; if ((argi + 1) < args.length) { arg = args[argi + 1]; - if (!ALL_ARG_NAMES.contains(arg)) { + if (!isExistingArg(arg)) { // depth is optional argument after -recursive try { depth = Integer.parseInt(arg); @@ -414,7 +411,7 @@ public class AnalyzeHeadless implements GhidraLaunchable { else if (checkArgument(Arg.LIBRARY_SEARCH_PATHS, args, argi)) { LibrarySearchPathManager.setLibraryPaths(args[++argi].split(";")); } - else if (ALL_ARG_NAMES.contains(args[argi])) { + else if (isExistingArg(args[argi])) { throw new AssertionError("Valid option was not processed: " + args[argi]); } else { @@ -559,10 +556,10 @@ public class AnalyzeHeadless implements GhidraLaunchable { usage("analyzeHeadless"); } - private String[] getSubArguments(String[] args, int argi, Set argNames) { + private String[] getSubArguments(String[] args, int argi) { List subArgs = new ArrayList<>(); int i = argi + 1; - while (i < args.length && !argNames.contains(args[i])) { + while (i < args.length && !isExistingArg(args[i])) { subArgs.add(args[i++]); } return subArgs.toArray(new String[subArgs.size()]); @@ -578,4 +575,8 @@ public class AnalyzeHeadless implements GhidraLaunchable { } return true; } + + private boolean isExistingArg(String s) { + return Arrays.stream(Arg.values()).anyMatch(e -> e.matches(s)); + } } From e5969a613cdca59252cc0366254d005597c313a2 Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:11:47 +0000 Subject: [PATCH 37/53] GP-4871 Don't ignore signed to unsigned integer casts for FLOAT_INT2FLOAT --- .../Decompiler/certification.manifest | 1 + .../src/decompile/cpp/architecture.cc | 2 +- .../Decompiler/src/decompile/cpp/block.cc | 35 ++++- .../Decompiler/src/decompile/cpp/block.hh | 5 +- .../src/decompile/cpp/coreaction.cc | 8 ++ .../Decompiler/src/decompile/cpp/fspec.cc | 20 ++- .../Decompiler/src/decompile/cpp/printc.cc | 13 ++ .../Decompiler/src/decompile/cpp/printc.hh | 6 +- .../src/decompile/cpp/ruleaction.cc | 130 ++++++++++++++++++ .../src/decompile/cpp/ruleaction.hh | 22 +++ .../Decompiler/src/decompile/cpp/subflow.cc | 51 +++++++ .../Decompiler/src/decompile/cpp/subflow.hh | 4 +- .../src/decompile/cpp/testfunction.cc | 12 +- .../Decompiler/src/decompile/cpp/typeop.cc | 57 ++++++++ .../Decompiler/src/decompile/cpp/typeop.hh | 3 + .../src/decompile/datatests/floatconv.xml | 55 ++++++++ 16 files changed, 408 insertions(+), 16 deletions(-) create mode 100644 Ghidra/Features/Decompiler/src/decompile/datatests/floatconv.xml diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index 6cb15d2b66..8e397e6d58 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -25,6 +25,7 @@ src/decompile/datatests/doublemove.xml||GHIDRA||||END| src/decompile/datatests/dupptr.xml||GHIDRA||||END| src/decompile/datatests/elseif.xml||GHIDRA||||END| src/decompile/datatests/floatcast.xml||GHIDRA||||END| +src/decompile/datatests/floatconv.xml||GHIDRA||||END| src/decompile/datatests/floatprint.xml||GHIDRA||||END| src/decompile/datatests/forloop1.xml||GHIDRA||||END| src/decompile/datatests/forloop_loaditer.xml||GHIDRA||||END| diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc index 841e9ec22d..494d160db8 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc @@ -33,7 +33,7 @@ using std::sqrt; vector ArchitectureCapability::thelist; const uint4 ArchitectureCapability::majorversion = 6; -const uint4 ArchitectureCapability::minorversion = 0; +const uint4 ArchitectureCapability::minorversion = 1; AttributeId ATTRIB_ADDRESS = AttributeId("address",148); AttributeId ATTRIB_ADJUSTVMA = AttributeId("adjustvma",103); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc index 7d783e7884..a24283890b 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc @@ -5,9 +5,9 @@ * 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. @@ -814,6 +814,37 @@ FlowBlock *FlowBlock::findCommonBlock(const vector &blockSet) return res; } +/// \brief Find conditional block that decides between the given control-flow edges +/// +/// There must be a unique path from the conditional block through the first edge, and +/// a second unique path through the second edge. Otherwise null is returned. The index of the +/// output block from the conditional that flows to the first edge is passed back. +/// \param bl1 is the destination block for the first given control-flow edge +/// \param edge1 is the input slot for the first edge +/// \param bl2 is the destination block for the second given control-flow edge +/// \param edge2 is the input slot for the second edge +/// \param slot1 will hold the output slot leading to the first control-flow edge +/// \return the conditional FlowBlock if it exists or null +FlowBlock *FlowBlock::findCondition(FlowBlock *bl1,int4 edge1,FlowBlock *bl2,int4 edge2,int4 &slot1) + +{ + FlowBlock *cond = bl1->getIn(edge1); + while (cond->sizeOut() != 2) { + if (cond->sizeOut() != 1) return (FlowBlock *)0; + bl1 = cond; + edge1 = 0; + cond = bl1->getIn(0); + } + + while (cond != bl2->getIn(edge2)) { + bl2 = bl2->getIn(edge2); + if (bl2->sizeOut() != 1) return (FlowBlock *)0; + edge2 = 0; + } + slot1 = bl1->getInRevIndex(edge1); + return cond; +} + /// Add the given FlowBlock to the list and make \b this the parent /// Update \b index so that it has the minimum over all components /// \param bl is the given FlowBlock diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh index aade37d7e6..d43b07ed3d 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh @@ -4,9 +4,9 @@ * 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. @@ -344,6 +344,7 @@ public: static bool compareFinalOrder(const FlowBlock *bl1,const FlowBlock *bl2); ///< Final FlowBlock comparison static FlowBlock *findCommonBlock(FlowBlock *bl1,FlowBlock *bl2); ///< Find the common dominator of two FlowBlocks static FlowBlock *findCommonBlock(const vector &blockSet); ///< Find common dominator of multiple FlowBlocks + static FlowBlock *findCondition(FlowBlock *bl1,int4 edge1,FlowBlock *bl2,int4 edge2,int4 &slot1); }; /// \brief A control-flow block built out of sub-components diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc index d8a2140dd3..a75a5e5929 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc @@ -3776,6 +3776,12 @@ void ActionDeadCode::propagateConsumed(vector &worklist) case CPUI_CALL: case CPUI_CALLIND: break; // Call output doesn't indicate consumption of inputs + case CPUI_FLOAT_INT2FLOAT: + a = 0; + if (outc != 0) + a = coveringmask(op->getIn(0)->getNZMask()); + pushConsumed(a,op->getIn(0), worklist); + break; default: a = (outc==0) ? 0 : ~((uintb)0); // all or nothing for(int4 i=0;inumInput();++i) @@ -5538,6 +5544,8 @@ void ActionDatabase::universalAction(Architecture *conf) actprop->addRule( new RuleSubfloatConvert("floatprecision") ); actprop->addRule( new RuleFloatCast("floatprecision") ); actprop->addRule( new RuleIgnoreNan("floatprecision") ); + actprop->addRule( new RuleUnsigned2Float("analysis") ); + actprop->addRule( new RuleInt2FloatCollapse("analysis") ); actprop->addRule( new RulePtraddUndo("typerecovery") ); actprop->addRule( new RulePtrsubUndo("typerecovery") ); actprop->addRule( new RuleSegment("segment") ); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.cc index a027b242a0..6ebec206c9 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.cc @@ -3105,6 +3105,8 @@ ProtoParameter *ProtoStoreSymbol::setInput(int4 i, const string &nm,const Parame bool isindirect = (pieces.flags & ParameterPieces::indirectstorage) != 0; bool ishidden = (pieces.flags & ParameterPieces::hiddenretparm) != 0; + bool istypelock = (pieces.flags & ParameterPieces::typelock) != 0; + bool isnamelock = (pieces.flags & ParameterPieces::namelock) != 0; if (res->sym != (Symbol *)0) { entry = res->sym->getFirstWholeMap(); if ((entry->getAddr() != pieces.addr)||(entry->getSize() != pieces.type->getSize())) { @@ -3117,12 +3119,16 @@ ProtoParameter *ProtoStoreSymbol::setInput(int4 i, const string &nm,const Parame usepoint = restricted_usepoint; res->sym = scope->addSymbol(nm,pieces.type,pieces.addr,usepoint)->getSymbol(); scope->setCategory(res->sym,Symbol::function_parameter,i); - if (isindirect || ishidden) { + if (isindirect || ishidden || istypelock || isnamelock) { uint4 mirror = 0; if (isindirect) mirror |= Varnode::indirectstorage; if (ishidden) mirror |= Varnode::hiddenretparm; + if (istypelock) + mirror |= Varnode::typelock; + if (isnamelock) + mirror |= Varnode::namelock; scope->setAttribute(res->sym,mirror); } return res; @@ -3139,6 +3145,18 @@ ProtoParameter *ProtoStoreSymbol::setInput(int4 i, const string &nm,const Parame else scope->clearAttribute(res->sym,Varnode::hiddenretparm); } + if (res->sym->isTypeLocked() != istypelock) { + if (istypelock) + scope->setAttribute(res->sym,Varnode::typelock); + else + scope->clearAttribute(res->sym,Varnode::typelock); + } + if (res->sym->isNameLocked() != isnamelock) { + if (isnamelock) + scope->setAttribute(res->sym,Varnode::namelock); + else + scope->clearAttribute(res->sym,Varnode::namelock); + } if ((nm.size()!=0)&&(nm!=res->sym->getName())) scope->renameSymbol(res->sym,nm); if (pieces.type != res->sym->getType()) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc index 6042986698..0e595426ac 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc @@ -827,6 +827,19 @@ void PrintC::opBoolNegate(const PcodeOp *op) } } +void PrintC::opFloatInt2Float(const PcodeOp *op) + +{ + const PcodeOp *zextOp = TypeOpFloatInt2Float::absorbZext(op); + const Varnode *vn0 = (zextOp != (const PcodeOp *)0) ? zextOp->getIn(0) : op->getIn(0); + Datatype *dt = op->getOut()->getHighTypeDefFacing(); + if (!option_nocasts) { + pushOp(&typecast,op); + pushType(dt); + } + pushVn(vn0,op,mods); +} + void PrintC::opSubpiece(const PcodeOp *op) { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.hh index 3cebbc3f91..380624335a 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.hh @@ -4,9 +4,9 @@ * 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. @@ -322,7 +322,7 @@ public: virtual void opFloatNeg(const PcodeOp *op) { opUnary(&unary_minus,op); } virtual void opFloatAbs(const PcodeOp *op) { opFunc(op); } virtual void opFloatSqrt(const PcodeOp *op) { opFunc(op); } - virtual void opFloatInt2Float(const PcodeOp *op) { opTypeCast(op); } + virtual void opFloatInt2Float(const PcodeOp *op); virtual void opFloatFloat2Float(const PcodeOp *op) { opTypeCast(op); } virtual void opFloatTrunc(const PcodeOp *op) { opTypeCast(op); } virtual void opFloatCeil(const PcodeOp *op) { opFunc(op); } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc index 7dab24a8c3..2242468e6e 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc @@ -9801,6 +9801,136 @@ int4 RuleIgnoreNan::applyOp(PcodeOp *op,Funcdata &data) return (count > 0) ? 1 : 0; } +/// \class RuleUnsigned2Float +/// \brief Simplify conversion: `T = int2float((X >> 1) | X & #1); T + T => int2float( zext(X) )` +/// +/// Architectures like x86 can use this sequence to simulate an unsigned integer to floating-point conversion, +/// when they don't have the conversion in hardware. +void RuleUnsigned2Float::getOpList(vector &oplist) const + +{ + oplist.push_back(CPUI_FLOAT_INT2FLOAT); +} + +int4 RuleUnsigned2Float::applyOp(PcodeOp *op,Funcdata &data) + +{ + Varnode *invn = op->getIn(0); + if (!invn->isWritten()) return 0; + PcodeOp *orop = invn->getDef(); + if (orop->code() != CPUI_INT_OR) return 0; + if (!orop->getIn(0)->isWritten() || !orop->getIn(1)->isWritten()) return 0; + PcodeOp *shiftop = orop->getIn(0)->getDef(); + PcodeOp *andop; + if (shiftop->code() != CPUI_INT_RIGHT) { + andop = shiftop; + shiftop = orop->getIn(1)->getDef(); + } + else { + andop = orop->getIn(1)->getDef(); + } + if (shiftop->code() != CPUI_INT_RIGHT) return 0; + if (!shiftop->getIn(1)->constantMatch(1)) return 0; // Shift to right by 1 exactly to clear high-bit + Varnode *basevn = shiftop->getIn(0); + if (basevn->isFree()) return 0; + if (andop->code() == CPUI_INT_ZEXT) { + if (!andop->getIn(0)->isWritten()) return 0; + andop = andop->getIn(0)->getDef(); + } + if (andop->code() != CPUI_INT_AND) return 0; + if (!andop->getIn(1)->constantMatch(1)) return 0; // Mask off least significant bit + Varnode *vn = andop->getIn(0); + if (basevn != vn) { + if (!vn->isWritten()) return 0; + PcodeOp *subop = vn->getDef(); + if (subop->code() != CPUI_SUBPIECE) return 0; + if (subop->getIn(1)->getOffset() != 0) return 0; + vn = subop->getIn(0); + if (basevn != vn) return 0; + } + Varnode *outvn = op->getOut(); + list::const_iterator iter; + for(iter=outvn->beginDescend();iter!=outvn->endDescend();++iter) { + PcodeOp *addop = *iter; + if (addop->code() != CPUI_FLOAT_ADD) continue; + if (addop->getIn(0) != outvn) continue; + if (addop->getIn(1) != outvn) continue; + PcodeOp *zextop = data.newOp(1,addop->getAddr()); + data.opSetOpcode(zextop, CPUI_INT_ZEXT); + Varnode *zextout = data.newUniqueOut(TypeOpFloatInt2Float::preferredZextSize(basevn->getSize()), zextop); + data.opSetOpcode(addop, CPUI_FLOAT_INT2FLOAT); + data.opRemoveInput(addop, 1); + data.opSetInput(zextop, basevn, 0); + data.opSetInput(addop, zextout, 0); + data.opInsertBefore(zextop, addop); + return 1; + } + return 0; +} + +/// \class RuleInt2FloatCollapse +/// \brief Collapse equivalent FLOAT_INT2FLOAT computations along converging data-flow paths +/// +/// Look for two code paths with different ways of calculating an unsigned integer to floating-point conversion, +/// one of which is chosen by examining the most significant bit of the integer. The two paths can be collapsed +/// into a single FLOAT_INT2FLOAT operation. +void RuleInt2FloatCollapse::getOpList(vector &oplist) const + +{ + oplist.push_back(CPUI_FLOAT_INT2FLOAT); +} + +int4 RuleInt2FloatCollapse::applyOp(PcodeOp *op,Funcdata &data) + +{ + if (!op->getIn(0)->isWritten()) return 0; + PcodeOp *zextop = op->getIn(0)->getDef(); + if (zextop->code() != CPUI_INT_ZEXT) return 0; // Original FLOAT_INT2FLOAT must be unsigned form + Varnode *basevn = zextop->getIn(0); + if (basevn->isFree()) return 0; + PcodeOp *multiop = op->getOut()->loneDescend(); + if (multiop == (PcodeOp *)0) return 0; + if (multiop->code() != CPUI_MULTIEQUAL) return 0; // Output comes together with 1 other flow + if (multiop->numInput() != 2) return 0; + int4 slot = multiop->getSlot(op->getOut()); + Varnode *otherout = multiop->getIn(1-slot); + if (!otherout->isWritten()) return 0; + PcodeOp *op2 = otherout->getDef(); + if (op2->code() != CPUI_FLOAT_INT2FLOAT) return 0; // The other flow must be a signed FLOAT_INT2FLOAT + if (basevn != op2->getIn(0)) return 0; // taking the same input + int4 dir2unsigned; // Control path to unsigned conversion + FlowBlock *cond = FlowBlock::findCondition(multiop->getParent(), slot, multiop->getParent(), 1-slot, dir2unsigned); + if (cond == (FlowBlock *)0) return 0; + PcodeOp *cbranch = cond->lastOp(); + if (cbranch == (PcodeOp *)0 || cbranch->code() != CPUI_CBRANCH) return 0; + if (!cbranch->getIn(1)->isWritten()) return 0; + if (cbranch->isBooleanFlip()) return 0; + PcodeOp *compare = cbranch->getIn(1)->getDef(); + if (compare->code() != CPUI_INT_SLESS) return 0; + if (compare->getIn(1)->constantMatch(0)) { // If condition is (basevn < 0) + if (compare->getIn(0) != basevn) return 0; + if (dir2unsigned != 1) return 0; // True branch must be the unsigned FLOAT_INT2FLOAT + } + else if (compare->getIn(0)->constantMatch(calc_mask(basevn->getSize()))) { // If condition is (-1 < basevn) + if (compare->getIn(1) != basevn) return 0; + if (dir2unsigned == 1) return 0; // True branch must be to signed FLOAT_INT2FLOAT + } + else + return 0; + BlockBasic *outbl = multiop->getParent(); + data.opUninsert(multiop); + data.opSetOpcode(multiop, CPUI_FLOAT_INT2FLOAT); // Redefine the MULTIEQUAL as unsigned FLOAT_INT2FLOAT + data.opRemoveInput(multiop, 0); + PcodeOp *newzext = data.newOp(1, multiop->getAddr()); + data.opSetOpcode(newzext, CPUI_INT_ZEXT); + Varnode *newout = data.newUniqueOut(TypeOpFloatInt2Float::preferredZextSize(basevn->getSize()), newzext); + data.opSetInput(newzext,basevn,0); + data.opSetInput(multiop, newout, 0); + data.opInsertBegin(multiop, outbl); // Reinsert modified MULTIEQUAL after any other MULTIEQUAL + data.opInsertBefore(newzext, multiop); + return 1; +} + /// \class RuleFuncPtrEncoding /// \brief Eliminate ARM/THUMB style masking of the low order bits on function pointers /// diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh index 377ab54039..5ba53e68de 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh @@ -1568,6 +1568,28 @@ public: virtual int4 applyOp(PcodeOp *op,Funcdata &data); }; +class RuleUnsigned2Float : public Rule { +public: + RuleUnsigned2Float(const string &g) : Rule( g, 0, "unsigned2float") {} ///< Constructor + virtual Rule *clone(const ActionGroupList &grouplist) const { + if (!grouplist.contains(getGroup())) return (Rule *)0; + return new RuleUnsigned2Float(getGroup()); + } + virtual void getOpList(vector &oplist) const; + virtual int4 applyOp(PcodeOp *op,Funcdata &data); +}; + +class RuleInt2FloatCollapse : public Rule { +public: + RuleInt2FloatCollapse(const string &g) : Rule( g, 0, "int2floatcollapse") {} ///< Constructor + virtual Rule *clone(const ActionGroupList &grouplist) const { + if (!grouplist.contains(getGroup())) return (Rule *)0; + return new RuleInt2FloatCollapse(getGroup()); + } + virtual void getOpList(vector &oplist) const; + virtual int4 applyOp(PcodeOp *op,Funcdata &data); +}; + class RuleFuncPtrEncoding : public Rule { public: RuleFuncPtrEncoding(const string &g) : Rule( g, 0, "funcptrencoding") {} ///< Constructor diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc index b6f930210c..bcdccecfca 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc @@ -330,6 +330,41 @@ bool SubvariableFlow::trySwitchPull(PcodeOp *op,ReplaceVarnode *rvn) return true; } +/// \brief Determine if the subgraph variable flows naturally into a terminal FLOAT_INT2FLOAT operation +/// +/// The original data-flow must pad the logical value with zero bits, making the conversion to +/// floating-point unsigned. A PatchRecord is created that preserves the FLOAT_INT2FLOAT but inserts an +/// additional INT_ZEXT operation to preserve the unsigned nature of the conversion. +/// \param op is the FLOAT_INT2FLOAT conversion operation +/// \param rvn is the logical value flowing into the conversion +bool SubvariableFlow::tryInt2FloatPull(PcodeOp *op,ReplaceVarnode *rvn) + +{ + if ((rvn->mask & 1) == 0) return false; // Logical value must be justified + if ((rvn->vn->getNZMask()&~rvn->mask)!=0) + return false; // Everything outside the logical value must be zero + if (rvn->vn->getSize() == flowsize) + return false; // There must be some (zero) extension + bool pullModification = true; + if (rvn->vn->isWritten() && rvn->vn->getDef()->code() == CPUI_INT_ZEXT) { + if (rvn->vn->getSize() == TypeOpFloatInt2Float::preferredZextSize(flowsize)) { + if (rvn->vn->loneDescend() == op) { + pullModification = false; // This patch does not count as a modification + // The INT_ZEXT -> FLOAT_INT2FLOAT has the correct form and does not need to be modified. + // We indicate this by NOT incrementing pullcount, so there has to be at least one other + // terminal patch in order for doTrace() to return true. + } + } + } + patchlist.emplace_back(); + patchlist.back().type = PatchRecord::int2float_patch; + patchlist.back().patchOp = op; + patchlist.back().in1 = rvn; + if (pullModification) + pullcount += 1; + return true; +} + /// Try to trace the logical variable through descendant Varnodes /// creating new nodes in the logical subgraph and updating the worklist. /// \param rvn is the given subgraph variable to trace @@ -586,6 +621,10 @@ bool SubvariableFlow::traceForward(ReplaceVarnode *rvn) if (rvn->mask != 1) return false; addBooleanPatch(op,rvn,slot); break; + case CPUI_FLOAT_INT2FLOAT: + if (!tryInt2FloatPull(op, rvn)) return false; + hcount += 1; + break; case CPUI_CBRANCH: if ((bitsize != 1)||(slot != 1)) return false; if (rvn->mask != 1) return false; @@ -1451,6 +1490,18 @@ void SubvariableFlow::doReplacement(void) } case PatchRecord::push_patch: break; // Shouldn't see these here, handled earlier + case PatchRecord::int2float_patch: + { + PcodeOp *zextOp = fd->newOp(1, pullop->getAddr()); + fd->opSetOpcode(zextOp, CPUI_INT_ZEXT); + Varnode *invn = getReplaceVarnode((*piter).in1); + fd->opSetInput(zextOp,invn,0); + int4 sizeout = TypeOpFloatInt2Float::preferredZextSize(invn->getSize()); + Varnode *outvn = fd->newUniqueOut(sizeout, zextOp); + fd->opInsertBefore(zextOp, pullop); + fd->opSetInput(pullop, outvn, 0); + break; + } } } } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh index 06b11cc92f..0f54a624b2 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh @@ -70,7 +70,8 @@ class SubvariableFlow { compare_patch, ///< Turn compare op inputs into logical values parameter_patch, ///< Convert a CALL/CALLIND/RETURN/BRANCHIND parameter into logical value extension_patch, ///< Convert op into something that copies/extends logical value, adding zero bits - push_patch ///< Convert an operator output to the logical value + push_patch, ///< Convert an operator output to the logical value + int2float_patch ///< Zero extend logical value into FLOAT_INT2FLOAT operator }; patchtype type; ///< The type of \b this patch PcodeOp *patchOp; ///< Op being affected @@ -101,6 +102,7 @@ class SubvariableFlow { bool tryReturnPull(PcodeOp *op,ReplaceVarnode *rvn,int4 slot); bool tryCallReturnPush(PcodeOp *op,ReplaceVarnode *rvn); bool trySwitchPull(PcodeOp *op,ReplaceVarnode *rvn); + bool tryInt2FloatPull(PcodeOp *op,ReplaceVarnode *rvn); bool traceForward(ReplaceVarnode *rvn); ///< Trace the logical data-flow forward for the given subgraph variable bool traceBackward(ReplaceVarnode *rvn); ///< Trace the logical data-flow backward for the given subgraph variable bool traceForwardSext(ReplaceVarnode *rvn); ///< Trace logical data-flow forward assuming sign-extensions diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc index a0fba842c4..c8e43af58a 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc @@ -4,9 +4,9 @@ * 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. @@ -267,14 +267,14 @@ void FunctionTestCollection::restoreXml(DocumentStorage &store,const Element *el buildProgram(store); } else - throw IfaceParseError("Unknown tag in : "+subel->getName()); + throw IfaceParseError("Unknown tag in : "+subel->getName()); } if (!sawScript) - throw IfaceParseError("Did not see +return \(float4\)\(int4\)\(fval - 0x10\) \* -0.001; +return \(float8\)\(lval - 0x10\) \* -0.001; +return \(float8\)\(llval - 0x10\) \* -0.001; + From f703538ce1cba5d303b57f0352af7dcfdd7ece10 Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Fri, 30 Aug 2024 21:00:14 +0000 Subject: [PATCH 38/53] GP-4887 Adjustments to nearestArrayedComponent --- .../src/decompile/cpp/ruleaction.cc | 4 +-- .../Decompiler/src/decompile/cpp/type.cc | 33 ++++++++++++++----- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc index 7dab24a8c3..d3d06d0033 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc @@ -5762,8 +5762,8 @@ bool AddTreeState::hasMatchingSubType(int8 off,uint4 arrayHint,int8 *newoff) con return true; } - uint8 distBefore = offBefore; - uint8 distAfter = -offAfter; + uint8 distBefore = (offBefore < 0) ? -offBefore : offBefore; + uint8 distAfter = (offAfter < 0) ? -offAfter : offAfter; if (arrayHint != 1) { if (elSizeBefore != arrayHint) distBefore += 0x1000; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc index 38376a0b26..7cda43127f 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc @@ -177,8 +177,8 @@ Datatype *Datatype::getSubType(int8 off,int8 *newoff) const return (Datatype *)0; } -/// Find the first component data-type after the given offset that is (or contains) -/// an array, and pass back the difference between the component's start and the given offset. +/// Find the first component data-type that is (or contains) an array starting after the given +/// offset, and pass back the difference between the component's start and the given offset. /// Return the component data-type or null if no array is found. /// \param off is the given offset into \b this data-type /// \param newoff is used to pass back the offset difference @@ -190,8 +190,8 @@ Datatype *Datatype::nearestArrayedComponentForward(int8 off,int8 *newoff,int8 *e return (TypeArray *)0; } -/// Find the first component data-type before the given offset that is (or contains) -/// an array, and pass back the difference between the component's start and the given offset. +/// Find the last component data-type that is (or contains) an array starting before the given +/// offset, and pass back the difference between the component's start and the given offset. /// Return the component data-type or null if no array is found. /// \param off is the given offset into \b this data-type /// \param newoff is used to pass back the offset difference @@ -1663,7 +1663,8 @@ int4 TypeStruct::getHoleSize(int4 off) const Datatype *TypeStruct::nearestArrayedComponentBackward(int8 off,int8 *newoff,int8 *elSize) const { - int4 i = getLowerBoundField(off); + int4 firstIndex = getLowerBoundField(off); + int4 i = firstIndex; while(i >= 0) { const TypeField &subfield( field[i] ); int8 diff = off - subfield.offset; @@ -1676,7 +1677,8 @@ Datatype *TypeStruct::nearestArrayedComponentBackward(int8 off,int8 *newoff,int8 } else { int8 suboff; - Datatype *res = subtype->nearestArrayedComponentBackward(subtype->getSize(), &suboff, elSize); + int8 remain = (i == firstIndex) ? diff : subtype->getSize() - 1; + Datatype *res = subtype->nearestArrayedComponentBackward(remain, &suboff, elSize); if (res != (Datatype *)0) { *newoff = diff; return subtype; @@ -1691,10 +1693,22 @@ Datatype *TypeStruct::nearestArrayedComponentForward(int8 off,int8 *newoff,int8 { int4 i = getLowerBoundField(off); - i += 1; + int8 remain; + if (i < 0) { // No component starting before off + i += 1; // First component starting after + remain = 0; + } + else { + const TypeField &subfield( field[i] ); + remain = off - subfield.offset; + if (remain != 0 && (subfield.type->getMetatype() != TYPE_STRUCT || remain >= subfield.type->getSize())) { + i += 1; // Middle of non-structure that we must go forward from, skip over it + remain = 0; + } + } while(i 128) break; Datatype *subtype = subfield.type; if (subtype->getMetatype() == TYPE_ARRAY) { @@ -1704,13 +1718,14 @@ Datatype *TypeStruct::nearestArrayedComponentForward(int8 off,int8 *newoff,int8 } else { int8 suboff; - Datatype *res = subtype->nearestArrayedComponentForward(0, &suboff, elSize); + Datatype *res = subtype->nearestArrayedComponentForward(remain, &suboff, elSize); if (res != (Datatype *)0) { *newoff = -diff; return subtype; } } i += 1; + remain = 0; } return (Datatype *)0; } From f094c9266f0d40ae60cba7d3a28bfae8141dee25 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Sat, 31 Aug 2024 11:30:57 -0400 Subject: [PATCH 39/53] GP-4888 - Fixed action enablement --- .../compositeeditor/FindReferencesToStructureFieldAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToStructureFieldAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToStructureFieldAction.java index 9497c47434..2b78f64499 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToStructureFieldAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToStructureFieldAction.java @@ -70,7 +70,7 @@ public class FindReferencesToStructureFieldAction extends CompositeEditorTableAc @Override public boolean isEnabledForContext(ActionContext context) { setEnabled(false); - if (!hasIncompleteFieldEntry()) { + if (hasIncompleteFieldEntry()) { return false; } if (model.getSelectedComponentRows().length != 1) { From 97f92c9a07cc28caad0215a5a5dd32da262f5bd1 Mon Sep 17 00:00:00 2001 From: Peter Lucia <133892712+plucia-mitre@users.noreply.github.com> Date: Tue, 3 Sep 2024 10:26:17 -0400 Subject: [PATCH 40/53] Fix spelling error in example code in Application.java --- .../Generic/src/main/java/ghidra/framework/Application.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java index f9b3f10800..76028e71e2 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java @@ -44,7 +44,7 @@ import utility.module.ModuleUtilities; *
  *   ApplicationLayout layout = new GhidraApplicationLayout();
  *   ApplicationConfiguration configuration = new GhidraApplicationConfiguration();
- *   Application.initalizeApplication(layout, configuration);
+ *   Application.initializeApplication(layout, configuration);
  * 
*/ public class Application { From 01f6bceff164db194ca42a62ca2db14b25bec5a8 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 3 Sep 2024 11:49:09 -0400 Subject: [PATCH 41/53] GP-0: Certify --- .../Generic/src/main/java/ghidra/framework/Application.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java index 76028e71e2..1ef2d6ff31 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java @@ -4,9 +4,9 @@ * 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. From 4eefa887fb9eac75067cab64b08ad330027665f3 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 3 Sep 2024 12:57:52 -0400 Subject: [PATCH 42/53] GP-0: Fixing some javadoc --- .../app/cmd/label/CreateNamespacesCmd.java | 8 +- .../java/ghidra/app/plugin/ProgramPlugin.java | 14 +- .../core/analysis/GolangStringAnalyzer.java | 8 +- .../plugin/core/console/CodeCompletion.java | 20 +-- .../app/plugin/core/cparser/ParseDialog.java | 8 +- .../core/function/tags/FunctionTagTable.java | 10 +- .../ui/InsertBytesWidget.java | 10 +- .../navigation/NextPrevCodeUnitPlugin.java | 18 +-- .../app/plugin/core/osgi/BundleHost.java | 6 +- .../scalartable/RangeFilterTextField.java | 8 +- .../scalartable/ScalarSearchProvider.java | 8 +- .../core/string/DefinedStringIterator.java | 12 +- .../plugin/core/terminal/vt/VtHandler.java | 10 +- .../structures/gccexcepttable/LSDATable.java | 11 +- .../dwarf/DWARFDataTypeConflictHandler.java | 10 +- .../elf/info/StandardElfInfoProducer.java | 15 +- .../format/golang/GoFunctionMultiReturn.java | 10 +- .../format/golang/GolangElfInfoProducer.java | 23 +-- .../bin/format/golang/rtti/GoFuncData.java | 8 +- .../bin/format/golang/rtti/GoRttiMapper.java | 30 ++-- .../app/util/bin/format/macho/MachHeader.java | 6 +- .../app/util/cparser/C/CParserUtils.java | 30 ++-- .../util/opinion/BoundedBufferedReader.java | 10 +- .../java/ghidra/formats/gfilesystem/FSRL.java | 16 +-- .../ghidra/formats/gfilesystem/FSRLRoot.java | 10 +- .../program/flatapi/FlatProgramAPI.java | 8 +- .../ghidra/util/bytesearch/AlignRule.java | 12 +- .../java/ghidra/util/bytesearch/Pattern.java | 6 +- .../util/bytesearch/PatternPairSet.java | 41 +++--- .../app/decompiler/DecompInterface.java | 14 +- .../app/util/DecompilerConcurrentQ.java | 6 +- .../DB/src/main/java/db/VarRecNode.java | 8 +- .../main/java/docking/ComponentProvider.java | 14 +- .../KeyBindingOverrideKeyEventDispatcher.java | 6 +- .../src/main/java/docking/PlaceholderSet.java | 8 +- .../src/main/java/docking/dnd/Draggable.java | 10 +- .../java/docking/widgets/OptionDialog.java | 50 ++----- .../autocomplete/TextFieldAutocompleter.java | 8 +- .../filechooser/GhidraFileChooser.java | 12 +- .../searchlist/DefaultSearchListModel.java | 8 +- .../widgets/table/GTableFilterPanel.java | 14 +- .../LocalDateSpinnerModel.java | 16 +-- .../table/threaded/TableUpdateJob.java | 18 +-- .../ghidra/util/task/BasicTaskMonitor.java | 6 +- .../java/ghidra/util/task/TaskLauncher.java | 36 +++-- .../ghidra/pcode/exec/PcodeUseropLibrary.java | 6 +- .../src/main/java/ghidra/util/CountLatch.java | 23 ++- .../java/ghidra/graph/GraphAlgorithms.java | 12 +- .../ghidra/graph/viewer/GraphViewerUtils.java | 10 +- .../edge/VisualGraphPathHighlighter.java | 6 +- .../java/generic/theme/SystemThemeIds.java | 20 +-- .../main/java/generic/theme/ThemeManager.java | 4 +- .../generic/theme/laf/UiDefaultsMapper.java | 16 +-- .../Help/src/main/java/help/GHelpBuilder.java | 8 +- .../main/logviewer/ui/FileViewer.java | 10 +- .../ghidra/framework/plugintool/Plugin.java | 132 ++++++++++-------- .../framework/plugintool/PluginTool.java | 14 +- .../sleigh/SleighAssemblerBuilder.java | 8 +- .../processors/sleigh/ParserWalker.java | 10 +- .../sleigh/SleighParserContext.java | 6 +- .../plugin/processors/sleigh/VarnodeData.java | 6 +- .../sleigh/template/ConstructTpl.java | 6 +- .../java/ghidra/app/util/NamespaceUtils.java | 20 +-- .../ghidra/app/util/PseudoDisassembler.java | 20 +-- .../main/java/ghidra/app/util/SymbolPath.java | 10 +- .../slgh_compile/ParseException.java | 29 ++-- .../database/ProgramAddressFactory.java | 6 +- .../program/database/SpecExtension.java | 49 ++++--- .../database/util/AddressRangeMapDB.java | 15 +- .../program/model/block/BasicBlockModel.java | 18 +-- .../program/model/block/SimpleBlockModel.java | 25 ++-- .../model/data/BitFieldPackingImpl.java | 6 +- .../ghidra/program/model/data/Composite.java | 10 +- .../program/model/data/DataUtilities.java | 2 +- .../program/model/data/StringDataType.java | 28 ++-- .../program/model/data/StringLayoutEnum.java | 16 +-- .../program/model/data/StructureDataType.java | 2 +- .../program/model/data/StructureFactory.java | 36 ++--- .../program/model/lang/BasicCompilerSpec.java | 6 +- .../program/model/lang/CompilerSpec.java | 6 +- .../program/model/lang/ContextSetting.java | 10 +- .../program/model/lang/InjectPayload.java | 8 +- .../program/model/pcode/AddressXML.java | 41 +++--- .../model/pcode/PcodeDataTypeManager.java | 8 +- .../ghidra/program/model/pcode/PcodeOp.java | 6 +- .../framework/ApplicationIdentifier.java | 6 +- .../ghidra/framework/ApplicationVersion.java | 12 +- .../java/ghidra/util/UserSearchUtils.java | 12 +- 88 files changed, 671 insertions(+), 653 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/CreateNamespacesCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/CreateNamespacesCmd.java index 64c25cb3c6..6fb1468af5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/CreateNamespacesCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/CreateNamespacesCmd.java @@ -4,9 +4,9 @@ * 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. @@ -30,8 +30,8 @@ import ghidra.util.exception.InvalidInputException; * * Example strings: *
    - *
  • global{@link Namespace#DELIMITER ::}child1{@link Namespace#DELIMITER ::}child2 - *
  • child1 + *
  • global{@link Namespace#DELIMITER ::}child1{@link Namespace#DELIMITER ::}child2
  • + *
  • child1
  • *
*

* diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java index 2c976a65a2..457d827d3a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java @@ -4,9 +4,9 @@ * 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. @@ -34,11 +34,11 @@ import ghidra.program.util.ProgramSelection; * Subclasses should override the following methods if they are interested in the corresponding * events: *

    - *
  • {@link #programOpened(Program)} - *
  • {@link #programClosed(Program)} - *
  • {@link #locationChanged(ProgramLocation)} - *
  • {@link #selectionChanged(ProgramSelection)} - *
  • {@link #highlightChanged(ProgramSelection)} + *
  • {@link #programOpened(Program)}
  • + *
  • {@link #programClosed(Program)}
  • + *
  • {@link #locationChanged(ProgramLocation)}
  • + *
  • {@link #selectionChanged(ProgramSelection)}
  • + *
  • {@link #highlightChanged(ProgramSelection)}
  • *
*/ public abstract class ProgramPlugin extends Plugin { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangStringAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangStringAnalyzer.java index ba96bd861a..0bb4a68fca 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangStringAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangStringAnalyzer.java @@ -4,9 +4,9 @@ * 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. @@ -50,8 +50,8 @@ import ghidra.util.task.UnknownProgressWrappingTaskMonitor; *

* The string struct is found in a couple of different ways: *

    - *
  • References from an instruction (see markupStaticStructRefsInFunction) - *
  • Iterating through data segments and making educated guesses (see markupDataSegmentStructs) + *
  • References from an instruction (see markupStaticStructRefsInFunction)
  • + *
  • Iterating through data segments and making educated guesses (see markupDataSegmentStructs)
  • *
* Some char[] data is only referenced from Golang string structs that exist temporarily * in registers after being set by an instruction that statically references the char[] data, diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/CodeCompletion.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/CodeCompletion.java index 7b58194c66..7c83260bbf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/CodeCompletion.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/CodeCompletion.java @@ -4,9 +4,9 @@ * 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. @@ -23,20 +23,20 @@ import javax.swing.JComponent; * It is intended to be used by the code completion process, especially the * CodeCompletionWindow. It encapsulates: *
    - *
  • a description of the completion (what are you completing?) - *
  • the actual String that will be inserted - *
  • an optional Component that will be in the completion List - *
  • the number of characters to remove before the insertion of the completion + *
  • a description of the completion (what are you completing?)
  • + *
  • the actual String that will be inserted
  • + *
  • an optional Component that will be in the completion List
  • + *
  • the number of characters to remove before the insertion of the completion
  • *
*

* For example, if one wants to autocomplete a string "Runscr" into "runScript", * the fields may look as follows: *

    - *
  • description: "runScript (Method)" - *
  • insertion: "runScript" - *
  • component: null or JLabel("runScript (Method)") + *
  • description: "runScript (Method)"
  • + *
  • insertion: "runScript"
  • + *
  • component: null or JLabel("runScript (Method)")
  • *
  • charsToRemove: 6 (i.e. the length of "Runscr", - * as it may be required later to correctly replace the string) + * as it may be required later to correctly replace the string)
  • *
*/ public class CodeCompletion implements Comparable { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/cparser/ParseDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/cparser/ParseDialog.java index c07818f942..eb3137f900 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/cparser/ParseDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/cparser/ParseDialog.java @@ -4,9 +4,9 @@ * 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. @@ -58,9 +58,9 @@ import resources.Icons; * Dialog that shows files used for parsing C header files. The profile has a list of source header * files to parse, followed by parse options (compiler directives). Ghidra supplies a Windows * profile by default in core/parserprofiles. The user can do "save as" on this default profile to - * create new profiles that will be written to the user's /userprofiles directory. The + * create new profiles that will be written to the user's {@code /userprofiles} directory. The * CParserPlugin creates this directory if it doesn't exist. - * + *

* The data types resulting from the parse operation can either be added to the data type manager in * the current program, or written to an archive data file. * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTable.java index 792693bfc7..97a2115689 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTable.java @@ -4,9 +4,9 @@ * 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. @@ -79,8 +79,10 @@ public class FunctionTagTable extends GhidraTable { /** * We need to override the renderer for the following cases: - *

  • italicize tags that cannot be edited
  • - *
  • disable rows in the source table that have already been added to the selected function
  • + *
      + *
    • italicize tags that cannot be edited
    • + *
    • disable rows in the source table that have already been added to the selected function
    • + *
    */ @Override public TableCellRenderer getCellRenderer(int row, int col) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InsertBytesWidget.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InsertBytesWidget.java index f5a11e3d98..63396d14d1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InsertBytesWidget.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InsertBytesWidget.java @@ -4,9 +4,9 @@ * 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. @@ -398,8 +398,10 @@ public class InsertBytesWidget extends ReusableDialogComponentProvider implement /** * Verifies that the input entered by the user is valid. Meaning: - *
  • The string represents a hex or binary number.
  • - *
  • The string contains only full bytes.
  • + *
      + *
    • The string represents a hex or binary number.
    • + *
    • The string contains only full bytes.
    • + *
    * * @return true if input is valid */ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPlugin.java index 424da1777d..63220d40d8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPlugin.java @@ -4,9 +4,9 @@ * 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. @@ -40,13 +40,13 @@ import resources.Icons; * The NextPrevCodeUnitPlugin generates a GoTo event based on where the cursor * is located in the program. The GoTo events provided by this plugin are: *
      - *
    • Next-Previous Instruction - *
    • Next-Previous Defined Data - *
    • Next-Previous Undefined Data - *
    • Next-Previous Function - *
    • Next-Previous Non-Function - *
    • Next-Previous Label - *
    • Next-Previous Bookmark + *
    • Next-Previous Instruction
    • + *
    • Next-Previous Defined Data
    • + *
    • Next-Previous Undefined Data
    • + *
    • Next-Previous Function
    • + *
    • Next-Previous Non-Function
    • + *
    • Next-Previous Label
    • + *
    • Next-Previous Bookmark
    • *
    */ //@formatter:off diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java index 6633f6d332..72fdec3d22 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java @@ -49,11 +49,11 @@ import utilities.util.FileUtilities; * Note: {@link GhidraBundle}, its implementations, and this class constitute a bridge between * OSGi's {@link Bundle} and Ghidra. *
      - *
    • unqualified, "bundle" will mean {@link GhidraBundle} + *
    • unqualified, "bundle" will mean {@link GhidraBundle}
    • *
    • use of OSGi types, including {@link Bundle} and {@link Framework}, should be package scoped - * (not public) + * (not public)
    • *
    • bundle lifecycle is simplified to "active"(same as OSGi "active" state) and "inactive" - * (OSGi "uninstalled" state) + * (OSGi "uninstalled" state)
    • *
    */ public class BundleHost { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/RangeFilterTextField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/RangeFilterTextField.java index 45fed24095..f74cd69291 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/RangeFilterTextField.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/RangeFilterTextField.java @@ -4,9 +4,9 @@ * 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. @@ -25,8 +25,8 @@ import ghidra.program.model.listing.Program; *

    * Specifically this provides the following: *

      - *
    • Ability to specify if this is a min/max range field - *
    • Allows hex input of the form "0x...." for hex values + *
    • Ability to specify if this is a min/max range field
    • + *
    • Allows hex input of the form "0x...." for hex values
    • *
    */ public class RangeFilterTextField extends IntegerTextField { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java index 6aebe42f08..320ac43882 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java @@ -4,9 +4,9 @@ * 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. @@ -42,8 +42,8 @@ import help.HelpService; /** * Displays the results of a query from the {@link ScalarSearchPlugin}. Consists of 2 components: *
      - *
    • The scalar table that is displayed to the user - *
    • The range filter that allows the user to filter the scalar table via a min and max value. + *
    • The scalar table that is displayed to the user
    • + *
    • The range filter that allows the user to filter the scalar table via a min and max value.
    • *
    */ public class ScalarSearchProvider extends ComponentProviderAdapter { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/DefinedStringIterator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/DefinedStringIterator.java index 7e23cf17ee..63bd2e94c5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/DefinedStringIterator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/DefinedStringIterator.java @@ -4,9 +4,9 @@ * 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. @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.string; -import static ghidra.program.util.string.FoundString.DefinedState.DEFINED; +import static ghidra.program.util.string.FoundString.DefinedState.*; import java.util.Iterator; @@ -31,9 +31,9 @@ import ghidra.program.util.string.FoundString; * Basic Algorithm: Uses a defined data iterator to find all defined data in a program. For * each defined data, strings are searched as follows: *
      - *
    1. is it a string? if so, add to the queue of results - *
    2. is it an array? if so, are they non-primitive elements? if so, recursively search them for strings. - *
    3. is it a composite (structure or union)? if so, recursively search each element of the structure. + *
    4. is it a string? if so, add to the queue of results
    5. + *
    6. is it an array? if so, are they non-primitive elements? if so, recursively search them for strings.
    7. + *
    8. is it a composite (structure or union)? if so, recursively search each element of the structure.
    9. *
    *

    * This class maintains a queue of all strings found at any given top-level data element. When diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/vt/VtHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/vt/VtHandler.java index 0685d9770c..f370d4a3a2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/vt/VtHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/vt/VtHandler.java @@ -4,9 +4,9 @@ * 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. @@ -62,17 +62,17 @@ import ghidra.util.Msg; * {@code "\033["} * * - * {@code OSC} + * {@code OSC} * {@code ESC ]} * {@code "\033]"} * * - * {@code ST} + * {@code ST} * {@code ESC \} * {@code "\033\\"} * * - * {@code BEL} + * {@code BEL} * byte 0x07 * {@code "\007"} * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/structures/gccexcepttable/LSDATable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/structures/gccexcepttable/LSDATable.java index fb4f7ca346..3604b672e1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/structures/gccexcepttable/LSDATable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/exceptionhandlers/gcc/structures/gccexcepttable/LSDATable.java @@ -1,13 +1,12 @@ /* ### * 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. * 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. @@ -38,10 +37,10 @@ import ghidra.util.task.TaskMonitor; * The LSDA is comprised of: *

      *
    • A header that describes the bounds of exception handling support and encoding - * modes for values found later in the LSDA table + * modes for values found later in the LSDA table
    • *
    • A call site table that describes each location a 'throws' occurs and where - * a corresponding catch block resides, and the actions to take. - *
    • An action table, that describes what the runtime needs to do during unwind + * a corresponding catch block resides, and the actions to take.
    • + *
    • An action table, that describes what the runtime needs to do during unwind
    • *
    *

    * The structures modeled here are described in detail in the C++ ABI. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFDataTypeConflictHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFDataTypeConflictHandler.java index 1b2b171960..9340af6564 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFDataTypeConflictHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFDataTypeConflictHandler.java @@ -4,9 +4,9 @@ * 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. @@ -39,16 +39,16 @@ import ghidra.util.SystemUtilities; *

    *

      *
    • discarded and replaced by the existing data type - * ({@link ConflictResult#USE_EXISTING}) + * ({@link ConflictResult#USE_EXISTING})
    • *
    • used to overwrite the existing data type - * ({@link ConflictResult#REPLACE_EXISTING}) + * ({@link ConflictResult#REPLACE_EXISTING})
    • *
    * or the candidate data type was NOT matched with an existing data type, * and the new data type is: *

    *

      *
    • kept, but renamed with a .conflictNNNN suffix to make it unique - * ({@link ConflictResult#RENAME_AND_ADD}) + * ({@link ConflictResult#RENAME_AND_ADD})
    • *
    * NOTE: structures with alignment (instead of being statically laid out) * are not treated specially and will not match other aligned or non-aligned diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/info/StandardElfInfoProducer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/info/StandardElfInfoProducer.java index 097e4163a9..73a35cf9e8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/info/StandardElfInfoProducer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/info/StandardElfInfoProducer.java @@ -4,9 +4,9 @@ * 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. @@ -31,7 +31,6 @@ import ghidra.program.model.listing.Program; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.util.CodeUnitInsertionException; -import ghidra.util.Msg; import ghidra.util.classfinder.ExtensionPointProperties; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -39,11 +38,11 @@ import ghidra.util.task.TaskMonitor; /** * Handles marking up and program info for basic ELF note (and note-like) sections. *
      - *
    • NoteAbiTag - *
    • NoteGnuBuildId - *
    • NoteGnuProperty - *
    • GnuDebugLink (not a note) - *
    • ElfComment (not a note) + *
    • NoteAbiTag
    • + *
    • NoteGnuBuildId
    • + *
    • NoteGnuProperty
    • + *
    • GnuDebugLink (not a note)
    • + *
    • ElfComment (not a note)
    • *
    *

    * Runs after other ElfInfoProducers that have a normal priority. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionMultiReturn.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionMultiReturn.java index 92c11096b1..ede4f2e851 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionMultiReturn.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionMultiReturn.java @@ -4,9 +4,9 @@ * 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. @@ -30,10 +30,10 @@ import ghidra.program.model.lang.Register; *

    * Assigning custom storage for the return value is complicated by: *

      - *
    • golang storage allocations depend on the formal ordering of the return values - *
    • stack storage must be last in a list of varnodes + *
    • golang storage allocations depend on the formal ordering of the return values
    • + *
    • stack storage must be last in a list of varnodes
    • *
    • the decompiler maps a structure's contents to the list of varnodes in an endian-dependent - * manner. + * manner.
    • *
    * To meet these complications, the structure's layout is modified to put all items that were * marked as being stack parameters to either the front or back of the structure. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangElfInfoProducer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangElfInfoProducer.java index 2654b53efd..301cc30800 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangElfInfoProducer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangElfInfoProducer.java @@ -4,9 +4,9 @@ * 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. @@ -28,16 +28,17 @@ import ghidra.util.task.TaskMonitor; /** * Handles marking up and program info for Golang binaries. - *

    *

      - *
    • NoteGoBuildId - *
    • GoBuildInfo - *
        - *
      • Go version - *
      • App path, main package - *
      • Module dependency list - *
      • Build settings / flags - *
      + *
    • NoteGoBuildId
    • + *
    • GoBuildInfo
    • + *
    • + *
        + *
      • Go version
      • + *
      • App path, main package
      • + *
      • Module dependency list
      • + *
      • Build settings / flags
      • + *
      + *
    • *
    */ public class GolangElfInfoProducer implements ElfInfoProducer { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java index 7ba8f8d25c..01c3660f2f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java @@ -4,9 +4,9 @@ * 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. @@ -225,8 +225,8 @@ public class GoFuncData implements StructureMarkup { *

    * The information that can be recovered about arguments is limited to: *

      - *
    • the size of the argument - *
    • general grouping (eg. grouping of arg values as a structure or array) + *
    • the size of the argument
    • + *
    • general grouping (eg. grouping of arg values as a structure or array)
    • *
    * Return value information is unknown and always represented as an "undefined" data type. * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java index 819f563831..891d828e1f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java @@ -4,9 +4,9 @@ * 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. @@ -58,22 +58,26 @@ import ghidra.util.task.UnknownProgressWrappingTaskMonitor; *
  • Find the GoBuildInfo struct. This struct is the easiest to locate, even when the binary * is stripped. This gives us the go pointerSize (probably same as ghidra pointer size) and the * goVersion. This struct does not rely on StructureMapping, allowing its use before a - * DataTypeMapper is created. - *
  • Create DataTypeMapper - *
  • Find the runtime.firstmoduledata structure. - *
      - *
    • If there are symbols, just use the symbol or named memory block. - *
    • If stripped: + * DataTypeMapper is created.
    • + *
    • Create DataTypeMapper
    • + *
    • Find the runtime.firstmoduledata structure.
    • + *
    • + *
        + *
      • If there are symbols, just use the symbol or named memory block.
      • + *
      • If stripped:
      • + *
      • *
          *
        • Find the pclntab. This has a magic signature, a pointerSize, and references - * to a couple of tables that are also referenced in the moduledata structure. + * to a couple of tables that are also referenced in the moduledata structure.
        • *
        • Search memory for a pointer to the pclntab struct. This should be the first * field of the moduledata structure. The values that are duplicated between the - * two structures can be compared to ensure validity. + * two structures can be compared to ensure validity.
        • *
        • Different binary formats (Elf vs PE) will determine which memory blocks to - * search. - *
        - *
      + * search.
    • + *
    + *
  • + * + * * */ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContext { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java index 4dd01eba16..3e34d12f47 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java @@ -4,9 +4,9 @@ * 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. @@ -196,7 +196,7 @@ public class MachHeader implements StructConverter { /** * Parses only this {@link MachHeader}'s {@link SegmentCommand segments} * - * @return A {@List} of this {@link MachHeader}'s {@link SegmentCommand segments} + * @return A {@link List} of this {@link MachHeader}'s {@link SegmentCommand segments} * @throws IOException If there was an IO-related error */ public List parseSegments() throws IOException { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/C/CParserUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/C/CParserUtils.java index 7389be2cd8..657a1ae83c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/C/CParserUtils.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/C/CParserUtils.java @@ -4,9 +4,9 @@ * 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. @@ -260,7 +260,7 @@ public class CParserUtils { * * @param filenames names of files in order to parse, could include strings with * "#" at start, which are ignored as comments - * @param args arguments for parsing, "-D=", "-I" + * @param args arguments for parsing, {@code -D=, -I} * * @param dataFileName name of data type archive file (include the .gdt extension) * @@ -297,8 +297,9 @@ public class CParserUtils { * * @param filenames names of files in order to parse, could include strings with * "#" at start, which are ignored as comments - * @param includePaths paths to include files, instead of using "-I" in args - * @param args arguments for parsing, "-D=", ( "-I" use includePaths parm instead) + * @param includePaths paths to include files, instead of using {@code -I} in args + * @param args arguments for parsing, {@code -D=}, ( {@code -I} use + * includePaths parm instead) * * @param dataFileName name of data type archive file (include the .gdt extension) * @@ -338,8 +339,8 @@ public class CParserUtils { * * @param filenames names of files in order to parse, could include strings with * "#" at start, which are ignored as comments - * @param includePaths path to include files, could also be in args with "-I" - * @param args arguments for parsing, "-D=", "-I" + * @param includePaths path to include files, could also be in args with {@code -I} + * @param args arguments for parsing, {@code -D=, -I} * * @param dataFileName name of data type archive file (include the .gdt extension) * @@ -391,11 +392,12 @@ public class CParserUtils { * * @param filenames names of files in order to parse, could include strings with * "#" at start, which are ignored as comments - * @param args arguments for parsing, "-D=", ( "-I" use includePaths parm instead) + * @param args arguments for parsing, {@code -D=}, ({@code -I} use + * includePaths parm instead) * * @param existingDTMgr datatypes will be populated into this provided DTMgr, can pass Program or File DTMgr * - * @param languageId language identication to use for data type organization definitions (int, long, ptr size) + * @param languageId language identification to use for data type organization definitions (int, long, ptr size) * @param compileSpecId compiler specification to use for parsing * * @param monitor used to cancel or provide results @@ -431,12 +433,12 @@ public class CParserUtils { * * @param filenames names of files in order to parse, could include strings with * "#" at start, which are ignored as comments - * @param includePaths paths to include files, instead of using "-I" in args - * @param args arguments for parsing, "-D=", ( "-I" use includePaths parm instead) + * @param includePaths paths to include files, instead of using {@code -I} in args + * @param args arguments for parsing, {@code -D=}, ( {@code -I} use includePaths parm instead) * * @param existingDTMgr datatypes will be populated into this provided DTMgr, can pass Program or File DTMgr * - * @param languageId language identication to use for data type organization definitions (int, long, ptr size) + * @param languageId language identification to use for data type organization definitions (int, long, ptr size) * @param compileSpecId compiler specification to use for parsing * * @param monitor used to cancel or provide results @@ -508,8 +510,8 @@ public class CParserUtils { * * @param filenames names of files in order to parse, could include strings with * "#" at start, which are ignored as comments - * @param includePaths paths to include files, instead of using "-I" in args - * @param args arguments for parsing, "-D=", ( "-I" use includePaths parm instead) + * @param includePaths paths to include files, instead of using {@code -I} in args + * @param args arguments for parsing, {@code -D=}, ( {@code -I} use includePaths parm instead) * * @param dtMgr datatypes will be populated into this provided DTMgr, can pass Program or File DTMgr * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/BoundedBufferedReader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/BoundedBufferedReader.java index 52cf576279..8b313a929a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/BoundedBufferedReader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/BoundedBufferedReader.java @@ -4,9 +4,9 @@ * 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. @@ -200,13 +200,13 @@ public class BoundedBufferedReader extends Reader { * true: *
      * - *
    • The specified number of characters have been read, + *
    • The specified number of characters have been read,
    • * *
    • The read method of the underlying stream returns - * -1, indicating end-of-file, or + * -1, indicating end-of-file, or
    • * *
    • The ready method of the underlying stream returns - * false, indicating that further input requests would block. + * false, indicating that further input requests would block.
    • * *
    * If the first read on the underlying stream returns diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java index 7c561acff8..f0d9fbc18d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java @@ -4,9 +4,9 @@ * 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. @@ -39,13 +39,13 @@ import ghidra.util.SystemUtilities; *

    * Examples (pipes shown in red since they are hard to see): *

      - *
    • file://dir/subdir -- simplest example, locates a file on local computer filesystem. - *
    • file://dir/subdir/example.zip|zip://readme.txt -- points to a file named "readme.txt" in a zip file. - *
    • file://dir/subdir/example.zip|zip://dir/nested.tar|tar://file.txt -- points to - * a file inside a TAR archive, which is inside a ZIP archive, which is on the local filesystem. - *
    • file://dir/subdir/example.zip?MD5=1234567|zip://readme.txt?MD5=987654 -- + *
    • file://dir/subdir -- simplest example, locates a file on local computer filesystem.
    • + *
    • file://dir/subdir/example.zip|zip://readme.txt -- points to a file named "readme.txt" in a zip file.
    • + *
    • file://dir/subdir/example.zip|zip://dir/nested.tar|tar://file.txt -- points to + * a file inside a TAR archive, which is inside a ZIP archive, which is on the local filesystem.
    • + *
    • file://dir/subdir/example.zip?MD5=1234567|zip://readme.txt?MD5=987654 -- * points to a file named "readme.txt" (with a MD5 hash) in a zip file (that has another - * MD5 hash). + * MD5 hash).
    • *
    *

    * See {@link FSRLRoot} for examples of how FSRL and FSRLRoot's are related. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRLRoot.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRLRoot.java index 1c8a6d4f9d..0c604d53be 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRLRoot.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRLRoot.java @@ -4,9 +4,9 @@ * 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. @@ -26,11 +26,11 @@ package ghidra.formats.gfilesystem; *

    *

      *
    • FSRLRoot [ file:// ]
      - * "file://" + * "file://"
    • *
    • {@literal FSRLRoot [ file:// ] <---- FSRL [ /filename.txt ]}
      - * "file:///filename.txt" + * "file:///filename.txt"
    • *
    • {@literal FSRLRoot [ file:// ] <---- FSRL [ /filename.txt ] <--- FSRLRoot [ subfs:// ]}
      - * "file:///filename.txt|subfs://" + * "file:///filename.txt|subfs://"
    • *
    */ public class FSRLRoot extends FSRL { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/flatapi/FlatProgramAPI.java b/Ghidra/Features/Base/src/main/java/ghidra/program/flatapi/FlatProgramAPI.java index c26ed1b82a..98bb4ec573 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/flatapi/FlatProgramAPI.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/flatapi/FlatProgramAPI.java @@ -4,9 +4,9 @@ * 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. @@ -57,8 +57,8 @@ import ghidra.util.task.TaskMonitor; *

    * NOTE: *

      - *
    1. NO METHODS *SHOULD* EVER BE REMOVED FROM THIS CLASS. - *
    2. NO METHOD SIGNATURES *SHOULD* EVER BE CHANGED IN THIS CLASS. + *
    3. NO METHODS *SHOULD* EVER BE REMOVED FROM THIS CLASS.
    4. + *
    5. NO METHOD SIGNATURES *SHOULD* EVER BE CHANGED IN THIS CLASS.
    6. *
    *

    * This class is used by GhidraScript. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/AlignRule.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/AlignRule.java index b6606476f8..3a5bc5a977 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/AlignRule.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/AlignRule.java @@ -4,9 +4,9 @@ * 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. @@ -22,12 +22,12 @@ import ghidra.xml.XmlPullParser; /** * ByteSearch post search rule when a pattern is found. Used when a pattern must have a certain * alignment at an offset from the location the pattern matches. - * + *

    * The pattern can be constructed or restored from XML of the form, * where alignOffset=mark, alignmask=bits - * - * - * + *

    {@code
    + *   
    + * }
    */ public class AlignRule implements PostRule { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/Pattern.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/Pattern.java index 7d65fa7afc..03893ebfa5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/Pattern.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/Pattern.java @@ -4,9 +4,9 @@ * 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. @@ -185,7 +185,7 @@ public class Pattern extends DittedBitSequence { } /** - * Read just the post patterns from the tags + * Read just the post patterns from the {@code } tags * @param file is the file to read from * @param patternList collects the resulting Pattern objects * @param pfactory is the factory for constructing postrules and matchactions diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/PatternPairSet.java b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/PatternPairSet.java index bd6fe348c5..a1cd57da6d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/PatternPairSet.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/bytesearch/PatternPairSet.java @@ -4,9 +4,9 @@ * 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. @@ -27,28 +27,31 @@ import ghidra.xml.XmlPullParser; * To match, a sequence from the "pre" sequence set must first match, then one of the "post" patterns * is matched relative to the matching "pre" pattern. This class is really a storage object for the * patterns and provides a mechanism to read the pre/post patterns from an XML file. - * + *

    * The larger pattern has the idea of bits of check, which means the number of bits that are fixed to * a value when matching (not don't care). There is a pre pattern bits of check and post pattern bits * of check. The bits of check are used to statistically gauge the accuracy of the pattern. - * + *

    * An example of the XML format follows: - * - * - * 0xe12fff1. - * 0xe12fff1e 0x46c0 - * 0xe12fff1e 0xe1a00000 - * + *

     {@code
    + * 
    + *   
    + *     0xe12fff1.                  
    + *     0xe12fff1e 0x46c0           
    + *     0xe12fff1e 0xe1a00000       
    + *   
      *    
    - *  
    - *       0xe24dd...                              11101001 00101101 .1...... ....0000  
    - *       11101001 00101101 .1...... ....0000     0xe24dd...                           
    - *       11101001 00101101 .1...... ....0000     0x........ 0xe24dd...                
    - *      
    - *      
    - *      
    - *    
    - *  
    + *   
    + *      0xe24dd...                              11101001 00101101 .1...... ....0000  
    + *      11101001 00101101 .1...... ....0000     0xe24dd...                           
    + *      11101001 00101101 .1...... ....0000     0x........ 0xe24dd...                
    + *     
    + *     
    + *     
    + *   
    + * 
    + * }
    + * 
    * * Note: The post Patterns can also have a set of rules that must be satisfied along with one of the * Pattern DittedBitSequence matches. diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java index 10ca35da40..4ecaf2e2b1 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java @@ -4,9 +4,9 @@ * 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. @@ -462,18 +462,18 @@ public class DecompInterface { * The current predefined analysis class are: *
      *
    • "decompile" - this is the default, and performs all - * analysis steps suitable for producing C code. + * analysis steps suitable for producing C code.
    • *
    • "normalize" - omits type recovery from the analysis * and some of the final clean-up steps involved in * making valid C code. It is suitable for creating - * normalized pcode syntax trees of the dataflow. + * normalized pcode syntax trees of the dataflow.
    • *
    • "firstpass" - does no analysis, but produces an - * unmodified syntax tree of the dataflow from the - *
    • "register" - does ???. + * unmodified syntax tree of the dataflow from the
    • + *
    • "register" - does ???.
    • *
    • "paramid" - does required amount of decompilation * followed by analysis steps that send parameter * measure information for parameter id analysis. - * raw pcode. + * raw pcode.
    • *
    * *

    diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/util/DecompilerConcurrentQ.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/util/DecompilerConcurrentQ.java index 3cca028fe6..df62938fec 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/util/DecompilerConcurrentQ.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/util/DecompilerConcurrentQ.java @@ -4,9 +4,9 @@ * 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. @@ -37,7 +37,7 @@ import utility.function.Dummy; * {@link #waitForResults()}. *

  • For non-blocking usage, simply call * {@link #process(Iterator, Consumer)}, passing the consumer of the results.
  • - * + * *

    * * @param The input data needed by the supplied {@link QCallback} diff --git a/Ghidra/Framework/DB/src/main/java/db/VarRecNode.java b/Ghidra/Framework/DB/src/main/java/db/VarRecNode.java index 9ed17d7ecc..e6180054bd 100644 --- a/Ghidra/Framework/DB/src/main/java/db/VarRecNode.java +++ b/Ghidra/Framework/DB/src/main/java/db/VarRecNode.java @@ -4,9 +4,9 @@ * 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. @@ -27,11 +27,11 @@ import ghidra.util.exception.AssertException; *

    * This type of node has the following layout within a single DataBuffer * (field size in bytes): - *

    + * 
    {@code
      *   | NodeType(1) | KeyCount(4) | PrevLeafId(4) | NextLeafId(4) | Key0(8) | RecOffset0(4) | IndFlag0(1) |...  
      *     
      *   | KeyN(8) | RecOffsetN(4) | IndFlagN(1) |...... | RecN |... | Rec0 |
    - * 
    + * }
    * IndFlag - if not zero the record has been stored within a chained DBBuffer * whose 4-byte integer buffer ID has been stored within this leaf at the record offset. */ diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java index 2fe3ef2d13..46d15f32aa 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java @@ -49,17 +49,17 @@ import utilities.util.reflection.ReflectionUtilities; *

    * This also provides several useful convenience methods: *

      - *
    • {@link #addLocalAction(DockingActionIf)} - *
    • {@link #addToTool()} - *
    • {@link #setVisible(boolean)} - *
    • {@link #setTitle(String)} - *
    • {@link #setIcon(Icon)} + *
    • {@link #addLocalAction(DockingActionIf)}
    • + *
    • {@link #addToTool()}
    • + *
    • {@link #setVisible(boolean)}
    • + *
    • {@link #setTitle(String)}
    • + *
    • {@link #setIcon(Icon)}
    • *
    *

    * There are a handful of stub methods that can be overridden as desired: *

      - *
    • {@link #componentActivated()} and {@link #componentDeactived()} - *
    • {@link #componentHidden()} and {@link #componentShown()} + *
    • {@link #componentActivated()} and {@link #componentDeactived()}
    • + *
    • {@link #componentHidden()} and {@link #componentShown()}
    • *
    * *

    diff --git a/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java b/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java index a660115b52..bb2d4c9ec3 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java @@ -4,9 +4,9 @@ * 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. @@ -86,7 +86,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher * * Ghidra has altered this flow to be: *

      - *
    1. Reserved keybinding actions + *
    2. Reserved keybinding actions
    3. *
    4. KeyListeners on the focused Component
    5. *
    6. InputMap and ActionMap actions for the Component
    7. *
    8. Ghidra tool-level actions
    9. diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PlaceholderSet.java b/Ghidra/Framework/Docking/src/main/java/docking/PlaceholderSet.java index 87c776ee48..30041c666e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/PlaceholderSet.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/PlaceholderSet.java @@ -4,9 +4,9 @@ * 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. @@ -21,8 +21,8 @@ import java.util.Map.Entry; /** * A class that tracks: *
        - *
      • placeholders that are being used for a given provider - *
      • placeholders that are no longer being used, which are available for reuse + *
      • placeholders that are being used for a given provider
      • + *
      • placeholders that are no longer being used, which are available for reuse
      • *
      */ class PlaceholderSet { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/dnd/Draggable.java b/Ghidra/Framework/Docking/src/main/java/docking/dnd/Draggable.java index f41c83bb3d..7244895ed4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/dnd/Draggable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/dnd/Draggable.java @@ -4,9 +4,9 @@ * 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. @@ -51,9 +51,9 @@ public interface Draggable { /** * Get the drag actions supported by this drag source: *
        - *
      • DnDConstants.ACTION_MOVE - *
      • DnDConstants.ACTION_COPY - *
      • DnDConstants.ACTION_COPY_OR_MOVE + *
      • DnDConstants.ACTION_MOVE
      • + *
      • DnDConstants.ACTION_COPY
      • + *
      • DnDConstants.ACTION_COPY_OR_MOVE
      • *
      * * @return the drag actions diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/OptionDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/OptionDialog.java index 6146a73b2c..e549fb0767 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/OptionDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/OptionDialog.java @@ -4,9 +4,9 @@ * 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. @@ -34,9 +34,7 @@ import ghidra.util.exception.AssertException; * A utility class to easily show dialogs that require input from the user. * * - *

      Option Dialogs


      - *
      - *

      + *

      Option Dialogs

      * The primary type of * dialog provided herein is the basic option dialog that allows the user to specify the buttons * that appear on the dialog. By default, the given option text will appear as a button(s), @@ -44,55 +42,37 @@ import ghidra.util.exception.AssertException; * {@link #showOptionNoCancelDialog(Component, String, String, String, String, int)} methods if * you do not want a Cancel button. To use this type of dialog you can use the * various showOptionDialog* methods. - *

      *

      * Each of the option dialog methods will return a result, which is a number indicating the * choice made by the user. See each method for more details. - *

      - *
      * * - *

      Data Input and Choice Dialogs


      - *
      - *

      - * The methods listed here allow the user to either enter data from the keyboard or to choose - * from a pre-populated list of data. - *

      - *
      - * {@link #showInputChoiceDialog(Component, String, String, String[], String, int)}
      - * {@link #showInputMultilineDialog(Component, String, String, String)}
      - * {@link #showInputSingleLineDialog(Component, String, String, String)} - *
      - *
      + *

      Data Input and Choice Dialogs

      + * The methods listed here allow the user to either enter data from the keyboard or to choose + * from a pre-populated list of data. + *
        + *
      • {@link #showInputChoiceDialog(Component, String, String, String[], String, int)}
      • + *
      • {@link #showInputMultilineDialog(Component, String, String, String)}
      • + *
      • {@link #showInputSingleLineDialog(Component, String, String, String)}
      • + *
      * * - *

      Yes/No Dialogs


      - *
      - *

      + *

      Yes/No Dialogs

      * Finally, there are a series of methods that present Yes and No buttons in * a dialog. There are versions that do and do not have a Cancel button. - *

      - *
      * * - *

      Basic Message / Warning / Error Dialogs


      - *
      - *

      + *

      Basic Message / Warning / Error Dialogs

      * If you would like to display a simple message to the user, but do not require input from the * user, then you should use the various methods of {@link Msg}, such as * {@link Msg#showInfo(Object, Component, String, Object)}. - *

      *

      * Note, the user will be unable to select any text shown in the message area of the dialog. - *

      - *
      * - *

      "Apply to All" / "Don't Show Again"


      - *
      - *

      For more advanced input dialog usage, to include allowing the user to tell the dialog + *

      "Apply to All" / "Don't Show Again"

      + * For more advanced input dialog usage, to include allowing the user to tell the dialog * to remember a particular decision, or to apply a given choice to all future request, see * {@link OptionDialogBuilder}. - *
      * * @see Msg * @see OptionDialogBuilder diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java index 263641006e..f4a67e99d0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java @@ -4,9 +4,9 @@ * 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. @@ -77,7 +77,7 @@ import ghidra.util.task.SwingUpdateManager; * The simplest use case is to create a text field, create an autocompleter with a custom model, and * then attach and show. * - *
      + * 
      {@code
        * JTextField field = new JTextField();
        *
        * AutocompletionModel model = new AutocompletionModel() {
      @@ -90,7 +90,7 @@ import ghidra.util.task.SwingUpdateManager;
        * completer.attachTo(field);
        * // ... Add the field to, e.g., a dialog, and show.
        *
      - * 
      + * }
      * * @param the type of suggestions presented by this autocompleter. */ diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java index 8096629abe..01727533c2 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java @@ -626,13 +626,13 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement * Sets the GhidraFileChooser to allow the user to just * select files, just select * directories, or select both files and directories. The default is - * JFilesChooser.FILES_ONLY. + * {@link JFileChooser#FILES_ONLY}. * * @param mode the type of files to be displayed: *
        - *
      • GhidraFileChooser.FILES_ONLY - *
      • GhidraFileChooser.DIRECTORIES_ONLY - *
      • GhidraFileChooser.FILES_AND_DIRECTORIES + *
      • {@link GhidraFileChooser#FILES_ONLY}
      • + *
      • {@link GhidraFileChooser#DIRECTORIES_ONLY}
      • + *
      • {@link GhidraFileChooser#FILES_AND_DIRECTORIES}
      • *
      * * @exception IllegalArgumentException if mode is an @@ -1007,8 +1007,8 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement *
        *
      • If the parent directory of the file exists, then the parent directory will be made * the current directory and the name of the file will be put into the filename - * textfield; otherwise, - *
      • If the parent file does not exist, then the selection is cleared. + * textfield; otherwise,
      • + *
      • If the parent file does not exist, then the selection is cleared.
      • *
      *

      * If the given file is null, then the selected file state is cleared. diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/searchlist/DefaultSearchListModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/searchlist/DefaultSearchListModel.java index bdd8ac2d2a..d38d9b1381 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/searchlist/DefaultSearchListModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/searchlist/DefaultSearchListModel.java @@ -4,9 +4,9 @@ * 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. @@ -26,8 +26,8 @@ import utility.function.Dummy; /** * Default implementation of the {@link SearchListModel}. Since this model's primary purpose is * to also implement the {@link ListModel}, this class extends the AbstractListModel. - * This model's primary type is T, but it implements the list model on SearchListEntry to provide - * more information for the custom rendering that groups items into categories. + * This model's primary type is T, but it implements the list model on {@code SearchListEntry} + * to provide more information for the custom rendering that groups items into categories. * * @param The type of items to display and select. */ diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java index e2f5e3bbed..e2eb1c8eb6 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java @@ -4,9 +4,9 @@ * 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. @@ -65,8 +65,8 @@ import utility.function.Callback; * * Important Usage Notes *

        - *
      • You must translate row values retrieved from the table using - * this panel. + *
      • You must translate row values retrieved from the table using + * this panel.
      • *

        * Since this class wraps the given table with a new model, you must use this class to * translate row number values. For example, when getting the selected row, the normal Java @@ -84,14 +84,14 @@ import utility.function.Callback; * * * - *

      • This class may set a new model on the given table, which can affect how tables are sized. + *
      • This class may set a new model on the given table, which can affect how tables are sized.
      • *

        * If {@link JTable#getAutoCreateColumnsFromModel()} returns true, then the columns will * be recreated and resized when this class is constructed. *

      • The {@link TableFilter} used by this class will be passed the empty string ("") when - * {@link TableFilter#acceptsRow(Object)} is called. + * {@link TableFilter#acceptsRow(Object)} is called.
      • *
      • You cannot rely on {@link JTable#getRowCount()} to access all of the table data, - * since the data may be filtered. + * since the data may be filtered.
      • *

        * To get a row count that is always all of the model's data, call * {@link #getUnfilteredRowCount()}. diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/LocalDateSpinnerModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/LocalDateSpinnerModel.java index b0f4ee2f79..41bfd6a670 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/LocalDateSpinnerModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/LocalDateSpinnerModel.java @@ -4,9 +4,9 @@ * 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. @@ -38,9 +38,9 @@ public class LocalDateSpinnerModel extends AbstractSpinnerModel { * @param maxDate maximum value for spinner model. (Can be null) * @param calendarField specifies the year, month, or day to increment/decrement. One of: *

          - *
        • Calendar.YEAR - *
        • Calendar.MONTH - *
        • Calendar.DAY_OF_MONTH + *
        • {@link Calendar#YEAR}
        • + *
        • {@link Calendar#MONTH}
        • + *
        • {@link Calendar#DAY_OF_MONTH}
        • *
        *

        */ @@ -128,9 +128,9 @@ public class LocalDateSpinnerModel extends AbstractSpinnerModel { * * @param calendarField one of *

          - *
        • Calendar.YEAR - *
        • Calendar.MONTH - *
        • Calendar.DAY_OF_MONTH + *
        • {@link Calendar#YEAR}
        • + *
        • {@link Calendar#MONTH}
        • + *
        • {@link Calendar#DAY_OF_MONTH}
        • *
        *

        * diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java index ba4e1cfe99..53ba881ce4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java @@ -191,18 +191,18 @@ public class TableUpdateJob { * effect depends on the running job's state: *

          *
        • If the sort state hasn't happened yet, all it does is set the comparator for when - * the sort occurs. + * the sort occurs.
        • *
        • If the sort state has already been started or completed, then this method attempts * to stop the current process phase and cause the state machine to return to the sort - * phase. + * phase.
        • *
        • If the current job has already entered the DONE state, then the sort cannot take * effect in this job and a false value is returned to indicate the - * sort was not handled by this job. + * sort was not handled by this job.
        • *
        * @param newSortingContext the TableColumnComparator to use to sort the data. * @param forceSort True signals to re-sort, even if this is already sorted * @return true if the sort can be processed by this job, false if this job is essentially - * already completed and therefor cannot perform the sort job. + * already completed and therefore cannot perform the sort job. */ public synchronized boolean requestSort(TableSortingContext newSortingContext, boolean forceSort) { @@ -225,16 +225,16 @@ public class TableUpdateJob { * depends on the running job's state: *
          *
        • If the filter state hasn't happened yet, then nothing needs to be done as this job - * will filter later anyway. + * will filter later anyway.
        • *
        • If the filter state has already been started or completed, then this method * attempts to stop the current process phase and cause the state machine to return to - * the filter phase. + * the filter phase.
        • *
        • If the current job has already entered the DONE state, then the filter cannot take * effect in this job and a false value is returned to indicate the filter was not - * handled by this job. + * handled by this job.
        • *
        * @return true if the filter can be processed by this job, false if this job is essentially - * already completed and therefor cannot perform the filter job. + * already completed and therefore cannot perform the filter job. */ public synchronized boolean requestFilter() { if (currentState == DONE) { @@ -718,7 +718,7 @@ public class TableUpdateJob { } /** - * Wraps a comparator to add progress monitoring and cancel checking + * Wraps a {@link Comparator} to add progress monitoring and cancel checking * * @param The type of data being sorted */ diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BasicTaskMonitor.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BasicTaskMonitor.java index 53d13b9450..dc72a9cb08 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BasicTaskMonitor.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BasicTaskMonitor.java @@ -4,9 +4,9 @@ * 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. @@ -30,7 +30,7 @@ import ghidra.util.exception.CancelledException; * the Java concurrent structures (e.g., {@link AtomicBoolean}). In order to keep the values of * this class's fields update-to-date, we have chosen to synchronize the package-level client of * this class. If this class is ever made public, then most of the methods herein need to - * be synchronized to prevent race conditions and to provide visibility. + * be synchronized to prevent race conditions and to provide visibility. */ class BasicTaskMonitor implements TaskMonitor { diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java index 8f1b49aaf4..210933b37a 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java @@ -4,9 +4,9 @@ * 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. @@ -26,7 +26,7 @@ import java.awt.Component; * {@link #TaskLauncher(Task, Component, int, int)}. Alternatively, for simpler uses, * see one of the many static convenience methods. * - *

        Modal Usage
        + *

        Modal Usage

        * Most clients of this class should not be concerned with where * the dialog used by this class will appear. By default, it will be shown over * the active window, which is the desired @@ -63,13 +63,12 @@ public class TaskLauncher { /** * A convenience method to directly run a {@link MonitoredRunnable} in a separate * thread as a {@link Task}, displaying a non-modal progress dialog. - *

        - * - * TaskLauncher.launchNonModal( "My task",
        - *   null, // parent
        - *   monitor -> { while ( !monitor.isCanceled() ) { longRunningWork(); } }
        + *

        {@code
        +	 * TaskLauncher.launchNonModal("My task",
        +	 *   null, // parent
        +	 *   monitor -> { while (!monitor.isCanceled()) { longRunningWork(); } }
         	 * );
        -	 * 
        +	 * }
        * *

        Note: the task created by this call will be both cancellable and have progress. If * you task cannot be cancelled or does not have progress, then do not use this @@ -93,13 +92,12 @@ public class TaskLauncher { /** * A convenience method to directly run a {@link MonitoredRunnable} in a separate * thread as a {@link Task}, displaying a modal progress dialog. - *

        - * - * TaskLauncher.launchModal( "My task",
        - *   null, // parent
        - *   monitor -> { while ( !monitor.isCanceled() ) { longRunningWork(); } }
        + *

        {@code
        +	 * TaskLauncher.launchModal("My task",
        +	 *    null, // parent
        +	 *    monitor -> { while (!monitor.isCanceled()) { longRunningWork(); } }
         	 * );
        -	 * 
        +	 * }
        * *

        Note: the task created by this call will be both cancellable and have progress. If * you task cannot be cancelled or does not have progress, then do not use this @@ -128,11 +126,11 @@ public class TaskLauncher { * to have it immediately go away. If you desire this default behavior, then do not use * this convenience method. * - *

        - * TaskLauncher.launchModal( "My task",
        - *   monitor -> { { foo(); }
        + *

        {@code
        +	 * TaskLauncher.launchModal("My task",
        +	 *   monitor -> { foo(); }
         	 * );
        -	 * 
        +	 * }
        * *

        Note: the task created by this call will not be cancellable nor have progress. If * you need either of these behaviors, the do not use this diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java index b9cba81972..91daf85621 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java @@ -4,9 +4,9 @@ * 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. @@ -40,7 +40,7 @@ public interface PcodeUseropLibrary { /** * The class of the empty userop library. * - * @see {@link PcodeUseropLibrary#nil()} + * @see PcodeUseropLibrary#nil() */ final class EmptyPcodeUseropLibrary implements PcodeUseropLibrary { @Override diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/CountLatch.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/CountLatch.java index 89b60bc34b..9a38981200 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/CountLatch.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/CountLatch.java @@ -1,13 +1,12 @@ /* ### * 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. * 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. @@ -105,15 +104,15 @@ public class CountLatch { * dormant until one of two things happen: *
          *
        • The count reaches zero due to invocations of the - * {@link #decrement} method; or + * {@link #decrement} method; or
        • *
        • Some other thread {@linkplain Thread#interrupt interrupts} - * the current thread. + * the current thread.
        • *
        * *

        If the current thread: *

          - *
        • has its interrupted status set on entry to this method; or - *
        • is {@linkplain Thread#interrupt interrupted} while waiting, + *
        • has its interrupted status set on entry to this method; or
        • + *
        • is {@linkplain Thread#interrupt interrupted} while waiting,
        • *
        * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. @@ -138,10 +137,10 @@ public class CountLatch { * dormant until one of three things happen: *
          *
        • The count reaches zero due to invocations of the - * {@link #decrement} method; or + * {@link #decrement} method; or
        • *
        • Some other thread {@linkplain Thread#interrupt interrupts} - * the current thread; or - *
        • The specified waiting time elapses. + * the current thread; or
        • + *
        • The specified waiting time elapses.
        • *
        * *

        If the count reaches zero then the method returns with the @@ -149,8 +148,8 @@ public class CountLatch { * *

        If the current thread: *

          - *
        • has its interrupted status set on entry to this method; or - *
        • is {@linkplain Thread#interrupt interrupted} while waiting, + *
        • has its interrupted status set on entry to this method; or
        • + *
        • is {@linkplain Thread#interrupt interrupted} while waiting,
        • *
        * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/GraphAlgorithms.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/GraphAlgorithms.java index 4a58588a9b..b8061dd9e2 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/GraphAlgorithms.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/GraphAlgorithms.java @@ -4,9 +4,9 @@ * 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. @@ -38,25 +38,30 @@ import util.CollectionUtils; * a node 'a' dominates node 'b' if all paths from start to 'b' contain 'a'; * a node always dominates itself (except in 'strict dominance', which is all * dominators except for itself) + * * *
      • * post-dominance: * A node 'b' is said to post-dominate node 'a' if all paths from 'a' * to END contain 'b' + *
      • * *
      • * immediate dominator: * the closest dominator of a node + *
      • * *
      • * dominance tree: * A dominator tree is a tree where each node's children are those nodes * it *immediately* dominates (a idom b) + *
      • * *
      • * dominance frontier: * the immediate successors of the nodes dominated by 'a'; it is the set of * nodes where d's dominance stops. + *
      • * *
      • * strongly connected components: @@ -64,8 +69,11 @@ import util.CollectionUtils; * from every other vertex. The strongly connected components * of an arbitrary directed graph form a partition into * subgraphs that are themselves strongly connected. + *
      • + * *
      • * graph density: + *
      • *
          *                        E
          *          Density =  --------
        diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java
        index ae0dd17646..17ea317fe6 100644
        --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java
        +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java
        @@ -4,9 +4,9 @@
          * 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.
        @@ -48,11 +48,11 @@ import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
          * 
          * 
          *
        • Layout Space - the layout contains Point2D objects that represent positions of the - * vertices. + * vertices.
        • *
        • Graph Space - the space where the Layout points are transformed as the view is moved - * around the screen (e.g., as the user pans) + * around the screen (e.g., as the user pans)
        • *
        • View Space - the coordinate system of Java 2D rendering; scaling (zooming) transformations - * are applied at this layer + * are applied at this layer
        • *
        * *

        Note: vertex relative means that the value is from inside the vertex, or the vertex's diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighter.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighter.java index c96435223d..8d963a27bb 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighter.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighter.java @@ -4,9 +4,9 @@ * 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. @@ -49,7 +49,7 @@ import utility.function.Callback; *

      • Results must be cached for speed, but may cleared as the graph is mutated
      • *
      • Algorithms must not block the UI thread
      • *
      • Related actions (i.e., hover vs. selection) should cancel any pending action, but not - * unrelated actions (e.g., a new hover request should cancel a pending hover update) + * unrelated actions (e.g., a new hover request should cancel a pending hover update)
      • * * * Based on these requirements, we need to use multi-threading. Further complicating the need diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/SystemThemeIds.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/SystemThemeIds.java index 08851486ce..4b2eb71eca 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/SystemThemeIds.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/SystemThemeIds.java @@ -4,9 +4,9 @@ * 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. @@ -35,18 +35,18 @@ import generic.theme.laf.UiDefaultsMapper; *
      • VIEW - these ids are used for the colors and fonts used for widgets that display data * such as Trees, Tables, TextFieds, and Lists
      • *
      • MENU - these ids are used by menu components such as Menus and MenuItems.
      • - *
      • TOOLTIP - these ids are used just by the tooltip component + *
      • TOOLTIP - these ids are used just by the tooltip component
      • * *

        * For each of those categories the ids specify a specific property for those components. *

          - *
        • BG - the background color - *
        • FG - the foreground color - *
        • BG_SELECTED - the background color when the component is selected - *
        • FG_SELECTED - the foreground color when the component is selected - *
        • FG_DISABLED - the foreground color when the component is disabled - *
        • BG_BORDER - the border color - *
        • FONT - the font + *
        • BG - the background color
        • + *
        • FG - the foreground color
        • + *
        • BG_SELECTED - the background color when the component is selected
        • + *
        • FG_SELECTED - the foreground color when the component is selected
        • + *
        • FG_DISABLED - the foreground color when the component is disabled
        • + *
        • BG_BORDER - the border color
        • + *
        • FONT - the font
        • *
        */ public class SystemThemeIds { diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemeManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemeManager.java index 508d76da55..243e762227 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemeManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemeManager.java @@ -41,13 +41,13 @@ import utility.function.Callback; * The basic idea is that all the colors, fonts, and icons used in an application should be * accessed indirectly via an "id" string. Then the actual color, font, or icon can be changed * without changing the source code. The default mapping of the id strings to a value is defined - * in .theme.properties files which are dynamically discovered by searching the module's + * in {@code .theme.properties} files which are dynamically discovered by searching the module's * data directory. Also, these files can optionally define a dark default value for an id which * would replace the standard default value in the event that the current theme specifies that it * is a dark theme. Themes are used to specify the application's {@link LookAndFeel}, whether or * not it is dark, and any customized values for colors, fonts, or icons. There are several * "built-in" themes, one for each supported {@link LookAndFeel}, but additional themes can - * be defined and stored in the users application home directory as a .theme file. + * be defined and stored in the users application home directory as a {@code .theme} file. *

        * Clients that just need to access the colors, fonts, and icons from the theme can use the * convenience methods in the {@link Gui} class. Clients that need to directly manipulate the diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/UiDefaultsMapper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/UiDefaultsMapper.java index 433717b7d7..17ef4d0129 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/UiDefaultsMapper.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/UiDefaultsMapper.java @@ -4,9 +4,9 @@ * 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. @@ -47,16 +47,16 @@ import ghidra.util.Msg; * Some basic concepts: *

          *
        • UI Defaults - key-value pairs defined by the Java LaF; there are 2 key types, widget - * keys and Java group/reusable keys (e.g., Button.background; control) + * keys and Java group/reusable keys (e.g., Button.background; control)
        • *
        • UI Indirection - UI Defaults values are changed to point to custom terms we created to - * allow for indirection (e.g., Button.background -> laf.color.Button.background) + * allow for indirection (e.g., Button.background -> laf.color.Button.background)
        • *
        • Normalized Keys - keys we created to facilitate the UI Indirection, based upon the Java - * keys (e.g., laf.color.Button.background) + * keys (e.g., laf.color.Button.background)
        • *
        • System Color/Font Keys - user facing terms for common color or font concepts into an - * easy-to-change setting (e.g., system.color.fg.text) + * easy-to-change setting (e.g., system.color.fg.text)
        • *
        • Palette Keys - dynamically generated color palette keys based on the LaF for any colors * and fonts that were not mapped into an system color or font (e.g., - * laf.palette.color.01) + * laf.palette.color.01)
        • *
        * *

        @@ -65,7 +65,7 @@ import ghidra.util.Msg; *

      • Extracts all color, font, and icon values from the UI Defaults.
      • *
      • Use the current LaF values to populate the pre-defined system colors and fonts.
      • *
      • Any UI Defaults values not assigned in the previous step will be assigned to a dynamic shared - * palette color or font. + * palette color or font.
      • *
      • Update Java UI Defaults to use our indirection and system values.
      • * * diff --git a/Ghidra/Framework/Help/src/main/java/help/GHelpBuilder.java b/Ghidra/Framework/Help/src/main/java/help/GHelpBuilder.java index c343bdc3bb..2d57f5f4be 100644 --- a/Ghidra/Framework/Help/src/main/java/help/GHelpBuilder.java +++ b/Ghidra/Framework/Help/src/main/java/help/GHelpBuilder.java @@ -4,9 +4,9 @@ * 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. @@ -37,9 +37,9 @@ import help.validator.location.HelpModuleCollection; * Note: Help links must not be absolute. They can be relative, including . and .. * syntax. Further, they can use the special help system syntax, which is: *
          - *
        • help/topics/topicName/Filename.html for referencing help topic files + *
        • help/topics/topicName/Filename.html for referencing help topic files
        • *
        • help/shared/image.png for referencing image files at paths rooted under - * the module's root help dir + * the module's root help dir
        • *
        */ public class GHelpBuilder { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/logviewer/ui/FileViewer.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/logviewer/ui/FileViewer.java index 53b03de770..6c5a8ed7e7 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/logviewer/ui/FileViewer.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/logviewer/ui/FileViewer.java @@ -4,9 +4,9 @@ * 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. @@ -40,17 +40,17 @@ import utilities.util.FileUtilities; *
          *
        1. The viewer consists of a simple JTable and a custom JSlider. The table displays lines of * text described by {@link Chunk} objects. The number of chunks visible at any given time - * is restricted by the {@link ChunkModel#MAX_VISIBLE_CHUNKS} property. + * is restricted by the {@link ChunkModel#MAX_VISIBLE_CHUNKS} property.
        2. * *
        3. Because only part of the file is loaded into the viewable table at any given time, the * built-in scrollbar associated with the scroll pane cannot be used. We want the scroll bar * maximum size to reflect the total size of the file, not just what's in view at the time. So * we use our own slider implementation ({@link FVSlider}) and manage the size/position * ourselves. If you're asking why a JSlider is used instead of a JScrollPane, it's because the - * former is more easily configuration for what we need. + * former is more easily configuration for what we need.
        4. * *
        5. Communication between modules (the table, the slider, the viewport utility, etc...) is done - * almost exclusively via events, using the custom {@link FVEvent} framework. + * almost exclusively via events, using the custom {@link FVEvent} framework.
        6. *
        */ public class FileViewer extends JPanel implements Observer { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java index 3efd06adc5..955b0eeff5 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java @@ -4,9 +4,9 @@ * 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. @@ -40,15 +40,17 @@ import ghidra.util.classfinder.ExtensionPoint; *

        *

        Well formed Plugins:

        *
          - *
        • Derive from Plugin (directly or indirectly). - *
        • Class name ends with "Plugin" and does not match any other Plugin, regardless of - * its location in the package tree. - *
        • Have a {@link PluginInfo @PluginInfo()} annotation. - *
        • Have a constructor with exactly 1 parameter: PluginTool. - *
            - *
          • public MyPlugin(PluginTool tool) { ... } - *
          - *
        • Usually overrides protected void init(). + *
        • Derive from Plugin (directly or indirectly).
        • + *
        • Class name ends with "Plugin" and does not match any other Plugin, regardless of its + * location in the package tree.
        • + *
        • Have a {@link PluginInfo @PluginInfo()} annotation.
        • + *
        • Have a constructor with exactly 1 parameter: PluginTool.
        • + *
        • + *
            + *
          • public MyPlugin(PluginTool tool) { ... }
          • + *
          + *
        • + *
        • Usually overrides protected void init().
        • *
        *

        Class naming

        * All Plugin Classes MUST END IN "Plugin". If not, the ClassSearcher will not find them. @@ -58,50 +60,60 @@ import ghidra.util.classfinder.ExtensionPoint; * *

        Plugin Life cycle

        *
          - *
        1. Your Plugin's constructor is called - *
            - *
          1. Plugin base class constructor is called. - *
              - *
            1. Services listed in the @PluginInfo annotation are automatically added - * to dependency list - *
            - *
          2. Your Plugin publishes any services listed in PluginInfo using - * {@link Plugin#registerServiceProvided(Class, Object) registerServiceProvided()}. - * (required) - *
          3. Create Actions (optional) - *
          4. Register {@link ghidra.framework.options.Options Options} with the - * {@link PluginTool#getOptions(String)}. (optional)
            - *
          - *
        2. Other Plugins are constructed, dependencies evaluated, etc.
          + *
        3. Your Plugin's constructor is called
        4. + *
        5. + *
            + *
          1. Plugin base class constructor is called.
          2. + *
          3. + *
              + *
            1. Services listed in the @PluginInfo annotation are automatically added to dependency + * list
            2. + *
            + *
          4. + *
          5. Your Plugin publishes any services listed in PluginInfo using + * {@link Plugin#registerServiceProvided(Class, Object) registerServiceProvided()}. + * (required)
          6. + *
          7. Create Actions (optional)
          8. + *
          9. Register {@link ghidra.framework.options.Options Options} with the + * {@link PluginTool#getOptions(String)}. (optional)

          10. + *
          + *
        6. + *
        7. Other Plugins are constructed, dependencies evaluated, etc.
          * If your dependencies are not available (i.e., not installed, threw an exception during their * initialization, etc), your Plugin's {@link #dispose()} will be called and then your Plugin - * instance will be discarded.
          - *
        8. Your Plugin's {@link #init()} method is called (when its dependencies are met). - *
            - *
          1. Call {@link PluginTool#getService(Class)} to get service - * implementations. (the service class being requested should already be - * listed in the @PluginInfo) - *
          2. Create Actions (optional) - *
          3. Other initialization stuff. - *
          - *
        9. Your Plugin's {@link #readConfigState(SaveState)} is called. - *
        10. ...user uses Ghidra... - *
            - *
          • Your Plugin's {@link #processEvent(PluginEvent)} is called for events. - *
          • Your Plugin's Action's methods (i.e., - * {@link DockingAction#actionPerformed(docking.ActionContext) actionPerformed}) are - * called. - *
          • Your Plugin's published service methods are called by other Plugins. - *
          • Your Plugin's listener methods are called. - *
          - *
        11. Plugin is unloaded due to shutdown of the Tool or being disabled by user - *
            - *
          1. Your Plugin's {@link #writeConfigState(SaveState)} is called - override this - * method to write configuration info into the Tool definition. - *
          2. Your Plugin's {@link #dispose()} is called - override this method to free - * any resources and perform any needed cleanup. - *
          3. Your Plugin's services and events are de-registered automatically. - *
          + * instance will be discarded.

        12. + *
        13. Your Plugin's {@link #init()} method is called (when its dependencies are met).
        14. + *
        15. + *
            + *
          1. Call {@link PluginTool#getService(Class)} to get service + * implementations. (the service class being requested should already be + * listed in the @PluginInfo)
          2. + *
          3. Create Actions (optional)
          4. + *
          5. Other initialization stuff.
          6. + *
          + *
        16. + *
        17. Your Plugin's {@link #readConfigState(SaveState)} is called.
        18. + *
        19. ...user uses Ghidra...
        20. + *
        21. + *
            + *
          • Your Plugin's {@link #processEvent(PluginEvent)} is called for events.
          • + *
          • Your Plugin's Action's methods (i.e., + * {@link DockingAction#actionPerformed(docking.ActionContext) actionPerformed}) are + * called.
          • + *
          • Your Plugin's published service methods are called by other Plugins.
          • + *
          • Your Plugin's listener methods are called.
          • + *
          + *
        22. + *
        23. Plugin is unloaded due to shutdown of the Tool or being disabled by user
        24. + *
        25. + *
            + *
          1. Your Plugin's {@link #writeConfigState(SaveState)} is called - override this + * method to write configuration info into the Tool definition.
          2. + *
          3. Your Plugin's {@link #dispose()} is called - override this method to free + * any resources and perform any needed cleanup.
          4. + *
          5. Your Plugin's services and events are de-registered automatically.
          6. + *
          + *
        26. *
        * *

        Plugin Service dependency

        @@ -164,26 +176,26 @@ import ghidra.util.classfinder.ExtensionPoint; *

        Plugin Events

        *
          *
        • Every type of plugin event should be represented by some class extending - * {@link PluginEvent}. + * {@link PluginEvent}.
        • *
        • One PluginEvent subclass may be used for more than one event type as long as there's some - * natural grouping. + * natural grouping.
        • *
        * *

        Component Providers

        *
          *
        • A plugin may supply a {@link ComponentProvider} that provides a visual component when - * the plugin is added to the tool. + * the plugin is added to the tool.
        • *
        * *

        Important interfaces Plugins often need to implement

        *
          *
        • {@link OptionsChangeListener} - to receive notification when a configuration option - * is changed by the user. + * is changed by the user.
        • *
        • {@link ApplicationLevelPlugin} - marks this Plugin as being suitable for inclusion in the - * application-level tool. + * application-level tool.
        • *
        • {@link ApplicationLevelOnlyPlugin} - marks this Plugin as application-level only, not - * usable in an application's sub-tools. - *
        • {@link ProgramaticUseOnly} - marks this Plugin as special and not for user configuration. + * usable in an application's sub-tools.
        • + *
        • {@link ProgramaticUseOnly} - marks this Plugin as special and not for user configuration.
        • *
        * */ diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java index 4a0f47d91e..5dd633d397 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java @@ -4,9 +4,9 @@ * 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. @@ -1197,13 +1197,13 @@ public abstract class PluginTool extends AbstractDockingTool { * Closes this tool, possibly with input from the user. The following conditions are checked * and can prompt the user for more info and allow them to cancel the close. *
          - *
        1. Running tasks. Closing with running tasks could lead to data loss. + *
        2. Running tasks. Closing with running tasks could lead to data loss.
        3. *
        4. Plugins get asked if they can be closed. They may prompt the user to resolve - * some plugin specific state. - *
        5. The user is prompted to save any data changes. + * some plugin specific state.
        6. + *
        7. The user is prompted to save any data changes.
        8. *
        9. Tools are saved, possibly asking the user to resolve any conflicts caused by - * changing multiple instances of the same tool in different ways. - *
        10. If all the above conditions passed, the tool is closed and disposed. + * changing multiple instances of the same tool in different ways.
        11. + *
        12. If all the above conditions passed, the tool is closed and disposed.
        13. *
        */ @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssemblerBuilder.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssemblerBuilder.java index ccf42cbb41..3ca1b5e3bc 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssemblerBuilder.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssemblerBuilder.java @@ -4,9 +4,9 @@ * 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. @@ -187,8 +187,8 @@ import ghidra.program.model.listing.Program; * *
          *
        1. Resolve operands from right to left, descending into sub-table operands.
        2. - *
        3. Solve context mutations, in reverse order. - *
        4. Apply the required patterns + *
        5. Solve context mutations, in reverse order.
        6. + *
        7. Apply the required patterns
        8. *
        * *

        diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/ParserWalker.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/ParserWalker.java index 0f2cc7d104..358c4c2aac 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/ParserWalker.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/ParserWalker.java @@ -4,9 +4,9 @@ * 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. @@ -26,9 +26,9 @@ import ghidra.util.exception.AssertException; * Class for walking the Sleigh Parser tree. The nodes of the tree are the Sleigh Constructors arranged for a particular * instruction. This tree is walked for various purposes: *

          - *
        • SleighInstructionPrototype.resolve - initial parsing of instruction and building the tree - *
        • SleighInstructionPrototype.resolveHandles - filling in Varnode values for all the Constructor exports - *
        • PcodeEmit - for weaving together p-code for an instruction + *
        • SleighInstructionPrototype.resolve - initial parsing of instruction and building the tree
        • + *
        • SleighInstructionPrototype.resolveHandles - filling in Varnode values for all the Constructor exports
        • + *
        • PcodeEmit - for weaving together p-code for an instruction
        • *
        * */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighParserContext.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighParserContext.java index 6949072495..a125bfc4e4 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighParserContext.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighParserContext.java @@ -4,9 +4,9 @@ * 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. @@ -72,7 +72,7 @@ public class SleighParserContext implements ParserContext { /** * Constructor for building precompiled templates. - * NOTE: This form does not support use of inst_next2. + * NOTE: This form does not support use of {@code inst_next2}. * @param aAddr = address to which 'inst_start' resolves * @param nAddr = address to which 'inst_next' resolves * @param rAddr = special address associated with original call diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/VarnodeData.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/VarnodeData.java index 5c60196e6b..c33008a617 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/VarnodeData.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/VarnodeData.java @@ -4,9 +4,9 @@ * 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. @@ -36,7 +36,7 @@ public class VarnodeData { public int size; /** - * Encode the data to stream as an \ element + * Encode the data to stream as an {@code } element * @param encoder is the stream encoder * @throws IOException for errors writing to the underlying stream */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/template/ConstructTpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/template/ConstructTpl.java index 194b68e05e..78c2a68b60 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/template/ConstructTpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/template/ConstructTpl.java @@ -4,9 +4,9 @@ * 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. @@ -96,7 +96,7 @@ public class ConstructTpl { } /** - * Decode this template from a \ tag in the stream. + * Decode this template from a {@code } tag in the stream. * @param decoder is the stream * @return the constructor section id described by the tag * @throws DecoderException for errors in the encoding diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/NamespaceUtils.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/NamespaceUtils.java index 792e471ad2..6ba53ea9bd 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/NamespaceUtils.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/NamespaceUtils.java @@ -4,9 +4,9 @@ * 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. @@ -29,25 +29,25 @@ import ghidra.util.exception.*; * * Example string format: *
          - *
        • global{@link Namespace#DELIMITER ::}child1{@link Namespace#DELIMITER ::}child2 - *
        • child1 + *
        • global{@link Namespace#DELIMITER ::}child1{@link Namespace#DELIMITER ::}child2
        • + *
        • child1
        • *
        * * Assumptions for creating namespaces from a path string: *
          *
        • All elements of a namespace path should be namespace symbols and not other - * symbol types. - *
        • Absolute paths can optionally start with the global namespace. + * symbol types.
        • + *
        • Absolute paths can optionally start with the global namespace.
        • *
        • You can provide a relative path that will start at the given - * parent namespace (or global if there is no parent provided). + * parent namespace (or global if there is no parent provided).
        • *
        • You can provide a path that has as its first entry the name of the * given parent. In this case, the first entry will not be created, - * but rather the provided parent will be used. + * but rather the provided parent will be used.
        • *
        • If you provide a path and a parent, but the first element of the * path is the global namespace, then the global namespace will be - * used as the parent namespace and not the one that was provided. + * used as the parent namespace and not the one that was provided.
        • *
        • You cannot embed the global namespace in a path, but it can be at - * the root. + * the root.
        • *
        * * diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoDisassembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoDisassembler.java index 424f94ed33..45b09b5962 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoDisassembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoDisassembler.java @@ -4,9 +4,9 @@ * 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. @@ -361,10 +361,10 @@ public class PseudoDisassembler { /** * Check that this entry point leads to valid code: *
          - *
        • May have multiple entries into the body of the code. - *
        • The intent is that it be valid code, not nice code. - *
        • Hit no bad instructions. - *
        • It should return. + *
        • May have multiple entries into the body of the code.
        • + *
        • The intent is that it be valid code, not nice code.
        • + *
        • Hit no bad instructions.
        • + *
        • It should return.
        • *
        * @param entryPoint * @return true if the entry point leads to valid code @@ -377,10 +377,10 @@ public class PseudoDisassembler { /** * Check that this entry point leads to valid code: *
          - *
        • May have multiple entries into the body of the code. - *
        • The intent is that it be valid code, not nice code. - *
        • Hit no bad instructions. - *
        • It should return. + *
        • May have multiple entries into the body of the code.
        • + *
        • The intent is that it be valid code, not nice code.
        • + *
        • Hit no bad instructions.
        • + *
        • It should return.
        • *
        * * @param entryPoint location to test for valid code diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPath.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPath.java index 4cb840c043..fe274942e0 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPath.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPath.java @@ -4,9 +4,9 @@ * 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. @@ -28,9 +28,9 @@ import ghidra.program.model.symbol.*; * name of a symbol in the "bar" namespace, which is in the "foo" namespace. *

        *

          - *
        • {@link #getName()} will return "baz". - *
        • {@link #getParentPath()} will return "foo:bar". - *
        • {@link #getPath()} will return "foo::bar::baz". + *
        • {@link #getName()} will return "baz".
        • + *
        • {@link #getParentPath()} will return "foo:bar".
        • + *
        • {@link #getPath()} will return "foo::bar::baz".
        • *
        * */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcodeCPort/slgh_compile/ParseException.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcodeCPort/slgh_compile/ParseException.java index 9dbd54948b..baeaed59fb 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcodeCPort/slgh_compile/ParseException.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcodeCPort/slgh_compile/ParseException.java @@ -1,13 +1,12 @@ /* ### * 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. * 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. @@ -28,18 +27,18 @@ package ghidra.pcodeCPort.slgh_compile; */ public class ParseException extends Exception { - /** - * This constructor is used by the method "generateParseException" - * in the generated parser. Calling this constructor generates - * a new object of this type with the fields "currentToken", - * "expectedTokenSequences", and "tokenImage" set. The boolean - * flag "specialConstructor" is also set to true to indicate that - * this constructor was used to create this object. - * This constructor calls its super class with the empty string - * to force the "toString" method of parent class "Throwable" to - * print the error message in the form: - * ParseException: - */ + /** + * This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form: + * {@code ParseException: } + */ public ParseException(Token currentTokenVal, int[][] expectedTokenSequencesVal, String[] tokenImageVal diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramAddressFactory.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramAddressFactory.java index 885437c1f1..3dc0b79009 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramAddressFactory.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramAddressFactory.java @@ -4,9 +4,9 @@ * 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. @@ -40,7 +40,7 @@ public class ProgramAddressFactory extends DefaultAddressFactory { *
      • A stack space (see {@link AddressSpace#TYPE_STACK})
      • *
      • {@link AddressSpace#HASH_SPACE}
      • *
      • A join space (see {@link AddressSpace#TYPE_JOIN})
      • - * + * * In addition, support is provided for {@link ProgramOverlayAddressSpace}. * @param language language specification * @param compilerSpec compiler specification diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/SpecExtension.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/SpecExtension.java index e89f614cfb..2944a4d58c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/SpecExtension.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/SpecExtension.java @@ -4,9 +4,9 @@ * 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. @@ -35,34 +35,37 @@ import ghidra.xml.*; /** * Utility class for installing/removing "specification extensions" to a Program. * A specification extension is a program specific version of either a: - * - Prototype Model - * - Call Fixup or - * - Callother Fixup + *
          + *
        • Prototype Model
        • + *
        • Call Fixup or
        • + *
        • Callother Fixup
        • + *
        * Normally these objects are provided by the language specific configuration files (.cspec or .pspec), * but this class allows additional objects to be added that are specific to the program. - * + *

        * Internally, each spec extension is stored as an XML document as a formal Program Option. Each type of * extension is described by a specific XML tag and is parsed as it would be in a .cspec or .pspec file. * The XML tags are: - * - \ - describing a Call Fixup - * - \ - describing a Callother Fixup - * - \ - describing a typical Prototype Model - * - \ - describing a Prototype Model merged from other models - * + *

          + *
        • {@code } - describing a Call Fixup
        • + *
        • {@code } - describing a Callother Fixup
        • + *
        • {@code } - describing a typical Prototype Model
        • + *
        • {@code } - describing a Prototype Model merged from other models
        • + *
        * Each type of object has a unique name or target, which must be specified as part of the XML tag, - * which is referred to in this class as the extension's "formal name". In the \ tag, - * the formal name is given by the "targetop" attribute; for all the other tags, the formal name is - * given by the "name" attribute". - * + * which is referred to in this class as the extension's "formal name". In the + * {@code } tag, the formal name is given by the "targetop" attribute; for all the + * other tags, the formal name is given by the "name" attribute". + *

        * The parent option for all extensions is given by the static field SPEC_EXTENSION. Under the parent * option, each extension is stored as a string with an option name, constructed by * concatenating the extension's formal name with a prefix corresponding to the extension's XML tag name. - * + *

        * testExtensionDocument() is used independently to extensively test whether a document * describes a valid extension. - * - * Extensions are installed on a program via addReplaceCompilerSpecExtension(). - * Extensions are removed from a program via removeCompilerSpecExtension(). + *

        + * Extensions are installed on a program via {@code addReplaceCompilerSpecExtension()}. + * Extensions are removed from a program via {@code removeCompilerSpecExtension()}. */ public class SpecExtension { @@ -394,10 +397,12 @@ public class SpecExtension { /** * Parse an XML string and build the corresponding compiler spec extension object. * Currently this can either be a - * - PrototypeModel or - * - InjectPayload + *

          + *
        • PrototypeModel
        • + *
        • InjectPayload
        • + *
        * - * For InjectPayloadCallfixup or InjectPayloadCallother, the p-code \ tag + * For InjectPayloadCallfixup or InjectPayloadCallother, the p-code {@code } tag * is also parsed, and the caller can control whether any parse errors * cause an exception or whether a dummy payload is provided instead. * @param optionName is the option name the extension is attached to diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java index 7c76443f37..b90bfa018d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java @@ -4,9 +4,9 @@ * 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. @@ -39,9 +39,9 @@ import ghidra.util.task.TaskMonitor; *

        * This is implemented by storing records for each contiguous range with the same value. *

          - *
        • The key is the encoded start address of the range. - *
        • The TO_COL column of the record stores the encoded end address of the range. - *
        • The VALUE_COL column of the record stores the value for the range. + *
        • The key is the encoded start address of the range.
        • + *
        • The TO_COL column of the record stores the encoded end address of the range.
        • + *
        • The VALUE_COL column of the record stores the value for the range.
        • *
        *

        * This implementation is complicated by several issues. @@ -51,7 +51,7 @@ import ghidra.util.task.TaskMonitor; * Encoded addresses do not necessarily encode to keys that have the same ordering. * Therefore, all comparisons must be done in address space and not in the encoded space. * Also, record iterators must use the {@link AddressKeyRecordIterator} which will return - * records in address order versus encoded key order. + * records in address order versus encoded key order. *

      • The default space's image base can be changed after records have been created. This can * cause the address ranges represented by a record to wrap around. For example, suppose * the image base is 0 and you paint a range from address 0 to 0x20, which say maps to @@ -66,7 +66,8 @@ import ghidra.util.task.TaskMonitor; * logic, any wrapping record will first be split into two records before painting. However * we can only do this during a write operation (when we can make changes). Since the getter * methods and iterators cannot modify the database, they have to deal with wrapping - * records on the fly. + * records on the fly.
      • + * */ public class AddressRangeMapDB implements DBListener { public static final String RANGE_MAP_TABLE_PREFIX = "Range Map - "; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/BasicBlockModel.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/BasicBlockModel.java index 515dbd03f8..345c880d98 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/BasicBlockModel.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/BasicBlockModel.java @@ -1,13 +1,12 @@ /* ### * 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. * 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. @@ -26,14 +25,14 @@ import ghidra.program.model.symbol.*; * Each Codeblock is made up of contiguous instructions in address order. * * Blocks satisfy the following:
          - *
        1. Any instruction with a label starts a block. + *
        2. Any instruction with a label starts a block.
        3. *
        4. Each instruction that could cause program control flow to change local to - * the containing function (i.e., excludes calls) is the last instruction of a Codeblock. + * the containing function (i.e., excludes calls) is the last instruction of a Codeblock.
        5. *
        6. All other instructions are "NOP" fallthroughs, meaning * after execution the program counter will be at - * the instruction immediately following. + * the instruction immediately following.
        7. *
        8. Any instruction that is unreachable and has no label is also considered the start - * of a block. + * of a block.
        9. *
        * So a CodeBlock in this model consists of contiguous code that has zero or * more fallthrough or call instructions followed by a single flow instruction. @@ -41,11 +40,12 @@ import ghidra.program.model.symbol.*; * have a label at any other instruction contained in the block. * * This model handles delay slot instructions with the following - * assumptions:
          + * assumptions: + *
            *
          1. The delay slot depth of the delayed instruction will always * correspond to the number of delay slot instructions immediately * following the instruction. The model may not behave properly if - * the disassembled code violates this assumption. + * the disassembled code violates this assumption.
          2. *
          * @see ghidra.program.model.block.CodeBlockModel */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/SimpleBlockModel.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/SimpleBlockModel.java index f01fdb7a30..449d48c42b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/SimpleBlockModel.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/SimpleBlockModel.java @@ -4,9 +4,9 @@ * 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. @@ -30,34 +30,35 @@ import ghidra.util.task.TaskMonitor; * Each Codeblock is made up of contiguous instructions in address order. * * Blocks satisfy the following:
            - *
          1. Any instruction with a label starts a block. + *
          2. Any instruction with a label starts a block.
          3. *
          4. Each instruction that could cause program control flow to change is the - * last instruction of a Codeblock. + * last instruction of a Codeblock.
          5. *
          6. All other instructions are "NOP" fallthroughs, meaning * after execution the program counter will be at - * the instruction immediately following. + * the instruction immediately following.
          7. *
          8. Any instruction that is unreachable and has no label is also considered the start - * of a block. + * of a block.
          9. *
          * So a CodeBlock in this model consists of contiguous code that has zero or * more nonflow fallthrough instructions followed by a single flow instruction. * Each block may or may not have a label at the first instruction, but may not * have a label at any other instruction contained in the block. - * + *

          * This model does not implement the pure simple block model * because unreachable code is still considered a block. - * + *

          * This model handles delay slot instructions with the following - * assumptions:

            + * assumptions: + *
              *
            1. A delayed instruction is always corresponds to a change in * flow and terminates a block. The delay slot instructions * following this instruction are always included with the - * block. Therefor, delay slot instructions will always fall - * at the bottom of a simple block. + * block. Therefore, delay slot instructions will always fall + * at the bottom of a simple block.
            2. *
            3. The delay slot depth of the delayed instruction will always * correspond to the number of delay slot instructions immediately * following the instruction. The model may not behave properly if - * the disassembled code violates this assumption. + * the disassembled code violates this assumption.
            4. *
            * @see ghidra.program.model.block.CodeBlockModel */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPackingImpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPackingImpl.java index e213342e22..899ba51463 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPackingImpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPackingImpl.java @@ -4,9 +4,9 @@ * 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. @@ -165,7 +165,7 @@ public class BitFieldPackingImpl implements BitFieldPacking { } /** - * Restore settings from a \ tag in an XML stream. + * Restore settings from a {@code } tag in an XML stream. * The XML is designed to override existing settings from the default constructor * @param parser is the XML stream */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Composite.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Composite.java index 320a5cadaf..7b95fdf200 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Composite.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Composite.java @@ -4,9 +4,9 @@ * 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. @@ -275,9 +275,9 @@ public interface Composite extends DataType { * affect of disabled packing differs between {@link Structure} and {@link Union}. When * packing disabled: *
              - *
            • Structures utilize explicit component offsets and produce undefined filler - * components where defined components do not consume space.
            • - *
            • Unions always place components at offset 0 and do not pad for alignment. + *
            • Structures utilize explicit component offsets and produce undefined filler + * components where defined components do not consume space.
            • + *
            • Unions always place components at offset 0 and do not pad for alignment.
            • *
            * In addition, when packing is disabled the default alignment is always 1 unless a * different minimum alignment has been set. When packing is enabled the overall diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataUtilities.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataUtilities.java index 6cf8153f3d..51c5b9493f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataUtilities.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataUtilities.java @@ -424,7 +424,7 @@ public final class DataUtilities { * *
          1. If the originalDataType is any type of pointer the supplied newDatatype * will replace the pointer's base type (e.g., int * would become db * when - * newDataType is {@link ByteDataType}). + * newDataType is {@link ByteDataType}).
          2. * *

            If false, only required transformations will be applied, Example: * if newDataType is a FunctionDefinitionDataType it will be transformed diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringDataType.java index f8977ca0c3..0629092f5e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringDataType.java @@ -4,9 +4,9 @@ * 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. @@ -23,23 +23,23 @@ import ghidra.util.classfinder.ClassTranslator; *

            * All string data types: *

              - *
            • {@link StringDataType} - this type, fixed length, user settable charset. - *
            • {@link StringUTF8DataType} - fixed length UTF-8 string. - *
            • {@link TerminatedStringDataType} - terminated and unbounded string, user settable charset. - *
            • {@link TerminatedUnicodeDataType} - terminated and unbounded UTF-16 string. - *
            • {@link TerminatedUnicode32DataType} - terminated and unbounded UTF-32 string. - *
            • {@link PascalString255DataType} - length-prefixed string (limited to 255 chars), user settable charset. - *
            • {@link PascalStringDataType} - length-prefixed string (limited to 64k), user settable charset. - *
            • {@link PascalUnicodeDataType} - length-prefixed UTF-16 (limited to 64k). - *
            • {@link UnicodeDataType} - fixed length UTF-16 string. - *
            • {@link Unicode32DataType} - fixed length UTF-32 string. + *
            • {@link StringDataType} - this type, fixed length, user settable charset.
            • + *
            • {@link StringUTF8DataType} - fixed length UTF-8 string.
            • + *
            • {@link TerminatedStringDataType} - terminated and unbounded string, user settable charset.
            • + *
            • {@link TerminatedUnicodeDataType} - terminated and unbounded UTF-16 string.
            • + *
            • {@link TerminatedUnicode32DataType} - terminated and unbounded UTF-32 string.
            • + *
            • {@link PascalString255DataType} - length-prefixed string (limited to 255 chars), user settable charset.
            • + *
            • {@link PascalStringDataType} - length-prefixed string (limited to 64k), user settable charset.
            • + *
            • {@link PascalUnicodeDataType} - length-prefixed UTF-16 (limited to 64k).
            • + *
            • {@link UnicodeDataType} - fixed length UTF-16 string.
            • + *
            • {@link Unicode32DataType} - fixed length UTF-32 string.
            • *
            *

            * The following settings are supported by all string types on the data instance: *

              *
            • {@link TranslationSettingsDefinition} - controls display of string values that have been - * translated to english. - *
            • {@link RenderUnicodeSettingsDefinition} - controls display of non-ascii Unicode characters. + * translated to English.
            • + *
            • {@link RenderUnicodeSettingsDefinition} - controls display of non-ascii Unicode characters.
            • *
            */ public class StringDataType extends AbstractStringDataType { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringLayoutEnum.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringLayoutEnum.java index 550a05c201..d8e0143a8c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringLayoutEnum.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StringLayoutEnum.java @@ -4,9 +4,9 @@ * 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. @@ -18,12 +18,12 @@ package ghidra.program.model.data; /** * Controls strings termination *
              - *
            • {@link StringLayoutEnum#FIXED_LEN} - *
            • {@link StringLayoutEnum#CHAR_SEQ} - *
            • {@link StringLayoutEnum#NULL_TERMINATED_UNBOUNDED} - *
            • {@link StringLayoutEnum#NULL_TERMINATED_BOUNDED} - *
            • {@link StringLayoutEnum#PASCAL_255} - *
            • {@link StringLayoutEnum#PASCAL_64k} + *
            • {@link StringLayoutEnum#FIXED_LEN}
            • + *
            • {@link StringLayoutEnum#CHAR_SEQ}
            • + *
            • {@link StringLayoutEnum#NULL_TERMINATED_UNBOUNDED}
            • + *
            • {@link StringLayoutEnum#NULL_TERMINATED_BOUNDED}
            • + *
            • {@link StringLayoutEnum#PASCAL_255}
            • + *
            • {@link StringLayoutEnum#PASCAL_64k}
            • *
            */ public enum StringLayoutEnum { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java index 3bc5fc85b9..497ea46d18 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java @@ -29,7 +29,7 @@ import ghidra.util.exception.AssertException; * NOTES: *
              *
            • Implementation is not thread safe when being modified.
            • - *
            • For a structure to treated as having a zero-length (see {@link #isZeroLength()}) it + *
            • For a structure to treated as having a zero-length (see {@link #isZeroLength()}) it
            • * *
            * diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureFactory.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureFactory.java index 149b48b7f9..5fdd018e4c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureFactory.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureFactory.java @@ -4,9 +4,9 @@ * 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. @@ -47,12 +47,12 @@ public class StructureFactory { * @return A new structure not yet added to memory. * @throws IllegalArgumentException for the following conditions: *
              - *
            • if dataLength is not greater than zero + *
            • if dataLength is not greater than zero
            • *
            • if the number of components to add exceeds the available - * address space + * address space
            • *
            • if there are any instructions in the provided - * address space - *
            • if there are no data components to add to the structure + * address space
            • + *
            • if there are no data components to add to the structure
            • *
            */ public static Structure createStructureDataType(Program program, Address address, @@ -73,13 +73,13 @@ public class StructureFactory { * @return A new structure not yet added to memory. * @throws IllegalArgumentException for the following conditions: *
              - *
            • if structureName is null - *
            • if dataLength is not greater than zero + *
            • if structureName is null
            • + *
            • if dataLength is not greater than zero
            • *
            • if the number of components to add exceeds the available - * address space + * address space
            • *
            • if there are any instructions in the provided - * address space - *
            • if there are no data components to add to the structure + * address space
            • + *
            • if there are no data components to add to the structure
            • *
            */ public static Structure createStructureDataType(Program program, Address address, @@ -147,9 +147,9 @@ public class StructureFactory { * @throws IllegalArgumentException for the following conditions: *
              *
            • if the component at fromPath or the component - * at toPath are null - *
            • if there is not data to add to the structure - *
            • if the parent data type is not a structure + * at toPath are null
            • + *
            • if there is not data to add to the structure
            • + *
            • if the parent data type is not a structure
            • *
            */ public static Structure createStructureDataTypeInStrucuture(Program program, Address address, @@ -176,11 +176,11 @@ public class StructureFactory { * @return A new structure not yet added to memory. * @throws IllegalArgumentException for the following conditions: *
              - *
            • if structureName is null + *
            • if structureName is null
            • *
            • if the component at fromPath or the component - * at toPath are null - *
            • if there is not data to add to the structure - *
            • if the parent data type is not a structure + * at toPath are null
            • + *
            • if there is not data to add to the structure
            • + *
            • if the parent data type is not a structure
            • *
            */ public static Structure createStructureDataTypeInStrucuture(Program program, Address address, diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java index 89bb059f43..9b36c235c5 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java @@ -5,9 +5,9 @@ * 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. @@ -582,7 +582,7 @@ public class BasicCompilerSpec implements CompilerSpec { } /** - * Initialize this object from an XML stream. A single \ tag is expected. + * Initialize this object from an XML stream. A single {@code } tag is expected. * @param parser is the XML stream * @throws XmlParseException for badly formed XML * @throws DuplicateNameException if we parse more than one PrototypeModel with the same name diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/CompilerSpec.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/CompilerSpec.java index ce9f62732d..550f7b9a07 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/CompilerSpec.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/CompilerSpec.java @@ -4,9 +4,9 @@ * 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. @@ -261,7 +261,7 @@ public interface CompilerSpec { /** * Encode this entire specification to a stream. A document is written with - * root element \. + * root element {@code }. * @param encoder is the stream encoder * @throws IOException for errors writing to the underlying stream */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ContextSetting.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ContextSetting.java index f71e63cfdf..c7999a2a3b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ContextSetting.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ContextSetting.java @@ -4,9 +4,9 @@ * 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. @@ -47,11 +47,11 @@ public final class ContextSetting { } /** - * Construct from an XML \ tag. The tag is a child of either \ or \ - * which provides details of the memory range affected. + * Construct from an XML {@code } tag. The tag is a child of either {@code } + * or {@code } which provides details of the memory range affected. * @param el is the XML tag * @param cspec is used to lookup register names present in the tag - * @param isContextReg is true for a \ parent, false for a \ parent + * @param isContextReg is true for a {@code } parent, false for a {@code } parent * @param first is the first Address in the affected memory range * @param last is the last Address in the affected memory range */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayload.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayload.java index f357d90ae5..6284a9244f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayload.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayload.java @@ -4,9 +4,9 @@ * 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. @@ -157,7 +157,7 @@ public interface InjectPayload { public boolean isIncidentalCopy(); /** - * Encode configuration parameters as a \ element to stream + * Encode configuration parameters as a {@code } element to stream * @param encoder is the stream encoder * @throws IOException for errors writing to the underlying stream */ @@ -165,7 +165,7 @@ public interface InjectPayload { /** * Restore the payload from an XML stream. The root expected document is - * the \ tag, which may be wrapped with another tag by the derived class. + * the {@code } tag, which may be wrapped with another tag by the derived class. * @param parser is the XML stream * @param language is used to resolve registers and address spaces * @throws XmlParseException for badly formed XML diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/AddressXML.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/AddressXML.java index 4e001d33bf..d4fc461e17 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/AddressXML.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/AddressXML.java @@ -4,9 +4,9 @@ * 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. @@ -34,15 +34,16 @@ import ghidra.xml.XmlParseException; /** * Utility class for the myriad ways of marshaling/unmarshaling an address and an optional size, * to/from XML for the various configuration files. - * + *

            * An object of the class itself is the most general form, where the specified address - * - MAY have an associated size given in bytes - * - MAY be in the JOIN address space, with physical pieces making up the logical value explicitly provided. - * - * The static buildXML methods write out an \ tag given component elements without allocating an object. + *

              + *
            • MAY have an associated size given in bytes
            • + *
            • MAY be in the JOIN address space, with physical pieces making up the logical value explicitly provided
            • + *
            + * The static buildXML methods write out an {@code } tag given component elements without allocating an object. * The static readXML methods read XML tags (presented in different forms) and returns an Address object. * The static appendAttributes methods write out attributes of an address to an arbitrary XML tag. - * The static restoreXML methods read an \ tag and produce a general AddressXML object. + * The static restoreXML methods read an {@code } tag and produce a general AddressXML object. */ public class AddressXML { @@ -136,7 +137,7 @@ public class AddressXML { } /** - * Encode this sized address as an \ element to the stream + * Encode this sized address as an {@code } element to the stream * @param encoder is the stream encoder * @throws IOException for errors in the underlying stream */ @@ -433,12 +434,14 @@ public class AddressXML { /** * Create an address from a stream encoding. This recognizes elements - * - \ - * - \ - * - \ or - * - any element with "space" and "offset" attributes - * - * An empty \ element, with no attributes, results in Address.NO_ADDRESS being returned. + *
              + *
            • {@code }
            • + *
            • {@code }
            • + *
            • {@code } or
            • + *
            • any element with "space" and "offset" attributes
            • + *
            + * An empty {@code } element, with no attributes, results in {@link Address#NO_ADDRESS} + * being returned. * @param decoder is the stream decoder * @return Address created from decode info * @throws DecoderException for any problems decoding the stream @@ -545,7 +548,7 @@ public class AddressXML { } /** - * Encode the given Address as an \ element to the stream + * Encode the given Address as an {@code } element to the stream * * @param encoder is the stream encoder * @param addr -- Address to encode @@ -563,7 +566,7 @@ public class AddressXML { } /** - * Encode the given Address and a size as an \ element to the stream + * Encode the given Address and a size as an {@code } element to the stream * * @param encoder is the stream encoder * @param addr is the given Address @@ -577,9 +580,9 @@ public class AddressXML { } /** - * Encode a sequence of Varnodes as a single \ element to the stream. + * Encode a sequence of Varnodes as a single {@code } element to the stream. * If there is more than one Varnode, or if the logical size is non-zero, - * the \ element will specify the address space as "join" and will have + * the {@code } element will specify the address space as "join" and will have * additional "piece" attributes. * * @param encoder is the stream encoder diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java index 9af94f1f9c..d2d0503538 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java @@ -4,9 +4,9 @@ * 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. @@ -826,8 +826,8 @@ public class PcodeDataTypeManager { /** * Encode a TypeDef data-type to the stream. Generally this sends - * a \ element with a \ reference to the underlying data-type being typedefed, - * but we check for Settings on the TypeDef object that can indicate + * a {@code } element with a {@code } reference to the underlying data-type being + * typedefed, but we check for Settings on the TypeDef object that can indicate * specialty data-types with their own encodings. * @param encoder is the stream encoder * @param type is the TypeDef to build the XML for diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeOp.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeOp.java index 8c993de27a..fedc30abfe 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeOp.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeOp.java @@ -4,9 +4,9 @@ * 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. @@ -431,7 +431,7 @@ public class PcodeOp { /** * Encode just the opcode and input/output Varnode data for this PcodeOp to a stream - * as an \ element + * as an {@code } element * @param encoder is the stream encoder * @param addrFactory is a factory for looking up encoded address spaces * @throws IOException for errors in the underlying stream diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/framework/ApplicationIdentifier.java b/Ghidra/Framework/Utility/src/main/java/ghidra/framework/ApplicationIdentifier.java index a2ff8d4753..a417595054 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/framework/ApplicationIdentifier.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/framework/ApplicationIdentifier.java @@ -4,9 +4,9 @@ * 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. @@ -29,7 +29,7 @@ import utility.application.ApplicationUtilities; *

            * Examples: *

              - *
            • ghidra-7.4_DEV + *
            • ghidra-7.4_DEV
            • *
            */ public class ApplicationIdentifier { diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/framework/ApplicationVersion.java b/Ghidra/Framework/Utility/src/main/java/ghidra/framework/ApplicationVersion.java index b549c37003..bf53b68224 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/framework/ApplicationVersion.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/framework/ApplicationVersion.java @@ -4,9 +4,9 @@ * 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. @@ -18,16 +18,16 @@ package ghidra.framework; /** * Class to represent an application's version information. *

            - * The version format is \d\.\d(\.\d)?(\-.+)? + * The version format is {@code \d\.\d(\.\d)?(\-.+)?} *

            * Note: this class has a natural ordering that is inconsistent with equals (the tag * part of the version is disregarded in the {@link #compareTo(ApplicationVersion)} method). *

            * Examples: *

              - *
            • 7.4 - *
            • 7.4.1 - *
            • 7.4.1-BETA + *
            • 7.4
            • + *
            • 7.4.1
            • + *
            • 7.4.1-BETA
            • *
            */ public class ApplicationVersion implements Comparable { diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/UserSearchUtils.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/UserSearchUtils.java index 3cd71fb1ed..4bd372ea8c 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/util/UserSearchUtils.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/UserSearchUtils.java @@ -4,9 +4,9 @@ * 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. @@ -82,10 +82,10 @@ public class UserSearchUtils { * Note: This method will escape regular expression * characters, such as: *
              - *
            • ? - *
            • . - *
            • $ - *
            • ...and many others + *
            • ?
            • + *
            • .
            • + *
            • $
            • + *
            • ...and many others
            • *
            * Thus, this method is not meant to accept regular expressions, but * rather generates regular expressions. From c09ba2ceeff488a7dfa0d578f4e1a605f33bf0a7 Mon Sep 17 00:00:00 2001 From: Ruffalo Lavoisier Date: Wed, 4 Sep 2024 22:48:17 +0900 Subject: [PATCH 43/53] Fix typo ghidra words on comments --- Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java | 2 +- .../src/main/java/docking/test/AbstractDockingTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java index efb81834c9..e64b5fc259 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java @@ -362,7 +362,7 @@ public class TestEnv { * @param ghidraClass The class of the dialog the user desires * @param maxTimeMS The max amount of time in milliseconds to wait for the requested dialog * to appear. - * @return The first occurrence of a dialog that extends the given ghirdraClass + * @return The first occurrence of a dialog that extends the given ghidraClass * @deprecated use instead {@link AbstractDockingTest#waitForDialogComponent(Class)} */ @Deprecated diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java index 53153bf18e..5436060e89 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java @@ -582,7 +582,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest { * Waits for the first window of the given class. * * @param ghidraClass The class of the dialog the user desires - * @return The first occurrence of a dialog that extends the given ghirdraClass + * @return The first occurrence of a dialog that extends the given ghidraClass * @see #waitForDialogComponent(Window, Class, int) */ public static T waitForDialogComponent( @@ -598,7 +598,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest { * @param clazz The class of the dialog the user desires * @param timeoutMS The max amount of time in milliseconds to wait for the requested dialog * to appear. - * @return The first occurrence of a dialog that extends the given ghirdraClass + * @return The first occurrence of a dialog that extends the given ghidraClass * @deprecated Instead call one of the methods that does not take a timeout * (we are standardizing timeouts). The timeouts passed to this method will * be ignored in favor of the standard value. From d6d00f78e6201103ecc018eaaf3b18b48f0b7128 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Wed, 4 Sep 2024 10:58:16 -0400 Subject: [PATCH 44/53] GP-0: Certify --- Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java | 4 ++-- .../src/main/java/docking/test/AbstractDockingTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java index e64b5fc259..24262fb15e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java @@ -4,9 +4,9 @@ * 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. diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java index 5436060e89..de514e9ba4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java @@ -4,9 +4,9 @@ * 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. From 0c365b7afd5cb2e57d3334ce71c70d22857b8146 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:16:52 -0400 Subject: [PATCH 45/53] GP-4861 - Created a way to show a message over a given component --- .../memsearch/gui/MemorySearchProvider.java | 31 +- .../Docking/data/docking.theme.properties | 16 +- .../java/docking/util/AnimationPainter.java | 13 +- .../java/docking/util/AnimationRunner.java | 256 +++++++++++++ .../java/docking/util/AnimationUtils.java | 21 +- .../java/docking/util/GGlassPaneMessage.java | 362 ++++++++++++++++++ .../main/java/docking/util/TextShaper.java | 340 ++++++++++++++++ .../java/docking/util/TextShaperTest.java | 168 ++++++++ .../generic/timer/GhidraTimerFactory.java | 57 +-- .../main/java/ghidra/util/timer/GTimer.java | 9 +- .../java/ghidra/util/bean/GGlassPane.java | 7 +- 11 files changed, 1200 insertions(+), 80 deletions(-) create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/util/AnimationRunner.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/util/GGlassPaneMessage.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/util/TextShaper.java create mode 100644 Ghidra/Framework/Docking/src/test.slow/java/docking/util/TextShaperTest.java diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java index 4cf79e60df..fb2e9d8894 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java @@ -16,6 +16,7 @@ package ghidra.features.base.memsearch.gui; import java.awt.*; +import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -29,6 +30,7 @@ import docking.action.DockingAction; import docking.action.ToggleDockingAction; import docking.action.builder.ActionBuilder; import docking.action.builder.ToggleActionBuilder; +import docking.util.GGlassPaneMessage; import generic.theme.GIcon; import ghidra.app.context.NavigatableActionContext; import ghidra.app.nav.Navigatable; @@ -100,6 +102,9 @@ public class MemorySearchProvider extends ComponentProviderAdapter private SearchGuiModel model; private boolean isPrivate = false; + // used to show a temporary message over the table + private GGlassPaneMessage glassPaneMessage; + public MemorySearchProvider(MemorySearchPlugin plugin, Navigatable navigatable, SearchSettings settings, MemorySearchOptions options, SearchHistory history) { super(plugin.getTool(), "Memory Search", plugin.getName()); @@ -355,9 +360,9 @@ public class MemorySearchProvider extends ComponentProviderAdapter setBusy(false); updateSubTitle(); if (!cancelled && terminatedEarly) { - Msg.showInfo(getClass(), resultsPanel, "Search Limit Exceeded!", - "Stopped search after finding " + options.getSearchLimit() + " matches.\n" + - "The search limit can be changed at Edit->Tool Options, under Search."); + showAlert("Search Limit Exceeded!\n\nStopped search after finding " + + options.getSearchLimit() + " matches.\n" + + "The search limit can be changed at Edit \u2192 Tool Options, under Search."); } else if (!foundResults) { @@ -545,6 +550,8 @@ public class MemorySearchProvider extends ComponentProviderAdapter } private void dispose() { + glassPaneMessage.hide(); + glassPaneMessage = null; matchHighlighter.dispose(); USED_IDS.remove(id); if (navigatable != null) { @@ -638,12 +645,6 @@ public class MemorySearchProvider extends ComponentProviderAdapter return byteMatcher.getDescription(); } - void showAlert(String alertMessage) { - // replace with water mark concept - Toolkit.getDefaultToolkit().beep(); - Msg.showInfo(this, null, "Search Results", alertMessage); - } - @Override protected ActionContext createContext(Component sourceComponent, Object contextObject) { ActionContext context = new NavigatableActionContext(this, navigatable); @@ -652,4 +653,16 @@ public class MemorySearchProvider extends ComponentProviderAdapter return context; } + private void showAlert(String message) { + Toolkit.getDefaultToolkit().beep(); + + if (glassPaneMessage == null) { + GhidraTable table = resultsPanel.getTable(); + glassPaneMessage = new GGlassPaneMessage(table); + glassPaneMessage.setHideDelay(Duration.ofSeconds(3)); + } + + glassPaneMessage.showCenteredMessage(message); + } + } diff --git a/Ghidra/Framework/Docking/data/docking.theme.properties b/Ghidra/Framework/Docking/data/docking.theme.properties index ddaa0432c9..6ae7b334bd 100644 --- a/Ghidra/Framework/Docking/data/docking.theme.properties +++ b/Ghidra/Framework/Docking/data/docking.theme.properties @@ -15,6 +15,8 @@ color.fg.dialog.status.error = color.fg.messages.error color.fg.dialog.status.normal = color.fg.messages.normal color.fg.dialog.status.warning = color.fg.messages.warning +color.fg.glasspane.message = color.palette.lightcornflowerblue + color.bg.selection = color.palette.palegreen color.bg.highlight = color.palette.lemonchiffon @@ -125,9 +127,6 @@ icon.widget.tabs.empty.small = empty8x16.png icon.widget.tabs.close = x.gif icon.widget.tabs.close.highlight = pinkX.gif icon.widget.tabs.list = VCRFastForward.gif -font.widget.tabs.selected = sansserif-plain-11 -font.widget.tabs = sansserif-plain-11 -font.widget.tabs.list = sansserif-bold-9 icon.dialog.error.expandable.report = icon.spreadsheet icon.dialog.error.expandable.exception = program_obj.png @@ -157,6 +156,10 @@ icon.task.progress.hourglass.11 = hourglass24_11.png // Fonts +font.input.hint = monospaced-plain-10 + +font.glasspane.message = sansserif-bold-24 + font.splash.header.default = serif-bold-35 font.splash.status = serif-bold-12 @@ -164,12 +167,13 @@ font.splash.status = serif-bold-12 font.table.base = [font]system.font.control font.table.header.number = arial-bold-12 -font.input.hint = monospaced-plain-10 - font.task.monitor.label.message = sansserif-plain-10 -font.wizard.border.title = sansserif-plain-10 +font.widget.tabs.selected = sansserif-plain-11 +font.widget.tabs = sansserif-plain-11 +font.widget.tabs.list = sansserif-bold-9 +font.wizard.border.title = sansserif-plain-10 diff --git a/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationPainter.java b/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationPainter.java index 27bb0f6af0..24043506e4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationPainter.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationPainter.java @@ -1,13 +1,12 @@ /* ### * 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. * 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. @@ -16,10 +15,10 @@ */ package docking.util; -import ghidra.util.bean.GGlassPane; - import java.awt.Graphics; +import ghidra.util.bean.GGlassPane; + /** * An interface used with {@link AnimationUtils} to allow clients to use the timing * framework while performing their own painting. @@ -31,7 +30,7 @@ public interface AnimationPainter { * * @param glassPane the glass pane upon which painting takes place * @param graphics the graphics used to paint - * @param percentComplete a value from 0 to 1, 1 being fully complete. + * @param value a value from from the range supplied to the animator when it was created */ - public void paint(GGlassPane glassPane, Graphics graphics, double percentComplete); + public void paint(GGlassPane glassPane, Graphics graphics, double value); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationRunner.java b/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationRunner.java new file mode 100644 index 0000000000..47b7864245 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationRunner.java @@ -0,0 +1,256 @@ +/* ### + * 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 docking.util; + +import java.awt.Graphics; +import java.time.Duration; +import java.util.Objects; + +import javax.swing.JComponent; + +import org.jdesktop.animation.timing.Animator; +import org.jdesktop.animation.timing.TimingTargetAdapter; +import org.jdesktop.animation.timing.interpolation.PropertySetter; + +import ghidra.util.Msg; +import ghidra.util.bean.GGlassPane; +import ghidra.util.bean.GGlassPanePainter; +import utility.function.Callback; +import utility.function.Dummy; + +/** + * A class that does basic setup work for creating an {@link Animator}. The animator will run a + * timer in a background thread, calling the client periodically until the animation progress is + * finished. The actual visual animation is handled by the client's {@link AnimationPainter}. + * This class is provided for convenience. Clients can create their own {@link Animator} as needed. + *

            + * A {@link #setPainter(AnimationPainter) painter} must be supplied before calling {@link #start()}. + * A simple example usage: + *

            + *  GTable table = ...;
            + *  AnimationPainter painter = new AnimationPainter() {
            + *  	public void paint(GGlassPane glassPane, Graphics graphics, double value) {
            + *  		
            + *  		// repaint some contents to the glass pane's graphics using the current value as to 
            + *  		// know where we are in the progress of animating
            + *  	}
            + *  };
            + *  AnimationRunner animation = new AnimationRunner(table);
            + *  animation.setPainter(painter);
            + *  animation.start();
            + *  
            + *  ...
            + *  
            + *  // code to stop animation, such as when a request for a new animation is received
            + *  if (animation != null) {
            + *  	animation.stop();
            + *  }
            + *  
            + * 
            + *

            + * Clients who wish to perform more configuration can call {@link #createAnimator()} to perform the + * basic setup, calling {@link #start()} when finished with any follow-up configuration. + *

            + * See {@link Animator} for details on the animation process. + */ +public class AnimationRunner { + + private JComponent component; + private GGlassPane glassPane; + private Animator animator; + private UserDefinedPainter painter; + private boolean removePainterWhenFinished = true; + private Callback doneCallback = Callback.dummy(); + + private Duration duration = Duration.ofSeconds(1); + private Double[] values = new Double[] { 0D, 1D }; + + public AnimationRunner(JComponent component) { + this.component = component; + } + + /** + * Sets the painter required for the animator to work. + * @param animationPainter the painter. + */ + public void setPainter(AnimationPainter animationPainter) { + this.painter = new UserDefinedPainter(animationPainter); + } + + /** + * Sets the values passed to the animator created by this class. These values will be split + * into a range of values, broken up by the duration of the animator. The default values are 0 + * and 1. + *

            + * See {@link PropertySetter#createAnimator(int, Object, String, Object...)}. + * @param values the values + */ + public void setValues(Double... values) { + if (values == null || values.length == 0) { + throw new IllegalArgumentException("'values' cannot be null or empty"); + } + this.values = values; + } + + /** + * Signals to remove the painter from the glass pane when the animation is finished. Clients + * can specify {@code false} which will allow the painting to continue after the animation has + * finished. + * @param b true to remove the painter. The default value is true. + */ + public void setRemovePainterWhenFinished(boolean b) { + this.removePainterWhenFinished = b; + } + + /** + * Sets a callback to be called when the animation is finished. + * @param c the callback + */ + public void setDoneCallback(Callback c) { + this.doneCallback = Dummy.ifNull(c); + } + + /** + * Sets the animation duration. The default is 1 second. + * @param duration the duration + */ + public void setDuration(Duration duration) { + this.duration = Objects.requireNonNull(duration); + } + + /** + * This is a method used by the animator. Clients should not call this method. + * @param value the current value created by the animator + */ + public void setCurrentValue(Double value) { + painter.setValue(value); + glassPane.repaint(); + } + + /** + * Creates the animator used to perform animation. Clients will call {@link Animator#start()} + * to begin animation. Many attributes of the animator can be configured before starting. + * @return the animator + * @throws IllegalStateException if require values have not been set on this class, such as + * {@link #setValues(Double...)} or {@link #setPainter(AnimationPainter)}. + */ + public Animator createAnimator() { + + if (animator != null) { + return animator; + } + + glassPane = AnimationUtils.getGlassPane(component); + if (glassPane == null) { + Msg.debug(AnimationUtils.class, + "Cannot animate without a " + GGlassPane.class.getName() + " installed"); + throw new IllegalStateException("Unable to find Glass Pane"); + } + + if (painter == null) { + throw new IllegalStateException("A painter must be supplied"); + } + + setCurrentValue(values[0]); // set initial value + + int aniationDuration = (int) duration.toMillis(); + if (!AnimationUtils.isAnimationEnabled()) { + aniationDuration = 0; // do not animate + } + + animator = PropertySetter.createAnimator(aniationDuration, this, "currentValue", values); + animator.setAcceleration(0.2f); + animator.setDeceleration(0.8f); + animator.addTarget(new TimingTargetAdapter() { + @Override + public void end() { + done(); + } + }); + + return animator; + } + + /** + * Starts the animation process, creating the animator as needed. This method can be called + * repeatedly without calling stop first. + */ + public void start() { + + if (painter == null) { + throw new IllegalStateException("A painter must be supplied"); + } + + if (animator != null) { + if (animator.isRunning()) { + animator.stop(); + } + } + else { + animator = createAnimator(); + } + + glassPane.addPainter(painter); + animator.start(); + } + + /** + * Stops all animation and removes the painter from the glass pane. {@link #start()} can be + * called again after calling this method. + */ + public void stop() { + if (animator != null) { + animator.stop(); + } + + glassPane.removePainter(painter); + } + + private void done() { + setCurrentValue(values[values.length - 1]); + + if (removePainterWhenFinished) { + glassPane.removePainter(painter); + } + else { + glassPane.repaint(); + } + + doneCallback.call(); + } + + /** + * A painter that will call the user-supplied painter with the current value. + */ + private class UserDefinedPainter implements GGlassPanePainter { + + private AnimationPainter userPainter; + private double value; + + UserDefinedPainter(AnimationPainter userPainter) { + this.userPainter = userPainter; + } + + void setValue(double value) { + this.value = value; + } + + @Override + public void paint(GGlassPane gp, Graphics graphics) { + userPainter.paint(gp, graphics, value); + } + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationUtils.java index 879865a2ce..5f6b8883be 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/util/AnimationUtils.java @@ -4,9 +4,9 @@ * 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. @@ -133,24 +133,19 @@ public class AnimationUtils { return driver.animator; } - public static Animator createPaintingAnimator(Component window, AnimationPainter painter) { + public static Animator createPaintingAnimator(Component component, AnimationPainter painter) { if (!animationEnabled) { return null; } - Component paneComponent = getGlassPane(window); - if (paneComponent == null) { + GGlassPane glassPane = getGlassPane(component); + if (glassPane == null) { // could happen if the given component has not yet been realized + Msg.debug(AnimationUtils.class, + "Cannot animate without a " + GGlassPane.class.getName() + " installed"); return null; } - if (!(paneComponent instanceof GGlassPane)) { - Msg.debug(AnimationUtils.class, - "Cannot animate without a " + GGlassPane.class.getName() + " installed"); - return null; // shouldn't happen - } - - GGlassPane glassPane = (GGlassPane) paneComponent; BasicAnimationDriver driver = new BasicAnimationDriver(glassPane, new UserDefinedPainter(painter)); return driver.animator; @@ -596,6 +591,8 @@ public class AnimationUtils { animator = PropertySetter.createAnimator(2000, this, "percentComplete", start, max); + painter.setPercentComplete(1D); // set initial value + animator.setAcceleration(0.2f); animator.setDeceleration(0.8f); animator.addTarget(new TimingTargetAdapter() { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/util/GGlassPaneMessage.java b/Ghidra/Framework/Docking/src/main/java/docking/util/GGlassPaneMessage.java new file mode 100644 index 0000000000..482926e365 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/util/GGlassPaneMessage.java @@ -0,0 +1,362 @@ +/* ### + * 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 docking.util; + +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.time.Duration; +import java.util.Objects; + +import javax.swing.*; + +import generic.json.Json; +import generic.theme.Gui; +import generic.util.WindowUtilities; +import generic.util.image.ImageUtils; +import ghidra.util.Swing; +import ghidra.util.bean.GGlassPane; +import ghidra.util.timer.GTimer; +import ghidra.util.timer.GTimerMonitor; + +/** + * A class that allows clients to paint a message over top of a given component. + *

            + * This class will honor newline characters and will word wrap as needed. If the message being + * displayed will not fit within the bounds of the given component, then the text will be clipped. + */ +public class GGlassPaneMessage { + + private static final int HIDE_DELAY_MILLIS = 2000; + + private AnimationRunner animationRunner; + private GTimerMonitor timerMonitor; + private Duration hideDelay = Duration.ofMillis(HIDE_DELAY_MILLIS); + + private JComponent component; + private String message; + + public GGlassPaneMessage(JComponent component) { + this.component = component; + + component.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + if (animationRunner != null) { + hide(); + e.consume(); + } + } + } + }); + + } + + /** + * Sets the amount of time the message will remain on screen after the animation has completed. + * To hide the message sooner, call {@link #hide()}. + * @param duration the duration + */ + public void setHideDelay(Duration duration) { + hideDelay = Objects.requireNonNull(duration); + } + + /** + * Shows the given message centered over the component used by this class. + * @param newMessage the message + */ + public void showCenteredMessage(String newMessage) { + AnimationPainter painter = new CenterTextPainter(); + showMessage(newMessage, painter); + } + + /** + * Shows a message at the bottom of the component used by this class. + * @param newMessage the message + */ + public void showBottomMessage(String newMessage) { + AnimationPainter painter = new BottomTextPainter(); + showMessage(newMessage, painter); + } + + public void showMessage(String newMessage, AnimationPainter painter) { + + hide(); + + this.message = Objects.requireNonNull(newMessage); + + AnimationRunner runner = new AnimationRunner(component); + + double full = 1D; + double emphasized = 1.2D; + Double[] stages = new Double[] { full, emphasized, emphasized, emphasized, full }; + runner.setValues(stages); + runner.setDuration(Duration.ofMillis(500)); + runner.setRemovePainterWhenFinished(false); // we will remove it ourselves + runner.setPainter(painter); + runner.start(); + + animationRunner = runner; + + // remove the text later so users have a chance to read it + timerMonitor = GTimer.scheduleRunnable(hideDelay.toMillis(), () -> { + Swing.runNow(() -> hide()); + }); + } + + /** + * Hides any message being displayed. This can be called even if the message has been hidden. + */ + public void hide() { + + if (animationRunner != null) { + animationRunner.stop(); + animationRunner = null; + } + + if (timerMonitor != null) { + timerMonitor.cancel(); + timerMonitor = null; + } + } + + @Override + public String toString() { + return Json.toString(message); + } + +//================================================================================================= +// Inner Classes +//================================================================================================= + + private abstract class AbstractTextPainer implements AnimationPainter { + + private static String FONT_ID = "font.glasspane.message"; + private static final String MESSAGE_FG_COLOR_ID = "color.fg.glasspane.message"; + + // use an image of the painted text to make scaling smoother; cache the image for speed + protected Image baseTextImage; + + private ComponentListener resizeListener = new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + baseTextImage = null; + Window w = WindowUtilities.windowForComponent(component); + w.repaint(); + } + }; + + AbstractTextPainer() { + component.addComponentListener(resizeListener); + } + + private void createImage() { + + if (baseTextImage != null) { + return; + } + + Font font = Gui.getFont(FONT_ID); + BufferedImage tempImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + Graphics2D scratchG2d = (Graphics2D) tempImage.getGraphics(); + + scratchG2d.setFont(font); + Dimension size = getComponentSize(); + int padding = 20; + size.width -= padding; + TextShaper textShaper = new TextShaper(message, size, scratchG2d); + + Dimension textSize = textShaper.getTextSize(); + if (textSize.width == 0 || textSize.height == 0) { + return; // not enough room to paint text + } + + // Add some space to handle float to int rounding in the text calculation. This prevents + // the edge of characters from getting clipped when painting. + int roundingPadding = 5; + int w = textSize.width + roundingPadding; + int h = textSize.height; + + BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = (Graphics2D) bi.getGraphics(); + g2d.setFont(font); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + g2d.setColor(Gui.getColor(MESSAGE_FG_COLOR_ID)); + + textShaper.drawText(g2d); + + g2d.dispose(); + + baseTextImage = bi; + } + + protected Dimension getComponentSize() { + Rectangle r = component.getVisibleRect(); + Dimension size = r.getSize(); + Container parent = component.getParent(); + if (parent instanceof JScrollPane || parent instanceof JViewport) { + // this handles covering the component when it is inside of a scroll pane + size = parent.getSize(); + } + return size; + } + + protected Rectangle getComponentBounds(GGlassPane glassPane) { + + Rectangle r = component.getVisibleRect(); + Point point = r.getLocation(); + Dimension size = r.getSize(); + + Container parent = component.getParent(); + Component coordinateSource = parent; + if (parent instanceof JScrollPane || parent instanceof JViewport) { + // this handles covering the component when it is inside of a scroll pane + point = parent.getLocation(); + size = parent.getSize(); + coordinateSource = parent.getParent(); + } + + point = SwingUtilities.convertPoint(coordinateSource, point, glassPane); + return new Rectangle(point, size); + } + + protected Image updateImage(Graphics2D g2d, double scale) { + + baseTextImage = null; + + createImage(); + + if (baseTextImage == null) { + return null; // this implies an exception happened + } + + int w = baseTextImage.getWidth(null); + int h = baseTextImage.getHeight(null); + + int sw = ((int) (w * scale)); + int sh = ((int) (h * scale)); + + int iw = baseTextImage.getWidth(null); + int ih = baseTextImage.getHeight(null); + + if (iw == sw && ih == sh) { + return baseTextImage; // nothing to change + } + + return ImageUtils.createScaledImage(baseTextImage, sw, sh, 0); + } + + protected void paintOverComponent(Graphics2D g2d, GGlassPane glassPane) { + + Rectangle bounds = getComponentBounds(glassPane); + + float alpha = .7F; // arbitrary; allow some of the background to be visible + AlphaComposite alphaComposite = AlphaComposite + .getInstance(AlphaComposite.SrcOver.getRule(), alpha); + Composite originalComposite = g2d.getComposite(); + Color originalColor = g2d.getColor(); + g2d.setComposite(alphaComposite); + g2d.setColor(component.getBackground()); + + g2d.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); + + g2d.setComposite(originalComposite); + g2d.setColor(originalColor); + } + } + + private class CenterTextPainter extends AbstractTextPainer { + + @Override + public void paint(GGlassPane glassPane, Graphics graphics, double intensity) { + + Graphics2D g2d = (Graphics2D) graphics; + + Image image = updateImage(g2d, intensity); + if (image == null) { + return; // this implies an exception happened + } + + // use visible rectangle to get the correct size when in a scroll pane + Rectangle componentBounds = getComponentBounds(glassPane); + + // without room to draw the message, skip so we don't draw over other components + int imageHeight = image.getHeight(null); + if (imageHeight > componentBounds.height) { + return; + } + + paintOverComponent(g2d, glassPane); + + // note: textHeight and textWidth will vary depending on the intensity + int textHeight = image.getHeight(null); + int textWidth = image.getWidth(null); + int padding = 5; + int middleY = componentBounds.y + (componentBounds.height / 2); + int middleX = componentBounds.x + (componentBounds.width / 2); + int requiredHeight = textHeight + padding; + int requiredWidth = textWidth + padding; + int y = middleY - (requiredHeight / 2); + int x = middleX - (requiredWidth / 2); + + g2d.drawImage(image, x, y, null); + + // debug + // g2d.setColor(Palette.BLUE); + // g2d.drawRect(x, y, textWidth, textHeight); + } + + } + + private class BottomTextPainter extends AbstractTextPainer { + + @Override + public void paint(GGlassPane glassPane, Graphics graphics, double intensity) { + + Graphics2D g2d = (Graphics2D) graphics; + + Image image = updateImage(g2d, intensity); + if (image == null) { + return; // this implies an exception happened + } + + // use visible rectangle to get the correct size when in a scroll pane + Rectangle componentBounds = getComponentBounds(glassPane); + + // without room to draw the message, skip so we don't draw over other components + int imageHeight = image.getHeight(null); + if (imageHeight > componentBounds.height) { + return; + } + + paintOverComponent(g2d, glassPane); + + int textHeight = image.getHeight(null); + int padding = 5; + int bottom = componentBounds.y + componentBounds.height; + int requiredHeight = textHeight + padding; + int y = bottom - requiredHeight; + int x = componentBounds.x + padding; + + g2d.drawImage(image, x, y, null); + + } + + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/util/TextShaper.java b/Ghidra/Framework/Docking/src/main/java/docking/util/TextShaper.java new file mode 100644 index 0000000000..d041dd563e --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/util/TextShaper.java @@ -0,0 +1,340 @@ +/* ### + * 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 docking.util; + +import java.awt.*; +import java.awt.font.*; +import java.text.AttributedCharacterIterator; +import java.text.AttributedString; +import java.util.*; +import java.util.List; + +/** + * A class that will layout text into lines based on the given display size. This class requires + * the graphics context in order to correctly size the text. + */ +public class TextShaper { + + private List lines = new ArrayList<>(); + private Dimension textSize = new Dimension(0, 0); + + private String originalText; + private String clippedText; + private Dimension displaySize = new Dimension(0, 0); + private Graphics2D g2d; + + /** + * Creates a text shaper with the given text, display size and graphics context. + * @param text the text + * @param displaySize the size + * @param g2d the graphics + */ + public TextShaper(String text, Dimension displaySize, Graphics2D g2d) { + this.originalText = text; + this.clippedText = text; + this.displaySize = displaySize; + this.g2d = g2d; + + // Trim blank lines we don't want + // Drop all blank lines before and after the non-blank lines. It seems pointless to paint + // these blank lines. We can change this if there is a valid reason to do so. + text = removeNewlinesAroundText(text); + text = text.replaceAll("\t", " "); + + init(text); + } + + private String removeNewlinesAroundText(String s) { + int first = 0; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c != '\n') { + first = i; + break; + } + } + + s = s.substring(first); + + int last = s.length() - 1; + for (int i = last; i >= 0; i--) { + char c = s.charAt(i); + if (c != '\n') { + last = i; + break; + } + } + return s.substring(0, last + 1); + } + + private void init(String currentText) { + + if (displaySize.width <= 0) { + return; + } + + // create the attributed string needed by the LineBreakMeasurer, setting the font over all + // of the text + AttributedString as = new AttributedString(currentText); + int length = currentText.length(); + Font font = g2d.getFont(); + as.addAttribute(TextAttribute.FONT, font, 0, length); + + // create the LineBreakMeasuerer we will use to split the text at the given width, using the + // rendering environment to get accurate size information + AttributedCharacterIterator paragraph = as.getIterator(); + FontRenderContext frc = g2d.getFontRenderContext(); + LineBreakMeasurer measurer = new LineBreakMeasurer(paragraph, frc); + measurer.setPosition(paragraph.getBeginIndex()); + + int totalHeight = 0; + int largestWidth = 0; + int position = 0; + while ((position = measurer.getPosition()) < paragraph.getEndIndex()) { + + TextShaperLine line = createLine(currentText, measurer); + + // Look ahead to see if the new row we created will fit within the height restrictions. + // If not, we must clip the text and do this work again. + float rowHeight = line.getHeight(); + totalHeight += rowHeight; + if (totalHeight > displaySize.height) { + + // Truncate the original text and try again with the smaller text that we now know + // will fit, adding an ellipsis. + int lineCount = lines.size(); + lines.clear(); + + if (lineCount == 0) { + return; // no room for a single line of text + } + + // clip the text of the and recalculate + int end = position; + int newEnd = end - 3; // 3 for '...' + clippedText = currentText.substring(0, newEnd) + "..."; + + init(clippedText); + return; + } + + lines.add(line); + + largestWidth = Math.max(largestWidth, (int) line.getWidth()); + + } + + textSize = new Dimension(largestWidth, totalHeight); + } + + private TextShaperLine createLine(String currentText, LineBreakMeasurer measurer) { + + // nextOffset() finds the end of the text that fits into the max width + int position = measurer.getPosition(); + int wrappingWidth = displaySize.width; + int nextEnd = measurer.nextOffset(wrappingWidth); + + // special case: look for newlines in the current line and split the text on that + // newline instead so that user-requested newlines are painted + int limit = updateLimitForNewline(currentText, position, nextEnd); + + TextShaperLine line = null; + if (limit == 0) { + // A limit of 0 implies the first character of the text is a newline. Add a full blank + // line to handle that case. This can happen with consecutive newlines or if a line + // happened to break with a leading newline. + Font font = g2d.getFont(); + FontRenderContext frc = g2d.getFontRenderContext(); + LineMetrics lm = font.getLineMetrics("W", frc); + line = new BlankLine(lm.getHeight()); + + // advance the measurer to move past the single newline + measurer.nextLayout(wrappingWidth, position + 1, false); + } + else { + // create a layout with the given limit (either restricted by width or by a newline) + TextLayout layout = measurer.nextLayout(wrappingWidth, position + limit, false); + int nextPosition = measurer.getPosition(); + String lineText = currentText.substring(position, nextPosition); + line = new TextLayoutLine(lineText, layout); + } + + // If we limited the current line to break on the newline, then move past that newline so it + // is not in the next line we process. Since we have broken the line already, we do not + // need that newline character. + movePastTrailingNewline(currentText, measurer); + + return line; + } + + private int updateLimitForNewline(String text, int position, int limit) { + int newline = text.indexOf('\n', position); + if (newline != -1) { + if (newline >= position && newline < limit) { + // newline will be in the current line; break on the newline + return newline - position; + } + } + return limit; + } + + private void movePastTrailingNewline(String text, LineBreakMeasurer measurer) { + int newPosition = measurer.getPosition(); + if (newPosition < text.length()) { + char nextChar = text.charAt(newPosition); + if (nextChar == '\n') { + measurer.setPosition(newPosition + 1); + } + } + } + + /** + * Returns the bounds of the wrapped text of this class + * @return the bounds of the wrapped text of this class + */ + public Dimension getTextSize() { + return textSize; + } + + /** + * Returns true if the text is too large to fit in the original display size + * @return true if the text is too large to fit in the original display size + */ + public boolean isClipped() { + return !Objects.equals(originalText, clippedText); + } + + public List getLines() { + return Collections.unmodifiableList(lines); + } + + /** + * Renders the wrapped text into the graphics used to create this class. + * @param g the graphics into which the text should be painted. + */ + public void drawText(Graphics2D g) { + float dy = 0; + for (TextShaperLine line : lines) { + float y = dy + line.getAscent(); // move the drawing down to the start of the next line + line.draw(g, 0, y); + dy += line.getHeight(); + } + } + + abstract class TextShaperLine { + abstract float getHeight(); + + abstract float getWidth(); + + abstract float getAscent(); + + abstract String getText(); + + abstract boolean isBlank(); + + abstract void draw(Graphics2D g, float x, float y); + } + + private class TextLayoutLine extends TextShaperLine { + private String lineText; + private TextLayout layout; + + TextLayoutLine(String text, TextLayout layout) { + this.lineText = text; + this.layout = layout; + } + + @Override + float getAscent() { + return layout.getAscent(); + } + + @Override + float getHeight() { + return (int) (layout.getAscent() + layout.getDescent() + layout.getLeading()); + } + + @Override + float getWidth() { + return (float) layout.getBounds().getWidth(); + } + + @Override + String getText() { + return lineText; + } + + @Override + void draw(Graphics2D g, float x, float y) { + layout.draw(g, x, y); + } + + @Override + boolean isBlank() { + return false; + } + + @Override + public String toString() { + return lineText; + } + } + + private class BlankLine extends TextShaperLine { + + private float lineHeight; + + BlankLine(float lineHeight) { + this.lineHeight = lineHeight; + } + + @Override + float getAscent() { + return 0; // the value shouldn't matter, since we don't actually draw anything + } + + @Override + float getHeight() { + return lineHeight; + } + + @Override + float getWidth() { + return 0; + } + + @Override + boolean isBlank() { + return true; + } + + @Override + String getText() { + return "\n"; + } + + @Override + void draw(Graphics2D g, float x, float y) { + // nothing to draw + } + + @Override + public String toString() { + return "Blank Line"; + } + } + +} diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/util/TextShaperTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/util/TextShaperTest.java new file mode 100644 index 0000000000..e487148591 --- /dev/null +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/util/TextShaperTest.java @@ -0,0 +1,168 @@ +/* ### + * 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 docking.util; + +import static org.junit.Assert.*; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.List; + +import javax.swing.JLabel; +import javax.swing.JPanel; + +import org.junit.Test; + +import docking.DockingFrame; +import docking.test.AbstractDockingTest; +import docking.util.TextShaper.TextShaperLine; +import generic.theme.Gui; +import ghidra.util.bean.GGlassPane; + +public class TextShaperTest extends AbstractDockingTest { + + // @Test + // for debugging + public void testShowMessage() { + + JLabel label = new JLabel("This is
            some text that
            spans multiple lines."); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(label); + + DockingFrame frame = new DockingFrame("Test Frame"); + frame.getContentPane().add(panel); + GGlassPane glassPane = new GGlassPane(); + frame.setGlassPane(glassPane); + frame.setSize(400, 400); + frame.setVisible(true); + + GGlassPaneMessage glassPaneMessage = new GGlassPaneMessage(label); + glassPaneMessage + .showCenteredMessage( + "This is a test and (newline\n\nhere) some more text to reach the width limit. " + + "More text to (tab here\t\t) to come as we type."); + fail(); + } + + @Test + public void testShaper() { + + BufferedImage tempImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + Graphics2D scratchG2d = (Graphics2D) tempImage.getGraphics(); + Font font = Gui.getFont("font.monospaced").deriveFont(24); + scratchG2d.setFont(font); + + Dimension size = new Dimension(1000, 100); + String message = "This is a message"; + TextShaper shaper = new TextShaper(message, size, scratchG2d); + + List lines = shaper.getLines(); + assertEquals(1, lines.size()); + assertFalse(shaper.isClipped()); + } + + @Test + public void testShaper_LineWrap() { + + BufferedImage tempImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + Graphics2D scratchG2d = (Graphics2D) tempImage.getGraphics(); + Font font = Gui.getFont("font.monospaced").deriveFont(24); + scratchG2d.setFont(font); + + Dimension size = new Dimension(100, 100); + String message = "This is a long message"; + TextShaper shaper = new TextShaper(message, size, scratchG2d); + + List lines = shaper.getLines(); + assertEquals(2, lines.size()); + assertFalse(shaper.isClipped()); + } + + @Test + public void testShaper_NewLine() { + + BufferedImage tempImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + Graphics2D scratchG2d = (Graphics2D) tempImage.getGraphics(); + Font font = Gui.getFont("font.monospaced").deriveFont(24); + scratchG2d.setFont(font); + + Dimension size = new Dimension(1000, 100); + String message = "This is a long\nmessage"; + TextShaper shaper = new TextShaper(message, size, scratchG2d); + + List lines = shaper.getLines(); + assertEquals(2, lines.size()); + assertEquals("This is a long", lines.get(0).getText()); + assertEquals("message", lines.get(1).getText()); + assertFalse(shaper.isClipped()); + } + + @Test + public void testShaper_NewLines_Consecutive() { + + BufferedImage tempImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + Graphics2D scratchG2d = (Graphics2D) tempImage.getGraphics(); + Font font = Gui.getFont("font.monospaced").deriveFont(24); + scratchG2d.setFont(font); + + Dimension size = new Dimension(1000, 100); + String message = "This is a long\n\nmessage"; + TextShaper shaper = new TextShaper(message, size, scratchG2d); + + List lines = shaper.getLines(); + assertEquals(3, lines.size()); + assertEquals("This is a long", lines.get(0).getText()); + assertEquals("\n", lines.get(1).getText()); + assertEquals("message", lines.get(2).getText()); + assertFalse(shaper.isClipped()); + } + + @Test + public void testShaper_NewLines_AroundText() { + + BufferedImage tempImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + Graphics2D scratchG2d = (Graphics2D) tempImage.getGraphics(); + Font font = Gui.getFont("font.monospaced").deriveFont(24); + scratchG2d.setFont(font); + + Dimension size = new Dimension(1000, 100); + String message = "\n\nThis is a long message\n\n"; + TextShaper shaper = new TextShaper(message, size, scratchG2d); + + List lines = shaper.getLines(); + assertEquals(1, lines.size()); + assertFalse(shaper.isClipped()); + } + + @Test + public void testShaper_Tabs() { + + BufferedImage tempImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + Graphics2D scratchG2d = (Graphics2D) tempImage.getGraphics(); + Font font = Gui.getFont("font.monospaced").deriveFont(24); + scratchG2d.setFont(font); + + Dimension size = new Dimension(1000, 100); + String message = "This is a\t\tmessage"; + TextShaper shaper = new TextShaper(message, size, scratchG2d); + + List lines = shaper.getLines(); + assertEquals(1, lines.size()); + assertEquals("This is a message", lines.get(0).getText()); + assertFalse(shaper.isClipped()); + } +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/timer/GhidraTimerFactory.java b/Ghidra/Framework/Generic/src/main/java/generic/timer/GhidraTimerFactory.java index db284049b7..eb09db8dfc 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/timer/GhidraTimerFactory.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/timer/GhidraTimerFactory.java @@ -1,13 +1,12 @@ /* ### * 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. * 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. @@ -16,51 +15,25 @@ */ package generic.timer; -import ghidra.util.SystemUtilities; +import java.util.Timer; +import ghidra.util.SystemUtilities; +import ghidra.util.timer.GTimer; + +/** + * Creates a new {@link GhidraTimer} appropriate for a headed or headless environment. + *

            + * If running a headed environment, the callback will happen on the Swing thread. Otherwise, the + * callback will happen on the non-Swing {@link Timer} thread. + *

            + * See also {@link GTimer} + */ public class GhidraTimerFactory { public static GhidraTimer getGhidraTimer(int initialDelay, int delay, TimerCallback callback) { if (SystemUtilities.isInHeadlessMode()) { return new GhidraSwinglessTimer(initialDelay, delay, callback); - + } return new GhidraSwingTimer(initialDelay, delay, callback); } - - public static void main(String[] args) throws InterruptedException { - System.setProperty(SystemUtilities.HEADLESS_PROPERTY, "true"); - final GhidraTimer t = GhidraTimerFactory.getGhidraTimer(500,500,null); - t.setDelay(500); - TimerCallback callback1 = new TimerCallback() { - int i = 0; - public void timerFired() { - System.out.println("A: "+i); - if (++i == 20) { - t.stop(); - } - } - }; - t.setTimerCallback(callback1); - t.start(); - - final GhidraTimer t2 = GhidraTimerFactory.getGhidraTimer(250, 1000, null); - - TimerCallback callback2 = new TimerCallback() { - int i = 0; - public void timerFired() { - System.out.println("B: "+i); - if (++i == 100) { - t2.stop(); - } - } - }; - t2.setInitialDelay(250); - t2.setTimerCallback(callback2); - t2.start(); - - - - Thread.sleep(20000); - - } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/timer/GTimer.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/timer/GTimer.java index bb80759137..7a5b9eccdf 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/timer/GTimer.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/timer/GTimer.java @@ -4,9 +4,9 @@ * 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. @@ -18,12 +18,17 @@ package ghidra.util.timer; import java.util.Timer; import java.util.TimerTask; +import generic.timer.GhidraTimerFactory; import ghidra.util.Msg; /** * A class to schedule {@link Runnable}s to run after some delay, optionally repeating. This class * uses a {@link Timer} internally to schedule work. Clients of this class are given a monitor * that allows them to check on the state of the runnable, as well as to cancel the runnable. + *

            + * Note: The callback will be called on the {@link Timer}'s thread. + *

            + * See also {@link GhidraTimerFactory} */ public class GTimer { private static Timer timer; diff --git a/Ghidra/Framework/Gui/src/main/java/ghidra/util/bean/GGlassPane.java b/Ghidra/Framework/Gui/src/main/java/ghidra/util/bean/GGlassPane.java index 8b67bf48c1..cb117b955d 100644 --- a/Ghidra/Framework/Gui/src/main/java/ghidra/util/bean/GGlassPane.java +++ b/Ghidra/Framework/Gui/src/main/java/ghidra/util/bean/GGlassPane.java @@ -4,9 +4,9 @@ * 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. @@ -69,6 +69,7 @@ public class GGlassPane extends JComponent { * @param painter the painter to add */ public void addPainter(GGlassPanePainter painter) { + painters.remove(painter); painters.add(painter); repaint(); } @@ -99,6 +100,7 @@ public class GGlassPane extends JComponent { /** * Sets the busy state of all glass panes created in the VM. + * @param isBusy the busy state of all glass panes created in the VM. */ public static void setAllGlassPanesBusy(boolean isBusy) { for (GGlassPane glassPane : systemGlassPanes) { @@ -108,6 +110,7 @@ public class GGlassPane extends JComponent { /** * Returns true if this glass pane is blocking user input. + * @return true if this glass pane is blocking user input. */ public boolean isBusy() { return isBusy; From 8458d5eea19fab10ff10d0e6d3b9ce33286de46c Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Wed, 4 Sep 2024 11:33:05 -0400 Subject: [PATCH 46/53] GP-4879: Fixing typo in pspec --- Ghidra/Processors/M16C/data/languages/M16C_80.pspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ghidra/Processors/M16C/data/languages/M16C_80.pspec b/Ghidra/Processors/M16C/data/languages/M16C_80.pspec index 012579c0eb..caf732d193 100644 --- a/Ghidra/Processors/M16C/data/languages/M16C_80.pspec +++ b/Ghidra/Processors/M16C/data/languages/M16C_80.pspec @@ -288,7 +288,7 @@ - + From 28ea0c99f023f896b72f13618cf79b2afc6fde1f Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:52:40 -0400 Subject: [PATCH 47/53] GP-4891 - Fixed an exception in the Stack editor when editing and using the down arrow --- .../compositeeditor/CompositeEditorPanel.java | 11 +++++---- .../core/stackeditor/StackEditorModel.java | 14 ++++------- .../compositeeditor/AbstractEditorTest.java | 14 +++++++++-- .../stackeditor/StackEditorProvider1Test.java | 23 +++++++++++++++++++ 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java index 98896f03a4..19eb46de69 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java @@ -486,8 +486,8 @@ public abstract class CompositeEditorPanel extends JPanel if (index >= 0) { row = index; table.setRowSelectionInterval(row, row); - if (model.isCellEditable(index, modelColumn)) { - return beginEditField(model.getRow(), model.getColumn()); + if (model.isCellEditable(row, modelColumn)) { + return beginEditField(row, modelColumn); } } return false; @@ -500,6 +500,7 @@ public abstract class CompositeEditorPanel extends JPanel protected boolean editBelowField() { int row = model.getRow(); int modelColumn = model.getColumn(); + // Get the current row (index) and column (fieldNum). int index = row; index++; @@ -508,8 +509,8 @@ public abstract class CompositeEditorPanel extends JPanel if (index < numComps) { row = index; table.setRowSelectionInterval(row, row); - if (model.isCellEditable(index, modelColumn)) { - return beginEditField(model.getRow(), model.getColumn()); + if (model.isCellEditable(row, modelColumn)) { + return beginEditField(row, modelColumn); } } return false; @@ -602,7 +603,7 @@ public abstract class CompositeEditorPanel extends JPanel model.setColumn(modelIndex); } else { - model.setColumn(-1); + model.setColumn(e.getFirstIndex()); } }); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java index 183d162308..71607a065e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java @@ -406,22 +406,16 @@ public class StackEditorModel extends CompositeEditorModel { if (columnIndex == LENGTH) { return false; } - if ((rowIndex < 0) || (rowIndex >= getRowCount())) { + if (rowIndex < 0 || rowIndex >= getRowCount()) { + return false; + } + if (columnIndex < 0 || columnIndex >= getColumnCount()) { return false; } DataTypeComponent dtc = stackDt.getComponent(rowIndex); if (dtc == null) { return false; } -// if (columnIndex != NAME) { -// int offset = dtc.getOffset(); -// if (!hasCustomParameterStorage && originalStack.isParameterOffset(offset)) { -// return false; -// } -// } -// if (dtc.getDataType() instanceof StackPieceDataType) { -// return false; -// } boolean notDefined = (stackDt.getDefinedComponentAtOrdinal(rowIndex) == null); return !(notDefined && (columnIndex == OFFSET)); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractEditorTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractEditorTest.java index 448f89aca1..4296fa4dcd 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractEditorTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractEditorTest.java @@ -4,9 +4,9 @@ * 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. @@ -474,6 +474,16 @@ public abstract class AbstractEditorTest extends AbstractGhidraHeadedIntegration waitForSwing(); } + protected void downArrow() { + triggerActionKey(getTable(), 0, KeyEvent.VK_DOWN); + waitForSwing(); + } + + protected void downArrow(JComponent component) { + triggerActionKey(component, 0, KeyEvent.VK_DOWN); + waitForSwing(); + } + protected void endKey() { triggerActionKey(getKeyEventDestination(), 0, KeyEvent.VK_END); waitForSwing(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java index 76afce5a1c..f743505a68 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java @@ -139,6 +139,29 @@ public class StackEditorProvider1Test extends AbstractStackEditorProviderTest { assertStackEditorHidden(function); } + @Test + public void testEditUsingArrowKeys() throws Exception { + + init(SIMPLE_STACK); + + int row = 1; + int column = model.getNameColumn(); + clickTableCell(getTable(), row, column, 1); + assertColumn(column); + performAction(editFieldAction, provider, true); + + // change name + JTextField tf = getCellEditorTextField(); + setText(tf, tf.getText() + "change"); + + downArrow(tf); + assertRow(row + 1); + assertColumn(column); + + assertIsEditingField(row + 1, column); + escape(); + } + @Test public void testDeleteAssociatedFunction() throws Exception { Window dialog; From 86cbbbda66d847a53b5dbf9691e753c7e9f3818c Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 5 Sep 2024 08:35:13 -0400 Subject: [PATCH 48/53] GP-0: Fix tests. --- .../DebuggerTraceManagerServicePlugin.java | 14 +++++++++----- .../gui/AbstractGhidraHeadedDebuggerTest.java | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 821cd14e34..2319d77722 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -984,16 +984,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin navigationHistoryService.clear(trace.getProgramView()); } synchronized (listenersByTrace) { - trace.release(this); lastCoordsByTrace.remove(trace); trace.removeListener(listenersByTrace.remove(trace)); //Msg.debug(this, "Remaining Consumers of " + trace + ": " + trace.getConsumerList()); } - if (current.getTrace() == trace) { - activate(DebuggerCoordinates.NOWHERE, ActivationCause.ACTIVATE_DEFAULT); + try { + if (current.getTrace() == trace) { + activate(DebuggerCoordinates.NOWHERE, ActivationCause.ACTIVATE_DEFAULT); + } + else { + contextChanged(); + } } - else { - contextChanged(); + finally { + trace.release(this); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java index c4ca0f3b7a..8e662d2a29 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java @@ -655,7 +655,7 @@ public abstract class AbstractGhidraHeadedDebuggerTest if (tb != null) { if (traceManager != null && traceManager.getOpenTraces().contains(tb.trace)) { - traceManager.closeTrace(tb.trace); + traceManager.closeTraceNoConfirm(tb.trace); } tb.close(); } From 16ff4c4d0830be40155eebc35fdbe43a5c44df31 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:35:28 -0400 Subject: [PATCH 49/53] GP-4847: Unify Debugger param dialogs. Prefixed integers allowed. --- .../data/debugger-launchers/remote-gdb.sh | 29 +- .../main/java/ghidra/debug/api/ValStr.java | 59 ++ .../api/model/DebuggerProgramLaunchOffer.java | 9 +- .../debug/api/tracermi/LaunchParameter.java | 106 +++ .../api/tracermi/TraceRmiLaunchOffer.java | 12 +- .../flatapi/FlatDebuggerRecorderAPI.java | 13 +- .../debug/flatapi/FlatDebuggerRmiAPI.java | 16 +- .../RemoteMethodInvocationDialog.java | 407 ++-------- .../connection/TraceRmiConnectDialog.java | 46 ++ .../TraceRmiConnectionManagerProvider.java | 47 +- .../AbstractScriptTraceRmiLaunchOffer.java | 20 +- .../launcher/AbstractTraceRmiLaunchOffer.java | 206 ++--- .../BatchScriptTraceRmiLaunchOffer.java | 26 +- .../launcher/ScriptAttributesParser.java | 392 +++++---- .../launcher/TraceRmiLaunchDialog.java | 86 ++ .../UnixShellScriptTraceRmiLaunchOffer.java | 21 +- .../service/tracermi/TraceRmiHandler.java | 12 +- .../service/tracermi/TraceRmiTarget.java | 31 +- ...TraceRmiConnectionManagerProviderTest.java | 28 +- .../gui/AbstractDebuggerParameterDialog.java | 767 ++++++++++++++++++ .../debug/gui/action/ByModuleAutoMapSpec.java | 12 + .../gui/objects/DebuggerObjectsProvider.java | 20 +- .../DebuggerMethodInvocationDialog.java | 675 ++------------- .../service/model/TraceRecorderTarget.java | 29 +- .../AbstractDebuggerProgramLaunchOffer.java | 72 +- .../core/debug/utils/MiscellaneousUtils.java | 20 +- .../debug/gui/InvocationDialogHelper.java | 94 +++ .../components/InvocationDialogHelper.java | 48 -- .../java/ghidra/util/NumericUtilities.java | 45 + .../ghidra/util/NumericUtilitiesTest.java | 90 +- ...ceRmiLauncherServicePluginScreenShots.java | 15 +- .../ScriptTraceRmiLaunchOfferTest.java | 584 +++++++++++++ .../launcher/TestTraceRmiLaunchOpinion.java | 27 +- .../launcher/TraceRmiLaunchDialogTest.java | 255 ++++++ .../TraceRmiLauncherServicePluginTest.java | 23 +- .../debug/flatapi/FlatDebuggerRmiAPITest.java | 9 +- 36 files changed, 2842 insertions(+), 1509 deletions(-) create mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/ValStr.java create mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/LaunchParameter.java create mode 100644 Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectDialog.java create mode 100644 Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialog.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerParameterDialog.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/InvocationDialogHelper.java delete mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/objects/components/InvocationDialogHelper.java create mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptTraceRmiLaunchOfferTest.java create mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialogTest.java diff --git a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/remote-gdb.sh b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/remote-gdb.sh index 4ef8f370a9..3048e2a217 100755 --- a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/remote-gdb.sh +++ b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/remote-gdb.sh @@ -1,18 +1,18 @@ #!/usr/bin/env bash ## ### -# 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. +# 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. ## #@title remote gdb #@no-image @@ -29,7 +29,7 @@ #@enum TargetType:str remote extended-remote #@env OPT_TARGET_TYPE:TargetType="remote" "Target" "The type of remote target" #@env OPT_HOST:str="localhost" "Host" "The hostname of the target" -#@env OPT_PORT:str="9999" "Port" "The host's listening port" +#@env OPT_PORT:int=9999 "Port" "The host's listening port" #@env OPT_ARCH:str="" "Architecture (optional)" "Target architecture override" #@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH." @@ -60,6 +60,7 @@ fi -ex "show version" \ -ex "python import ghidragdb" \ $archcmd \ + -ex "echo Connecting to $OPT_HOST:$OPT_PORT... " \ -ex "target $OPT_TARGET_TYPE $OPT_HOST:$OPT_PORT" \ -ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \ -ex "ghidra trace start" \ diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/ValStr.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/ValStr.java new file mode 100644 index 0000000000..d9631d3b91 --- /dev/null +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/ValStr.java @@ -0,0 +1,59 @@ +/* ### + * 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.debug.api; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +public record ValStr(T val, String str) { + + public interface Decoder { + default ValStr decodeValStr(String string) { + return new ValStr<>(decode(string), string); + } + + T decode(String string); + } + + public static ValStr str(String value) { + return new ValStr<>(value, value); + } + + public static ValStr from(T value) { + return new ValStr<>(value, value == null ? "" : value.toString()); + } + + @SuppressWarnings("unchecked") + public static ValStr cast(Class cls, ValStr value) { + if (cls.isInstance(value.val)) { + return (ValStr) value; + } + return new ValStr<>(cls.cast(value.val), value.str); + } + + public static Map> fromPlainMap(Map map) { + return map.entrySet() + .stream() + .collect(Collectors.toMap(Entry::getKey, e -> ValStr.from(e.getValue()))); + } + + public static Map toPlainMap(Map> map) { + return map.entrySet() + .stream() + .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().val())); + } +} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerProgramLaunchOffer.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerProgramLaunchOffer.java index 7b43e4e5a4..9e72519c55 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerProgramLaunchOffer.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerProgramLaunchOffer.java @@ -4,9 +4,9 @@ * 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. @@ -24,6 +24,7 @@ import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.target.TargetLauncher; import ghidra.dbg.target.TargetObject; +import ghidra.debug.api.ValStr; import ghidra.util.task.TaskMonitor; /** @@ -117,8 +118,8 @@ public interface DebuggerProgramLaunchOffer { * @param relPrompt describes the timing of this callback relative to prompting the user * @return the adjusted arguments */ - default Map configureLauncher(TargetLauncher launcher, - Map arguments, RelPrompt relPrompt) { + default Map> configureLauncher(TargetLauncher launcher, + Map> arguments, RelPrompt relPrompt) { return arguments; } } diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/LaunchParameter.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/LaunchParameter.java new file mode 100644 index 0000000000..d2cc2de391 --- /dev/null +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/LaunchParameter.java @@ -0,0 +1,106 @@ +/* ### + * 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.debug.api.tracermi; + +import java.util.*; + +import ghidra.debug.api.ValStr; + +public record LaunchParameter(Class type, String name, String display, String description, + boolean required, List choices, ValStr defaultValue, ValStr.Decoder decoder) { + + public static LaunchParameter create(Class type, String name, String display, + String description, boolean required, ValStr defaultValue, + ValStr.Decoder decoder) { + return new LaunchParameter<>(type, name, display, description, required, List.of(), + defaultValue, decoder); + } + + public static LaunchParameter choices(Class type, String name, String display, + String description, Collection choices, ValStr defaultValue) { + return new LaunchParameter<>(type, name, display, description, false, + List.copyOf(new LinkedHashSet<>(choices)), defaultValue, str -> { + for (T t : choices) { + if (t.toString().equals(str)) { + return t; + } + } + return null; + }); + } + + public static Map> mapOf(Collection> parameters) { + Map> result = new LinkedHashMap<>(); + for (LaunchParameter param : parameters) { + LaunchParameter exists = result.put(param.name(), param); + if (exists != null) { + throw new IllegalArgumentException( + "Duplicate names in parameter map: first=%s, second=%s".formatted(exists, + param)); + } + } + return Collections.unmodifiableMap(result); + } + + public static Map> validateArguments( + Map> parameters, Map> arguments) { + if (!parameters.keySet().containsAll(arguments.keySet())) { + Set extraneous = new TreeSet<>(arguments.keySet()); + extraneous.removeAll(parameters.keySet()); + throw new IllegalArgumentException("Extraneous parameters: " + extraneous); + } + + Map typeErrors = null; + for (Map.Entry> ent : arguments.entrySet()) { + String name = ent.getKey(); + ValStr val = ent.getValue(); + LaunchParameter param = parameters.get(name); + if (val.val() != null && !param.type.isAssignableFrom(val.val().getClass())) { + if (typeErrors == null) { + typeErrors = new LinkedHashMap<>(); + } + typeErrors.put(name, "val '%s' is not a %s".formatted(val.val(), param.type())); + } + } + if (typeErrors != null) { + throw new IllegalArgumentException("Type errors: " + typeErrors); + } + return arguments; + } + + public static Map> mapOf(LaunchParameter... parameters) { + return mapOf(Arrays.asList(parameters)); + } + + public ValStr decode(String string) { + return decoder.decodeValStr(string); + } + + public ValStr get(Map> arguments) { + if (arguments.containsKey(name)) { + return ValStr.cast(type, arguments.get(name)); + } + if (required) { + throw new IllegalArgumentException( + "Missing required parameter '%s' (%s)".formatted(display, name)); + } + return defaultValue; + } + + public void set(Map> arguments, ValStr value) { + arguments.put(name, value); + } +} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/TraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/TraceRmiLaunchOffer.java index 85c16bbeb2..6351a965c7 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/TraceRmiLaunchOffer.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/TraceRmiLaunchOffer.java @@ -4,9 +4,9 @@ * 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. @@ -20,7 +20,7 @@ import java.util.Map; import javax.swing.Icon; -import ghidra.dbg.target.TargetMethod.ParameterDescription; +import ghidra.debug.api.ValStr; import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; import ghidra.util.HelpLocation; @@ -150,8 +150,8 @@ public interface TraceRmiLaunchOffer { * @param relPrompt describes the timing of this callback relative to prompting the user * @return the adjusted arguments */ - default Map configureLauncher(TraceRmiLaunchOffer offer, - Map arguments, RelPrompt relPrompt) { + default Map> configureLauncher(TraceRmiLaunchOffer offer, + Map> arguments, RelPrompt relPrompt) { return arguments; } } @@ -293,7 +293,7 @@ public interface TraceRmiLaunchOffer { * * @return the parameters */ - Map> getParameters(); + Map> getParameters(); /** * Check if this offer requires an open program diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRecorderAPI.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRecorderAPI.java index 118732a5db..33a2b03a63 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRecorderAPI.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRecorderAPI.java @@ -4,9 +4,9 @@ * 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. @@ -28,6 +28,7 @@ import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher; import ghidra.dbg.target.TargetSteppable.TargetStepKind; import ghidra.dbg.util.PathUtils; +import ghidra.debug.api.ValStr; import ghidra.debug.api.model.DebuggerProgramLaunchOffer; import ghidra.debug.api.model.DebuggerProgramLaunchOffer.*; import ghidra.debug.api.model.TraceRecorder; @@ -578,10 +579,10 @@ public interface FlatDebuggerRecorderAPI extends FlatDebuggerAPI { try { return waitOn(offer.launchProgram(monitor, PromptMode.NEVER, new LaunchConfigurator() { @Override - public Map configureLauncher(TargetLauncher launcher, - Map arguments, RelPrompt relPrompt) { - Map adjusted = new HashMap<>(arguments); - adjusted.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, commandLine); + public Map> configureLauncher(TargetLauncher launcher, + Map> arguments, RelPrompt relPrompt) { + Map> adjusted = new HashMap<>(arguments); + adjusted.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, ValStr.str(commandLine)); return adjusted; } })); diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRmiAPI.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRmiAPI.java index e4f71f1ad3..479b96d5d4 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRmiAPI.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRmiAPI.java @@ -4,9 +4,9 @@ * 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. @@ -16,8 +16,10 @@ package ghidra.debug.flatapi; import java.util.*; +import java.util.Map.Entry; import ghidra.app.services.TraceRmiLauncherService; +import ghidra.debug.api.ValStr; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*; import ghidra.program.model.listing.Program; @@ -116,13 +118,15 @@ public interface FlatDebuggerRmiAPI extends FlatDebuggerAPI { TaskMonitor monitor) { return offer.launchProgram(monitor, new LaunchConfigurator() { @Override - public Map configureLauncher(TraceRmiLaunchOffer offer, - Map arguments, RelPrompt relPrompt) { + public Map> configureLauncher(TraceRmiLaunchOffer offer, + Map> arguments, RelPrompt relPrompt) { if (arguments.isEmpty()) { return arguments; } - Map args = new HashMap<>(arguments); - args.putAll(overrideArgs); + Map> args = new HashMap<>(arguments); + for (Entry ent : overrideArgs.entrySet()) { + args.put(ent.getKey(), ValStr.from(ent.getValue())); + } return args; } }); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/RemoteMethodInvocationDialog.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/RemoteMethodInvocationDialog.java index 3c582a5dfc..cb049e0540 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/RemoteMethodInvocationDialog.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/RemoteMethodInvocationDialog.java @@ -4,9 +4,9 @@ * 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. @@ -15,397 +15,128 @@ */ package ghidra.app.plugin.core.debug.gui.tracermi; -import java.awt.BorderLayout; import java.awt.Component; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Graphics; -import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.beans.PropertyEditor; -import java.beans.PropertyEditorManager; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.beans.*; +import java.util.*; -import javax.swing.BorderFactory; import javax.swing.Icon; -import javax.swing.JButton; import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.border.EmptyBorder; -import org.apache.commons.collections4.BidiMap; -import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.text.StringEscapeUtils; -import org.jdom.Element; - -import docking.DialogComponentProvider; -import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget; -import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils; +import ghidra.app.plugin.core.debug.gui.AbstractDebuggerParameterDialog; +import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget.Missing; +import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.SchemaContext; +import ghidra.debug.api.ValStr; import ghidra.debug.api.tracermi.RemoteParameter; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.AutoConfigState.ConfigStateField; import ghidra.framework.plugintool.PluginTool; -import ghidra.util.Msg; -import ghidra.util.layout.PairLayout; +import ghidra.trace.model.target.TraceObject; -public class RemoteMethodInvocationDialog extends DialogComponentProvider - implements PropertyChangeListener { - private static final String KEY_MEMORIZED_ARGUMENTS = "memorizedArguments"; +public class RemoteMethodInvocationDialog extends AbstractDebuggerParameterDialog { - static class ChoicesPropertyEditor implements PropertyEditor { - private final List choices; - private final String[] tags; - - private final List listeners = new ArrayList<>(); - - private Object value; - - public ChoicesPropertyEditor(Set choices) { - this.choices = List.copyOf(choices); - this.tags = choices.stream().map(Objects::toString).toArray(String[]::new); - } + /** + * TODO: Make this a proper editor which can browse and select objects of a required schema. + */ + public static class TraceObjectEditor extends PropertyEditorSupport { + private final JLabel unmodifiableField = new JLabel(); @Override public void setValue(Object value) { - if (Objects.equals(value, this.value)) { + super.setValue(value); + if (value == null) { + unmodifiableField.setText(""); return; } - if (!choices.contains(value)) { - throw new IllegalArgumentException("Unsupported value: " + value); + if (!(value instanceof TraceObject obj)) { + throw new IllegalArgumentException(); } - Object oldValue; - List listeners; - synchronized (this.listeners) { - oldValue = this.value; - this.value = value; - if (this.listeners.isEmpty()) { - return; - } - listeners = List.copyOf(this.listeners); - } - PropertyChangeEvent evt = new PropertyChangeEvent(this, null, oldValue, value); - for (PropertyChangeListener l : listeners) { - l.propertyChange(evt); - } - } - - @Override - public Object getValue() { - return value; - } - - @Override - public boolean isPaintable() { - return false; - } - - @Override - public void paintValue(Graphics gfx, Rectangle box) { - // Not paintable - } - - @Override - public String getJavaInitializationString() { - if (value == null) { - return "null"; - } - if (value instanceof String str) { - return "\"" + StringEscapeUtils.escapeJava(str) + "\""; - } - return Objects.toString(value); - } - - @Override - public String getAsText() { - return Objects.toString(value); - } - - @Override - public void setAsText(String text) throws IllegalArgumentException { - int index = ArrayUtils.indexOf(tags, text); - if (index < 0) { - throw new IllegalArgumentException("Unsupported value: " + text); - } - setValue(choices.get(index)); - } - - @Override - public String[] getTags() { - return tags.clone(); - } - - @Override - public Component getCustomEditor() { - return null; + unmodifiableField.setText(obj.getCanonicalPath().toString()); } @Override public boolean supportsCustomEditor() { - return false; + return true; } @Override - public void addPropertyChangeListener(PropertyChangeListener listener) { - synchronized (listeners) { - listeners.add(listener); - } - } - - @Override - public void removePropertyChangeListener(PropertyChangeListener listener) { - synchronized (listeners) { - listeners.remove(listener); - } + public Component getCustomEditor() { + return unmodifiableField; } } - record NameTypePair(String name, Class type) { - public static NameTypePair fromParameter(SchemaContext ctx, RemoteParameter parameter) { - return new NameTypePair(parameter.name(), ctx.getSchema(parameter.type()).getType()); - } - - public static NameTypePair fromString(String name) throws ClassNotFoundException { - String[] parts = name.split(",", 2); - if (parts.length != 2) { - // This appears to be a bad assumption - empty fields results in solitary labels - return new NameTypePair(parts[0], String.class); - //throw new IllegalArgumentException("Could not parse name,type"); - } - return new NameTypePair(parts[0], Class.forName(parts[1])); - } + static { + PropertyEditorManager.registerEditor(TraceObject.class, TraceObjectEditor.class); } - private final BidiMap paramEditors = - new DualLinkedHashBidiMap<>(); + private final SchemaContext ctx; - private JPanel panel; - private JLabel descriptionLabel; - private JPanel pairPanel; - private PairLayout layout; - - protected JButton invokeButton; - protected JButton resetButton; - - private final PluginTool tool; - private SchemaContext ctx; - private Map parameters; - private Map defaults; - - // TODO: Not sure this is the best keying, but I think it works. - private Map memorized = new HashMap<>(); - private Map arguments; - - public RemoteMethodInvocationDialog(PluginTool tool, String title, String buttonText, - Icon buttonIcon) { - super(title, true, true, true, false); - this.tool = tool; - - populateComponents(buttonText, buttonIcon); - setRememberSize(false); - } - - protected Object computeMemorizedValue(RemoteParameter parameter) { - return memorized.computeIfAbsent(NameTypePair.fromParameter(ctx, parameter), - ntp -> parameter.getDefaultValue()); - } - - public Map promptArguments(SchemaContext ctx, - Map parameterMap, Map defaults) { - setParameters(ctx, parameterMap); - setDefaults(defaults); - tool.showDialog(this); - - return getArguments(); - } - - public void setParameters(SchemaContext ctx, Map parameterMap) { + public RemoteMethodInvocationDialog(PluginTool tool, SchemaContext ctx, String title, + String buttonText, Icon buttonIcon) { + super(tool, title, buttonText, buttonIcon); this.ctx = ctx; - this.parameters = parameterMap; - populateOptions(); - } - - public void setDefaults(Map defaults) { - this.defaults = defaults; - } - - private void populateComponents(String buttonText, Icon buttonIcon) { - panel = new JPanel(new BorderLayout()); - panel.setBorder(new EmptyBorder(10, 10, 10, 10)); - - layout = new PairLayout(5, 5); - pairPanel = new JPanel(layout); - - JPanel centering = new JPanel(new FlowLayout(FlowLayout.CENTER)); - JScrollPane scrolling = new JScrollPane(centering, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, - JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - //scrolling.setPreferredSize(new Dimension(100, 130)); - panel.add(scrolling, BorderLayout.CENTER); - centering.add(pairPanel); - - descriptionLabel = new JLabel(); - descriptionLabel.setMaximumSize(new Dimension(300, 100)); - panel.add(descriptionLabel, BorderLayout.NORTH); - - addWorkPanel(panel); - - invokeButton = new JButton(buttonText, buttonIcon); - addButton(invokeButton); - resetButton = new JButton("Reset", DebuggerResources.ICON_REFRESH); - addButton(resetButton); - addCancelButton(); - - invokeButton.addActionListener(this::invoke); - resetButton.addActionListener(this::reset); } @Override - protected void cancelCallback() { - this.arguments = null; - close(); + protected String parameterName(RemoteParameter parameter) { + return parameter.name(); } - protected void invoke(ActionEvent evt) { - this.arguments = collectArguments(); - close(); - } - - private void reset(ActionEvent evt) { - this.arguments = new HashMap<>(); - for (RemoteParameter param : parameters.values()) { - if (defaults.containsKey(param.name())) { - arguments.put(param.name(), defaults.get(param.name())); - } - else { - arguments.put(param.name(), param.getDefaultValue()); - } + @Override + protected Class parameterType(RemoteParameter parameter) { + Class type = ctx.getSchema(parameter.type()).getType(); + if (TargetObject.class.isAssignableFrom(type)) { + return TraceObject.class; } - populateValues(); + return type; } - protected PropertyEditor createEditor(RemoteParameter param) { - Class type = ctx.getSchema(param.type()).getType(); - PropertyEditor editor = PropertyEditorManager.findEditor(type); - if (editor != null) { - return editor; - } - Msg.warn(this, "No editor for " + type + "? Trying String instead"); - return PropertyEditorManager.findEditor(String.class); + @Override + protected String parameterLabel(RemoteParameter parameter) { + return "".equals(parameter.display()) ? parameter.name() : parameter.display(); } - void populateOptions() { - pairPanel.removeAll(); - paramEditors.clear(); - for (RemoteParameter param : parameters.values()) { - String text = param.display().equals("") ? param.name() : param.display(); - JLabel label = new JLabel(text); - label.setToolTipText(param.description()); - pairPanel.add(label); - - PropertyEditor editor = createEditor(param); - Object val = computeMemorizedValue(param); - if (val == null || val.equals(TraceRmiTarget.Missing.MISSING)) { - editor.setValue(""); - } else { - editor.setValue(val); - } - editor.addPropertyChangeListener(this); - pairPanel.add(MiscellaneousUtils.getEditorComponent(editor)); - paramEditors.put(param, editor); - } + @Override + protected String parameterToolTip(RemoteParameter parameter) { + return parameter.description(); } - void populateValues() { - for (Map.Entry ent : arguments.entrySet()) { - RemoteParameter param = parameters.get(ent.getKey()); - if (param == null) { - Msg.warn(this, "No parameter for argument: " + ent); - continue; - } - PropertyEditor editor = paramEditors.get(param); - editor.setValue(ent.getValue()); - } + @Override + protected ValStr parameterDefault(RemoteParameter parameter) { + return ValStr.from(parameter.getDefaultValue()); } - protected Map collectArguments() { - Map map = new LinkedHashMap<>(); - for (RemoteParameter param : paramEditors.keySet()) { - Object val = memorized.get(NameTypePair.fromParameter(ctx, param)); - if (val != null) { - map.put(param.name(), val); - } - } - return map; + @Override + protected Collection parameterChoices(RemoteParameter parameter) { + return Set.of(); } - public Map getArguments() { + @Override + protected Map> validateArguments(Map parameters, + Map> arguments) { return arguments; } - public void setMemorizedArgument(String name, Class type, T value) { - if (value == null) { - return; - } - memorized.put(new NameTypePair(name, type), value); - } - - public T getMemorizedArgument(String name, Class type) { - return type.cast(memorized.get(new NameTypePair(name, type))); + @Override + protected void parameterSaveValue(RemoteParameter parameter, SaveState state, String key, + ValStr value) { + ConfigStateField.putState(state, parameterType(parameter).asSubclass(Object.class), key, + value.val()); } @Override - public void propertyChange(PropertyChangeEvent evt) { - PropertyEditor editor = (PropertyEditor) evt.getSource(); - RemoteParameter param = paramEditors.getKey(editor); - memorized.put(NameTypePair.fromParameter(ctx, param), editor.getValue()); + protected ValStr parameterLoadValue(RemoteParameter parameter, SaveState state, String key) { + return ValStr.from( + ConfigStateField.getState(state, parameterType(parameter), key)); } - public void writeConfigState(SaveState saveState) { - SaveState subState = new SaveState(); - for (Map.Entry ent : memorized.entrySet()) { - NameTypePair ntp = ent.getKey(); - ConfigStateField.putState(subState, ntp.type().asSubclass(Object.class), ntp.name(), - ent.getValue()); - } - saveState.putXmlElement(KEY_MEMORIZED_ARGUMENTS, subState.saveToXml()); - } - - public void readConfigState(SaveState saveState) { - Element element = saveState.getXmlElement(KEY_MEMORIZED_ARGUMENTS); - if (element == null) { - return; - } - SaveState subState = new SaveState(element); - for (String name : subState.getNames()) { - try { - NameTypePair ntp = NameTypePair.fromString(name); - memorized.put(ntp, ConfigStateField.getState(subState, ntp.type(), ntp.name())); - } - catch (Exception e) { - Msg.error(this, "Error restoring memorized parameter " + name, e); - } - } - } - - public void setDescription(String htmlDescription) { - if (htmlDescription == null) { - descriptionLabel.setBorder(BorderFactory.createEmptyBorder()); - descriptionLabel.setText(""); - } - else { - descriptionLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); - descriptionLabel.setText(htmlDescription); - } + @Override + protected void setEditorValue(PropertyEditor editor, RemoteParameter param, ValStr val) { + ValStr v = switch (val.val()) { + case Missing __ -> new ValStr<>(null, ""); + case TraceObject obj -> new ValStr<>(obj, obj.getCanonicalPath().toString()); + default -> val; + }; + super.setEditorValue(editor, param, v); } } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectDialog.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectDialog.java new file mode 100644 index 0000000000..99960efc18 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectDialog.java @@ -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.plugin.core.debug.gui.tracermi.connection; + +import java.util.Map; + +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLaunchDialog; +import ghidra.debug.api.ValStr; +import ghidra.debug.api.tracermi.LaunchParameter; +import ghidra.framework.plugintool.PluginTool; + +public class TraceRmiConnectDialog extends TraceRmiLaunchDialog { + + static final LaunchParameter PARAM_ADDRESS = + LaunchParameter.create(String.class, "address", + "Host/Address", "Address or hostname for interface(s) to listen on", + true, ValStr.str("localhost"), str -> str); + static final LaunchParameter PARAM_PORT = + LaunchParameter.create(Integer.class, "port", + "Port", "TCP port number, 0 for ephemeral", + true, ValStr.from(0), Integer::decode); + private static final Map> PARAMETERS = + LaunchParameter.mapOf(PARAM_ADDRESS, PARAM_PORT); + + public TraceRmiConnectDialog(PluginTool tool, String title, String buttonText) { + super(tool, title, buttonText, DebuggerResources.ICON_CONNECTION); + } + + public Map> promptArguments() { + return promptArguments(PARAMETERS, Map.of(), Map.of()); + } +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProvider.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProvider.java index 676b6e5a1a..9ab1fdedaa 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProvider.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProvider.java @@ -4,9 +4,9 @@ * 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. @@ -34,11 +34,9 @@ import docking.action.builder.ActionBuilder; import docking.widgets.tree.*; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog; import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.*; import ghidra.app.services.*; -import ghidra.dbg.target.TargetMethod.ParameterDescription; -import ghidra.dbg.target.TargetMethod.TargetParameterMap; +import ghidra.debug.api.ValStr; import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; @@ -62,16 +60,6 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter private static final String GROUP_CONNECT = "1. Connect"; private static final String GROUP_MAINTENANCE = "3. Maintenance"; - private static final ParameterDescription PARAM_ADDRESS = - ParameterDescription.create(String.class, "address", true, "localhost", - "Host/Address", "Address or hostname for interface(s) to listen on"); - private static final ParameterDescription PARAM_PORT = - ParameterDescription.create(Integer.class, "port", true, 0, - "Port", "TCP port number, 0 for ephemeral"); - private static final TargetParameterMap PARAMETERS = TargetParameterMap.ofEntries( - Map.entry(PARAM_ADDRESS.name, PARAM_ADDRESS), - Map.entry(PARAM_PORT.name, PARAM_PORT)); - interface StartServerAction { String NAME = "Start Server"; String DESCRIPTION = "Start a TCP server for incoming connections (indefinitely)"; @@ -344,25 +332,24 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter return traceRmiService != null && !traceRmiService.isServerStarted(); } - private InetSocketAddress promptSocketAddress(String title, String okText) { - DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool, - title, okText, DebuggerResources.ICON_CONNECTION); - Map arguments; - do { - dialog.forgetMemorizedArguments(); - arguments = dialog.promptArguments(PARAMETERS); - } - while (dialog.isResetRequested()); + private InetSocketAddress promptSocketAddress(String title, String okText, + HelpLocation helpLocation) { + TraceRmiConnectDialog dialog = new TraceRmiConnectDialog(tool, title, okText); + dialog.setHelpLocation(helpLocation); + Map> arguments = dialog.promptArguments(); + if (arguments == null) { + // Cancelled return null; } - String address = PARAM_ADDRESS.get(arguments); - int port = PARAM_PORT.get(arguments); + String address = TraceRmiConnectDialog.PARAM_ADDRESS.get(arguments).val(); + int port = TraceRmiConnectDialog.PARAM_PORT.get(arguments).val(); return new InetSocketAddress(address, port); } private void doActionStartServerActivated(ActionContext __) { - InetSocketAddress sockaddr = promptSocketAddress("Start Trace RMI Server", "Start"); + InetSocketAddress sockaddr = promptSocketAddress("Start Trace RMI Server", "Start", + actionStartServer.getHelpLocation()); if (sockaddr == null) { return; } @@ -395,7 +382,8 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter } private void doActionConnectAcceptActivated(ActionContext __) { - InetSocketAddress sockaddr = promptSocketAddress("Accept Trace RMI Connection", "Listen"); + InetSocketAddress sockaddr = promptSocketAddress("Accept Trace RMI Connection", "Listen", + actionConnectAccept.getHelpLocation()); if (sockaddr == null) { return; } @@ -420,7 +408,8 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter } private void doActionConnectOutboundActivated(ActionContext __) { - InetSocketAddress sockaddr = promptSocketAddress("Connect to Trace RMI", "Connect"); + InetSocketAddress sockaddr = promptSocketAddress("Connect to Trace RMI", "Connect", + actionConnectOutbound.getHelpLocation()); if (sockaddr == null) { return; } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractScriptTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractScriptTraceRmiLaunchOffer.java index effa0aac33..21b1567c2f 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractScriptTraceRmiLaunchOffer.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractScriptTraceRmiLaunchOffer.java @@ -4,9 +4,9 @@ * 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. @@ -23,7 +23,8 @@ import javax.swing.Icon; import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes; import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.TtyCondition; -import ghidra.dbg.target.TargetMethod.ParameterDescription; +import ghidra.debug.api.ValStr; +import ghidra.debug.api.tracermi.LaunchParameter; import ghidra.debug.api.tracermi.TerminalSession; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; @@ -84,7 +85,7 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi } @Override - public Map> getParameters() { + public Map> getParameters() { return attrs.parameters(); } @@ -93,12 +94,15 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi return attrs.timeoutMillis(); } - protected abstract void prepareSubprocess(List commandLine, Map env, - Map args, SocketAddress address); + protected void prepareSubprocess(List commandLine, Map env, + Map> args, SocketAddress address) { + ScriptAttributesParser.processArguments(commandLine, env, script, attrs.parameters(), args, + address); + } @Override protected void launchBackEnd(TaskMonitor monitor, Map sessions, - Map args, SocketAddress address) throws Exception { + Map> args, SocketAddress address) throws Exception { List commandLine = new ArrayList<>(); Map env = new HashMap<>(System.getenv()); prepareSubprocess(commandLine, env, args, address); @@ -112,7 +116,7 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi } NullPtyTerminalSession ns = nullPtyTerminal(); env.put(ent.getKey(), ns.name()); - sessions.put(ns.name(), ns); + sessions.put(ent.getKey(), ns); } sessions.put("Shell", diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java index a8e113c610..ba882eca4a 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java @@ -28,7 +28,7 @@ import java.util.concurrent.*; import javax.swing.Icon; import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog; +import ghidra.app.plugin.core.debug.gui.action.ByModuleAutoMapSpec; import ghidra.app.plugin.core.debug.gui.tracermi.launcher.LaunchFailureDialog.ErrPromptResponse; import ghidra.app.plugin.core.debug.service.tracermi.DefaultTraceRmiAcceptor; import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler; @@ -36,8 +36,8 @@ import ghidra.app.plugin.core.terminal.TerminalListener; import ghidra.app.services.*; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.async.AsyncUtils; -import ghidra.dbg.target.TargetMethod.ParameterDescription; import ghidra.dbg.util.ShellUtils; +import ghidra.debug.api.ValStr; import ghidra.debug.api.action.AutoMapSpec; import ghidra.debug.api.modules.DebuggerMissingProgramActionContext; import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener; @@ -212,14 +212,13 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer return mappingService.getOpenMappedLocation(trace, probe, snap) != null; } - protected SaveState saveLauncherArgsToState(Map args, - Map> params) { + protected SaveState saveLauncherArgsToState(Map> args, + Map> params) { SaveState state = new SaveState(); - for (ParameterDescription param : params.values()) { - Object val = args.get(param.name); + for (LaunchParameter param : params.values()) { + ValStr val = args.get(param.name()); if (val != null) { - ConfigStateField.putState(state, param.type.asSubclass(Object.class), - "param_" + param.name, val); + state.putString("param_" + param.name(), val.str()); } } return state; @@ -233,56 +232,56 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer plugin.writeProgramLaunchConfig(program, getConfigName(), state); } - protected void saveLauncherArgs(Map args, - Map> params) { + protected void saveLauncherArgs(Map> args, + Map> params) { saveState(saveLauncherArgsToState(args, params)); } interface ImageParamSetter { @SuppressWarnings("unchecked") - static ImageParamSetter get(ParameterDescription param) { - if (param.type == String.class) { - return new StringImageParamSetter((ParameterDescription) param); + static ImageParamSetter get(LaunchParameter param) { + if (param.type() == String.class) { + return new StringImageParamSetter((LaunchParameter) param); } - if (param.type == PathIsFile.class) { - return new FileImageParamSetter((ParameterDescription) param); + if (param.type() == PathIsFile.class) { + return new FileImageParamSetter((LaunchParameter) param); } Msg.warn(ImageParamSetter.class, - "'Image' parameter has unsupported type: " + param.type); + "'Image' parameter has unsupported type: " + param.type()); return null; } - void setImage(Map map, Program program); + void setImage(Map> map, Program program); } static class StringImageParamSetter implements ImageParamSetter { - private final ParameterDescription param; + private final LaunchParameter param; - public StringImageParamSetter(ParameterDescription param) { + public StringImageParamSetter(LaunchParameter param) { this.param = param; } @Override - public void setImage(Map map, Program program) { + public void setImage(Map> map, Program program) { // str-type Image is a hint that the launcher is remote String value = TraceRmiLauncherServicePlugin.getProgramPath(program, false); - param.set(map, value); + param.set(map, ValStr.str(value)); } } static class FileImageParamSetter implements ImageParamSetter { - private final ParameterDescription param; + private final LaunchParameter param; - public FileImageParamSetter(ParameterDescription param) { + public FileImageParamSetter(LaunchParameter param) { this.param = param; } @Override - public void setImage(Map map, Program program) { + public void setImage(Map> map, Program program) { // file-type Image is a hint that the launcher is local String str = TraceRmiLauncherServicePlugin.getProgramPath(program, true); PathIsFile value = str == null ? null : new PathIsFile(Paths.get(str)); - param.set(map, value); + param.set(map, new ValStr<>(value, str)); } } @@ -297,33 +296,34 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer * @return the default arguments */ @SuppressWarnings("unchecked") - protected Map generateDefaultLauncherArgs( - Map> params) { - Map map = new LinkedHashMap(); + protected Map> generateDefaultLauncherArgs( + Map> params) { + Map> map = new LinkedHashMap<>(); ImageParamSetter imageSetter = null; - for (Entry> entry : params.entrySet()) { - ParameterDescription param = entry.getValue(); - map.put(entry.getKey(), param.defaultValue); - if (PARAM_DISPLAY_IMAGE.equals(param.display)) { + for (Entry> entry : params.entrySet()) { + LaunchParameter param = entry.getValue(); + map.put(entry.getKey(), ValStr.cast(Object.class, param.defaultValue())); + if (PARAM_DISPLAY_IMAGE.equals(param.display())) { imageSetter = ImageParamSetter.get(param); // May still be null if type is not supported } - else if (param.name.startsWith(PREFIX_PARAM_EXTTOOL)) { - String tool = param.name.substring(PREFIX_PARAM_EXTTOOL.length()); + else if (param.name().startsWith(PREFIX_PARAM_EXTTOOL)) { + String tool = param.name().substring(PREFIX_PARAM_EXTTOOL.length()); List names = program.getLanguage().getLanguageDescription().getExternalNames(tool); if (names != null && !names.isEmpty()) { - if (param.type == String.class) { - var paramStr = (ParameterDescription) param; - paramStr.set(map, names.get(0)); + String toolName = names.get(0); + if (param.type() == String.class) { + var paramStr = (LaunchParameter) param; + paramStr.set(map, ValStr.str(toolName)); } - else if (param.type == PathIsFile.class) { - var paramPIF = (ParameterDescription) param; - paramPIF.set(map, PathIsFile.fromString(names.get(0))); + else if (param.type() == PathIsFile.class) { + var paramPIF = (LaunchParameter) param; + paramPIF.set(map, new ValStr<>(PathIsFile.fromString(toolName), toolName)); } - else if (param.type == PathIsDir.class) { - var paramPID = (ParameterDescription) param; - paramPID.set(map, PathIsDir.fromString(names.get(0))); + else if (param.type() == PathIsDir.class) { + var paramPID = (LaunchParameter) param; + paramPID.set(map, new ValStr<>(PathIsDir.fromString(toolName), toolName)); } } } @@ -337,50 +337,33 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer /** * Prompt the user for arguments, showing those last used or defaults * - * @param lastExc - * - * @param params the parameters of the model's launcher + * @param configurator a thing to generate/modify the (default) arguments * @param lastExc if re-prompting, an error to display * @return the arguments given by the user, or null if cancelled */ - protected Map promptLauncherArgs(LaunchConfigurator configurator, + protected Map> promptLauncherArgs(LaunchConfigurator configurator, Throwable lastExc) { - Map> params = getParameters(); - DebuggerMethodInvocationDialog dialog = - new DebuggerMethodInvocationDialog(tool, getTitle(), "Launch", getIcon()); + Map> params = getParameters(); + TraceRmiLaunchDialog dialog = + new TraceRmiLaunchDialog(tool, getTitle(), "Launch", getIcon()); dialog.setDescription(getDescription()); dialog.setHelpLocation(getHelpLocation()); + if (lastExc != null) { + dialog.setStatusText(lastExc.toString(), MessageType.ERROR); + } + else { + dialog.setStatusText(""); + } + // NB. Do not invoke read/writeConfigState - Map args; - boolean reset = false; - do { - args = - configurator.configureLauncher(this, loadLastLauncherArgs(true), RelPrompt.BEFORE); - for (ParameterDescription param : params.values()) { - Object val = args.get(param.name); - if (val != null) { - dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class), - val); - } - } - if (lastExc != null) { - dialog.setStatusText(lastExc.toString(), MessageType.ERROR); - } - else { - dialog.setStatusText(""); - } - args = dialog.promptArguments(params); - if (args == null) { - // Cancelled - return null; - } - reset = dialog.isResetRequested(); - if (reset) { - args = generateDefaultLauncherArgs(params); - } + + Map> defaultArgs = generateDefaultLauncherArgs(params); + Map> lastArgs = + configurator.configureLauncher(this, loadLastLauncherArgs(true), RelPrompt.BEFORE); + Map> args = dialog.promptArguments(params, lastArgs, defaultArgs); + if (args != null) { saveLauncherArgs(args, params); } - while (reset); return args; } @@ -398,31 +381,40 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer * @param forPrompt true if the user will be confirming the arguments * @return the loaded arguments, or defaults */ - protected Map loadLastLauncherArgs(boolean forPrompt) { - Map> params = getParameters(); - Map args = loadLauncherArgsFromState(loadState(forPrompt), params); + protected Map> loadLastLauncherArgs(boolean forPrompt) { + Map> params = getParameters(); + Map> args = loadLauncherArgsFromState(loadState(forPrompt), params); saveLauncherArgs(args, params); return args; } - protected Map loadLauncherArgsFromState(SaveState state, - Map> params) { - Map defaultArgs = generateDefaultLauncherArgs(params); + protected Map> loadLauncherArgsFromState(SaveState state, + Map> params) { + Map> defaultArgs = generateDefaultLauncherArgs(params); if (state == null) { return defaultArgs; } - List names = List.of(state.getNames()); - Map args = new LinkedHashMap<>(); - for (ParameterDescription param : params.values()) { - String key = "param_" + param.name; - Object configState = - names.contains(key) ? ConfigStateField.getState(state, param.type, key) : null; - if (configState != null) { - args.put(param.name, configState); + Map> args = new LinkedHashMap<>(); + Set names = Set.of(state.getNames()); + for (LaunchParameter param : params.values()) { + String key = "param_" + param.name(); + if (!names.contains(key)) { + args.put(param.name(), defaultArgs.get(param.name())); + continue; } - else { - args.put(param.name, defaultArgs.get(param.name)); + String str = state.getString(key, null); + if (str != null) { + args.put(param.name(), param.decode(str)); + continue; } + // Perhaps wrong type; was saved in older version. + Object fallback = ConfigStateField.getState(state, param.type(), param.name()); + if (fallback != null) { + args.put(param.name(), ValStr.from(fallback)); + continue; + } + Msg.warn(this, "Could not load saved launcher arg '%s' (%s)".formatted(param.name(), + param.display())); } return args; } @@ -435,7 +427,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer } /** - * Obtain the launcher args + * Obtain the launcher arguments * *

            * This should either call {@link #promptLauncherArgs(LaunchConfigurator, Throwable)} or @@ -447,7 +439,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer * @param lastExc if retrying, the last exception to display as an error message * @return the chosen arguments, or null if the user cancels at the prompt */ - public Map getLauncherArgs(boolean prompt, LaunchConfigurator configurator, + public Map> getLauncherArgs(boolean prompt, LaunchConfigurator configurator, Throwable lastExc) { return prompt ? configurator.configureLauncher(this, promptLauncherArgs(configurator, lastExc), @@ -543,8 +535,8 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer } protected abstract void launchBackEnd(TaskMonitor monitor, - Map sessions, Map args, SocketAddress address) - throws Exception; + Map sessions, Map> args, + SocketAddress address) throws Exception; static class NoStaticMappingException extends Exception { public NoStaticMappingException(String message) { @@ -557,9 +549,18 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer } } - protected void initializeMonitor(TaskMonitor monitor) { + protected AutoMapSpec getAutoMapSpec() { DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class); - AutoMapSpec spec = auto.getAutoMapSpec(); + return auto == null ? ByModuleAutoMapSpec.instance() : auto.getAutoMapSpec(); + } + + protected AutoMapSpec getAutoMapSpec(Trace trace) { + DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class); + return auto == null ? ByModuleAutoMapSpec.instance() : auto.getAutoMapSpec(trace); + } + + protected void initializeMonitor(TaskMonitor monitor) { + AutoMapSpec spec = getAutoMapSpec(); if (requiresImage() && spec.hasTask()) { monitor.setMaximum(6); } @@ -574,8 +575,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer if (!requiresImage()) { return; } - DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class); - AutoMapSpec spec = auto.getAutoMapSpec(trace); + AutoMapSpec spec = getAutoMapSpec(trace); if (!spec.hasTask()) { return; } @@ -625,7 +625,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer while (true) { try { monitor.setMessage("Gathering arguments"); - Map args = getLauncherArgs(prompt, configurator, lastExc); + Map> args = getLauncherArgs(prompt, configurator, lastExc); if (args == null) { if (lastExc == null) { lastExc = new CancelledException(); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/BatchScriptTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/BatchScriptTraceRmiLaunchOffer.java index b883ae3d01..ee2ecd2ff7 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/BatchScriptTraceRmiLaunchOffer.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/BatchScriptTraceRmiLaunchOffer.java @@ -4,9 +4,9 @@ * 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. @@ -22,18 +22,31 @@ import java.util.List; import java.util.Map; import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes; +import ghidra.debug.api.ValStr; import ghidra.program.model.listing.Program; /** * A launcher implemented by a simple DOS/Windows batch file. * *

            - * The script must start with an attributes header in a comment block. + * The script must start with an attributes header in a comment block. See + * {@link ScriptAttributesParser}. */ public class BatchScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunchOffer { public static final String REM = "::"; public static final int REM_LEN = REM.length(); + /** + * Create a launch offer from the given batch file. + * + * @param plugin the launcher service plugin + * @param program the current program, usually the target image. In general, this should be used + * for at least two purposes. 1) To populate the default command line. 2) To ensure + * the target image is mapped in the resulting target trace. + * @param script the batch file that implements this offer + * @return the offer + * @throws FileNotFoundException if the batch file does not exist + */ public static BatchScriptTraceRmiLaunchOffer create(TraceRmiLauncherServicePlugin plugin, Program program, File script) throws FileNotFoundException { ScriptAttributesParser parser = new ScriptAttributesParser() { @@ -60,11 +73,4 @@ public class BatchScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunch File script, String configName, ScriptAttributes attrs) { super(plugin, program, script, configName, attrs); } - - @Override - protected void prepareSubprocess(List commandLine, Map env, - Map args, SocketAddress address) { - ScriptAttributesParser.processArguments(commandLine, env, script, attrs.parameters(), args, - address); - } } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptAttributesParser.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptAttributesParser.java index 2722740ec3..90bb8110fd 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptAttributesParser.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptAttributesParser.java @@ -4,9 +4,9 @@ * 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. @@ -27,18 +27,23 @@ import javax.swing.Icon; import generic.theme.GIcon; import generic.theme.Gui; -import ghidra.dbg.target.TargetMethod.ParameterDescription; import ghidra.dbg.util.ShellUtils; +import ghidra.debug.api.ValStr; +import ghidra.debug.api.tracermi.LaunchParameter; import ghidra.framework.Application; import ghidra.framework.plugintool.AutoConfigState.PathIsDir; import ghidra.framework.plugintool.AutoConfigState.PathIsFile; -import ghidra.util.HelpLocation; -import ghidra.util.Msg; +import ghidra.util.*; /** * A parser for reading attributes from a script header */ public abstract class ScriptAttributesParser { + public static final String ENV_GHIDRA_HOME = "GHIDRA_HOME"; + public static final String ENV_GHIDRA_TRACE_RMI_ADDR = "GHIDRA_TRACE_RMI_ADDR"; + public static final String ENV_GHIDRA_TRACE_RMI_HOST = "GHIDRA_TRACE_RMI_HOST"; + public static final String ENV_GHIDRA_TRACE_RMI_PORT = "GHIDRA_TRACE_RMI_PORT"; + public static final String AT_TITLE = "@title"; public static final String AT_DESC = "@desc"; public static final String AT_MENU_PATH = "@menu-path"; @@ -69,10 +74,29 @@ public abstract class ScriptAttributesParser { public static final String MSGPAT_INVALID_ARGS_SYNTAX = "%s: Invalid %s syntax. Use \"Display\" \"Tool Tip\""; public static final String MSGPAT_INVALID_TTY_SYNTAX = - "%s: Invalid %s syntax. Use TTY_TARGET [if env:OPT_EXTRA_TTY]"; + "%s: Invalid %s syntax. Use TTY_TARGET [if env:OPT [== VAL]]"; + public static final String MSGPAT_INVALID_TTY_NO_PARAM = + "%s: In %s: No such parameter '%s'"; + public static final String MSGPAT_INVALID_TTY_NOT_BOOL = + "%s: In %s: Parameter '%s' must have bool type"; + public static final String MSGPAT_INVALID_TTY_BAD_VAL = + "%s: In %s: Parameter '%s' has type %s, but '%s' cannot be parsed as such"; public static final String MSGPAT_INVALID_TIMEOUT_SYNTAX = "" + "%s: Invalid %s syntax. Use [milliseconds]"; + public static class ParseException extends Exception { + private Location loc; + + public ParseException(Location loc, String message) { + super(message); + this.loc = loc; + } + + public Location getLocation() { + return loc; + } + } + protected record Location(String fileName, int lineNo) { @Override public String toString() { @@ -80,31 +104,36 @@ public abstract class ScriptAttributesParser { } } - protected interface OptType { - static OptType parse(Location loc, String typeName, - Map> userEnums) { + protected interface OptType extends ValStr.Decoder { + static OptType parse(Location loc, String typeName, Map> userEnums) + throws ParseException { OptType type = BaseType.parseNoErr(typeName); if (type == null) { type = userEnums.get(typeName); } if (type == null) { // still - Msg.error(ScriptAttributesParser.class, - "%s: Invalid type %s".formatted(loc, typeName)); - return null; + throw new ParseException(loc, "%s: Invalid type %s".formatted(loc, typeName)); } return type; } - default TypeAndDefault withCastDefault(Object defaultValue) { - return new TypeAndDefault<>(this, cls().cast(defaultValue)); + default TypeAndDefault withCastDefault(ValStr defaultValue) { + return new TypeAndDefault<>(this, ValStr.cast(cls(), defaultValue)); } Class cls(); - T decode(Location loc, String str); + default T decode(Location loc, String str) throws ParseException { + try { + return decode(str); + } + catch (Exception e) { + throw new ParseException(loc, "%s: %s".formatted(loc, e.getMessage())); + } + } - ParameterDescription createParameter(String name, T defaultValue, String display, - String description); + LaunchParameter createParameter(String name, String display, String description, + boolean required, ValStr defaultValue); } protected interface BaseType extends OptType { @@ -120,12 +149,10 @@ public abstract class ScriptAttributesParser { }; } - public static BaseType parse(Location loc, String typeName) { + public static BaseType parse(Location loc, String typeName) throws ParseException { BaseType type = parseNoErr(typeName); if (type == null) { - Msg.error(ScriptAttributesParser.class, - "%s: Invalid base type %s".formatted(loc, typeName)); - return null; + throw new ParseException(loc, "%s: Invalid base type %s".formatted(loc, typeName)); } return type; } @@ -137,7 +164,7 @@ public abstract class ScriptAttributesParser { } @Override - public String decode(Location loc, String str) { + public String decode(String str) { return str; } }; @@ -149,18 +176,14 @@ public abstract class ScriptAttributesParser { } @Override - public BigInteger decode(Location loc, String str) { + public BigInteger decode(String str) { try { - if (str.startsWith("0x")) { - return new BigInteger(str.substring(2), 16); - } - return new BigInteger(str); + return NumericUtilities.decodeBigInteger(str); } catch (NumberFormatException e) { - Msg.error(ScriptAttributesParser.class, - ("%s: Invalid int for %s: %s. You may prefix with 0x for hexadecimal. " + - "Otherwise, decimal is used.").formatted(loc, AT_ENV, str)); - return null; + throw new IllegalArgumentException( + "Invalid int %s. Prefixes 0x, 0b, and 0 (octal) are allowed." + .formatted(str)); } } }; @@ -172,17 +195,16 @@ public abstract class ScriptAttributesParser { } @Override - public Boolean decode(Location loc, String str) { - Boolean result = switch (str) { + public Boolean decode(String str) { + Boolean result = switch (str.trim().toLowerCase()) { case "true" -> true; case "false" -> false; default -> null; }; if (result == null) { - Msg.error(ScriptAttributesParser.class, - "%s: Invalid bool for %s: %s. Only true or false (in lower case) is allowed." - .formatted(loc, AT_ENV, str)); - return null; + throw new IllegalArgumentException( + "Invalid bool for %s: %s. Only true or false is allowed." + .formatted(AT_ENV, str)); } return result; } @@ -195,7 +217,7 @@ public abstract class ScriptAttributesParser { } @Override - public Path decode(Location loc, String str) { + public Path decode(String str) { return Paths.get(str); } }; @@ -207,7 +229,7 @@ public abstract class ScriptAttributesParser { } @Override - public PathIsDir decode(Location loc, String str) { + public PathIsDir decode(String str) { return new PathIsDir(Paths.get(str)); } }; @@ -219,7 +241,7 @@ public abstract class ScriptAttributesParser { } @Override - public PathIsFile decode(Location loc, String str) { + public PathIsFile decode(String str) { return new PathIsFile(Paths.get(str)); } }; @@ -228,11 +250,15 @@ public abstract class ScriptAttributesParser { return new UserType<>(this, choices.stream().map(cls()::cast).toList()); } + default UserType withChoices(List choices) { + return new UserType<>(this, choices); + } + @Override - default ParameterDescription createParameter(String name, T defaultValue, String display, - String description) { - return ParameterDescription.create(cls(), name, false, defaultValue, display, - description); + default LaunchParameter createParameter(String name, String display, String description, + boolean required, ValStr defaultValue) { + return LaunchParameter.create(cls(), name, display, description, required, defaultValue, + this); } } @@ -243,62 +269,57 @@ public abstract class ScriptAttributesParser { } @Override - public T decode(Location loc, String str) { - return base.decode(loc, str); + public T decode(String str) { + return base.decode(str); } @Override - public ParameterDescription createParameter(String name, T defaultValue, String display, - String description) { - return ParameterDescription.choices(cls(), name, choices, defaultValue, display, - description); + public LaunchParameter createParameter(String name, String display, String description, + boolean required, ValStr defaultValue) { + return LaunchParameter.choices(cls(), name, display, description, choices, + defaultValue); } } - protected record TypeAndDefault(OptType type, T defaultValue) { + protected record TypeAndDefault(OptType type, ValStr defaultValue) { public static TypeAndDefault parse(Location loc, String typeName, String defaultString, - Map> userEnums) { + Map> userEnums) throws ParseException { OptType tac = OptType.parse(loc, typeName, userEnums); - if (tac == null) { - return null; - } Object value = tac.decode(loc, defaultString); - if (value == null) { - return null; - } - return tac.withCastDefault(value); + return tac.withCastDefault(new ValStr<>(value, defaultString)); } - public ParameterDescription createParameter(String name, String display, - String description) { - return type.createParameter(name, defaultValue, display, description); + public LaunchParameter createParameter(String name, String display, String description) { + return type.createParameter(name, display, description, false, defaultValue); } } public interface TtyCondition { - boolean isActive(Map args); + boolean isActive(Map> args); } enum ConstTtyCondition implements TtyCondition { ALWAYS { @Override - public boolean isActive(Map args) { + public boolean isActive(Map> args) { return true; } }, } - record EqualsTtyCondition(String key, String repr) implements TtyCondition { + record EqualsTtyCondition(LaunchParameter param, Object value) implements TtyCondition { @Override - public boolean isActive(Map args) { - return Objects.toString(args.get(key)).equals(repr); + public boolean isActive(Map> args) { + ValStr valStr = param.get(args); + return Objects.equals(valStr == null ? null : valStr.val(), value); } } - record BoolTtyCondition(String key) implements TtyCondition { + record BoolTtyCondition(LaunchParameter param) implements TtyCondition { @Override - public boolean isActive(Map args) { - return args.get(key) instanceof Boolean b && b.booleanValue(); + public boolean isActive(Map> args) { + ValStr valStr = param.get(args); + return valStr != null && valStr.val(); } } @@ -318,9 +339,8 @@ public abstract class ScriptAttributesParser { public record ScriptAttributes(String title, String description, List menuPath, String menuGroup, String menuOrder, Icon icon, HelpLocation helpLocation, - Map> parameters, Map extraTtys, - int timeoutMillis, boolean noImage) { - } + Map> parameters, Map extraTtys, + int timeoutMillis, boolean noImage) {} /** * Convert an arguments map into a command line and environment variables @@ -335,34 +355,35 @@ public abstract class ScriptAttributesParser { * @param address the address of the listening TraceRmi socket */ public static void processArguments(List commandLine, Map env, - File script, Map> parameters, Map args, + File script, Map> parameters, Map> args, SocketAddress address) { commandLine.add(script.getAbsolutePath()); - env.put("GHIDRA_HOME", Application.getInstallationDirectory().getAbsolutePath()); + env.put(ENV_GHIDRA_HOME, Application.getInstallationDirectory().getAbsolutePath()); if (address != null) { - env.put("GHIDRA_TRACE_RMI_ADDR", sockToString(address)); + env.put(ENV_GHIDRA_TRACE_RMI_ADDR, sockToString(address)); if (address instanceof InetSocketAddress tcp) { - env.put("GHIDRA_TRACE_RMI_HOST", tcp.getAddress().getHostAddress()); - env.put("GHIDRA_TRACE_RMI_PORT", Integer.toString(tcp.getPort())); + env.put(ENV_GHIDRA_TRACE_RMI_HOST, tcp.getAddress().getHostAddress()); + env.put(ENV_GHIDRA_TRACE_RMI_PORT, Integer.toString(tcp.getPort())); } } - ParameterDescription paramDesc; - for (int i = 1; (paramDesc = parameters.get("arg:" + i)) != null; i++) { - commandLine.add(Objects.toString(paramDesc.get(args))); + LaunchParameter param; + for (int i = 1; (param = parameters.get("arg:" + i)) != null; i++) { + // Don't use ValStr.str here. I'd like the script's input normalized + commandLine.add(Objects.toString(param.get(args).val())); } - paramDesc = parameters.get("args"); - if (paramDesc != null) { - commandLine.addAll(ShellUtils.parseArgs((String) paramDesc.get(args))); + param = parameters.get("args"); + if (param != null) { + commandLine.addAll(ShellUtils.parseArgs(param.get(args).str())); } - for (Entry> ent : parameters.entrySet()) { + for (Entry> ent : parameters.entrySet()) { String key = ent.getKey(); if (key.startsWith(PREFIX_ENV)) { String varName = key.substring(PREFIX_ENV.length()); - env.put(varName, Objects.toString(ent.getValue().get(args))); + env.put(varName, Objects.toString(ent.getValue().get(args).val())); } } } @@ -376,7 +397,7 @@ public abstract class ScriptAttributesParser { private String iconId; private HelpLocation helpLocation; private final Map> userTypes = new HashMap<>(); - private final Map> parameters = new LinkedHashMap<>(); + private final Map> parameters = new LinkedHashMap<>(); private final Map extraTtys = new LinkedHashMap<>(); private int timeoutMillis = AbstractTraceRmiLaunchOffer.DEFAULT_TIMEOUT_MILLIS; private boolean noImage = false; @@ -401,9 +422,17 @@ public abstract class ScriptAttributesParser { */ protected abstract String removeDelimiter(String line); - public ScriptAttributes parseFile(File script) throws FileNotFoundException { + /** + * Parse the header from the give input stream + * + * @param stream the stream from of the input stream file + * @param scriptName the name of the script file + * @return the parsed attributes + * @throws IOException if there was an issue reading the stream + */ + public ScriptAttributes parseStream(InputStream stream, String scriptName) throws IOException { try (BufferedReader reader = - new BufferedReader(new InputStreamReader(new FileInputStream(script)))) { + new BufferedReader(new InputStreamReader(stream))) { String line; for (int lineNo = 1; (line = reader.readLine()) != null; lineNo++) { if (ignoreLine(lineNo, line)) { @@ -413,9 +442,22 @@ public abstract class ScriptAttributesParser { if (comment == null) { break; } - parseComment(new Location(script.getName(), lineNo), comment); + parseComment(new Location(scriptName, lineNo), comment); } - return validate(script.getName()); + return validate(scriptName); + } + } + + /** + * Parse the header of the given script file + * + * @param script the file + * @return the parsed attributes + * @throws FileNotFoundException if the script file could not be found + */ + public ScriptAttributes parseFile(File script) throws FileNotFoundException { + try { + return parseStream(new FileInputStream(script), script.getName()); } catch (FileNotFoundException e) { // Avoid capture by IOException @@ -468,7 +510,7 @@ public abstract class ScriptAttributesParser { protected void parseTitle(Location loc, String str) { if (title != null) { - Msg.warn(this, "%s: Duplicate @title".formatted(loc)); + reportWarning("%s: Duplicate %s".formatted(loc, AT_TITLE)); } title = str; } @@ -483,161 +525,222 @@ public abstract class ScriptAttributesParser { protected void parseMenuPath(Location loc, String str) { if (menuPath != null) { - Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_MENU_PATH)); + reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_PATH)); } menuPath = List.of(str.trim().split("\\.")); if (menuPath.isEmpty()) { - Msg.error(this, + reportError( "%s: Empty %s. Ignoring.".formatted(loc, AT_MENU_PATH)); } } protected void parseMenuGroup(Location loc, String str) { if (menuGroup != null) { - Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_MENU_GROUP)); + reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_GROUP)); } menuGroup = str; } protected void parseMenuOrder(Location loc, String str) { if (menuOrder != null) { - Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_MENU_ORDER)); + reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_ORDER)); } menuOrder = str; } protected void parseIcon(Location loc, String str) { if (iconId != null) { - Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_ICON)); + reportWarning("%s: Duplicate %s".formatted(loc, AT_ICON)); } iconId = str.trim(); if (!Gui.hasIcon(iconId)) { - Msg.error(this, + reportError( "%s: Icon id %s not registered in the theme".formatted(loc, iconId)); } } protected void parseHelp(Location loc, String str) { if (helpLocation != null) { - Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_HELP)); + reportWarning("%s: Duplicate %s".formatted(loc, AT_HELP)); } String[] parts = str.trim().split("#", 2); if (parts.length != 2) { - Msg.error(this, MSGPAT_INVALID_HELP_SYNTAX.formatted(loc, AT_HELP)); + reportError(MSGPAT_INVALID_HELP_SYNTAX.formatted(loc, AT_HELP)); return; } helpLocation = new HelpLocation(parts[0].trim(), parts[1].trim()); } + protected UserType parseEnumChoices(Location loc, BaseType baseType, + List choiceParts) { + List choices = new ArrayList<>(); + boolean err = false; + for (String s : choiceParts) { + try { + choices.add(baseType.decode(loc, s)); + } + catch (ParseException e) { + reportError(e.getMessage()); + } + } + if (err) { + return null; + } + return baseType.withChoices(choices); + } + protected void parseEnum(Location loc, String str) { List parts = ShellUtils.parseArgs(str); if (parts.size() < 2) { - Msg.error(this, MSGPAT_INVALID_ENUM_SYNTAX.formatted(loc, AT_ENUM)); + reportError(MSGPAT_INVALID_ENUM_SYNTAX.formatted(loc, AT_ENUM)); return; } String[] nameParts = parts.get(0).split(":", 2); if (nameParts.length != 2) { - Msg.error(this, MSGPAT_INVALID_ENUM_SYNTAX.formatted(loc, AT_ENUM)); + reportError(MSGPAT_INVALID_ENUM_SYNTAX.formatted(loc, AT_ENUM)); return; } String name = nameParts[0].trim(); - BaseType baseType = BaseType.parse(loc, nameParts[1]); - if (baseType == null) { + BaseType baseType; + try { + baseType = BaseType.parse(loc, nameParts[1]); + } + catch (ParseException e) { + reportError(e.getMessage()); return; } - List choices = parts.stream().skip(1).map(s -> baseType.decode(loc, s)).toList(); - if (choices.contains(null)) { - return; + UserType userType = parseEnumChoices(loc, baseType, parts.subList(1, parts.size())); + if (userType == null) { + return; // errors already reported } - UserType userType = baseType.withCastChoices(choices); if (userTypes.put(name, userType) != null) { - Msg.warn(this, "%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENUM, name)); + reportWarning("%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENUM, name)); } } protected void parseEnv(Location loc, String str) { List parts = ShellUtils.parseArgs(str); if (parts.size() != 3) { - Msg.error(this, MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV)); + reportError(MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV)); return; } String[] nameParts = parts.get(0).split(":", 2); if (nameParts.length != 2) { - Msg.error(this, MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV)); + reportError(MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV)); return; } String trimmed = nameParts[0].trim(); String name = PREFIX_ENV + trimmed; String[] tadParts = nameParts[1].split("=", 2); if (tadParts.length != 2) { - Msg.error(this, MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV)); + reportError(MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV)); return; } - TypeAndDefault tad = - TypeAndDefault.parse(loc, tadParts[0].trim(), tadParts[1].trim(), userTypes); - ParameterDescription param = tad.createParameter(name, parts.get(1), parts.get(2)); - if (parameters.put(name, param) != null) { - Msg.warn(this, "%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENV, trimmed)); + try { + TypeAndDefault tad = + TypeAndDefault.parse(loc, tadParts[0].trim(), tadParts[1].trim(), userTypes); + LaunchParameter param = tad.createParameter(name, parts.get(1), parts.get(2)); + if (parameters.put(name, param) != null) { + reportWarning("%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENV, trimmed)); + } + } + catch (ParseException e) { + reportError(e.getMessage()); } } protected void parseArg(Location loc, String str, int argNum) { List parts = ShellUtils.parseArgs(str); if (parts.size() != 3) { - Msg.error(this, MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG)); + reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG)); return; } String colonType = parts.get(0).trim(); if (!colonType.startsWith(":")) { - Msg.error(this, MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG)); + reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG)); return; } - OptType type = OptType.parse(loc, colonType.substring(1), userTypes); - if (type == null) { - return; + OptType type; + try { + type = OptType.parse(loc, colonType.substring(1), userTypes); + String name = PREFIX_ARG + argNum; + parameters.put(name, + type.createParameter(name, parts.get(1), parts.get(2), true, + new ValStr<>(null, ""))); + } + catch (ParseException e) { + reportError(e.getMessage()); } - String name = PREFIX_ARG + argNum; - parameters.put(name, ParameterDescription.create(type.cls(), name, true, null, - parts.get(1), parts.get(2))); } protected void parseArgs(Location loc, String str) { List parts = ShellUtils.parseArgs(str); if (parts.size() != 2) { - Msg.error(this, MSGPAT_INVALID_ARGS_SYNTAX.formatted(loc, AT_ARGS)); + reportError(MSGPAT_INVALID_ARGS_SYNTAX.formatted(loc, AT_ARGS)); return; } - ParameterDescription parameter = ParameterDescription.create(String.class, - "args", false, "", parts.get(0), parts.get(1)); + + LaunchParameter parameter = BaseType.STRING.createParameter( + "args", parts.get(0), parts.get(1), false, ValStr.str("")); if (parameters.put(KEY_ARGS, parameter) != null) { - Msg.warn(this, "%s: Duplicate %s. Replaced".formatted(loc, AT_ARGS)); + reportWarning("%s: Duplicate %s. Replaced".formatted(loc, AT_ARGS)); } } protected void putTty(Location loc, String name, TtyCondition condition) { if (extraTtys.put(name, condition) != null) { - Msg.warn(this, "%s: Duplicate %s. Ignored".formatted(loc, AT_TTY)); + reportWarning("%s: Duplicate %s. Ignored".formatted(loc, AT_TTY)); } } protected void parseTty(Location loc, String str) { List parts = ShellUtils.parseArgs(str); switch (parts.size()) { - case 1: + case 1 -> { putTty(loc, parts.get(0), ConstTtyCondition.ALWAYS); return; - case 3: + } + case 3 -> { if ("if".equals(parts.get(1))) { - putTty(loc, parts.get(0), new BoolTtyCondition(parts.get(2))); + LaunchParameter param = parameters.get(parts.get(2)); + if (param == null) { + reportError( + MSGPAT_INVALID_TTY_NO_PARAM.formatted(loc, AT_TTY, parts.get(2))); + return; + } + if (param.type() != Boolean.class) { + reportError( + MSGPAT_INVALID_TTY_NOT_BOOL.formatted(loc, AT_TTY, param.name())); + return; + } + @SuppressWarnings("unchecked") + LaunchParameter asBoolParam = (LaunchParameter) param; + putTty(loc, parts.get(0), new BoolTtyCondition(asBoolParam)); return; } - case 5: + } + case 5 -> { if ("if".equals(parts.get(1)) && "==".equals(parts.get(3))) { - putTty(loc, parts.get(0), new EqualsTtyCondition(parts.get(2), parts.get(4))); - return; + LaunchParameter param = parameters.get(parts.get(2)); + if (param == null) { + reportError( + MSGPAT_INVALID_TTY_NO_PARAM.formatted(loc, AT_TTY, parts.get(2))); + return; + } + try { + Object value = param.decode(parts.get(4)).val(); + putTty(loc, parts.get(0), new EqualsTtyCondition(param, value)); + return; + } + catch (Exception e) { + reportError(MSGPAT_INVALID_TTY_BAD_VAL.formatted(loc, AT_TTY, + param.name(), param.type(), parts.get(4))); + return; + } } + } } - Msg.error(this, MSGPAT_INVALID_TTY_SYNTAX.formatted(loc, AT_TTY)); + reportError(MSGPAT_INVALID_TTY_SYNTAX.formatted(loc, AT_TTY)); } protected void parseTimeout(Location loc, String str) { @@ -645,7 +748,7 @@ public abstract class ScriptAttributesParser { timeoutMillis = Integer.parseInt(str); } catch (NumberFormatException e) { - Msg.error(this, MSGPAT_INVALID_TIMEOUT_SYNTAX.formatted(loc, AT_TIMEOUT)); + reportError(MSGPAT_INVALID_TIMEOUT_SYNTAX.formatted(loc, AT_TIMEOUT)); } } @@ -654,12 +757,13 @@ public abstract class ScriptAttributesParser { } protected void parseUnrecognized(Location loc, String line) { - Msg.warn(this, "%s: Unrecognized metadata: %s".formatted(loc, line)); + reportWarning("%s: Unrecognized metadata: %s".formatted(loc, line)); } protected ScriptAttributes validate(String fileName) { if (title == null) { - Msg.error(this, "%s is required. Using script file name.".formatted(AT_TITLE)); + reportError( + "%s is required. Using script file name: '%s'".formatted(AT_TITLE, fileName)); title = fileName; } if (menuPath == null) { @@ -683,4 +787,12 @@ public abstract class ScriptAttributesParser { private String getDescription() { return description == null ? null : description.toString(); } + + protected void reportWarning(String message) { + Msg.warn(this, message); + } + + protected void reportError(String message) { + Msg.error(this, message); + } } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialog.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialog.java new file mode 100644 index 0000000000..70885a0fcb --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialog.java @@ -0,0 +1,86 @@ +/* ### + * 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.debug.gui.tracermi.launcher; + +import java.util.List; +import java.util.Map; + +import javax.swing.Icon; + +import ghidra.app.plugin.core.debug.gui.AbstractDebuggerParameterDialog; +import ghidra.debug.api.ValStr; +import ghidra.debug.api.tracermi.LaunchParameter; +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.PluginTool; + +public class TraceRmiLaunchDialog extends AbstractDebuggerParameterDialog> { + + public TraceRmiLaunchDialog(PluginTool tool, String title, String buttonText, Icon buttonIcon) { + super(tool, title, buttonText, buttonIcon); + } + + @Override + protected String parameterName(LaunchParameter parameter) { + return parameter.name(); + } + + @Override + protected Class parameterType(LaunchParameter parameter) { + return parameter.type(); + } + + @Override + protected String parameterLabel(LaunchParameter parameter) { + return parameter.display(); + } + + @Override + protected String parameterToolTip(LaunchParameter parameter) { + return parameter.description(); + } + + @Override + protected ValStr parameterDefault(LaunchParameter parameter) { + return parameter.defaultValue(); + } + + @Override + protected List parameterChoices(LaunchParameter parameter) { + return parameter.choices(); + } + + @Override + protected Map> validateArguments(Map> parameters, + Map> arguments) { + return LaunchParameter.validateArguments(parameters, arguments); + } + + @Override + protected void parameterSaveValue(LaunchParameter parameter, SaveState state, String key, + ValStr value) { + state.putString(key, value.str()); + } + + @Override + protected ValStr parameterLoadValue(LaunchParameter parameter, SaveState state, + String key) { + String str = state.getString(key, null); + if (str == null) { + return null; + } + return parameter.decode(str); + } +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/UnixShellScriptTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/UnixShellScriptTraceRmiLaunchOffer.java index 052b3d9dfa..e6175c2f3b 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/UnixShellScriptTraceRmiLaunchOffer.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/UnixShellScriptTraceRmiLaunchOffer.java @@ -4,9 +4,9 @@ * 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. @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes; +import ghidra.debug.api.ValStr; import ghidra.program.model.listing.Program; /** @@ -32,6 +33,8 @@ import ghidra.program.model.listing.Program; * {@link ScriptAttributesParser}. */ public class UnixShellScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunchOffer { + public static final String HASH = "#"; + public static final int HASH_LEN = HASH.length(); public static final String SHEBANG = "#!"; /** @@ -56,10 +59,10 @@ public class UnixShellScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLa @Override protected String removeDelimiter(String line) { String stripped = line.stripLeading(); - if (!stripped.startsWith("#")) { + if (!stripped.startsWith(HASH)) { return null; } - return stripped.substring(1); + return stripped.substring(HASH_LEN); } }; ScriptAttributes attrs = parser.parseFile(script); @@ -68,15 +71,7 @@ public class UnixShellScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLa } private UnixShellScriptTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin, - Program program, - File script, String configName, ScriptAttributes attrs) { + Program program, File script, String configName, ScriptAttributes attrs) { super(plugin, program, script, configName, attrs); } - - @Override - protected void prepareSubprocess(List commandLine, Map env, - Map args, SocketAddress address) { - ScriptAttributesParser.processArguments(commandLine, env, script, attrs.parameters(), args, - address); - } } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java index 6750bd9f1a..721862cefb 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java @@ -54,7 +54,6 @@ import ghidra.program.model.address.*; import ghidra.program.model.lang.*; import ghidra.program.util.DefaultLanguageService; import ghidra.rmi.trace.TraceRmi.*; -import ghidra.rmi.trace.TraceRmi.Compiler; import ghidra.rmi.trace.TraceRmi.Language; import ghidra.trace.database.DBTrace; import ghidra.trace.model.Lifespan; @@ -129,11 +128,9 @@ public class TraceRmiHandler implements TraceRmiConnection { } } - protected record Tid(DoId doId, int txId) { - } + protected record Tid(DoId doId, int txId) {} - protected record OpenTx(Tid txId, Transaction tx, boolean undoable) { - } + protected record OpenTx(Tid txId, Transaction tx, boolean undoable) {} protected class OpenTraceMap { private final Map byId = new HashMap<>(); @@ -388,7 +385,7 @@ public class TraceRmiHandler implements TraceRmiConnection { protected static void sendDelimited(OutputStream out, RootMessage msg, long dbgSeq) throws IOException { - ByteBuffer buf = ByteBuffer.allocate(4); + ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES); buf.putInt(msg.getSerializedSize()); out.write(buf.array()); msg.writeTo(out); @@ -867,6 +864,9 @@ public class TraceRmiHandler implements TraceRmiConnection { throws InvalidNameException, IOException, CancelledException { DomainFolder traces = getOrCreateNewTracesFolder(); List path = sanitizePath(req.getPath().getPath()); + if (path.isEmpty()) { + throw new IllegalArgumentException("CreateTrace: path (name) cannot be empty"); + } DomainFolder folder = createFolders(traces, path.subList(0, path.size() - 1)); CompilerSpec cs = requireCompilerSpec(req.getLanguage(), req.getCompiler()); DBTrace trace = new DBTrace(path.get(path.size() - 1), cs, this); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java index b255eb991e..673c3a444b 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java @@ -4,9 +4,9 @@ * 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. @@ -37,6 +37,7 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.util.PathMatcher; import ghidra.dbg.util.PathPredicates; import ghidra.dbg.util.PathPredicates.Align; +import ghidra.debug.api.ValStr; import ghidra.debug.api.model.DebuggerObjectActionContext; import ghidra.debug.api.model.DebuggerSingleObjectPathActionContext; import ghidra.debug.api.target.ActionName; @@ -345,25 +346,15 @@ public class TraceRmiTarget extends AbstractTarget { } private Map promptArgs(RemoteMethod method, Map defaults) { - SchemaContext ctx = getSchemaContext(); + /** + * TODO: RemoteMethod parameter descriptions should also use ValStr. This map conversion + * stuff is getting onerous and hacky. + */ + Map> defs = ValStr.fromPlainMap(defaults); RemoteMethodInvocationDialog dialog = new RemoteMethodInvocationDialog(tool, - method.display(), method.display(), null); - while (true) { - for (RemoteParameter param : method.parameters().values()) { - Object val = defaults.get(param.name()); - if (val != null) { - Class type = ctx.getSchema(param.type()).getType(); - dialog.setMemorizedArgument(param.name(), type.asSubclass(Object.class), - val); - } - } - Map args = dialog.promptArguments(ctx, method.parameters(), defaults); - if (args == null) { - // Cancelled - return null; - } - return args; - } + getSchemaContext(), method.display(), method.display(), null); + Map> args = dialog.promptArguments(method.parameters(), defs, defs); + return args == null ? null : ValStr.toPlainMap(args); } private CompletableFuture invokeMethod(boolean prompt, RemoteMethod method, diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProviderTest.java b/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProviderTest.java index fbcd3d62f8..529e0c8e97 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProviderTest.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProviderTest.java @@ -4,9 +4,9 @@ * 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. @@ -30,7 +30,7 @@ import org.junit.Test; import generic.Unique; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; -import ghidra.app.plugin.core.debug.gui.objects.components.InvocationDialogHelper; +import ghidra.app.plugin.core.debug.gui.InvocationDialogHelper; import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.*; import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin; import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiClient; @@ -60,13 +60,17 @@ public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedD provider = waitForComponentProvider(TraceRmiConnectionManagerProvider.class); } + InvocationDialogHelper waitDialog() { + return InvocationDialogHelper.waitFor(TraceRmiConnectDialog.class); + } + @Test public void testActionAccept() throws Exception { performEnabledAction(provider, provider.actionConnectAccept, false); - InvocationDialogHelper helper = InvocationDialogHelper.waitFor(); + InvocationDialogHelper helper = waitDialog(); helper.dismissWithArguments(Map.ofEntries( - Map.entry("address", "localhost"), - Map.entry("port", 0))); + helper.entry("address", "localhost"), + helper.entry("port", 0))); waitForPass(() -> Unique.assertOne(traceRmiService.getAllAcceptors())); } @@ -78,10 +82,10 @@ public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedD throw new AssertionError(); } performEnabledAction(provider, provider.actionConnectOutbound, false); - InvocationDialogHelper helper = InvocationDialogHelper.waitFor(); + InvocationDialogHelper helper = waitDialog(); helper.dismissWithArguments(Map.ofEntries( - Map.entry("address", sockaddr.getHostString()), - Map.entry("port", sockaddr.getPort()))); + helper.entry("address", sockaddr.getHostString()), + helper.entry("port", sockaddr.getPort()))); try (SocketChannel channel = server.accept()) { TestTraceRmiClient client = new TestTraceRmiClient(channel); client.sendNegotiate("Test client"); @@ -94,10 +98,10 @@ public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedD @Test public void testActionStartServer() throws Exception { performEnabledAction(provider, provider.actionStartServer, false); - InvocationDialogHelper helper = InvocationDialogHelper.waitFor(); + InvocationDialogHelper helper = waitDialog(); helper.dismissWithArguments(Map.ofEntries( - Map.entry("address", "localhost"), - Map.entry("port", 0))); + helper.entry("address", "localhost"), + helper.entry("port", 0))); waitForPass(() -> assertTrue(traceRmiService.isServerStarted())); waitForPass(() -> assertFalse(provider.actionStartServer.isEnabled())); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerParameterDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerParameterDialog.java new file mode 100644 index 0000000000..8b229e15af --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerParameterDialog.java @@ -0,0 +1,767 @@ +/* ### + * 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.debug.gui; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.beans.*; +import java.io.File; +import java.math.BigInteger; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.List; +import java.util.Map.Entry; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.text.StringEscapeUtils; + +import docking.DialogComponentProvider; +import docking.options.editor.FileChooserEditor; +import docking.widgets.button.BrowseButton; +import docking.widgets.filechooser.GhidraFileChooser; +import docking.widgets.filechooser.GhidraFileChooserMode; +import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils; +import ghidra.debug.api.ValStr; +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.AutoConfigState.PathIsDir; +import ghidra.framework.plugintool.AutoConfigState.PathIsFile; +import ghidra.framework.plugintool.PluginTool; +import ghidra.util.*; +import ghidra.util.layout.PairLayout; + +public abstract class AbstractDebuggerParameterDialog

            extends DialogComponentProvider + implements PropertyChangeListener { + static final String KEY_MEMORIZED_ARGUMENTS = "memorizedArguments"; + + public static class BigIntEditor extends PropertyEditorSupport { + String asText = ""; + + @Override + public String getJavaInitializationString() { + Object value = getValue(); + return value == null + ? "null" + : "new BigInteger(\"%s\")".formatted(value); + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + /** + * Set asText first, since setValue will fire change listener. It will call getAsText(). + */ + asText = text; + setValueNoAsText(text == null + ? null + : NumericUtilities.decodeBigInteger(text)); + } + + public void setValueNoAsText(Object value) { + super.setValue(value); + } + + @Override + public void setValue(Object value) { + super.setValue(value); + asText = value == null ? "" : value.toString(); + } + + @Override + public String getAsText() { + return asText; + } + } + + public static class FileChooserPanel extends JPanel { + private final static int NUMBER_OF_COLUMNS = 20; + + private final JTextField textField = new JTextField(NUMBER_OF_COLUMNS); + private final JButton browseButton = new BrowseButton(); + private final Runnable propertyChange; + + private GhidraFileChooser fileChooser; // lazy + + public FileChooserPanel(Runnable propertyChange) { + this.propertyChange = propertyChange; + + setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + add(textField); + add(Box.createHorizontalStrut(5)); + add(browseButton); + setBorder(BorderFactory.createEmptyBorder()); + + textField.addActionListener(e -> propertyChange.run()); + textField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + propertyChange.run(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + propertyChange.run(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + propertyChange.run(); + } + }); + + browseButton.addActionListener(e -> displayFileChooser()); + } + + public void setValue(File file) { + textField.setText(file == null ? "" : file.getAbsolutePath()); + } + + private void displayFileChooser() { + if (fileChooser == null) { + fileChooser = createFileChooser(); + } + + String path = textField.getText().trim(); + if (!path.isEmpty()) { + File f = new File(path); + if (f.isDirectory()) { + fileChooser.setCurrentDirectory(f); + } + else { + File pf = f.getParentFile(); + if (pf != null && pf.isDirectory()) { + fileChooser.setSelectedFile(f); + } + } + } + + File chosen = fileChooser.getSelectedFile(true); + if (chosen != null) { + textField.setText(chosen.getAbsolutePath()); + propertyChange.run(); + } + } + + protected String getTitle() { + return "Choose Path"; + } + + protected GhidraFileChooserMode getSelectionMode() { + return GhidraFileChooserMode.FILES_AND_DIRECTORIES; + } + + private GhidraFileChooser createFileChooser() { + GhidraFileChooser chooser = new GhidraFileChooser(browseButton); + chooser.setTitle(getTitle()); + chooser.setApproveButtonText(getTitle()); + chooser.setFileSelectionMode(getSelectionMode()); + // No way for script to specify filter.... + + return chooser; + } + } + + /** + * Compared to {@link FileChooserEditor}, this does not require the user to enter a full path. + * Nor will it resolve file names against the working directory. It's just a text box with a + * file browser assist. + */ + public static class PathEditor extends PropertyEditorSupport { + private final FileChooserPanel panel = newChooserPanel(); + + protected FileChooserPanel newChooserPanel() { + return new FileChooserPanel(this::firePropertyChange); + } + + @Override + public String getAsText() { + return panel.textField.getText().trim(); + } + + @Override + public Object getValue() { + String text = panel.textField.getText().trim(); + if (text.isEmpty()) { + return null; + } + return Paths.get(text); + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (text == null || text.isBlank()) { + panel.textField.setText(""); + } + else { + panel.textField.setText(text); + } + } + + @Override + public void setValue(Object value) { + if (value == null) { + panel.textField.setText(""); + } + else if (value instanceof String s) { + panel.textField.setText(s); + } + else if (value instanceof Path p) { + panel.textField.setText(p.toString()); + } + else { + throw new IllegalArgumentException("value=" + value); + } + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + @Override + public Component getCustomEditor() { + return panel; + } + } + + public static class PathIsDirEditor extends PathEditor { + @Override + protected FileChooserPanel newChooserPanel() { + return new FileChooserPanel(this::firePropertyChange) { + @Override + protected String getTitle() { + return "Choose Directory"; + } + + @Override + protected GhidraFileChooserMode getSelectionMode() { + return GhidraFileChooserMode.DIRECTORIES_ONLY; + } + }; + } + + @Override + public Object getValue() { + Object value = super.getValue(); + if (value == null) { + return null; + } + if (value instanceof Path p) { + return new PathIsDir(p); + } + throw new AssertionError(); + } + + @Override + public void setValue(Object value) { + if (value instanceof PathIsDir dir) { + super.setValue(dir.path()); + } + else { + super.setValue(value); + } + } + } + + public static class PathIsFileEditor extends PathEditor { + @Override + protected FileChooserPanel newChooserPanel() { + return new FileChooserPanel(this::firePropertyChange) { + @Override + protected String getTitle() { + return "Choose File"; + } + + @Override + protected GhidraFileChooserMode getSelectionMode() { + return GhidraFileChooserMode.FILES_ONLY; + } + }; + } + + @Override + public Object getValue() { + Object value = super.getValue(); + if (value == null) { + return null; + } + if (value instanceof Path p) { + return new PathIsFile(p); + } + throw new AssertionError(); + } + + @Override + public void setValue(Object value) { + if (value instanceof PathIsFile file) { + super.setValue(file.path()); + } + else { + super.setValue(value); + } + } + } + + static { + PropertyEditorManager.registerEditor(BigInteger.class, BigIntEditor.class); + PropertyEditorManager.registerEditor(Path.class, PathEditor.class); + PropertyEditorManager.registerEditor(PathIsDir.class, PathIsDirEditor.class); + PropertyEditorManager.registerEditor(PathIsFile.class, PathIsFileEditor.class); + } + + static class ChoicesPropertyEditor implements PropertyEditor { + private final List choices; + private final String[] tags; + + private final List listeners = new ArrayList<>(); + + private Object value; + + public ChoicesPropertyEditor(Collection choices) { + this.choices = choices.stream().distinct().toList(); + this.tags = choices.stream().map(Objects::toString).toArray(String[]::new); + } + + @Override + public void setValue(Object value) { + if (Objects.equals(value, this.value)) { + return; + } + if (!choices.contains(value)) { + throw new IllegalArgumentException("Unsupported value: " + value); + } + Object oldValue; + List listeners; + synchronized (this.listeners) { + oldValue = this.value; + this.value = value; + if (this.listeners.isEmpty()) { + return; + } + listeners = List.copyOf(this.listeners); + } + PropertyChangeEvent evt = new PropertyChangeEvent(this, null, oldValue, value); + for (PropertyChangeListener l : listeners) { + l.propertyChange(evt); + } + } + + @Override + public Object getValue() { + return value; + } + + @Override + public boolean isPaintable() { + return false; + } + + @Override + public void paintValue(Graphics gfx, Rectangle box) { + // Not paintable + } + + @Override + public String getJavaInitializationString() { + if (value == null) { + return "null"; + } + if (value instanceof String str) { + return "\"" + StringEscapeUtils.escapeJava(str) + "\""; + } + return Objects.toString(value); + } + + @Override + public String getAsText() { + return Objects.toString(value); + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + int index = ArrayUtils.indexOf(tags, text); + if (index < 0) { + throw new IllegalArgumentException("Unsupported value: " + text); + } + setValue(choices.get(index)); + } + + @Override + public String[] getTags() { + return tags.clone(); + } + + @Override + public Component getCustomEditor() { + return null; + } + + @Override + public boolean supportsCustomEditor() { + return false; + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + } + + protected record NameTypePair(String name, Class type) { + public static NameTypePair fromString(String name) throws ClassNotFoundException { + String[] parts = name.split(",", 2); + if (parts.length != 2) { + // This appears to be a bad assumption - empty fields results in solitary labels + return new NameTypePair(parts[0], String.class); + //throw new IllegalArgumentException("Could not parse name,type"); + } + return new NameTypePair(parts[0], Class.forName(parts[1])); + } + + public final String encodeString() { + return name + "," + type.getName(); + } + } + + private final BidiMap paramEditors = new DualLinkedHashBidiMap<>(); + + private JPanel panel; + private JLabel descriptionLabel; + private JPanel pairPanel; + private PairLayout layout; + + protected JButton invokeButton; + protected JButton resetButton; + + private final PluginTool tool; + // package access for testing + Map parameters; + + private Map> defaults = Map.of(); + // TODO: Not sure this is the best keying, but I think it works. + private Map> memorized = new HashMap<>(); + private Map> arguments; + + public AbstractDebuggerParameterDialog(PluginTool tool, String title, String buttonText, + Icon buttonIcon) { + super(title, true, true, true, false); + this.tool = tool; + + populateComponents(buttonText, buttonIcon); + setRememberSize(false); + } + + protected abstract String parameterName(P parameter); + + protected abstract Class parameterType(P parameter); + + protected NameTypePair parameterNameAndType(P parameter) { + return new NameTypePair(parameterName(parameter), parameterType(parameter)); + } + + protected abstract String parameterLabel(P parameter); + + protected abstract String parameterToolTip(P parameter); + + protected abstract ValStr parameterDefault(P parameter); + + protected abstract Collection parameterChoices(P parameter); + + protected abstract Map> validateArguments(Map parameters, + Map> arguments); + + protected abstract void parameterSaveValue(P parameter, SaveState state, String key, + ValStr value); + + protected abstract ValStr parameterLoadValue(P parameter, SaveState state, String key); + + protected ValStr computeInitialValue(P parameter) { + ValStr val = memorized.computeIfAbsent(parameterNameAndType(parameter), + ntp -> defaults.get(parameterName(parameter))); + return val; + } + + /** + * Prompt the user for the given arguments, all at once + * + *

            + * This displays a single dialog with each option listed. The parameter map contains the + * description of each parameter to be displayed. The {@code initial} values are the values to + * pre-populate the options with, e.g., because they are saved from a previous session, or + * because they are the suggested values. If the user clicks the "Reset" button, the values are + * revered to the defaults given in each parameter's description, unless that value is + * overridden in {@code defaults}. This may be appropriate if a value is suggested for a + * (perhaps required) option that otherwise has no default. + * + * @param parameterMap the map of parameters, keyed by {@link #parameterName(Object)}. This map + * may be ordered to control the order of options displayed. + * @param initial the initial values of the options. If a key is not provided, the initial value + * is its default value. Extraneous keys are ignored. + * @param defaults the default values to use upon reset. If a key is not provided, the default + * is taken from the parameter description. Extraneous keys are ignored. + * @return the arguments provided by the user + */ + public Map> promptArguments(Map parameterMap, + Map> initial, Map> defaults) { + setDefaults(defaults); + setParameters(parameterMap); + setMemorizedArguments(initial); + populateValues(initial); + tool.showDialog(this); + + return getArguments(); + } + + protected void setParameters(Map parameterMap) { + this.parameters = parameterMap; + for (P param : parameterMap.values()) { + if (!defaults.containsKey(parameterName(param))) { + defaults.put(parameterName(param), parameterDefault(param)); + } + } + populateOptions(); + } + + protected void setMemorizedArguments(Map> initial) { + for (P param : parameters.values()) { + ValStr val = initial.get(parameterName(param)); + if (val != null) { + setMemorizedArgument(param, val); + } + } + } + + protected void setDefaults(Map> defaults) { + this.defaults = new HashMap<>(defaults); + } + + private void populateComponents(String buttonText, Icon buttonIcon) { + panel = new JPanel(new BorderLayout()); + panel.setBorder(new EmptyBorder(10, 10, 10, 10)); + + layout = new PairLayout(5, 5); + pairPanel = new JPanel(layout); + + JPanel centering = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JScrollPane scrolling = new JScrollPane(centering, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + //scrolling.setPreferredSize(new Dimension(100, 130)); + panel.add(scrolling, BorderLayout.CENTER); + centering.add(pairPanel); + + descriptionLabel = new JLabel(); + descriptionLabel.setMaximumSize(new Dimension(300, 100)); + panel.add(descriptionLabel, BorderLayout.NORTH); + + addWorkPanel(panel); + + invokeButton = new JButton(buttonText, buttonIcon); + addButton(invokeButton); + resetButton = new JButton("Reset", DebuggerResources.ICON_REFRESH); + addButton(resetButton); + addCancelButton(); + + invokeButton.addActionListener(this::invoke); + resetButton.addActionListener(this::reset); + } + + @Override + protected void cancelCallback() { + this.arguments = null; + close(); + } + + void invoke(ActionEvent evt) { + try { + this.arguments = validateArguments(parameters, collectArguments()); + close(); + } + catch (IllegalStateException e) { + setStatusText(e.getMessage(), MessageType.ERROR, true); + } + } + + void reset(ActionEvent evt) { + this.arguments = null; + populateValues(defaults); + } + + protected PropertyEditor createEditor(P parameter) { + Collection choices = parameterChoices(parameter); + if (!choices.isEmpty()) { + return new ChoicesPropertyEditor(choices); + } + Class type = parameterType(parameter); + PropertyEditor editor = PropertyEditorManager.findEditor(type); + if (editor != null) { + return editor; + } + Msg.warn(this, "No editor for " + type + "? Trying String instead"); + editor = PropertyEditorManager.findEditor(String.class); + return editor; + } + + // test access + PropertyEditor getEditor(P parameter) { + return paramEditors.get(parameter); + } + + protected void setEditorValue(PropertyEditor editor, P param, ValStr val) { + switch (val.val()) { + case null -> { + } + case BigInteger bi -> editor.setAsText(val.str()); + default -> editor.setValue(val.val()); + } + } + + void populateOptions() { + pairPanel.removeAll(); + paramEditors.clear(); + for (P param : parameters.values()) { + JLabel label = new JLabel(parameterLabel(param)); + label.setToolTipText(parameterToolTip(param)); + pairPanel.add(label); + + PropertyEditor editor = createEditor(param); + ValStr val = computeInitialValue(param); + setEditorValue(editor, param, val); + editor.addPropertyChangeListener(this); + pairPanel.add(MiscellaneousUtils.getEditorComponent(editor)); + paramEditors.put(param, editor); + } + } + + void populateValues(Map> values) { + for (Map.Entry> ent : values.entrySet()) { + P param = parameters.get(ent.getKey()); + if (param == null) { + continue; + } + PropertyEditor editor = paramEditors.get(param); + setEditorValue(editor, param, ent.getValue()); + } + } + + protected Map> collectArguments() { + Map> map = new LinkedHashMap<>(); + Set invalid = new LinkedHashSet<>(); + for (Entry ent : paramEditors.entrySet()) { + P param = ent.getKey(); + PropertyEditor editor = ent.getValue(); + ValStr val = memorized.get(parameterNameAndType(param)); + if (!Objects.equals(editor.getAsText(), val.str())) { + invalid.add(parameterLabel(param)); + } + if (val != null) { + map.put(parameterName(param), val); + } + } + if (!invalid.isEmpty()) { + throw new IllegalStateException("Invalid value for " + invalid); + } + return map; + } + + public Map> getArguments() { + return arguments; + } + + void setMemorizedArgument(P parameter, ValStr value) { + if (value == null) { + return; + } + memorized.put(parameterNameAndType(parameter), value); + } + + public void forgetMemorizedArguments() { + memorized.clear(); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + PropertyEditor editor = (PropertyEditor) evt.getSource(); + P param = paramEditors.getKey(editor); + memorized.put(parameterNameAndType(param), + new ValStr<>(editor.getValue(), editor.getAsText())); + } + + public void writeConfigState(SaveState saveState) { + SaveState subState = new SaveState(); + for (Map.Entry> ent : memorized.entrySet()) { + NameTypePair ntp = ent.getKey(); + P param = parameters.get(ntp.name()); + if (param == null) { + continue; + } + parameterSaveValue(param, subState, ntp.encodeString(), ent.getValue()); + } + saveState.putSaveState(KEY_MEMORIZED_ARGUMENTS, subState); + } + + public void readConfigState(SaveState saveState) { + /** + * TODO: This method is defunct. It is only used by the DebuggerObjectsProvider, which is + * now deprecated, but I suspect other providers intend to use this in the same way. If + * those providers don't manually load/compute initial and default values at the time of + * prompting, then this will need to be fixed. The decode of the values will need to be + * delayed until (and repeated every time) parameters are populated. + */ + SaveState subState = saveState.getSaveState(KEY_MEMORIZED_ARGUMENTS); + if (subState == null) { + return; + } + for (String name : subState.getNames()) { + try { + NameTypePair ntp = NameTypePair.fromString(name); + P param = parameters.get(ntp.name()); + if (param == null) { + continue; + } + memorized.put(ntp, parameterLoadValue(param, subState, ntp.encodeString())); + } + catch (Exception e) { + Msg.error(this, "Error restoring memorized parameter " + name, e); + } + } + } + + public void setDescription(String htmlDescription) { + if (htmlDescription == null) { + descriptionLabel.setBorder(BorderFactory.createEmptyBorder()); + descriptionLabel.setText(""); + } + else { + descriptionLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + descriptionLabel.setText(htmlDescription); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByModuleAutoMapSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByModuleAutoMapSpec.java index eeae1b1771..ff8a5234a8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByModuleAutoMapSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByModuleAutoMapSpec.java @@ -40,6 +40,18 @@ import ghidra.util.task.TaskMonitor; public class ByModuleAutoMapSpec implements AutoMapSpec { public static final String CONFIG_NAME = "1_MAP_BY_MODULE"; + /** + * Get the instance. + * + *

            + * Note this will not work until after the class searcher is done. + * + * @return the instance + */ + public static ByModuleAutoMapSpec instance() { + return (ByModuleAutoMapSpec) AutoMapSpec.fromConfigName(CONFIG_NAME); + } + @Override public String getConfigName() { return CONFIG_NAME; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java index 843428f55c..8dac3586de 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java @@ -4,9 +4,9 @@ * 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. @@ -26,6 +26,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.stream.Collectors; import javax.swing.JComponent; import javax.swing.JPanel; @@ -63,6 +64,7 @@ import ghidra.dbg.target.TargetMethod.TargetParameterMap; import ghidra.dbg.target.TargetSteppable.TargetStepKind; import ghidra.dbg.util.DebuggerCallbackReorderer; import ghidra.dbg.util.PathUtils; +import ghidra.debug.api.ValStr; import ghidra.debug.api.model.DebuggerMemoryMapper; import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.tracemgr.DebuggerCoordinates; @@ -135,8 +137,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter private final AutoService.Wiring autoServiceWiring; @AutoOptionDefined( - name = "Default Extended Step", - description = "The default string for the extended step command") + name = "Default Extended Step", + description = "The default string for the extended step command") String extendedStep = ""; @SuppressWarnings("unused") @@ -1367,13 +1369,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter boolean prompt = p; }; return AsyncUtils.loop(TypeSpec.VOID, (loop) -> { - Map args = launchOffer.getLauncherArgs(launcher, locals.prompt); + Map> args = launchOffer.getLauncherArgs(launcher, locals.prompt); if (args == null) { // Cancelled loop.exit(); } else { - launcher.launch(args).thenAccept(loop::exit).exceptionally(ex -> { + Map a = ValStr.toPlainMap(args); + launcher.launch(a).thenAccept(loop::exit).exceptionally(ex -> { loop.repeat(); return null; }); @@ -1428,7 +1431,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter } return; } - Map args = methodDialog.promptArguments(parameters); + Map args = methodDialog.promptArguments(parameters, Map.of(), Map.of()); if (args != null) { String script = (String) args.get("Script"); if (script != null && !script.isEmpty()) { @@ -1623,7 +1626,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter if (configParameters.isEmpty()) { return AsyncUtils.nil(); } - Map args = configDialog.promptArguments(configParameters); + Map args = + configDialog.promptArguments(configParameters, Map.of(), Map.of()); if (args == null) { // User cancelled return AsyncUtils.nil(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerMethodInvocationDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerMethodInvocationDialog.java index 090a717c3f..0f111d2ea8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerMethodInvocationDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerMethodInvocationDialog.java @@ -4,9 +4,9 @@ * 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. @@ -15,654 +15,75 @@ */ package ghidra.app.plugin.core.debug.gui.objects.components; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.beans.*; -import java.io.File; -import java.math.BigInteger; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; -import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; +import javax.swing.Icon; -import org.apache.commons.collections4.BidiMap; -import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.tuple.MutablePair; -import org.apache.commons.text.StringEscapeUtils; -import org.jdom.Element; - -import docking.DialogComponentProvider; -import docking.options.editor.FileChooserEditor; -import docking.widgets.button.BrowseButton; -import docking.widgets.filechooser.GhidraFileChooser; -import docking.widgets.filechooser.GhidraFileChooserMode; -import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils; +import ghidra.app.plugin.core.debug.gui.AbstractDebuggerParameterDialog; import ghidra.dbg.target.TargetMethod; import ghidra.dbg.target.TargetMethod.ParameterDescription; +import ghidra.debug.api.ValStr; import ghidra.framework.options.SaveState; -import ghidra.framework.plugintool.AutoConfigState.*; +import ghidra.framework.plugintool.AutoConfigState.ConfigStateField; import ghidra.framework.plugintool.PluginTool; -import ghidra.util.Msg; -import ghidra.util.layout.PairLayout; -public class DebuggerMethodInvocationDialog extends DialogComponentProvider - implements PropertyChangeListener { - - public static class BigIntEditor extends PropertyEditorSupport { - @Override - public String getJavaInitializationString() { - Object value = getValue(); - return value == null - ? "null" - : "new BigInteger(\"%s\")".formatted(value); - } - - @Override - public void setAsText(String text) throws IllegalArgumentException { - setValue(text == null - ? null - : new BigInteger(text)); - } - } - - public static class FileChooserPanel extends JPanel { - private final static int NUMBER_OF_COLUMNS = 20; - - private final JTextField textField = new JTextField(NUMBER_OF_COLUMNS); - private final JButton browseButton = new BrowseButton(); - private final Runnable propertyChange; - - private GhidraFileChooser fileChooser; // lazy - - public FileChooserPanel(Runnable propertyChange) { - this.propertyChange = propertyChange; - - setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); - add(textField); - add(Box.createHorizontalStrut(5)); - add(browseButton); - setBorder(BorderFactory.createEmptyBorder()); - - textField.addActionListener(e -> propertyChange.run()); - textField.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void removeUpdate(DocumentEvent e) { - propertyChange.run(); - } - - @Override - public void insertUpdate(DocumentEvent e) { - propertyChange.run(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - propertyChange.run(); - } - }); - - browseButton.addActionListener(e -> displayFileChooser()); - } - - public void setValue(File file) { - textField.setText(file == null ? "" : file.getAbsolutePath()); - } - - private void displayFileChooser() { - if (fileChooser == null) { - fileChooser = createFileChooser(); - } - - String path = textField.getText().trim(); - if (!path.isEmpty()) { - File f = new File(path); - if (f.isDirectory()) { - fileChooser.setCurrentDirectory(f); - } - else { - File pf = f.getParentFile(); - if (pf != null && pf.isDirectory()) { - fileChooser.setSelectedFile(f); - } - } - } - - File chosen = fileChooser.getSelectedFile(true); - if (chosen != null) { - textField.setText(chosen.getAbsolutePath()); - propertyChange.run(); - } - } - - protected String getTitle() { - return "Choose Path"; - } - - protected GhidraFileChooserMode getSelectionMode() { - return GhidraFileChooserMode.FILES_AND_DIRECTORIES; - } - - private GhidraFileChooser createFileChooser() { - GhidraFileChooser chooser = new GhidraFileChooser(browseButton); - chooser.setTitle(getTitle()); - chooser.setApproveButtonText(getTitle()); - chooser.setFileSelectionMode(getSelectionMode()); - // No way for script to specify filter.... - - return chooser; - } - } - - /** - * Compared to {@link FileChooserEditor}, this does not require the user to enter a full path. - * Nor will it resolve file names against the working directory. It's just a text box with a - * file browser assist. - */ - public static class PathEditor extends PropertyEditorSupport { - private final FileChooserPanel panel = newChooserPanel(); - - protected FileChooserPanel newChooserPanel() { - return new FileChooserPanel(this::firePropertyChange); - } - - @Override - public String getAsText() { - return panel.textField.getText().trim(); - } - - @Override - public Object getValue() { - String text = panel.textField.getText().trim(); - if (text.isEmpty()) { - return null; - } - return Paths.get(text); - } - - @Override - public void setAsText(String text) throws IllegalArgumentException { - if (text == null || text.isBlank()) { - panel.textField.setText(""); - } - else { - panel.textField.setText(text); - } - } - - @Override - public void setValue(Object value) { - if (value == null) { - panel.textField.setText(""); - } - else if (value instanceof String s) { - panel.textField.setText(s); - } - else if (value instanceof Path p) { - panel.textField.setText(p.toString()); - } - else { - throw new IllegalArgumentException("value=" + value); - } - } - - @Override - public boolean supportsCustomEditor() { - return true; - } - - @Override - public Component getCustomEditor() { - return panel; - } - } - - public static class PathIsDirEditor extends PathEditor { - @Override - protected FileChooserPanel newChooserPanel() { - return new FileChooserPanel(this::firePropertyChange) { - @Override - protected String getTitle() { - return "Choose Directory"; - } - - @Override - protected GhidraFileChooserMode getSelectionMode() { - return GhidraFileChooserMode.DIRECTORIES_ONLY; - } - }; - } - - @Override - public Object getValue() { - Object value = super.getValue(); - if (value == null) { - return null; - } - if (value instanceof Path p) { - return new PathIsDir(p); - } - throw new AssertionError(); - } - - @Override - public void setValue(Object value) { - if (value instanceof PathIsDir dir) { - super.setValue(dir.path()); - } - else { - super.setValue(value); - } - } - } - - public static class PathIsFileEditor extends PathEditor { - @Override - protected FileChooserPanel newChooserPanel() { - return new FileChooserPanel(this::firePropertyChange) { - @Override - protected String getTitle() { - return "Choose File"; - } - - @Override - protected GhidraFileChooserMode getSelectionMode() { - return GhidraFileChooserMode.FILES_ONLY; - } - }; - } - - @Override - public Object getValue() { - Object value = super.getValue(); - if (value == null) { - return null; - } - if (value instanceof Path p) { - return new PathIsFile(p); - } - throw new AssertionError(); - } - - @Override - public void setValue(Object value) { - if (value instanceof PathIsFile file) { - super.setValue(file.path()); - } - else { - super.setValue(value); - } - } - } - - static { - PropertyEditorManager.registerEditor(BigInteger.class, BigIntEditor.class); - PropertyEditorManager.registerEditor(Path.class, PathEditor.class); - PropertyEditorManager.registerEditor(PathIsDir.class, PathIsDirEditor.class); - PropertyEditorManager.registerEditor(PathIsFile.class, PathIsFileEditor.class); - } - - private static final String KEY_MEMORIZED_ARGUMENTS = "memorizedArguments"; - - static class ChoicesPropertyEditor implements PropertyEditor { - private final List choices; - private final String[] tags; - - private final List listeners = new ArrayList<>(); - - private Object value; - - public ChoicesPropertyEditor(Set choices) { - this.choices = List.copyOf(choices); - this.tags = choices.stream().map(Objects::toString).toArray(String[]::new); - } - - @Override - public void setValue(Object value) { - if (Objects.equals(value, this.value)) { - return; - } - if (!choices.contains(value)) { - throw new IllegalArgumentException("Unsupported value: " + value); - } - Object oldValue; - List listeners; - synchronized (this.listeners) { - oldValue = this.value; - this.value = value; - if (this.listeners.isEmpty()) { - return; - } - listeners = List.copyOf(this.listeners); - } - PropertyChangeEvent evt = new PropertyChangeEvent(this, null, oldValue, value); - for (PropertyChangeListener l : listeners) { - l.propertyChange(evt); - } - } - - @Override - public Object getValue() { - return value; - } - - @Override - public boolean isPaintable() { - return false; - } - - @Override - public void paintValue(Graphics gfx, Rectangle box) { - // Not paintable - } - - @Override - public String getJavaInitializationString() { - if (value == null) { - return "null"; - } - if (value instanceof String str) { - return "\"" + StringEscapeUtils.escapeJava(str) + "\""; - } - return Objects.toString(value); - } - - @Override - public String getAsText() { - return Objects.toString(value); - } - - @Override - public void setAsText(String text) throws IllegalArgumentException { - int index = ArrayUtils.indexOf(tags, text); - if (index < 0) { - throw new IllegalArgumentException("Unsupported value: " + text); - } - setValue(choices.get(index)); - } - - @Override - public String[] getTags() { - return tags.clone(); - } - - @Override - public Component getCustomEditor() { - return null; - } - - @Override - public boolean supportsCustomEditor() { - return false; - } - - @Override - public void addPropertyChangeListener(PropertyChangeListener listener) { - synchronized (listeners) { - listeners.add(listener); - } - } - - @Override - public void removePropertyChangeListener(PropertyChangeListener listener) { - synchronized (listeners) { - listeners.remove(listener); - } - } - } - - final static class NameTypePair extends MutablePair> { - - public static NameTypePair fromParameter(ParameterDescription parameter) { - return new NameTypePair(parameter.name, parameter.type); - } - - public static NameTypePair fromString(String name) throws ClassNotFoundException { - String[] parts = name.split(",", 2); - if (parts.length != 2) { - // This appears to be a bad assumption - empty fields results in solitary labels - return new NameTypePair(parts[0], String.class); - //throw new IllegalArgumentException("Could not parse name,type"); - } - return new NameTypePair(parts[0], Class.forName(parts[1])); - } - - public NameTypePair(String name, Class type) { - super(name, type); - } - - @Override - public String toString() { - return getName() + "," + getType().getName(); - } - - @Override - public Class setValue(Class value) { - throw new UnsupportedOperationException(); - } - - public String getName() { - return getLeft(); - } - - public Class getType() { - return getRight(); - } - } - - private final BidiMap, PropertyEditor> paramEditors = - new DualLinkedHashBidiMap<>(); - - private JPanel panel; - private JLabel descriptionLabel; - private JPanel pairPanel; - private PairLayout layout; - - protected JButton invokeButton; - protected JButton resetButton; - protected boolean resetRequested; - - private final PluginTool tool; - Map> parameters; - - // TODO: Not sure this is the best keying, but I think it works. - private Map memorized = new HashMap<>(); - private Map arguments; +public class DebuggerMethodInvocationDialog + extends AbstractDebuggerParameterDialog> { public DebuggerMethodInvocationDialog(PluginTool tool, String title, String buttonText, Icon buttonIcon) { - super(title, true, true, true, false); - this.tool = tool; - - populateComponents(buttonText, buttonIcon); - setRememberSize(false); - } - - protected Object computeMemorizedValue(ParameterDescription parameter) { - return memorized.computeIfAbsent(NameTypePair.fromParameter(parameter), - ntp -> parameter.defaultValue); - } - - public Map promptArguments(Map> parameterMap) { - setParameters(parameterMap); - tool.showDialog(this); - - return getArguments(); - } - - public void setParameters(Map> parameterMap) { - this.parameters = parameterMap; - populateOptions(); - } - - private void populateComponents(String buttonText, Icon buttonIcon) { - panel = new JPanel(new BorderLayout()); - panel.setBorder(new EmptyBorder(10, 10, 10, 10)); - - layout = new PairLayout(5, 5); - pairPanel = new JPanel(layout); - - JPanel centering = new JPanel(new FlowLayout(FlowLayout.CENTER)); - JScrollPane scrolling = new JScrollPane(centering, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, - JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - //scrolling.setPreferredSize(new Dimension(100, 130)); - panel.add(scrolling, BorderLayout.CENTER); - centering.add(pairPanel); - - descriptionLabel = new JLabel(); - descriptionLabel.setMaximumSize(new Dimension(300, 100)); - panel.add(descriptionLabel, BorderLayout.NORTH); - - addWorkPanel(panel); - - invokeButton = new JButton(buttonText, buttonIcon); - addButton(invokeButton); - resetButton = new JButton("Reset", DebuggerResources.ICON_REFRESH); - addButton(resetButton); - addCancelButton(); - - invokeButton.addActionListener(this::invoke); - resetButton.addActionListener(this::reset); - resetRequested = false; + super(tool, title, buttonText, buttonIcon); } @Override - protected void cancelCallback() { - this.arguments = null; - this.resetRequested = false; - close(); - } - - void invoke(ActionEvent evt) { - this.arguments = TargetMethod.validateArguments(parameters, collectArguments(), false); - this.resetRequested = false; - close(); - } - - void reset(ActionEvent evt) { - this.arguments = new LinkedHashMap<>(); - this.resetRequested = true; - close(); - } - - protected PropertyEditor getEditor(ParameterDescription param) { - if (!param.choices.isEmpty()) { - return new ChoicesPropertyEditor(param.choices); - } - Class type = param.type; - PropertyEditor editor = PropertyEditorManager.findEditor(type); - if (editor != null) { - return editor; - } - Msg.warn(this, "No editor for " + type + "? Trying String instead"); - return PropertyEditorManager.findEditor(String.class); - } - - void populateOptions() { - pairPanel.removeAll(); - paramEditors.clear(); - for (ParameterDescription param : parameters.values()) { - JLabel label = new JLabel(param.display); - label.setToolTipText(param.description); - pairPanel.add(label); - - PropertyEditor editor = getEditor(param); - Object val = computeMemorizedValue(param); - if (val == null) { - editor.setValue(""); - } - else { - editor.setValue(val); - } - editor.addPropertyChangeListener(this); - pairPanel.add(MiscellaneousUtils.getEditorComponent(editor)); - paramEditors.put(param, editor); - } - } - - protected Map collectArguments() { - Map map = new LinkedHashMap<>(); - for (ParameterDescription param : paramEditors.keySet()) { - Object val = memorized.get(NameTypePair.fromParameter(param)); - if (val != null) { - map.put(param.name, val); - } - } - return map; - } - - public Map getArguments() { - return arguments; - } - - public void setMemorizedArgument(String name, Class type, T value) { - if (value == null) { - return; - } - memorized.put(new NameTypePair(name, type), value); - } - - public T getMemorizedArgument(String name, Class type) { - return type.cast(memorized.get(new NameTypePair(name, type))); - } - - public void forgetMemorizedArguments() { - memorized.clear(); + protected String parameterName(ParameterDescription parameter) { + return parameter.name; } @Override - public void propertyChange(PropertyChangeEvent evt) { - PropertyEditor editor = (PropertyEditor) evt.getSource(); - ParameterDescription param = paramEditors.getKey(editor); - memorized.put(NameTypePair.fromParameter(param), editor.getValue()); + protected Class parameterType(ParameterDescription parameter) { + return parameter.type; } - public void writeConfigState(SaveState saveState) { - SaveState subState = new SaveState(); - for (Map.Entry ent : memorized.entrySet()) { - NameTypePair ntp = ent.getKey(); - ConfigStateField.putState(subState, ntp.getType().asSubclass(Object.class), - ntp.getName(), ent.getValue()); - } - saveState.putXmlElement(KEY_MEMORIZED_ARGUMENTS, subState.saveToXml()); + @Override + protected String parameterLabel(ParameterDescription parameter) { + return parameter.display; } - public void readConfigState(SaveState saveState) { - Element element = saveState.getXmlElement(KEY_MEMORIZED_ARGUMENTS); - if (element == null) { - return; - } - SaveState subState = new SaveState(element); - for (String name : subState.getNames()) { - try { - NameTypePair ntp = NameTypePair.fromString(name); - memorized.put(ntp, - ConfigStateField.getState(subState, ntp.getType(), ntp.getName())); - } - catch (Exception e) { - Msg.error(this, "Error restoring memorized parameter " + name, e); - } - } + @Override + protected String parameterToolTip(ParameterDescription parameter) { + return parameter.description; } - public boolean isResetRequested() { - return resetRequested; + @Override + protected ValStr parameterDefault(ParameterDescription parameter) { + return ValStr.from(parameter.defaultValue); } - public void setDescription(String htmlDescription) { - if (htmlDescription == null) { - descriptionLabel.setBorder(BorderFactory.createEmptyBorder()); - descriptionLabel.setText(""); - } - else { - descriptionLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); - descriptionLabel.setText(htmlDescription); - } + @Override + protected Set parameterChoices(ParameterDescription parameter) { + return parameter.choices; + } + + @Override + protected Map> validateArguments( + Map> parameters, Map> arguments) { + Map args = ValStr.toPlainMap(arguments); + return ValStr.fromPlainMap(TargetMethod.validateArguments(parameters, args, false)); + } + + @Override + protected void parameterSaveValue(ParameterDescription parameter, SaveState state, + String key, ValStr value) { + ConfigStateField.putState(state, parameter.type.asSubclass(Object.class), key, value.val()); + } + + @Override + protected ValStr parameterLoadValue(ParameterDescription parameter, SaveState state, + String key) { + return ValStr.from(ConfigStateField.getState(state, parameter.type, key)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java index 0cc8aeaf44..5e4a766a83 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java @@ -4,9 +4,9 @@ * 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. @@ -16,10 +16,12 @@ package ghidra.app.plugin.core.debug.service.model; import java.util.*; +import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import docking.ActionContext; import ghidra.app.context.ProgramLocationActionContext; @@ -38,6 +40,7 @@ import ghidra.dbg.target.TargetMethod.TargetParameterMap; import ghidra.dbg.target.TargetSteppable.TargetStepKind; import ghidra.dbg.util.PathMatcher; import ghidra.dbg.util.PathPredicates; +import ghidra.debug.api.ValStr; import ghidra.debug.api.model.*; import ghidra.debug.api.target.ActionName; import ghidra.debug.api.tracemgr.DebuggerCoordinates; @@ -237,26 +240,12 @@ public class TraceRecorderTarget extends AbstractTarget { } private Map promptArgs(TargetMethod method, Map defaults) { + Map> defs = ValStr.fromPlainMap(defaults); DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool, method.getDisplay(), method.getDisplay(), null); - while (true) { - for (ParameterDescription param : method.getParameters().values()) { - Object val = defaults.get(param.name); - if (val != null) { - dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class), - val); - } - } - Map args = dialog.promptArguments(method.getParameters()); - if (args == null) { - // Cancelled - return null; - } - if (dialog.isResetRequested()) { - continue; - } - return args; - } + + Map> args = dialog.promptArguments(method.getParameters(), defs, defs); + return args == null ? null : ValStr.toPlainMap(args); } private CompletableFuture invokeMethod(boolean prompt, TargetMethod method, diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java index f9000c5268..0295f0abcc 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java @@ -4,9 +4,9 @@ * 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. @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.debug.service.model.launch; -import static ghidra.async.AsyncUtils.*; +import static ghidra.async.AsyncUtils.loop; import java.io.File; import java.io.IOException; @@ -44,6 +44,7 @@ import ghidra.dbg.target.TargetMethod.ParameterDescription; import ghidra.dbg.target.TargetMethod.TargetParameterMap; import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.util.PathUtils; +import ghidra.debug.api.ValStr; import ghidra.debug.api.model.DebuggerProgramLaunchOffer; import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.modules.*; @@ -281,14 +282,14 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg return proposal; } - private void saveLauncherArgs(Map args, + private void saveLauncherArgs(Map> args, Map> params) { SaveState state = new SaveState(); for (ParameterDescription param : params.values()) { - Object val = args.get(param.name); + ValStr val = args.get(param.name); if (val != null) { ConfigStateField.putState(state, param.type.asSubclass(Object.class), param.name, - val); + val.val()); } } if (program != null) { @@ -316,19 +317,19 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg * @param params the parameters * @return the default arguments */ - protected Map generateDefaultLauncherArgs( + protected Map> generateDefaultLauncherArgs( Map> params) { if (program == null) { return Map.of(); } - Map map = new LinkedHashMap(); + Map> map = new LinkedHashMap<>(); for (Entry> entry : params.entrySet()) { - map.put(entry.getKey(), entry.getValue().defaultValue); + map.put(entry.getKey(), ValStr.from(entry.getValue().defaultValue)); } String almostExecutablePath = program.getExecutablePath(); File f = new File(almostExecutablePath); map.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, - TargetCmdLineLauncher.quoteImagePathIfSpaces(f.getAbsolutePath())); + ValStr.from(TargetCmdLineLauncher.quoteImagePathIfSpaces(f.getAbsolutePath()))); return map; } @@ -338,36 +339,19 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg * @param params the parameters of the model's launcher * @return the arguments given by the user, or null if cancelled */ - protected Map promptLauncherArgs(TargetLauncher launcher, + protected Map> promptLauncherArgs(TargetLauncher launcher, LaunchConfigurator configurator) { TargetParameterMap params = launcher.getParameters(); DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool, getButtonTitle(), "Launch", getIcon()); + // NB. Do not invoke read/writeConfigState - Map args; - boolean reset = false; - do { - args = configurator.configureLauncher(launcher, - loadLastLauncherArgs(launcher, true), RelPrompt.BEFORE); - for (ParameterDescription param : params.values()) { - Object val = args.get(param.name); - if (val != null) { - dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class), - val); - } - } - args = dialog.promptArguments(params); - if (args == null) { - // Cancelled - return null; - } - reset = dialog.isResetRequested(); - if (reset) { - args = generateDefaultLauncherArgs(params); - } - saveLauncherArgs(args, params); - } - while (reset); + + Map> defaultArgs = generateDefaultLauncherArgs(params); + Map> lastArgs = configurator.configureLauncher(launcher, + loadLastLauncherArgs(launcher, true), RelPrompt.BEFORE); + Map> args = dialog.promptArguments(params, lastArgs, defaultArgs); + saveLauncherArgs(args, params); return args; } @@ -386,7 +370,8 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg * @param forPrompt true if the user will be confirming the arguments * @return the loaded arguments, or defaults */ - protected Map loadLastLauncherArgs(TargetLauncher launcher, boolean forPrompt) { + protected Map> loadLastLauncherArgs(TargetLauncher launcher, + boolean forPrompt) { /** * TODO: Supposedly, per-program, per-user config stuff is being generalized for analyzers. * Re-examine this if/when that gets merged @@ -401,13 +386,13 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg Element element = XmlUtilities.fromString(property); SaveState state = new SaveState(element); List names = List.of(state.getNames()); - Map args = new LinkedHashMap<>(); + Map> args = new LinkedHashMap<>(); for (ParameterDescription param : params.values()) { if (names.contains(param.name)) { Object configState = ConfigStateField.getState(state, param.type, param.name); if (configState != null) { - args.put(param.name, configState); + args.put(param.name, ValStr.from(configState)); } } } @@ -426,7 +411,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg e); } } - Map args = generateDefaultLauncherArgs(params); + Map> args = generateDefaultLauncherArgs(params); saveLauncherArgs(args, params); return args; } @@ -447,7 +432,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg * @param configurator a means of configuring the launcher * @return the chosen arguments, or null if the user cancels at the prompt */ - public Map getLauncherArgs(TargetLauncher launcher, boolean prompt, + public Map> getLauncherArgs(TargetLauncher launcher, boolean prompt, LaunchConfigurator configurator) { return prompt ? configurator.configureLauncher(launcher, @@ -456,7 +441,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg RelPrompt.NONE); } - public Map getLauncherArgs(TargetLauncher launcher, boolean prompt) { + public Map> getLauncherArgs(TargetLauncher launcher, boolean prompt) { return getLauncherArgs(launcher, prompt, LaunchConfigurator.NOP); } @@ -541,13 +526,14 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg // Eww. protected CompletableFuture launch(TargetLauncher launcher, boolean prompt, LaunchConfigurator configurator, TaskMonitor monitor) { - Map args = getLauncherArgs(launcher, prompt, configurator); + Map> args = getLauncherArgs(launcher, prompt, configurator); if (args == null) { throw new CancellationException(); } + Map a = ValStr.toPlainMap(args); return AsyncTimer.DEFAULT_TIMER.mark() .timeOut( - launcher.launch(args), getTimeoutMillis(), () -> onTimedOutLaunch(monitor)); + launcher.launch(a), getTimeoutMillis(), () -> onTimedOutLaunch(monitor)); } protected void checkCancelled(TaskMonitor monitor) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java index 82b159ab51..f54933bee4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java @@ -33,8 +33,9 @@ public enum MiscellaneousUtils { * Obtain a swing component which may be used to edit the property. * *

            - * This has been shamelessly stolen from {@link EditorState#getEditorComponent()}, which seems - * entangled with Ghidra's whole options system. I think this portion could be factored out. + * This has was originally stolen from {@link EditorState#getEditorComponent()}, which seems + * entangled with Ghidra's whole options system. Can that be factored out? Since then, the two + * have drifted apart. * * @param editor the editor for which to obtain an interactive component for editing * @return the component @@ -53,16 +54,11 @@ public enum MiscellaneousUtils { return new PropertyText(editor); } - Class clazz = editor.getClass(); - String clazzName = clazz.getSimpleName(); - if (clazzName.startsWith("String")) { - // Most likely some kind of string editor with a null value. Just use a string - // property and let the value be empty. - return new PropertyText(editor); - } - - throw new IllegalStateException( - "Ghidra does not know how to use PropertyEditor: " + editor.getClass().getName()); + /** + * TODO: Would be nice to know the actual type, but alas! Just default to a PropertyText and + * hope all goes well. + */ + return new PropertyText(editor); } public static void rigFocusAndEnter(Component c, Runnable runnable) { diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/InvocationDialogHelper.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/InvocationDialogHelper.java new file mode 100644 index 0000000000..b65286bbbe --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/InvocationDialogHelper.java @@ -0,0 +1,94 @@ +/* ### + * 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.debug.gui; + +import java.beans.PropertyEditor; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +import docking.test.AbstractDockingTest; +import ghidra.async.SwingExecutorService; +import ghidra.debug.api.ValStr; +import ghidra.framework.options.SaveState; + +public class InvocationDialogHelper> { + + public static > InvocationDialogHelper waitFor( + Class cls) { + D dialog = AbstractDockingTest.waitForDialogComponent(cls); + return new InvocationDialogHelper<>(dialog); + } + + private final AbstractDebuggerParameterDialog

            dialog; + + public InvocationDialogHelper(AbstractDebuggerParameterDialog

            dialog) { + this.dialog = dialog; + } + + public void dismissWithArguments(Map> args) { + dialog.setMemorizedArguments(args); + invoke(); + } + + public Map.Entry> entry(String key, T value) { + return Map.entry(key, ValStr.from(value)); + } + + public void setArg(P param, Object value) { + PropertyEditor editor = dialog.getEditor(param); + runSwing(() -> editor.setValue(value)); + } + + protected void runSwing(Runnable r) { + try { + CompletableFuture.runAsync(r, SwingExecutorService.LATER).get(); + } + catch (ExecutionException e) { + switch (e.getCause()) { + case RuntimeException t -> throw t; + case Exception t -> throw new RuntimeException(t); + default -> ExceptionUtils.rethrow(e.getCause()); + } + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void setArgAsString(P param, String value) { + PropertyEditor editor = dialog.getEditor(param); + runSwing(() -> editor.setAsText(value)); + } + + public void invoke() { + runSwing(() -> dialog.invoke(null)); + } + + public SaveState saveState() { + SaveState parent = new SaveState(); + runSwing(() -> dialog.writeConfigState(parent)); + return parent.getSaveState(AbstractDebuggerParameterDialog.KEY_MEMORIZED_ARGUMENTS); + } + + public void loadState(SaveState state) { + SaveState parent = new SaveState(); + parent.putSaveState(AbstractDebuggerParameterDialog.KEY_MEMORIZED_ARGUMENTS, state); + runSwing(() -> dialog.readConfigState(parent)); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/objects/components/InvocationDialogHelper.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/objects/components/InvocationDialogHelper.java deleted file mode 100644 index 1b1c43af88..0000000000 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/objects/components/InvocationDialogHelper.java +++ /dev/null @@ -1,48 +0,0 @@ -/* ### - * 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.debug.gui.objects.components; - -import static org.junit.Assert.assertNotNull; - -import java.util.Map; - -import docking.test.AbstractDockingTest; -import ghidra.dbg.target.TargetMethod.ParameterDescription; -import ghidra.util.Swing; - -public class InvocationDialogHelper { - - public static InvocationDialogHelper waitFor() { - DebuggerMethodInvocationDialog dialog = - AbstractDockingTest.waitForDialogComponent(DebuggerMethodInvocationDialog.class); - return new InvocationDialogHelper(dialog); - } - - private final DebuggerMethodInvocationDialog dialog; - - public InvocationDialogHelper(DebuggerMethodInvocationDialog dialog) { - this.dialog = dialog; - } - - public void dismissWithArguments(Map args) { - for (Map.Entry a : args.entrySet()) { - ParameterDescription p = dialog.parameters.get(a.getKey()); - assertNotNull(p); - dialog.setMemorizedArgument(a.getKey(), p.type.asSubclass(Object.class), a.getValue()); - } - Swing.runNow(() -> dialog.invoke(null)); - } -} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java index beafa27700..a7e3ec1a23 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java @@ -33,6 +33,8 @@ public final class NumericUtilities { private final static String HEX_PREFIX_X = "0X"; private final static String HEX_PREFIX_x = "0x"; + private final static String BIN_PREFIX = "0B"; + private final static String OCT_PREFIX = "0"; private final static Set> INTEGER_TYPES = new HashSet<>(); static { @@ -235,6 +237,49 @@ public final class NumericUtilities { return new BigInteger(s, 16); } + private static BigInteger decodeMagnitude(int p, String s) { + // Special case, so it doesn't get chewed by octal parser + if ("0".equals(s)) { + return BigInteger.ZERO; + } + if (s.regionMatches(true, p, HEX_PREFIX_X, 0, HEX_PREFIX_X.length())) { + return new BigInteger(s.substring(p + HEX_PREFIX_X.length()), 16); + } + if (s.regionMatches(true, p, BIN_PREFIX, 0, BIN_PREFIX.length())) { + return new BigInteger(s.substring(p + BIN_PREFIX.length()), 2); + } + // Check last, because prefix is shortest. + if (s.regionMatches(true, p, OCT_PREFIX, 0, OCT_PREFIX.length())) { + return new BigInteger(s.substring(p + OCT_PREFIX.length()), 8); + } + return new BigInteger(s.substring(p), 10); + } + + /** + * Decode a big integer in hex, binary, octal, or decimal, based on the prefix 0x, 0b, or 0. + * + *

            + * This checks for the presence of a case-insensitive prefix. 0x denotes hex, 0b denotes binary, + * 0 denotes octal. If no prefix is given, decimal is assumed. A sign +/- may immediately + * precede the prefix. If no sign is given, a positive value is assumed. + * + * @param s the string to parse + * @return the decoded value + */ + public static BigInteger decodeBigInteger(String s) { + int p = 0; + boolean negative = false; + if (s.startsWith("+")) { + p = 1; + } + else if (s.startsWith("-")) { + p = 1; + negative = true; + } + BigInteger mag = decodeMagnitude(p, s); + return negative ? mag.negate() : mag; + } + /** * returns the value of the specified long as hexadecimal, prefixing with the * {@link #HEX_PREFIX_x} string. diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/NumericUtilitiesTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/NumericUtilitiesTest.java index cc63d8d81e..b8c4731ed5 100644 --- a/Ghidra/Framework/Generic/src/test/java/ghidra/util/NumericUtilitiesTest.java +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/NumericUtilitiesTest.java @@ -4,9 +4,9 @@ * 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. @@ -18,6 +18,7 @@ package ghidra.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import java.math.BigInteger; import java.util.*; import java.util.stream.Collectors; @@ -332,4 +333,89 @@ public class NumericUtilitiesTest { assertEquals(errorMessage, expected[i], actual[i]); } } + + @Test + public void testDecodeBigInteger() { + // Zero special cases + assertEquals(BigInteger.ZERO, NumericUtilities.decodeBigInteger("0")); + assertEquals(BigInteger.ZERO, NumericUtilities.decodeBigInteger("000")); + // Decimal + assertEquals(BigInteger.valueOf(99), NumericUtilities.decodeBigInteger("99")); + assertEquals(BigInteger.valueOf(99), NumericUtilities.decodeBigInteger("+99")); + assertEquals(BigInteger.valueOf(-99), NumericUtilities.decodeBigInteger("-99")); + // Hex + assertEquals(BigInteger.valueOf(0x99), NumericUtilities.decodeBigInteger("0x99")); + assertEquals(BigInteger.valueOf(0x99), NumericUtilities.decodeBigInteger("+0x99")); + assertEquals(BigInteger.valueOf(-0x99), NumericUtilities.decodeBigInteger("-0x99")); + // Binary + assertEquals(BigInteger.valueOf(0b110), NumericUtilities.decodeBigInteger("0b110")); + assertEquals(BigInteger.valueOf(0b110), NumericUtilities.decodeBigInteger("+0b110")); + assertEquals(BigInteger.valueOf(-0b110), NumericUtilities.decodeBigInteger("-0b110")); + // Octal + assertEquals(BigInteger.valueOf(0755), NumericUtilities.decodeBigInteger("0755")); + assertEquals(BigInteger.valueOf(0755), NumericUtilities.decodeBigInteger("+0755")); + assertEquals(BigInteger.valueOf(-0755), NumericUtilities.decodeBigInteger("-0755")); + + // Errors + try { + NumericUtilities.decodeBigInteger(""); + fail(); + } + catch (NumberFormatException e) { + } + try { + NumericUtilities.decodeBigInteger("+"); + fail(); + } + catch (NumberFormatException e) { + } + try { + NumericUtilities.decodeBigInteger("-"); + fail(); + } + catch (NumberFormatException e) { + } + try { + NumericUtilities.decodeBigInteger("0x"); + fail(); + } + catch (NumberFormatException e) { + } + try { + NumericUtilities.decodeBigInteger("0b"); + fail(); + } + catch (NumberFormatException e) { + } + try { + NumericUtilities.decodeBigInteger("a01"); + fail(); + } + catch (NumberFormatException e) { + } + try { + NumericUtilities.decodeBigInteger("081"); + fail(); + } + catch (NumberFormatException e) { + } + try { + NumericUtilities.decodeBigInteger("0x9g"); + fail(); + } + catch (NumberFormatException e) { + } + try { + NumericUtilities.decodeBigInteger(" 10"); + fail(); + } + catch (NumberFormatException e) { + } + try { + NumericUtilities.decodeBigInteger("10 "); + fail(); + } + catch (NumberFormatException e) { + } + } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginScreenShots.java b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginScreenShots.java index 22f8a7be08..5146eac664 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginScreenShots.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginScreenShots.java @@ -4,9 +4,9 @@ * 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. @@ -22,6 +22,7 @@ import org.junit.Test; import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog; import ghidra.app.plugin.core.terminal.TerminalProvider; +import ghidra.debug.api.ValStr; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; import ghidra.framework.plugintool.AutoConfigState.PathIsFile; import ghidra.test.ToyProgramBuilder; @@ -30,7 +31,8 @@ import help.screenshot.GhidraScreenShotGenerator; public class TraceRmiLauncherServicePluginScreenShots extends GhidraScreenShotGenerator { TraceRmiLauncherServicePlugin servicePlugin; - protected void captureLauncherByTitle(String title, Map args) throws Throwable { + protected void captureLauncherByTitle(String title, Map> args) + throws Throwable { servicePlugin = addPlugin(tool, TraceRmiLauncherServicePlugin.class); ToyProgramBuilder pb = new ToyProgramBuilder("demo", false); @@ -49,10 +51,13 @@ public class TraceRmiLauncherServicePluginScreenShots extends GhidraScreenShotGe captureDialog(DebuggerMethodInvocationDialog.class); } + protected ValStr fileArg(String path) { + return new ValStr<>(new PathIsFile(Paths.get(path)), path); + } + @Test public void testCaptureGdbLauncher() throws Throwable { - captureLauncherByTitle("gdb", - Map.of("arg:1", new PathIsFile(Paths.get("/home/user/demo")))); + captureLauncherByTitle("gdb", Map.of("arg:1", fileArg("/home/user/demo"))); } @Test diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptTraceRmiLaunchOfferTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptTraceRmiLaunchOfferTest.java new file mode 100644 index 0000000000..3f0b7a3229 --- /dev/null +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptTraceRmiLaunchOfferTest.java @@ -0,0 +1,584 @@ +/* ### + * 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.debug.gui.tracermi.launcher; + +import static org.junit.Assert.*; + +import java.io.*; +import java.math.BigInteger; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.AbstractTraceRmiLaunchOffer.NullPtyTerminalSession; +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.AbstractTraceRmiLaunchOffer.PtyTerminalSession; +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ParseException; +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes; +import ghidra.app.plugin.core.debug.service.target.DebuggerTargetServicePlugin; +import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler; +import ghidra.app.plugin.core.terminal.TerminalListener; +import ghidra.app.services.Terminal; +import ghidra.debug.api.ValStr; +import ghidra.debug.api.tracermi.TerminalSession; +import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; +import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*; +import ghidra.framework.plugintool.util.PluginException; +import ghidra.program.model.listing.Program; +import ghidra.pty.*; +import ghidra.rmi.trace.TraceRmi.*; +import ghidra.util.Msg; +import ghidra.util.task.ConsoleTaskMonitor; + +public class ScriptTraceRmiLaunchOfferTest extends AbstractGhidraHeadedDebuggerTest { + + static class TestScriptAttributesParser extends ScriptAttributesParser { + List errors = new ArrayList<>(); + + @Override + protected boolean ignoreLine(int lineNo, String line) { + return false; + } + + @Override + protected String removeDelimiter(String line) { + return line; + } + + @Override + protected void reportError(String message) { + super.reportError(message); + errors.add(message); + } + } + + static ScriptAttributes parse(String header, String name) throws ParseException { + try { + TestScriptAttributesParser parser = new TestScriptAttributesParser(); + ScriptAttributes attributes = + parser.parseStream(new ByteArrayInputStream(header.getBytes()), name); + if (!parser.errors.isEmpty()) { + throw new ParseException(null, parser.errors.toString()); + } + return attributes; + } + catch (IOException e) { + throw new AssertionError(e); + } + } + + record Config(Map> args) implements LaunchConfigurator { + public static final Config DEFAULTS = new Config(Map.of()); + + @Override + public PromptMode getPromptMode() { + return PromptMode.NEVER; + } + + @Override + public Map> configureLauncher(TraceRmiLaunchOffer offer, + Map> arguments, RelPrompt relPrompt) { + Map> mod = new HashMap<>(arguments); + mod.putAll(args); + return mod; + } + } + + record MockTerminal() implements Terminal { + @Override + public void addTerminalListener(TerminalListener listener) { + } + + @Override + public void removeTerminalListener(TerminalListener listener) { + } + + @Override + public void injectDisplayOutput(ByteBuffer bb) { + } + + @Override + public void setSubTitle(String title) { + } + + @Override + public String getSubTitle() { + return null; + } + + @Override + public void setFixedSize(short cols, short rows) { + } + + @Override + public void setDynamicSize() { + } + + @Override + public void setMaxScrollBackRows(int rows) { + } + + @Override + public int getColumns() { + return 0; + } + + @Override + public int getRows() { + return 0; + } + + @Override + public int getScrollBackRows() { + return 0; + } + + @Override + public String getFullText() { + return null; + } + + @Override + public String getDisplayText() { + return null; + } + + @Override + public String getLineText(int line) { + return null; + } + + @Override + public String getRangeText(int startCol, int startLine, int endCol, int endLine) { + return null; + } + + @Override + public int getCursorRow() { + return 0; + } + + @Override + public int getCursorColumn() { + return 0; + } + + @Override + public void close() { + } + + @Override + public void terminated() { + } + + @Override + public void setTerminateAction(Runnable action) { + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public void toFront() { + } + } + + record MockPtySession() implements PtySession { + @Override + public int waitExited() throws InterruptedException { + return 0; + } + + @Override + public int waitExited(long timeout, TimeUnit unit) + throws InterruptedException, TimeoutException { + return 0; + } + + @Override + public void destroyForcibly() { + } + + @Override + public String description() { + return null; + } + } + + record MockPty() implements Pty { + @Override + public String toString() { + return getClass().getSimpleName(); + } + + @Override + public PtyParent getParent() { + return null; + } + + @Override + public PtyChild getChild() { + return null; + } + + @Override + public void close() throws IOException { + } + } + + File nameTempFile() { + return Paths.get(getTestDirectoryPath(), name.getMethodName()).toFile(); + } + + static class MockClient extends Thread implements AutoCloseable { + private static final DomObjId TRACE_ID = DomObjId.newBuilder().setId(0).build(); + private final SocketAddress addr; + private final String name; + + private Throwable exc; + + Socket s; + OutputStream out; + InputStream in; + + public MockClient(SocketAddress addr, String name) { + setDaemon(true); + this.addr = addr; + this.name = name; + } + + void send(RootMessage msg) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES); + buf.putInt(msg.getSerializedSize()); + out.write(buf.array()); + msg.writeTo(out); + out.flush(); + } + + RootMessage recv() throws IOException { + int len = ByteBuffer.wrap(in.readNBytes(Integer.BYTES)).getInt(); + return RootMessage.parseFrom(in.readNBytes(len)); + } + + void completeNegotiation() throws IOException { + send(RootMessage.newBuilder() + .setRequestNegotiate(RequestNegotiate.newBuilder() + .setVersion(TraceRmiHandler.VERSION) + .setDescription("Mock Client")) + .build()); + Msg.debug(this, "Sent negotation request"); + RootMessage reply = recv(); + Msg.debug(this, "Received: " + reply); + assertNotNull(reply.getReplyNegotiate()); + } + + void createTrace() throws IOException { + send(RootMessage.newBuilder() + .setRequestCreateTrace(RequestCreateTrace.newBuilder() + .setOid(TRACE_ID) + .setLanguage(Language.newBuilder().setId("Toy:BE:64:default")) + .setCompiler(Compiler.newBuilder().setId("default")) + .setPath(FilePath.newBuilder().setPath(name))) + .build()); + Msg.debug(this, "Sent createTrace request"); + RootMessage reply = recv(); + Msg.debug(this, "Received: " + reply); + assertNotNull(reply.getReplyCreateTrace()); + } + + protected void doRun() throws Throwable { + s = new Socket(); + s.connect(addr); + out = s.getOutputStream(); + in = s.getInputStream(); + + completeNegotiation(); + + createTrace(); + + s.close(); + } + + @Override + public void run() { + try { + doRun(); + } + catch (Throwable e) { + Msg.error(this, "Mock client crashed", e); + this.exc = e; + } + } + + @Override + public void close() throws Exception { + join(1000); + if (exc != null) { + throw new RuntimeException("Exception in mock client", exc); + } + assertFalse(isAlive()); + } + } + + class TestScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunchOffer { + int nextNullId = 0; + + public TestScriptTraceRmiLaunchOffer(Program program, String header) throws ParseException { + super(launchPlugin, program, nameTempFile(), name.getMethodName(), + parse(header, name.getMethodName())); + } + + @Override + protected NullPtyTerminalSession nullPtyTerminal() throws IOException { + return new NullPtyTerminalSession(new MockTerminal(), new MockPty(), + "null-" + (++nextNullId)); + } + + @Override + protected PtyTerminalSession runInTerminal(List commandLine, + Map env, File workingDirectory, + Collection subordinates) throws IOException { + String host = env.get(ScriptAttributesParser.ENV_GHIDRA_TRACE_RMI_HOST); + int port = Integer.parseInt(env.get(ScriptAttributesParser.ENV_GHIDRA_TRACE_RMI_PORT)); + // The plugin is waiting for a connection. Have to satisfy it to move on. + client = new MockClient(new InetSocketAddress(host, port), name.getMethodName()); + client.start(); + return new PtyTerminalSession(new MockTerminal(), new MockPty(), new MockPtySession(), + client); + } + } + + TraceRmiLauncherServicePlugin launchPlugin; + + MockClient client; + + record ResultAndClient(LaunchResult result, MockClient client) implements AutoCloseable { + @Override + public void close() throws Exception { + client.close(); + result.close(); + } + + public PtyTerminalSession mockSession() { + return new PtyTerminalSession(new MockTerminal(), new MockPty(), new MockPtySession(), + client); + } + + public NullPtyTerminalSession mockNull(String name) { + return new NullPtyTerminalSession(new MockTerminal(), new MockPty(), name); + } + } + + ResultAndClient launchNoErr(TraceRmiLaunchOffer offer, Map> args) + throws Throwable { + LaunchResult result = offer.launchProgram(new ConsoleTaskMonitor(), new Config(args)); + if (result.exception() != null) { + throw (result.exception()); + } + return new ResultAndClient(result, client); + } + + ResultAndClient launchNoErr(TraceRmiLaunchOffer offer) throws Throwable { + return launchNoErr(offer, Map.of()); + } + + @Before + public void setupOfferTest() throws PluginException { + // BUG: Seems I shouldn't have to do this. It's in servicesRequired (transitive) + addPlugin(tool, DebuggerTargetServicePlugin.class); + launchPlugin = addPlugin(tool, TraceRmiLauncherServicePlugin.class); + } + + @Test + public void testTitleOnly() throws Throwable { + createProgram(); + TraceRmiLaunchOffer offer = new TestScriptTraceRmiLaunchOffer(program, """ + @title Test + """); + + try (ResultAndClient rac = launchNoErr(offer)) { + assertEquals(Map.ofEntries( + Map.entry("Shell", rac.mockSession())), + rac.result.sessions()); + } + } + + @Test + public void testTtyAlways() throws Throwable { + TraceRmiLaunchOffer offer = new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @tty TTY_TARGET + """); + try (ResultAndClient rac = launchNoErr(offer)) { + assertEquals(Map.ofEntries( + Map.entry("Shell", rac.mockSession()), + Map.entry("TTY_TARGET", rac.mockNull("null-1"))), + rac.result.sessions()); + } + } + + @Test + public void testTtyCondBoolFalse() throws Throwable { + TraceRmiLaunchOffer offer = new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @env OPT_EXTRA_TTY:bool=false "Extra TTY" "Provide a separate tty." + @tty TTY_TARGET if env:OPT_EXTRA_TTY + """); + try (ResultAndClient rac = launchNoErr(offer)) { + assertEquals(Map.ofEntries( + Map.entry("Shell", rac.mockSession())), + rac.result.sessions()); + } + } + + @Test + public void testTtyCondBoolTrue() throws Throwable { + TraceRmiLaunchOffer offer = new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @env OPT_EXTRA_TTY:bool=false "Extra TTY" "Provide a separate tty." + @tty TTY_TARGET if env:OPT_EXTRA_TTY + """); + try (ResultAndClient rac = launchNoErr(offer, Map.of( + "env:OPT_EXTRA_TTY", ValStr.from(true)))) { + assertEquals(Map.ofEntries( + Map.entry("Shell", rac.mockSession()), + Map.entry("TTY_TARGET", rac.mockNull("null-1"))), + rac.result.sessions()); + } + } + + @Test(expected = ParseException.class) + public void testTtyCondBoolTypeMismatch() throws Throwable { + new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @env OPT_SOME_INT:int=0 "An integer" "Just an option for testing." + @tty TTY_TARGET if env:OPT_SOME_INT + """); + } + + @Test(expected = ParseException.class) + public void testTtyCondBoolNoSuch() throws Throwable { + new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @tty TTY_TARGET if env:NO_SUCH + """); + } + + @Test + public void testTtyCondStrEqFalse() throws Throwable { + TraceRmiLaunchOffer offer = new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @env OPT_EXTRA_TTY:str="No" "Extra TTY" "Provide a separate tty." + @tty TTY_TARGET if env:OPT_EXTRA_TTY == "Yes" + """); + try (ResultAndClient rac = launchNoErr(offer)) { + assertEquals(Map.ofEntries( + Map.entry("Shell", rac.mockSession())), + rac.result.sessions()); + } + } + + @Test + public void testTtyCondStrEqTrue() throws Throwable { + TraceRmiLaunchOffer offer = new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @env OPT_EXTRA_TTY:str="No" "Extra TTY" "Provide a separate tty." + @tty TTY_TARGET if env:OPT_EXTRA_TTY == "Yes" + """); + try (ResultAndClient rac = launchNoErr(offer, Map.of( + "env:OPT_EXTRA_TTY", ValStr.str("Yes")))) { + assertEquals(Map.ofEntries( + Map.entry("Shell", rac.mockSession()), + Map.entry("TTY_TARGET", rac.mockNull("null-1"))), + rac.result.sessions()); + } + } + + @Test(expected = ParseException.class) + public void testTtyCondStrEqNoSuch() throws Throwable { + new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @tty TTY_TARGET if env:NO_SUCH == "Yes" + """); + } + + @Test + public void testTtyCondIntEqFalse() throws Throwable { + TraceRmiLaunchOffer offer = new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @env OPT_EXTRA_TTY:int=0 "Extra TTY" "Provide a separate tty." + @tty TTY_TARGET if env:OPT_EXTRA_TTY == 6 + """); + try (ResultAndClient rac = launchNoErr(offer)) { + assertEquals(Map.ofEntries( + Map.entry("Shell", rac.mockSession())), + rac.result.sessions()); + } + } + + @Test + public void testTtyCondIntEqTrue() throws Throwable { + TraceRmiLaunchOffer offer = new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @env OPT_EXTRA_TTY:int=0 "Extra TTY" "Provide a separate tty." + @tty TTY_TARGET if env:OPT_EXTRA_TTY == 0b110 + """); + try (ResultAndClient rac = launchNoErr(offer, Map.of( + "env:OPT_EXTRA_TTY", ValStr.from(BigInteger.valueOf(6))))) { + assertEquals(Map.ofEntries( + Map.entry("Shell", rac.mockSession()), + Map.entry("TTY_TARGET", rac.mockNull("null-1"))), + rac.result.sessions()); + } + } + + @Test(expected = ParseException.class) + public void testTtyCondIntEqParseErr() throws Throwable { + new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @env OPT_SOME_INT:int=0 "An integer" "Just an option for testing." + @tty TTY_TARGET if env:OPT_SOME_INT == "Yes" + """); + } + + @Test(expected = ParseException.class) + public void testTtyCondIntEqNoSuch() throws Throwable { + new TestScriptTraceRmiLaunchOffer(null, """ + @title Test + @no-image + @tty TTY_TARGET if env:NO_SUCH == 6 + """); + } +} diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TestTraceRmiLaunchOpinion.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TestTraceRmiLaunchOpinion.java index c467376c51..51e753a693 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TestTraceRmiLaunchOpinion.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TestTraceRmiLaunchOpinion.java @@ -4,9 +4,9 @@ * 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. @@ -20,9 +20,8 @@ import static org.junit.Assert.assertEquals; import java.net.SocketAddress; import java.util.*; -import ghidra.dbg.target.TargetMethod.ParameterDescription; -import ghidra.debug.api.tracermi.TerminalSession; -import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; +import ghidra.debug.api.ValStr; +import ghidra.debug.api.tracermi.*; import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion; import ghidra.program.model.listing.Program; import ghidra.util.task.TaskMonitor; @@ -30,9 +29,9 @@ import ghidra.util.task.TaskMonitor; public class TestTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion { public static class TestTraceRmiLaunchOffer extends AbstractTraceRmiLaunchOffer { - private static final ParameterDescription PARAM_DESC_IMAGE = - ParameterDescription.create(String.class, "image", true, "", - PARAM_DISPLAY_IMAGE, "Image to execute"); + private static final LaunchParameter PARAM_IMAGE = + LaunchParameter.create(String.class, "image", PARAM_DISPLAY_IMAGE, "Image to execute", + true, ValStr.str(""), str -> str); public TestTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin, Program program) { super(plugin, program); @@ -58,8 +57,8 @@ public class TestTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion { } @Override - public Map> getParameters() { - return Map.ofEntries(Map.entry(PARAM_DESC_IMAGE.name, PARAM_DESC_IMAGE)); + public Map> getParameters() { + return LaunchParameter.mapOf(PARAM_IMAGE); } @Override @@ -69,19 +68,19 @@ public class TestTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion { @Override protected void launchBackEnd(TaskMonitor monitor, Map sessions, - Map args, SocketAddress address) throws Exception { + Map> args, SocketAddress address) throws Exception { } @Override public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator configurator) { assertEquals(PromptMode.NEVER, configurator.getPromptMode()); - Map args = + Map> args = configurator.configureLauncher(this, loadLastLauncherArgs(false), RelPrompt.NONE); return new LaunchResult(program, null, null, null, null, - new RuntimeException("Test launcher cannot launch " + args.get("image"))); + new RuntimeException("Test launcher cannot launch " + PARAM_IMAGE.get(args).val())); } - public void saveLauncherArgs(Map args) { + public void saveLauncherArgs(Map> args) { super.saveLauncherArgs(args, getParameters()); } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialogTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialogTest.java new file mode 100644 index 0000000000..f44bbb2c4e --- /dev/null +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLaunchDialogTest.java @@ -0,0 +1,255 @@ +/* ### + * 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.debug.gui.tracermi.launcher; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.math.BigInteger; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.junit.*; + +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; +import ghidra.app.plugin.core.debug.gui.InvocationDialogHelper; +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.BaseType; +import ghidra.async.SwingExecutorService; +import ghidra.debug.api.ValStr; +import ghidra.debug.api.tracermi.LaunchParameter; +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.AutoConfigState.PathIsDir; +import ghidra.framework.plugintool.AutoConfigState.PathIsFile; + +public class TraceRmiLaunchDialogTest extends AbstractGhidraHeadedDebuggerTest { + private static final LaunchParameter PARAM_STRING = + BaseType.STRING.createParameter("some_string", "A String", "A string", + true, ValStr.str("Hello")); + private static final LaunchParameter PARAM_INT = + BaseType.INT.createParameter("some_int", "An Int", "An integer", + true, intVal(99)); + private static final LaunchParameter PARAM_BOOL = + BaseType.BOOL.createParameter("some_bool", "A Bool", "A boolean", + true, ValStr.from(true)); + private static final LaunchParameter PARAM_PATH = + BaseType.PATH.createParameter("some_path", "A Path", "A path", + true, pathVal("my_path")); + private static final LaunchParameter PARAM_DIR = + BaseType.DIR.createParameter("some_dir", "A Dir", "A directory", + true, dirVal("my_dir")); + private static final LaunchParameter PARAM_FILE = + BaseType.FILE.createParameter("some_file", "A File", "A file", + true, fileVal("my_file")); + + private TraceRmiLaunchDialog dialog; + + @Before + public void setupRmiLaunchDialogTest() throws Exception { + dialog = new TraceRmiLaunchDialog(tool, "Launch Test", "Launch", null); + } + + record PromptResult(CompletableFuture>> args, + InvocationDialogHelper, ?> h) {} + + protected PromptResult prompt(LaunchParameter... params) { + CompletableFuture>> args = CompletableFuture.supplyAsync( + () -> dialog.promptArguments(LaunchParameter.mapOf(params), Map.of(), Map.of()), + SwingExecutorService.LATER); + InvocationDialogHelper, ?> helper = + InvocationDialogHelper.waitFor(TraceRmiLaunchDialog.class); + return new PromptResult(args, helper); + } + + static ValStr intVal(long val, String str) { + return new ValStr<>(BigInteger.valueOf(val), str); + } + + static ValStr intVal(long val) { + return ValStr.from(BigInteger.valueOf(val)); + } + + static ValStr pathVal(String path) { + return new ValStr<>(Paths.get(path), path); + } + + static ValStr dirVal(String path) { + return new ValStr<>(PathIsDir.fromString(path), path); + } + + static ValStr fileVal(String path) { + return new ValStr<>(PathIsFile.fromString(path), path); + } + + @Test + public void testStringDefaultValue() throws Throwable { + PromptResult result = prompt(PARAM_STRING); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_string", ValStr.str("Hello")), args); + } + + @Test + public void testStringInputValue() throws Throwable { + PromptResult result = prompt(PARAM_STRING); + result.h.setArgAsString(PARAM_STRING, "World"); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_string", ValStr.str("World")), args); + } + + @Test + public void testIntDefaultValue() throws Throwable { + PromptResult result = prompt(PARAM_INT); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_int", intVal(99)), args); + } + + @Test + public void testIntInputHexValue() throws Throwable { + PromptResult result = prompt(PARAM_INT); + result.h.setArgAsString(PARAM_INT, "0x11"); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_int", intVal(17, "0x11")), args); + } + + @Test + public void testIntInputHexValueIncomplete() throws Throwable { + PromptResult result = prompt(PARAM_INT); + try { + result.h.setArgAsString(PARAM_INT, "0x"); + fail(); + } + catch (NumberFormatException e) { + // pass + } + result.h.invoke(); + } + + @Test + public void testIntSaveHexValue() throws Throwable { + PromptResult result = prompt(PARAM_INT); + result.h.setArgAsString(PARAM_INT, "0x11"); + result.h.invoke(); + + SaveState state = result.h.saveState(); + assertEquals("0x11", state.getString("some_int,java.math.BigInteger", null)); + } + + @Test + @Ignore + public void testIntLoadHexValue() throws Throwable { + /** + * TODO: This is a bit out of order. However, the dialog cannot load/decode from the state + * until it has the parameters. Worse, to check that user input was valid, the dialog + * verifies that the value it gets back matches the text in the box, because if it doesn't, + * then the editor must have failed to parse/decode the value. Currently, loading the state + * while the dialog box has already populated its values, does not modify the contents of + * any editor, so the text will not match, causing this test to fail. + */ + PromptResult result = prompt(PARAM_INT); + SaveState state = new SaveState(); + state.putString("some_int,java.math.BigInteger", "0x11"); + result.h.loadState(state); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_int", intVal(17, "0x11")), args); + } + + @Test + public void testBoolDefaultValue() throws Throwable { + PromptResult result = prompt(PARAM_BOOL); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_bool", ValStr.from(true)), args); + } + + @Test + public void testBoolInputValue() throws Throwable { + PromptResult result = prompt(PARAM_BOOL); + result.h.setArg(PARAM_BOOL, false); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_bool", ValStr.from(false)), args); + } + + @Test + public void testPathDefaultValue() throws Throwable { + PromptResult result = prompt(PARAM_PATH); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_path", pathVal("my_path")), args); + } + + @Test + public void testPathInputValue() throws Throwable { + PromptResult result = prompt(PARAM_PATH); + result.h.setArgAsString(PARAM_PATH, "your_path"); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_path", pathVal("your_path")), args); + } + + @Test + public void testDirDefaultValue() throws Throwable { + PromptResult result = prompt(PARAM_DIR); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_dir", dirVal("my_dir")), args); + } + + @Test + public void testDirInputValue() throws Throwable { + PromptResult result = prompt(PARAM_DIR); + result.h.setArgAsString(PARAM_DIR, "your_dir"); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_dir", dirVal("your_dir")), args); + } + + @Test + public void testFileDefaultValue() throws Throwable { + PromptResult result = prompt(PARAM_FILE); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_file", fileVal("my_file")), args); + } + + @Test + public void testFileInputValue() throws Throwable { + PromptResult result = prompt(PARAM_FILE); + result.h.setArgAsString(PARAM_FILE, "your_file"); + result.h.invoke(); + + Map> args = waitOn(result.args); + assertEquals(Map.of("some_file", fileVal("your_file")), args); + } +} diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java index 719b30a6f3..dbec222909 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java @@ -4,9 +4,9 @@ * 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. @@ -28,6 +28,7 @@ import org.junit.Test; import db.Transaction; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.services.TraceRmiLauncherService; +import ghidra.debug.api.ValStr; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*; import ghidra.framework.OperatingSystem; @@ -57,11 +58,11 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug protected LaunchConfigurator gdbFileOnly(String file) { return new LaunchConfigurator() { @Override - public Map configureLauncher(TraceRmiLaunchOffer offer, - Map arguments, RelPrompt relPrompt) { - Map args = new HashMap<>(arguments); - args.put("arg:1", new PathIsFile(Paths.get(file))); - args.put("env:OPT_START_CMD", "starti"); + public Map> configureLauncher(TraceRmiLaunchOffer offer, + Map> arguments, RelPrompt relPrompt) { + Map> args = new HashMap<>(arguments); + args.put("arg:1", new ValStr<>(new PathIsFile(Paths.get(file)), file)); + args.put("env:OPT_START_CMD", ValStr.str("starti")); return args; } }; @@ -93,10 +94,10 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug protected LaunchConfigurator dbgengFileOnly(String file) { return new LaunchConfigurator() { @Override - public Map configureLauncher(TraceRmiLaunchOffer offer, - Map arguments, RelPrompt relPrompt) { - Map args = new HashMap<>(arguments); - args.put("env:OPT_TARGET_IMG", new PathIsFile(Paths.get(file))); + public Map> configureLauncher(TraceRmiLaunchOffer offer, + Map> arguments, RelPrompt relPrompt) { + Map> args = new HashMap<>(arguments); + args.put("env:OPT_TARGET_IMG", new ValStr<>(new PathIsFile(Paths.get(file)), file)); return args; } }; diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/debug/flatapi/FlatDebuggerRmiAPITest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/debug/flatapi/FlatDebuggerRmiAPITest.java index ec30950d01..11ffd910dc 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/debug/flatapi/FlatDebuggerRmiAPITest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/debug/flatapi/FlatDebuggerRmiAPITest.java @@ -4,9 +4,9 @@ * 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. @@ -31,6 +31,7 @@ import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TestTraceRmiLaunchOpin import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin; import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteMethod; import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget; +import ghidra.debug.api.ValStr; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.LaunchResult; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; @@ -182,7 +183,7 @@ public class FlatDebuggerRmiAPITest extends AbstractLiveFlatDebuggerAPITest Date: Thu, 5 Sep 2024 13:21:12 -0400 Subject: [PATCH 50/53] Test fixes --- .../base/memsearch/gui/MemorySearchProvider.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java index fb2e9d8894..f6031ae460 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java @@ -550,13 +550,19 @@ public class MemorySearchProvider extends ComponentProviderAdapter } private void dispose() { - glassPaneMessage.hide(); - glassPaneMessage = null; + if (glassPaneMessage != null) { + glassPaneMessage.hide(); + glassPaneMessage = null; + } + matchHighlighter.dispose(); + USED_IDS.remove(id); + if (navigatable != null) { navigatable.removeNavigatableListener(this); } + resultsPanel.dispose(); tool.removeContextListener(this); program.removeCloseListener(this); From 279698c4b2d0321fac66dfd26586794565d069e8 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:15:08 -0400 Subject: [PATCH 51/53] GP-4892 - Enum Editor - Fixed incorrect cell being edited on Tab key press while editing --- .../core/datamgr/editor/EnumEditorPanel.java | 188 ++++++++++++------ .../core/datamgr/editor/EnumEditor2Test.java | 105 +++++++++- 2 files changed, 230 insertions(+), 63 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java index 1a1da3fc33..a66d522d1b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java @@ -17,7 +17,8 @@ package ghidra.app.plugin.core.datamgr.editor; import java.awt.*; import java.awt.event.*; -import java.util.*; +import java.util.Arrays; +import java.util.EventObject; import javax.swing.*; import javax.swing.event.*; @@ -99,20 +100,15 @@ class EnumEditorPanel extends JPanel { // invoke later because the key press on the table causes the selection to change Swing.runLater(() -> { - try { - if (table.isEditing()) { - return; // don't change the selection if a new edit is in progress - } - - int row = tableModel.getRow(name); - if (row >= 0 && row < tableModel.getRowCount()) { - table.setRowSelectionInterval(row, row); - Rectangle rect = table.getCellRect(row, 0, false); - table.scrollRectToVisible(rect); - } + if (table.isEditing()) { + return; // don't change the selection if a new edit is in progress } - catch (NoSuchElementException e) { - // ignore + + int row = tableModel.getRow(name); + if (row >= 0 && row < tableModel.getRowCount()) { + table.setRowSelectionInterval(row, row); + Rectangle rect = table.getCellRect(row, 0, false); + table.scrollRectToVisible(rect); } }); } @@ -500,6 +496,26 @@ class EnumEditorPanel extends JPanel { EnumEntry enumEntry = tableModel.getRowObject(row); return enumEntry.getName(); } + + private void edit(int row, int col) { + scrollToCell(row, col); + table.setRowSelectionInterval(row, row); + table.editCellAt(row, col); + } + + private void scrollToCell(int row, int col) { + if (table.getAutoscrolls()) { + Rectangle cellRect = table.getCellRect(row, col, false); + if (cellRect != null) { + table.scrollRectToVisible(cellRect); + } + } + } + + private int getRow(EnumEntry entry) { + return tableModel.getRowIndex(entry); + } + //================================================================================================== // Inner Classes //================================================================================================== @@ -578,61 +594,113 @@ class EnumEditorPanel extends JPanel { return; } - int row = table.getEditingRow(); - int col = table.getEditingColumn(); - int rowCount = table.getRowCount(); - int columnCount = table.getColumnCount(); - int keycode = e.getKeyCode(); - switch (keycode) { - case KeyEvent.VK_TAB: - if (e.isShiftDown()) { - if (--col < 0) { - col = columnCount - 1; - if (--row < 0) { - row = rowCount - 1; - col = columnCount - 1; - } - } - } - else { - if (++col == columnCount) { - col = 0; - - if (++row == rowCount) { - row = 0; - } - } - } - break; - case KeyEvent.VK_DOWN: - if (++row == rowCount) { - row = 0; - } - break; - case KeyEvent.VK_UP: - if (--row < 0) { - row = rowCount - 1; - } - break; - default: - return; + int code = e.getKeyCode(); + boolean moveEdit = + code == KeyEvent.VK_TAB || code == KeyEvent.VK_UP || code == KeyEvent.VK_DOWN; + if (!moveEdit) { + return; } e.consume(); - scrollToCell(row, col); - table.setRowSelectionInterval(row, row); - table.editCellAt(row, col); + int row = table.getEditingRow(); + int col = table.getEditingColumn(); + + // + // The user has attempted to edit a new cell while there is an edit in progress. The + // table may get re-sorted when this happens, as the current edit may get committed, + // which can affect the table's sort. In this case, we need to find where the + // currently edited cell is moved to so that we can correctly move to the user's + // requested cell, which is relative to the current cell being edited. + // + EnumEntry editedEntry = tableModel.getRowObject(row); + + TableCellEditor editor = table.getCellEditor(); + editor.stopCellEditing(); + + CellEditRequest cellEditRequest = + new CellEditRequest(EnumEditorPanel.this, editedEntry, col, e); + Swing.runLater(cellEditRequest); } }; - private void scrollToCell(int row, int col) { - if (table.getAutoscrolls()) { - Rectangle cellRect = table.getCellRect(row, col, false); - if (cellRect != null) { - table.scrollRectToVisible(cellRect); + private record CellEditRequest(EnumEditorPanel editorPanel, EnumEntry editedEntry, + int editCol, + KeyEvent e) + implements Runnable { + + @Override + public void run() { + + JTable table = editorPanel.table; + + // note: this lookup works because equals() is *not* overridden and any edits are + // applied to the object in memory so that the default '==' lookup works. + int row = editorPanel.getRow(editedEntry); + int col = editCol; + int rowCount = table.getRowCount(); + switch (e.getKeyCode()) { + case KeyEvent.VK_TAB: + boolean forward = !e.isShiftDown(); + editNextCell(table, forward, row, col); + return; + case KeyEvent.VK_DOWN: + if (++row == rowCount) { + row = 0; + } + editorPanel.edit(row, col); + return; + case KeyEvent.VK_UP: + if (--row < 0) { + row = rowCount - 1; + } + editorPanel.edit(row, col); + return; + default: + return; + } + + } + + private void editNextCell(JTable table, boolean forward, int row, int col) { + + int columnCount = table.getColumnCount(); + int rowCount = table.getRowCount(); + if (forward) { + + int nextRow = row; + int nextCol = col + 1; + if (nextCol == columnCount) { + + // wrap to the next row + nextCol = 0; + nextRow++; + if (nextRow == rowCount) { + // wrap to the first row + nextRow = 0; + } + } + + editorPanel.edit(nextRow, nextCol); + return; + } + + // going backward + int nextRow = row; + int nextCol = col - 1; + if (nextCol < 0) { + nextCol = columnCount - 1; + + nextRow--; + if (nextRow < 0) { + nextRow = rowCount - 1; + nextCol = columnCount - 1; + } + + editorPanel.edit(nextRow, nextCol); } } + } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/EnumEditor2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/EnumEditor2Test.java index dbda274b50..1b466e7bda 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/EnumEditor2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/EnumEditor2Test.java @@ -4,9 +4,9 @@ * 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. @@ -18,6 +18,7 @@ package ghidra.app.plugin.core.datamgr.editor; import static org.junit.Assert.*; import java.awt.*; +import java.awt.event.KeyEvent; import javax.swing.*; import javax.swing.table.*; @@ -27,6 +28,8 @@ import org.junit.*; import docking.*; import docking.action.DockingActionIf; import docking.widgets.OptionDialog; +import docking.widgets.table.*; +import docking.widgets.table.ColumnSortState.SortDirection; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.data.*; @@ -469,8 +472,8 @@ public class EnumEditor2Test extends AbstractGhidraHeadedIntegrationTest { @Test public void testEditName() throws Exception { - Enum enummDt = editSampleEnum(); + Enum enummDt = editSampleEnum(); EnumEditorPanel panel = findEditorPanel(tool.getToolFrame()); runSwing(() -> { @@ -482,6 +485,37 @@ public class EnumEditor2Test extends AbstractGhidraHeadedIntegrationTest { assertEquals("MyColors", enummDt.getName()); } + @Test + public void testEditName_RowChanges_NavigationWithTab() throws Exception { + + editSampleEnum(); + + EnumEditorPanel panel = findEditorPanel(tool.getToolFrame()); + JTable table = panel.getTable(); + + sortOnNameColumn(); + + int row = 0; + int col = 0; + clickTableCell(table, row, col, 2); + assertTrue(runSwing(() -> table.isEditing())); + + // note: this new name will cause the enum entry at row 0 to get moved to row 1 + String newName = "MyColors"; + JTextField editorField = getCellEditorTextField(); + assertNotNull(editorField); + setText(editorField, newName); + + pressTab(editorField); + + // get the row after the table has been sorted + int newRow = findRowByName(newName); + assertNotEquals(row, newRow); + + int newColumn = col + 1; + assertEditing(newRow, newColumn); + } + @Test public void testDuplicateName() throws Exception { @@ -672,6 +706,71 @@ public class EnumEditor2Test extends AbstractGhidraHeadedIntegrationTest { // Private Methods //================================================================================================== + private void sortOnNameColumn() { + + JTable table = getTable(); + SortedTableModel sortedModel = (SortedTableModel) table.getModel(); + TableSortState sortState = getSortState(sortedModel); + ColumnSortState primarySortState = sortState.iterator().next(); + SortDirection sortDirection = primarySortState.getSortDirection(); + if (primarySortState.getColumnModelIndex() == EnumTableModel.NAME_COL) { + if (SortDirection.ASCENDING == sortDirection) { + return; // already sorted + } + } + + TableSortState newSortState = + TableSortState.createDefaultSortState(EnumTableModel.NAME_COL, true); + runSwing(() -> sortedModel.setTableSortState(newSortState)); + } + + private TableSortState getSortState(SortedTableModel sortedModel) { + return runSwing(() -> sortedModel.getTableSortState()); + } + + private JTable getTable() { + EnumEditorPanel panel = findEditorPanel(tool.getToolFrame()); + return panel.getTable(); + } + + protected JTextField getCellEditorTextField() { + Object editorComponent = getTable().getEditorComponent(); + if (editorComponent instanceof JTextField) { + return (JTextField) editorComponent; + } + + fail("Either not editing, or editing a field that is a custom editor (not a text field)"); + return null; + } + + private void assertEditing(int row, int column) { + JTable table = getTable(); + assertTrue(runSwing(table::isEditing)); + assertEquals(row, (int) runSwing(table::getEditingRow)); + assertEquals(row, (int) runSwing(table::getEditingColumn)); + } + + private int findRowByName(String name) { + + JTable table = getTable(); + return runSwing(() -> { + int col = 0; // Name column defaults to 0 + int n = table.getRowCount(); + for (int i = 0; i < n; i++) { + String value = table.getValueAt(i, col).toString(); + if (name.equals(value)) { + return i; + } + } + return -1; + }); + } + + private void pressTab(JComponent component) { + triggerActionKey(component, 0, KeyEvent.VK_TAB); + waitForSwing(); + } + private EnumEditorPanel findEditorPanel(Window w) { Window[] windows = w.getOwnedWindows(); for (Window window : windows) { From 066a883e7903086b30967ee3f64da7b2abbf329e Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Thu, 5 Sep 2024 21:35:08 +0000 Subject: [PATCH 52/53] GP-4987 fix DWARF handling of rangelist and locationlists The logic was not handling attributes encoded via dw_form_dataN (a constant). Additionally, quieted warnings about mismatched attribute/form combinations. Fixes issue #5982 --- .../app/util/bin/format/dwarf/DWARFProgram.java | 12 +++++++++--- .../bin/format/dwarf/attribs/DWARFAttribute.java | 14 +++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java index 4d7ef32aea..d8304b878d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java @@ -4,9 +4,9 @@ * 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. @@ -1090,7 +1090,10 @@ public class DWARFProgram implements Closeable { debugRngLists.setPointerIndex(rnglistOffset); return DWARFRangeList.readV5(debugRngLists, cu); } - case DW_FORM_sec_offset: { + case DW_FORM_sec_offset: + case DW_FORM_data2: + case DW_FORM_data4: + case DW_FORM_data8: { long rnglistOffset = rngListAttr.getValue(); short dwarfVersion = cu.getDWARFVersion(); if (dwarfVersion < 5) { @@ -1204,6 +1207,9 @@ public class DWARFProgram implements Closeable { try { switch (loclistAttr.getAttributeForm()) { case DW_FORM_sec_offset: + case DW_FORM_data2: + case DW_FORM_data4: + case DW_FORM_data8: int dwarfVer = cu.getDWARFVersion(); if (dwarfVer < 5) { debugLocation.setPointerIndex(loclistAttr.getUnsignedValue()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttribute.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttribute.java index bf4350ac00..360071b195 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttribute.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttribute.java @@ -4,9 +4,9 @@ * 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. @@ -31,7 +31,7 @@ import ghidra.app.util.bin.BinaryReader; */ public enum DWARFAttribute { DW_AT_sibling(0x1, reference), - DW_AT_location(0x2, exprloc, loclist, block), + DW_AT_location(0x2, exprloc, loclist, block, constant), DW_AT_name(0x3, string), DW_AT_ordering(0x9, constant), //DW_AT_subscr_data(0xa), @@ -70,7 +70,7 @@ public enum DWARFAttribute { DW_AT_base_types(0x35, reference), DW_AT_calling_convention(0x36, constant), DW_AT_count(0x37, constant, exprloc, reference), - DW_AT_data_member_location(0x38, constant, exprloc, loclist), + DW_AT_data_member_location(0x38, constant, exprloc, loclist, block), DW_AT_decl_column(0x39, constant), DW_AT_decl_file(0x3a, constant), DW_AT_decl_line(0x3b, constant), @@ -78,7 +78,7 @@ public enum DWARFAttribute { DW_AT_discr_list(0x3d, block), DW_AT_encoding(0x3e, constant), DW_AT_external(0x3f, flag), - DW_AT_frame_base(0x40, exprloc, loclist, block), + DW_AT_frame_base(0x40, exprloc, loclist, block, constant), DW_AT_friend(0x41, reference), DW_AT_identifier_case(0x42, constant), DW_AT_macro_info(0x43, macptr), @@ -91,7 +91,7 @@ public enum DWARFAttribute { DW_AT_use_location(0x4a, exprloc, loclist), DW_AT_variable_parameter(0x4b, flag), DW_AT_virtuality(0x4c, constant), - DW_AT_vtable_elem_location(0x4d, exprloc, loclist), + DW_AT_vtable_elem_location(0x4d, exprloc, loclist, block), DW_AT_allocated(0x4e, constant, exprloc, reference), DW_AT_associated(0x4f, constant, exprloc, reference), DW_AT_data_location(0x50, exprloc), @@ -99,7 +99,7 @@ public enum DWARFAttribute { DW_AT_entry_pc(0x52, address, constant), DW_AT_use_UTF8(0x53, flag), DW_AT_extension(0x54, reference), - DW_AT_ranges(0x55, rnglist), + DW_AT_ranges(0x55, rnglist, constant), DW_AT_trampoline(0x56, address, flag, reference, string), DW_AT_call_column(0x57, constant), DW_AT_call_file(0x58, constant), From 54f0995d85515f89f042b281dd056e2aa68feb9d Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:51:57 -0400 Subject: [PATCH 53/53] Fixed popup window placement issue --- .../gui/MemorySearchControlPanel.java | 14 ++++++++--- .../java/docking/widgets/PopupWindow.java | 24 ++++++++++++++----- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java index 389a6ff228..1fc4e88de5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java @@ -43,6 +43,7 @@ import ghidra.util.Swing; import ghidra.util.layout.PairLayout; import ghidra.util.layout.VerticalLayout; import ghidra.util.timer.GTimer; +import ghidra.util.timer.GTimerMonitor; /** * Internal panel of the memory search window that manages the controls for the search feature. This @@ -62,8 +63,10 @@ class MemorySearchControlPanel extends JPanel { private List> initialSearchButtonStates; private List> combinerSearchButtonStates; private JComboBox formatComboBox; + private PopupWindow popup; private String errorMessage; + private GTimerMonitor clearInputMonitor; MemorySearchControlPanel(MemorySearchProvider provider, SearchGuiModel model, SearchHistory history) { @@ -296,7 +299,7 @@ class MemorySearchControlPanel extends JPanel { if (errorMessage == null) { return; } - errorMessage = null; + DockingUtils.setTipWindowEnabled(false); Point location = searchInputField.getLocation(); @@ -305,13 +308,15 @@ class MemorySearchControlPanel extends JPanel { JToolTip tip = new JToolTip(); tip.setTipText(errorMessage); + errorMessage = null; if (popup != null) { popup.dispose(); + clearInputMonitor.cancel(); } popup = new PopupWindow(tip); - popup.showPopup(searchInputField, location, true); - GTimer.scheduleRunnable(1500, this::clearInputError); + popup.showPopup(searchInputField.getParent(), location, true); + clearInputMonitor = GTimer.scheduleRunnable(2000, this::clearInputError); Toolkit.getDefaultToolkit().beep(); } @@ -321,6 +326,9 @@ class MemorySearchControlPanel extends JPanel { PopupWindow.hideAllWindows(); if (popup != null) { popup.dispose(); + popup = null; + clearInputMonitor.cancel(); + clearInputMonitor = null; } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java index 667bfa7545..b23ae5b4e4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java @@ -4,9 +4,9 @@ * 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. @@ -264,7 +264,18 @@ public class PopupWindow { * Shows this popup window unless popups are disabled as reported by * {@link DockingUtils#isTipWindowEnabled()}. If {@code forceShow} is true, then the popup * will be shown regardless of the state returned by {@link DockingUtils#isTipWindowEnabled()}. - * @param component the component for the popup + *

            + * Note: the component passed in is the component to which the {@code location} the location + * belongs. In the example below, the component used to get the location is to the component + * passed to this method. This is because the location is relative to the parent's coordinate + * space. Thus, when calling this method, make sure to use the correct component. + *

            +	 * Point location = textField.getLocation(); // this is relative to the text field's parent
            +	 * Component parent = textField.getParent();
            +	 * PopupWindow.showPopup(parent, location, true);
            +	 * 
            + * + * @param component the component whose coordinate space the location belongs * @param location the location to show the popup * @param forceShow true to show the popup even popups are disabled application-wide */ @@ -395,14 +406,14 @@ public class PopupWindow { Rectangle newArea = new Rectangle(keepVisibleAea); Point point = newArea.getLocation(); - SwingUtilities.convertPointToScreen(point, source.getParent()); + SwingUtilities.convertPointToScreen(point, source); newArea.setLocation(point); return newArea; } // for debug private void installDebugPainter(Rectangle keepVisibleArea) { -// + // GGlassPane glassPane = GGlassPane.getGlassPane(source); // for (GGlassPanePainter p : painters) { // glassPane.removePainter(p); @@ -439,7 +450,8 @@ public class PopupWindow { // show where the user hovered if (location != null) { Point p = new Point(location); - p = SwingUtilities.convertPoint(source.getParent(), p.x, p.y, glassPane); + p = SwingUtilities.convertPoint(source, p.x, p.y, glassPane); + g.setColor(Palette.RED.withAlpha(alpha)); int offset = 10; g.fillRect(p.x - offset, p.y - offset, (offset * 2), (offset * 2));