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 @@
-
+
+
+