GP-1268: Fix recording of multiple register banks. Supports GP-2870.

This commit is contained in:
d-millar 2022-11-22 15:38:05 -05:00 committed by Dan
parent 79c0f3f1de
commit f2b0883c70
12 changed files with 93 additions and 35 deletions

View file

@ -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();
}
}
}

View file

@ -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) {

View file

@ -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));

View file

@ -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"

View file

@ -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));

View file

@ -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"

View file

@ -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;
}

View file

@ -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) {

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -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()