mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-4512 Constant propagation and stack analysis performance changes
This commit is contained in:
parent
df505c40a3
commit
c396867209
12 changed files with 1724 additions and 1167 deletions
|
@ -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.
|
||||
|
@ -73,13 +73,27 @@ public class CallDepthChangeInfo {
|
|||
|
||||
/**
|
||||
* Construct a new CallDepthChangeInfo object.
|
||||
* Using this constructor will NOT track the stack depth at the start/end of each instruction.
|
||||
*
|
||||
* @param func function to examine
|
||||
*/
|
||||
public CallDepthChangeInfo(Function func) {
|
||||
this(func, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new CallDepthChangeInfo object.
|
||||
* Allows calls to getRegDepth() and getRegValueRepresentation()
|
||||
*
|
||||
* @param func function to examine
|
||||
* @param storeDepthAtEachInstuction true to track stack at start/end of each instruction. allowing
|
||||
* a call to
|
||||
*/
|
||||
public CallDepthChangeInfo(Function func, boolean storeDepthAtEachInstuction) {
|
||||
this.program = func.getProgram();
|
||||
frameReg = program.getCompilerSpec().getStackPointer();
|
||||
try {
|
||||
initialize(func, func.getBody(), frameReg, TaskMonitor.DUMMY);
|
||||
initialize(func, func.getBody(), frameReg, storeDepthAtEachInstuction, TaskMonitor.DUMMY);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
throw new RuntimeException("Unexpected Exception", e);
|
||||
|
@ -88,10 +102,8 @@ public class CallDepthChangeInfo {
|
|||
|
||||
/**
|
||||
* Construct a new CallDepthChangeInfo object.
|
||||
* @param func
|
||||
* function to examine
|
||||
* @param monitor
|
||||
* monitor used to cancel the operation
|
||||
* @param func function to examine
|
||||
* @param monitor used to cancel the operation
|
||||
*
|
||||
* @throws CancelledException
|
||||
* if the operation was canceled
|
||||
|
@ -102,6 +114,8 @@ public class CallDepthChangeInfo {
|
|||
|
||||
/**
|
||||
* Construct a new CallDepthChangeInfo object.
|
||||
* Using this constructor will track the stack depth at the start/end of each instruction.
|
||||
*
|
||||
* @param function function to examine
|
||||
* @param restrictSet set of addresses to restrict flow flowing to.
|
||||
* @param frameReg register that is to have it's depth(value) change tracked
|
||||
|
@ -116,34 +130,18 @@ public class CallDepthChangeInfo {
|
|||
if (frameReg == null) {
|
||||
frameReg = program.getCompilerSpec().getStackPointer();
|
||||
}
|
||||
initialize(function, restrictSet, frameReg, monitor);
|
||||
// track start/end values at each instruction
|
||||
initialize(function, restrictSet, frameReg, true, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new CallDepthChangeInfo object.
|
||||
*
|
||||
* @param program program containing the function to examime
|
||||
* @param addr address within the function to examine
|
||||
* @param restrictSet set of addresses to restrict flow flowing to.
|
||||
* @param frameReg register that is to have it's depth(value) change tracked
|
||||
* @param monitor monitor used to cancel the operation
|
||||
* @throws CancelledException
|
||||
* if the operation was canceled
|
||||
*/
|
||||
public CallDepthChangeInfo(Program program, Address addr, AddressSetView restrictSet,
|
||||
Register frameReg, TaskMonitor monitor) throws CancelledException {
|
||||
Function func = program.getFunctionManager().getFunctionContaining(addr);
|
||||
Register stackPtrReg = program.getCompilerSpec().getStackPointer();
|
||||
initialize(func, restrictSet, stackPtrReg, monitor);
|
||||
}
|
||||
|
||||
private void initialize(Function func, AddressSetView restrictSet, Register reg,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
boolean storeDepthAtEachInstuction, TaskMonitor monitor) throws CancelledException {
|
||||
changeMap = new DefaultIntPropertyMap("change");
|
||||
depthMap = new DefaultIntPropertyMap("depth");
|
||||
trans = new VarnodeTranslator(program);
|
||||
|
||||
symEval = new SymbolicPropogator(program);
|
||||
symEval = new SymbolicPropogator(program,storeDepthAtEachInstuction);
|
||||
symEval.setParamRefCheck(false);
|
||||
symEval.setReturnRefCheck(false);
|
||||
symEval.setStoredRefCheck(false);
|
||||
|
@ -494,18 +492,14 @@ public class CallDepthChangeInfo {
|
|||
@Override
|
||||
public boolean evaluateContextBefore(VarnodeContext context, Instruction instr) {
|
||||
Varnode stackRegVarnode = context.getRegisterVarnode(frameReg);
|
||||
Varnode stackValue = null;
|
||||
try {
|
||||
stackValue = context.getValue(stackRegVarnode, true, this);
|
||||
Varnode stackValue = context.getValue(stackRegVarnode, true, this);
|
||||
|
||||
if (stackValue != null && context.isSymbol(stackValue) &&
|
||||
context.isStackSymbolicSpace(stackValue)) {
|
||||
int stackPointerDepth = (int) stackValue.getOffset();
|
||||
setDepth(instr, stackPointerDepth);
|
||||
}
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
// ignore
|
||||
if (stackValue != null && context.isSymbol(stackValue) &&
|
||||
context.isStackSymbolicSpace(stackValue)) {
|
||||
long stackPointerDepth = stackValue.getOffset();
|
||||
int size = stackValue.getSize();
|
||||
stackPointerDepth = (stackPointerDepth << 8 * (8 - size)) >> 8 * (8 - size);
|
||||
setDepth(instr, (int) stackPointerDepth);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -621,25 +615,14 @@ public class CallDepthChangeInfo {
|
|||
return getRegDepth(addr, stackReg);
|
||||
}
|
||||
|
||||
/**
|
||||
/** Get the stack register depth at address.
|
||||
* To have a valid value, the class must be constructed to storeDepthAtEachInstuction
|
||||
*
|
||||
* @param addr the address to get the register depth at.
|
||||
* @param reg the register to get the depth of.
|
||||
* @return the depth of the register at the address.
|
||||
*/
|
||||
public int getRegDepth(Address addr, Register reg) {
|
||||
// OK lets CHEAT...
|
||||
// Since single instructions will give the wrong value,
|
||||
// get the value as of the end of the last instruction that fell into this one!
|
||||
Instruction instr = this.program.getListing().getInstructionAt(addr);
|
||||
if (instr != null && instr.getLength() < 2) {
|
||||
Address fallAddr = instr.getFallFrom();
|
||||
if (fallAddr != null) {
|
||||
addr = fallAddr;
|
||||
}
|
||||
// just in case this instruction falling from is bigger than 1 byte
|
||||
instr = program.getListing().getInstructionAt(addr);
|
||||
addr = instr.getMaxAddress();
|
||||
}
|
||||
Value rValue = symEval.getRegisterValue(addr, reg);
|
||||
if (rValue == null) {
|
||||
return Function.INVALID_STACK_DEPTH_CHANGE;
|
||||
|
@ -657,6 +640,11 @@ public class CallDepthChangeInfo {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the stack register value as a printable string. This can be an equation
|
||||
* of register+value.
|
||||
*
|
||||
* To have a valid value, the class must be constructed to storeDepthAtEachInstuction
|
||||
*
|
||||
* @param addr the address of the register value to get the representation of.
|
||||
* @param reg the register to get the representation of.
|
||||
* @return the string representation of the register value.
|
||||
|
|
|
@ -43,6 +43,8 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
private static final int MAX_PARAM_OFFSET = 2048; // max size of param reference space
|
||||
private static final int MAX_LOCAL_OFFSET = -(64 * 1024); // max size of local reference space
|
||||
|
||||
private final static String X86_NAME = "x86";
|
||||
|
||||
private boolean dontCreateNewVariables = false;
|
||||
|
||||
private final boolean forceProcessing;
|
||||
|
@ -53,6 +55,8 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
private Program program;
|
||||
private Register stackReg;
|
||||
private int purge = 0;
|
||||
|
||||
private boolean isX86 = false;
|
||||
|
||||
static String DEFAULT_FUNCTION_COMMENT = " FUNCTION";
|
||||
|
||||
|
@ -101,6 +105,8 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
@Override
|
||||
public boolean applyTo(Program p, TaskMonitor monitor) {
|
||||
program = p;
|
||||
|
||||
isX86 = checkForX86(p);
|
||||
|
||||
int count = 0;
|
||||
long numAddresses = entryPoints.getNumAddresses();
|
||||
|
@ -138,6 +144,11 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
return true;
|
||||
}
|
||||
|
||||
private boolean checkForX86(Program p) {
|
||||
return program.getLanguage().getProcessor().equals(
|
||||
Processor.findOrPossiblyCreateProcessor(X86_NAME)) && program.getDefaultPointerSize() <= 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze a function to build a stack frame based on stack references.
|
||||
*
|
||||
|
@ -245,7 +256,7 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
*
|
||||
* @param func - function to analyze stack pointer references
|
||||
*/
|
||||
private int createStackPointerVariables(Function func, TaskMonitor monitor)
|
||||
private int createStackPointerVariables(final Function func, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
// check if this is a jump thunk through a function pointer
|
||||
// if (checkThunk(func, monitor)) {
|
||||
|
@ -271,7 +282,7 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
@Override
|
||||
public boolean evaluateContext(VarnodeContext context, Instruction instr) {
|
||||
if (instr.getFlowType().isTerminal()) {
|
||||
RegisterValue value = context.getRegisterValue(stackReg, instr.getMaxAddress());
|
||||
RegisterValue value = context.getRegisterValue(stackReg);
|
||||
if (value != null) {
|
||||
BigInteger signedValue = value.getSignedValue();
|
||||
if (signedValue != null) {
|
||||
|
@ -279,7 +290,7 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (instr.getMnemonicString().equals("LEA")) {
|
||||
if (isX86 && instr.getMnemonicString().equals("LEA")) {
|
||||
Register destReg = instr.getRegister(0);
|
||||
if (destReg != null) {
|
||||
Varnode value = context.getRegisterVarnodeValue(destReg);
|
||||
|
@ -320,18 +331,17 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
if (opIndex == -1) {
|
||||
return;
|
||||
}
|
||||
// if restoring the same value into the register, don't record the reference
|
||||
// TODO: Dirty Dirty nasty Hack for POP EBP problem, only very few cases of this!
|
||||
if (instr.getMnemonicString().equals("POP")) {
|
||||
if (isX86 && instr.getMnemonicString().equals("POP")) {
|
||||
Register reg = instr.getRegister(opIndex);
|
||||
if (reg != null && reg.getName().contains("BP")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
long extendedOffset =
|
||||
extendOffset(address.getOffset(), stackReg.getBitLength());
|
||||
Function func =
|
||||
program.getFunctionManager().getFunctionContaining(instr.getMinAddress());
|
||||
defineFuncVariable(func, instr, opIndex, (int) extendedOffset, sortedVariables);
|
||||
long extendedOffset = extendOffset(address.getOffset(), stackReg.getBitLength());
|
||||
|
||||
defineFuncVariable(symEval, func, instr, opIndex, (int) extendedOffset, sortedVariables);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -342,9 +352,10 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
};
|
||||
|
||||
// set the stack pointer to be tracked
|
||||
symEval.setRegister(func.getEntryPoint(), stackReg);
|
||||
Address entryPoint = func.getEntryPoint();
|
||||
symEval.setRegister(entryPoint, stackReg);
|
||||
|
||||
symEval.flowConstants(func.getEntryPoint(), func.getBody(), eval, true, monitor);
|
||||
symEval.flowConstants(entryPoint, func.getBody(), eval, true, monitor);
|
||||
|
||||
if (sortedVariables.size() != 0) {
|
||||
|
||||
|
@ -623,11 +634,11 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
// return true;
|
||||
// }
|
||||
|
||||
private void defineFuncVariable(Function func, Instruction instr, int opIndex, int stackOffset,
|
||||
private void defineFuncVariable(SymbolicPropogator symEval, Function func, Instruction instr, int opIndex, int stackOffset,
|
||||
List<Variable> sortedVariables) {
|
||||
|
||||
ReferenceManager refMgr = program.getReferenceManager();
|
||||
int refSize = getRefSize(instr, opIndex);
|
||||
int refSize = getRefSize(symEval, instr, opIndex);
|
||||
try {
|
||||
// don't create crazy offsets
|
||||
if (stackOffset > MAX_PARAM_OFFSET || stackOffset < MAX_LOCAL_OFFSET) {
|
||||
|
@ -660,15 +671,16 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
|
||||
/**
|
||||
* Look at the result register to try and figure out stack access size.
|
||||
* @param symEval
|
||||
*
|
||||
* @param instr instruction being analyzed
|
||||
* @param opIndex operand that has a stack reference.
|
||||
*
|
||||
* @return size of value referenced on the stack
|
||||
*/
|
||||
private int getRefSize(Instruction instr, int opIndex) {
|
||||
private int getRefSize(SymbolicPropogator symEval, Instruction instr, int opIndex) {
|
||||
if (instr.getProgram().getLanguage().supportsPcode()) {
|
||||
PcodeOp[] pcode = instr.getPcode();
|
||||
PcodeOp[] pcode = symEval.getInstructionPcode(instr);
|
||||
for (int i = pcode.length - 1; i >= 0; i--) {
|
||||
if (pcode[i].getOpcode() == PcodeOp.LOAD) {
|
||||
Varnode out = pcode[i].getOutput();
|
||||
|
@ -810,7 +822,7 @@ public class NewFunctionStackAnalysisCmd extends BackgroundCommand<Program> {
|
|||
offset = minOffset;
|
||||
}
|
||||
else {
|
||||
dt = Undefined.getUndefinedDataType(size);
|
||||
dt = Undefined.getUndefinedDataType(refSize);
|
||||
}
|
||||
|
||||
Variable var;
|
||||
|
|
|
@ -108,7 +108,6 @@ public class ConstantPropagationAnalyzer extends AbstractAnalyzer {
|
|||
|
||||
final static HashSet<String> handledProcessors = new HashSet<String>();
|
||||
protected String processorName = "Basic";
|
||||
protected AddressSetView EMPTY_ADDRESS_SET = new AddressSet();
|
||||
|
||||
public ConstantPropagationAnalyzer() {
|
||||
this("Basic");
|
||||
|
@ -189,7 +188,8 @@ public class ConstantPropagationAnalyzer extends AbstractAnalyzer {
|
|||
int locationCount = locations.size();
|
||||
monitor.initialize(locationCount);
|
||||
if (locationCount != 0) {
|
||||
AddressSetView resultSet = runAddressAnalysis(program, locations, monitor);
|
||||
monitor.setMessage(getName());
|
||||
AddressSetView resultSet = runParallelAddressAnalysis(program, locations, null, maxThreadCount, monitor);
|
||||
// get rid of any reached addresses
|
||||
unanalyzedSet.delete(resultSet);
|
||||
}
|
||||
|
|
|
@ -15,21 +15,32 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.function;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.cmd.function.FunctionStackAnalysisCmd;
|
||||
import ghidra.app.cmd.function.NewFunctionStackAnalysisCmd;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.framework.cmd.BackgroundCommand;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class StackVariableAnalyzer extends AbstractAnalyzer {
|
||||
private static final String NAME = "Stack";
|
||||
private static final String DESCRIPTION = "Creates stack variables for a function.";
|
||||
|
||||
|
||||
protected static final String MAX_THREAD_COUNT_OPTION_NAME = "Max Threads";
|
||||
protected static final String MAX_THREAD_COUNT_OPTION_DESCRIPTION =
|
||||
"Maximum threads for stack variable reference creation. Too many threads causes thrashing in DB.";
|
||||
protected static final int MAX_THREAD_COUNT_OPTION_DEFAULT_VALUE = 2;
|
||||
|
||||
protected int maxThreadCount = MAX_THREAD_COUNT_OPTION_DEFAULT_VALUE;
|
||||
private boolean doNewStackAnalysis = true;
|
||||
private boolean doCreateLocalStackVars = true;
|
||||
private boolean doCreateStackParams = false;
|
||||
|
@ -42,37 +53,88 @@ public class StackVariableAnalyzer extends AbstractAnalyzer {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) {
|
||||
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws CancelledException {
|
||||
BackgroundCommand<Program> cmd;
|
||||
|
||||
// first split out all the function locations, make those the starts
|
||||
// remove those from the bodies from the given set of addresses
|
||||
Set<Address> locations = new HashSet<Address>();
|
||||
findDefinedFunctions(program, set, locations, monitor);
|
||||
|
||||
int locationCount = locations.size();
|
||||
monitor.initialize(locationCount);
|
||||
try {
|
||||
monitor.setMessage(getName());
|
||||
AddressSetView resultSet = runParallelAddressAnalysis(program, locations, null, maxThreadCount, monitor);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "caught exception", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSetView analyzeLocation(final Program program, Address start, AddressSetView set,
|
||||
final TaskMonitor monitor) throws CancelledException {
|
||||
BackgroundCommand<Program> cmd;
|
||||
|
||||
if (doNewStackAnalysis) {
|
||||
cmd = new NewFunctionStackAnalysisCmd(set, doCreateStackParams, doCreateLocalStackVars,
|
||||
cmd = new NewFunctionStackAnalysisCmd(new AddressSet(start, start), doCreateStackParams, doCreateLocalStackVars,
|
||||
false);
|
||||
}
|
||||
else {
|
||||
cmd = new FunctionStackAnalysisCmd(set, doCreateStackParams, doCreateLocalStackVars,
|
||||
cmd = new FunctionStackAnalysisCmd(new AddressSet(start, start), doCreateStackParams, doCreateLocalStackVars,
|
||||
false);
|
||||
}
|
||||
|
||||
}
|
||||
cmd.applyTo(program, monitor);
|
||||
return true;
|
||||
|
||||
return EMPTY_ADDRESS_SET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find function locations and adding the function entry points to locations
|
||||
*
|
||||
* @param program program
|
||||
* @param set remove known function bodies from the set, leave entry points
|
||||
* @param locations set of known function start addresses
|
||||
* @param monitor to cancel
|
||||
* @throws CancelledException if cancelled
|
||||
*/
|
||||
protected void findDefinedFunctions(Program program, AddressSetView set,
|
||||
Set<Address> locations, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
monitor.setMessage("Finding function locations...");
|
||||
long total = set.getNumAddresses();
|
||||
monitor.initialize(total);
|
||||
|
||||
// iterate over functions in program
|
||||
// add each defined function start to the list
|
||||
// return the address set that is minus the bodies of each function
|
||||
Iterator<Function> fiter = program.getFunctionManager().getFunctionsOverlapping(set);
|
||||
while (fiter.hasNext()) {
|
||||
monitor.checkCancelled();
|
||||
Function function = fiter.next();
|
||||
locations.add(function.getEntryPoint());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean useOldStackAnalysisByDefault(Program program) {
|
||||
Language language = program.getLanguage();
|
||||
if (language.getProcessor().equals(Processor.findOrPossiblyCreateProcessor("x86"))) {
|
||||
if (language.getLanguageDescription().getSize() == 16) {
|
||||
// Prefer using old stack analysis for x86 16-bit with segmented addresses
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// private boolean useOldStackAnalysisByDefault(Program program) {
|
||||
// Language language = program.getLanguage();
|
||||
// if (language.getProcessor().equals(Processor.findOrPossiblyCreateProcessor("x86"))) {
|
||||
// if (language.getLanguageDescription().getSize() == 16) {
|
||||
// // Prefer using old stack analysis for x86 16-bit with segmented addresses
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void registerOptions(Options options, Program program) {
|
||||
options.registerOption(GhidraLanguagePropertyKeys.USE_NEW_FUNCTION_STACK_ANALYSIS,
|
||||
!useOldStackAnalysisByDefault(program), null,
|
||||
true, null,
|
||||
"Use General Stack Reference Propogator (This works best on most processors)");
|
||||
|
||||
options.registerOption("Create Local Variables", doCreateLocalStackVars, null,
|
||||
|
@ -80,18 +142,23 @@ public class StackVariableAnalyzer extends AbstractAnalyzer {
|
|||
|
||||
options.registerOption("Create Param Variables", doCreateStackParams, null,
|
||||
"Create Function Parameter stack variables and references");
|
||||
|
||||
options.registerOption(MAX_THREAD_COUNT_OPTION_NAME, maxThreadCount, null,
|
||||
MAX_THREAD_COUNT_OPTION_DESCRIPTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void optionsChanged(Options options, Program program) {
|
||||
doNewStackAnalysis =
|
||||
options.getBoolean(GhidraLanguagePropertyKeys.USE_NEW_FUNCTION_STACK_ANALYSIS,
|
||||
!useOldStackAnalysisByDefault(program));
|
||||
true);
|
||||
|
||||
doCreateLocalStackVars =
|
||||
options.getBoolean("Create Local Variables", doCreateLocalStackVars);
|
||||
|
||||
doCreateStackParams = options.getBoolean("Create Param Variables", doCreateStackParams);
|
||||
|
||||
maxThreadCount = options.getInt(MAX_THREAD_COUNT_OPTION_NAME, maxThreadCount);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,9 +15,13 @@
|
|||
*/
|
||||
package ghidra.app.services;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import generic.concurrent.*;
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -30,6 +34,8 @@ public abstract class AbstractAnalyzer implements Analyzer {
|
|||
private boolean supportsOneTimeAnalysis;
|
||||
private boolean isPrototype = false;
|
||||
private AnalysisPriority priority = AnalysisPriority.LOW_PRIORITY;
|
||||
|
||||
protected static final AddressSetView EMPTY_ADDRESS_SET = new AddressSetViewAdapter();
|
||||
|
||||
protected AbstractAnalyzer(String name, String description, AnalyzerType type) {
|
||||
this.name = name;
|
||||
|
@ -117,5 +123,96 @@ public abstract class AbstractAnalyzer implements Analyzer {
|
|||
public void registerOptions(Options options, Program program) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze a single location
|
||||
*
|
||||
* @param program - program to analyze
|
||||
* @param start - location to start flowing constants
|
||||
* @param set - restriction set of addresses to analyze
|
||||
* @param monitor - monitor to check canceled
|
||||
*
|
||||
* @return - set of addresses actually flowed to
|
||||
* @throws CancelledException
|
||||
*/
|
||||
public AddressSetView analyzeLocation(final Program program, Address start, AddressSetView set,
|
||||
final TaskMonitor monitor) throws CancelledException {
|
||||
return EMPTY_ADDRESS_SET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run constant an analysis at each location in parallel
|
||||
*
|
||||
* @param program program
|
||||
* @param locations points to analyze
|
||||
* @param restrictedSet set to restrict analysis to, null if none
|
||||
* @param maxThreads maximum number of threads to use
|
||||
* @param monitor to cancel
|
||||
* @return set of addresses covered during analysis
|
||||
*
|
||||
* @throws CancelledException if cancelled
|
||||
* @throws InterruptedException if interrupted
|
||||
* @throws Exception any exception
|
||||
*/
|
||||
protected AddressSetView runParallelAddressAnalysis(final Program program, final Set<Address> locations, final AddressSetView restrictedSet, int maxThreads,
|
||||
final TaskMonitor monitor) throws CancelledException, InterruptedException, Exception {
|
||||
|
||||
monitor.checkCancelled();
|
||||
|
||||
final AddressSet analyzedSet = new AddressSet();
|
||||
if (locations.isEmpty()) {
|
||||
return analyzedSet;
|
||||
}
|
||||
|
||||
GThreadPool pool = AutoAnalysisManager.getSharedAnalsysThreadPool();
|
||||
monitor.setMaximum(locations.size());
|
||||
|
||||
QCallback<Address, AddressSetView> callback = new QCallback<Address, AddressSetView>() {
|
||||
@Override
|
||||
public AddressSetView process(Address loc, TaskMonitor taskMonitor) {
|
||||
synchronized (analyzedSet) {
|
||||
if (analyzedSet.contains(loc)) {
|
||||
taskMonitor.incrementProgress(1);
|
||||
return EMPTY_ADDRESS_SET;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
AddressSetView result = analyzeLocation(program, loc, restrictedSet, taskMonitor);
|
||||
synchronized (analyzedSet) {
|
||||
analyzedSet.add(result);
|
||||
}
|
||||
|
||||
taskMonitor.incrementProgress(1);
|
||||
return result;
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return null; // monitor was cancelled
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// bound check thread limit
|
||||
if (maxThreads > pool.getMaxThreadCount()) {
|
||||
maxThreads = pool.getMaxThreadCount();
|
||||
}
|
||||
if (maxThreads < 1) {
|
||||
maxThreads = 1;
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
ConcurrentQ<Address, AddressSetView> queue = new ConcurrentQBuilder<Address, AddressSetView>()
|
||||
.setThreadPool(pool)
|
||||
.setMaxInProgress(maxThreads)
|
||||
.setMonitor(monitor)
|
||||
.build(callback);
|
||||
// @formatter:on
|
||||
|
||||
queue.add(locations);
|
||||
|
||||
queue.waitUntilDone();
|
||||
|
||||
return analyzedSet;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -155,126 +155,133 @@ public class ConstantPropogationReferenceTest extends AbstractGenericTest {
|
|||
return super.evaluateContextBefore(context, instr);
|
||||
}
|
||||
|
||||
private Varnode regValue(VarnodeContext context, String regName) {
|
||||
return context.getRegisterVarnodeValue(context.getRegister(regName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluateContext(VarnodeContext context, Instruction instr) {
|
||||
String loc = instr.getMinAddress().toString();
|
||||
Varnode registerVarnode;
|
||||
Long constVal;
|
||||
boolean isBad = false;
|
||||
|
||||
switch (loc) {
|
||||
case "00001010":
|
||||
// gp should be 0x14 + t9 offset space
|
||||
registerVarnode = regValue(context, "gp");
|
||||
assertTrue("symbolic value", context.isSymbol(registerVarnode));
|
||||
assertEquals("(t9, 0x13b334, 4)", registerVarnode.toString());
|
||||
// S3 should be S3 at entry
|
||||
registerVarnode = regValue(context, "s3");
|
||||
assertTrue("register s3", context.isRegister(registerVarnode));
|
||||
assertEquals("s3", context.getRegister(registerVarnode).getName());
|
||||
break;
|
||||
case "0000102c":
|
||||
// s1 restored from space 0x10(s0) space
|
||||
registerVarnode = regValue(context, "s1");
|
||||
assertTrue("constant value", registerVarnode.isConstant());
|
||||
assertEquals("(const, 0xa0b0c0d, 4)", registerVarnode.toString());
|
||||
break;
|
||||
case "00001030":
|
||||
// s1 restored from space 0x10(a1) space
|
||||
registerVarnode = regValue(context, "s1");
|
||||
assertTrue("symbolic value", registerVarnode.isConstant());
|
||||
assertEquals("(const, 0x12344567, 4)", registerVarnode.toString());
|
||||
break;
|
||||
case "00001034":
|
||||
// s1 restored from space 0x10(sp) space
|
||||
registerVarnode = regValue(context, "s1");
|
||||
assertTrue("symbolic value", context.isSymbol(registerVarnode));
|
||||
assertEquals("(t9, 0x13b334, 4)", registerVarnode.toString());
|
||||
break;
|
||||
case "00001038":
|
||||
// s1 restored from space 0x10(s2) space
|
||||
registerVarnode = regValue(context, "s1");
|
||||
//assertTrue("Still s1", registerVarnode.isRegister());
|
||||
boolean isBad = false;
|
||||
try {
|
||||
context.getConstant(registerVarnode, null);
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
isBad = true;
|
||||
}
|
||||
assertTrue("Can get constant value", isBad);
|
||||
break;
|
||||
case "00001040":
|
||||
// s1 restored from space 0x10(s2) space - stored a3
|
||||
registerVarnode = regValue(context, "s1");
|
||||
assertTrue("register s3", registerVarnode.isRegister());
|
||||
assertEquals("s3", context.getRegister(registerVarnode).getName());
|
||||
|
||||
Address lastSetLocation = context.getLastSetLocation(
|
||||
context.getRegisterVarnode(context.getRegister("s2")), null);
|
||||
assertEquals("s2 last set", null, lastSetLocation);
|
||||
break;
|
||||
case "00001048":
|
||||
// s1 restored from space 0x10(s2) after s2 has been set again
|
||||
// it should no longer be s3 that was stored in another s2 relative space
|
||||
registerVarnode = regValue(context, "s1");
|
||||
//assertTrue("Still s1", registerVarnode.isRegister());
|
||||
isBad = false;
|
||||
try {
|
||||
context.getConstant(registerVarnode, null);
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
isBad = true;
|
||||
}
|
||||
assertTrue("Can get constant value", isBad);
|
||||
break;
|
||||
case "0000104c":
|
||||
// s1 restored from space 0x10(s2) after s2 has been set again
|
||||
// it should no longer be s3 that was stored in another s2 relative space
|
||||
registerVarnode = regValue(context, "s2");
|
||||
//assertTrue("Still s2", registerVarnode.isRegister());
|
||||
isBad = false;
|
||||
try {
|
||||
context.getConstant(registerVarnode, null);
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
isBad = true;
|
||||
}
|
||||
assertTrue("Can get constant value", isBad);
|
||||
lastSetLocation = context.getLastSetLocation(
|
||||
context.getRegisterVarnode(context.getRegister("s2")), null);
|
||||
assertEquals("s2 last set", 0x104fL, lastSetLocation.getOffset());
|
||||
break;
|
||||
case "00001054":
|
||||
// s1 restored from space 0x10(s2) after s2 has been set again
|
||||
// it should no longer be s3 that was stored in another s2 relative space
|
||||
registerVarnode = regValue(context, "s1");
|
||||
//assertTrue("Still s1", registerVarnode.isRegister());
|
||||
//assertEquals(context.getRegister(registerVarnode).getName(),"s1");
|
||||
isBad = false;
|
||||
try {
|
||||
context.getConstant(registerVarnode, null);
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
isBad = true;
|
||||
}
|
||||
assertTrue("Can get constant value", isBad);
|
||||
break;
|
||||
switch(loc) {
|
||||
case "00001010":
|
||||
// gp should be 0x14 + t9 offset space
|
||||
registerVarnode = regValue(context,"gp");
|
||||
assertTrue("symbolic value", context.isSymbol(registerVarnode));
|
||||
assertEquals("(t9, 0x13b334, 4)", registerVarnode.toString());
|
||||
// S3 should be S3 at entry
|
||||
registerVarnode = regValue(context,"s3");
|
||||
assertTrue("register s3", context.isRegister(registerVarnode));
|
||||
assertEquals("s3", context.getRegister(registerVarnode).getName());
|
||||
break;
|
||||
case "0000102c":
|
||||
// s1 restored from space 0x10(s0) space
|
||||
registerVarnode = regValue(context,"s1");
|
||||
assertTrue("constant value", registerVarnode.isConstant());
|
||||
assertEquals("(const, 0xa0b0c0d, 4)", registerVarnode.toString());
|
||||
break;
|
||||
case "00001030":
|
||||
// s1 restored from space 0x10(a1) space
|
||||
registerVarnode = regValue(context,"s1");
|
||||
assertTrue("symbolic value", registerVarnode.isConstant());
|
||||
assertEquals("(const, 0x12344567, 4)", registerVarnode.toString());
|
||||
break;
|
||||
case "00001034":
|
||||
// s1 restored from space 0x10(sp) space
|
||||
registerVarnode = regValue(context,"s1");
|
||||
assertTrue("symbolic value", context.isSymbol(registerVarnode));
|
||||
assertEquals("(t9, 0x13b334, 4)", registerVarnode.toString());
|
||||
break;
|
||||
case "00001038":
|
||||
// s1 restored from space 0x10(s2) space
|
||||
registerVarnode = regValue(context,"s1");
|
||||
//assertTrue("Still s1", registerVarnode.isRegister());
|
||||
constVal = context.getConstant(registerVarnode, null);
|
||||
isBad = constVal == null;
|
||||
assertTrue("Can get constant value", isBad);
|
||||
break;
|
||||
case "00001040":
|
||||
// s1 restored from space 0x10(s2) space - stored a3
|
||||
registerVarnode = regValue(context,"s1");
|
||||
assertTrue("register s3", registerVarnode.isRegister());
|
||||
assertEquals("s3", context.getRegister(registerVarnode).getName());
|
||||
|
||||
Address lastSetLocation = context.getLastSetLocation(context.getRegisterVarnode(context.getRegister("s2")), null);
|
||||
assertEquals("s2 last set", null, lastSetLocation);
|
||||
break;
|
||||
case "00001048":
|
||||
// s1 restored from space 0x10(s2) after s2 has been set again
|
||||
// it should no longer be s3 that was stored in another s2 relative space
|
||||
registerVarnode = regValue(context,"s1");
|
||||
//assertTrue("Still s1", registerVarnode.isRegister());
|
||||
constVal = context.getConstant(registerVarnode, null);
|
||||
isBad = constVal == null;
|
||||
assertTrue("Can get constant value", isBad);
|
||||
break;
|
||||
case "0000104c":
|
||||
// s1 restored from space 0x10(s2) after s2 has been set again
|
||||
// it should no longer be s3 that was stored in another s2 relative space
|
||||
registerVarnode = regValue(context,"s2");
|
||||
//assertTrue("Still s2", registerVarnode.isRegister());
|
||||
constVal = context.getConstant(registerVarnode, null);
|
||||
isBad = constVal == null;
|
||||
assertTrue("Can get constant value", isBad);
|
||||
lastSetLocation = context.getLastSetLocation(context.getRegisterVarnode(context.getRegister("s2")), null);
|
||||
assertEquals("s2 last set", 0x104cL, lastSetLocation.getOffset());
|
||||
break;
|
||||
case "00001054":
|
||||
// s1 restored from space 0x10(s2) after s2 has been set again
|
||||
// it should no longer be s3 that was stored in another s2 relative space
|
||||
registerVarnode = regValue(context,"s1");
|
||||
//assertTrue("Still s1", registerVarnode.isRegister());
|
||||
//assertEquals(context.getRegister(registerVarnode).getName(),"s1");
|
||||
constVal = context.getConstant(registerVarnode, null);
|
||||
isBad = constVal == null;
|
||||
assertTrue("Can get constant value", isBad);
|
||||
break;
|
||||
}
|
||||
return super.evaluateContext(context, instr);
|
||||
}
|
||||
};
|
||||
|
||||
setRegister(addr("0x1000"), "s1", 0);
|
||||
SymbolicPropogator symEval = new SymbolicPropogator(program);
|
||||
SymbolicPropogator symEval = new SymbolicPropogator(program,true);
|
||||
|
||||
Function func = program.getFunctionManager().getFunctionAt(builder.addr(0x1000));
|
||||
|
||||
symEval.flowConstants(codeStart, func.getBody(), eval, true, TaskMonitor.DUMMY);
|
||||
|
||||
Value registerValue = symEval.getRegisterValue(addr("0x1010"), null);
|
||||
|
||||
Value registerValue;
|
||||
|
||||
// get gp at start of instruction
|
||||
registerValue = regValue(symEval, addr("0x1010"), "gp");
|
||||
assertNotNull(registerValue);
|
||||
assertTrue(registerValue.isRegisterRelativeValue());
|
||||
assertEquals(registerValue.getRelativeRegister().getName(), "t9");
|
||||
assertEquals(registerValue.getValue(), 0x13b334L);
|
||||
|
||||
// get s1 at start of instruction
|
||||
registerValue = regValue(symEval, addr("0x1034"), "s1");
|
||||
assertNotNull(registerValue);
|
||||
assertFalse(registerValue.isRegisterRelativeValue());
|
||||
assertEquals(registerValue.getValue(), 0x12344567L);
|
||||
|
||||
// get s1 at end of instruction
|
||||
registerValue = regEndValue(symEval, addr("0x1034"), "s1");
|
||||
assertNotNull(registerValue);
|
||||
assertTrue(registerValue.isRegisterRelativeValue());
|
||||
assertEquals(registerValue.getRelativeRegister().getName(), "t9");
|
||||
assertEquals(registerValue.getValue(), 0x13b334L);
|
||||
|
||||
// get sp at end of instruction
|
||||
registerValue = regValue(symEval, addr("0x1058"), "sp");
|
||||
assertNotNull(registerValue);
|
||||
assertTrue(registerValue.isRegisterRelativeValue());
|
||||
assertEquals(registerValue.getRelativeRegister().getName(), "sp");
|
||||
assertEquals(-32, registerValue.getValue());
|
||||
|
||||
// TODO: should be 0 at end
|
||||
// registerValue = regEndValue(symEval, addr("0x1058"), "sp");
|
||||
// assertEquals(0, registerValue.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -542,6 +549,22 @@ public class ConstantPropogationReferenceTest extends AbstractGenericTest {
|
|||
Register gp = context.getRegister(regname);
|
||||
context.setRegisterValue(a, a.add(12), new RegisterValue(gp, BigInteger.valueOf(value)));
|
||||
}
|
||||
|
||||
private Varnode regValue(VarnodeContext context, String regName) {
|
||||
return context.getRegisterVarnodeValue(context.getRegister(regName));
|
||||
}
|
||||
|
||||
private Value regValue(SymbolicPropogator symEval, Address addr, String regName) {
|
||||
ProgramContext context = program.getProgramContext();
|
||||
Register reg = context.getRegister(regName);
|
||||
return symEval.getRegisterValue(addr, reg);
|
||||
}
|
||||
|
||||
private Value regEndValue(SymbolicPropogator symEval, Address addr, String regName) {
|
||||
ProgramContext context = program.getProgramContext();
|
||||
Register reg = context.getRegister(regName);
|
||||
return symEval.getEndRegisterValue(addr, reg);
|
||||
}
|
||||
|
||||
private Address addr(String address) {
|
||||
return builder.addr(address);
|
||||
|
|
|
@ -70,6 +70,16 @@ public class VarnodeTranslator {
|
|||
Varnode node = new Varnode(register.getAddress(), register.getMinimumByteSize());
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get register given a register name
|
||||
*
|
||||
* @param name register name
|
||||
* @return register
|
||||
*/
|
||||
public Register getRegister(String name) {
|
||||
return language.getRegister(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all defined registers for the program this translator was created
|
||||
|
|
|
@ -73,30 +73,25 @@ public class Motorola68KAnalyzer extends ConstantPropagationAnalyzer {
|
|||
|
||||
if (mnemonic.equals("pea")) {
|
||||
// retrieve the value pushed onto the stack
|
||||
try {
|
||||
Varnode stackValue = context.getValue(context.getStackVarnode(), this);
|
||||
Varnode value = context.getValue(stackValue, this);
|
||||
if (value != null && value.isConstant()) {
|
||||
long lval = value.getOffset();
|
||||
Address refAddr = instr.getMinAddress().getNewAddress(lval);
|
||||
if (lval <= 4096 || ((lval % 1024) == 0) || lval < 0 ||
|
||||
lval == 0xffff || lval == 0xff00 || lval == 0xffffff ||
|
||||
lval == 0xff0000 || lval == 0xff00ff || lval == 0xffffffff ||
|
||||
lval == 0xffffff00 || lval == 0xffff0000 ||
|
||||
lval == 0xff000000) {
|
||||
return false;
|
||||
}
|
||||
if (program.getMemory().contains(refAddr)) {
|
||||
if (instr.getOperandReferences(0).length == 0) {
|
||||
instr.addOperandReference(0, refAddr, RefType.DATA,
|
||||
SourceType.ANALYSIS);
|
||||
}
|
||||
Varnode stackValue = context.getValue(context.getStackVarnode(), this);
|
||||
Varnode value = context.getValue(stackValue, this);
|
||||
if (value != null && value.isConstant()) {
|
||||
long lval = value.getOffset();
|
||||
Address refAddr = instr.getMinAddress().getNewAddress(lval);
|
||||
if (lval <= 4096 || ((lval % 1024) == 0) || lval < 0 ||
|
||||
lval == 0xffff || lval == 0xff00 || lval == 0xffffff ||
|
||||
lval == 0xff0000 || lval == 0xff00ff || lval == 0xffffffff ||
|
||||
lval == 0xffffff00 || lval == 0xffff0000 ||
|
||||
lval == 0xff000000) {
|
||||
return false;
|
||||
}
|
||||
if (program.getMemory().contains(refAddr)) {
|
||||
if (instr.getOperandReferences(0).length == 0) {
|
||||
instr.addOperandReference(0, refAddr, RefType.DATA,
|
||||
SourceType.ANALYSIS);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
// value not found doesn't matter
|
||||
}
|
||||
}
|
||||
if (mnemonic.equals("lea")) {
|
||||
Register destReg = instr.getRegister(1);
|
||||
|
|
|
@ -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.
|
||||
|
@ -799,7 +799,7 @@ public class ArmAnalyzer extends ConstantPropagationAnalyzer {
|
|||
if (tmodeRegister != null && listing.getUndefinedDataAt(addr) != null) {
|
||||
boolean inThumbMode = false;
|
||||
RegisterValue curvalue =
|
||||
context.getRegisterValue(tmodeRegister, instruction.getMinAddress());
|
||||
context.getRegisterValue(tmodeRegister);
|
||||
if (curvalue != null && curvalue.hasValue()) {
|
||||
inThumbMode = (curvalue.getUnsignedValue().intValue() == 1);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -552,7 +552,7 @@ public class MipsAddressAnalyzer extends ConstantPropagationAnalyzer {
|
|||
|
||||
if (isamode != null && listing.getUndefinedDataAt(addr) != null) {
|
||||
boolean inM16Mode = false;
|
||||
RegisterValue curvalue = context.getRegisterValue(isamode, instruction.getMinAddress());
|
||||
RegisterValue curvalue = context.getRegisterValue(isamode);
|
||||
if (curvalue != null && curvalue.hasValue()) {
|
||||
inM16Mode = (curvalue.getUnsignedValue().intValue() == 1);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue