diff --git a/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html b/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html index 3752223cc7..dec1372be0 100644 --- a/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html +++ b/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html @@ -7,14 +7,16 @@ -

Ghidra 10.1 Change History (November 2021)

+

Ghidra 10.1 Change History (December 2021)

New Features

Improvements

Bugs

diff --git a/Ghidra/Configurations/Public_Release/src/global/docs/WhatsNew.html b/Ghidra/Configurations/Public_Release/src/global/docs/WhatsNew.html index 9fd4cf9db4..68d81b1bfe 100644 --- a/Ghidra/Configurations/Public_Release/src/global/docs/WhatsNew.html +++ b/Ghidra/Configurations/Public_Release/src/global/docs/WhatsNew.html @@ -42,7 +42,7 @@ vulnerabilities in networks and systems.

-

What's new in Ghidra 10.1 BETA

+

What's new in Ghidra 10.1

The not-so-fine print: Please Read!

@@ -52,6 +52,12 @@

This release includes many new features and capabilities, performance improvements, quite a few bug fixes, and many pull-request contributions. Thanks to all those who have contributed their time, thoughts, and code. The Ghidra user community thanks you too!

+ +

NOTE: Please note that any programs imported with a Ghidra beta versions or code built directly from source outside of a release tag may not be compatible + and may have flaws that have been corrected. Any programs analyzed with a beta should be considered experimental and re-imported and analyzed with + a release version. As an example, Ghidra 10.1 beta had an import flaw affecting symbol de-mangling that was not correctable. + Programs imported with previous release versions should upgrade correctly through various automatic upgrade mechanisms. Any program + you will continue to reverse engineer should be imported fresh with a release version or a build you trust with the latest code fixes.

NOTE: Ghidra Server: The Ghidra 10.1 server is compatible with Ghidra 9.2 and later Ghidra clients. Ghidra 10.1 clients are compatible with all 9.x servers.

@@ -89,6 +95,11 @@ and build LLDB with language bindings for Java. Once done, the new connectors for LLDB can be used in the normal fashion. While intended for macOS, these connectors also work on Linux, and may work on Windows, too. This offers an alternative for those who prefer lldb to gdb.

+

Decompiler

+

Many improvements have been made to the decompiler output to improve readability. These include the production of else-if syntax in control flow, + and the reduction of casting when typedefs are involved. In addition, pointer calculation during sub-expression elimination has been improved, and + a new API for iterating and accessing the decompiler output syntax tokens has been added.

+

Data Types

Support for zero-length data types and components has been improved, although such types will continue to @@ -100,14 +111,23 @@ The static method DataTypeComponent.usesZeroLengthComponent(DataType) may be used to determine if a zero-length component will be used for a specific data type. Due to the overlapping behavior of zero-length components, a data type which returns true for isNotYetDefined() will not produce a zero-length component.

+ +

Improved parsing of C header files to correctly extract data type definitions, including corrected sizeof() handling, expression + simplification to a constant for many types such as array size and enumeration value, and handling of type declarations within function + and structure declarations. We have re-parsed most of the included data type archives to take advantage of the changes, and plan to + update the archives to more recent versions of the header files in the near future.

Mach-O Binary Import

Mach-O binary import has been greatly improved, including handling of relocation pointer chains, support for newer Objective-C - class structures with RelativePointers, many additional load commands such as encrypted blocks, and more recent dyld and kernel caches.

+ class structures with RelativePointers, additional load commands, and support for more recent versions of dyld and kernel caches + including split-file dyld_shared_cache variants.

Android

-

Added support for Android formats (ART, OAT, ODEX, DEX, CDEX, VDEX) and Dalvik VM Sleigh modules for each major Android release up to version 12.x. - Support for the latest android release is in progress for a future release.

+

Import and analysis of the entire existing set (almost) of Android binaries up to version 12.x is now supported. The type of binaries supported + include: Android Run-Time (ART), Ahead-of-Time (OAT)/ELF, Dalvik Executables (DEX), Compact DEX (CDEX), Verified DEX (VEX), Boot Image, + and Boot Loader formats. Also included are Sleigh modules for DEX files covering each major release of Android; the optimized instructions + vary across versions. Now when importing DEX files, you can select the Dalvik language appropriate to the Android release, which will result + in better analysis.

Performance Improvements

There have been many performance improvements to import, analysis, program data base access, many API calls, and the user interface.

@@ -120,10 +140,6 @@

DWARF

Support for loading DWARF debug information from a separate file during import has been added. In addition data type information contained in the separate debug file can be loaded without application to a program, enabling the use of debug information from a related version of the binary.

- -

- ... WORK IN PROGRESS ... See release notes for more details. -

Bug Fixes and Enhancements

Numerous other bug fixes and improvements are fully listed in the ChangeHistory file.

diff --git a/Ghidra/Debug/Debugger/build.gradle b/Ghidra/Debug/Debugger/build.gradle index 29c6dd693b..7030ea7f14 100644 --- a/Ghidra/Debug/Debugger/build.gradle +++ b/Ghidra/Debug/Debugger/build.gradle @@ -31,6 +31,7 @@ dependencies { api project(':ProposedUtils') helpPath project(path: ':Base', configuration: 'helpPath') + helpPath project(path: ':ProgramDiff', configuration: 'helpPath') testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts') testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts') diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 3f74ff1794..8c5d47f488 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -120,6 +120,9 @@ src/main/help/help/topics/DebuggerThreadsPlugin/images/stepinto.png||GHIDRA||||E src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END| +src/main/help/help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html||GHIDRA||||END| +src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTimeSelectionDialog.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTraceViewDiffPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END| src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END| @@ -185,6 +188,7 @@ src/main/resources/images/stop.png||GHIDRA||||END| src/main/resources/images/sync_enabled.png||GHIDRA||||END| src/main/resources/images/system-switch-user.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/table.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| +src/main/resources/images/table_relationship.png||FAMFAMFAM Icons - CC 2.5||||END| src/main/resources/images/text-xml.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/thread.png||GHIDRA||||END| src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END| diff --git a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml index 9e57f22ea4..f45b46b223 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -157,6 +157,10 @@ sortgroup="p" target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" /> + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html index 559d91baf8..6f49e8d8ee 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html @@ -56,15 +56,20 @@

Actions

-

The time window provides the following action:

+

Rename Snapshot

+ +

This action is available in the Debugger menu whenever the focused + window has an associated snapshot. It will prompt for a new description for the current + snapshot. This is a shortcut to modifying the description in the time table, but can be + accessed outside of the time window.

Hide Scratch

-

This toggle action is always available. It is enabled by default. The emulation service, - which enables trace extrapolation and interpolation, writes emulated state into the trace's - "scratch space," which comprises all negative snaps. When this toggle is enabled, those - snapshots are hidden. They can be displayed by disabling this toggle. Note that navigating into - scratch space may cause temporary undefined behavior in some windows, and may prevent - interaction with the target.

+

This toggle action is always available in the drop-down actions of the Time window. It is + enabled by default. The emulation service, which enables trace extrapolation and interpolation, + writes emulated state into the trace's "scratch space," which comprises all negative snaps. + When this toggle is enabled, those snapshots are hidden. They can be displayed by disabling + this toggle. Note that navigating into scratch space may cause temporary undefined behavior in + some windows, and may prevent interaction with the target.

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html new file mode 100644 index 0000000000..0221bc8c13 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html @@ -0,0 +1,158 @@ + + + + + + + Debugger: Comparing Times + + + + + +

Debugger: Comparing Times

+ + + + + + + +
+ +

A common strategy in dynamic analysis is to compare machine + state between two points in time. To this end, to support comparison of bytes in memory, the + "trace diff" plugin extends the Dynamic Listing to provide + side-by-side comparison of two different points in time. When active, listings for both points + in time are displayed and the byte value differences between them are highlighted. NOTE: + This does not compare annotations. It only compares raw byte values. Additionally, all stale + values are ignored, i.e., to show as a difference, the memory must be observed at both + points in time, and the values must differ.

+ +

NOTE: This plugin only facilitates the comparison of memory displayed in listings. To + compare registers or SLEIGH expressions, use the respective windows: Registers and Watches. By navigating back + and forth between two points in time, using the Time Window, the differences are + displayed in red.

+ +

Actions

+ +

The plugin adds actions to the main Dynamic Listing. When active, additional actions are + present.

+ +

Compare

+ +

This action is available whenever a trace is active in the main listing. It prompts for an + alternative point in time:

+ + + + + + + +
+ +

The snapshot table is exactly the same as that in the Time Window. In most cases, simply + selecting a snapshot suffices.

+ +

Perhaps the most common use of this action is to identify where a given variable is stored + in memory. The trace saves a record of observed memory from the debugging session. Comparing + snapshots thus identifies changes over time; however, there is no guarantee that the desired + variable was ever observed. Assuming the general vicinity of the variable is known, e.g., + "somewhere in the .data section," the Read Selected + Memory action can ensure its value is recorded. Of course, it can also read "all memory," + but that operation and the follow-on comparison could take time. In general, the procedure to + locate a variable is to capture a baseline, execute the target until the variable has changed, + capture again, then compare:

+ +
    +
  1. Execute the target up to a baseline, and take note of the variable's value, as displayed + by the target program.
  2. + +
  3. Consider naming the current snapshot for later reference, using the Rename Current + Snapshot action. Ideally, the name should indicate the variable's value.
  4. + +
  5. Select the range of memory believed to contain the variable. Consider using the Modules or Regions window to form the + selection.
  6. + +
  7. Use the Read Selected + Memory action to ensure the variable's value is stored in the trace.
  8. + +
  9. Allow the target to execute until the variable has changed. Ideally, execute as little as + necessary, so that few or no other variables change.
  10. + +
  11. Execution will cause the trace to advance some number of snapshots. Once suspended, it's + a good idea to rename the current snapshot, again indicating the variable's new value and/or + the cause of its change.
  12. + +
  13. Repeat the selection and capture steps to ensure the variable's new value is stored in + the trace.
  14. + +
  15. Use this Compare action and select the baseline snapshot. It's easy to locate in + the table if named appropriately.
  16. +
+ +

Assuming the variable is actually contained in the captured memory ranges, then it should be + among the differences shown. If too many differences appear, repeat the experiment. Consider + executing less code, establishing a new baseline, taking the intersection of the results, etc. + Remember, the variable's storage should encode its value.

+ +

Optionally, the specified time may also include emulation. See the Go To Time action + for the syntax of the Time Schedule expression. For simple schedules, the step buttons + provide convenient forward and backward changes to the emulation schedule. Perhaps the most + common use of this is to see what changes from executing an isolated block of code. Ideally, + the baseline is a relatively complete capture or represents the present in a live session, so + that the emulator does not depend on un-recorded state:

+ +
    +
  1. Execute the target up to a baseline, probably using a breakpoint at the start of the + interesting block of code.
  2. + +
  3. Keeping the target alive, use the Emulate + Forward and/or Go To Time + actions to reach the end of the interesting block.
  4. + +
  5. Use this Compare action and select the baseline snapshot.
  6. +
+ +

Alternatively, if the number of steps to reach the end of the block is already known, just + use the emulation expression in the Compare action's dialog. NOTE: When used this + way, the baseline snapshot will be in the left pane, and the emulated snapshot in the right, + which is opposite the result from the steps above.

+ +

In either case, this will highlight any memory that was modified by the emulated code. Of + course, this could also be accomplished by setting a second breakpoint and allowing the target + to execute; however, emulation does not necessarily require large memory captures. It only + observes what it needs, and its internal state contains everything that changed. Furthermore, + if establishing the baseline is difficult, emulation allows the target to remain at that + baseline. Assuming sufficient state is captured, emulation can also be performed offline, + without a live target.

+ +

Previous / Next Difference

+ +

These actions are only present when the comparison listing is visible. Each is available + when there exists a previous or next range from the main listing's cursor. Clicking the action + navigates to the nearest address in that range.

+ +

Tool Options: Colors

+ +

The difference highlight color is replicated from the Program Differences plugin.

+ + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTimeSelectionDialog.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTimeSelectionDialog.png new file mode 100644 index 0000000000..e4a617d110 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTimeSelectionDialog.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTraceViewDiffPlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTraceViewDiffPlugin.png new file mode 100644 index 0000000000..7056ec724d Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTraceViewDiffPlugin.png differ diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java index 8d0c47fba0..ddaa613ae9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java @@ -241,6 +241,10 @@ public class DebuggerCoordinates { return all(trace, recorder, thread, view, newTime, frame); } + public DebuggerCoordinates withView(TraceProgramView newView) { + return all(trace, recorder, thread, newView, time, frame); + } + public TraceSchedule getTime() { return time; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index d9caf4108f..dba8c1420d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -147,6 +147,8 @@ public interface DebuggerResources { ImageIcon ICON_READ_MEMORY = ICON_REGIONS; //ResourceManager.loadImage("images/read-memory.png"); + ImageIcon ICON_RENAME_SNAPSHOT = ICON_TIME; + // TODO: Draw an icon ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png"); ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png"); @@ -176,6 +178,10 @@ public interface DebuggerResources { ImageIcon ICON_CONFIG = ResourceManager.loadImage("images/conf.png"); ImageIcon ICON_TOGGLE = ResourceManager.loadImage("images/system-switch-user.png"); + ImageIcon ICON_DIFF = ResourceManager.loadImage("images/table_relationship.png"); + ImageIcon ICON_DIFF_PREV = ResourceManager.loadImage("images/up.png"); + ImageIcon ICON_DIFF_NEXT = ResourceManager.loadImage("images/down.png"); + HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package"); String HELP_ANCHOR_PLUGIN = "plugin"; @@ -368,6 +374,7 @@ public interface DebuggerResources { String GROUP_TRACE_CLOSE = "Dbg7.b. Trace Close"; String GROUP_MAINTENANCE = "Dbg8. Maintenance"; String GROUP_MAPPING = "Dbg9. Map Modules/Sections"; + String GROUP_DIFF_NAV = "DiffNavigate"; static void tableRowActivationAction(GTable table, Runnable runnable) { table.addMouseListener(new MouseAdapter() { @@ -1639,6 +1646,26 @@ public interface DebuggerResources { } } + // TODO: Perhaps to reduce overloading of "snapshot" we should use "event" instead? + interface RenameSnapshotAction { + String NAME = "Rename Current Snapshot"; + String DESCRIPTION = + "Modify the description of the snapshot (event) in the current view"; + String GROUP = GROUP_TRACE; + Icon ICON = ICON_RENAME_SNAPSHOT; + String HELP_ANCHOR = "rename_snapshot"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .menuGroup(GROUP, "zzz") + .keyBinding("CTRL SHIFT N") + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + interface SynchronizeFocusAction { String NAME = "Synchronize Focus"; String DESCRIPTION = "Synchronize trace activation with debugger focus/select"; @@ -1876,6 +1903,57 @@ public interface DebuggerResources { } } + interface CompareTimesAction { + String NAME = "Compare"; + String DESCRIPTION = "Compare this point in time to another"; + String GROUP = "zzz"; // Same as for "Diff" action + Icon ICON = ICON_DIFF; + String HELP_ANCHOR = "compare"; + + static ToggleActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ToggleActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarGroup(GROUP) + .toolBarIcon(ICON) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface PrevDifferenceAction { + String NAME = "Previous Difference"; + String DESCRIPTION = "Go to the previous highlighted difference"; + String GROUP = GROUP_DIFF_NAV; + Icon ICON = ICON_DIFF_PREV; + String HELP_ANCHOR = "prev_diff"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarGroup(GROUP) + .toolBarIcon(ICON) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface NextDifferenceAction { + String NAME = "Next Difference"; + String DESCRIPTION = "Go to the next highlighted difference"; + String GROUP = GROUP_DIFF_NAV; + Icon ICON = ICON_DIFF_NEXT; + String HELP_ANCHOR = "next_diff"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarGroup(GROUP) + .toolBarIcon(ICON) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + public abstract class AbstractDebuggerConnectionsNode extends GTreeNode { @Override public String getName() { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerSnapActionContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerSnapActionContext.java index b092e2ed4c..bb1072a60a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerSnapActionContext.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerSnapActionContext.java @@ -16,16 +16,22 @@ package ghidra.app.plugin.core.debug.gui; import docking.ActionContext; +import ghidra.trace.model.Trace; public class DebuggerSnapActionContext extends ActionContext { - private final long tick; + private final Trace trace; + private final long snap; - public DebuggerSnapActionContext(long tick) { - // TODO: Also require track object? - this.tick = tick; + public DebuggerSnapActionContext(Trace trace, long snap) { + this.trace = trace; + this.snap = snap; } - public long getTick() { - return tick; + public Trace getTrace() { + return trace; + } + + public long getSnap() { + return snap; } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java index d7857f63da..61585af598 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java @@ -285,6 +285,11 @@ public abstract class DebuggerReadsMemoryTrait { return autoSpec; } + /* testing */ + public AddressSetView getVisible() { + return visible; + } + protected abstract AddressSetView getSelection(); protected abstract void repaintPanel(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java index 6f343444ad..7a064623ab 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java @@ -227,7 +227,7 @@ public class DebuggerTrackLocationTrait { protected void doSetSpec(LocationTrackingSpec spec) { if (this.spec != spec) { this.spec = spec; - specChanged(); + specChanged(spec); } doTrack(); } @@ -299,7 +299,7 @@ public class DebuggerTrackLocationTrait { // Listener method } - protected void specChanged() { + protected void specChanged(LocationTrackingSpec spec) { // Listener method } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPlugin.java new file mode 100644 index 0000000000..bba465234e --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPlugin.java @@ -0,0 +1,630 @@ +/* ### + * 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.plugin.core.debug.gui.diff; + +import java.awt.Color; +import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiPredicate; +import java.util.function.Function; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.ToggleDockingAction; +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel; +import ghidra.app.plugin.core.debug.*; +import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; +import ghidra.app.plugin.core.debug.gui.action.DebuggerTrackLocationTrait; +import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec; +import ghidra.app.plugin.core.debug.gui.listing.MultiBlendedListingBackgroundColorModel; +import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog; +import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService; +import ghidra.app.services.*; +import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener; +import ghidra.app.util.viewer.listingpanel.ListingPanel; +import ghidra.async.AsyncUtils; +import ghidra.framework.options.AutoOptions; +import ghidra.framework.options.annotation.AutoOptionConsumed; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.annotation.AutoServiceConsumed; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemoryManager; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.util.Msg; + +@PluginInfo( + shortDescription = "Compare memory state between times in a trace", + description = "Provides a side-by-side diff view between snapshots (points in time) in a " + + "trace. The comparison is limited to raw bytes.", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.RELEASED, + eventsConsumed = { + TraceClosedPluginEvent.class, + }, + eventsProduced = {}, + servicesRequired = { + DebuggerListingService.class, + }, + servicesProvided = {}) +public class DebuggerTraceViewDiffPlugin extends AbstractDebuggerPlugin { + protected static final String MARKER_NAME = "Trace Diff"; + protected static final String MARKER_DESCRIPTION = "Difference between snapshots in this trace"; + + public static final String DIFF_COLOR_CATEGORY = "Listing Fields"; + public static final String DIFF_COLOR_NAME = "Selection Colors.Difference Color"; + public static final Color DEFAULT_DIFF_COLOR = new Color(255, 230, 180); // light orange + + protected class ListingCoordinationListener implements CoordinatedListingPanelListener { + @Override + public boolean listingClosed() { + return endComparison(); + } + + @Override + public void activeProgramChanged(Program activeProgram) { + endComparison(); + } + } + + protected class ForAltListingTrackingTrait extends DebuggerTrackLocationTrait { + public ForAltListingTrackingTrait() { + super(DebuggerTraceViewDiffPlugin.this.getTool(), DebuggerTraceViewDiffPlugin.this, + null); + } + + @Override + protected void locationTracked() { + if (altListingPanel == null) { + return; + } + // NB. Don't goTo here. The left listing controls navigation + altListingPanel.getFieldPanel().repaint(); + } + } + + protected class SyncAltListingTrackingSpecChangeListener + implements LocationTrackingSpecChangeListener { + @Override + public void locationTrackingSpecChanged(LocationTrackingSpec spec) { + trackingTrait.setSpec(spec); + } + } + + protected class MarkerSetChangeListener implements ChangeListener { + @Override + public void stateChanged(ChangeEvent e) { + if (altListingPanel == null) { + return; + } + altListingPanel.getFieldPanel().repaint(); + } + } + + // @AutoServiceConsumed via method + private DebuggerListingService listingService; + @AutoServiceConsumed + private DebuggerTraceManagerService traceManager; + //@AutoServiceConsumed via method + private MarkerService markerService; + + @AutoOptionConsumed(category = DIFF_COLOR_CATEGORY, name = DIFF_COLOR_NAME) + private Color diffColor = DEFAULT_DIFF_COLOR; + @SuppressWarnings("unused") + private final AutoOptions.Wiring autoOptions; + + protected final DebuggerTimeSelectionDialog timeDialog; + + protected ToggleDockingAction actionCompare; + protected DockingAction actionPrevDiff; + protected DockingAction actionNextDiff; + + protected ListingPanel altListingPanel; + protected final ForAltListingTrackingTrait trackingTrait; + protected boolean sessionActive; + + protected final ListingCoordinationListener coordinationListener = + new ListingCoordinationListener(); + protected final SyncAltListingTrackingSpecChangeListener syncTrackingSpecListener = + new SyncAltListingTrackingSpecChangeListener(); + + protected MultiBlendedListingBackgroundColorModel colorModel; + protected final MarkerSetChangeListener markerChangeListener = new MarkerSetChangeListener(); + protected MarkerServiceBackgroundColorModel markerServiceColorModel; + + protected MarkerSet diffMarkersL; + protected MarkerSet diffMarkersR; + + public DebuggerTraceViewDiffPlugin(PluginTool tool) { + super(tool); + autoOptions = AutoOptions.wireOptions(this); + + timeDialog = new DebuggerTimeSelectionDialog(tool); + trackingTrait = new ForAltListingTrackingTrait(); + createActions(); + } + + protected void createActions() { + actionCompare = CompareTimesAction.builder(this) + .enabled(false) + .enabledWhen(ctx -> traceManager != null && traceManager.getCurrentTrace() != null) + .onAction(this::activatedCompare) + .build(); + actionPrevDiff = PrevDifferenceAction.builder(this) + .enabled(false) + .enabledWhen(ctx -> hasPrevDiff()) + .onAction(ctx -> gotoPrevDiff()) + .build(); + actionNextDiff = NextDifferenceAction.builder(this) + .enabled(false) + .enabledWhen(ctx -> hasNextDiff()) + .onAction(ctx -> gotoNextDiff()) + .build(); + } + + protected void activatedCompare(ActionContext ctx) { + if (!actionCompare.isSelected()) { + endComparison(); + return; + } + if (sessionActive) { + return; + } + + DebuggerCoordinates current = traceManager.getCurrent(); + TraceSchedule time = timeDialog.promptTime(current.getTrace(), current.getTime()); + if (time == null) { + // Cancelled + return; + } + if (traceManager == null) { + // Can happen if tool is closed while dialog was up + return; + } + if (traceManager.getCurrentTrace() != current.getTrace()) { + Msg.warn(this, "Trace changed during time prompt. Aborting"); + return; + } + // NB. startComparison will handle failure + startComparison(time); + } + + /** + * Begin a snapshot/time comparison session + * + *

+ * NOTE: This method handles asynchronous errors by popping an error dialog. Callers need not + * handle exceptional completion. + * + * @param time the alternative time + * @return a future which completes when the alternative listing and difference is presented + */ + public CompletableFuture startComparison(TraceSchedule time) { + sessionActive = true; // prevents the action from performing anything + actionCompare.setSelected(true); + + DebuggerCoordinates current = traceManager.getCurrent(); + DebuggerCoordinates alternate = + traceManager.resolveCoordinates(DebuggerCoordinates.time(time)); + PluginToolExecutorService toolExecutorService = + new PluginToolExecutorService(tool, "Computing diff", true, true, false, 500); + return traceManager.materialize(alternate).thenApplyAsync(snap -> { + clearMarkers(); + TraceProgramView altView = alternate.getTrace().getFixedProgramView(snap); + altListingPanel.setProgram(altView); + trackingTrait.goToCoordinates(alternate.withView(altView)); + listingService.setListingPanel(altListingPanel); + return altView; + }, AsyncUtils.SWING_EXECUTOR).thenApplyAsync(altView -> { + return computeDiff(current.getView(), altView); + }, toolExecutorService).thenAcceptAsync(diffSet -> { + addMarkers(diffSet); + listingService.addLocalAction(actionNextDiff); + listingService.addLocalAction(actionPrevDiff); + updateActions(); + }, AsyncUtils.SWING_EXECUTOR).exceptionally(ex -> { + Msg.showError(this, null, "Compare", "Could not compare trace snapshots/times", ex); + return null; + }); + } + + protected void updateActions() { + // May not be necessary often, since contextChanged in ListingProvider should do it + actionNextDiff.setEnabled(actionNextDiff.isEnabledForContext(null)); + actionPrevDiff.setEnabled(actionPrevDiff.isEnabledForContext(null)); + } + + public boolean endComparison() { + sessionActive = false; + actionCompare.setSelected(false); + clearMarkers(); + if (altListingPanel.getProgram() != null) { + listingService.removeListingPanel(altListingPanel); + altListingPanel.setProgram(null); + + listingService.removeLocalAction(actionNextDiff); + listingService.removeLocalAction(actionPrevDiff); + + return true; + } + return false; + } + + protected Address getCurrentAddress() { + if (listingService == null) { + return null; + } + ProgramLocation loc = listingService.getCurrentLocation(); + if (loc == null) { + return null; + } + return loc.getAddress(); + } + + public AddressSetView getDiffs() { + if (diffMarkersL == null) { + return null; + } + return diffMarkersL.getAddressSet(); + } + + protected boolean hasSeqDiff(Function getExtremeRange, + BiPredicate checkRange) { + Address cur = getCurrentAddress(); + if (cur == null) { + return false; + } + AddressSetView set = getDiffs(); + if (set == null) { + return false; + } + AddressRange extreme = getExtremeRange.apply(set); + if (extreme == null) { + return false; + } + return checkRange.test(extreme, cur); + } + + public boolean hasPrevDiff() { + return hasSeqDiff(AddressSetView::getFirstRange, + (first, cur) -> first.getMaxAddress().compareTo(cur) < 0); + } + + public boolean hasNextDiff() { + return hasSeqDiff(AddressSetView::getLastRange, + (last, cur) -> cur.compareTo(last.getMinAddress()) < 0); + } + + protected Address getSeqDiff(boolean forward, + Function getFarthestAddress, + Function getStepped) { + Address cur = getCurrentAddress(); + if (cur == null) { + return null; + } + AddressSetView set = getDiffs(); + if (set == null) { + return null; + } + AddressRange range = set.getRangeContaining(cur); + if (range != null) { + cur = getFarthestAddress.apply(range); + } + cur = getStepped.apply(cur); + if (cur == null) { + return null; + } + AddressIterator it = set.getAddresses(cur, forward); + if (!it.hasNext()) { + return null; + } + return it.next(); + } + + public Address getPrevDiff() { + return getSeqDiff(false, AddressRange::getMinAddress, Address::previous); + } + + public Address getNextDiff() { + return getSeqDiff(true, AddressRange::getMaxAddress, Address::next); + } + + public boolean gotoPrevDiff() { + Address prevDiff = getPrevDiff(); + if (prevDiff == null) { + return false; + } + return listingService.goTo(prevDiff, true) && altListingPanel.goTo(prevDiff); + } + + public boolean gotoNextDiff() { + Address nextDiff = getNextDiff(); + if (nextDiff == null) { + return false; + } + return listingService.goTo(nextDiff, true) && altListingPanel.goTo(nextDiff); + } + + protected void injectOnListingService() { + if (listingService != null) { + listingService.addLocalAction(actionCompare); + altListingPanel = new ListingPanel(listingService.getFormatManager()); + listingService.setCoordinatedListingPanelListener(coordinationListener); + listingService.addTrackingSpecChangeListener(syncTrackingSpecListener); + + colorModel = listingService.createListingBackgroundColorModel(altListingPanel); + colorModel.addModel(trackingTrait.createListingBackgroundColorModel(altListingPanel)); + altListingPanel.setBackgroundColorModel(colorModel); + updateMarkerServiceColorModel(); + } + } + + protected void ejectFromListingService() { + if (altListingPanel != null) { + altListingPanel.dispose(); + altListingPanel = null; + } + colorModel = null; + if (listingService != null) { + listingService.removeLocalAction(actionCompare); + listingService.setCoordinatedListingPanelListener(null); + listingService.removeTrackingSpecChangeListener(syncTrackingSpecListener); + } + } + + @AutoServiceConsumed + private void setListingService(DebuggerListingService listingService) { + ejectFromListingService(); + this.listingService = listingService; + injectOnListingService(); + } + + protected void updateMarkerServiceColorModel() { + if (colorModel == null) { + return; + } + colorModel.removeModel(markerServiceColorModel); + if (markerService != null && altListingPanel != null) { + colorModel.addModel(markerServiceColorModel = new MarkerServiceBackgroundColorModel( + markerService, altListingPanel.getProgram(), altListingPanel.getAddressIndexMap())); + } + } + + protected void createMarkers() { + if (diffMarkersL != null) { + return; + } + if (markerService == null) { + diffMarkersL = null; + diffMarkersR = null; + return; + } + if (altListingPanel == null) { + diffMarkersL = null; + diffMarkersR = null; + return; + } + Program viewR = altListingPanel.getProgram(); + if (viewR == null) { + diffMarkersR = null; + diffMarkersL = null; + return; + } + Color diffColor = this.diffColor == null ? DEFAULT_DIFF_COLOR : this.diffColor; + TraceProgramView viewL = traceManager.getCurrentView(); + diffMarkersL = markerService.createAreaMarker(MARKER_NAME, MARKER_DESCRIPTION, viewL, 0, + true, true, true, diffColor, true); + diffMarkersR = markerService.createAreaMarker(MARKER_NAME, MARKER_DESCRIPTION, viewR, 0, + true, true, true, diffColor, true); + return; + } + + protected void addMarkers(AddressSetView diffSet) { + createMarkers(); + if (diffMarkersL != null) { + diffMarkersL.add(diffSet); + } + if (diffMarkersR != null) { + diffMarkersR.add(diffSet); + } + } + + protected void clearMarkers() { + if (diffMarkersL != null) { + diffMarkersL.clearAll(); + } + if (diffMarkersR != null) { + diffMarkersR.clearAll(); + } + } + + protected void deleteMarkers() { + if (diffMarkersL == null) { + return; + } + if (markerService == null) { + return; + } + if (altListingPanel == null) { + return; + } + Program altView = altListingPanel.getProgram(); + if (altView == null) { + return; + } + markerService.removeMarker(diffMarkersL, altView); + markerService.removeMarker(diffMarkersR, altView); + } + + @AutoServiceConsumed + private void setMarkerService(MarkerService markerService) { + if (this.markerService != null) { + this.markerService.removeChangeListener(markerChangeListener); + deleteMarkers(); + } + this.markerService = markerService; + updateMarkerServiceColorModel(); + + if (this.markerService != null) { + this.markerService.addChangeListener(markerChangeListener); + } + } + + @AutoOptionConsumed(category = DIFF_COLOR_CATEGORY, name = DIFF_COLOR_NAME) + private void setDiffColor(Color diffColor) { + if (diffMarkersL != null) { + diffMarkersL.setMarkerColor(diffColor); + } + if (diffMarkersR != null) { + diffMarkersR.setMarkerColor(diffColor); + } + } + + @Override + public void processEvent(PluginEvent event) { + super.processEvent(event); + if (event instanceof TraceClosedPluginEvent) { + TraceClosedPluginEvent evt = (TraceClosedPluginEvent) event; + if (timeDialog.getTrace() == evt.getTrace()) { + timeDialog.close(); + } + } + } + + public static int lenRemainsBlock(int blockSize, long off) { + return blockSize - (int) (off % blockSize); + } + + public static long minOfBlock(int blockSize, long off) { + return off / blockSize * blockSize; + } + + public static long maxOfBlock(int blockSize, long off) { + return (off + blockSize - 1) / blockSize * blockSize - 1; + } + + public static Address maxOfBlock(int blockSize, Address address) { + long off = address.getOffset(); + long max = maxOfBlock(blockSize, off); + AddressSpace space = address.getAddressSpace(); + return space.getAddress(max); + } + + public static AddressRange blockFor(int blockSize, Address address) { + long off = address.getOffset(); + // TODO: Require powers of 2? + long min = minOfBlock(blockSize, off); + long max = maxOfBlock(blockSize, off); + AddressSpace space = address.getAddressSpace(); + return new AddressRangeImpl(space.getAddress(min), space.getAddress(max)); + } + + protected AddressSetView computeDiff(TraceProgramView view1, TraceProgramView view2) { + Trace trace = view1.getTrace(); + assert trace == view2.getTrace(); + long snap1 = view1.getSnap(); + long snap2 = view2.getSnap(); + + if (snap1 == snap2) { + // Punt on the degenerate case + return new AddressSet(); + } + + TraceMemoryManager mm = trace.getMemoryManager(); + + AddressSetView known1 = mm.getAddressesWithState(snap1, s -> s == TraceMemoryState.KNOWN); + AddressSetView known2 = mm.getAddressesWithState(snap2, s -> s == TraceMemoryState.KNOWN); + + //AddressSet knownEither = known1.union(known2); + AddressSet knownBoth = known1.intersect(known2); // Will need byte-by-byte examination + + // Symmetric difference in state counts as difference? + // TODO: Should that be togglable? + + AddressSet diff = new AddressSet(); //knownEither; + //knownEither = null; // Don't need knownEither anymore. Avoid accidental use + //diff.delete(knownBoth); + + int blockSize = mm.getBlockSize(); + if (blockSize == 0) { + throw new UnsupportedOperationException("TODO: Unoptimized byte diff"); + } + ByteBuffer buf1 = ByteBuffer.allocate(blockSize); + ByteBuffer buf2 = ByteBuffer.allocate(blockSize); + + while (!knownBoth.isEmpty()) { + Address next = knownBoth.getMinAddress(); + Long mrs1 = mm.getSnapOfMostRecentChangeToBlock(snap1, next); + Long mrs2 = mm.getSnapOfMostRecentChangeToBlock(snap2, next); + if (Objects.equals(mrs1, mrs2)) { + knownBoth.delete(blockFor(blockSize, next)); + continue; + } + + int len = lenRemainsBlock(blockSize, next.getOffset()); + buf1.clear(); + buf1.limit(len); + if (len != mm.getBytes(snap1, next, buf1)) { + throw new AssertionError("Read failed"); + } + buf2.clear(); + buf2.limit(len); + if (len != mm.getBytes(snap2, next, buf2)) { + throw new AssertionError("Read failed"); + } + + compareBytes(diff, next, buf1, buf2); + knownBoth.delete(blockFor(blockSize, next)); + } + + return diff; + } + + protected void compareBytes(AddressSet diff, Address addr, ByteBuffer buf1, ByteBuffer buf2) { + int len = buf1.limit(); + byte[] arr1 = buf1.array(); + byte[] arr2 = buf2.array(); + Address rngStart = null; + for (int i = 0; i < len; i++) { + if (arr1[i] != arr2[i]) { + if (rngStart == null) { + rngStart = addr.add(i); + } + } + else { + if (rngStart != null) { + diff.add(rngStart, addr.add(i - 1)); + rngStart = null; + } + } + } + if (rngStart != null) { + diff.add(rngStart, addr.add(len - 1)); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/CursorBackgroundColorModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/CursorBackgroundColorModel.java index e5b00f5971..32ac0957b0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/CursorBackgroundColorModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/CursorBackgroundColorModel.java @@ -26,6 +26,7 @@ import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.framework.options.AutoOptions; import ghidra.framework.options.AutoOptions.Wiring; import ghidra.framework.options.annotation.AutoOptionConsumed; +import ghidra.framework.plugintool.Plugin; import ghidra.program.model.address.Address; import ghidra.program.util.ProgramLocation; @@ -41,7 +42,7 @@ class CursorBackgroundColorModel implements ListingBackgroundColorModel { @SuppressWarnings("unused") private final Wiring autoOptionsWiring; - public CursorBackgroundColorModel(DebuggerListingPlugin plugin, ListingPanel listingPanel) { + public CursorBackgroundColorModel(Plugin plugin, ListingPanel listingPanel) { autoOptionsWiring = AutoOptions.wireOptions(plugin, this); modelDataChanged(listingPanel); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java index b00daed407..4fc878e3e8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java @@ -38,6 +38,7 @@ import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec; import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec; import ghidra.app.services.*; import ghidra.app.util.viewer.format.FormatManager; +import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.framework.options.AutoOptions; import ghidra.framework.options.SaveState; import ghidra.framework.options.annotation.AutoOptionDefined; @@ -162,6 +163,16 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin trackingSpecChangeListeners = + new ListenerSet<>(LocationTrackingSpecChangeListener.class); protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel(); @@ -275,10 +285,8 @@ public class DebuggerListingProvider extends CodeViewerProvider { readsMemTrait = new ForListingReadsMemoryTrait(); ListingPanel listingPanel = getListingPanel(); - colorModel = new MultiBlendedListingBackgroundColorModel(); + colorModel = plugin.createListingBackgroundColorModel(listingPanel); colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel)); - colorModel.addModel(new MemoryStateListingBackgroundColorModel(plugin, listingPanel)); - colorModel.addModel(new CursorBackgroundColorModel(plugin, listingPanel)); listingPanel.setBackgroundColorModel(colorModel); autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); @@ -493,12 +501,10 @@ public class DebuggerListingProvider extends CodeViewerProvider { if (this.markerService != null) { this.markerService.removeChangeListener(markerChangeListener); } - this.markerService = markerService; - updateMarkerServiceColorModel(); - removeOldStaticTrackingMarker(); this.markerService = markerService; createNewStaticTrackingMarker(); + updateMarkerServiceColorModel(); if (this.markerService != null && !isMainListing()) { // NOTE: Connected provider marker listener is taken care of by CodeBrowserPlugin @@ -594,6 +600,38 @@ public class DebuggerListingProvider extends CodeViewerProvider { setSubTitle(computeSubTitle()); } + @Override + protected String computePanelTitle(Program panelProgram) { + if (!(panelProgram instanceof TraceProgramView)) { + // really shouldn't happen anyway... + return super.computePanelTitle(panelProgram); + } + TraceProgramView view = (TraceProgramView) panelProgram; + TraceSnapshot snapshot = + view.getTrace().getTimeManager().getSnapshot(view.getSnap(), false); + if (snapshot == null) { + return Long.toString(view.getSnap()); + } + String description = snapshot.getDescription(); + String schedule = snapshot.getScheduleString(); + if (description == null) { + description = ""; + } + if (schedule == null) { + schedule = ""; + } + if (!description.isBlank() && !schedule.isBlank()) { + return description + " (" + schedule + ")"; + } + if (!description.isBlank()) { + return description; + } + if (!schedule.isBlank()) { + return schedule; + } + return DateUtils.formatDateTimestamp(new Date(snapshot.getRealTime())); + } + protected void createActions() { if (isMainListing()) { actionSyncToStaticListing = new SyncToStaticListingAction(); @@ -842,6 +880,14 @@ public class DebuggerListingProvider extends CodeViewerProvider { return trackingTrait.getSpec(); } + public void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) { + trackingSpecChangeListeners.add(listener); + } + + public void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) { + trackingSpecChangeListeners.remove(listener); + } + public void setSyncToStaticListing(boolean sync) { if (!isMainListing()) { throw new IllegalStateException( diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/MemoryStateListingBackgroundColorModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/MemoryStateListingBackgroundColorModel.java index 01773dcf50..a8fbe4b64a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/MemoryStateListingBackgroundColorModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/MemoryStateListingBackgroundColorModel.java @@ -25,6 +25,7 @@ import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.framework.options.AutoOptions; import ghidra.framework.options.annotation.AutoOptionConsumed; +import ghidra.framework.plugintool.Plugin; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.trace.model.TraceAddressSnapRange; @@ -47,7 +48,7 @@ public class MemoryStateListingBackgroundColorModel implements ListingBackground @SuppressWarnings("unused") private final AutoOptions.Wiring autoOptionsWiring; - public MemoryStateListingBackgroundColorModel(DebuggerListingPlugin plugin, + public MemoryStateListingBackgroundColorModel(Plugin plugin, ListingPanel listingPanel) { autoOptionsWiring = AutoOptions.wireOptions(plugin, this); modelDataChanged(listingPanel); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java index 8d8c16953d..177e2caa2b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java @@ -379,7 +379,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { // TODO: Should I receive clicks on that renderer to seek to a given snap? setDefaultWindowPosition(WindowPosition.BOTTOM); - myActionContext = new DebuggerSnapActionContext(0); + myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap()); createActions(); contextChanged(); @@ -617,7 +617,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { snap = 0; } traceManager.activateSnap(snap); - myActionContext = new DebuggerSnapActionContext(snap); + myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap); contextChanged(); }); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java new file mode 100644 index 0000000000..38d4346201 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java @@ -0,0 +1,262 @@ +/* ### + * 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.plugin.core.debug.gui.time; + +import java.awt.BorderLayout; +import java.util.Collection; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import javax.swing.*; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; + +import com.google.common.collect.Collections2; + +import docking.widgets.table.*; +import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; +import ghidra.framework.model.DomainObject; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.TraceSnapshotChangeType; +import ghidra.trace.model.TraceDomainObjectListener; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.trace.model.time.TraceTimeManager; +import ghidra.util.table.GhidraTableFilterPanel; + +public class DebuggerSnapshotTablePanel extends JPanel { + + protected enum SnapshotTableColumns + implements EnumeratedTableColumn { + SNAP("Snap", Long.class, SnapshotRow::getSnap), + TIMESTAMP("Timestamp", String.class, SnapshotRow::getTimeStamp), // TODO: Use Date type here + EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName), + SCHEDULE("Schedule", String.class, SnapshotRow::getSchedule), + DESCRIPTION("Description", String.class, SnapshotRow::getDescription, SnapshotRow::setDescription); + + private final String header; + private final Function getter; + private final BiConsumer setter; + private final Class cls; + + SnapshotTableColumns(String header, Class cls, Function getter) { + this(header, cls, getter, null); + } + + @SuppressWarnings("unchecked") + SnapshotTableColumns(String header, Class cls, Function getter, + BiConsumer setter) { + this.header = header; + this.cls = cls; + this.getter = getter; + this.setter = (BiConsumer) setter; + } + + @Override + public Class getValueClass() { + return cls; + } + + @Override + public Object getValueOf(SnapshotRow row) { + return getter.apply(row); + } + + @Override + public String getHeader() { + return header; + } + + @Override + public boolean isEditable(SnapshotRow row) { + return setter != null; + } + + @Override + public void setValueOf(SnapshotRow row, Object value) { + setter.accept(row, value); + } + } + + private class SnapshotListener extends TraceDomainObjectListener { + public SnapshotListener() { + listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored()); + + listenFor(TraceSnapshotChangeType.ADDED, this::snapAdded); + listenFor(TraceSnapshotChangeType.CHANGED, this::snapChanged); + listenFor(TraceSnapshotChangeType.DELETED, this::snapDeleted); + } + + private void objectRestored() { + loadSnapshots(); + } + + private void snapAdded(TraceSnapshot snapshot) { + if (snapshot.getKey() < 0 && hideScratch) { + return; + } + SnapshotRow row = new SnapshotRow(currentTrace, snapshot); + snapshotTableModel.add(row); + if (currentSnap == snapshot.getKey()) { + snapshotFilterPanel.setSelectedItem(row); + } + } + + private void snapChanged(TraceSnapshot snapshot) { + if (snapshot.getKey() < 0 && hideScratch) { + return; + } + snapshotTableModel.notifyUpdatedWith(row -> row.getSnapshot() == snapshot); + } + + private void snapDeleted(TraceSnapshot snapshot) { + if (snapshot.getKey() < 0 && hideScratch) { + return; + } + snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot); + } + } + + protected final EnumeratedColumnTableModel snapshotTableModel = + new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class); + protected final GTable snapshotTable; + protected final GhidraTableFilterPanel snapshotFilterPanel; + protected boolean hideScratch = true; + + private Trace currentTrace; + private Long currentSnap; + + protected final SnapshotListener listener = new SnapshotListener(); + + public DebuggerSnapshotTablePanel() { + super(new BorderLayout()); + snapshotTable = new GTable(snapshotTableModel); + snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + add(new JScrollPane(snapshotTable)); + + snapshotFilterPanel = new GhidraTableFilterPanel<>(snapshotTable, snapshotTableModel); + add(snapshotFilterPanel, BorderLayout.SOUTH); + + TableColumnModel columnModel = snapshotTable.getColumnModel(); + TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal()); + snapCol.setPreferredWidth(40); + TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal()); + timeCol.setPreferredWidth(200); + TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal()); + etCol.setPreferredWidth(40); + TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal()); + schdCol.setPreferredWidth(60); + TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal()); + descCol.setPreferredWidth(200); + } + + private void addNewListeners() { + if (currentTrace == null) { + return; + } + currentTrace.addListener(listener); + } + + private void removeOldListeners() { + if (currentTrace == null) { + return; + } + currentTrace.removeListener(listener); + } + + public void setTrace(Trace trace) { + if (currentTrace == trace) { + return; + } + removeOldListeners(); + currentTrace = trace; + addNewListeners(); + loadSnapshots(); + } + + public Trace getTrace() { + return currentTrace; + } + + public void setHideScratchSnapshots(boolean hideScratch) { + if (this.hideScratch == hideScratch) { + return; + } + this.hideScratch = hideScratch; + if (hideScratch) { + deleteScratchSnapshots(); + } + else { + loadScratchSnapshots(); + } + } + + protected void loadSnapshots() { + snapshotTableModel.clear(); + if (currentTrace == null) { + return; + } + TraceTimeManager manager = currentTrace.getTimeManager(); + Collection snapshots = hideScratch + ? manager.getSnapshots(0, true, Long.MAX_VALUE, true) + : manager.getAllSnapshots(); + snapshotTableModel.addAll(Collections2.transform(snapshots, + s -> new SnapshotRow(currentTrace, s))); + } + + protected void deleteScratchSnapshots() { + snapshotTableModel.deleteWith(s -> s.getSnap() < 0); + } + + protected void loadScratchSnapshots() { + if (currentTrace == null) { + return; + } + TraceTimeManager manager = currentTrace.getTimeManager(); + snapshotTableModel.addAll(Collections2.transform( + manager.getSnapshots(Long.MIN_VALUE, true, 0, false), + s -> new SnapshotRow(currentTrace, s))); + } + + public ListSelectionModel getSelectionModel() { + return snapshotTable.getSelectionModel(); + } + + public Long getSelectedSnapshot() { + SnapshotRow row = snapshotFilterPanel.getSelectedItem(); + return row == null ? null : row.getSnap(); + } + + public void setSelectedSnapshot(Long snap) { + currentSnap = snap; + if (snap == null) { + snapshotTable.clearSelection(); + return; + } + + SnapshotRow sel = snapshotFilterPanel.getSelectedItem(); + Long curSnap = sel == null ? null : sel.getSnap(); + if (Objects.equals(curSnap, snap)) { + return; + } + SnapshotRow row = snapshotTableModel.findFirst(r -> r.getSnap() == snap); + if (row == null) { + snapshotTable.clearSelection(); + return; + } + snapshotFilterPanel.setSelectedItem(row); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimePlugin.java index 3cb19e1c49..8d42bcf254 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimePlugin.java @@ -15,14 +15,29 @@ */ package ghidra.app.plugin.core.debug.gui.time; +import java.util.Map; +import java.util.Map.Entry; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.widgets.dialogs.InputDialog; +import ghidra.app.context.ProgramLocationActionContext; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.RenameSnapshotAction; +import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext; import ghidra.app.services.DebuggerTraceManagerService; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.model.listing.Program; +import ghidra.trace.model.Trace; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.trace.model.time.TraceTimeManager; +import ghidra.util.database.UndoableTransaction; @PluginInfo( shortDescription = "Lists recorded snapshots in a trace", @@ -39,8 +54,12 @@ import ghidra.framework.plugintool.util.PluginStatus; public class DebuggerTimePlugin extends AbstractDebuggerPlugin { protected DebuggerTimeProvider provider; + protected DockingAction actionRenameSnapshot; + public DebuggerTimePlugin(PluginTool tool) { super(tool); + + createActions(); } @Override @@ -49,6 +68,58 @@ public class DebuggerTimePlugin extends AbstractDebuggerPlugin { super.init(); } + protected void createActions() { + actionRenameSnapshot = RenameSnapshotAction.builder(this) + .enabled(false) + .enabledWhen(ctx -> contextGetTraceSnap(ctx) != null) + .onAction(this::activatedRenameSnapshot) + .buildAndInstall(tool); + } + + protected Entry contextGetTraceSnap(ActionContext context) { + if (context instanceof ProgramLocationActionContext) { + ProgramLocationActionContext ctx = (ProgramLocationActionContext) context; + Program program = ctx.getProgram(); + if (program instanceof TraceProgramView) { + TraceProgramView view = (TraceProgramView) program; + return Map.entry(view.getTrace(), view.getSnap()); + } + return null; + } + if (context instanceof DebuggerSnapActionContext) { + DebuggerSnapActionContext ctx = (DebuggerSnapActionContext) context; + if (ctx.getTrace() != null) { + return Map.entry(ctx.getTrace(), ctx.getSnap()); + } + return null; + } + return null; + } + + protected void activatedRenameSnapshot(ActionContext context) { + Entry traceSnap = contextGetTraceSnap(context); + if (traceSnap == null) { + return; + } + Trace trace = traceSnap.getKey(); + long snap = traceSnap.getValue(); + TraceTimeManager manager = trace.getTimeManager(); + TraceSnapshot snapshot = manager.getSnapshot(snap, false); + + InputDialog dialog = new InputDialog("Rename Snapshot", "Description", + snapshot == null ? "" : snapshot.getDescription()); + tool.showDialog(dialog); + if (dialog.isCanceled()) { + return; + } + try (UndoableTransaction tid = UndoableTransaction.start(trace, "Rename Snapshot", true)) { + if (snapshot == null) { + snapshot = manager.getSnapshot(snap, true); + } + snapshot.setDescription(dialog.getValue()); + } + } + @Override protected void dispose() { tool.removeComponentProvider(provider); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java index a7a10ff5cb..97d173bd64 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java @@ -17,138 +17,30 @@ package ghidra.app.plugin.core.debug.gui.time; import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*; -import java.awt.BorderLayout; import java.awt.event.MouseEvent; import java.lang.invoke.MethodHandles; -import java.util.Collection; import java.util.Objects; -import java.util.function.BiConsumer; -import java.util.function.Function; -import javax.swing.*; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; - -import com.google.common.collect.Collections2; +import javax.swing.JComponent; import docking.ActionContext; import docking.action.DockingActionIf; import docking.action.ToggleDockingAction; -import docking.widgets.table.*; -import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext; import ghidra.app.services.DebuggerTraceManagerService; -import ghidra.framework.model.DomainObject; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.AutoService.Wiring; import ghidra.framework.plugintool.annotation.AutoConfigStateField; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; -import ghidra.trace.model.Trace; -import ghidra.trace.model.Trace.TraceSnapshotChangeType; -import ghidra.trace.model.TraceDomainObjectListener; -import ghidra.trace.model.time.TraceSnapshot; -import ghidra.trace.model.time.TraceTimeManager; -import ghidra.util.table.GhidraTableFilterPanel; public class DebuggerTimeProvider extends ComponentProviderAdapter { private static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER = AutoConfigState.wireHandler(DebuggerTimeProvider.class, MethodHandles.lookup()); - protected enum SnapshotTableColumns - implements EnumeratedTableColumn { - SNAP("Snap", Long.class, SnapshotRow::getSnap), - TIMESTAMP("Timestamp", String.class, SnapshotRow::getTimeStamp), // TODO: Use Date type here - EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName), - SCHEDULE("Schedule", String.class, SnapshotRow::getSchedule), - DESCRIPTION("Description", String.class, SnapshotRow::getDescription, SnapshotRow::setDescription); - - private final String header; - private final Function getter; - private final BiConsumer setter; - private final Class cls; - - SnapshotTableColumns(String header, Class cls, Function getter) { - this(header, cls, getter, null); - } - - @SuppressWarnings("unchecked") - SnapshotTableColumns(String header, Class cls, Function getter, - BiConsumer setter) { - this.header = header; - this.cls = cls; - this.getter = getter; - this.setter = (BiConsumer) setter; - } - - @Override - public Class getValueClass() { - return cls; - } - - @Override - public Object getValueOf(SnapshotRow row) { - return getter.apply(row); - } - - @Override - public String getHeader() { - return header; - } - - @Override - public boolean isEditable(SnapshotRow row) { - return setter != null; - } - - @Override - public void setValueOf(SnapshotRow row, Object value) { - setter.accept(row, value); - } - } - - private class SnapshotListener extends TraceDomainObjectListener { - public SnapshotListener() { - listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored()); - - listenFor(TraceSnapshotChangeType.ADDED, this::snapAdded); - listenFor(TraceSnapshotChangeType.CHANGED, this::snapChanged); - listenFor(TraceSnapshotChangeType.DELETED, this::snapDeleted); - } - - private void objectRestored() { - loadSnapshots(); - } - - private void snapAdded(TraceSnapshot snapshot) { - if (snapshot.getKey() < 0 && hideScratch) { - return; - } - SnapshotRow row = new SnapshotRow(current.getTrace(), snapshot); - snapshotTableModel.add(row); - if (current.getSnap() == snapshot.getKey()) { - snapshotFilterPanel.setSelectedItem(row); - } - } - - private void snapChanged(TraceSnapshot snapshot) { - if (snapshot.getKey() < 0 && hideScratch) { - return; - } - snapshotTableModel.notifyUpdatedWith(row -> row.getSnapshot() == snapshot); - } - - private void snapDeleted(TraceSnapshot snapshot) { - if (snapshot.getKey() < 0 && hideScratch) { - return; - } - snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot); - } - } - protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) { if (!Objects.equals(a.getTrace(), b.getTrace())) { return false; @@ -162,28 +54,20 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { protected final DebuggerTimePlugin plugin; DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; - private Trace currentTrace; // copy for transition - - protected final SnapshotListener listener = new SnapshotListener(); @AutoServiceConsumed protected DebuggerTraceManagerService viewManager; @SuppressWarnings("unused") private final Wiring autoServiceWiring; - private final JPanel mainPanel = new JPanel(new BorderLayout()); - - /* testing */ final EnumeratedColumnTableModel snapshotTableModel = - new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class); - /* testing */ GTable snapshotTable; - /* testing */ GhidraTableFilterPanel snapshotFilterPanel; + /*testing*/ final DebuggerSnapshotTablePanel mainPanel = new DebuggerSnapshotTablePanel(); private DebuggerSnapActionContext myActionContext; ToggleDockingAction actionHideScratch; @AutoConfigStateField - /* testing */ boolean hideScratch = true; + /*testing*/ boolean hideScratch = true; public DebuggerTimeProvider(DebuggerTimePlugin plugin) { super(plugin.getTool(), TITLE_PROVIDER_TIME, plugin.getName()); @@ -198,7 +82,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { buildMainPanel(); - myActionContext = new DebuggerSnapActionContext(0); + myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getSnap()); createActions(); contextChanged(); @@ -224,42 +108,22 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { } protected void buildMainPanel() { - snapshotTable = new GTable(snapshotTableModel); - snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - mainPanel.add(new JScrollPane(snapshotTable)); - - snapshotFilterPanel = new GhidraTableFilterPanel<>(snapshotTable, snapshotTableModel); - mainPanel.add(snapshotFilterPanel, BorderLayout.SOUTH); - - snapshotTable.getSelectionModel().addListSelectionListener(evt -> { + mainPanel.getSelectionModel().addListSelectionListener(evt -> { if (evt.getValueIsAdjusting()) { return; } - SnapshotRow row = snapshotFilterPanel.getSelectedItem(); - if (row == null) { + Long snap = mainPanel.getSelectedSnapshot(); + if (snap == null) { myActionContext = null; return; } - long snap = row.getSnap(); - if (snap == current.getSnap().longValue()) { + if (snap.longValue() == current.getSnap().longValue()) { return; } - myActionContext = new DebuggerSnapActionContext(snap); + myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap); viewManager.activateSnap(snap); contextChanged(); }); - - TableColumnModel columnModel = snapshotTable.getColumnModel(); - TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal()); - snapCol.setPreferredWidth(40); - TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal()); - timeCol.setPreferredWidth(200); - TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal()); - etCol.setPreferredWidth(40); - TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal()); - schdCol.setPreferredWidth(60); - TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal()); - descCol.setPreferredWidth(200); } protected void createActions() { @@ -271,51 +135,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { private void activatedHideScratch(ActionContext ctx) { hideScratch = !hideScratch; - if (hideScratch) { - deleteScratchSnapshots(); - } - else { - loadScratchSnapshots(); - } - } - - private void addNewListeners() { - if (currentTrace == null) { - return; - } - currentTrace.addListener(listener); - } - - private void removeOldListeners() { - if (currentTrace == null) { - return; - } - currentTrace.removeListener(listener); - } - - protected void doSetTrace(Trace trace) { - if (currentTrace == trace) { - return; - } - removeOldListeners(); - currentTrace = trace; - addNewListeners(); - loadSnapshots(); - } - - protected void doSetSnap(long snap) { - SnapshotRow sel = snapshotFilterPanel.getSelectedItem(); - Long curSnap = sel == null ? null : sel.getSnap(); - if (curSnap != null && curSnap.longValue() == snap) { - return; - } - SnapshotRow row = snapshotTableModel.findFirst(r -> r.getSnap() == snap); - if (row == null) { - snapshotTable.clearSelection(); - } - else { - snapshotFilterPanel.setSelectedItem(row); - } + mainPanel.setHideScratchSnapshots(hideScratch); } public void coordinatesActivated(DebuggerCoordinates coordinates) { @@ -325,37 +145,8 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { } current = coordinates; - doSetTrace(current.getTrace()); - doSetSnap(current.getSnap()); - } - - protected void loadSnapshots() { - snapshotTableModel.clear(); - Trace curTrace = current.getTrace(); - if (curTrace == null) { - return; - } - TraceTimeManager manager = curTrace.getTimeManager(); - Collection snapshots = hideScratch - ? manager.getSnapshots(0, true, Long.MAX_VALUE, true) - : manager.getAllSnapshots(); - snapshotTableModel.addAll(Collections2.transform(snapshots, - s -> new SnapshotRow(curTrace, s))); - } - - protected void deleteScratchSnapshots() { - snapshotTableModel.deleteWith(s -> s.getSnap() < 0); - } - - protected void loadScratchSnapshots() { - Trace curTrace = current.getTrace(); - if (curTrace == null) { - return; - } - TraceTimeManager manager = curTrace.getTimeManager(); - snapshotTableModel.addAll(Collections2.transform( - manager.getSnapshots(Long.MIN_VALUE, true, 0, false), - s -> new SnapshotRow(curTrace, s))); + mainPanel.setTrace(current.getTrace()); + mainPanel.setSelectedSnapshot(current.getSnap()); } public void writeConfigState(SaveState saveState) { @@ -366,5 +157,6 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { CONFIG_STATE_HANDLER.readConfigState(this, saveState); actionHideScratch.setSelected(hideScratch); + mainPanel.setHideScratchSnapshots(hideScratch); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java new file mode 100644 index 0000000000..aa82540213 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java @@ -0,0 +1,193 @@ +/* ### + * 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.plugin.core.debug.gui.time; + +import java.awt.BorderLayout; +import java.util.function.Function; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import docking.DialogComponentProvider; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.framework.plugintool.PluginTool; +import ghidra.trace.model.Trace; +import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.util.MessageType; +import ghidra.util.Msg; + +public class DebuggerTimeSelectionDialog extends DialogComponentProvider { + + private final PluginTool tool; + + DebuggerSnapshotTablePanel snapshotPanel; + JTextField scheduleText; + TraceSchedule schedule; + + JButton tickStep; + JButton tickBack; + JButton opStep; + JButton opBack; + + public DebuggerTimeSelectionDialog(PluginTool tool) { + super("Select Time", true, true, true, false); + this.tool = tool; + populateComponents(); + } + + protected void doStep(Function stepper) { + try { + TraceSchedule stepped = stepper.apply(schedule); + if (stepped == null) { + return; + } + setScheduleText(stepped.toString()); + } + catch (Throwable e) { + Msg.warn(this, e.getMessage()); + } + } + + protected void populateComponents() { + JPanel workPanel = new JPanel(new BorderLayout()); + + { + Box hbox = Box.createHorizontalBox(); + hbox.setBorder(BorderFactory.createTitledBorder("Schedule")); + hbox.add(new JLabel("Expression: ")); + scheduleText = new JTextField(); + hbox.add(scheduleText); + hbox.add(new JLabel("Ticks: ")); + hbox.add(tickBack = new JButton(DebuggerResources.ICON_STEP_BACK)); + hbox.add(tickStep = new JButton(DebuggerResources.ICON_STEP_INTO)); + hbox.add(new JLabel("Ops: ")); + hbox.add(opBack = new JButton(DebuggerResources.ICON_STEP_BACK)); + hbox.add(opStep = new JButton(DebuggerResources.ICON_STEP_INTO)); + workPanel.add(hbox, BorderLayout.NORTH); + } + + tickBack.addActionListener(evt -> doStep(s -> s.steppedBackward(getTrace(), 1))); + tickStep.addActionListener(evt -> doStep(s -> s.steppedForward(null, 1))); + opBack.addActionListener(evt -> doStep(s -> s.steppedPcodeBackward(1))); + opStep.addActionListener(evt -> doStep(s -> s.steppedPcodeForward(null, 1))); + + { + snapshotPanel = new DebuggerSnapshotTablePanel(); + workPanel.add(snapshotPanel, BorderLayout.CENTER); + } + + snapshotPanel.getSelectionModel().addListSelectionListener(evt -> { + Long snap = snapshotPanel.getSelectedSnapshot(); + if (snap == null) { + return; + } + if (schedule.getSnap() == snap.longValue()) { + return; + } + scheduleText.setText(snap.toString()); + }); + + scheduleText.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + scheduleTextChanged(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + scheduleTextChanged(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + scheduleTextChanged(); + } + }); + + addWorkPanel(workPanel); + addOKButton(); + addCancelButton(); + + setMinimumSize(600, 600); + } + + protected void scheduleTextChanged() { + schedule = null; + try { + schedule = TraceSchedule.parse(scheduleText.getText()); + snapshotPanel.setSelectedSnapshot(schedule.getSnap()); + schedule.validate(getTrace()); + setStatusText(""); + setOkEnabled(true); + } + catch (Exception e) { + setStatusText(e.getMessage(), MessageType.ERROR); + setOkEnabled(false); + } + enableStepButtons(schedule != null); + } + + protected void enableStepButtons(boolean enabled) { + tickBack.setEnabled(enabled); + tickStep.setEnabled(enabled); + opBack.setEnabled(enabled); + opStep.setEnabled(enabled); + } + + @Override // Public for test access + public void okCallback() { + assert schedule != null; + super.okCallback(); + close(); + } + + @Override // Public for test access + public void cancelCallback() { + this.schedule = null; + super.cancelCallback(); + } + + @Override + public void close() { + super.close(); + snapshotPanel.setTrace(null); + snapshotPanel.setSelectedSnapshot(null); + } + + /** + * Prompts the user to select a snapshot and optionally specify a full schedule + * + * @param trace the trace from whose snapshots to select + * @param defaultTime, optionally the time to select initially + * @return the schedule, likely specifying just the snapshot selection + */ + public TraceSchedule promptTime(Trace trace, TraceSchedule defaultTime) { + snapshotPanel.setTrace(trace); + schedule = defaultTime; + scheduleText.setText(defaultTime.toString()); + tool.showDialog(this); + return schedule; + } + + public Trace getTrace() { + return snapshotPanel.getTrace(); + } + + public void setScheduleText(String text) { + scheduleText.setText(text); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index daa02b2ea5..498e9f7a78 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -661,44 +661,39 @@ public class DebuggerTraceManagerServicePlugin extends Plugin return current.getFrame(); } + @Override + public CompletableFuture materialize(DebuggerCoordinates coordinates) { + if (coordinates.getTime().isSnapOnly()) { + return CompletableFuture.completedFuture(coordinates.getSnap()); + } + Collection suitable = coordinates.getTrace() + .getTimeManager() + .getSnapshotsWithSchedule(coordinates.getTime()); + if (!suitable.isEmpty()) { + TraceSnapshot found = suitable.iterator().next(); + return CompletableFuture.completedFuture(found.getKey()); + } + if (emulationService == null) { + throw new IllegalStateException( + "Cannot navigate to coordinates with execution schedules, " + + "because the emulation service is not available."); + } + return emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime()); + } + protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) { TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView(); if (varView == null) { // Should only happen with NOWHERE fireLocationEvent(coordinates); + return; } - else if (coordinates.getTime().isSnapOnly()) { - varView.setSnap(coordinates.getSnap()); + materialize(coordinates).thenAcceptAsync(snap -> { + if (!coordinates.equals(current)) { + return; // We navigated elsewhere before emulation completed + } + varView.setSnap(snap); fireLocationEvent(coordinates); - } - else { - Collection suitable = coordinates.getTrace() - .getTimeManager() - .getSnapshotsWithSchedule(coordinates.getTime()); - if (!suitable.isEmpty()) { - TraceSnapshot found = suitable.iterator().next(); - varView.setSnap(found.getKey()); - fireLocationEvent(coordinates); - return; - } - if (emulationService == null) { - throw new IllegalStateException( - "Cannot navigate to coordinates with execution schedules, " + - "because the emulation service is not available."); - } - CompletableFuture bg = - emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime()); - bg.thenAccept(emuSnap -> Swing.runLater(() -> { - if (!coordinates.equals(current)) { - return; // We navigated elsewhere before emulation completed - } - varView.setSnap(emuSnap); - fireLocationEvent(coordinates); - })).exceptionally(ex -> { - Msg.showError(this, null, "Emulate", "Could not navigate to emulated coordinates", - ex); - return null; - }); - } + }, AsyncUtils.SWING_EXECUTOR); } protected void fireLocationEvent(DebuggerCoordinates coordinates) { @@ -892,6 +887,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin future.completeExceptionally(e); } } + }); } return future; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/BackgroundUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/BackgroundUtils.java index ce0d24eb1c..a35f337a0c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/BackgroundUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/BackgroundUtils.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.debug.utils; +import java.util.List; import java.util.concurrent.*; import java.util.function.BiFunction; @@ -24,8 +25,8 @@ import ghidra.framework.cmd.BackgroundCommand; import ghidra.framework.model.DomainObject; import ghidra.framework.model.UndoableDomainObject; import ghidra.framework.plugintool.PluginTool; -import ghidra.util.task.CancelledListener; -import ghidra.util.task.TaskMonitor; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.*; public enum BackgroundUtils { ; @@ -82,4 +83,59 @@ public enum BackgroundUtils { tool.executeBackgroundCommand(cmd, obj); return cmd; } + + public static class PluginToolExecutorService extends AbstractExecutorService { + private final PluginTool tool; + private String name; + private boolean canCancel; + private boolean hasProgress; + private boolean isModal; + private final int delay; + + public PluginToolExecutorService(PluginTool tool, String name, boolean canCancel, + boolean hasProgress, boolean isModal, int delay) { + this.tool = tool; + this.name = name; + this.canCancel = canCancel; + this.hasProgress = hasProgress; + this.isModal = isModal; + this.delay = delay; + } + + @Override + public void shutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public List shutdownNow() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public void execute(Runnable command) { + Task task = new Task(name, canCancel, hasProgress, isModal) { + @Override + public void run(TaskMonitor monitor) throws CancelledException { + command.run(); + } + }; + tool.execute(task, delay); + } + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerListingService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerListingService.java index 0dca9e813d..031c65922b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerListingService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerListingService.java @@ -17,19 +17,89 @@ package ghidra.app.services; import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.gui.listing.MultiBlendedListingBackgroundColorModel; +import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.framework.plugintool.ServiceInfo; import ghidra.program.model.address.Address; import ghidra.program.util.ProgramSelection; +/** + * A service providing access to the main listing panel + */ @ServiceInfo( // defaultProvider = DebuggerListingPlugin.class, // description = "Replacement CodeViewerService for Debugger" // ) public interface DebuggerListingService extends CodeViewerService { + /** + * A listener for changes in location tracking specification + */ + interface LocationTrackingSpecChangeListener { + /** + * The specification has changed + * + * @param spec the new specification + */ + void locationTrackingSpecChanged(LocationTrackingSpec spec); + } + + /** + * Set the tracking specification of the listing. Navigates immediately. + * + * @param spec the desired specification + */ void setTrackingSpec(LocationTrackingSpec spec); + /** + * Get the tracking specification of the listing. + * + * @return the current specification + */ + LocationTrackingSpec getTrackingSpec(); + + /** + * Add a listener for changes to the tracking specification. + * + * @param listener the listener to receive change notifications + */ + void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener); + + /** + * Remove a listener for changes to the tracking specification. + * + * @param listener the listener receiving change notifications + */ + void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener); + + /** + * Set the selection of addresses in this listing. + * + * @param selection the desired selection + */ void setCurrentSelection(ProgramSelection selection); + /** + * Navigate to the given address + * + * @param address the desired address + * @param centerOnScreen true to center the cursor in the listing + * @return true if the request was effective + */ boolean goTo(Address address, boolean centerOnScreen); + + /** + * Obtain a coloring background model suitable for the given listing + * + *

+ * This may be used, e.g., to style an alternative view in the same manner as listings managed + * by this service. Namely, this provides coloring for memory state and the user's cursor. + * Coloring for tracked locations and the marker service in general must still be added + * separately, since they incorporate additional dependencies. + * + * @param listingPanel the panel to be colored + * @return a blended background color model implementing the common debugger listing style + */ + MultiBlendedListingBackgroundColorModel createListingBackgroundColorModel( + ListingPanel listingPanel); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java index 4f60525cd3..6f7b3f73f4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java @@ -20,39 +20,117 @@ import java.util.concurrent.CompletableFuture; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; +import ghidra.async.AsyncReference; import ghidra.framework.model.DomainFile; import ghidra.framework.plugintool.ServiceInfo; +import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.TriConsumer; +/** + * The interface for managing open traces and navigating among them and their contents + */ @ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class) public interface DebuggerTraceManagerService { + + /** + * An adapter that works nicely with an {@link AsyncReference} + * + *

+ * TODO: Seems this is still leaking an implementation detail + */ public interface BooleanChangeAdapter extends TriConsumer { @Override default void accept(Boolean oldVal, Boolean newVal, Void cause) { changed(newVal); } + /** + * The value has changed + * + * @param value the new value + */ void changed(Boolean value); } + /** + * Get all the open traces + * + * @return all open traces + */ Collection getOpenTraces(); + /** + * Get the current coordinates + * + *

+ * This entails everything except the current address + * + * @return the current coordinates + */ DebuggerCoordinates getCurrent(); + /** + * Get the active trace + * + * @return the active trace, or null + */ Trace getCurrentTrace(); + /** + * Get the active view + * + *

+ * Every trace has an associated variable-snap view. When the manager navigates to a new point + * in time, it is accomplished by changing the snap of this view. This view is suitable for use + * in most places where a {@link Program} is ordinarily required. + * + * @return the active view, or null + */ TraceProgramView getCurrentView(); + /** + * Get the active thread + * + *

+ * It is possible to have an active trace, but no active thread. + * + * @return the active thread, or null + */ TraceThread getCurrentThread(); + /** + * Get the active thread for a given trace + * + *

+ * The manager remembers the last active thread for every open trace. If the trace has never + * been active, then the last active thread is null. If trace is the active trace, then this + * will return the currently active thread. + * + * @param trace the trace + * @return the thread, or null + */ TraceThread getCurrentThreadFor(Trace trace); + /** + * Get the active snap + * + *

+ * Note that if emulation was used to materialize the current coordinates, then the current snap + * will differ from the view's snap. + * + * @return the active snap, or 0 + */ long getCurrentSnap(); + /** + * Get the active frame + * + * @return the active frame, or 0 + */ int getCurrentFrame(); /** @@ -105,10 +183,23 @@ public interface DebuggerTraceManagerService { */ CompletableFuture saveTrace(Trace trace); + /** + * Close the given trace + * + * @param trace the trace to close + */ void closeTrace(Trace trace); + /** + * Close all traces + */ void closeAllTraces(); + /** + * Close all traces except the given one + * + * @param keep the trace to keep open + */ void closeOtherTraces(Trace keep); /** @@ -120,24 +211,84 @@ public interface DebuggerTraceManagerService { */ void closeDeadTraces(); + /** + * Activate the given coordinates + * + *

+ * This operation may be completed asynchronously, esp., if emulation is required to materialize + * the coordinates. The coordinates are "resolved" as a means of filling in missing parts. For + * example, if the thread is not specified, the manager may activate the last-active thread for + * the desired trace. + * + * @param coordinates the desired coordinates + */ void activate(DebuggerCoordinates coordinates); + /** + * Activate the given trace + * + * @param trace the desired trace + */ void activateTrace(Trace trace); + /** + * Activate the given thread + * + * @param thread the desired thread + */ void activateThread(TraceThread thread); + /** + * Activate the given snapshot key + * + * @param snap the desired snapshot key + */ void activateSnap(long snap); + /** + * Activate the given point in time, possibly invoking emulation + * + * @param time the desired schedule + */ void activateTime(TraceSchedule time); + /** + * Activate the given stack frame + * + * @param frameLevel the level of the desired frame, 0 being innermost + */ void activateFrame(int frameLevel); + /** + * Control whether the trace manager automatically activates the "present snapshot" + * + *

+ * Auto activation only applies when the current trace advances. It never changes to another + * trace. + * + * @param enabled true to enable auto activation + */ void setAutoActivatePresent(boolean enabled); + /** + * Check if the trace manager automatically activate the "present snapshot" + * + * @return true if auto activation is enabled + */ boolean isAutoActivatePresent(); + /** + * Add a listener for changes to auto activation enablement + * + * @param listener the listener to receive change notifications + */ void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener); + /** + * Remove a listener for changes to auto activation enablement + * + * @param listener the listener receiving change notifications + */ void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener); /** @@ -154,8 +305,18 @@ public interface DebuggerTraceManagerService { */ boolean isSynchronizeFocus(); + /** + * Add a listener for changes to focus synchronization enablement + * + * @param listener the listener to receive change notifications + */ void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener); + /** + * Remove a listener for changes to focus synchronization enablement + * + * @param listener the listener receiving change notifications + */ void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener); /** @@ -172,8 +333,18 @@ public interface DebuggerTraceManagerService { */ boolean isSaveTracesByDefault(); + /** + * Add a listener for changes to save-by-default enablement + * + * @param listener the listener to receive change notifications + */ void addSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener); + /** + * Remove a listener for changes to save-by-default enablement + * + * @param listener the listener receiving change notifications + */ void removeSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener); /** @@ -190,15 +361,40 @@ public interface DebuggerTraceManagerService { */ boolean isAutoCloseOnTerminate(); + /** + * Add a listener for changes to close-on-terminate enablement + * + * @param listener the listener to receive change notifications + */ void addAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener); + /** + * Remove a listener for changes to close-on-terminate enablement + * + * @param listener the listener receiving change notifications + */ void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener); /** - * Fill in an incomplete coordinate specification, using the manager's "best judgement" + * Fill in an incomplete coordinate specification, using the manager's "best judgment" * * @param coords the possibly-incomplete coordinates * @return the complete resolved coordinates */ - DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coords); + DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coordinates); + + /** + * Materialize the given coordinates to a snapshot in the same trace + * + *

+ * If the given coordinates do not require emulation, then this must complete immediately with + * the snapshot key given by the coordinates. If the given schedule is already materialized in + * the trace, then this may complete immediately with the previously-materialized snapshot key. + * Otherwise, this must invoke emulation, store the result into a chosen snapshot, and complete + * with its key. + * + * @param coordinates the coordinates to materialize + * @return a future that completes with the snapshot key of the materialized coordinates + */ + CompletableFuture materialize(DebuggerCoordinates coordinates); } diff --git a/Ghidra/Debug/Debugger/src/main/resources/images/table_relationship.png b/Ghidra/Debug/Debugger/src/main/resources/images/table_relationship.png new file mode 100644 index 0000000000..28b8505c0e Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/resources/images/table_relationship.png differ diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPluginScreenShots.java new file mode 100644 index 0000000000..0ccc5377a1 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPluginScreenShots.java @@ -0,0 +1,128 @@ +/* ### + * 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.plugin.core.debug.gui.diff; + +import static org.junit.Assert.assertTrue; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.junit.*; + +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider; +import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog; +import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.async.AsyncTestUtils; +import ghidra.test.ToyProgramBuilder; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.database.memory.DBTraceMemoryManager; +import ghidra.trace.database.thread.DBTraceThread; +import ghidra.trace.database.time.DBTraceTimeManager; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.util.Swing; +import ghidra.util.database.UndoableTransaction; +import help.screenshot.GhidraScreenShotGenerator; + +public class DebuggerTraceViewDiffPluginScreenShots extends GhidraScreenShotGenerator + implements AsyncTestUtils { + + DebuggerTraceManagerService traceManager; + DebuggerTraceViewDiffPlugin diffPlugin; + DebuggerListingPlugin listingPlugin; + DebuggerListingProvider listingProvider; + ToyDBTraceBuilder tb; + + @Before + public void setUpMine() throws Throwable { + traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class); + diffPlugin = addPlugin(tool, DebuggerTraceViewDiffPlugin.class); + listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); + listingProvider = waitForComponentProvider(DebuggerListingProvider.class); + + tb = new ToyDBTraceBuilder("tictactoe", ToyProgramBuilder._X64); + } + + @After + public void tearDownMine() { + tb.close(); + } + + @Test + public void testCaptureDebuggerTraceViewDiffPlugin() throws Throwable { + long snap1, snap2; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceTimeManager tm = tb.trace.getTimeManager(); + snap1 = tm.createSnapshot("Baseline").getKey(); + snap2 = tm.createSnapshot("X's first move").getKey(); + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.createRegion(".data", snap1, tb.range(0x00600000, 0x0060ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + + ByteBuffer buf = ByteBuffer.allocate(0x1000).order(ByteOrder.LITTLE_ENDIAN); + buf.put((byte) 'X'); + buf.putInt(3); + buf.putInt(3); + for (int i = 0; i < 9; i++) { + buf.put((byte) ' '); + } + buf.flip(); + buf.limit(0x1000); + mm.putBytes(snap1, tb.addr(0x00600000), buf); + + buf.put(0, (byte) 'O'); + buf.put(13, (byte) 'X'); + buf.position(0); + buf.limit(0x1000); + mm.putBytes(snap2, tb.addr(0x00600000), buf); + } + + traceManager.openTrace(tb.trace); + traceManager.activateTrace(tb.trace); + traceManager.activateSnap(snap1); + waitForSwing(); + + waitOn(diffPlugin.startComparison(TraceSchedule.snap(snap2))); + assertTrue(diffPlugin.gotoNextDiff()); + + captureIsolatedProvider(DebuggerListingProvider.class, 900, 600); + } + + @Test + public void testCaptureDebuggerTimeSelectionDialog() throws Throwable { + DBTraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceTimeManager tm = tb.trace.getTimeManager(); + thread = tb.getOrAddThread("main", 0); + tm.createSnapshot("Break on main").setEventThread(thread); + tm.createSnapshot("Game started").setEventThread(thread); + tm.createSnapshot("X's moved").setEventThread(thread); + tm.createSnapshot("O's moved").setEventThread(thread); + } + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + performAction(diffPlugin.actionCompare, false); + DebuggerTimeSelectionDialog dialog = + waitForDialogComponent(DebuggerTimeSelectionDialog.class); + Swing.runNow(() -> dialog.setScheduleText("2")); + + captureDialog(dialog); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java index a8906486d3..fc6b48d048 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.*; import java.awt.*; import java.awt.event.*; +import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.math.BigInteger; @@ -40,11 +41,13 @@ import org.junit.runner.Description; import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; import generic.Unique; +import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal; import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.services.*; +import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.dbg.model.AbstractTestTargetRegisterBank; import ghidra.dbg.model.TestDebuggerModelBuilder; import ghidra.dbg.target.*; @@ -57,6 +60,7 @@ import ghidra.program.model.data.DataType; import ghidra.program.model.lang.*; import ghidra.program.model.listing.Program; import ghidra.program.util.DefaultLanguageService; +import ghidra.program.util.ProgramLocation; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; import ghidra.trace.database.ToyDBTraceBuilder; @@ -409,6 +413,64 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest clickMouse(button, m); } + protected static void assertListingBackgroundAt(Color expected, ListingPanel panel, + Address addr, int yAdjust) throws AWTException, InterruptedException { + ProgramLocation oneBack = new ProgramLocation(panel.getProgram(), addr.previous()); + runSwing(() -> panel.goTo(addr)); + runSwing(() -> panel.goTo(oneBack, false)); + waitForPass(() -> { + Rectangle r = panel.getBounds(); + // Capture off screen, so that focus/stacking doesn't matter + BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + try { + runSwing(() -> panel.paint(g)); + } + finally { + g.dispose(); + } + Point locP = panel.getLocationOnScreen(); + Point locFP = panel.getLocationOnScreen(); + locFP.translate(-locP.x, -locP.y); + Rectangle cursor = panel.getCursorBounds(); + assertNotNull("Cannot get cursor bounds", cursor); + Color actual = new Color(image.getRGB(locFP.x + cursor.x - 1, + locFP.y + cursor.y + cursor.height * 3 / 2 + yAdjust)); + assertEquals(expected, actual); + }); + } + + protected static void goTo(ListingPanel listingPanel, ProgramLocation location) { + waitForPass(() -> { + runSwing(() -> listingPanel.goTo(location)); + ProgramLocation confirm = listingPanel.getCursorLocation(); + assertNotNull(confirm); + assertEquals(location.getAddress(), confirm.getAddress()); + }); + } + + protected static LocationTrackingSpec getLocationTrackingSpec(String name) { + return LocationTrackingSpec.fromConfigName(name); + } + + protected static AutoReadMemorySpec getAutoReadMemorySpec(String name) { + return AutoReadMemorySpec.fromConfigName(name); + } + + protected final LocationTrackingSpec trackNone = + getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME); + protected final LocationTrackingSpec trackPc = + getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME); + protected final LocationTrackingSpec trackSp = + getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME); + + protected final AutoReadMemorySpec readNone = + getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME); + protected final AutoReadMemorySpec readVisible = + getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME); + protected final AutoReadMemorySpec readVisROOnce = + getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME); + protected TestEnv env; protected PluginTool tool; @@ -468,8 +530,12 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest if (mb != null) { if (mb.testModel != null) { - // TODO: Stop recordings, too? modelService.removeModel(mb.testModel); + + runSwing(() -> traceManager.setSaveTracesByDefault(false)); + for (TraceRecorder recorder : modelService.getTraceRecorders()) { + recorder.stopRecording(); + } } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPluginTest.java new file mode 100644 index 0000000000..c7c9445079 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPluginTest.java @@ -0,0 +1,233 @@ +/* ### + * 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.plugin.core.debug.gui.diff; + +import static org.junit.Assert.*; + +import java.nio.ByteBuffer; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider; +import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog; +import ghidra.async.AsyncTestUtils; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.database.memory.DBTraceMemoryManager; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.util.Swing; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerTraceViewDiffPluginTest extends AbstractGhidraHeadedDebuggerGUITest + implements AsyncTestUtils { + + protected DebuggerTraceViewDiffPlugin traceDiffPlugin; + protected DebuggerListingPlugin listingPlugin; + + protected DebuggerListingProvider listingProvider; + + @Before + public void setUpTraceViewDiffPluginTest() throws Exception { + traceDiffPlugin = addPlugin(tool, DebuggerTraceViewDiffPlugin.class); + listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); + + listingProvider = waitForComponentProvider(DebuggerListingProvider.class); + } + + @Test + public void testActionCompareConfirm() throws Exception { + assertFalse(traceDiffPlugin.actionCompare.isEnabled()); + assertNull(listingPlugin.getProvider().getOtherPanel()); + + createAndOpenTrace(); + traceManager.activateTrace(tb.trace); + waitForSwing(); + + assertTrue(traceDiffPlugin.actionCompare.isEnabled()); + performAction(traceDiffPlugin.actionCompare, false); + + DebuggerTimeSelectionDialog dialog = + waitForDialogComponent(DebuggerTimeSelectionDialog.class); + Swing.runNow(() -> { + dialog.setScheduleText("0"); + dialog.okCallback(); + }); + waitForSwing(); + + assertNotNull(listingPlugin.getProvider().getOtherPanel()); + } + + @Test + public void testActionCompareCancel() throws Exception { + assertFalse(traceDiffPlugin.actionCompare.isEnabled()); + assertNull(listingPlugin.getProvider().getOtherPanel()); + + createAndOpenTrace(); + traceManager.activateTrace(tb.trace); + waitForSwing(); + + assertTrue(traceDiffPlugin.actionCompare.isEnabled()); + performAction(traceDiffPlugin.actionCompare, false); + + DebuggerTimeSelectionDialog dialog = + waitForDialogComponent(DebuggerTimeSelectionDialog.class); + Swing.runNow(() -> { + dialog.setScheduleText("0"); + dialog.cancelCallback(); + }); + waitForSwing(); + + assertNull(listingPlugin.getProvider().getOtherPanel()); + } + + // TODO: Test schedule input validation? + // TODO: Test stepping buttons? + + @Test + public void testActionCompareClosesWhenAlreadyActive() throws Exception { + assertFalse(traceDiffPlugin.actionCompare.isEnabled()); + assertNull(listingPlugin.getProvider().getOtherPanel()); + + createAndOpenTrace(); + traceManager.activateTrace(tb.trace); + waitForSwing(); + + assertTrue(traceDiffPlugin.actionCompare.isEnabled()); + performAction(traceDiffPlugin.actionCompare, false); + + DebuggerTimeSelectionDialog dialog = + waitForDialogComponent(DebuggerTimeSelectionDialog.class); + Swing.runNow(() -> { + dialog.setScheduleText("0"); + dialog.okCallback(); + }); + waitForSwing(); + + assertNotNull(listingPlugin.getProvider().getOtherPanel()); + + assertTrue(traceDiffPlugin.actionCompare.isEnabled()); + performAction(traceDiffPlugin.actionCompare, false); + assertNull(listingPlugin.getProvider().getOtherPanel()); + } + + @Test + public void testColorsDiffBytes() throws Throwable { + createAndOpenTrace(); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + + ByteBuffer buf = ByteBuffer.allocate(0x1000); // Yes, smaller than .text + buf.limit(0x1000); + mm.putBytes(0, tb.addr(0x00400000), buf); + buf.position(0); + buf.putLong(0x0123, 0x1122334455667788L); + mm.putBytes(1, tb.addr(0x00400000), buf); + } + traceManager.activateTrace(tb.trace); + waitForSwing(); + + waitOn(traceDiffPlugin.startComparison(TraceSchedule.snap(1))); + + assertListingBackgroundAt(DebuggerTraceViewDiffPlugin.DEFAULT_DIFF_COLOR, + traceDiffPlugin.altListingPanel, tb.addr(0x00400123), 0); + assertListingBackgroundAt(DebuggerTraceViewDiffPlugin.DEFAULT_DIFF_COLOR, + listingProvider.getListingPanel(), tb.addr(0x00400123), 0); + + AddressSetView expected = tb.set(tb.range(0x00400123, 0x0040012a)); + assertEquals(expected, Swing.runNow(() -> traceDiffPlugin.diffMarkersL.getAddressSet())); + assertEquals(expected, Swing.runNow(() -> traceDiffPlugin.diffMarkersR.getAddressSet())); + + Swing.runNow(() -> traceDiffPlugin.endComparison()); + + assertTrue(Swing.runNow(() -> traceDiffPlugin.diffMarkersL.getAddressSet()).isEmpty()); + assertTrue(Swing.runNow(() -> traceDiffPlugin.diffMarkersR.getAddressSet()).isEmpty()); + } + + @Test + public void testActionPrevDiff() throws Throwable { + createAndOpenTrace(); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + + ByteBuffer buf = ByteBuffer.allocate(0x1000); // Yes, smaller than .text + buf.limit(0x1000); + mm.putBytes(0, tb.addr(0x00400000), buf); + buf.position(0); + buf.putLong(0x0123, 0x1122334455667788L); + buf.putLong(0x0321, 0x1122334455667788L); + mm.putBytes(1, tb.addr(0x00400000), buf); + } + traceManager.activateTrace(tb.trace); + waitForSwing(); + + waitOn(traceDiffPlugin.startComparison(TraceSchedule.snap(1))); + + assertFalse(traceDiffPlugin.actionPrevDiff.isEnabled()); + goTo(listingProvider.getListingPanel(), + new ProgramLocation(tb.trace.getProgramView(), tb.addr(0x00401000))); + waitForSwing(); + + assertTrue(traceDiffPlugin.actionPrevDiff.isEnabled()); + performAction(traceDiffPlugin.actionPrevDiff); + assertEquals(tb.addr(0x00400328), traceDiffPlugin.getCurrentAddress()); + + assertTrue(traceDiffPlugin.actionPrevDiff.isEnabled()); + performAction(traceDiffPlugin.actionPrevDiff); + assertEquals(tb.addr(0x0040012a), traceDiffPlugin.getCurrentAddress()); + + assertFalse(traceDiffPlugin.actionPrevDiff.isEnabled()); + } + + @Test + public void testActionNextDiff() throws Throwable { + createAndOpenTrace(); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + + ByteBuffer buf = ByteBuffer.allocate(0x1000); // Yes, smaller than .text + buf.limit(0x1000); + mm.putBytes(0, tb.addr(0x00400000), buf); + buf.position(0); + buf.putLong(0x0123, 0x1122334455667788L); + buf.putLong(0x0321, 0x1122334455667788L); + mm.putBytes(1, tb.addr(0x00400000), buf); + } + traceManager.activateTrace(tb.trace); + waitForSwing(); + + waitOn(traceDiffPlugin.startComparison(TraceSchedule.snap(1))); + + assertTrue(traceDiffPlugin.actionNextDiff.isEnabled()); + performAction(traceDiffPlugin.actionNextDiff); + waitForPass(() -> assertEquals(tb.addr(0x00400123), traceDiffPlugin.getCurrentAddress())); + + assertTrue(traceDiffPlugin.actionNextDiff.isEnabled()); + performAction(traceDiffPlugin.actionNextDiff); + assertEquals(tb.addr(0x00400321), traceDiffPlugin.getCurrentAddress()); + + assertFalse(traceDiffPlugin.actionNextDiff.isEnabled()); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index 42bfe2da00..ed9bc6f32f 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.gui.listing; import static ghidra.lifecycle.Unfinished.TODO; import static org.junit.Assert.*; -import java.awt.*; -import java.awt.image.BufferedImage; +import java.awt.Color; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; @@ -37,14 +36,13 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction; -import ghidra.app.plugin.core.debug.gui.action.*; +import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow; import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.services.*; -import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.async.SwingExecutorService; import ghidra.framework.model.*; import ghidra.plugin.importer.ImporterPlugin; @@ -68,27 +66,6 @@ import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest { - static LocationTrackingSpec getLocationTrackingSpec(String name) { - return LocationTrackingSpec.fromConfigName(name); - } - - static AutoReadMemorySpec getAutoReadMemorySpec(String name) { - return AutoReadMemorySpec.fromConfigName(name); - } - - final LocationTrackingSpec trackNone = - getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME); - final LocationTrackingSpec trackPc = - getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME); - final LocationTrackingSpec trackSp = - getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME); - - final AutoReadMemorySpec readNone = - getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME); - final AutoReadMemorySpec readVisible = - getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME); - final AutoReadMemorySpec readVisROOnce = - getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME); protected DebuggerListingPlugin listingPlugin; protected DebuggerListingProvider listingProvider; @@ -110,12 +87,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI } protected void goToDyn(ProgramLocation location) { - waitForPass(() -> { - runSwing(() -> listingProvider.goTo(location.getProgram(), location)); - ProgramLocation confirm = listingProvider.getLocation(); - assertNotNull(confirm); - assertEquals(location.getAddress(), confirm.getAddress()); - }); + goTo(listingProvider.getListingPanel(), location); } protected static byte[] incBlock() { @@ -572,32 +544,6 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI assertEquals(ss.getAddress(0x00601234), loc.getAddress()); } - protected void assertListingBackgroundAt(Color expected, ListingPanel panel, - Address addr, int yAdjust) throws AWTException, InterruptedException { - ProgramLocation oneBack = new ProgramLocation(panel.getProgram(), addr.previous()); - runSwing(() -> panel.goTo(addr)); - runSwing(() -> panel.goTo(oneBack, false)); - waitForPass(() -> { - Rectangle r = panel.getBounds(); - // Capture off screen, so that focus/stacking doesn't matter - BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB); - Graphics g = image.getGraphics(); - try { - runSwing(() -> panel.paint(g)); - } - finally { - g.dispose(); - } - Point locP = panel.getLocationOnScreen(); - Point locFP = panel.getLocationOnScreen(); - locFP.translate(-locP.x, -locP.y); - Rectangle cursor = panel.getCursorBounds(); - Color actual = new Color(image.getRGB(locFP.x + cursor.x - 1, - locFP.y + cursor.y + cursor.height * 3 / 2 + yAdjust)); - assertEquals(expected, actual); - }); - } - @Test public void testDynamicListingMarksTrackedRegister() throws Exception { createAndOpenTrace(); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java index 011d92f4c7..30612df880 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java @@ -66,26 +66,6 @@ import ghidra.util.database.UndoableTransaction; @Category(NightlyCategory.class) public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebuggerGUITest { - static LocationTrackingSpec getLocationTrackingSpec(String name) { - return LocationTrackingSpec.fromConfigName(name); - } - - static AutoReadMemorySpec getAutoReadMemorySpec(String name) { - return AutoReadMemorySpec.fromConfigName(name); - } - - final LocationTrackingSpec trackNone = - getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME); - final LocationTrackingSpec trackPc = - getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME); - final LocationTrackingSpec trackSp = - getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME); - - final AutoReadMemorySpec readNone = getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME); - final AutoReadMemorySpec readVisible = - getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME); - final AutoReadMemorySpec readVisROOnce = - getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME); protected DebuggerMemoryBytesPlugin memBytesPlugin; protected DebuggerMemoryBytesProvider memBytesProvider; @@ -104,7 +84,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge protected void goToDyn(ProgramLocation location) { waitForPass(() -> { runSwing(() -> memBytesProvider.goTo(location.getProgram(), location)); - ProgramLocation confirm = memBytesProvider.getLocation(); + ProgramLocation confirm = runSwing(() -> memBytesProvider.getLocation()); assertNotNull(confirm); assertEquals(location.getAddress(), confirm.getAddress()); }); @@ -316,8 +296,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge //Pre-check assertNull(memBytesProvider.getLocation()); - memBytesProvider.setTrackingSpec(trackSp); - waitForSwing(); + runSwing(() -> memBytesProvider.setTrackingSpec(trackSp)); ProgramLocation loc = memBytesProvider.getLocation(); assertEquals(tb.trace.getProgramView(), loc.getProgram()); @@ -326,7 +305,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge @Test public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception { - memBytesProvider.setTrackingSpec(trackNone); + runSwing(() -> memBytesProvider.setTrackingSpec(trackNone)); try ( // ToyDBTraceBuilder b1 = new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); // @@ -377,7 +356,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge @Test public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception { - memBytesProvider.setTrackingSpec(trackNone); + runSwing(() -> memBytesProvider.setTrackingSpec(trackNone)); try ( // ToyDBTraceBuilder b1 = new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); // @@ -483,7 +462,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge byte[] zero = new byte[data.length]; ByteBuffer buf = ByteBuffer.allocate(data.length); assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec()); - memBytesProvider.setAutoReadMemorySpec(readNone); + runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone)); createTestModel(); mb.createTestProcessesAndThreads(); @@ -517,10 +496,19 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge assertArrayEquals(zero, buf.array()); /** - * NOTE: Should read immediately upon setting auto-read, but we're not focused on the + * Assure ourselves the block under test is not on screen + */ + waitForPass(() -> { + AddressSetView visible = memBytesProvider.readsMemTrait.getVisible(); + assertFalse(visible.isEmpty()); + assertFalse(visible.contains(addr(trace, 0x55550000))); + assertFalse(visible.contains(addr(trace, 0x55550fff))); + }); + /** + * NOTE: Should read immediately upon setting auto-read, but we're not looking at the * written block */ - memBytesProvider.setAutoReadMemorySpec(readVisROOnce); + runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readVisROOnce)); waitForDomainObject(trace); buf.clear(); assertEquals(data.length, @@ -595,13 +583,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge public static void setActionStateWithTrigger(MultiStateDockingAction action, T userData, EventTrigger trigger) { - for (ActionState actionState : action.getAllActionStates()) { - if (actionState.getUserData() == userData) { - action.setCurrentActionStateWithTrigger(actionState, trigger); - return; + runSwing(() -> { + for (ActionState actionState : action.getAllActionStates()) { + if (actionState.getUserData() == userData) { + action.setCurrentActionStateWithTrigger(actionState, trigger); + return; + } } - } - fail("Invalid action state user data"); + fail("Invalid action state user data"); + }); } @Test @@ -682,8 +672,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge waitForSwing(); assertEquals(tb.addr(0x1fff8765), memBytesProvider.getLocation().getAddress()); - memBytesProvider.setTrackingSpec(trackNone); - waitForSwing(); + runSwing(() -> memBytesProvider.setTrackingSpec(trackNone)); assertEquals(trackNone, memBytesProvider.actionTrackLocation.getCurrentUserData()); } @@ -747,7 +736,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge byte[] zero = new byte[data.length]; ByteBuffer buf = ByteBuffer.allocate(data.length); assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled()); - memBytesProvider.setAutoReadMemorySpec(readNone); + runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone)); // To verify enabled requires live target createAndOpenTrace(); @@ -845,18 +834,16 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec()); assertEquals(readVisROOnce, memBytesProvider.actionAutoReadMemory.getCurrentUserData()); - memBytesProvider.actionAutoReadMemory.setCurrentActionStateByUserData(readNone); - waitForSwing(); + runSwing( + () -> memBytesProvider.actionAutoReadMemory.setCurrentActionStateByUserData(readNone)); assertEquals(readNone, memBytesProvider.getAutoReadMemorySpec()); assertEquals(readNone, memBytesProvider.actionAutoReadMemory.getCurrentUserData()); - memBytesProvider.setAutoReadMemorySpec(readVisROOnce); - waitForSwing(); + runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readVisROOnce)); assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec()); assertEquals(readVisROOnce, memBytesProvider.actionAutoReadMemory.getCurrentUserData()); - memBytesProvider.setAutoReadMemorySpec(readNone); - waitForSwing(); + runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone)); assertEquals(readNone, memBytesProvider.getAutoReadMemorySpec()); assertEquals(readNone, memBytesProvider.actionAutoReadMemory.getCurrentUserData()); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProviderTest.java index c09ec92354..5a9d931dce 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProviderTest.java @@ -23,7 +23,14 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import docking.ActionContext; +import docking.action.ActionContextProvider; +import docking.action.DockingActionIf; +import docking.widgets.dialogs.InputDialog; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider; +import ghidra.trace.database.time.DBTraceSnapshot; import ghidra.trace.database.time.DBTraceTimeManager; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; @@ -64,11 +71,11 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes } protected void assertProviderEmpty() { - assertTrue(timeProvider.snapshotTableModel.getModelData().isEmpty()); + assertTrue(timeProvider.mainPanel.snapshotTableModel.getModelData().isEmpty()); } protected void assertProviderPopulated() { - List snapsDisplayed = timeProvider.snapshotTableModel.getModelData(); + List snapsDisplayed = timeProvider.mainPanel.snapshotTableModel.getModelData(); // I should be able to assume this is sorted by key assertEquals(2, snapsDisplayed.size()); @@ -85,6 +92,59 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes // Timestamp is left unchecked, since default is current time } + protected static void assertDisabled(ActionContextProvider provider, DockingActionIf action) { + ActionContext context = provider.getActionContext(null); + assertFalse(action.isEnabledForContext(context)); + } + + protected static void assertEnabled(ActionContextProvider provider, DockingActionIf action) { + ActionContext context = provider.getActionContext(null); + assertTrue(action.isEnabledForContext(context)); + } + + protected static void performEnabledAction(ActionContextProvider provider, + DockingActionIf action, boolean wait) { + ActionContext context = provider.getActionContext(null); + waitForCondition(() -> action.isEnabledForContext(context)); + performAction(action, context, wait); + } + + @Test // TODO: Technically, this is a plugin action.... Different test case? + public void testActionRenameSnapshot() throws Exception { + // More often than not, this action will be used from the dynamic listing + addPlugin(tool, DebuggerListingPlugin.class); + DebuggerListingProvider listingProvider = + waitForComponentProvider(DebuggerListingProvider.class); + assertDisabled(listingProvider, timePlugin.actionRenameSnapshot); + + createSnaplessTrace(); + addSnapshots(); + assertDisabled(listingProvider, timePlugin.actionRenameSnapshot); + + traceManager.openTrace(tb.trace); + waitForSwing(); + assertDisabled(listingProvider, timePlugin.actionRenameSnapshot); + + traceManager.activateTrace(tb.trace); + waitForSwing(); + assertEnabled(listingProvider, timePlugin.actionRenameSnapshot); + + traceManager.activateSnap(10); + waitForSwing(); + performEnabledAction(listingProvider, timePlugin.actionRenameSnapshot, false); + InputDialog dialog = waitForDialogComponent(InputDialog.class); + assertEquals("Snap 10", dialog.getValue()); + + dialog.setValue("My Snapshot"); + dialog.close(); // isCancelled (private) defaults to false + waitForSwing(); + + DBTraceSnapshot snapshot = tb.trace.getTimeManager().getSnapshot(10, false); + assertEquals("My Snapshot", snapshot.getDescription()); + + // TODO: Test cancelled has no effect + } + @Test public void testEmpty() { assertProviderEmpty(); @@ -158,7 +218,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes } waitForDomainObject(tb.trace); - assertEquals(1, timeProvider.snapshotTableModel.getModelData().size()); + assertEquals(1, timeProvider.mainPanel.snapshotTableModel.getModelData().size()); } @Test @@ -238,7 +298,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes traceManager.activateTrace(tb.trace); waitForSwing(); - SnapshotRow row = timeProvider.snapshotTableModel.getModelData().get(0); + SnapshotRow row = timeProvider.mainPanel.snapshotTableModel.getModelData().get(0); runSwing(() -> row.setDescription("Custom Description")); waitForDomainObject(tb.trace); @@ -258,14 +318,14 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes traceManager.activateTrace(tb.trace); waitForSwing(); - List data = timeProvider.snapshotTableModel.getModelData(); + List data = timeProvider.mainPanel.snapshotTableModel.getModelData(); - timeProvider.snapshotFilterPanel.setSelectedItem(data.get(0)); + timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(0)); waitForSwing(); assertEquals(0, traceManager.getCurrentSnap()); - timeProvider.snapshotFilterPanel.setSelectedItem(data.get(1)); + timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(1)); waitForSwing(); assertEquals(10, traceManager.getCurrentSnap()); @@ -283,22 +343,22 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes traceManager.activateTrace(tb.trace); waitForSwing(); - List data = timeProvider.snapshotTableModel.getModelData(); + List data = timeProvider.mainPanel.snapshotTableModel.getModelData(); traceManager.activateSnap(0); waitForSwing(); - assertEquals(data.get(0), timeProvider.snapshotFilterPanel.getSelectedItem()); + assertEquals(data.get(0), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem()); traceManager.activateSnap(10); waitForSwing(); - assertEquals(data.get(1), timeProvider.snapshotFilterPanel.getSelectedItem()); + assertEquals(data.get(1), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem()); traceManager.activateSnap(5); waitForSwing(); - assertNull(timeProvider.snapshotFilterPanel.getSelectedItem()); + assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem()); } @Test @@ -312,7 +372,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes traceManager.activateTrace(tb.trace); waitForSwing(); - List data = timeProvider.snapshotTableModel.getModelData(); + List data = timeProvider.mainPanel.snapshotTableModel.getModelData(); assertEquals(2, data.size()); for (SnapshotRow row : data) { assertTrue(row.getSnap() >= 0); @@ -329,12 +389,12 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes traceManager.activateTrace(tb.trace); waitForSwing(); - assertEquals(2, timeProvider.snapshotTableModel.getModelData().size()); + assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size()); addScratchSnapshot(); waitForDomainObject(tb.trace); - List data = timeProvider.snapshotTableModel.getModelData(); + List data = timeProvider.mainPanel.snapshotTableModel.getModelData(); assertEquals(2, data.size()); for (SnapshotRow row : data) { assertTrue(row.getSnap() >= 0); @@ -353,17 +413,17 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes waitForSwing(); assertEquals(true, timeProvider.hideScratch); - assertEquals(2, timeProvider.snapshotTableModel.getModelData().size()); + assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size()); performAction(timeProvider.actionHideScratch); assertEquals(false, timeProvider.hideScratch); - assertEquals(3, timeProvider.snapshotTableModel.getModelData().size()); + assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size()); performAction(timeProvider.actionHideScratch); assertEquals(true, timeProvider.hideScratch); - assertEquals(2, timeProvider.snapshotTableModel.getModelData().size()); + assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size()); } @Test @@ -380,6 +440,6 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes waitForSwing(); assertEquals(false, timeProvider.hideScratch); - assertEquals(3, timeProvider.snapshotTableModel.getModelData().size()); + assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size()); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java index 1590f03fca..9fb62a9e82 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java @@ -327,6 +327,17 @@ public class DBTraceMemoryManager return delegateRead(start.getAddressSpace(), m -> m.getBufferAt(snap, start, byteOrder)); } + @Override + public Long getSnapOfMostRecentChangeToBlock(long snap, Address address) { + return delegateRead(address.getAddressSpace(), + m -> m.getSnapOfMostRecentChangeToBlock(snap, address)); + } + + @Override + public int getBlockSize() { + return DBTraceMemorySpace.BLOCK_SIZE; + } + @Override public void pack() { delegateWriteAll(getActiveSpaces(), m -> m.pack()); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java index c4ef09557d..60a5c08353 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java @@ -884,6 +884,26 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace return false; } + @Override + public Long getSnapOfMostRecentChangeToBlock(long snap, Address address) { + assertInSpace(address); + try (LockHold hold = LockHold.lock(lock.readLock())) { + long offset = address.getOffset(); + long roundOffset = offset & BLOCK_MASK; + OffsetSnap loc = new OffsetSnap(roundOffset, snap); + DBTraceMemoryBlockEntry ent = findMostRecentBlockEntry(loc, true); + if (ent == null) { + return null; + } + return ent.getSnap(); + } + } + + @Override + public int getBlockSize() { + return BLOCK_SIZE; + } + public long getFirstChange(Range span, AddressRange range) { assertInSpace(range); long lower = DBTraceUtils.lowerEndpoint(span); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java index 0f5bc95cab..bb63c1d9b9 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java @@ -472,6 +472,32 @@ public interface TraceMemoryOperations { : ByteOrder.LITTLE_ENDIAN); } + /** + * Find the internal storage block that most-recently defines the value at the given snap and + * address, and return the block's snap. + * + *

+ * This method reveals portions of the internal storage so that clients can optimize difference + * computations by eliminating corresponding ranges defined by the same block. If the underlying + * implementation cannot answer this question, this returns the given snap. + * + * @param snap the time + * @param address the location + * @return the most snap for the most recent containing block + */ + Long getSnapOfMostRecentChangeToBlock(long snap, Address address); + + /** + * Get the block size used by internal storage. + * + *

+ * This method reveals portions of the internal storage so that clients can optimize searches. + * If the underlying implementation cannot answer this question, this returns 0. + * + * @return the block size + */ + int getBlockSize(); + /** * Optimize storage space * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java index 8e1e1c0a29..9b0b3def02 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java @@ -368,7 +368,7 @@ public class Sequence implements Comparable { * * @param trace the trace to which the machine is bound * @param eventThread the thread for the first step, if it applies to the "last thread" - * @param machine the machine to step + * @param machine the machine to step, or null to validate the sequence * @param action the action to step each thread * @param monitor a monitor for cancellation and progress reports * @return the last trace thread stepped during execution @@ -384,6 +384,22 @@ public class Sequence implements Comparable { return thread; } + /** + * Validate this sequence for the given trace + * + * @param trace the trace + * @param eventThread the thread for the first step, if it applies to the "last thread" + * @return the last trace thread that would be stepped by this sequence + */ + public TraceThread validate(Trace trace, TraceThread eventThread) { + try { + return execute(trace, eventThread, null, null, null); + } + catch (CancelledException e) { + throw new AssertionError(e); + } + } + /** * Get the key of the last thread stepped * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java index 2c87399a9d..88287559a0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java @@ -91,8 +91,8 @@ public interface Step extends Comparable { TraceThread thread = isEventThread() ? eventThread : tm.getThread(getThreadKey()); if (thread == null) { if (isEventThread()) { - throw new IllegalArgumentException( - "Thread key -1 can only be used if last/event thread is given"); + throw new IllegalArgumentException("Thread must be given, e.g., 0:t1-3, " + + "since the last thread or snapshot event thread is not given."); } throw new IllegalArgumentException( "Thread with key " + getThreadKey() + " does not exist in given trace"); @@ -160,6 +160,10 @@ public interface Step extends Comparable { PcodeMachine machine, Consumer> stepAction, TaskMonitor monitor) throws CancelledException { TraceThread thread = getThread(tm, eventThread); + if (machine == null) { + // Just performing validation (specifically thread parts) + return thread; + } PcodeThread emuThread = machine.getThread(thread.getPath(), true); execute(emuThread, stepAction, monitor); return thread; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java index 5d4d4e6eaf..0f8f77c54e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java @@ -344,6 +344,22 @@ public class TraceSchedule implements Comparable { pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor); } + /** + * Validate this schedule for the given trace + * + *

+ * This performs a dry run of the sequence on the given trace. If the schedule starts on the + * "last thread," it verifies the snapshot gives the event thread. It also checks that every + * thread key in the sequence exists in the trace. + * + * @param trace the trace against which to validate this schedule + */ + public void validate(Trace trace) { + TraceThread lastThread = getEventThread(trace); + lastThread = steps.validate(trace, lastThread); + lastThread = pSteps.validate(trace, lastThread); + } + /** * Realize the machine state for this schedule using the given trace and pre-positioned machine * @@ -385,13 +401,13 @@ public class TraceSchedule implements Comparable { * This schedule is left unmodified. If it had any p-code steps, those steps are dropped in the * resulting schedule. * - * @param thread the thread to step + * @param thread the thread to step, or null for the "last thread" * @param tickCount the number of ticks to take the thread forward * @return the resulting schedule */ public TraceSchedule steppedForward(TraceThread thread, long tickCount) { Sequence steps = this.steps.clone(); - steps.advance(new TickStep(thread.getKey(), tickCount)); + steps.advance(new TickStep(thread == null ? -1 : thread.getKey(), tickCount)); return new TraceSchedule(snap, steps, new Sequence()); } @@ -441,13 +457,13 @@ public class TraceSchedule implements Comparable { * Returns the equivalent of executing the schedule followed by stepping the given thread * {@code pTickCount} more p-code operations * - * @param thread the thread to step + * @param thread the thread to step, or null for the "last thread" * @param pTickCount the number of p-code ticks to take the thread forward * @return the resulting schedule */ public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) { Sequence pTicks = this.pSteps.clone(); - pTicks.advance(new TickStep(thread.getKey(), pTickCount)); + pTicks.advance(new TickStep(thread == null ? -1 : thread.getKey(), pTickCount)); return new TraceSchedule(snap, steps.clone(), pTicks); } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/AutoService.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/AutoService.java index a57158c83c..7245283f31 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/AutoService.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/AutoService.java @@ -75,14 +75,16 @@ public interface AutoService { } } - public static Wiring wireServicesConsumed(Plugin plugin, Object receiver) { - // TODO: Validate against PluginInfo? - + public static Wiring wireServicesConsumed(PluginTool tool, Object receiver) { AutoServiceListener listener = new AutoServiceListener<>(receiver); - PluginTool tool = plugin.getTool(); tool.addServiceListener(listener); listener.notifyCurrentServices(tool); return new WiringImpl(listener); } + + public static Wiring wireServicesConsumed(Plugin plugin, Object receiver) { + // TODO: Validate against PluginInfo? + return wireServicesConsumed(plugin.getTool(), receiver); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/framework/options/AutoOptionsTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/framework/options/AutoOptionsTest.java index e296da32f6..2925e7642e 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/framework/options/AutoOptionsTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/framework/options/AutoOptionsTest.java @@ -41,14 +41,14 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { protected static final String OPT2_NAME = "Test Option 2"; protected static final String OPT2_DEFAULT = "Default value"; protected static final String OPT2_DESC = "Another test option"; + protected static final String OPT2_NEW_VALUE = "A new value"; - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class",// - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsPlugin extends Plugin { @AutoOptionDefined(name = OPT1_NAME, description = OPT1_DESC) private int myIntOption = OPT1_DEFAULT; @@ -65,13 +65,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsNoParamPlugin extends AnnotatedWithOptionsPlugin { protected int updateNoParamCount; @@ -85,13 +84,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsNewOnlyParamDefaultPlugin extends AnnotatedWithOptionsPlugin { protected int updateNewOnlyParamDefaultNew; @@ -107,13 +105,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsNewOnlyParamAnnotatedPlugin extends AnnotatedWithOptionsPlugin { protected int updateNewOnlyParamAnnotatedNew; @@ -128,13 +125,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsOldOnlyParamAnnotatedPlugin extends AnnotatedWithOptionsPlugin { protected int updateOldOnlyParamAnnotatedOld; @@ -149,13 +145,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsNewOldParamDefaultPlugin extends AnnotatedWithOptionsPlugin { protected int updateNewOldParamDefaultNew; @@ -172,13 +167,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsNewOldParamNewAnnotPlugin extends AnnotatedWithOptionsPlugin { protected int updateNewOldParamNewAnnotNew; @@ -195,13 +189,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsNewOldParamOldAnnotPlugin extends AnnotatedWithOptionsPlugin { protected int updateNewOldParamOldAnnotNew; @@ -218,13 +211,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsNewOldParamNewOldAnnotPlugin extends AnnotatedWithOptionsPlugin { protected int updateNewOldParamNewOldAnnotNew; @@ -242,13 +234,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsOldNewParamNewAnnotPlugin extends AnnotatedWithOptionsPlugin { protected int updateOldNewParamNewAnnotNew; @@ -265,13 +256,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsOldNewParamOldAnnotPlugin extends AnnotatedWithOptionsPlugin { protected int updateOldNewParamOldAnnotNew; @@ -288,13 +278,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "A plugin class replete with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "An annotated plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "A plugin class replete with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "An annotated plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedWithOptionsOldNewParamOldNewAnnotPlugin extends AnnotatedWithOptionsPlugin { protected int updateOldNewParamOldNewAnnotNew; @@ -312,13 +301,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { } } - @PluginInfo(// - category = "Testing", // - description = "Consumer-only plugin class with auto option annotations", // - packageName = MiscellaneousPluginPackage.NAME, // - shortDescription = "A consumer-only plugin class", // - status = PluginStatus.HIDDEN // - ) + @PluginInfo( + category = "Testing", + description = "Consumer-only plugin class with auto option annotations", + packageName = MiscellaneousPluginPackage.NAME, + shortDescription = "A consumer-only plugin class", + status = PluginStatus.HIDDEN) public static class AnnotatedConsumerOnlyPlugin extends Plugin { @AutoOptionConsumed(name = OPT1_NAME) private int othersIntOption; @@ -387,7 +375,18 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(6, plugin.myIntOption); options.setInt(OPT1_NAME, OPT1_NEW_VALUE); - assertEquals(10, plugin.myIntOption); + assertEquals(OPT1_NEW_VALUE, plugin.myIntOption); + } + + @Test + public void testOptionsUpdatedExplicitCategory() throws PluginException { + AnnotatedWithOptionsPlugin plugin = addPlugin(tool, AnnotatedWithOptionsPlugin.class); + + ToolOptions options = tool.getOptions(OPT2_CATEGORY); + assertEquals(1, options.getOptionNames().size()); + options.setString(OPT2_NAME, OPT2_NEW_VALUE); + + assertEquals(OPT2_NEW_VALUE, plugin.myStringOption); } @Test diff --git a/Ghidra/Features/Base/data/typeinfo/generic/generic_clib.gdt b/Ghidra/Features/Base/data/typeinfo/generic/generic_clib.gdt index 98e749d1af..4dad0502af 100644 Binary files a/Ghidra/Features/Base/data/typeinfo/generic/generic_clib.gdt and b/Ghidra/Features/Base/data/typeinfo/generic/generic_clib.gdt differ diff --git a/Ghidra/Features/Base/data/typeinfo/generic/generic_clib_64.gdt b/Ghidra/Features/Base/data/typeinfo/generic/generic_clib_64.gdt index 7e27fef190..5669acb4f1 100644 Binary files a/Ghidra/Features/Base/data/typeinfo/generic/generic_clib_64.gdt and b/Ghidra/Features/Base/data/typeinfo/generic/generic_clib_64.gdt differ diff --git a/Ghidra/Features/Base/data/typeinfo/win32/windows_vs12_32.gdt b/Ghidra/Features/Base/data/typeinfo/win32/windows_vs12_32.gdt index f295f45101..f5eeb6f54f 100644 Binary files a/Ghidra/Features/Base/data/typeinfo/win32/windows_vs12_32.gdt and b/Ghidra/Features/Base/data/typeinfo/win32/windows_vs12_32.gdt differ diff --git a/Ghidra/Features/Base/data/typeinfo/win32/windows_vs12_64.gdt b/Ghidra/Features/Base/data/typeinfo/win32/windows_vs12_64.gdt index 77c7e630b4..37eb0ab301 100644 Binary files a/Ghidra/Features/Base/data/typeinfo/win32/windows_vs12_64.gdt and b/Ghidra/Features/Base/data/typeinfo/win32/windows_vs12_64.gdt differ diff --git a/Ghidra/Features/Base/ghidra_scripts/SynchronizeGDTCategoryPaths.java b/Ghidra/Features/Base/ghidra_scripts/SynchronizeGDTCategoryPaths.java new file mode 100644 index 0000000000..368a87344f --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/SynchronizeGDTCategoryPaths.java @@ -0,0 +1,97 @@ +/* ### + * 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. + */ +// +// Synchronize the category path name case from the first archive into the second +// archive. +// +//@category Data Types + +import java.io.File; + +import ghidra.app.script.GhidraScript; +import ghidra.program.model.data.Category; +import ghidra.program.model.data.FileDataTypeManager; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; + +public class SynchronizeGDTCategoryPaths extends GhidraScript { + + private File firstFile; + private File secondFile; + private FileDataTypeManager firstArchive; + private FileDataTypeManager secondArchive; + + @Override + protected void run() throws Exception { + firstFile = askFile("Select First GDT File", "Select 1st"); + secondFile = askFile("Select Second GDT File", "Select 2nd"); + + try { + firstArchive = FileDataTypeManager.openFileArchive(firstFile, false); + secondArchive = FileDataTypeManager.openFileArchive(secondFile, true); + + int transactionID = secondArchive.startTransaction("Synchronize Category Path Names"); + Category firstCategory = firstArchive.getRootCategory(); + Category secondCategory = secondArchive.getRootCategory(); + + synchronizeCategory(firstCategory, secondCategory); + secondArchive.endTransaction(transactionID, true); + } + finally { + if (firstArchive != null) { + firstArchive.close(); + } + secondArchive.save(); + if (secondArchive != null) { + secondArchive.close(); + } + } + } + + private void synchronizeCategory(Category firstCategory, Category secondCategory) { + Category[] firstCategories = firstCategory.getCategories(); + for (Category categoryA : firstCategories) { + + // loop through categories looking for a case agnostic path match + Category[] secondCategories = secondCategory.getCategories(); + boolean foundIt = false; + for (Category categoryB : secondCategories) { + if (categoryA.getName().equalsIgnoreCase(categoryB.getName())) { + // if not the exact same name, rename it + if (!categoryA.getName().equals(categoryB.getName())) { + try { + println( + "Renamed " + categoryB.getName() + " to " + categoryA.getName()); + categoryB.setName(categoryA.getName()); + foundIt = true; + } + catch (DuplicateNameException | InvalidNameException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + else { + foundIt = true; + } + synchronizeCategory(categoryA, categoryB); + } + } + if (!foundIt) { + println("Couldn't find matching category for " + categoryA.getName()); + } + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java index b9b6c9ded9..03d4b85c03 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java @@ -705,6 +705,16 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter return true; } + /** + * Extension point to specify titles when dual panels are active + * + * @param panelProgram the program assigned to the panel whose title is requested + * @return the title of the panel for the given program + */ + protected String computePanelTitle(Program panelProgram) { + return panelProgram.getDomainFile().toString(); + } + public void setPanel(ListingPanel lp) { Program myProgram = listingPanel.getListingModel().getProgram(); Program otherProgram = lp.getListingModel().getProgram(); @@ -712,10 +722,10 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter String otherName = myName; if (myProgram != null) { - myName = myProgram.getDomainFile().toString(); + myName = computePanelTitle(myProgram); } if (otherProgram != null) { - otherName = otherProgram.getDomainFile().toString(); + otherName = computePanelTitle(otherProgram); } if (otherPanel != null) { removeHoverServices(otherPanel); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/CPP/DefineTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/CPP/DefineTable.java index 70ab14a32f..6525339e69 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/CPP/DefineTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/CPP/DefineTable.java @@ -22,7 +22,6 @@ import ghidra.app.util.cparser.CPP.PreProcessor.PPToken; import ghidra.program.model.data.*; import ghidra.program.util.AddressEvaluator; import ghidra.util.Msg; -import ghidra.util.NumericUtilities; /** * @@ -113,7 +112,8 @@ public class DefineTable { node = new Hashtable(); findTable.put(chObj, node); findTable = node; - } else { + } + else { findTable = node; } } @@ -271,14 +271,14 @@ public class DefineTable { */ private String macroSub(String image, int pos, ArrayList sublist) { int replaceCount = 0; - + StringBuffer buf = new StringBuffer(image); - + // don't replace an infinite number of times. //HashMap lastReplStrings = new HashMap(); while (pos < buf.length() && replaceCount < 900000) { String defName = getDefineAt(buf, pos); - if (shouldReplace(buf,defName,pos)) { + if (shouldReplace(buf, defName, pos)) { // stop recursion on the same replacement string // if (lastReplStrings.containsKey(defName)) { // int lastpos = lastReplStrings.get(defName); @@ -296,13 +296,15 @@ public class DefineTable { // is there a replacement string if (newpos == -1) { pos++; - } else { + } + else { //System.err.println(" replace " + defName + " with " + buf.substring(pos,newpos)); //lastReplStrings.put(defName,pos + defName.length()); pos = newpos; replaceCount++; } - } else { + } + else { pos++; } } @@ -312,36 +314,34 @@ public class DefineTable { return buf.toString(); } - private boolean shouldReplace(StringBuffer buf, String defName, int pos) { if (defName == null) { return false; } - + //String nextRepl = ""; int currIndex = buf.indexOf(defName, pos); - if (currIndex < 0) + if (currIndex < 0) { return false; // nothing to replace + } // this match is not exact so skip it (borrowing from JavaCharacter) - if (currIndex > 0 - && (Character - .isJavaIdentifierStart(buf.charAt(currIndex - 1)) || Character - .isJavaIdentifierPart(buf.charAt(currIndex - 1)))) { + if (currIndex > 0 && (Character.isJavaIdentifierStart(buf.charAt(currIndex - 1)) || + Character.isJavaIdentifierPart(buf.charAt(currIndex - 1)))) { return false; } int afterIndex = currIndex + defName.length(); - if (afterIndex < buf.length() - && (Character.isJavaIdentifierStart(buf.charAt(afterIndex)) || Character - .isJavaIdentifierPart(buf.charAt(afterIndex)))) { + if (afterIndex < buf.length() && (Character.isJavaIdentifierStart(buf.charAt(afterIndex)) || + Character.isJavaIdentifierPart(buf.charAt(afterIndex)))) { return false; } //nextRepl = image.substring(0, currIndex); // shift to location String replacementString = defs.get(defName).image; // get replacement text - if (replacementString.equals(defName)) + if (replacementString.equals(defName)) { return false; // no need to replace - + } + // // check that macro argv arguments match // Vector argv = getArgs(defName); // if (argv != null && argv.size() > 0) { @@ -360,41 +360,40 @@ public class DefineTable { // return false; // } // } - + return true; } int replace(StringBuffer buf, String currKey, int fromIndex, ArrayList sublist) { String replacementString = null; - + if (sublist == null) { sublist = new ArrayList(); } //String nextRepl = ""; int currIndex = buf.indexOf(currKey, fromIndex); - if (currIndex < 0) + if (currIndex < 0) { return -1; // nothing to replace + } // this match is not exact so skip it (borrowing from JavaCharacter) - if (currIndex > 0 - && (Character - .isJavaIdentifierStart(buf.charAt(currIndex - 1)) || Character - .isJavaIdentifierPart(buf.charAt(currIndex - 1)))) { + if (currIndex > 0 && (Character.isJavaIdentifierStart(buf.charAt(currIndex - 1)) || + Character.isJavaIdentifierPart(buf.charAt(currIndex - 1)))) { return -1; } int afterIndex = currIndex + currKey.length(); - if (afterIndex < buf.length() - && (Character.isJavaIdentifierStart(buf.charAt(afterIndex)) || Character - .isJavaIdentifierPart(buf.charAt(afterIndex)))) { + if (afterIndex < buf.length() && (Character.isJavaIdentifierStart(buf.charAt(afterIndex)) || + Character.isJavaIdentifierPart(buf.charAt(afterIndex)))) { return -1; } //nextRepl = image.substring(0, currIndex); // shift to location replacementString = defs.get(currKey).image; // get replacement text - if (replacementString.equals(currKey)) + if (replacementString.equals(currKey)) { return -1; // no need to replace - + } + // if current def has args, take care of the replacement of them Vector argv = getArgs(currKey); int replacedSubpieceLen = currKey.length(); @@ -406,8 +405,7 @@ public class DefineTable { // need to scan carefully, and recursively // there shouldn't be so many globals... // could be screwed up by so many things - String parms = getParams(buf, currIndex + currKey.length(), - (char) 0); + String parms = getParams(buf, currIndex + currKey.length(), (char) 0); int parmslen = parms.length(); if (parmslen < 2) { @@ -417,30 +415,30 @@ public class DefineTable { if (!parms.startsWith("(") || !parms.endsWith(")")) { return -1; } - + parms = parms.substring(1, parms.length() - 1); replacementString = subParams(replacementString, currKey, parms, argv); replacementString = joinPdPd(replacementString); - + replacedSubpieceLen += parmslen; } // you may add an else if{} block to warn of malformed macros - // but the actual culprit may be the Define() non-terminal + // but the actual culprit may be the Define() non-terminal //if (replString != null) // nextRepl += replString; - + sublist = new ArrayList(sublist); sublist.add(currKey); - String newReplString = macroSub(replacementString,0, sublist); + String newReplString = macroSub(replacementString, 0, sublist); if (newReplString != null) { replacementString = newReplString; } - buf.replace(currIndex, currIndex+replacedSubpieceLen, replacementString); + buf.replace(currIndex, currIndex + replacedSubpieceLen, replacementString); //nextRepl += image.substring(currIndex + currKey.length()); - return currIndex+replacementString.length(); + return currIndex + replacementString.length(); } - + /** * expand a define with arguments * @@ -462,13 +460,10 @@ public class DefineTable { } pos += argValue.length() + 1; if (index >= argv.size()) { - Msg.error( - this, - "Define parameter mismatch for macro " + defName - + "(" + parms + ")" + " Expected " - + argv.size() + " arguments. " - + " badarg(" + index + ") " + argValue - + " args processed : " + argsfound); + Msg.error(this, + "Define parameter mismatch for macro " + defName + "(" + parms + ")" + + " Expected " + argv.size() + " arguments. " + " badarg(" + index + ") " + + argValue + " args processed : " + argsfound); return replString; } String curArgName = argv.elementAt(index).image; @@ -489,20 +484,16 @@ public class DefineTable { // this match is not exact so skip it (borrowing from // JavaCharacter) - if (curpos > 0 - && (Character.isJavaIdentifierStart(substString - .charAt(curpos - 1)) || Character - .isJavaIdentifierPart(substString - .charAt(curpos - 1)))) { + if (curpos > 0 && + (Character.isJavaIdentifierStart(substString.charAt(curpos - 1)) || + Character.isJavaIdentifierPart(substString.charAt(curpos - 1)))) { continue; } int afterIndex = curpos + curArgName.length(); - if (afterIndex < substString.length() - && (Character.isJavaIdentifierStart(substString - .charAt(afterIndex)) || Character - .isJavaIdentifierPart(substString - .charAt(afterIndex)))) { + if (afterIndex < substString.length() && + (Character.isJavaIdentifierStart(substString.charAt(afterIndex)) || + Character.isJavaIdentifierPart(substString.charAt(afterIndex)))) { continue; } @@ -516,10 +507,10 @@ public class DefineTable { } beginPos.add(insertLoc, begin); - endPos.add(insertLoc, - new Integer(curpos + curArgName.length())); + endPos.add(insertLoc, new Integer(curpos + curArgName.length())); subValue.add(insertLoc, argValue); - } while (curpos >= 0); + } + while (curpos >= 0); } StringBuffer buf = new StringBuffer(); @@ -566,8 +557,9 @@ public class DefineTable { } if (!hitQuote && ch == ')') { depth--; - if (depth == 0 && endChar == 0) + if (depth == 0 && endChar == 0) { break; + } // hit a paren above depth, back up if (depth < 0) { pos--; @@ -617,35 +609,37 @@ public class DefineTable { inString = !inString; } quotePos--; - } while (quotePos > currIndex); + } + while (quotePos > currIndex); int afterIndex = currIndex + 2; while (currIndex > 0 && image.charAt(currIndex - 1) == ' ') { currIndex--; // scan back for first non-blank before ## } - while (afterIndex < image.length() - && image.charAt(afterIndex) == ' ') { + while (afterIndex < image.length() && image.charAt(afterIndex) == ' ') { afterIndex++; // scan back for first non-blank before ## } if (!inString) { buf.replace(currIndex, afterIndex, ""); currIndex--; - } else { + } + else { currIndex -= 2; } } - } while (currIndex > 0); + } + while (currIndex > 0); image = buf.toString(); return image; } - + /** * Given a data type manager, populate defines with constant values as Enums * */ - + public void populateDefineEquates(DataTypeManager dtMgr) { int transactionID = dtMgr.startTransaction("Add Equates"); @@ -663,15 +657,20 @@ public class DefineTable { String strValue = getValue(defName); String strExpanded = expand(strValue, true); strValue = strExpanded; - + // strip off any casting/parentheses strValue = stripCast(strValue); - + long value = 0; Long lvalue = getCValue(strValue); if (lvalue == null) { - lvalue = AddressEvaluator.evaluateToLong(strValue); + try { + lvalue = AddressEvaluator.evaluateToLong(strValue); + } + catch (Exception exc) { + // ignore didn't parse well + } if (lvalue == null) { continue; } @@ -710,9 +709,10 @@ public class DefineTable { if (strValue.startsWith("0x")) { start = 2; radix = 16; - } else if (strValue.startsWith("0")) { + } + else if (strValue.startsWith("0")) { start = 1; - radix = 8; + radix = 8; } if (strValue.endsWith("ul") || strValue.endsWith("ll")) { strValue = strValue.substring(0, strValue.length() - 2); @@ -720,11 +720,11 @@ public class DefineTable { else if (strValue.endsWith("l") || strValue.endsWith("u")) { strValue = strValue.substring(0, strValue.length() - 1); } - + if (start != 0) { strValue = strValue.substring(start); } - + return Long.parseLong(strValue, radix); } catch (RuntimeException e) { @@ -732,7 +732,7 @@ public class DefineTable { } return null; } - + /* * create a category path based on a name, or the root category with no name */ @@ -757,41 +757,42 @@ public class DefineTable { } return path.substring(slashpos + 1); } - + /* * Strip off any casts */ private static String stripCast(String strValue) { strValue = strValue.trim(); - + int pos = 0; while (pos < strValue.length()) { int procLen = 1; - int startPos = strValue.indexOf('(',pos); + int startPos = strValue.indexOf('(', pos); if (startPos == -1) { return strValue; // done, no more open parens } pos = startPos; - int endParen = strValue.indexOf(')', pos+1); - if (endParen != -1) { - String subStr = strValue.substring(pos+1, endParen); - if (subStr.length() > 0) { - int subPos = 0; - subStr = subStr.trim(); - boolean isValid = Character.isJavaIdentifierStart(subStr.charAt(0)); - while (isValid && subPos < subStr.length()) { - char ch = subStr.charAt(subPos++); - isValid |= Character.isJavaIdentifierPart(ch); - } - // if looks like a cast, throw it away - if (isValid) { - strValue = strValue.substring(0, pos) + strValue.substring(endParen+1); - procLen = 0; - } + int endParen = strValue.indexOf(')', pos + 1); + if (endParen != -1) { + String subStr = strValue.substring(pos + 1, endParen); + if (subStr.length() > 0) { + int subPos = 0; + subStr = subStr.trim(); + boolean isValid = Character.isJavaIdentifierStart(subStr.charAt(0)); + while (isValid && subPos < subStr.length()) { + char ch = subStr.charAt(subPos++); + isValid |= Character.isJavaIdentifierPart(ch); + } + // if looks like a cast, throw it away + if (isValid) { + strValue = strValue.substring(0, pos) + strValue.substring(endParen + 1); + procLen = 0; } - } else { - return strValue; // no more end parens, just finish } + } + else { + return strValue; // no more end parens, just finish + } pos = pos + procLen; } return strValue; diff --git a/Ghidra/Features/Base/src/main/javacc/ghidra/app/util/cparser/C/C.jj b/Ghidra/Features/Base/src/main/javacc/ghidra/app/util/cparser/C/C.jj index 45d660e73e..035c5c9665 100644 --- a/Ghidra/Features/Base/src/main/javacc/ghidra/app/util/cparser/C/C.jj +++ b/Ghidra/Features/Base/src/main/javacc/ghidra/app/util/cparser/C/C.jj @@ -2596,7 +2596,7 @@ Object CastExpression() : { ( LOOKAHEAD("(" TypeName() ")") ( - "(" TypeName() ")" ( CastExpression() | + "(" TypeName() ")" ( obj = CastExpression() | ( "{" InitializerList() [ "," ] "}" ) ) ) | @@ -2633,13 +2633,12 @@ Object UnaryExpression() : { | ( - LOOKAHEAD(UnaryExpression() ) - UnaryExpression() - | - "(" dt = TypeName() ")" + "(" (dt = TypeName() | | obj = ConstantExpression() ) ")" { if (dt != null) { - obj = new Integer(dt.getLength()); + obj = Long.valueOf(dt.getLength()); + } else if (obj != null && obj instanceof String) { + obj = Long.valueOf(((String) obj).length() - 1); // will include "" plus \0 } } ) @@ -2690,7 +2689,7 @@ Object PrimaryExpression() : { Object obj = null; } { - ( | + ( obj = | obj = Constant() | "(" obj = Expression() ")" ) { @@ -2729,11 +2728,11 @@ Object Constant() : { } if (sval.startsWith("0x") || sval.startsWith("0X")) { BigInteger bigConst = new BigInteger(sval.substring(2), 16); - obj = new Long(bigConst.longValue()); + obj = Long.valueOf(bigConst.longValue()); } else { BigInteger bigConst = new BigInteger(sval); - obj = new Long(bigConst.longValue()); + obj = Long.valueOf(bigConst.longValue()); } } | @@ -2748,7 +2747,7 @@ Object Constant() : { obj = new Character(t.image.charAt(0)); } else if (t.image.length() == 4) { long cval = ((long)t.image.charAt(0) << 24) + ((long)t.image.charAt(1) << 16) + ((long)t.image.charAt(2) << 8) + ((long) t.image.charAt(3)); - obj = new Long(cval); + obj = Long.valueOf(cval); } } | diff --git a/Ghidra/Features/Base/src/main/javacc/ghidra/app/util/cparser/CPP/CPP.jj b/Ghidra/Features/Base/src/main/javacc/ghidra/app/util/cparser/CPP/CPP.jj index cfce415df1..329e74345c 100644 --- a/Ghidra/Features/Base/src/main/javacc/ghidra/app/util/cparser/CPP/CPP.jj +++ b/Ghidra/Features/Base/src/main/javacc/ghidra/app/util/cparser/CPP/CPP.jj @@ -789,11 +789,28 @@ public class PreProcessor { iFile = new File(parent + File.separator + filename); if (iFile.exists()) return iFile; + // try just in this directory File sameiFile = new File(parent + File.separator + (new File(filename)).getName()); if (sameiFile.exists()) return sameiFile; + + // try all files in this directory doing to-lower on both input file and output file + // if match return it + File folder = new File(parent); + if (folder.isDirectory()) { + File[] listOfFiles = folder.listFiles(); + + if (listOfFiles != null) { + for (File file : listOfFiles) { + if (file.isFile() && filename.compareToIgnoreCase(file.getName()) == 0) { + return file; + } + } + } + } + if (possibleXsym) { String child = ""; // look up parent chain, looking for a file that has an Xsym in it diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java new file mode 100644 index 0000000000..d8176ec876 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java @@ -0,0 +1,176 @@ +/* ### + * 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.plugin.core.datamgr; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; + +import org.junit.Test; + +import generic.jar.ResourceFile; +import generic.test.AbstractGenericTest; +import ghidra.framework.Application; +import ghidra.program.model.data.*; + +public class DataTypeArchiveIDTest extends AbstractGenericTest { + + private static final String WIN_VS12_32_GDT_PATH = "typeinfo/win32/windows_vs12_32.gdt"; + private static final String WIN_VS12_64_GDT_PATH = "typeinfo/win32/windows_vs12_64.gdt"; + private static final String GENERIC_CLIB_32_GDT_PATH = "typeinfo/generic/generic_clib.gdt"; + private static final String GENERIC_CLIB_64_GDT_PATH = "typeinfo/generic/generic_clib_64.gdt"; + private static final String MAC_OS_10_9_GDT_PATH = "typeinfo/mac_10.9/mac_osx.gdt"; + + private static final HashMap archiveIdMap = new HashMap<>(); + static { + archiveIdMap.put(WIN_VS12_32_GDT_PATH, "2644092282468053077"); + archiveIdMap.put(WIN_VS12_64_GDT_PATH, "3193696833254024484"); + archiveIdMap.put(GENERIC_CLIB_32_GDT_PATH, "2644097909188870631"); + archiveIdMap.put(GENERIC_CLIB_64_GDT_PATH, "3193699959493190971"); + archiveIdMap.put(MAC_OS_10_9_GDT_PATH, "2650667045259492112"); + } + + @Test + public void testArchiveIDMatch() throws IOException { + + HashSet notFound = new HashSet<>(archiveIdMap.keySet()); + + for (ResourceFile gdtFile : Application.findFilesByExtensionInApplication(".gdt")) { + + String path = gdtFile.getAbsolutePath(); + if (!path.contains("/data/typeinfo/")) { + continue; // only verify standard archives + } + + int ix = path.indexOf("/typeinfo/"); + path = path.substring(ix + 1); // path starts with typeinfo/... + + String oldID = archiveIdMap.get(path); + if (oldID == null) { + fail("New archive added, test must be updated: " + path); + } + + notFound.remove(path); + + FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false); + try { + assertEquals("Archive UniversalID mismatch: " + path, oldID, + dtm.getUniversalID().toString()); + } + finally { + dtm.close(); + } + } + + if (!notFound.isEmpty()) { + System.out.println("The following standard archives were not found:"); + for (String p : notFound) { + System.out.println("missing archive: " + p); + } + fail("One or more standard archives are missing"); + } + + } + + private void verifyArchive(DataType dt, String gdtPath) { + SourceArchive sourceArchive = dt.getSourceArchive(); + assertEquals(archiveIdMap.get(gdtPath), sourceArchive.getSourceArchiveID().toString()); + int ix = gdtPath.lastIndexOf('/'); + String gdtName = gdtPath.substring(ix + 1); + ix = gdtName.indexOf(".gdt"); + gdtName = gdtName.substring(0, ix); // strip-off file extension + assertEquals(gdtName, sourceArchive.getName()); + } + + @Test + public void spotCheckWindowsVS12_32() throws IOException { + ResourceFile gdtFile = Application.getModuleDataFile(WIN_VS12_32_GDT_PATH); + FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false); + try { + DataType dt = dtm.getDataType("/winsock.h/fd_set"); + assertNotNull(dt); + assertEquals("2592696207400888580", dt.getUniversalID().toString()); + verifyArchive(dt, WIN_VS12_32_GDT_PATH); + } + finally { + dtm.close(); + } + } + + + @Test + public void spotCheckWindowsVS12_64() throws IOException { + ResourceFile gdtFile = Application.getModuleDataFile(WIN_VS12_64_GDT_PATH); + FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false); + try { + DataType dt = dtm.getDataType("/winsock.h/fd_set"); + assertNotNull(dt); + assertEquals("3193696894570554681", dt.getUniversalID().toString()); + verifyArchive(dt, WIN_VS12_64_GDT_PATH); + } + finally { + dtm.close(); + } + } + + @Test + public void spotCheckGenericCLib32() throws IOException { + ResourceFile gdtFile = Application.getModuleDataFile(GENERIC_CLIB_32_GDT_PATH); + FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false); + try { + DataType dt = dtm.getDataType("/select.h/fd_set"); + assertNotNull(dt); + assertEquals("2592696207400888580", dt.getUniversalID().toString()); + verifyArchive(dt, GENERIC_CLIB_32_GDT_PATH); + } + finally { + dtm.close(); + } + } + + @Test + public void spotCheckGenericCLib64() throws IOException { + ResourceFile gdtFile = Application.getModuleDataFile(GENERIC_CLIB_64_GDT_PATH); + FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false); + try { + DataType dt = dtm.getDataType("/select.h/fd_set"); + assertNotNull(dt); + assertEquals("3193700096632251689", dt.getUniversalID().toString()); + verifyArchive(dt, GENERIC_CLIB_64_GDT_PATH); + } + finally { + dtm.close(); + } + } + + @Test + public void spotCheckMacOS10_9() throws IOException { + ResourceFile gdtFile = Application.getModuleDataFile(MAC_OS_10_9_GDT_PATH); + FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false); + try { + DataType dt = dtm.getDataType("/_fd_def.h/fd_set"); + assertNotNull(dt); + assertEquals("3015963966244190568", dt.getUniversalID().toString()); + verifyArchive(dt, MAC_OS_10_9_GDT_PATH); + } + finally { + dtm.close(); + } + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/cparser/CParserTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/cparser/CParserTest.java index 7d0433eba9..1ff332295a 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/cparser/CParserTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/cparser/CParserTest.java @@ -429,5 +429,23 @@ public class CParserTest extends AbstractGenericTest { assertTrue(a.isZeroLength()); assertTrue(a.getDataType() instanceof UnsignedLongDataType); assertEquals(4, a.getElementLength()); + + dt = dtMgr.getDataType(new CategoryPath("/"), "sizeof_t"); + assertTrue(dt instanceof Structure); + sdt = (Structure) dt; + DataTypeComponent cdt = sdt.getComponent(0); + assertTrue(cdt.getDataType() instanceof Array); + assertEquals("Array field defined with sizeof typedef", 128, cdt.getLength()); + + dt = dtMgr.getDataType(new CategoryPath("/"), "cpu_set_t"); + assertTrue(dt instanceof Structure); + sdt = (Structure) dt; + cdt = sdt.getComponent(0); + assertTrue(cdt.getDataType() instanceof Array); + assertEquals("Array field defined with sizeof typedef", 128, cdt.getLength()); + cdt = sdt.getComponent(1); + assertTrue(cdt.getDataType() instanceof Array); + assertEquals("Array field defined with sizeof typedef", 2084, cdt.getLength()); + } } diff --git a/Ghidra/Features/Base/src/test/resources/ghidra/app/util/cparser/CParserTest.h b/Ghidra/Features/Base/src/test/resources/ghidra/app/util/cparser/CParserTest.h index ab6c604fb0..cbcdc9ba78 100644 --- a/Ghidra/Features/Base/src/test/resources/ghidra/app/util/cparser/CParserTest.h +++ b/Ghidra/Features/Base/src/test/resources/ghidra/app/util/cparser/CParserTest.h @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * EXCLUDE: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +17,7 @@ /** Test parsing header file for CParser. Most of the file is just checked to make sure it gets through parsing. ** Some data types are checked. More checking of the parsed information would be beneficial at some point. **/ + /** ** use of long as an attribute @@ -229,6 +229,8 @@ __checkint(int val, int* err) { if (val < (-2147483647-1) || val > 2147483647) { *err |= OVR_ERR; } + + val = 8 * sizeof(val); return (int32_t) val; } @@ -339,6 +341,22 @@ typedef struct sigevent } sigevent_t; +/* + * Complicated sizeof array size + */ +typedef struct +{ + unsigned long int val[(1024 / (8 * sizeof (unsigned long int)))]; +} sizeof_t; + + +typedef unsigned long int __cpu_mask; + +typedef struct +{ + __cpu_mask __bits[1024 / (8 * (int) sizeof (__cpu_mask))]; + char szUrl[(2048 + 32 + sizeof("://"))] ; +} cpu_set_t; void __mem_func (void *, char **, int ***, long (*) (size_t), @@ -389,7 +407,7 @@ struct sockaddr_in struct in_addr sin_addr; - unsigned char sin_zero[sizeof (struct bob) - + unsigned char sin_zero[3 * sizeof (struct bob) - (sizeof (unsigned short int)) - sizeof (int) - sizeof (struct bob)]; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/InputDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/InputDialog.java index e8f4f1d6f3..e461cb0d87 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/InputDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/InputDialog.java @@ -43,12 +43,12 @@ public class InputDialog extends DialogComponentProvider { private InputDialogListener listener; /** - * Creates a provider for a generic input dialog with the specified title, a text field, - * labeled by the specified label. The user should check the value of - * "isCanceled()" to know whether or not the user canceled the operation. - * Otherwise, use the "getValue()" or "getValues()" to get the value(s) - * entered by the user. Use the tool's "showDialog()" to display the dialog. + * Creates a provider for a generic input dialog with the specified title, a text field, labeled + * by the specified label. The user should check the value of "isCanceled()" to know whether or + * not the user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get + * the value(s) entered by the user. Use the tool's "showDialog()" to display the dialog. *

+ * * @param dialogTitle used as the name of the dialog's title bar * @param label value to use for the label of the text field */ @@ -57,12 +57,12 @@ public class InputDialog extends DialogComponentProvider { } /** - * Creates a generic input dialog with the specified title, a text field, - * labeled by the specified label. The user should check the value of - * "isCanceled()" to know whether or not the user canceled the operation. - * Otherwise, use the "getValue()" or "getValues()" to get the value(s) - * entered by the user. Use the tool's "showDialog()" to display the dialog. + * Creates a generic input dialog with the specified title, a text field, labeled by the + * specified label. The user should check the value of "isCanceled()" to know whether or not the + * user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the + * value(s) entered by the user. Use the tool's "showDialog()" to display the dialog. *

+ * * @param dialogTitle used as the name of the dialog's title bar * @param label value to use for the label of the text field * @param initialValue initial value to use for the text field @@ -72,12 +72,12 @@ public class InputDialog extends DialogComponentProvider { } /** - * Creates a generic input dialog with the specified title, a text field, - * labeled by the specified label. The user should check the value of - * "isCanceled()" to know whether or not the user canceled the operation. - * Otherwise, use the "getValue()" or "getValues()" to get the value(s) - * entered by the user. Use the tool's "showDialog()" to display the dialog. + * Creates a generic input dialog with the specified title, a text field, labeled by the + * specified label. The user should check the value of "isCanceled()" to know whether or not the + * user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the + * value(s) entered by the user. Use the tool's "showDialog()" to display the dialog. *

+ * * @param dialogTitle used as the name of the dialog's title bar * @param label value to use for the label of the text field * @param initialValue initial value to use for the text field @@ -89,12 +89,12 @@ public class InputDialog extends DialogComponentProvider { } /** - * Creates a generic input dialog with the specified title, a text field, - * labeled by the specified label. The user should check the value of - * "isCanceled()" to know whether or not the user canceled the operation. - * Otherwise, use the "getValue()" or "getValues()" to get the value(s) - * entered by the user. Use the tool's "showDialog()" to display the dialog. + * Creates a generic input dialog with the specified title, a text field, labeled by the + * specified label. The user should check the value of "isCanceled()" to know whether or not the + * user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the + * value(s) entered by the user. Use the tool's "showDialog()" to display the dialog. *

+ * * @param dialogTitle used as the name of the dialog's title bar * @param label value to use for the label of the text field * @param initialValue initial value to use for the text field @@ -105,12 +105,12 @@ public class InputDialog extends DialogComponentProvider { } /** - * Creates a generic input dialog with the specified title, a text field, - * labeled by the specified label. The user should check the value of - * "isCanceled()" to know whether or not the user canceled the operation. - * Otherwise, use the "getValue()" or "getValues()" to get the value(s) - * entered by the user. Use the tool's "showDialog()" to display the dialog. + * Creates a generic input dialog with the specified title, a text field, labeled by the + * specified label. The user should check the value of "isCanceled()" to know whether or not the + * user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the + * value(s) entered by the user. Use the tool's "showDialog()" to display the dialog. *

+ * * @param dialogTitle used as the name of the dialog's title bar * @param labels values to use for the labels of the text fields * @param initialValues initial values to use for the text fields @@ -120,12 +120,12 @@ public class InputDialog extends DialogComponentProvider { } /** - * Creates a generic input dialog with the specified title, a text field, - * labeled by the specified label. The user should check the value of - * "isCanceled()" to know whether or not the user canceled the operation. - * Otherwise, use the "getValue()" or "getValues()" to get the value(s) - * entered by the user. Use the tool's "showDialog()" to display the dialog. + * Creates a generic input dialog with the specified title, a text field, labeled by the + * specified label. The user should check the value of "isCanceled()" to know whether or not the + * user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the + * value(s) entered by the user. Use the tool's "showDialog()" to display the dialog. *

+ * * @param dialogTitle used as the name of the dialog's title bar * @param labels values to use for the labels of the text fields * @param initialValues initial values to use for the text fields @@ -194,6 +194,7 @@ public class InputDialog extends DialogComponentProvider { textFields = new MyTextField[inputLabels.length]; for (int i = 0; i < inputValues.length; i++) { textFields[i] = new MyTextField(initialValues[i]); + inputValues[i] = initialValues[i]; textFields[i].addKeyListener(keyListener); textFields[i].setName("input.dialog.text.field." + i); panel.add(new GLabel(inputLabels[i], SwingConstants.RIGHT)); @@ -221,11 +222,16 @@ public class InputDialog extends DialogComponentProvider { @Override protected void cancelCallback() { isCanceled = true; + for (int v = 0; v < inputValues.length; v++) { + inputValues[v] = null; + } + close(); } /** * Returns if this dialog is cancelled + * * @return true if cancelled */ public boolean isCanceled() { @@ -234,6 +240,7 @@ public class InputDialog extends DialogComponentProvider { /** * Return the value of the first (and maybe only) text field + * * @return the text field value */ public String getValue() { @@ -242,6 +249,7 @@ public class InputDialog extends DialogComponentProvider { /** * Sets the text of the primary text field + * * @param text the text */ public void setValue(String text) { @@ -250,15 +258,18 @@ public class InputDialog extends DialogComponentProvider { /** * Sets the text of the text field at the given index + * * @param text the text * @param index the index of the text field */ public void setValue(String text, int index) { textFields[index].setText(text); + inputValues[index] = text; } /** * Return the values for all the text field(s) + * * @return the text field values */ public String[] getValues() { @@ -285,7 +296,8 @@ public class InputDialog extends DialogComponentProvider { } /** - * @see javax.swing.text.Document#insertString(int, java.lang.String, javax.swing.text.AttributeSet) + * @see javax.swing.text.Document#insertString(int, java.lang.String, + * javax.swing.text.AttributeSet) */ @Override public void insertString(int offs, String str, AttributeSet a) diff --git a/Ghidra/Framework/Generic/Module.manifest b/Ghidra/Framework/Generic/Module.manifest index de2c2194e7..8fc3010c6e 100644 --- a/Ghidra/Framework/Generic/Module.manifest +++ b/Ghidra/Framework/Generic/Module.manifest @@ -1,8 +1,8 @@ MODULE FILE LICENSE: lib/cglib-nodep-2.2.jar Apache License 2.0 MODULE FILE LICENSE: lib/guava-19.0.jar Apache License 2.0 MODULE FILE LICENSE: lib/jdom-legacy-1.1.3.jar JDOM License -MODULE FILE LICENSE: lib/log4j-api-2.12.1.jar Apache License 2.0 -MODULE FILE LICENSE: lib/log4j-core-2.12.1.jar Apache License 2.0 +MODULE FILE LICENSE: lib/log4j-api-2.15.0.jar Apache License 2.0 +MODULE FILE LICENSE: lib/log4j-core-2.15.0.jar Apache License 2.0 MODULE FILE LICENSE: lib/commons-collections4-4.1.jar Apache License 2.0 MODULE FILE LICENSE: lib/commons-lang3-3.9.jar Apache License 2.0 MODULE FILE LICENSE: lib/commons-io-2.6.jar Apache License 2.0 diff --git a/Ghidra/Framework/Generic/build.gradle b/Ghidra/Framework/Generic/build.gradle index 77daa8fc7a..f5ced2c0b3 100644 --- a/Ghidra/Framework/Generic/build.gradle +++ b/Ghidra/Framework/Generic/build.gradle @@ -29,8 +29,8 @@ dependencies { api "cglib:cglib-nodep:2.2" api "com.google.guava:guava:19.0" api "org.jdom:jdom-legacy:1.1.3" - api "org.apache.logging.log4j:log4j-api:2.12.1" - api "org.apache.logging.log4j:log4j-core:2.12.1" + api "org.apache.logging.log4j:log4j-api:2.15.0" + api "org.apache.logging.log4j:log4j-core:2.15.0" api "org.apache.commons:commons-collections4:4.1" api "org.apache.commons:commons-lang3:3.9" api "org.apache.commons:commons-text:1.6" diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeArchiveTransformerPanel.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeArchiveTransformerPanel.java index 67b14972b1..a00cf45c2f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeArchiveTransformerPanel.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeArchiveTransformerPanel.java @@ -105,6 +105,7 @@ public class DataTypeArchiveTransformerPanel extends JPanel { gbc.gridx = 3; gbc.gridwidth = 1; useOldFileIDCheckBox = new GCheckBox(" Use Old File ID"); + useOldFileIDCheckBox.setSelected(true); filePanel.add(useOldFileIDCheckBox, gbc); }