diff --git a/.gitattributes b/.gitattributes index 04b1ff2926..3b556a8b3a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,53 +3,79 @@ # Explicitly declare text files you want to always be normalized and converted # to native line endings on checkout. -*.java text -*.gradle text -*.manifest text +*.bash text +*.c text +*.cc text +*.command text +*.cpp text +*.cspec text *.css text +*.gradle text +*.groovy text +*.h text +*.hh text *.htm text *.html text +*.java text +*.jj text *.js text *.json text *.jsp text *.jspf text *.jspx text +*.l text +*.lang text +*.ldefs text +*.manifest text +*.opinion text +*.props text *.properties text -*.tld text -*.txt text +*.proto text +*.pspec text +*.py text +*.rxg text +*.sh text +*.sla text *.tag text +*.tld text +*.tool text +*.trans text +*.txt text *.xml text -*.c text -*.h text -*.cpp text -*.hh text -*.cc text +*.y text # Declare files that will always have CRLF line endings on checkout. +*.bat text eol=crlf *.sln text eol=crlf *.vcproj text eol=crlf *.vcxproj text eol=crlf -*.bat text eol=crlf # Denote all files that are truly binary and should not be modified. -*.png binary -*.jpg binary +*.a binary +*.apk binary +*.bmp binary *.class binary *.dll binary +*.dmg binary *.ear binary -*.gif binary -*.ico binary -*.jar binary -*.jpeg binary -*.so binary -*.war binary -*.pdf binary *.exe binary -*.lib binary -*.sa binary +*.gdt binary +*.gif binary *.gz binary *.gzf binary -*.tgz binary +*.ico binary +*.ipsw binary +*.jar binary +*.jpeg binary +*.jpg binary +*.lib binary +*.o binary +*.obj binary +*.pdf binary +*.png binary +*.sa binary +*.so binary *.tar binary -*.sh binary - +*.tgz binary +*.war binary +*.zip binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 385782a424..2882356cb6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -29,7 +29,8 @@ If applicable, please attach any files that caused problems or log files generat **Environment (please complete the following information):** - OS: [e.g. macOS 10.14.2] - Java Version: [e.g. 11.0] - - Ghidra Version: [e.g. 9.0] + - Ghidra Version: [e.g. 9.1.2] + - Ghidra Origin: [e.g. official ghidra-sre.org distro, third party distro, locally built] **Additional context** Add any other context about the problem here. diff --git a/GPL/CabExtract/build.gradle b/GPL/CabExtract/build.gradle index 95b36dd4d4..46994edbc7 100644 --- a/GPL/CabExtract/build.gradle +++ b/GPL/CabExtract/build.gradle @@ -1,3 +1,6 @@ +/* ### + * IP: Public Domain + */ apply from: file("../gpl.gradle").getCanonicalPath() if (findProject(':Generic') != null) { @@ -57,4 +60,4 @@ if (['linux64', 'osx64'].contains(currentPlatform)) { delete file("build/unpack/${cabextract}") } } -} \ No newline at end of file +} diff --git a/GPL/CabExtract/certification.manifest b/GPL/CabExtract/certification.manifest index d63ae0e1ac..aa851666c6 100644 --- a/GPL/CabExtract/certification.manifest +++ b/GPL/CabExtract/certification.manifest @@ -2,6 +2,4 @@ ##MODULE IP: GPL 3 ##MODULE IP: Public Domain Module.manifest||Public Domain||||END| -build.gradle||Public Domain||||END| data/cabextract-1.6.tar.gz||GPL 3||||END| -settings.gradle||Public Domain||||END| diff --git a/GPL/CabExtract/settings.gradle b/GPL/CabExtract/settings.gradle index e69de29bb2..1092a7aaab 100644 --- a/GPL/CabExtract/settings.gradle +++ b/GPL/CabExtract/settings.gradle @@ -0,0 +1,3 @@ +/* ### + * IP: Public Domain + */ diff --git a/GPL/DMG/build.gradle b/GPL/DMG/build.gradle index d66cd2d0f2..690d55e941 100644 --- a/GPL/DMG/build.gradle +++ b/GPL/DMG/build.gradle @@ -1,3 +1,6 @@ +/* ### + * IP: Public Domain + */ apply from: file("../gpl.gradle").getCanonicalPath() if (findProject(':Generic') != null) { diff --git a/GPL/DMG/certification.manifest b/GPL/DMG/certification.manifest index 7311651d24..d7bac0f1d3 100644 --- a/GPL/DMG/certification.manifest +++ b/GPL/DMG/certification.manifest @@ -3,7 +3,6 @@ ##MODULE IP: LGPL 2.1 ##MODULE IP: Public Domain Module.manifest||Public Domain||||END| -build.gradle||Public Domain||||END| data/lib/catacombae_csframework.jar||LGPL 2.1||||END| data/lib/catacombae_hfsx.jar||GPL 3||||END| data/lib/catacombae_hfsx_dmglib.jar||GPL 3||||END| @@ -16,4 +15,3 @@ data/os/win64/llio_amd64.dll||GPL 3||||END| data/os/win64/llio_i386.dll||GPL 3||||END| data/os/win64/llio_ia64.dll||GPL 3||||END| data/server_memory.cfg||Public Domain||||END| -settings.gradle||Public Domain||||END| diff --git a/GPL/DMG/settings.gradle b/GPL/DMG/settings.gradle index e69de29bb2..1092a7aaab 100644 --- a/GPL/DMG/settings.gradle +++ b/GPL/DMG/settings.gradle @@ -0,0 +1,3 @@ +/* ### + * IP: Public Domain + */ diff --git a/GPL/DemanglerGnu/build.gradle b/GPL/DemanglerGnu/build.gradle index 6244215680..0e9f80bb3f 100644 --- a/GPL/DemanglerGnu/build.gradle +++ b/GPL/DemanglerGnu/build.gradle @@ -1,3 +1,6 @@ +/* ### + * IP: Public Domain + */ apply from: file("../gpl.gradle").getCanonicalPath() if (findProject(':Generic') != null) { diff --git a/GPL/DemanglerGnu/certification.manifest b/GPL/DemanglerGnu/certification.manifest index f3ecf6e571..6f2767e5f8 100644 --- a/GPL/DemanglerGnu/certification.manifest +++ b/GPL/DemanglerGnu/certification.manifest @@ -5,7 +5,5 @@ ##MODULE IP: LGPL 3.0 ##MODULE IP: Public Domain Module.manifest||Public Domain||||END| -build.gradle||Public Domain||||END| -settings.gradle||Public Domain||||END| src/demangler_gnu_v2_24/README.txt||Public Domain||||END| src/demangler_gnu_v2_33_1/README.txt||Public Domain||||END| diff --git a/GPL/DemanglerGnu/settings.gradle b/GPL/DemanglerGnu/settings.gradle index e69de29bb2..1092a7aaab 100644 --- a/GPL/DemanglerGnu/settings.gradle +++ b/GPL/DemanglerGnu/settings.gradle @@ -0,0 +1,3 @@ +/* ### + * IP: Public Domain + */ diff --git a/GPL/GnuDisassembler/build.gradle b/GPL/GnuDisassembler/build.gradle index e3261bf24e..0fd000d05e 100644 --- a/GPL/GnuDisassembler/build.gradle +++ b/GPL/GnuDisassembler/build.gradle @@ -1,3 +1,6 @@ +/* ### + * IP: Public Domain + */ // If extension module does not reside within the Ghidra GPL directory, the Ghidra installation directory // must be specified either by setting the GHIDRA_INSTALL_DIR environment variable or Gradle // project property: diff --git a/GPL/GnuDisassembler/buildGdis.gradle b/GPL/GnuDisassembler/buildGdis.gradle index 2859dd073f..d145467092 100644 --- a/GPL/GnuDisassembler/buildGdis.gradle +++ b/GPL/GnuDisassembler/buildGdis.gradle @@ -1,3 +1,6 @@ +/* ### + * IP: Public Domain + */ /******************************************************************************************* * build.gradle file that applies this script must define two properties * 1) binutilsLocation - the folder where the original binutils.zip lives diff --git a/GPL/GnuDisassembler/certification.manifest b/GPL/GnuDisassembler/certification.manifest index f439b820d8..5ef30394a2 100644 --- a/GPL/GnuDisassembler/certification.manifest +++ b/GPL/GnuDisassembler/certification.manifest @@ -4,10 +4,7 @@ .project||GHIDRA||||END| Module.manifest||Public Domain||||END| README.txt||Public Domain||||END| -build.gradle||Public Domain||||END| -buildGdis.gradle||Public Domain||||END| data/arm_test1.s||Public Domain||||END| data/big.elf||Public Domain||||END| data/little.elf||Public Domain||||END| extension.properties||Public Domain||||END| -settings.gradle||Public Domain||||END| diff --git a/GPL/GnuDisassembler/settings.gradle b/GPL/GnuDisassembler/settings.gradle index e69de29bb2..1092a7aaab 100644 --- a/GPL/GnuDisassembler/settings.gradle +++ b/GPL/GnuDisassembler/settings.gradle @@ -0,0 +1,3 @@ +/* ### + * IP: Public Domain + */ diff --git a/GPL/certification.local.manifest b/GPL/certification.local.manifest index 1d8b8c1740..6f4737a251 100644 --- a/GPL/certification.local.manifest +++ b/GPL/certification.local.manifest @@ -1,5 +1,2 @@ ##VERSION: 2.0 ##MODULE IP: Public Domain -gpl.gradle||Public Domain||||END| -nativeBuildProperties.gradle||Public Domain||||END| -vsconfig.gradle||GHIDRA||||END| diff --git a/GPL/gpl.gradle b/GPL/gpl.gradle index 9428b3bdf4..4dafd461f7 100644 --- a/GPL/gpl.gradle +++ b/GPL/gpl.gradle @@ -1,4 +1,6 @@ - +/* ### + * IP: Public Domain + */ // BIN_REPO only useable in full Ghidra source configuration project.ext.BIN_REPO = file("../../../ghidra.bin").absolutePath diff --git a/GPL/nativeBuildProperties.gradle b/GPL/nativeBuildProperties.gradle index 759e2c0ceb..36b9a07875 100644 --- a/GPL/nativeBuildProperties.gradle +++ b/GPL/nativeBuildProperties.gradle @@ -1,3 +1,6 @@ +/* ### + * IP: Public Domain + */ /**************************************************************************** * nativeBuildProperties.gradle * diff --git a/GPL/vsconfig.gradle b/GPL/vsconfig.gradle index a3e4d3c6d8..a57b7eb54a 100644 --- a/GPL/vsconfig.gradle +++ b/GPL/vsconfig.gradle @@ -1,3 +1,18 @@ +/* ### + * 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. + */ /**************************************************************************** * Establish Visual Studio configuration environment for Windows native builds * diff --git a/Ghidra/Configurations/Public_Release/build.gradle b/Ghidra/Configurations/Public_Release/build.gradle index 9b1f4a62db..defe20000a 100644 --- a/Ghidra/Configurations/Public_Release/build.gradle +++ b/Ghidra/Configurations/Public_Release/build.gradle @@ -1,3 +1,18 @@ +/* ### + * 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. + */ apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle" apply from: "$rootProject.projectDir/gradle/javaProject.gradle" apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle" diff --git a/Ghidra/Configurations/Public_Release/certification.manifest b/Ghidra/Configurations/Public_Release/certification.manifest index 2e4833ec30..3a50c1b4a9 100644 --- a/Ghidra/Configurations/Public_Release/certification.manifest +++ b/Ghidra/Configurations/Public_Release/certification.manifest @@ -1,6 +1,5 @@ ##VERSION: 2.0 Module.manifest||GHIDRA||||END| -build.gradle||GHIDRA||||END| data/PDB_SYMBOL_SERVER_URLS.pdburl||GHIDRA||||END| src/global/docs/ChangeHistory.html||GHIDRA||||END| src/global/docs/UserAgreement.html||GHIDRA||||END| diff --git a/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html b/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html index aabe465742..98bad04149 100644 --- a/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html +++ b/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html @@ -6,14 +6,66 @@ +

Ghidra 9.2.3 Change History (March 2021)

+

Improvements

+ +
+

Bugs

+ +
+

Ghidra 9.2.2 Change History (December 2020)

Bugs

@@ -24,6 +76,7 @@
  • Analysis. Updated RTTI analyzer to find type_info vftable when it cannot be found with its mangled name. This will enable many more Windows programs to have their RTTI structures created that were unable to be parsed in previous Ghidra versions. (GP-141)
  • API. Relaxed memory block naming restrictions and restored ability to have spaces in memory block names. However, if a memory block is flagged as an overlay, the associated overlay space name may be modified to ensure validity and uniqueness. The DuplicateNameException has been removed from all memory block API methods since this was entirely an overlay space concern. Memory block GUI has also been changed eliminate the duplicate block name restriction. (GP-420, Issue #2465)
  • Build. Eliminated the need for installation of bison and flex when performing source-based gradle build of Ghidra or the Decompiler module. The generated files are now included with source files and maintained in source control. A separate gradle Decompiler:generateParsers task, which still requires bison and flex, must be used, explicitly, when changes are made to lex/yacc source files. (GP-467)
  • +
  • Graphing. Fixed issue with exporting graphs to DOT format due to invalid vertex IDs. (GP-280)
  • Graphing. Improved graphing where it did not navigate when clicking on external function nodes. Now it will navigate to the fake function location in the program, which is the location of the pointer to the external function. (GP-493)
  • Listing:Symbols. Removed restriction for naming labels that resemble default label names. (GT-3185, Issue #1057)
  • PDB. Crafted PDB type ID records 0x1608 and 0x1609 with presumed class and struct types and follow-on application of these types. Also fixed up some fall-back data type logic and improved some warning messages to reflect the cause of the conditions. (GP-474, Issue #2523)
  • @@ -37,7 +90,7 @@
  • Decompiler. Fixed issue with the Auto Create/Fill Structure command that caused it to silently miss some pointer accesses. (GP-344)
  • Decompiler. Jump table recovery now takes into account encoded bits, like ARM/THUMB mode transition, that may be present in address tables. (GP-387, Issue #2420)
  • Decompiler. Fixed a bug in the Decompiler renaming action when applied to function references. (GP-477, Issue #2415)
  • -
  • Decompiler. Corrected 8-byte return value storage specification in compiler-spec affecting longlong and double return values. Endianess ordering of r0/r1 was incorrect. (GP-512, Issue #2547)
  • +
  • Decompiler. Corrected 8-byte return value storage specification in compiler-spec affecting longlong and double return values. Endianess ordering of r0/r1 was incorrect. (GP-512, Issue #2547)
  • Graphing. Fixed the Function Graph's drag-to-select-nodes feature. (GP-430)
  • Graphing. Fixed issue where the graph in the satellite view is sometimes truncated. (GP-469)
  • Graphing. Fixed a stack trace issue caused by reusing a graph display window to show a graph that is larger than is allowed. (GP-492)
  • @@ -126,7 +179,7 @@
  • Importer:ELF. Added support for processing Android packed ELF Relocation Tables. (GT-3320, Issue #1192)
  • Importer:ELF. Added ELF import opinion for ARM BE8. (GT-3642, Issue #1187)
  • Importer:ELF. Added support for ELF RELR relocations, such as those produced for Android. (GP-348)
  • -
  • Importer:MachO. DYLD Loader can now load x86_64 DYLD from macOS. (GT-3611, Issue #1566)
  • +
  • Importer:Mach-O. DYLD Loader can now load x86_64 DYLD from macOS. (GT-3611, Issue #1566)
  • Importer:PE. Improved parsing of Microsoft ordinal map files produced with DUMPBIN /EXPORTS (see Ghidra/Features/Base/data/symbols/README.txt). (GT-3235)
  • Jython. Upgraded Jython to version 2.7.2. (GP-109)
  • Listing. In the PCode field of the Listing, accesses of varnodes in the unique space are now always shown with the size of the access. Fixed bug which would cause the PCode emulator to reject valid pcode in rare instances. (GP-196)
  • @@ -282,8 +335,8 @@
  • Disassembly. Corrected potential infinite loop with disassembler caused by branch to self with invalid delay slot instruction. (GT-3511, Issue #1486)
  • GUI. Corrected processor manual display for Microsoft Windows users, which was not displaying processor manual and was, instead, rendering a blank page in web browser. (GT-3444)
  • GUI:Bitfield Editor. Added field comment support to composite bitfield editor. (GT-3410)
  • -
  • Importer:MachO. A MachO loader regression, in Ghidra 9.1.1, when laying down symbols at the correct location, has been fixed. (GT-3487, Issue #1446)
  • -
  • Multi-User:Ghidra Server. Corrected Ghidra Server remote interface errors that occur when running with Java 11.0.6 (and later) release, which would throw RemoteException "Method is not Remote" errors. (GT-3521, Issue #1440)
  • +
  • Importer:Mach-O. A Mach-O loader regression, in Ghidra 9.1.1, when laying down symbols at the correct location, has been fixed. (GT-3487, Issue #1446)
  • +
  • Multi-User:Ghidra Server. Corrected Ghidra Server remote interface errors that occur when running with Java 11.0.6 (and later) release, which would throw RemoteException Method is not Remote errors. (GT-3521, Issue #1440)
  • PDB. Corrected PDB XML generation for zero-length classes and structures and resolved various datatype dependency issues encountered during PDB Analysis. Changed line numbers from hex to decimal. (GT-3462, Issue #1410)
  • Processors. Corrected mnemonic for ARM thumb RSB.w instruction. (GT-3420, Issue #1365)
  • Processors. Corrected issue in M68000 with some move instructions not creating correct array assignments. (GT-3429, Issue #1394)
  • @@ -294,7 +347,7 @@

    Ghidra 9.1.1 Change History (December 2019)

    Improvements

    @@ -331,7 +384,7 @@
  • Eclipse Integration. Added new GhidraSleighEditor Eclipse plugin in the installation directory under Extensions/Eclipse. (GT-113)
  • GUI. Added method for turning off table sorting by control-clicking the only sorted table column. (GT-2763, Issue #87)
  • GUI. Hovering on an address will now show where the byte at that address came from in the imported file. (GT-3016, Issue #154)
  • -
  • Importer:MachO. Added new importer/loader for DYLD-shared cache files. (GT-2343)
  • +
  • Importer:Mach-O. Added new importer/loader for DYLD-shared cache files. (GT-2343)
  • Memory. Added new API to preserve imported program's original bytes and how they map to memory blocks. (GT-2845)
  • Processors. Implemented Intel MCS-96 processor module. (GT-2350)
  • Processors. Added SH1/2/2a sleigh processor specification. (GT-3029, Issue #715)
  • @@ -488,7 +541,7 @@
  • Listing. Cursor in the listing now stays in the proper column after editing a field. (GT-3045, Issue #702)
  • Listing. Fixed a problem with register highlighting that could occur on certain register/sub-register combinations. (GT-3071, Issue #810)
  • Multi-User. Corrected terminate checkout from viewed checkout list which was always terminating first row range based upon number of selected rows and not the actual selected rows. (GT-2903)
  • -
  • Multi-user. Corrected ability for user to cancel checkin/checkout to Ghidra Server. (GT-3208)
  • +
  • Multi-User. Corrected ability for user to cancel checkin/checkout to Ghidra Server. (GT-3208)
  • Multi-User:Ghidra Server. Added proper Ghidra Server interface binding with new -i option. Corrected -ip option to strictly convey remote access hostname to clients. The updated server will only accept connections from Ghidra 9.1 and later clients due to the registry port now employing TLS. (GT-2685, Issue #101, #645)
  • Multi-User:Ghidra Server. Fixed argument-passing bug in svrAdmin script. (GT-3082, Issue #907)
  • Multi-User:Merge. Corrected merge problem affecting modified Function Definition datatypes which could result in a NullPointerException. (GT-2922)
  • @@ -518,7 +571,7 @@
  • Program API. Corrected parameter storage which failed to properly refresh after undo/redo. (GT-3130, Issue #960)
  • Program API. Corrected function parameter ordinal numbering when more than one auto-parameter is present. (GT-3214)
  • Project Manager. Fixed a problem with creating Ghidra projects in Windows root directories (e.g., Z:\). (GT-2585)
  • -
  • Project Manager. Fixed a path traversal vulnerability that could occur when restoring a malicious project archive. (GT-3001, Issue #789)
  • +
  • Project Manager. Fixed a path-traversal vulnerability that could occur when restoring a malicious project archive. (GT-3001, Issue #789)
  • Scripting. GhidraScript.askDomainFile() now correctly throws a CancelledException when the cancel button is clicked. (GT-2841)
  • Scripting. Removed deprecated scripting methods older than 5 releases. (GT-2949)
  • Security. Removed use of nonsecure XMLEncoder/XMLDecoder from Ghidra code base. (GT-3198, Issue #1090)
  • @@ -531,128 +584,123 @@

    Ghidra 9.0.4 Change History (May 2019)

    Bugs

    Ghidra 9.0.3 Change History (April 2019)

    New Features

    Improvements

    Bugs

    Ghidra 9.0.2 Change History (April 2019)

    Bugs

    Security

    Ghidra 9.0.1 Change History (March 2019)

    New Features

    Improvements

    Bugs

    Security

    diff --git a/Ghidra/Extensions/SampleTablePlugin/build.gradle b/Ghidra/Extensions/SampleTablePlugin/build.gradle index b49b2ef531..5950dcfe29 100644 --- a/Ghidra/Extensions/SampleTablePlugin/build.gradle +++ b/Ghidra/Extensions/SampleTablePlugin/build.gradle @@ -1,3 +1,18 @@ +/* ### + * 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. + */ apply from: "$rootProject.projectDir/gradle/distributableGhidraExtension.gradle" apply from: "$rootProject.projectDir/gradle/javaProject.gradle" apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle" diff --git a/Ghidra/Extensions/SampleTablePlugin/certification.manifest b/Ghidra/Extensions/SampleTablePlugin/certification.manifest index 147e3af647..b9b2c11b56 100644 --- a/Ghidra/Extensions/SampleTablePlugin/certification.manifest +++ b/Ghidra/Extensions/SampleTablePlugin/certification.manifest @@ -2,7 +2,6 @@ ##MODULE IP: FAMFAMFAM Icons - CC 2.5 ##MODULE IP: Oxygen Icons - LGPL 3.0 Module.manifest||GHIDRA||reviewed||END| -build.gradle||GHIDRA||||END| data/ExtensionPoint.manifest||GHIDRA||||END| extension.properties||GHIDRA||||END| src/main/help/help/TOC_Source.xml||GHIDRA||||END| diff --git a/Ghidra/Extensions/SleighDevTools/build.gradle b/Ghidra/Extensions/SleighDevTools/build.gradle index 189b77a16f..4dbcca1cae 100644 --- a/Ghidra/Extensions/SleighDevTools/build.gradle +++ b/Ghidra/Extensions/SleighDevTools/build.gradle @@ -1,3 +1,18 @@ +/* ### + * 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. + */ apply from: "$rootProject.projectDir/gradle/distributableGhidraExtension.gradle" apply from: "$rootProject.projectDir/gradle/javaProject.gradle" apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle" diff --git a/Ghidra/Extensions/SleighDevTools/certification.manifest b/Ghidra/Extensions/SleighDevTools/certification.manifest index 07bcf00587..4dc3953d2b 100644 --- a/Ghidra/Extensions/SleighDevTools/certification.manifest +++ b/Ghidra/Extensions/SleighDevTools/certification.manifest @@ -1,14 +1,12 @@ ##VERSION: 2.0 .project||GHIDRA||||END| Module.manifest||GHIDRA||||END| -build.gradle||GHIDRA||||END| data/ExtensionPoint.manifest||GHIDRA||||END| data/LanguageMap.txt||GHIDRA||||END| extension.properties||GHIDRA||||END| pcodetest/.gitignore||GHIDRA||||END| pcodetest/README.txt||GHIDRA||||END| pcodetest/build||GHIDRA||||END| -pcodetest/build.py||GHIDRA||||END| pcodetest/c_src/BIOPS.test||GHIDRA||||END| pcodetest/c_src/BIOPS2.test||GHIDRA||||END| pcodetest/c_src/BIOPS4.test||GHIDRA||||END| @@ -28,7 +26,3 @@ pcodetest/c_src/PointerManipulation.test||GHIDRA||||END| pcodetest/c_src/StructUnionManipulation.test||GHIDRA||||END| pcodetest/c_src/misc.test||GHIDRA||||END| pcodetest/c_src/msp430x.ld||GHIDRA||||END| -pcodetest/defaults.py||GHIDRA||||END| -pcodetest/pcode_defs.py||GHIDRA||||END| -pcodetest/pcodetest.py||GHIDRA||||END| -pcodetest/tpp.py||GHIDRA||||END| diff --git a/Ghidra/Extensions/SleighDevTools/pcodetest/build.py b/Ghidra/Extensions/SleighDevTools/pcodetest/build.py index 988edb84b6..ee663bb3ea 100644 --- a/Ghidra/Extensions/SleighDevTools/pcodetest/build.py +++ b/Ghidra/Extensions/SleighDevTools/pcodetest/build.py @@ -1,3 +1,18 @@ +## ### +# 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. +## import os import shutil import subprocess diff --git a/Ghidra/Extensions/SleighDevTools/pcodetest/defaults.py b/Ghidra/Extensions/SleighDevTools/pcodetest/defaults.py index 16bd50ef87..1505aa8e20 100644 --- a/Ghidra/Extensions/SleighDevTools/pcodetest/defaults.py +++ b/Ghidra/Extensions/SleighDevTools/pcodetest/defaults.py @@ -1,4 +1,18 @@ - +## ### +# 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. +## # Default values can be modified here, or (in # some cases) on the build command line (see ./build --help) diff --git a/Ghidra/Extensions/SleighDevTools/pcodetest/pcode_defs.py b/Ghidra/Extensions/SleighDevTools/pcodetest/pcode_defs.py index 4a8e096ec6..c8cf6cc787 100644 --- a/Ghidra/Extensions/SleighDevTools/pcodetest/pcode_defs.py +++ b/Ghidra/Extensions/SleighDevTools/pcodetest/pcode_defs.py @@ -1,4 +1,18 @@ - +## ### +# 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. +## # The available pcode tests are recorded here as instances of the 'name' # python class. diff --git a/Ghidra/Extensions/SleighDevTools/pcodetest/pcodetest.py b/Ghidra/Extensions/SleighDevTools/pcodetest/pcodetest.py index dfc796f48d..628a8759a4 100644 --- a/Ghidra/Extensions/SleighDevTools/pcodetest/pcodetest.py +++ b/Ghidra/Extensions/SleighDevTools/pcodetest/pcodetest.py @@ -1,3 +1,18 @@ +## ### +# 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. +## import os import glob import re diff --git a/Ghidra/Extensions/SleighDevTools/pcodetest/tpp.py b/Ghidra/Extensions/SleighDevTools/pcodetest/tpp.py index d574530166..6c6246eabe 100644 --- a/Ghidra/Extensions/SleighDevTools/pcodetest/tpp.py +++ b/Ghidra/Extensions/SleighDevTools/pcodetest/tpp.py @@ -1,4 +1,19 @@ #!/usr/bin/python +## ### +# 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. +## import re import os diff --git a/Ghidra/Extensions/bundle_examples/build.gradle b/Ghidra/Extensions/bundle_examples/build.gradle index 7d477c15a9..4fce7fc287 100644 --- a/Ghidra/Extensions/bundle_examples/build.gradle +++ b/Ghidra/Extensions/bundle_examples/build.gradle @@ -1,3 +1,18 @@ +/* ### + * 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. + */ /* This extension is different from the others. It produces a zip containing * directories of source bundles and jar bundles. * - Each source directory is added as a sourceset so that the eclipse plugin diff --git a/Ghidra/Extensions/bundle_examples/certification.manifest b/Ghidra/Extensions/bundle_examples/certification.manifest index b951f42732..7cbc96e96a 100644 --- a/Ghidra/Extensions/bundle_examples/certification.manifest +++ b/Ghidra/Extensions/bundle_examples/certification.manifest @@ -1,6 +1,5 @@ ##VERSION: 2.0 Module.manifest||GHIDRA||||END| -build.gradle||GHIDRA||||END| extension.properties||GHIDRA||||END| scripts_jar1/META-INF/MANIFEST.MF||GHIDRA||||END| scripts_jar2/META-INF/MANIFEST.MF||GHIDRA||||END| diff --git a/Ghidra/Extensions/sample/build.gradle b/Ghidra/Extensions/sample/build.gradle index b0e9354f12..a6a9893a0c 100644 --- a/Ghidra/Extensions/sample/build.gradle +++ b/Ghidra/Extensions/sample/build.gradle @@ -1,3 +1,18 @@ +/* ### + * 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. + */ apply from: "$rootProject.projectDir/gradle/distributableGhidraExtension.gradle" apply from: "$rootProject.projectDir/gradle/javaProject.gradle" apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle" diff --git a/Ghidra/Extensions/sample/certification.manifest b/Ghidra/Extensions/sample/certification.manifest index 4e6d44c844..f76dd9c0ce 100644 --- a/Ghidra/Extensions/sample/certification.manifest +++ b/Ghidra/Extensions/sample/certification.manifest @@ -2,7 +2,6 @@ ##MODULE IP: FAMFAMFAM Icons - CC 2.5 ##MODULE IP: Oxygen Icons - LGPL 3.0 Module.manifest||GHIDRA||reviewed||END| -build.gradle||GHIDRA||||END| data/README.txt||GHIDRA||||END| extension.properties||GHIDRA||||END| src/main/help/help/TOC_Source.xml||GHIDRA||||END| diff --git a/Ghidra/Features/Base/build.gradle b/Ghidra/Features/Base/build.gradle index 2a77bdc815..f306645e5f 100644 --- a/Ghidra/Features/Base/build.gradle +++ b/Ghidra/Features/Base/build.gradle @@ -1,3 +1,18 @@ +/* ### + * 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. + */ apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle" apply from: "$rootProject.projectDir/gradle/javaProject.gradle" apply from: "$rootProject.projectDir/gradle/helpProject.gradle" diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 1fa71b205c..a22037ed99 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -13,7 +13,6 @@ .launch/Ghidra Code Coverage.launch||GHIDRA||||END| .launch/Ghidra.launch||GHIDRA||||END| Module.manifest||GHIDRA||||END| -build.gradle||GHIDRA||||END| data/ElfFunctionsThatDoNotReturn||GHIDRA||||END| data/ExtensionPoint.manifest||GHIDRA||||END| data/MachOFunctionsThatDoNotReturn||GHIDRA||||END| @@ -78,8 +77,6 @@ data/typeinfo/win32/windows_vs12_64.gdt||GHIDRA||||END| data/x64_linux_syscall_numbers||GHIDRA||||END| data/x86_linux_syscall_numbers||GHIDRA||||END| ghidra_scripts/AskScript.properties||GHIDRA||||END| -ghidra_scripts/RecursiveStringFinder.py||GHIDRA||||END| -ghidra_scripts/mark_in_out.py||GHIDRA||reviewed||END| ghidra_scripts/world.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/TOC_Source.xml||GHIDRA||||END| src/main/help/help/shared/arrow.gif||GHIDRA||||END| diff --git a/Ghidra/Features/Base/data/PEFunctionsThatDoNotReturn b/Ghidra/Features/Base/data/PEFunctionsThatDoNotReturn index e8ed7404f4..51c5089410 100644 --- a/Ghidra/Features/Base/data/PEFunctionsThatDoNotReturn +++ b/Ghidra/Features/Base/data/PEFunctionsThatDoNotReturn @@ -1,7 +1,15 @@ +abort CxxThrowException CxxThrowException@8 CxxFrameHandler3 crtExitProcess ExitProcess +ExitThread exit +FreeLibraryAndExitThread +invalid_parameter_noinfo_noreturn +invoke_watson longjmp +quick_exit +RpcRaiseException +terminate diff --git a/Ghidra/Features/Base/ghidra_scripts/BuildGhidraJarScript.java b/Ghidra/Features/Base/ghidra_scripts/BuildGhidraJarScript.java index b6a3f8e809..675904cb82 100644 --- a/Ghidra/Features/Base/ghidra_scripts/BuildGhidraJarScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/BuildGhidraJarScript.java @@ -17,10 +17,9 @@ //@category Examples import java.io.File; -import java.util.*; +import java.util.List; import generic.jar.ApplicationModule; -import generic.jar.ResourceFile; import ghidra.app.script.GhidraScript; import ghidra.framework.Application; import ghidra.util.GhidraJarBuilder; @@ -32,8 +31,7 @@ public class BuildGhidraJarScript extends GhidraScript { @Override public void run() throws Exception { - GhidraJarBuilder builder = - new GhidraJarBuilder(toFiles(Application.getApplicationRootDirectories())); + GhidraJarBuilder builder = new GhidraJarBuilder(Application.getApplicationLayout()); builder.setMainClass("ghidra.JarRun"); // default is ghidra.JarRun, only here if you want // to change it to something else. @@ -69,12 +67,4 @@ public class BuildGhidraJarScript extends GhidraScript { // uncomment the following line to create a src zip for debugging. // builder.buildSrcZip(new File(installDir, "GhidraSrc.zip"), monitor); } - - private List toFiles(Collection resourceFiles) { - List fileList = new ArrayList<>(); - for (ResourceFile resourceFile : resourceFiles) { - fileList.add(resourceFile.getFile(true)); - } - return fileList; - } } diff --git a/Ghidra/Features/Base/ghidra_scripts/DWARF_ExtractorScript.java b/Ghidra/Features/Base/ghidra_scripts/DWARF_ExtractorScript.java index 6b4f87803f..59885a6062 100644 --- a/Ghidra/Features/Base/ghidra_scripts/DWARF_ExtractorScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/DWARF_ExtractorScript.java @@ -63,7 +63,7 @@ public class DWARF_ExtractorScript extends GhidraScript { @Override public void run() throws Exception { - if (!DWARFProgram.isDWARF(currentProgram, monitor)) { + if (!DWARFProgram.isDWARF(currentProgram)) { popup("Unable to find DWARF information, aborting"); return; } diff --git a/Ghidra/Features/Base/ghidra_scripts/RecursiveStringFinder.py b/Ghidra/Features/Base/ghidra_scripts/RecursiveStringFinder.py index da27f23e7d..91bd2c455b 100644 --- a/Ghidra/Features/Base/ghidra_scripts/RecursiveStringFinder.py +++ b/Ghidra/Features/Base/ghidra_scripts/RecursiveStringFinder.py @@ -1,3 +1,18 @@ +## ### +# 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. +## #Given a function, find all strings used within all called funtions. # @category: Strings diff --git a/Ghidra/Features/Base/ghidra_scripts/mark_in_out.py b/Ghidra/Features/Base/ghidra_scripts/mark_in_out.py index 1e0fdbf719..8e9020ea18 100644 --- a/Ghidra/Features/Base/ghidra_scripts/mark_in_out.py +++ b/Ghidra/Features/Base/ghidra_scripts/mark_in_out.py @@ -1,3 +1,18 @@ +## ### +# 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. +## # Sets up IOPORT IN/OUT references for the Program #@category Instructions # Before running this script, you should have created an OVERLAY memory diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm b/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm index 1509a09ba1..4a8ff89bdd 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm @@ -284,6 +284,20 @@ + +

    ELF

    + +
    +

    Writes an ELF program that was imported with the ELF loader back to its original file + layout. Any file-backed bytes that were modified by the user in the program database will + be reflected in the new file.

    + +

    Writing back a modified Memory + Map is not supported.

    + +

    Relocation bytes are always + restored to their original values, even if the user modifies them.

    +

    Ghidra Zip File (.gzf)

    @@ -320,7 +334,7 @@
    • Address Space - Specifies which address space to export as Intel Hex format - only supports one address space. This option will be intialized to the "default" + only supports one address space. This option will be initialized to the "default" address space.
    • Record Size - Specifies the size (in bytes) of each record in the output file. The default 16.
    • @@ -330,6 +344,20 @@ 2 bytes will be dropped.
    + +

    PE

    + +
    +

    Writes a PE program that was imported with the PE loader back to its original file + layout. Any file-backed bytes that were modified by the user in the program database will + be reflected in the new file.

    + +

    Writing back a modified Memory + Map is not supported.

    + +

    Relocation bytes are always + restored to their original values, even if the user modifies them.

    +

    XML

    diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java index 6722672bfa..7763217f21 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java @@ -57,19 +57,38 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer { public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws CancelledException { + try { + monitor.setIndeterminate(true); + return doAdded(program, set, monitor, log); + } + finally { + monitor.setIndeterminate(false); + } + } + + private boolean doAdded(Program program, AddressSetView set, TaskMonitor monitor, + MessageLog log) + throws CancelledException { + DemanglerOptions options = getOptions(); if (!validateOptions(options, log)) { log.appendMsg(getName(), "Invalid demangler options--cannot demangle"); return false; } - monitor.initialize(100); + int count = 0; + + String defaultMessage = monitor.getMessage(); SymbolTable symbolTable = program.getSymbolTable(); SymbolIterator it = symbolTable.getPrimarySymbolIterator(set, true); while (it.hasNext()) { monitor.checkCanceled(); + if (++count % 100 == 0) { + monitor.setMessage(defaultMessage + " - " + count + " symbols"); + } + Symbol symbol = it.next(); if (skipSymbol(symbol)) { continue; @@ -81,12 +100,6 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer { if (demangled != null) { apply(program, address, demangled, options, log, monitor); } - - Address min = set.getMinAddress(); - Address max = set.getMaxAddress(); - int distance = (int) (address.getOffset() - min.getOffset()); - int percent = (int) ((distance / max.getOffset()) * 100); - monitor.setProgress(percent); } return true; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java index 01838c2ed7..d8a52fba89 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java @@ -1110,7 +1110,14 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl int answer = OptionDialog.showYesNoDialog(tool.getToolFrame(), "Analyze", "" + HTMLUtilities.escapeHTML(program.getDomainFile().getName()) + " has not been analyzed. Would you like to analyze it now?"); - return answer == OptionDialog.OPTION_ONE; // Analyze + //Set to false for now. ANALYZED is a tri-valued variable: + // null means not asked. + // false means asked but could still turn true when analysis happens. + // true means analysis has started. + //Setting false here only works due to this code only being reachable + // because of the behavior of GhidraProgramUtilities.shouldAskToAnalyze(program) above. + GhidraProgramUtilities.setAnalyzedFlag(program, false); + return answer == OptionDialog.OPTION_ONE; //Analyze } return false; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java index 13e4f68cfa..802540285e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java @@ -190,10 +190,12 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis if (!showOptionsDialog(program)) { return; } - GhidraProgramUtilities.setAnalyzedFlag(program, true); analysisMgr.initializeOptions(); // options may have changed + // At this point, any analysis that is done is consider to be true for analyzed. + GhidraProgramUtilities.setAnalyzedFlag(program, true); + // start analysis to set the flag, but it probably won't do more. A bit goofy but better // than the way it was //TODO simplify all this after creating a taskManager per program instead of per tool. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java index 45627bfbdf..6a501ea5e1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java @@ -34,6 +34,8 @@ import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; public class DWARFAnalyzer extends AbstractAnalyzer { + private static final String DWARF_LOADED_OPTION_NAME = "DWARF Loaded"; + private static final String OPTION_IMPORT_DATATYPES = "Import data types"; private static final String OPTION_IMPORT_DATATYPES_DESC = "Import data types defined in the DWARF debug info."; @@ -84,6 +86,7 @@ public class DWARFAnalyzer extends AbstractAnalyzer { "Automatically extracts DWARF info from an ELF file."; private DWARFImportOptions importOptions = new DWARFImportOptions(); + private long lastTxId = -1; public DWARFAnalyzer() { super(DWARF_ANALYZER_NAME, DWARF_ANALYZER_DESCRIPTION, AnalyzerType.BYTE_ANALYZER); @@ -105,22 +108,24 @@ public class DWARFAnalyzer extends AbstractAnalyzer { public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws CancelledException { - if (!canAnalyze(program)) { - // Check again because external DWARF data (ie. dSYM files) could have been moved - // between the time canAnalyze() was called the first time and when this method - // is called - log.appendMsg("Unable to find DWARF information, skipping DWARF analysis"); - return false; + long txId = program.getCurrentTransaction().getID(); + if (txId == lastTxId) { + // Only run once per analysis session - as denoted by being in the same transaction + return true; } + lastTxId = txId; - if (DWARFProgram.alreadyDWARFImported(program)) { - Msg.warn(this, "DWARF already imported, skipping. (Detected DWARF program module)"); + Options propList = program.getOptions(Program.PROGRAM_INFO); + boolean alreadyLoaded = propList.getBoolean(DWARF_LOADED_OPTION_NAME, false) || + oldCheckIfDWARFImported(program); + if (alreadyLoaded) { + Msg.info(this, "DWARF already imported, skipping."); return false; } DWARFSectionProvider dsp = DWARFSectionProviderFactory.createSectionProviderFor(program); if (dsp == null) { - // silently return, canAnalyze() was false positive + log.appendMsg("Unable to find DWARF information, skipping DWARF analysis"); return false; } @@ -139,6 +144,7 @@ public class DWARFAnalyzer extends AbstractAnalyzer { DWARFImportSummary parseResults = dp.parse(); parseResults.logSummaryResults(); } + propList.setBoolean(DWARF_LOADED_OPTION_NAME, true); return true; } catch (CancelledException ce) { @@ -157,9 +163,16 @@ public class DWARFAnalyzer extends AbstractAnalyzer { return false; } + private boolean oldCheckIfDWARFImported(Program prog) { + // this was the old way of checking if the DWARF analyzer had already been run. Keep + // it around for a little bit so existing programs that have already imported DWARF data + // don't get re-run. Remove after a release or two. + return DWARFFunctionImporter.hasDWARFProgModule(prog, DWARFProgram.DWARF_ROOT_NAME); + } + @Override public boolean canAnalyze(Program program) { - return DWARFProgram.isDWARF(program, null); + return DWARFProgram.isDWARF(program); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java index eb3e5a4c09..216a85bbd6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java @@ -24,6 +24,8 @@ import java.util.concurrent.CopyOnWriteArraySet; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import org.apache.commons.lang3.StringUtils; + import docking.ActionContext; import docking.ComponentProvider; import docking.dnd.GenericDataFlavor; @@ -303,12 +305,9 @@ public class CodeBrowserClipboardProvider extends ByteCopier private Transferable copyAddress() { AddressSetView addressSet = getSelectedAddresses(); - StringBuilder buffy = new StringBuilder(); AddressIterator it = addressSet.getAddresses(true); - while (it.hasNext()) { - buffy.append(it.next()).append('\n'); - } - return createStringTransferable(buffy.toString()); + String joined = StringUtils.join((Iterator
    ) it, "\n"); + return createStringTransferable(joined); } protected Transferable copyCode(TaskMonitor monitor) { @@ -377,8 +376,8 @@ public class CodeBrowserClipboardProvider extends ByteCopier private boolean pasteLabelsComments(Transferable pasteData, boolean pasteLabels, boolean pasteComments) { try { - List list = (List) pasteData.getTransferData( - CodeUnitInfoTransferable.localDataTypeFlavor); + List list = + (List) pasteData.getTransferData(CodeUnitInfoTransferable.localDataTypeFlavor); List infos = CollectionUtils.asList(list, CodeUnitInfo.class); Command cmd = new CodeUnitInfoPasteCmd(currentLocation.getAddress(), infos, pasteLabels, pasteComments); @@ -420,7 +419,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier return pasteOperandField((OperandFieldLocation) currentLocation, labelName); } - // try pasting onto something that is not a label + // try pasting onto something that is not a label return maybePasteNonLabelString(labelName); } @@ -451,12 +450,12 @@ public class CodeBrowserClipboardProvider extends ByteCopier String oldName = symbol.getName(); Namespace namespace = symbol.getParentNamespace(); Address symbolAddress = symbol.getAddress(); - RenameLabelCmd cmd = new RenameLabelCmd(symbolAddress, oldName, labelName, - namespace, SourceType.USER_DEFINED); + RenameLabelCmd cmd = new RenameLabelCmd(symbolAddress, oldName, labelName, namespace, + SourceType.USER_DEFINED); return tool.execute(cmd, currentProgram); } - // try pasting onto something that is not a label + // try pasting onto something that is not a label return maybePasteNonLabelString(labelName); } @@ -646,7 +645,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier //================================================================================================== // Unsupported Operations -//================================================================================================== +//================================================================================================== @Override public void lostOwnership(Transferable transferable) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java index 48c12b49cc..f5a6be5794 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java @@ -310,8 +310,10 @@ class EnumEditorPanel extends JPanel { EnumCellRenderer cellRenderer = new EnumCellRenderer(); table.setRowHeight(table.getRowHeight() + 4); table.setDefaultEditor(String.class, new EnumStringCellEditor()); - table.getColumnModel().getColumn(EnumTableModel.VALUE_COL).setCellEditor( - new EnumLongCellEditor()); + table.getColumnModel() + .getColumn(EnumTableModel.VALUE_COL) + .setCellEditor( + new EnumLongCellEditor()); table.setDefaultRenderer(String.class, cellRenderer); add(createInfoPanel(), BorderLayout.SOUTH); @@ -335,13 +337,17 @@ class EnumEditorPanel extends JPanel { } private void changed() { - String name = nameField.getText(); + String name = nameField.getText().trim(); + if (name.length() == 0) { + return; + } + if (!name.equals(editedEnumDT.getName())) { try { editedEnumDT.setName(name); } catch (InvalidNameException e) { - e.printStackTrace(); + setStatusMessage("'" + name + "' is not a valid name"); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/DisassemblerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/DisassemblerPlugin.java index ce0e969af0..e97357118b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/DisassemblerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/DisassemblerPlugin.java @@ -302,15 +302,12 @@ public class DisassemblerPlugin extends Plugin { } public void setDefaultContext(ListingActionContext context) { - Program contextProgram = context.getProgram(); Register baseContextReg = contextProgram.getLanguage().getContextBaseRegister(); if (baseContextReg != null && baseContextReg.hasChildren()) { - return; + tool.showDialog(new ProcessorStateDialog(contextProgram.getProgramContext()), + context.getComponentProvider()); } - - tool.showDialog(new ProcessorStateDialog(contextProgram.getProgramContext()), - context.getComponentProvider()); } public boolean hasContextRegisters(Program currentProgram) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java index bd4f612642..56626eccaf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java @@ -20,7 +20,6 @@ import java.awt.Component; import java.io.File; import java.io.IOException; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.*; import javax.swing.event.DocumentEvent; @@ -53,8 +52,7 @@ import ghidra.util.filechooser.ExtensionFileFilter; import ghidra.util.filechooser.GhidraFileFilter; import ghidra.util.layout.PairLayout; import ghidra.util.layout.VerticalLayout; -import ghidra.util.task.TaskLauncher; -import ghidra.util.task.TaskMonitor; +import ghidra.util.task.*; /** * Dialog for exporting a program from a Ghidra project to an external file in one of the @@ -444,10 +442,65 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa private boolean doExport() { - AtomicBoolean success = new AtomicBoolean(); - TaskLauncher.launchModal("Exporting " + domainFile.getName(), - monitor -> success.set(tryExport(monitor))); - return success.get(); + ExportTask task = new ExportTask(); + TaskLauncher.launch(task); + task.showResults(); + return task.getSuccess(); + } + + private class ExportTask extends Task { + + private boolean success; + private boolean showResults; + private Exporter exporter; + private DomainObject exportedDomainObject; + + public ExportTask() { + super("Export " + domainFile.getName(), true, true, true, false); + } + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + + exporter = getSelectedExporter(); + + exporter.setExporterServiceProvider(tool); + exportedDomainObject = getDomainObject(monitor); + if (exportedDomainObject == null) { + return; + } + ProgramSelection selection = getApplicableProgramSeletion(); + File outputFile = getSelectedOutputFile(); + + try { + if (outputFile.exists() && + OptionDialog.showOptionDialog(getComponent(), "Overwrite Existing File?", + "The file " + outputFile + " already exists.\nDo you want to overwrite it?", + "Overwrite", OptionDialog.QUESTION_MESSAGE) != OptionDialog.OPTION_ONE) { + return; + } + if (options != null) { + exporter.setOptions(options); + } + success = exporter.export(outputFile, exportedDomainObject, selection, monitor); + showResults = true; + } + catch (Exception e) { + Msg.error(this, "Exception exporting", e); + SystemUtilities.runSwingLater(() -> setStatusText( + "Exception exporting: " + e.getMessage() + ". If null, see log for details.")); + } + } + + void showResults() { + if (showResults) { + displaySummaryResults(exporter, exportedDomainObject); + } + } + + boolean getSuccess() { + return success; + } } private boolean tryExport(TaskMonitor monitor) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java index b35b86f87a..dc46d07bd9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java @@ -986,7 +986,7 @@ public class FunctionEditorModel { if (!paramInfo.isAutoParameter() && paramInfo.isNameModified()) { Parameter param = paramInfo.getOriginalParameter(); if (param != null) { - if (!param.getSymbol().checkIsValid()) { + if (param.getSymbol().isDeleted()) { // concurrent removal of param - must do full update paramsOrReturnModified = true; break; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagLoader.java index 6cc4bc68a4..2fc0bc764b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagLoader.java @@ -41,8 +41,10 @@ public class FunctionTagLoader { * * @param tagFile tag file * @return List list of function tags + * @throws IOException if there is an exception reading the file + * @throws SAXException if there is an exception parsing the file */ - protected static Set loadTags(File tagFile) { + protected static Set loadTags(File tagFile) throws SAXException, IOException { return loadTags(new ResourceFile(tagFile)); } @@ -56,16 +58,17 @@ public class FunctionTagLoader { try { return loadTags(Application.getModuleDataFile(moduleDataFilePath)); } - catch (FileNotFoundException e) { + catch (SAXException | IOException e) { Msg.error(FunctionTagLoader.class, "Error loading function tags file from " + moduleDataFilePath, e); } return new HashSet<>(); } - protected static Set loadTags(final ResourceFile tagDataFile) { - Set tags = new HashSet<>(); + protected static Set loadTags(final ResourceFile tagDataFile) + throws SAXException, IOException { + Set tags = new HashSet<>(); try { ErrorHandler errHandler = new ErrorHandler() { @Override @@ -116,10 +119,6 @@ public class FunctionTagLoader { Msg.error(FunctionTagLoader.class, "Error parsing function tags from " + tagDataFile, e); } - catch (SAXException | IOException e) { - Msg.error(FunctionTagLoader.class, "Error loading function tags from " + tagDataFile, - e); - } return tags; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/AboutProgramPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/AboutProgramPlugin.java index 830b13d6dc..3819a3d611 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/AboutProgramPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/help/AboutProgramPlugin.java @@ -106,7 +106,7 @@ public class AboutProgramPlugin extends Plugin implements FrontEndable { ProgramActionContext pac = (ProgramActionContext) context; Program program = pac.getProgram(); if (program != null) { - String menuName = "About " + program.getDomainFile().getName() + "..."; + String menuName = "About " + program.getDomainFile().getName(); getMenuBarData().setMenuItemName(menuName); return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractHoverProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractHoverProvider.java index 564268be37..3eccd3e4bf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractHoverProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractHoverProvider.java @@ -139,6 +139,13 @@ public abstract class AbstractHoverProvider implements HoverProvider { return; } + Component component = event.getComponent(); + if (!component.isShowing()) { + // This can happen since we are using a timer. When the timer fires, the source + // component may have been hidden. + return; + } + ProgramLocation loc = getHoverLocation(fieldLocation, field, fieldBounds, event); for (HoverService hoverService : hoverServices) { JComponent comp = hoverService.getHoverComponent(program, loc, fieldLocation, field); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPlugin.java index eef0b65f3c..b77dafd9f5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPlugin.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.navigation; import java.awt.event.KeyEvent; +import docking.ActionContext; import docking.action.*; import docking.tool.ToolConstants; import ghidra.GhidraOptions; @@ -29,7 +30,7 @@ import ghidra.app.util.HelpTopics; import ghidra.app.util.navigation.GoToAddressLabelDialog; import ghidra.framework.options.*; import ghidra.framework.plugintool.*; -import ghidra.framework.plugintool.util.*; +import ghidra.framework.plugintool.util.PluginStatus; import ghidra.util.HelpLocation; import ghidra.util.bean.opteditor.OptionsVetoException; @@ -91,11 +92,21 @@ public class GoToAddressLabelPlugin extends Plugin implements OptionsChangeListe public void actionPerformed(NavigatableActionContext context) { goToDialog.show(context.getNavigatable(), context.getAddress(), tool); } + + @Override + protected boolean isEnabledForContext(NavigatableActionContext context) { + return context.getProgram() != null; + } + + @Override + public boolean isAddToPopup(ActionContext context) { + return true; + } }; action.setHelpLocation(new HelpLocation(HelpTopics.NAVIGATION, action.getName())); - action.setMenuBarData(new MenuData( - new String[] { ToolConstants.MENU_NAVIGATION, "Go To..." }, null, "GoTo", - MenuData.NO_MNEMONIC, "2")); // second item in the menu + action.setMenuBarData( + new MenuData(new String[] { ToolConstants.MENU_NAVIGATION, "Go To..." }, null, "GoTo", + MenuData.NO_MNEMONIC, "2")); // second item in the menu action.setKeyBindingData(new KeyBindingData(KeyEvent.VK_G, 0)); @@ -144,10 +155,11 @@ public class GoToAddressLabelPlugin extends Plugin implements OptionsChangeListe * @param newValue new value of the option */ @Override - public void optionsChanged(ToolOptions options, String opName, Object oldValue, Object newValue) { + public void optionsChanged(ToolOptions options, String opName, Object oldValue, + Object newValue) { if (opName.equals(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES)) { maximumGotoEntries = - options.getInt(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES, DEFAULT_MAX_GOTO_ENTRIES); + options.getInt(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES, DEFAULT_MAX_GOTO_ENTRIES); if (maximumGotoEntries <= 0) { throw new OptionsVetoException("Search limit must be greater than 0"); } @@ -155,7 +167,7 @@ public class GoToAddressLabelPlugin extends Plugin implements OptionsChangeListe } else if (opName.equals(GhidraOptions.OPTION_NUMERIC_FORMATTING)) { cStyleInput = - options.getBoolean(GhidraOptions.OPTION_NUMERIC_FORMATTING, DEFAULT_C_STYLE); + options.getBoolean(GhidraOptions.OPTION_NUMERIC_FORMATTING, DEFAULT_C_STYLE); goToDialog.setCStyleInput(cStyleInput); } else if (opName.equals(GO_TO_MEMORY)) { @@ -182,20 +194,20 @@ public class GoToAddressLabelPlugin extends Plugin implements OptionsChangeListe ToolOptions opt = tool.getOptions(ToolConstants.TOOL_OPTIONS); // descriptions opt.registerOption(GhidraOptions.OPTION_NUMERIC_FORMATTING, DEFAULT_C_STYLE, null, - "Interpret value entered in the Go To dialog as either hex, " - + "octal, or binary number."); + "Interpret value entered in the Go To dialog as either hex, " + + "octal, or binary number."); opt.registerOption(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES, DEFAULT_MAX_GOTO_ENTRIES, null, - "Max number of entries remembered in the go to list."); + "Max number of entries remembered in the go to list."); opt.registerOption(GO_TO_MEMORY, DEFAULT_MEMORY, null, - "Remember the last successful go to input in the " - + "Go To dialog. If this option is enabled, then the " - + "Go To dialog will leave the last " - + "successful go to input in the combo box of the Go " - + "To dialog and will select the " + "value for easy paste replacement."); + "Remember the last successful go to input in the " + + "Go To dialog. If this option is enabled, then the " + + "Go To dialog will leave the last " + + "successful go to input in the combo box of the Go " + + "To dialog and will select the " + "value for easy paste replacement."); // options maximumGotoEntries = - opt.getInt(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES, DEFAULT_MAX_GOTO_ENTRIES); + opt.getInt(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES, DEFAULT_MAX_GOTO_ENTRIES); cStyleInput = opt.getBoolean(GhidraOptions.OPTION_NUMERIC_FORMATTING, DEFAULT_C_STYLE); goToDialog.setCStyleInput(cStyleInput); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ProgramTreeActionManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ProgramTreeActionManager.java index 0b1cbca5e5..a437dab6d0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ProgramTreeActionManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ProgramTreeActionManager.java @@ -267,8 +267,8 @@ class ProgramTreeActionManager implements ClipboardOwner { }; goToViewAction.setEnabled(false); - goToViewAction.setPopupMenuData( - new MenuData(new String[] { "Go To in View" }, null, "aview")); + goToViewAction + .setPopupMenuData(new MenuData(new String[] { "Go To in View" }, null, "aview")); list.add(goToViewAction); @@ -282,8 +282,8 @@ class ProgramTreeActionManager implements ClipboardOwner { }; removeViewAction.setEnabled(false); - removeViewAction.setPopupMenuData( - new MenuData(new String[] { "Remove from View" }, null, "aview")); + removeViewAction + .setPopupMenuData(new MenuData(new String[] { "Remove from View" }, null, "aview")); list.add(removeViewAction); @@ -297,8 +297,8 @@ class ProgramTreeActionManager implements ClipboardOwner { }; replaceViewAction.setEnabled(false); - replaceViewAction.setPopupMenuData( - new MenuData(new String[] { "Replace View" }, null, "aview")); + replaceViewAction + .setPopupMenuData(new MenuData(new String[] { "Replace View" }, null, "aview")); list.add(replaceViewAction); @@ -446,8 +446,8 @@ class ProgramTreeActionManager implements ClipboardOwner { collapseAction.setEnabled(false); // ACTIONS - auto generated - collapseAction.setPopupMenuData( - new MenuData(new String[] { "Collapse All" }, null, "expand")); + collapseAction + .setPopupMenuData(new MenuData(new String[] { "Collapse All" }, null, "expand")); list.add(collapseAction); @@ -577,8 +577,7 @@ class ProgramTreeActionManager implements ClipboardOwner { return; } - for (int i = 0; i < list.size(); i++) { - ProgramNode node = list.get(i); + for (ProgramNode node : list) { if (tree.getModel().getRoot() != node.getRoot()) { break; } @@ -642,11 +641,12 @@ class ProgramTreeActionManager implements ClipboardOwner { try { Clipboard systemClipboard = GClipboard.getSystemClipboard(); - Transferable t = systemClipboard.getContents(this); - if (t == null) { + if (!systemClipboard.isDataFlavorAvailable(TreeTransferable.localTreeNodeFlavor)) { return; } - if (!t.isDataFlavorSupported(TreeTransferable.localTreeNodeFlavor)) { + + Object data = systemClipboard.getData(TreeTransferable.localTreeNodeFlavor); + if (data == null) { return; } @@ -659,9 +659,8 @@ class ProgramTreeActionManager implements ClipboardOwner { } private void doClearSystemClipboard(Clipboard systemClipboard) { - // for some reason setting the contents to null for the - // system clipboard causes a NullPointerException, so just - // set it with an empty transferable. + // for some reason setting the contents to null for the system clipboard causes a + // NullPointerException, so just set it with an empty transferable. TreeTransferable dummyContents = new TreeTransferable(new ProgramNode[0]); systemClipboard.setContents(dummyContents, (clipboard, contents) -> { // a dummy implementation that will not prevent this plugin from being @@ -979,8 +978,7 @@ class ProgramTreeActionManager implements ClipboardOwner { ArrayList list = tree.getSortedSelection(); CompoundCmd compCmd = new CompoundCmd("Merge with Parent"); String treeName = tree.getTreeName(); - for (int i = 0; i < list.size(); i++) { - ProgramNode node = list.get(i); + for (ProgramNode node : list) { tree.removeSelectionPath(node.getTreePath()); ProgramNode parentNode = (ProgramNode) node.getParent(); if (node.isModule() && parentNode != null) { @@ -1036,7 +1034,7 @@ class ProgramTreeActionManager implements ClipboardOwner { ProgramNode node = (ProgramNode) tree.getLastSelectedPathComponent(); // if the node has not been yet visited, then when the group is added via the - // command below, the new child node in the parent will not be found + // command below, the new child node in the parent will not be found node.visit(); String name = tree.getNewFolderName(); @@ -1242,14 +1240,15 @@ class ProgramTreeActionManager implements ClipboardOwner { @SuppressWarnings("unchecked") // the cast is safe, since we checked the flavor private boolean isPasteOk(ProgramNode destNode) { - Transferable t = null; + boolean isCutOperation = false; + Clipboard systemClipboard = GClipboard.getSystemClipboard(); + if (!systemClipboard.isDataFlavorAvailable(TreeTransferable.localTreeNodeFlavor)) { + return false; + } try { - t = GClipboard.getSystemClipboard().getContents(this); - if (t == null) { - return false; - } + // we will put items on the 'tempClipboard' when the cut action is executed Transferable temp = tempClipboard.getContents(this); isCutOperation = (temp != null); } @@ -1258,26 +1257,16 @@ class ProgramTreeActionManager implements ClipboardOwner { return false; } - if (!t.isDataFlavorSupported(TreeTransferable.localTreeNodeFlavor)) { - return false; - } - try { - if (!t.isDataFlavorSupported(TreeTransferable.localTreeNodeFlavor)) { - return false; - } List list = - (List) t.getTransferData(TreeTransferable.localTreeNodeFlavor); - + (List) systemClipboard.getData(TreeTransferable.localTreeNodeFlavor); if (list == null) { // SCR 7990--something bad has happened to the copy buffer return false; } boolean pasteEnabled = false; - for (int i = 0; i < list.size(); i++) { - ProgramNode pasteNode = list.get(i); - + for (ProgramNode pasteNode : list) { boolean pasteAllowed = pasteMgr.isPasteAllowed(destNode, pasteNode, isCutOperation); if (isCutOperation && !pasteAllowed) { // for cut operation all nodes must be able to be pasted at destNode diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java index d4417e963d..ed3a55eefb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java @@ -38,7 +38,6 @@ import ghidra.app.plugin.core.compositeeditor.CompositeEditorModel; import ghidra.app.plugin.core.compositeeditor.DataTypeHelper; import ghidra.app.util.datatype.EmptyCompositeException; import ghidra.framework.plugintool.Plugin; -import ghidra.program.database.DatabaseObject; import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.SourceType; @@ -1271,11 +1270,8 @@ class StackEditorModel extends CompositeEditorModel { for (int i = comps.length - 1; i >= 0; i--) { DataTypeComponent component = comps[i]; DataType compDt = component.getDataType(); - if (compDt instanceof DatabaseObject) { - DatabaseObject dbObj = (DatabaseObject) compDt; - if (!dbObj.checkIsValid()) { - clearComponent(component.getOrdinal()); - } + if (compDt.isDeleted()) { + clearComponent(component.getOrdinal()); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java index a5d8ce4537..2561edee13 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.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,18 +15,16 @@ */ package ghidra.app.plugin.core.stackeditor; -import ghidra.app.plugin.core.compositeeditor.CompositeEditorPanel; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.database.DatabaseObject; -import ghidra.program.model.data.*; -import ghidra.program.model.listing.*; -import ghidra.util.exception.UsrException; - import java.awt.event.*; import javax.swing.*; import docking.widgets.OptionDialog; +import ghidra.app.plugin.core.compositeeditor.CompositeEditorPanel; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.*; +import ghidra.util.exception.UsrException; /** * Panel for editing a function stack. @@ -274,14 +271,12 @@ public class StackEditorPanel extends CompositeEditorPanel { if (originalDt instanceof StackFrameDataType) { StackFrameDataType sfdt = (StackFrameDataType) originalDt; Function function = sfdt.getFunction(); - if (function instanceof DatabaseObject) { - if (!((DatabaseObject) function).checkIsValid()) { - // Cancel Editor. - provider.dispose(); - PluginTool tool = ((StackEditorProvider) provider).getPlugin().getTool(); - tool.setStatusInfo("Stack Editor was closed for " + provider.getName()); - return; - } + if (function.isDeleted()) { + // Cancel Editor. + provider.dispose(); + PluginTool tool = ((StackEditorProvider) provider).getPlugin().getTool(); + tool.setStatusInfo("Stack Editor was closed for " + provider.getName()); + return; } StackFrame stack = function.getStackFrame(); StackFrameDataType newSfdt = new StackFrameDataType(stack, dtm); @@ -329,13 +324,10 @@ public class StackEditorPanel extends CompositeEditorPanel { super.dispose(); } - /** - * @param localSizeField - */ private void removeFocusListeners(JTextField textField) { FocusListener[] fl = textField.getFocusListeners(); - for (int i = 0; i < fl.length; i++) { - textField.removeFocusListener(fl[i]); + for (FocusListener element : fl) { + textField.removeFocusListener(element); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java index ab81ad4a09..84e1255168 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java @@ -568,7 +568,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { SymbolTreeRootNode rootNode = (SymbolTreeRootNode) tree.getModelRoot(); // the symbol may have been deleted while we are processing bulk changes - if (symbol.checkIsValid()) { + if (!symbol.isDeleted()) { GTreeNode newNode = rootNode.symbolAdded(symbol); tree.refilterLater(newNode); } @@ -588,7 +588,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { root.symbolRemoved(symbol, monitor); // the symbol may have been deleted while we are processing bulk changes - if (symbol.checkIsValid()) { + if (!symbol.isDeleted()) { root.symbolAdded(symbol); } tree.refilterLater(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java index 1ffb91758b..a680fddcd9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java @@ -134,6 +134,10 @@ class ReferenceProvider extends ComponentProviderAdapter { setVisible(true); } + boolean isBusy() { + return referenceKeyModel.isBusy(); + } + @Override public void componentHidden() { referenceKeyModel.setProgram(null); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java index 1f09fcc85a..cc04711930 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java @@ -151,8 +151,8 @@ class SymbolPanel extends JPanel { filterDialog.adjustFilter(symProvider, tableModel); } - NewSymbolFilter getFilter() { - return filterDialog.getFilter(); + SymbolFilter getFilter() { + return tableModel.getFilter(); } void readConfigState(SaveState saveState) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java index 77ee04ffa0..54b9a4f3e7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java @@ -149,10 +149,14 @@ class SymbolProvider extends ComponentProviderAdapter { return symbolPanel.getTable(); } - NewSymbolFilter getFilter() { + SymbolFilter getFilter() { return symbolPanel.getFilter(); } + boolean isShowingDynamicSymbols() { + return getFilter().acceptsDefaultLabelSymbols(); + } + private String generateSubTitle() { SymbolFilter filter = symbolKeyModel.getFilter(); int rowCount = symbolKeyModel.getRowCount(); @@ -174,11 +178,15 @@ class SymbolProvider extends ComponentProviderAdapter { } } + boolean isBusy() { + return symbolKeyModel.isBusy(); + } + @Override public void componentHidden() { symbolKeyModel.reload(null); if (plugin != null) { - plugin.closeReferenceProvider(); + plugin.symbolProviderClosed(); } } @@ -199,5 +207,4 @@ class SymbolProvider extends ComponentProviderAdapter { void writeConfigState(SaveState saveState) { symbolPanel.writeConfigState(saveState); } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolReferenceModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolReferenceModel.java index a3f99f8114..beafc26df3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolReferenceModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolReferenceModel.java @@ -71,7 +71,7 @@ public class SymbolReferenceModel extends AddressBasedTableModel { @Override protected TableColumnDescriptor createTableColumnDescriptor() { - TableColumnDescriptor descriptor = new TableColumnDescriptor(); + TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); descriptor.addVisibleColumn( DiscoverableTableUtils.adaptColumForModel(this, new ReferenceFromAddressTableColumn()), @@ -139,7 +139,6 @@ public class SymbolReferenceModel extends AddressBasedTableModel { void symbolChanged(Symbol symbol) { if (currentSymbol != null && currentSymbol.equals(symbol)) { - setCurrentSymbol(symbol); return; } checkRefs(symbol); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java index 365a3699ee..fb922b22c0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java @@ -264,12 +264,13 @@ class SymbolTableModel extends AddressBasedTableModel { updateObject(Symbol); } else { + // the symbol may be in the table, as it could have passed the filter before the change removeObject(Symbol); } } void delete(List rowObjects) { - if (rowObjects == null || rowObjects.size() == 0) { + if (rowObjects == null || rowObjects.isEmpty()) { return; } @@ -379,7 +380,7 @@ class SymbolTableModel extends AddressBasedTableModel { public Symbol getValue(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) throws IllegalArgumentException { - if (!symbol.checkIsValid()) { + if (symbol.isDeleted()) { return null; } return symbol; @@ -400,7 +401,7 @@ class SymbolTableModel extends AddressBasedTableModel { public Boolean getValue(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) throws IllegalArgumentException { - if (!symbol.checkIsValid()) { + if (symbol.isDeleted()) { return null; } return symbol.isPinned(); @@ -435,7 +436,7 @@ class SymbolTableModel extends AddressBasedTableModel { public ProgramLocation getProgramLocation(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) { - if (!symbol.checkIsValid()) { + if (symbol.isDeleted()) { return null; } return symbol.getProgramLocation(); @@ -454,7 +455,7 @@ class SymbolTableModel extends AddressBasedTableModel { public String getValue(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) throws IllegalArgumentException { - if (!symbol.checkIsValid()) { + if (symbol.isDeleted()) { return null; } @@ -483,7 +484,7 @@ class SymbolTableModel extends AddressBasedTableModel { public String getValue(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) throws IllegalArgumentException { - if (!symbol.checkIsValid()) { + if (symbol.isDeleted()) { return null; } @@ -520,7 +521,7 @@ class SymbolTableModel extends AddressBasedTableModel { public String getValue(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) throws IllegalArgumentException { - if (!symbol.checkIsValid()) { + if (symbol.isDeleted()) { return null; } return symbol.getParentNamespace().getName(true); @@ -530,7 +531,7 @@ class SymbolTableModel extends AddressBasedTableModel { private class SourceTableColumn extends AbstractProgramBasedDynamicTableColumn { - private GColumnRenderer renderer = new AbstractGColumnRenderer() { + private GColumnRenderer renderer = new AbstractGColumnRenderer<>() { @Override protected String getText(Object value) { if (value == null) { @@ -580,7 +581,7 @@ class SymbolTableModel extends AddressBasedTableModel { @Override public Integer getValue(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) throws IllegalArgumentException { - if (!symbol.checkIsValid()) { + if (symbol.isDeleted()) { return null; } return Integer.valueOf(symbol.getReferenceCount()); @@ -612,7 +613,7 @@ class SymbolTableModel extends AddressBasedTableModel { @Override public Integer getValue(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) throws IllegalArgumentException { - if (!symbol.checkIsValid()) { + if (symbol.isDeleted()) { return null; } @@ -665,7 +666,7 @@ class SymbolTableModel extends AddressBasedTableModel { public String getValue(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) throws IllegalArgumentException { - if (!symbol.checkIsValid()) { + if (symbol.isDeleted()) { return null; } @@ -702,7 +703,7 @@ class SymbolTableModel extends AddressBasedTableModel { public String getValue(Symbol symbol, Settings settings, Program p, ServiceProvider svcProvider) throws IllegalArgumentException { - if (!symbol.checkIsValid()) { + if (symbol.isDeleted()) { return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java index 95d35fc0d6..2f0c82bbd9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java @@ -44,6 +44,9 @@ import ghidra.util.table.GhidraTable; import ghidra.util.table.SelectionNavigationAction; import ghidra.util.table.actions.MakeProgramSelectionAction; import ghidra.util.task.SwingUpdateManager; +import ghidra.util.task.TaskMonitor; +import ghidra.util.worker.Job; +import ghidra.util.worker.Worker; import resources.Icons; import resources.ResourceManager; @@ -88,6 +91,13 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { private BlockModelService blockModelService; private SwingUpdateManager swingMgr; + /** + * A worker that will process domain object change event work off of the Swing thread. This + * solves the issue of db lock contention that can happen during analysis while this class + * attempts to get symbols from the db while processing a flurry of domain events. + */ + private Worker domainObjectWorker = Worker.createGuiWorker(); + public SymbolTablePlugin(PluginTool tool) { super(tool); @@ -124,6 +134,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { deleteAction.dispose(); makeSelectionAction.dispose(); + domainObjectWorker.dispose(); if (symProvider != null) { symProvider.dispose(); symProvider = null; @@ -165,17 +176,15 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { if (oldProg != null) { inspector.setProgram(null); oldProg.removeListener(this); + domainObjectWorker.clearAllJobs(); symProvider.setProgram(null, inspector); refProvider.setProgram(null, inspector); tool.contextChanged(symProvider); } currentProgram = newProg; if (newProg != null) { - currentProgram.addListener(this); - inspector.setProgram(currentProgram); - symProvider.setProgram(currentProgram, inspector); refProvider.setProgram(currentProgram, inspector); } @@ -184,17 +193,27 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { } } + boolean isBusy() { + return domainObjectWorker.isBusy() || symProvider.isBusy() || refProvider.isBusy(); + } + + private void reload() { + domainObjectWorker.clearAllJobs(); + symProvider.reload(); + refProvider.reload(); + } + @Override public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (!symProvider.isVisible()) { + if (!symProvider.isVisible() && !refProvider.isVisible()) { return; } + if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED) || ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_ADDED) || ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED)) { - symProvider.reload(); - refProvider.reload(); + reload(); return; } @@ -208,103 +227,74 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { } ProgramChangeRecord rec = (ProgramChangeRecord) doRecord; - Symbol symbol = null; - SymbolTable symbolTable = currentProgram.getSymbolTable(); switch (eventType) { case ChangeManager.DOCR_CODE_ADDED: case ChangeManager.DOCR_CODE_REMOVED: if (rec.getNewValue() instanceof Data) { - symbol = symbolTable.getPrimarySymbol(rec.getStart()); - if (symbol != null && symbol.isDynamic()) { - symProvider.symbolChanged(symbol); - refProvider.symbolChanged(symbol); - } + domainObjectWorker.schedule( + new CodeAddedRemoveJob(currentProgram, rec.getStart())); } break; case ChangeManager.DOCR_SYMBOL_ADDED: + Address addAddr = rec.getStart(); - Symbol primaryAtAdd = symbolTable.getPrimarySymbol(addAddr); - if (primaryAtAdd != null && primaryAtAdd.isDynamic()) { - symProvider.symbolRemoved(primaryAtAdd); - } - symbol = (Symbol) rec.getNewValue(); - symProvider.symbolAdded(symbol); - refProvider.symbolAdded(symbol); + Symbol symbol = (Symbol) rec.getNewValue(); + domainObjectWorker.schedule( + new SymbolAddedJob(currentProgram, symbol, addAddr)); break; case ChangeManager.DOCR_SYMBOL_REMOVED: + Address removeAddr = rec.getStart(); Long symbolID = (Long) rec.getNewValue(); - Symbol removedSymbol = - symbolTable.createSymbolPlaceholder(removeAddr, symbolID); - symProvider.symbolRemoved(removedSymbol); - refProvider.symbolRemoved(removedSymbol); - Symbol primaryAtRemove = symbolTable.getPrimarySymbol(removeAddr); - if (primaryAtRemove != null && primaryAtRemove.isDynamic()) { - symProvider.symbolAdded(primaryAtRemove); - } + domainObjectWorker.schedule( + new SymbolRemovedJob(currentProgram, removeAddr, symbolID)); break; case ChangeManager.DOCR_SYMBOL_RENAMED: case ChangeManager.DOCR_SYMBOL_SCOPE_CHANGED: case ChangeManager.DOCR_SYMBOL_DATA_CHANGED: + symbol = (Symbol) rec.getObject(); - if (symbol.checkIsValid()) { // symbol may have been removed (e.g., parameter) - symProvider.symbolChanged(symbol); - refProvider.symbolChanged(symbol); - } + domainObjectWorker.schedule(new SymbolChangedJob(currentProgram, symbol)); break; case ChangeManager.DOCR_SYMBOL_SOURCE_CHANGED: + symbol = (Symbol) rec.getObject(); - symProvider.symbolChanged(symbol); + domainObjectWorker.schedule(new SymbolSourceChangedJob(currentProgram, symbol)); break; case ChangeManager.DOCR_SYMBOL_SET_AS_PRIMARY: + symbol = (Symbol) rec.getNewValue(); - symProvider.symbolChanged(symbol); - Symbol oldSymbol = (Symbol) rec.getOldValue(); - if (oldSymbol != null) { - symProvider.symbolChanged(oldSymbol); - } + Symbol oldPrimarySymbol = (Symbol) rec.getOldValue(); + domainObjectWorker.schedule( + new SymbolSetAsPrimaryJob(currentProgram, symbol, oldPrimarySymbol)); break; case ChangeManager.DOCR_SYMBOL_ASSOCIATION_ADDED: case ChangeManager.DOCR_SYMBOL_ASSOCIATION_REMOVED: break; case ChangeManager.DOCR_MEM_REFERENCE_ADDED: - Reference ref = (Reference) rec.getObject(); - symbol = symbolTable.getSymbol(ref); - if (symbol != null) { - symProvider.symbolChanged(symbol); - refProvider.symbolChanged(symbol); - } - break; - case ChangeManager.DOCR_MEM_REFERENCE_REMOVED: - ref = (Reference) rec.getObject(); - Address toAddr = ref.getToAddress(); - if (toAddr.isMemoryAddress()) { - symbol = symbolTable.getSymbol(ref); - if (symbol == null) { - long id = symbolTable.getDynamicSymbolID(ref.getToAddress()); - removedSymbol = symbolTable.createSymbolPlaceholder(toAddr, id); - symProvider.symbolRemoved(removedSymbol); - } - else { - refProvider.symbolChanged(symbol); - } - } + Reference ref = (Reference) rec.getObject(); + domainObjectWorker.schedule(new ReferenceAddedJob(currentProgram, ref)); + break; + + case ChangeManager.DOCR_MEM_REFERENCE_REMOVED: + + ref = (Reference) rec.getObject(); + domainObjectWorker.schedule(new ReferenceRemovedJob(currentProgram, ref)); break; case ChangeManager.DOCR_EXTERNAL_ENTRY_POINT_ADDED: case ChangeManager.DOCR_EXTERNAL_ENTRY_POINT_REMOVED: - Symbol[] symbols = symbolTable.getSymbols(rec.getStart()); - for (Symbol element : symbols) { - symProvider.symbolChanged(element); - refProvider.symbolChanged(element); - } + + Address address = rec.getStart(); + domainObjectWorker.schedule( + new ExternalEntryChangedJob(currentProgram, address)); break; } } @@ -336,7 +326,8 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { } } - void closeReferenceProvider() { + void symbolProviderClosed() { + domainObjectWorker.clearAllJobs(); if (refProvider != null) { refProvider.closeComponent(); } @@ -514,4 +505,268 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { action.setSelected(false); action.setSelected(true); } + +//================================================================================================== +// Table Update Jobs +//================================================================================================== + + private abstract class AbstractSymbolUpdateJob extends Job { + + protected Program program; + + AbstractSymbolUpdateJob(Program program) { + this.program = program; + } + + @Override + public final void run(TaskMonitor taskMonitor) { + if (program != currentProgram) { + return; + } + doRun(); + } + + protected abstract void doRun(); + } + + private class CodeAddedRemoveJob extends AbstractSymbolUpdateJob { + + private Address start; + + CodeAddedRemoveJob(Program program, Address start) { + super(program); + this.start = start; + } + + @Override + protected void doRun() { + + if (!symProvider.isShowingDynamicSymbols()) { + return; + } + + // Note: this code *should* be checking the entire address range to handle the case + // where large address range was cleared. This implementation will handle the + // case where individual code units are cleared. This feature has been this way + // for many years. The assumption is that most users are not showing dynamic + // symbols often, especially not when performing analysis or clearing large + // address ranges. Checking each address of the changed range is very slow. + // This code will need to be updated in the future if we decide updating the + // dynamic symbols in the symbol table is worth the cost. For now, if the table + // becomes out-of-date, then user can simply close and re-open the table to + // trigger an update. + SymbolTable symbolTable = currentProgram.getSymbolTable(); + Symbol symbol = symbolTable.getPrimarySymbol(start); + if (symbol != null && symbol.isDynamic()) { + symProvider.symbolChanged(symbol); + refProvider.symbolChanged(symbol); + } + } + } + + private class SymbolAddedJob extends AbstractSymbolUpdateJob { + + private Symbol symbol; + private Address address; + + SymbolAddedJob(Program program, Symbol symbol, Address address) { + super(program); + this.symbol = symbol; + this.address = address; + } + + @Override + protected void doRun() { + + symProvider.symbolAdded(symbol); + refProvider.symbolAdded(symbol); + + if (!symProvider.isShowingDynamicSymbols()) { + return; + } + + SymbolTable symbolTable = program.getSymbolTable(); + Symbol primaryAtAdd = symbolTable.getPrimarySymbol(address); + if (primaryAtAdd != null && primaryAtAdd.isDynamic()) { + symProvider.symbolRemoved(primaryAtAdd); + refProvider.symbolRemoved(primaryAtAdd); + } + } + } + + private class SymbolRemovedJob extends AbstractSymbolUpdateJob { + + private long symbolId; + private Address address; + + SymbolRemovedJob(Program program, Address address, long symbolId) { + super(program); + this.address = address; + this.symbolId = symbolId; + } + + @Override + protected void doRun() { + + SymbolTable symbolTable = currentProgram.getSymbolTable(); + Symbol removedSymbol = + symbolTable.createSymbolPlaceholder(address, symbolId); + symProvider.symbolRemoved(removedSymbol); + refProvider.symbolRemoved(removedSymbol); + + if (!symProvider.isShowingDynamicSymbols()) { + return; + } + + Symbol primaryAtRemove = symbolTable.getPrimarySymbol(address); + if (primaryAtRemove != null && primaryAtRemove.isDynamic()) { + symProvider.symbolAdded(primaryAtRemove); + refProvider.symbolAdded(primaryAtRemove); + } + } + } + + private class SymbolChangedJob extends AbstractSymbolUpdateJob { + + private Symbol symbol; + + SymbolChangedJob(Program program, Symbol symbol) { + super(program); + this.symbol = symbol; + } + + @Override + protected void doRun() { + + // Note: should not need this check--the provider should be built to handle this + // if (symbol.checkIsValid()) + symProvider.symbolChanged(symbol); + refProvider.symbolChanged(symbol); + } + } + + private class SymbolSourceChangedJob extends AbstractSymbolUpdateJob { + + private Symbol symbol; + + SymbolSourceChangedJob(Program program, Symbol symbol) { + super(program); + this.symbol = symbol; + } + + @Override + protected void doRun() { + symProvider.symbolChanged(symbol); + } + } + + private class SymbolSetAsPrimaryJob extends AbstractSymbolUpdateJob { + + private Symbol symbol; + private Symbol oldPrimarySymbol; + + SymbolSetAsPrimaryJob(Program program, Symbol symbol, Symbol oldPrimarySymbol) { + super(program); + this.symbol = symbol; + this.oldPrimarySymbol = oldPrimarySymbol; + } + + @Override + protected void doRun() { + + symProvider.symbolChanged(symbol); + if (oldPrimarySymbol != null) { + symProvider.symbolChanged(oldPrimarySymbol); + } + } + } + + private class ReferenceAddedJob extends AbstractSymbolUpdateJob { + + private Reference reference; + + ReferenceAddedJob(Program program, Reference reference) { + super(program); + this.reference = reference; + } + + @Override + protected void doRun() { + + Address toAddr = reference.getToAddress(); + boolean isValid = toAddr.isMemoryAddress() || toAddr.isExternalAddress(); + if (!isValid) { + return; + } + + SymbolTable symbolTable = program.getSymbolTable(); + Symbol symbol = symbolTable.getSymbol(reference); + if (symbol == null) { + return; + } + + if (!symProvider.isShowingDynamicSymbols() && symbol.isDynamic()) { + return; + } + + symProvider.symbolChanged(symbol); + refProvider.symbolChanged(symbol); + } + } + + private class ReferenceRemovedJob extends AbstractSymbolUpdateJob { + + private Reference reference; + + ReferenceRemovedJob(Program program, Reference reference) { + super(program); + this.reference = reference; + } + + @Override + protected void doRun() { + + Address toAddr = reference.getToAddress(); + boolean isValid = toAddr.isMemoryAddress() || toAddr.isExternalAddress(); + if (!isValid) { + return; + } + + SymbolTable symbolTable = program.getSymbolTable(); + Symbol symbol = symbolTable.getSymbol(reference); + if (symbol != null) { + symProvider.symbolChanged(symbol); + refProvider.symbolChanged(symbol); + } + + if (symProvider.isShowingDynamicSymbols()) { + long id = symbolTable.getDynamicSymbolID(reference.getToAddress()); + Symbol removedSymbol = symbolTable.createSymbolPlaceholder(toAddr, id); + symProvider.symbolRemoved(removedSymbol); + refProvider.symbolRemoved(removedSymbol); + } + } + } + + private class ExternalEntryChangedJob extends AbstractSymbolUpdateJob { + + private Address address; + + ExternalEntryChangedJob(Program program, Address address) { + super(program); + this.address = address; + } + + @Override + protected void doRun() { + + SymbolTable symbolTable = program.getSymbolTable(); + Symbol[] symbols = symbolTable.getSymbols(address); + for (Symbol element : symbols) { + symProvider.symbolChanged(element); + refProvider.symbolChanged(element); + } + } + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java index d94e14cab1..82da554bf4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java @@ -148,4 +148,31 @@ public abstract class GhidraScriptProvider return scriptName; } + /** + * Return the start of certification header line if this file type is + * subject to certification. + * @return start of certification header or null if not supported + */ + protected String getCertifyHeaderStart() { + return null; + } + + /** + * Return the prefix for each certification header bofy line if + * this file is subject to certification + * @return certification heaber body prefix or null if not supported + */ + protected String getCertificationBodyPrefix() { + return null; + } + + /** + * Return the end of certification header line if this file type is + * subject to certification. + * @return end of certification header or null if not supported + */ + protected String getCertifyHeaderEnd() { + return null; + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java index 39f99ffd91..8f3539576b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java @@ -164,6 +164,16 @@ public class JavaScriptProvider extends GhidraScriptProvider { return "//"; } + @Override + protected String getCertifyHeaderStart() { + return "/* ###"; + } + + @Override + protected String getCertifyHeaderEnd() { + return "*/"; + } + /** * * Fix script name for search in script directories, such as Java package parts in the name and inner class names. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java index 37c5e52016..f92c30647f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java @@ -20,7 +20,6 @@ import static ghidra.util.HTMLUtilities.*; import java.io.*; import java.util.List; import java.util.StringTokenizer; -import java.util.regex.Pattern; import javax.swing.ImageIcon; import javax.swing.KeyStroke; @@ -47,9 +46,6 @@ public class ScriptInfo { static final String AT_MENUPATH = "@menupath"; static final String AT_TOOLBAR = "@toolbar"; - private static final Pattern DOCUMENTATION_START = Pattern.compile("/\\*"); - private static final Pattern DOCUMENTATION_END = Pattern.compile("\\*/"); - // omit from METADATA to avoid pre-populating in new scripts private static final String AT_IMPORTPACKAGE = "@importpackage"; @@ -186,6 +182,16 @@ public class ScriptInfo { init(); + String commentPrefix = provider.getCommentCharacter(); + + // Note that skipping certification header presumes that the header + // is intact with an appropriate start and end + String certifyHeaderStart = provider.getCertifyHeaderStart(); + String certifyHeaderEnd = provider.getCertifyHeaderEnd(); + String certifyHeaderBodyPrefix = provider.getCertificationBodyPrefix(); + boolean allowCertifyHeader = (certifyHeaderStart != null); + boolean skipCertifyHeader = false; + BufferedReader reader = null; try { StringBuffer buffer = new StringBuffer(); @@ -197,16 +203,34 @@ public class ScriptInfo { break; } - if (DOCUMENTATION_START.matcher(line).find()) { - while (line != null && !DOCUMENTATION_END.matcher(line).find()) { - line = reader.readLine(); + if (allowCertifyHeader) { + // Skip past certification header if found + if (skipCertifyHeader) { + String trimLine = line.trim(); + if (trimLine.startsWith(certifyHeaderEnd)) { + allowCertifyHeader = false; + skipCertifyHeader = false; + continue; + } + if (certifyHeaderBodyPrefix == null || + trimLine.startsWith(certifyHeaderBodyPrefix)) { + continue; // skip certification header body + } + // broken certification header - unexpected line + Msg.error(this, + "Script contains invalid certification header: " + getName()); + allowCertifyHeader = false; + skipCertifyHeader = false; + } + else if (line.startsWith(certifyHeaderStart)) { + skipCertifyHeader = true; + continue; } - continue; } - String commentPrefix = provider.getCommentCharacter(); - if (line.startsWith(commentPrefix)) { + allowCertifyHeader = false; + line = line.substring(commentPrefix.length()).trim(); if (line.startsWith("@")) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java index b9ddf2cacf..46369f05e4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; import javax.swing.*; import javax.swing.table.TableCellRenderer; @@ -38,7 +39,7 @@ import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.util.HelpLocation; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; import ghidra.util.table.*; @@ -47,13 +48,13 @@ import ghidra.util.task.TaskMonitor; import utility.function.Callback; /** - * Dialog to show a table of items. If the dialog is constructed with a non-null + * Dialog to show a table of items. If the dialog is constructed with a non-null * {@link TableChooserExecutor}, then a button will be placed in the dialog, allowing the user * to perform the action defined by the executor. - * - *

    Each button press will use the selected items as the items to be processed. While the - * items are scheduled to be processed, they will still be in the table, painted light gray. - * Attempting to reschedule any of these pending items will have no effect. Each time the + * + *

    Each button press will use the selected items as the items to be processed. While the + * items are scheduled to be processed, they will still be in the table, painted light gray. + * Attempting to reschedule any of these pending items will have no effect. Each time the * button is pressed, a new {@link SwingWorker} is created, which will put the processing into * a background thread. Further, by using multiple workers, the work will be performed in * parallel. @@ -113,8 +114,7 @@ public class TableChooserDialog extends DialogComponentProvider table.installNavigation(goToService, navigatable); } table.getSelectionModel() - .addListSelectionListener( - e -> setOkEnabled(table.getSelectedRowCount() > 0)); + .addListSelectionListener(e -> setOkEnabled(table.getSelectedRowCount() > 0)); GhidraTableFilterPanel filterPanel = new GhidraTableFilterPanel<>(table, model); @@ -128,12 +128,12 @@ public class TableChooserDialog extends DialogComponentProvider * @param callback the callback to notify */ public void setClosedListener(Callback callback) { - this.closedCallback = Callback.dummyIfNull(callback); + Swing.runNow(() -> closedCallback = Callback.dummyIfNull(callback)); } /** * Adds the given object to this dialog. This method can be called from any thread. - * + * * @param rowObject the object to add */ public void add(AddressableRowObject rowObject) { @@ -141,9 +141,9 @@ public class TableChooserDialog extends DialogComponentProvider } /** - * Removes the given object from this dialog. Nothing will happen if the given item is not + * Removes the given object from this dialog. Nothing will happen if the given item is not * in this dialog. This method can be called from any thread. - * + * * @param rowObject the object to remove */ public void remove(AddressableRowObject rowObject) { @@ -153,8 +153,7 @@ public class TableChooserDialog extends DialogComponentProvider private void createTableModel() { // note: the task monitor is installed later when this model is added to the threaded panel - SystemUtilities.runSwingNow( - () -> model = new TableChooserTableModel("Test", tool, program, null)); + Swing.runNow(() -> model = new TableChooserTableModel("Test", tool, program, null)); } private void createActions() { @@ -175,8 +174,8 @@ public class TableChooserDialog extends DialogComponentProvider }; DockingAction selectionNavigationAction = new SelectionNavigationAction(owner, table); - selectionNavigationAction.setHelpLocation( - new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation")); + selectionNavigationAction + .setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation")); addAction(selectAction); addAction(selectionNavigationAction); @@ -296,7 +295,49 @@ public class TableChooserDialog extends DialogComponentProvider } public void addCustomColumn(ColumnDisplay columnDisplay) { - model.addCustomColumn(columnDisplay); + Swing.runNow(() -> model.addCustomColumn(columnDisplay)); + } + + /** + * Sets the default sorted column for this dialog. + * + *

    This method should be called after all custom columns have been added via + * {@link #addCustomColumn(ColumnDisplay)}. + * + * @param index the view's 0-based column index + * @see #setSortState(TableSortState) + * @throws IllegalArgumentException if an invalid column is requested for sorting + */ + public void setSortColumn(int index) { + setSortState(TableSortState.createDefaultSortState(index)); + } + + /** + * Sets the column sort state for this dialog. The {@link TableSortState} allows for + * combinations of sorted columns in ascending or descending order. + * + *

    This method should be called after all custom columns have been added via + * {@link #addCustomColumn(ColumnDisplay)}. + * + * @param state the sort state + * @see #setSortColumn(int) + * @throws IllegalArgumentException if an invalid column is requested for sorting + */ + public void setSortState(TableSortState state) { + AtomicReference ref = new AtomicReference<>(); + Swing.runNow(() -> { + try { + model.setTableSortState(state); + } + catch (IllegalArgumentException e) { + ref.set(e); + } + }); + IllegalArgumentException exception = ref.get(); + if (exception != null) { + // use a new exception so the stack trace points to this class, not the runnable above + throw new IllegalArgumentException(exception); + } } @Override @@ -313,15 +354,17 @@ public class TableChooserDialog extends DialogComponentProvider } public void clearSelection() { - table.clearSelection(); + Swing.runNow(() -> table.clearSelection()); } public void selectRows(int... rows) { - ListSelectionModel selectionModel = table.getSelectionModel(); - for (int row : rows) { - selectionModel.addSelectionInterval(row, row); - } + Swing.runNow(() -> { + ListSelectionModel selectionModel = table.getSelectionModel(); + for (int row : rows) { + selectionModel.addSelectionInterval(row, row); + } + }); } public int[] getSelectedRows() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/DisplayableEol.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/DisplayableEol.java index c2dad1ca28..2457f84129 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/DisplayableEol.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/DisplayableEol.java @@ -36,7 +36,6 @@ import ghidra.program.util.*; */ public class DisplayableEol { - private static final String VAR_ARGS = "..."; private static final String POINTER_ARROW = "-> "; public static final int MY_EOLS = 0; @@ -48,19 +47,16 @@ public class DisplayableEol { private boolean alwaysShowRepeatable = false; private boolean alwaysShowRefRepeats = false; private boolean alwaysShowAutomatic = false; + private boolean showAutomaticFunctions; private boolean operandsFollowPointerRefs = false; private int maxDisplayLines; private int totalCommentsFound; private boolean useAbbreviatedAutomatic; - /** - * Construct a new DisplayableEol. - * @param cu code unit that may have end of line or repeatable comments. - */ public DisplayableEol(CodeUnit cu, boolean alwaysShowRepeatable, boolean alwaysShowRefRepeats, boolean alwaysShowAutomatic, boolean operandsFollowPointerRefs, int maxDisplayLines, - boolean useAbbreviatedAutomatic) { + boolean useAbbreviatedAutomatic, boolean showAutomaticFunctions) { this.codeUnit = cu; this.alwaysShowRepeatable = alwaysShowRepeatable; this.alwaysShowRefRepeats = alwaysShowRefRepeats; @@ -68,6 +64,7 @@ public class DisplayableEol { this.operandsFollowPointerRefs = operandsFollowPointerRefs; this.maxDisplayLines = maxDisplayLines; this.useAbbreviatedAutomatic = useAbbreviatedAutomatic; + this.showAutomaticFunctions = showAutomaticFunctions; initComments(); } @@ -118,7 +115,8 @@ public class DisplayableEol { } /** - * Return whether the associated code unit has an end of line comment. + * Return whether the associated code unit has an end of line comment + * @return whether the associated code unit has an end of line comment */ public boolean hasEOL() { return (displayCommentArrays[MY_EOLS] != null) && @@ -126,7 +124,8 @@ public class DisplayableEol { } /** - * Return whether the associated code unit has a repeatable comment. + * Return whether the associated code unit has a repeatable comment + * @return whether the associated code unit has a repeatable comment */ public boolean hasRepeatable() { return (displayCommentArrays[MY_REPEATABLES] != null) && @@ -135,7 +134,9 @@ public class DisplayableEol { /** * Return whether any memory reference from this code unit has a repeatable - * comment at the reference's to address. + * comment at the reference's to address + * @return whether any memory reference from this code unit has a repeatable + * comment at the reference's to address */ public boolean hasReferencedRepeatable() { return (displayCommentArrays[REF_REPEATABLES] != null) && @@ -143,10 +144,10 @@ public class DisplayableEol { } /** - * Return whether this code unit has an automatic comment. - * (i.e. any memory reference from this code unit has a - * function defined at the reference's to address, or if the to - * address is a pointer.) + * Return whether this code unit has an automatic comment. For example, a memory reference + * from this code unit has a function defined at the reference's to address, or if the to + * address is a pointer. + * @return whether this code unit has an automatic comment */ public boolean hasAutomatic() { return (displayCommentArrays[MY_AUTOMATIC] != null) && @@ -367,6 +368,10 @@ public class DisplayableEol { private boolean handleDirectFlow(Set set, Reference reference, Program program, Address toAddr) { + if (!showAutomaticFunctions) { + return false; + } + RefType type = reference.getReferenceType(); if (!type.isFlow()) { return false; @@ -537,7 +542,8 @@ public class DisplayableEol { } /** - * Return all the comments (End of Line, Repeatable, Referenced Repeatables, and Referenced Data). + * Return all the comments + * @return the comments */ public String[] getComments() { ArrayList list = new ArrayList<>(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/StructConverter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/StructConverter.java index b58676d8a0..23259ebcfe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/StructConverter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/StructConverter.java @@ -71,6 +71,10 @@ public interface StructConverter { * Reusable 32-bit image base offset datatype. */ public final static DataType IBO32 = new ImageBaseOffset32DataType(); + /** + * Reusable 64-bit image base offset datatype. + */ + public final static DataType IBO64 = new ImageBaseOffset64DataType(); /** * Returns a structure datatype representing the diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java index 888e101975..35893f8ff8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java @@ -15,9 +15,10 @@ */ package ghidra.app.util.bin.format.dwarf4; -import java.io.IOException; import java.util.*; +import java.io.IOException; + import org.apache.commons.lang3.ArrayUtils; import ghidra.app.util.bin.BinaryReader; @@ -884,6 +885,30 @@ public class DIEAggregate { throw new IOException("Bad/unsupported DW_AT_high_pc attribute value or type"); } + /** + * Returns true if the raw lowPc and highPc values are the same. + *

    + * This indicates an empty range, in which case the caller may want to take + * special steps to avoid issues with Ghidra ranges. + *

    + * Only seen in extremely old gcc versions. Typically the low & high + * pc values are omitted if the CU is empty. + * + * @return boolean true if the LowPC and HighPC values are present and equal + */ + public boolean isLowPCEqualHighPC() { + AttrInfo low = findAttribute(DWARFAttribute.DW_AT_low_pc); + AttrInfo high = findAttribute(DWARFAttribute.DW_AT_high_pc); + if (low != null && high != null && low.form == high.form && + low.attr instanceof DWARFNumericAttribute && + high.attr instanceof DWARFNumericAttribute) { + DWARFNumericAttribute lowVal = (DWARFNumericAttribute) low.attr; + DWARFNumericAttribute highVal = (DWARFNumericAttribute) high.attr; + return lowVal.getValue() == highVal.getValue(); + } + return false; + } + /** * A simple class used by findAttribute() to return the found attribute, along with * the DIE it was found in, and the DWARFForm type of the raw attribute. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFCompileUnit.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFCompileUnit.java index 2b46c53442..3c8bd88929 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFCompileUnit.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFCompileUnit.java @@ -49,11 +49,14 @@ public class DWARFCompileUnit { String comp_dir = diea.getString(DWARFAttribute.DW_AT_comp_dir, null); Number high_pc = null, low_pc = null, language = null, stmt_list = null; + if (diea.hasAttribute(DWARFAttribute.DW_AT_low_pc)) { low_pc = diea.getLowPC(0); } - if (diea.hasAttribute(DWARFAttribute.DW_AT_high_pc)) { + // if lowPC and highPC values are the same, don't read the high value + // because Ghidra can't express an empty range. + if (diea.hasAttribute(DWARFAttribute.DW_AT_high_pc) && !diea.isLowPCEqualHighPC()) { high_pc = diea.getHighPC(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java index 786da2b95c..5cdfc55a1c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java @@ -15,10 +15,11 @@ */ package ghidra.app.util.bin.format.dwarf4.next; -import java.io.IOException; import java.util.*; import java.util.stream.Collectors; +import java.io.IOException; + import ghidra.app.cmd.comments.AppendCommentCmd; import ghidra.app.cmd.label.SetLabelPrimaryCmd; import ghidra.app.util.bin.format.dwarf4.*; @@ -202,26 +203,26 @@ public class DWARFFunctionImporter { return; } - DWARFFunction function = new DWARFFunction(prog.getName(diea)); - function.namespace = function.dni.getParentNamespace(currentProgram); + DWARFFunction dfunc = new DWARFFunction(prog.getName(diea)); + dfunc.namespace = dfunc.dni.getParentNamespace(currentProgram); Number lowPC = diea.getLowPC(0); - function.address = toAddr(lowPC); - function.highAddress = + dfunc.address = toAddr(lowPC); + dfunc.highAddress = diea.hasAttribute(DWARFAttribute.DW_AT_high_pc) ? toAddr(diea.getHighPC()) : null; - String previousFunctionProcessed = functionsProcessed.get(function.address); + String previousFunctionProcessed = functionsProcessed.get(dfunc.address); if (previousFunctionProcessed != null) { // Msg.info(this, "Duplicate function defintion found for " + dni.getCategoryPath() + // " at " + function.address + " in DIE " + diea.getHexOffset() + ", skipping"); markAllChildrenAsProcessed(diea.getHeadFragment()); return; } - functionsProcessed.put(function.address, - function.dni.getNamespacePath() + " DIE: " + diea.getHexOffset()); + functionsProcessed.put(dfunc.address, + dfunc.dni.getNamespacePath() + " DIE: " + diea.getHexOffset()); // Check if the function is an external function - function.isExternal = diea.getBool(DWARFAttribute.DW_AT_external, false); + dfunc.isExternal = diea.getBool(DWARFAttribute.DW_AT_external, false); // Retrieve the frame base if it exists DWARFLocation frameLoc = null; @@ -229,9 +230,9 @@ public class DWARFFunctionImporter { List frameBase = diea.getAsLocation(DWARFAttribute.DW_AT_frame_base); // get the framebase register, find where the frame is finally set // up. - frameLoc = getTopLocation(frameBase, function.address.getOffset()); + frameLoc = getTopLocation(frameBase, dfunc.address.getOffset()); if (frameLoc != null) { - function.frameBase = (int) diea.evaluateLocation(frameLoc); + dfunc.frameBase = (int) diea.evaluateLocation(frameLoc); } } @@ -245,26 +246,134 @@ public class DWARFFunctionImporter { // function passing a pointer to the callee function where the object is // then operated on. DIEAggregate typeRef = diea.getTypeRef(); - if (typeRef != null) { - function.retval = new DWARFVariable(); - function.retval.type = dwarfDTM.getDataType(typeRef, dwarfDTM.getVoidType()); - } + DataType formalReturnType = (typeRef != null) + ? dwarfDTM.getDataType(typeRef, DataType.DEFAULT) + : dwarfDTM.getVoidType(); + dfunc.retval = new DWARFVariable(); + dfunc.retval.type = formalReturnType; + + boolean formalParamsOnly = false; + boolean skipFuncSignature = false; + List formalParams = new ArrayList<>(); for (DebugInfoEntry childEntry : diea.getHeadFragment().getChildren( DWARFTag.DW_TAG_formal_parameter)) { DIEAggregate childDIEA = prog.getAggregate(childEntry); - DWARFVariable var = processVariable(childDIEA, function, null, -1); - if (var != null) { - function.params.add(var); + Parameter formalParam = createFormalParameter(childDIEA); + if (formalParam == null) { + skipFuncSignature = true; + break; + } + formalParams.add(formalParam); + + if (!formalParamsOnly) { + DWARFVariable var = processVariable(childDIEA, dfunc, null, -1); + if (var == null) { + // we had an error, can't rely on detailed param data, fallback to + // formal params + formalParamsOnly = true; + dfunc.params.clear(); + } + else { + dfunc.params.add(var); + } } } - function.varArg = + dfunc.varArg = !diea.getHeadFragment().getChildren(DWARFTag.DW_TAG_unspecified_parameters).isEmpty(); - processFuncChildren(diea, function); - outputFunction(function, diea); + processFuncChildren(diea, dfunc); + Function gfunc = createFunction(dfunc, diea); + if (gfunc != null) { + + if (formalParams.isEmpty() && dfunc.localVarErrors) { + // if there were no defined parameters and we had problems decoding local variables, + // don't force the method to have an empty param signature because there are other + // issues afoot. + skipFuncSignature = true; + } + + if (skipFuncSignature) { + Msg.error(this, + "Failed to get function signature information, leaving undefined: " + + gfunc.getName() + "@" + gfunc.getEntryPoint()); + Msg.debug(this, "DIE info: " + diea.toString()); + return; + } + + if (formalParamsOnly) { + updateFunctionSignatureWithFormalParams(gfunc, formalParams, + formalReturnType, dfunc.varArg, diea); + } + else { + updateFunctionSignatureWithDetailParams(gfunc, dfunc, diea); + } + } + + } + + private void updateFunctionSignatureWithFormalParams(Function gfunc, List params, + DataType returnType, boolean varArgs, DIEAggregate diea) { + try { + ReturnParameterImpl returnVar = new ReturnParameterImpl(returnType, currentProgram); + try { + gfunc.setVarArgs(varArgs); + gfunc.updateFunction(null, returnVar, params, + FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED); + } + catch (DuplicateNameException e) { + // try again after adjusting param names + setUniqueParameterNames(gfunc, params); + gfunc.updateFunction(null, returnVar, params, + FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED); + } + } + catch (InvalidInputException | DuplicateNameException e) { + Msg.error(this, + "Error updating function " + gfunc.getName() + " with formal params at " + + gfunc.getEntryPoint().toString() + ": " + e.getMessage()); + Msg.error(this, "DIE info: " + diea.toString()); + } + } + + private void updateFunctionSignatureWithDetailParams(Function gfunc, DWARFFunction dfunc, + DIEAggregate diea) { + try { + CompilerSpec compilerSpec = currentProgram.getCompilerSpec(); + PrototypeModel convention = null; + Variable returnVariable; + List params = new ArrayList<>(); + + returnVariable = buildReturnVariable(dfunc.retval); + for (int i = 0; i < dfunc.params.size(); ++i) { + Parameter curparam = buildParameter(gfunc, i, dfunc.params.get(i), diea); + params.add(curparam); + if (i == 0 && checkThisParameter(dfunc.params.get(0), diea)) { + convention = compilerSpec.matchConvention(GenericCallingConvention.thiscall); + } + } + + for (int i = 0; i < dfunc.local.size(); ++i) { + commitLocal(gfunc, dfunc.local.get(i)); + } + + if (dfunc.retval != null || params.size() > 0) { + // Add the function signature definition into the data type manager +// TODO: createFunctionDefinition(dfunc, infopath); + + // NOTE: Storage is computed above for the purpose of identifying + // a best fit calling convention. The commitPrototype method currently + // always employs dynamic storage. + commitPrototype(gfunc, returnVariable, params, convention); + gfunc.setVarArgs(dfunc.varArg); + } + } + catch (InvalidInputException | DuplicateNameException iie) { + Msg.error(this, "Error updating function " + dfunc.dni.getName() + " at " + + dfunc.address.toString() + ": " + iie.getMessage()); + } } private void processFuncChildren(DIEAggregate diea, DWARFFunction dfunc) @@ -305,6 +414,19 @@ public class DWARFFunctionImporter { } } + private Parameter createFormalParameter(DIEAggregate diea) { + String name = diea.getString(DWARFAttribute.DW_AT_name, null); + DataType dt = dwarfDTM.getDataType(diea.getTypeRef(), dwarfDTM.getVoidType()); + + try { + return new ParameterImpl(name, dt, currentProgram); + } + catch (InvalidInputException e) { + Msg.debug(this, "Failed to create parameter for " + diea.toString()); + } + return null; + } + /** * Creates a new {@link DWARFVariable} from the specified {@link DIEAggregate DIEA} and * as a child of the specified function (if not null). @@ -349,6 +471,9 @@ public class DWARFFunctionImporter { DWARFLocation topLocation = getTopLocation(locList, funcAddr); if (topLocation == null) { + if (dfunc != null) { + dfunc.localVarErrors = true; + } return null; } @@ -368,12 +493,18 @@ public class DWARFFunctionImporter { catch (DWARFExpressionException | UnsupportedOperationException | IndexOutOfBoundsException ex) { importSummary.exprReadError++; + if (dfunc != null) { + dfunc.localVarErrors = true; + } return null; } if (exprEvaluator.isDwarfStackValue()) { importSummary.varDWARFExpressionValue++; + if (dfunc != null) { + dfunc.localVarErrors = true; + } return null; } else if (exprEvaluator.useUnknownRegister() && exprEvaluator.isRegisterLocation()) { @@ -386,6 +517,9 @@ public class DWARFFunctionImporter { } else if (exprEvaluator.useUnknownRegister()) { importSummary.varDynamicRegisterError++; + if (dfunc != null) { + dfunc.localVarErrors = true; + } return null; } else if (exprEvaluator.isStackRelative()) { @@ -420,6 +554,9 @@ public class DWARFFunctionImporter { " can not fit into specified register " + dvar.reg.getName() + ", size=" + dvar.reg.getMinimumByteSize() + ", skipping. DWARF DIE: " + diea.getHexOffset()); + if (dfunc != null) { + dfunc.localVarErrors = true; + } return null; } @@ -430,6 +567,9 @@ public class DWARFFunctionImporter { // The DWARF register did not have a mapping to a Ghidra register, so // log it to be displayed in an error summary at end of import phase. importSummary.unknownRegistersEncountered.add(exprEvaluator.getRawLastRegister()); + if (dfunc != null) { + dfunc.localVarErrors = true; + } return null; } } @@ -1030,75 +1170,7 @@ public class DWARFFunctionImporter { return false; } - private void outputFunction(DWARFFunction dfunc, DIEAggregate diea) { - try { - Function function = createFunction(dfunc); - if (function == null) { - Msg.error(this, "DWARF DIE: " + diea.getHexOffset()); - return; - } - - DWARFSourceInfo sourceInfo = DWARFSourceInfo.create(diea); - if (sourceInfo != null) { - // Move the function into the program tree of the file - moveIntoFragment(function.getName(), dfunc.address, - dfunc.highAddress != null ? dfunc.highAddress : dfunc.address.add(1), - sourceInfo.getFilename()); - - if (importOptions.isOutputSourceLocationInfo()) { - appendComment(dfunc.address, CodeUnit.PLATE_COMMENT, - sourceInfo.getDescriptionStr(), "\n"); - } - } - if (importOptions.isOutputDIEInfo()) { - appendComment(dfunc.address, CodeUnit.PLATE_COMMENT, - "DWARF DIE: " + diea.getHexOffset(), "\n"); - } - - DWARFNameInfo dni = prog.getName(diea); - if (dni.isNameModified()) { - appendComment(dfunc.address, CodeUnit.PLATE_COMMENT, - "Original name: " + dni.getOriginalName(), "\n"); - } - - CompilerSpec compilerSpec = currentProgram.getCompilerSpec(); - PrototypeModel convention = null; - Variable returnVariable; - ArrayList params = new ArrayList<>(); - - // boolean specifyStorage = evaluateParameterStorage(dfunc); - - returnVariable = buildReturnVariable(dfunc.retval); - for (int i = 0; i < dfunc.params.size(); ++i) { - Parameter curparam = buildParameter(function, i, dfunc.params.get(i), diea); - params.add(curparam); - if (i == 0 && checkThisParameter(dfunc.params.get(0), diea)) { - convention = compilerSpec.matchConvention(GenericCallingConvention.thiscall); - } - } - - for (int i = 0; i < dfunc.local.size(); ++i) { - commitLocal(function, dfunc.local.get(i)); - } - - if (dfunc.retval != null || params.size() > 0) { - // Add the function signature definition into the data type manager -// TODO: createFunctionDefinition(dfunc, infopath); - - // NOTE: Storage is computed above for the purpose of identifying - // a best fit calling convention. The commitPrototype method currently - // always employs dynamic storage. - commitPrototype(function, returnVariable, params, convention); - function.setVarArgs(dfunc.varArg); - } - } - catch (InvalidInputException | DuplicateNameException iie) { - Msg.error(this, "Error updating function " + dfunc.dni.getName() + " at " + - dfunc.address.toString() + ": " + iie.getMessage()); - } - } - - private Function createFunction(DWARFFunction dfunc) { + private Function createFunction(DWARFFunction dfunc, DIEAggregate diea) { try { // create a new symbol if one does not exist (symbol table will figure this out) SymbolTable symbolTable = currentProgram.getSymbolTable(); @@ -1129,6 +1201,30 @@ public class DWARFFunctionImporter { function = currentProgram.getFunctionManager().createFunction(null, dfunc.address, new AddressSet(dfunc.address), SourceType.IMPORTED); } + + DWARFSourceInfo sourceInfo = DWARFSourceInfo.create(diea); + if (sourceInfo != null) { + // Move the function into the program tree of the file + moveIntoFragment(function.getName(), dfunc.address, + dfunc.highAddress != null ? dfunc.highAddress : dfunc.address.add(1), + sourceInfo.getFilename()); + + if (importOptions.isOutputSourceLocationInfo()) { + appendComment(dfunc.address, CodeUnit.PLATE_COMMENT, + sourceInfo.getDescriptionStr(), "\n"); + } + } + if (importOptions.isOutputDIEInfo()) { + appendComment(dfunc.address, CodeUnit.PLATE_COMMENT, + "DWARF DIE: " + diea.getHexOffset(), "\n"); + } + + DWARFNameInfo dni = prog.getName(diea); + if (dni.isNameModified()) { + appendComment(dfunc.address, CodeUnit.PLATE_COMMENT, + "Original name: " + dni.getOriginalName(), "\n"); + } + return function; } catch (OverlappingFunctionException e) { @@ -1152,14 +1248,14 @@ public class DWARFFunctionImporter { * @throws InvalidInputException invalid parameter name * @throws DuplicateNameException (should not occur on non-DB parameter) */ - private void setUniqueParameterNames(Function function, Parameter[] parameters) + private void setUniqueParameterNames(Function function, List parameters) throws DuplicateNameException, InvalidInputException { SymbolTable symbolTable = currentProgram.getSymbolTable(); // Create a set containing all the unique parameter names determined so far so they can // be avoided as additional parameter names are determined. Set namesSoFar = new HashSet<>(); - for (int ordinal = 0; ordinal < parameters.length; ordinal++) { - Parameter parameter = parameters[ordinal]; + for (int ordinal = 0; ordinal < parameters.size(); ordinal++) { + Parameter parameter = parameters.get(ordinal); String baseName = parameter.getName(); if (ordinal == 0 && Function.THIS_PARAM_NAME.equals(baseName)) { continue; @@ -1233,14 +1329,13 @@ public class DWARFFunctionImporter { } private void commitPrototype(Function function, Variable returnVariable, - ArrayList params, PrototypeModel protoModel) + List params, PrototypeModel protoModel) throws InvalidInputException, DuplicateNameException { - Parameter[] paramarray = new Parameter[params.size()]; - params.toArray(paramarray); CompilerSpec compilerSpec = currentProgram.getCompilerSpec(); if (protoModel == null) { + Parameter[] paramarray = params.toArray(Parameter[]::new); protoModel = compilerSpec.findBestCallingConvention(paramarray); } @@ -1249,7 +1344,7 @@ public class DWARFFunctionImporter { FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED); } catch (DuplicateNameException e) { - setUniqueParameterNames(function, paramarray); + setUniqueParameterNames(function, params); function.updateFunction(protoModel.getName(), returnVariable, params, FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED); } @@ -1361,9 +1456,10 @@ public class DWARFFunctionImporter { public DWARFVariable retval; public boolean isExternal; public long frameBase; - public ArrayList params = new ArrayList<>(); - public ArrayList local = new ArrayList<>(); + public List params = new ArrayList<>(); + public List local = new ArrayList<>(); public boolean varArg; + public boolean localVarErrors; // set to true if problem w/local var decoding public DWARFFunction(DWARFNameInfo dni) { this.dni = dni; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java index 6462efea8e..afd2246961 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java @@ -54,12 +54,8 @@ public class DWARFProgram implements Closeable { private static final int NAME_HASH_REPLACEMENT_SIZE = 8 + 2 + 2; private static final String ELLIPSES_STR = "..."; - public static boolean alreadyDWARFImported(Program prog) { - return DWARFFunctionImporter.hasDWARFProgModule(prog, DWARF_ROOT_NAME); - } - /** - * Returns true if the {@link Program program} probably DWARF information. + * Returns true if the {@link Program program} probably has DWARF information. *

    * If the program is an Elf binary, it must have (at least) ".debug_info" and ".debug_abbr" program sections. *

    @@ -67,17 +63,17 @@ public class DWARFProgram implements Closeable { * original binary file on the native filesystem. (ie. outside of Ghidra). See the DSymSectionProvider * for more info. *

    - * @param program - * @param monitor - * @return + * @param program {@link Program} to test + * @return boolean true if program has DWARF info, false if not */ - public static boolean isDWARF(Program program, TaskMonitor monitor) { + public static boolean isDWARF(Program program) { String format = program.getExecutableFormat(); - if (ElfLoader.ELF_NAME.equals(format)) { + if (ElfLoader.ELF_NAME.equals(format) && + DWARFSectionProviderFactory.createSectionProviderFor(program) != null) { return true; } - else if (MachoLoader.MACH_O_NAME.equals(format) && + if (MachoLoader.MACH_O_NAME.equals(format) && DSymSectionProvider.getDSYMForProgram(program) != null) { return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProviderFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProviderFactory.java index 7ea6cdce59..7cb75f4a24 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProviderFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/DWARFSectionProviderFactory.java @@ -15,14 +15,14 @@ */ package ghidra.app.util.bin.format.dwarf4.next.sectionprovider; -import ghidra.program.model.listing.Program; -import ghidra.util.Msg; - import java.io.Closeable; import java.util.ArrayList; import java.util.List; import java.util.function.Function; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; + /** * Auto-detects which {@link DWARFSectionProvider} matches a Ghidra program. */ @@ -44,7 +44,6 @@ public class DWARFSectionProviderFactory { static { sectionProviderFactoryFuncs.add(BaseSectionProvider::createSectionProviderFor); sectionProviderFactoryFuncs.add(DSymSectionProvider::createSectionProviderFor); - sectionProviderFactoryFuncs.add(ElfSectionProvider::createSectionProviderFor); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/ElfSectionProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/ElfSectionProvider.java deleted file mode 100644 index 4eb692cabc..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/sectionprovider/ElfSectionProvider.java +++ /dev/null @@ -1,95 +0,0 @@ -/* ### - * 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.util.bin.format.dwarf4.next.sectionprovider; - -import ghidra.app.util.bin.*; -import ghidra.app.util.bin.format.elf.*; -import ghidra.app.util.opinion.ElfLoader; -import ghidra.program.model.listing.Program; - -import java.io.File; -import java.io.IOException; - -import generic.continues.RethrowContinuesFactory; - -/** - * Fetches DWARF section data from ELF files, directly, without going through - * the Ghidra memory block api. This section provider usually isn't needed as - * ELF sections are normally provided as Ghidra memory blocks. In case of extra- - * large binaries, Ghidra may not be able to map the debug sections into memory - * and this section provider will allow the DWARF analyzer to still function. - */ -public class ElfSectionProvider implements DWARFSectionProvider { - - private ElfHeader header; - private RandomAccessByteProvider provider; - - public static ElfSectionProvider createSectionProviderFor(Program program) { - if (ElfLoader.ELF_NAME.equals(program.getExecutableFormat())) { - try { - File exePath = new File(program.getExecutablePath()); - return new ElfSectionProvider(exePath); - } - catch (IOException ioe) { - // ignore - } - } - return null; - } - - public ElfSectionProvider(File exeFile) throws IOException { - provider = new RandomAccessByteProvider(exeFile); - try { - // Parse the ELF header to get the sections - header = ElfHeader.createElfHeader(RethrowContinuesFactory.INSTANCE, provider); - header.parse(); - } - catch (ElfException e) { - provider.close(); - throw new IOException("Error parsing ELF", e); - } - } - - @Override - public ByteProvider getSectionAsByteProvider(String sectionName) throws IOException { - - ElfSectionHeader section = header.getSection("." + sectionName); - - return (section != null) ? new ByteProviderWrapper(section.getReader().getByteProvider(), - section.getOffset(), section.getSize()) : null; - } - - @Override - public void close() { - try { - provider.close(); - } - catch (IOException e) { - // ignore - } - } - - @Override - public boolean hasSection(String... sectionNames) { - for (String sectionName : sectionNames) { - if (header.getSection("." + sectionName) == null) { - return false; - } - } - return true; - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSymbol.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSymbol.java index 297dedfd3b..170c317d97 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSymbol.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSymbol.java @@ -103,11 +103,24 @@ public class ElfSymbol implements ByteArrayConverter { private String nameAsString; + /** + * create an ElfSymbol() + * Warning! the routine initSymbolName() must be called on the symbol later + * to initialize the string name. This is a performance enhancement. + * + * @param reader to read symbol from + * @param symbolIndex index of the symbol to read + * @param symbolTable symbol table to associate the symbol to + * @param stringTable string table to read symbols from + * @param header else header + * @return newly created ElfSymbol + * + * @throws IOException if an issue with reading occurs + */ public static ElfSymbol createElfSymbol(FactoryBundledWithBinaryReader reader, int symbolIndex, - ElfSymbolTable symbolTable, ElfStringTable stringTable, ElfHeader header) - throws IOException { + ElfSymbolTable symbolTable, ElfHeader header) throws IOException { ElfSymbol elfSymbol = (ElfSymbol) reader.getFactory().create(ElfSymbol.class); - elfSymbol.initElfSymbol(reader, symbolIndex, symbolTable, stringTable, header); + elfSymbol.initElfSymbol(reader, symbolIndex, symbolTable, header); return elfSymbol; } @@ -117,49 +130,6 @@ public class ElfSymbol implements ByteArrayConverter { public ElfSymbol() { } - private void initElfSymbol(FactoryBundledWithBinaryReader reader, int symbolIndex, - ElfSymbolTable symbolTable, ElfStringTable stringTable, ElfHeader header) - throws IOException { - this.header = header; - this.symbolTable = symbolTable; - this.symbolTableIndex = symbolIndex; - - if (header.is32Bit()) { - st_name = reader.readNextInt(); - st_value = reader.readNextInt() & Conv.INT_MASK; - st_size = reader.readNextInt() & Conv.INT_MASK; - st_info = reader.readNextByte(); - st_other = reader.readNextByte(); - st_shndx = reader.readNextShort(); - } - else { - st_name = reader.readNextInt(); - st_info = reader.readNextByte(); - st_other = reader.readNextByte(); - st_shndx = reader.readNextShort(); - st_value = reader.readNextLong(); - st_size = reader.readNextLong(); - } - - if (st_name == 0) { - if (getType() == STT_SECTION) { - ElfSectionHeader[] sections = header.getSections(); - if (st_shndx < 0 || st_shndx >= sections.length) { - //invalid section reference... - //this is a bug in objcopy, whereby sections are removed - //but the corresponding section symbols are left behind. - } - else { - ElfSectionHeader section = sections[st_shndx]; - nameAsString = section.getNameAsString(); - } - } - } - else { - nameAsString = stringTable.readString(reader, st_name); - } - } - /** * Creates a new section symbol. * @param header the corresponding ELF header @@ -209,6 +179,69 @@ public class ElfSymbol implements ByteArrayConverter { this.symbolTableIndex = symbolIndex; } + private void initElfSymbol(FactoryBundledWithBinaryReader reader, int symbolIndex, + ElfSymbolTable symbolTable, ElfHeader header) throws IOException { + this.header = header; + this.symbolTable = symbolTable; + this.symbolTableIndex = symbolIndex; + + if (header.is32Bit()) { + st_name = reader.readNextInt(); + st_value = reader.readNextInt() & Conv.INT_MASK; + st_size = reader.readNextInt() & Conv.INT_MASK; + st_info = reader.readNextByte(); + st_other = reader.readNextByte(); + st_shndx = reader.readNextShort(); + } + else { + st_name = reader.readNextInt(); + st_info = reader.readNextByte(); + st_other = reader.readNextByte(); + st_shndx = reader.readNextShort(); + st_value = reader.readNextLong(); + st_size = reader.readNextLong(); + } + + if (st_name == 0) { + if (getType() == STT_SECTION) { + ElfSectionHeader[] sections = header.getSections(); + if (st_shndx < 0 || st_shndx >= sections.length) { + //invalid section reference... + //this is a bug in objcopy, whereby sections are removed + //but the corresponding section symbols are left behind. + } + else { + ElfSectionHeader section = sections[st_shndx]; + nameAsString = section.getNameAsString(); + } + } + } + else { + // The string name will be initialized later + // in a call to initSymbolName() + } + } + + /** + * Initialize the string name of the symbol. + * + * NOTE: This routine MUST be called for each + * ELFSymbol after the elf symbols have been created. + * + * This is done separately from the initial symbol entry read because + * the string names are in a separate location. If they are read + * at the same time the reading buffer will jump around and significantly + * degrade reading performance. + * + * @param reader to read from + * @param stringTable stringTable to initialize symbol name + */ + public void initSymbolName(FactoryBundledWithBinaryReader reader, ElfStringTable stringTable) { + if (nameAsString == null) { + nameAsString = stringTable.readString(reader, st_name); + } + } + /** * Get the symbol table containing this symbol * @return symbol table @@ -249,27 +282,37 @@ public class ElfSymbol implements ByteArrayConverter { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } ElfSymbol other = (ElfSymbol) obj; - if (st_info != other.st_info) + if (st_info != other.st_info) { return false; - if (st_name != other.st_name) + } + if (st_name != other.st_name) { return false; - if (st_other != other.st_other) + } + if (st_other != other.st_other) { return false; - if (st_shndx != other.st_shndx) + } + if (st_shndx != other.st_shndx) { return false; - if (st_size != other.st_size) + } + if (st_size != other.st_size) { return false; - if (st_value != other.st_value) + } + if (st_value != other.st_value) { return false; - if (symbolTableIndex != other.symbolTableIndex) + } + if (symbolTableIndex != other.symbolTableIndex) { return false; + } return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSymbolTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSymbolTable.java index 09eea496c9..fcb4631af9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSymbolTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSymbolTable.java @@ -18,6 +18,7 @@ package ghidra.app.util.bin.format.elf; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import ghidra.app.util.bin.ByteArrayConverter; import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader; @@ -96,15 +97,26 @@ public class ElfSymbolTable implements ElfFileSection, ByteArrayConverter { long entryPos = reader.getPointerIndex(); + // load the all the symbol entries first, don't initialize the string name + // that will be done later to help localize memory access for (int i = 0; i < symbolCount; i++) { // Reposition reader to start of symbol element since ElfSymbol object // may not consume all symbol element data reader.setPointerIndex(entryPos); - ElfSymbol sym = ElfSymbol.createElfSymbol(reader, i, this, stringTable, header); + ElfSymbol sym = ElfSymbol.createElfSymbol(reader, i, this, header); symbolList.add(sym); entryPos += entrySize; } + // sort the entries by the index in the string table, so don't jump around reading + List sortedList = symbolList.stream().sorted( + (o1, o2) -> Integer.compare(o1.getName(), o2.getName())).collect(Collectors.toList()); + + // initialize the Symbol string names from string table + for (ElfSymbol sym : sortedList) { + sym.initSymbolName(reader, stringTable); + } + reader.setPointerIndex(ptr); symbols = new ElfSymbol[symbolList.size()]; @@ -163,9 +175,9 @@ public class ElfSymbolTable implements ElfFileSection, ByteArrayConverter { * @return the symbol at the specified address */ public ElfSymbol getSymbolAt(long addr) { - for (int i = 0; i < symbols.length; i++) { - if (symbols[i].getValue() == addr) { - return symbols[i]; + for (ElfSymbol symbol : symbols) { + if (symbol.getValue() == addr) { + return symbol; } } return null; @@ -177,9 +189,9 @@ public class ElfSymbolTable implements ElfFileSection, ByteArrayConverter { */ public ElfSymbol[] getGlobalSymbols() { List list = new ArrayList<>(); - for (int i = 0; i < symbols.length; i++) { - if (symbols[i].getBind() == ElfSymbol.STB_GLOBAL) { - list.add(symbols[i]); + for (ElfSymbol symbol : symbols) { + if (symbol.getBind() == ElfSymbol.STB_GLOBAL) { + list.add(symbol); } } ElfSymbol[] array = new ElfSymbol[list.size()]; @@ -193,11 +205,11 @@ public class ElfSymbolTable implements ElfFileSection, ByteArrayConverter { */ public String[] getSourceFiles() { List list = new ArrayList<>(); - for (int j = 0; j < symbols.length; j++) { - if (symbols[j].getType() == ElfSymbol.STT_FILE) { - String name = symbols[j].getNameAsString(); + for (ElfSymbol symbol : symbols) { + if (symbol.getType() == ElfSymbol.STT_FILE) { + String name = symbol.getNameAsString(); if (name != null) { - list.add(symbols[j].getNameAsString()); + list.add(symbol.getNameAsString()); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/BuildVersionCommand.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/BuildVersionCommand.java index 45d8b4b115..fa918132d3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/BuildVersionCommand.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/BuildVersionCommand.java @@ -80,8 +80,10 @@ public class BuildVersionCommand extends LoadCommand { struct.add(DWORD, "minos", null); struct.add(DWORD, "sdk", null); struct.add(DWORD, "ntools", null); - struct.add(new ArrayDataType(buildToolVersionDataType, ntools, - buildToolVersionDataType.getLength()), "build_tool_version[]", null); + if (ntools > 0) { + struct.add(new ArrayDataType(buildToolVersionDataType, ntools, + buildToolVersionDataType.getLength()), "build_tool_version[]", null); + } struct.setCategoryPath(new CategoryPath(MachConstants.DATA_TYPE_CATEGORY)); return struct; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/DelayImportDataDirectory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/DelayImportDataDirectory.java index 24c8a3d42a..376604e513 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/DelayImportDataDirectory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/DelayImportDataDirectory.java @@ -136,13 +136,13 @@ public class DelayImportDataDirectory extends DataDirectory { createSymbol(program, tmpAddr, SymbolUtilities.getAddressAppendedName( DelayImportDescriptor.NAME + "_IAT", tmpAddr)); markupThunk(program, isBinary, space, descriptor, descriptor.getAddressOfIAT(), - descriptor.getThunksIAT(), monitor, log); + descriptor.getThunksIAT(), true, monitor, log); tmpAddr = addr(space, isBinary, descriptor, descriptor.getAddressOfINT()); createSymbol(program, tmpAddr, SymbolUtilities.getAddressAppendedName( DelayImportDescriptor.NAME + "_INT", tmpAddr)); markupThunk(program, isBinary, space, descriptor, descriptor.getAddressOfINT(), - descriptor.getThunksINT(), monitor, log); + descriptor.getThunksINT(), false, monitor, log); // This table is optional if (descriptor.getAddressOfBoundIAT() != 0) { @@ -150,7 +150,7 @@ public class DelayImportDataDirectory extends DataDirectory { createSymbol(program, tmpAddr, SymbolUtilities.getAddressAppendedName( DelayImportDescriptor.NAME + "_Bound_IAT", tmpAddr)); markupThunk(program, isBinary, space, descriptor, descriptor.getAddressOfBoundIAT(), - descriptor.getThunksBoundIAT(), monitor, log); + descriptor.getThunksBoundIAT(), false, monitor, log); } // This table is optional @@ -159,8 +159,8 @@ public class DelayImportDataDirectory extends DataDirectory { createSymbol(program, tmpAddr, SymbolUtilities.getAddressAppendedName( DelayImportDescriptor.NAME + "_Unload_IAT", tmpAddr)); markupThunk(program, isBinary, space, descriptor, - descriptor.getAddressOfOriginalIAT(), descriptor.getThunksUnloadIAT(), monitor, - log); + descriptor.getAddressOfOriginalIAT(), descriptor.getThunksUnloadIAT(), false, + monitor, log); } @@ -224,9 +224,11 @@ public class DelayImportDataDirectory extends DataDirectory { DelayImportDescriptor descriptor, long ptr, List thunks, + boolean isIAT, TaskMonitor monitor, MessageLog log) { + boolean is64bit = ntHeader.getOptionalHeader().is64bit(); long thunkPtr = va(ptr, isBinary); if (!descriptor.isUsingRVA()) { thunkPtr -= ntHeader.getOptionalHeader().getImageBase(); @@ -237,12 +239,14 @@ public class DelayImportDataDirectory extends DataDirectory { return; } DataType dt; - if (thunk.getAddressOfData() == 0) { - dt = ntHeader.getOptionalHeader().is64bit() ? QWORD : DWORD; + if (thunk.isOrdinal() || thunk.getAddressOfData() == 0) { + dt = is64bit ? QWORD : DWORD; + } + else if (isIAT) { + dt = is64bit ? Pointer64DataType.dataType : Pointer32DataType.dataType; } else { - dt = ntHeader.getOptionalHeader().is64bit() ? Pointer64DataType.dataType - : Pointer32DataType.dataType; + dt = is64bit ? IBO64 : IBO32; } Address thunkAddress = space.getAddress(thunkPtr); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/FileHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/FileHeader.java index 2a9cfebdd2..7d1dd90681 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/FileHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/FileHeader.java @@ -22,6 +22,7 @@ import java.util.List; import ghidra.app.util.bin.StructConverter; import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader; +import ghidra.app.util.bin.format.pe.ImageRuntimeFunctionEntries._IMAGE_RUNTIME_FUNCTION_ENTRY; import ghidra.app.util.bin.format.pe.debug.DebugCOFFSymbol; import ghidra.app.util.bin.format.pe.debug.DebugCOFFSymbolAux; import ghidra.program.model.data.*; @@ -45,7 +46,7 @@ import ghidra.util.exception.DuplicateNameException; * WORD Characteristics; // MANDATORY * } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; * - * + * */ public class FileHeader implements StructConverter { /** @@ -55,128 +56,131 @@ public class FileHeader implements StructConverter { /** * The size of the IMAGE_FILE_HEADER in bytes. */ - public final static int IMAGE_SIZEOF_FILE_HEADER = 20; + public final static int IMAGE_SIZEOF_FILE_HEADER = 20; /** * Relocation info stripped from file. */ - public final static int IMAGE_FILE_RELOCS_STRIPPED = 0x0001; - /** - * File is executable (no unresolved externel references). - */ - public final static int IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002; - /** - * Line nunbers stripped from file. - */ - public final static int IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004; - /** - * Local symbols stripped from file. - */ - public final static int IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008; - /** - * Agressively trim working set - */ - public final static int IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010; - /** - * App can handle >2gb addresses - */ - public final static int IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020; - /** - * Bytes of machine word are reversed. - */ - public final static int IMAGE_FILE_BYTES_REVERSED_LO = 0x0080; - /** - * 32 bit word machine. - */ - public final static int IMAGE_FILE_32BIT_MACHINE = 0x0100; - /** - * Debugging info stripped from file in .DBG file - */ - public final static int IMAGE_FILE_DEBUG_STRIPPED = 0x0200; - /** - * If Image is on removable media, copy and run from the swap file. - */ - public final static int IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400; - /** - * If Image is on Net, copy and run from the swap file. - */ - public final static int IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800; - /** - * System File. - */ - public final static int IMAGE_FILE_SYSTEM = 0x1000; - /** - * File is a DLL. - */ - public final static int IMAGE_FILE_DLL = 0x2000; - /** - * File should only be run on a UP machine - */ - public final static int IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000; - /** - * Bytes of machine word are reversed. - */ - public final static int IMAGE_FILE_BYTES_REVERSED_HI = 0x8000; + public final static int IMAGE_FILE_RELOCS_STRIPPED = 0x0001; + /** + * File is executable (no unresolved externel references). + */ + public final static int IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002; + /** + * Line nunbers stripped from file. + */ + public final static int IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004; + /** + * Local symbols stripped from file. + */ + public final static int IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008; + /** + * Agressively trim working set + */ + public final static int IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010; + /** + * App can handle >2gb addresses + */ + public final static int IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020; + /** + * Bytes of machine word are reversed. + */ + public final static int IMAGE_FILE_BYTES_REVERSED_LO = 0x0080; + /** + * 32 bit word machine. + */ + public final static int IMAGE_FILE_32BIT_MACHINE = 0x0100; + /** + * Debugging info stripped from file in .DBG file + */ + public final static int IMAGE_FILE_DEBUG_STRIPPED = 0x0200; + /** + * If Image is on removable media, copy and run from the swap file. + */ + public final static int IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400; + /** + * If Image is on Net, copy and run from the swap file. + */ + public final static int IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800; + /** + * System File. + */ + public final static int IMAGE_FILE_SYSTEM = 0x1000; + /** + * File is a DLL. + */ + public final static int IMAGE_FILE_DLL = 0x2000; + /** + * File should only be run on a UP machine. + */ + public final static int IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000; + /** + * Bytes of machine word are reversed. + */ + public final static int IMAGE_FILE_BYTES_REVERSED_HI = 0x8000; - public final static String [] CHARACTERISTICS = { - "Relocation info stripped from file", - "File is executable (i.e. no unresolved externel references)", - "Line nunbers stripped from file", - "Local symbols stripped from file", - "Agressively trim working set", - "App can handle >2gb addresses", - "Bytes of machine word are reversed", - "32 bit word machine", - "Debugging info stripped from file in .DBG file", - "If Image is on removable media, copy and run from the swap file", - "If Image is on Net, copy and run from the swap file", - "System file", - "File is a DLL", - "File should only be run on a UP machine", - "Bytes of machine word are reversed" - }; + /** + * Magic value in LordPE's Symbol Table pointer field. + */ + private final static int LORDPE_SYMBOL_TABLE = 0x726F4C5B; + /** + * Magic value in LordPE's Number of Symbols field. + */ + private final static int LORDPE_NUMBER_OF_SYMBOLS = 0x5D455064; - private short machine; - private short numberOfSections; - private int timeDateStamp; - private int pointerToSymbolTable; - private int numberOfSymbols; - private short sizeOfOptionalHeader; // delta between start of OptionalHeader and start of section table - private short characteristics; + public final static String[] CHARACTERISTICS = { "Relocation info stripped from file", + "File is executable (i.e. no unresolved externel references)", + "Line nunbers stripped from file", "Local symbols stripped from file", + "Agressively trim working set", "App can handle >2gb addresses", + "Bytes of machine word are reversed", "32 bit word machine", + "Debugging info stripped from file in .DBG file", + "If Image is on removable media, copy and run from the swap file", + "If Image is on Net, copy and run from the swap file", "System file", "File is a DLL", + "File should only be run on a UP machine", "Bytes of machine word are reversed" }; - private SectionHeader [] sectionHeaders; - private Listsymbols = new ArrayList<>(); + private short machine; + private short numberOfSections; + private int timeDateStamp; + private int pointerToSymbolTable; + private int numberOfSymbols; + private short sizeOfOptionalHeader; // delta between start of OptionalHeader and start of section table + private short characteristics; - private FactoryBundledWithBinaryReader reader; - private int startIndex; - private NTHeader ntHeader; + private SectionHeader[] sectionHeaders; + private List symbols = new ArrayList<>(); + private List<_IMAGE_RUNTIME_FUNCTION_ENTRY> irfes = new ArrayList<>(); - static FileHeader createFileHeader( - FactoryBundledWithBinaryReader reader, int startIndex, - NTHeader ntHeader) throws IOException { - FileHeader fileHeader = (FileHeader) reader.getFactory().create(FileHeader.class); - fileHeader.initFileHeader(reader, startIndex, ntHeader); - return fileHeader; - } + private FactoryBundledWithBinaryReader reader; + private int startIndex; + private NTHeader ntHeader; - /** - * DO NOT USE THIS CONSTRUCTOR, USE create*(GenericFactory ...) FACTORY METHODS INSTEAD. - */ - public FileHeader() {} + static FileHeader createFileHeader(FactoryBundledWithBinaryReader reader, int startIndex, + NTHeader ntHeader) throws IOException { + FileHeader fileHeader = (FileHeader) reader.getFactory().create(FileHeader.class); + fileHeader.initFileHeader(reader, startIndex, ntHeader); + return fileHeader; + } - private void initFileHeader(FactoryBundledWithBinaryReader reader, int startIndex, NTHeader ntHeader) throws IOException { - this.reader = reader; - this.startIndex = startIndex; - this.ntHeader = ntHeader; + /** + * DO NOT USE THIS CONSTRUCTOR, USE create*(GenericFactory ...) FACTORY METHODS INSTEAD. + */ + public FileHeader() { + } + + private void initFileHeader(FactoryBundledWithBinaryReader reader, int startIndex, + NTHeader ntHeader) throws IOException { + this.reader = reader; + this.startIndex = startIndex; + this.ntHeader = ntHeader; + + parse(); + } - parse(); - } - /** * Returns the architecture type of the computer. * @return the architecture type of the computer */ - public short getMachine() { + public short getMachine() { return machine; } @@ -184,131 +188,137 @@ public class FileHeader implements StructConverter { * Returns a string representation of the architecture type of the computer. * @return a string representation of the architecture type of the computer */ - public String getMachineName() { - return MachineName.getName(machine); - } - + public String getMachineName() { + return MachineName.getName(machine); + } + /** - * Returns the number of sections. + * Returns the number of sections. * Sections equate to Ghidra memory blocks. * @return the number of sections */ - public int getNumberOfSections() { - return numberOfSections; - } + public int getNumberOfSections() { + return numberOfSections; + } /** * Returns the array of section headers. * @return the array of section headers */ - public SectionHeader [] getSectionHeaders() { - if (sectionHeaders == null) { - return new SectionHeader[0]; - } - return sectionHeaders; - } + public SectionHeader[] getSectionHeaders() { + if (sectionHeaders == null) { + return new SectionHeader[0]; + } + return sectionHeaders; + } /** * Returns the array of symbols. * @return the array of symbols */ - public List getSymbols() { + public List getSymbols() { return symbols; } + public List<_IMAGE_RUNTIME_FUNCTION_ENTRY> getImageRuntimeFunctionEntries() { + return irfes; + } + /** * Returns the section header that contains the specified virtual address. * @param virtualAddr the virtual address * @return the section header that contains the specified virtual address */ - public SectionHeader getSectionHeaderContaining(int virtualAddr) { - for (SectionHeader sectionHeader : sectionHeaders) { - int start = sectionHeader.getVirtualAddress(); - int end = sectionHeader.getVirtualAddress()+sectionHeader.getVirtualSize()-1; - if (virtualAddr >= start && virtualAddr <= end) { - return sectionHeader; - } - } - return null; - } + public SectionHeader getSectionHeaderContaining(int virtualAddr) { + for (SectionHeader sectionHeader : sectionHeaders) { + int start = sectionHeader.getVirtualAddress(); + int end = sectionHeader.getVirtualAddress() + sectionHeader.getVirtualSize() - 1; + if (virtualAddr >= start && virtualAddr <= end) { + return sectionHeader; + } + } + return null; + } /** * Returns the section header at the specified position in the array. * @param index index of section header to return * @return the section header at the specified position in the array, or null if invalid */ - public SectionHeader getSectionHeader(int index) { + public SectionHeader getSectionHeader(int index) { if (index >= 0 && index < sectionHeaders.length) { - return sectionHeaders[index]; - } - return null; - } + return sectionHeaders[index]; + } + return null; + } /** * Returns the time stamp of the image. * @return the time stamp of the image */ - public int getTimeDateStamp() { - return timeDateStamp; - } + public int getTimeDateStamp() { + return timeDateStamp; + } /** * Returns the file offset of the COFF symbol table * @return the file offset of the COFF symbol table */ - public int getPointerToSymbolTable() { - return pointerToSymbolTable; - } + public int getPointerToSymbolTable() { + return pointerToSymbolTable; + } /** * Returns the number of symbols in the COFF symbol table * @return the number of symbols in the COFF symbol table */ - public int getNumberOfSymbols() { - return numberOfSymbols; - } + public int getNumberOfSymbols() { + return numberOfSymbols; + } /** * Returns the size of the optional header data * @return the size of the optional header, in bytes */ - public int getSizeOfOptionalHeader() { + public int getSizeOfOptionalHeader() { return sizeOfOptionalHeader; } /** - * Returns a set of bit flags indicating attributes of the file. + * Returns a set of bit flags indicating attributes of the file. * @return a set of bit flags indicating attributes */ - public int getCharacteristics() { - return characteristics; - } + public int getCharacteristics() { + return characteristics; + } /** * Returns the file pointer to the section headers. * @return the file pointer to the section headers */ - public int getPointerToSections() { - short sizeOptHdr = ntHeader.getFileHeader().sizeOfOptionalHeader; + public int getPointerToSections() { + short sizeOptHdr = ntHeader.getFileHeader().sizeOfOptionalHeader; int ptrToSections = startIndex + IMAGE_SIZEOF_FILE_HEADER + sizeOptHdr; - int testSize = ntHeader.getOptionalHeader().is64bit() - ? Constants.IMAGE_SIZEOF_NT_OPTIONAL64_HEADER - : Constants.IMAGE_SIZEOF_NT_OPTIONAL32_HEADER; - if (sizeOptHdr != testSize) { + int testSize = + ntHeader.getOptionalHeader().is64bit() ? Constants.IMAGE_SIZEOF_NT_OPTIONAL64_HEADER + : Constants.IMAGE_SIZEOF_NT_OPTIONAL32_HEADER; + if (sizeOptHdr != testSize) { Msg.warn(this, "Non-standard optional header size: " + sizeOptHdr + " bytes"); - } + } return ptrToSections; - } + } - void processSections(OptionalHeader optHeader) throws IOException { - long oldIndex = reader.getPointerIndex(); + void processSections(OptionalHeader optHeader) throws IOException { + long oldIndex = reader.getPointerIndex(); - int tmpIndex = getPointerToSections(); - if (numberOfSections < 0) { - Msg.error(this, "Number of sections = "+numberOfSections); - } else if (optHeader.getFileAlignment() == 0) { - Msg.error(this, "File alignment == 0: section processing skipped"); - } else { + int tmpIndex = getPointerToSections(); + if (numberOfSections < 0) { + Msg.error(this, "Number of sections = " + numberOfSections); + } + else if (optHeader.getFileAlignment() == 0) { + Msg.error(this, "File alignment == 0: section processing skipped"); + } + else { sectionHeaders = new SectionHeader[numberOfSections]; for (int i = 0; i < numberOfSections; ++i) { sectionHeaders[i] = SectionHeader.createSectionHeader(reader, tmpIndex); @@ -330,8 +340,8 @@ public class FileHeader implements StructConverter { optHeader.getSectionAlignment()); if (virtualAddress == alignedVirtualAddress) { if (sizeOfRawData > virtualSize) { - sectionHeaders[i].setVirtualSize( - Math.min(sizeOfRawData, alignedVirtualSize)); + sectionHeaders[i] + .setVirtualSize(Math.min(sizeOfRawData, alignedVirtualSize)); } } else { @@ -341,68 +351,101 @@ public class FileHeader implements StructConverter { } } - reader.setPointerIndex(oldIndex); - } + reader.setPointerIndex(oldIndex); + } - void processSymbols() throws IOException { - if (isLordPE()) { - return; - } + void processImageRuntimeFunctionEntries() throws IOException { + FileHeader fh = ntHeader.getFileHeader(); + SectionHeader[] sections = fh.getSectionHeaders(); - long oldIndex = reader.getPointerIndex(); + // Look for an exception handler section for an array of + // RUNTIME_FUNCTION structures, bail if one isn't found + SectionHeader irfeHeader = null; + for (SectionHeader header : sections) { + if (header.getName().equals(".pdata")) { + irfeHeader = header; + break; + } + } - int tmpIndex = getPointerToSymbolTable(); - if (!ntHeader.checkRVA(tmpIndex)) { - Msg.error(this, "Invalid file index "+Integer.toHexString(tmpIndex)); - return; - } + if (irfeHeader == null) { + return; + } - if ( numberOfSymbols < 0 || numberOfSymbols > reader.length()) { - Msg.error(this, "Invalid symbol count "+Integer.toHexString(numberOfSymbols)); - return; - } + long oldIndex = reader.getPointerIndex(); - int stringTableIndex = tmpIndex + DebugCOFFSymbol.IMAGE_SIZEOF_SYMBOL * numberOfSymbols; - - for (int i = 0; i < numberOfSymbols; ++i) { - if (!ntHeader.checkRVA(tmpIndex)) { - Msg.error(this, "Invalid file index "+Integer.toHexString(tmpIndex)); - break; - } + int start = irfeHeader.getPointerToRawData(); + reader.setPointerIndex(start); - DebugCOFFSymbol symbol = DebugCOFFSymbol.createDebugCOFFSymbol(reader, tmpIndex, stringTableIndex); + ImageRuntimeFunctionEntries entries = + ImageRuntimeFunctionEntries.createImageRuntimeFunctionEntries(reader, start, ntHeader); + irfes = entries.getRuntimeFunctionEntries(); - tmpIndex += DebugCOFFSymbol.IMAGE_SIZEOF_SYMBOL; + reader.setPointerIndex(oldIndex); + } - tmpIndex += (DebugCOFFSymbolAux.IMAGE_SIZEOF_AUX_SYMBOL * symbol.getNumberOfAuxSymbols()); + void processSymbols() throws IOException { + if (isLordPE()) { + return; + } - int numberOfAuxSymbols = symbol.getNumberOfAuxSymbols(); + long oldIndex = reader.getPointerIndex(); + + int tmpIndex = getPointerToSymbolTable(); + if (!ntHeader.checkRVA(tmpIndex)) { + Msg.error(this, "Invalid file index " + Integer.toHexString(tmpIndex)); + return; + } + + if (numberOfSymbols < 0 || numberOfSymbols > reader.length()) { + Msg.error(this, "Invalid symbol count " + Integer.toHexString(numberOfSymbols)); + return; + } + + int stringTableIndex = tmpIndex + DebugCOFFSymbol.IMAGE_SIZEOF_SYMBOL * numberOfSymbols; + + for (int i = 0; i < numberOfSymbols; ++i) { + if (!ntHeader.checkRVA(tmpIndex)) { + Msg.error(this, "Invalid file index " + Integer.toHexString(tmpIndex)); + break; + } + + DebugCOFFSymbol symbol = + DebugCOFFSymbol.createDebugCOFFSymbol(reader, tmpIndex, stringTableIndex); + + tmpIndex += DebugCOFFSymbol.IMAGE_SIZEOF_SYMBOL; + + tmpIndex += + (DebugCOFFSymbolAux.IMAGE_SIZEOF_AUX_SYMBOL * symbol.getNumberOfAuxSymbols()); + + int numberOfAuxSymbols = symbol.getNumberOfAuxSymbols(); i += numberOfAuxSymbols > 0 ? numberOfAuxSymbols : 0; - symbols.add( symbol ); - } + symbols.add(symbol); + } - reader.setPointerIndex(oldIndex); - } + reader.setPointerIndex(oldIndex); + } - public boolean isLordPE() { - if (getPointerToSymbolTable() == 0x726F4C5B && getNumberOfSymbols() == 0x5D455064) { - return true; - } - return false; - } + public boolean isLordPE() { + if (getPointerToSymbolTable() == LORDPE_SYMBOL_TABLE && + getNumberOfSymbols() == LORDPE_NUMBER_OF_SYMBOLS) { + return true; + } + return false; + } - private void parse() throws IOException { - reader.setPointerIndex(startIndex); + private void parse() throws IOException { + reader.setPointerIndex(startIndex); - machine = reader.readNextShort(); - numberOfSections = reader.readNextShort(); - timeDateStamp = reader.readNextInt (); - pointerToSymbolTable = reader.readNextInt (); - numberOfSymbols = reader.readNextInt (); - sizeOfOptionalHeader = reader.readNextShort(); - characteristics = reader.readNextShort(); - } + machine = reader.readNextShort(); + numberOfSections = reader.readNextShort(); + timeDateStamp = reader.readNextInt(); + pointerToSymbolTable = reader.readNextInt(); + numberOfSymbols = reader.readNextInt(); + sizeOfOptionalHeader = reader.readNextShort(); + characteristics = reader.readNextShort(); + } /** * @see ghidra.app.util.bin.StructConverter#toDataType() @@ -411,22 +454,22 @@ public class FileHeader implements StructConverter { public DataType toDataType() throws DuplicateNameException { StructureDataType struct = new StructureDataType(NAME, 0); - struct.add(WORD,2,"Machine",getMachineName()); - struct.add(WORD,2,"NumberOfSections",null); - struct.add(DWORD,4,"TimeDateStamp",null); - struct.add(DWORD,4,"PointerToSymbolTable",null); - struct.add(DWORD,4,"NumberOfSymbols",null); - struct.add(WORD,2,"SizeOfOptionalHeader",null); - struct.add(WORD,2,"Characteristics",null); + struct.add(WORD, 2, "Machine", getMachineName()); + struct.add(WORD, 2, "NumberOfSections", null); + struct.add(DWORD, 4, "TimeDateStamp", null); + struct.add(DWORD, 4, "PointerToSymbolTable", null); + struct.add(DWORD, 4, "NumberOfSymbols", null); + struct.add(WORD, 2, "SizeOfOptionalHeader", null); + struct.add(WORD, 2, "Characteristics", null); struct.setCategoryPath(new CategoryPath("/PE")); return struct; } - private void setSectionHeaders(SectionHeader [] sectionHeaders) { + private void setSectionHeaders(SectionHeader[] sectionHeaders) { this.sectionHeaders = sectionHeaders; - numberOfSections = (short)sectionHeaders.length; + numberOfSections = (short) sectionHeaders.length; } void writeHeader(RandomAccessFile raf, DataConverter dc) throws IOException { @@ -436,7 +479,7 @@ public class FileHeader implements StructConverter { raf.write(dc.getBytes(pointerToSymbolTable)); raf.write(dc.getBytes(numberOfSymbols)); raf.write(dc.getBytes(sizeOfOptionalHeader)); - raf.write(dc.getBytes(characteristics)); + raf.write(dc.getBytes(characteristics)); } /** @@ -449,61 +492,61 @@ public class FileHeader implements StructConverter { * @throws RuntimeException if the memory block is uninitialized */ public void addSection(MemoryBlock block, OptionalHeader optionalHeader) { - DataDirectory [] directories = optionalHeader.getDataDirectories(); - - DataDirectory [] dataDirectories = optionalHeader.getDataDirectories(); + DataDirectory[] directories = optionalHeader.getDataDirectories(); + DataDirectory[] dataDirectories = optionalHeader.getDataDirectories(); SecurityDataDirectory sdd = null; if (dataDirectories.length > OptionalHeader.IMAGE_DIRECTORY_ENTRY_SECURITY) { - sdd = (SecurityDataDirectory)dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_SECURITY]; + sdd = + (SecurityDataDirectory) dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_SECURITY]; if (sdd != null && sdd.getSize() > 0) { - sdd.updatePointers( PortableExecutable.computeAlignment( (int)block.getSize( ), optionalHeader.getFileAlignment( ) ) ); + sdd.updatePointers(PortableExecutable.computeAlignment((int) block.getSize(), + optionalHeader.getFileAlignment())); } } - - int lastPos = computeAlignedNewPosition( optionalHeader, directories ); + int lastPos = computeAlignedNewPosition(optionalHeader, directories); SectionHeader newSection = new SectionHeader(block, optionalHeader, lastPos); - SectionHeader [] newSectionHeaders = new SectionHeader[sectionHeaders.length + 1]; - System.arraycopy(sectionHeaders, 0, newSectionHeaders, 0, sectionHeaders.length); + SectionHeader[] newSectionHeaders = new SectionHeader[sectionHeaders.length + 1]; + System.arraycopy(sectionHeaders, 0, newSectionHeaders, 0, sectionHeaders.length); newSectionHeaders[sectionHeaders.length] = newSection; setSectionHeaders(newSectionHeaders); int firstSectionStart = sectionHeaders[0].getPointerToRawData(); - int lastSectionEnd = sectionHeaders[sectionHeaders.length-1].getPointerToRawData() - +sectionHeaders[sectionHeaders.length-1].getSizeOfRawData(); + int lastSectionEnd = sectionHeaders[sectionHeaders.length - 1].getPointerToRawData() + + sectionHeaders[sectionHeaders.length - 1].getSizeOfRawData(); - for (int i = 0 ; i < directories.length ; i++) { - if (directories[i] == null || - directories[i].getSize() == 0 || + for (int i = 0; i < directories.length; i++) { + if (directories[i] == null || directories[i].getSize() == 0 || directories[i].isContainedInSection()) { continue; } if (directories[i].getVirtualAddress() < firstSectionStart) { if (i != OptionalHeader.IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT) { - throw new RuntimeException("PE - Unexpected directory before sections: "+i); + throw new RuntimeException("PE - Unexpected directory before sections: " + i); } } if (directories[i].getVirtualAddress() > lastSectionEnd) { if (i != OptionalHeader.IMAGE_DIRECTORY_ENTRY_SECURITY) { - throw new RuntimeException("PE - Unexpected directory after sections: "+i); + throw new RuntimeException("PE - Unexpected directory after sections: " + i); } } } int offset = 0; - if (dataDirectories.length > OptionalHeader.IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT) { - BoundImportDataDirectory bidd = (BoundImportDataDirectory)dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]; + BoundImportDataDirectory bidd = + (BoundImportDataDirectory) dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]; if (bidd != null && bidd.getSize() > 0) { bidd.updatePointers(SectionHeader.IMAGE_SIZEOF_SECTION_HEADER); int endptr = bidd.getVirtualAddress() + bidd.getSize() - 1; if (endptr >= sectionHeaders[0].getPointerToRawData()) { - int alignedPtr = PortableExecutable.computeAlignment(endptr, optionalHeader.getFileAlignment()); + int alignedPtr = PortableExecutable.computeAlignment(endptr, + optionalHeader.getFileAlignment()); offset = alignedPtr - sectionHeaders[0].getPointerToRawData(); for (SectionHeader sectionHeader : sectionHeaders) { sectionHeader.updatePointers(offset); @@ -514,9 +557,9 @@ public class FileHeader implements StructConverter { } } - if (dataDirectories.length > OptionalHeader.IMAGE_DIRECTORY_ENTRY_DEBUG) { - DebugDataDirectory ddd = (DebugDataDirectory)dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_DEBUG]; + DebugDataDirectory ddd = + (DebugDataDirectory) dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_DEBUG]; if (ddd != null && ddd.getSize() > 0) { if (ddd.getVirtualAddress() > newSection.getVirtualAddress()) { if (sdd != null && sdd.getSize() > 0) { @@ -530,12 +573,12 @@ public class FileHeader implements StructConverter { } if (block.isExecute()) { - optionalHeader.setSizeOfCode(optionalHeader.getSizeOfCode() + - newSection.getSizeOfRawData()); + optionalHeader + .setSizeOfCode(optionalHeader.getSizeOfCode() + newSection.getSizeOfRawData()); } else { - optionalHeader.setSizeOfInitializedData(optionalHeader.getSizeOfInitializedData() + - newSection.getSizeOfRawData()); + optionalHeader.setSizeOfInitializedData( + optionalHeader.getSizeOfInitializedData() + newSection.getSizeOfRawData()); } int soi = newSection.getVirtualAddress() + newSection.getSizeOfRawData(); @@ -543,7 +586,8 @@ public class FileHeader implements StructConverter { optionalHeader.setSizeOfImage(soi); } - private int computeAlignedNewPosition( OptionalHeader optionalHeader, DataDirectory [] directories ) { + private int computeAlignedNewPosition(OptionalHeader optionalHeader, + DataDirectory[] directories) { int lastPos = 0; for (SectionHeader sectionHeader : sectionHeaders) { if (sectionHeader.getPointerToRawData() + sectionHeader.getSizeOfRawData() > lastPos) { @@ -551,14 +595,13 @@ public class FileHeader implements StructConverter { } } for (DataDirectory directorie : directories) { - if (directorie == null || - directorie.getSize() == 0) { + if (directorie == null || directorie.getSize() == 0) { continue; } if (directorie.rvaToPointer() + directorie.getSize() > lastPos) { lastPos = directorie.rvaToPointer() + directorie.getSize(); } } - return PortableExecutable.computeAlignment( lastPos, optionalHeader.getFileAlignment( ) ); + return PortableExecutable.computeAlignment(lastPos, optionalHeader.getFileAlignment()); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ImageRuntimeFunctionEntries.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ImageRuntimeFunctionEntries.java new file mode 100644 index 0000000000..f03e7b91db --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ImageRuntimeFunctionEntries.java @@ -0,0 +1,486 @@ +/* ### + * 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.util.bin.format.pe; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.StructConverter; +import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + +/** + * typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { + * DWORD BeginAddress; + * DWORD EndAddress; + * union { + * DWORD UnwindInfoAddress; + * DWORD UnwindData; + * } DUMMYUNIONNAME; + * } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION, _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY; + * + * #define UNW_FLAG_NHANDLER 0x0 + * #define UNW_FLAG_EHANDLER 0x1 + * #define UNW_FLAG_UHANDLER 0x2 + * #define UNW_FLAG_CHAININFO 0x4 + * + * typedef struct _UNWIND_INFO { + * UCHAR Version : 3; + * UCHAR Flags : 5; + * UCHAR SizeOfProlog; + * UCHAR CountOfUnwindCodes; + * UCHAR FrameRegister : 4; + * UCHAR FrameOffset : 4; + * UNWIND_CODE UnwindCode[1]; + * + * // + * // The unwind codes are followed by an optional DWORD aligned field that + * // contains the exception handler address or the address of chained unwind + * // information. If an exception handler address is specified, then it is + * // followed by the language specified exception handler data. + * // + * // union { + * // ULONG ExceptionHandler; + * // ULONG FunctionEntry; + * // }; + * // + * // ULONG ExceptionData[]; + * // + * } UNWIND_INFO, *PUNWIND_INFO; + */ +public class ImageRuntimeFunctionEntries { + private final static int UNWIND_INFO_VERSION_BITMASK = 0x07; + private final static int UNWIND_INFO_FLAGS_SHIFT = 0x03; + private final static int UNWIND_INFO_FRAME_REGISTER_MASK = 0x0F; + private final static int UNWIND_INFO_FRAME_OFFSET_SHIFT = 0x04; + private final static int UNWIND_INFO_OPCODE_MASK = 0x0F; + private final static int UNWIND_INFO_OPCODE_INFO_SHIFT = 0x04; + private final static int UNWIND_INFO_SIZE = 0x0C; + + List<_IMAGE_RUNTIME_FUNCTION_ENTRY> functionEntries = new ArrayList<>(); + + static ImageRuntimeFunctionEntries createImageRuntimeFunctionEntries( + FactoryBundledWithBinaryReader reader, long index, NTHeader ntHeader) + throws IOException { + ImageRuntimeFunctionEntries imageRuntimeFunctionEntriesSection = + (ImageRuntimeFunctionEntries) reader.getFactory() + .create(ImageRuntimeFunctionEntries.class); + imageRuntimeFunctionEntriesSection.initImageRuntimeFunctionEntries(reader, index, ntHeader); + return imageRuntimeFunctionEntriesSection; + } + + /** + * DO NOT USE THIS CONSTRUCTOR, USE create*(GenericFactory ...) FACTORY METHODS INSTEAD. + */ + public ImageRuntimeFunctionEntries() { + } + + private void initImageRuntimeFunctionEntries(FactoryBundledWithBinaryReader reader, long index, + NTHeader ntHeader) throws IOException { + + int entryCount = 0; + + // Find the exception handler data section. This is an unbounded array of + // RUNTIME_INFO structures one after another and there's no count field + // to tell us how many there are, so get the maximum number there could be + // based on the size of the section. + FileHeader fh = ntHeader.getFileHeader(); + for (SectionHeader section : fh.getSectionHeaders()) { + if (section.getName().contentEquals(".pdata")) { + entryCount = section.getSizeOfRawData() / UNWIND_INFO_SIZE; + break; + } + } + + if (entryCount == 0) { + return; + } + + long origIndex = reader.getPointerIndex(); + + reader.setPointerIndex(index); + + for (int i = 0; i < entryCount; i++) { + _IMAGE_RUNTIME_FUNCTION_ENTRY entry = new _IMAGE_RUNTIME_FUNCTION_ENTRY(); + entry.beginAddress = reader.readNextUnsignedInt(); + entry.endAddress = reader.readNextUnsignedInt(); + entry.unwindInfoAddressOrData = reader.readNextUnsignedInt(); + + // When the size of the section is bigger than the number of structures + // the structure data fields will all be null, signaling the end of the + // array of structures. Break out here. + if (entry.beginAddress == 0 && entry.endAddress == 0 && + entry.unwindInfoAddressOrData == 0) { + break; + } + + // Read and process the UNWIND_INFO structures the RUNTIME_INFO + // structures point to + entry.unwindInfo = readUnwindInfo(reader, entry.unwindInfoAddressOrData, ntHeader); + + functionEntries.add(entry); + } + + reader.setPointerIndex(origIndex); + } + + private UNWIND_INFO readUnwindInfo(FactoryBundledWithBinaryReader reader, long offset, + NTHeader ntHeader) throws IOException { + long origIndex = reader.getPointerIndex(); + + long pointer = ntHeader.rvaToPointer(offset); + UNWIND_INFO unwindInfo = new UNWIND_INFO(pointer); + + if (pointer < 0) { + return unwindInfo; + } + + reader.setPointerIndex(pointer); + byte splitByte = reader.readNextByte(); + unwindInfo.version = (byte) (splitByte & UNWIND_INFO_VERSION_BITMASK); + unwindInfo.flags = (byte) (splitByte >> UNWIND_INFO_FLAGS_SHIFT); + + unwindInfo.sizeOfProlog = reader.readNextUnsignedByte(); + unwindInfo.countOfUnwindCodes = reader.readNextUnsignedByte(); + + splitByte = reader.readNextByte(); + unwindInfo.frameRegister = (byte) (splitByte & UNWIND_INFO_FRAME_REGISTER_MASK); + unwindInfo.frameOffset = (byte) (splitByte >> UNWIND_INFO_FRAME_OFFSET_SHIFT); + + unwindInfo.unwindCodes = new UNWIND_CODE[unwindInfo.countOfUnwindCodes]; + for (int i = 0; i < unwindInfo.countOfUnwindCodes; i++) { + UNWIND_CODE code = new UNWIND_CODE(); + code.offsetInProlog = reader.readNextByte(); + + int opCodeData = reader.readNextUnsignedByte(); + code.opCode = UNWIND_CODE_OPCODE.fromInt((opCodeData & UNWIND_INFO_OPCODE_MASK)); + code.opInfoRegister = + UNWIND_CODE_OPINFO_REGISTER.fromInt(opCodeData >> UNWIND_INFO_OPCODE_INFO_SHIFT); + + unwindInfo.unwindCodes[i] = code; + } + + // You can have an exception handler and/or an unwind handler, or you + // can have chained exception handling info only. + if (unwindInfo.hasExceptionHandler() || unwindInfo.hasUnwindHandler()) { + if (unwindInfo.hasExceptionHandler()) { + unwindInfo.exceptionHandlerFunction = reader.readNextInt(); + } + if (unwindInfo.hasUnwindHandler()) { + unwindInfo.unwindHandlerFunction = reader.readNextInt(); + } + } + else if (unwindInfo.hasChainedUnwindInfo()) { + unwindInfo.unwindHandlerChainInfo = new _IMAGE_RUNTIME_FUNCTION_ENTRY(); + unwindInfo.unwindHandlerChainInfo.beginAddress = reader.readNextInt(); + unwindInfo.unwindHandlerChainInfo.endAddress = reader.readNextInt(); + unwindInfo.unwindHandlerChainInfo.unwindInfoAddressOrData = reader.readNextInt(); + + // Follow the chain to the referenced UNWIND_INFO structure until we + // get to the end + unwindInfo.unwindHandlerChainInfo.unwindInfo = readUnwindInfo(reader, + unwindInfo.unwindHandlerChainInfo.unwindInfoAddressOrData, ntHeader); + } + + reader.setPointerIndex(origIndex); + + return unwindInfo; + } + + public List<_IMAGE_RUNTIME_FUNCTION_ENTRY> getRuntimeFunctionEntries() { + return functionEntries; + } + + public class _IMAGE_RUNTIME_FUNCTION_ENTRY { + public long beginAddress; + public long endAddress; + public long unwindInfoAddressOrData; + public UNWIND_INFO unwindInfo; + } + + public enum UNWIND_CODE_OPCODE { + UWOP_PUSH_NONVOL(0x00), + UWOP_ALLOC_LARGE(0x01), + UWOP_ALLOC_SMALL(0x02), + UWOP_SET_FPREG(0x03), + UWOP_SAVE_NONVOL(0x04), + UWOP_SAVE_NONVOL_FAR(0x05), + UWOP_SAVE_XMM(0x06), + UWOP_SAVE_XMM_FAR(0x07), + UWOP_SAVE_XMM128(0x08), + UWOP_SAVE_XMM128_FAR(0x09), + UWOP_PUSH_MACHFRAME(0x0A); + + private final int id; + + UNWIND_CODE_OPCODE(int value) { + id = value; + } + + public int id() { + return id; + } + + public static UNWIND_CODE_OPCODE fromInt(int id) { + UNWIND_CODE_OPCODE[] values = UNWIND_CODE_OPCODE.values(); + for (UNWIND_CODE_OPCODE value : values) { + if (value.id == id) { + return value; + } + } + return null; + } + } + + public enum UNWIND_CODE_OPINFO_REGISTER { + UNWIND_OPINFO_REGISTER_RAX(0x00), + UNWIND_OPINFO_REGISTER_RCX(0x01), + UNWIND_OPINFO_REGISTER_RDX(0x02), + UNWIND_OPINFO_REGISTER_RBX(0x03), + UNWIND_OPINFO_REGISTER_RSP(0x04), + UNWIND_OPINFO_REGISTER_RBP(0x05), + UNWIND_OPINFO_REGISTER_RSI(0x06), + UNWIND_OPINFO_REGISTER_RDI(0x07), + UNWIND_OPINFO_REGISTER_R8(0x08), + UNWIND_OPINFO_REGISTER_R9(0x09), + UNWIND_OPINFO_REGISTER_R10(0x0A), + UNWIND_OPINFO_REGISTER_R11(0x0B), + UNWIND_OPINFO_REGISTER_R12(0x0C), + UNWIND_OPINFO_REGISTER_R13(0x0D), + UNWIND_OPINFO_REGISTER_R14(0x0E), + UNWIND_OPINFO_REGISTER_R15(0x0F); + + private final int id; + + UNWIND_CODE_OPINFO_REGISTER(int value) { + id = value; + } + + public int id() { + return id; + } + + public static UNWIND_CODE_OPINFO_REGISTER fromInt(int id) { + UNWIND_CODE_OPINFO_REGISTER[] values = UNWIND_CODE_OPINFO_REGISTER.values(); + for (UNWIND_CODE_OPINFO_REGISTER value : values) { + if (value.id == id) { + return value; + } + } + return null; + } + } + + public class UNWIND_CODE { + public byte offsetInProlog; + public UNWIND_CODE_OPCODE opCode; + public UNWIND_CODE_OPINFO_REGISTER opInfoRegister; + } + + public class UNWIND_INFO implements StructConverter { + private static final String NAME = "UNWIND_INFO"; + + private final static int UNW_FLAG_NHANDLER = 0x0; + private final static int UNW_FLAG_EHANDLER = 0x1; + private final static int UNW_FLAG_UHANDLER = 0x2; + private final static int UNW_FLAG_CHAININFO = 0x4; + + private final static int UNWIND_VERSION_FIELD_LENGTH = 0x03; + private final static int UNWIND_FLAGS_FIELD_LENGTH = 0x05; + private final static int UNWIND_FRAME_REGISTER_LENGTH = 0x04; + private final static int UNWIND_OP_FIELD_LENGTH = 0x04; + + byte version; + byte flags; + int sizeOfProlog; + int countOfUnwindCodes; + byte frameRegister; + byte frameOffset; + UNWIND_CODE[] unwindCodes; + int exceptionHandlerFunction; + int unwindHandlerFunction; + _IMAGE_RUNTIME_FUNCTION_ENTRY unwindHandlerChainInfo; + + long startOffset; + + public UNWIND_INFO(long offset) { + startOffset = offset; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + StructureDataType struct = new StructureDataType(NAME + "_" + startOffset, 0); + try { + StructureDataType vf = new StructureDataType("VersionFlags", 0); + vf.insertBitField(0, 1, 0, BYTE, UNWIND_VERSION_FIELD_LENGTH, "Version", null); + vf.insertBitField(0, 1, UNWIND_VERSION_FIELD_LENGTH, defineFlagsField(), + UNWIND_FLAGS_FIELD_LENGTH, "Flags", null); + + struct.add(vf, "Version + Flags", null); + } + catch (InvalidDataTypeException e) { + struct.add(BYTE, "Version + Flags", null); + } + + struct.add(BYTE, "SizeOfProlog", null); + struct.add(BYTE, "CountOfUnwindCodes", null); + + try { + StructureDataType fr = new StructureDataType("FrameRegisterAndOffset", 0); + fr.insertBitField(0, 1, 0, BYTE, UNWIND_FRAME_REGISTER_LENGTH, "FrameRegister", + null); + fr.insertBitField(0, 1, UNWIND_FRAME_REGISTER_LENGTH, BYTE, + UNWIND_FRAME_REGISTER_LENGTH, "FrameOffset", null); + struct.add(fr, "FrameRegister + FrameOffset", null); + } + catch (InvalidDataTypeException e) { + struct.add(BYTE, "FrameRegister + FrameOffset", null); + } + + for (int i = 0; i < countOfUnwindCodes; i++) { + StructureDataType unwindCode = new StructureDataType("UnwindCode", 0); + unwindCode.add(BYTE, "OffsetInProlog", null); + + StructureDataType unwindCodeInfo = new StructureDataType("UnwindCodeInfo", 0); + try { + if (unwindCodes[i].opCode != null) { + unwindCodeInfo.insertBitField(0, 1, 0, defineUnwindOpCodeField(), + UNWIND_OP_FIELD_LENGTH, "UnwindOpCode", null); + } + else { + unwindCodeInfo.insertBitField(0, 1, 0, BYTE, UNWIND_OP_FIELD_LENGTH, + "UnwindOpCode", null); + } + + if (unwindCodes[i].opInfoRegister != null) { + unwindCodeInfo.insertBitField(0, 1, UNWIND_OP_FIELD_LENGTH, + defineUnwindCodeRegisterField(), UNWIND_OP_FIELD_LENGTH, "OpInfo", + null); + } + else { + unwindCodeInfo.insertBitField(0, 1, UNWIND_OP_FIELD_LENGTH, BYTE, + UNWIND_OP_FIELD_LENGTH, "OpInfo", null); + } + } + catch (InvalidDataTypeException e) { + } + unwindCode.add(unwindCodeInfo, "UnwindCodeInfo", null); + + struct.add(unwindCode, "UnwindCode", null); + } + + if (hasExceptionHandler() || hasUnwindHandler()) { + if (hasExceptionHandler()) { + struct.add(IBO32, "ExceptionHandler", null); + } + if (hasUnwindHandler()) { + struct.add(IBO32, "UnwindHandler", null); + } + } + else { + if (hasChainedUnwindInfo()) { + struct.add(IBO32, "FunctionStartAddress", null); + struct.add(IBO32, "FunctionEndAddress", null); + struct.add(IBO32, "FunctionUnwindInfoAddress", null); + } + } + + return struct; + } + + public boolean hasExceptionHandler() { + return (flags & UNW_FLAG_EHANDLER) == UNW_FLAG_EHANDLER; + } + + public boolean hasUnwindHandler() { + return (flags & UNW_FLAG_UHANDLER) == UNW_FLAG_UHANDLER; + } + + public boolean hasChainedUnwindInfo() { + return (flags & UNW_FLAG_CHAININFO) == UNW_FLAG_CHAININFO; + } + + private EnumDataType defineFlagsField() { + EnumDataType flagsField = new EnumDataType("Flags", 5); + flagsField.add("UNW_FLAG_NHANDLER", UNW_FLAG_NHANDLER); + flagsField.add("UNW_FLAG_EHANDLER", UNW_FLAG_EHANDLER); + flagsField.add("UNW_FLAG_UHANDLER", UNW_FLAG_UHANDLER); + flagsField.add("UNW_FLAG_CHAININFO", UNW_FLAG_CHAININFO); + + return flagsField; + } + + private EnumDataType defineUnwindOpCodeField() { + EnumDataType unwindOpCodeField = new EnumDataType("UNWIND_CODE_OPCODE", 4); + unwindOpCodeField.add("UWOP_PUSH_NONVOL", UNWIND_CODE_OPCODE.UWOP_PUSH_NONVOL.id); + unwindOpCodeField.add("UWOP_ALLOC_LARGE", UNWIND_CODE_OPCODE.UWOP_ALLOC_LARGE.id); + unwindOpCodeField.add("UWOP_ALLOC_SMALL", UNWIND_CODE_OPCODE.UWOP_ALLOC_SMALL.id); + unwindOpCodeField.add("UWOP_SET_FPREG", UNWIND_CODE_OPCODE.UWOP_SET_FPREG.id); + unwindOpCodeField.add("UWOP_SAVE_NONVOL", UNWIND_CODE_OPCODE.UWOP_SAVE_NONVOL.id); + unwindOpCodeField.add("UWOP_SAVE_NONVOL_FAR", + UNWIND_CODE_OPCODE.UWOP_SAVE_NONVOL_FAR.id); + unwindOpCodeField.add("UWOP_SAVE_XMM", UNWIND_CODE_OPCODE.UWOP_SAVE_XMM.id); + unwindOpCodeField.add("UWOP_SAVE_XMM_FAR", UNWIND_CODE_OPCODE.UWOP_SAVE_XMM_FAR.id); + unwindOpCodeField.add("UWOP_SAVE_XMM128", UNWIND_CODE_OPCODE.UWOP_SAVE_XMM128.id); + unwindOpCodeField.add("UWOP_SAVE_XMM128_FAR", + UNWIND_CODE_OPCODE.UWOP_SAVE_XMM128_FAR.id); + unwindOpCodeField.add("UWOP_PUSH_MACHFRAME", UNWIND_CODE_OPCODE.UWOP_PUSH_MACHFRAME.id); + + return unwindOpCodeField; + } + + private EnumDataType defineUnwindCodeRegisterField() { + EnumDataType unwindCodeRegisterField = + new EnumDataType("UNWIND_CODE_OPINFO_REGISTER", 4); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RAX", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RAX.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RCX", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RCX.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RDX", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RDX.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RBX", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RBX.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RSP", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RSP.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RBP", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RBP.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RSI", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RSI.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RDI", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RDI.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R8", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R8.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R9", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R9.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R10", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R10.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R11", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R11.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R12", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R12.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R13", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R13.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R14", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R14.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R15", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R15.id); + + return unwindCodeRegisterField; + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/NTHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/NTHeader.java index 0912d75484..e88d1fe976 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/NTHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/NTHeader.java @@ -30,7 +30,7 @@ import ghidra.util.task.TaskMonitorAdapter; /** * A class to represent the IMAGE_NT_HEADERS32 and - * IMAGE_NT_HEADERS64 structs as defined in + * IMAGE_NT_HEADERS64 structs as defined in * winnt.h. *

      * typedef struct _IMAGE_NT_HEADERS {
    @@ -39,8 +39,8 @@ import ghidra.util.task.TaskMonitorAdapter;
      *    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
      * };
      * 
    - * - * + * + * */ public class NTHeader implements StructConverter, OffsetValidator { /** @@ -82,8 +82,8 @@ public class NTHeader implements StructConverter, OffsetValidator { public NTHeader() { } - private void initNTHeader(FactoryBundledWithBinaryReader reader, int index, SectionLayout layout, - boolean advancedProcess, boolean parseCliHeaders) + private void initNTHeader(FactoryBundledWithBinaryReader reader, int index, + SectionLayout layout, boolean advancedProcess, boolean parseCliHeaders) throws InvalidNTHeaderException, IOException { this.reader = reader; this.index = index; @@ -172,9 +172,9 @@ public class NTHeader implements StructConverter, OffsetValidator { //low alignment mode? // if (optionalHeader != null) { - if (optionalHeader.getFileAlignment() == optionalHeader.getSectionAlignment() - && optionalHeader.getSectionAlignment() < 800 - && optionalHeader.getFileAlignment() > 1) { + if (optionalHeader.getFileAlignment() == optionalHeader.getSectionAlignment() && + optionalHeader.getSectionAlignment() < 800 && + optionalHeader.getFileAlignment() > 1) { return rva; } } @@ -270,6 +270,7 @@ public class NTHeader implements StructConverter, OffsetValidator { fileHeader.processSections(optionalHeader); fileHeader.processSymbols(); + fileHeader.processImageRuntimeFunctionEntries(); if (advancedProcess) { optionalHeader.processDataDirectories(TaskMonitorAdapter.DUMMY_MONITOR); @@ -278,7 +279,7 @@ public class NTHeader implements StructConverter, OffsetValidator { void writeHeader(RandomAccessFile raf, DataConverter dc) throws IOException { - raf.seek( index ); + raf.seek(index); raf.write(dc.getBytes(signature)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliAbstractSig.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliAbstractSig.java index e82c38b122..303b5138b7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliAbstractSig.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliAbstractSig.java @@ -4,9 +4,9 @@ * 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. @@ -107,12 +107,12 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable ELEMENT_TYPE_VALUETYPE(0x11), ELEMENT_TYPE_VAR(0x13), // "Class type variable VAR" (0x16), - + ELEMENT_TYPE_MVAR(0x1e), // Method type variable MVAR - + ELEMENT_TYPE_INTERNAL(0x21), // Internal (generated internally, "will not be persisted in any way") ELEMENT_TYPE_MAX(0x22), - + */ switch (typeCode) { @@ -431,32 +431,95 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable } public class CliTypeFnPtr extends CliSigType { - private CliAbstractSig sig; - private boolean isDefSig; // true => MethodDef, false => MethodRef + private long dataOffset; + + private CliRetType retType; + private CliParam params[]; + private int sizeOfCount; + private byte flags; + private int genericParamCount; + private int sizeOfGenericCount; + + private int sentinelIndex; // SENTINEL is before the parameter index in this field + + private final int FNPTR_FLAGS_DEFAULT = 0x00; + private final int FNPTR_FLAGS_VARARG = 0x05; + private final int FNPTR_FLAGS_GENERIC = 0x10; + private final int FNPTR_FLAGS_HASTHIS = 0x20; + private final int FNPTR_FLAGS_EXPLICITTHIS = 0x40; public CliTypeFnPtr(BinaryReader reader, CliElementType typeCode) throws IOException { super(typeCode); - // TODO: MethodDef and MethodRef sig need to have static isX(reader) methods so I can tell the difference - //sig = new CliSigMethodRef(blob); // MethodRef is just Def plus possible sentinel and minus potential XORed args in the first byte + + sentinelIndex = -1; + + dataOffset = reader.getPointerIndex(); + + // Flags is similar to a MethodDef unless vararg is used. + flags = reader.readNextByte(); + + if ((flags & FNPTR_FLAGS_GENERIC) == FNPTR_FLAGS_GENERIC) { + long origIndex = reader.getPointerIndex(); + genericParamCount = decodeCompressedUnsignedInt(reader); + sizeOfGenericCount = (int) (reader.getPointerIndex() - origIndex); + } + + long origIndex = reader.getPointerIndex(); + int paramCount = decodeCompressedUnsignedInt(reader); + this.sizeOfCount = (int) (reader.getPointerIndex() - origIndex); + + try { + retType = new CliRetType(reader); + } + catch (InvalidInputException e) { + retType = null; + } + params = new CliParam[paramCount]; + + for (int i = 0; i < paramCount; i++) { + if (reader.peekNextByte() == CliElementType.ELEMENT_TYPE_SENTINEL.id()) { + reader.readNextByte(); + sentinelIndex = i; + } + try { + params[i] = new CliParam(reader); + } + catch (InvalidInputException e) { + params[i] = null; + } + } } @Override public String getRepresentation() { - return "FnPtr " + sig.getRepresentation(); + return "FnPtr " /*+ sig.getRepresentation()*/; } @Override public String getShortRepresentation() { - return "FnPtr " + sig.getShortRepresentation(); + return "FnPtr " /*+ sig.getShortRepresentation()*/; } @Override public DataType getDefinitionDataType() { - StructureDataType struct = new StructureDataType(new CategoryPath(PATH), "FnPtr", 0); - struct.add(CliTypeCodeDataType.dataType, "FnPtr", "FnPtr"); - struct.add(DWORD, "MethodDefOrRef", "index into blob heap"); + StructureDataType struct = + new StructureDataType(new CategoryPath(PATH), "FnPtr_" + dataOffset, 0); + struct.add(CliTypeCodeDataType.dataType, CliElementType.ELEMENT_TYPE_FNPTR.toString(), + ""); + struct.add(BYTE, "Flags", "ORed VARARG/GENERIC/HASTHIS/EXPLICITTHIS"); + + if (genericParamCount > 0) { + struct.add(getDataTypeForBytes(sizeOfGenericCount), "GenParamCount", + "Number of generic paramameters for the method"); + } + struct.add(getDataTypeForBytes(sizeOfCount), "ParamCount", + "Number of parameter types to follow RetType"); + struct.add(retType.getDefinitionDataType(), "RetType", null); + + for (int i = 0; i < params.length; i++) { + struct.add(params[i].getDefinitionDataType(), "Param" + i, null); + } return struct; - // TODO: Return the correct size of a signature reference (always 4B in this context perchance?) } } @@ -552,17 +615,18 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable } public class CliTypeVarOrMvar extends CliSigType { + private long dataOffset; private int number; private int numberBytes; public CliTypeVarOrMvar(BinaryReader reader, CliElementType typeCode) throws IOException { super(typeCode); - long origIndex = reader.getPointerIndex(); + dataOffset = reader.getPointerIndex(); number = decodeCompressedUnsignedInt(reader); long endIndex = reader.getPointerIndex(); - numberBytes = (int) (endIndex - origIndex); + numberBytes = (int) (endIndex - dataOffset); } @Override @@ -572,9 +636,9 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable @Override public DataType getDefinitionDataType() { - StructureDataType struct = - new StructureDataType(new CategoryPath(PATH), "VarOrMvar", 0); - struct.add(BYTE, "Type", "Var or Mvar"); + StructureDataType struct = new StructureDataType(new CategoryPath(PATH), + baseTypeCode.toString() + "_" + dataOffset, 0); + struct.add(CliTypeCodeDataType.dataType, baseTypeCode.toString(), ""); struct.add(getDataTypeForBytes(numberBytes), "number", null); return struct; } @@ -582,16 +646,24 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable public class CliTypePtr extends CliSigType { private List customMods = new ArrayList<>(); - private CliElementType typeCode; + private CliSigType type; + private long dataOffset; - public CliTypePtr(BinaryReader reader, CliElementType typeCode) throws IOException { + public CliTypePtr(BinaryReader reader, CliElementType typeCode) + throws IOException, InvalidInputException { super(typeCode); + dataOffset = reader.getPointerIndex(); + while (CliCustomMod.isCustomMod(reader)) { customMods.add(new CliCustomMod(reader)); } - typeCode = CliElementType.fromInt(reader.readNextByte()); + type = readCliType(reader); + } + + public CliSigType getType() { + return type; } @Override @@ -601,17 +673,22 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable modsRep += mod.toString() + ", "; } modsRep = modsRep.substring(0, modsRep.length() - 2); - return String.format("Ptr %s %s", modsRep, typeCode.toString()); + return String.format("Ptr %s %s", modsRep, type.toString()); } @Override public DataType getDefinitionDataType() { - StructureDataType struct = new StructureDataType(new CategoryPath(PATH), "Ptr", 0); - struct.add(CliTypeCodeDataType.dataType, "TypeCode", "Ptr"); + StructureDataType struct = new StructureDataType(new CategoryPath(PATH), + CliElementType.ELEMENT_TYPE_PTR.toString() + "_" + dataOffset, 0); + struct.add(CliTypeCodeDataType.dataType, CliElementType.ELEMENT_TYPE_PTR.toString(), + ""); + for (CliCustomMod mod : customMods) { struct.add(mod.getDefinitionDataType()); } - struct.add(CliTypeCodeDataType.dataType, "Type", "type or void"); + + struct.add(type.getDefinitionDataType(), type.baseTypeCode.toString(), ""); + return struct; } } @@ -682,6 +759,19 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable typeBytes = (int) (endIndex - origIndex); } + public CliTypeTable getTable() { + try { + return CliIndexTypeDefOrRef.getTableName(encodedType); + } + catch (InvalidInputException e) { + return null; + } + } + + public int getRowIndex() { + return CliIndexTypeDefOrRef.getRowIndex(encodedType); + } + @Override public String getRepresentation() { return "ValueType " + Integer.toHexString(encodedType); @@ -714,12 +804,17 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable @Override public DataType getDefinitionDataType() { + String tableName = getTable().name(); + StructureDataType struct = new StructureDataType(new CategoryPath(PATH), "ValueType", 0); struct.add(CliTypeCodeDataType.dataType, "ValueType", "ValueType"); - struct.add(getDataTypeForBytes(typeBytes), "Type", "TypeDefOrRefOrSpecEncoded"); + struct.add(getDataTypeForBytes(typeBytes), "TypeDefOrRefEncoded", + tableName + ": Row 0x" + Integer.toHexString(getRowIndex())); + return struct; } + } public CliSigType readCliType(BinaryReader reader) throws IOException, InvalidInputException { @@ -761,10 +856,11 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable } } - // The CustomMod signature part contains a required CMOD option (CMOD_OPT or CMOD_REQD) then a compressed TypeDefOrRefOrSpecEncoded - public static class CliCustomMod { + // The CustomMod signature part contains a required CMOD + // option (CMOD_OPT or CMOD_REQD) then a compressed TypeDefOrRefOrSpecEncoded + public static class CliCustomMod implements CliRepresentable { private CliElementType cmod; - private int typeEncoded; + private int encodedType; private int sizeOfCount; public static boolean isCustomMod(BinaryReader reader) throws IOException { @@ -776,7 +872,7 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable cmod = CliElementType.fromInt(reader.readNextByte()); long origIndex = reader.getPointerIndex(); - typeEncoded = decodeCompressedUnsignedInt(reader); + encodedType = decodeCompressedUnsignedInt(reader); long endIndex = reader.getPointerIndex(); sizeOfCount = (int) (endIndex - origIndex); @@ -787,12 +883,12 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable } public int getTypeEncoded() { - return typeEncoded; + return encodedType; } public CliTypeTable getTable() { try { - return CliIndexTypeDefOrRef.getTableName(typeEncoded); + return CliIndexTypeDefOrRef.getTableName(encodedType); } catch (InvalidInputException e) { return null; @@ -800,7 +896,7 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable } public int getRowIndex() { - return CliIndexTypeDefOrRef.getRowIndex(typeEncoded); + return CliIndexTypeDefOrRef.getRowIndex(encodedType); } public CliAbstractTableRow getRow(CliStreamMetadata stream) { @@ -808,25 +904,40 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable } public DataType getDefinitionDataType() { - StructureDataType struct = new StructureDataType(new CategoryPath(PATH), - CliCustomMod.class.getSimpleName(), 0); - struct.add(BYTE, "CMOD", "CMOD_OPT or CMOD_REQD"); - struct.add(getDataTypeForBytes(this.sizeOfCount), "Type", - "TypeDefOrRefOrSpec encoded type"); + StructureDataType struct = + new StructureDataType(new CategoryPath(PATH), "CustomMod", 0); + + String tableName = getTable().name(); + + struct.add(CliTypeCodeDataType.dataType, cmod.toString(), null); + struct.add(getDataTypeForBytes(this.sizeOfCount), "TypeDefOrRefEncoded", + tableName + ": Row 0x" + Integer.toHexString(getRowIndex())); return struct; } + @Override public String getRepresentation(CliStreamMetadata stream) { return String.format("%s %s", cmod.toString(), getRow(stream)); } + @Override public String getRepresentation() { - return String.format("%s %x", cmod.toString(), typeEncoded); + return String.format("%s %x", cmod.toString(), encodedType); + } + + @Override + public String getShortRepresentation() { + return getRepresentation(); + } + + @Override + public String getShortRepresentation(CliStreamMetadata stream) { + return getRepresentation(stream); } } // The only possible constraint is ELEMENT_TYPE_PINNED (CliTypeCode.Pinned) - public static class CliConstraint { + public static class CliConstraint implements CliRepresentable { private CliElementType constraint; public static boolean isConstraint(BinaryReader reader) throws IOException { @@ -841,6 +952,7 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable return constraint; } + @Override public String getRepresentation() { if (constraint == CliElementType.ELEMENT_TYPE_PINNED) { return constraint.toString(); @@ -848,36 +960,96 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable return String.format("Invalid Constraint (%s - %x)", constraint.toString(), constraint.id()); } + + @Override + public String getShortRepresentation() { + return getRepresentation(); + } + + @Override + public String getRepresentation(CliStreamMetadata stream) { + return getRepresentation(); + } + + @Override + public String getShortRepresentation(CliStreamMetadata stream) { + return getRepresentation(); + } + } + + // The only possible result is ELEMENT_TYPE_BYREF + public static class CliByRef implements CliRepresentable { + private CliElementType byRef; + + public static boolean isByRef(BinaryReader reader) throws IOException { + return (reader.peekNextByte() == CliElementType.ELEMENT_TYPE_BYREF.id()); + } + + public CliByRef(BinaryReader reader) throws IOException { + byRef = CliElementType.fromInt(reader.readNextByte()); + } + + public CliElementType getByRef() { + return byRef; + } + + @Override + public String getRepresentation() { + if (byRef == CliElementType.ELEMENT_TYPE_BYREF) { + return byRef.toString(); + } + return String.format("Invalid ByRef (%s - %x)", byRef.toString(), byRef.id()); + } + + @Override + public String getShortRepresentation() { + return getRepresentation(); + } + + @Override + public String getRepresentation(CliStreamMetadata stream) { + return getRepresentation(); + } + + @Override + public String getShortRepresentation(CliStreamMetadata stream) { + return getRepresentation(); + } } public class CliTypeBase implements CliRepresentable { - private List customMods = new ArrayList<>(); - private boolean constraint = false; - private boolean byRef = false; + private List modifiers = new ArrayList<>(); + private CliSigType type; + private long dataOffset = 0; private boolean isVoidAllowed = false; public CliTypeBase(BinaryReader reader, boolean isRetType) throws IOException, InvalidInputException { + dataOffset = reader.getPointerIndex(); + this.isVoidAllowed = isRetType; - // Get any custom modifiers - while (CliCustomMod.isCustomMod(reader)) { - customMods.add(new CliCustomMod(reader)); - } + // Check for any of the modifiers, which can repeat out of order + // before encountering the base type + while (CliCustomMod.isCustomMod(reader) || CliConstraint.isConstraint(reader) || + CliByRef.isByRef(reader)) { - // Check to see if it's a constrained variable - if (CliConstraint.isConstraint(reader)) { - constraint = true; - reader.readNextByte(); - } + // Get any custom modifiers + while (CliCustomMod.isCustomMod(reader)) { + modifiers.add(new CliCustomMod(reader)); + } - // Check to see if it's a ByRef - byte byRefCheck = reader.peekNextByte(); - if (byRefCheck == CliElementType.ELEMENT_TYPE_BYREF.id()) { - byRef = true; - reader.readNextByte(); + // Check to see if it's a constrained variable + if (CliConstraint.isConstraint(reader)) { + modifiers.add(new CliConstraint(reader)); + } + + // Check to see if it's a ByRef + if (CliByRef.isByRef(reader)) { + modifiers.add(new CliByRef(reader)); + } } type = readCliType(reader); @@ -888,34 +1060,48 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable } public List getCustomMods() { + List customMods = new ArrayList(); + for (CliRepresentable mod : modifiers) { + if (mod instanceof CliCustomMod) { + customMods.add((CliCustomMod) mod); + } + } + return customMods; } public boolean isByRef() { - return byRef; + for (CliRepresentable mod : modifiers) { + if (mod instanceof CliByRef) { + return true; + } + } + return false; } public boolean isConstrained() { - return constraint; + for (CliRepresentable mod : modifiers) { + if (mod instanceof CliConstraint) { + return true; + } + } + return false; } private String getRepresentationCommon(CliStreamMetadata stream, boolean shortRep) { String rep = ""; - for (CliCustomMod mod : customMods) { - rep += mod.getRepresentation() + "; "; - } - - if (customMods.size() > 0) { - rep = rep.substring(0, rep.length() - 2) + " "; - } - - if (constraint) { - rep += "constrained "; - } - - if (byRef) { - rep += "byref "; + for (CliRepresentable mod : modifiers) { + if (mod instanceof CliCustomMod) { + CliCustomMod customMod = (CliCustomMod) mod; + rep += customMod.getRepresentation() + "; "; + } + else if (mod instanceof CliConstraint) { + rep += "constrained "; + } + else if (mod instanceof CliByRef) { + rep += "byref "; + } } // The one special case value we have is the SENTINEL, which @@ -952,20 +1138,25 @@ public abstract class CliAbstractSig extends CliBlob implements CliRepresentable } public DataType getDefinitionDataType() { - StructureDataType struct = new StructureDataType(new CategoryPath(PATH), "Type", 0); + StructureDataType struct = + new StructureDataType(new CategoryPath(PATH), "Type_" + dataOffset, 0); - for (CliCustomMod mod : customMods) { - struct.add(mod.getDefinitionDataType(), "CustomMod", null); + for (CliRepresentable mod : modifiers) { + if (mod instanceof CliCustomMod) { + CliCustomMod customMod = (CliCustomMod) mod; + struct.add(customMod.getDefinitionDataType(), "CustomMod", null); + } + else if (mod instanceof CliConstraint) { + struct.add(CliTypeCodeDataType.dataType, + CliElementType.ELEMENT_TYPE_PINNED.toString(), "Constrained"); + } + else if (mod instanceof CliByRef) { + struct.add(CliTypeCodeDataType.dataType, + CliElementType.ELEMENT_TYPE_BYREF.toString(), "By Reference"); + } } - if (constraint) { - struct.add(BYTE, "CONSTRAINT", "Constrained"); - } - - if (byRef) { - struct.add(BYTE, "BYREF", "By reference"); - } - struct.add(type.getDefinitionDataType(), "Type", null); + struct.add(type.getDefinitionDataType(), type.baseTypeCode.toString(), null); return struct; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliBlobCustomAttrib.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliBlobCustomAttrib.java new file mode 100644 index 0000000000..f27e125b47 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliBlobCustomAttrib.java @@ -0,0 +1,475 @@ +/* ### + * 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.util.bin.format.pe.cli.blobs; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.pe.cli.blobs.CliAbstractSig.CliElementType; +import ghidra.app.util.bin.format.pe.cli.blobs.CliAbstractSig.CliParam; +import ghidra.app.util.bin.format.pe.cli.streams.CliStreamMetadata; +import ghidra.app.util.bin.format.pe.cli.tables.CliAbstractTableRow; +import ghidra.app.util.bin.format.pe.cli.tables.CliTableCustomAttribute.CliCustomAttributeRow; +import ghidra.app.util.bin.format.pe.cli.tables.CliTableMemberRef.CliMemberRefRow; +import ghidra.app.util.bin.format.pe.cli.tables.CliTableMethodDef.CliMethodDefRow; +import ghidra.app.util.bin.format.pe.cli.tables.CliTypeTable; +import ghidra.app.util.bin.format.pe.cli.tables.indexes.CliIndexCustomAttributeType; +import ghidra.program.model.data.*; +import ghidra.util.Msg; +import ghidra.util.exception.InvalidInputException; + +public class CliBlobCustomAttrib extends CliBlob { + + private CliFixedArg[] fixedArgs; + private CliNamedArg[] namedArgs; + private short numNamed; + + // Fixed constants for validating the structure + private static final short CLIBLOBCUSTOMATTRIB_PROLOG = 0x0001; + private static final byte CLIBLOBCUSTOMATTRIB_TYPE_FIELD = 0x53; + private static final byte CLIBLOBCUSTOMATTRIB_TYPE_PROPERTY = 0x54; + + private static final int CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_128 = 0x80; + private static final int CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_192 = 0xC0; + + private static final int CLIBLOBCUSTOMATTRIB_STRING_SIZE_ONE = 0x01; + private static final int CLIBLOBCUSTOMATTRIB_STRING_SIZE_TWO = 0x02; + private static final int CLIBLOBCUSTOMATTRIB_STRING_SIZE_FOUR = 0x03; + private static final int CLIBLOBCUSTOMATTRIB_STRING_SIZE_BITMASK = 0x3F; + + private static final int CLIBLOBCUSTOMATTRIB_STRING_INDICATOR_SHIFT = 0x06; + private static final int CLIBLOBCUSTOMATTRIB_STRING_INDICATOR_BITMASK = 0x03; + + // UTF-8 boundaries that help detect the end of a string where + // lengths aren't specified in FixedArg + private static final int CLIBLOBCUSTOMATTRIB_UTF8_LOW = 0x1F; + private static final int CLIBLOBCUSTOMATTRIB_UTF8_HIGH = 0x7F; + + private class CliFixedArg { + private CliElementType elem; + private Object value; + + public CliFixedArg(CliElementType elem, Object value) { + this.elem = elem; + this.value = value; + } + + public CliElementType getElem() { + return elem; + } + + public Object getValue() { + return value; + } + } + + private class CliNamedArg { + private int fieldOrProp; + private CliElementType fieldOrPropType; + private String fieldOrPropName; + + public CliNamedArg(int fieldOrProp, CliElementType fieldOrPropType, + String fieldOrPropName) { + this.fieldOrProp = fieldOrProp; + this.fieldOrPropType = fieldOrPropType; + this.fieldOrPropName = fieldOrPropName; + } + + public int getFieldOrProp() { + return fieldOrProp; + } + + public CliElementType getFieldOrPropType() { + return fieldOrPropType; + } + + public String getFieldOrPropName() { + return fieldOrPropName; + } + } + + public CliBlobCustomAttrib(CliBlob blob, CliCustomAttributeRow row, + CliStreamMetadata metadataStream) throws IOException { + super(blob); + + BinaryReader reader = blob.getContentsReader(); + + // Validate the blob prolog + short prolog = reader.readNextShort(); + if (prolog != CLIBLOBCUSTOMATTRIB_PROLOG) { + Msg.warn(this, + getName() + " had unexpected prolog (0x" + Integer.toHexString(prolog) + ")"); + return; + } + + // The location in the blob table for the actual CustomAttrib blob + int valueIndex = row.valueIndex; + + // The entry in the MethodRef or MethodDef table that corresponds to the method + // This is a CustomAttributeType coded index + int typeIndex = row.typeIndex; + + // The entry of the parent table index, also a CustomAttributeType coded index + int parentIndex = row.parentIndex; + + // The FixedArg parameters in the CustomAttrib blob are stored concatenated + // against each other without delimeters or type indicators, so you have to look + // back to the originating method signature to figure out what's what. + + CliParam[] params = null; + try { + // Get the table type and row for the attribute and depending on the type + // get the parameters + CliTypeTable tableType = CliIndexCustomAttributeType.getTableName(typeIndex); + int tableRowIndex = CliIndexCustomAttributeType.getRowIndex(typeIndex); + CliAbstractTableRow tableRow = metadataStream.getTable(tableType).getRow(tableRowIndex); + + if (tableType == CliTypeTable.MemberRef) { + CliMemberRefRow memberRefRow = (CliMemberRefRow) tableRow; + CliBlob memberRefBlob = + metadataStream.getBlobStream().getBlob(memberRefRow.signatureIndex); + CliSigMethodRef methodRefSig = new CliSigMethodRef(memberRefBlob); + params = methodRefSig.getParams(); + } + else if (tableType == CliTypeTable.MethodDef) { + CliMethodDefRow methodDefRow = (CliMethodDefRow) tableRow; + CliBlob methodDefBlob = + metadataStream.getBlobStream().getBlob(methodDefRow.sigIndex); + CliSigMethodDef methodDefSig = new CliSigMethodDef(methodDefBlob); + params = methodDefSig.getParamTypes(); + } + } + catch (InvalidInputException e) { + Msg.warn(this, "Unable to process the parameters in " + getName()); + return; + } + + // Process zero to multiple FixedArgs + fixedArgs = processFixedArgs(reader, params).toArray(CliFixedArg[]::new); + + // Process zero to multiple NamedArgs here + namedArgs = processNamedArgs(reader).toArray(CliNamedArg[]::new); + } + + @Override + public DataType getContentsDataType() { + StructureDataType struct = new StructureDataType(new CategoryPath(PATH), getName(), 0); + struct.add(WORD, "PROLOG", "Magic (0x0001)"); + + // Display the FixedArgs + if (fixedArgs != null) { + for (int i = 0; i < fixedArgs.length; i++) { + CliElementType elem = fixedArgs[i].elem; + + switch (elem) { + case ELEMENT_TYPE_CHAR: + struct.add(UTF16, "FixedArg_" + i, "Elem (" + fixedArgs[i].getElem() + ")"); + break; + + case ELEMENT_TYPE_I1: + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_BOOLEAN: + struct.add(BYTE, "FixedArg_" + i, "Elem (" + fixedArgs[i].getElem() + ")"); + break; + + case ELEMENT_TYPE_I2: + case ELEMENT_TYPE_U2: + struct.add(WORD, "FixedArg_" + i, "Elem (" + fixedArgs[i].getElem() + ")"); + break; + + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_R4: + case ELEMENT_TYPE_VALUETYPE: + struct.add(DWORD, "FixedArg_" + i, "Elem (" + fixedArgs[i].getElem() + ")"); + break; + + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: + case ELEMENT_TYPE_R8: + struct.add(QWORD, "FixedArg_" + i, "Elem (" + fixedArgs[i].getElem() + ")"); + + case ELEMENT_TYPE_STRING: + String s = (String) fixedArgs[i].value; + int l = s.length(); + if (l < CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_128) { + struct.add(BYTE, "PackedLen", ""); + } + else if (l < CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_192) { + struct.add(WORD, "PackedLen", ""); + } + else { + struct.add(DWORD, "PackedLen", ""); + + } + struct.add(UTF8, ((String) fixedArgs[i].value).length(), "FixedArg_" + i, + ""); + break; + + case ELEMENT_TYPE_I: + struct.add(BYTE, "ELEMENT_TYPE_I", ""); + struct.add(UTF8, ((String) fixedArgs[i].value).length(), "FixedArg_" + i, + ""); + break; + + default: + Msg.warn(this, "Unprocessed FixedArg element type in CustomAttr #" + + (i + 1) + ": " + fixedArgs[i].getElem().name()); + break; + } + } + } + + struct.add(WORD, "NumNamed", "Number of NamedArgs to follow"); + + // Display the NamedArgs + if (namedArgs != null) { + for (CliNamedArg cliNamedArg : namedArgs) { + int fieldOrProp = cliNamedArg.getFieldOrProp(); + if (fieldOrProp == CLIBLOBCUSTOMATTRIB_TYPE_FIELD) { + struct.add(BYTE, "FieldOrProp", "FIELD"); + } + else if (fieldOrProp == CLIBLOBCUSTOMATTRIB_TYPE_PROPERTY) { + struct.add(BYTE, "FieldOrProp", "PROPERTY"); + } + else { + struct.add(BYTE, "FieldOrProp", "Unknown value"); + } + + struct.add(BYTE, "FieldOrPropType", cliNamedArg.getFieldOrPropType().name()); + + int nameLen = cliNamedArg.getFieldOrPropName().length(); + if (nameLen < CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_128) { + struct.add(BYTE, "PackedLen", ""); + } + else if (nameLen < CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_192) { + struct.add(WORD, "PackedLen", ""); + } + else { + struct.add(DWORD, "PackedLen", ""); + + } + + struct.add(UTF8, nameLen, "FieldOrPropName", ""); + } + } + + return struct; + } + + @Override + public String getContentsName() { + return "CustomAttrib"; + } + + @Override + public String getContentsComment() { + return "A CustomAttrib blob stores values of fixed or named parameters supplied when " + + "instantiating a custom attribute"; + } + + @Override + public String getRepresentation() { + return "Blob (" + getContentsDataType().getDisplayName() + ")"; + } + + // SerStrings ("serialized strings") have a length field that varies in size + // based on the length of the string. This measures and decodes the Byte, Word, + // or DWord length field and returns it. + private int readSerStringLength(BinaryReader reader) throws IOException { + byte[] lengthBytes; + int length = 0; + ByteBuffer buf; + + // The first byte is either the size or an indicator that we have more + // size bytes ahead. Values contained in more than one bytes are stored + // big-endian. + byte firstByte = reader.readNextByte(); + + // Shift the highest two bits to the bottom and mask off to detect the + // size of the field holding the size of the string (1, 2, or 4 bytes), + // then cut the indicator bits off the first byte of the length. + byte stringSizeIndicator = (byte) (firstByte >> CLIBLOBCUSTOMATTRIB_STRING_INDICATOR_SHIFT & + CLIBLOBCUSTOMATTRIB_STRING_INDICATOR_BITMASK); + firstByte = (byte) (firstByte & CLIBLOBCUSTOMATTRIB_STRING_SIZE_BITMASK); + + if (stringSizeIndicator <= CLIBLOBCUSTOMATTRIB_STRING_SIZE_ONE) { + length = firstByte; + } + else if (stringSizeIndicator == CLIBLOBCUSTOMATTRIB_STRING_SIZE_TWO) { + lengthBytes = new byte[] { firstByte, reader.readNextByte() }; + + // Convert from big-endian + buf = ByteBuffer.wrap(lengthBytes); + buf.order(ByteOrder.BIG_ENDIAN); + length = buf.getShort(); + } + else if (stringSizeIndicator == CLIBLOBCUSTOMATTRIB_STRING_SIZE_FOUR) { + lengthBytes = new byte[] { firstByte, reader.readNextByte(), reader.readNextByte(), + reader.readNextByte() }; + + // Convert from big-endian + buf = ByteBuffer.wrap(lengthBytes); + buf.order(ByteOrder.BIG_ENDIAN); + length = buf.getInt(); + } + + return length; + } + + private ArrayList processFixedArgs(BinaryReader reader, CliParam[] params) + throws IOException { + ArrayList processFixedArgs = new ArrayList<>(); + if (params == null) { + return processFixedArgs; + } + + for (CliParam param : params) { + byte elemByte = reader.peekNextByte(); + if (elemByte == CliElementType.ELEMENT_TYPE_I.id()) { + reader.readNextByte(); + + // IntPtr followed by a string of the name of the element, the + // length of which is not specified and must be read until a + // non-printable UTF-8 character is encountered to signal the + // end of the name + + StringBuilder sb = new StringBuilder(); + while (((reader.peekNextByte() & + CLIBLOBCUSTOMATTRIB_UTF8_HIGH) > CLIBLOBCUSTOMATTRIB_UTF8_LOW) && + ((reader.peekNextByte() & + CLIBLOBCUSTOMATTRIB_UTF8_HIGH) < CLIBLOBCUSTOMATTRIB_UTF8_HIGH)) { + sb.append((char) reader.readNextByte()); + } + + processFixedArgs.add(new CliFixedArg(CliElementType.ELEMENT_TYPE_I, sb.toString())); + } + else { + // Process Elem types + CliElementType baseTypeCode = param.getType().baseTypeCode; + switch (baseTypeCode) { + case ELEMENT_TYPE_BOOLEAN: + addFixedArg(processFixedArgs, baseTypeCode, reader.readNextByte()); + break; + + case ELEMENT_TYPE_CHAR: + addFixedArg(processFixedArgs, baseTypeCode, reader.readNextShort()); + break; + + case ELEMENT_TYPE_I1: + addFixedArg(processFixedArgs, baseTypeCode, reader.readNextByte()); + break; + + case ELEMENT_TYPE_U1: + addFixedArg(processFixedArgs, baseTypeCode, reader.readNextUnsignedByte()); + break; + + case ELEMENT_TYPE_I2: + addFixedArg(processFixedArgs, baseTypeCode, reader.readNextShort()); + break; + + case ELEMENT_TYPE_U2: + addFixedArg(processFixedArgs, baseTypeCode, reader.readNextUnsignedShort()); + break; + + case ELEMENT_TYPE_I4: + addFixedArg(processFixedArgs, baseTypeCode, reader.readNextInt()); + break; + + case ELEMENT_TYPE_U4: + addFixedArg(processFixedArgs, baseTypeCode, reader.readNextUnsignedInt()); + break; + + case ELEMENT_TYPE_I8: + addFixedArg(processFixedArgs, baseTypeCode, reader.readNextByte()); + processFixedArgs.add( + new CliFixedArg(param.getType().baseTypeCode, reader.readNextLong())); + break; + + case ELEMENT_TYPE_U8: + addFixedArg(processFixedArgs, baseTypeCode, + reader.readNextByteArray(LongLongDataType.dataType.getLength())); + break; + + case ELEMENT_TYPE_R4: + addFixedArg(processFixedArgs, baseTypeCode, + reader.readNextByteArray(Float4DataType.dataType.getLength())); + break; + + case ELEMENT_TYPE_R8: + addFixedArg(processFixedArgs, baseTypeCode, + reader.readNextByteArray(Float8DataType.dataType.getLength())); + break; + + case ELEMENT_TYPE_STRING: + int length = readSerStringLength(reader); + if (length > 0) { + addFixedArg(processFixedArgs, baseTypeCode, new String( + reader.readNextByteArray(length), StandardCharsets.UTF_8)); + } + break; + + case ELEMENT_TYPE_VALUETYPE: + addFixedArg(processFixedArgs, baseTypeCode, reader.readNextInt()); + break; + + default: + Msg.info(this, + "A CustomAttrib with an unprocessed element type was deteceted: " + + param.getRepresentation()); + } + } + } + + return processFixedArgs; + } + + private void addFixedArg(ArrayList fixedArgs, CliElementType baseTypeCode, + Object value) { + fixedArgs.add(new CliFixedArg(baseTypeCode, value)); + } + + private ArrayList processNamedArgs(BinaryReader reader) throws IOException { + numNamed = reader.readNextShort(); + + // Process zero to multiple NamedArgs here + ArrayList processNamedArgs = new ArrayList<>(); + for (int i = 0; i < numNamed; i++) { + byte fieldOrProp = reader.readNextByte(); + if ((fieldOrProp != CLIBLOBCUSTOMATTRIB_TYPE_FIELD) && + fieldOrProp != CLIBLOBCUSTOMATTRIB_TYPE_PROPERTY) { + Msg.warn(this, "Invalid FieldOrProp value in NamedArg #" + (i + 1) + ": 0x" + + Integer.toHexString(fieldOrProp)); + continue; + } + + CliElementType fieldOrPropType = CliElementType.fromInt(reader.readNextByte()); + + // +1 to account for the null terminator + int nameLen = readSerStringLength(reader) + 1; + String fieldOrPropName = + new String(reader.readNextByteArray(nameLen), StandardCharsets.UTF_8); + + processNamedArgs.add(new CliNamedArg(fieldOrProp, fieldOrPropType, fieldOrPropName)); + } + + return processNamedArgs; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigField.java index 22a4331886..19f00c6ff2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigField.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigField.java @@ -26,12 +26,14 @@ import ghidra.util.exception.InvalidInputException; public class CliSigField extends CliAbstractSig { private CliParam type; + private long dataOffset; private static final byte CLISIGFIELD_PROLOG = 0x06; public CliSigField(CliBlob blob) throws IOException { super(blob); BinaryReader reader = getContentsReader(); + dataOffset = reader.getPointerIndex(); byte prolog = reader.readNextByte(); if (prolog != CLISIGFIELD_PROLOG) { @@ -67,7 +69,7 @@ public class CliSigField extends CliAbstractSig { public DataType getContentsDataType() { StructureDataType struct = new StructureDataType(new CategoryPath(PATH), getName(), 0); struct.add(BYTE, "FIELD", "Magic (0x06)"); - struct.add(type.getDefinitionDataType(), "Type", null); + struct.add(type.getDefinitionDataType(), type.getType().baseTypeCode.toString(), null); return struct; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigMethodDef.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigMethodDef.java index 6ffb69f5d9..687ba588f8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigMethodDef.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigMethodDef.java @@ -85,17 +85,19 @@ public class CliSigMethodDef extends CliAbstractSig { @Override public DataType getContentsDataType() { StructureDataType struct = new StructureDataType(new CategoryPath(PATH), getName(), 0); - struct.add(BYTE, "flags", "ORed calling convention and THIS presence"); // TODO: enum + struct.add(BYTE, "Flags", "ORed VARARG/GENERIC/HASTHIS/EXPLICITTHIS"); // TODO: enum if (genericParamCount > 0) { struct.add(getDataTypeForBytes(sizeOfGenericCount), "GenParamCount", "Number of generic paramameters for the method"); } struct.add(getDataTypeForBytes(sizeOfCount), "Count", - "Number of param types to follow RetType"); + "Number of parameter types to follow RetType"); struct.add(retType.getDefinitionDataType(), "RetType", null); - for (CliParam param : params) { - struct.add(param.getDefinitionDataType(), null, null); + + for (int i = 0; i < params.length; i++) { + struct.add(params[i].getDefinitionDataType(), "Param" + i, null); } + return struct; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigMethodRef.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigMethodRef.java index 7e423ff732..c5e3734dbb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigMethodRef.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/blobs/CliSigMethodRef.java @@ -23,7 +23,7 @@ import ghidra.program.model.data.*; import ghidra.util.exception.InvalidInputException; public class CliSigMethodRef extends CliAbstractSig { - + private long dataOffset; private CliRetType retType; private CliParam params[]; private int sizeOfCount; @@ -45,6 +45,8 @@ public class CliSigMethodRef extends CliAbstractSig { // Flags is similar to a MethodDef unless vararg is used. BinaryReader reader = getContentsReader(); + dataOffset = reader.getPointerIndex(); + flags = reader.readNextByte(); if ((flags & METHODREFSIG_FLAGS_GENERIC) == METHODREFSIG_FLAGS_GENERIC) { @@ -90,16 +92,21 @@ public class CliSigMethodRef extends CliAbstractSig { @Override public DataType getContentsDataType() { StructureDataType struct = new StructureDataType(new CategoryPath(PATH), getName(), 0); - struct.add(BYTE, "FirstByte", "ORed VARARG and HASTHIS/EXPLICITTHIS"); + struct.add(BYTE, "Flags", "ORed VARARG/GENERIC/HASTHIS/EXPLICITTHIS"); if (genericParamCount > 0) { struct.add(getDataTypeForBytes(sizeOfGenericCount), "GenParamCount", "Number of generic paramameters for the method"); } struct.add(getDataTypeForBytes(sizeOfCount), "ParamCount", - "Number of param types to follow RetType"); + "Number of parameter types to follow RetType"); struct.add(retType.getDefinitionDataType(), "RetType", null); for (int i = 0; i < params.length; i++) { - struct.add(params[i].getDefinitionDataType(), "Type" + i, null); + if (sentinelIndex == i) { + struct.add(CliTypeCodeDataType.dataType, + CliElementType.ELEMENT_TYPE_SENTINEL.toString(), "SENTINEL"); + } + + struct.add(params[i].getDefinitionDataType(), "Param" + i, null); } return struct; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/methods/CliMethodDef.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/methods/CliMethodDef.java index a8186c6ecb..fb63c99122 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/methods/CliMethodDef.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/methods/CliMethodDef.java @@ -4,9 +4,9 @@ * 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. @@ -24,69 +24,81 @@ import ghidra.program.model.data.*; import ghidra.util.exception.DuplicateNameException; public class CliMethodDef implements StructConverter { - + public static final String PATH = "/PE/CLI/Methods/MethodDefs"; private Address addr; - private boolean isFatHeader; - private boolean hasMoreSections; - private boolean initLocals; + public enum HeaderFormat { + Fat, Tiny + } + + private int headerFlags; + private HeaderFormat headerFormat; + private int headerSize; // Size of this header private int maxStack; // Max number of items on operand stack private int methodSize; // Size of method body (code) private int localVarSigTok; - + + private static final int CLIMETHODDEF_HEADER_FLAGS_SHIFT = 0x08; + private static final int CLIMETHODDEF_HEADER_FLAGS_MASK = 0x0FFF; + private static final int CLIMETHODDEF_HEADER_SIZE_SHIFT = 0x0C; + private static final int CLIMETHODDEF_HEADER_SIZE_FAT_MULTIPLIER = 0x04; + private static final byte CorILMethod_TinyFormat = 0x2; private static final byte CorILMethod_FatFormat = 0x3; private static final byte CorILMethod_MoreSects = 0x8; private static final byte CorILMethod_InitLocals = 0x10; - + public CliMethodDef(Address addr, BinaryReader reader) throws IOException { this.addr = addr; // Read first byte, see if tiny or fat. - byte one = reader.readNextByte(); - if ((one & CorILMethod_FatFormat) == CorILMethod_FatFormat) { - isFatHeader = true; - if ((one & CorILMethod_MoreSects) == CorILMethod_MoreSects) - hasMoreSections = true; - if ((one & CorILMethod_InitLocals) == CorILMethod_InitLocals) - initLocals = true; - byte two = reader.readNextByte(); // TODO: need to read byte two? Seems to only have header length (in the wrong order?? >_<) + int firstByte = reader.readNextUnsignedByte(); + if ((firstByte & CorILMethod_FatFormat) == CorILMethod_FatFormat) { + headerFormat = HeaderFormat.Fat; + + // The header flags are stored across 12 bits, the top 4 bits + // indicate the size of the header + headerFlags = + (firstByte << CLIMETHODDEF_HEADER_FLAGS_SHIFT) + reader.readNextUnsignedByte(); + headerSize = headerFlags >> CLIMETHODDEF_HEADER_SIZE_SHIFT; + headerFlags = (headerFlags & CLIMETHODDEF_HEADER_FLAGS_MASK); + + // The raw header size bits indicate: "Size of this header + // expressed as the count of 4-bytes integers occupied." + headerSize = headerSize * CLIMETHODDEF_HEADER_SIZE_FAT_MULTIPLIER; + maxStack = reader.readNextShort(); methodSize = reader.readNextInt(); localVarSigTok = reader.readNextInt(); } - else if ((one & CorILMethod_TinyFormat) == CorILMethod_TinyFormat) { - isFatHeader = false; - hasMoreSections = false; - initLocals = false; + else if ((firstByte & CorILMethod_TinyFormat) == CorILMethod_TinyFormat) { + headerFormat = HeaderFormat.Tiny; + headerSize = 1; + headerFlags = 0; maxStack = 8; - methodSize = (((one & ~0x3) & 0xff) >> 2); // Mask off first 2 bits, right shift to get 6 length bits. 0xff mask to convert to right sign. + methodSize = (((firstByte & ~0x3) & 0xff) >> 2); // Mask off first 2 bits, right shift to get 6 length bits. 0xff mask to convert to right sign. } } - - private void fillTinyHeaderType(Structure struct) { - struct.add(BYTE, "Size+Flags", "L.S. Bits 0:1 Flags, Bits 2:7 Size of method in Bytes"); - } - - private void fillFatHeaderType(Structure struct) { - struct.add( WORD, "Size+Flags", "L.S. Bits 0:3 Size of hdr in B, Bits 4:15 Flags"); - struct.add( WORD, "MaxStack", "Maximum number of items on the operand stack"); - struct.add(DWORD, "CodeSize", "Size of actual method body in B"); - struct.add(DWORD, "LocalVarSigTok", "Signature for the local variables of the method. 0 means no locals. References standalone signature in Metadata tables, which references #Blob heap."); - } - + @Override public DataType toDataType() throws DuplicateNameException, IOException { - StructureDataType struct = - new StructureDataType(new CategoryPath(PATH), "MethodDefHdr_" + addr, 0); - if (isFatHeader) { - fillFatHeaderType(struct); + StructureDataType struct; + + if (headerFormat == HeaderFormat.Fat) { + struct = new StructureDataType(new CategoryPath(PATH), "MethodDefHdr_Fat", 0); + struct.add(WORD, "Size+Flags", "L.S. Bits 0:3 Size of hdr in bytes, Bits 4:15 Flags"); + struct.add(WORD, "MaxStack", "Maximum number of items on the operand stack"); + struct.add(DWORD, "CodeSize", "Size of actual method body in bytes"); + struct.add(DWORD, "LocalVarSigTok", + "Signature for the local variables of the method. 0 means no locals. References standalone signature in Metadata tables, which references #Blob heap."); } else { - fillTinyHeaderType(struct); + struct = new StructureDataType(new CategoryPath(PATH), "MethodDefHdr_Tiny", 0); + struct.add(BYTE, "Size+Flags", "L.S. Bits 0:1 Flags, Bits 2:7 Size of method in Bytes"); } + return struct; } @@ -95,7 +107,14 @@ public class CliMethodDef implements StructConverter { } public boolean hasMoreSections() { - return hasMoreSections; + return (headerFlags & CorILMethod_MoreSects) == CorILMethod_MoreSects; } + public boolean hasLocals() { + return (headerFlags & CorILMethod_InitLocals) == CorILMethod_InitLocals; + } + + public HeaderFormat getHeaderFormat() { + return headerFormat; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/streams/CliStreamBlob.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/streams/CliStreamBlob.java index 7432ac7ed7..87b3ea13f2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/streams/CliStreamBlob.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/streams/CliStreamBlob.java @@ -4,9 +4,9 @@ * 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. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/streams/CliStreamMetadata.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/streams/CliStreamMetadata.java index 65d8faf138..5b6b437656 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/streams/CliStreamMetadata.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/streams/CliStreamMetadata.java @@ -35,7 +35,7 @@ import ghidra.util.task.TaskMonitor; /** * The Metadata stream is giant and complicated. It is made up of {@link CliAbstractTable}s. - * + * * @see CliTypeTable */ public class CliStreamMetadata extends CliAbstractStream { @@ -55,7 +55,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the name of this stream. - * + * * @return The name of this stream. */ public static String getName() { @@ -64,13 +64,13 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Creates a new Metadata stream. - * + * * @param header The stream header associated with this stream. * @param guidStream The GUID stream. * @param userStringsStream The user strings stream. * @param stringsStream The strings stream. * @param blobStream The blob stream. - * @param fileOffset The file offset where this stream starts. + * @param fileOffset The file offset where this stream starts. * @param rva The relative virtual address where this stream starts. * @param reader A reader that is set to the start of the stream. * @throws IOException if there is a problem reading the stream. @@ -133,7 +133,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the GUID stream. - * + * * @return The GUID stream. Could be null if one doesn't exist. */ public CliStreamGuid getGuidStream() { @@ -142,7 +142,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the user strings stream. - * + * * @return The user strings stream. Could be null if one doesn't exist. */ public CliStreamUserStrings getUserStringsStream() { @@ -151,7 +151,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the strings stream. - * + * * @return The strings stream. Could be null if one doesn't exist. */ public CliStreamStrings getStringsStream() { @@ -160,7 +160,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the blob stream. - * + * * @return The blob stream. Could be null if one doesn't exist. */ public CliStreamBlob getBlobStream() { @@ -170,7 +170,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Creates a new {@link CliAbstractTable} from the table at the current reader index * with the given table type. - * + * * @param tableType The type of table to create. * @return A new table with the given type. Could be null if we don't support the table type. * @throws IOException if there was an issue reading the new table. @@ -300,7 +300,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the major version. - * + * * @return The major version. */ public short getMajorVersion() { @@ -309,7 +309,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the minor version. - * + * * @return The minor version. */ public short getMinorVersion() { @@ -318,7 +318,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the sorted field. - * + * * @return The sorted field. */ public long getSorted() { @@ -327,7 +327,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the valid field. - * + * * @return The valid field. */ public long getValid() { @@ -336,7 +336,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the table with the provided table type from the metadata stream. - * + * * @param tableType The type of table to get. * @return The table with the provided table type. Could be null if it doesn't exist. */ @@ -360,7 +360,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the table with the provided table type id from the metadata stream. - * + * * @param tableId The id of the table type to get. * @return The table with the provided table id. Could be null if it doesn't exist. */ @@ -370,9 +370,9 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the number of rows in the table with the given table type. - * + * * @param tableType The type of table to get the number of rows of. - * @return The number of rows in the table with the given table type. Could be 0 if + * @return The number of rows in the table with the given table type. Could be 0 if * the table of the given type was not found. */ public int getNumberRowsForTable(CliTypeTable tableType) { @@ -383,7 +383,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the data type of the index into the string stream. Will be either * {@link DWordDataType} or {@link WordDataType}. - * + * * @return The data type of the index into the string stream. */ public DataType getStringIndexDataType() { @@ -393,7 +393,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the data type of the index into the GUID stream. Will be either * {@link DWordDataType} or {@link WordDataType}. - * + * * @return The data type of the index into the string stream. */ public DataType getGuidIndexDataType() { @@ -403,7 +403,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the data type of the index into the Blob stream. Will be either * {@link DWordDataType} or {@link WordDataType}. - * + * * @return The data type of the index into the string stream. */ public DataType getBlobIndexDataType() { @@ -413,7 +413,7 @@ public class CliStreamMetadata extends CliAbstractStream { /** * Gets the data type of the index into a metadata table. Will be either * {@link DWordDataType} or {@link WordDataType}. - * + * * @return The data type of the index into the string stream. */ public DataType getTableIndexDataType(CliTypeTable table) { @@ -435,7 +435,7 @@ public class CliStreamMetadata extends CliAbstractStream { table.markup(program, isBinary, monitor, log, ntHeader); } catch (Exception e) { - Msg.error(this, "Failed to markup " + table); + Msg.error(this, "Failed to markup " + table + ": " + e.getMessage()); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableCustomAttribute.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableCustomAttribute.java index 4f5bad550f..6323c15a43 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableCustomAttribute.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableCustomAttribute.java @@ -18,22 +18,30 @@ package ghidra.app.util.bin.format.pe.cli.tables; import java.io.IOException; import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.format.pe.cli.streams.CliStreamMetadata; +import ghidra.app.util.bin.format.pe.NTHeader; +import ghidra.app.util.bin.format.pe.cli.blobs.CliBlobCustomAttrib; +import ghidra.app.util.bin.format.pe.cli.streams.*; import ghidra.app.util.bin.format.pe.cli.tables.indexes.CliIndexCustomAttributeType; import ghidra.app.util.bin.format.pe.cli.tables.indexes.CliIndexHasCustomAttribute; +import ghidra.app.util.importer.MessageLog; +import ghidra.program.model.address.Address; import ghidra.program.model.data.CategoryPath; import ghidra.program.model.data.StructureDataType; +import ghidra.program.model.listing.Program; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; /** - * Describes the CustomAttribute table. + * Describes the CustomAttribute table. */ public class CliTableCustomAttribute extends CliAbstractTable { public class CliCustomAttributeRow extends CliAbstractTableRow { public int parentIndex; public int typeIndex; public int valueIndex; - + public CliCustomAttributeRow(int parentIndex, int typeIndex, int valueIndex) { super(); this.parentIndex = parentIndex; @@ -45,13 +53,17 @@ public class CliTableCustomAttribute extends CliAbstractTable { public String getRepresentation() { String parentRep, typeRep; try { - parentRep = getRowRepresentationSafe(CliIndexHasCustomAttribute.getTableName(parentIndex), CliIndexHasCustomAttribute.getRowIndex(parentIndex)); + parentRep = + getRowRepresentationSafe(CliIndexHasCustomAttribute.getTableName(parentIndex), + CliIndexHasCustomAttribute.getRowIndex(parentIndex)); } catch (InvalidInputException e) { parentRep = Integer.toHexString(parentIndex); } try { - typeRep = getRowRepresentationSafe(CliIndexCustomAttributeType.getTableName(parentIndex), CliIndexCustomAttributeType.getRowIndex(parentIndex)); + typeRep = + getRowRepresentationSafe(CliIndexCustomAttributeType.getTableName(parentIndex), + CliIndexCustomAttributeType.getRowIndex(parentIndex)); } catch (InvalidInputException e) { typeRep = Integer.toHexString(typeIndex); @@ -59,23 +71,47 @@ public class CliTableCustomAttribute extends CliAbstractTable { return String.format("Parent %s Type %s Value %x", parentRep, typeRep, valueIndex); } } - public CliTableCustomAttribute(BinaryReader reader, CliStreamMetadata stream, CliTypeTable tableId) throws IOException { + + public CliTableCustomAttribute(BinaryReader reader, CliStreamMetadata stream, + CliTypeTable tableId) throws IOException { super(reader, stream, tableId); for (int i = 0; i < this.numRows; i++) { - CliCustomAttributeRow row = new CliCustomAttributeRow(CliIndexHasCustomAttribute.readCodedIndex(reader, stream), + CliCustomAttributeRow row = new CliCustomAttributeRow( + CliIndexHasCustomAttribute.readCodedIndex(reader, stream), CliIndexCustomAttributeType.readCodedIndex(reader, stream), readBlobIndex(reader)); rows.add(row); blobs.add(row.valueIndex); } reader.setPointerIndex(this.readerOffset); } - + @Override public StructureDataType getRowDataType() { - StructureDataType rowDt = new StructureDataType(new CategoryPath(PATH), "CustomAttribute Row", 0); + StructureDataType rowDt = + new StructureDataType(new CategoryPath(PATH), "CustomAttribute Row", 0); rowDt.add(CliIndexHasCustomAttribute.toDataType(metadataStream), "Parent", null); rowDt.add(CliIndexCustomAttributeType.toDataType(metadataStream), "Type", null); rowDt.add(metadataStream.getBlobIndexDataType(), "Value", null); return rowDt; } + + @Override + public void markup(Program program, boolean isBinary, TaskMonitor monitor, MessageLog log, + NTHeader ntHeader) + throws DuplicateNameException, CodeUnitInsertionException, IOException { + CliStreamBlob blobStream = metadataStream.getBlobStream(); + + for (CliAbstractTableRow row : rows) { + CliCustomAttributeRow customRow = (CliCustomAttributeRow) row; + int valueIndex = customRow.valueIndex; + + Address addr = CliAbstractStream.getStreamMarkupAddress(program, isBinary, monitor, log, + ntHeader, blobStream, valueIndex); + + // Create CustomAttrib Blob object + CliBlobCustomAttrib blob = + new CliBlobCustomAttrib(blobStream.getBlob(valueIndex), customRow, metadataStream); + blobStream.updateBlob(blob, addr, program); + } + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableField.java index 93f2102206..2122af05cc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableField.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableField.java @@ -30,6 +30,7 @@ import ghidra.program.model.data.CategoryPath; import ghidra.program.model.data.StructureDataType; import ghidra.program.model.listing.Program; import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.util.Msg; import ghidra.util.exception.DuplicateNameException; import ghidra.util.task.TaskMonitor; @@ -100,16 +101,24 @@ public class CliTableField extends CliAbstractTable { public void markup(Program program, boolean isBinary, TaskMonitor monitor, MessageLog log, NTHeader ntHeader) throws DuplicateNameException, CodeUnitInsertionException, IOException { + int fieldRowIndex = 0; for (CliAbstractTableRow row : rows) { CliFieldRow fieldRow = (CliFieldRow) row; + fieldRowIndex++; - // Create FieldSig object and bookmark it + // Create FieldSig object Address sigAddr = CliAbstractStream.getStreamMarkupAddress(program, isBinary, monitor, log, ntHeader, metadataStream.getBlobStream(), fieldRow.sigIndex); CliSigField fieldSig = new CliSigField(metadataStream.getBlobStream().getBlob(fieldRow.sigIndex)); - metadataStream.getBlobStream().updateBlob(fieldSig, sigAddr, program); + + if (!metadataStream.getBlobStream().updateBlob(fieldSig, sigAddr, program)) { + Msg.warn(CliTableField.class, + "Couldn't update FieldSig blob " + + metadataStream.getStringsStream().getString(fieldRow.nameIndex) + + " at Field table index " + fieldRowIndex); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableMethodDef.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableMethodDef.java index 4ea7aaf787..f6a841479a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableMethodDef.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableMethodDef.java @@ -4,9 +4,9 @@ * 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. @@ -16,14 +16,13 @@ package ghidra.app.util.bin.format.pe.cli.tables; import java.io.IOException; -import java.util.Arrays; -import java.util.List; +import java.util.*; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.MemoryByteProvider; import ghidra.app.util.bin.format.pe.NTHeader; import ghidra.app.util.bin.format.pe.PeUtils; -import ghidra.app.util.bin.format.pe.cli.blobs.CliAbstractSig.CliParam; +import ghidra.app.util.bin.format.pe.cli.blobs.CliAbstractSig.*; import ghidra.app.util.bin.format.pe.cli.blobs.CliBlob; import ghidra.app.util.bin.format.pe.cli.blobs.CliSigMethodDef; import ghidra.app.util.bin.format.pe.cli.methods.CliMethodDef; @@ -31,6 +30,8 @@ import ghidra.app.util.bin.format.pe.cli.methods.CliMethodExtraSections; import ghidra.app.util.bin.format.pe.cli.streams.CliAbstractStream; import ghidra.app.util.bin.format.pe.cli.streams.CliStreamMetadata; import ghidra.app.util.bin.format.pe.cli.tables.CliTableParam.CliParamRow; +import ghidra.app.util.bin.format.pe.cli.tables.CliTableTypeDef.CliTypeDefRow; +import ghidra.app.util.bin.format.pe.cli.tables.CliTableTypeRef.CliTypeRefRow; import ghidra.app.util.bin.format.pe.cli.tables.flags.CliFlags.CliEnumMethodAttributes; import ghidra.app.util.bin.format.pe.cli.tables.flags.CliFlags.CliEnumMethodImplAttributes; import ghidra.app.util.importer.MessageLog; @@ -40,6 +41,7 @@ import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.model.listing.Function.FunctionUpdateType; import ghidra.program.model.symbol.SourceType; +import ghidra.program.model.symbol.SymbolUtilities; import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.util.Msg; import ghidra.util.exception.DuplicateNameException; @@ -52,6 +54,8 @@ import ghidra.util.task.TaskMonitor; */ public class CliTableMethodDef extends CliAbstractTable { + private static final int CLITABLEMETHODDEF_PINVOKE_JUMP_LENGTH = 0x06; + public class CliMethodDefRow extends CliAbstractTableRow { public int RVA; public short ImplFlags; // MethodImplAttributes @@ -63,6 +67,42 @@ public class CliTableMethodDef extends CliAbstractTable { private static final int NEXT_ROW_PARAM_INIT_VALUE = -1; private int nextRowParamIndex = NEXT_ROW_PARAM_INIT_VALUE; + private static final int METHODIMPLATTRIBUTES_CODETYPE_IL = 0x00; + private static final int METHODIMPLATTRIBUTES_CODETYPE_NATIVE = 0x01; + private static final int METHODIMPLATTRIBUTES_CODETYPE_OPTIL = 0x02; + private static final int METHODIMPLATTRIBUTES_CODETYPE_RUNTIME = 0x03; + private static final int METHODIMPLATTRIBUTES_MANAGED_MANAGED = 0x00; + private static final int METHODIMPLATTRIBUTES_MANAGED_UNMANAGED = 0x04; + private static final int METHODIMPLATTRIBUTES_FORWARDREF = 0x10; + private static final int METHODIMPLATTRIBUTES_PRESERVESIG = 0x80; + private static final int METHODIMPLATTRIBUTES_INTERNALCALL = 0x1000; + private static final int METHODIMPLATTRIBUTES_SYNCHRONIZED = 0x20; + private static final int METHODIMPLATTRIBUTES_NOINLINING = 0x08; + private static final int METHODIMPLATTRIBUTES_AGGRESSIVEINLINING = 0x1000; + private static final int METHODIMPLATTRIBUTES_MAXMETHODIMPLVAL = 0xffff; + + private static final int METHODATTRIBUTES_MEMBERACCESS_COMPILERCONTROLLED = 0x00; + private static final int METHODATTRIBUTES_MEMBERACCESS_PRIVATE = 0x01; + private static final int METHODATTRIBUTES_MEMBERACCESS_FAMANDASSEM = 0x02; + private static final int METHODATTRIBUTES_MEMBERACCESS_ASSEM = 0x03; + private static final int METHODATTRIBUTES_MEMBERACCESS_FAMILY = 0x04; + private static final int METHODATTRIBUTES_MEMBERACCESS_FAMORASSEM = 0x05; + private static final int METHODATTRIBUTES_MEMBERACCESS_PUBLIC = 0x06; + private static final int METHODATTRIBUTES_STATIC = 0x10; + private static final int METHODATTRIBUTES_FINAL = 0x20; + private static final int METHODATTRIBUTES_VIRTUAL = 0x40; + private static final int METHODATTRIBUTES_HIDEBYSIG = 0x80; + private static final int METHODATTRIBUTES_VTABLELAYOUT_REUSESLOT = 0x0000; + private static final int METHODATTRIBUTES_VTABLELAYOUT_NEWSLOT = 0x0100; + private static final int METHODATTRIBUTES_STRICT = 0x0200; + private static final int METHODATTRIBUTES_ABSTRACT = 0x0400; + private static final int METHODATTRIBUTES_SPECIALNAME = 0x0800; + private static final int METHODATTRIBUTES_PINVOKEIMPL = 0x2000; + private static final int METHODATTRIBUTES_UNMANAGEDEXPORT = 0x08; + private static final int METHODATTRIBUTES_RTSPECIALNAME = 0x1000; + private static final int METHODATTRIBUTES_HASSECURITY = 0x4000; + private static final int METHODATTRIBUTES_REQUIRESECOBJECT = 0x8000; + public CliMethodDefRow(int rva, short implFlags, short flags, int nameIndex, int sigIndex, int paramIndex) { this.RVA = rva; @@ -141,6 +181,28 @@ public class CliTableMethodDef extends CliAbstractTable { CliEnumMethodImplAttributes.dataType.getName(ImplFlags & 0xffff), CliEnumMethodAttributes.dataType.getName(Flags & 0xffff)); } + + // Static functions have four parameters but the first is an implied + // pointer to the object they're associated with. It's not in the + // ParameterTable and looking for the extra parameter will walk + // you into the parameter of another function. + boolean isStatic() { + return (Flags & METHODATTRIBUTES_STATIC) == METHODATTRIBUTES_STATIC; + } + + boolean isPInvokeImpl() { + return (Flags & METHODATTRIBUTES_PINVOKEIMPL) == METHODATTRIBUTES_PINVOKEIMPL; + } + + boolean isNative() { + return (ImplFlags & + METHODIMPLATTRIBUTES_CODETYPE_NATIVE) == METHODIMPLATTRIBUTES_CODETYPE_NATIVE; + } + + boolean isManaged() { + return (ImplFlags & + METHODIMPLATTRIBUTES_CODETYPE_IL) == METHODIMPLATTRIBUTES_CODETYPE_IL; + } } public CliTableMethodDef(BinaryReader reader, CliStreamMetadata stream, CliTypeTable tableId) @@ -159,7 +221,7 @@ public class CliTableMethodDef extends CliAbstractTable { } lastRow = row; } - reader.setPointerIndex(this.readerOffset); // TODO: why do this, also elsewhere + reader.setPointerIndex(this.readerOffset); } @Override @@ -167,54 +229,64 @@ public class CliTableMethodDef extends CliAbstractTable { NTHeader ntHeader) throws DuplicateNameException, CodeUnitInsertionException, IOException { - int rvaZero = 0; - + int methodRowIndex = 0; for (CliAbstractTableRow method : rows) { + methodRowIndex++; + CliMethodDefRow methodRow = (CliMethodDefRow) method; // This indicates the method is abstract, runtime, or PInvokeImpl if (methodRow.RVA == 0) { - rvaZero++; continue; } Address addr = PeUtils.getMarkupAddress(program, isBinary, ntHeader, methodRow.RVA); + Address startAddr = addr; + Address endAddr = addr; - // Create MethodDef at this RVA - BinaryReader reader = - new BinaryReader(new MemoryByteProvider(program.getMemory(), addr), - !program.getMemory().isBigEndian()); - CliMethodDef methodDef = new CliMethodDef(addr, reader); - - PeUtils.createData(program, addr, methodDef.toDataType(), log); - - // Get the function's address space, default to zero-length just in case - Address startAddr = addr.add(methodDef.toDataType().getLength()); - Address endAddr = startAddr; - if (methodDef.getMethodSize() > 0) { - endAddr = startAddr.add(methodDef.getMethodSize() - 1); + if (methodRow.isPInvokeImpl() && methodRow.isNative()) { + endAddr = startAddr.add(CLITABLEMETHODDEF_PINVOKE_JUMP_LENGTH - 1); } + else { + // Create MethodDef at this RVA + BinaryReader reader = + new BinaryReader(new MemoryByteProvider(program.getMemory(), addr), + !program.getMemory().isBigEndian()); + CliMethodDef methodDef = new CliMethodDef(addr, reader); + + DataType methodDefDataType = methodDef.toDataType(); + PeUtils.createData(program, addr, methodDefDataType, log); + + // Get the function's address space, default to zero-length just in case + startAddr = addr.add(methodDefDataType.getLength()); + endAddr = startAddr; + if (methodDef.getMethodSize() > 0) { + endAddr = startAddr.add(methodDef.getMethodSize() - 1); + } + + // Do extra data sections in MethodDef + if (methodDef.hasMoreSections()) { + int extraSectionOffset = + methodDefDataType.getLength() + methodDef.getMethodSize(); + + // Round up to the next offset divisible by 4 + extraSectionOffset = ((extraSectionOffset + 3) / 4) * 4; + + reader.setPointerIndex(extraSectionOffset); + CliMethodExtraSections extraSections = new CliMethodExtraSections(reader); + Address extraSectionAddr = addr.add(extraSectionOffset); + PeUtils.createData(program, extraSectionAddr, extraSections.toDataType(), log); + } + } + AddressSetView funcAddrSet = new AddressSet(startAddr, endAddr); // Let Ghidra assign a default function name and then try to decode the // real one if it exists String funcName = null; if (methodRow.nameIndex > 0) { - funcName = metadataStream.getStringsStream().getString(methodRow.nameIndex); - } - - // Do extra data sections in MethodDef - if (methodDef.hasMoreSections()) { - int extraSectionOffset = - methodDef.toDataType().getLength() + methodDef.getMethodSize(); - - // Round up to the next offset divisible by 4 - extraSectionOffset = ((extraSectionOffset + 3) / 4) * 4; - - reader.setPointerIndex(extraSectionOffset); - CliMethodExtraSections extraSections = new CliMethodExtraSections(reader); - Address extraSectionAddr = addr.add(extraSectionOffset); - PeUtils.createData(program, extraSectionAddr, extraSections.toDataType(), log); + funcName = SymbolUtilities.replaceInvalidChars( + metadataStream.getStringsStream().getString(methodRow.nameIndex), true); } // Get the function signature blob @@ -227,27 +299,136 @@ public class CliTableMethodDef extends CliAbstractTable { metadataStream.getBlobStream().updateBlob(methodSig, sigAddr, program); DataType returnType = methodSig.getReturnType().getExecutionDataType(); - // Pull apart the function parameter names and types + int maxSequence = 0; int stackOffset = 0; CliParam paramTypes[] = methodSig.getParamTypes(); + int paramCount = paramTypes.length; CliTableParam paramTable = (CliTableParam) metadataStream.getTable(CliTypeTable.Param); - ParameterImpl parameters[] = new ParameterImpl[paramTypes.length]; - for (int i = 0; i < paramTypes.length; i++) { + // Store the parameters in a Hashtable because by the time processing + // finishes the number of actual parameters might change + HashMap parameterList = new HashMap(); + + // Some Static function first parameters being pointers to a ValueType + // have the same number of parameters specified, but one or more are implied + // pointers to the object they're associated with. It's not in the Parameter + // Table and looking for the extra parameter in the table will walk you + // into the parameter list of another function. + ParameterImpl staticParameter = null; + if (methodRow.isStatic() && paramCount > 0) { + CliParam staticParam = paramTypes[0]; + String paramName = ""; + + // Walk the path from the ELEMENT_TYPE_PTR to the ELEMENT_TYPE_VALUETYPE + if (staticParam.getType() instanceof CliTypePtr) { + CliTypePtr ptrToValueType = (CliTypePtr) staticParam.getType(); + if (ptrToValueType.getType() instanceof CliTypeValueType) { + CliTypeValueType valueType = (CliTypeValueType) ptrToValueType.getType(); + + // Get the table and row specifying the type name + CliTypeTable tableType = valueType.getTable(); + int rowIndex = valueType.getRowIndex(); + + int paramNameStringIndex = 0; + CliAbstractTable table = metadataStream.getTable(tableType); + CliAbstractTableRow row = table.getRow(rowIndex); + if (tableType.id() == tableType.TypeDef.id()) { + CliTypeDefRow typeDefRow = (CliTypeDefRow) row; + paramNameStringIndex = typeDefRow.typeNameIndex; + } + else if (tableType.id() == tableType.TypeRef.id()) { + CliTypeRefRow typeRefRow = (CliTypeRefRow) row; + paramNameStringIndex = typeRefRow.typeNameIndex; + } + + if (paramNameStringIndex > 0) { + paramName = + metadataStream.getStringsStream().getString(paramNameStringIndex); + paramName = SymbolUtilities.replaceInvalidChars(paramName, true); + + DataType dataType = staticParam.getExecutionDataType(); + + try { + staticParameter = + new ParameterImpl(paramName, dataType, stackOffset, program); + } + catch (InvalidInputException e) { + Msg.warn(this, "Error processing parameter \"" + paramName + + "\" in function \"" + funcName + "\": " + e.getMessage()); + } + + stackOffset += dataType.getLength(); + + paramCount--; + } + } + } + } + + // Pull apart the function's Param table entries + for (int i = 0; i < paramCount; i++) { CliParamRow paramRow = (CliParamRow) paramTable.getRow(methodRow.paramIndex + i); - String paramName = metadataStream.getStringsStream().getString(paramRow.nameIndex); + if (paramRow.sequence > maxSequence) { + maxSequence = paramRow.sequence; + } + + String paramName = SymbolUtilities.replaceInvalidChars( + metadataStream.getStringsStream().getString(paramRow.nameIndex), true); + DataType dataType = paramTypes[i].getExecutionDataType(); - try { - parameters[i] = new ParameterImpl(paramName, dataType, stackOffset, program); - } - catch (InvalidInputException e) { - Msg.warn(this, "Error processing parameter \"" + paramName + - "\" in function \"" + funcName + "\": " + e.getMessage()); + if (paramRow.sequence == 0) { + // Parameters with a 0 sequence number are the return type, + // reduce the size of the array and put any previously discovered + // parameters into it + returnType = dataType; } + else { + // Parameters are placed in the proper order based on the sequence + // field (1-based) to compensate for some static methods having an implied + // first parameter that won't be represented in the Parameter Table + // and some return types that are represented as parameters. + try { + parameterList.put((int) paramRow.sequence, + new ParameterImpl(paramName, dataType, stackOffset, program)); + } + catch (InvalidInputException e) { + Msg.warn(this, "Error processing parameter \"" + paramName + + "\" in function \"" + funcName + "\": " + e.getMessage()); + } - stackOffset += dataType.getLength(); + stackOffset += dataType.getLength(); + } + } + + ParameterImpl[] parameters = new ParameterImpl[maxSequence]; + parameterList.forEach((key, value) -> + // Sequences are 1-based + parameters[key - 1] = value); + + // For static functions, fill in the pointer to ValueType + // parameters that are implied before the actual parameters + if (methodRow.isStatic()) { + if (staticParameter != null) { + for (int i = 0; i < parameters.length; i++) { + if (parameters[i] == null) { + ParameterImpl param = null; + try { + param = new ParameterImpl(staticParameter.getName() + i, + staticParameter.getDataType(), staticParameter.getStackOffset(), + staticParameter.getProgram()); + } + catch (InvalidInputException e1) { + Msg.warn(this, + "Couldn't clone " + staticParameter.getName() + + " implied static function parameter in function : " + + funcName + "in position " + i); + } + parameters[i] = param; + } + } + } } try { @@ -257,15 +438,44 @@ public class CliTableMethodDef extends CliAbstractTable { newFunc.updateFunction(null, null, FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS, parameters); } + catch (NullPointerException e) { + Msg.warn(this, "Error processing function \"" + funcName + "\" (" + methodRowIndex + + "): Bad parameters provided"); + } catch (InvalidInputException e) { - Msg.warn(this, "Error processing function \"" + funcName + "\""); + Msg.warn(this, "Error processing function \" (\" + methodRowIndex + \")" + + funcName + "\": Invalid function"); } catch (OverlappingFunctionException e) { - Msg.warn(this, "Error processing function \"" + funcName + "\""); + String err = "Error processing function \" (\" + methodRowIndex + \")" + funcName + + "\": Overlapping function (" + startAddr + ", " + endAddr + ": "; + + Function existingFuncA = program.getFunctionManager().getFunctionAt(startAddr); + Function existingFuncB = program.getFunctionManager().getFunctionAt(endAddr); + + if (existingFuncA != null && existingFuncB == null) { + err = err + existingFuncA.getName(); + } + else if (existingFuncA == null && existingFuncB != null) { + err = err + existingFuncB.getName(); + } + else if (existingFuncA != null && existingFuncA == existingFuncB) { + err = err + existingFuncA.getName(); + } + + err = err + ")"; + + Msg.warn(this, err); + } + catch (DuplicateNameException e) { + String paramNames = ""; + for (int i = 0; i < parameters.length - 1; i++) { + paramNames += parameters[i].getName() + ", "; + } + paramNames += parameters[parameters.length - 1].getName(); + Msg.warn(this, "Error processing function \"" + funcName + "\" (" + methodRowIndex + + "): Duplicate parameter name (" + paramNames + ")"); } - } - if (rvaZero > 0) { - Msg.warn(this, rvaZero + " methods with RVA 0"); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableParam.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableParam.java index 097258ec86..875acf1a9b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableParam.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableParam.java @@ -31,7 +31,14 @@ public class CliTableParam extends CliAbstractTable { public short flags; public short sequence; public int nameIndex; - + + private static final int PARAMATTRIBUTES_IN = 0x1; + private static final int PARAMATTRIBUTES_OUT = 0x2; + private static final int PARAMATTRIBUTES_OPTIONAL = 0x10; + private static final int PARAMATTRIBUTES_HASDEFAULT = 0x1000; + private static final int PARAMATTRIBUTES_HASFIELDMARSHAL = 0x2000; + private static final int PARAMATTRIBUTES_UNUSED = 0xCFE0; + public CliParamRow(short flags, short sequence, int nameIndex) { super(); this.flags = flags; @@ -46,11 +53,13 @@ public class CliTableParam extends CliAbstractTable { CliEnumParamAttributes.dataType.getName(flags & 0xffff), sequence); } } - - public CliTableParam(BinaryReader reader, CliStreamMetadata stream, CliTypeTable tableId) throws IOException { + + public CliTableParam(BinaryReader reader, CliStreamMetadata stream, CliTypeTable tableId) + throws IOException { super(reader, stream, tableId); for (int i = 0; i < this.numRows; i++) { - CliParamRow row = new CliParamRow(reader.readNextShort(), reader.readNextShort(), readStringIndex(reader)); + CliParamRow row = new CliParamRow(reader.readNextShort(), reader.readNextShort(), + readStringIndex(reader)); rows.add(row); strings.add(row.nameIndex); } @@ -59,9 +68,9 @@ public class CliTableParam extends CliAbstractTable { @Override public StructureDataType getRowDataType() { - StructureDataType rowDt = new StructureDataType(new CategoryPath(PATH), "ParamRow",0); + StructureDataType rowDt = new StructureDataType(new CategoryPath(PATH), "ParamRow", 0); rowDt.add(CliEnumParamAttributes.dataType, "Flags", "bitmask of type ParamAttributes"); - rowDt.add( WORD, "Sequence", "constant"); + rowDt.add(WORD, "Sequence", "constant"); rowDt.add(metadataStream.getStringIndexDataType(), "Name", "index into String heap"); return rowDt; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableStandAloneSig.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableStandAloneSig.java index 025eae5acf..aefe83d791 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableStandAloneSig.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableStandAloneSig.java @@ -4,9 +4,9 @@ * 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. @@ -90,6 +90,7 @@ public class CliTableStandAloneSig extends CliAbstractTable { throws DuplicateNameException, CodeUnitInsertionException, IOException { for (CliAbstractTableRow row : rows) { Integer sigIndex = ((CliStandAloneSigRow) row).signatureIndex; + CliBlob blob = metadataStream.getBlobStream().getBlob(((CliStandAloneSigRow) row).signatureIndex); Address sigAddr = CliAbstractStream.getStreamMarkupAddress(program, isBinary, monitor, diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java index 23af6041e9..cc0e591505 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java @@ -461,8 +461,12 @@ public class DemangledFunction extends DemangledObject { // If returnType is null check for constructor or destructor names if (THIS_CALL.equals(function.getCallingConventionName())) { String n = getName(); - if (n.equals("~" + namespace.getName()) || n.equals(namespace.getName())) { - // constructor && destructor + if (n.equals(namespace.getName())) { + // constructor + return DataType.DEFAULT; + } + if (n.equals("~" + namespace.getName())) { + // destructor return VoidDataType.dataType; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java index b55dffb890..03b7d601bb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java @@ -441,6 +441,7 @@ public abstract class DemangledObject implements Demangled { namespace = program.getGlobalNamespace(); } + SymbolTable symbolTable = program.getSymbolTable(); for (String namespaceName : getNamespaceList(typeNamespace)) { // TODO - This is compensating for too long templates. We should probably genericize @@ -448,45 +449,43 @@ public abstract class DemangledObject implements Demangled { // same name is the same class--would that reflect reality? namespaceName = ensureNameLength(namespaceName); - SymbolTable symbolTable = program.getSymbolTable(); - - List symbols = symbolTable.getSymbols(namespaceName, namespace); - Symbol namespaceSymbol = - symbols.stream().filter(s -> (s.getSymbolType() == SymbolType.NAMESPACE || - s.getSymbolType() == SymbolType.CLASS)).findFirst().orElse(null); - if (namespaceSymbol == null) { - try { - namespace = - symbolTable.createNameSpace(namespace, namespaceName, SourceType.IMPORTED); - } - catch (DuplicateNameException e) { - Msg.error(DemangledObject.class, - "Failed to create namespace due to name conflict: " + - NamespaceUtils.getNamespaceQualifiedName(namespace, namespaceName, - false)); - break; - } - catch (InvalidInputException e) { - Msg.error(DemangledObject.class, - "Failed to create namespace: " + e.getMessage()); - break; - } + try { + namespace = + symbolTable.getOrCreateNameSpace(namespace, namespaceName, SourceType.IMPORTED); } - else if (isPermittedNamespaceSymbol(namespaceSymbol, functionPermitted)) { - namespace = (Namespace) namespaceSymbol.getObject(); - } - else { + catch (DuplicateNameException e) { Msg.error(DemangledObject.class, "Failed to create namespace due to name conflict: " + + NamespaceUtils.getNamespaceQualifiedName(namespace, namespaceName, + false)); + break; + } + catch (InvalidInputException e) { + Msg.error(DemangledObject.class, + "Failed to create namespace: " + e.getMessage()); + break; + } + + Symbol nsSymbol = namespace.getSymbol(); + if (!isPermittedNamespaceType(nsSymbol.getSymbolType(), functionPermitted)) { + + String allowedTypes = "SymbolType.CLASS, SymbolType.NAMESPACE"; + if (functionPermitted) { + allowedTypes += ", SymbolType.FUNCTION"; + } + + Msg.error(DemangledObject.class, + "Bad namespace type - must be one of: " + allowedTypes + NamespaceUtils.getNamespaceQualifiedName(namespace, namespaceName, false)); break; } + } return namespace; } - private static boolean isPermittedNamespaceSymbol(Symbol symbol, boolean functionPermitted) { - SymbolType symbolType = symbol.getSymbolType(); + private static boolean isPermittedNamespaceType(SymbolType symbolType, + boolean functionPermitted) { if (symbolType == SymbolType.CLASS || symbolType == SymbolType.NAMESPACE) { return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java new file mode 100644 index 0000000000..4645369886 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java @@ -0,0 +1,167 @@ +/* ### + * 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.util.exporter; + +import java.io.*; +import java.nio.file.*; +import java.util.List; + +import ghidra.app.util.DomainObjectService; +import ghidra.app.util.Option; +import ghidra.app.util.opinion.Loader; +import ghidra.framework.model.DomainObject; +import ghidra.program.database.mem.AddressSourceInfo; +import ghidra.program.database.mem.FileBytes; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.reloc.Relocation; +import ghidra.util.Conv; +import ghidra.util.HelpLocation; +import ghidra.util.task.TaskMonitor; +import utilities.util.FileUtilities; + +/** + * An {@link Exporter} that can export programs imported with a particular {@link Loader} + */ +public abstract class AbstractLoaderExporter extends Exporter { + + /** + * Creates a new {@link AbstractLoaderExporter} + * + * @param name The display name of this exporter + * @param extension The default extension for this exporter + * @param help The {@link HelpLocation} for this exporter + */ + protected AbstractLoaderExporter(String name, String extension, HelpLocation help) { + super(name, extension, help); + } + + /** + * Checks to see if the given file format is supported by this exporter + * + * @param fileFormat The file format (loader name) of the program to export + * @return True if the given file format is supported by this exporter; otherwise, false + */ + protected abstract boolean supportsFileFormat(String fileFormat); + + @Override + public boolean export(File file, DomainObject domainObj, AddressSetView addrSet, + TaskMonitor monitor) throws IOException, ExporterException { + + if (!(domainObj instanceof Program)) { + log.appendMsg("Unsupported type: " + domainObj.getClass().getSimpleName()); + return false; + } + + Program program = (Program) domainObj; + Memory memory = program.getMemory(); + + String fileFormat = program.getExecutableFormat(); + if (!supportsFileFormat(fileFormat)) { + log.appendMsg("Unsupported file format: " + fileFormat); + return false; + } + + // Write source program's file bytes to a temp file + File tempFile = File.createTempFile("ghidra_export_", null); + try (OutputStream out = new FileOutputStream(tempFile, false)) { + FileBytes[] fileBytes = memory.getAllFileBytes() + .stream() + .filter(fb -> program.getExecutablePath().endsWith(fb.getFilename())) + .toArray(FileBytes[]::new); + for (FileBytes bytes : fileBytes) { + FileUtilities.copyStreamToStream(new FileBytesInputStream(bytes), out, monitor); + } + } + + // Undo relocations in the temp file + String error = null; + try (RandomAccessFile fout = new RandomAccessFile(tempFile, "rw")) { + Iterable relocs = () -> program.getRelocationTable().getRelocations(); + for (Relocation reloc : relocs) { + AddressSourceInfo info = memory.getAddressSourceInfo(reloc.getAddress()); + if (info == null) { + error = "Failed to get relocation address source"; + break; + } + if (info.getFileOffset() < 0) { + error = "Failed to get relocation file offset"; + break; + } + if (info.getFileOffset() >= fout.length()) { + error = "Relocation file offset exceeds file length"; + break; + } + fout.seek(info.getFileOffset()); + fout.write(reloc.getBytes()); + } + } + + // If errors occurred, log them and delete the malformed temp file + if (error != null) { + log.appendMsg(error); + if (!tempFile.delete()) { + log.appendMsg("Failed to delete malformed file: " + tempFile); + } + return false; + } + + // Move temp file to desired output file + Path from = Paths.get(tempFile.toURI()); + Path to = Paths.get(file.toURI()); + Files.move(from, to, StandardCopyOption.REPLACE_EXISTING); + return true; + } + + @Override + public List