From 921247f640c9a313bce80dc26e0f99939ddae4ad Mon Sep 17 00:00:00 2001 From: 1635321 <1635321@localhost> Date: Thu, 24 Aug 2023 00:11:20 +0000 Subject: [PATCH] GP-2412: Improved support for Rust binaries --- Ghidra/Features/Base/certification.manifest | 2 + .../Base/data/RustFunctionsThatDoNotReturn | 20 + .../Base/data/noReturnFunctionConstraints.xml | 6 + .../Base/data/typeinfo/rust/rust-common.gdt | Bin 0 -> 3656 bytes .../analysis/NoReturnFunctionAnalyzer.java | 142 +-- .../core/analysis/rust/RustConstants.java | 27 + .../analysis/rust/RustDemanglerAnalyzer.java | 255 +++++ .../analysis/rust/RustStringAnalyzer.java | 129 +++ .../core/analysis/rust/RustUtilities.java | 106 ++ .../rust/demangler/RustDemangler.java | 130 +++ .../rust/demangler/RustDemanglerFormat.java | 74 ++ .../rust/demangler/RustDemanglerLegacy.java | 131 +++ .../rust/demangler/RustDemanglerOptions.java | 98 ++ .../rust/demangler/RustDemanglerParser.java | 195 ++++ .../rust/demangler/RustDemanglerV0.java | 934 ++++++++++++++++++ .../datamgr/util/DataTypeArchiveUtility.java | 9 +- .../ghidra/app/util/opinion/ElfLoader.java | 3 +- .../app/util/opinion/ElfProgramBuilder.java | 19 + .../ghidra/app/util/opinion/MachoLoader.java | 6 +- .../app/util/opinion/MachoProgramBuilder.java | 20 + .../ghidra/app/util/opinion/PeLoader.java | 43 +- .../analyzers/RustDemanglerLegacyTest.java | 61 ++ .../app/analyzers/RustDemanglerV0Test.java | 96 ++ .../util/constraint/CompilerConstraint.java | 58 +- .../ExecutableFormatConstraint.java | 8 +- .../util/constraint/LanguageConstraint.java | 11 +- Ghidra/Processors/x86/certification.manifest | 5 + .../x86/data/extensions/rust/unix/cc.xml | 88 ++ .../data/extensions/rust/unix/probe_fixup.xml | 8 + .../data/extensions/rust/unix/try_fixup.xml | 8 + .../extensions/rust/windows/probe_fixup.xml | 8 + .../extensions/rust/windows/try_fixup.xml | 8 + .../Processors/x86/data/languages/x86.opinion | 4 +- 33 files changed, 2627 insertions(+), 85 deletions(-) create mode 100644 Ghidra/Features/Base/data/RustFunctionsThatDoNotReturn create mode 100644 Ghidra/Features/Base/data/typeinfo/rust/rust-common.gdt create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustConstants.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustDemanglerAnalyzer.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustStringAnalyzer.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustUtilities.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemangler.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerFormat.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerLegacy.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerOptions.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerParser.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerV0.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/app/analyzers/RustDemanglerLegacyTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/app/analyzers/RustDemanglerV0Test.java create mode 100644 Ghidra/Processors/x86/data/extensions/rust/unix/cc.xml create mode 100644 Ghidra/Processors/x86/data/extensions/rust/unix/probe_fixup.xml create mode 100644 Ghidra/Processors/x86/data/extensions/rust/unix/try_fixup.xml create mode 100644 Ghidra/Processors/x86/data/extensions/rust/windows/probe_fixup.xml create mode 100644 Ghidra/Processors/x86/data/extensions/rust/windows/try_fixup.xml diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 38fff796af..6a4820c267 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -18,6 +18,7 @@ data/ExtensionPoint.manifest||GHIDRA||||END| data/GolangFunctionsThatDoNotReturn||GHIDRA||||END| data/MachOFunctionsThatDoNotReturn||GHIDRA||||END| data/PEFunctionsThatDoNotReturn||GHIDRA||||END| +data/RustFunctionsThatDoNotReturn||GHIDRA||||END| data/base.file.extensions.icons.theme.properties||GHIDRA||||END| data/base.icons.theme.properties||GHIDRA||||END| data/base.listing.theme.properties||GHIDRA||||END| @@ -91,6 +92,7 @@ data/typeinfo/golang/golang_1.18_anybit_any.gdt||GHIDRA||||END| data/typeinfo/golang/golang_1.19_anybit_any.gdt||GHIDRA||||END| data/typeinfo/golang/golang_1.20_anybit_any.gdt||GHIDRA||||END| data/typeinfo/mac_10.9/mac_osx.gdt||GHIDRA||||END| +data/typeinfo/rust/rust-common.gdt||GHIDRA||||END| data/typeinfo/win32/msvcrt/clsids.txt||GHIDRA||reviewed||END| data/typeinfo/win32/msvcrt/guids.txt||GHIDRA||reviewed||END| data/typeinfo/win32/msvcrt/iids.txt||GHIDRA||||END| diff --git a/Ghidra/Features/Base/data/RustFunctionsThatDoNotReturn b/Ghidra/Features/Base/data/RustFunctionsThatDoNotReturn new file mode 100644 index 0000000000..ec06a1d4e1 --- /dev/null +++ b/Ghidra/Features/Base/data/RustFunctionsThatDoNotReturn @@ -0,0 +1,20 @@ +# Rust function names which do not return +# (names should not start with '_' since these are stripped during checking) +ZN5alloc5alloc18handle_alloc_error17h* +ZN5alloc5alloc18handle_alloc_error8rt_error17h* +ZN5alloc7raw_vec17capacity_overflow17h* +ZN3std5alloc8rust_oom17h* +ZN3std10sys_common9backtrace26__rust_end_short_backtrace17h* +ZN4core9panicking5panic17h* +ZN4core9panicking18panic_bounds_check17h* +ZN4core9panicking9panic_fmt17h* +ZN5alloc5alloc18handle_alloc_error17h* +ZN3std7process5abort17h* +ZN3std3sys4unix14abort_internal17h* +rust_start_panic +rust_panic_cleanup +rust_foreign_exception +rust_drop_panic +rust_begin_unwind +rg_oom +abort diff --git a/Ghidra/Features/Base/data/noReturnFunctionConstraints.xml b/Ghidra/Features/Base/data/noReturnFunctionConstraints.xml index 5658ad2140..c4e7bfc079 100644 --- a/Ghidra/Features/Base/data/noReturnFunctionConstraints.xml +++ b/Ghidra/Features/Base/data/noReturnFunctionConstraints.xml @@ -3,6 +3,9 @@ GolangFunctionsThatDoNotReturn + + RustFunctionsThatDoNotReturn + ElfFunctionsThatDoNotReturn @@ -15,6 +18,9 @@ GolangFunctionsThatDoNotReturn + + RustFunctionsThatDoNotReturn + PEFunctionsThatDoNotReturn diff --git a/Ghidra/Features/Base/data/typeinfo/rust/rust-common.gdt b/Ghidra/Features/Base/data/typeinfo/rust/rust-common.gdt new file mode 100644 index 0000000000000000000000000000000000000000..1ebeeff1b774e6ae6e9c51b13745e327f661eba4 GIT binary patch literal 3656 zcmZuzcT`hp7SAj@k`V+ffFo_-ZN5ELG|;TlA~18noArktzS9}> zZ)iPqVfr2vsxs=)IfN$(}1|I?6&V!rszq`Ho_UE63kS9=d z$24A$?^D|ULqw`o=o}uPPdqW7Xv61Y%o8ku`BrIXF7Qcen7;XjIRo5BGaqT%ovenK2Rt9=>%0Rw->fXx5@liM6xkT%2bOQ1cePvWmk(TX17i<16k8{rOxOQ0Q%GDc&gSEg6>e!3?7-+J(#=v3+Ygeboa{+Hru_3a=3eH9S+?H zMsf%5_Eq!}ORM1zU%w*tojJjscJfLpVS6)6MAzpNjL4R`Y~tym-#kNUzQh8Y&xhSP z-HdyeO6{&%SeQ{RT~4l{Bap9(2%$#kh4sNO%<1qeqJVHRH12aU(*H}7jhV^NWNd{q zth-FdS-xD}KJWW92Gb09JMdwGh>ezTC^5>x6xUTb{9hN#C&mi3G=`#AYD-dLV-~w- z=zf*}vRkj`w9wsDZG@U@6JvmRpwjHg?tivK7WO6=v%_C`xzh79_P<7l1iz=Y=h(8H zkA-*)W`|XGp^fL9?}iiRI@ZSpgV2ZZ|EAX+65-|oPoOgb*C!^K<5k3L>kV`F$%&Ql z^PDbE+uDaKu@-7`R}My9ELrwVb5}(~zKTwn2;+)S^)WD5G-)9SBeF-+DG!)`j6Ecm zq6Piz-hq104ss9TgODXP)#xba#No@#*pzzBHTtU$ER1R^hLOmsPSOS=#a@;uw>eR|LADhY8ySk7BoxN?v)P+h$OFHG3;T+e}#;EIDW`Xh8AeZP!QD!M0c zLNHdFZOO=I3Fc`D$Pfs_y_O^1yJy)}U!9lQixQob*fY#G;w-I?B^ zxJsqnMmX1rL2_SABx`7Km#*B2NAs{+4LDHsA7D==N1?Cdmf9B+2xJxlx3 z+Uo@;KxWEx^m?rO%B9k=Tom<*uNIxU-ac7+H6r|r7CDN%XG7Lv=$dwIGKt{lhcTwD z>gqTOi^FDWd`|22D3v=|^@Rut<)%|4)|(aP4_!4%0*B~=mAN$%HS!!89sh(AKwhS< z3K<8rNMT3N6ZCw=*HdG`ORQ>h>Uu>hF6czts$e9$oV^*%3k<~haB*=7o7ak6>ltEO z$JAS`XYibaU#Y8l6SsLKxO5X00=@c??~#Gry}J$qN3@I>P!)6`_Cn^&8r?@7Q8BoY zS6M4|ONFBiX+p&Z8+oNq#A?aO^pDe)2PBTZvW1i5yvyi>O>JI%xEMwUj=rQjajp1{ zy=CO9!@_m-iMsxF!f2ET@+7ssh}ht-xjuI{c6h@!;8YFt3LzVHg7JHr5C9(jZDxEm zYkeZ_W2eY`eC_ZYSH19}W8~hb%L0CzG^B%ui|EoI64$x0*^67V+a& z-7Kpj+*}bZvIytV<(RB@!?5M;c&i5HxzsoG znR4q-IZd#aZ|^I(|Ct>|@5TF(eV_}8h{*-D-T1oPN9uMmBwWWe*Gc}A%Tvx!L?65L z6*)FWQ>l&}@v2Tpjwbmq^a$Q$oiZZ?)h570u6Z9T9+~uXcHz3=-5H?=PU!hGWpSiZ zd`-K`b6ZvQOI0#b9a%MTp909$G+(cBXS&;5neiZbxv|inQj86VFqvVgWD-+HN7s)z z5t9!i4OM9qZz(a9ypHjg?#`Aeqg9o|u#H&8n^+6!_G*s`;{suOPcBlJ#&XyCAR{yN zy^G`AKU~_mDnBV^ur~F54}VsW%w~9}e0w0E`TbK-!5H#JCmbDD%}suR79%h-jrOz4 zjRBaFk1s1em)>*KG_3$K?bEy&`|-PWY;W#1mWYKwws_1COSl3#@R%xEh?U#nG<&Z@ zo1J%vEZW@MOuHN7OvGVVrX40G^n8;g@W^Z}&y!8I$NbEbg>)4akA|w+tOQ6 zZ#{m8hI-T$F_{vqV6AO+tkC81I8?*B<69@tCx9Hdw4|)t4#Px91}J)XA~SJnU54%$ zUt^R%L!qHlC7&Jgf-BlE!jyP^Nh|!%`eWOyMhD<%(vE%uy_Q;ejufpn3xR<*O`7@Z zzS3H~)ZR4*Z{^qBk5UE%ocCPZy~K#fO` ziABnSs5-fPrT-$uN02VfG^<-Q41EZROCXyLkv}i%-LSRk1MHFHD$YwiU)dwwOn(wv`qxWPlq1_sZc_dJ{bDp+p0mF+?CueM{mH2ZDqMsYo`Ri zo*&ubA~)g@_~J&CMS57)lO(J3O6Zd9*6&m0p@y^I;(*8JvyNd8G?XhalpAIhQjev> zAQs9EJcSGnV?pZM@Ci3r#Qhc8c*|V1<1L0;T`6*ArUn)gG)dcSDS5+Y6z5(_S$_CT zal(}G@X}fy8PhQ?Psk)!81u?9UnFDB_Oga;D$0o|{Dr;7eKnY__o7?=?U6>dZnT0+ zs^)EGRsgMfE=i{}adElxj-iUFjT>k4yY)rUJf!^^&EQ|WaruuPDuqXbgT|0Qheb%9 zV@L)8s34O=KcCdSs%U$@y{)UQE<3e;aB{r~Mp*xxDFPzOk zhY|pREb<0OmVXhFCqupXt{1MZ#b1CEj`LgLyi4!uWs;mBbB^EE4*FDHiM=ew(p=AY zztEU7Q|JR)yp$V-z=AqBdJE2ndjlGgAGYBk=r(M*cN-o#y9LjA@7;pYm$%{gL))<4 zO(C!&KF3Ec=WUT{ACM<%36^NK!uwfeDHix0ui$s+Qxs{abpo{pRgfH|w7}@ZN}%t) zO;!%io|m*CCS!&ZLyA8lbWP$ckS|Yl_T0Z;U3OdJ%EtYara$-FMToK2$T_mdBe}bG zqzAng%k9}75VE&q#A7V*{}~WdQRdg|x7Zs)Pn%Rayw^yTh3#bLobmJ5B=MEr-140% zqHtMQUgOyIMeg8YdE4HrzLgU>m}x2?f1b6k zLs`PIAHB;xpVw%ceYZLyT!N_0)>83uRC`x_n(9*T?iCVSG4cLU%+)2>i}EcU$R|no z?gXp2;j>v@fCjQ<19SjY@*uFnejy!{_C&BF9Y~)8{KLgT8vNt_JA~mqTTlPm1^N%J CA=rcf literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/NoReturnFunctionAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/NoReturnFunctionAnalyzer.java index c040a416c4..d0cedf1bb0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/NoReturnFunctionAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/NoReturnFunctionAnalyzer.java @@ -16,9 +16,7 @@ package ghidra.app.plugin.core.analysis; import java.io.*; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import generic.jar.ResourceFile; import ghidra.app.cmd.function.CreateFunctionCmd; @@ -47,6 +45,7 @@ public class NoReturnFunctionAnalyzer extends AbstractAnalyzer { private boolean createBookmarksEnabled = OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED; private Set functionNames; + private Set wildcardFunctionNames; public NoReturnFunctionAnalyzer() { super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER); @@ -91,62 +90,19 @@ public class NoReturnFunctionAnalyzer extends AbstractAnalyzer { name = name.substring(startIndex); } - if (!functionNames.contains(name)) { + // check for exact match + if (functionNames.contains(name)) { + makeNoReturnFunction(program, symbol, monitor, log); continue; } - // skip noreturn marking if its namespace is not global, library, or std - // it prevents class methods (e.g. Menu::_exit()) incorrectly marked as noreturn - Namespace parentNamespace = symbol.getParentNamespace(); - if (parentNamespace != null && !parentNamespace.isGlobal() && !parentNamespace.isLibrary()) { - List pathList = parentNamespace.getPathList(true); - if (!(pathList.size() == 1 && pathList.get(0) == "std")) { - continue; + // if any wildcarded names, check for prefix match + for (String functionName : wildcardFunctionNames) { + if (name.startsWith(functionName)) { + makeNoReturnFunction(program, symbol, monitor, log); + break; } } - - // if this is an external entry place holder, create the function in the external entry location - symbol = checkForAssociatedExternalSymbol(symbol); - - if (symbol.isExternal()) { - ExternalLocation externalLocation = - program.getExternalManager().getExternalLocation(symbol); - if (externalLocation != null) { - Function functionAt = externalLocation.createFunction(); - //Msg.debug(this, - // "Setting \"no return\" flag on external function " + symbol.getName(true)); - functionAt.setNoReturn(true); - } - continue; - } - - Address address = symbol.getAddress(); - if (symbol.getSymbolType() == SymbolType.LABEL) { - if (!SymbolType.FUNCTION.isValidParent(program, parentNamespace, address, false)) { - continue; // skip if parent does not permit function creation - } - CreateFunctionCmd fCommand = new CreateFunctionCmd(address); - fCommand.applyTo(program, monitor); - } - - Function functionAt = program.getFunctionManager().getFunctionAt(address); - if (functionAt == null) { - log.appendMsg("Failed to create \"no return\" function " + symbol.getName(true) + - " at " + address); - continue; - } - - //Msg.debug(this, "Setting \"no return\" flag on function " + symbol.getName(true) + - // " at " + address); - - functionAt.setNoReturn(true); - - // disassembled later after all bad functions have been marked - - if (createBookmarksEnabled) { - program.getBookmarkManager().setBookmark(address, BookmarkType.ANALYSIS, - "Non-Returning Function", "Non-Returning Function Identified"); - } } // now that all the functions are set, safe to disassemble @@ -154,6 +110,72 @@ public class NoReturnFunctionAnalyzer extends AbstractAnalyzer { return true; } + /** + * Make the symbol into a non-returning function + * + * @param program program + * @param symbol symbol for a function + * @param monitor monitor + * @param log log for errors + */ + private void makeNoReturnFunction(Program program, Symbol symbol, TaskMonitor monitor, + MessageLog log) { + // skip noreturn marking if its namespace is not global, library, or std + // it prevents class methods (e.g. Menu::_exit()) incorrectly marked as noreturn + Namespace parentNamespace = symbol.getParentNamespace(); + if (parentNamespace != null && !parentNamespace.isGlobal() && + !parentNamespace.isLibrary()) { + List pathList = parentNamespace.getPathList(true); + if (!(pathList.size() == 1 && pathList.get(0) == "std")) { + return; + } + } + + // if this is an external entry place holder, create the function in the external entry location + symbol = checkForAssociatedExternalSymbol(symbol); + + if (symbol.isExternal()) { + ExternalLocation externalLocation = + program.getExternalManager().getExternalLocation(symbol); + if (externalLocation != null) { + Function functionAt = externalLocation.createFunction(); + //Msg.debug(this, + // "Setting \"no return\" flag on external function " + symbol.getName(true)); + functionAt.setNoReturn(true); + } + return; + } + + Address address = symbol.getAddress(); + if (symbol.getSymbolType() == SymbolType.LABEL) { + if (!SymbolType.FUNCTION.isValidParent(program, parentNamespace, address, false)) { + return; // skip if parent does not permit function creation + } + CreateFunctionCmd fCommand = new CreateFunctionCmd(address); + fCommand.applyTo(program, monitor); + } + + Function functionAt = program.getFunctionManager().getFunctionAt(address); + if (functionAt == null) { + log.appendMsg("Failed to create \"no return\" function " + symbol.getName(true) + + " at " + address); + return; + } + + //Msg.debug(this, "Setting \"no return\" flag on function " + symbol.getName(true) + + // " at " + address); + + functionAt.setNoReturn(true); + + // disassembled later after all bad functions have been marked + + if (createBookmarksEnabled) { + program.getBookmarkManager() + .setBookmark(address, BookmarkType.ANALYSIS, "Non-Returning Function", + "Non-Returning Function Identified"); + } + } + /** * If symbol corresponds to a pointer which references an external symbol return * the referenced external symbol, otherwise return the symbol provided. @@ -192,6 +214,7 @@ public class NoReturnFunctionAnalyzer extends AbstractAnalyzer { } functionNames = new HashSet<>(); + wildcardFunctionNames = new HashSet<>(); ResourceFile[] files = NonReturningFunctionNames.findDataFiles(program); for (ResourceFile file : files) { @@ -217,7 +240,16 @@ public class NoReturnFunctionAnalyzer extends AbstractAnalyzer { "' specified in file: " + file.getAbsolutePath()); line = line.substring(startIndex); } - functionNames.add(line.trim()); + + String funcName = line.trim(); + if (funcName.endsWith("*")) { + // if funcName has a wildcard at the end, put on wildcard list + funcName = funcName.substring(0, funcName.length() - 1); + wildcardFunctionNames.add(funcName); + } + else { + functionNames.add(funcName); + } } } finally { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustConstants.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustConstants.java new file mode 100644 index 0000000000..032cb0565a --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustConstants.java @@ -0,0 +1,27 @@ +/* ### + * 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.analysis.rust; + +import ghidra.program.model.data.CategoryPath; + +public class RustConstants { + public static final CategoryPath RUST_CATEGORYPATH = new CategoryPath("/rust"); + public static final byte[] RUST_SIGNATURE_1 = "RUST_BACKTRACE".getBytes(); + public static final byte[] RUST_SIGNATURE_2 = "/rustc/".getBytes(); + public static final String RUST_EXTENSIONS_PATH = "/extensions/rust/"; + public static final String RUST_EXTENSIONS_UNIX = "unix"; + public static final String RUST_EXTENSIONS_WINDOWS = "windows"; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustDemanglerAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustDemanglerAnalyzer.java new file mode 100644 index 0000000000..e9586fa3ac --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustDemanglerAnalyzer.java @@ -0,0 +1,255 @@ +/* ### + * 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.analysis.rust; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Arrays; + +import docking.options.editor.BooleanEditor; +import ghidra.app.plugin.core.analysis.AbstractDemanglerAnalyzer; +import ghidra.app.plugin.core.analysis.rust.demangler.*; +import ghidra.app.services.AnalysisPriority; +import ghidra.app.util.demangler.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.HelpLocation; +import ghidra.util.SystemUtilities; +import ghidra.util.task.TaskMonitor; + +/** + * A version of the demangler analyzer to handle Rust symbols + */ +public class RustDemanglerAnalyzer extends AbstractDemanglerAnalyzer { + + private static final String NAME = "Demangler Rust"; + private static final String DESCRIPTION = "Attempt to demangle any symbols mangled by rustc."; + + private static final String OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS = + "Demangle Only Known Mangled Symbols"; + private static final String OPTION_DESCRIPTION_USE_KNOWN_PATTERNS = + "Only demangle symbols that follow known compiler mangling patterns. " + + "Leaving this option off may cause non-mangled symbols to get demangled."; + + private static final String OPTION_NAME_APPLY_CALLING_CONVENTION = + "Apply Function Calling Conventions"; + private static final String OPTION_DESCRIPTION_APPLY_CALLING_CONVENTION = + "Apply any recovered function signature calling convention"; + + static final String OPTION_NAME_USE_DEPRECATED_DEMANGLER = "Use Deprecated Demangler"; + private static final String OPTION_DESCRIPTION_DEPRECATED_DEMANGLER = + "Use the deprecated demangler when the modern demangler cannot demangle a " + + "given string"; + + static final String OPTION_NAME_DEMANGLER_FORMAT = "Demangler Format"; + private static final String OPTION_DESCRIPTION_DEMANGLER_FORMAT = + "The demangling format to use"; + + private boolean applyCallingConvention = true; + private boolean demangleOnlyKnownPatterns = true; + private RustDemanglerFormat demanglerFormat = RustDemanglerFormat.AUTO; + private boolean useDeprecatedDemangler = false; + + private RustDemangler demangler = new RustDemangler(); + + public RustDemanglerAnalyzer() { + super(NAME, DESCRIPTION); + // Set priority to one before the default AbstractDemanglerAnalyzer priority + setPriority(AnalysisPriority.DATA_TYPE_PROPOGATION.before().before().before().before()); + setDefaultEnablement(true); + } + + @Override + public boolean canAnalyze(Program program) { + return demangler.canDemangle(program); + } + + @Override + public void registerOptions(Options options, Program program) { + + HelpLocation help = new HelpLocation("AutoAnalysisPlugin", "Demangler_Analyzer"); + + options.registerOption(OPTION_NAME_APPLY_CALLING_CONVENTION, applyCallingConvention, help, + OPTION_DESCRIPTION_APPLY_CALLING_CONVENTION); + + options.registerOption(OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS, demangleOnlyKnownPatterns, + help, OPTION_DESCRIPTION_USE_KNOWN_PATTERNS); + + BooleanEditor deprecatedEditor = null; + FormatEditor formatEditor = null; + if (!SystemUtilities.isInHeadlessMode()) { + // Only add the custom options editor when not headless. The custom editor allows + // the list of choices presented to the user to change depending on the state of the + // useDeprecatedDemangler flag. + deprecatedEditor = new BooleanEditor(); + deprecatedEditor.setValue(Boolean.valueOf(useDeprecatedDemangler)); + formatEditor = new FormatEditor(demanglerFormat, deprecatedEditor); + deprecatedEditor.addPropertyChangeListener(formatEditor); + } + + options.registerOption(OPTION_NAME_USE_DEPRECATED_DEMANGLER, OptionType.BOOLEAN_TYPE, + useDeprecatedDemangler, help, OPTION_DESCRIPTION_DEPRECATED_DEMANGLER, + deprecatedEditor); + + options.registerOption(OPTION_NAME_DEMANGLER_FORMAT, OptionType.ENUM_TYPE, demanglerFormat, + help, OPTION_DESCRIPTION_DEMANGLER_FORMAT, formatEditor); + } + + @Override + public void optionsChanged(Options options, Program program) { + applyCallingConvention = + options.getBoolean(OPTION_NAME_APPLY_CALLING_CONVENTION, applyCallingConvention); + demangleOnlyKnownPatterns = + options.getBoolean(OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS, demangleOnlyKnownPatterns); + demanglerFormat = options.getEnum(OPTION_NAME_DEMANGLER_FORMAT, RustDemanglerFormat.AUTO); + useDeprecatedDemangler = + options.getBoolean(OPTION_NAME_USE_DEPRECATED_DEMANGLER, useDeprecatedDemangler); + } + + @Override + protected DemanglerOptions getOptions() { + RustDemanglerOptions options = + new RustDemanglerOptions(demanglerFormat, useDeprecatedDemangler); + options.setDoDisassembly(true); + options.setApplyCallingConvention(applyCallingConvention); + options.setDemangleOnlyKnownPatterns(demangleOnlyKnownPatterns); + return options; + } + + @Override + protected DemangledObject doDemangle(String mangled, DemanglerOptions demanglerOptions, + MessageLog log) throws DemangledException { + return demangler.demangle(mangled, demanglerOptions); + } + + @Override + protected void apply(Program program, Address address, DemangledObject demangled, + DemanglerOptions options, MessageLog log, TaskMonitor monitor) { + try { + if (demangled instanceof DemangledFunction defunc) { + defunc.applyTo(program, address, options, monitor); + Function func = program.getFunctionManager().getFunctionAt(address); + if (func != null) { + // if has no return type and no parameters, don't trust that it is void and unlock + // the signature so the decompiler can figure it out + if (defunc.getReturnType() == null && defunc.getParameters().size() == 0) { + func.setSignatureSource(SourceType.DEFAULT); + } + return; + } + } + } + catch (Exception e) { + // Failed to apply demangled function + } + + // Apply it as a variable instead of as a function + + String mangled = demangled.getMangledString(); + String original = demangled.getOriginalDemangled(); + String name = demangled.getName(); + Demangled namespace = demangled.getNamespace(); + + DemangledVariable demangledVariable = new DemangledVariable(mangled, original, name); + demangledVariable.setNamespace(namespace); + + super.apply(program, address, demangledVariable, options, log, monitor); + } + + private static class FormatEditor extends EnumEditor implements PropertyChangeListener { + + private final FormatSelector selector; + private final BooleanEditor isDeprecated; + + FormatEditor(RustDemanglerFormat value, BooleanEditor isDeprecated) { + setValue(value); + this.isDeprecated = isDeprecated; + this.selector = new FormatSelector(this); + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + @Override + public FormatSelector getCustomEditor() { + return selector; + } + + @Override + public RustDemanglerFormat[] getEnums() { + return Arrays.stream(RustDemanglerFormat.values()) + .filter(this::filter) + .toArray(RustDemanglerFormat[]::new); + } + + @Override + public String[] getTags() { + return Arrays.stream(RustDemanglerFormat.values()) + .filter(this::filter) + .map(RustDemanglerFormat::name) + .toArray(String[]::new); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + RustDemanglerFormat format = selector.getFormat(); + selector.reset(getTags()); + if (format.isAvailable(isDeprecatedDemangler())) { + setValue(format); + selector.setFormat(format); + } + else { + setValue(RustDemanglerFormat.AUTO); + } + } + + private boolean isDeprecatedDemangler() { + return (Boolean) isDeprecated.getValue(); + } + + private boolean filter(RustDemanglerFormat f) { + return f.isAvailable(isDeprecatedDemangler()); + } + } + + private static class FormatSelector extends PropertySelector { + + public FormatSelector(FormatEditor fe) { + super(fe); + } + + void reset(String[] tags) { + removeAllItems(); + for (String tag : tags) { + addItem(tag); + } + } + + RustDemanglerFormat getFormat() { + return RustDemanglerFormat.valueOf((String) getSelectedItem()); + } + + void setFormat(RustDemanglerFormat format) { + setSelectedItem(format.name()); + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustStringAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustStringAnalyzer.java new file mode 100644 index 0000000000..e8c4877a2b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustStringAnalyzer.java @@ -0,0 +1,129 @@ +/* ### + * 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.analysis.rust; + +import ghidra.app.services.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.Options; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.Program; +import ghidra.program.util.DefinedDataIterator; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Splits non-terminated strings into separate strings + */ +public class RustStringAnalyzer extends AbstractAnalyzer { + + private final static String NAME = "Rust String Analyzer"; + private final static String DESCRIPTION = "Analyzer to split rust static strings into slices"; + + public RustStringAnalyzer() { + super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER); + setPriority(AnalysisPriority.LOW_PRIORITY); + setDefaultEnablement(true); + setSupportsOneTimeAnalysis(true); + } + + @Override + public boolean canAnalyze(Program program) { + String name = program.getCompiler(); + return name.contains("rustc"); + } + + @Override + public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) + throws CancelledException { + + DefinedDataIterator dataIterator = DefinedDataIterator.definedStrings(program); + + for (Data data : dataIterator) { + Address start = data.getAddress(); + int length = data.getLength(); + + recurseString(program, start, length); + monitor.checkCancelled(); + } + + return true; + } + + @Override + public void registerOptions(Options options, Program program) { + // No options + } + + @Override + public void optionsChanged(Options options, Program program) { + // Options changed + } + + private static void recurseString(Program program, Address start, int maxLen) { + int newLength = getMaxStringLength(program, start, maxLen); + if (newLength <= 0) { + return; + } + + DataType dt = + new ArrayDataType(CharDataType.dataType, newLength, CharDataType.dataType.getLength()); + + try { + DataUtilities.createData(program, start, dt, 0, + DataUtilities.ClearDataMode.CLEAR_ALL_CONFLICT_DATA); + + if (newLength < maxLen) { + recurseString(program, start.add(newLength), maxLen - newLength); + } + } + catch (Exception e) { + // Couldn't define string + } + } + + /** + * Get the number of bytes to the next reference, or the max length + * @param program + * @param address + * @param maxLen + * @return maximum length to create the string + */ + private static int getMaxStringLength(Program program, Address address, int maxLen) { + AddressIterator refIter = + program.getReferenceManager().getReferenceDestinationIterator(address.next(), true); + + Address next = refIter.next(); + if (next == null) { + return -1; + } + + long len = -1; + try { + len = next.subtract(address); + if (len > maxLen) { + len = maxLen; + } + return (int) len; + } + catch (IllegalArgumentException e) { + // bad address subtraction + } + + return (int) len; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustUtilities.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustUtilities.java new file mode 100644 index 0000000000..bbb83b573b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustUtilities.java @@ -0,0 +1,106 @@ +/* ### + * 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.analysis.rust; + +import java.io.IOException; +import java.io.InputStream; + +import org.xml.sax.SAXException; + +import generic.jar.ResourceFile; +import ghidra.app.plugin.processors.sleigh.SleighException; +import ghidra.framework.Application; +import ghidra.framework.store.LockException; +import ghidra.program.database.SpecExtension; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; +import ghidra.xml.XmlParseException; + +/** + * Rust utility functions + */ +public class RustUtilities { + /** + * Checks if a given {@link Program} was written in Rust + * + * @param program The {@link Program} to check + * @param blockName The name of the {@link MemoryBlock} to scan for Rust signatures + * @return True if the given {@link Program} was written in Rust; otherwise, false + * @throws IOException if there was an IO-related error + */ + public static boolean isRust(Program program, String blockName) throws IOException { + MemoryBlock[] blocks = program.getMemory().getBlocks(); + for (MemoryBlock block : blocks) { + if (block.getName().equals(blockName)) { + byte[] bytes = block.getData().readAllBytes(); + if (containsBytes(bytes, RustConstants.RUST_SIGNATURE_1)) { + return true; + } + if (containsBytes(bytes, RustConstants.RUST_SIGNATURE_2)) { + return true; + } + } + } + return false; + } + + public static int addExtensions(Program program, TaskMonitor monitor, String subPath) + throws IOException { + var processor = program.getLanguageCompilerSpecPair().getLanguage().getProcessor(); + ResourceFile module = Application.getModuleDataSubDirectory(processor.toString(), + RustConstants.RUST_EXTENSIONS_PATH + subPath); + + int extensionCount = 0; + + ResourceFile[] files = module.listFiles(); + for (ResourceFile file : files) { + InputStream stream = file.getInputStream(); + byte[] bytes = stream.readAllBytes(); + String xml = new String(bytes); + + try { + SpecExtension extension = new SpecExtension(program); + extension.addReplaceCompilerSpecExtension(xml, monitor); + extensionCount += 1; + } + catch (SleighException | SAXException | XmlParseException | LockException e) { + Msg.error(program, "Failed to load load cspec extensions"); + } + } + + return extensionCount; + } + + private static boolean containsBytes(byte[] data, byte[] bytes) { + for (int i = 0; i < data.length - bytes.length; i++) { + boolean isMatch = true; + for (int j = 0; j < bytes.length; j++) { + if (Byte.compare(data[i + j], bytes[j]) != 0) { + isMatch = false; + break; + } + } + + if (isMatch) { + return true; + } + } + + return false; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemangler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemangler.java new file mode 100644 index 0000000000..81df013870 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemangler.java @@ -0,0 +1,130 @@ +/* ### + * 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.analysis.rust.demangler; + +import ghidra.app.util.demangler.*; +import ghidra.program.model.listing.Program; + +/** + * A class for demangling debug symbols created using rustc + */ +public class RustDemangler implements Demangler { + + public RustDemangler() { + // needed to instantiate dynamically + } + + @Override + public DemanglerOptions createDefaultOptions() { + return new RustDemanglerOptions(); + } + + @Override + public boolean canDemangle(Program program) { + String name = program.getCompiler(); + return name.contains("rustc"); + } + + @Override + @Deprecated(since = "9.2", forRemoval = true) + public DemangledObject demangle(String mangled, boolean demangleOnlyKnownPatterns) + throws DemangledException { + return null; + } + + @Override + public DemangledObject demangle(String mangled, DemanglerOptions options) { + RustDemanglerOptions rustOptions = getRustOptions(options); + + if (skip(mangled, rustOptions)) { + return null; + } + + String demangled = null; + + if (rustOptions.getDemanglerFormat() == RustDemanglerFormat.LEGACY || + rustOptions.getDemanglerFormat() == RustDemanglerFormat.AUTO) { + demangled = RustDemanglerLegacy.demangle(mangled); + } + + if (rustOptions.getDemanglerFormat() == RustDemanglerFormat.V0 || + (rustOptions.getDemanglerFormat() == RustDemanglerFormat.AUTO && demangled == null)) { + demangled = RustDemanglerV0.demangle(mangled); + } + + RustDemanglerParser parser = new RustDemanglerParser(); + DemangledObject demangledObject = parser.parse(mangled, demangled); + + if (options.applyCallingConvention() && demangledObject instanceof DemangledFunction) { + ((DemangledFunction) demangledObject).setCallingConvention("rustcall"); + } + + return demangledObject; + } + + private RustDemanglerOptions getRustOptions(DemanglerOptions options) { + if (options instanceof RustDemanglerOptions) { + return (RustDemanglerOptions) options; + } + + return new RustDemanglerOptions(options); + } + + /** + * Determines if the given mangled string should not be demangled, on the basis + * of if it has a known start pattern + * + * @param mangled the mangled string + * @param options the options + * @return true if the string should not be demangled + */ + private boolean skip(String mangled, RustDemanglerOptions options) { + + // The current list of demangler start patterns + + if (!options.demangleOnlyKnownPatterns()) { + return false; + } + + return !isRustMangled(mangled); + } + + /** + * Return true if the string is a mangled rust string in a rust program + * + * @param mangled potential mangled string + * @return true if the string could be a mangled string in a rust program + */ + public static boolean isRustMangled(String mangled) { + if (mangled.startsWith("_ZN")) { + return true; + } + + if (mangled.startsWith("__ZN")) { + return true; + } + + if (mangled.startsWith("_R")) { + return true; + } + + if (mangled.startsWith("__R")) { + return true; + } + + return false; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerFormat.java new file mode 100644 index 0000000000..8a461e577c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerFormat.java @@ -0,0 +1,74 @@ +/* ### + * 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.analysis.rust.demangler; + +/** + * Enum representation of the available Rust demangler formats + */ +public enum RustDemanglerFormat { + /** Automatic mangling format detection */ + AUTO("", Version.ALL), + /** legacy mangling format */ + LEGACY("legacy", Version.ALL), + /** v0 mangling format */ + V0("v0", Version.MODERN); + + /** the format option string used by the native demangler */ + private final String format; + private final Version version; + + private RustDemanglerFormat(String format, Version version) { + this.format = format; + this.version = version; + } + + /** + * Checks if this format is available in the deprecated rust demangler + * @return true if this format is available in the deprecated rust demangler + */ + public boolean isDeprecatedFormat() { + return version == Version.DEPRECATED || version == Version.ALL; + } + + /** + * Checks if this format is available in a modern version of the rust demangler + * @return true if this format is available in a modern version of the rust demangler + */ + public boolean isModernFormat() { + return version == Version.MODERN || version == Version.ALL; + } + + /** + * Checks if this format is available for the specified demangler + * @param isDeprecated true for the deprecated demangler, false for the modern demangler + * @return true if the format is available + */ + public boolean isAvailable(boolean isDeprecated) { + return isDeprecated ? isDeprecatedFormat() : isModernFormat(); + } + + /** + * Gets the format option to be passed to the demangler via the -s option + * @return the format option to be passed to the demangler + */ + public String getFormat() { + return format; + } + + private enum Version { + DEPRECATED, MODERN, ALL + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerLegacy.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerLegacy.java new file mode 100644 index 0000000000..64646bc1a1 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerLegacy.java @@ -0,0 +1,131 @@ +/* ### + * 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.analysis.rust.demangler; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RustDemanglerLegacy { + public static String demangle(String symbol) { + if (symbol.startsWith("_ZN")) { // Expected + symbol = symbol.substring(3); + } + else if (symbol.startsWith("ZN")) { + // On windows, dbghelp may strip leading underscore + symbol = symbol.substring(2); + } + else if (symbol.startsWith("__ZN")) { + // On macOS, symbols are prefixed with an extra _ + symbol = symbol.substring(4); + } + else { + return null; + } + + // Should only contain ASCII characters + if (!symbol.matches("\\A\\p{ASCII}*\\z")) { + return null; + } + + ArrayList elements = new ArrayList(); + char[] chars = symbol.toCharArray(); + int i = 0; + + while (chars[i] != 'E') { + if (chars[i] < '0' || chars[i] > '9') { + return null; + } + + int l = 0; + while (chars[i + l] >= '0' && chars[i + l] <= '9') { + l += 1; + } + + String lengthString = symbol.substring(i, i + l); + int length = Integer.parseInt(lengthString); + String element = symbol.substring(i + l, i + l + length); + elements.add(element); + i = i + l + length; + } + + for (int j = 0; j < elements.size(); j++) { + String element = elements.get(j); + element = element.replace("$SP$", "@"); + element = element.replace("$BP$", "*"); + element = element.replace("$RF$", "&"); + element = element.replace("$LT$", "<"); + element = element.replace("$GT$", ">"); + element = element.replace("$LP$", "("); + element = element.replace("$RP$", ")"); + element = element.replace("$C$", ","); + + int k = 0; + while (k < element.length()) { + if (element.charAt(k) == '$') { + int l = k + 1; + while (element.charAt(l) != '$') { + l += 1; + } + + l += 1; + + String inner = element.substring(k, l); + if (inner.startsWith("$u")) { + int num = Integer.parseInt(element.substring(k + 2, l - 1), 16); + char newChar = (char) num; + element = element.substring(0, k) + newChar + element.substring(l); + } + } + + k += 1; + } + + elements.set(j, element); + } + + // remove the last hash + if (elements.size() > 1) { + String string = elements.get(elements.size()-1); + if (isUID(string)) { + elements.remove(elements.size()-1); + } + } + + return String.join("::", elements); + } + + /** + * Pattern to identify a legacy rust hash id suffix + * + * Legacy mangled rust symbols: + * - start with _ZN + * - end withe E or E. + * - have a 16 digit hash that starts with 17h + * + * The demangled string has the leading '17' and trailing 'E|E.' removed. + * + * Sample: std::io::Read::read_to_end::hb85a0f6802e14499 + */ + private static final Pattern RUST_LEGACY_HASHID_PATTERN = + Pattern.compile("h[0-9a-f]{16}"); + + private static boolean isUID(String string) { + string = string.trim(); + Matcher m = RUST_LEGACY_HASHID_PATTERN.matcher(string); + return m.matches(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerOptions.java new file mode 100644 index 0000000000..c64483693c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerOptions.java @@ -0,0 +1,98 @@ +/* ### + * 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.analysis.rust.demangler; + +import ghidra.app.util.demangler.DemanglerOptions; + +/** + * Rust demangler options + */ +public class RustDemanglerOptions extends DemanglerOptions { + + private final RustDemanglerFormat format; + private final boolean isDeprecated; + + /** + * Default constructor to use the modern demangler with auto-detect for the format. This + * constructor will limit demangling to only known symbols. + */ + public RustDemanglerOptions() { + this(RustDemanglerFormat.AUTO); + } + + /** + * Constructor to specify a particular format + * + * @param format signals to use the given format + */ + public RustDemanglerOptions(RustDemanglerFormat format) { + this(format, !format.isModernFormat()); + } + + /** + * Constructor to specify the format to use and whether to prefer the deprecated format when + * both deprecated and modern are available + * + * @param format the format + * @param isDeprecated true if the format is not available in the modern demangler + * @throws IllegalArgumentException if the given format is not available in the deprecated + * demangler + */ + public RustDemanglerOptions(RustDemanglerFormat format, boolean isDeprecated) { + this.format = format; + this.isDeprecated = isDeprecated; + if (!format.isAvailable(isDeprecated)) { + throw new IllegalArgumentException(format.name() + " is not available"); + } + } + + /** + * Copy constructor to create a version of this class from a more generic set of options + * @param copy the options to copy + */ + public RustDemanglerOptions(DemanglerOptions copy) { + super(copy); + + if (copy instanceof RustDemanglerOptions) { + RustDemanglerOptions gCopy = (RustDemanglerOptions) copy; + format = gCopy.format; + isDeprecated = gCopy.isDeprecated; + } + else { + format = RustDemanglerFormat.AUTO; + isDeprecated = false; + } + } + + /** + * Gets the current demangler format + * @return the demangler format + */ + public RustDemanglerFormat getDemanglerFormat() { + return format; + } + + @Override + public String toString() { + //@formatter:off + return "{\n" + + "\tdoDisassembly: " + doDisassembly() + ",\n" + + "\tapplySignature: " + applySignature() + ",\n" + + "\tdemangleOnlyKnownPatterns: " + demangleOnlyKnownPatterns() + ",\n" + + "}"; + //@formatter:on + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerParser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerParser.java new file mode 100644 index 0000000000..461ca0d528 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerParser.java @@ -0,0 +1,195 @@ +/* ### + * 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.analysis.rust.demangler; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import generic.json.Json; +import ghidra.app.util.SymbolPathParser; +import ghidra.app.util.demangler.*; + +/** Parses a demangled rust string */ +public class RustDemanglerParser { + + private static final char NULL_CHAR = '\u0000'; + + private String mangledSource; + private String demangledSource; + + /** + * Parses the given demangled string and creates a {@link DemangledObject} + * + * @param mangled the original mangled text + * @param demangled the demangled text + * @return the demangled object + * @throws RuntimeException if there is an unexpected error parsing + */ + public DemangledObject parse(String mangled, String demangled) throws RuntimeException { + + this.mangledSource = mangled; + this.demangledSource = demangled; + + return parseNext(demangled); + } + + private String removeBadSpaces(String text) { + CondensedString condensedString = new CondensedString(text); + return condensedString.getCondensedText(); + } + + private void setNameAndNamespace(DemangledObject object, String name) { + List names = SymbolPathParser.parse(name, false); + DemangledType namespace = null; + if (names.size() > 1) { + namespace = convertToNamespaces(names.subList(0, names.size() - 1)); + } + + String objectName = names.get(names.size() - 1); + + object.setName(objectName); + object.setNamespace(namespace); + } + + private DemangledObject parseNext(String demangled) { + String nameString = removeBadSpaces(demangled).trim(); + DemangledFunction variable = + new DemangledFunction(mangledSource, demangledSource, (String) null); + setNameAndNamespace(variable, nameString); + return variable; + } + + /** + * Converts the list of names into a namespace demangled type. + * Given names = { "A", "B", "C" }, which represents "A::B::C". + * The following will be created {@literal "Namespace{A}->Namespace{B}->Namespace{C}"} + * and Namespace{C} will be returned. + * + *

This method will also escape spaces separators inside of templates + * (see {@link #removeBadSpaces(String)}). + * + * @param names the names to convert + * @return the newly created type + */ + private DemangledType convertToNamespaces(List names) { + if (names.isEmpty()) { + return null; + } + int index = names.size() - 1; + String rawName = names.get(index); + String escapedName = removeBadSpaces(rawName); + DemangledType myNamespace = new DemangledType(mangledSource, demangledSource, escapedName); + + DemangledType namespace = myNamespace; + while (--index >= 0) { + rawName = names.get(index); + escapedName = removeBadSpaces(rawName); + DemangledType parentNamespace = + new DemangledType(mangledSource, demangledSource, escapedName); + namespace.setNamespace(parentNamespace); + namespace = parentNamespace; + } + return myNamespace; + } + + /** + * A class to handle whitespace manipulation within demangled strings. This class will + * remove bad spaces, which is all whitespace that is not needed to separate distinct objects + * inside of a demangled string. + * + *

Generally, this class removes spaces within templates and parameter lists. It will + * remove some spaces, while converting some to underscores. + */ + private class CondensedString { + + @SuppressWarnings("unused") // used by toString() + private String sourceText; + private String condensedText; + private List parts = new ArrayList<>(); + + CondensedString(String input) { + this.sourceText = input; + this.condensedText = convertGenericSpace(input); + } + + private String convertGenericSpace(String name) { + + int depth = 0; + char last = NULL_CHAR; + for (int i = 0; i < name.length(); ++i) { + + Part part = new Part(); + parts.add(part); + char ch = name.charAt(i); + part.original = Character.toString(ch); + part.condensed = part.original; // default case + if (ch == '<' || ch == '(') { + ++depth; + } + else if ((ch == '>' || ch == ')') && depth != 0) { + --depth; + } + + if (depth > 0 && ch == ' ') { + char next = (i + 1) < name.length() ? name.charAt(i + 1) : NULL_CHAR; + if (isSurroundedByCharacters(last, next)) { + // separate words with a value so they don't run together; drop the other spaces + part.condensed = "_"; + } + else { + part.condensed = ""; + } + } + + last = ch; + } + + return parts.stream().map(p -> p.condensed).collect(Collectors.joining()).trim(); + } + + private boolean isSurroundedByCharacters(char last, char next) { + if (last == NULL_CHAR || next == NULL_CHAR) { + return false; + } + return Character.isLetterOrDigit(last) && Character.isLetterOrDigit(next); + } + + /** + * Returns the original string value that has been 'condensed', which means to remove + * internal spaces + * @return the condensed string + */ + String getCondensedText() { + return condensedText; + } + + @Override + public String toString() { + return Json.toString(this); + } + + private class Part { + String original; + String condensed = ""; + + @Override + public String toString() { + return Json.toString(this); + } + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerV0.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerV0.java new file mode 100644 index 0000000000..bfad6d6c6a --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/RustDemanglerV0.java @@ -0,0 +1,934 @@ +/* ### + * 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.analysis.rust.demangler; + +import java.net.IDN; +import java.util.*; + +/** + * A class that will demangle Rust symbols mangled according to the V0 format. This class + * implements the grammar that will translate a mangled string into a demangled one. + * Documentation is {@link "https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html"} here. + */ +public class RustDemanglerV0 { + + /** + * Demangles a symbol according to the format + * @param symbol the mangled symbol name + * @return the demangled symbol name + */ + public static String demangle(String symbol) { + if (symbol.startsWith("_R")) { + symbol = symbol.substring(2); + } + else if (symbol.startsWith("R")) { + symbol = symbol.substring(1); + } + else if (symbol.startsWith("__R")) { + symbol = symbol.substring(3); + } + + if (!symbol.matches("\\A\\p{ASCII}*\\z")) { + return null; + } + + Symbol cursor = new Symbol(symbol); + + return RustPath.parse(cursor).toString(); + } +} + +/** + * A class that represents a symbol in the demangling process. It keeps track of + * the current state of the symbol and implements various methods to assist with + * demangling it. + */ +class Symbol { + /** A list of backref objects */ + Map backrefs = new HashMap(); + + /** The mangled symbol */ + String mangled; + + /** The current position in the mangled symbol */ + int pos = 0; + + /** + * Creates a symbol object + * @param mangled the mangled symbol name + */ + public Symbol(String mangled) { + this.mangled = mangled; + } + + /** + * Adds a backref to the list + * @param index the index of the backref + * @param value the backref object to add + */ + public void backrefAdd(int index, SymbolNode value) { + backrefs.put(Integer.valueOf(index), value); + index += 1; + } + + /** + * Gets the backref at a certain index + * @param index the index of he backref to return + * @return the backref object + */ + public String getBackref(int index) { + SymbolNode backref = backrefs.get(index); + if (backref != null) { + return backref.toString(); + } + + return "{backref " + index + "}"; + } + + /** + * Returns the number of the encoded backref + * @return the number sting + */ + public String parseBackref() { + if (stripPrefix('B')) { + return parseBase62Number(); + } + + return null; + } + + /** + * Returns the remaining string to be demangled + * @return the mangled string + */ + public String remaining() { + return mangled.substring(pos); + } + + /** + * Strips the first char of the mangled string if it's equal to the argument + * @param c the char to strip + * @return if the strip succeeded + */ + public boolean stripPrefix(char c) { + if (c == nextChar()) { + popChar(); + return true; + } + + return false; + } + + /** + * Gets the next char in the mangled string + * @return the next char + */ + public char nextChar() { + return mangled.charAt(pos); + } + + /** + * Gets the next int in the mangled string + * @return the next int + */ + public int nextInt() { + return mangled.charAt(pos); + } + + /** + * Pops the next char in the mangled string + * @return the next char + */ + public char popChar() { + char c = mangled.charAt(pos); + pos += 1; + return c; + } + + /** + * Parses the following numerical digits in the mangled sting + * @return the parsed integer + */ + public int parseDigits() { + String num = ""; + + if (nextChar() == '0') { + return 0; + } + + while (nextChar() >= '0' && nextChar() <= '9') { + num += popChar(); + } + + return Integer.parseInt(num); + } + + /** + * Parses the string until the passed char is reached + * @param c the char to parse until + * @return the parsed string + */ + public String parseUntil(char c) { + String data = ""; + + while (nextChar() != c) { + data += popChar(); + } + + return data; + } + + /** + * Subtracts one from the position in the mangled string + */ + public void backChar() { + pos -= 1; + } + + /** + * Parses the + * @param n number of characters + * @return the parsed string + */ + public String parseString(int n) { + String s = mangled.substring(pos, pos + n); + pos += n; + return s; + } + + /** + * Returns if the end of the mangled string has been reached + * @return if the end has been reached + */ + public boolean isEmpty() { + return mangled.length() <= pos; + } + + /** + * Parses the following base 62 number + * @return the parsed num string + */ + public String parseBase62Number() { + String numString = parseUntil('_'); + popChar(); + return numString; + } +} + +/** + * A node to be used in symbol parsing + */ +interface SymbolNode { + // Parent class +} + +/** + * A class to represent a nested path node + */ +class RustPathNested implements SymbolNode { + SymbolNode parent; + RustIdentifier identifier; + + public RustPathNested(SymbolNode parent, RustIdentifier identifier) { + this.parent = parent; + this.identifier = identifier; + } + + @Override + public String toString() { + return parent.toString() + "::" + identifier.toString(); + } +} + +/** + * A class to represent a string node + */ +class RustString implements SymbolNode { + String data; + + public RustString(String data) { + this.data = data; + } + + @Override + public String toString() { + return data; + } +} + +/** + * A class that will represent and parse a backref node + */ +class RustBackref implements SymbolNode { + int backref; + Symbol s; + + public RustBackref(int backref, Symbol s) { + this.backref = backref; + this.s = s; + } + + @Override + public String toString() { + return s.getBackref(backref); + } +} + +/** + * A class to represent and parse a rust symbol path node + */ +class RustPath implements SymbolNode { + SymbolNode child; + + public RustPath(SymbolNode child) { + this.child = child; + } + + public RustPath(String child) { + this.child = new RustString(child); + } + + /** + * Parses a rust path from a mangled symbol + * @param s parse the rust path + * @return the rust path object + */ + public static RustPath parse(Symbol s) { + int pos = s.pos - 1; + + if (s.nextChar() == 'B') { + String backref = s.parseBackref(); + int i = Integer.parseInt(backref, 16); + RustBackref b = new RustBackref(i, s); + RustPath path = new RustPath(b); + return path; + } + + char c = s.popChar(); + if (c == 'C') { + // Crate root? + RustIdentifier identifier = RustIdentifier.parse(s, new RustNamespace("crate")); + s.backrefAdd(pos, identifier); + return new RustPath(identifier.toString()); + } + else if (c == 'M') { + RustImplPath implPath = RustImplPath.parse(s); + RustType type = RustType.parse(s); + RustPath path = new RustPath("<" + implPath + "::" + type + ">"); + + s.backrefAdd(pos, path); + return path; + // + // (inherent impl) + } + else if (c == 'X') { + RustImplPath.parse(s); + RustType type = RustType.parse(s); + RustPath parent = RustPath.parse(s); + RustPath path = new RustPath("<" + type + " as " + parent + ">"); + s.backrefAdd(pos, path); + return path; + // + // (trait impl) + } + else if (c == 'Y') { + RustType type = RustType.parse(s); + RustPath parent = RustPath.parse(s); + + RustPath path = new RustPath("<" + type + " as " + parent + ">"); + s.backrefAdd(pos, path); + return path; + // + // (trait definition) + } + else if (c == 'N') { + RustNamespace namespace = RustNamespace.parse(s); + RustPath parent = RustPath.parse(s); + RustIdentifier id = RustIdentifier.parse(s, namespace); + RustPathNested nested = new RustPathNested(parent, id); + + RustPath path = new RustPath(nested.toString()); + s.backrefAdd(pos, path); + return path; + } + else if (c == 'I') { + RustPath parent = RustPath.parse(s); + RustGenericArgs args = RustGenericArgs.parse(s); + + if (args == null) { + RustPath path = new RustPath(parent); + s.backrefAdd(pos, path); + return path; + } + + RustPath path = new RustPath("" + parent + args); + s.backrefAdd(pos, path); + return path; + } + else if (c == 'B') { + s.backChar(); + String b = s.parseBackref(); + int i = Integer.parseInt(b, 16); + RustBackref br = new RustBackref(i, s); + return new RustPath(br); + } + + return null; + } + + @Override + public String toString() { + return child.toString(); + } +} + +/** + * Parses and represents a rust symbol namespace node + */ +class RustNamespace { + String data; + + public RustNamespace(String data) { + this.data = data; + } + + /** + * Parses a rust namespace from a mangled symbol + * @param s symbol to parse + * @return the rust path object + */ + public static RustNamespace parse(Symbol s) { + char c = s.popChar(); + + if (c == 'C') { + // closure + return new RustNamespace("{closure}"); + } + else if (c == 'S') { + // shim + return new RustNamespace("{shim}"); + } + else if (c >= 'A' && c <= 'Z') { + // other special namespaces + return new RustNamespace(String.valueOf(c)); + } + else if (c >= 'a' && c <= 'z') { + // internal namespaces + return new RustNamespace(String.valueOf(c)); + } + + return null; + } + + @Override + public String toString() { + return data; + } +} + +/** + * Parses and represents a rust symbol impl path node + */ +class RustImplPath implements SymbolNode { + RustPath path; + RustString disambiguator; + + public RustImplPath(RustPath path, RustString disambiguator) { + this.path = path; + this.disambiguator = disambiguator; + } + + /** + * Parses a impl rust path from a mangled symbol + * @param s symbol to parse + * @return the rust impl path object + */ + public static RustImplPath parse(Symbol s) { + RustString disambiguator = null; + if (s.nextChar() == 's') { + disambiguator = RustIdentifier.parseDisambiguator(s); + } + + RustPath path = RustPath.parse(s); + + return new RustImplPath(path, disambiguator); + } + + @Override + public String toString() { + String s = path.toString(); + + if (disambiguator != null && disambiguator.toString() != "") { + s += "::" + "[" + disambiguator.toString() + "]"; + } + + return s; + } +} + +/** + * Parses and represents an rust symbol identifier + */ +class RustIdentifier implements SymbolNode { + String id; + RustNamespace namespace; + RustString disambiguator; + + public RustIdentifier(RustNamespace namespace, String id, RustString disambiguator) { + this.id = id; + this.namespace = namespace; + this.disambiguator = disambiguator; + } + + /** + * Parses a rust identifier from a mangled symbol + * @param s symbol to parse + * @param namespace namespace of symbol + * @return the rust identifier object + */ + public static RustIdentifier parse(Symbol s, RustNamespace namespace) { + RustString disambiguator = null; + + if (s.nextChar() == 's') { + disambiguator = parseDisambiguator(s); + } + + String id = parseUndisambiguatedIdentifier(s); + return new RustIdentifier(namespace, id, disambiguator); + } + + /** + * Parses a rust disambiguator from a mangled symbol + * @param s symbol to parse + * @return a string representing the disambiguator + */ + public static RustString parseDisambiguator(Symbol s) { + char c = s.popChar(); + assert c == 's'; + return new RustString(s.parseBase62Number()); + } + + /** + * Parses a rust undisambiguated identifier from a mangled symbol + * @param s symbol to parse + * @return the corresponding string object + */ + public static String parseUndisambiguatedIdentifier(Symbol s) { + boolean punycode = s.stripPrefix('u'); + int num = s.parseDigits(); + + if (s.nextChar() == '_') { + s.popChar(); + } + + if (num == 0) { + char c = s.popChar(); + return "{closure#" + c + "}"; + } + + String bytes = s.parseString(num); + + if (punycode) { + return IDN.toASCII(bytes, IDN.ALLOW_UNASSIGNED); + } + + return bytes; + } + + @Override + public String toString() { + return id.toString(); + } +} + +/** + * Parses and represents rust generic arguments + */ +class RustGenericArgs implements SymbolNode { + ArrayList args; + + public RustGenericArgs(ArrayList args) { + this.args = args; + } + + /** + * Parses generics arguments from a mangled symbol + * @param s symbol to parse + * @return the rust generic arguments object + */ + public static RustGenericArgs parse(Symbol s) { + ArrayList genericArgs = new ArrayList(); + + while (s.nextChar() != 'E') { + RustGenericArg arg = RustGenericArg.parse(s); + if (arg == null) { + return null; + } + + genericArgs.add(arg); + } + + s.popChar(); + + return new RustGenericArgs(genericArgs); + } + + @Override + public String toString() { + String s = ""; + + for (RustGenericArg arg : args) { + s += arg.toString() + ", "; + } + + return "<" + s.substring(0, s.length() - 2) + ">"; + } +} + +/** + * Parses and represents a generic argument node in a rust symbol + */ +class RustGenericArg implements SymbolNode { + SymbolNode child; + + public RustGenericArg(SymbolNode child) { + this.child = child; + } + + /** + * Parses a rust generic argument from a mangled symbol + * @param s symbol to parse + * @return the rust generic argument object + */ + public static RustGenericArg parse(Symbol s) { + SymbolNode lifetime = RustLifetime.parse(s); + if (lifetime != null) { + return new RustGenericArg(lifetime); + } + + if (s.nextChar() == 'K') { + s.popChar(); + SymbolNode constant = RustConst.parse(s); + if (constant != null) { + return new RustGenericArg(constant); + } + } + + SymbolNode type = RustType.parse(s); + if (type != null) { + return new RustGenericArg(type); + } + + return null; + } + + @Override + public String toString() { + return child.toString(); + } +} + +/** + * Parses a rust lifetime from a mangled symbol + */ +class RustLifetime implements SymbolNode { + String num; + + public RustLifetime(String num) { + this.num = num; + } + + /** + * Parses a rust lifetime node from a mangled symbol + * @param s symbol to parse + * @return the rust lifetime node + */ + public static SymbolNode parse(Symbol s) { + if (s.nextChar() != 'L') { + return null; + } + + s.popChar(); + + String num = s.parseBase62Number(); + if (num != null) { + return new RustLifetime(num); + } + + return null; + } + + @Override + public String toString() { + return num; + } +} + +/** + * Parses and represents a rust symbol type node + */ +class RustType implements SymbolNode { + String typeName; + RustPath path; + + public RustType(String typeName) { + this.typeName = typeName; + } + + public RustType(RustPath path) { + this.path = path; + } + + /** + * Parses a rust type from a mangled symbol + * @param s symbol to parse + * @return the rust type object + */ + public static RustType parse(Symbol s) { + char c = s.popChar(); + + switch (c) { + case 'a': + return new RustType("i8"); + case 'b': + return new RustType("bool"); + case 'c': + return new RustType("char"); + case 'd': + return new RustType("f64"); + case 'e': + return new RustType("str"); + case 'f': + return new RustType("f32"); + case 'h': + return new RustType("u8"); + case 'i': + return new RustType("isize"); + case 'j': + return new RustType("usize"); + case 'l': + return new RustType("i32"); + case 'm': + return new RustType("u32"); + case 'n': + return new RustType("i128"); + case 'o': + return new RustType("u128"); + case 's': + return new RustType("i16"); + case 't': + return new RustType("u16"); + case 'u': + return new RustType("()"); + case 'v': + return new RustType("..."); + case 'x': + return new RustType("i64"); + case 'y': + return new RustType("u64"); + case 'z': + return new RustType("!"); + case 'p': + return new RustType("_"); + default: + switch (c) { + case 'A': // Array sized + RustType rustType = RustType.parse(s); + RustConst constant = RustConst.parse(s); + return new RustType( + "[" + rustType.toString() + "; " + constant.toString() + "]"); + case 'S': // Array unsized + SymbolNode symbolType = RustType.parse(s); + return new RustType("[" + symbolType.toString() + "]"); + case 'T': // Tuple + ArrayList types = new ArrayList(); + + while (s.nextChar() != 'E') { + SymbolNode symbolNode = RustType.parse(s); + if (symbolNode != null) { + types.add(symbolNode.toString()); + } + else { + return null; // null type in parse + } + } + + s.popChar(); + + String type = "(" + String.join(", ", types) + ")"; + return new RustType(type); + case 'R': // &T + RustLifetime.parse(s); + SymbolNode type1 = RustType.parse(s); + return new RustType("&" + type1); + case 'Q': // &mut T + RustLifetime.parse(s); + SymbolNode type2 = RustType.parse(s); + return new RustType("&mut " + type2); + case 'P': // *const T + SymbolNode type3 = RustType.parse(s); + return new RustType("*const " + type3); + case 'O': // *mut T + SymbolNode type4 = RustType.parse(s); + return new RustType("*mut " + type4); + case 'F': // fn(...) -> ... + // TODO: FnSig type + case 'D': // dyn Trait + Send + 'a + String bounds = parseDynBounds(s); + RustLifetime.parse(s); + String data = "dyn Trait"; + + if (bounds != null) { + data += bounds; + } + + return new RustType(data); + case 'B': + s.backChar(); + String b1 = s.parseBackref(); + int b2 = Integer.parseInt(b1); + RustBackref b3 = new RustBackref(b2, s); + return new RustType(new RustPath(b3)); + default: + s.backChar(); + RustPath path = RustPath.parse(s); + return new RustType(path); + } + } + } + + /** + * Parses a rust dyn bounds from a mangled symbol + * @param s symbol to parse + * @return a string representing the dyn bounds + */ + public static String parseDynBounds(Symbol s) { + ArrayList traits = new ArrayList(); + @SuppressWarnings("unused") + String binder = parseBinder(s); + + while (s.nextChar() != 'E') { + String trait = parseDynTrait(s); + traits.add(trait); + } + + s.popChar(); + + return " + " + String.join(" + ", traits); + } + + /** + * Parses a rust dyn trait from a mangled symbol + * @param s symbol to parse + * @return a string representing the dyn trait + */ + public static String parseDynTrait(Symbol s) { + RustPath path = RustPath.parse(s); + @SuppressWarnings("unused") + String bindings = ""; + while (s.nextChar() == 'p') { + String binding = parseDynTraitAssocBinding(s); + bindings += binding; + } + + if (path == null) { + return ""; + } + + return path.toString(); + } + + /** + * Parses a rust dyn trait associated binding from a mangled symbol + * @param s symbol to parse + * @return a string representing the dyn trait associated binding + */ + public static String parseDynTraitAssocBinding(Symbol s) { + s.popChar(); + + RustIdentifier.parseUndisambiguatedIdentifier(s); + SymbolNode type = RustType.parse(s); + + return "dyn " + type.toString(); + } + + /** + * Parses a rust binding from a mangled symbol + * @param s symbol to parse + * @return a string representing the binding + */ + public static String parseBinder(Symbol s) { + if (s.nextChar() != 'G') { + return null; + } + + s.popChar(); + return s.parseBase62Number(); + } + + @Override + public String toString() { + if (path != null) { + return path.toString(); + } + + return typeName; + } +} + +/** + * Parses and represents a a rust symbol const node + */ +class RustConst implements SymbolNode { + String name; + + public RustConst(String name) { + this.name = name; + } + + /** + * Parses a rust const from a mangled symbol + * @param s symbol to parse + * @return the rust const object + */ + public static RustConst parse(Symbol s) { + SymbolNode type = RustType.parse(s); + String constData = RustConst.parseConstData(s); + + return new RustConst(constData + type.toString()); + } + + /** + * Parses a rust const data from a mangled symbol + * @param s symbol to parse + * @return a string representing the const data + */ + public static String parseConstData(Symbol s) { + if (s.nextChar() == 'n') { + s.popChar(); + } + + String name = s.parseUntil('_'); + s.popChar(); + + return name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeArchiveUtility.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeArchiveUtility.java index 8acd5d5df4..e0f7bc49fe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeArchiveUtility.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeArchiveUtility.java @@ -40,8 +40,8 @@ public class DataTypeArchiveUtility { public static final Map GHIDRA_ARCHIVES = new HashMap(); static { - for (ResourceFile file : Application.findFilesByExtensionInApplication( - FileDataTypeManager.SUFFIX)) { + for (ResourceFile file : Application + .findFilesByExtensionInApplication(FileDataTypeManager.SUFFIX)) { String name = file.getName(); ResourceFile resourceFile = GHIDRA_ARCHIVES.get(name); if (resourceFile == null) { @@ -143,6 +143,11 @@ public class DataTypeArchiveUtility { else { list.add("generic_clib"); } + + if (program.getCompiler().contains("rustc")) { + list.add("rust-common"); + } + return list; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfLoader.java index ea717720aa..6a29468f8b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfLoader.java @@ -93,6 +93,7 @@ public class ElfLoader extends AbstractLibrarySupportLoader { return validationErrorStr; } } + return super.validateOptions(provider, loadSpec, options, program); } @@ -102,7 +103,7 @@ public class ElfLoader extends AbstractLibrarySupportLoader { try { ElfHeader elf = new ElfHeader(provider, null); - // TODO: Why do we convey image base to loader ? This will be managed by each loader ! + List results = QueryOpinionService.query(getName(), elf.getMachineName(), elf.getFlags()); for (QueryResult result : results) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java index 308a3f5c3f..15f4172802 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java @@ -25,6 +25,8 @@ import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; import org.apache.commons.lang3.StringUtils; import ghidra.app.cmd.label.SetLabelPrimaryCmd; +import ghidra.app.plugin.core.analysis.rust.RustConstants; +import ghidra.app.plugin.core.analysis.rust.RustUtilities; import ghidra.app.util.*; import ghidra.app.util.bin.*; import ghidra.app.util.bin.format.MemoryLoadable; @@ -186,6 +188,8 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { markupElfInfoProducers(monitor); + setCompiler(monitor); + success = true; } finally { @@ -2436,6 +2440,21 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { } } + private void setCompiler(TaskMonitor monitor) { + // Check for Rust + try { + if (RustUtilities.isRust(program, ElfSectionHeaderConstants.dot_rodata)) { + int extensionCount = RustUtilities.addExtensions(program, monitor, + RustConstants.RUST_EXTENSIONS_UNIX); + log.appendMsg("Installed " + extensionCount + " Rust cspec extensions"); + program.setCompiler("rustc"); + } + } + catch (IOException e) { + log.appendException(e); + } + } + private void markupHashTable(TaskMonitor monitor) { ElfDynamicTable dynamicTable = elf.getDynamicTable(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java index bb6b95dec7..9f4d4bcb71 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java @@ -91,8 +91,8 @@ public class MachoLoader extends AbstractLibrarySupportLoader { } } catch (CancelledException e) { - return; - } + return; + } catch (IOException e) { throw e; } @@ -137,7 +137,7 @@ public class MachoLoader extends AbstractLibrarySupportLoader { for (FatArch architecture : architectures) { ByteProvider bp = new ByteProviderWrapper(provider, architecture.getOffset(), architecture.getSize()) { - + @Override // Ensure the parent provider gets closed when the wrapper does public void close() throws IOException { super.provider.close(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java index 4846878a4a..a34dc04ab4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java @@ -15,9 +15,12 @@ */ package ghidra.app.util.opinion; +import java.io.IOException; import java.math.BigInteger; import java.util.*; +import ghidra.app.plugin.core.analysis.rust.RustConstants; +import ghidra.app.plugin.core.analysis.rust.RustUtilities; import ghidra.app.util.MemoryBlockUtils; import ghidra.app.util.bin.*; import ghidra.app.util.bin.format.RelocationException; @@ -149,6 +152,7 @@ public class MachoProgramBuilder { // Perform additional actions renameObjMsgSendRtpSymbol(); fixupProgramTree(null); // should be done last to account for new memory blocks + setCompiler(); } /** @@ -1687,6 +1691,22 @@ public class MachoProgramBuilder { } } + protected void setCompiler() { + // Check for Rust + try { + if (RustUtilities.isRust(program, + SegmentNames.SEG_TEXT + "." + SectionNames.TEXT_CONST)) { + int extensionCount = RustUtilities.addExtensions(program, monitor, + RustConstants.RUST_EXTENSIONS_UNIX); + log.appendMsg("Installed " + extensionCount + " Rust cspec extensions"); + program.setCompiler("rustc"); + } + } + catch (IOException e) { + log.appendException(e); + } + } + protected void renameObjMsgSendRtpSymbol() throws DuplicateNameException, InvalidInputException { Address address = space.getAddress(ObjectiveC1_Constants.OBJ_MSGSEND_RTP); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java index a3e06f8aa2..ea4133fca4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java @@ -15,13 +15,14 @@ */ package ghidra.app.util.opinion; -import java.util.*; - import java.io.IOException; import java.io.InputStream; +import java.util.*; import com.google.common.primitives.Bytes; +import ghidra.app.plugin.core.analysis.rust.RustConstants; +import ghidra.app.plugin.core.analysis.rust.RustUtilities; import ghidra.app.util.MemoryBlockUtils; import ghidra.app.util.Option; import ghidra.app.util.bin.BinaryReader; @@ -83,7 +84,8 @@ public class PeLoader extends AbstractPeDebugLoader { if (ntHeader != null && ntHeader.getOptionalHeader() != null) { long imageBase = ntHeader.getOptionalHeader().getImageBase(); String machineName = ntHeader.getFileHeader().getMachineName(); - String compilerFamily = CompilerOpinion.getOpinion(pe, provider).family; + String compilerFamily = CompilerOpinion.getOpinion(pe, provider, null, + TaskMonitor.DUMMY, new MessageLog()).family; for (QueryResult result : QueryOpinionService.query(getName(), machineName, compilerFamily)) { loadSpecs.add(new LoadSpec(this, imageBase, result)); @@ -146,9 +148,9 @@ public class PeLoader extends AbstractPeDebugLoader { processSymbols(ntHeader, sectionToAddress, program, monitor, log); processEntryPoints(ntHeader, program, monitor); - String compiler = CompilerOpinion.getOpinion(pe, provider).toString(); + String compiler = + CompilerOpinion.getOpinion(pe, provider, program, monitor, log).toString(); program.setCompiler(compiler); - } catch (AddressOverflowException e) { throw new IOException(e); @@ -220,8 +222,7 @@ public class PeLoader extends AbstractPeDebugLoader { return PARSE_CLI_HEADERS_OPTION_DEFAULT; } - private void layoutHeaders(Program program, PortableExecutable pe, - NTHeader ntHeader, + private void layoutHeaders(Program program, PortableExecutable pe, NTHeader ntHeader, DataDirectory[] datadirs) { try { DataType dt = pe.getDOSHeader().toDataType(); @@ -379,8 +380,8 @@ public class PeLoader extends AbstractPeDebugLoader { try { ReferenceManager refManager = pointerData.getProgram().getReferenceManager(); refManager.addExternalReference(pointerData.getAddress(), - importInfo.getDLL().toUpperCase(), - importInfo.getName(), extAddr, SourceType.IMPORTED, 0, RefType.DATA); + importInfo.getDLL().toUpperCase(), importInfo.getName(), extAddr, + SourceType.IMPORTED, 0, RefType.DATA); } catch (DuplicateNameException e) { log.appendMsg("External location not created: " + e.getMessage()); @@ -868,9 +869,8 @@ public class PeLoader extends AbstractPeDebugLoader { static final byte[] asm16_Borland = { (byte) 0xBA, 0x10, 0x00, 0x0E, 0x1F, (byte) 0xB4, 0x09, (byte) 0xCD, 0x21, (byte) 0xB8, 0x01, 0x4C, (byte) 0xCD, 0x21, (byte) 0x90, (byte) 0x90 }; - static final byte[] asm16_GCC_VS_Clang = - { 0x0e, 0x1f, (byte) 0xba, 0x0e, 0x00, (byte) 0xb4, 0x09, (byte) 0xcd, 0x21, - (byte) 0xb8, 0x01, 0x4c, (byte) 0xcd, 0x21 }; + static final byte[] asm16_GCC_VS_Clang = { 0x0e, 0x1f, (byte) 0xba, 0x0e, 0x00, (byte) 0xb4, + 0x09, (byte) 0xcd, 0x21, (byte) 0xb8, 0x01, 0x4c, (byte) 0xcd, 0x21 }; static final byte[] THIS_BYTES = "This".getBytes(); public enum CompilerEnum { @@ -882,6 +882,7 @@ public class PeLoader extends AbstractPeDebugLoader { BorlandCpp("borland:c++", "borlandcpp"), BorlandUnk("borland:unknown", "borlandcpp"), CLI("cli", "cli"), + Rustc("rustc", "rustc"), GOLANG("golang", "golang"), Unknown("unknown", "unknown"), @@ -926,8 +927,8 @@ public class PeLoader extends AbstractPeDebugLoader { return (i == chararray.length); } - public static CompilerEnum getOpinion(PortableExecutable pe, ByteProvider provider) - throws IOException { + public static CompilerEnum getOpinion(PortableExecutable pe, ByteProvider provider, + Program program, TaskMonitor monitor, MessageLog log) throws IOException { CompilerEnum offsetChoice = CompilerEnum.Unknown; CompilerEnum asmChoice = CompilerEnum.Unknown; @@ -936,6 +937,19 @@ public class PeLoader extends AbstractPeDebugLoader { DOSHeader dh = pe.getDOSHeader(); + // Check for Rust. Program object is required, which may be null. + try { + if (program != null && RustUtilities.isRust(program, ".rdata")) { + int extensionCount = RustUtilities.addExtensions(program, monitor, + RustConstants.RUST_EXTENSIONS_WINDOWS); + log.appendMsg("Installed " + extensionCount + " Rust cspec extensions"); + return CompilerEnum.Rustc; + } + } + catch (IOException e) { + log.appendException(e); + } + // Check for managed code (.NET) if (pe.getNTHeader().getOptionalHeader().isCLI()) { return CompilerEnum.CLI; @@ -1086,6 +1100,7 @@ public class PeLoader extends AbstractPeDebugLoader { // fail } } + SectionHeader dataSection = pe.getNTHeader().getFileHeader().getSectionHeader(".data"); if (dataSection != null) { try (InputStream is = dataSection.getDataStream()) { diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/analyzers/RustDemanglerLegacyTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/analyzers/RustDemanglerLegacyTest.java new file mode 100644 index 0000000000..97dfdd4d8c --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/analyzers/RustDemanglerLegacyTest.java @@ -0,0 +1,61 @@ +/* ### + * 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.analyzers; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ghidra.app.plugin.core.analysis.rust.demangler.RustDemangler; +import ghidra.app.util.demangler.DemangledException; +import ghidra.app.util.demangler.DemangledObject; + +public class RustDemanglerLegacyTest { + private static String[] symbols = + { "_ZN43_$LT$char$u20$as$u20$core..fmt..Display$GT$3fmt17h31c4c24bbd08aa24E", + "_ZN4core6option13expect_failed17h09b982639336e7eaE", + "_ZN4core3fmt9Formatter15debug_lower_hex17heb5fb064687c1b3cE", + "_ZN3std4path10Components7as_path17h3cc3e688e3107704E", + "_ZN5alloc5alloc18handle_alloc_error8rt_error17h4b79f8a717741b7cE", + "_ZN3std6thread7current17h20e47a880e55afd5E", }; + + private static String[] names = { "rustcall _::fmt(void)", + "rustcall core::option::expect_failed(void)", + "rustcall core::fmt::Formatter::debug_lower_hex(void)", + "rustcall std::path::Components::as_path(void)", + "rustcall alloc::alloc::handle_alloc_error::rt_error(void)", + "rustcall std::thread::current(void)", + "rustcall gimli::read::abbrev::Attributes::new(void)" }; + + @Test + public void demangle() { + RustDemangler demangler = new RustDemangler(); + for (int i = 0; i < symbols.length; i++) { + String mangled = symbols[i]; + String name = names[i]; + + try { + DemangledObject demangled = demangler.demangle(mangled); + if (!name.equals(demangled.toString())) { + fail("Demangled symbol to wrong name \n" + demangled + "\n" + name); + } + } + catch (DemangledException e) { + fail("Couldn't demangle symbol " + mangled); + } + } + } +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/analyzers/RustDemanglerV0Test.java b/Ghidra/Features/Base/src/test/java/ghidra/app/analyzers/RustDemanglerV0Test.java new file mode 100644 index 0000000000..d5b6cd5f51 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/analyzers/RustDemanglerV0Test.java @@ -0,0 +1,96 @@ +/* ### + * 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.analyzers; + +import static org.junit.Assert.fail; + +import org.junit.Test; + +import ghidra.app.plugin.core.analysis.rust.demangler.RustDemangler; +import ghidra.app.util.demangler.DemangledException; +import ghidra.app.util.demangler.DemangledObject; + +public class RustDemanglerV0Test { + + private static String[] symbols = { + "_RNvCsL39EUhRVRM_5tests4main", + "_RNvCsL39EUhRVRM_5tests6test_1", + "_RNvMCsL39EUhRVRM_5testsNtB2_10TestStruct8method_1", + "_RNvNtNtCsL39EUhRVRM_5tests5stuff6stuff26test_3", + "_RNCINvNtCsekVQb2M45Qb_3std2rt10lang_startuE0CsL39EUhRVRM_5tests.llvm.3070730145566890858", + "_RNvYNtNtCsheJZGYyU57U_5alloc6string6StringNtNtCscuN2HtZYDVi_4core3fmt5Write9write_fmtCsL39EUhRVRM_5tests", + "_RNSNvYNCINvNtCsekVQb2M45Qb_3std2rt10lang_startuE0INtNtNtCscuN2HtZYDVi_4core3ops8function6FnOnceuE9call_once6vtableCsL39EUhRVRM_5tests.llvm.3070730145566890858", + "_RNvXss_NtCsheJZGYyU57U_5alloc3vecINtB5_3VecAhj4_ENtNtCscuN2HtZYDVi_4core3fmt5Debug3fmtCsL39EUhRVRM_5tests", + "_RNSNvYNCINvNtCsekVQb2M45Qb_3std2rt10lang_startuE0INtNtNtCscuN2HtZYDVi_4core3ops8function6FnOnceuE9call_once6vtableCsL39EUhRVRM_5tests.llvm.6912067296627029035", + "_RNvXs_CsL39EUhRVRM_5testsNtB4_10TestStructNtB4_9TestTrait14trait_method_1", + "_RNvXs_CsL39EUhRVRM_5testsNtB4_10TestStructNtB4_9TestTrait14trait_method_2", + "_RNvXs_CsL39EUhRVRM_5testsNtB4_10TestStructNtB4_9TestTrait14trait_method_3", + "_RNvXNtCscuN2HtZYDVi_4core3fmtQNtNtCsheJZGYyU57U_5alloc6string6StringNtB2_5Write10write_charCsL39EUhRVRM_5tests", + "_RNvXNtCscuN2HtZYDVi_4core3fmtQNtNtCsheJZGYyU57U_5alloc6string6StringNtB2_5Write9write_fmtCsL39EUhRVRM_5tests", + "_RNvXNtCscuN2HtZYDVi_4core3fmtQNtNtCsheJZGYyU57U_5alloc6string6StringNtB2_5Write9write_strCsL39EUhRVRM_5tests", + "_RNvNtCsL39EUhRVRM_5tests5stuff6test_2", + "_RNvMs_NtCsheJZGYyU57U_5alloc7raw_vecINtB4_6RawVechE16reserve_for_pushCsL39EUhRVRM_5tests", + "_RNvXsX_NtCscuN2HtZYDVi_4core3fmtReNtB5_7Display3fmtCsL39EUhRVRM_5tests", + "_RNvXsV_NtCscuN2HtZYDVi_4core3fmtRAhj4_NtB5_5Debug3fmtCsL39EUhRVRM_5tests", + "_RNvXsV_NtCscuN2HtZYDVi_4core3fmtRhNtB5_5Debug3fmtCsL39EUhRVRM_5tests", + "_RNvYNtNtCsheJZGYyU57U_5alloc6string6StringNtNtCscuN2HtZYDVi_4core3fmt5Write9write_fmtCsL39EUhRVRM_5tests", + "_RNCINkXs25_NgCsbmNqQUJIY6D_4core5sliceINyB9_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB9_6memchr7memrchrs_0E0Bb_", + }; + + + private static String[] names = { + "tests::main", + "tests::test_1", + "::method_1", + "tests::stuff::stuff2::test_3", + "std::rt::lang_start<()>::{closure#0}", + "::write_fmt", + "::{closure#0} as core::ops::function::FnOnce<()>>::call_once::vtable", + " as core::fmt::Debug>::fmt", + "::{closure#0} as core::ops::function::FnOnce<()>>::call_once::vtable", + "::trait_method_1", + "::trait_method_2", + "::trait_method_3", + "<&mut alloc::string::String as core::fmt::Write>::write_char", + "<&mut alloc::string::String as core::fmt::Write>::write_fmt", + "<&mut alloc::string::String as core::fmt::Write>::write_str", + "tests::stuff::test_2", + ">::reserve_for_push", + "<&str as core::fmt::Display>::fmt", + "<&[u8; 4usize] as core::fmt::Debug>::fmt", + "<&u8 as core::fmt::Debug>::fmt", + "::write_fmt", + " as core::iter::iterator::Iterator>::rposition::{closure#0}", + }; + + @Test + public void demangle() { + RustDemangler demangler = new RustDemangler(); + for (int i = 0; i < symbols.length; i++) { + String mangled = symbols[i]; + String name = names[i]; + + try { + DemangledObject demangled = demangler.demangle(mangled); + if (name.equals(demangled.getName())) { + fail("Demangled symbol to wrong name " + mangled); + } + } catch (DemangledException e) { + fail("Couldn't demangle symbol " + mangled); + } + } + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/CompilerConstraint.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/CompilerConstraint.java index e72d056064..ef98555586 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/CompilerConstraint.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/CompilerConstraint.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +15,11 @@ */ package ghidra.util.constraint; +import java.util.Objects; + import generic.constraint.ConstraintData; import ghidra.program.model.listing.Program; +import ghidra.util.xml.XmlAttributeException; public class CompilerConstraint extends ProgramConstraint { @@ -26,15 +28,41 @@ public class CompilerConstraint extends ProgramConstraint { } private String compilerid; + private String compilerName; @Override public boolean isSatisfied(Program program) { - return compilerid.equals(program.getCompilerSpec().getCompilerSpecID().getIdAsString()); + if (compilerid == null && compilerName == null) { + return false; + } + + boolean satisfied = true; + + if (compilerid != null) { + satisfied &= + compilerid.equals(program.getCompilerSpec().getCompilerSpecID().getIdAsString()); + } + + if (compilerName != null) { + satisfied &= compilerName.contains(program.getCompiler()); + } + + return satisfied; } @Override public void loadConstraintData(ConstraintData data) { - compilerid = data.getString("id"); + if (data.hasValue("id")) { + compilerid = data.getString("id"); + } + + if (data.hasValue("name")) { + compilerName = data.getString("name"); + } + + if (compilerid == null && compilerName == null) { + throw new XmlAttributeException("Missing both id and name attributes"); + } } @Override @@ -42,12 +70,32 @@ public class CompilerConstraint extends ProgramConstraint { if (!(obj instanceof CompilerConstraint)) { return false; } - return ((CompilerConstraint) obj).compilerid.equals(compilerid); + + CompilerConstraint constraint = (CompilerConstraint) obj; + + if (compilerid != constraint.compilerid) { + if (compilerid == null || !compilerid.equals(constraint.compilerid)) { + return false; + } + } + + if (compilerName != constraint.compilerName) { + if (compilerName == null || !compilerName.equals(constraint.compilerName)) { + return false; + } + } + + return true; + } + + @Override + public int hashCode() { + return Objects.hash(compilerid, compilerName); } @Override public String getDescription() { - return "compiler = " + compilerid; + return "compiler = " + compilerid + " compilerName = " + compilerName; } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/ExecutableFormatConstraint.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/ExecutableFormatConstraint.java index 84863cc321..43d843fbde 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/ExecutableFormatConstraint.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/ExecutableFormatConstraint.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +15,8 @@ */ package ghidra.util.constraint; +import java.util.Objects; + import generic.constraint.ConstraintData; import ghidra.program.model.listing.Program; @@ -49,6 +50,11 @@ public class ExecutableFormatConstraint extends ProgramConstraint { return ((ExecutableFormatConstraint) obj).executableFormat.equals(executableFormat); } + @Override + public int hashCode() { + return Objects.hash(executableFormat); + } + @Override public String getDescription() { return "executableFormat = " + executableFormat; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/LanguageConstraint.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/LanguageConstraint.java index a97a88cd40..3ca936ad04 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/LanguageConstraint.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/LanguageConstraint.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +15,12 @@ */ package ghidra.util.constraint; +import java.util.Objects; +import java.util.StringTokenizer; + import generic.constraint.ConstraintData; import ghidra.program.model.listing.Program; -import java.util.StringTokenizer; - public class LanguageConstraint extends ProgramConstraint { public LanguageConstraint() { @@ -63,6 +63,11 @@ public class LanguageConstraint extends ProgramConstraint { return ((LanguageConstraint) obj).languageID.equals(languageID); } + @Override + public int hashCode() { + return Objects.hash(languageID); + } + @Override public String getDescription() { return "languageID = " + languageID; diff --git a/Ghidra/Processors/x86/certification.manifest b/Ghidra/Processors/x86/certification.manifest index 639c65acd5..ee601a6774 100644 --- a/Ghidra/Processors/x86/certification.manifest +++ b/Ghidra/Processors/x86/certification.manifest @@ -1,5 +1,10 @@ ##VERSION: 2.0 Module.manifest||GHIDRA||||END| +data/extensions/rust/unix/cc.xml||GHIDRA||||END| +data/extensions/rust/unix/probe_fixup.xml||GHIDRA||||END| +data/extensions/rust/unix/try_fixup.xml||GHIDRA||||END| +data/extensions/rust/windows/probe_fixup.xml||GHIDRA||||END| +data/extensions/rust/windows/try_fixup.xml||GHIDRA||||END| data/languages/adx.sinc||GHIDRA||||END| data/languages/avx.sinc||GHIDRA||||END| data/languages/avx2.sinc||GHIDRA||||END| diff --git a/Ghidra/Processors/x86/data/extensions/rust/unix/cc.xml b/Ghidra/Processors/x86/data/extensions/rust/unix/cc.xml new file mode 100644 index 0000000000..bbb7608cea --- /dev/null +++ b/Ghidra/Processors/x86/data/extensions/rust/unix/cc.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghidra/Processors/x86/data/extensions/rust/unix/probe_fixup.xml b/Ghidra/Processors/x86/data/extensions/rust/unix/probe_fixup.xml new file mode 100644 index 0000000000..9bb5fd098a --- /dev/null +++ b/Ghidra/Processors/x86/data/extensions/rust/unix/probe_fixup.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/Ghidra/Processors/x86/data/extensions/rust/unix/try_fixup.xml b/Ghidra/Processors/x86/data/extensions/rust/unix/try_fixup.xml new file mode 100644 index 0000000000..5a81f267cd --- /dev/null +++ b/Ghidra/Processors/x86/data/extensions/rust/unix/try_fixup.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/Ghidra/Processors/x86/data/extensions/rust/windows/probe_fixup.xml b/Ghidra/Processors/x86/data/extensions/rust/windows/probe_fixup.xml new file mode 100644 index 0000000000..9bb5fd098a --- /dev/null +++ b/Ghidra/Processors/x86/data/extensions/rust/windows/probe_fixup.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/Ghidra/Processors/x86/data/extensions/rust/windows/try_fixup.xml b/Ghidra/Processors/x86/data/extensions/rust/windows/try_fixup.xml new file mode 100644 index 0000000000..8c6c42d510 --- /dev/null +++ b/Ghidra/Processors/x86/data/extensions/rust/windows/try_fixup.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/Ghidra/Processors/x86/data/languages/x86.opinion b/Ghidra/Processors/x86/data/languages/x86.opinion index bca1e2d2cc..33e60083a4 100644 --- a/Ghidra/Processors/x86/data/languages/x86.opinion +++ b/Ghidra/Processors/x86/data/languages/x86.opinion @@ -34,9 +34,11 @@ - + + +