mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 02:09:44 +02:00
GP-1268: Fix recording of multiple register banks. Supports GP-2870.
This commit is contained in:
parent
79c0f3f1de
commit
f2b0883c70
12 changed files with 93 additions and 35 deletions
|
@ -13,6 +13,8 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.services.*;
|
||||
|
@ -69,10 +71,12 @@ public class RefreshRegistersScript extends GhidraScript {
|
|||
// Now, we need to get the relevant recorder
|
||||
TraceRecorder recorder = modelService.getRecorder(current.getTrace());
|
||||
// There's a chance of an NPE here if there is no "current frame"
|
||||
TargetRegisterBank bank =
|
||||
recorder.getTargetRegisterBank(current.getThread(), current.getFrame());
|
||||
Set<TargetRegisterBank> banks =
|
||||
recorder.getTargetRegisterBanks(current.getThread(), current.getFrame());
|
||||
for (TargetRegisterBank bank : banks) {
|
||||
// Now do the same to the bank as before
|
||||
bank.invalidateCaches().get();
|
||||
bank.fetchElements(true).get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1296,8 +1296,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
return AsyncUtils.NIL;
|
||||
}
|
||||
toRead.retainAll(regMapper.getRegistersOnTarget());
|
||||
TargetRegisterBank bank = recorder.getTargetRegisterBank(traceThread, current.getFrame());
|
||||
if (bank == null || !bank.isValid()) {
|
||||
Set<TargetRegisterBank> banks = recorder.getTargetRegisterBanks(traceThread, current.getFrame());
|
||||
if (banks == null || banks.isEmpty()) {
|
||||
Msg.error(this, "Current frame's bank does not exist");
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
|
@ -1320,10 +1320,10 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
if (recorder.getSnap() != current.getSnap()) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
if (current.getFrame() == 0) {
|
||||
// Should have been pushed by model. non-zero frames are poll-only
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
// if (current.getFrame() == 0) {
|
||||
// // Should have been pushed by model. non-zero frames are poll-only
|
||||
// return AsyncUtils.NIL;
|
||||
// }
|
||||
TraceThread traceThread = current.getThread();
|
||||
TargetThread targetThread = recorder.getTargetThread(traceThread);
|
||||
if (targetThread == null) {
|
||||
|
|
|
@ -67,7 +67,7 @@ public class FridaArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
boolean is64Bit =
|
||||
arch.contains("AARCH64") || arch.contains("arm64") || arch.contains("arm");
|
||||
String os = env.getOperatingSystem();
|
||||
if (os.contains("macos")) {
|
||||
if (os.contains("macos") || os.contains("ios")) {
|
||||
if (is64Bit) {
|
||||
Msg.info(this, "Using os=" + os + " arch=" + arch);
|
||||
return Set.of(new FridaAarch64MacosOffer((TargetProcess) target));
|
||||
|
|
|
@ -95,7 +95,7 @@ public class FridaDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinio
|
|||
}
|
||||
String lcOS = os.toLowerCase();
|
||||
boolean isLinux = lcOS.contains("linux");
|
||||
boolean isMacOS = lcOS.contains("darwin") || lcOS.contains("macos");
|
||||
boolean isMacOS = lcOS.contains("darwin") || lcOS.contains("macos") || lcOS.contains("ios");
|
||||
boolean isWindows = lcOS.contains("windows");
|
||||
String lcArch = arch.toLowerCase();
|
||||
// "arm" subsumes "arm64"
|
||||
|
|
|
@ -66,7 +66,7 @@ public class LldbArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
String arch = env.getArchitecture();
|
||||
boolean is64Bit = arch.contains("AARCH64") || arch.contains("arm64");
|
||||
String os = env.getOperatingSystem();
|
||||
if (os.contains("macos")) {
|
||||
if (os.contains("macos") || os.contains("ios")) {
|
||||
if (is64Bit) {
|
||||
Msg.info(this, "Using os=" + os + " arch=" + arch);
|
||||
return Set.of(new LldbAarch64MacosOffer((TargetProcess) target));
|
||||
|
|
|
@ -86,6 +86,33 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
|
|||
}
|
||||
}
|
||||
|
||||
protected static class LldbDebuggerPlatformOffer extends AbstractDebuggerPlatformOffer {
|
||||
public static LldbDebuggerPlatformOffer fromArchLCSP(String arch,
|
||||
LanguageCompilerSpecPair lcsp)
|
||||
throws CompilerSpecNotFoundException, LanguageNotFoundException {
|
||||
return new LldbDebuggerPlatformOffer("Default LLDB for " + arch, lcsp.getCompilerSpec());
|
||||
}
|
||||
|
||||
public LldbDebuggerPlatformOffer(String description, CompilerSpec cSpec) {
|
||||
super(description, cSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConfidence() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
|
||||
return new LldbDebuggerPlatformMapper(tool, trace, cSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
|
||||
return mapper.getClass() == LldbDebuggerPlatformMapper.class;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
|
||||
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
|
||||
|
@ -95,7 +122,7 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
|
|||
}
|
||||
String lcOS = os.toLowerCase();
|
||||
boolean isLinux = lcOS.contains("linux");
|
||||
boolean isMacOS = lcOS.contains("darwin") || lcOS.contains("macos");
|
||||
boolean isMacOS = lcOS.contains("darwin") || lcOS.contains("macos") || lcOS.contains("ios");
|
||||
boolean isWindows = lcOS.contains("windows");
|
||||
String lcArch = arch.toLowerCase();
|
||||
// "arm" subsumes "arm64"
|
||||
|
|
|
@ -117,7 +117,7 @@ public class DefaultStackRecorder implements ManagedStackRecorder {
|
|||
for (TargetObject p = successor; p != null; p = p.getParent()) {
|
||||
if (p instanceof TargetStackFrame) {
|
||||
if (!PathUtils.isIndex(p.getPath())) {
|
||||
return 0;
|
||||
throw new AssertionError("Invalid path index "+p.getPath());
|
||||
}
|
||||
int index = Integer.decode(p.getIndex());
|
||||
TargetStackFrame frame;
|
||||
|
@ -125,7 +125,10 @@ public class DefaultStackRecorder implements ManagedStackRecorder {
|
|||
frame = stack.get(index);
|
||||
}
|
||||
if (!Objects.equals(p, frame)) {
|
||||
return 0;
|
||||
// NB: This really ought to be an error but the dead frames ask for updates
|
||||
// after they're dead - until we can figure that out...
|
||||
//throw new AssertionError("Recorder stack has lost synchronization");
|
||||
return -1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
|
@ -48,7 +49,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
|||
//private AbstractRecorderRegisterSet threadRegisters;
|
||||
protected TargetBreakpointSpecContainer threadBreakpointContainer;
|
||||
|
||||
protected Map<Integer, TargetRegisterBank> regs = new HashMap<>();
|
||||
protected Map<Integer, Set<TargetRegisterBank>> regs = new HashMap<>();
|
||||
protected Collection<TargetRegister> extraRegs;
|
||||
|
||||
protected TargetExecutionState state = TargetExecutionState.ALIVE;
|
||||
|
@ -173,16 +174,27 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
|||
List<TargetRegister> tRegs =
|
||||
registers.stream().map(regMapper::traceToTarget).collect(Collectors.toList());
|
||||
|
||||
TargetRegisterBank bank = getTargetRegisterBank(thread, frameLevel);
|
||||
if (bank == null) {
|
||||
Set<TargetRegisterBank> banks = getTargetRegisterBank(thread, frameLevel);
|
||||
if (banks == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Given thread and frame level does not have a live register bank");
|
||||
}
|
||||
// NOTE: Cache update, if applicable, will cause recorder to write values to trace
|
||||
return bank.readRegisters(tRegs).thenApply(regMapper::targetToTrace);
|
||||
AsyncFence fence = new AsyncFence();
|
||||
Map<Register, RegisterValue> result = new HashMap<>();
|
||||
for (TargetRegisterBank bank : banks) {
|
||||
fence.include(bank.readRegisters(tRegs)
|
||||
.thenApply(regMapper::targetToTrace)
|
||||
.thenAccept(br -> {
|
||||
synchronized (result) {
|
||||
result.putAll(br);
|
||||
}
|
||||
}));
|
||||
}
|
||||
return fence.ready().thenApply(__ -> result);
|
||||
}
|
||||
|
||||
public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) {
|
||||
public Set<TargetRegisterBank> getTargetRegisterBank(TraceThread thread, int frameLevel) {
|
||||
return regs.get(frameLevel);
|
||||
}
|
||||
|
||||
|
@ -210,18 +222,23 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
|||
doFetchAndInitRegMapper(bank);
|
||||
}
|
||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||
//System.err.println("offerRegisters " + this.targetThread.getDisplay() + ":" + frameLevel);
|
||||
TargetRegisterBank old = regs.put(frameLevel, bank);
|
||||
if (null != old) {
|
||||
Set<TargetRegisterBank> set = regs.get(frameLevel);
|
||||
if (set == null) {
|
||||
set = new HashSet<>();
|
||||
regs.put(frameLevel, set);
|
||||
}
|
||||
if (set.contains(bank)) {
|
||||
Msg.warn(this, "Unexpected register bank replacement");
|
||||
}
|
||||
set.add(bank);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRegisters(TargetRegisterBank bank) {
|
||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||
TargetRegisterBank old = regs.remove(frameLevel);
|
||||
if (bank != old) {
|
||||
Set<TargetRegisterBank> set = regs.get(frameLevel);
|
||||
boolean remove = set.remove(bank);
|
||||
if (!remove) {
|
||||
Msg.warn(this, "Unexpected register bank upon removal");
|
||||
}
|
||||
}
|
||||
|
@ -261,6 +278,9 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
|||
}
|
||||
}
|
||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||
if (frameLevel < 0) {
|
||||
return;
|
||||
}
|
||||
long snap = recorder.getSnap();
|
||||
String path = bank.getJoinedPath(".");
|
||||
TimedMsg.debug(this, "Reg values changed: " + updates.keySet());
|
||||
|
@ -364,13 +384,17 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
|||
return regMapper.traceToTarget(ent.getValue());
|
||||
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
|
||||
|
||||
TargetRegisterBank bank = getTargetRegisterBank(traceThread, frameLevel);
|
||||
if (bank == null) {
|
||||
Set<TargetRegisterBank> banks = getTargetRegisterBank(traceThread, frameLevel);
|
||||
if (banks == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Given thread and frame level does not have a live register bank");
|
||||
}
|
||||
// NOTE: Model + recorder will cause applicable trace updates
|
||||
return bank.writeRegistersNamed(tVals).thenApply(__ -> null);
|
||||
AsyncFence fence = new AsyncFence();
|
||||
for (TargetRegisterBank bank : banks) {
|
||||
fence.include(bank.writeRegistersNamed(tVals).thenApply(__ -> null));
|
||||
}
|
||||
return fence.ready();
|
||||
}
|
||||
|
||||
Address registerValueToTargetAddress(RegisterValue rv, byte[] value) {
|
||||
|
|
|
@ -232,7 +232,7 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) {
|
||||
public Set<TargetRegisterBank> getTargetRegisterBanks(TraceThread thread, int frameLevel) {
|
||||
DefaultThreadRecorder rec = getThreadRecorder(thread);
|
||||
return rec.getTargetRegisterBank(thread, frameLevel);
|
||||
}
|
||||
|
|
|
@ -426,8 +426,8 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) {
|
||||
return objectRecorder.getTargetFrameInterface(thread, frameLevel, TargetRegisterBank.class);
|
||||
public Set<TargetRegisterBank> getTargetRegisterBanks(TraceThread thread, int frameLevel) {
|
||||
return Set.of(objectRecorder.getTargetFrameInterface(thread, frameLevel, TargetRegisterBank.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -332,7 +332,7 @@ public interface TraceRecorder {
|
|||
* @param frameLevel the frame level
|
||||
* @return the bank, or null
|
||||
*/
|
||||
TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel);
|
||||
Set<TargetRegisterBank> getTargetRegisterBanks(TraceThread thread, int frameLevel);
|
||||
|
||||
/**
|
||||
* Get the trace thread corresponding to the given target thread
|
||||
|
|
|
@ -512,7 +512,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU
|
|||
TraceObjectThread thread = (TraceObjectThread) recorder.getTraceThread(mb.testThread1);
|
||||
assertNotNull(thread);
|
||||
|
||||
assertEquals(mb.testBank1, recorder.getTargetRegisterBank(thread, 0));
|
||||
assertEquals(Set.of(mb.testBank1), recorder.getTargetRegisterBanks(thread, 0));
|
||||
assertEquals(thread, recorder.getTraceThreadForSuccessor(mb.testBank1));
|
||||
|
||||
TraceObject traceBank = thread.getObject()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue