mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-09-20 09:31:47 +00:00
Merge tag 'Ghidra_11.1.1_build' into stable
This commit is contained in:
commit
c9984da974
|
@ -22,6 +22,21 @@
|
|||
|
||||
<BODY>
|
||||
|
||||
<H1 align="center">Ghidra 11.1.1 Change History (June 2024)</H1>
|
||||
<blockquote><p><u><B>Bugs</B></u></p>
|
||||
<ul>
|
||||
<li><I>Debugger</I>. Fixes an error in <span class="gcode">dbgeng</span> launcher. (GP-4674)</li>
|
||||
<li><I>Debugger:GDB</I>. Fixed issue with using QEMU launchers in Trace RMI (<span class="gcode">ClassCastException</span> from <span class="gcode">String</span> to <span class="gcode">PathIsFile</span>) (GP-4690, Issue #6634)</li>
|
||||
<li><I>Decompiler</I>. Fixed a bug in the Decompiler that could cause it to drop a control-flow edge from a switch's case statement that looped back to the switch. (GP-4582, Issue #6282)</li>
|
||||
<li><I>Decompiler</I>. Fixed bug causing "Undefined Pullsub" exceptions. (GP-4672, Issue #6614)</li>
|
||||
<li><I>Decompiler</I>. Corrected Decompiler process issue which can occur when analysis is cancelled. Issue would incorrectly popup error indicating <em>"Decompiler executable may not be compatible with your system..."</em>. (GP-4689)</li>
|
||||
<li><I>GUI</I>. Fixed mouse button 4/5 processing failure that caused the left-click to stop working. (GP-4681, Issue #6624)</li>
|
||||
<li><I>Processors</I>. Added support for the Z80 processor undocumented registers. (GP-2881, Issue #4485)</li>
|
||||
<li><I>Processors</I>. Fixed 6805 branch conditional instruction semantics. (GP-4585, Issue #6482)</li>
|
||||
<li><I>Project</I>. Fixed a severe regression bug introduced with Ghidra 11.1 which could prevent a user from completing a project file add-to-version-control, checkin or merge when they currently have the file open in a tool. The corresponding open project file would remain in a bad state following the operation. (GP-4692)</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
|
||||
<H1 align="center">Ghidra 11.1 Change History (June 2024)</H1>
|
||||
<blockquote><p><u><B>New Features</B></u></p>
|
||||
<ul>
|
||||
|
|
|
@ -39,7 +39,8 @@ else:
|
|||
def main():
|
||||
# Delay these imports until sys.path is patched
|
||||
from ghidradbg import commands as cmd
|
||||
from ghidradbg.hooks import on_stop
|
||||
from pybag.dbgeng import core as DbgEng
|
||||
from ghidradbg.hooks import on_state_changed
|
||||
from ghidradbg.util import dbg
|
||||
|
||||
# So that the user can re-enter by typing repl()
|
||||
|
@ -62,7 +63,7 @@ def main():
|
|||
cmd.ghidra_trace_start(os.getenv('OPT_TARGET_IMG'))
|
||||
cmd.ghidra_trace_sync_enable()
|
||||
|
||||
on_stop()
|
||||
on_state_changed(DbgEng.DEBUG_CES_EXECUTION_STATUS, DbgEng.DEBUG_STATUS_BREAK)
|
||||
cmd.repl()
|
||||
|
||||
|
||||
|
|
|
@ -42,8 +42,7 @@ import ghidra.debug.api.modules.*;
|
|||
import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
|
||||
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
|
||||
import ghidra.framework.plugintool.AutoConfigState.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
@ -299,8 +298,18 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
List<String> names =
|
||||
program.getLanguage().getLanguageDescription().getExternalNames(tool);
|
||||
if (names != null && !names.isEmpty()) {
|
||||
var paramStr = (ParameterDescription<String>) param;
|
||||
paramStr.set(map, names.get(0));
|
||||
if (param.type == String.class) {
|
||||
var paramStr = (ParameterDescription<String>) param;
|
||||
paramStr.set(map, names.get(0));
|
||||
}
|
||||
else if (param.type == PathIsFile.class) {
|
||||
var paramPIF = (ParameterDescription<PathIsFile>) param;
|
||||
paramPIF.set(map, PathIsFile.fromString(names.get(0)));
|
||||
}
|
||||
else if (param.type == PathIsDir.class) {
|
||||
var paramPID = (ParameterDescription<PathIsDir>) param;
|
||||
paramPID.set(map, PathIsDir.fromString(names.get(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -305,6 +305,10 @@ public interface AutoConfigState {
|
|||
}
|
||||
|
||||
record PathIsDir(Path path) {
|
||||
public static PathIsDir fromString(String string) {
|
||||
return new PathIsDir(Paths.get(string));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return path.toString();
|
||||
|
@ -328,6 +332,10 @@ public interface AutoConfigState {
|
|||
}
|
||||
|
||||
record PathIsFile(Path path) {
|
||||
public static PathIsFile fromString(String string) {
|
||||
return new PathIsFile(Paths.get(string));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return path.toString();
|
||||
|
|
|
@ -434,7 +434,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
MouseBinding defaultMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||
assertNull(defaultMouseBinding);
|
||||
|
||||
int button = 1;
|
||||
int button = 4;
|
||||
int modifiers = 0;
|
||||
MouseBinding newMouseBinding = setMouseBinding(actionName, actionOwner, modifiers, button);
|
||||
|
||||
|
@ -462,7 +462,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
MouseBinding defaultMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||
assertNull(defaultMouseBinding);
|
||||
|
||||
int button = 1;
|
||||
int button = 4;
|
||||
int modifiers = 0;
|
||||
MouseBinding newMouseBinding = setMouseBinding(actionName, actionOwner, modifiers, button);
|
||||
|
||||
|
@ -500,7 +500,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
int modifiers = InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK;
|
||||
KeyStroke newKeyStroke = setKeyBinding(actionName, actionOwner, modifiers, keyCode, 'Q');
|
||||
|
||||
int button = 1;
|
||||
int button = 4;
|
||||
modifiers = 0;
|
||||
MouseBinding newMouseBinding = setMouseBinding(actionName, actionOwner, modifiers, button);
|
||||
|
||||
|
@ -533,7 +533,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
MouseBinding defaultMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||
assertNull(defaultMouseBinding);
|
||||
|
||||
int button = 1;
|
||||
int button = 4;
|
||||
int modifiers = 0;
|
||||
MouseBinding newMouseBinding = setMouseBinding(actionName, actionOwner, modifiers, button);
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ src/decompile/datatests/condmulti.xml||GHIDRA||||END|
|
|||
src/decompile/datatests/convert.xml||GHIDRA||||END|
|
||||
src/decompile/datatests/deadvolatile.xml||GHIDRA||||END|
|
||||
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/dupptr.xml||GHIDRA||||END|
|
||||
|
|
|
@ -1674,13 +1674,20 @@ BlockMultiGoto *BlockGraph::newBlockMultiGoto(FlowBlock *bl,int4 outedge)
|
|||
}
|
||||
else {
|
||||
ret = new BlockMultiGoto(bl);
|
||||
int4 origSizeOut = bl->sizeOut();
|
||||
vector<FlowBlock *> nodes;
|
||||
nodes.push_back(bl);
|
||||
identifyInternal(ret,nodes);
|
||||
addBlock(ret);
|
||||
ret->addEdge(targetbl);
|
||||
if (targetbl != bl) // If the target is itself, edge is already removed by identifyInternal
|
||||
removeEdge(ret,targetbl);
|
||||
if (targetbl != bl) {
|
||||
if (ret->sizeOut() != origSizeOut) { // If there are less out edges after identifyInternal
|
||||
// it must have collapsed a self edge (switch out edges are already deduped)
|
||||
ret->forceOutputNum(ret->sizeOut()+1); // preserve the self edge (it is not the goto edge)
|
||||
}
|
||||
removeEdge(ret,targetbl); // Remove the edge to the goto target
|
||||
}
|
||||
// else -- the goto edge is a self edge and will get removed by identifyInternal
|
||||
if (isdefaultedge)
|
||||
ret->setDefaultGoto();
|
||||
}
|
||||
|
|
|
@ -84,12 +84,14 @@ void LoopBody::extendToContainer(const LoopBody &container,vector<FlowBlock *> &
|
|||
}
|
||||
}
|
||||
|
||||
/// This updates the \b head and \b tail nodes to FlowBlock in the current collapsed graph.
|
||||
/// This returns the first \b tail and passes back the head.
|
||||
/// \param top is where \b head is passed back
|
||||
/// This updates the \b head node to the FlowBlock in the current collapsed graph.
|
||||
/// The \b tail nodes are also updated until one is found that has not collapsed into \b head.
|
||||
/// This first updated \b tail is returned. The loop may still exist as a \b head node with an
|
||||
/// out edge back into itself, in which case \b head is returned as the active \b tail.
|
||||
/// If the loop has been completely collapsed, null is returned.
|
||||
/// \param graph is the containing control-flow structure
|
||||
/// \return the current loop \b head
|
||||
FlowBlock *LoopBody::getCurrentBounds(FlowBlock **top,FlowBlock *graph)
|
||||
/// \return the current loop \b tail or null
|
||||
FlowBlock *LoopBody::update(FlowBlock *graph)
|
||||
|
||||
{
|
||||
while(head->getParent() != graph)
|
||||
|
@ -101,10 +103,13 @@ FlowBlock *LoopBody::getCurrentBounds(FlowBlock **top,FlowBlock *graph)
|
|||
bottom = bottom->getParent();
|
||||
tails[i] = bottom;
|
||||
if (bottom != head) { // If the loop hasn't been fully collapsed yet
|
||||
*top = head;
|
||||
return bottom;
|
||||
}
|
||||
}
|
||||
for(int4 i=head->sizeOut()-1;i>=0;--i) {
|
||||
if (head->getOut(i) == head) // Check for head looping with itself
|
||||
return head;
|
||||
}
|
||||
return (FlowBlock *)0;
|
||||
}
|
||||
|
||||
|
@ -391,17 +396,17 @@ void LoopBody::emitLikelyEdges(list<FloatingEdge> &likely,FlowBlock *graph)
|
|||
break;
|
||||
}
|
||||
}
|
||||
likely.push_back(FloatingEdge(inbl,outbl));
|
||||
likely.emplace_back(inbl,outbl);
|
||||
}
|
||||
for(int4 i=tails.size()-1;i>=0;--i) { // Go in reverse order, to put out less preferred back-edges first
|
||||
if ((holdin!=(FlowBlock *)0)&&(i==0))
|
||||
likely.push_back(FloatingEdge(holdin,holdout)); // Put in delayed exit, right before final backedge
|
||||
likely.emplace_back(holdin,holdout); // Put in delayed exit, right before final backedge
|
||||
FlowBlock *tail = tails[i];
|
||||
int4 sizeout = tail->sizeOut();
|
||||
for(int4 j=0;j<sizeout;++j) {
|
||||
FlowBlock *bl = tail->getOut(j);
|
||||
if (bl == head) // If out edge to head (back-edge for this loop)
|
||||
likely.push_back(FloatingEdge(tail,head)); // emit it
|
||||
likely.emplace_back(tail,head); // emit it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -652,7 +657,7 @@ void TraceDAG::removeTrace(BlockTrace *trace)
|
|||
|
||||
{
|
||||
// Record that we should now treat this edge like goto
|
||||
likelygoto.push_back(FloatingEdge(trace->bottom,trace->destnode)); // Create goto record
|
||||
likelygoto.emplace_back(trace->bottom,trace->destnode); // Create goto record
|
||||
trace->destnode->setVisitCount( trace->destnode->getVisitCount() + trace->edgelump ); // Ignore edge(s)
|
||||
|
||||
BranchPoint *parentbp = trace->top;
|
||||
|
@ -1194,11 +1199,21 @@ bool CollapseStructure::updateLoopBody(void)
|
|||
FlowBlock *loopbottom = (FlowBlock *)0;
|
||||
FlowBlock *looptop = (FlowBlock *)0;
|
||||
while (loopbodyiter != loopbody.end()) { // Last innermost loop
|
||||
loopbottom = (*loopbodyiter).getCurrentBounds(&looptop,&graph);
|
||||
LoopBody &curBody( *loopbodyiter );
|
||||
loopbottom = curBody.update(&graph);
|
||||
if (loopbottom != (FlowBlock *)0) {
|
||||
if ((!likelylistfull) ||
|
||||
(likelyiter != likelygoto.end())) // Reaching here means, we removed edges but loop still didn't collapse
|
||||
looptop = curBody.getHead();
|
||||
if (loopbottom == looptop) { // Check for single node looping back to itself
|
||||
// If sizeout is 1 or 2, the loop would have collapsed, so the node is likely a switch.
|
||||
likelygoto.clear();
|
||||
likelygoto.emplace_back(looptop,looptop); // Mark the loop edge as a goto
|
||||
likelyiter = likelygoto.begin();
|
||||
likelylistfull = true;
|
||||
return true;
|
||||
}
|
||||
if (!likelylistfull || likelyiter != likelygoto.end()) {
|
||||
break; // Loop still exists
|
||||
}
|
||||
}
|
||||
++loopbodyiter;
|
||||
likelylistfull = false; // Need to generate likely list for new loopbody (or no loopbody)
|
||||
|
|
|
@ -55,7 +55,7 @@ class LoopBody {
|
|||
public:
|
||||
LoopBody(FlowBlock *h) { head=h; immed_container = (LoopBody *)0; depth=0; } ///< Construct with a loop head
|
||||
FlowBlock *getHead(void) const { return head; } ///< Return the head FlowBlock of the loop
|
||||
FlowBlock *getCurrentBounds(FlowBlock **top,FlowBlock *graph); ///< Return current loop bounds (\b head and \b bottom).
|
||||
FlowBlock *update(FlowBlock *graph); ///< Update loop body to current view
|
||||
void addTail(FlowBlock *bl) { tails.push_back(bl); } ///< Add a \e tail to the loop
|
||||
FlowBlock *getExitBlock(void) const { return exitblock; } ///< Get the exit FlowBlock or NULL
|
||||
void findBase(vector<FlowBlock *> &body); ///< Mark the body FlowBlocks of \b this loop
|
||||
|
|
|
@ -4984,38 +4984,37 @@ int4 FuncCallSpecs::transferLockedInputParam(ProtoParameter *param)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/// Return the p-code op whose output Varnode corresponds to the given parameter (return value)
|
||||
/// \brief Return any outputs of \b this CALL that contain or are contained by the given return value parameter
|
||||
///
|
||||
/// The Varnode may be attached to the base CALL or CALLIND, but it also may be
|
||||
/// attached to an INDIRECT preceding the CALL. The output Varnode may not exactly match
|
||||
/// the dimensions of the given parameter. We return non-null if either:
|
||||
/// The output Varnodes may be attached to the base CALL or CALLIND, but also may be
|
||||
/// attached to an INDIRECT preceding the CALL. The output Varnodes may not exactly match
|
||||
/// the dimensions of the given parameter. We pass back a Varnode if either:
|
||||
/// - The parameter contains the Varnode (the easier case) OR if
|
||||
/// - The Varnode properly contains the parameter
|
||||
/// \param param is the given paramter (return value)
|
||||
/// \param newoutput will hold any overlapping output Varnodes
|
||||
/// \return the matching PcodeOp or NULL
|
||||
PcodeOp *FuncCallSpecs::transferLockedOutputParam(ProtoParameter *param)
|
||||
void FuncCallSpecs::transferLockedOutputParam(ProtoParameter *param,vector<Varnode *> &newoutput)
|
||||
|
||||
{
|
||||
Varnode *vn = op->getOut();
|
||||
if (vn != (Varnode *)0) {
|
||||
if (param->getAddress().justifiedContain(param->getSize(),vn->getAddr(),vn->getSize(),false)==0)
|
||||
return op;
|
||||
if (vn->getAddr().justifiedContain(vn->getSize(),param->getAddress(),param->getSize(),false)==0)
|
||||
return op;
|
||||
return (PcodeOp *)0;
|
||||
if (param->getAddress().justifiedContain(param->getSize(),vn->getAddr(),vn->getSize(),false)>=0)
|
||||
newoutput.push_back(vn);
|
||||
else if (vn->getAddr().justifiedContain(vn->getSize(),param->getAddress(),param->getSize(),false)>=0)
|
||||
newoutput.push_back(vn);
|
||||
}
|
||||
PcodeOp *indop = op->previousOp();
|
||||
while((indop!=(PcodeOp *)0)&&(indop->code()==CPUI_INDIRECT)) {
|
||||
if (indop->isIndirectCreation()) {
|
||||
vn = indop->getOut();
|
||||
if (param->getAddress().justifiedContain(param->getSize(),vn->getAddr(),vn->getSize(),false)==0)
|
||||
return indop;
|
||||
if (vn->getAddr().justifiedContain(vn->getSize(),param->getAddress(),param->getSize(),false)==0)
|
||||
return indop;
|
||||
if (param->getAddress().justifiedContain(param->getSize(),vn->getAddr(),vn->getSize(),false)>=0)
|
||||
newoutput.push_back(vn);
|
||||
else if (vn->getAddr().justifiedContain(vn->getSize(),param->getAddress(),param->getSize(),false)>=0)
|
||||
newoutput.push_back(vn);
|
||||
}
|
||||
indop = indop->previousOp();
|
||||
}
|
||||
return (PcodeOp *)0;
|
||||
}
|
||||
|
||||
/// \brief List and/or create a Varnode for each input parameter of matching a source prototype
|
||||
|
@ -5057,19 +5056,14 @@ bool FuncCallSpecs::transferLockedInput(vector<Varnode *> &newinput,const FuncPr
|
|||
/// \param newoutput will hold the passed back Varnode
|
||||
/// \param source is the source prototype
|
||||
/// \return \b true if the passed back value is accurate
|
||||
bool FuncCallSpecs::transferLockedOutput(Varnode *&newoutput,const FuncProto &source)
|
||||
bool FuncCallSpecs::transferLockedOutput(vector<Varnode *> &newoutput,const FuncProto &source)
|
||||
|
||||
{
|
||||
ProtoParameter *param = source.getOutput();
|
||||
if (param->getType()->getMetatype() == TYPE_VOID) {
|
||||
newoutput = (Varnode *)0;
|
||||
return true;
|
||||
}
|
||||
PcodeOp *outop = transferLockedOutputParam(param);
|
||||
if (outop == (PcodeOp *)0)
|
||||
newoutput = (Varnode *)0;
|
||||
else
|
||||
newoutput = outop->getOut();
|
||||
transferLockedOutputParam(param,newoutput);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -5126,103 +5120,136 @@ void FuncCallSpecs::commitNewInputs(Funcdata &data,vector<Varnode *> &newinput)
|
|||
|
||||
/// \brief Update output Varnode to \b this CALL to reflect the formal return value
|
||||
///
|
||||
/// The current return value must be locked and is presumably out of date
|
||||
/// with the current CALL output. Unless the return value is \e void, the
|
||||
/// output Varnode must exist and must be provided.
|
||||
/// The Varnode is updated to reflect the return value,
|
||||
/// which may involve truncating or extending. Any active trials are updated,
|
||||
/// and the new Varnode is set as the CALL output.
|
||||
/// The current return value must be locked and is presumably out of date with the current CALL output.
|
||||
/// Unless the return value is \e void, the output Varnode must exist and must be provided.
|
||||
/// The Varnode is created/updated to reflect the return value and is set as the CALL output.
|
||||
/// Any other intersecting outputs are updated to be either truncations or extensions of this.
|
||||
/// Any active trials are updated,
|
||||
/// \param data is the calling function
|
||||
/// \param newout is the provided old output Varnode (or NULL)
|
||||
void FuncCallSpecs::commitNewOutputs(Funcdata &data,Varnode *newout)
|
||||
/// \param newout is the list of intersecting outputs
|
||||
void FuncCallSpecs::commitNewOutputs(Funcdata &data,vector<Varnode *> &newoutput)
|
||||
|
||||
{
|
||||
if (!isOutputLocked()) return;
|
||||
activeoutput.clear();
|
||||
|
||||
if (newout != (Varnode *)0) {
|
||||
if (!newoutput.empty()) {
|
||||
ProtoParameter *param = getOutput();
|
||||
// We could conceivably truncate the output to the correct size to match the parameter
|
||||
activeoutput.registerTrial(param->getAddress(),param->getSize());
|
||||
PcodeOp *indop = newout->getDef();
|
||||
if (newout->getSize() == 1 && param->getType()->getMetatype() == TYPE_BOOL && data.isTypeRecoveryOn())
|
||||
if (param->getSize() == 1 && param->getType()->getMetatype() == TYPE_BOOL && data.isTypeRecoveryOn())
|
||||
data.opMarkCalculatedBool(op);
|
||||
if (newout->getSize() == param->getSize()) {
|
||||
if (indop != op) {
|
||||
data.opUnsetOutput(indop);
|
||||
data.opUnlink(indop); // We know this is an indirect creation which is no longer used
|
||||
Varnode *exactMatch = (Varnode *)0;
|
||||
for(int4 i=0;i<newoutput.size();++i) {
|
||||
if (newoutput[i]->getSize() == param->getSize()) {
|
||||
exactMatch = newoutput[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
Varnode *realOut;
|
||||
PcodeOp *indOp;
|
||||
if (exactMatch != (Varnode *)0) {
|
||||
// If we have a Varnode that exactly matches param, make sure it is the output of the CALL
|
||||
indOp = exactMatch->getDef();
|
||||
if (op != indOp) {
|
||||
// If we reach here, we know -op- must have no output
|
||||
data.opSetOutput(op,newout);
|
||||
data.opSetOutput(op,exactMatch);
|
||||
data.opUnlink(indOp); // We know this is an indirect creation which is no longer used
|
||||
}
|
||||
realOut = exactMatch;
|
||||
}
|
||||
else if (newout->getSize() < param->getSize()) {
|
||||
// We know newout is properly justified within param
|
||||
if (indop != op) {
|
||||
data.opUninsert(indop);
|
||||
data.opSetOpcode(indop,CPUI_SUBPIECE);
|
||||
}
|
||||
else {
|
||||
indop = data.newOp(2,op->getAddr());
|
||||
data.opSetOpcode(indop,CPUI_SUBPIECE);
|
||||
data.opSetOutput(indop,newout); // Move -newout- from -op- to -indop-
|
||||
}
|
||||
Varnode *realout = data.newVarnodeOut(param->getSize(),param->getAddress(),op);
|
||||
data.opSetInput(indop,realout,0);
|
||||
data.opSetInput(indop,data.newConstant(4,0),1);
|
||||
data.opInsertAfter(indop,op);
|
||||
else {
|
||||
// Otherwise, we create a Varnode matching param
|
||||
data.opUnsetOutput(op);
|
||||
realOut = data.newVarnodeOut(param->getSize(),param->getAddress(),op);
|
||||
}
|
||||
else { // param->getSize() < newout->getSize()
|
||||
// We know param is justified contained in newout
|
||||
VarnodeData vardata;
|
||||
// Test whether the new prototype naturally extends its output
|
||||
OpCode opc = assumedOutputExtension(param->getAddress(),param->getSize(),vardata);
|
||||
Address hiaddr = newout->getAddr();
|
||||
if (opc != CPUI_COPY) {
|
||||
// If -newout- looks like a natural extension of the true output type, create the extension op
|
||||
if (opc == CPUI_PIECE) { // Extend based on the datatype
|
||||
if (param->getType()->getMetatype() == TYPE_INT)
|
||||
opc = CPUI_INT_SEXT;
|
||||
else
|
||||
opc = CPUI_INT_ZEXT;
|
||||
}
|
||||
if (indop != op) {
|
||||
data.opUninsert(indop);
|
||||
data.opRemoveInput(indop,1);
|
||||
data.opSetOpcode(indop,opc);
|
||||
Varnode *outvn = data.newVarnodeOut(param->getSize(),param->getAddress(),op);
|
||||
data.opSetInput(indop,outvn,0);
|
||||
data.opInsertAfter(indop,op);
|
||||
|
||||
for(int4 i=0;i<newoutput.size();++i) {
|
||||
Varnode *oldOut = newoutput[i];
|
||||
if (oldOut == exactMatch) continue;
|
||||
indOp = oldOut->getDef();
|
||||
if (indOp == op)
|
||||
indOp = (PcodeOp *)0;
|
||||
if (oldOut->getSize() < param->getSize()) {
|
||||
if (indOp != (PcodeOp *)0) {
|
||||
data.opUninsert(indOp);
|
||||
data.opSetOpcode(indOp,CPUI_SUBPIECE);
|
||||
}
|
||||
else {
|
||||
PcodeOp *extop = data.newOp(1,op->getAddr());
|
||||
data.opSetOpcode(extop,opc);
|
||||
data.opSetOutput(extop,newout); // Move newout from -op- to -extop-
|
||||
Varnode *outvn = data.newVarnodeOut(param->getSize(),param->getAddress(),op);
|
||||
data.opSetInput(extop,outvn,0);
|
||||
data.opInsertAfter(extop,op);
|
||||
indOp = data.newOp(2,op->getAddr());
|
||||
data.opSetOpcode(indOp,CPUI_SUBPIECE);
|
||||
data.opSetOutput(indOp,oldOut); // Move oldOut from op to indOp
|
||||
}
|
||||
int4 overlap = oldOut->overlap(realOut->getAddr(),realOut->getSize());
|
||||
data.opSetInput(indOp,realOut,0);
|
||||
data.opSetInput(indOp,data.newConstant(4,(uintb)overlap),1);
|
||||
data.opInsertAfter(indOp,op);
|
||||
}
|
||||
else { // If all else fails, concatenate in extra byte from something "indirectly created" by -op-
|
||||
int4 hisz = newout->getSize() - param->getSize();
|
||||
if (!newout->getAddr().getSpace()->isBigEndian())
|
||||
hiaddr = hiaddr + param->getSize();
|
||||
PcodeOp *newindop = data.newIndirectCreation(op,hiaddr,hisz,true);
|
||||
if (indop != op) {
|
||||
data.opUninsert(indop);
|
||||
data.opSetOpcode(indop,CPUI_PIECE);
|
||||
Varnode *outvn = data.newVarnodeOut(param->getSize(),param->getAddress(),op);
|
||||
data.opSetInput(indop,newindop->getOut(),0);
|
||||
data.opSetInput(indop,outvn,1);
|
||||
data.opInsertAfter(indop,op);
|
||||
else if (param->getSize() < oldOut->getSize()) {
|
||||
int4 overlap = oldOut->getAddr().justifiedContain(oldOut->getSize(), param->getAddress(), param->getSize(), false);
|
||||
VarnodeData vardata;
|
||||
// Test whether the new prototype naturally extends its output
|
||||
OpCode opc = assumedOutputExtension(param->getAddress(),param->getSize(),vardata);
|
||||
if (opc != CPUI_COPY && overlap == 0) {
|
||||
// If oldOut looks like a natural extension of the true output type, create the extension op
|
||||
if (opc == CPUI_PIECE) { // Extend based on the data-type
|
||||
if (param->getType()->getMetatype() == TYPE_INT)
|
||||
opc = CPUI_INT_SEXT;
|
||||
else
|
||||
opc = CPUI_INT_ZEXT;
|
||||
}
|
||||
if (indOp != (PcodeOp *)0) {
|
||||
data.opUninsert(indOp);
|
||||
data.opRemoveInput(indOp,1);
|
||||
data.opSetOpcode(indOp,opc);
|
||||
data.opSetInput(indOp,realOut,0);
|
||||
data.opInsertAfter(indOp,op);
|
||||
}
|
||||
else {
|
||||
PcodeOp *extop = data.newOp(1,op->getAddr());
|
||||
data.opSetOpcode(extop,opc);
|
||||
data.opSetOutput(extop,oldOut); // Move newout from -op- to -extop-
|
||||
data.opSetInput(extop,realOut,0);
|
||||
data.opInsertAfter(extop,op);
|
||||
}
|
||||
}
|
||||
else {
|
||||
PcodeOp *concatop = data.newOp(2,op->getAddr());
|
||||
data.opSetOpcode(concatop,CPUI_PIECE);
|
||||
data.opSetOutput(concatop,newout); // Move newout from -op- to -concatop-
|
||||
Varnode *outvn = data.newVarnodeOut(param->getSize(),param->getAddress(),op);
|
||||
data.opSetInput(concatop,newindop->getOut(),0);
|
||||
data.opSetInput(concatop,outvn,1);
|
||||
data.opInsertAfter(concatop,op);
|
||||
else { // If all else fails, concatenate in extra byte from something "indirectly created" by -op-
|
||||
if (indOp != (PcodeOp *)0) {
|
||||
data.opUnlink(indOp);
|
||||
}
|
||||
int4 mostSigSize = oldOut->getSize() - overlap - realOut->getSize();
|
||||
PcodeOp *lastOp = op;
|
||||
if (overlap != 0) { // We need to append less significant bytes to realOut for this oldOut
|
||||
Address loAddr = oldOut->getAddr();
|
||||
if (loAddr.isBigEndian())
|
||||
loAddr = loAddr + (oldOut->getSize() - overlap);
|
||||
PcodeOp *newIndOp = data.newIndirectCreation(op,loAddr,overlap,true);
|
||||
PcodeOp *concatOp = data.newOp(2,op->getAddr());
|
||||
data.opSetOpcode(concatOp,CPUI_PIECE);
|
||||
data.opSetInput(concatOp,realOut,0); // Most significant part
|
||||
data.opSetInput(concatOp,newIndOp->getOut(),1); // Least sig
|
||||
data.opInsertAfter(concatOp,op);
|
||||
if (mostSigSize != 0) {
|
||||
if (loAddr.isBigEndian())
|
||||
data.newVarnodeOut(overlap+realOut->getSize(),realOut->getAddr(),concatOp);
|
||||
else
|
||||
data.newVarnodeOut(overlap+realOut->getSize(),loAddr,concatOp);
|
||||
}
|
||||
lastOp = concatOp;
|
||||
}
|
||||
if (mostSigSize != 0) { // We need to append more significant bytes to realOut for this oldOut
|
||||
Address hiAddr = oldOut->getAddr();
|
||||
if (!hiAddr.isBigEndian())
|
||||
hiAddr = hiAddr + (realOut->getSize() + overlap);
|
||||
PcodeOp *newIndOp = data.newIndirectCreation(op,hiAddr,mostSigSize,true);
|
||||
PcodeOp *concatOp = data.newOp(2,op->getAddr());
|
||||
data.opSetOpcode(concatOp,CPUI_PIECE);
|
||||
data.opSetInput(concatOp,newIndOp->getOut(),0);
|
||||
data.opSetInput(concatOp,lastOp->getOut(),1);
|
||||
data.opInsertAfter(concatOp,lastOp);
|
||||
lastOp = concatOp;
|
||||
}
|
||||
data.opSetOutput(lastOp,oldOut); // We have completed the redefinition of this oldOut
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5307,7 +5334,8 @@ void FuncCallSpecs::doInputJoin(int4 slot1,bool ishislot)
|
|||
/// \param newinput will hold the new list of input Varnodes for the CALL
|
||||
/// \param newoutput will hold the new output Varnode or NULL
|
||||
/// \return \b true if \b this can be fully converted
|
||||
bool FuncCallSpecs::lateRestriction(const FuncProto &restrictedProto,vector<Varnode *> &newinput,Varnode *&newoutput)
|
||||
bool FuncCallSpecs::lateRestriction(const FuncProto &restrictedProto,vector<Varnode *> &newinput,
|
||||
vector<Varnode *> &newoutput)
|
||||
|
||||
{
|
||||
if (!hasModel()) {
|
||||
|
@ -5357,7 +5385,7 @@ void FuncCallSpecs::deindirect(Funcdata &data,Funcdata *newfd)
|
|||
// Try our best to merge existing prototype
|
||||
// with the one we have just been handed
|
||||
vector<Varnode *> newinput;
|
||||
Varnode *newoutput;
|
||||
vector<Varnode *> newoutput;
|
||||
FuncProto &newproto( newfd->getFuncProto() );
|
||||
if ((!newproto.isNoReturn())&&(!newproto.isInline())) {
|
||||
if (isOverride()) // If we are overridden at the call-site
|
||||
|
@ -5387,7 +5415,7 @@ void FuncCallSpecs::forceSet(Funcdata &data,const FuncProto &fp)
|
|||
|
||||
{
|
||||
vector<Varnode *> newinput;
|
||||
Varnode *newoutput;
|
||||
vector<Varnode *> newoutput;
|
||||
|
||||
// Copy the recovered prototype into the override manager so that
|
||||
// future restarts don't have to rediscover it
|
||||
|
|
|
@ -1657,11 +1657,11 @@ class FuncCallSpecs : public FuncProto {
|
|||
Varnode *getSpacebaseRelative(void) const; ///< Get the active stack-pointer Varnode at \b this call site
|
||||
Varnode *buildParam(Funcdata &data,Varnode *vn,ProtoParameter *param,Varnode *stackref);
|
||||
int4 transferLockedInputParam(ProtoParameter *param);
|
||||
PcodeOp *transferLockedOutputParam(ProtoParameter *param);
|
||||
void transferLockedOutputParam(ProtoParameter *param,vector<Varnode *> &newoutput);
|
||||
bool transferLockedInput(vector<Varnode *> &newinput,const FuncProto &source);
|
||||
bool transferLockedOutput(Varnode *&newoutput,const FuncProto &source);
|
||||
bool transferLockedOutput(vector<Varnode *> &newoutput,const FuncProto &source);
|
||||
void commitNewInputs(Funcdata &data,vector<Varnode *> &newinput);
|
||||
void commitNewOutputs(Funcdata &data,Varnode *newout);
|
||||
void commitNewOutputs(Funcdata &data,vector<Varnode *> &newoutput);
|
||||
void collectOutputTrialVarnodes(vector<Varnode *> &trialvn);
|
||||
void setStackPlaceholderSlot(int4 slot) { stackPlaceholderSlot = slot;
|
||||
if (isinputactive) activeinput.setPlaceholderSlot(); } ///< Set the slot of the stack-pointer placeholder
|
||||
|
@ -1702,7 +1702,7 @@ public:
|
|||
|
||||
bool checkInputJoin(int4 slot1,bool ishislot,Varnode *vn1,Varnode *vn2) const;
|
||||
void doInputJoin(int4 slot1,bool ishislot);
|
||||
bool lateRestriction(const FuncProto &restrictedProto,vector<Varnode *> &newinput,Varnode *&newoutput);
|
||||
bool lateRestriction(const FuncProto &restrictedProto,vector<Varnode *> &newinput,vector<Varnode *> &newoutput);
|
||||
void deindirect(Funcdata &data,Funcdata *newfd);
|
||||
void forceSet(Funcdata &data,const FuncProto &fp);
|
||||
void insertPcode(Funcdata &data);
|
||||
|
|
|
@ -1515,6 +1515,8 @@ bool SplitFlow::addOp(PcodeOp *op,TransformVar *rvn,int4 slot)
|
|||
if (op->code() == CPUI_INDIRECT) {
|
||||
opSetInput(loOp,newIop(op->getIn(1)),1);
|
||||
opSetInput(hiOp,newIop(op->getIn(1)),1);
|
||||
loOp->inheritIndirect(op);
|
||||
hiOp->inheritIndirect(op);
|
||||
numParam = 1;
|
||||
}
|
||||
for(int4 i=0;i<numParam;++i) {
|
||||
|
|
|
@ -268,6 +268,19 @@ bool TransformOp::attemptInsertion(Funcdata *fd)
|
|||
return true; // Already inserted
|
||||
}
|
||||
|
||||
/// Prepare to build the transformed INDIRECT PcodeOp based on settings from the given INDIRECT.
|
||||
/// \param indOp is the given INDIRECT
|
||||
void TransformOp::inheritIndirect(PcodeOp *indOp)
|
||||
|
||||
{
|
||||
if (indOp->isIndirectCreation()) {
|
||||
if (indOp->getIn(0)->isIndirectZero())
|
||||
special |= TransformOp::indirect_creation;
|
||||
else
|
||||
special |= TransformOp::indirect_creation_possible_out;
|
||||
}
|
||||
}
|
||||
|
||||
void LanedRegister::LanedIterator::normalize(void)
|
||||
|
||||
{
|
||||
|
|
|
@ -87,6 +87,7 @@ private:
|
|||
public:
|
||||
TransformVar *getOut(void) const { return output; } ///< Get the output placeholder variable for \b this operator
|
||||
TransformVar *getIn(int4 i) const { return input[i]; } ///< Get the i-th input placeholder variable for \b this
|
||||
void inheritIndirect(PcodeOp *indOp); ///< Set \e indirect \e creation flags for \b this based on given INDIRECT
|
||||
};
|
||||
|
||||
/// \brief Describes a (register) storage location and the ways it might be split into lanes
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<decompilertest>
|
||||
<binaryimage arch="x86:LE:64:default:gcc">
|
||||
<!--
|
||||
A contrived function with an indirect call that eventually collapses to a function
|
||||
with an 8-byte return value. Prior to collapse, there are likely to be 2 separate
|
||||
pieces being read as output from the function. After collapse these should get
|
||||
concatenated and leave no shadow varnodes.
|
||||
-->
|
||||
<bytechunk space="ram" offset="0x100000" readonly="true">
|
||||
554889e54883ec3048c7c34600100048
|
||||
895de84889f94889f7488b5de8ffd348
|
||||
8d59104889036631c0c3
|
||||
</bytechunk>
|
||||
<symbol space="ram" offset="0x100000" name="deind26"/>
|
||||
<symbol space="ram" offset="0x100046" name="obtainPtr"/>
|
||||
</binaryimage>
|
||||
<script>
|
||||
<com>parse line extern int2 deind26(int4 **ptr,char *nm);</com>
|
||||
<com>parse line extern int4 *obtainPtr(char *nm);</com>
|
||||
<com>lo fu deind26</com>
|
||||
<com>decompile</com>
|
||||
<com>print C</com>
|
||||
<com>quit</com>
|
||||
</script>
|
||||
<stringmatch name="Deindirect Output #1" min="1" max="1">piVar1 = obtainPtr\(nm\);</stringmatch>
|
||||
<stringmatch name="Deindirect Output #2" min="1" max="1">return 0;</stringmatch>
|
||||
<stringmatch name="Deindirect Output #3" min="0" max="0">CONCAT</stringmatch>
|
||||
</decompilertest>
|
|
@ -145,42 +145,48 @@ public class DecompileProcess {
|
|||
throw new IOException("Could not find decompiler executable");
|
||||
}
|
||||
IOException exc = null;
|
||||
String err = "";
|
||||
statusGood = false;
|
||||
|
||||
try {
|
||||
nativeProcess = runtime.exec(exepath);
|
||||
|
||||
// Give process time to load and report possible error
|
||||
nativeProcess.waitFor(200, TimeUnit.MILLISECONDS);
|
||||
try {
|
||||
nativeProcess.waitFor(200, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Restore interrupted thread state since we are continuing with setup.
|
||||
// It does not appear that the decompiler interface is affected by an
|
||||
// interrupted thread so why stop here.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
nativeIn = nativeProcess.getInputStream();
|
||||
nativeOut = nativeProcess.getOutputStream();
|
||||
|
||||
statusGood = nativeProcess.isAlive();
|
||||
if (!statusGood) {
|
||||
err = new String(nativeProcess.getErrorStream().readAllBytes());
|
||||
nativeProcess.destroy();
|
||||
nativeProcess = null;
|
||||
if (!getAndSetErrorDisplayed()) {
|
||||
String err = new String(nativeProcess.getErrorStream().readAllBytes());
|
||||
String errorDetail = "Decompiler executable may not be compatible with your system and may need to be rebuilt.\n" +
|
||||
"(see InstallationGuide.html, 'Building Native Components').\n\n" +
|
||||
err;
|
||||
Msg.showError(this, null, "Failed to launch Decompiler process", errorDetail);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
exc = e;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// ignore
|
||||
exc = e; // permission error, etc.
|
||||
}
|
||||
finally {
|
||||
if (!statusGood) {
|
||||
disposestate = DisposeState.DISPOSED_ON_STARTUP_FAILURE;
|
||||
if (!getAndSetErrorDisplayed()) {
|
||||
String errorDetail = err;
|
||||
if (exc != null) {
|
||||
errorDetail = exc.getMessage() + "\n" + errorDetail;
|
||||
}
|
||||
errorDetail = "Decompiler executable may not be compatible with your system and may need to be rebuilt.\n" +
|
||||
"(see InstallationGuide.html, 'Building Native Components').\n\n" +
|
||||
errorDetail;
|
||||
Msg.showError(this, null, "Failed to launch Decompiler process", errorDetail);
|
||||
if (nativeProcess != null) {
|
||||
nativeProcess.destroy();
|
||||
nativeProcess = null;
|
||||
}
|
||||
if (exc == null) {
|
||||
throw new IOException("Decompiler process failed to launch (see log for details)");
|
||||
exc = new IOException("Decompiler process failed to launch (see log for details)");
|
||||
}
|
||||
throw exc;
|
||||
}
|
||||
|
|
|
@ -197,9 +197,8 @@ public class DecompilerNavigationTest extends AbstractDecompilerTest {
|
|||
public void testFunctionNavigation_WithAViewThatCachesTheLastValidFunction() throws Exception {
|
||||
|
||||
//
|
||||
// This is testing the case where the user starts on a function foo(). Ancillary windows
|
||||
// will display tool, such as a decompiled view. Now, if the user clicks to a
|
||||
// non-function location, such as data, the ancillary window may still show foo(), even
|
||||
// This is testing the case where the user starts on a function foo(). When the user clicks
|
||||
// away to a non-function location, such as data, the window may still show foo(), even
|
||||
// though the user is no longer in foo. At this point, if the user wishes to go to the
|
||||
// previous function, then from the ancillary window's perspective, it is the function
|
||||
// that came before foo().
|
||||
|
@ -212,12 +211,13 @@ public class DecompilerNavigationTest extends AbstractDecompilerTest {
|
|||
goTo(f1);
|
||||
goTo(f2);
|
||||
goTo(nonFunctionAddress);
|
||||
waitForDecompiler();
|
||||
|
||||
String title = provider.getTitle();
|
||||
assertTrue("Decompiler did not retain last function visited", title.contains("sscanf"));
|
||||
assertTrue("Decompiler did not retain last function visited. " +
|
||||
"Expected sscanf, but was '%s'".formatted(title), title.contains("sscanf"));
|
||||
|
||||
provider.requestFocus();
|
||||
waitForSwing();
|
||||
focusDecompiler();
|
||||
|
||||
//
|
||||
// The Decompiler is focused, showing 'entry'. Going back while it is focused should go
|
||||
|
@ -227,22 +227,6 @@ public class DecompilerNavigationTest extends AbstractDecompilerTest {
|
|||
assertCurrentAddress(f1);
|
||||
}
|
||||
|
||||
private void previousFunction() {
|
||||
NextPrevAddressPlugin plugin = env.getPlugin(NextPrevAddressPlugin.class);
|
||||
DockingAction previousFunctionAction =
|
||||
(DockingAction) getInstanceField("previousFunctionAction", plugin);
|
||||
|
||||
ActionContext context = provider.getActionContext(null);
|
||||
assertTrue(previousFunctionAction.isEnabledForContext(context));
|
||||
performAction(previousFunctionAction, context, true);
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void assertListingAddress(Address expected) {
|
||||
waitForCondition(() -> expected.equals(codeBrowser.getCurrentLocation().getAddress()),
|
||||
"The Listing is not at the expected address");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertCurrentAddress(Address expected) {
|
||||
codeBrowser.updateNow();
|
||||
|
@ -255,6 +239,27 @@ public class DecompilerNavigationTest extends AbstractDecompilerTest {
|
|||
}, "Listing is not at the expected address");
|
||||
}
|
||||
|
||||
private void focusDecompiler() {
|
||||
runSwing(() -> provider.requestFocus());
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void previousFunction() {
|
||||
NextPrevAddressPlugin plugin = env.getPlugin(NextPrevAddressPlugin.class);
|
||||
DockingAction previousFunctionAction =
|
||||
(DockingAction) getInstanceField("previousFunctionAction", plugin);
|
||||
|
||||
ActionContext context = runSwing(() -> provider.getActionContext(null));
|
||||
assertTrue(previousFunctionAction.isEnabledForContext(context));
|
||||
performAction(previousFunctionAction, context, true);
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void assertListingAddress(Address expected) {
|
||||
waitForCondition(() -> expected.equals(codeBrowser.getCurrentLocation().getAddress()),
|
||||
"The Listing is not at the expected address");
|
||||
}
|
||||
|
||||
private void assertExternalNavigationPerformed() {
|
||||
// going to the 'external linkage' means we went to the thunk function and not the
|
||||
// external program
|
||||
|
@ -269,9 +274,7 @@ public class DecompilerNavigationTest extends AbstractDecompilerTest {
|
|||
|
||||
private void createThunkToExternal(String addressString) throws Exception {
|
||||
|
||||
int txId = program.startTransaction("Set External Location");
|
||||
try {
|
||||
|
||||
tx(program, () -> {
|
||||
program.getExternalManager().setExternalPath("ADVAPI32.dll", "/FILE1", true);
|
||||
|
||||
Address address = addr(addressString);
|
||||
|
@ -289,12 +292,6 @@ public class DecompilerNavigationTest extends AbstractDecompilerTest {
|
|||
|
||||
Function function = program.getFunctionManager().getFunctionAt(addr(addressString));
|
||||
function.setThunkedFunction(externalLocation.getFunction());
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txId, true);
|
||||
}
|
||||
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,8 +78,8 @@ public class BufferMgr {
|
|||
*/
|
||||
private BufferNode cacheHead;
|
||||
private BufferNode cacheTail;
|
||||
private int cacheSize = 0;
|
||||
private int buffersOnHand = 0;
|
||||
private int cacheSize;
|
||||
private int buffersOnHand;
|
||||
private int lockCount = 0;
|
||||
|
||||
/**
|
||||
|
@ -235,6 +235,10 @@ public class BufferMgr {
|
|||
|
||||
private void initializeCache() throws IOException {
|
||||
|
||||
if (lockCount != 0) {
|
||||
throw new IOException("Unable to re-initialize buffer cache while in-use");
|
||||
}
|
||||
|
||||
if (cacheFile != null) {
|
||||
cacheFile.delete();
|
||||
}
|
||||
|
@ -244,6 +248,9 @@ public class BufferMgr {
|
|||
cacheTail = new BufferNode(TAIL, -1);
|
||||
cacheHead.nextCached = cacheTail;
|
||||
cacheTail.prevCached = cacheHead;
|
||||
|
||||
cacheSize = 0;
|
||||
buffersOnHand = 0;
|
||||
|
||||
// Create disk cache file
|
||||
cacheFile = new LocalBufferFile(bufferSize, CACHE_FILE_PREFIX, CACHE_FILE_EXT);
|
||||
|
@ -257,6 +264,8 @@ public class BufferMgr {
|
|||
cacheFile.setParameter(name, sourceFile.getParameter(name));
|
||||
}
|
||||
}
|
||||
|
||||
resetCacheStatistics();
|
||||
|
||||
if (alwaysPreCache) {
|
||||
startPreCacheIfNeeded();
|
||||
|
@ -2058,7 +2067,7 @@ public class BufferMgr {
|
|||
public void resetCacheStatistics() {
|
||||
cacheHits = 0;
|
||||
cacheMisses = 0;
|
||||
lowWaterMark = cacheSize;
|
||||
lowWaterMark = cacheSize - 1;
|
||||
}
|
||||
|
||||
public String getStatusInfo() {
|
||||
|
|
|
@ -181,7 +181,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
|
|||
}
|
||||
|
||||
if (isVisible()) {
|
||||
// if we are visible, then we don't need to update as the system updates all
|
||||
// if we are visible, then we don't need to update as the system updates all
|
||||
// visible components
|
||||
return;
|
||||
}
|
||||
|
@ -231,7 +231,6 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
|
|||
return;
|
||||
}
|
||||
|
||||
dockingTool.toFront();
|
||||
if (defaultFocusComponent != null) {
|
||||
DockingWindowManager.requestFocus(defaultFocusComponent);
|
||||
return;
|
||||
|
@ -848,7 +847,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
|
|||
* will adjust the font for the registered font id if it has been registered using
|
||||
* {@link #registeredFontId}. Subclasses can override this method to a more comprehensive
|
||||
* adjustment to multiple fonts if necessary.
|
||||
* @param bigger if true, the font should be made bigger, otherwise the font should be made
|
||||
* @param bigger if true, the font should be made bigger, otherwise the font should be made
|
||||
* smaller
|
||||
*/
|
||||
public void adjustFontSize(boolean bigger) {
|
||||
|
|
|
@ -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,17 @@ public class MouseBindingMouseEventDispatcher {
|
|||
toolkit.addAWTEventListener(listener, AWTEvent.MOUSE_EVENT_MASK);
|
||||
}
|
||||
|
||||
private boolean isSettingMouseBinding(MouseEvent e) {
|
||||
Component destination = e.getComponent();
|
||||
if (destination == null) {
|
||||
Component focusOwner = focusProvider.getFocusOwner();
|
||||
destination = focusOwner;
|
||||
}
|
||||
|
||||
// This is the class we use to set mouse bindings
|
||||
return destination instanceof MouseEntryTextField;
|
||||
}
|
||||
|
||||
private void process(MouseEvent e) {
|
||||
|
||||
int id = e.getID();
|
||||
|
@ -77,6 +88,10 @@ public class MouseBindingMouseEventDispatcher {
|
|||
return;
|
||||
}
|
||||
|
||||
if (isSettingMouseBinding(e)) {
|
||||
return; // the user is setting the binding; do not process system actions
|
||||
}
|
||||
|
||||
// always let the application finish processing key events that it started
|
||||
if (actionInProgress(e)) {
|
||||
return;
|
||||
|
@ -126,6 +141,16 @@ public class MouseBindingMouseEventDispatcher {
|
|||
new ActionEvent(source, ActionEvent.ACTION_PERFORMED, command, when, modifiers));
|
||||
}
|
||||
|
||||
int eventButton = e.getButton();
|
||||
int bindingButton = mouseBinding.getButton();
|
||||
if (eventButton != bindingButton) {
|
||||
// We may have missed an event or the user pressed multiple mouse buttons in an
|
||||
// unexpected sequence. Clear the in-progress action here so we do not consume all
|
||||
// mouse events indefinitely.
|
||||
inProgressAction = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
e.consume();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -92,6 +92,15 @@ public class MouseEntryTextField extends HintTextField {
|
|||
|
||||
int modifiersEx = e.getModifiersEx();
|
||||
int button = e.getButton();
|
||||
|
||||
int buttonDownMask = InputEvent.getMaskForButton(button);
|
||||
if (button == MouseEvent.BUTTON1 && buttonDownMask == modifiersEx) {
|
||||
// Don't allow the user to bind an unmodified left-click, as odd behavior can ensue.
|
||||
// We can revisit this if a valid use case is found.
|
||||
e.consume();
|
||||
return;
|
||||
}
|
||||
|
||||
processMouseBinding(new MouseBinding(button, modifiersEx), true);
|
||||
e.consume();
|
||||
}
|
||||
|
|
|
@ -1149,7 +1149,7 @@ public class GhidraFileData {
|
|||
|
||||
if (keepCheckedOut) {
|
||||
|
||||
// Maintain exclusive chekout if private repository or file is open for update
|
||||
// Maintain exclusive checkout if private repository or file is open for update
|
||||
boolean exclusive = !versionedFileSystem.isShared() || (inUseDomainObj != null);
|
||||
|
||||
ProjectLocator projectLocator = parent.getProjectLocator();
|
||||
|
@ -1169,6 +1169,11 @@ public class GhidraFileData {
|
|||
projectLocator.isTransient()));
|
||||
folderItem.setCheckout(checkout.getCheckoutId(), exclusive,
|
||||
checkout.getCheckoutVersion(), folderItem.getCurrentVersion());
|
||||
|
||||
if (inUseDomainObj != null) {
|
||||
// Reset source file and change-sets for open database
|
||||
getContentHandler().resetDBSourceFile(folderItem, inUseDomainObj);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// NOTE: file open read-only may prevent removal and result in hijack
|
||||
|
@ -1180,10 +1185,7 @@ public class GhidraFileData {
|
|||
// Ignore - should result in Hijacked file
|
||||
}
|
||||
}
|
||||
|
||||
if (inUseDomainObj != null) {
|
||||
getContentHandler().resetDBSourceFile(folderItem, inUseDomainObj);
|
||||
}
|
||||
|
||||
} // end of synchronized block
|
||||
|
||||
if (inUseDomainObj != null) {
|
||||
|
@ -1535,15 +1537,16 @@ public class GhidraFileData {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inUseDomainObj != null) {
|
||||
// Reset source file and change-sets for open database
|
||||
contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
|
||||
}
|
||||
}
|
||||
else {
|
||||
undoCheckout(false, true);
|
||||
}
|
||||
|
||||
if (inUseDomainObj != null) {
|
||||
contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
|
||||
}
|
||||
|
||||
} // end of synchronized block
|
||||
|
||||
if (inUseDomainObj != null) {
|
||||
|
@ -1915,6 +1918,8 @@ public class GhidraFileData {
|
|||
try {
|
||||
inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("merge");
|
||||
|
||||
ContentHandler<?> contentHandler = getContentHandler();
|
||||
|
||||
if (!modifiedSinceCheckout()) {
|
||||
// Quick merge
|
||||
folderItem.updateCheckout(versionedFolderItem, true, monitor);
|
||||
|
@ -1925,8 +1930,6 @@ public class GhidraFileData {
|
|||
throw new IOException("Merge failed, merge is not supported in headless mode");
|
||||
}
|
||||
|
||||
ContentHandler<?> contentHandler = getContentHandler();
|
||||
|
||||
// Test versioned file for VersionException
|
||||
int mergeVer = versionedFolderItem.getCurrentVersion();
|
||||
if (!okToUpgrade) {
|
||||
|
@ -1995,14 +1998,13 @@ public class GhidraFileData {
|
|||
versionedFolderItem.updateCheckoutVersion(checkoutId, mergeVer,
|
||||
ClientUtil.getUserName());
|
||||
tmpItem = null;
|
||||
Msg.info(this, "Merge completed for " + name);
|
||||
|
||||
if (inUseDomainObj != null) {
|
||||
contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
|
||||
}
|
||||
}
|
||||
|
||||
Msg.info(this, "Updated checkout completed for " + name);
|
||||
|
||||
if (inUseDomainObj != null) {
|
||||
// Reset source file and change-sets for open database
|
||||
contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
|
||||
inUseDomainObj.invalidate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,7 +169,7 @@ DIRECT: imm8 is imm8 { export *:1 imm8; }
|
|||
}
|
||||
:BHI REL is op=0x22;REL
|
||||
{
|
||||
local tmp = C + Z;
|
||||
local tmp = C || Z;
|
||||
if (tmp == 0) goto REL;
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ DIRECT: imm8 is imm8 { export *:1 imm8; }
|
|||
|
||||
:BLS REL is op=0x23;REL
|
||||
{
|
||||
local tmp = C + Z;
|
||||
local tmp = C || Z;
|
||||
if (tmp) goto REL;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,4 +18,6 @@ apply from: "$rootProject.projectDir/gradle/processorProject.gradle"
|
|||
apply plugin: 'eclipse'
|
||||
eclipse.project.name = 'Processors Z80'
|
||||
|
||||
|
||||
sleighCompileOptions = [
|
||||
'-l'
|
||||
]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -36,6 +36,8 @@ import generic.theme.GIcon;
|
|||
import ghidra.framework.main.projectdata.actions.VersionControlAction;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
|
@ -219,7 +221,7 @@ public class VersionControlAction2Test extends AbstractVersionControlActionTest
|
|||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
Program program = (Program) ((DomainFileNode) node).getDomainFile()
|
||||
ProgramDB program = (ProgramDB) ((DomainFileNode) node).getDomainFile()
|
||||
.getDomainObject(this,
|
||||
true, false, TaskMonitor.DUMMY);
|
||||
int transactionID = program.startTransaction("test");
|
||||
|
@ -253,6 +255,58 @@ public class VersionControlAction2Test extends AbstractVersionControlActionTest
|
|||
assertTrue(!df.isCheckedOut());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckInWhileOpen() throws Exception {
|
||||
GTreeNode node = getNode(PROGRAM_A);
|
||||
addToVersionControl(node, false);
|
||||
|
||||
selectNode(node);
|
||||
DockingActionIf action = getAction("CheckOut");
|
||||
runSwing(() -> action.actionPerformed(getDomainFileActionContext(node)), false);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
ProgramDB program = (ProgramDB) ((DomainFileNode) node).getDomainFile()
|
||||
.getDomainObject(this,
|
||||
true, false, TaskMonitor.DUMMY);
|
||||
int transactionID = program.startTransaction("test");
|
||||
try {
|
||||
// Ensure that buffer memory cache has been completely consumed
|
||||
// Max BufferMgr cache size is 256*16KByte=4MByte
|
||||
AddressSpace space = program.getAddressFactory().getDefaultAddressSpace();
|
||||
program.getMemory().createInitializedBlock("BigBlock", space.getAddress(0x80000000L),
|
||||
4*1024*1024, (byte)0xff, TaskMonitor.DUMMY, false);
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(transactionID, true);
|
||||
program.save(null, TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
try {
|
||||
DockingActionIf checkInAction = getAction("CheckIn");
|
||||
runSwing(() -> checkInAction.actionPerformed(getDomainFileActionContext(node)), false);
|
||||
waitForSwing();
|
||||
VersionControlDialog dialog = waitForDialogComponent(VersionControlDialog.class);
|
||||
assertNotNull(dialog);
|
||||
JTextArea textArea = findComponent(dialog, JTextArea.class);
|
||||
assertNotNull(textArea);
|
||||
JCheckBox cb = findComponent(dialog, JCheckBox.class);
|
||||
assertNotNull(cb);
|
||||
runSwing(() -> {
|
||||
textArea.setText("This is a test");
|
||||
cb.setSelected(false);
|
||||
});
|
||||
pressButtonByText(dialog, "OK");
|
||||
waitForTasks();
|
||||
DomainFile df = ((DomainFileNode) node).getDomainFile();
|
||||
assertTrue(df.isCheckedOut());
|
||||
}
|
||||
finally {
|
||||
program.release(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteVersionCheckedOut() throws Exception {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
application.name=Ghidra
|
||||
application.version=11.1
|
||||
application.version=11.1.1
|
||||
application.release.name=DEV
|
||||
application.layout.version=2
|
||||
application.gradle.min=7.3
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="22">
|
||||
<profile kind="CodeFormatterProfile" name="GhidraEclipseFormatter" version="22">
|
||||
<profiles version="23">
|
||||
<profile kind="CodeFormatterProfile" name="GhidraEclipseFormatter" version="23">
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
|
@ -158,12 +158,14 @@
|
|||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
|
@ -335,7 +337,7 @@
|
|||
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_if_empty"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="0"/>
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user