From 957e74ec8449c10b5e392daa94bd65da9ae9e1b2 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 12 Aug 2022 13:53:10 -0400 Subject: [PATCH] GP-2163: Add choose platform actions --- Ghidra/Debug/Debugger/certification.manifest | 2 + .../src/main/help/help/TOC_Source.xml | 4 + .../DebuggerPlatformPlugin.html | 69 ++++ .../DebuggerSelectPlatformOfferDialog.png | Bin 0 -> 25696 bytes .../core/debug/DebuggerCoordinates.java | 103 ++++-- .../event/DebuggerPlatformPluginEvent.java | 42 +++ .../core/debug/gui/DebuggerResources.java | 16 +- .../gui/platform/DebuggerPlatformPlugin.java | 337 +++++++++++++++++ .../DebuggerSelectPlatformOfferDialog.java | 340 ++++++++++++++++++ .../AbstractDebuggerPlatformMapper.java | 2 - .../AbstractDebuggerPlatformOffer.java | 29 ++ .../AbstractDebuggerPlatformOpinion.java | 7 +- .../debug/mapping/DebuggerPlatformMapper.java | 22 ++ .../debug/mapping/DebuggerPlatformOffer.java | 38 +- .../DefaultDebuggerPlatformMapper.java | 13 +- .../legacy/LegacyDebuggerPlatformOpinion.java | 7 +- .../OverrideDebuggerPlatformOpinion.java | 126 +++++++ .../dbgeng/DbgengDebuggerPlatformOpinion.java | 7 +- .../frida/FridaDebuggerPlatformOpinion.java | 7 +- .../gdb/GdbDebuggerPlatformOpinion.java | 9 +- .../jdi/JdiDebuggerPlatformOpinion.java | 8 +- .../lldb/LldbDebuggerPlatformOpinion.java | 9 +- .../DebuggerPlatformServicePlugin.java | 31 +- .../DebuggerTraceManagerServicePlugin.java | 61 +++- .../app/services/DebuggerPlatformService.java | 22 +- .../services/DebuggerTraceManagerService.java | 15 + .../DebuggerPlatformPluginScreenShots.java | 63 ++++ .../platform/DebuggerPlatformPluginTest.java | 102 ++++++ .../mapping/TestDebuggerPlatformOpinion.java | 16 +- .../DebuggerStateEditingServiceTest.java | 6 +- .../DebuggerTraceManagerServiceTest.java | 53 ++- .../database/guest/DBTraceGuestPlatform.java | 11 +- .../DBTraceGuestPlatformMappedRange.java | 2 +- .../guest/DBTracePlatformManager.java | 17 +- .../main/java/ghidra/trace/model/Trace.java | 12 +- .../model/guest/TracePlatformManager.java | 18 +- .../target/DBTraceObjectManagerTest.java | 48 +-- .../java/docking/DialogComponentProvider.java | 15 + 38 files changed, 1555 insertions(+), 134 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/images/DebuggerSelectPlatformOfferDialog.png create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/event/DebuggerPlatformPluginEvent.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPlugin.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/OverrideDebuggerPlatformOpinion.java create mode 100644 Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginScreenShots.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginTest.java diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 2470f40bad..c71daadd7e 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -97,6 +97,8 @@ src/main/help/help/topics/DebuggerObjectsPlugin/images/stepover.png||GHIDRA||||E src/main/help/help/topics/DebuggerObjectsPlugin/images/stop.png||GHIDRA||||END| src/main/help/help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerPcodeStepperPlugin/images/DebuggerPcodeStepperPlugin.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html||GHIDRA||||END| +src/main/help/help/topics/DebuggerPlatformPlugin/images/DebuggerSelectPlatformOfferDialog.png||GHIDRA||||END| src/main/help/help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerRegionsPlugin/images/DebuggerRegionMapProposalDialog.png||GHIDRA||||END| src/main/help/help/topics/DebuggerRegionsPlugin/images/DebuggerRegionsPlugin.png||GHIDRA||||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 2e86c6a687..d40d06104b 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -169,6 +169,10 @@ sortgroup="p1" target="help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html" /> + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html new file mode 100644 index 0000000000..23ac62c62a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html @@ -0,0 +1,69 @@ + + + + + + + Debugger: Platform Selection + + + + + +

Debugger: Platform Selection

+ +

The Debugger allows the user to work with multiple platforms, i.e., architectures, + compilers, etc., within the context of a single debug target. This plugin allows the user to + switch among platforms and/or introduce new platforms via overrides.

+ +

Note we are in a transitional period between two trace database conventions. This plugin is + a necessary component when working with the new convention. In the legacy convention, the + system must ascertain the target's language and compiler at the time it begins recording. If it + fails or makes an incorrect decision, the UI remains mostly empty, or worse, the system + interrogates and instructs the target using the wrong architecture. This mode still works in + most circumstances and is the default and recommended convention. Additional platforms can + still be introduced, but there is perhaps little use for doing so. One case might be to, e.g., + disassemble JVM bytecodes when debugging a Java process at the native level.

+ +

In the new convention, the system always records the target, using the "DATA" language, + i.e., no language. The object tree is recorded, and objects with memory are assigned overlay + spaces where their contents are recorded. The only way to disassemble instructions is to add a + guest platform. This plugin will do that automatically, in more or less the same fashion as the + language and compiler are ascertained in legacy mode. If this fails, the recording still + proceeds, and most of the UI is still useful. The user can add (perhaps by trial and error) the + correct platform later. In the meantime, memory, registers, threads, etc., are all still + displayed.

+ +

Actions

+ +

This plugin adds actions into the Debugger menu under "Choose Platform."

+ +

Choose Platform

+ +

This action adds or changes to a specific platform in the current trace. One of these + actions is presented for each recommended or previously chosen platform, if any. The + recommendations are given by an opinion service, so new options may be added by extension + modules. It is possible there are no recommendations for the current trace. The current + platform is designated by a check mark or other selection indicator.

+ +

More...

+ +

This action is enabled whenever there is a current trace. It presents a dialog with the + recommended platforms for the trace.

+ + + + + + + +
+ +

The "Show Only Recommended Offers" check can be disabled to display override offers as well. + Selecting an offer and confirming the dialog will add or change to the selected platform in the + trace. Furthermore, the choice will be added to the "Choose Platform" menu until the trace is + closed.

+ + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/images/DebuggerSelectPlatformOfferDialog.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/images/DebuggerSelectPlatformOfferDialog.png new file mode 100644 index 0000000000000000000000000000000000000000..48d0fd9b3e029972e3b427a4e96b83e634d542e5 GIT binary patch literal 25696 zcmbrm1yoe+-af2iQz9}nCrn-5}j40>Ti|4N8hIbPpljAV|khk~4Jo z{|2AuoaemXdB63AYq^#>d)W8B<9Ge8*ap0jmB7ABa`(!WE7+2+UMgIMgSkGKUfL2oDxJb^Y|dqVXv@LHhSm1d;$Ngb=LmXlQ|#td)Ix`$_IZ~0VD z*BZK~RsfOcbkmqcCto?A`1!r-UvFJc3grH#Q~c=SFFc>&_}8B=5g4GZKfmXCW0e2- zy^lK2%Vfbvs((JZ$L(N2HzIJvTPm=CzE*bLZ&mHV@VGf#>d(h_CrP<*&Bm9N5IyXl zo9GLxrP_u9$n++MJcQjsoz{fkXk{cAxcGY&*v9f3XOvq%{NAfuCeQfn9?7*i90IH) zA7v7Lfl>Z22XuVtUZUVR%lhQ_NvP{OZv`|dWqjJ!LOEKSeNpXb*b#kSzca7V)J?!% zG?)@SqJiz?+)_RGgxj^jBAJ-gf{GocVmb2~?7;ot*_Cke++)r;k{1u*~O3%3z0ed@AZX*)SoUhVm_(mxFESB%do-Z`YcPSY| zHWJ&1%qki=u4_{AI9z3qFk;5T#Q(S;Q{ePlSSL*-?W|11x5VKphNY&vY7U+*S6+6S zn(9sPFa)d|+6WgGw8cpYXUqdVWqF=i?h^rKaWP>t8LD$rBc+Te~$%_2|~_ z=5V8bBzFKhKbSCfuX}{3b{ukCY1YZxm`u2wT!(7Zf@Sg{n-6Mt?8A^i1ZQ6vM4?M z%wFvx&xon7c|y0+@C6DMboG6G*i+|P*?}1)NWY>%ZPzmX_4r}M_ogs;)fR6uUQUM zY>dsuQkfWfEj%_?;0~l`O?NRSUfJYwTTF$HWFQ2lAYWUr^VRLo8rnxhGm2=kg!l!xV<_IY6rg4h&h6kC`0YH)^VgYYR=N z`pneR);2V_^T9eJ^jJ+NX_P-%?4Hvq->1P-;nUOQ6GKvuek_)Ils^k_QFYop;K#oXLLh+BDrWqg(!x5JK%7wNBjTg=)(orZgS%?Kxy zBIJ{K4#Rj=zrj#paFudyH>CyHy~dyF6n2g(hTwcooqpbI#V}yeGuR+EvTXR3aP}Ju zMk%C+1F4C&dfS}ocDkH4G&H#X!6NAm6jYM6$&32JeeBzOyP!?gW$agqOaR&3Fpd!G z;$$sdo8{8SqhO5z^MO>4#o8A_{j`09QI_QU7<*+JM=LHf9p^|m0~{>k+(`R&JfGu9 zt!#u!k12~mwG-LLC?7*}_X1vb^v>AsFC7HJ-YJFM$+x0>kgIGLEIymHCHf~x@fr4b zzmAQa=6<3eC5&SMewODrhS*y90MA+WwP$cgfXevd=&A%w2tQ)re5USw)=f}JP$Q?m z1+7-D?N#n}3*f@X3h*JlDo;oGA*i$1z4CgIbEGe#;d`AEHYo@BFB=tUQv#!I=E++< zntMDG?rCyghWqRE)^erD6O3N8$}CcbZzdL9DYmdx%`vw?ZA{@MbDZzp@t%=QD!xCT zQ5ModcwPzSHtki9RzYUfThw5b+oQ?5EMR}wu%E2Is^dyO4|#d-dRW%0<1O#<^TSu% za>GA=Uhg7+OCjV&Z8e#hL`%>N8885&wohRka}A>sd+XvKlKen532{$EuYZ80E?23dK;HK* zL94UuGe3AZNk%;MZOCwg^>^h~mD~;uxdb+U<=^*t=(}(ECyNEq-LWMgVl%^nY%lg& z8>uu@TDoy(dyQ{+DZ~ZvQ!cVOiuwkokV2S)$SGFYaEO;_(|t2wPtFI2m8D~t6${=z zKS5YDo7HlF*&3CFSSHncVP$)|2UA2zMr4r-K{`}jLQ*~|hrkm*$cgQ&q5ParKDJvB z6jx%wT!M&0n_YKF$#|X7(=+%<{7zAW1YvnX#3UgjCC;|C1P3*$Q}A&osKR+`sF*E#uPusY920GSREm8>)s02DTxk2t5rRv~=DMvSa=k)cvRoAG zoEk8*G5IH*6B8eZAv^7KDP6H#G4KX=NLjNts?mF@DrK_Rz{Iz1w-*z*^;t;lOdL_h!SfLXT*gWi?{67ou7unp)g_y!<#Qx zAbLOhy9?R-gDQr+PumAXH{~*s2_7p$Il8%OYZ``JNru^`(#ELCijD#4smFq}Il>Jb+gSF^A|88el3^V&05Ox_29 z6n;C|Dk+QS(S>Eq#$O!E@|0Ac(t@3P2khLz?_L5P3lkoJ3ISn3lctT{T5^Y<9hk}^oPfpBd^ZzZPX@+)jQ2p zX2Py*c8Cn$NC|wq-g-!1=%Jpl=Ho|F;DoY9rDFbL++v#67Usr=%0q2!OW?%lwku>z zSJS;{JRFNFUQ|BK95YVwP?_lJ8p>O)!cI`vF*1{C{(-KZgtDYJg7I7J)YpHmd=bGqALo)+}QK!~y_h^^2++u7NVK^QF7e?6oTh^X| zBfZ>j>_Sk_!3KP*i@zQ$AzV^r*K7VSb39{))`T9w zBfic&%A$H9E3tG>FYMy3zTmEH*{}ZbJwt1K_JZ9*%owW;(>NT?(jIehRL$K3&KEm_WN*xhXYd-#RU+$C;5R<#DiMkIQ@`A~u zQuV&m7^v4)U>M86NFtteM^m@Bht$dE3x1ZZ-6vkBfYMj@z*JKeK#ABbh!?P6e)7f* zL+8kf)fDeWu+p~1ZD3;iGv3qw_2N2Z^E7}x;NeEm{x?u|s-7XKIn~kW#vq0AvC$XB&G^t7tRl9fH zLr#f^tk$>Alf%05>sxu>oLrQ6BCO42^G1&rik%z+DJY69HA<8*?tUtb{IMZ5NYkCj zS8Q38r&B|(8{_ilD;kP>IGyG^3YRxpT^8VL`Z0=hMT01wy2Be>Xne|27ojMi0RI*|OzPLOjV>=LE+3|}hpDG#)nf-OIpzk57HHHR{u zPr9=>jis#d*&%8jmQnz14jdgHPvNx{Re2~eSmj`jR+wuEKHf#~7_@!xU2z;0FT1{Defq!2K?zXD?n?J1|MX-n10_~7T;>8WhDu|mz* zPQ#tyNAfypIH$kJuruC~lr~zm?haOd#lCNYps?FsZde#zuUs5^hCzGy;nsZP?BVtz z!U3g{`tlA32gh|agXXtkR#sLS(lIUH@8HvJWO9O*;-tZCpxj#5$!VX@Zh?&7{*{jG zJya&+Q`F>-_ZZsR+8M;?!!OT$?p?orozr}%tEuVwww7R8BELf*!RK9#iIwRu%YG{yq zh(HORoLl;cC;1l%^6O@JZr7vaLPs5%%TOCdy2A@Dg<)9P$n@^^{ar9P?lpGw{tY+f zH3ESnX!83Rg6-Nju{TVJ73)a2%r>f%k|z7oBu1%ZMQDBef9*EnicD_?&$H2*4d>N( zoqNiB6tG|V5gy;!*C&sj1Ur9z9!ZEw6C}l_Mv-eYe!Q*9qWRLhd3yS7Qxv_j2Evia zO8->@W@1v3I*yC{5mQkzIO(bRSvjh zy!S{*0zDdqJ~r)b;%%*WYb;+%H`mD8BJ6 z)01|I2h=bqW@7CrMhD5){w#k#Os+WE&1zF00g7LQ-NMb)pH z@2(8OC72=7>?3`I+gP^FgdugA1lSgr-Eo|YU+m( zcIL7+G$CjD$BGOw^W8oFCD^OwQ99IX*9XOZG}xi z$K+;KC}_AK`Z7}f-%12A)5DYOqp*$Anvfn_?R4n$wsiZidUP*DMeluy<`fsO&M~9! z_RrY)DJl0}scXW?AddD=`OwXN5V6DiYw?Po?s&gCPY#cd*5;A6Nc z)C;hfGK!irBA5hR$V?@Z42F$7htcRp&U2+l`*56pS^}rokMUfBk=mEsm93YEEGGd#PV1t$Hb@>8+AM1KNd0WAuFK-zwq$t6QWArK=@9#b$trW0qGd!T~$fs0B4blMqB$WoI&=*mEL zk->*8U)o8A47C@g!i*UTe6(LsY zyx$o_C~RW*W#xtRu&c9akF=u`Pgu5aR@Ga z-eGxU7OmwlYxHZcjx-_q3OGGmH`mXtAJ#tUHowm?;w8~gphiZ-pc!?e1@oM#;n|=U z+bhVHYQD}YsJsF8kA6^f>LMJmsq^iZ`ymhrhf&wt%jpU@?Y+&_a4%9>p1ROU20%@& za!xd7F{mKDBIng_>pvKFM8*lauurj#Qhe`XXij}s<)m3=@&1PU-l%aijPU$$F{QDg zK`nVTs{UAXVWB%=2~DW$E)USky6)6*b*!|1=$$Hb-Qc*l>c>_Ql<}2}40^0ICm`UC zQ3X*T^sp;=2f&tq$?4H93Cox>OHoc4(;qX)G5%ya&y zN6ndN#xdwv8HLug8%iH^w{vL{)^(|*5arkD-aAqQI>ip#E;EaCsGAe@VcrEa!bF6= z$^{Ki^rb%{I50;l`vi5mZlCR}v*J)Dj% zYXWh;p|Ubb+Fp)t{g_S*afeyA1~0`%Xj5FOAC7QZ6LLK`IGi(PX}=(9_T(e33h%by z0|LX=w5@bnumHrnGX?jZ=8A8ag?N30`m^`*w4|k~JoeWb>2Q;OBM}a^br(a40NlY90#cKABd*-x`iwW+T`p08zqnnF&!bA@mlLcCVp6Cx8;CT!bpP_)}6 zU!L6U_qvuaKfS4m&fypyQ}Y5dIz`A@veerAqgeCR>pGY*Fb6Tw9<3+(roIeJ%xt?B zJ5fVDC^XBkcfnblv=B$x#aL{*m zWrr24{75$zb)5+Kt9^PWVqr6_#+KKq(EHRZT9zC7`odr6UdQd~Qws>%ygeH!tC5~M z^HJB~pRLQ&=qH2_i;#c^ZpfTHrYd7wGQhxacD~*m; z_|C~#riZ`mWV*Tags1DY&F#$JHOH03GJcw2tIBI1vVU&=d;+<3BkS}b4y^8==$*}o z3q=DwCk70urV)Hul%(3`MybdAFt)l&&MM=#mUh>ObmCl9L_7iDqvxZ*HIyFh})@hPI z9nx!k9uB80Ja9*2Z^qXTJ~7`*6Wzgi-4grC@0Lng>`eRM(;XuNDN7IJ2VZv0)->YL zDsh>3c1(`hh^#cjLv85)mRN2401E z?Oms$Fp4?+2+G;pUs9bnf0P{%9OF@qRrM1o%KdY6fnMwxy>R0j7|&HBDAF^%QmfB@ z=(V)azMcMp7w<)8Hu6eNkZ>+8&(go=q*tAk``BaO1KuDm9!r8nu~1J*6jeqwl~wK* zg?PJ!a9r2UjWw-&M_}`U-C3a48k|GRg=&`C+#noJ&9ntjLl}E8udKrntPb>Q1rM!M zpLD6-xFIo+t9*+9$is&1TU>&@7!xx#HtDZD9>40X%{``Xjdf)gEGQDAc=Bhj)BYF3 zl)!C&U9Q@bgNBA{US(xBJ(3|{n59McQbudbiBL6#heF!KpPgzV)J~-6l5mH0*DS16if4$#;`=>=)2o0F$sxF@Cx61AElM>ibA99cK zgC9Ll4By6-sXX2g744YWIAFR(T}BzahZ`{NcGMxd?j3U4icPdDfu3&~ccT0Zk9hT1 z0r@lU#Um#Q!LZSzvyCR|zIIIi`lfMv zq?}s>Bu~ssUrBZR0S*N}f&5}>yZxCrG@2sBZi_!gJ4GBQ3f}qjHn#Qxm+UDk>(Bc7 zuO@w|8B$Rz$eQET@pAREDX(*KVRz?3`(QG@`>^$4MI~qFikh0!tQ1d;LTK|YYPdMP z{y02(e}6w;r>5%bSG~dS$~=~1P2p0Ss$=6?@_us&QQy0*A>?4jqs%$r4R^h zl)g+&)7(>Y^;0PFuaWhJDN2=5kU8<3jGZRYQJHZ$Rk|`AJ{qke866opI6MS=k%5n| z60}cD8YOQJL4!O5I#Xfo+bX$go1Q|0fdK)9MMVu!j(}}`{P+=voWG#D|MrhvQ!A^G z%Er^0L3xzVtYMg=cvBnV0wtIhEetiylD434rq+=|R>8dH> zcKTdqB?6Bl##Ki5WMdh#$RT)Vx*kL3qqdhugIt$MNy;(C`Xp+c6&Nf)mtmKHckhr93S@LCuP)Dq+{1 zrPr=8mB{o2dDIYGet2YLq`m!)6RnD>qk+M2P;OZ08)$1(ORQ_Q2siU!r?ZnxbPf=>M(G_%ZiH|eQqp-GBY##+`=_-S^ku-0@rx^b~=JiZfz2=xUevkCV^WRWG{~; zEE{l%Q8W~C8PqJAO8k}lG&RI=@guGcX@pjD#&XGZ@fJe*zP`RuEV>lsR%M@BO9)wX z3W%P+zjgyjpbQDr+j*Ro<8izPmOmoTV-KxvV)7&txwf{(sQB&8qsJMBvFsC77S^TI zGQK@Y0 zp6;4Ty?QkbH?%Bc2Fq(;VsZdPI0*Iht%8CLQcgw|p@xc-(Xp{v^;mKj&$MuD(pfHQ zhK)i=bSgLbedLn(XOn!CQg%PE z@?6-Mmn$SD0eMKz%&c|Yy(`z`PQcvO8cJDw00_Y>mrqXE4a%&S>puZxc5HxNhePns z+K`ML%^!!Fl`n~w<$W71Hr8KoJ3Dbv_XKyQ7lEbf0Id;lO?} zSP#HH>96zhipIsoEeEr(yX&zreU*Y@ywtq)^2BMbnxIDtad2>k`+apbHdp88eJ?@l z&YkDR>N>8Q)3OV5bKB_A;);rjA^0y(AHR9q)_7LEhBS%LpNdKkSSUsybx;w}fU=A3 zjG+;OL9=V)Uo+UwqC0$)%`sY)igjJc{ovlA*ZHZ_NP!;hG6@^>;{v7s*3M3o9b57; zztdXRd19%4Yludv88I=jp4s%w41lh$+6c$OUbxqGH%|hOwUJ~7ymNnd6=^0xbX4QE z&&|aJfAjU4mEcY5)_rht0SDEsd}nB1VALJ&>FEiOtXlHiSvXVn1}3D*@{aEz{X`d- zB%eU#?@H=G8|&S^;>1pN-Bgh>Al+Df$m62?{Ts=`kwG#=4uF~TGlX5#`*0n}7n*e^ z(`+@GbYZX31s`Ht3O#17|EehR=PCzN6O{QWu046ITZ$&3*I)oJAKnD`l_7`ihJL$v zgqZ!%OdkGhh0p53xY;C=8^VIbAuX$+56$HuZV`brac8rBkaMi8_x5ZYCGYt)`aQ3)$ z4Vqk|-~{a={oO;8nvovo;f6+qRxZQ+cMGD#(hL zTNSv(zGAzY?G`!$(;XNLrq`U4+-HGlfyeV(UU^rt^n5J}10R(pQg}g07S^2vRS0 z#^A!r*Xl@w9HFB{^Bdm%ea&2KhhHl&Tzd!8Z@G893JX!&o&MGnTYRrrN%7b0q}RGX zN5eMb4|Tg$B|qiVGy|H9(JQh zya+b)D~9}T$aGol;oGL_x_t*yZg}fYk?8zMh%2qeN{IoAq!2lt6=tajnKPTA<6U@H zR9G0GrgOb^fzOscIfPAW%mMwOlCM+XtlwzlRd46;Iu(b(0TE+Sb-xOG!Z1#SAQg)i z!8YidEk`Qus9f!0>r;$gr1m`cX8Q!R(QS9z^6sezeQ|-Q#AXyVODY@*mWfQ+@$QumcxkPhf|#s~2_rnMdbfgC(Z$2?cO)5C zGdZ2pYmTeTxJ1q|h7~F1w$T4zZ3fg<_zT@SrOo@#HUgQBkAim1#`@snKQ^~jEe_~5 zoShA14D1<4T^E4aIyP1%XfXyj4etH9&Jy~tjJ;2)g5pH z?tJn{Ba|-|w)KQ-0~G93Hj##+YxFEwrV2;MUvCcHlh&?KU)Av$HJNnf?oa214*I0U zZ}Pdi^+qSB3BK>YqDUgxk0j?MgZ}zC)bx8YpE|%W9R~dz9JEDL?mkG8Lhg1p>UHqy zOo1bU&M=U&k{_L9wiMm{DULf$?v4ia*g)7ABYm+p} z^$+VC1rHDoGVVXYCMW#y6HnhVfp>!tF-t%dZ0N^sc%-FNF9tJboPJ|doI;jiz8ec9 z5S*JyEfXBG?xLYORwt8H{hyR^L+C|rfL+~O=QaI!%L(-)uG~WVas!yIcQHxjjaQJ% z(PvTXYnDkJJ3NkV731&McZY7h&@%t_Bt%YT?4af`;vskC>HHl#k7CXv#2x^M5-q%%GQ! z&Yy9im_7^ad^0xS{{r~(VV zwjs$%MhpxLmlco0g99~<%+s5}?!GrWG?>D}g}=|m=@|nREgWtfdJO}EoQy0G!ldzC zDI2usQ?(um^XI4gQ!|bJgwn%pVbp~Ooha z;xI|T>Hhuu{J?|U0_MT)#zsX#o_w0PUfBG_u6NIS(x`V4Z)2Qtez!B*j&C+mu~}w0 zo~K)PHeK%nJmHbCF;M(JDd&f-uo{i1Cy~b)phj7=L}ITO67?Y5_N^e)-@wI)cO4%> z0a199?6KX_)^qy?`Pkh2%P~NESw#S;io`*+A+?HdJ%@rC4cVs0c z+1S`zRAtqHP4Fx8b>lBKaY+y>VC6AB1Q$8Gx*`!yxN&UyEpPwKPqibb)kFpD>v%2D ze6q3O*+~k2G44&aG&c6Xbz3kEwAfe_g0b@%)U}`s25PfhvS9sl8#_BxUz)#~c-51e zl<>FWidnMCd<-!F;~Vr07I8#bJx`pIjj#F{0=Af+)rk~d9S%b(54kK~4%q1b8Hc;T zD?rG6fcp{E2#CQ&zto_#G*M~C7!o9NtYQvP&C@yrxe!KKDJjug>bab2o7GTL)Am`d zNg<=pEuz5rB_x?yWTvOTn-#vO?k#x)+AH-SFogwwrm$)xgGz|u7A^%N zFh^``SbGgDb%HdWbU}F$adE^jwkRSlvj<+~A!0f@^A}x02o^SWim*oq^!mva2;_o* zWJvIGat;g)L3$g|ZQG>b=&^hG#Om)u-)xX{iCbD&q)Xa@LExnAOw1^431WVB*+Ubv zd@jd9Tr~8%7XM^(MypJYfXq!VFp9qcF@_5~sYf_;1*E2>sRt2LQVszZy~_QNou!3_ zeE%B)@QQ(f0ibov1gNdhhEj?cZFVQ{G@IX4rN$IC((7^u=hR(({*y8bnVjbuYvEws zXqD&1P0@XTLb0J{FyhTGsNz9a`*-CWeuotRAV6g(5=1+9#j;tH0lC4%s`bzGlL$EG z`uJdYiFCG>$HWjqY_(_DZ{N9NYj1DhvfQj#WFQI5%C1{;v$GoIvd7pY(XC%&|KerT z7rbnOon+>FN_ZO z&6fqB+VSye!F4Ov|KwwWz5}`Hzj}K^=UuM0O-)fMW_`GU*%1CFHa{>ZC|5BHhh8OH z!4UXgKv}FH;%Bg2q3#2<3M(;<>KG>0b9U-4C~TJ%9;5 zv#yDSq)?j>@dU&&NHa2y!x*P$0MWtE0_2Q~3jI{V9*7gM2tnx%mkSn`j*dhij=`ek zYgIyxjd@IdK0Gv2=z&=zy`(bCs;;hPWMTqYBDXzs`?0I}tk7MmZnJqmyEeD6f_(P9gC*yXeFQo?_Iwh;PQGp*tjB!@fjHC0Nl7(FJir!;VIju zzb}kyq`B8%oA{q zl{hIC_yO8R#_v<0R~WlGcK}cyG*`dlZi-}jlykvmCP7QfhFkS+PUnqH6H8s0;USuz zcGNA?o|0^wi?l2m;mkC%a0;|>OOgw?uze#Z>-@PVT+bK;jZ@~wMMhmI3LKtRc(UGs zvT5B^l!s6se3kVmtiCO1-h#dXtt|+{BQa*v!i6!aT(m1-2o+P_kEB&{4Pm6^Yxb+i z1l+a1$X96*D)!ak21itRMf{F8CFRq8FguBv#C8y9w3(Sk_3`?tRi^Nq3uL0=^nr1) z8k=I|^UIzaV{#c;%;HE9>cg>^d0K+sIaPITwlfj{Vrq>MS`viAO~3uKm$et9UZ*&g zSPYUlz#v>!ef@X#r2zt6KcK=i2sR)D;MePFog!ZLT0JPoH)T4uvmNOAP?xa#3y@^| ztl4FvOV29Szo>eS#nLZ$r`r9Lnf--dX#HPgeAOE-h}bqN|9erKZ1B2yiXeHnKNoXy z8>VWr#;Ai#y8A&~$BQ39EwZtx?^KwDa8~L~%|pj<~Gi)>xL1cnR25`7g@=bd>^f1xKGL7&Y_-wkqWtcuF4>s z?QF{d(WjpgHhAC_8uT|7;h;ZRP8H^B?5+b1LLb&kku0N}3^yKT(=!;pm}qlWQn+TX zc>IGeNE&yTWg+9Qswfi7HE!ErvoPW}Su(K_`U79zss~)QHa0nc{R(W^AGwAM)`y}(F6Io#R3P&W=#78y1vXt6SX1z{ z^bR|CF%~&n^gd3ghx_8CwZ?r{oRt`0o10M!QQW11PU^ruq0+j`Que zCeA_Hzl?w?f}tgtMa5kBmt=ACJF|o7yZ@j+o1og_AG#b(b7ag}jG*@?_e(cq6kGbg z1Idat$-w1M-r0?8K>zcZB#}94t2r?4Y$y%aYYMutw=;9rH@NGRNF8~ozk&v!7#O_7 z*dMw%&D|^DfI7=DGyl@Nn!1xBn(J{fLy~!x2)@o1Kre<%NzvEW8r->+;GZnKMcnDcQ^FHb+y`bO( z5_$GZP6V!2I1Ac&b#`XvwTr~0B;mEcC8H45FEK2-%-v{YjV6D`>FFtGYphI6)Jn|; zL3a6KB_h1NuLuYrsKf%NWgLJ*^7N^s$6+;d#utS|LOCyRDfiFh!{=CbHTV0j;vts)~66ct5~ zhyu38ojZ5P`R(5xRym+tot-CMgG^Q$NE`jtKJ$XC-~t$*bEfTF$$FU)TlM(F1ljf% z;CM)givwB|JztE9eX9xFFNEjm5%6qiWn$l&o9EQJ>%=H{MS)&t{^PCXTz0g@*~wwC z`!StnFGd+NK^~>d2Q{pidZozb+bhHQ=Xr@m)9RRn%#p@Z)okqSqH~OXD7~=ezehpA zZqNn>I5L~3jd0zU1#45b1e^+x;dRQGqDO=TDB)5HcPW86GWE&`zn~SS8YL!pM%f^; zv_70a1uz%XFOa2e9OdNV5+jc6y3^ai_f-IxKg~UJwlvc4u|t~<>EQBwJP5^ zhub+gcF=lx|*6y0ynl?D-jJytJMoe&V!5QmX}@DkwOM7LBu|PG2byfaaN-y&y8063Vn5&>VDFCM_^Ty$tZVu-Q_%s^H#g7$S@24?-i z<{Wr5-W#ADtV?EgJ1)_ca8*@b?`0=0#4_{!G0E{2Ck~Ut&-9MBhWlwlj*mOFk+M1a!{dJ5aB{c zo<0>p$A!ScIFt?!UP+zx190+YIK?C+iiz+wPZQwV$QkvI>+Zm&1;NrTn5`rLO_2VR zeEr%32qu6H9neqEQ;2wW%>s`q&?@BkI%bi4UfpifM~^+wrK{$SR+t`#XrGhu5?!}5 zF2j)%vFGGPTkHUbpW-t-=YLQ`_vXK;A@3zM%r(BChDQ7s)bKBCRdC7PHUN%4;P(0X zEL|kH4S+o9Uo_D`;?UF6i-l>|xDIxA2iKXa(5DP%4Cn$ALx=Y@u=&uFRW&s=Sy^BJjJX`qYDfXWz0IWkt*r(sGRm9e_wUaxEwyd+BcmAA`&(OY$o##8m+y|=6WA*%v5zxR^dF2fP3bQsS;?RiO|*mn`bmQxNTV*4XOvT<-Ua%h9S zyW~m>+GZ>ieXOuKYYbrNbEpbU4?ootFCXhBuKr!!Nbi2O56OU?D&Yz za;0&%c#ef!yXpi9kav29LZ411ncoh)Pf63M3RhpOaX7|FAxF&N=S|ffPlLqaC11to zciUs^(oUwCuO=Divg~Up47NM&Nz(Q@uUUV#|A<;VBo0+-a@xW~T3s$Ku3H`EF0j0( zeF?*16O)q{Z==q45C8;x?sKzUHR7Gl?#lRp^@e_0%WJauQj=Y2l;f&^gWW2d^-Oqh zLb-NO&3&)kqbPI9i@QrIy12NgYiNMb?>>6;LikL7JqRE6zUpN?(zdsctf?utfK@*5 zdU|Fya45}Yd*@OFjf^RAIzMYd@hB=NC|YIc$VpX(JD);NqLyH`@`O<8RF;)MIIM03 zyp8+(o5Ap!kYsy%xx%_uT^w184HNBJ$D#D*C8>5H`18&zcnum5?fi$Dbnl3Pmdf_j z5Co?(I@5ucT0#G!NCoz0HjO`-*ii*UK?a5vnbbxy%*!+PaJacehO!m+kJru1)_(?A zXj7k@;Er(HK2b>jCf2UNtX-umRnfdvPFYPcEj-bbPGmzVcpnDUjv^EG%q&c*3G2(6 z?^M;QdPgpu91`SUvR7PKc<0%r${JXIe7`&u)1X3szC(Eg|K8&TuJ;%A*_ z1U|&2|75oEDyl<@qg>SHzo}oME5M*|$Ku$G9~ot5W;P?vk+>Inpv~N#;_zCH?T0rv zX_{A8*j+i;4a?5to;@sgSZ5M8K&k81l*>_%XEOTLZR3;K@d4}kB_oSM!$gDKtP)7n zvi{$BimWPuggidSs)yfK28eZ@?&}1O_X{^l1e;${Ug&S%9BfaS3#lxH({Ly>7`^f1 z{h|Kf%y#+H`M(TaGy`4S@H1ror3Q zr0JMf^T@y}R|GO&aL91#AJjUDIN55B-BSEI_f?(|2RN~fNB{5#mmb2RT7;5SrD6(| z)WxffB1sNk-9zMq7>1AIL8SoXet=>-aD5-2{$4hLq@`DAYl~kdW@`mb^-u}rhk(|3 z*K2Ne{e%BXZJX?V`9py3a9I7#e=oFH;YN63aBV7kzQHcf(^CAZOT%h01|6(Ao1S-o@wyw=OIuHYGE)||q_do=Mmyf*lGu01=Tf#G>Zc*Hc zqK!hj=48oE-mK{V2i>*OzMIU@%_J4*vg*Uv7ofPPb}7#Oud&Yzt9ML?c(C49Bv6L6 zf{Tnq=wGhCxg5>OgdQ}jH)U@jyZfqrNr}2Kp zBjRfEDUc+H=e9!<>Z-Khc>T8oXzG7YfI8X!Nr1Md|4D#i^^4rmx74P{S}&*YI5>tN z*;~|rYu88Hal+=n-4i+TsV9|(Is#PoPh>M~0XDISSh{y4Bd+h>b<`Z=T4x|@0FrHr zziTK>nBE#B-Mll5Z=L>lvdV1{ndTpwBI3Che(_zbrODn?cXTi?%e^cNhFdK^t9iGX zzD0pnaCK)2+Yx>3>|MVq_+zo%ezI2a#mm}M{`a(bS}g&jfOmoavhi@n_&YBCmj;bz zZe>V>oY%!=5@CFHA>&n?F91v#uvqugbYGC;=fCy-ddd55Y&sRHUg*Af`k(O=+qz4X-0fsICVd#=@kVa6D?i}Lo!E=1?`#ksl z!}BonWAC-suJzmdyFS>B)Zp_Ry6@x5h)Kwq#HLxKRh{l1EMT;ZLt(0H^`y#9yYuoU z87twwN6SB#289g>4E;9RR`Z*+0@pbUw5@KAR=AvO=_`qSp#H&r7FKitOB%Z2j$!JhBdd8Qf|lpS^3dpk^ZtDU-x-!c?#Bt;iLi`sHbMW? zf*LQj*_85{r?z8=_C620& z7Pu~wceDm!v#$^ZcUf8kVXoP8cf~ftn(E!@7Mqo1X1J%iIf@hro}(3X5@JwAq*dcC zx|=sn<5vCHm}<9Yqz4+>W(;cV9(7gODCrcht%5#_Y|Z&(sp_*mlh*;aSW@4}R&N9^ zh3`%$FLFzGa+f(T2-G1OKi@D=(&T#hPk)&$=?hKKmG8OYc7_9Z#X?jL_q+!hl8V>F zaM`m{!a*wvb`6W89b7OzOG)1Y+0Fn6d8~6c)|}o-os=#pD2QMn9$esp`<3s821*tE zN+8u83_R}Jx`q{^h0V!JKEiA+2GQ=@O-}BI{fd2}r|}6^wNS_JtD<>D^~B2V^r`Ec zp9d3_Tbbr*v`YtaorkdIYn>p;rC4(PeR7y~0RE=MO8apIxqn})aI!=Fe9h9z{B0Qn zzm<|jPy4kL{lr^@`rgd0J6}KK7x@>}iM?IlTnHaV$}JqR8O!K`z99TUjlBKj;_$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.