99TUjlBKj;_$bM&`#_#p+dl+0S8j%AJ8sF!n^L32U@=AE;%cOc_>;#Te0Is*jV~e
z%@&8l=th|aJSwB%qvVgPMNP@^LhrIFB)qfb0T1H<%CJW#SP$4GAhAIUb=dRnHrKO?
z+!Duoe+HrOu1HMMp>t#Ou%EvB*8G7m*Sjfh@ly-O?G)L5d)job(i@A)eT|7x>GcH@
zuvc2fJ&zgtD^N?+eTxZl7ZnVX0=)=c-#xdNzjkRAEe8{B
zo=wBE;5=LWL*>pvN}qEwwVB|JV$G@J(|$H&vd?Z#zn39XCD6BA*Dm`>dgcV_vqQbn
zSWn%)3t%1x;kzojjICrR_08{IGh4b(61kT7Fc>67l>w#~9+pP_@4EPJ%#P<`y!?zE
zxr3)5#5oP5qq-`Uz+qwb^}o*W3I1O|oClUb&lY*JCBlVkTl6tM!@%#OY!I8YYBO-w
zg#>yXaCi@rx6(+-AmQes3^k<)rEah;|KbKbh0Ekj;tTw){s3rVjzR^fq|z$R5u43`
zrP}J{AeR1N5E29KE0{*03~%|Y784CMIrYB12mNU(#
nzv)y*>-!W1*VK+LPZu&JpCV4QsU6S&cQN3lK{mBEyCk6iS`(`K1ORPj?E)tl
z@jM%J&*|}+UwpoI`DZV#sfS<-S@{bGV%bJx?&K8RV%ffp*y3N`Iyv^A$*k4eg|w+A
zb!%#C$G6NhwRE8u`x>yrA9OQ*inm6{*BcxYetQd08P1F2@%AkX!$R!bc8M)%dNr2-
zXU78rNGM?~m~M6oRSdw_Y=$|wTA?)?j>0qqEj(C*aEfDqJ@Ms2wf&MMl$S1B)qVt8
za5da(k4c);PeyNJ3-*muX@G>9z1XO~hX*wIT1mZz<>xKhhX+0
z=yI3)=%l32;Go@(xeh8YU_um?jcOX7x^cWlG~f#O7G+YaWAGG|E+Wn>Ji~kQ3Rhzb
zli&EhBr!zDfmw0f#meX@{d;7!5j11oh#Xlh8q-GkJ$`+dHsUEH-hwv}D}V6&Mk?sB{2sp(n?UTUGgv0!nHfn+TNzk${|4{i}0gs)9h4nDCX76VHu+1ws
z2!~FYLNv78dU&M7hp0%vYtV6>q7^``8v`ITcg<3YBmvl^7h03RAS0+`Iz~=+$A)U|3dx&ji{}+OB`(XI
zCGq*EyOVRJrhVyt7r<3YSPc<56He*^1yi37In|}i*buFJETukIX(Oj`Qn7Lp8#lEa
zu_%prLe_2Fwt(gVn*1ledy^N5qEboW9*1DJ1lXCOW3?tIK2sCYUoOz~LE%rOO)6jO
zko_^JKEo>KfIZFAPehT|Nm_V=(vaj)5%(Eg_`&q^S0gF77IbAS(4$G(IJ@|1(#KzA
zq&y>sVH(zzxD^Ta{Y$PSI0!vcmk{7)JutgWN>y=XEAUN|f^i<7M!yxkPRY)_r8>vp
zD{w~!;nWbPM1(OYRr=vT>f!ip1bYOvEAdmBw1f@A@2M3#4Z?Ofw(0ebFAn3jLy344
zm`sv(^FQ?>E2_LMGFzqR{aX-Yl6<+SUdFV>W3L(}0(=
z!OB;P&7=?+@RNsL_TRrckgAh+BC0NBbk)b>8~{FHMcBMOq*ndDLjEo~1W&YI;A~h_
z+(tz1+AskHA8bzC+rK_3@kU4#0q{J@smWwB+c(J8@4hDVVhpJ5kvtzvnRQ6?a(Wu?
z>@-1*KQ)~hWNN`KG79{~Y*inUMY3&GwO
z^V4x+^d7nm>v+4CjP6;MoN_`i4Hl(!3I^G9Fw5fZitxEdWqiI(W|#QlKrS3IBgvKI
z!SX;(N`W?=%yj0gHzhhJC;pH}z;EHlKnvI(6134TY@6jON)ojBUwKJ^J$>p~elKls
z)b{(Ql_&KV$)SG|lPBj6mr>bv-4#{W!_1$I$!E
zOjDuwc^+TaA78IX!@$g7ft%Ra9F>zk;79X{fHwmDU=>>0+kk|$>C~lpB;Ddd(qQU{
zaKD>zq@x`_vCt6JlB8o|7h*IL_I25$;sE_*kNFeBJ(VrJZZbf+Q>>^~l{g4geVsLE
z*;YkXjIavtHlOBB(l$+xq!Sgh?)BF0BE0t5++iGnkB^rhLE)cDLB^jke>&g(P*N7Y
ziQ_!)2Hg-0S)vTIYNKR12Bw1p;>JV|ltmx^%y}=lF9t)#3k3_DCz|r%xtFde)*6vG
zDR@~r3be(IMPL!g3g`tK))ml&aevR|@j>!6a6}dCTLkr&Z#HH}v|LVt1*VBpJj8fY
zMJ;~Lv;-O6(`reKP}o>l5S82llwz4Fxi3|mf+*AbfY_v~fDSv;k=43)Zyu)Cxyjdg
zVWAVuqI&Oxby%08Xw#Ds(aEZ0gtoWi
zL-?H%D;gc1@5V0g@ZQ-0Vk9VqknP5CuFekGq;8t8?M)sIO-YCKB%ow>CgNW`?>)y$
zKp6L71P;p*(0m)$iXR)+Hl83-%&!^^~@kFh{%wE%T~o4N0*_PfNF|{Xi=U`
zgcGlf*H?zsPo^f*&dIt=IiA}~J~xYCB3Pm?!c0?I+9>@9Rwbz%qIyJV|
zYngP4TLUuXJ=4~;dT}|MCV>q-_cmL^xAfyGOm8PglJJyIyRNvtTa%uW{_47u9R@3n$oSrrj-vp
zz$)XvBjt=2QcS`XaGQ={_vQ{JRn#w`)plIcAOyYXW<4NkADu^B&8
zGSJ47zstf`yuIOefBn*6ts9FxTi)x7Qk+7{sy291Y9Tuo!hG7mj(F@4VcNpucH8=br%Gdb{MtP3VhjNaiIOnQbUP
zHZ^?$Vni-yE1dF4>_h6Ce^GW_wv@T+uj5;UL`(s6
z#|If73s>I}<`JeA24H|fC
zB@YFaUN)BDzd(9t&750){CPs4d=GXh|eV$->p>aOvolzWe#`cb=3BSPMx6n)a
zJ>`t$r=I)kQ<5JpJeK{;&e$S2@(6=qGpCeSS-Tuj=ebq*IMcoC#_sLJ_o|Z28T-?1
zI`PsQ&mSy@FJCuJi$mc!`HrQnWPA2F-^}#;e@h&nY=7U`rL{V7`Xon9Oy?>ND%u8$
z6V&v5qUVv7eVFi1ar0Dko;Y0XX&o-$_b0Nhk!Y39TK&Zt#cl#F#D~2XA5H~hy9MY>l@piQlQY6V{RT;z0*S8-BrnN4X5Xbh=PUn
z^e_5LV+hE|(Yz{QzzE35{Z=>!qoh~Q+wqd1mjd_t#$f9RlOWzP_h*T(H#*A17I-l=
zcS96=H%N$)gBcLjA{M;2DtEf{wXE$%;@%IsIk=Kv31{cmxOF=Z$#^R7s2uRHAUqZd
z9{b(q)^0?!Lt%Di;Hs!MpHROy9eqB@800pRa%rF5g|i2C#2B>8R+TfmAN4+YIY}cDuKWhB^f(@VSxE?b`PV2CBXxR)76sM|y4lqW6Q3
z%m8Ma!C=~>*w*=82w*ey)P=S@mi;rrk9}wSA6jlQhE*D_zYcVodl=z!*H+d%+Ob)8-C8}f>e}bVf)C8uoOf9D5AZWANy!z{fg5r+uS|s)8YWuV3lNwhZJes
z6p}Z5=|025xF
znZT=U%-Nt0dBEzWr;ocYBC@@*wD@xcrc4+tH?30(L}F>R&Liu4JeN(ZCdG;CjJRv^OX{NwRVR_$cDON9R28NBeL%J4;z!x3IE`_+t;Re4N3r)M3&O7+w@VhVn
zj?TE=#~ChZ_(7fB_b(J8E`k<(8pz|WccI}z%ma|L
z{Hhe)V<{RnPGY%{vno44lmcWaQ|Q4#=MNuuzx(+f9yEM>rT&DkQ*J{}S3HKvq##y9
zHfZx+#@b actions =
+ new LinkedHashMap<>();
+
+ public PlatformActionSet(Trace trace) {
+ this.trace = trace;
+ this.current = traceManager.getCurrentFor(trace);
+ }
+
+ protected Set computePlatformOffers(boolean includeOverrides) {
+ return new LinkedHashSet<>(
+ DebuggerPlatformOpinion.queryOpinions(trace, current.getObject(), 0,
+ includeOverrides));
+ }
+
+ protected ToggleDockingAction createActionChoosePlatform(DebuggerPlatformOffer offer) {
+ ToggleDockingAction action = ChoosePlatformAction.builder(DebuggerPlatformPlugin.this)
+ .menuPath(DebuggerPluginPackage.NAME, ChoosePlatformAction.NAME,
+ offer.getDescription())
+ .onAction(ctx -> activatePlatform(offer))
+ .build();
+ String[] path = action.getMenuBarData().getMenuPath();
+ tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), ChoosePlatformAction.GROUP);
+ return action;
+ }
+
+ protected void activatePlatform(DebuggerPlatformOffer offer) {
+ platformService.setCurrentMapperFor(trace, offer.take(tool, trace), current.getSnap());
+ }
+
+ protected void cleanOffers() {
+ actions.keySet().retainAll(computePlatformOffers(true));
+ }
+
+ protected void addPreferredOffers() {
+ for (DebuggerPlatformOffer offer : computePlatformOffers(false)) {
+ addOfferAction(offer);
+ }
+ }
+
+ protected void updatePlatformOffers() {
+ cleanOffers();
+ addPreferredOffers();
+ }
+
+ protected ToggleDockingAction addOfferAction(DebuggerPlatformOffer offer) {
+ return actions.computeIfAbsent(offer, this::createActionChoosePlatform);
+ }
+
+ protected void addChosenOffer(DebuggerPlatformOffer offer) {
+ ToggleDockingAction action = addOfferAction(offer);
+ // NB. PluginEvent will cause selections to update
+ if (currentTrace == trace) {
+ tool.addAction(action);
+ }
+ }
+
+ protected void installActions() {
+ for (ToggleDockingAction action : actions.values()) {
+ tool.addAction(action);
+ }
+ }
+
+ protected void uninstallActions() {
+ for (ToggleDockingAction action : actions.values()) {
+ tool.removeAction(action);
+ }
+ }
+
+ protected void coordinatesActivated(DebuggerCoordinates coordinates) {
+ if (sameCoordinates(current, coordinates)) {
+ current = coordinates;
+ return;
+ }
+ current = coordinates;
+ updatePlatformOffers();
+ }
+
+ protected void mapperActivated(DebuggerPlatformMapper mapper) {
+ for (Entry ent : actions.entrySet()) {
+ DebuggerPlatformOffer offer = ent.getKey();
+ ToggleDockingAction action = ent.getValue();
+ action.setSelected(mapper != null && offer.isCreatorOf(mapper));
+ }
+ }
+ }
+
+ @AutoServiceConsumed
+ DebuggerTraceManagerService traceManager;
+ @AutoServiceConsumed
+ DebuggerPlatformService platformService;
+ @SuppressWarnings("unused")
+ private final Wiring autoServiceWiring;
+
+ private Trace currentTrace;
+
+ private final ChangeListener classChangeListener = evt -> this.classesChanged();
+
+ protected final DebuggerSelectPlatformOfferDialog offerDialog =
+ new DebuggerSelectPlatformOfferDialog();
+
+ final Map actionsChoosePlatform = new WeakHashMap<>();
+ DockingAction actionMore;
+
+ public DebuggerPlatformPlugin(PluginTool tool) {
+ super(tool);
+ autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
+
+ ClassSearcher.addChangeListener(classChangeListener);
+
+ createActions();
+ }
+
+ protected void installActions() {
+ if (currentTrace == null) {
+ return;
+ }
+ PlatformActionSet actions =
+ actionsChoosePlatform.computeIfAbsent(currentTrace, PlatformActionSet::new);
+ actions.updatePlatformOffers();
+ actions.mapperActivated(platformService.getCurrentMapperFor(currentTrace));
+ actions.installActions();
+ }
+
+ protected void uninstallActions() {
+ PlatformActionSet actions = actionsChoosePlatform.get(currentTrace);
+ if (actions != null) {
+ actions.uninstallActions();
+ }
+ }
+
+ protected void createActions() {
+ installActions();
+ actionMore = ChooseMorePlatformsActon.builder(this)
+ .enabledWhen(ctx -> currentTrace != null)
+ .onAction(this::activatedChooseMore)
+ .buildAndInstall(tool);
+ String[] path = actionMore.getMenuBarData().getMenuPath();
+ tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), ChoosePlatformAction.GROUP);
+ tool.contextChanged(null);
+ }
+
+ private void activatedChooseMore(ActionContext ctx) {
+ if (platformService == null) {
+ // Still initializing or finalizing
+ return;
+ }
+ // Sort of a backwards way to retrieve the current coordinates....
+ PlatformActionSet actions = actionsChoosePlatform.get(currentTrace);
+ if (actions == null) {
+ return;
+ }
+ DebuggerCoordinates current = actions.current;
+ Trace trace = current.getTrace();
+ TraceObject object = current.getObject();
+ long snap = current.getSnap();
+
+ DebuggerPlatformOffer offer = chooseOffer(trace, object, snap);
+ // Dialog allows Swing to do other things, so re-check platformService
+ if (offer != null && platformService != null) {
+ actions.addChosenOffer(offer);
+ platformService.setCurrentMapperFor(trace, offer.take(tool, trace), snap);
+ // NOTE: DebuggerPlatformPluginEvent will cause selection change
+ }
+ }
+
+ private void classesChanged() {
+ uninstallActions();
+ for (PlatformActionSet actions : actionsChoosePlatform.values()) {
+ actions.updatePlatformOffers();
+ }
+ installActions();
+ }
+
+ protected void coordinatesActivated(DebuggerCoordinates coordinates) {
+ uninstallActions();
+ this.currentTrace = coordinates.getTrace();
+ installActions();
+ tool.contextChanged(null);
+ }
+
+ protected void traceClosed(Trace trace) {
+ if (trace == currentTrace) {
+ coordinatesActivated(DebuggerCoordinates.NOWHERE);
+ }
+ actionsChoosePlatform.remove(trace);
+ }
+
+ protected void mapperActivated(Trace trace, DebuggerPlatformMapper mapper) {
+ if (trace != currentTrace) {
+ return;
+ }
+ PlatformActionSet actions = actionsChoosePlatform.get(trace);
+ if (actions != null) {
+ actions.mapperActivated(mapper);
+ }
+ }
+
+ @Override
+ public void processEvent(PluginEvent event) {
+ super.processEvent(event);
+ if (event instanceof TraceActivatedPluginEvent evt) {
+ coordinatesActivated(evt.getActiveCoordinates());
+ }
+ if (event instanceof TraceClosedPluginEvent evt) {
+ traceClosed(evt.getTrace());
+ }
+ if (event instanceof DebuggerPlatformPluginEvent evt) {
+ mapperActivated(evt.getTrace(), evt.getMapper());
+ }
+ }
+
+ /**
+ * Display a dialog for the user to manually select an offer for the given object
+ *
+ * @param object the object for which an offer is desired
+ * @param snap the snap, usually the current snap
+ * @return the offer, or null if the dialog was cancelled
+ */
+ protected DebuggerPlatformOffer chooseOffer(Trace trace, TraceObject object, long snap) {
+ List offers =
+ DebuggerPlatformOpinion.queryOpinions(trace, object, snap, true);
+ offerDialog.setOffers(offers);
+ tool.showDialog(offerDialog);
+ if (offerDialog.isCancelled()) {
+ return null;
+ }
+ return offerDialog.getSelectedOffer();
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java
new file mode 100644
index 0000000000..e5b2812ae6
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java
@@ -0,0 +1,340 @@
+/* ###
+ * 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.platform;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.util.*;
+import java.util.function.Function;
+
+import javax.swing.*;
+
+import docking.DialogComponentProvider;
+import docking.widgets.table.*;
+import docking.widgets.table.ColumnSortState.SortDirection;
+import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
+import ghidra.app.plugin.core.debug.gui.DebuggerResources;
+import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
+import ghidra.program.model.lang.*;
+import ghidra.program.util.DefaultLanguageService;
+import ghidra.util.table.GhidraTable;
+import ghidra.util.table.GhidraTableFilterPanel;
+
+public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
+
+ protected enum OfferTableColumns
+ implements EnumeratedTableColumn {
+ CONFIDENCE("Confidence", Integer.class, DebuggerPlatformOffer::getConfidence, SortDirection.DESCENDING),
+ PROCESSOR("Processor", String.class, OfferTableColumns::getProcessor),
+ VARIANT("Variant", String.class, OfferTableColumns::getVariant),
+ SIZE("Size", Integer.class, OfferTableColumns::getSize),
+ ENDIAN("Endian", Endian.class, OfferTableColumns::getEndian),
+ COMPILER("Compiler", CompilerSpecID.class, DebuggerPlatformOffer::getCompilerSpecID);
+
+ private static final LanguageService LANG_SERV =
+ DefaultLanguageService.getLanguageService();
+
+ private static String getProcessor(DebuggerPlatformOffer offer) {
+ if (offer.getLanguageID() == null) {
+ return "Unspecified";
+ }
+ try {
+ return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getProcessor().toString();
+ }
+ catch (LanguageNotFoundException e) {
+ return "Not Found";
+ }
+ }
+
+ private static String getVariant(DebuggerPlatformOffer offer) {
+ if (offer.getLanguageID() == null) {
+ return null;
+ }
+ try {
+ return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getVariant();
+ }
+ catch (LanguageNotFoundException e) {
+ return "Not Found";
+ }
+ }
+
+ private static int getSize(DebuggerPlatformOffer offer) {
+ try {
+ return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getSize();
+ }
+ catch (LanguageNotFoundException e) {
+ return 0;
+ }
+ }
+
+ private static Endian getEndian(DebuggerPlatformOffer offer) {
+ try {
+ return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getEndian();
+ }
+ catch (LanguageNotFoundException e) {
+ return null;
+ }
+ }
+
+ private final String header;
+ private final Class> cls;
+ private final Function getter;
+ private final SortDirection sortDir;
+
+ OfferTableColumns(String header, Class cls,
+ Function getter, SortDirection sortDir) {
+ this.header = header;
+ this.cls = cls;
+ this.getter = getter;
+ this.sortDir = sortDir;
+ }
+
+ OfferTableColumns(String header, Class cls,
+ Function getter) {
+ this(header, cls, getter, SortDirection.ASCENDING);
+ }
+
+ @Override
+ public Class> getValueClass() {
+ return cls;
+ }
+
+ @Override
+ public Object getValueOf(DebuggerPlatformOffer row) {
+ return getter.apply(row);
+ }
+
+ @Override
+ public String getHeader() {
+ return header;
+ }
+
+ @Override
+ public SortDirection defaultSortDirection() {
+ return sortDir;
+ }
+ }
+
+ public static class OfferTableModel
+ extends DefaultEnumeratedColumnTableModel {
+
+ public OfferTableModel() {
+ super("Offers", OfferTableColumns.class);
+ }
+
+ @Override
+ public List defaultSortOrder() {
+ return List.of(OfferTableColumns.CONFIDENCE, OfferTableColumns.PROCESSOR,
+ OfferTableColumns.VARIANT, OfferTableColumns.COMPILER);
+ }
+ }
+
+ public static class OfferPanel extends JPanel {
+ private final OfferTableModel offerTableModel = new OfferTableModel();
+ private final GhidraTable offerTable = new GhidraTable(offerTableModel);
+ private final GhidraTableFilterPanel offerTableFilterPanel =
+ new GhidraTableFilterPanel<>(offerTable, offerTableModel);
+ private final JLabel descLabel = new JLabel();
+ private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers");
+
+ private final JScrollPane scrollPane = new JScrollPane(offerTable) {
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension pref = super.getPreferredSize();
+ if (pref.width != 0) {
+ pref.height = 150;
+ }
+ return pref;
+ }
+ };
+ private final TableFilter filterRecommended = new TableFilter<>() {
+ @Override
+ public boolean acceptsRow(DebuggerPlatformOffer offer) {
+ return !offer.isOverride();
+ }
+
+ @Override
+ public boolean isSubFilterOf(TableFilter> tableFilter) {
+ return false;
+ }
+ };
+
+ private LanguageID preferredLangID;
+ private CompilerSpecID preferredCsID;
+
+ {
+ JPanel descPanel = new JPanel(new BorderLayout());
+ descPanel.setBorder(BorderFactory.createTitledBorder("Description"));
+ descPanel.add(descLabel, BorderLayout.CENTER);
+
+ JPanel nested1 = new JPanel(new BorderLayout());
+ nested1.add(scrollPane, BorderLayout.CENTER);
+ nested1.add(offerTableFilterPanel, BorderLayout.SOUTH);
+
+ JPanel nested2 = new JPanel(new BorderLayout());
+ nested2.add(nested1, BorderLayout.CENTER);
+ nested2.add(descPanel, BorderLayout.SOUTH);
+
+ setLayout(new BorderLayout());
+ add(nested2, BorderLayout.CENTER);
+ add(overrideCheckBox, BorderLayout.SOUTH);
+
+ setFilterRecommended(true);
+ offerTable.getSelectionModel().addListSelectionListener(e -> {
+ DebuggerPlatformOffer offer = getSelectedOffer();
+ descLabel.setText(offer == null ? "" : offer.getDescription());
+ });
+
+ overrideCheckBox.addActionListener(evt -> {
+ setFilterRecommended(overrideCheckBox.isSelected());
+ });
+ }
+
+ public void setPreferredIDs(LanguageID langID, CompilerSpecID csID) {
+ this.preferredLangID = langID;
+ this.preferredCsID = csID;
+ }
+
+ public void setOffers(Collection offers) {
+ offerTableModel.clear();
+ offerTableModel.addAll(offers);
+
+ selectPreferred();
+ }
+
+ private void selectPreferred() {
+ // As sorted and filtered, pick the first matching offer
+ // NB. It should never be one or the other. Always both or none.
+ RowObjectFilterModel model =
+ offerTableFilterPanel.getTableFilterModel();
+ int count = model.getRowCount();
+ if (preferredLangID != null && preferredCsID != null) {
+ for (int i = 0; i < count; i++) {
+ DebuggerPlatformOffer offer = model.getRowObject(i);
+ if (offer.getLanguageID().equals(preferredLangID) &&
+ offer.getCompilerSpecID().equals(preferredCsID)) {
+ offerTable.getSelectionModel().setSelectionInterval(i, i);
+ return;
+ }
+ }
+ }
+ // Fall back to first offer; disregard preference
+ if (model.getRowCount() > 0) {
+ offerTable.getSelectionModel().setSelectionInterval(0, 0);
+ }
+ }
+
+ public void setFilterRecommended(boolean recommendedOnly) {
+ boolean hasSelection = offerTableFilterPanel.getSelectedItem() != null;
+ overrideCheckBox.setSelected(recommendedOnly);
+ offerTableFilterPanel.setSecondaryFilter(recommendedOnly ? filterRecommended : null);
+ if (!hasSelection) {
+ selectPreferred();
+ }
+ }
+
+ public void setSelectedOffer(DebuggerPlatformOffer offer) {
+ offerTableFilterPanel.setSelectedItem(offer);
+ }
+
+ public DebuggerPlatformOffer getSelectedOffer() {
+ return offerTableFilterPanel.getSelectedItem();
+ }
+
+ // For tests
+ public List getDisplayedOffers() {
+ return List.copyOf(offerTableFilterPanel.getTableFilterModel().getModelData());
+ }
+ }
+
+ private final OfferPanel offerPanel = new OfferPanel();
+
+ private boolean isCancelled = false;
+
+ protected DebuggerSelectPlatformOfferDialog() {
+ super(DebuggerResources.NAME_CHOOSE_PLATFORM, Set.of(Opt.MODAL, Opt.INCLUDE_BUTTONS));
+
+ populateComponents();
+ }
+
+ protected void populateComponents() {
+ offerPanel.setBorder(BorderFactory.createTitledBorder(" Select Platform "));
+ addWorkPanel(offerPanel);
+ addOKButton();
+ addCancelButton();
+
+ setDefaultButton(okButton);
+ setOkEnabled(false);
+
+ // TODO: Separate this a bit
+ offerPanel.offerTable.getSelectionModel().addListSelectionListener(e -> {
+ setOkEnabled(getSelectedOffer() != null);
+ });
+ }
+
+ /**
+ * Set the preferred language and compiler spec IDs, typically from the current program.
+ *
+ *
+ * This must be called before {@link #setOffers(Collection)}.
+ *
+ * @param langID the preferred language
+ * @param csID the preferred compiler spec (ABI)
+ */
+ public void setPreferredIDs(LanguageID langID, CompilerSpecID csID) {
+ offerPanel.setPreferredIDs(langID, csID);
+ }
+
+ public void setOffers(Collection offers) {
+ offerPanel.setOffers(offers);
+ }
+
+ public boolean isCancelled() {
+ return isCancelled;
+ }
+
+ public void setSelectedOffer(DebuggerPlatformOffer offer) {
+ offerPanel.setSelectedOffer(offer);
+ }
+
+ public DebuggerPlatformOffer getSelectedOffer() {
+ return offerPanel.getSelectedOffer();
+ }
+
+ // For tests
+ protected List getDisplayedOffers() {
+ return offerPanel.getDisplayedOffers();
+ }
+
+ protected void setFilterRecommended(boolean recommendedOnly) {
+ offerPanel.setFilterRecommended(recommendedOnly);
+ }
+
+ @Override
+ protected void cancelCallback() {
+ isCancelled = true;
+ super.cancelCallback();
+ }
+
+ @Override
+ protected void okCallback() {
+ if (getSelectedOffer() != null) {
+ isCancelled = false;
+ close();
+ }
+ // Do nothing. Should be disabled anyway
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java
index b1da0a65fe..32fcd97de4 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java
@@ -64,8 +64,6 @@ public abstract class AbstractDebuggerPlatformMapper implements DebuggerPlatform
return Set.of();
}
- protected abstract CompilerSpec getCompilerSpec(TraceObject object);
-
@Override
public DisassemblyResult disassemble(TraceThread thread, TraceObject object,
Address start, AddressSetView restricted, long snap, TaskMonitor monitor) {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOffer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOffer.java
index 0e190f1514..638de62e07 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOffer.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOffer.java
@@ -15,15 +15,21 @@
*/
package ghidra.app.plugin.core.debug.mapping;
+import java.util.Objects;
+
import ghidra.program.model.lang.CompilerSpec;
public abstract class AbstractDebuggerPlatformOffer implements DebuggerPlatformOffer {
private final String description;
protected final CompilerSpec cSpec;
+ private final int hash;
+
public AbstractDebuggerPlatformOffer(String description, CompilerSpec cSpec) {
this.description = description;
this.cSpec = cSpec;
+
+ this.hash = Objects.hash(description, cSpec);
}
@Override
@@ -35,4 +41,27 @@ public abstract class AbstractDebuggerPlatformOffer implements DebuggerPlatformO
public CompilerSpec getCompilerSpec() {
return cSpec;
}
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (this.getClass() != obj.getClass()) {
+ return false;
+ }
+ AbstractDebuggerPlatformOffer that = (AbstractDebuggerPlatformOffer) obj;
+ if (!Objects.equals(this.description, that.description)) {
+ return false;
+ }
+ if (!Objects.equals(this.cSpec, that.cSpec)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOpinion.java
index 6a9234f164..653f68c6bd 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOpinion.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOpinion.java
@@ -24,19 +24,20 @@ import ghidra.trace.model.target.TraceObject;
public abstract class AbstractDebuggerPlatformOpinion implements DebuggerPlatformOpinion {
protected abstract Set getOffers(TraceObject object, long snap,
- TraceObject env, String debugger, String arch, String os, Endian endian);
+ TraceObject env, String debugger, String arch, String os, Endian endian,
+ boolean includeOverrides);
@Override
public Set getOffers(Trace trace, TraceObject object, long snap,
boolean includeOverrides) {
TraceObject env = DebuggerPlatformOpinion.getEnvironment(object, snap);
if (env == null) {
- return getOffers(object, snap, env, null, null, null, null);
+ return getOffers(object, snap, env, null, null, null, null, includeOverrides);
}
String debugger = DebuggerPlatformOpinion.getDebugggerFromEnv(env, snap);
String arch = DebuggerPlatformOpinion.getArchitectureFromEnv(env, snap);
String os = DebuggerPlatformOpinion.getOperatingSystemFromEnv(env, snap);
Endian endian = DebuggerPlatformOpinion.getEndianFromEnv(env, snap);
- return getOffers(object, snap, env, debugger, arch, os, endian);
+ return getOffers(object, snap, env, debugger, arch, os, endian, includeOverrides);
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformMapper.java
index 730d66cf46..92f3268c03 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformMapper.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformMapper.java
@@ -17,6 +17,8 @@ package ghidra.app.plugin.core.debug.mapping;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
+import ghidra.program.model.lang.CompilerSpec;
+import ghidra.program.model.lang.Language;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.task.TaskMonitor;
@@ -25,6 +27,26 @@ import ghidra.util.task.TaskMonitor;
* An object for interpreting a trace according to a chosen platform
*/
public interface DebuggerPlatformMapper {
+
+ /**
+ * Get the compiler for a given object
+ *
+ * @param object the object
+ * @return the compiler spec
+ */
+ CompilerSpec getCompilerSpec(TraceObject object);
+
+ /**
+ * Get the language for a given object
+ *
+ * @param object the object
+ * @return the language
+ */
+ default Language getLangauge(TraceObject object) {
+ CompilerSpec cSpec = getCompilerSpec(object);
+ return cSpec == null ? null : cSpec.getLanguage();
+ }
+
/**
* Prepare the given trace for interpretation under this mapper
*
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOffer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOffer.java
index 714bb427b7..c836929c1f 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOffer.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOffer.java
@@ -62,13 +62,23 @@ public interface DebuggerPlatformOffer {
/**
* Get the language to which this offer can map
*
- * @return the langauge
+ * @return the language
*/
default Language getLanguage() {
CompilerSpec cSpec = getCompilerSpec();
return cSpec == null ? null : cSpec.getLanguage();
}
+ /**
+ * Get the language ID to which this offer can map
+ *
+ * @return the language ID
+ */
+ default LanguageID getLanguageID() {
+ Language language = getLanguage();
+ return language == null ? null : language.getLanguageID();
+ }
+
/**
* Get the compiler to which this offer can map
*
@@ -76,6 +86,24 @@ public interface DebuggerPlatformOffer {
*/
CompilerSpec getCompilerSpec();
+ /**
+ * Get the compiler spec ID to which this offer can map
+ *
+ * @return the language ID
+ */
+ default CompilerSpecID getCompilerSpecID() {
+ CompilerSpec cSpec = getCompilerSpec();
+ return cSpec == null ? null : cSpec.getCompilerSpecID();
+ }
+
+ /**
+ * Load a compiler spec from the language service given the language and cspec IDs
+ *
+ * @param langID the langauge ID
+ * @param cSpecID the compiler spec ID
+ * @return the compiler spec
+ * @throws AssertionError if either the language or the compiler spec is not found
+ */
default CompilerSpec getCompilerSpec(LanguageID langID, CompilerSpecID cSpecID) {
try {
LanguageService langServ = DefaultLanguageService.getLanguageService();
@@ -96,4 +124,12 @@ public interface DebuggerPlatformOffer {
* @return the mapper
*/
DebuggerPlatformMapper take(PluginTool tool, Trace trace);
+
+ /**
+ * Check if this or an equivalent offer was the creator of the given mapper
+ *
+ * @param mapper the mapper
+ * @return true if this offer could be the mapper's creator
+ */
+ boolean isCreatorOf(DebuggerPlatformMapper mapper);
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java
index e2d607933d..c78382eac4 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java
@@ -20,8 +20,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
-import ghidra.trace.model.guest.TraceGuestPlatform;
-import ghidra.trace.model.guest.TracePlatformManager;
+import ghidra.trace.model.guest.*;
import ghidra.trace.model.target.TraceObject;
import ghidra.util.MathUtilities;
import ghidra.util.database.UndoableTransaction;
@@ -49,7 +48,7 @@ public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMappe
}
@Override
- protected CompilerSpec getCompilerSpec(TraceObject object) {
+ public CompilerSpec getCompilerSpec(TraceObject object) {
return cSpec;
}
@@ -59,11 +58,11 @@ public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMappe
cSpec.getLanguage().getLanguageDescription() + "/" + cSpec.getCompilerSpecDescription(),
true)) {
TracePlatformManager platformManager = trace.getPlatformManager();
- TraceGuestPlatform platform = platformManager.getOrAddGuestPlatform(cSpec);
- if (platform == null) {
- return; // It's the host compiler spec
+ TracePlatform platform = platformManager.getOrAddPlatform(cSpec);
+ if (platform.isHost()) {
+ return;
}
- addMappedRanges(platform);
+ addMappedRanges((TraceGuestPlatform) platform);
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java
index ce4834f6b4..bd9481fba6 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java
@@ -40,7 +40,7 @@ public class LegacyDebuggerPlatformOpinion implements DebuggerPlatformOpinion {
}
@Override
- protected CompilerSpec getCompilerSpec(TraceObject object) {
+ public CompilerSpec getCompilerSpec(TraceObject object) {
return trace.getBaseCompilerSpec();
}
@@ -86,6 +86,11 @@ public class LegacyDebuggerPlatformOpinion implements DebuggerPlatformOpinion {
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
return new LegacyDebuggerPlatformMapper(tool, trace);
}
+
+ @Override
+ public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
+ return mapper.getClass() == LegacyDebuggerPlatformMapper.class;
+ }
};
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/OverrideDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/OverrideDebuggerPlatformOpinion.java
new file mode 100644
index 0000000000..a5eb05c5a3
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/OverrideDebuggerPlatformOpinion.java
@@ -0,0 +1,126 @@
+/* ###
+ * 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.platform;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import ghidra.app.plugin.core.debug.mapping.*;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.lang.*;
+import ghidra.program.util.DefaultLanguageService;
+import ghidra.trace.model.Trace;
+import ghidra.trace.model.target.TraceObject;
+
+/**
+ * An "opinion" which offers every known compiler, but with only default mapping logic.
+ */
+public class OverrideDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion {
+ private static final LanguageCompilerSpecQuery ALL_SPECS =
+ new LanguageCompilerSpecQuery(null, null, null, null, null);
+ private static final Map> CACHE = new HashMap<>();
+
+ protected static class OverridePlatformOffer implements DebuggerPlatformOffer {
+
+ private final String description;
+ private final LanguageID languageID;
+ private final CompilerSpecID cSpecID;
+ private final int confidence;
+
+ public OverridePlatformOffer(String description, LanguageID languageID,
+ CompilerSpecID cSpecID, int confidence) {
+ this.description = description;
+ this.languageID = languageID;
+ this.cSpecID = cSpecID;
+ this.confidence = confidence;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public LanguageID getLanguageID() {
+ return languageID;
+ }
+
+ @Override
+ public CompilerSpecID getCompilerSpecID() {
+ return cSpecID;
+ }
+
+ @Override
+ public int getConfidence() {
+ return confidence;
+ }
+
+ @Override
+ public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
+ return new OverrideDebuggerPlatformMapper(tool, trace, getCompilerSpec());
+ }
+
+ @Override
+ public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
+ return mapper.getClass() == OverrideDebuggerPlatformMapper.class;
+ }
+
+ @Override
+ public CompilerSpec getCompilerSpec() {
+ return getCompilerSpec(languageID, cSpecID);
+ }
+ }
+
+ protected static class OverrideDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
+ public OverrideDebuggerPlatformMapper(PluginTool tool, Trace trace, CompilerSpec cSpec) {
+ super(tool, trace, cSpec);
+ }
+ }
+
+ protected DebuggerPlatformOffer computeOfferForEndianAndLCSP(Endian endian,
+ LanguageCompilerSpecPair lcsp) {
+ try {
+ LanguageDescription ldesc = lcsp.getLanguageDescription();
+ return new OverridePlatformOffer("Override to " + lcsp,
+ ldesc.getLanguageID(),
+ lcsp.getCompilerSpecDescription().getCompilerSpecID(),
+ ldesc.getEndian() == endian ? -10 : -20);
+ }
+ catch (LanguageNotFoundException | CompilerSpecNotFoundException e) {
+ // It couldn't have been generated unless it existed
+ throw new AssertionError(e);
+ }
+ }
+
+ protected Set computeOffersForEndian(Endian endian) {
+ LanguageService langServ = DefaultLanguageService.getLanguageService();
+ return langServ.getLanguageCompilerSpecPairs(ALL_SPECS)
+ .stream()
+ .map(lcsp -> computeOfferForEndianAndLCSP(endian, lcsp))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ protected Set getOffers(TraceObject object, long snap, TraceObject env,
+ String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
+ if (!includeOverrides) {
+ return Set.of();
+ }
+ synchronized (CACHE) {
+ return CACHE.computeIfAbsent(endian, this::computeOffersForEndian);
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java
index 176f88a033..95b4dc606e 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java
@@ -65,12 +65,17 @@ public class DbgengDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpini
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
return new DbgengX64DebuggerPlatformMapper(tool, trace, getCompilerSpec());
}
+
+ @Override
+ public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
+ return mapper.getClass() == DbgengX64DebuggerPlatformMapper.class;
+ }
};
}
@Override
protected Set getOffers(TraceObject object, long snap, TraceObject env,
- String debugger, String arch, String os, Endian endian) {
+ String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (debugger == null || arch == null || !debugger.toLowerCase().contains("dbg")) {
return Set.of();
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java
index 1ff3247f48..0bc8f55c10 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java
@@ -79,11 +79,16 @@ public class FridaDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinio
// TODO: May need these per offer
return new FridaDebuggerPlatformMapper(tool, trace, getCompilerSpec());
}
+
+ @Override
+ public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
+ return mapper.getClass() == FridaDebuggerPlatformMapper.class;
+ }
}
@Override
protected Set getOffers(TraceObject object, long snap, TraceObject env,
- String debugger, String arch, String os, Endian endian) {
+ String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (debugger == null || arch == null ||
os == null | !debugger.toLowerCase().contains("frida")) {
return Set.of();
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerPlatformOpinion.java
index f9bdbe240b..842dfa338f 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerPlatformOpinion.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerPlatformOpinion.java
@@ -66,6 +66,11 @@ public class GdbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
return new GdbDebuggerPlatformMapper(tool, trace, cSpec);
}
+
+ @Override
+ public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
+ return mapper.getClass() == GdbDebuggerPlatformMapper.class;
+ }
}
protected static class GdbDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
@@ -78,12 +83,12 @@ public class GdbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
protected Set offersForLanguageAndCSpec(String arch, Endian endian,
LanguageCompilerSpecPair lcsp)
throws CompilerSpecNotFoundException, LanguageNotFoundException {
- return Set.of(GdbDebuggerPlatformOffer.fromArchLCSP("Default GDB for " + arch, lcsp));
+ return Set.of(GdbDebuggerPlatformOffer.fromArchLCSP(arch, lcsp));
}
@Override
protected Set getOffers(TraceObject object, long snap, TraceObject env,
- String debugger, String arch, String os, Endian endian) {
+ String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (debugger == null || !"gdb".equals(debugger.toLowerCase())) {
return Set.of();
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/jdi/JdiDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/jdi/JdiDebuggerPlatformOpinion.java
index a3a1345164..a2fc91c6a6 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/jdi/JdiDebuggerPlatformOpinion.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/jdi/JdiDebuggerPlatformOpinion.java
@@ -29,7 +29,6 @@ public class JdiDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
protected static final CompilerSpecID COMP_ID_DEFAULT = new CompilerSpecID("default");
protected static class JdiDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
- // TODO: Delete this class?
public JdiDebuggerPlatformMapper(PluginTool tool, Trace trace, CompilerSpec cSpec) {
super(tool, trace, cSpec);
}
@@ -68,11 +67,16 @@ public class JdiDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
return new JdiDebuggerPlatformMapper(tool, trace, getCompilerSpec());
}
+
+ @Override
+ public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
+ return mapper.getClass() == JdiDebuggerPlatformMapper.class;
+ }
}
@Override
protected Set getOffers(TraceObject object, long snap, TraceObject env,
- String debugger, String arch, String os, Endian endian) {
+ String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (debugger == null || arch == null || !debugger.contains("Java Debug Interface")) {
return Set.of();
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java
index 4548ba78fe..626477fd98 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java
@@ -31,7 +31,7 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
protected static final CompilerSpecID COMP_ID_GCC = new CompilerSpecID("gcc");
protected static final CompilerSpecID COMP_ID_VS = new CompilerSpecID("windows");
- protected static class LldbDebuggerPlatformMapper
+ protected static final class LldbDebuggerPlatformMapper
extends DefaultDebuggerPlatformMapper {
public LldbDebuggerPlatformMapper(PluginTool tool, Trace trace,
CompilerSpec cSpec) {
@@ -79,11 +79,16 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
// TODO: May need these per offer
return new LldbDebuggerPlatformMapper(tool, trace, getCompilerSpec());
}
+
+ @Override
+ public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
+ return mapper.getClass() == LldbDebuggerPlatformMapper.class;
+ }
}
@Override
protected Set getOffers(TraceObject object, long snap, TraceObject env,
- String debugger, String arch, String os, Endian endian) {
+ String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (debugger == null || arch == null ||
os == null | !debugger.toLowerCase().contains("lldb")) {
return Set.of();
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/platform/DebuggerPlatformServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/platform/DebuggerPlatformServicePlugin.java
index fb64b1e1cc..18c2da95d9 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/platform/DebuggerPlatformServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/platform/DebuggerPlatformServicePlugin.java
@@ -15,21 +15,18 @@
*/
package ghidra.app.plugin.core.debug.service.platform;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
-import docking.action.builder.ActionBuilder;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
+import ghidra.app.plugin.core.debug.event.DebuggerPlatformPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
-import ghidra.app.plugin.core.debug.gui.DebuggerResources.ChoosePlatformAction;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.services.DebuggerPlatformService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
-import ghidra.lifecycle.Unfinished;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
@@ -55,8 +52,6 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
- ActionBuilder actionChoosePlatform;
-
private final Map mappersByTrace = new HashMap<>();
public DebuggerPlatformServicePlugin(PluginTool tool) {
@@ -65,13 +60,10 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
}
@Override
- protected void init() {
- super.init();
- createActions();
- }
-
- protected void createActions() {
- actionChoosePlatform = ChoosePlatformAction.builder(this); // TODO:
+ public DebuggerPlatformMapper getCurrentMapperFor(Trace trace) {
+ synchronized (mappersByTrace) {
+ return mappersByTrace.get(trace);
+ }
}
@Override
@@ -93,7 +85,7 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
mappersByTrace.put(trace, mapper);
}
mapper.addToTrace(snap);
- // TODO: Fire a listener
+ firePluginEvent(new DebuggerPlatformPluginEvent(getName(), trace, mapper));
return mapper;
}
@@ -109,13 +101,10 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
return null;
}
- @Override
- public DebuggerPlatformMapper chooseMapper(Trace trace, TraceObject object, long snap) {
- return Unfinished.TODO();
- }
-
@Override
public void setCurrentMapperFor(Trace trace, DebuggerPlatformMapper mapper, long snap) {
+ Objects.requireNonNull(trace);
+ Objects.requireNonNull(mapper);
if (!traceManager.getOpenTraces().contains(trace)) {
throw new IllegalArgumentException("Trace is not opened in this tool");
}
@@ -123,7 +112,7 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
mappersByTrace.put(trace, mapper);
}
mapper.addToTrace(snap);
- // TODO: Fire a listener
+ firePluginEvent(new DebuggerPlatformPluginEvent(getName(), trace, mapper));
}
@Override
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 4366d1a03f..cf41855161 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
@@ -51,6 +51,9 @@ import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceVariableSnapProgramView;
import ghidra.trace.model.stack.TraceStackFrame;
+import ghidra.trace.model.target.TraceObject;
+import ghidra.trace.model.target.TraceObjectKeyPath;
+import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
@@ -404,6 +407,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return focus == null ? null : recorder.getTraceThreadForSuccessor(focus);
}
+ protected TraceObject objectFromTargetFocus(TraceRecorder recorder, TargetObject focus) {
+ return focus == null ? null
+ : recorder.getTrace()
+ .getObjectManager()
+ .getObjectByCanonicalPath(TraceObjectKeyPath.of(focus.getPath()));
+ }
+
protected TraceStackFrame frameFromTargetFocus(TraceRecorder recorder, TargetObject focus) {
return focus == null ? null : recorder.getTraceStackFrameForSuccessor(focus);
}
@@ -439,6 +449,32 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
// NOTE, if still null without focus support,
// we will take the eldest live thread at the resolved snap
}
+ TraceObject object = coordinates.getObject();
+ if (object == null) {
+ if (supportsFocus(recorder)) {
+ object = objectFromTargetFocus(recorder, focus);
+ }
+ if (object /*still*/ == null) { // either no focus support, or focus is not recorded
+ object = lastForTrace == null ? null : lastForTrace.getObject();
+ if (object != null) {
+ TraceObjectThread objThread =
+ object.queryCanonicalAncestorsInterface(TraceObjectThread.class)
+ .findFirst()
+ .orElse(null);
+ if (objThread != thread) {
+ object = null; // Abandon remembered object
+ }
+ }
+ }
+ if (object /*still*/ == null && thread instanceof TraceObjectThread objThread) {
+ // TODO: Seek the frame out?
+ object = objThread.getObject();
+ }
+ if (object /*still*/ == null) {
+ object = trace.getObjectManager().getRootObject();
+ // Could still be null, but that means no objects in the trace at all
+ }
+ }
/**
* Only select a default thread if the trace is not live. If it is live, and the model
* supports focus, then we should expect the debugger to control thread/frame focus.
@@ -500,7 +536,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
frame = 0;
}
return DebuggerCoordinates.all(trace, recorder, thread, view, Objects.requireNonNull(time),
- Objects.requireNonNull(frame));
+ Objects.requireNonNull(frame), object);
}
protected DebuggerCoordinates doSetCurrent(DebuggerCoordinates newCurrent) {
@@ -557,11 +593,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
}
TraceThread thread = threadFromTargetFocus(recorder, obj);
+ TraceObject object = objectFromTargetFocus(recorder, obj);
long snap = recorder.getSnap();
TraceStackFrame traceFrame = frameFromTargetFocus(recorder, obj);
Integer frame = traceFrame == null ? null : traceFrame.getLevel();
activateNoFocus(DebuggerCoordinates.all(trace, recorder, thread, null,
- TraceSchedule.snap(snap), frame));
+ TraceSchedule.snap(snap), frame, object));
return true;
}
@@ -667,6 +704,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return current.getFrame();
}
+ @Override
+ public TraceObject getCurrentObject() {
+ return current.getObject();
+ }
+
public Long findSnapshot(DebuggerCoordinates coordinates) {
if (coordinates.getTime().isSnapOnly()) {
return coordinates.getSnap();
@@ -985,6 +1027,16 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return null;
}
TraceRecorder recorder = resolved.getRecorder();
+ if (!Objects.equals(prev.getObject(), resolved.getObject())) {
+ TraceObject obj = resolved.getObject();
+ if (obj != null) {
+ TargetObject object =
+ recorder.getTarget().getSuccessor(obj.getCanonicalPath().getKeyList());
+ if (object != null) {
+ return object;
+ }
+ }
+ }
if (!Objects.equals(prev.getFrame(), resolved.getFrame())) {
TargetStackFrame frame =
recorder.getTargetStackFrame(resolved.getThread(), resolved.getFrame());
@@ -1066,6 +1118,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
activate(DebuggerCoordinates.frame(frameLevel));
}
+ @Override
+ public void activateObject(TraceObject object) {
+ activate(DebuggerCoordinates.object(object));
+ }
+
@Override
public void setAutoActivatePresent(boolean enabled) {
autoActivatePresent.set(enabled, null);
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java
index 7dab95f9b4..b5527c35f9 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java
@@ -16,6 +16,7 @@
package ghidra.app.services;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
+import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
@@ -23,6 +24,15 @@ import ghidra.trace.model.target.TraceObject;
* A service to manage the current mapper for active traces
*/
public interface DebuggerPlatformService {
+
+ /**
+ * Get the current mapper for the given trace
+ *
+ * @param trace the trace
+ * @return the mapper, or null
+ */
+ DebuggerPlatformMapper getCurrentMapperFor(Trace trace);
+
/**
* Get a mapper applicable to the given object
*
@@ -37,8 +47,7 @@ public interface DebuggerPlatformService {
* @param snap the snap, usually the current snap
* @return the mapper, or null if no offer was provided
*/
- DebuggerPlatformMapper getMapper(Trace trace, TraceObject object,
- long snap);
+ DebuggerPlatformMapper getMapper(Trace trace, TraceObject object, long snap);
/**
* Get a new mapper for the given object, ignoring the trace's current mapper
@@ -54,15 +63,6 @@ public interface DebuggerPlatformService {
*/
DebuggerPlatformMapper getNewMapper(Trace trace, TraceObject object, long snap);
- /**
- * Display a dialog for the user to manually select a mapper for the given object
- *
- * @param object the object for which a mapper is desired
- * @param snap the snap, usually the current snap
- * @return the mapper, or null if the dialog was cancelled
- */
- DebuggerPlatformMapper chooseMapper(Trace trace, TraceObject object, long snap);
-
/**
* Set the current mapper for the trace and initialize the trace for the mapper
*
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 a6e4f6ffeb..0f94111f81 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
@@ -26,6 +26,7 @@ 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.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.TriConsumer;
@@ -141,6 +142,13 @@ public interface DebuggerTraceManagerService {
*/
int getCurrentFrame();
+ /**
+ * Get the active object
+ *
+ * @return the active object, or null
+ */
+ TraceObject getCurrentObject();
+
/**
* Open a trace
*
@@ -282,6 +290,13 @@ public interface DebuggerTraceManagerService {
*/
void activateFrame(int frameLevel);
+ /**
+ * Activate the given object
+ *
+ * @param object the desired object
+ */
+ void activateObject(TraceObject object);
+
/**
* Control whether the trace manager automatically activates the "present snapshot"
*
diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginScreenShots.java
new file mode 100644
index 0000000000..947566be71
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginScreenShots.java
@@ -0,0 +1,63 @@
+/* ###
+ * 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.platform;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
+import ghidra.app.services.DebuggerTraceManagerService;
+import ghidra.dbg.target.schema.SchemaContext;
+import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
+import ghidra.dbg.target.schema.XmlSchemaContext;
+import ghidra.trace.database.ToyDBTraceBuilder;
+import ghidra.trace.database.target.DBTraceObjectManagerTest;
+import ghidra.util.database.UndoableTransaction;
+import help.screenshot.GhidraScreenShotGenerator;
+
+public class DebuggerPlatformPluginScreenShots extends GhidraScreenShotGenerator {
+
+ DebuggerTraceManagerService traceManager;
+ DebuggerPlatformPlugin platformPlugin;
+
+ @Before
+ public void setUpMine() throws Throwable {
+ traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
+ platformPlugin = addPlugin(tool, DebuggerPlatformPlugin.class);
+ }
+
+ @Test
+ public void testCaptureDebuggerSelectPlatformOfferDialog() throws Throwable {
+ SchemaContext ctx = XmlSchemaContext.deserialize(DBTraceObjectManagerTest.XML_CTX);
+ try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("echo", "DATA:BE:64:default")) {
+ try (UndoableTransaction tid = tb.startTransaction()) {
+ tb.trace.getObjectManager()
+ .createRootObject(ctx.getSchema(new SchemaName("Session")));
+ }
+ traceManager.openTrace(tb.trace);
+ traceManager.activateTrace(tb.trace);
+ waitForSwing();
+
+ performAction(platformPlugin.actionMore, false);
+ DebuggerSelectPlatformOfferDialog dialog =
+ waitForDialogComponent(DebuggerSelectPlatformOfferDialog.class);
+ dialog.setFilterRecommended(false);
+ waitForSwing();
+
+ captureDialog(dialog);
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginTest.java
new file mode 100644
index 0000000000..5bf700b8d5
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginTest.java
@@ -0,0 +1,102 @@
+/* ###
+ * 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.platform;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import docking.action.ToggleDockingActionIf;
+import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
+import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
+import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
+import ghidra.app.services.DebuggerPlatformService;
+import ghidra.program.model.lang.LanguageID;
+import ghidra.trace.database.ToyDBTraceBuilder;
+
+public class DebuggerPlatformPluginTest extends AbstractGhidraHeadedDebuggerGUITest {
+ DebuggerPlatformPlugin platformPlugin;
+ DebuggerPlatformService platformService;
+
+ protected List getPlatformActions() {
+ return tool.getAllActions()
+ .stream()
+ .filter(a -> a.getOwner().equals(platformPlugin.getName()))
+ .filter(a -> a instanceof ToggleDockingActionIf)
+ .filter(a -> a != platformPlugin.actionMore)
+ .map(a -> (ToggleDockingActionIf) a)
+ .collect(Collectors.toList());
+ }
+
+ @Before
+ public void setUpPlatformTest() throws Throwable {
+ platformPlugin = addPlugin(tool, DebuggerPlatformPlugin.class);
+ platformService = tool.getService(DebuggerPlatformService.class);
+ }
+
+ protected void chooseLanguageIDViaMore(LanguageID langID) {
+ performAction(platformPlugin.actionMore, false);
+ DebuggerSelectPlatformOfferDialog dialog =
+ waitForDialogComponent(DebuggerSelectPlatformOfferDialog.class);
+ dialog.setFilterRecommended(false);
+ waitForSwing();
+
+ List offers = runSwing(() -> dialog.getDisplayedOffers());
+ DebuggerPlatformOffer toyOffer = offers.stream()
+ .filter(o -> Objects.equals(langID, o.getLanguageID()))
+ .findFirst()
+ .orElseThrow();
+ runSwing(() -> dialog.setSelectedOffer(toyOffer));
+ runSwing(() -> dialog.okCallback());
+ waitForSwing();
+ }
+
+ @Test
+ public void testActionMore() throws Throwable {
+ createAndOpenTrace("DATA:BE:64:default");
+ traceManager.activateTrace(tb.trace);
+
+ chooseLanguageIDViaMore(new LanguageID("Toy:BE:64:default"));
+ DebuggerPlatformMapper mapper = platformService.getCurrentMapperFor(tb.trace);
+ assertEquals(new LanguageID("Toy:BE:64:default"), mapper.getLangauge(null).getLanguageID());
+ }
+
+ @Test
+ public void testRemembersChosenOffer() throws Throwable {
+ createAndOpenTrace("DATA:BE:64:default");
+ try (ToyDBTraceBuilder tb2 =
+ new ToyDBTraceBuilder("second-" + name.getMethodName(), "DATA:BE:64:default")) {
+ traceManager.openTrace(tb2.trace);
+ traceManager.activateTrace(tb2.trace);
+
+ chooseLanguageIDViaMore(new LanguageID("Toy:BE:64:default"));
+ assertEquals(2, getPlatformActions().size());
+
+ traceManager.activateTrace(tb.trace);
+ waitForSwing();
+ assertEquals(1, getPlatformActions().size());
+
+ traceManager.activateTrace(tb2.trace);
+ waitForSwing();
+ assertEquals(2, getPlatformActions().size());
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java
index 64aed3469f..a5956da67e 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java
@@ -24,6 +24,12 @@ import ghidra.trace.model.target.TraceObject;
public class TestDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion {
+ protected static class TestDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
+ public TestDebuggerPlatformMapper(PluginTool tool, Trace trace, CompilerSpec cSpec) {
+ super(tool, trace, cSpec);
+ }
+ }
+
enum Offers implements DebuggerPlatformOffer {
ARM_V8_LE("Test armv8le", "ARM:LE:32:v8", "default"),
X86_64("Test x86-64", "x86:LE:64:default", "gcc");
@@ -58,13 +64,19 @@ public class TestDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
@Override
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
- return new DefaultDebuggerPlatformMapper(tool, trace, getCompilerSpec());
+ return new TestDebuggerPlatformMapper(tool, trace, getCompilerSpec());
+ }
+
+ @Override
+ public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
+ return mapper.getClass() == TestDebuggerPlatformMapper.class;
}
}
@Override
protected Set getOffers(TraceObject object, long snap,
- TraceObject env, String debugger, String arch, String os, Endian endian) {
+ TraceObject env, String debugger, String arch, String os, Endian endian,
+ boolean includeOverrides) {
if (!"test".equals(debugger)) {
return Set.of();
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java
index 473ce0e94e..7ac389202c 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java
@@ -145,7 +145,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
TraceThread thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor executor =
TracePcodeUtils.executorForCoordinates(
- DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0));
+ DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0,
+ null));
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
@@ -182,7 +183,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor executor =
TracePcodeUtils.executorForCoordinates(
- DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0));
+ DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0,
+ null));
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java
index 1e0bca2d3d..abf3ff2d30 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java
@@ -17,8 +17,7 @@ package ghidra.app.plugin.core.debug.service.tracemgr;
import static org.junit.Assert.*;
-import java.util.Collection;
-import java.util.Set;
+import java.util.*;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -30,9 +29,17 @@ import ghidra.app.services.ActionSource;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.model.TestTargetStack;
import ghidra.dbg.model.TestTargetStackFrameHasRegisterBank;
+import ghidra.dbg.target.schema.SchemaContext;
+import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
+import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.framework.model.DomainFile;
+import ghidra.trace.database.target.DBTraceObjectManager;
+import ghidra.trace.database.target.DBTraceObjectManagerTest;
import ghidra.trace.model.Trace;
import ghidra.trace.model.stack.TraceStack;
+import ghidra.trace.model.target.TraceObject;
+import ghidra.trace.model.target.TraceObjectKeyPath;
+import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.UndoableTransaction;
@@ -194,6 +201,48 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
assertEquals(0, traceManager.getCurrentFrame());
}
+ @Test
+ public void testGetCurrentObject() throws Exception {
+ assertEquals(null, traceManager.getCurrentObject());
+
+ createTrace();
+ waitForDomainObject(tb.trace);
+
+ assertEquals(null, traceManager.getCurrentObject());
+
+ traceManager.openTrace(tb.trace);
+ waitForSwing();
+
+ assertEquals(null, traceManager.getCurrentObject());
+
+ traceManager.activateTrace(tb.trace);
+ waitForSwing();
+
+ assertEquals(null, traceManager.getCurrentObject());
+
+ SchemaContext ctx = XmlSchemaContext.deserialize(DBTraceObjectManagerTest.XML_CTX);
+ TraceObject objThread0;
+ try (UndoableTransaction tid = tb.startTransaction()) {
+ DBTraceObjectManager objectManager = tb.trace.getObjectManager();
+ objectManager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
+ objThread0 =
+ objectManager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"));
+ }
+ TraceThread thread =
+ Objects.requireNonNull(objThread0.queryInterface(TraceObjectThread.class));
+
+ traceManager.activateObject(objThread0);
+ waitForSwing();
+
+ assertEquals(objThread0, traceManager.getCurrentObject());
+ assertEquals(thread, traceManager.getCurrentThread());
+
+ traceManager.activateTrace(null);
+ waitForSwing();
+
+ assertEquals(null, traceManager.getCurrentObject());
+ }
+
@Test
public void testOpenTrace() throws Exception {
createTrace();
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java
index d5e6325645..3f3eb0b025 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java
@@ -33,7 +33,9 @@ import ghidra.program.util.DefaultLanguageService;
import ghidra.trace.database.DBTraceUtils.CompilerSpecIDDBFieldCodec;
import ghidra.trace.database.DBTraceUtils.LanguageIDDBFieldCodec;
import ghidra.trace.model.Trace;
+import ghidra.trace.model.Trace.TracePlatformChangeType;
import ghidra.trace.model.guest.TraceGuestPlatform;
+import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
@@ -184,6 +186,8 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
hostAddressSet.delete(hostRange);
guestAddressSet.delete(guestRange);
}
+ manager.trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.MAPPING_DELETED,
+ null, this, range, null));
}
@Override
@@ -211,6 +215,7 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
@Override
public DBTraceGuestPlatformMappedRange addMappedRange(Address hostStart, Address guestStart,
long length) throws AddressOverflowException {
+ DBTraceGuestPlatformMappedRange mappedRange;
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
Address hostEnd = hostStart.addWrap(length - 1);
if (hostAddressSet.intersects(hostStart, hostEnd)) {
@@ -222,14 +227,16 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
if (guestAddressSet.intersects(guestStart, guestEnd)) {
throw new IllegalArgumentException("Range overlaps existing guest mapped range(s)");
}
- DBTraceGuestPlatformMappedRange mappedRange = manager.rangeMappingStore.create();
+ mappedRange = manager.rangeMappingStore.create();
mappedRange.set(hostStart, this, guestStart, length);
rangesByHostAddress.put(hostStart, mappedRange);
rangesByGuestAddress.put(guestStart, mappedRange);
hostAddressSet.add(mappedRange.getHostRange());
guestAddressSet.add(mappedRange.getGuestRange());
- return mappedRange;
}
+ manager.trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.MAPPING_ADDED,
+ null, this, null, mappedRange));
+ return mappedRange;
}
@Override
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java
index ea5b72fbfe..29d17c8df5 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java
@@ -161,6 +161,6 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
@Override
public void delete(TaskMonitor monitor) throws CancelledException {
- manager.platformStore.getObjectAt(guestPlatformKey).deleteMappedRange(this, monitor);
+ platform.deleteMappedRange(this, monitor);
}
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java
index cf3156f53a..f9a05376be 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java
@@ -29,7 +29,9 @@ import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceManager;
import ghidra.trace.database.guest.DBTraceGuestPlatform.DBTraceGuestLanguage;
import ghidra.trace.model.Trace;
+import ghidra.trace.model.Trace.TracePlatformChangeType;
import ghidra.trace.model.guest.*;
+import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.exception.CancelledException;
@@ -261,6 +263,7 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
platformsByCompiler.remove(platform.getCompilerSpec());
platformStore.delete(platform);
}
+ trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.DELETED, null, platform));
}
@Override
@@ -281,9 +284,12 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
throw new IllegalArgumentException(
"Base compiler spec cannot be a guest compiler spec");
}
+ DBTraceGuestPlatform platform;
try (LockHold hold = LockHold.lock(lock.writeLock())) {
- return doAddGuestPlatform(compilerSpec);
+ platform = doAddGuestPlatform(compilerSpec);
}
+ trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.ADDED, null, platform));
+ return platform;
}
@Override
@@ -297,18 +303,21 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
}
@Override
- public DBTraceGuestPlatform getOrAddGuestPlatform(CompilerSpec compilerSpec) {
+ public InternalTracePlatform getOrAddPlatform(CompilerSpec compilerSpec) {
if (compilerSpec.getCompilerSpecID()
.equals(trace.getBaseCompilerSpec().getCompilerSpecID())) {
- throw new IllegalArgumentException("Base language cannot be a guest language");
+ return hostPlatform;
}
+ DBTraceGuestPlatform platform;
try (LockHold hold = LockHold.lock(lock.writeLock())) {
DBTraceGuestPlatform exists = platformsByCompiler.get(compilerSpec);
if (exists != null) {
return exists;
}
- return doAddGuestPlatform(compilerSpec);
+ platform = doAddGuestPlatform(compilerSpec);
}
+ trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.ADDED, null, platform));
+ return platform;
}
@Override
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java
index fd3f31b9e9..454e4cdcbe 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java
@@ -32,7 +32,7 @@ import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointManager;
import ghidra.trace.model.context.TraceRegisterContextManager;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
-import ghidra.trace.model.guest.TracePlatformManager;
+import ghidra.trace.model.guest.*;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.*;
@@ -379,6 +379,16 @@ public interface Trace extends DataTypeManagerDomainObject {
public static final TraceSnapshotChangeType DELETED = new TraceSnapshotChangeType<>();
}
+ public static final class TracePlatformChangeType
+ extends DefaultTraceChangeType {
+ public static final TracePlatformChangeType ADDED = new TracePlatformChangeType<>();
+ public static final TracePlatformChangeType DELETED = new TracePlatformChangeType<>();
+ public static final TracePlatformChangeType MAPPING_ADDED =
+ new TracePlatformChangeType<>();
+ public static final TracePlatformChangeType MAPPING_DELETED =
+ new TracePlatformChangeType<>();
+ }
+
public interface TraceProgramViewListener {
void viewCreated(TraceProgramView view);
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatformManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatformManager.java
index 2a471245fd..1582732f3d 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatformManager.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatformManager.java
@@ -33,6 +33,13 @@ public interface TracePlatformManager {
*/
TracePlatform getHostPlatform();
+ /**
+ * Get all guest platforms
+ *
+ * @return the collection of platforms
+ */
+ Collection getGuestPlatforms();
+
/**
* Add a guest platform
*
@@ -53,14 +60,7 @@ public interface TracePlatformManager {
* Get or add a platform for the given compiler spec
*
* @param compilerSpec the compiler spec
- * @return the new or existing platform, or null if compiler spec is the base compiler spec
+ * @return the new or existing platform
*/
- TraceGuestPlatform getOrAddGuestPlatform(CompilerSpec compilerSpec);
-
- /**
- * Get all guest platforms
- *
- * @return the collection of platforms
- */
- Collection getGuestPlatforms();
+ TracePlatform getOrAddPlatform(CompilerSpec compilerSpec);
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java
index 12e11aed86..35e89715b7 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java
@@ -40,6 +40,30 @@ import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.util.database.*;
public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationTest {
+ public static final String XML_CTX = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """;
protected ToyDBTraceBuilder b;
protected DBTraceObjectManager manager;
@@ -54,29 +78,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
b = new ToyDBTraceBuilder("Testing", "Toy:BE:64:default");
manager = b.trace.getObjectManager();
- ctx = XmlSchemaContext.deserialize("" + //
- "" + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- "");
+ ctx = XmlSchemaContext.deserialize(XML_CTX);
}
protected void populateModel(int targetCount) {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java
index 67922c58fc..0f262fff87 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java
@@ -45,6 +45,10 @@ import utility.function.Callback;
public class DialogComponentProvider
implements ActionContextProvider, StatusListener, TaskListener {
+ protected enum Opt {
+ MODAL, INCLUDE_STATUS, INCLUDE_BUTTONS, CAN_RUN_TASKS;
+ }
+
private static final Color WARNING_COLOR = new Color(0xff9900);
private final static int DEFAULT_DELAY = 750;
@@ -99,6 +103,17 @@ public class DialogComponentProvider
private Dimension defaultSize;
+ /**
+ * Constructor for GhidraDialogComponent using option set instead of booleans
+ * @param title the dialog title
+ * @param options the options. See {@link Option} and
+ * {{@link #DialogComponentProvider(String, boolean, boolean, boolean, boolean)}
+ */
+ protected DialogComponentProvider(String title, Set options) {
+ this(title, options.contains(Opt.MODAL), options.contains(Opt.INCLUDE_STATUS),
+ options.contains(Opt.INCLUDE_BUTTONS), options.contains(Opt.CAN_RUN_TASKS));
+ }
+
/**
* Constructor for a GhidraDialogComponent that will be modal and will include a status line and
* a button panel. Its title will be the same as its name.