mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
|
@ -69,10 +71,12 @@ public class RefreshRegistersScript extends GhidraScript {
|
||||||
// Now, we need to get the relevant recorder
|
// Now, we need to get the relevant recorder
|
||||||
TraceRecorder recorder = modelService.getRecorder(current.getTrace());
|
TraceRecorder recorder = modelService.getRecorder(current.getTrace());
|
||||||
// There's a chance of an NPE here if there is no "current frame"
|
// There's a chance of an NPE here if there is no "current frame"
|
||||||
TargetRegisterBank bank =
|
Set<TargetRegisterBank> banks =
|
||||||
recorder.getTargetRegisterBank(current.getThread(), current.getFrame());
|
recorder.getTargetRegisterBanks(current.getThread(), current.getFrame());
|
||||||
|
for (TargetRegisterBank bank : banks) {
|
||||||
// Now do the same to the bank as before
|
// Now do the same to the bank as before
|
||||||
bank.invalidateCaches().get();
|
bank.invalidateCaches().get();
|
||||||
bank.fetchElements(true).get();
|
bank.fetchElements(true).get();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1296,8 +1296,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||||
return AsyncUtils.NIL;
|
return AsyncUtils.NIL;
|
||||||
}
|
}
|
||||||
toRead.retainAll(regMapper.getRegistersOnTarget());
|
toRead.retainAll(regMapper.getRegistersOnTarget());
|
||||||
TargetRegisterBank bank = recorder.getTargetRegisterBank(traceThread, current.getFrame());
|
Set<TargetRegisterBank> banks = recorder.getTargetRegisterBanks(traceThread, current.getFrame());
|
||||||
if (bank == null || !bank.isValid()) {
|
if (banks == null || banks.isEmpty()) {
|
||||||
Msg.error(this, "Current frame's bank does not exist");
|
Msg.error(this, "Current frame's bank does not exist");
|
||||||
return AsyncUtils.NIL;
|
return AsyncUtils.NIL;
|
||||||
}
|
}
|
||||||
|
@ -1320,10 +1320,10 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||||
if (recorder.getSnap() != current.getSnap()) {
|
if (recorder.getSnap() != current.getSnap()) {
|
||||||
return AsyncUtils.NIL;
|
return AsyncUtils.NIL;
|
||||||
}
|
}
|
||||||
if (current.getFrame() == 0) {
|
// if (current.getFrame() == 0) {
|
||||||
// Should have been pushed by model. non-zero frames are poll-only
|
// // Should have been pushed by model. non-zero frames are poll-only
|
||||||
return AsyncUtils.NIL;
|
// return AsyncUtils.NIL;
|
||||||
}
|
// }
|
||||||
TraceThread traceThread = current.getThread();
|
TraceThread traceThread = current.getThread();
|
||||||
TargetThread targetThread = recorder.getTargetThread(traceThread);
|
TargetThread targetThread = recorder.getTargetThread(traceThread);
|
||||||
if (targetThread == null) {
|
if (targetThread == null) {
|
||||||
|
|
|
@ -67,7 +67,7 @@ public class FridaArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
||||||
boolean is64Bit =
|
boolean is64Bit =
|
||||||
arch.contains("AARCH64") || arch.contains("arm64") || arch.contains("arm");
|
arch.contains("AARCH64") || arch.contains("arm64") || arch.contains("arm");
|
||||||
String os = env.getOperatingSystem();
|
String os = env.getOperatingSystem();
|
||||||
if (os.contains("macos")) {
|
if (os.contains("macos") || os.contains("ios")) {
|
||||||
if (is64Bit) {
|
if (is64Bit) {
|
||||||
Msg.info(this, "Using os=" + os + " arch=" + arch);
|
Msg.info(this, "Using os=" + os + " arch=" + arch);
|
||||||
return Set.of(new FridaAarch64MacosOffer((TargetProcess) target));
|
return Set.of(new FridaAarch64MacosOffer((TargetProcess) target));
|
||||||
|
|
|
@ -95,7 +95,7 @@ public class FridaDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinio
|
||||||
}
|
}
|
||||||
String lcOS = os.toLowerCase();
|
String lcOS = os.toLowerCase();
|
||||||
boolean isLinux = lcOS.contains("linux");
|
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");
|
boolean isWindows = lcOS.contains("windows");
|
||||||
String lcArch = arch.toLowerCase();
|
String lcArch = arch.toLowerCase();
|
||||||
// "arm" subsumes "arm64"
|
// "arm" subsumes "arm64"
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class LldbArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
||||||
String arch = env.getArchitecture();
|
String arch = env.getArchitecture();
|
||||||
boolean is64Bit = arch.contains("AARCH64") || arch.contains("arm64");
|
boolean is64Bit = arch.contains("AARCH64") || arch.contains("arm64");
|
||||||
String os = env.getOperatingSystem();
|
String os = env.getOperatingSystem();
|
||||||
if (os.contains("macos")) {
|
if (os.contains("macos") || os.contains("ios")) {
|
||||||
if (is64Bit) {
|
if (is64Bit) {
|
||||||
Msg.info(this, "Using os=" + os + " arch=" + arch);
|
Msg.info(this, "Using os=" + os + " arch=" + arch);
|
||||||
return Set.of(new LldbAarch64MacosOffer((TargetProcess) target));
|
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
|
@Override
|
||||||
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
|
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
|
||||||
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
|
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
|
||||||
|
@ -95,7 +122,7 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
|
||||||
}
|
}
|
||||||
String lcOS = os.toLowerCase();
|
String lcOS = os.toLowerCase();
|
||||||
boolean isLinux = lcOS.contains("linux");
|
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");
|
boolean isWindows = lcOS.contains("windows");
|
||||||
String lcArch = arch.toLowerCase();
|
String lcArch = arch.toLowerCase();
|
||||||
// "arm" subsumes "arm64"
|
// "arm" subsumes "arm64"
|
||||||
|
|
|
@ -117,7 +117,7 @@ public class DefaultStackRecorder implements ManagedStackRecorder {
|
||||||
for (TargetObject p = successor; p != null; p = p.getParent()) {
|
for (TargetObject p = successor; p != null; p = p.getParent()) {
|
||||||
if (p instanceof TargetStackFrame) {
|
if (p instanceof TargetStackFrame) {
|
||||||
if (!PathUtils.isIndex(p.getPath())) {
|
if (!PathUtils.isIndex(p.getPath())) {
|
||||||
return 0;
|
throw new AssertionError("Invalid path index "+p.getPath());
|
||||||
}
|
}
|
||||||
int index = Integer.decode(p.getIndex());
|
int index = Integer.decode(p.getIndex());
|
||||||
TargetStackFrame frame;
|
TargetStackFrame frame;
|
||||||
|
@ -125,7 +125,10 @@ public class DefaultStackRecorder implements ManagedStackRecorder {
|
||||||
frame = stack.get(index);
|
frame = stack.get(index);
|
||||||
}
|
}
|
||||||
if (!Objects.equals(p, frame)) {
|
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;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.mapping.*;
|
import ghidra.app.plugin.core.debug.mapping.*;
|
||||||
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
|
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
|
||||||
|
import ghidra.async.AsyncFence;
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||||
|
@ -48,7 +49,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
||||||
//private AbstractRecorderRegisterSet threadRegisters;
|
//private AbstractRecorderRegisterSet threadRegisters;
|
||||||
protected TargetBreakpointSpecContainer threadBreakpointContainer;
|
protected TargetBreakpointSpecContainer threadBreakpointContainer;
|
||||||
|
|
||||||
protected Map<Integer, TargetRegisterBank> regs = new HashMap<>();
|
protected Map<Integer, Set<TargetRegisterBank>> regs = new HashMap<>();
|
||||||
protected Collection<TargetRegister> extraRegs;
|
protected Collection<TargetRegister> extraRegs;
|
||||||
|
|
||||||
protected TargetExecutionState state = TargetExecutionState.ALIVE;
|
protected TargetExecutionState state = TargetExecutionState.ALIVE;
|
||||||
|
@ -173,16 +174,27 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
||||||
List<TargetRegister> tRegs =
|
List<TargetRegister> tRegs =
|
||||||
registers.stream().map(regMapper::traceToTarget).collect(Collectors.toList());
|
registers.stream().map(regMapper::traceToTarget).collect(Collectors.toList());
|
||||||
|
|
||||||
TargetRegisterBank bank = getTargetRegisterBank(thread, frameLevel);
|
Set<TargetRegisterBank> banks = getTargetRegisterBank(thread, frameLevel);
|
||||||
if (bank == null) {
|
if (banks == null) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Given thread and frame level does not have a live register bank");
|
"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
|
// 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);
|
return regs.get(frameLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,18 +222,23 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
||||||
doFetchAndInitRegMapper(bank);
|
doFetchAndInitRegMapper(bank);
|
||||||
}
|
}
|
||||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||||
//System.err.println("offerRegisters " + this.targetThread.getDisplay() + ":" + frameLevel);
|
Set<TargetRegisterBank> set = regs.get(frameLevel);
|
||||||
TargetRegisterBank old = regs.put(frameLevel, bank);
|
if (set == null) {
|
||||||
if (null != old) {
|
set = new HashSet<>();
|
||||||
|
regs.put(frameLevel, set);
|
||||||
|
}
|
||||||
|
if (set.contains(bank)) {
|
||||||
Msg.warn(this, "Unexpected register bank replacement");
|
Msg.warn(this, "Unexpected register bank replacement");
|
||||||
}
|
}
|
||||||
|
set.add(bank);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeRegisters(TargetRegisterBank bank) {
|
public void removeRegisters(TargetRegisterBank bank) {
|
||||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||||
TargetRegisterBank old = regs.remove(frameLevel);
|
Set<TargetRegisterBank> set = regs.get(frameLevel);
|
||||||
if (bank != old) {
|
boolean remove = set.remove(bank);
|
||||||
|
if (!remove) {
|
||||||
Msg.warn(this, "Unexpected register bank upon removal");
|
Msg.warn(this, "Unexpected register bank upon removal");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,6 +278,9 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||||
|
if (frameLevel < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
long snap = recorder.getSnap();
|
long snap = recorder.getSnap();
|
||||||
String path = bank.getJoinedPath(".");
|
String path = bank.getJoinedPath(".");
|
||||||
TimedMsg.debug(this, "Reg values changed: " + updates.keySet());
|
TimedMsg.debug(this, "Reg values changed: " + updates.keySet());
|
||||||
|
@ -364,13 +384,17 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
||||||
return regMapper.traceToTarget(ent.getValue());
|
return regMapper.traceToTarget(ent.getValue());
|
||||||
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
|
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
|
||||||
|
|
||||||
TargetRegisterBank bank = getTargetRegisterBank(traceThread, frameLevel);
|
Set<TargetRegisterBank> banks = getTargetRegisterBank(traceThread, frameLevel);
|
||||||
if (bank == null) {
|
if (banks == null) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Given thread and frame level does not have a live register bank");
|
"Given thread and frame level does not have a live register bank");
|
||||||
}
|
}
|
||||||
// NOTE: Model + recorder will cause applicable trace updates
|
// 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) {
|
Address registerValueToTargetAddress(RegisterValue rv, byte[] value) {
|
||||||
|
|
|
@ -232,7 +232,7 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) {
|
public Set<TargetRegisterBank> getTargetRegisterBanks(TraceThread thread, int frameLevel) {
|
||||||
DefaultThreadRecorder rec = getThreadRecorder(thread);
|
DefaultThreadRecorder rec = getThreadRecorder(thread);
|
||||||
return rec.getTargetRegisterBank(thread, frameLevel);
|
return rec.getTargetRegisterBank(thread, frameLevel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -426,8 +426,8 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) {
|
public Set<TargetRegisterBank> getTargetRegisterBanks(TraceThread thread, int frameLevel) {
|
||||||
return objectRecorder.getTargetFrameInterface(thread, frameLevel, TargetRegisterBank.class);
|
return Set.of(objectRecorder.getTargetFrameInterface(thread, frameLevel, TargetRegisterBank.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -332,7 +332,7 @@ public interface TraceRecorder {
|
||||||
* @param frameLevel the frame level
|
* @param frameLevel the frame level
|
||||||
* @return the bank, or null
|
* @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
|
* 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);
|
TraceObjectThread thread = (TraceObjectThread) recorder.getTraceThread(mb.testThread1);
|
||||||
assertNotNull(thread);
|
assertNotNull(thread);
|
||||||
|
|
||||||
assertEquals(mb.testBank1, recorder.getTargetRegisterBank(thread, 0));
|
assertEquals(Set.of(mb.testBank1), recorder.getTargetRegisterBanks(thread, 0));
|
||||||
assertEquals(thread, recorder.getTraceThreadForSuccessor(mb.testBank1));
|
assertEquals(thread, recorder.getTraceThreadForSuccessor(mb.testBank1));
|
||||||
|
|
||||||
TraceObject traceBank = thread.getObject()
|
TraceObject traceBank = thread.getObject()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue