mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-04-25 17:25:17 +02:00
Merge tag 'Ghidra_12.0.3_build' into stable
This commit is contained in:
@@ -1,29 +1,54 @@
|
||||
# Ghidra 12.0.3 Change History (February 2026)
|
||||
|
||||
### New Features
|
||||
* _Listing_. In order to mitigate possible security risks, auto comments will not longer render annotations in such a way as to make them valid annotation links. Normal comments will continue to work as usual. (GP-6414)
|
||||
|
||||
### Improvements
|
||||
* _Demangler_. The __Demangler GNU__ analyzer now has a timeout option. (GP-6408)
|
||||
* _GUI_. Corrected Ghidra GUI to fail-fast in headless environment and avoid stack traces. (GP-6399)
|
||||
* _Listing_. The `@execute` annotation is no longer supported. (GP-6413)
|
||||
|
||||
### Bugs
|
||||
* _Data Types_. Corrected multi-user merge issues related to non-packed structures which could negatively affect merge results. (GP-6320, Issue #8776)
|
||||
* _Debugger_. Fixed a `NullPointerException` that could occur upon closing the Debugger. (GP-6376)
|
||||
* _Debugger:Breakpoints_. Fixed an issue where restarting a target (e.g., the `run` command from GDB's CLI) caused duplicate breakpoint entries and GUI glitches. (GP-6027)
|
||||
* _Decompiler_. Fixed _"PTRSUB off of non structured pointer type"_ exceptions caused by `void *` data-type. (GP-6388, Issue #8887)
|
||||
* _Decompiler_. Fixed source of _"Forced merge caused intersection"_ exceptions when decompiling optimized string copies. (GP-6393, Issue #8651)
|
||||
* _Multi-User_. Revised Ghidra Server self-signed certificate generation to include all associated FQDNs and IP addresses as subject alternative names. This will address the forced hostname check imposed with the release of JDK 21.0.10. To benefit from this change the Ghidra Server will need to be upgraded to this release. A client-side workaround is to set the following JVM property within `support/launch.properties` by adding the line: `VMARGS=-Djdk.rmi.ssl.client.enableEndpointIdentification=false`. (GP-6426, Issue #8940)
|
||||
* _Processors_. Fixed bug in AARCH64 `sha1h` instruction to shift instead of rotate bits. (GP-4501, Issue #6398)
|
||||
* _Processors_. Fixed 80251 disassembly errors for instructions referencing the SPX register. (GP-5905, Issue #8395)
|
||||
* _Processors_. Fixed disassembly of MIPS16e2 `lui` instruction to only parse on extended words. (GP-6419)
|
||||
* _Search_. Fixed a memory leak in the `Find References...` action. (GP-6395, Issue #8921)
|
||||
|
||||
### Notable API Changes
|
||||
* _Data Types_. (GP-6320) Structure offset-based insert methods `Structure.insertAtOffset` will now skip forward over existing zero-length components at the insert offset before performing insert of new component.
|
||||
|
||||
# Ghidra 12.0.2 Change History (January 2026)
|
||||
|
||||
### New Features
|
||||
* _Emulator_. Fixed emulator's evaluation of `inst_next2` (GP-6134, Issue #8646)
|
||||
* _Emulator_. Fixed the Emulator's evaluation of `inst_next2`. (GP-6134, Issue #8646)
|
||||
|
||||
### Improvements
|
||||
* _Basic Infrastructure_. Upgraded `commons-lang3` , `log4j`, and `postgresql` jars. (GP-6243)
|
||||
* _Debugger_. Several Address and Value columns are now displayed in fixed-width font: Register Value, Stack PC, Snapshot PC, Watch Value (GP-6025)
|
||||
* _Debugger_. Several Address and Value columns are now displayed in fixed-width font: Register Value, Stack PC, Snapshot PC, and Watch Value. (GP-6025)
|
||||
* _Debugger:Breakpoints_. Added __Expression__ column to __Breakpoints__ locations table. (GP-6026)
|
||||
* _Documentation_. Updated Debugger tutorial to reflect the addition of the Comment column to the Watches panel, and the moving of the schedule display to trace tabs instead of the Threads panel title bar. (GP-6032)
|
||||
* _Extensions_. Fixed a potential zip path traversal vulnerability when unzipping Ghidra Extension archives. (GP-6354)
|
||||
* _Multi-User_. Upgraded yajsw to 13.18. (GP-6364)
|
||||
|
||||
### Bugs
|
||||
* _Data Types_. Corrected Union update notification issue which impacted proper archive sync indicators and related operations. (GP-6359, Issue #8884)
|
||||
* _Debugger_. Fixed missing "Dynamic Listing" entry in Window menu, when the Dynamic Listing is closed. (GP-6086, Issue #8604)
|
||||
* _Debugger:Emulator_. Fixed a silent infinite read loop during some situations in an emulator forked from a live target. (GP-6340)
|
||||
* _Data Types_. Corrected a Union update notification issue which impacted proper archive sync indicators and related operations. (GP-6359, Issue #8884)
|
||||
* _Debugger_. Fixed missing __Dynamic Listing__ entry in Window menu, when the Dynamic Listing is closed. (GP-6086, Issue #8604)
|
||||
* _Debugger:Emulator_. Fixed a silent infinite-read loop during some situations in an emulator forked from a live target. (GP-6340)
|
||||
* _Demangler_. Fixed Gnu Demangler failure to parse a global guard variable. (GP-6371, Issue #8900)
|
||||
* _GUI_. Updated the Symbol Tree's filter to fix an issue that sometimes caused it to not get painted. (GP-6366, Issue #2448)
|
||||
* _Processors_. Corrected AARCH64 `ldapr` instruction semantics to properly read memory (GP-6358, Issue #6593)
|
||||
* _Processors_. Corrected AARCH64 `ldapr` instruction semantics to properly read memory. (GP-6358, Issue #6593)
|
||||
* _Processors_. Corrected PowerPC VLE `se_blrl` instruction semantics. (GP-6379, Issue #6207)
|
||||
* _Processors_. Corrected issue with ARM `ldrexd` instruction when the operands are the same register. (GP-6381, Issue #6590)
|
||||
|
||||
### Notable API Changes
|
||||
* _Debugger:Emulator_. (GP-6340) Removed `PcodeTraceDataAccess.intersectUnknown` in favor of `intersectViewKnown` with sutract.
|
||||
* _Emulator_. (GP-6134) Added `InstructionPrototype.hasNext2Dependency()`
|
||||
* _Emulator_. (GP-6134) Added `InstructionPrototype.hasNext2Dependency()`.
|
||||
|
||||
# Ghidra 12.0.1 Change History (January 2026)
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -27,7 +27,7 @@ import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraTestApplicationLayout;
|
||||
import ghidra.base.project.GhidraProject;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.GhidraApplicationConfiguration;
|
||||
import ghidra.framework.HeadlessGhidraApplicationConfiguration;
|
||||
import ghidra.framework.data.OpenMode;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.ProjectData;
|
||||
@@ -163,8 +163,8 @@ public class IsfServer extends Thread {
|
||||
public static void main(String[] args) throws FileNotFoundException, IOException {
|
||||
GhidraApplicationLayout layout =
|
||||
new GhidraTestApplicationLayout(new File(AbstractGTest.getTestDirectoryPath()));
|
||||
GhidraApplicationConfiguration config = new GhidraApplicationConfiguration();
|
||||
config.setShowSplashScreen(false);
|
||||
HeadlessGhidraApplicationConfiguration config =
|
||||
new HeadlessGhidraApplicationConfiguration();
|
||||
Application.initializeApplication(layout, config);
|
||||
|
||||
IsfServer server = new IsfServer(null, 54321);
|
||||
|
||||
@@ -73,6 +73,9 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceStac
|
||||
@Override
|
||||
public Address getValue() {
|
||||
TraceObjectValue entry = row.getAttributeEntry(attributeName);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
return entry.getValue() instanceof Address addr ? addr : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||
return;
|
||||
}
|
||||
try {
|
||||
info.trackTraceBreakpoint(c.a, tb, getMode(info.trace), false);
|
||||
info.trackTraceBreakpoint(c, tb, getMode(info.trace), false);
|
||||
}
|
||||
catch (TrackedTooSoonException e) {
|
||||
Msg.info(this,
|
||||
@@ -244,7 +244,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||
return;
|
||||
}
|
||||
try {
|
||||
info.trackTraceBreakpoint(c.a, tb, getMode(info.trace), true);
|
||||
info.trackTraceBreakpoint(c, tb, getMode(info.trace), true);
|
||||
}
|
||||
catch (TrackedTooSoonException e) {
|
||||
Msg.info(this,
|
||||
@@ -269,7 +269,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||
}
|
||||
else {
|
||||
try {
|
||||
info.trackTraceBreakpoint(c.a, tb, getMode(info.trace), false);
|
||||
info.trackTraceBreakpoint(c, tb, getMode(info.trace), false);
|
||||
}
|
||||
catch (TrackedTooSoonException e) {
|
||||
Msg.info(this, "Ignoring " + tb +
|
||||
@@ -482,7 +482,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||
|
||||
protected void reloadBreakpoints(ChangeCollector c) {
|
||||
forgetTraceInvalidBreakpoints(c.r);
|
||||
trackTraceBreakpoints(c.a);
|
||||
trackTraceBreakpoints(c);
|
||||
}
|
||||
|
||||
protected void forgetAllBreakpoints(RemoveCollector r) {
|
||||
@@ -531,7 +531,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
protected void trackTraceBreakpoints(AddCollector a) {
|
||||
protected void trackTraceBreakpoints(ChangeCollector c) {
|
||||
ControlMode mode = getMode(trace);
|
||||
if (!mode.useEmulatedBreakpoints() && target == null) {
|
||||
return;
|
||||
@@ -541,10 +541,10 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||
visible.addAll(trace.getBreakpointManager()
|
||||
.getBreakpointsIntersecting(Lifespan.at(snap), range));
|
||||
}
|
||||
trackTraceBreakpoints(a, visible, mode);
|
||||
trackTraceBreakpoints(c, visible, mode);
|
||||
}
|
||||
|
||||
protected void trackTraceBreakpoints(AddCollector a,
|
||||
protected void trackTraceBreakpoints(ChangeCollector c,
|
||||
Collection<TraceBreakpointLocation> breakpoints, ControlMode mode) {
|
||||
for (TraceBreakpointLocation tb : breakpoints) {
|
||||
try {
|
||||
@@ -553,7 +553,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||
* events that the manager punts to OBJECT_RESTORED. Thus, we have to set
|
||||
* forceUpdate here.
|
||||
*/
|
||||
trackTraceBreakpoint(a, tb, mode, true);
|
||||
trackTraceBreakpoint(c, tb, mode, true);
|
||||
}
|
||||
catch (TrackedTooSoonException e) {
|
||||
// This can still happen during reload (on OBJECT_RESTORED)
|
||||
@@ -579,37 +579,44 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||
new DefaultTraceLocation(trace, null, Lifespan.at(snap), minAddress));
|
||||
}
|
||||
|
||||
protected void trackTraceBreakpoint(AddCollector a, TraceBreakpointLocation tb,
|
||||
ControlMode mode,
|
||||
boolean forceUpdate) throws TrackedTooSoonException {
|
||||
protected void trackTraceBreakpoint(ChangeCollector c, TraceBreakpointLocation tb,
|
||||
ControlMode mode, boolean forceUpdate) throws TrackedTooSoonException {
|
||||
if (!mode.useEmulatedBreakpoints() &&
|
||||
(target == null || !target.isBreakpointValid(tb))) {
|
||||
return;
|
||||
}
|
||||
Address traceAddr = tb.getMinAddress(snap);
|
||||
if (traceAddr == null) {
|
||||
LogicalBreakpointInternal oldLb = logicalByBreakpoint.remove(tb);
|
||||
if (oldLb != null) {
|
||||
// Happens when existing location's range is deleted
|
||||
doRemoveFromLogicalBreakpoint(c.r, oldLb, tb);
|
||||
}
|
||||
return; // Will update via breakpointChanged when address is set
|
||||
}
|
||||
ProgramLocation progLoc = computeStaticLocation(tb);
|
||||
LogicalBreakpointInternal lb;
|
||||
if (progLoc != null) {
|
||||
InfoPerProgram progInfo = programInfos.get(progLoc.getProgram());
|
||||
lb = progInfo.getOrCreateLogicalBreakpointFor(a, progLoc.getByteAddress(), tb,
|
||||
lb = progInfo.getOrCreateLogicalBreakpointFor(c.a, progLoc.getByteAddress(), tb,
|
||||
snap);
|
||||
}
|
||||
else {
|
||||
lb = getOrCreateLogicalBreakpointFor(a, traceAddr, tb, snap);
|
||||
lb = getOrCreateLogicalBreakpointFor(c.a, traceAddr, tb, snap);
|
||||
}
|
||||
assert logicalByAddress.get(traceAddr).contains(lb);
|
||||
logicalByBreakpoint.put(tb, lb);
|
||||
LogicalBreakpointInternal oldLb = logicalByBreakpoint.put(tb, lb);
|
||||
if (oldLb != lb && oldLb != null) {
|
||||
// Happens when existing location's range changes
|
||||
doRemoveFromLogicalBreakpoint(c.r, oldLb, tb);
|
||||
}
|
||||
if (lb.trackBreakpoint(tb) || forceUpdate) {
|
||||
a.updated(lb);
|
||||
c.a.updated(lb);
|
||||
}
|
||||
}
|
||||
|
||||
protected LogicalBreakpointInternal removeFromLogicalBreakpoint(RemoveCollector r,
|
||||
TraceBreakpointLocation tb) {
|
||||
LogicalBreakpointInternal lb = logicalByBreakpoint.remove(tb);
|
||||
protected LogicalBreakpointInternal doRemoveFromLogicalBreakpoint(RemoveCollector r,
|
||||
LogicalBreakpointInternal lb, TraceBreakpointLocation tb) {
|
||||
if (lb == null || !lb.untrackBreakpoint(tb)) {
|
||||
return null;
|
||||
}
|
||||
@@ -625,6 +632,12 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||
return lb;
|
||||
}
|
||||
|
||||
protected LogicalBreakpointInternal removeFromLogicalBreakpoint(RemoveCollector r,
|
||||
TraceBreakpointLocation tb) {
|
||||
LogicalBreakpointInternal lb = logicalByBreakpoint.remove(tb);
|
||||
return doRemoveFromLogicalBreakpoint(r, lb, tb);
|
||||
}
|
||||
|
||||
protected void forgetTraceBreakpoint(RemoveCollector r, TraceBreakpointLocation tb) {
|
||||
LogicalBreakpointInternal lb = removeFromLogicalBreakpoint(r, tb);
|
||||
if (lb == null) {
|
||||
|
||||
@@ -159,6 +159,51 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
||||
cast.getNewValue());
|
||||
return new TraceChangeRecord<>(type, getSpace(life), iface, null, null);
|
||||
}
|
||||
if (rec.getEventType() == TraceEvents.VALUE_LIFESPAN_CHANGED) {
|
||||
if (object.isDeleted()) {
|
||||
return null;
|
||||
}
|
||||
TraceEvent<T, ?> type = getChangedType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
TraceChangeRecord<TraceObjectValue, Lifespan> cast =
|
||||
TraceEvents.VALUE_LIFESPAN_CHANGED.cast(rec);
|
||||
TraceObjectValue affected = cast.getAffectedObject();
|
||||
String key = affected.getEntryKey();
|
||||
if (!appliesToKey(key)) {
|
||||
return null;
|
||||
}
|
||||
assert affected.getParent() == object;
|
||||
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
|
||||
return null; // Object is not complete
|
||||
}
|
||||
emitExtraValueChanged(affected.getLifespan(), key, null, null);
|
||||
return new TraceChangeRecord<>(type, getSpace(life), iface, null, null);
|
||||
}
|
||||
if (rec.getEventType() == TraceEvents.VALUE_DELETED) {
|
||||
if (object.isDeleted()) {
|
||||
return null;
|
||||
}
|
||||
TraceEvent<T, ?> type = getChangedType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
TraceChangeRecord<TraceObjectValue, Void> cast =
|
||||
TraceEvents.VALUE_DELETED.cast(rec);
|
||||
TraceObjectValue affected = cast.getAffectedObject();
|
||||
String key = affected.getEntryKey();
|
||||
if (!appliesToKey(key)) {
|
||||
return null;
|
||||
}
|
||||
assert affected.getParent() == object;
|
||||
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
|
||||
return null; // Object is not complete
|
||||
}
|
||||
emitExtraValueChanged(affected.getLifespan(), key, cast.getOldValue(),
|
||||
cast.getNewValue());
|
||||
return new TraceChangeRecord<>(type, getSpace(life), iface, null, null);
|
||||
}
|
||||
if (rec.getEventType() == TraceEvents.OBJECT_DELETED) {
|
||||
return translateDeleted(life);
|
||||
}
|
||||
|
||||
@@ -285,48 +285,6 @@
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD valign="top" width="5%">Execute<BR>
|
||||
</TD>
|
||||
|
||||
<TD valign="top" width="15%">Launches the specified executable with given optional
|
||||
parameters.<BR>
|
||||
</TD>
|
||||
|
||||
<TD valign="top" width="12%">
|
||||
<OL style="margin-left: 15px;">
|
||||
<LI>"executable path"</LI>
|
||||
</OL>
|
||||
|
||||
<b>OR</b>
|
||||
<OL style="margin-left: 15px;">
|
||||
<LI>"executable path"</LI>
|
||||
<LI>"parameter list" (may be empty quotes)</LI>
|
||||
<LI>"display text" (may be empty quotes)</LI>
|
||||
</OL>
|
||||
</TD>
|
||||
|
||||
<TD valign="top" width="10%">
|
||||
<UL style="margin-left: 10px;">
|
||||
<LI>@execute</LI>
|
||||
</UL>
|
||||
</TD>
|
||||
|
||||
<TD valign="top" width="15%">
|
||||
<UL style="margin-left: 10px;">
|
||||
<LI>{@execute "C:\Program Files\Mozilla Firefox\firefox.exe"}</LI>
|
||||
|
||||
<LI>{@execute "C:\Program Files\Mozilla Firefox\firefox.exe"
|
||||
"http://my.website.com" "Opens a web browser to Website"}</LI>
|
||||
|
||||
<LI>{@execute "C:\Program Files\Mozilla Firefox\firefox.exe" "" "My display text"}</LI>
|
||||
|
||||
<LI>{@execute "C:\Path\To\Some\executable.exe" "arg1 arg2" ""}</LI>
|
||||
|
||||
</UL><FONT size="2">Note: quotes are required for this annotation</FONT>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD valign="top" width="5%"><I>Discovered Annotations</I><BR>
|
||||
</TD>
|
||||
|
||||
@@ -451,6 +451,13 @@
|
||||
<B>The GNU Demangler</B> adds the following analysis options:
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
<U><B>Timeout (seconds)</B></U> -
|
||||
The maximum amount of seconds to allow the native GNU Demangler process to
|
||||
attempt to demangle a mangled string before failing. Some inputs to the
|
||||
native GNU Demangler program have been shown to not terminate and consume excessive
|
||||
resources. This timeout protects against these inputs.
|
||||
</P>
|
||||
<P>
|
||||
<U><B>Use Deprecated Demangler</B></U> -
|
||||
By default, GCC symbols will be demangled using the most up-to-date demangler
|
||||
|
||||
@@ -528,7 +528,7 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
}
|
||||
sb.append(dt.getDisplayName());
|
||||
sb.append(", ");
|
||||
if (info.index < 0) {
|
||||
if (info.resultOrdinal < 0) {
|
||||
if (dt instanceof FunctionDefinition) {
|
||||
sb.append("return-type");
|
||||
}
|
||||
@@ -539,16 +539,16 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
else {
|
||||
if (dt instanceof FunctionDefinition) {
|
||||
sb.append("param-");
|
||||
sb.append(info.index);
|
||||
sb.append(info.resultOrdinal);
|
||||
}
|
||||
else if (dt instanceof Union) {
|
||||
sb.append("component-");
|
||||
sb.append(info.index);
|
||||
sb.append(info.resultOrdinal);
|
||||
}
|
||||
else if (dt instanceof Structure) {
|
||||
Structure resultStruct = (Structure) info.ht.get(info.id);
|
||||
sb.append("component-");
|
||||
sb.append(info.index);
|
||||
sb.append(info.resultOrdinal);
|
||||
if (!resultStruct.isPackingEnabled()) {
|
||||
sb.append(", offset-");
|
||||
sb.append("0x");
|
||||
@@ -556,8 +556,8 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
}
|
||||
}
|
||||
else {
|
||||
sb.append("index-"); // unknown use case
|
||||
sb.append(info.index);
|
||||
sb.append("resultOrdinal-"); // unknown use case
|
||||
sb.append(info.resultOrdinal);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1254,36 +1254,19 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
// Add each of the defined components back in.
|
||||
DataTypeComponent[] comps = sourceDt.getDefinedComponents();
|
||||
|
||||
// Component sequence must be flipped for components that share the same offset
|
||||
// (i.e., when zero-length components exist) to ensure that the insert at offset
|
||||
// does not alter the intended order.
|
||||
List<DataTypeComponent> compList = new ArrayList<>();
|
||||
int prevOffset = -1;
|
||||
int index = -1;
|
||||
for (DataTypeComponent dtc : comps) {
|
||||
int offset = dtc.getOffset();
|
||||
if (offset == prevOffset) {
|
||||
compList.add(index, dtc);
|
||||
}
|
||||
else {
|
||||
prevOffset = offset;
|
||||
index = compList.size();
|
||||
compList.add(dtc);
|
||||
}
|
||||
}
|
||||
comps = null; // prevent improper use
|
||||
|
||||
// Track dependency errors to avoid duplicate popups
|
||||
HashMap<Long, String> badIdDtMsgs = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < compList.size(); i++) {
|
||||
for (int i = 0; i < comps.length; i++) {
|
||||
|
||||
DataTypeComponent sourceComp = compList.get(i);
|
||||
DataTypeComponent sourceComp = comps[i];
|
||||
DataTypeComponent resultComp;
|
||||
|
||||
DataType sourceCompDt = sourceComp.getDataType();
|
||||
BitFieldDataType bfDt = null;
|
||||
String comment = sourceComp.getComment();
|
||||
DataType resultCompDt = null;
|
||||
boolean fixupRequired = false;
|
||||
|
||||
if (sourceComp.isBitFieldComponent()) {
|
||||
// NOTE: primitive type will be used if unable to resolve base type
|
||||
@@ -1307,13 +1290,7 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
// Not added so should be in result if it wasn't deleted there.
|
||||
resultCompDt = dtms[RESULT].getDataType(sourceComponentID);
|
||||
}
|
||||
if (resultCompDt == null) {
|
||||
// Not added/resolved yet
|
||||
// put an entry in the fixup list
|
||||
fixUpList.add(new FixUpInfo(sourceDtID, sourceComponentID,
|
||||
sourceComp.getOrdinal(), sourceComp, resolvedDataTypes));
|
||||
fixUpIDSet.add(sourceDtID);
|
||||
}
|
||||
fixupRequired = (resultCompDt == null);
|
||||
if (bfDt != null &&
|
||||
(resultCompDt == null || !BitFieldDataType.isValidBaseDataType(resultCompDt))) {
|
||||
// use primitive type as fixup placeholder
|
||||
@@ -1335,7 +1312,8 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
if (packed) {
|
||||
if (bfDt != null) {
|
||||
try {
|
||||
destStruct.addBitField(resultCompDt, bfDt.getDeclaredBitSize(),
|
||||
resultComp =
|
||||
destStruct.addBitField(resultCompDt, bfDt.getDeclaredBitSize(),
|
||||
sourceComp.getFieldName(), comment);
|
||||
}
|
||||
catch (InvalidDataTypeException e) {
|
||||
@@ -1345,7 +1323,8 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
comment = buildDataTypeFailureComment(sourceCompDt, e.getMessage(),
|
||||
sourceComp.getComment());
|
||||
try {
|
||||
destStruct.addBitField(primitiveBaseDt, bfDt.getDeclaredBitSize(),
|
||||
resultComp = destStruct.addBitField(primitiveBaseDt,
|
||||
bfDt.getDeclaredBitSize(),
|
||||
sourceComp.getFieldName(), comment);
|
||||
}
|
||||
catch (InvalidDataTypeException exc) {
|
||||
@@ -1356,13 +1335,15 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
else if (badMsg == null) {
|
||||
try {
|
||||
// If I have compDt, it should now be from result DTM.
|
||||
destStruct.add(resultCompDt, length, sourceComp.getFieldName(),
|
||||
resultComp =
|
||||
destStruct.add(resultCompDt, length, sourceComp.getFieldName(),
|
||||
comment);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
comment =
|
||||
buildDataTypeFailureComment(sourceCompDt, e.getMessage(), comment);
|
||||
destStruct.add(BadDataType.dataType, sourceComp.getLength(),
|
||||
resultComp =
|
||||
destStruct.add(BadDataType.dataType, sourceComp.getLength(),
|
||||
sourceComp.getFieldName(), comment);
|
||||
if (e.getCause() instanceof DataTypeDependencyException) {
|
||||
badIdDtMsgs.put(dtId, e.getMessage());
|
||||
@@ -1373,14 +1354,15 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
else {
|
||||
// Preserve non-fixup ordinal with bad placeholder
|
||||
comment = buildDataTypeFailureComment(sourceCompDt, badMsg, comment);
|
||||
destStruct.add(BadDataType.dataType, sourceComp.getLength(),
|
||||
resultComp = destStruct.add(BadDataType.dataType, sourceComp.getLength(),
|
||||
sourceComp.getFieldName(), badMsg + "; " + comment);
|
||||
}
|
||||
}
|
||||
else if (bfDt != null) {
|
||||
// non-packed bitfield
|
||||
try {
|
||||
destStruct.insertBitFieldAt(sourceComp.getOffset(), sourceComp.getLength(),
|
||||
resultComp = destStruct
|
||||
.insertBitFieldAt(sourceComp.getOffset(), sourceComp.getLength(),
|
||||
bfDt.getBitOffset(), resultCompDt, bfDt.getDeclaredBitSize(),
|
||||
sourceComp.getFieldName(), comment);
|
||||
}
|
||||
@@ -1391,7 +1373,8 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
comment = buildDataTypeFailureComment(sourceCompDt, e.getMessage(),
|
||||
sourceComp.getComment());
|
||||
try {
|
||||
destStruct.addBitField(primitiveBaseDt, bfDt.getDeclaredBitSize(),
|
||||
resultComp =
|
||||
destStruct.addBitField(primitiveBaseDt, bfDt.getDeclaredBitSize(),
|
||||
sourceComp.getFieldName(), comment);
|
||||
}
|
||||
catch (InvalidDataTypeException exc) {
|
||||
@@ -1404,9 +1387,9 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
if (badMsg == null) {
|
||||
try {
|
||||
// If not last component must constrain length to original component size
|
||||
if (i < compList.size() - 1) {
|
||||
if (i < (comps.length - 1)) {
|
||||
int offset = sourceComp.getOffset();
|
||||
DataTypeComponent nextDtc = compList.get(i + 1);
|
||||
DataTypeComponent nextDtc = comps[i + 1];
|
||||
int available = nextDtc.getOffset() - offset;
|
||||
if (length > available) {
|
||||
// The data type is too big, so adjust the component length to what will fit.
|
||||
@@ -1423,13 +1406,15 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
}
|
||||
}
|
||||
|
||||
destStruct.insertAtOffset(sourceComp.getOffset(), resultCompDt, length,
|
||||
resultComp = destStruct.insertAtOffset(sourceComp.getOffset(),
|
||||
resultCompDt, length,
|
||||
sourceComp.getFieldName(), comment);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
comment =
|
||||
buildDataTypeFailureComment(sourceCompDt, e.getMessage(), comment);
|
||||
destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType,
|
||||
resultComp = destStruct.insertAtOffset(sourceComp.getOffset(),
|
||||
BadDataType.dataType,
|
||||
length, sourceComp.getFieldName(), comment);
|
||||
|
||||
if (e.getCause() instanceof DataTypeDependencyException) {
|
||||
@@ -1441,7 +1426,8 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
else {
|
||||
// Preserve non-fixup ordinal with bad placeholder
|
||||
comment = buildDataTypeFailureComment(sourceCompDt, badMsg, comment);
|
||||
destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType,
|
||||
resultComp =
|
||||
destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType,
|
||||
sourceComp.getLength(), sourceComp.getFieldName(), comment);
|
||||
}
|
||||
}
|
||||
@@ -1450,15 +1436,22 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
// Add fixup placeholder to prevent the ordinal values and component sizes from
|
||||
// changing. Nothing we can do about packing which may be affected.
|
||||
// These should get fixed-up later.
|
||||
destStruct.add(BadDataType.dataType, sourceComp.getLength(),
|
||||
resultComp = destStruct.add(BadDataType.dataType, sourceComp.getLength(),
|
||||
sourceComp.getFieldName(), comment);
|
||||
}
|
||||
else {
|
||||
// Add fixup placeholder to prevent the ordinal values and component sizes from
|
||||
// changing. These should get fixed-up later.
|
||||
destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType,
|
||||
resultComp = destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType,
|
||||
sourceComp.getLength(), sourceComp.getFieldName(), comment);
|
||||
}
|
||||
|
||||
if (fixupRequired) {
|
||||
// Component datatype has not been added/resolved yet, put an entry in the fixup list
|
||||
fixUpList.add(new FixUpInfo(sourceDtID, sourceComponentID,
|
||||
resultComp.getOrdinal(), sourceComp, resolvedDataTypes));
|
||||
fixUpIDSet.add(sourceDtID);
|
||||
}
|
||||
}
|
||||
if (!packed) {
|
||||
adjustStructureSize(destStruct, sourceDt.getLength());
|
||||
@@ -2548,22 +2541,22 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
long lastChangeTime = fd.getLastChangeTime(); // Don't let the time change.
|
||||
try {
|
||||
if (dt != null) {
|
||||
if (info.index < 0) { // -1 for return type
|
||||
if (info.resultOrdinal < 0) { // -1 for return type
|
||||
fd.setReturnType(dt);
|
||||
}
|
||||
else {
|
||||
ParameterDefinition[] args = fd.getArguments();
|
||||
args[info.index].setDataType(dt);
|
||||
args[info.resultOrdinal].setDataType(dt);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (info.index < 0) { // -1 for return type
|
||||
if (info.resultOrdinal < 0) { // -1 for return type
|
||||
// nowhere to set error comment
|
||||
}
|
||||
else {
|
||||
ParameterDefinition[] args = fd.getArguments();
|
||||
ParameterDefinition arg = args[info.index];
|
||||
ParameterDefinition arg = args[info.resultOrdinal];
|
||||
String comment =
|
||||
buildDataTypeFailureComment(info.componentDataType, null, arg.getComment());
|
||||
arg.setComment(comment);
|
||||
@@ -2583,15 +2576,15 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
* @return false if component not found, else true
|
||||
*/
|
||||
private boolean fixUpPackedStructureComponent(FixUpInfo info, Structure struct, DataType dt) {
|
||||
int ordinal = info.index;
|
||||
int ordinal = info.resultOrdinal;
|
||||
|
||||
DataTypeComponent dtc = null;
|
||||
DataTypeComponent dtc;
|
||||
if (ordinal >= 0 || ordinal < struct.getNumComponents()) {
|
||||
dtc = struct.getComponent(ordinal);
|
||||
}
|
||||
|
||||
if (dtc == null) {
|
||||
throw new AssertException("Expected bad datatype placeholder");
|
||||
else {
|
||||
throw new AssertException(
|
||||
"Expected fixup component at ordinal " + ordinal + " in " + struct.getPathName());
|
||||
}
|
||||
|
||||
long lastChangeTime = struct.getLastChangeTime(); // Don't let the time change.
|
||||
@@ -2680,12 +2673,16 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
private boolean fixUpNonPackedStructureComponent(FixUpInfo info, Structure struct,
|
||||
DataType dt) {
|
||||
|
||||
int ordinal = info.index;
|
||||
int ordinal = info.resultOrdinal;
|
||||
|
||||
DataTypeComponent dtc = null;
|
||||
DataTypeComponent dtc;
|
||||
if (ordinal >= 0 || ordinal < struct.getNumComponents()) {
|
||||
dtc = struct.getComponent(ordinal);
|
||||
}
|
||||
else {
|
||||
throw new AssertException(
|
||||
"Expected fixup component at ordinal " + ordinal + " in " + struct.getPathName());
|
||||
}
|
||||
|
||||
long lastChangeTime = struct.getLastChangeTime(); // Don't let the time change.
|
||||
try {
|
||||
@@ -2740,35 +2737,13 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
}
|
||||
|
||||
// handle non-bitfield component fixup
|
||||
int offset = dtc.getOffset();
|
||||
int dtcLength = dtc.getLength();
|
||||
int length = dt.getLength();
|
||||
if (length <= 0) {
|
||||
length = dtcLength;
|
||||
}
|
||||
int bytesNeeded = length - dtcLength;
|
||||
if (bytesNeeded > 0) {
|
||||
int nextOffset = offset + dtcLength;
|
||||
DataTypeComponent nextDefinedDtc =
|
||||
struct.getDefinedComponentAtOrAfterOffset(nextOffset);
|
||||
if (nextDefinedDtc != null) {
|
||||
int bytesAvailable = nextDefinedDtc.getOffset() - nextOffset;
|
||||
if (bytesAvailable < bytesNeeded) {
|
||||
// The data type is too big, so adjust the component length to what will fit.
|
||||
length = dtcLength + bytesAvailable;
|
||||
// Output a warning indicating the structure has a data type that doesn't fit.
|
||||
String message = "Structure Merge: Not enough undefined bytes to fit " +
|
||||
dt.getPathName() + " in structure " + struct.getPathName() +
|
||||
" at offset 0x" + Integer.toHexString(offset) + "." +
|
||||
"\nIt needs " + (bytesNeeded - bytesAvailable) +
|
||||
" more byte(s) to be able to fit.";
|
||||
Msg.warn(this, message);
|
||||
}
|
||||
}
|
||||
length = dtc.getLength();
|
||||
}
|
||||
try {
|
||||
struct.replaceAtOffset(offset, dt, length, dtc.getFieldName(),
|
||||
dtc.getComment());
|
||||
struct.replace(ordinal, dt, length, dtc.getFieldName(), dtc.getComment());
|
||||
return true;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
displayError(struct, e);
|
||||
@@ -2804,7 +2779,7 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
|
||||
String loc;
|
||||
if (struct.isPackingEnabled()) {
|
||||
loc = "ordinal " + info.index;
|
||||
loc = "ordinal " + info.resultOrdinal;
|
||||
}
|
||||
else {
|
||||
loc = "offset 0x" + Integer.toHexString(info.offset);
|
||||
@@ -2818,7 +2793,7 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
|
||||
DataType compDt = resolve(info.compID, info.getDataTypeManager(), info.ht);
|
||||
|
||||
int ordinal = info.index;
|
||||
int ordinal = info.resultOrdinal;
|
||||
|
||||
DataTypeComponent dtc = null;
|
||||
if (ordinal >= 0 && ordinal <= union.getNumComponents()) {
|
||||
@@ -2889,7 +2864,7 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
}
|
||||
Msg.warn(this,
|
||||
"Union Merge: Failed to resolve data type '" + info.componentDataType.getName() +
|
||||
"' at ordinal " + info.index + " in " + union.getPathName());
|
||||
"' at ordinal " + info.resultOrdinal + " in " + union.getPathName());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2913,7 +2888,7 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
FixUpInfo info = fixUpList.get(i); // assume info applies to union
|
||||
if (!fixUpUnionComponent(union, info)) {
|
||||
Msg.warn(this, "Union Merge: Failed to apply data type at ordinal " +
|
||||
info.index + " in " + union.getPathName());
|
||||
info.resultOrdinal + " in " + union.getPathName());
|
||||
unresolvedFixups.add(info);
|
||||
}
|
||||
}
|
||||
@@ -3447,13 +3422,18 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
*/
|
||||
private class FixUpInfo implements Comparable<FixUpInfo> {
|
||||
|
||||
final long id;
|
||||
final long compID;
|
||||
final DataType componentDataType;
|
||||
final int index;
|
||||
final long id; // Souce datatype ID
|
||||
final long compID; // Source datatype ID for component
|
||||
final DataType componentDataType; // Source component datatype
|
||||
|
||||
// Result ordinal when id represents a container such as a structure/union or
|
||||
// function definition. In such cases, 'compID' corresponds to the component datatype.
|
||||
// A -1 may be used when not applicable.
|
||||
final int resultOrdinal;
|
||||
|
||||
final MyIdentityHashMap<Long, DataType> ht;
|
||||
|
||||
// component offset - for display/logging use only
|
||||
// source component offset - for display/logging use only
|
||||
// only meaningful for non-packed structure
|
||||
// may not be unique (e.g., bitfields, 0-length components)
|
||||
int offset = -1;
|
||||
@@ -3468,16 +3448,16 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
* @param id source data type ID needing to be fixed up
|
||||
* @param compID source datatype ID of either param/component or bitfield base type
|
||||
* @param componentDataType source component/dependency datatype
|
||||
* @param index offset into non-packed structure, or ordinal into union or packed
|
||||
* structure; or parameter/return ordinal; for other data types index is not used (specify -1).
|
||||
* @param resultOrdinal the result ordinal into a structure/union; or
|
||||
* parameter/return ordinal; or -1 for other cases where index is not used
|
||||
* @param resolvedDataTypes hashtable used for resolving the data type
|
||||
*/
|
||||
FixUpInfo(long id, long compID, DataType componentDataType, int index,
|
||||
FixUpInfo(long id, long compID, DataType componentDataType, int resultOrdinal,
|
||||
MyIdentityHashMap<Long, DataType> resolvedDataTypes) {
|
||||
this.id = id;
|
||||
this.compID = compID;
|
||||
this.componentDataType = componentDataType;
|
||||
this.index = index;
|
||||
this.resultOrdinal = resultOrdinal;
|
||||
this.ht = resolvedDataTypes;
|
||||
|
||||
if (componentDataType instanceof BitFieldDataType) {
|
||||
@@ -3491,13 +3471,13 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
* or components were resolved.
|
||||
* @param id id of data type needing to be fixed up
|
||||
* @param compID datatype ID of either param/component or bitfield base type
|
||||
* @param destOrdinal component ordinal within destination composite
|
||||
* @param resultOrdinal component ordinal within result composite
|
||||
* @param sourceDtc associated composite datatype component
|
||||
* @param resolvedDataTypes hashtable used for resolving the data type
|
||||
*/
|
||||
FixUpInfo(long id, long compID, int destOrdinal, DataTypeComponent sourceDtc,
|
||||
FixUpInfo(long id, long compID, int resultOrdinal, DataTypeComponent sourceDtc,
|
||||
MyIdentityHashMap<Long, DataType> resolvedDataTypes) {
|
||||
this(id, compID, getDataType(sourceDtc), destOrdinal, resolvedDataTypes);
|
||||
this(id, compID, getDataType(sourceDtc), resultOrdinal, resolvedDataTypes);
|
||||
offset = sourceDtc.getOffset();
|
||||
if (sourceDtc.isBitFieldComponent()) {
|
||||
BitFieldDataType bfDt = (BitFieldDataType) sourceDtc.getDataType();
|
||||
@@ -3516,11 +3496,11 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
|
||||
@Override
|
||||
public int compareTo(FixUpInfo o) {
|
||||
// Compare such that items are grouped by id and sort such that the greatest index
|
||||
// Compare such that items are grouped by id and sort such that the greatest resultOrdinal
|
||||
// is first within that group.
|
||||
long c = id - o.id;
|
||||
if (c == 0) {
|
||||
c = Integer.toUnsignedLong(o.index) - Integer.toUnsignedLong(index);
|
||||
c = Integer.toUnsignedLong(o.resultOrdinal) - Integer.toUnsignedLong(resultOrdinal);
|
||||
}
|
||||
if (c == 0) {
|
||||
return 0;
|
||||
@@ -3546,7 +3526,8 @@ public class DataTypeMergeManager implements MergeResolver {
|
||||
}
|
||||
return "\n" + "ID = " + Long.toHexString(id) + ",\ndt = " + dtm.getDataType(id) +
|
||||
",\ncomponent ID = " + Long.toHexString(compID) + ",\ncomponent dt = " +
|
||||
dtm.getDataType(compID) + ",\noffset/index = " + index + ",\n" + bitInfo + "ht = " +
|
||||
dtm.getDataType(compID) + ",\nresultOrdinal = " + resultOrdinal + ",\n" + bitInfo +
|
||||
"ht = " +
|
||||
htStr + "\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,28 @@ import util.CollectionUtils;
|
||||
* Repeatable comment for the code unit, any repeatable comments for the code units that this code
|
||||
* unit has references to, and possibly a comment indicating the data at a code unit that is
|
||||
* referenced by this code unit.
|
||||
*
|
||||
* This section describes the various EOL comment types that may appear. The user can
|
||||
* toggle which types are enabled. The comments are displayed in the order listed below.
|
||||
*
|
||||
* EOL Types:
|
||||
*
|
||||
* - EOL - user end of line comment
|
||||
*
|
||||
* - Repeatable - user repeatable source comment *at the code unit*
|
||||
* - Ref Repeatable- for every reference *from a code unit*, show the target:
|
||||
* - address repeatable,
|
||||
* - function repeatable,
|
||||
* - code unit repeatable
|
||||
*
|
||||
* - Auto - fabricated reference preview:
|
||||
* - function,
|
||||
* - indirect data pointer,
|
||||
* - direct data access preview
|
||||
* *depending on the options, this typically do not appear when
|
||||
* repeatable comments exist
|
||||
*
|
||||
* - Offcut - comments at addresses inside of a code unit
|
||||
*/
|
||||
public class EolComments {
|
||||
|
||||
@@ -67,6 +89,7 @@ public class EolComments {
|
||||
this.operandsShowReferences = operandsShowReferences;
|
||||
this.maxDisplayComments = maxDisplayComments;
|
||||
this.extraCommentsOption = extraCommentsOption;
|
||||
|
||||
loadComments();
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +47,10 @@ public interface OptionChooser {
|
||||
* Gets the {@link Loader} arguments associated with this {@link OptionChooser}
|
||||
*
|
||||
* @return The {@link Loader} arguments associated with this {@link OptionChooser}
|
||||
* @throws UnsupportedOperationException if a subclass has not implemented this method
|
||||
* @deprecated Use {@link ProgramLoader.Builder#loaderArgs(List)} instead
|
||||
*/
|
||||
@Deprecated(since = "12.0", forRemoval = true)
|
||||
public default List<Pair<String, String>> getArgs() {
|
||||
throw new UnsupportedOperationException();
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ package ghidra.app.util.viewer.field;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import docking.widgets.fieldpanel.field.*;
|
||||
import docking.widgets.fieldpanel.support.*;
|
||||
@@ -26,6 +26,7 @@ import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.app.util.*;
|
||||
import ghidra.app.util.viewer.field.ListingColors.CommentColors;
|
||||
import ghidra.app.util.viewer.format.FieldFormatModel;
|
||||
import ghidra.app.util.viewer.listingpanel.ListingModel;
|
||||
import ghidra.app.util.viewer.options.OptionsGui;
|
||||
import ghidra.app.util.viewer.proxy.ProxyObj;
|
||||
import ghidra.framework.options.*;
|
||||
@@ -33,8 +34,10 @@ import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.util.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.StringUtilities;
|
||||
import ghidra.util.bean.field.AnnotatedTextFieldElement;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import utility.function.Dummy;
|
||||
|
||||
/**
|
||||
* Generates End of line comment Fields.
|
||||
@@ -236,6 +239,7 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
if (!enabled || !(obj instanceof CodeUnit)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CodeUnit cu = (CodeUnit) obj;
|
||||
Program program = cu.getProgram();
|
||||
|
||||
@@ -244,14 +248,8 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
// comments if open. If this was allowed, then the comment would appear
|
||||
// on the outside data container and on the 1st internal member
|
||||
//
|
||||
if (cu instanceof Data) {
|
||||
Data data = (Data) cu;
|
||||
if (data.getNumComponents() > 0) {
|
||||
boolean isOpen = proxy.getListingLayoutModel().isOpen((Data) proxy.getObject());
|
||||
if (isOpen) {
|
||||
return null; // avoid double showing
|
||||
}
|
||||
}
|
||||
if (isOpenData(cu, proxy)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EolComments comments = new EolComments(cu, codeUnitFormatOptions.followReferencedPointers(),
|
||||
@@ -261,25 +259,59 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
List<FieldElement> elementList = new ArrayList<>();
|
||||
AttributedString prefix = createPrefix(CommentStyle.EOL);
|
||||
List<String> eols = comments.getEOLComments();
|
||||
List<FieldElement> eolElements = convertToFieldElements(program, eols, prefix, 0);
|
||||
List<FieldElement> eolElements = convertToFieldElements(program, eols, prefix, 0, true);
|
||||
elementList.addAll(eolElements);
|
||||
|
||||
/*
|
||||
This section describes the various EOL comment types that may appear. The user can
|
||||
toggle which types are enabled. The comments are displayed in the order listed below.
|
||||
|
||||
EOL Types:
|
||||
|
||||
- EOL - user end of line comment
|
||||
|
||||
- Repeatable - user repeatable source comment *at the code unit*
|
||||
- Ref Repeatable- for every reference *from a code unit*, show the target:
|
||||
- address repeatable,
|
||||
- function repeatable,
|
||||
- code unit repeatable
|
||||
|
||||
- Auto - fabricated reference preview:
|
||||
- function,
|
||||
- indirect data pointer,
|
||||
- direct data access preview
|
||||
*depending on the options, this typically do not appear when
|
||||
repeatable comments exist
|
||||
|
||||
- Offcut - comments at addresses inside of a code unit
|
||||
*/
|
||||
|
||||
if (comments.isShowingRepeatables()) {
|
||||
prefix = createPrefix(CommentStyle.REPEATABLE);
|
||||
int row = getNextRow(elementList);
|
||||
List<String> repeatables = comments.getRepeatableComments();
|
||||
List<FieldElement> elements = convertToFieldElements(program, repeatables, prefix, row);
|
||||
List<FieldElement> elements =
|
||||
convertToFieldElements(program, repeatables, prefix, row, true);
|
||||
elementList.addAll(elements);
|
||||
}
|
||||
|
||||
if (comments.isShowingRefRepeatables()) {
|
||||
prefix = createPrefix(CommentStyle.REF_REPEATABLE);
|
||||
|
||||
AttributedString refPrefix = createPrefix(CommentStyle.REF_REPEATABLE);
|
||||
List<RefRepeatComment> refRepeatables = comments.getReferencedRepeatableComments();
|
||||
for (RefRepeatComment comment : refRepeatables) {
|
||||
|
||||
int row = getNextRow(elementList);
|
||||
String[] lines = comment.getCommentLines();
|
||||
Address refAddress = comment.getAddress();
|
||||
List<String> linesList = Arrays.asList(lines);
|
||||
|
||||
Consumer<List<FieldElement>> decorator = elements -> {
|
||||
prependRefAddress(program, refPrefix, refAddress, elements);
|
||||
};
|
||||
|
||||
List<FieldElement> elements =
|
||||
convertToRefFieldElements(lines, program, prefix, comment.getAddress(), row);
|
||||
convertToFieldElements(program, linesList, decorator, prefix, row, true);
|
||||
elementList.addAll(elements);
|
||||
}
|
||||
}
|
||||
@@ -288,7 +320,11 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
prefix = createPrefix(CommentStyle.AUTO);
|
||||
int row = getNextRow(elementList);
|
||||
List<String> autos = comments.getAutomaticComment();
|
||||
List<FieldElement> elements = convertToFieldElements(program, autos, prefix, row);
|
||||
|
||||
// Note: we pass 'false' for allowing annotations so that the user will see the raw data
|
||||
// and not an interpreted annotation.
|
||||
List<FieldElement> elements =
|
||||
convertToFieldElements(program, autos, prefix, row, false);
|
||||
elementList.addAll(elements);
|
||||
}
|
||||
|
||||
@@ -296,7 +332,8 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
prefix = createPrefix(CommentStyle.OFFCUT);
|
||||
int row = getNextRow(elementList);
|
||||
List<String> offcuts = comments.getOffcutEolComments();
|
||||
List<FieldElement> elements = convertToFieldElements(program, offcuts, prefix, row);
|
||||
List<FieldElement> elements =
|
||||
convertToFieldElements(program, offcuts, prefix, row, true);
|
||||
elementList.addAll(elements);
|
||||
}
|
||||
|
||||
@@ -307,6 +344,18 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
maxDisplayLines, hlProvider);
|
||||
}
|
||||
|
||||
private boolean isOpenData(CodeUnit cu, ProxyObj<?> proxy) {
|
||||
if (cu instanceof Data data) {
|
||||
if (data.getNumComponents() > 0) {
|
||||
ListingModel listingModel = proxy.getListingLayoutModel();
|
||||
if (listingModel.isOpen(data)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private AttributedString createPrefix(CommentStyle commentStyle) {
|
||||
if (commentStyle == CommentStyle.EOL) {
|
||||
return new AttributedString(SEMICOLON_PREFIX, CommentColors.EOL, getMetrics(style),
|
||||
@@ -349,7 +398,15 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
}
|
||||
|
||||
private List<FieldElement> convertToFieldElements(Program program, List<String> comments,
|
||||
AttributedString prefix, int row) {
|
||||
AttributedString prefix, int row, boolean allowAnnotations) {
|
||||
|
||||
Consumer<List<FieldElement>> decorator = Dummy.consumer(); // no decorations by default
|
||||
return convertToFieldElements(program, comments, decorator, prefix, row, allowAnnotations);
|
||||
}
|
||||
|
||||
private List<FieldElement> convertToFieldElements(Program program, List<String> comments,
|
||||
Consumer<List<FieldElement>> decorator, AttributedString prefix, int row,
|
||||
boolean allowAnnotations) {
|
||||
|
||||
List<FieldElement> fieldElements = new ArrayList<>();
|
||||
if (comments.isEmpty()) {
|
||||
@@ -357,11 +414,15 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
}
|
||||
|
||||
for (int commentRow = 0; commentRow < comments.size(); commentRow++) {
|
||||
int offsetRow = row + commentRow;
|
||||
fieldElements.add(CommentUtils.parseTextForAnnotations(comments.get(commentRow),
|
||||
program, prefix, offsetRow));
|
||||
int encodedRow = row + commentRow;
|
||||
String commentText = comments.get(commentRow);
|
||||
FieldElement element =
|
||||
createCommentField(commentText, prefix, program, encodedRow, allowAnnotations);
|
||||
fieldElements.add(element);
|
||||
}
|
||||
|
||||
decorator.accept(fieldElements);
|
||||
|
||||
if (isWordWrap) {
|
||||
int lineWidth = showSemicolon ? width - prefix.getStringWidth() : width;
|
||||
fieldElements = FieldUtils.wrap(fieldElements, lineWidth);
|
||||
@@ -381,57 +442,43 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
return fieldElements;
|
||||
}
|
||||
|
||||
private List<FieldElement> convertToRefFieldElements(String[] comments, Program program,
|
||||
AttributedString currentPrefixString, Address refAddress, int nextRow) {
|
||||
private FieldElement createCommentField(String commentText, AttributedString prototype,
|
||||
Program program, int row, boolean allowAnnotations) {
|
||||
|
||||
int numCommentLines = comments.length;
|
||||
List<FieldElement> fieldElements = new ArrayList<>();
|
||||
if (numCommentLines == 0) {
|
||||
return fieldElements;
|
||||
}
|
||||
for (int rowIndex = 0; rowIndex < numCommentLines; rowIndex++) {
|
||||
int encodedRow = nextRow + rowIndex;
|
||||
fieldElements.add(CommentUtils.parseTextForAnnotations(comments[rowIndex], program,
|
||||
currentPrefixString, encodedRow));
|
||||
}
|
||||
if (prependRefAddress) {
|
||||
FieldElement commentElement = fieldElements.get(0);
|
||||
// Address
|
||||
String refAddrComment = "{@address " + refAddress.toString() + "}";
|
||||
RowColLocation startRowCol = commentElement.getDataLocationForCharacterIndex(0);
|
||||
int encodedRow = startRowCol.row();
|
||||
int encodedCol = startRowCol.col();
|
||||
Annotation annotation = new Annotation(refAddrComment, program);
|
||||
FieldElement addressElement =
|
||||
new AnnotatedTextFieldElement(annotation, currentPrefixString, program, encodedRow,
|
||||
encodedCol);
|
||||
|
||||
// Space character
|
||||
AttributedString spaceStr = new AttributedString(" ", currentPrefixString.getColor(0),
|
||||
currentPrefixString.getFontMetrics(0), false, null);
|
||||
FieldElement spacerElement = new TextFieldElement(spaceStr, encodedRow, encodedCol);
|
||||
fieldElements.add(new CompositeFieldElement(
|
||||
new FieldElement[] { addressElement, spacerElement, commentElement }));
|
||||
if (allowAnnotations) {
|
||||
return CommentUtils.parseTextForAnnotations(commentText, program, prototype, row);
|
||||
}
|
||||
|
||||
if (isWordWrap) {
|
||||
int lineWidth = showSemicolon ? width - currentPrefixString.getStringWidth() : width;
|
||||
fieldElements = FieldUtils.wrap(fieldElements, lineWidth);
|
||||
String text = StringUtilities.convertTabsToSpaces(commentText);
|
||||
AttributedString as = new AttributedString(text, prototype.getColor(0),
|
||||
prototype.getFontMetrics(0), false, null);
|
||||
return new TextFieldElement(as, row, 0);
|
||||
}
|
||||
|
||||
private void prependRefAddress(Program program, AttributedString prefix, Address refAddress,
|
||||
List<FieldElement> fieldElements) {
|
||||
|
||||
if (!prependRefAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (showSemicolon) {
|
||||
for (int i = 0; i < fieldElements.size(); i++) {
|
||||
RowColLocation startRowCol =
|
||||
fieldElements.get(i).getDataLocationForCharacterIndex(0);
|
||||
int encodedRow = startRowCol.row();
|
||||
int encodedCol = startRowCol.col();
|
||||
FieldElement prefixFieldElement =
|
||||
new TextFieldElement(currentPrefixString, encodedRow, encodedCol);
|
||||
fieldElements.set(i, new CompositeFieldElement(
|
||||
new FieldElement[] { prefixFieldElement, fieldElements.get(i) }));
|
||||
}
|
||||
}
|
||||
return fieldElements;
|
||||
FieldElement commentElement = fieldElements.get(0);
|
||||
|
||||
// Address
|
||||
String refAddrComment = "{@address " + refAddress.toString() + "}";
|
||||
RowColLocation startRowCol = commentElement.getDataLocationForCharacterIndex(0);
|
||||
int encodedRow = startRowCol.row();
|
||||
int encodedCol = startRowCol.col();
|
||||
Annotation annotation = new Annotation(refAddrComment, program);
|
||||
FieldElement addressElement =
|
||||
new AnnotatedTextFieldElement(annotation, prefix, program, encodedRow, encodedCol);
|
||||
|
||||
// Space character
|
||||
AttributedString spaceStr = new AttributedString(" ", prefix.getColor(0),
|
||||
prefix.getFontMetrics(0), false, null);
|
||||
FieldElement spacerElement = new TextFieldElement(spaceStr, encodedRow, encodedCol);
|
||||
fieldElements.set(0, new CompositeFieldElement(
|
||||
new FieldElement[] { addressElement, spacerElement, commentElement }));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.viewer.field;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import docking.widgets.fieldpanel.field.AttributedString;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class ExecutableTaskStringHandler implements AnnotatedStringHandler {
|
||||
private static final String INVALID_SYMBOL_TEXT =
|
||||
"@execute annotation must have an executable name";
|
||||
private static final String[] SUPPORTED_ANNOTATIONS = { "execute" };
|
||||
|
||||
@Override
|
||||
public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
|
||||
Program program) throws AnnotationException {
|
||||
if (text.length <= 1) {
|
||||
throw new AnnotationException(INVALID_SYMBOL_TEXT);
|
||||
}
|
||||
|
||||
String displayText = getDisplayText(text);
|
||||
if (displayText == null) {
|
||||
// some kind of error
|
||||
throw new AnnotationException(INVALID_SYMBOL_TEXT);
|
||||
}
|
||||
|
||||
return new AttributedString(displayText, prototypeString.getColor(0),
|
||||
prototypeString.getFontMetrics(0), true, prototypeString.getColor(0));
|
||||
}
|
||||
|
||||
private String getDisplayText(String[] text) {
|
||||
//
|
||||
// We currently support two modes of: 3 parameters or 1. The user can leave off the
|
||||
// executable's parameter and display string OR they can have all three.
|
||||
//
|
||||
if (text.length == 4) {
|
||||
return text[3]; // 4 items means they have display text
|
||||
}
|
||||
else if (text.length != 2) {
|
||||
throw new AnnotationException(
|
||||
"Invalid number of inputs - " + (text.length - 1) + " found - 1 or 3 required");
|
||||
}
|
||||
|
||||
// otherwise, no display text, just use the executable name
|
||||
String programInfo = text[1];
|
||||
return getDisplayTextForFilePathOrName(programInfo);
|
||||
}
|
||||
|
||||
private String getDisplayTextForFilePathOrName(String fileString) {
|
||||
File file = new File(fileString);
|
||||
if (file.isAbsolute() && file.exists()) {
|
||||
return file.getName();
|
||||
}
|
||||
return fileString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayString() {
|
||||
return "Execute";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrototypeString() {
|
||||
return "{@execute \"executable_path_and_name\" \"arg1 arg2\" \"Display Text\"}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedAnnotations() {
|
||||
return SUPPORTED_ANNOTATIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable,
|
||||
ServiceProvider serviceProvider) {
|
||||
|
||||
String executableName = annotationParts[1];
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(executableName);
|
||||
|
||||
if (annotationParts.length > 2) {
|
||||
String commandParameterString = annotationParts[2];
|
||||
StringTokenizer tokenizer = new StringTokenizer(commandParameterString, " ");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
command.add(tokenizer.nextToken());
|
||||
}
|
||||
}
|
||||
|
||||
new ProcessThread(command).start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
private static class ProcessThread extends Thread {
|
||||
|
||||
private final List<String> command;
|
||||
|
||||
ProcessThread(List<String> command) {
|
||||
super("Process Runner - " + command.get(0));
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||
processBuilder = processBuilder.redirectErrorStream(true);
|
||||
|
||||
IOThread ioThread = null;
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
int exitValue = 1;
|
||||
InputStream inputStream = null;
|
||||
Process process = null;
|
||||
String executableName = command.get(0);
|
||||
try {
|
||||
Msg.info(this, "Launching process: " + executableName);
|
||||
process = processBuilder.start();
|
||||
inputStream = process.getInputStream();
|
||||
ioThread = new IOThread(buffer, inputStream);
|
||||
ioThread.start();
|
||||
exitValue = process.waitFor();
|
||||
ioThread.join();
|
||||
inputStream.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(this, null, "Error Launching Executable",
|
||||
"Unexpected exception trying to launch process: " + executableName, e);
|
||||
|
||||
}
|
||||
finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore; we tried
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exitValue != 0) {
|
||||
Msg.warn(this, "Process \"" + executableName + "\" exited abnormally with value: " +
|
||||
exitValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class IOThread extends Thread {
|
||||
private BufferedReader shellOutput;
|
||||
private StringBuilder buffer;
|
||||
|
||||
IOThread(StringBuilder buffer, InputStream input) {
|
||||
super("IO Thread - Executable Annotation Task");
|
||||
this.buffer = buffer;
|
||||
shellOutput = new BufferedReader(new InputStreamReader(input));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String line = null;
|
||||
try {
|
||||
while ((line = shellOutput.readLine()) != null) {
|
||||
buffer.append(line).append('\n');
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Exception reading output for executable annotation", e);
|
||||
buffer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrototypeString(String displayText) {
|
||||
return "{@execute " + displayText.trim() + "}";
|
||||
}
|
||||
}
|
||||
@@ -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,6 +15,8 @@
|
||||
*/
|
||||
package ghidra.framework;
|
||||
|
||||
import java.awt.GraphicsEnvironment;
|
||||
|
||||
import docking.DockingErrorDisplay;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.framework.ApplicationInformationDisplayFactory;
|
||||
@@ -37,6 +39,16 @@ public class GhidraApplicationConfiguration extends HeadlessGhidraApplicationCon
|
||||
private static final String USER_AGREEMENT_PROPERTY_NAME = "USER_AGREEMENT";
|
||||
private boolean showSplashScreen = true;
|
||||
|
||||
public GhidraApplicationConfiguration() {
|
||||
if (GraphicsEnvironment.isHeadless()) {
|
||||
System.err.println(
|
||||
"ERROR: Unable to launch Ghidra GUI application in headless environment.");
|
||||
System.err.println(
|
||||
"If launching from a remote SSH shell, you must have an X11 compatible client with X11 forwarding enabled.");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHeadless() {
|
||||
return false;
|
||||
|
||||
@@ -866,6 +866,18 @@ public class ProgramBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a non-null-terminated ascii string at the given address
|
||||
* @param address the address
|
||||
* @param string the string
|
||||
* @return the new data
|
||||
* @throws Exception if there is an exception
|
||||
*/
|
||||
public Data createString(String address, String string) throws Exception {
|
||||
return createString(address, string, StandardCharsets.US_ASCII, false,
|
||||
StringDataType.dataType);
|
||||
}
|
||||
|
||||
public Data createString(String address, String string, Charset charset, boolean nullTerminate,
|
||||
DataType dataType) throws Exception {
|
||||
if (nullTerminate) {
|
||||
|
||||
@@ -115,6 +115,8 @@ public class DataTypeMerge8Test extends AbstractDataTypeMergeTest {
|
||||
// choose MY for Bar conflict
|
||||
chooseOption(DataTypeMergeManager.OPTION_MY);
|
||||
|
||||
pressButtonByName(waitForWindow("Structure Update Failed"), "OK"); // expected dependency error on ABC
|
||||
|
||||
waitForCompletion();
|
||||
|
||||
FrontEndPlugin frontEndPlugin = getPlugin(frontEndTool, FrontEndPlugin.class);
|
||||
@@ -122,8 +124,9 @@ public class DataTypeMerge8Test extends AbstractDataTypeMergeTest {
|
||||
JLabel label = (JLabel) TestUtils.getInstanceField("label", logPanel);
|
||||
String statusText = label.getText();
|
||||
String expectedText =
|
||||
"Structure Merge: Not enough undefined bytes to fit /XYZ in structure " +
|
||||
"/MISC/ABC at offset 0x4. It needs 3 more byte(s) to be able to fit.";
|
||||
"Structure Update Failed: Some of your changes to ABC cannot be merged. " +
|
||||
"Problem: Not enough undefined bytes to fit /XYZ in structure /MISC/ABC at " +
|
||||
"offset 0x4. It needs 3 more byte(s) to be able to fit.";
|
||||
assertTrue("Wrong status text: " + statusText, statusText.contains(expectedText));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,4 +367,85 @@ public class DataTypeMergeFixupTest extends AbstractDataTypeMergeTest {
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonPackedZeroLengthComponentFixup() throws Exception {
|
||||
|
||||
// Goal is to fixup zero-length component at end of structure where its ordinal will
|
||||
// be revised during the merge processing
|
||||
|
||||
final CategoryPath rootPath = new CategoryPath("/");
|
||||
|
||||
mtf.initialize("notepad", new OriginalProgramModifierListener() {
|
||||
|
||||
@Override
|
||||
public void modifyOriginal(ProgramDB program) throws Exception {
|
||||
DataTypeManager dtm = program.getDataTypeManager();
|
||||
|
||||
Union inner = new UnionDataType("inner");
|
||||
inner.add(DWordDataType.dataType);
|
||||
inner = (Union) dtm.addDataType(inner, null);
|
||||
|
||||
Structure other = new StructureDataType("other", 0);
|
||||
other.add(WordDataType.dataType);
|
||||
other = (Structure) dtm.addDataType(other, null);
|
||||
|
||||
Structure outer = new StructureDataType("outer", 20, dtm);
|
||||
outer.replaceAtOffset(0, other, -1, null, null); // prevent size change
|
||||
outer = (Structure) dtm.addDataType(outer, null);
|
||||
assertEquals(20, outer.getLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyLatest(ProgramDB program) throws Exception {
|
||||
DataTypeManager dtm = program.getDataTypeManager();
|
||||
|
||||
// Increase size of other struct
|
||||
Structure other = (Structure) dtm.getDataType(rootPath, "other");
|
||||
other.add(DWordDataType.dataType);
|
||||
|
||||
// remove inner to trigger conflict with its modification
|
||||
Union inner = (Union) dtm.getDataType(rootPath, "inner");
|
||||
dtm.remove(inner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyPrivate(ProgramDB program) throws Exception {
|
||||
DataTypeManager dtm = program.getDataTypeManager();
|
||||
Structure outer = (Structure) dtm.getDataType(rootPath, "outer");
|
||||
Union inner = (Union) dtm.getDataType(rootPath, "inner");
|
||||
|
||||
// change inner to trigger conflict with its removal
|
||||
inner.add(WordDataType.dataType);
|
||||
|
||||
// Add zero-length array at end of struct
|
||||
outer.insertAtOffset(20, new ArrayDataType(inner, 0), -1);
|
||||
assertEquals(20, outer.getLength());
|
||||
}
|
||||
});
|
||||
|
||||
executeMerge();
|
||||
|
||||
chooseOption(DataTypeMergeManager.OPTION_MY); // resolve inner conflict
|
||||
|
||||
chooseOption(DataTypeMergeManager.OPTION_MY); // resolve outer conflict
|
||||
|
||||
waitForCompletion();
|
||||
|
||||
DataTypeManager dtm = resultProgram.getDataTypeManager();
|
||||
|
||||
StructureInternal outer = (StructureInternal) dtm.getDataType(rootPath, "outer");
|
||||
assertNotNull(outer);
|
||||
|
||||
//@formatter:off
|
||||
assertEquals("/outer\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure outer {\n" +
|
||||
" 0 other 6 \"\"\n" +
|
||||
" 20 inner[0] 0 \"\"\n" +
|
||||
"}\n" +
|
||||
"Length: 20 Alignment: 1\n", outer.toString());
|
||||
//@formatter:on
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -692,16 +692,16 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest
|
||||
options.setBoolean(WORD_WRAP, false);
|
||||
cb.updateNow();
|
||||
btf = (ListingTextField) cb.getCurrentField();
|
||||
assertEquals(12, getNumberOfLines(btf));
|
||||
assertEquals(11, getNumberOfLines(btf));
|
||||
assertTrue("; ".equals(btf.getFieldElement(5, 0).getText()));
|
||||
|
||||
options.setBoolean(SHOW_SEMICOLON, false);
|
||||
cb.updateNow();
|
||||
btf = (ListingTextField) cb.getCurrentField();
|
||||
assertEquals(12, getNumberOfLines(btf));
|
||||
assertFalse("; ".equals(btf.getFieldElement(1, 0).getText()));
|
||||
assertEquals("01003fa1", btf.getFieldElement(11, 4).getText());
|
||||
assertEquals("Mem ref line1.", btf.getFieldElement(11, 11).getText());
|
||||
assertEquals(11, getNumberOfLines(btf));
|
||||
assertTrue("".equals(btf.getFieldElement(1, 0).getText())); // blank line - leading ';' not present
|
||||
assertEquals("01003fa1", btf.getFieldElement(9, 4).getText());
|
||||
assertEquals("Mem ref line1.", btf.getFieldElement(9, 11).getText());
|
||||
|
||||
options.setBoolean(SHOW_REF_ADDR, false);
|
||||
cb.updateNow();
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package ghidra.app.util.viewer.field;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.StringContains.*;
|
||||
import static org.hamcrest.core.StringStartsWith.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -29,10 +31,17 @@ import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.RefType;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.test.*;
|
||||
|
||||
public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private static final String STRING_ADDRESS_WITH_ANNOTATION = "0x01002c98";
|
||||
private static final String ADDRESS_CALLING_STRING_WITH_ANNOTATION = "0X01002d37";
|
||||
private static final String AUTO_COMMENT_TEXT_WITH_ANNOTATION =
|
||||
"Annotation: {@address 12345678 foo}";
|
||||
|
||||
private TestEnv env;
|
||||
private CodeBrowserPlugin cb;
|
||||
private Options fieldOptions;
|
||||
@@ -75,21 +84,45 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
|
||||
public void testRepeatableComment_FunctionCall() throws Exception {
|
||||
|
||||
// check existing auto comment
|
||||
ListingTextField tf = getFieldText(addr("0x010022e6"));
|
||||
String from = "0x010022e6";
|
||||
ListingTextField tf = getFieldText(addr(from));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertThat(tf.getText(), startsWith("undefined ghidra(undefined4 param_1,"));
|
||||
|
||||
// set repeatable comment at destination
|
||||
Address destination = addr("0x01002cf5");
|
||||
// set repeatable comment at source
|
||||
String to = "0x01002cf5";
|
||||
String repeatableComment = "My repeatable comment";
|
||||
setRepeatableComment(destination, repeatableComment);
|
||||
setRepeatableComment(addr(to), repeatableComment);
|
||||
|
||||
// check that the auto comment now matches the updated comment
|
||||
tf = getFieldText(addr("0x010022e6"));
|
||||
// check that the repeatable comment now matches the updated comment
|
||||
tf = getFieldText(addr(from));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertEquals(tf.getText(), repeatableComment);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatableComment_FunctionCall_PrependRefAddress() throws Exception {
|
||||
|
||||
setBooleanOption(EolCommentFieldFactory.ENABLE_PREPEND_REF_ADDRESS_KEY, true);
|
||||
|
||||
// check existing auto comment
|
||||
String from = "0x010022e6";
|
||||
ListingTextField tf = getFieldText(addr(from));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertThat(tf.getText(), startsWith("undefined ghidra(undefined4 param_1,"));
|
||||
|
||||
// set repeatable comment at source
|
||||
String to = "0x01002cf5";
|
||||
String repeatableComment = "My repeatable comment";
|
||||
setRepeatableComment(addr(to), repeatableComment);
|
||||
|
||||
// check that the repeatable comment now matches the updated comment and has the ref address
|
||||
// prepended
|
||||
tf = getFieldText(addr(from));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertEquals("01002cf5 " + repeatableComment, tf.getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatableComment_DataAccess() throws Exception {
|
||||
|
||||
@@ -106,7 +139,21 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
|
||||
// check that the auto comment now matches the updated comment
|
||||
tf = getFieldText(addr("0x01002265"));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertEquals(tf.getText(), repeatableComment);
|
||||
assertEquals(repeatableComment, tf.getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoCommentDoesNotRenderAnnotation() {
|
||||
|
||||
/*
|
||||
Creates a data reference to a string containing an annotation. Tests that the
|
||||
annotation is not rendered, but is shown in its raw form.
|
||||
*/
|
||||
|
||||
goTo(env.getTool(), program, STRING_ADDRESS_WITH_ANNOTATION);
|
||||
ListingTextField tf = getFieldText(addr(ADDRESS_CALLING_STRING_WITH_ANNOTATION));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertThat(tf.getText(), containsString(AUTO_COMMENT_TEXT_WITH_ANNOTATION));
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
@@ -115,6 +162,13 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
|
||||
|
||||
private ProgramDB buildProgram() throws Exception {
|
||||
ClassicSampleX86ProgramBuilder builder = new ClassicSampleX86ProgramBuilder();
|
||||
|
||||
builder.createString(STRING_ADDRESS_WITH_ANNOTATION, AUTO_COMMENT_TEXT_WITH_ANNOTATION);
|
||||
|
||||
builder.createMemoryReference(ADDRESS_CALLING_STRING_WITH_ANNOTATION,
|
||||
STRING_ADDRESS_WITH_ANNOTATION,
|
||||
RefType.DATA, SourceType.ANALYSIS);
|
||||
|
||||
return builder.getProgram();
|
||||
}
|
||||
|
||||
|
||||
@@ -338,6 +338,74 @@ public class StructureDataTypeTest extends AbstractGenericTest {
|
||||
assertEquals(ByteDataType.class, comps[2].getDataType().getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAtSameOffset1() {
|
||||
|
||||
DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0);
|
||||
|
||||
struct = createStructure("Test", 100);
|
||||
assertFalse(struct.isPackingEnabled());
|
||||
|
||||
struct.insertAtOffset(0, zeroDt, -1, "a", "comment a");
|
||||
struct.insertAtOffset(0, zeroDt, -1, "b", "comment b");
|
||||
struct.insertAtOffset(0, WordDataType.dataType, -1, "c", "comment c");
|
||||
|
||||
DataTypeComponent[] definedComponents = struct.getDefinedComponents();
|
||||
assertEquals("a", definedComponents[0].getFieldName());
|
||||
assertEquals("b", definedComponents[1].getFieldName());
|
||||
assertEquals("c", definedComponents[2].getFieldName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAtSameOffset2() {
|
||||
|
||||
DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0);
|
||||
|
||||
struct = createStructure("Test", 100);
|
||||
assertFalse(struct.isPackingEnabled());
|
||||
|
||||
struct.insertAtOffset(0, WordDataType.dataType, -1, "c", "comment c");
|
||||
struct.insertAtOffset(0, zeroDt, -1, "a", "comment a");
|
||||
struct.insertAtOffset(0, zeroDt, -1, "b", "comment b");
|
||||
|
||||
DataTypeComponent[] definedComponents = struct.getDefinedComponents();
|
||||
assertEquals("a", definedComponents[0].getFieldName());
|
||||
assertEquals("b", definedComponents[1].getFieldName());
|
||||
assertEquals("c", definedComponents[2].getFieldName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAtEndOffset() {
|
||||
|
||||
DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0);
|
||||
|
||||
struct = createStructure("Test2", 100);
|
||||
assertFalse(struct.isPackingEnabled());
|
||||
|
||||
struct.insertAtOffset(100, zeroDt, -1, "a", "comment a");
|
||||
struct.insertAtOffset(100, zeroDt, -1, "b", "comment b");
|
||||
|
||||
assertEquals("/Test2\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure Test2 {\n" +
|
||||
" 100 byte[0] 0 a \"comment a\"\n" +
|
||||
" 100 byte[0] 0 b \"comment b\"\n" +
|
||||
"}\n" +
|
||||
"Length: 100 Alignment: 1\n", struct.toString());
|
||||
|
||||
// Insert non-zero-length component will increase struct size
|
||||
struct.insertAtOffset(100, WordDataType.dataType, -1, "c", "comment c");
|
||||
|
||||
assertEquals("/Test2\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure Test2 {\n" +
|
||||
" 100 byte[0] 0 a \"comment a\"\n" +
|
||||
" 100 byte[0] 0 b \"comment b\"\n" +
|
||||
" 100 word 2 c \"comment c\"\n" +
|
||||
"}\n" +
|
||||
"Length: 102 Alignment: 1\n", struct.toString());
|
||||
}
|
||||
|
||||
// test inserting at offset 0
|
||||
@Test
|
||||
public void testInsertAtOffset() {
|
||||
@@ -458,32 +526,18 @@ public class StructureDataTypeTest extends AbstractGenericTest {
|
||||
Array zeroArray = new ArrayDataType(FloatDataType.dataType, 0, -1);
|
||||
struct.insertAtOffset(2, zeroArray, -1);
|
||||
struct.insertAtOffset(2, FloatDataType.dataType, -1);
|
||||
assertEquals(13, struct.getLength());
|
||||
|
||||
DataTypeComponent[] comps = struct.getDefinedComponents();
|
||||
|
||||
assertEquals(6, comps.length);
|
||||
|
||||
assertEquals(0, comps[0].getOffset());
|
||||
assertEquals(0, comps[0].getOrdinal());
|
||||
assertEquals(ByteDataType.class, comps[0].getDataType().getClass());
|
||||
|
||||
assertEquals(2, comps[1].getOffset());
|
||||
assertEquals(2, comps[1].getOrdinal());
|
||||
assertEquals(FloatDataType.class, comps[1].getDataType().getClass());
|
||||
|
||||
assertEquals(6, comps[2].getOffset());
|
||||
assertEquals(3, comps[2].getOrdinal());
|
||||
assertTrue(zeroArray.isEquivalent(comps[2].getDataType()));
|
||||
|
||||
assertEquals(6, comps[3].getOffset());
|
||||
assertEquals(4, comps[3].getOrdinal());
|
||||
assertEquals(WordDataType.class, comps[3].getDataType().getClass());
|
||||
|
||||
assertEquals(8, comps[4].getOffset());
|
||||
assertEquals(5, comps[4].getOrdinal());
|
||||
assertEquals(DWordDataType.class, comps[4].getDataType().getClass());
|
||||
|
||||
assertEquals("/TestStruct\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure TestStruct {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
" 2 float[0] 0 \"\"\n" +
|
||||
" 2 float 4 \"\"\n" +
|
||||
" 6 word 2 \"Comment2\"\n" +
|
||||
" 8 dword 4 field3 \"\"\n" +
|
||||
" 12 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 13 Alignment: 1\n", struct.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -660,7 +714,15 @@ public class StructureDataTypeTest extends AbstractGenericTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertBitFieldAtLittleEndian() throws Exception {
|
||||
public void testInsertBitFieldAtLittleEndian2() throws Exception {
|
||||
|
||||
Array zeroArray = new ArrayDataType(CharDataType.dataType, 0, -1);
|
||||
struct.insertAtOffset(2, zeroArray, -1, "A", null);
|
||||
struct.insertAtOffset(2, zeroArray, -1, "B", null);
|
||||
struct.insertAtOffset(2, zeroArray, -1, "C", null);
|
||||
struct.insertAtOffset(2, zeroArray, -1, "D", null);
|
||||
struct.insertAtOffset(2, zeroArray, -1, "E", null);
|
||||
struct.insertAtOffset(2, CharDataType.dataType, -1, "XXX", null);
|
||||
|
||||
struct.insertBitFieldAt(2, 4, 0, IntegerDataType.dataType, 3, "bf1", "bf1Comment");
|
||||
|
||||
@@ -670,15 +732,21 @@ public class StructureDataTypeTest extends AbstractGenericTest {
|
||||
"Structure TestStruct {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 char[0] 0 A \"\"\n" +
|
||||
" 2 char[0] 0 B \"\"\n" +
|
||||
" 2 char[0] 0 C \"\"\n" +
|
||||
" 2 char[0] 0 D \"\"\n" +
|
||||
" 2 char[0] 0 E \"\"\n" +
|
||||
" 2 int:3(0) 1 bf1 \"bf1Comment\"\n" +
|
||||
// " 3 undefined 1 \"\"\n" +
|
||||
// " 4 undefined 1 \"\"\n" +
|
||||
// " 5 undefined 1 \"\"\n" +
|
||||
" 6 word 2 \"Comment2\"\n" +
|
||||
" 8 dword 4 field3 \"\"\n" +
|
||||
" 12 byte 1 field4 \"Comment4\"\n" +
|
||||
// " 5 undefined 1 \"\"\n" +
|
||||
" 6 char 1 XXX \"\"\n" +
|
||||
" 7 word 2 \"Comment2\"\n" +
|
||||
" 9 dword 4 field3 \"\"\n" +
|
||||
" 13 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 13 Alignment: 1", struct);
|
||||
"Length: 14 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
|
||||
struct.insertBitFieldAt(2, 4, 3, IntegerDataType.dataType, 3, "bf2", "bf2Comment");
|
||||
@@ -688,17 +756,23 @@ public class StructureDataTypeTest extends AbstractGenericTest {
|
||||
"pack(disabled)\n" +
|
||||
"Structure TestStruct {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 char[0] 0 A \"\"\n" +
|
||||
" 2 char[0] 0 B \"\"\n" +
|
||||
" 2 char[0] 0 C \"\"\n" +
|
||||
" 2 char[0] 0 D \"\"\n" +
|
||||
" 2 char[0] 0 E \"\"\n" +
|
||||
" 2 int:3(0) 1 bf1 \"bf1Comment\"\n" +
|
||||
" 2 int:3(3) 1 bf2 \"bf2Comment\"\n" +
|
||||
// " 3 undefined 1 \"\"\n" +
|
||||
// " 4 undefined 1 \"\"\n" +
|
||||
// " 5 undefined 1 \"\"\n" +
|
||||
" 6 word 2 \"Comment2\"\n" +
|
||||
" 8 dword 4 field3 \"\"\n" +
|
||||
" 12 byte 1 field4 \"Comment4\"\n" +
|
||||
" 6 char 1 XXX \"\"\n" +
|
||||
" 7 word 2 \"Comment2\"\n" +
|
||||
" 9 dword 4 field3 \"\"\n" +
|
||||
" 13 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 13 Alignment: 1", struct);
|
||||
"Length: 14 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
|
||||
struct.insertBitFieldAt(2, 4, 6, IntegerDataType.dataType, 15, "bf3", "bf3Comment");
|
||||
@@ -708,16 +782,22 @@ public class StructureDataTypeTest extends AbstractGenericTest {
|
||||
"pack(disabled)\n" +
|
||||
"Structure TestStruct {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 char[0] 0 A \"\"\n" +
|
||||
" 2 char[0] 0 B \"\"\n" +
|
||||
" 2 char[0] 0 C \"\"\n" +
|
||||
" 2 char[0] 0 D \"\"\n" +
|
||||
" 2 char[0] 0 E \"\"\n" +
|
||||
" 2 int:3(0) 1 bf1 \"bf1Comment\"\n" +
|
||||
" 2 int:3(3) 1 bf2 \"bf2Comment\"\n" +
|
||||
" 2 int:15(6) 3 bf3 \"bf3Comment\"\n" +
|
||||
// " 5 undefined 1 \"\"\n" +
|
||||
" 6 word 2 \"Comment2\"\n" +
|
||||
" 8 dword 4 field3 \"\"\n" +
|
||||
" 12 byte 1 field4 \"Comment4\"\n" +
|
||||
" 6 char 1 XXX \"\"\n" +
|
||||
" 7 word 2 \"Comment2\"\n" +
|
||||
" 9 dword 4 field3 \"\"\n" +
|
||||
" 13 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 13 Alignment: 1", struct);
|
||||
"Length: 14 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
|
||||
try {
|
||||
@@ -737,15 +817,47 @@ public class StructureDataTypeTest extends AbstractGenericTest {
|
||||
"Structure TestStruct {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 char[0] 0 A \"\"\n" +
|
||||
" 2 char[0] 0 B \"\"\n" +
|
||||
" 2 char[0] 0 C \"\"\n" +
|
||||
" 2 char[0] 0 D \"\"\n" +
|
||||
" 2 char[0] 0 E \"\"\n" +
|
||||
" 2 int:3(0) 1 bf1 \"bf1Comment\"\n" +
|
||||
" 2 int:3(3) 1 bf2 \"bf2Comment\"\n" +
|
||||
" 2 int:15(6) 3 bf3 \"bf3Comment\"\n" +
|
||||
" 4 int:11(5) 2 bf4 \"bf4Comment\"\n" +
|
||||
" 6 word 2 \"Comment2\"\n" +
|
||||
" 8 dword 4 field3 \"\"\n" +
|
||||
" 12 byte 1 field4 \"Comment4\"\n" +
|
||||
" 6 char 1 XXX \"\"\n" +
|
||||
" 7 word 2 \"Comment2\"\n" +
|
||||
" 9 dword 4 field3 \"\"\n" +
|
||||
" 13 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 13 Alignment: 1", struct);
|
||||
"Length: 14 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
|
||||
struct.insertBitFieldAt(2, 4, 0, IntegerDataType.dataType, 0, "z", "zero bitfield");
|
||||
|
||||
//@formatter:off
|
||||
CompositeTestUtils.assertExpectedComposite(this, "/TestStruct\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure TestStruct {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 char[0] 0 A \"\"\n" +
|
||||
" 2 char[0] 0 B \"\"\n" +
|
||||
" 2 char[0] 0 C \"\"\n" +
|
||||
" 2 char[0] 0 D \"\"\n" +
|
||||
" 2 char[0] 0 E \"\"\n" +
|
||||
" 2 int:0(0) 0 \"zero bitfield\"\n" + // field name discarded
|
||||
" 2 int:3(0) 1 bf1 \"bf1Comment\"\n" +
|
||||
" 2 int:3(3) 1 bf2 \"bf2Comment\"\n" +
|
||||
" 2 int:15(6) 3 bf3 \"bf3Comment\"\n" +
|
||||
" 4 int:11(5) 2 bf4 \"bf4Comment\"\n" +
|
||||
" 6 char 1 XXX \"\"\n" +
|
||||
" 7 word 2 \"Comment2\"\n" +
|
||||
" 9 dword 4 field3 \"\"\n" +
|
||||
" 13 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 14 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@@ -839,6 +951,26 @@ public class StructureDataTypeTest extends AbstractGenericTest {
|
||||
"}\n" +
|
||||
"Length: 13 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
|
||||
struct.insertBitFieldAt(2, 4, 31, IntegerDataType.dataType, 0, "z", "zero bitfield");
|
||||
|
||||
//@formatter:off
|
||||
CompositeTestUtils.assertExpectedComposite(this, "/TestStruct\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure TestStruct {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 int:0(7) 0 \"zero bitfield\"\n" + // field name discarded
|
||||
" 2 int:3(5) 1 bf1 \"bf1Comment\"\n" +
|
||||
" 2 int:3(2) 1 bf2 \"bf2Comment\"\n" +
|
||||
" 2 int:15(3) 3 bf3 \"bf3Comment\"\n" +
|
||||
" 4 int:11(0) 2 bf4 \"bf4Comment\"\n" +
|
||||
" 6 word 2 \"Comment2\"\n" +
|
||||
" 8 dword 4 field3 \"\"\n" +
|
||||
" 12 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 13 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -767,7 +767,7 @@ PcodeOp *HeapSequence::buildStringCopy(void)
|
||||
/// the initial input and final output are gathered.
|
||||
/// \param indirects will hold the INDIRECT ops attached to sequence STOREs
|
||||
/// \param pairs will hold Varnode pairs where the first in the pair is the input and the second is the output
|
||||
void HeapSequence::gatherIndirectPairs(vector<PcodeOp *> &indirects,vector<Varnode *> &pairs)
|
||||
void HeapSequence::gatherIndirectPairs(vector<PcodeOp *> &indirects,vector<IndirectPair> &pairs)
|
||||
|
||||
{
|
||||
for(int4 i=0;i<moveOps.size();++i) {
|
||||
@@ -798,26 +798,83 @@ void HeapSequence::gatherIndirectPairs(vector<PcodeOp *> &indirects,vector<Varno
|
||||
if (!defOp->isMark()) break;
|
||||
invn = defOp->getIn(0);
|
||||
}
|
||||
pairs.push_back(invn);
|
||||
pairs.push_back(outvn);
|
||||
data.opUnsetOutput(op);
|
||||
pairs.emplace_back(invn,outvn);
|
||||
}
|
||||
}
|
||||
for(int4 i=0;i<indirects.size();++i)
|
||||
indirects[i]->clearMark();
|
||||
}
|
||||
|
||||
bool HeapSequence::IndirectPair::compareOutput(const IndirectPair *a,const IndirectPair *b)
|
||||
|
||||
{
|
||||
Varnode *vn1 = a->outVn;
|
||||
Varnode *vn2 = b->outVn;
|
||||
if (vn1->getSpace() != vn2->getSpace())
|
||||
return vn1->getSpace()->getIndex() < vn2->getSpace()->getIndex();
|
||||
if (vn1->getOffset() != vn2->getOffset())
|
||||
return vn1->getOffset() < vn2->getOffset();
|
||||
if (vn1->getSize() != vn2->getSize())
|
||||
return vn1->getSize() < vn2->getSize();
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Its possible that INDIRECTs collected from different \e effect ops may share
|
||||
/// the same output storage. Find any output Varnodes that share storage and
|
||||
/// replace all their reads with a single representative Varnode.
|
||||
/// \param pairs is the list of INDIRECT pairs
|
||||
/// \return \b true if the deduplication succeeded
|
||||
bool HeapSequence::deduplicatePairs(vector<IndirectPair> &pairs)
|
||||
|
||||
{
|
||||
if (pairs.empty()) return true;
|
||||
vector<IndirectPair *> copy(pairs.size(),(IndirectPair *)0);
|
||||
for(int4 i=0;i<pairs.size();++i)
|
||||
copy[i] = &pairs[i];
|
||||
sort(copy.begin(),copy.end(),IndirectPair::compareOutput);
|
||||
|
||||
IndirectPair *head = copy[0];
|
||||
int4 dupCount = 0;
|
||||
for(int4 i=1;i<copy.size();++i) {
|
||||
Varnode *vn = copy[i]->outVn;
|
||||
int4 overlap = head->outVn->characterizeOverlap(*vn);
|
||||
if (overlap == 1)
|
||||
return false; // Partial overlap
|
||||
if (overlap == 2) {
|
||||
if (copy[i]->inVn != head->inVn) {
|
||||
return false; // Same storage coming from different sources
|
||||
}
|
||||
copy[i]->markDuplicate();
|
||||
dupCount += 1; // Found a duplicate, keep the same head for next iteration
|
||||
}
|
||||
else // No overlap, move to next headVn
|
||||
head = copy[i];
|
||||
}
|
||||
if (dupCount > 0) {
|
||||
head = copy[0];
|
||||
for(int4 i=1;i<copy.size();++i) {
|
||||
if (copy[i]->isDuplicate()) {
|
||||
data.totalReplace(copy[i]->outVn, head->outVn);
|
||||
}
|
||||
else
|
||||
head = copy[i];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// If the STORE pointer no longer has any other uses, remove the PTRADD producing it, recursively,
|
||||
/// up to the base pointer. INDIRECT ops surrounding any STORE that is removed are replaced with
|
||||
/// INDIRECTs around the user-op replacing the STOREs.
|
||||
/// \param indirects are the list of INDIRECTs cause by the STOREs
|
||||
/// \param indirectPairs are the flow pairs across the STOREs that need to be preserved
|
||||
/// \param replaceOp is the user-op replacement for the STOREs
|
||||
void HeapSequence::removeStoreOps(PcodeOp *replaceOp)
|
||||
void HeapSequence::removeStoreOps(vector<PcodeOp *> &indirects,vector<IndirectPair> &indirectPairs,PcodeOp *replaceOp)
|
||||
|
||||
{
|
||||
vector<PcodeOp *> indirects;
|
||||
vector<Varnode *> indirectPairs;
|
||||
vector<PcodeOp *> scratch;
|
||||
gatherIndirectPairs(indirects, indirectPairs);
|
||||
for(int4 i=0;i<indirectPairs.size();++i) { // Unhook Varnodes we don't want destroyed
|
||||
data.opUnsetOutput(indirectPairs[i].outVn->getDef());
|
||||
}
|
||||
for(int4 i=0;i<moveOps.size();++i) {
|
||||
PcodeOp *op = moveOps[i].op;
|
||||
data.opDestroyRecursive(op, scratch);
|
||||
@@ -825,13 +882,12 @@ void HeapSequence::removeStoreOps(PcodeOp *replaceOp)
|
||||
for(int4 i=0;i<indirects.size();++i) {
|
||||
data.opDestroy(indirects[i]);
|
||||
}
|
||||
for(int4 i=0;i<indirectPairs.size();i+=2) {
|
||||
Varnode *invn = indirectPairs[i];
|
||||
Varnode *outvn = indirectPairs[i+1];
|
||||
for(int4 i=0;i<indirectPairs.size();++i) {
|
||||
if (indirectPairs[i].isDuplicate()) continue;
|
||||
PcodeOp *newInd = data.newOp(2,replaceOp->getAddr());
|
||||
data.opSetOpcode(newInd, CPUI_INDIRECT);
|
||||
data.opSetOutput(newInd,outvn);
|
||||
data.opSetInput(newInd,invn,0);
|
||||
data.opSetOutput(newInd,indirectPairs[i].outVn);
|
||||
data.opSetInput(newInd,indirectPairs[i].inVn,0);
|
||||
data.opSetInput(newInd,data.newVarnodeIop(replaceOp),1);
|
||||
data.opInsertBefore(newInd, replaceOp);
|
||||
}
|
||||
@@ -871,10 +927,15 @@ HeapSequence::HeapSequence(Funcdata &fdata,Datatype *ct,PcodeOp *root)
|
||||
bool HeapSequence::transform(void)
|
||||
|
||||
{
|
||||
vector<PcodeOp *> indirects;
|
||||
vector<IndirectPair> indirectPairs;
|
||||
gatherIndirectPairs(indirects, indirectPairs);
|
||||
if (!deduplicatePairs(indirectPairs))
|
||||
return false;
|
||||
PcodeOp *memCpyOp = buildStringCopy();
|
||||
if (memCpyOp == (PcodeOp *)0)
|
||||
return false;
|
||||
removeStoreOps(memCpyOp);
|
||||
removeStoreOps(indirects,indirectPairs,memCpyOp);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,16 @@ public:
|
||||
/// a single string into memory. If the transform() method is called, an explicit string is constructed, and
|
||||
/// the STOREs are replaced with a \b strncpy or similar CALLOTHER that takes the string as its source input.
|
||||
class HeapSequence : public ArraySequence {
|
||||
/// \brief Helper class containing Varnode pairs that flow across a sequence of INDIRECTs
|
||||
class IndirectPair {
|
||||
public:
|
||||
Varnode *inVn; ///< Input to INDIRECTs
|
||||
Varnode *outVn; ///< Output of INDIRECTs
|
||||
IndirectPair(Varnode *in,Varnode *out) { inVn = in; outVn = out; } ///< Constructor
|
||||
void markDuplicate(void) { inVn = (Varnode *)0; } ///< Note that \b this is a duplicate of another pair
|
||||
bool isDuplicate(void) const { return (inVn == (Varnode *)0); } ///< Return \b true if \b this is marked as a duplicate
|
||||
static bool compareOutput(const IndirectPair *a,const IndirectPair *b); ///< Compare pairs by output storage
|
||||
};
|
||||
Varnode *basePointer; ///< Pointer that sequence is stored to
|
||||
uint8 baseOffset; ///< Offset relative to pointer to root STORE
|
||||
AddrSpace *storeSpace; ///< Address space being STOREed to
|
||||
@@ -98,8 +108,9 @@ class HeapSequence : public ArraySequence {
|
||||
bool testValue(PcodeOp *op); ///< Test if a STORE value has the matching form for the sequence
|
||||
bool collectStoreOps(void); ///< Collect ops STOREing into a memory region from the same root pointer
|
||||
PcodeOp *buildStringCopy(void); ///< Build the strncpy,wcsncpy, or memcpy function with string as input
|
||||
void gatherIndirectPairs(vector<PcodeOp *> &indirects,vector<Varnode *> &pairs);
|
||||
void removeStoreOps(PcodeOp *replaceOp); ///< Remove all STORE ops from the basic block
|
||||
void gatherIndirectPairs(vector<PcodeOp *> &indirects,vector<IndirectPair> &pairs);
|
||||
bool deduplicatePairs(vector<IndirectPair> &pairs); ///< Find and eliminate duplicate INDIRECT pairs
|
||||
void removeStoreOps(vector<PcodeOp *> &indirects,vector<IndirectPair> &indirectPairs,PcodeOp *replaceOp); ///< Remove all STORE ops from the basic block
|
||||
public:
|
||||
HeapSequence(Funcdata &fdata,Datatype *ct,PcodeOp *root);
|
||||
bool transform(void); ///< Transform STOREs into a single memcpy user-op
|
||||
|
||||
@@ -2254,7 +2254,13 @@ Datatype *TypeOpPtradd::getInputCast(const PcodeOp *op,int4 slot,const CastStrat
|
||||
// not the (possibly different) type of the HIGH
|
||||
Datatype *reqtype = op->getIn(0)->getTypeReadFacing(op);
|
||||
Datatype *curtype = op->getIn(0)->getHighTypeReadFacing(op);
|
||||
return castStrategy->castStandard(reqtype,curtype,false,false);
|
||||
if (reqtype->getMetatype() != TYPE_PTR) return reqtype;
|
||||
if (curtype->getMetatype() != TYPE_PTR) return reqtype;
|
||||
Datatype *reqbase = ((TypePointer *)reqtype)->getPtrTo(); // Go down exactly one level
|
||||
Datatype *curbase = ((TypePointer *)curtype)->getPtrTo();
|
||||
if (reqbase->getAlignSize() == curbase->getAlignSize())
|
||||
return (Datatype *)0;
|
||||
return reqtype;
|
||||
}
|
||||
return TypeOp::getInputCast(op,slot,castStrategy);
|
||||
}
|
||||
@@ -2318,7 +2324,24 @@ Datatype *TypeOpPtrsub::getInputCast(const PcodeOp *op,int4 slot,const CastStrat
|
||||
// not the (possibly different) type of the HIGH
|
||||
Datatype *reqtype = op->getIn(0)->getTypeReadFacing(op);
|
||||
Datatype *curtype = op->getIn(0)->getHighTypeReadFacing(op);
|
||||
return castStrategy->castStandard(reqtype,curtype,false,false);
|
||||
if (curtype == reqtype)
|
||||
return (Datatype *)0;
|
||||
if (reqtype->getMetatype() != TYPE_PTR) return reqtype;
|
||||
if (curtype->getMetatype() != TYPE_PTR) return reqtype;
|
||||
Datatype *reqbase = ((TypePointer *)reqtype)->getPtrTo(); // Go down exactly one level
|
||||
Datatype *curbase = ((TypePointer *)curtype)->getPtrTo();
|
||||
if (curbase->getMetatype() == TYPE_ARRAY && reqbase->getMetatype() == TYPE_ARRAY) {
|
||||
curbase = ((TypeArray *)curbase)->getBase();
|
||||
reqbase = ((TypeArray *)reqbase)->getBase();
|
||||
}
|
||||
while(reqbase->getTypedef() != (Datatype *)0)
|
||||
reqbase = reqbase->getTypedef();
|
||||
while(curbase->getTypedef() != (Datatype *)0)
|
||||
curbase = curbase->getTypedef();
|
||||
|
||||
if (curbase == reqbase)
|
||||
return (Datatype *)0;
|
||||
return reqtype;
|
||||
}
|
||||
return TypeOp::getInputCast(op,slot,castStrategy);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ import ghidra.util.Msg;
|
||||
* A package utility class to allow for tests to selectively enable debug output. This class is
|
||||
* used instead of generic logging with the intent that this class will be removed when the bug(s)
|
||||
* are fixed.
|
||||
* <p>
|
||||
* Until {@link #enable()} is called, no data is recorded. Once enabled, all messages are buffered
|
||||
* until a call to {@link #disable(boolean)} is made.
|
||||
*/
|
||||
class DtrfDbg {
|
||||
|
||||
@@ -41,18 +44,24 @@ class DtrfDbg {
|
||||
|
||||
private static Map<Function, List<String>> linesByFunction = new ConcurrentHashMap<>();
|
||||
|
||||
DtrfDbg() {
|
||||
private static volatile boolean isEnabled = false;
|
||||
|
||||
private DtrfDbg() {
|
||||
// static class
|
||||
}
|
||||
|
||||
static void enable() {
|
||||
debugBytes = new ByteArrayOutputStream();
|
||||
debugWriter = new PrintWriter(debugBytes);
|
||||
linesByFunction = new ConcurrentHashMap<>();
|
||||
isEnabled = true;
|
||||
}
|
||||
|
||||
private static void close() {
|
||||
isEnabled = false;
|
||||
debugWriter.close();
|
||||
debugWriter = new NullPrintWriter();
|
||||
linesByFunction.clear();
|
||||
}
|
||||
|
||||
static void disable(boolean write) {
|
||||
@@ -91,11 +100,31 @@ class DtrfDbg {
|
||||
clientFilters.addAll(Arrays.asList(filters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a message to later be printed.
|
||||
*
|
||||
* @param f the function
|
||||
* @param s the message
|
||||
*/
|
||||
static void println(Function f, String s) {
|
||||
linesByFunction.computeIfAbsent(f, ff -> new ArrayList<>()).add(s);
|
||||
if (isEnabled) {
|
||||
linesByFunction.computeIfAbsent(f, ff -> new ArrayList<>()).add(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a message to later be printed, filtering messages based on the 'client' parameter.
|
||||
*
|
||||
* @param f the function
|
||||
* @param client the client
|
||||
* @param s the message
|
||||
* @see #setClientToStringFilters(String...)
|
||||
*/
|
||||
static void println(Function f, Object client, String s) {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passesFilter(client)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@ import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.registry.Registry;
|
||||
import java.rmi.server.*;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import javax.rmi.ssl.SslRMIClientSocketFactory;
|
||||
import javax.rmi.ssl.SslRMIServerSocketFactory;
|
||||
@@ -752,11 +751,49 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||
// Ensure that remote access hostname is properly set for RMI registration
|
||||
String hostname = initRemoteAccessHostname();
|
||||
|
||||
if (DefaultKeyManagerFactory.getPreferredKeyStore() == null) {
|
||||
log.info("Ghidra Server " + Application.getApplicationVersion());
|
||||
log.info(" Server remote access address: " + hostname);
|
||||
if (bindAddress == null) {
|
||||
log.info(" Server listening on all interfaces");
|
||||
}
|
||||
else {
|
||||
log.info(" Server listening on interface: " + bindAddress.getHostAddress());
|
||||
}
|
||||
|
||||
String preferredKeyStore = DefaultKeyManagerFactory.getPreferredKeyStore();
|
||||
if (preferredKeyStore == null) {
|
||||
|
||||
// keystore has not been identified - use self-signed certificate
|
||||
log.info(" Generating self-signed certificate...");
|
||||
log.info(" Subject Alternative Names:");
|
||||
log.info(" " + hostname);
|
||||
|
||||
DefaultKeyManagerFactory.setDefaultIdentity(new X500Principal("CN=GhidraServer"));
|
||||
DefaultKeyManagerFactory.addSubjectAlternativeName(hostname);
|
||||
|
||||
// Collect alternate hostnames for inclusion in certificate
|
||||
Set<String> altNames = new TreeSet<>();
|
||||
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
|
||||
while (nets.hasMoreElements()) {
|
||||
NetworkInterface netint = nets.nextElement();
|
||||
Enumeration<InetAddress> addrs = netint.getInetAddresses();
|
||||
while (addrs.hasMoreElements()) {
|
||||
InetAddress addr = addrs.nextElement();
|
||||
altNames.add(addr.getHostAddress());
|
||||
altNames.add(addr.getHostName());
|
||||
altNames.add(addr.getCanonicalHostName());
|
||||
}
|
||||
}
|
||||
altNames.remove(hostname);
|
||||
for (String name : altNames) {
|
||||
log.info(" " + name);
|
||||
DefaultKeyManagerFactory.addSubjectAlternativeName(name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.info(" Using server certificate keystore: " + preferredKeyStore);
|
||||
}
|
||||
|
||||
if (!DefaultKeyManagerFactory.initialize()) {
|
||||
log.fatal("Failed to initialize PKI/SSL keystore");
|
||||
System.exit(0);
|
||||
@@ -769,14 +806,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
||||
// localhost.getCanonicalHostName() + ":" + classSvrPort + "/";
|
||||
// System.setProperty(RMI_CODEBASE_PROPERTY, codeBaseProp);
|
||||
|
||||
log.info("Ghidra Server " + Application.getApplicationVersion());
|
||||
log.info(" Server remote access address: " + hostname);
|
||||
if (bindAddress == null) {
|
||||
log.info(" Server listening on all interfaces");
|
||||
}
|
||||
else {
|
||||
log.info(" Server listening on interface: " + bindAddress.getHostAddress());
|
||||
}
|
||||
|
||||
log.info(" RMI Registry port: " + ServerPortFactory.getRMIRegistryPort());
|
||||
log.info(" RMI SSL port: " + ServerPortFactory.getRMISSLPort());
|
||||
log.info(" Block Stream port: " + ServerPortFactory.getStreamPort());
|
||||
|
||||
@@ -66,10 +66,15 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer {
|
||||
private static final String OPTION_DESCRIPTION_DEMANGLER_FORMAT =
|
||||
"The demangling format to use";
|
||||
|
||||
static final String OPTION_NAME_TIMEOUT_SECONDS = "Timeout (seconds)";
|
||||
private static final String OPTION_DESCRIPTION_TIMEOUT_SECONDS =
|
||||
"The maximum amount of seconds to spend demangling a string";
|
||||
|
||||
private boolean applyFunctionSignature = true;
|
||||
private boolean applyCallingConvention = true;
|
||||
private boolean demangleOnlyKnownPatterns = false;
|
||||
private boolean useStandardReplacements = true;
|
||||
private long timeoutSeconds = GnuDemanglerOptions.DEFAULT_TIMEOUT_SECONDS;
|
||||
private GnuDemanglerFormat demanglerFormat = GnuDemanglerFormat.AUTO;
|
||||
private boolean useDeprecatedDemangler = false;
|
||||
|
||||
@@ -110,6 +115,8 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer {
|
||||
demanglerFormat, help, OPTION_DESCRIPTION_DEMANGLER_FORMAT,
|
||||
() -> optionsEditor.getFormatEditor());
|
||||
|
||||
options.registerOption(OPTION_NAME_TIMEOUT_SECONDS, timeoutSeconds, help,
|
||||
OPTION_DESCRIPTION_TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,6 +130,8 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer {
|
||||
useStandardReplacements =
|
||||
options.getBoolean(OPTION_NAME_DEMANGLE_USE_STANDARD_REPLACEMENTS,
|
||||
useStandardReplacements);
|
||||
timeoutSeconds = options.getLong(OPTION_NAME_TIMEOUT_SECONDS,
|
||||
GnuDemanglerOptions.DEFAULT_TIMEOUT_SECONDS);
|
||||
demanglerFormat = options.getEnum(OPTION_NAME_DEMANGLER_FORMAT, GnuDemanglerFormat.AUTO);
|
||||
useDeprecatedDemangler =
|
||||
options.getBoolean(OPTION_NAME_USE_DEPRECATED_DEMANGLER, useDeprecatedDemangler);
|
||||
@@ -131,7 +140,7 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer {
|
||||
@Override
|
||||
protected DemanglerOptions getOptions() {
|
||||
GnuDemanglerOptions options =
|
||||
new GnuDemanglerOptions(demanglerFormat, useDeprecatedDemangler);
|
||||
new GnuDemanglerOptions(demanglerFormat, useDeprecatedDemangler, timeoutSeconds);
|
||||
options.setDoDisassembly(true);
|
||||
options.setApplySignature(applyFunctionSignature);
|
||||
options.setApplyCallingConvention(applyCallingConvention);
|
||||
|
||||
@@ -108,7 +108,7 @@ public class GnuDemangler implements Demangler {
|
||||
try {
|
||||
|
||||
GnuDemanglerNativeProcess process = getNativeProcess(options);
|
||||
String demangled = process.demangle(mangled);
|
||||
String demangled = process.demangle(mangled, options.getTimeoutSeconds());
|
||||
if (demangled == null) {
|
||||
throw new DemangledException(false);
|
||||
}
|
||||
|
||||
@@ -15,16 +15,34 @@
|
||||
*/
|
||||
package ghidra.app.util.demangler.gnu;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.framework.*;
|
||||
import com.google.common.util.concurrent.SimpleTimeLimiter;
|
||||
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.OSFileNotFoundException;
|
||||
import ghidra.framework.Platform;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
@@ -113,16 +131,39 @@ public class GnuDemanglerNativeProcess {
|
||||
createProcess();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the demangled string}
|
||||
*
|
||||
* @param mangled The string to demangle
|
||||
* @throws IOException if an IO-related error occurred
|
||||
*/
|
||||
public synchronized String demangle(String mangled) throws IOException {
|
||||
return demangle(mangled, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the demangled string}
|
||||
*
|
||||
* @param mangled The string to demangle
|
||||
* @param timeoutSeconds The number of seconds to attempt the demangle, or {@code null} for no
|
||||
* timeout
|
||||
* @throws IOException if a timeout or IO-related error occurred
|
||||
*/
|
||||
public synchronized String demangle(String mangled, Long timeoutSeconds) throws IOException {
|
||||
if (isDisposed) {
|
||||
throw new IOException("Demangled process has been terminated.");
|
||||
}
|
||||
return demangle(mangled, true);
|
||||
return demangle(mangled, true, timeoutSeconds);
|
||||
}
|
||||
|
||||
private String demangle(String mangled, boolean restart) throws IOException {
|
||||
private String demangle(String mangled, boolean restart, Long timeoutSeconds)
|
||||
throws IOException {
|
||||
try {
|
||||
return doDemangle(mangled);
|
||||
return doDemangle(mangled, timeoutSeconds);
|
||||
}
|
||||
catch (TimeoutException e) {
|
||||
dispose();
|
||||
throw new IOException("Timeout reached", e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
dispose();
|
||||
@@ -130,14 +171,25 @@ public class GnuDemanglerNativeProcess {
|
||||
throw new IOException("Demangler process is not running.", e);
|
||||
}
|
||||
createProcess();
|
||||
return demangle(mangled, false);
|
||||
return demangle(mangled, false, timeoutSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
private String doDemangle(String mangled) throws IOException {
|
||||
private String doDemangle(String mangled, Long timeoutSeconds)
|
||||
throws TimeoutException, IOException {
|
||||
writer.println(mangled);
|
||||
writer.flush();
|
||||
return reader.readLine();
|
||||
try {
|
||||
return timeoutSeconds != null
|
||||
? SimpleTimeLimiter
|
||||
.create(AutoAnalysisManager.getSharedAnalsysThreadPool()
|
||||
.getExecutorService())
|
||||
.callWithTimeout(reader::readLine, timeoutSeconds, TimeUnit.SECONDS)
|
||||
: reader.readLine();
|
||||
}
|
||||
catch (ExecutionException | InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
@@ -161,6 +213,7 @@ public class GnuDemanglerNativeProcess {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
private void createProcess() throws IOException {
|
||||
|
||||
String[] command = buildCommand();
|
||||
@@ -227,9 +280,14 @@ public class GnuDemanglerNativeProcess {
|
||||
// Send a test string over and read the result. If the test string is blank, then
|
||||
// there was an error.
|
||||
//
|
||||
String testResult = doDemangle("test");
|
||||
if (!StringUtils.isBlank(testResult)) {
|
||||
return;
|
||||
try {
|
||||
String testResult = doDemangle("test", null);
|
||||
if (!StringUtils.isBlank(testResult)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (TimeoutException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
InputStream err = process.getErrorStream();
|
||||
|
||||
@@ -44,9 +44,15 @@ public class GnuDemanglerOptions extends DemanglerOptions {
|
||||
*/
|
||||
public static final String GNU_DEMANGLER_DEFAULT = GNU_DEMANGLER_V2_41;
|
||||
|
||||
/**
|
||||
* The default GNU demangler timeout (in seconds)
|
||||
*/
|
||||
public static final long DEFAULT_TIMEOUT_SECONDS = 3;
|
||||
|
||||
private final GnuDemanglerFormat format;
|
||||
private final boolean isDeprecated;
|
||||
private boolean useStandardReplacements;
|
||||
private long timeout;
|
||||
|
||||
/**
|
||||
* Default constructor to use the modern demangler with auto-detect for the format. This
|
||||
@@ -75,9 +81,25 @@ public class GnuDemanglerOptions extends DemanglerOptions {
|
||||
* demangler
|
||||
*/
|
||||
public GnuDemanglerOptions(GnuDemanglerFormat format, boolean isDeprecated) {
|
||||
this(format, isDeprecated, GnuDemanglerOptions.DEFAULT_TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor to specify the format to use, whether to prefer the deprecated format when
|
||||
* both deprecated and modern are available, and the timeout
|
||||
*
|
||||
* @param format the format
|
||||
* @param isDeprecated true if the format is not available in the modern demangler
|
||||
* @param timeoutSeconds the demangler timeout in seconds
|
||||
* @throws IllegalArgumentException if the given format is not available in the deprecated
|
||||
* demangler
|
||||
*/
|
||||
public GnuDemanglerOptions(GnuDemanglerFormat format, boolean isDeprecated,
|
||||
long timeoutSeconds) {
|
||||
this.format = format;
|
||||
this.isDeprecated = isDeprecated;
|
||||
this.useStandardReplacements = true;
|
||||
this.timeout = timeoutSeconds;
|
||||
if (!format.isAvailable(isDeprecated)) {
|
||||
throw new IllegalArgumentException(
|
||||
format.name() + " is not available in the " + getDemanglerName());
|
||||
@@ -91,30 +113,32 @@ public class GnuDemanglerOptions extends DemanglerOptions {
|
||||
public GnuDemanglerOptions(DemanglerOptions copy) {
|
||||
super(copy);
|
||||
|
||||
if (copy instanceof GnuDemanglerOptions) {
|
||||
GnuDemanglerOptions gCopy = (GnuDemanglerOptions) copy;
|
||||
if (copy instanceof GnuDemanglerOptions gCopy) {
|
||||
format = gCopy.format;
|
||||
isDeprecated = gCopy.isDeprecated;
|
||||
timeout = gCopy.timeout;
|
||||
}
|
||||
else {
|
||||
format = GnuDemanglerFormat.AUTO;
|
||||
isDeprecated = false;
|
||||
timeout = DEFAULT_TIMEOUT_SECONDS;
|
||||
}
|
||||
|
||||
this.useStandardReplacements = true;
|
||||
}
|
||||
|
||||
private GnuDemanglerOptions(GnuDemanglerOptions copy, GnuDemanglerFormat format,
|
||||
boolean deprecated) {
|
||||
this(copy, format, deprecated, true);
|
||||
boolean deprecated, long timeoutSeconds) {
|
||||
this(copy, format, deprecated, true, timeoutSeconds);
|
||||
}
|
||||
|
||||
private GnuDemanglerOptions(GnuDemanglerOptions copy, GnuDemanglerFormat format,
|
||||
boolean deprecated, boolean useStandardReplacements) {
|
||||
boolean deprecated, boolean useStandardReplacements, long timeoutSeconds) {
|
||||
super(copy);
|
||||
this.format = format;
|
||||
this.isDeprecated = deprecated;
|
||||
this.useStandardReplacements = useStandardReplacements;
|
||||
this.timeout = timeoutSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,7 +174,7 @@ public class GnuDemanglerOptions extends DemanglerOptions {
|
||||
return this;
|
||||
}
|
||||
if (demanglerFormat.isAvailable(useDeprecated)) {
|
||||
return new GnuDemanglerOptions(this, demanglerFormat, useDeprecated);
|
||||
return new GnuDemanglerOptions(this, demanglerFormat, useDeprecated, this.timeout);
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
demanglerFormat.name() + " is not available in the " + getDemanglerName());
|
||||
@@ -186,6 +210,13 @@ public class GnuDemanglerOptions extends DemanglerOptions {
|
||||
return useStandardReplacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the demangler timeout (in seconds)}
|
||||
*/
|
||||
public long getTimeoutSeconds() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
//@formatter:off
|
||||
@@ -194,6 +225,7 @@ public class GnuDemanglerOptions extends DemanglerOptions {
|
||||
"\tapplySignature: " + applySignature() + ",\n" +
|
||||
"\tuseStandardReplacements: " + useStandardReplacements + ",\n" +
|
||||
"\tdemangleOnlyKnownPatterns: " + demangleOnlyKnownPatterns() + ",\n" +
|
||||
"\ttimeout (sec): " + timeout + ",\n" +
|
||||
"\tdemanglerName: " + getDemanglerName() + ",\n" +
|
||||
"\tdemanglerApplicationArguments: " + getDemanglerApplicationArguments() + ",\n" +
|
||||
"}";
|
||||
|
||||
@@ -30,7 +30,7 @@ import ghidra.util.UniversalID;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* Database implementation for a structure or union.
|
||||
* {@link CompositeDB} provides an abstract database implementation for a structure or union.
|
||||
*/
|
||||
abstract class CompositeDB extends DataTypeDB implements CompositeInternal {
|
||||
|
||||
|
||||
@@ -1007,6 +1007,26 @@ class StructureDB extends CompositeDB implements StructureInternal {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find insertion index such that the index falls after all zero-length components at the
|
||||
* specified offset and before any bitfields at that offset.
|
||||
*
|
||||
* @param index any defined component index which contains offset.
|
||||
* @param offset offset within structure.
|
||||
* @return index of first non-zero-length defined checking in the forward direction.
|
||||
*/
|
||||
private int afterNonZeroComponentsAtOffset(int index, int offset) {
|
||||
int maxIndex = components.size();
|
||||
while (index < maxIndex) {
|
||||
DataTypeComponentDB dtc = components.get(index);
|
||||
if (dtc.getOffset() != offset || dtc.getLength() != 0) {
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify defined-component index of the first non-zero-length component which contains the
|
||||
* specified offset. If only zero-length components exist, the last zero-length component which
|
||||
@@ -1322,14 +1342,20 @@ class StructureDB extends CompositeDB implements StructureInternal {
|
||||
structLength = offset;
|
||||
}
|
||||
|
||||
// Any component insert at an offset should be placed after any zero-length components
|
||||
// at the same offset but before any non-zero-length component.
|
||||
|
||||
int index = Collections.binarySearch(components, Integer.valueOf(offset),
|
||||
OffsetComparator.INSTANCE);
|
||||
|
||||
int additionalShift = 0;
|
||||
if (index >= 0) {
|
||||
index = backupToFirstComponentContainingOffset(index, offset);
|
||||
DataTypeComponentDB dtc = components.get(index);
|
||||
additionalShift = offset - dtc.getOffset();
|
||||
index = afterNonZeroComponentsAtOffset(index, offset);
|
||||
if (index < components.size()) {
|
||||
DataTypeComponentDB dtc = components.get(index);
|
||||
additionalShift = offset - dtc.getOffset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
index = -index - 1;
|
||||
|
||||
@@ -184,6 +184,11 @@ public class BitFieldDataType extends AbstractDataType {
|
||||
* Get the packing storage size in bytes associated with this bit-field which may be
|
||||
* larger than the base type associated with the fields original definition.
|
||||
* Returned value is the same as {@link #getLength()}.
|
||||
* <p>
|
||||
* NOTE: Bitfields with a bit-size of zero will report a storage size of 1, although
|
||||
* {@link #isZeroLength()} will return true. This is consistent with other datatypes which
|
||||
* support a zero-length {@link DataTypeComponent} such as a zero-element Array.
|
||||
*
|
||||
* @return packing storage size in bytes
|
||||
*/
|
||||
public int getStorageSize() {
|
||||
@@ -331,6 +336,9 @@ public class BitFieldDataType extends AbstractDataType {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getStorageSize()
|
||||
*/
|
||||
@Override
|
||||
public int getLength() {
|
||||
return storageSize;
|
||||
|
||||
@@ -192,7 +192,7 @@ public interface DataTypeComponent {
|
||||
if (dataType instanceof Array) {
|
||||
return true;
|
||||
}
|
||||
// assumes undefined types will ultimately have a non-zero length
|
||||
// assumes not-yet-defined types will ultimately have a non-zero length
|
||||
return !dataType.isNotYetDefined();
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -61,6 +61,9 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali
|
||||
this.fieldName = InternalDataTypeComponent.cleanupFieldName(fieldName);
|
||||
setDataType(dataType);
|
||||
setComment(comment);
|
||||
if (isZeroBitFieldComponent()) {
|
||||
this.length = 0; // previously stored as 1, force to 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -240,9 +240,16 @@ public interface Structure extends Composite {
|
||||
/**
|
||||
* Inserts a new datatype at the specified offset into this structure. Inserting a component
|
||||
* will cause any conflicting components to shift down to the extent necessary to avoid a
|
||||
* conflict.
|
||||
* conflict. The overall structure length will always increase when a non-zero-length
|
||||
* component is inserted. NOTE: bitfields may share an offset with other bitfields and
|
||||
* zero-length components.
|
||||
* <p>
|
||||
* This method does not support bit-field insertions which must use the method
|
||||
* Any component insert at an offset will be placed after any zero-length components
|
||||
* at the same offset but before any non-zero-length components. The components which
|
||||
* fall after the insertion point will have there ordinal incremented and offset
|
||||
* adjusted as needed.
|
||||
* <p>
|
||||
* This method will defer bit-field insertions to the method
|
||||
* {@link #insertBitFieldAt(int, int, int, DataType, int, String, String)}.
|
||||
*
|
||||
* @param offset the byte offset into the structure where the new datatype is to be inserted.
|
||||
@@ -389,7 +396,8 @@ public interface Structure extends Composite {
|
||||
/**
|
||||
* Replaces all components containing the specified byte offset with a new component using the
|
||||
* specified datatype, length, name and comment. If the offset corresponds to a bit-field
|
||||
* more than one component may be consumed by this replacement.
|
||||
* more than one component may be consumed by this replacement. In general, this method
|
||||
* should not be used to replace bitfield components.
|
||||
* <p>
|
||||
* This method may not be used to replace a zero-length component since there may be any number
|
||||
* of zero-length components at the same offset. If the only defined component(s) at the specified
|
||||
|
||||
@@ -523,14 +523,20 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur
|
||||
structLength = offset;
|
||||
}
|
||||
|
||||
// Any component insert at an offset should be placed after any zero-length components
|
||||
// at the same offset but before any non-zero-length component.
|
||||
|
||||
int index = Collections.binarySearch(components, Integer.valueOf(offset),
|
||||
OffsetComparator.INSTANCE);
|
||||
|
||||
int additionalShift = 0;
|
||||
if (index >= 0) {
|
||||
index = backupToFirstComponentContainingOffset(index, offset);
|
||||
DataTypeComponent dtc = components.get(index);
|
||||
additionalShift = offset - dtc.getOffset();
|
||||
index = afterNonZeroComponentsAtOffset(index, offset);
|
||||
if (index < components.size()) {
|
||||
DataTypeComponentImpl dtc = components.get(index);
|
||||
additionalShift = offset - dtc.getOffset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
index = -index - 1;
|
||||
@@ -898,6 +904,26 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find insertion index such that the index falls after all zero-length components at the
|
||||
* specified offset and before any bitfields at that offset.
|
||||
*
|
||||
* @param index any defined component index which contains offset.
|
||||
* @param offset offset within structure.
|
||||
* @return index of first non-zero-length defined checking in the forward direction.
|
||||
*/
|
||||
private int afterNonZeroComponentsAtOffset(int index, int offset) {
|
||||
int maxIndex = components.size();
|
||||
while (index < maxIndex) {
|
||||
DataTypeComponentImpl dtc = components.get(index);
|
||||
if (dtc.getOffset() != offset || dtc.getLength() != 0) {
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify defined-component index of the first non-zero-length component which contains the specified offset.
|
||||
* If only zero-length components exist, the last zero-length component which contains the offset will be returned.
|
||||
|
||||
@@ -364,6 +364,74 @@ public class StructureDBTest extends AbstractGenericTest {
|
||||
assertEquals(ByteDataType.class, comps[2].getDataType().getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAtSameOffset1() {
|
||||
|
||||
DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0);
|
||||
|
||||
struct = createStructure("Test", 100);
|
||||
assertFalse(struct.isPackingEnabled());
|
||||
|
||||
struct.insertAtOffset(0, zeroDt, -1, "a", "comment a");
|
||||
struct.insertAtOffset(0, zeroDt, -1, "b", "comment b");
|
||||
struct.insertAtOffset(0, WordDataType.dataType, -1, "c", "comment c");
|
||||
|
||||
DataTypeComponentDB[] definedComponents = struct.getDefinedComponents();
|
||||
assertEquals("a", definedComponents[0].getFieldName());
|
||||
assertEquals("b", definedComponents[1].getFieldName());
|
||||
assertEquals("c", definedComponents[2].getFieldName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAtSameOffset2() {
|
||||
|
||||
DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0);
|
||||
|
||||
struct = createStructure("Test", 100);
|
||||
assertFalse(struct.isPackingEnabled());
|
||||
|
||||
struct.insertAtOffset(0, WordDataType.dataType, -1, "c", "comment c");
|
||||
struct.insertAtOffset(0, zeroDt, -1, "a", "comment a");
|
||||
struct.insertAtOffset(0, zeroDt, -1, "b", "comment b");
|
||||
|
||||
DataTypeComponentDB[] definedComponents = struct.getDefinedComponents();
|
||||
assertEquals("a", definedComponents[0].getFieldName());
|
||||
assertEquals("b", definedComponents[1].getFieldName());
|
||||
assertEquals("c", definedComponents[2].getFieldName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAtEndOffset() {
|
||||
|
||||
DataType zeroDt = new ArrayDataType(ByteDataType.dataType, 0);
|
||||
|
||||
struct = createStructure("Test2", 100);
|
||||
assertFalse(struct.isPackingEnabled());
|
||||
|
||||
struct.insertAtOffset(100, zeroDt, -1, "a", "comment a");
|
||||
struct.insertAtOffset(100, zeroDt, -1, "b", "comment b");
|
||||
|
||||
assertEquals("/Test2\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure Test2 {\n" +
|
||||
" 100 byte[0] 0 a \"comment a\"\n" +
|
||||
" 100 byte[0] 0 b \"comment b\"\n" +
|
||||
"}\n" +
|
||||
"Length: 100 Alignment: 1\n", struct.toString());
|
||||
|
||||
// Insert non-zero-length component will increase struct size
|
||||
struct.insertAtOffset(100, WordDataType.dataType, -1, "c", "comment c");
|
||||
|
||||
assertEquals("/Test2\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure Test2 {\n" +
|
||||
" 100 byte[0] 0 a \"comment a\"\n" +
|
||||
" 100 byte[0] 0 b \"comment b\"\n" +
|
||||
" 100 word 2 c \"comment c\"\n" +
|
||||
"}\n" +
|
||||
"Length: 102 Alignment: 1\n", struct.toString());
|
||||
}
|
||||
|
||||
// test inserting at offset 0
|
||||
@Test
|
||||
public void testInsertAtOffset() {
|
||||
@@ -485,32 +553,18 @@ public class StructureDBTest extends AbstractGenericTest {
|
||||
Array zeroArray = new ArrayDataType(FloatDataType.dataType, 0, -1);
|
||||
struct.insertAtOffset(2, zeroArray, -1);
|
||||
struct.insertAtOffset(2, FloatDataType.dataType, -1);
|
||||
assertEquals(13, struct.getLength());
|
||||
|
||||
DataTypeComponent[] comps = struct.getDefinedComponents();
|
||||
|
||||
assertEquals(6, comps.length);
|
||||
|
||||
assertEquals(0, comps[0].getOffset());
|
||||
assertEquals(0, comps[0].getOrdinal());
|
||||
assertEquals(ByteDataType.class, comps[0].getDataType().getClass());
|
||||
|
||||
assertEquals(2, comps[1].getOffset());
|
||||
assertEquals(2, comps[1].getOrdinal());
|
||||
assertEquals(FloatDataType.class, comps[1].getDataType().getClass());
|
||||
|
||||
assertEquals(6, comps[2].getOffset());
|
||||
assertEquals(3, comps[2].getOrdinal());
|
||||
assertTrue(zeroArray.isEquivalent(comps[2].getDataType()));
|
||||
|
||||
assertEquals(6, comps[3].getOffset());
|
||||
assertEquals(4, comps[3].getOrdinal());
|
||||
assertEquals(WordDataType.class, comps[3].getDataType().getClass());
|
||||
|
||||
assertEquals(8, comps[4].getOffset());
|
||||
assertEquals(5, comps[4].getOrdinal());
|
||||
assertEquals(DWordDataType.class, comps[4].getDataType().getClass());
|
||||
|
||||
assertEquals("/Test\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure Test {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
" 2 float[0] 0 \"\"\n" +
|
||||
" 2 float 4 \"\"\n" +
|
||||
" 6 word 2 \"Comment2\"\n" +
|
||||
" 8 dword 4 field3 \"\"\n" +
|
||||
" 12 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 13 Alignment: 1\n", struct.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -815,6 +869,154 @@ public class StructureDBTest extends AbstractGenericTest {
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertBitFieldAtLittleEndian2() throws Exception {
|
||||
|
||||
Array zeroArray = new ArrayDataType(CharDataType.dataType, 0, -1);
|
||||
struct.insertAtOffset(2, zeroArray, -1, "A", null);
|
||||
struct.insertAtOffset(2, zeroArray, -1, "B", null);
|
||||
struct.insertAtOffset(2, zeroArray, -1, "C", null);
|
||||
struct.insertAtOffset(2, zeroArray, -1, "D", null);
|
||||
struct.insertAtOffset(2, zeroArray, -1, "E", null);
|
||||
struct.insertAtOffset(2, CharDataType.dataType, -1, "XXX", null);
|
||||
|
||||
struct.insertBitFieldAt(2, 4, 0, IntegerDataType.dataType, 3, "bf1", "bf1Comment");
|
||||
|
||||
//@formatter:off
|
||||
CompositeTestUtils.assertExpectedComposite(this, "/Test\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure Test {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 char[0] 0 A \"\"\n" +
|
||||
" 2 char[0] 0 B \"\"\n" +
|
||||
" 2 char[0] 0 C \"\"\n" +
|
||||
" 2 char[0] 0 D \"\"\n" +
|
||||
" 2 char[0] 0 E \"\"\n" +
|
||||
" 2 int:3(0) 1 bf1 \"bf1Comment\"\n" +
|
||||
// " 3 undefined 1 \"\"\n" +
|
||||
// " 4 undefined 1 \"\"\n" +
|
||||
// " 5 undefined 1 \"\"\n" +
|
||||
" 6 char 1 XXX \"\"\n" +
|
||||
" 7 word 2 \"Comment2\"\n" +
|
||||
" 9 dword 4 field3 \"\"\n" +
|
||||
" 13 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 14 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
|
||||
struct.insertBitFieldAt(2, 4, 3, IntegerDataType.dataType, 3, "bf2", "bf2Comment");
|
||||
|
||||
//@formatter:off
|
||||
CompositeTestUtils.assertExpectedComposite(this, "/Test\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure Test {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 char[0] 0 A \"\"\n" +
|
||||
" 2 char[0] 0 B \"\"\n" +
|
||||
" 2 char[0] 0 C \"\"\n" +
|
||||
" 2 char[0] 0 D \"\"\n" +
|
||||
" 2 char[0] 0 E \"\"\n" +
|
||||
" 2 int:3(0) 1 bf1 \"bf1Comment\"\n" +
|
||||
" 2 int:3(3) 1 bf2 \"bf2Comment\"\n" +
|
||||
// " 3 undefined 1 \"\"\n" +
|
||||
// " 4 undefined 1 \"\"\n" +
|
||||
// " 5 undefined 1 \"\"\n" +
|
||||
" 6 char 1 XXX \"\"\n" +
|
||||
" 7 word 2 \"Comment2\"\n" +
|
||||
" 9 dword 4 field3 \"\"\n" +
|
||||
" 13 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 14 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
|
||||
struct.insertBitFieldAt(2, 4, 6, IntegerDataType.dataType, 15, "bf3", "bf3Comment");
|
||||
|
||||
//@formatter:off
|
||||
CompositeTestUtils.assertExpectedComposite(this, "/Test\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure Test {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 char[0] 0 A \"\"\n" +
|
||||
" 2 char[0] 0 B \"\"\n" +
|
||||
" 2 char[0] 0 C \"\"\n" +
|
||||
" 2 char[0] 0 D \"\"\n" +
|
||||
" 2 char[0] 0 E \"\"\n" +
|
||||
" 2 int:3(0) 1 bf1 \"bf1Comment\"\n" +
|
||||
" 2 int:3(3) 1 bf2 \"bf2Comment\"\n" +
|
||||
" 2 int:15(6) 3 bf3 \"bf3Comment\"\n" +
|
||||
// " 5 undefined 1 \"\"\n" +
|
||||
" 6 char 1 XXX \"\"\n" +
|
||||
" 7 word 2 \"Comment2\"\n" +
|
||||
" 9 dword 4 field3 \"\"\n" +
|
||||
" 13 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 14 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
|
||||
try {
|
||||
struct.insertBitFieldAt(2, 4, 21, IntegerDataType.dataType, 12, "bf4", "bf4Comment");
|
||||
fail(
|
||||
"expected - IllegalArgumentException: Bitfield does not fit within specified constraints");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
struct.insertBitFieldAt(2, 4, 21, IntegerDataType.dataType, 11, "bf4", "bf4Comment");
|
||||
|
||||
//@formatter:off
|
||||
CompositeTestUtils.assertExpectedComposite(this, "/Test\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure Test {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 char[0] 0 A \"\"\n" +
|
||||
" 2 char[0] 0 B \"\"\n" +
|
||||
" 2 char[0] 0 C \"\"\n" +
|
||||
" 2 char[0] 0 D \"\"\n" +
|
||||
" 2 char[0] 0 E \"\"\n" +
|
||||
" 2 int:3(0) 1 bf1 \"bf1Comment\"\n" +
|
||||
" 2 int:3(3) 1 bf2 \"bf2Comment\"\n" +
|
||||
" 2 int:15(6) 3 bf3 \"bf3Comment\"\n" +
|
||||
" 4 int:11(5) 2 bf4 \"bf4Comment\"\n" +
|
||||
" 6 char 1 XXX \"\"\n" +
|
||||
" 7 word 2 \"Comment2\"\n" +
|
||||
" 9 dword 4 field3 \"\"\n" +
|
||||
" 13 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 14 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
|
||||
struct.insertBitFieldAt(2, 4, 0, IntegerDataType.dataType, 0, "z", "zero bitfield");
|
||||
|
||||
//@formatter:off
|
||||
CompositeTestUtils.assertExpectedComposite(this, "/Test\n" +
|
||||
"pack(disabled)\n" +
|
||||
"Structure Test {\n" +
|
||||
" 0 byte 1 field1 \"Comment1\"\n" +
|
||||
// " 1 undefined 1 \"\"\n" +
|
||||
" 2 char[0] 0 A \"\"\n" +
|
||||
" 2 char[0] 0 B \"\"\n" +
|
||||
" 2 char[0] 0 C \"\"\n" +
|
||||
" 2 char[0] 0 D \"\"\n" +
|
||||
" 2 char[0] 0 E \"\"\n" +
|
||||
" 2 int:0(0) 0 \"zero bitfield\"\n" + // field name discarded
|
||||
" 2 int:3(0) 1 bf1 \"bf1Comment\"\n" +
|
||||
" 2 int:3(3) 1 bf2 \"bf2Comment\"\n" +
|
||||
" 2 int:15(6) 3 bf3 \"bf3Comment\"\n" +
|
||||
" 4 int:11(5) 2 bf4 \"bf4Comment\"\n" +
|
||||
" 6 char 1 XXX \"\"\n" +
|
||||
" 7 word 2 \"Comment2\"\n" +
|
||||
" 9 dword 4 field3 \"\"\n" +
|
||||
" 13 byte 1 field4 \"Comment4\"\n" +
|
||||
"}\n" +
|
||||
"Length: 14 Alignment: 1", struct);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertBitFieldAtBigEndian() throws Exception {
|
||||
|
||||
|
||||
@@ -188,6 +188,21 @@ public class GThreadPool {
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the {@link ExecutorService} used by this thread pool.
|
||||
*
|
||||
* <P>Note: normal usage of this thread pool contraindicates accessing the executor service of
|
||||
* this pool. For managing your own jobs, you should use the method on this class directly.
|
||||
* The intent of this method is to provide access to the executor service so that it may be
|
||||
* passed to other asynchronous APIs.
|
||||
*
|
||||
* @return the executor service
|
||||
*/
|
||||
public ExecutorService getExecutorService() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
|
||||
@@ -15,6 +15,7 @@ define token srcDestByte (8)
|
||||
drk03 = (0,3)
|
||||
# constraint bits
|
||||
d7 = (7,7)
|
||||
d67 = (6,7)
|
||||
d57 = (5,7)
|
||||
d47 = (4,7)
|
||||
s3 = (3,3)
|
||||
@@ -36,6 +37,7 @@ define token srcDestByte2 (8)
|
||||
drk03_ = (0,3)
|
||||
# constraint bits
|
||||
d7_ = (7,7)
|
||||
d67_ = (6,7)
|
||||
d57_ = (5,7)
|
||||
s3_ = (3,3)
|
||||
s13_ = (1,3)
|
||||
@@ -85,13 +87,13 @@ attach variables [ wrj47_d2 ] [
|
||||
# NOTE: must use constraints DRK, DRKD and DRKS
|
||||
attach variables [ drk47 drk03 drk47_ drk03_ ] [
|
||||
DR0 DR4 DR8 DR12 DR16 DR20 DR24 DR28
|
||||
DPX SPX _ _ _ _ _ _
|
||||
_ _ _ _ _ _ DPX SPX
|
||||
];
|
||||
|
||||
@define DRK47 "drk47 & (d7=0 | d57=4)" # constraint for using drk47
|
||||
@define DRK03 "drk03 & (s3=0 | s13=4)" # constraint for using drk03
|
||||
@define DRK47_ "drk47_ & (d7_=0 | d57_=4)" # constraint for using drk47_
|
||||
|
||||
@define DRK47 "drk47 & (d7=0 | d67=3)" # constraint for using drk47
|
||||
@define DRK03 "drk03 & (s3=0 | s23=3)" # constraint for using drk03
|
||||
@define DRK47_ "drk47_ & (d7_=0 | d67_=3)" # constraint for using drk47_
|
||||
|
||||
AtWRjb: "@"^wrj47 is wrj47 { ptr:3 = zext(wrj47); export *:1 ptr; }
|
||||
AtWRjw: "@"^wrj47 is wrj47 { ptr:3 = zext(wrj47); export *:2 ptr; }
|
||||
|
||||
@@ -497,7 +499,7 @@ macro pop24(val) {
|
||||
|
||||
|
||||
# MOVH DRk,#data16
|
||||
:MOVH drk47,Data16x0 is $(GROUP3) & ophi=7 & oplo=14; $(DRK47) & s03=12; Data16x0 { drk47 = (drk47 & 0xffff0000) | (Data16x0 << 16); }
|
||||
:MOVH drk47,Data16x0 is $(GROUP3) & ophi=7 & oplo=0xa; $(DRK47) & s03=0xc; Data16x0 { drk47 = (drk47 & 0xffff) | (Data16x0 << 16); }
|
||||
|
||||
# MOVS WRj,Rm
|
||||
:MOVZ wrj47,rm03 is $(GROUP3) & ophi=1 & oplo=10; wrj47 & rm03 { wrj47 = sext(rm03); }
|
||||
|
||||
@@ -16042,7 +16042,7 @@ is b_2431=0b01011110 & b_2223=0b00 & b_2121=0 & Rm_VPR128.4S & b_1015=0b000000 &
|
||||
:sha1h Rd_FPR32, Rn_FPR32
|
||||
is b_2431=0b01011110 & b_2223=0b00 & b_1721=0b10100 & b_1216=0b00000 & b_1011=0b10 & Rn_FPR32 & Rd_FPR32 & Zd
|
||||
{
|
||||
Rd_FPR32 = Rn_FPR32 << 30:1;
|
||||
Rd_FPR32 = Rn_FPR32 << 30:1 | (Rn_FPR32 >> 2:1);
|
||||
zext_zs(Zd); # zero upper 28 bytes of Zd
|
||||
}
|
||||
|
||||
|
||||
@@ -517,7 +517,8 @@ SAVE_TOP: SAVE_ARG^EXT_FRAME^SAVE_RA^SAVE_SREG^SAVE_STAT is EXT_FRAME & SAVE_RA
|
||||
<done>
|
||||
}
|
||||
|
||||
:jal Abs26_m16 is ISA_MODE=1 & RELP=1 & ext_isjal=1 & ext_tgt_x=0 & Abs26_m16 [ ext_delay=0b10; globalset(inst_next, ext_delay);] {
|
||||
:jal Abs26_m16 is ISA_MODE=1 & RELP=1 & ext_isjal=1 & ext_tgt_x=0 & Abs26_m16
|
||||
[ ext_delay=0b10; globalset(inst_next, ext_delay); ] {
|
||||
ra = inst_next | 0x1;
|
||||
delayslot( 1 );
|
||||
call Abs26_m16;
|
||||
@@ -525,7 +526,7 @@ SAVE_TOP: SAVE_ARG^EXT_FRAME^SAVE_RA^SAVE_SREG^SAVE_STAT is EXT_FRAME & SAVE_RA
|
||||
}
|
||||
|
||||
:jalr m16_rx is ISA_MODE=1 & RELP=1 & ext_isjal=0 & m16_op=0b11101 & m16_rr_nd=0 & m16_rr_l=1 & m16_rr_ra=0 & m16_rr_f=0b00000 & m16_rx
|
||||
[ ext_delay=0b01; globalset(inst_next, ext_delay); globalset(inst_start, ext_delay); ] {
|
||||
[ ext_delay=0b01; globalset(inst_next, ext_delay); ] {
|
||||
JXWritePC(m16_rx);
|
||||
ra = inst_next | 0x1;
|
||||
delayslot( 1 );
|
||||
@@ -539,7 +540,7 @@ SAVE_TOP: SAVE_ARG^EXT_FRAME^SAVE_RA^SAVE_SREG^SAVE_STAT is EXT_FRAME & SAVE_RA
|
||||
}
|
||||
|
||||
:jalx Abs26_m16 is ISA_MODE=1 & RELP=1 & ext_isjal=1 & ext_tgt_x=1 & Abs26_m16
|
||||
[ ext_delay=0b10; ISA_MODE = 0; globalset(Abs26_m16, ISA_MODE); globalset(inst_next, ext_delay); globalset(inst_start, ext_delay); ] {
|
||||
[ ext_delay=0b10; ISA_MODE = 0; globalset(Abs26_m16, ISA_MODE); globalset(inst_next, ext_delay); ] {
|
||||
ra = inst_next | 0x1;
|
||||
delayslot( 1 );
|
||||
ISAModeSwitch = 0;
|
||||
@@ -547,14 +548,14 @@ SAVE_TOP: SAVE_ARG^EXT_FRAME^SAVE_RA^SAVE_SREG^SAVE_STAT is EXT_FRAME & SAVE_RA
|
||||
}
|
||||
|
||||
:jr ra is ISA_MODE=1 & RELP=1 & ext_isjal=0 & m16_op=0b11101 & m16_rr_nd=0 & m16_rr_l=0 & m16_rr_ra=1 & ra & m16_rr_f=0b00000 & m16_rx=0
|
||||
[ ext_delay=0b01; globalset(inst_next, ext_delay); globalset(inst_start, ext_delay); ] {
|
||||
[ ext_delay=0b01; globalset(inst_next, ext_delay); ] {
|
||||
JXWritePC(ra);
|
||||
delayslot( 1 );
|
||||
return [pc];
|
||||
}
|
||||
|
||||
:jr m16_rx is ISA_MODE=1 & RELP=1 & ext_isjal=0 & m16_op=0b11101 & m16_rr_nd=0 & m16_rr_l=0 & m16_rr_ra=0 & m16_rr_f=0b00000 & m16_rx
|
||||
[ ext_delay=0b01; globalset(inst_next, ext_delay); globalset(inst_start, ext_delay); ] {
|
||||
[ ext_delay=0b01; globalset(inst_next, ext_delay); ] {
|
||||
JXWritePC(m16_rx);
|
||||
delayslot( 1 );
|
||||
goto [pc];
|
||||
@@ -883,7 +884,7 @@ E2_REGOFF: imm is ext_imm_2124 & m16_i_imm [ imm = m16_i_imm | (ext_imm_2124 <<
|
||||
lockload(tmp);
|
||||
}
|
||||
|
||||
:lui m16_rx, EXT_LIU8 is ISA_MODE=1 & RELP=1 & ext_isjal=0 & m16_op=0b01101 & m16_rx & m16_ri_z=1 & EXT_LIU8 {
|
||||
:lui m16_rx, EXT_LIU8 is ISA_MODE=1 & RELP=1 & ext_isjal=0 & ext_is_ext=1 & m16_op=0b01101 & m16_rx & m16_ri_z=1 & EXT_LIU8 {
|
||||
m16_rx = zext(EXT_LIU8) << 16;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -51,6 +51,13 @@ VMARGS=-Djdk.tls.client.protocols=TLSv1.2,TLSv1.3
|
||||
#
|
||||
#VMARGS=-Djavax.net.debug=ssl
|
||||
|
||||
# When using Java 21.0.10 or later and connecting to an older Ghidra Server (pre-12.0.3) the following
|
||||
# connection error may occur.
|
||||
# ... SSLHandshakeException: (certificate_unknown) No matching <name> found
|
||||
# If unable to upgrade your Ghidra Server this property setting may be uncommented to disable the
|
||||
# hostname check.
|
||||
#VMARGS=-Djdk.rmi.ssl.client.enableEndpointIdentification=false
|
||||
|
||||
# The following property will limit the number of processor cores that Ghidra
|
||||
# will use for thread pools. If not specified, it will use the default number
|
||||
# of processors returned from Runtime.getRuntime().getAvailableProcessors().
|
||||
|
||||
@@ -16,22 +16,31 @@
|
||||
package ghidra.app.plugin.core.debug.service.breakpoint;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import db.Transaction;
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.model.DebuggerModelPlugin;
|
||||
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget;
|
||||
import ghidra.app.services.DebuggerControlService;
|
||||
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.breakpoint.*;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointLocation;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointSpec;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.modules.TraceStaticMapping;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
@@ -249,4 +258,203 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends
|
||||
assertEquals(Map.ofEntries(
|
||||
Map.entry("breakpoint", expectedLoc.getSpecification().getObject())), args);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddTraceBreakpointThenModifyRange_Lone() throws Throwable {
|
||||
// These are for interactive debugging
|
||||
//addPlugin(tool, DebuggerModelPlugin.class);
|
||||
//addPlugin(tool, DebuggerBreakpointsPlugin.class);
|
||||
|
||||
DebuggerControlService controlService =
|
||||
addPlugin(tool, DebuggerControlServicePlugin.class);
|
||||
|
||||
createTrace();
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activate(DebuggerCoordinates.NOWHERE.trace(tb.trace).snap(1));
|
||||
// Needs to have a target or be emulated for the breakpoint service to care
|
||||
controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR);
|
||||
|
||||
TraceBreakpointLocation bpt;
|
||||
try (Transaction tid = tb.startTransaction()) {
|
||||
tb.createRootObject(SCHEMA_CTX);
|
||||
bpt = tb.trace.getBreakpointManager()
|
||||
.addBreakpoint("Processes[1].Breakpoints[0][0]", Lifespan.nowOn(0),
|
||||
tb.addr(0x55550123), Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE),
|
||||
false, "");
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
waitOn(breakpointService.changesSettled());
|
||||
changeListener.assertAgreesWithService();
|
||||
|
||||
LogicalBreakpoint lbBefore =
|
||||
Unique.assertOne(breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550123)));
|
||||
|
||||
try (Transaction tid = tb.startTransaction()) {
|
||||
bpt.setRange(Lifespan.nowOn(1), tb.range(0x56660123));
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
waitOn(breakpointService.changesSettled());
|
||||
changeListener.assertAgreesWithService();
|
||||
|
||||
LogicalBreakpoint lbAfter =
|
||||
Unique.assertOne(breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x56660123)));
|
||||
assertNotSame(lbBefore, lbAfter);
|
||||
assertEquals(Set.of(lbAfter), breakpointService.getAllBreakpoints());
|
||||
assertEquals(Set.of(bpt), lbAfter.getTraceBreakpoints());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddTraceBreakpointThenModifyRange_LoneThenNullAndBack() throws Throwable {
|
||||
// These are for interactive debugging
|
||||
addPlugin(tool, DebuggerModelPlugin.class);
|
||||
addPlugin(tool, DebuggerBreakpointsPlugin.class);
|
||||
|
||||
DebuggerControlService controlService =
|
||||
addPlugin(tool, DebuggerControlServicePlugin.class);
|
||||
|
||||
createTrace();
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activate(DebuggerCoordinates.NOWHERE.trace(tb.trace).snap(1));
|
||||
// Needs to have a target or be emulated for the breakpoint service to care
|
||||
controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR);
|
||||
|
||||
TraceBreakpointLocation bpt;
|
||||
try (Transaction tid = tb.startTransaction()) {
|
||||
tb.createRootObject(SCHEMA_CTX);
|
||||
bpt = tb.trace.getBreakpointManager()
|
||||
.addBreakpoint("Processes[1].Breakpoints[0][0]", Lifespan.nowOn(0),
|
||||
tb.addr(0x55550123), Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE),
|
||||
false, "");
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
waitOn(breakpointService.changesSettled());
|
||||
changeListener.assertAgreesWithService();
|
||||
|
||||
Unique.assertOne(breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550123)));
|
||||
|
||||
try (Transaction tid = tb.startTransaction()) {
|
||||
bpt.getObject()
|
||||
.setAttribute(Lifespan.nowOn(1), TraceBreakpointLocation.KEY_RANGE, null);
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
waitOn(breakpointService.changesSettled());
|
||||
changeListener.assertAgreesWithService();
|
||||
|
||||
assertEquals(Set.of(), breakpointService.getAllBreakpoints());
|
||||
|
||||
try (Transaction tid = tb.startTransaction()) {
|
||||
bpt.setRange(Lifespan.nowOn(1), tb.range(0x56660123));
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
waitOn(breakpointService.changesSettled());
|
||||
changeListener.assertAgreesWithService();
|
||||
|
||||
LogicalBreakpoint lbAfter =
|
||||
Unique.assertOne(breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x56660123)));
|
||||
assertEquals(Set.of(lbAfter), breakpointService.getAllBreakpoints());
|
||||
assertEquals(Set.of(bpt), lbAfter.getTraceBreakpoints());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddTraceBreakpointThenModifyRange_MappedThenLone() throws Throwable {
|
||||
// These are for interactive debugging
|
||||
//addPlugin(tool, DebuggerModelPlugin.class);
|
||||
//addPlugin(tool, DebuggerBreakpointsPlugin.class);
|
||||
|
||||
DebuggerControlService controlService =
|
||||
addPlugin(tool, DebuggerControlServicePlugin.class);
|
||||
|
||||
createTrace();
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activate(DebuggerCoordinates.NOWHERE.trace(tb.trace).snap(1));
|
||||
// Needs to have a target or be emulated for the breakpoint service to care
|
||||
controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR);
|
||||
|
||||
createProgramFromTrace();
|
||||
intoProject(program);
|
||||
programManager.openProgram(program);
|
||||
|
||||
TraceBreakpointLocation bpt;
|
||||
try (Transaction tid = tb.startTransaction()) {
|
||||
tb.createRootObject(SCHEMA_CTX);
|
||||
bpt = tb.trace.getBreakpointManager()
|
||||
.addBreakpoint("Processes[1].Breakpoints[0][0]", Lifespan.nowOn(0),
|
||||
tb.addr(0x55550123), Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE),
|
||||
false, "");
|
||||
addTextMappingDead(0, program, tb);
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
waitOn(mappingService.changesSettled());
|
||||
waitOn(breakpointService.changesSettled());
|
||||
changeListener.assertAgreesWithService();
|
||||
|
||||
LogicalBreakpoint lbBefore =
|
||||
Unique.assertOne(breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550123)));
|
||||
|
||||
try (Transaction tid = tb.startTransaction()) {
|
||||
bpt.setRange(Lifespan.nowOn(1), tb.range(0x56660123));
|
||||
// NOTE: New address is not mapped
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
waitOn(breakpointService.changesSettled());
|
||||
changeListener.assertAgreesWithService();
|
||||
|
||||
LogicalBreakpoint lbAfter =
|
||||
Unique.assertOne(breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x56660123)));
|
||||
assertNotSame(lbBefore, lbAfter);
|
||||
assertEquals(Set.of(lbBefore, lbAfter), breakpointService.getAllBreakpoints());
|
||||
assertEquals(Set.of(), lbBefore.getTraceBreakpoints());
|
||||
assertEquals(Set.of(bpt), lbAfter.getTraceBreakpoints());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddTraceBreakpointThenModifyRange_Mapped() throws Throwable {
|
||||
// These are for interactive debugging
|
||||
//addPlugin(tool, DebuggerModelPlugin.class);
|
||||
//addPlugin(tool, DebuggerBreakpointsPlugin.class);
|
||||
|
||||
DebuggerControlService controlService =
|
||||
addPlugin(tool, DebuggerControlServicePlugin.class);
|
||||
|
||||
createTrace();
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activate(DebuggerCoordinates.NOWHERE.trace(tb.trace).snap(1));
|
||||
// Needs to have a target or be emulated for the breakpoint service to care
|
||||
controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR);
|
||||
|
||||
createProgramFromTrace();
|
||||
intoProject(program);
|
||||
programManager.openProgram(program);
|
||||
|
||||
TraceBreakpointLocation bpt;
|
||||
try (Transaction tid = tb.startTransaction()) {
|
||||
tb.createRootObject(SCHEMA_CTX);
|
||||
bpt = tb.trace.getBreakpointManager()
|
||||
.addBreakpoint("Processes[1].Breakpoints[0][0]", Lifespan.nowOn(0),
|
||||
tb.addr(0x55550123), Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE),
|
||||
false, "");
|
||||
addTextMappingDead(0, program, tb);
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
waitOn(mappingService.changesSettled());
|
||||
waitOn(breakpointService.changesSettled());
|
||||
changeListener.assertAgreesWithService();
|
||||
|
||||
LogicalBreakpoint lbBefore =
|
||||
Unique.assertOne(breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550123)));
|
||||
|
||||
try (Transaction tid = tb.startTransaction()) {
|
||||
bpt.setRange(Lifespan.nowOn(1), tb.range(0x55550124));
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
waitOn(breakpointService.changesSettled());
|
||||
changeListener.assertAgreesWithService();
|
||||
|
||||
LogicalBreakpoint lbAfter =
|
||||
Unique.assertOne(breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550124)));
|
||||
assertNotSame(lbBefore, lbAfter);
|
||||
assertEquals(Set.of(lbBefore, lbAfter), breakpointService.getAllBreakpoints());
|
||||
assertEquals(Set.of(), lbBefore.getTraceBreakpoints());
|
||||
assertEquals(Set.of(bpt), lbAfter.getTraceBreakpoints());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,12 +114,6 @@ public class TraceRmiPcodeExecTest extends AbstractGhidraHeadedDebuggerIntegrati
|
||||
return null;
|
||||
});
|
||||
|
||||
/**
|
||||
* TODO: This second handle should not be necessary. The KNOWN ought to carry into the
|
||||
* scratch snapshot.
|
||||
*/
|
||||
handleReadRegsInvocation(objRegs, () -> null);
|
||||
|
||||
byte[] result = waitOn(futResult);
|
||||
assertEquals(new BigInteger("11"),
|
||||
executor.getArithmetic().toBigInteger(result, Purpose.INSPECT));
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.net.*;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.registry.Registry;
|
||||
import java.security.KeyStore.PrivateKeyEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
@@ -955,7 +955,25 @@ public class ServerTestUtil {
|
||||
TEST_PKI_SERVER_PASSPHRASE + "): " + serverKeystorePath);
|
||||
|
||||
PKIUtils.createKeyEntry("test-sig", TEST_PKI_SERVER_DN, 2, caEntry, serverKeystoreFile,
|
||||
"PKCS12", null, TEST_PKI_SERVER_PASSPHRASE.toCharArray());
|
||||
"PKCS12", getLocalHostnames(), TEST_PKI_SERVER_PASSPHRASE.toCharArray());
|
||||
}
|
||||
|
||||
private static Collection<String> getLocalHostnames() throws SocketException {
|
||||
|
||||
// Collect alternate hostnames for inclusion in certificate
|
||||
Set<String> altNames = new TreeSet<>();
|
||||
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
|
||||
while (nets.hasMoreElements()) {
|
||||
NetworkInterface netint = nets.nextElement();
|
||||
Enumeration<InetAddress> addrs = netint.getInetAddresses();
|
||||
while (addrs.hasMoreElements()) {
|
||||
InetAddress addr = addrs.nextElement();
|
||||
altNames.add(addr.getHostAddress());
|
||||
altNames.add(addr.getHostName());
|
||||
altNames.add(addr.getCanonicalHostName());
|
||||
}
|
||||
}
|
||||
return altNames;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
application.name=Ghidra
|
||||
application.version=12.0.2
|
||||
application.version=12.0.3
|
||||
application.release.name=DEV
|
||||
application.layout.version=3
|
||||
application.gradle.min=8.5
|
||||
|
||||
Reference in New Issue
Block a user