GP-42 Initial implementation of Pdb symbol store / symbol servers
@ -17,6 +17,10 @@ eclipse.project.name = 'GPL CabExtract'
|
||||
|
||||
project.ext.cabextract = "cabextract-1.6"
|
||||
|
||||
/*********************************************************************************
|
||||
* Deprecated - will be removed
|
||||
*********************************************************************************/
|
||||
|
||||
/*********************************************************************************
|
||||
* CabExtract platform specific tasks
|
||||
*
|
||||
|
@ -1 +1,6 @@
|
||||
Internet,https://msdl.microsoft.com/download/symbols
|
||||
Internet|https://msdl.microsoft.com/download/symbols/|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
Internet|https://chromium-browser-symsrv.commondatastorage.googleapis.com|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
Internet|https://symbols.mozilla.org/|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
Internet|https://software.intel.com/sites/downloads/symbols/|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
Internet|https://driver-symbols.nvidia.com/|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
Internet|https://download.amd.com/dir/bin|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
|
@ -1,156 +0,0 @@
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name="generator" content=
|
||||
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||
|
||||
<TITLE>Load PDB File</TITLE>
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="Load_PDB_File"></A>Load PDB File</H1>
|
||||
|
||||
<P>A program database (PDB) file holds debugging and project state information about a program
|
||||
and can be created in a number of ways. Historically, it has been created using a Microsoft
|
||||
compiler and written in <CODE>C/C++</CODE>, <CODE>C#</CODE>, and <CODE>Visual Basic</CODE>.
|
||||
A user generates a PDB file using the <CODE>/ZI or /Zi</CODE> flag (for C/C++ programs) or the
|
||||
<CODE>/debug</CODE> flag (for Visual Basic/C# programs).</P>
|
||||
|
||||
<P>There are two mechanisms for processing a PDB file. First, the platform-independent
|
||||
PDB Universal Reader/Analyzer, which can read a raw PDB file and apply it. Its capabilities
|
||||
are expected to be expanded in future releases. Second, the legacy capability that uses the
|
||||
<A href="#dia">DIA SDK</A> to read information from the PDB file. This mechanism can only run
|
||||
on a Windows platform, however it creates an XML representation of information gleaned using
|
||||
the DIA SDK. These XML files can be saved and then used on Windows and non-Windows platforms
|
||||
hosting Ghidra.</P>
|
||||
|
||||
<P>If loading a PDB, this should be done prior to other analysis, except in special cases,
|
||||
such as when only loading data types.</P>
|
||||
|
||||
<P>Restricted loading of data types or public symbols is
|
||||
supported by PDB Universal.</P>
|
||||
|
||||
<H2>To Load a PDB</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<OL>
|
||||
<LI>From the menu-bar of a tool, select <B>File <IMG src="../../shared/arrow.gif" alt=""
|
||||
width="18" height="14"> Load PDB File</B></LI>
|
||||
|
||||
<LI>In the file chooser, select the PDB file (*.PDB or *.PDB.XML)</LI>
|
||||
|
||||
<LI>Click the "Select PDB" button</LI>
|
||||
</OL>
|
||||
|
||||
<BLOCKQUOTE><UL>
|
||||
<LI>PDB Universal is automatically used for *.PDB on non-Windows platforms</LI>
|
||||
<LI>PDB MSDIA is used for *.PDB.XML files</LI>
|
||||
</UL></BLOCKQUOTE>
|
||||
|
||||
<P>When a user chooses a PDB or XML file to load for a program, Ghidra will verify its
|
||||
signature to be valid for the program. At this time, the PDB MSDIA loader cannot be used to
|
||||
force-load a mismatched PDB. To perform a force-load of a PDB file, the user must choose the
|
||||
PDB Universal loader if given the option. Force-loading an mismatched file can have
|
||||
consequences, such as loading incorrect data types and symbols located at the wrong
|
||||
addresses.</P>
|
||||
|
||||
<P>
|
||||
PDB files may also be loaded using the PDB Analyzer, which is available through
|
||||
<A HREF="help/topics/AutoAnalysisPlugin/AutoAnalysis.htm#Auto_Analyze">Auto Analysis</A> or as
|
||||
a <A HREF="help/topics/AutoAnalysisPlugin/AutoAnalysis.htm#Analyze_One_Shot">One Shot Analyzer</A>.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Information Loaded From PDB</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<OL>
|
||||
<LI>Structure and union definitions</LI>
|
||||
|
||||
<LI>Typedefs</LI>
|
||||
|
||||
<LI>Enumerations</LI>
|
||||
|
||||
<LI>Class definitions</LI>
|
||||
|
||||
<LI>Function prototypes</LI>
|
||||
|
||||
<LI>Stack variable names and data types</LI>
|
||||
|
||||
<LI>Source line numbers</LI>
|
||||
|
||||
<LI>Instruction and data symbols</LI>
|
||||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Loading Errors</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Before the PDB file is loaded into the program, then PDB signature and age are matched
|
||||
against the information stored in the executable. If these values do not match, then the PDB
|
||||
will not be loaded.</P>
|
||||
|
||||
<P align="center"><IMG border="0" src="images/About_pdb.png"></P>
|
||||
|
||||
<P align="center">Figure 1</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>The DIA SDK-Based Capability</H2>
|
||||
|
||||
<P>*.PDB.XML files can be created in three different ways:
|
||||
|
||||
<BLOCKQUOTE><UL>
|
||||
<LI>From the Ghidra GUI in Windows, use the
|
||||
<A href="help/topics/GhidraScriptMgrPlugin/GhidraScriptMgrPlugin.htm">Ghidra Script Manager</A>
|
||||
to run the <I>CreatePdbXmlFilesScript.java</I> script. Follow the prompts to choose
|
||||
the .PDB file (or directory containing .PDB file(s)) to be converted to .PDB.XML form.
|
||||
When given a directory, the script recursively traverses all subfolders to find .PDB
|
||||
files. A created .PDB.XML file is placed in the same location as the corresponding original
|
||||
.PDB file.</LI>
|
||||
<br>
|
||||
<LI>From a Windows command line, navigate to the following directory:
|
||||
<I><ghidra install root>/support</I>
|
||||
and run the <I>createPdbXmlFiles.bat</I> script. The script takes one argument representing
|
||||
either one .PDB file or a directory of .PDB files. When given a directory, the script
|
||||
recursively traverses all subdirectories to find .PDB files. A created .PDB.XML file is
|
||||
placed in the same location as the corresponding original .PDB file. Sample calls to the
|
||||
script are shown below.
|
||||
<br><br>
|
||||
<CODE> createPdbXmlFiles.bat C:\Symbols\samplePdb.pdb</CODE>
|
||||
<br>
|
||||
<CODE> createPdbXmlFiles.bat C:\Symbols</CODE>
|
||||
<br>
|
||||
</LI>
|
||||
<br>
|
||||
<LI>Run the included <I>pdb.exe</I> executable (found in the <I><ghidra install
|
||||
root>/Ghidra/Features/PDB/os/win64</I> directory) and redirect (save) its output to an
|
||||
XML file as shown below:
|
||||
<br><br>
|
||||
<CODE> pdb.exe samplePdb.pdb > samplePdb.pdb.xml</CODE>
|
||||
</LI>
|
||||
</UL></BLOCKQUOTE>
|
||||
</P>
|
||||
<P><B>NOTE:</B> Execution of <i>pdb.exe</i> has runtime dependencies which must be satisfied.
|
||||
Please refer to the <a href="docs/README_PDB.html">README_PDB</a> document for details.</P>
|
||||
|
||||
<H2><A name="dia"></A>Debug Interface Access SDK</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The Microsoft Debug Interface Access Software Development Kit (DIA SDK) provides access to
|
||||
debug information stored in program database (.PDB) files generated by Microsoft
|
||||
post-compiler tools. Because the format of the .PDB file generated by the post-compiler tools
|
||||
undergoes constant revision, exposing the format is impractical. Using the DIA API, you can
|
||||
develop applications that search for and browse debug information stored in a .PDB file. Such
|
||||
applications could, for example, report stack trace-back information and analyze performance
|
||||
data.</P>
|
||||
|
||||
<P><IMG src="../../shared/note.png" border="0">If you are attempting to load a PDB on a
|
||||
Windows machine and see an error message such as "Unable to locate the DIA SDK,"
|
||||
you will need to add and register one or more files on your computer. Refer to the
|
||||
<a href="docs/README_PDB.html">README_PDB</a> document for detailed instructions.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
</BODY>
|
||||
</HTML>
|
@ -68,9 +68,11 @@ public class BinaryReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a clone of this reader positioned at the new index.
|
||||
* Returns a clone of this reader, with its own independent current position,
|
||||
* positioned at the new index.
|
||||
*
|
||||
* @param newIndex the new index
|
||||
* @return a clone of this reader positioned at the new index
|
||||
* @return an independent clone of this reader positioned at the new index
|
||||
*/
|
||||
public BinaryReader clone(long newIndex) {
|
||||
BinaryReader clone = new BinaryReader(provider, isLittleEndian());
|
||||
@ -88,6 +90,36 @@ public class BinaryReader {
|
||||
return clone(currentIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BinaryReader that is in BigEndian mode.
|
||||
*
|
||||
* @return either this same instance (if already BigEndian), or a new instance
|
||||
* (at the same location) in BigEndian mode
|
||||
*/
|
||||
public BinaryReader asBigEndian() {
|
||||
if (isBigEndian()) {
|
||||
return this;
|
||||
}
|
||||
BinaryReader result = clone(currentIndex);
|
||||
result.setLittleEndian(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BinaryReader that is in LittleEndian mode.
|
||||
*
|
||||
* @return either this same instance (if already LittleEndian), or a new instance
|
||||
* (at the same location) in LittleEndian mode
|
||||
*/
|
||||
public BinaryReader asLittleEndian() {
|
||||
if (!isBigEndian()) {
|
||||
return this;
|
||||
}
|
||||
BinaryReader result = clone(currentIndex);
|
||||
result.setLittleEndian(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this reader will extract values in little endian,
|
||||
* otherwise in big endian.
|
||||
@ -97,6 +129,15 @@ public class BinaryReader {
|
||||
return converter instanceof LittleEndianDataConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this reader will extract values in big endian.
|
||||
*
|
||||
* @return true is big endian, false is little endian
|
||||
*/
|
||||
public boolean isBigEndian() {
|
||||
return converter instanceof BigEndianDataConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the endian of this binary reader.
|
||||
* @param isLittleEndian true for little-endian and false for big-endian
|
||||
|
@ -1,53 +0,0 @@
|
||||
/* ###
|
||||
* 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.
|
||||
* 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.pdb;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import ghidra.app.util.bin.*;
|
||||
import ghidra.framework.*;
|
||||
|
||||
public class PdbFactory {
|
||||
static {
|
||||
PluggableServiceRegistry.registerPluggableService(PdbFactory.class,
|
||||
new PdbFactory());
|
||||
}
|
||||
|
||||
public static PdbInfoDotNetIface getPdbInfoDotNetInstance(
|
||||
BinaryReader reader, int ptr) throws IOException {
|
||||
PdbFactory factory = PluggableServiceRegistry
|
||||
.getPluggableService(PdbFactory.class);
|
||||
return factory.doGetPdbInfoDotNetInstance(reader, ptr);
|
||||
}
|
||||
|
||||
public static PdbInfoIface getPdbInfoInstance(BinaryReader reader, int ptr)
|
||||
throws IOException {
|
||||
PdbFactory factory = PluggableServiceRegistry
|
||||
.getPluggableService(PdbFactory.class);
|
||||
return factory.doGetPdbInfoInstance(reader, ptr);
|
||||
}
|
||||
|
||||
protected PdbInfoDotNetIface doGetPdbInfoDotNetInstance(
|
||||
BinaryReader reader, int ptr) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected PdbInfoIface doGetPdbInfoInstance(BinaryReader reader, int ptr)
|
||||
throws IOException {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.bin.format.pdb;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.framework.options.Options;
|
||||
|
||||
/**
|
||||
* Bag of information about a Pdb symbol file, usually extracted from information present in a PE
|
||||
* binary.
|
||||
*
|
||||
*/
|
||||
public interface PdbInfo {
|
||||
|
||||
/**
|
||||
* Read either a {@link PdbInfoCodeView} object or a {@link PdbInfoDotNet} object
|
||||
* from the BinaryReader of a PE binary.
|
||||
*
|
||||
* @param reader BinaryReader
|
||||
* @param offset position of the debug info
|
||||
* @return new PdbInfoCodeView or PdbInfoDotNet object
|
||||
* @throws IOException if error
|
||||
*/
|
||||
public static PdbInfo read(BinaryReader reader, long offset) throws IOException {
|
||||
if (PdbInfoCodeView.isMatch(reader, offset)) {
|
||||
return PdbInfoCodeView.read(reader, offset);
|
||||
}
|
||||
if (PdbInfoDotNet.isMatch(reader, offset)) {
|
||||
return PdbInfoDotNet.read(reader, offset);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this instance is valid.
|
||||
*
|
||||
* @return boolean true if valid (magic signature matches and fields have valid data)
|
||||
*/
|
||||
boolean isValid();
|
||||
|
||||
/**
|
||||
* Writes the various PDB info fields to a program's options.
|
||||
*
|
||||
* @param options Options of a Program to write to
|
||||
*/
|
||||
void serializeToOptions(Options options);
|
||||
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/* ###
|
||||
* 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.pdb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.app.util.bin.format.pe.debug.DebugCodeViewConstants;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.Conv;
|
||||
|
||||
/**
|
||||
* Older style pdb information, using a simple 32bit hash to link the pdb to its binary.
|
||||
*/
|
||||
public class PdbInfoCodeView implements StructConverter, PdbInfo {
|
||||
private static final int MAGIC =
|
||||
DebugCodeViewConstants.SIGNATURE_NB << 16 | DebugCodeViewConstants.VERSION_10;
|
||||
|
||||
/**
|
||||
* Returns true if the pdb information at the specified offset is a {@link PdbInfoCodeView}
|
||||
* type (based on the signature at that offset).
|
||||
*
|
||||
* @param reader {@link BinaryReader}
|
||||
* @param offset offset of the Pdb information
|
||||
* @return boolean true if it is a {@link PdbInfoCodeView} type
|
||||
* @throws IOException if error reading data
|
||||
*/
|
||||
public static boolean isMatch(BinaryReader reader, long offset) throws IOException {
|
||||
//read value out as big endian
|
||||
int value = reader.asBigEndian().readInt(offset);
|
||||
return MAGIC == value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the pdb information from a PE binary.
|
||||
*
|
||||
* @param reader {@link BinaryReader}
|
||||
* @param offset offset of the Pdb information
|
||||
* @return new {@link PdbInfoCodeView} instance, never null
|
||||
* @throws IOException if error reading data
|
||||
*/
|
||||
public static PdbInfoCodeView read(BinaryReader reader, long offset) throws IOException {
|
||||
reader = reader.clone(offset);
|
||||
|
||||
PdbInfoCodeView result = new PdbInfoCodeView();
|
||||
result.magic = reader.readNextByteArray(4);
|
||||
result.offset = reader.readNextInt();
|
||||
result.sig = reader.readNextInt();
|
||||
result.age = reader.readNextInt();
|
||||
result.pdbPath = reader.readNextAsciiString();
|
||||
result.pdbName = FilenameUtils.getName(result.pdbPath);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] magic;
|
||||
private int offset;
|
||||
private int sig;
|
||||
private int age;
|
||||
private String pdbName;
|
||||
private String pdbPath;
|
||||
|
||||
private PdbInfoCodeView() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return magic.length == 4 && !pdbName.isBlank();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeToOptions(Options options) {
|
||||
options.setString(PdbParserConstants.PDB_VERSION,
|
||||
new String(magic, StandardCharsets.US_ASCII));
|
||||
options.setString(PdbParserConstants.PDB_SIGNATURE, Conv.toHexString(sig));
|
||||
options.setString(PdbParserConstants.PDB_AGE, Integer.toHexString(age));
|
||||
options.setString(PdbParserConstants.PDB_FILE, pdbName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() {
|
||||
StructureDataType struct = new StructureDataType("PdbInfo", 0);
|
||||
struct.add(new StringDataType(), magic.length, "signature", null);
|
||||
struct.add(new DWordDataType(), "offset", null);
|
||||
struct.add(new DWordDataType(), "sig", null);
|
||||
struct.add(new DWordDataType(), "age", null);
|
||||
if (pdbName.length() > 0) {
|
||||
struct.add(new StringDataType(), pdbName.length(), "pdbname", null);
|
||||
}
|
||||
struct.setCategoryPath(new CategoryPath("/PDB"));
|
||||
return struct;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.bin.format.pdb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.app.util.bin.format.pe.debug.DebugCodeViewConstants;
|
||||
import ghidra.app.util.datatype.microsoft.GUID;
|
||||
import ghidra.app.util.datatype.microsoft.GuidDataType;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.data.*;
|
||||
|
||||
/**
|
||||
* Newer style pdb information, using a GUID to link the pdb to its binary.
|
||||
*/
|
||||
public class PdbInfoDotNet implements StructConverter, PdbInfo {
|
||||
private static final int MAGIC =
|
||||
DebugCodeViewConstants.SIGNATURE_DOT_NET << 16 | DebugCodeViewConstants.VERSION_DOT_NET;
|
||||
|
||||
/**
|
||||
* Returns true if the pdb information at the specified offset is a {@link PdbInfoDotNet}
|
||||
* type (based on the signature at that offset).
|
||||
*
|
||||
* @param reader {@link BinaryReader}
|
||||
* @param offset offset of the Pdb information
|
||||
* @return boolean true if it is a {@link PdbInfoDotNet} type
|
||||
* @throws IOException if error reading data
|
||||
*/
|
||||
public static boolean isMatch(BinaryReader reader, long offset) throws IOException {
|
||||
//read value out as big endian
|
||||
int value = reader.asBigEndian().readInt(offset);
|
||||
return MAGIC == value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an instance from the stream.
|
||||
*
|
||||
* @param reader {@link BinaryReader} to read from
|
||||
* @param offset position of the pdb info
|
||||
* @return new instance, never null
|
||||
* @throws IOException if IO error or data format error
|
||||
*/
|
||||
public static PdbInfoDotNet read(BinaryReader reader, long offset) throws IOException {
|
||||
reader = reader.clone(offset);
|
||||
|
||||
PdbInfoDotNet result = new PdbInfoDotNet();
|
||||
result.magic = reader.readNextByteArray(4);
|
||||
result.guid = new GUID(reader);
|
||||
result.age = reader.readNextInt();
|
||||
result.pdbPath = reader.readNextAsciiString();
|
||||
result.pdbName = FilenameUtils.getName(result.pdbPath);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance from explicit values.
|
||||
*
|
||||
* @param pdbPath String path / filename of the pdb file
|
||||
* @param age age
|
||||
* @param guid {@link GUID}
|
||||
* @return new instance, never null
|
||||
*/
|
||||
public static PdbInfoDotNet fromValues(String pdbPath, int age, GUID guid) {
|
||||
PdbInfoDotNet result = new PdbInfoDotNet();
|
||||
result.pdbPath = pdbPath;
|
||||
result.pdbName = FilenameUtils.getName(result.pdbPath);
|
||||
result.age = age;
|
||||
result.guid = guid;
|
||||
result.magic = "????".getBytes();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private byte[] magic;
|
||||
private GUID guid;
|
||||
private int age;
|
||||
private String pdbName;
|
||||
private String pdbPath;
|
||||
|
||||
private PdbInfoDotNet() {
|
||||
// empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return magic.length == 4 && !pdbName.isBlank() && guid != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeToOptions(Options options) {
|
||||
options.setString(PdbParserConstants.PDB_VERSION,
|
||||
new String(magic, StandardCharsets.US_ASCII));
|
||||
options.setString(PdbParserConstants.PDB_GUID, guid.toString());
|
||||
options.setString(PdbParserConstants.PDB_AGE, Integer.toHexString(age));
|
||||
options.setString(PdbParserConstants.PDB_FILE, pdbName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() {
|
||||
StructureDataType struct = new StructureDataType("DotNetPdbInfo", 0);
|
||||
struct.add(new StringDataType(), magic.length, "signature", null);
|
||||
struct.add(new GuidDataType(), "guid", null);
|
||||
struct.add(new DWordDataType(), "age", null);
|
||||
if (pdbName.length() > 0) {
|
||||
struct.add(new StringDataType(), pdbName.length(), "pdbname", null);
|
||||
}
|
||||
struct.setCategoryPath(new CategoryPath("/PDB"));
|
||||
return struct;
|
||||
}
|
||||
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
/* ###
|
||||
* 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.
|
||||
* 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.pdb;
|
||||
|
||||
import ghidra.app.util.bin.*;
|
||||
import ghidra.app.util.datatype.microsoft.*;
|
||||
|
||||
public interface PdbInfoDotNetIface extends StructConverter {
|
||||
|
||||
public abstract String getPdbName();
|
||||
|
||||
public abstract int getAge();
|
||||
|
||||
public abstract int getSignature();
|
||||
|
||||
public abstract GUID getGUID();
|
||||
|
||||
public abstract byte[] getMagic();
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/* ###
|
||||
* 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.
|
||||
* 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.pdb;
|
||||
|
||||
import ghidra.app.util.bin.*;
|
||||
|
||||
public interface PdbInfoIface extends StructConverter {
|
||||
|
||||
public abstract byte[] getMagic();
|
||||
|
||||
public abstract int getOffset();
|
||||
|
||||
public abstract int getSig();
|
||||
|
||||
public abstract int getAge();
|
||||
|
||||
public abstract String getPdbName();
|
||||
}
|
@ -19,8 +19,8 @@ import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoDotNetIface;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoIface;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoCodeView;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoDotNet;
|
||||
import ghidra.app.util.bin.format.pe.debug.*;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.program.model.address.Address;
|
||||
@ -112,12 +112,12 @@ public class DebugDataDirectory extends DataDirectory {
|
||||
if (dcv != null) {
|
||||
Address dataAddr = getDataAddress(dcv.getDebugDirectory(), isBinary, space, ntHeader);
|
||||
if (dataAddr != null) {
|
||||
PdbInfoIface pdbInfo = dcv.getPdbInfo();
|
||||
PdbInfoCodeView pdbInfo = dcv.getPdbInfo();
|
||||
if (pdbInfo != null) {
|
||||
setPlateComment(program, dataAddr, "CodeView PDB Info");
|
||||
PeUtils.createData(program, dataAddr, pdbInfo.toDataType(), log);
|
||||
}
|
||||
PdbInfoDotNetIface dotNetPdbInfo = dcv.getDotNetPdbInfo();
|
||||
PdbInfoDotNet dotNetPdbInfo = dcv.getDotNetPdbInfo();
|
||||
if (dotNetPdbInfo != null) {
|
||||
setPlateComment(program, dataAddr, ".NET PDB Info");
|
||||
PeUtils.createData(program, dataAddr, dotNetPdbInfo.toDataType(), log);
|
||||
|
@ -15,30 +15,25 @@
|
||||
*/
|
||||
package ghidra.app.util.bin.format.pe.debug;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader;
|
||||
import ghidra.app.util.bin.format.pdb.PdbFactory;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoDotNetIface;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoIface;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoCodeView;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoDotNet;
|
||||
import ghidra.app.util.bin.format.pe.OffsetValidator;
|
||||
import ghidra.program.model.data.ArrayDataType;
|
||||
import ghidra.program.model.data.CategoryPath;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.Structure;
|
||||
import ghidra.program.model.data.StructureDataType;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A class to represent the code view debug information.
|
||||
*/
|
||||
public class DebugCodeView implements StructConverter {
|
||||
private DebugDirectory debugDir;
|
||||
private DebugCodeViewSymbolTable symbolTable;
|
||||
private PdbInfoIface pdbInfo;
|
||||
private PdbInfoDotNetIface dotNetPdbInfo;
|
||||
private PdbInfoCodeView pdbInfo;
|
||||
private PdbInfoDotNet dotNetPdbInfo;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -70,8 +65,8 @@ public class DebugCodeView implements StructConverter {
|
||||
return;
|
||||
}
|
||||
|
||||
dotNetPdbInfo = PdbFactory.getPdbInfoDotNetInstance(reader, ptr);
|
||||
pdbInfo = PdbFactory.getPdbInfoInstance(reader, ptr);
|
||||
dotNetPdbInfo = PdbInfoDotNet.isMatch(reader, ptr) ? PdbInfoDotNet.read(reader, ptr) : null;
|
||||
pdbInfo = PdbInfoCodeView.isMatch(reader, ptr) ? PdbInfoCodeView.read(reader, ptr) : null;
|
||||
if (DebugCodeViewSymbolTable.isMatch(reader, ptr)) {
|
||||
symbolTable =
|
||||
DebugCodeViewSymbolTable.createDebugCodeViewSymbolTable(reader,
|
||||
@ -106,17 +101,18 @@ public class DebugCodeView implements StructConverter {
|
||||
* Returns the code view .PDB info.
|
||||
* @return the code view .PDB info
|
||||
*/
|
||||
public PdbInfoIface getPdbInfo() {
|
||||
public PdbInfoCodeView getPdbInfo() {
|
||||
return pdbInfo;
|
||||
}
|
||||
|
||||
public PdbInfoDotNetIface getDotNetPdbInfo() {
|
||||
public PdbInfoDotNet getDotNetPdbInfo() {
|
||||
return dotNetPdbInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.app.util.bin.StructConverter#toDataType()
|
||||
*/
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException {
|
||||
Structure es = new StructureDataType("DebugCodeView", 0);
|
||||
es.add(WORD, "Signature", null);
|
||||
|
@ -21,11 +21,7 @@ import java.util.Arrays;
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.util.BigEndianDataConverter;
|
||||
import ghidra.util.Conv;
|
||||
import ghidra.util.DataConverter;
|
||||
import ghidra.util.LittleEndianDataConverter;
|
||||
import ghidra.util.NumericUtilities;
|
||||
import ghidra.util.*;
|
||||
|
||||
/**
|
||||
* GUIDs identify objects such as interfaces, manager entry-point vectors (EPVs),
|
||||
@ -61,54 +57,52 @@ public class GUID {
|
||||
|
||||
/**
|
||||
* Creates a GUID object using the GUID string form.
|
||||
* @param guidString - "6B29FC40-CA47-1067-B31D-00DD010662DA"
|
||||
* @param guidString - either with or without dashes between parts -
|
||||
* "6B29FC40-CA47-1067-B31D-00DD010662DA", or "6B29FC40CA471067B31D00DD010662DA", and
|
||||
* with or without leading and trailing "{" "}" characters
|
||||
* @throws IllegalArgumentException if string does not represent a valid GUID
|
||||
*/
|
||||
public GUID(String guidString) {
|
||||
if (guidString.length() != 36) {
|
||||
throw new IllegalArgumentException("Invalid GUID string.");
|
||||
}
|
||||
int pos = guidString.indexOf('-');
|
||||
if (pos == -1) {
|
||||
throw new IllegalArgumentException("Invalid GUID string.");
|
||||
}
|
||||
data1 = (int) NumericUtilities.parseHexLong(guidString.substring(0, pos));
|
||||
|
||||
guidString = guidString.substring(pos + 1);
|
||||
pos = guidString.indexOf('-');
|
||||
if (pos == -1) {
|
||||
throw new IllegalArgumentException("Invalid GUID string.");
|
||||
}
|
||||
data2 = (short) Integer.parseInt(guidString.substring(0, pos), 16);
|
||||
|
||||
guidString = guidString.substring(pos + 1);
|
||||
pos = guidString.indexOf('-');
|
||||
if (pos == -1) {
|
||||
throw new IllegalArgumentException("Invalid GUID string.");
|
||||
}
|
||||
data3 = (short) Integer.parseInt(guidString.substring(0, pos), 16);
|
||||
|
||||
guidString = guidString.substring(pos + 1);
|
||||
pos = guidString.indexOf('-');
|
||||
if (pos == -1) {
|
||||
throw new IllegalArgumentException("Invalid GUID string.");
|
||||
}
|
||||
int value = Integer.parseInt(guidString.substring(0, pos), 16);
|
||||
public GUID(String guidString) throws IllegalArgumentException {
|
||||
String[] parts = getGUIDParts(guidString);
|
||||
data1 = (int) NumericUtilities.parseHexLong(parts[0]);
|
||||
data2 = (short) Integer.parseInt(parts[1], 16);
|
||||
data3 = (short) Integer.parseInt(parts[2], 16);
|
||||
int value = Integer.parseInt(parts[3], 16);
|
||||
data4[0] = (byte) (value >> 8);
|
||||
data4[1] = (byte) (value & 0xff);
|
||||
data4[2] = (byte) Integer.parseInt(parts[4].substring(0, 2), 16);
|
||||
data4[3] = (byte) Integer.parseInt(parts[4].substring(2, 4), 16);
|
||||
data4[4] = (byte) Integer.parseInt(parts[4].substring(4, 6), 16);
|
||||
data4[5] = (byte) Integer.parseInt(parts[4].substring(6, 8), 16);
|
||||
data4[6] = (byte) Integer.parseInt(parts[4].substring(8, 10), 16);
|
||||
data4[7] = (byte) Integer.parseInt(parts[4].substring(10, 12), 16);
|
||||
}
|
||||
|
||||
guidString = guidString.substring(pos + 1);
|
||||
data4[2] = (byte) Integer.parseInt(guidString.substring(0, 2), 16);
|
||||
guidString = guidString.substring(2);
|
||||
data4[3] = (byte) Integer.parseInt(guidString.substring(0, 2), 16);
|
||||
guidString = guidString.substring(2);
|
||||
data4[4] = (byte) Integer.parseInt(guidString.substring(0, 2), 16);
|
||||
guidString = guidString.substring(2);
|
||||
data4[5] = (byte) Integer.parseInt(guidString.substring(0, 2), 16);
|
||||
guidString = guidString.substring(2);
|
||||
data4[6] = (byte) Integer.parseInt(guidString.substring(0, 2), 16);
|
||||
guidString = guidString.substring(2);
|
||||
data4[7] = (byte) Integer.parseInt(guidString.substring(0, 2), 16);
|
||||
private String[] getGUIDParts(String guidString) throws IllegalArgumentException {
|
||||
String[] results = new String[5];
|
||||
guidString = (guidString.startsWith("{") && guidString.endsWith("}"))
|
||||
? guidString.substring(1, guidString.length() - 1)
|
||||
: guidString;
|
||||
if (guidString.length() == 36 && guidString.charAt(8) == '-' &&
|
||||
guidString.charAt(13) == '-' && guidString.charAt(18) == '-' &&
|
||||
guidString.charAt(23) == '-') {
|
||||
results[0] = guidString.substring(0, 8);
|
||||
results[1] = guidString.substring(9, 13);
|
||||
results[2] = guidString.substring(14, 18);
|
||||
results[3] = guidString.substring(19, 23);
|
||||
results[4] = guidString.substring(24);
|
||||
}
|
||||
else if (guidString.length() == 32) {
|
||||
results[0] = guidString.substring(0, 8);
|
||||
results[1] = guidString.substring(8, 12);
|
||||
results[2] = guidString.substring(12, 16);
|
||||
results[3] = guidString.substring(16, 20);
|
||||
results[4] = guidString.substring(20);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Invalid GUID string.");
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,23 +166,23 @@ public class GUID {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.append(Conv.toHexString(data1));
|
||||
buffer.append("-");
|
||||
buffer.append(Conv.toHexString(data2));
|
||||
buffer.append("-");
|
||||
buffer.append(Conv.toHexString(data3));
|
||||
buffer.append("-");
|
||||
buffer.append(Conv.toHexString(data4[0]));
|
||||
buffer.append(Conv.toHexString(data4[1]));
|
||||
buffer.append("-");
|
||||
buffer.append(Conv.toHexString(data4[2]));
|
||||
buffer.append(Conv.toHexString(data4[3]));
|
||||
buffer.append(Conv.toHexString(data4[4]));
|
||||
buffer.append(Conv.toHexString(data4[5]));
|
||||
buffer.append(Conv.toHexString(data4[6]));
|
||||
buffer.append(Conv.toHexString(data4[7]));
|
||||
return buffer.toString();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(Conv.toHexString(data1));
|
||||
sb.append("-");
|
||||
sb.append(Conv.toHexString(data2));
|
||||
sb.append("-");
|
||||
sb.append(Conv.toHexString(data3));
|
||||
sb.append("-");
|
||||
sb.append(Conv.toHexString(data4[0]));
|
||||
sb.append(Conv.toHexString(data4[1]));
|
||||
sb.append("-");
|
||||
sb.append(Conv.toHexString(data4[2]));
|
||||
sb.append(Conv.toHexString(data4[3]));
|
||||
sb.append(Conv.toHexString(data4[4]));
|
||||
sb.append(Conv.toHexString(data4[5]));
|
||||
sb.append(Conv.toHexString(data4[6]));
|
||||
sb.append(Conv.toHexString(data4[7]));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,11 +17,11 @@ package ghidra.app.util.opinion;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.util.bin.format.pdb.*;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoCodeView;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoDotNet;
|
||||
import ghidra.app.util.bin.format.pe.FileHeader;
|
||||
import ghidra.app.util.bin.format.pe.SectionHeader;
|
||||
import ghidra.app.util.bin.format.pe.debug.*;
|
||||
import ghidra.app.util.datatype.microsoft.GUID;
|
||||
import ghidra.app.util.demangler.DemangledObject;
|
||||
import ghidra.app.util.demangler.DemanglerUtil;
|
||||
import ghidra.framework.options.Options;
|
||||
@ -112,56 +112,14 @@ abstract class AbstractPeDebugLoader extends AbstractLibrarySupportLoader {
|
||||
|
||||
Options proplist = program.getOptions(Program.PROGRAM_INFO);
|
||||
|
||||
PdbInfoIface cvPdbInfo = dcv.getPdbInfo();
|
||||
PdbInfoCodeView cvPdbInfo = dcv.getPdbInfo();
|
||||
if (cvPdbInfo != null) {
|
||||
byte[] magic = cvPdbInfo.getMagic();
|
||||
int sig = cvPdbInfo.getSig();
|
||||
int age = cvPdbInfo.getAge();
|
||||
String name = cvPdbInfo.getPdbName();
|
||||
|
||||
proplist.setString(PdbParserConstants.PDB_VERSION, Conv.toString(magic));
|
||||
proplist.setString(PdbParserConstants.PDB_SIGNATURE, Conv.toHexString(sig));
|
||||
proplist.setString(PdbParserConstants.PDB_AGE, Integer.toHexString(age));
|
||||
proplist.setString(PdbParserConstants.PDB_FILE, name);
|
||||
/*
|
||||
DebugDirectory dd = dcv.getDebugDirectory();
|
||||
if (dd.getAddressOfRawData() > 0) {
|
||||
Address address = space.getAddress(imageBase + dd.getAddressOfRawData());
|
||||
listing.setComment(address, CodeUnit.PLATE_COMMENT, "CodeView PDB Info");
|
||||
try {
|
||||
listing.createData(address, cvPdbInfo.toDataType());
|
||||
}
|
||||
catch (IOException e) {}
|
||||
catch (DuplicateNameException e) {}
|
||||
catch (CodeUnitInsertionException e) {}
|
||||
}
|
||||
*/
|
||||
cvPdbInfo.serializeToOptions(proplist);
|
||||
}
|
||||
|
||||
PdbInfoDotNetIface dotnetPdbInfo = dcv.getDotNetPdbInfo();
|
||||
PdbInfoDotNet dotnetPdbInfo = dcv.getDotNetPdbInfo();
|
||||
if (dotnetPdbInfo != null) {
|
||||
byte[] magic = dotnetPdbInfo.getMagic();
|
||||
GUID guid = dotnetPdbInfo.getGUID();
|
||||
int age = dotnetPdbInfo.getAge();
|
||||
String name = dotnetPdbInfo.getPdbName();
|
||||
|
||||
proplist.setString(PdbParserConstants.PDB_VERSION, Conv.toString(magic));
|
||||
proplist.setString(PdbParserConstants.PDB_GUID, guid.toString());
|
||||
proplist.setString(PdbParserConstants.PDB_AGE, Integer.toHexString(age));
|
||||
proplist.setString(PdbParserConstants.PDB_FILE, name);
|
||||
/*
|
||||
DebugDirectory dd = dcv.getDebugDirectory();
|
||||
if (dd.getAddressOfRawData() > 0) {
|
||||
Address address = space.getAddress(imageBase + dd.getAddressOfRawData());
|
||||
listing.setComment(address, CodeUnit.PLATE_COMMENT, ".NET PDB Info");
|
||||
try {
|
||||
listing.createData(address, dotnetPdbInfo.toDataType());
|
||||
}
|
||||
catch (IOException e) {}
|
||||
catch (DuplicateNameException e) {}
|
||||
catch (CodeUnitInsertionException e) {}
|
||||
}
|
||||
*/
|
||||
dotnetPdbInfo.serializeToOptions(proplist);
|
||||
}
|
||||
|
||||
DebugCodeViewSymbolTable dcvst = dcv.getSymbolTable();
|
||||
|
@ -1,4 +1,7 @@
|
||||
##VERSION: 2.0
|
||||
##MODULE IP: Crystal Clear Icons - LGPL 2.1
|
||||
##MODULE IP: FAMFAMFAM Icons - CC 2.5
|
||||
##MODULE IP: Nuvola Icons - LGPL 2.1
|
||||
##MODULE IP: Oxygen Icons - LGPL 3.0
|
||||
Module.manifest||GHIDRA||||END|
|
||||
src/global/docs/README_PDB.html||GHIDRA||||END|
|
||||
@ -13,13 +16,19 @@ src/main/help/help/shared/redo.png||GHIDRA||||END|
|
||||
src/main/help/help/shared/tip.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||
src/main/help/help/shared/undo.png||GHIDRA||||END|
|
||||
src/main/help/help/shared/warning.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||
src/main/help/help/topics/Pdb/LoadPDBNew.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/PDB.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/download_pdb_file.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/KnownSymbolServerURLsDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/PdbOrXmlDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/PeSpecifiedPathDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/SuccessDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/SymbolServerURLDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/LoadPdb_Advanced_NeedsConfig.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/LoadPdb_Advanced_Screenshot.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/LoadPdb_Initial_Screenshot.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/Plus2.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/SymbolServerConfig_AddButtonMenu.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/SymbolServerConfig_Screenshot.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/disk.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||
src/main/help/help/topics/Pdb/images/down.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Pdb/images/error.png||Nuvola Icons - LGPL 2.1||||END|
|
||||
src/main/help/help/topics/Pdb/images/reload3.png||Crystal Clear Icons - LGPL 2.1||||END|
|
||||
src/main/help/help/topics/Pdb/images/up.png||GHIDRA||||END|
|
||||
src/pdb/README.txt||GHIDRA||||END|
|
||||
src/pdb/pdb.sln||GHIDRA||||END|
|
||||
src/pdb/pdb.vcxproj||GHIDRA||||END|
|
||||
|
@ -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.
|
||||
@ -14,29 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.bin.format.pdb;
|
||||
//Example preScript to force a PdbAnalyzer to use a custom PDB
|
||||
//symbol file when analyzing a binary.
|
||||
//@category PDB
|
||||
import java.io.File;
|
||||
|
||||
import ghidra.app.util.bin.*;
|
||||
import ghidra.app.plugin.core.analysis.PdbAnalyzer;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class GhidraPdbFactory extends PdbFactory {
|
||||
public class PdbExamplePrescript extends GhidraScript {
|
||||
|
||||
@Override
|
||||
protected PdbInfoDotNetIface doGetPdbInfoDotNetInstance(
|
||||
BinaryReader reader, int ptr) throws IOException {
|
||||
if (PdbInfoDotNet.isMatch(reader, ptr)) {
|
||||
return new PdbInfoDotNet(reader, ptr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
protected void run() throws Exception {
|
||||
// contrived example of choosing a pdb file with custom logic
|
||||
File pdbFile = new File(getProgramFile().getPath() + ".pdb");
|
||||
|
||||
@Override
|
||||
protected PdbInfoIface doGetPdbInfoInstance(BinaryReader reader, int ptr)
|
||||
throws IOException {
|
||||
if (PdbInfo.isMatch(reader, ptr)) {
|
||||
return new PdbInfo(reader, ptr);
|
||||
}
|
||||
return null;
|
||||
PdbAnalyzer.setPdbFileOption(currentProgram, pdbFile);
|
||||
// or
|
||||
//PdbUniversalAnalyzer.setPdbFileOption(currentProgram, pdbFile);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
//Example preScript to configure the PDB symbol server service to use the ~/symbols directory
|
||||
//as the location to store symbol files, and to search Microsoft's public
|
||||
//symbol server.
|
||||
//The ~/symbols directory should already exist and be initialized as a symbol
|
||||
//storage location.
|
||||
//@category PDB
|
||||
import java.util.List;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
import ghidra.app.plugin.core.analysis.PdbAnalyzer;
|
||||
import ghidra.app.plugin.core.analysis.PdbUniversalAnalyzer;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import pdb.PdbPlugin;
|
||||
import pdb.symbolserver.*;
|
||||
|
||||
public class PdbSymbolServerExamplePrescript extends GhidraScript {
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
File homeDir = new File(System.getProperty("user.home"));
|
||||
File symDir = new File(homeDir, "symbols");
|
||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(symDir);
|
||||
HttpSymbolServer msSymbolServer =
|
||||
new HttpSymbolServer(URI.create("https://msdl.microsoft.com/download/symbols/"));
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore, List.of(msSymbolServer));
|
||||
|
||||
PdbPlugin.saveSymbolServerServiceConfig(symbolServerService);
|
||||
|
||||
// You only need to enable the "allow remote" option on the specific
|
||||
// analyzer you are using
|
||||
PdbUniversalAnalyzer.setAllowRemoteOption(currentProgram, true);
|
||||
PdbAnalyzer.setAllowRemoteOption(currentProgram, true);
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ native execution issue and the use of an intermediate XML format.
|
||||
<p>Although GHIDRA has been primarily designed to utilize locally stored PDB files during analysis,
|
||||
the ability to interactively download individual PDB files from a web-based Microsoft Symbol Server
|
||||
is also provided. This capability is accessed via the GUI while a program is open via the
|
||||
<B><I>File->Download PDB File...</I></B> action.
|
||||
<B><I>File → Load PDB File...</I></B> action.
|
||||
|
||||
<H2><a name="DIASDK">DIA SDK Dependency</a></H2>
|
||||
|
||||
|
@ -51,8 +51,8 @@
|
||||
<tocroot>
|
||||
<tocref id="Program Annotation">
|
||||
<tocdef id="PDB" sortgroup="q" text="PDB" target="help/topics/Pdb/PDB.htm" >
|
||||
<tocdef id="README_PDB" sortgroup="a" text="PDB Parser (README_PDB)" target="docs/README_PDB.html" />
|
||||
<tocdef id="Download PDB" sortgroup="b" text="Download PDB File" target="help/topics/Pdb/download_pdb_file.html" />
|
||||
<tocdef id="LoadPDBNew" sortgroup="a" text="Load PDB File" target="help/topics/Pdb/LoadPDBNew.html" />
|
||||
<tocdef id="README_PDB" sortgroup="b" text="PDB Parser (README_PDB)" target="docs/README_PDB.html" />
|
||||
</tocdef>
|
||||
</tocref>
|
||||
</tocroot>
|
||||
|
@ -0,0 +1,226 @@
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<meta name="generator" content="Bluefish 2.2.11" >
|
||||
<TITLE>Load PDB</TITLE>
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
|
||||
<H1>Load PDB</H1>
|
||||
|
||||
|
||||
<H2><A name="SymbolServers"></A>Symbol Servers and Symbol Storage</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>In an effort to manage large collections of symbol files, Microsoft specified a scheme to organize
|
||||
symbol files into directory structures.</P>
|
||||
|
||||
<P>Ghidra can search Microsoft-style symbol servers (web-based HTTP/HTTPS) or local file system symbol
|
||||
storage directories as well as unorganized, non-MS symbol storage directories for the PE executable's
|
||||
matching PDB symbol file.</P>
|
||||
|
||||
<UL>
|
||||
<LI>Microsoft symbol servers / symbol storage directories:</LI>
|
||||
<UL>
|
||||
<LI>Organize symbol files (typically PDB files) into a directory hierarchy using information
|
||||
from the symbol file being stored.</LI>
|
||||
<LI><A name="OneLevel_SymbolDirectory"></A>In a single-level symbol server directory hierarchy, a symbol file named <CODE>myprogram.pdb</CODE> might be stored
|
||||
as:</LI>
|
||||
<UL>
|
||||
<LI><CODE><FONT COLOR="BLUE">myprogram.pdb</FONT>/<FONT COLOR="RED">012345670123012301230123456789AB</FONT><FONT COLOR="GREEN">1</FONT>/<FONT COLOR="BLUE">myprogram.pdb</FONT></CODE>.</LI>
|
||||
<LI><CODE><FONT COLOR="BLUE">myprogram.pdb</FONT></CODE> is the name of the file and the name of the initial subdirectory off the root of the server.</LI>
|
||||
<LI><CODE><FONT COLOR="RED">012345670123012301230123456789AB</FONT></CODE> is the 32 character hexadecimal value (made up for this example) of the GUID
|
||||
"012345678-0123-0123-0123-0123456789ABC" of the PDB file.</LI>
|
||||
<UL><LI>This value might instead be a 8 character hexadecimal value of the ID of the symbol file if it was created by an older version of the tool chain.</LI></UL>
|
||||
<LI><CODE><FONT COLOR="GREEN">1</FONT></CODE> is the hexadecimal value of the 'age' (build number) of the PDB file. Note: most PDB files will have an age value of 1.</LI>
|
||||
</UL>
|
||||
<LI><A name="TwoLevel_SymbolDirectory"></A>In a two-level symbol server directory hierarchy, the same symbol file would be stored
|
||||
as: <CODE>my/myprogram.pdb/012345670123012301230123456789AB1/myprogram.pdb</CODE>, where the
|
||||
first two letters of the symbol file's name are used to create a bucketing directory called "my" where all symbol files starting with
|
||||
"my" are stored.</LI>
|
||||
<UL><LI>A two-level symbol server directory hierarchy is indicated by the presence of a file called <CODE>index2.txt</CODE> in the root directory of
|
||||
the hierarchy.</LI></UL>
|
||||
<LI>Symbol storage directories are expected to have a <CODE>pingme.txt</CODE> file and a special directory called <CODE>000admin</CODE>.</LI>
|
||||
<LI>Compressed symbol files (<CODE>*.pd_</CODE>):</LI>
|
||||
<UL>
|
||||
<LI>Microsoft symbol server tools can compress symbol files to save space. The compressed file is stored in place of the original file, renamed
|
||||
to have a trailing underscore ("_") character in the file extension.</LI>
|
||||
<LI>Before use, Ghidra will decompress the file into the <b>Local Symbol Storage</b> directory, using whatever organization scheme
|
||||
that directory is configured with to store the uncompressed version of the file.</LI>
|
||||
</UL>
|
||||
<LI>Remote symbol files hosted on a HTTP server will be copied to and stored in the configured <b>Local Symbol Storage</b> directory before they
|
||||
can be used.</LI>
|
||||
</UL>
|
||||
<LI><A name="Unorganized_SymbolDirectory"></A>Unorganized directories:</LI>
|
||||
<UL>
|
||||
<LI>Symbol files are are found by matching the leading part of the filename found in the unorganized directory with the sought-after symbol
|
||||
file's name.</LI>
|
||||
<UL>
|
||||
<LI><CODE>helloworld.pdb</CODE> and <CODE>hellokitty.pdb</CODE> would both be found as possible matches when searching for
|
||||
<CODE>hello.pdb</CODE>.</LI>
|
||||
<LI>During searches, each possible matching PDB symbol file will be opened and partially parsed to extract its GUID and age values to determine if
|
||||
it matches the user's full search criteria.</LI>
|
||||
</UL>
|
||||
</UL>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Menu Actions</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H3><A name="Load_PDB_File"></A>Load PDB File</H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>Allows the user to pick a PDB file or search for a PDB file and apply it to the currently open program in the CodeBrowser.</P>
|
||||
<P>Use this action instead of the <b>PDB Analyzer</b> if the PDB file can't be found automatically with the currently configured
|
||||
symbol server search locations, if you need to force load a non-exact PDB file, or you need to use other PDB options.</P>
|
||||
<H4>Steps:</H4>
|
||||
<UL>
|
||||
<LI>Invoke <b>File → Load PDB File...</b>
|
||||
<BLOCKQUOTE><IMG border="0" src="images/LoadPdb_Initial_Screenshot.png"></BLOCKQUOTE></LI>
|
||||
<LI>The <B>PDB Location</B> field will either have the path of an existing matching PDB file, or
|
||||
it will prompt the user to use the browse button to select a file or use the
|
||||
<b>Advanced</b> screen to search for the file.</LI>
|
||||
<UL><LI>A <CODE>PDB.XML</CODE> file can be selected using the browse button. This will limit the selected parser to be the MSDIA parser.</LI></UL>
|
||||
<LI>Use the information displayed in the <B>Program PDB Information</B> panel to help you decide
|
||||
which PDB file to choose.</LI>
|
||||
<LI>If needed, click the <B>Advanced</B> button:
|
||||
<BLOCKQUOTE><IMG border="0" src="images/LoadPdb_Advanced_NeedsConfig.png"></BLOCKQUOTE></LI>
|
||||
<LI>The <b>Local Symbol Storage</b> location is required to enable searching. If missing, set it to a directory where Ghidra can store PDB files.</LI>
|
||||
<UL>
|
||||
<LI>For example, <CODE>/home/your_id/Symbols</CODE> or <CODE>C:\Users\your_name\Symbols</CODE>.</LI>
|
||||
<LI>If the location is a new empty directory, the user will be prompted to initialize the directory as a Microsoft symbol storage directory.</LI>
|
||||
</UL>
|
||||
<LI><A href="#SymbolServerConfig_Add">Add</A> additional search locations by clicking the <img border="0" src="images/Plus2.png"> button.
|
||||
The Microsoft symbol server and <b>Program's Import Location</b> are good defaults.</LI>
|
||||
<LI>Save any changes to the configuration by clicking the <img border="0" src="images/disk.png"> button.</LI>
|
||||
<LI>Set <A href="#PDB_Search_Search_Options">search options</A> as needed.</LI>
|
||||
<LI>Click the <b>Search</b> button to search the configured locations.</LI>
|
||||
<BLOCKQUOTE><IMG border="0" src="images/LoadPdb_Advanced_Screenshot.png"></BLOCKQUOTE></LI>
|
||||
<LI>The <b>Local Symbol Storage</b> location is searched first, followed by any locations listed in the
|
||||
<b>Additional Search Paths</b> list, in listed order.</LI>
|
||||
<LI>Select one of the found PDB files and click the <b>Load</b> button to start the import process.</LI>
|
||||
<LI>Remote symbol files will be downloaded to your <b>Local Symbol Storage</b> location before continuing.</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H3><A name="Symbol_Server_Config"></A>Symbol Server Config</H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>Allows the user to configure the location where PDB symbol files are stored and additional locations to search for
|
||||
existing PDB files. This is also available in the <b>Load PDB File</b>, <b>Advanced</b> screen.</P>
|
||||
<H4>Steps:</H4>
|
||||
<UL>
|
||||
<LI>Invoke <b>Edit → Symbol Server Config</b>
|
||||
<BLOCKQUOTE><IMG border="0" src="images/SymbolServerConfig_Screenshot.png"></BLOCKQUOTE></LI>
|
||||
<LI>The <b>Local Symbol Storage</b> location is required to be able to search. If missing, set it to
|
||||
a directory where Ghidra can store PDB files.</LI>
|
||||
<UL>
|
||||
<LI>For example, <CODE>/home/your_id/Symbols</CODE> or <CODE>C:\Users\your_name\Symbols</CODE>.</LI>
|
||||
<LI>If the location is a new empty directory, the user will be prompted to initialize the directory as a Microsoft symbol storage directory.</LI>
|
||||
</UL>
|
||||
<LI><a href="#SymbolServerConfig_Add">Add</a> additional search locations by clicking the <img border="0" src="images/Plus2.png"> button.</LI>
|
||||
<LI>Save any changes to the configuration by clicking the <img border="0" src="images/disk.png"> button.</LI>
|
||||
<LI>Search locations can be disabled by toggling the <b>enabled</b> checkbox at the beginning of the row.</LI>
|
||||
</UL>
|
||||
|
||||
<H4><A name="SymbolServerConfig_Add"></A><img border="0" src="images/Plus2.png"> (Add)</H4>
|
||||
<BLOCKQUOTE>
|
||||
<P>Allows the user to add a location to the search path list. Pick from the offered types of locations, or pick
|
||||
a predefined location.</P>
|
||||
<BLOCKQUOTE><IMG border="0" src="images/SymbolServerConfig_AddButtonMenu.png"></BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI><B>Directory</B> - allows the user to pick an existing directory that will be searched for symbol files.
|
||||
See <A href="#OneLevel_SymbolDirectory">level 1</A>/<A href="#TwoLevel_SymbolDirectory">level 2</A> or
|
||||
<A href="#Unorganized_SymbolDirectory">unorganized</A> directory descriptions.</LI>
|
||||
<LI><B>URL</B> - allows the user to enter a HTTP or HTTPS URL to a web-based symbol server.</LI>
|
||||
<LI><B>Program's Import Location</B> - automatically references the directory from which the program was imported.</LI>
|
||||
<LI><B>Import _NT_SYMBOL_PATH</B> - parses the current value of the <code>_NT_SYMBOL_PATH</code> system environment variable to extract
|
||||
URLs and symbol directory locations to be added to the Ghidra configuration. If no environment value is present,
|
||||
the user can paste their own value into the text field.</LI>
|
||||
</UL>
|
||||
<P>All items listed after the menu dividing line are automatically added from resource files that have a
|
||||
<CODE>*.pdburl</CODE> extension. The default file included with Ghidra is called <CODE>PDB_SYMBOL_SERVER_URLS.pdburl</CODE> and
|
||||
is located in the <CODE>Ghidra/Configurations/Public_Release/data</CODE> directory under the Ghidra install directory.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4><A name="SymbolServerConfig_Delete"></A><img border="0" src="images/error.png"> (Delete)</H4>
|
||||
<BLOCKQUOTE>
|
||||
<P>Deletes the currently selected locations from the <b>Additional Search Paths</b> table.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4><A name="SymbolServerConfig_MoveUpDown"></A><img border="0" src="images/up.png"><img border="0" src="images/down.png"> (Up/Down)</H4>
|
||||
<BLOCKQUOTE>
|
||||
<P>Moves the currently selected item up or down in the <b>Additional Search Paths</b> table.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4><A name="SymbolServerConfig_Refresh_Status"></A><img border="0" src="images/reload3.png"> (Refresh)</H4>
|
||||
<BLOCKQUOTE>
|
||||
<P>Updates the status column of the locations listed in the <b>Additional Search Paths</b>
|
||||
table. Symbol servers or storage locations that are unreachable or misconfigured will show an error status in that column.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4><A name="SymbolServerConfig_Save"></A><img border="0" src="images/disk.png"> (Save)</H4>
|
||||
<BLOCKQUOTE>
|
||||
<P>Saves the currently displayed search and storage locations to the preferences file. This is shared between all Ghidra tools.</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H3><A name="PDB_Search_Search_Options"></A>PDB Search - Search Options</H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>These options control how PDB symbol files are found.</P>
|
||||
<UL>
|
||||
<LI><B>Ignore Age</B> - allows matching symbol files with the correct GUID, but incorrect age value. Only affects searches of local symbol directories.</LI>
|
||||
<LI><B>Ignore GUID/ID</B> - allows matching symbol files with the correct name, but incorrect GUID or age. Only affects searches of local symbol directories.</LI>
|
||||
<LI><B>Allow Remote</B> - allows the searching of web-based symbol servers. Off by default to prevent sharing possibly sensitive information (PDB name,
|
||||
GUID, age) with web-based symbol servers outside your organization.</LI>
|
||||
</UL>
|
||||
<P>Additionally, there are <b>override</b> checkboxes in the <b>Program PDB Information</b> panel in the <b>Advanced</b> screen. These override values only
|
||||
change the search criteria, they are not persisted to your program's metadata.</P>
|
||||
<UL>
|
||||
<LI><B>PDB Name Checkbox</B> - this checkbox allows entering a custom value for the desired PDB file name.</LI>
|
||||
<LI><B>PDB Unique Id Checkbox</B> - this checkbox allows entering a custom GUID or ID value.</LI>
|
||||
<LI><B>PDB Age Checkbox</B> - this checkbox allows entering a custom age value.</LI>
|
||||
</UL>
|
||||
<P>After changing a search option, you will need to perform another search to use the new options.</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H3><A name="PDB_Parser_Panel"></A>PDB Parser</H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>These options control which PDB parser will be used and any options used during parsing after the <b>Load</b> button is pressed.</P>
|
||||
<UL>
|
||||
<LI><B>Universal</B> - Platform-independent PDB analyzer (No PDB.XML support).</LI>
|
||||
<LI><B>MSDIA</B> - Legacy PDB Analyzer. Requires MS DIA-SDK for raw PDB processing (Windows only), or preprocessed PDB.XML file.</LI>
|
||||
<LI><B>Control</B> - All, Data Types Only, Public Symbols Only.</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Troubleshooting</H2>
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI>If you are connecting to a Symbol Server that requires user authentication using PKI,
|
||||
you must first set your PKI Certificate before attempting to download from the server. See
|
||||
<A href="../../../help/topics/FrontEndPlugin/Ghidra_Front_end_Menus.htm#Set_PKI_Certificate">
|
||||
PKI Certificate</A> for more details.</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BR><BR><BR>
|
||||
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
<UL>
|
||||
<LI><P class="relatedtopic"><A href="PDB.htm">PDB (general)</A></P></LI>
|
||||
</UL>
|
||||
|
||||
<BR><BR><BR>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
@ -1,36 +1,130 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name="generator" content=
|
||||
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||
|
||||
<TITLE>Searching</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<TITLE>Microsoft Program Databases (PDB)</TITLE>
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
<META name="generator" content="Microsoft FrontPage 4.0">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1>PDB</H1>
|
||||
<H1><A name="Using_Microsoft_PDB_Files"></A>Using Microsoft Program Database (PDB) Files</H1>
|
||||
|
||||
<P>Ghidra offers the ability to download and apply PDB debug information for programs that run
|
||||
on Microsoft Windows operating systems.
|
||||
The <I><A href="download_pdb_file.html">Download PDB File</A></I> feature allows users to
|
||||
download and optionally load/apply a PDB file that matches the user's current program, given an
|
||||
accessible Symbol Server.
|
||||
The <I><A href="help/topics/ImporterPlugin/load_pdb.html">Load PDB File</A></I> feature
|
||||
allows users to apply a local PDB file to the current program. The <I>PDB Analyzer</I> also
|
||||
automatically applies PDB symbols (attempting a search for matching PDB files locally) during
|
||||
<A href="help/topics/AutoAnalysisPlugin/AutoAnalysis.htm">Auto-Analysis</A>.</P>
|
||||
<P>A program database (PDB) file holds debugging and project state information about a program
|
||||
and can be created in a number of ways. Historically, it has been created using a Microsoft
|
||||
compiler and written in <CODE>C/C++</CODE>, <CODE>C#</CODE>, and <CODE>Visual Basic</CODE>.
|
||||
A user generates a PDB file using the <CODE>/ZI or /Zi</CODE> flag (for C/C++ programs) or the
|
||||
<CODE>/debug</CODE> flag (for Visual Basic/C# programs).</P>
|
||||
|
||||
<P>There are two mechanisms for processing a PDB file. First, the platform-independent
|
||||
PDB Universal Reader/Analyzer, which can read a raw PDB file and apply it. Its capabilities
|
||||
are expected to be expanded in future releases. Second, the legacy capability that uses the
|
||||
<A href="#dia">DIA SDK</A> to read information from the PDB file. This mechanism can only run
|
||||
on a Windows platform, however it creates an XML representation of information gleaned using
|
||||
the DIA SDK. These XML files can be saved and then used on Windows and non-Windows platforms
|
||||
hosting Ghidra.</P>
|
||||
|
||||
<P>If loading a PDB, this should be done prior to other analysis, except in special cases,
|
||||
such as when only loading data types.</P>
|
||||
|
||||
<P>Restricted loading of data types or public symbols is
|
||||
supported by PDB Universal.</P>
|
||||
|
||||
<H2>To Load a PDB</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>PDB files can be loaded in two ways:</P>
|
||||
<UL>
|
||||
<LI><B>File → <a href="LoadPDBNew.html#Load_PDB_File">Load PDB File</a></B></LI>
|
||||
<LI>PDB Analyzer via <B>Analysis →
|
||||
<A HREF="help/topics/AutoAnalysisPlugin/AutoAnalysis.htm#Auto_Analyze">Auto Analyze</A></B> or
|
||||
<B>Analysis → <A HREF="help/topics/AutoAnalysisPlugin/AutoAnalysis.htm#Analyze_One_Shot">One Shot</A></B>.
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Information Loaded From PDB</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<OL>
|
||||
<LI>Structure and union definitions</LI>
|
||||
|
||||
<LI>Typedefs</LI>
|
||||
|
||||
<LI>Enumerations</LI>
|
||||
|
||||
<LI>Class definitions</LI>
|
||||
|
||||
<LI>Function prototypes</LI>
|
||||
|
||||
<LI>Stack variable names and data types</LI>
|
||||
|
||||
<LI>Source line numbers</LI>
|
||||
|
||||
<LI>Instruction and data symbols</LI>
|
||||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>The DIA SDK-Based Capability</H2>
|
||||
|
||||
<P>*.PDB.XML files can be created in three different ways:
|
||||
|
||||
<BLOCKQUOTE><UL>
|
||||
<LI>From the Ghidra GUI in Windows, use the
|
||||
<A href="help/topics/GhidraScriptMgrPlugin/GhidraScriptMgrPlugin.htm">Ghidra Script Manager</A>
|
||||
to run the <I>CreatePdbXmlFilesScript.java</I> script. Follow the prompts to choose
|
||||
the .PDB file (or directory containing .PDB file(s)) to be converted to .PDB.XML form.
|
||||
When given a directory, the script recursively traverses all subfolders to find .PDB
|
||||
files. A created .PDB.XML file is placed in the same location as the corresponding original
|
||||
.PDB file.</LI>
|
||||
<br>
|
||||
<LI>From a Windows command line, navigate to the following directory:
|
||||
<I><ghidra install root>/support</I>
|
||||
and run the <I>createPdbXmlFiles.bat</I> script. The script takes one argument representing
|
||||
either one .PDB file or a directory of .PDB files. When given a directory, the script
|
||||
recursively traverses all subdirectories to find .PDB files. A created .PDB.XML file is
|
||||
placed in the same location as the corresponding original .PDB file. Sample calls to the
|
||||
script are shown below.
|
||||
<br><br>
|
||||
<CODE> createPdbXmlFiles.bat C:\Symbols\samplePdb.pdb</CODE>
|
||||
<br>
|
||||
<CODE> createPdbXmlFiles.bat C:\Symbols</CODE>
|
||||
<br>
|
||||
</LI>
|
||||
<br>
|
||||
<LI>Run the included <I>pdb.exe</I> executable (found in the <I><ghidra install
|
||||
root>/Ghidra/Features/PDB/os/win64</I> directory) and redirect (save) its output to an
|
||||
XML file as shown below:
|
||||
<br><br>
|
||||
<CODE> pdb.exe samplePdb.pdb > samplePdb.pdb.xml</CODE>
|
||||
</LI>
|
||||
</UL></BLOCKQUOTE>
|
||||
</P>
|
||||
<P><B>NOTE:</B> Execution of <i>pdb.exe</i> has runtime dependencies which must be satisfied.
|
||||
Please refer to the <a href="docs/README_PDB.html">README_PDB</a> document for details.</P>
|
||||
|
||||
<H2><A name="dia"></A>Debug Interface Access SDK</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The Microsoft Debug Interface Access Software Development Kit (DIA SDK) provides access to
|
||||
debug information stored in program database (.PDB) files generated by Microsoft
|
||||
post-compiler tools. Because the format of the .PDB file generated by the post-compiler tools
|
||||
undergoes constant revision, exposing the format is impractical. Using the DIA API, you can
|
||||
develop applications that search for and browse debug information stored in a .PDB file. Such
|
||||
applications could, for example, report stack trace-back information and analyze performance
|
||||
data.</P>
|
||||
|
||||
<P><IMG src="../../shared/note.png" border="0">If you are attempting to load a PDB on a
|
||||
Windows machine and see an error message such as "Unable to locate the DIA SDK,"
|
||||
you will need to add and register one or more files on your computer. Refer to the
|
||||
<a href="docs/README_PDB.html">README_PDB</a> document for detailed instructions.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
|
||||
<UL>
|
||||
<LI><A href="download_pdb_file.html">Download PDB File</A></LI>
|
||||
|
||||
<LI><A href="help/topics/ImporterPlugin/load_pdb.html">Load PDB File</A></LI>
|
||||
|
||||
<LI><A href="LoadPDBNew.html">Load PDB File</A></LI>
|
||||
<LI><A href="help/topics/AutoAnalysisPlugin/AutoAnalysis.htm">Auto Analysis</A></LI>
|
||||
</UL>
|
||||
|
||||
|
@ -1,184 +0,0 @@
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name="generator" content=
|
||||
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||
|
||||
<TITLE>Download PDB File</TITLE>
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="Download_PDB_File"></A>Download PDB File</H1>
|
||||
|
||||
<P>Ghidra offers the ability to download and apply a PDB file that corresponds to the program
|
||||
currently open in the CodeBrowser. Successful downloading requires, at a minimum, that:</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<OL>
|
||||
<LI>A Symbol Server URL is available and accessible from the client or computer where you are running Ghidra.</LI>
|
||||
<LI>The program open in the CodeBrowser is a PE file that was compiled by a Microsoft compiler.</LI>
|
||||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>A Note for Windows Users</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>If set, Ghidra parses the <CODE>_NT_SYMBOL_PATH</CODE>
|
||||
environment variable that is used to specify a PDB download location and Symbol Server URL(s).
|
||||
The syntax for <CODE>_NT_SYMBOL_PATH</CODE> is shown below:</P>
|
||||
|
||||
<P><SPAN STYLE="margin-left:50px"><CODE>srv*[local symbols location]*[Symbol Server URL]</CODE></SPAN></P>
|
||||
|
||||
<P>The <CODE>_NT_SYMBOL_PATH</CODE> symbols location is used to pre-populate the dialog that asks
|
||||
for the local storage location (as long as that location is valid). The <CODE>_NT_SYMBOL_PATH</CODE>
|
||||
Symbol Server URL is used to pre-populate the dialog that asks for the Symbol Server location.</P>
|
||||
|
||||
<P><IMG src="../../shared/note.png" border="0">Although multiple Symbol Server URLs can be
|
||||
specified in the <CODE>_NT_SYMBOL_PATH</CODE> variable, Ghidra only uses the first listed URL.</IMG></P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>To Download a PDB</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
<OL>
|
||||
<LI>From the menu-bar of a tool, select <B>File <IMG src="../../shared/arrow.gif" alt=""
|
||||
width="18" height="14"> Download PDB File</B></LI>
|
||||
|
||||
<BR>
|
||||
|
||||
<LI>A dialog appears asking whether you want to download a PDB or XML (PDB.XML) file. Select
|
||||
the type of file you want to download and click OK.</LI>
|
||||
|
||||
<P align="center"><IMG border="0" src="images/PdbOrXmlDialog.png"></P>
|
||||
|
||||
<P><IMG src="../../shared/note.png" border="0">A Symbol Server should always have PDB
|
||||
files available for download. In contrast, .PDB.XML files are Ghidra-created files, and are
|
||||
only available to download from the Symbol Server if Ghidra tools have been used to create
|
||||
them and the server's admin has made them available. If you choose to download a .PDB.XML
|
||||
file and it is not found on the server, you will see a dialog message telling you so. For
|
||||
more information on creating and using .PDB.XML files, see the
|
||||
<A href="../../../help/topics/ImporterPlugin/load_pdb.html">Load PDB File</A> section.</P>
|
||||
|
||||
<BR>
|
||||
|
||||
<LI>Before attempting to download the file, an attempt will first be made to locate it
|
||||
using file and path names associated with the program. A dialog appears asking whether you
|
||||
want to include the PE-Header-Specified Path, which could include a Universal Naming
|
||||
Convention (UNC) path of a location that might not be trusted. Select OK if you want to
|
||||
perform this potentially unsafe retrieval.</LI>
|
||||
|
||||
<P align="center"><IMG border="0" src="images/PeSpecifiedPathDialog.png"></P>
|
||||
|
||||
<BR>
|
||||
|
||||
<LI>A dialog appears asking where to save the downloaded file. Pick a location to store your
|
||||
PDB files. A common location on Windows is <I>C:\Symbols</I>.</LI>
|
||||
|
||||
<BR>
|
||||
|
||||
<LI>At this point, if a PDB file of the type you have chosen (either .PDB or .PDB.XML) already
|
||||
exists in the selected location, you will see a message indicating that a potential matching
|
||||
PDB has been found. You will then be asked if you would like to continue with the download.
|
||||
<BR><BR>
|
||||
<UL>
|
||||
<LI>If you select "No", jump to Step 7.</LI>
|
||||
<BR>
|
||||
<LI>If you select "Yes", please keep the following things in mind relating to a found
|
||||
.PDB or .PDB.XML file:</LI>
|
||||
<BR>
|
||||
<UL>
|
||||
<LI>If the found file is not in a directory that contains the current binary's GUID
|
||||
(i.e., <I>C:\Symbols\<pdbfilename>\<GUID></I>), then the file is not
|
||||
guaranteed to be an exact match for the current binary (when there is no GUID subfolder,
|
||||
a matching file is found based on expected PDB filename).</LI>
|
||||
|
||||
<BR>
|
||||
|
||||
<LI>If there is any doubt about whether the found PDB file matches, it is a good
|
||||
idea to try to download the matching file, anyway (the matching file will be saved
|
||||
in a directory of the form <I><download location>/<pdbfilename>/
|
||||
<GUID></I>).</LI>
|
||||
|
||||
<BR>
|
||||
|
||||
<LI>If you do choose to continue to apply the found PDB file, and its GUID does not
|
||||
match the GUID of the current binary, you will be warned and given the option of
|
||||
canceling the application of the PDB file.</LI>
|
||||
</UL>
|
||||
</UL>
|
||||
|
||||
<BR>
|
||||
|
||||
<LI>Next, you will see a dialog asking for the Symbol Server URL.
|
||||
|
||||
<BR><BR>
|
||||
|
||||
<P align="center"><IMG border="0" src="images/SymbolServerURLDialog.png"></P>
|
||||
|
||||
<BR>
|
||||
|
||||
If a list of known URLs exists in your distribution (the file will have the extension
|
||||
<CODE>.pdburl</CODE>), the dialog will also include a button with the text "Choose from
|
||||
known URLs". When this button is pressed, a separate dialog appears showing known Symbol
|
||||
Server URLs.
|
||||
|
||||
<BR>
|
||||
|
||||
<P align="center"><IMG border="0" src="images/KnownSymbolServerURLsDialog.png"></P>
|
||||
|
||||
<BR>
|
||||
|
||||
You may choose any of these URLs or manually type one in. If manually typing in a URL,
|
||||
be sure to include the protocol (<I>http</I> or <I>https</I>).
|
||||
|
||||
<BR>
|
||||
|
||||
<P><IMG src="../../shared/warning.png" border="0">Always be sure to check your organization's
|
||||
security policy before downloading any file from the internet.</P>
|
||||
</LI>
|
||||
|
||||
<BR>
|
||||
|
||||
<LI>Next, if the Symbol Server contains a matching PDB that is the same file type that you
|
||||
chose earlier, it will return with a message indicating that the download was successful.
|
||||
The message also contains the path where you can find the downloaded file.</LI>
|
||||
|
||||
<P align="center"><IMG border="0" src="images/SuccessDialog.png"></P>
|
||||
<BR>
|
||||
|
||||
<LI>If the download was successful or an existing PDB file was found, you may be asked
|
||||
whether you want to apply the PDB to the program.</LI>
|
||||
|
||||
<P><IMG src="../../shared/note.png" border="0">If Yes is chosen, see
|
||||
<A href="help/topics/ImporterPlugin/load_pdb.html">Load PDB File</A> for continued help.</P>
|
||||
|
||||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BR><BR>
|
||||
<H2>Troubleshooting</H2>
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI>If you are connecting to a Symbol Server that requires user authentication using PKI,
|
||||
you must first set your PKI Certificate before attempting to download from the server. See
|
||||
<A href="../../../help/topics/FrontEndPlugin/Ghidra_Front_end_Menus.htm#Set_PKI_Certificate">
|
||||
PKI Certificate</A> for more details.</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<BR><BR><BR>
|
||||
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
<UL>
|
||||
<LI>
|
||||
<P class="relatedtopic"><A href="../../../help/topics/ImporterPlugin/load_pdb.html">Load PDB</A></P>
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
<BR><BR><BR>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
Before Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 752 B |
Before Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 620 B |
After Width: | Height: | Size: 192 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 979 B |
BIN
Ghidra/Features/PDB/src/main/help/help/topics/Pdb/images/up.png
Normal file
After Width: | Height: | Size: 193 B |
@ -17,19 +17,14 @@ package ghidra.app.plugin.core.analysis;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.util.bin.format.pdb.*;
|
||||
import ghidra.app.util.bin.format.pdb.PdbException;
|
||||
import ghidra.app.util.bin.format.pdb.PdbParser;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.app.util.opinion.PeLoader;
|
||||
import ghidra.app.util.pdb.PdbLocator;
|
||||
import ghidra.framework.options.OptionType;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@ -40,25 +35,14 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
||||
static final String NAME = "PDB MSDIA";
|
||||
static final boolean DEFAULT_ENABLEMENT = !PdbUniversalAnalyzer.DEFAULT_ENABLEMENT;
|
||||
private static final String DESCRIPTION =
|
||||
"PDB Analyzer.\n" + "Requires MS DIA-SDK for raw PDB processing (Windows only).\n" +
|
||||
"Also supports pre-processed XML files.";
|
||||
"PDB Analyzer.\n" +
|
||||
"Requires MS DIA-SDK for raw PDB processing (Windows only).\n" +
|
||||
"Also supports pre-processed XML files.\n" +
|
||||
"PDB Symbol Server searching is configured in Edit -> Symbol Server Config.\n";
|
||||
|
||||
private static final String ERROR_TITLE = "Error in PDB Analyzer";
|
||||
|
||||
private static final String SYMBOLPATH_OPTION_NAME = "Symbol Repository Path";
|
||||
private static final String SYMBOLPATH_OPTION_DESCRIPTION =
|
||||
"Directory path to root of Microsoft Symbol Repository Directory";
|
||||
|
||||
private File symbolsRepositoryDir = PdbLocator.DEFAULT_SYMBOLS_DIR;
|
||||
|
||||
//==============================================================================================
|
||||
// Include the PE-Header-Specified PDB path for searching for appropriate PDB file.
|
||||
private static final String OPTION_NAME_INCLUDE_PE_PDB_PATH =
|
||||
"Unsafe: Include PE PDB Path in PDB Search";
|
||||
private static final String OPTION_DESCRIPTION_INCLUDE_PE_PDB_PATH =
|
||||
"If checked, specifically searching for PDB in PE-Header-Specified Location.";
|
||||
|
||||
private boolean includePeSpecifiedPdbPath = false;
|
||||
private boolean searchRemoteLocations = false;
|
||||
|
||||
// only try once per transaction due to extensive error logging which may get duplicated
|
||||
private long lastTransactionId = -1;
|
||||
@ -101,75 +85,21 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
||||
return false;
|
||||
}
|
||||
|
||||
File pdb = lookForPdb(program, log);
|
||||
|
||||
if (pdb == null) {
|
||||
Msg.info(this, "PDB analyzer failed to locate PDB file");
|
||||
File pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchRemoteLocations, monitor);
|
||||
if (pdbFile == null) {
|
||||
// warnings have already been logged
|
||||
return false;
|
||||
}
|
||||
Msg.info(this, "PDB analyzer parsing file: " + pdb.getAbsolutePath());
|
||||
|
||||
AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(program);
|
||||
return parsePdb(pdb, program, mgr, monitor, log);
|
||||
}
|
||||
|
||||
private static class PdbMissingState implements AnalysisState {
|
||||
// object existence indicates missing PDB has already been reported
|
||||
}
|
||||
|
||||
File lookForPdb(Program program, MessageLog log) {
|
||||
String message = "";
|
||||
File pdb;
|
||||
|
||||
try {
|
||||
|
||||
pdb = PdbParser.findPDB(program, includePeSpecifiedPdbPath, symbolsRepositoryDir);
|
||||
|
||||
if (pdb == null) {
|
||||
|
||||
PdbMissingState missingState =
|
||||
AnalysisStateInfo.getAnalysisState(program, PdbMissingState.class);
|
||||
if (missingState != null) {
|
||||
return null; // already notified user
|
||||
}
|
||||
AnalysisStateInfo.putAnalysisState(program, new PdbMissingState());
|
||||
|
||||
String pdbName = program.getOptions(Program.PROGRAM_INFO).getString(
|
||||
PdbParserConstants.PDB_FILE, (String) null);
|
||||
if (StringUtils.isBlank(pdbName)) {
|
||||
message = "Program has no associated PDB file.";
|
||||
}
|
||||
else {
|
||||
message = "Unable to locate PDB file \"" + pdbName + "\" with matching GUID.";
|
||||
}
|
||||
if (SystemUtilities.isInHeadlessMode()) {
|
||||
message += "\n Use a script to set the PDB file location. I.e.,\n" +
|
||||
" setAnalysisOption(currentProgram, \"PDB.Symbol Repository Path\", \"/path/to/pdb/folder\");\n" +
|
||||
" This must be done using a pre-script (prior to analysis).";
|
||||
}
|
||||
else {
|
||||
message += "\n You may set the PDB \"Symbol Repository Path\"" +
|
||||
"\n using \"Edit->Options for [program]\" prior to analysis." +
|
||||
"\nIt is important that a PDB is used during initial analysis " +
|
||||
"\nif available.";
|
||||
}
|
||||
}
|
||||
|
||||
return pdb;
|
||||
}
|
||||
finally {
|
||||
if (message.length() > 0) {
|
||||
log.appendMsg(getName(), message);
|
||||
log.setStatus(message);
|
||||
}
|
||||
}
|
||||
|
||||
return parsePdb(pdbFile, program, mgr, monitor, log);
|
||||
}
|
||||
|
||||
boolean parsePdb(File pdb, Program program, AutoAnalysisManager mgr, TaskMonitor monitor,
|
||||
MessageLog log) {
|
||||
DataTypeManagerService dataTypeManagerService = mgr.getDataTypeManagerService();
|
||||
PdbParser parser = new PdbParser(pdb, program, dataTypeManagerService, true, monitor);
|
||||
PdbParser parser =
|
||||
new PdbParser(pdb, program, dataTypeManagerService, true, false, monitor);
|
||||
|
||||
String message;
|
||||
|
||||
@ -201,32 +131,52 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
||||
|
||||
@Override
|
||||
public boolean canAnalyze(Program program) {
|
||||
return PeLoader.PE_NAME.equals(program.getExecutableFormat());
|
||||
return PdbAnalyzerCommon.canAnalyzeProgram(program);
|
||||
//return PeLoader.PE_NAME.equals(program.getExecutableFormat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOptions(Options options, Program program) {
|
||||
|
||||
symbolsRepositoryDir = PdbLocator.getDefaultPdbSymbolsDir();
|
||||
|
||||
options.registerOption(SYMBOLPATH_OPTION_NAME, OptionType.FILE_TYPE, symbolsRepositoryDir,
|
||||
null, SYMBOLPATH_OPTION_DESCRIPTION);
|
||||
|
||||
options.registerOption(OPTION_NAME_INCLUDE_PE_PDB_PATH, includePeSpecifiedPdbPath, null,
|
||||
OPTION_DESCRIPTION_INCLUDE_PE_PDB_PATH);
|
||||
options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS,
|
||||
searchRemoteLocations, null,
|
||||
PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void optionsChanged(Options options, Program program) {
|
||||
|
||||
File symbolsDir = options.getFile(SYMBOLPATH_OPTION_NAME, symbolsRepositoryDir);
|
||||
if (!symbolsDir.equals(symbolsRepositoryDir)) {
|
||||
symbolsRepositoryDir = symbolsDir;
|
||||
PdbLocator.setDefaultPdbSymbolsDir(symbolsDir);
|
||||
searchRemoteLocations = options.getBoolean(
|
||||
PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, searchRemoteLocations);
|
||||
}
|
||||
|
||||
includePeSpecifiedPdbPath =
|
||||
options.getBoolean(OPTION_NAME_INCLUDE_PE_PDB_PATH, includePeSpecifiedPdbPath);
|
||||
/**
|
||||
* Sets the PDB file that will be used by the analyzer when it is next invoked
|
||||
* on the specified program.
|
||||
* <p>
|
||||
* Normally the analyzer would locate the PDB file on its own, but if a
|
||||
* headless script wishes to override the analyzer's behaivor, it can
|
||||
* use this method to specify a file.
|
||||
*
|
||||
* @param program {@link Program}
|
||||
* @param pdbFile the pdb file
|
||||
*/
|
||||
public static void setPdbFileOption(Program program, File pdbFile) {
|
||||
PdbAnalyzerCommon.setPdbFileOption(NAME, program, pdbFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the "allow remote" option that will be used by the analyzer when it is next invoked
|
||||
* on the specified program.
|
||||
* <p>
|
||||
* Normally when the analyzer attempts to locate a matching PDB file it
|
||||
* will default to NOT searching remote symbol servers. A headless script could
|
||||
* use this method to allow the analyzer to search remote symbol servers.
|
||||
*
|
||||
* @param program {@link Program}
|
||||
* @param allowRemote boolean flag, true means analyzer can search remote symbol
|
||||
* servers
|
||||
*/
|
||||
public static void setAllowRemoteOption(Program program, boolean allowRemote) {
|
||||
PdbAnalyzerCommon.setAllowRemoteOption(NAME, program, allowRemote);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,182 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.analysis;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import ghidra.app.services.Analyzer;
|
||||
import ghidra.app.util.opinion.PeLoader;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import pdb.PdbPlugin;
|
||||
import pdb.symbolserver.FindOption;
|
||||
import pdb.symbolserver.SymbolFileInfo;
|
||||
|
||||
/**
|
||||
* Shared configuration values and pdb searching logic
|
||||
*/
|
||||
public class PdbAnalyzerCommon {
|
||||
static final String OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS =
|
||||
"If checked, allow searching remote symbol servers for PDB files.";
|
||||
static final String OPTION_NAME_SEARCH_REMOTE_LOCATIONS = "Search remote symbol servers";
|
||||
|
||||
static final String OPTION_DESCRIPTION_PDB_FILE = "Path to a manually chosen PDB file.";
|
||||
static final String OPTION_NAME_PDB_FILE = "PDB File";
|
||||
|
||||
// TODO: I changed this method from what was lifted in the old code. I check for null string
|
||||
// and I also check for MSCOFF_NAME (TODO: check on the validity of this!!!). Also, changed
|
||||
// the comparison to a substring search from a .equals).
|
||||
/**
|
||||
* Returns true if the specified program is supported by either of the
|
||||
* Pdb analyzers.
|
||||
*
|
||||
* @param program {@link Program}
|
||||
* @return boolean true if program is supported by Pdb analyzers
|
||||
*/
|
||||
public static boolean canAnalyzeProgram(Program program) {
|
||||
String executableFormat = program.getExecutableFormat();
|
||||
return executableFormat != null && (executableFormat.indexOf(PeLoader.PE_NAME) != -1);
|
||||
// TODO: Check for MSCOFF_NAME. Initial investigation shows that the .debug$T section of
|
||||
// the MSCOFF (*.obj) file has type records and the .debug$S section has symbol records.
|
||||
// More than that, in at least one instance, there has been a TypeServer2MsType type
|
||||
// record that give the GUID, age, and name of the PDB file associated with the MSCOFF
|
||||
// file. At this point in time, these two sections of the MSCOFF are read (header and
|
||||
// raw data), but we do not interpret these sections any further. Suggest that we "might"
|
||||
// want to parse some of these records at load time? Maybe not. We could, at analysis
|
||||
// time, add the ability to process these two sections (as part of analysis (though we
|
||||
// will not be aware of a PDB file yet), and upon discovery of a TypeServer2MsType (or
|
||||
// perhaps other?), proceed to find the file (if possible) and also process that file.
|
||||
// We posit that if a record indicates a separate PDB for the types (Note: MSFT indicates
|
||||
// that only data types will be found in an MSCOFF PDB file), then that will likely be
|
||||
// the only record in the .debug$T section.
|
||||
// TODO: If the MSCOFF file is located in a MSCOFF ARCHIVE (*.lib), there can be a PDB
|
||||
// associated with the archive. We currently do not pass on this association of the
|
||||
// PDB archive to each underlying MSCOFF file. Moreover, we believe that we are not
|
||||
// currently discovering the associated MSCOFF ARCHIVE PDB file when processing the
|
||||
// MSCOFF ARCHIVE. Initial indication is that each MSCOFF within the archive will have
|
||||
// the PDB file that it needs listed, even if redundant for each MSCOFF within the
|
||||
// archive.
|
||||
// return executableFormat != null && (executableFormat.indexOf(PeLoader.PE_NAME) != -1 ||
|
||||
// executableFormat.indexOf(MSCoffLoader.MSCOFF_NAME) != -1);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Common logic to set a manual Pdb file that the specified analyzer will find and use
|
||||
* when it is invoked later<p>
|
||||
* Each specific analyzer has a public method that calls this to supply the
|
||||
* actual analyzer name to make it easier for script writers to call.
|
||||
*
|
||||
* @param analyzerName name of analyzer
|
||||
* @param program {@link Program}
|
||||
* @param pdbFile the file
|
||||
*/
|
||||
static void setPdbFileOption(String analyzerName, Program program, File pdbFile) {
|
||||
Options options = program.getOptions(Program.ANALYSIS_PROPERTIES);
|
||||
options.setFile(analyzerName + "." + OPTION_NAME_PDB_FILE, pdbFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common logic to set the "allow remote" option that the specified analyzer will find and use
|
||||
* when it is invoked later<p>
|
||||
* Each specific analyzer has a public method that calls this to supply the
|
||||
* actual analyzer name to make it easier for script writers to call.
|
||||
*
|
||||
* @param analyzerName name of analyzer
|
||||
* @param program {@link Program}
|
||||
* @param allowRemote boolean flag, true means the analyzer can search remote
|
||||
* symbol servers
|
||||
*/
|
||||
static void setAllowRemoteOption(String analyzerName, Program program, boolean allowRemote) {
|
||||
Options options = program.getOptions(Program.ANALYSIS_PROPERTIES);
|
||||
options.setBoolean(analyzerName + "." + OPTION_NAME_SEARCH_REMOTE_LOCATIONS, allowRemote);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common pdb searching logic between both analyzers.
|
||||
*
|
||||
* @param pdbAnalyzer the analyzer doing the searching
|
||||
* @param program the program
|
||||
* @param allowRemote boolean flag, true means searching remote symbol servers
|
||||
* is allowed
|
||||
* @param monitor {@link TaskMonitor} to let user cancel
|
||||
* @return File pointing to the found pdb, or null if not found or error
|
||||
*/
|
||||
static File findPdb(Analyzer pdbAnalyzer, Program program, boolean allowRemote,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
SymbolFileInfo symbolFileInfo = SymbolFileInfo.fromMetadata(program.getMetadata());
|
||||
if (symbolFileInfo == null) {
|
||||
Msg.info(pdbAnalyzer,
|
||||
"Skipping PDB processing: missing PDB information in program metadata");
|
||||
return null;
|
||||
}
|
||||
|
||||
// First look in the program's analysis options to see if there is a
|
||||
// manually specified pdbFile. (see setPdbFileOption)
|
||||
// If not set, then do a search using the currently configured symbol servers.
|
||||
Options options = program.getOptions(Program.ANALYSIS_PROPERTIES);
|
||||
String pdbFileOptionName = pdbAnalyzer.getName() + "." + OPTION_NAME_PDB_FILE;
|
||||
|
||||
// check existence first to avoid creating option value
|
||||
File pdbFile = options.contains(pdbFileOptionName)
|
||||
? options.getFile(pdbFileOptionName, null)
|
||||
: null;
|
||||
if (pdbFile == null) {
|
||||
Set<FindOption> findOpts = allowRemote
|
||||
? FindOption.of(FindOption.ALLOW_REMOTE)
|
||||
: FindOption.NO_OPTIONS;
|
||||
pdbFile = PdbPlugin.findPdb(program, findOpts, monitor);
|
||||
}
|
||||
if (pdbFile == null) {
|
||||
Msg.info(pdbAnalyzer,
|
||||
"Skipping PDB processing: failed to locate PDB file in configured locations");
|
||||
if (SystemUtilities.isInHeadlessMode()) {
|
||||
Msg.info(pdbAnalyzer,
|
||||
"Use a script to set the PDB file location. I.e.,\n" +
|
||||
" PdbAnalyzer.setPdbFileOption(currentProgram, new File(\"/path/to/pdb/file.pdb\")); or\n" +
|
||||
" PdbUniversalAnalyzer.setPdbFileOption(currentProgram, new File(\"/path/to/pdb/file.pdb\"));\n" +
|
||||
"Or set the symbol server search configuration using:" +
|
||||
" PdbPlugin.saveSymbolServerServiceConfig(...);\n" +
|
||||
" This must be done using a pre-script (prior to analysis).");
|
||||
}
|
||||
else {
|
||||
Msg.info(pdbAnalyzer,
|
||||
"You may set the PDB \"Symbol Server Config\"" +
|
||||
"\n using \"Edit->Symbol Server Config\" prior to analysis." +
|
||||
"\nIt is important that a PDB is used during initial analysis " +
|
||||
"\nif available.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
Msg.info(pdbAnalyzer, "PDB analyzer parsing file: " + pdbFile);
|
||||
if (!pdbFile.isFile()) {
|
||||
Msg.error(pdbAnalyzer,
|
||||
"Skipping PDB processing: specified file does not exist or is not readable: " +
|
||||
pdbFile);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
return pdbFile;
|
||||
}
|
||||
|
||||
}
|
@ -19,12 +19,9 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.util.bin.format.pdb2.pdbreader.*;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.app.util.opinion.PeLoader;
|
||||
import ghidra.app.util.pdb.PdbLocator;
|
||||
import ghidra.app.util.pdb.PdbProgramAttributes;
|
||||
import ghidra.app.util.pdb.pdbapplicator.PdbApplicator;
|
||||
@ -35,7 +32,6 @@ import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@ -63,7 +59,8 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||
static final boolean DEFAULT_ENABLEMENT = true;
|
||||
private static final String DESCRIPTION =
|
||||
"Platform-independent PDB analyzer (No XML support).\n" +
|
||||
"NOTE: still undergoing development, so options may change.";
|
||||
"NOTE: still undergoing development, so options may change.\n" +
|
||||
"PDB Symbol Server searching is configured in Edit -> Symbol Server Config.\n";
|
||||
|
||||
//==============================================================================================
|
||||
// Force-load a PDB file.
|
||||
@ -79,18 +76,7 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||
private File DEFAULT_FORCE_LOAD_FILE = new File(PdbLocator.DEFAULT_SYMBOLS_DIR, "sample.pdb");
|
||||
private File forceLoadFile;
|
||||
|
||||
// Symbol Repository Path.
|
||||
private static final String OPTION_NAME_SYMBOLPATH = "Symbol Repository Path";
|
||||
private static final String OPTION_DESCRIPTION_SYMBOLPATH =
|
||||
"Directory path to root of Microsoft Symbol Repository Directory";
|
||||
private File symbolsRepositoryDir;
|
||||
|
||||
// Include the PE-Header-Specified PDB path for searching for appropriate PDB file.
|
||||
private static final String OPTION_NAME_INCLUDE_PE_PDB_PATH =
|
||||
"Unsafe: Include PE PDB Path in PDB Search";
|
||||
private static final String OPTION_DESCRIPTION_INCLUDE_PE_PDB_PATH =
|
||||
"If checked, specifically searching for PDB in PE-Header-Specified Location.";
|
||||
private boolean includePeSpecifiedPdbPath = false;
|
||||
private boolean searchRemoteLocations = false;
|
||||
|
||||
//==============================================================================================
|
||||
// Additional instance data
|
||||
@ -162,34 +148,21 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (failMissingFilename(programAttributes, log) ||
|
||||
failMissingAttributes(programAttributes, log)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String pdbFilename;
|
||||
if (doForceLoad) {
|
||||
if (!confirmFile(forceLoadFile)) {
|
||||
File pdbFile = null;
|
||||
if (doForceLoad && forceLoadFile != null) {
|
||||
if (!forceLoadFile.isFile()) {
|
||||
logFailure("Force-load PDB file does not exist: " + forceLoadFile, log);
|
||||
return false;
|
||||
}
|
||||
pdbFilename = forceLoadFile.getAbsolutePath();
|
||||
pdbFile = forceLoadFile;
|
||||
}
|
||||
else {
|
||||
PdbLocator locator = new PdbLocator(symbolsRepositoryDir);
|
||||
pdbFilename =
|
||||
locator.findPdb(program, programAttributes, !SystemUtilities.isInHeadlessMode(),
|
||||
includePeSpecifiedPdbPath, monitor, log, getName());
|
||||
if (pdbFilename == null) {
|
||||
if (!confirmDirectory(symbolsRepositoryDir)) {
|
||||
logFailure("PDB symbol repository directory not found: " + symbolsRepositoryDir,
|
||||
log);
|
||||
pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchRemoteLocations, monitor);
|
||||
}
|
||||
Msg.info(this, "PDB analyzer failed to locate PDB file");
|
||||
if (pdbFile == null) {
|
||||
// warnings have already been logged
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Msg.info(this, "PDB analyzer parsing file: " + pdbFilename);
|
||||
|
||||
PdbLog.message(
|
||||
"================================================================================");
|
||||
@ -197,61 +170,33 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||
PdbLog.message("Ghidra Version: " + Application.getApplicationVersion());
|
||||
PdbLog.message(NAME);
|
||||
PdbLog.message(DESCRIPTION);
|
||||
PdbLog.message("PDB Filename: " + pdbFilename + "\n");
|
||||
PdbLog.message("PDB Filename: " + pdbFile + "\n");
|
||||
|
||||
try (AbstractPdb pdb = PdbParser.parse(pdbFilename, pdbReaderOptions, monitor)) {
|
||||
monitor.setMessage("PDB: Parsing " + pdbFilename + "...");
|
||||
try (AbstractPdb pdb = PdbParser.parse(pdbFile.getPath(), pdbReaderOptions, monitor)) {
|
||||
monitor.setMessage("PDB: Parsing " + pdbFile + "...");
|
||||
pdb.deserialize(monitor);
|
||||
PdbApplicator applicator = new PdbApplicator(pdbFilename, pdb);
|
||||
PdbApplicator applicator = new PdbApplicator(pdbFile.getPath(), pdb);
|
||||
applicator.applyTo(program, program.getDataTypeManager(), program.getImageBase(),
|
||||
pdbApplicatorOptions, monitor, log);
|
||||
|
||||
}
|
||||
catch (PdbException | IOException e) {
|
||||
log.appendMsg(getName(),
|
||||
"Issue processing PDB file: " + pdbFilename + ":\n " + e.toString());
|
||||
"Issue processing PDB file: " + pdbFile + ":\n " + e.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: I changed this method from what was lifted in the old code. I check for null string
|
||||
// and I also check for MSCOFF_NAME (TODO: check on the validity of this!!!). Also, changed
|
||||
// the comparison to a substring search from a .equals).
|
||||
@Override
|
||||
public boolean canAnalyze(Program program) {
|
||||
String executableFormat = program.getExecutableFormat();
|
||||
return executableFormat != null && (executableFormat.indexOf(PeLoader.PE_NAME) != -1);
|
||||
// TODO: Check for MSCOFF_NAME. Initial investigation shows that the .debug$T section of
|
||||
// the MSCOFF (*.obj) file has type records and the .debug$S section has symbol records.
|
||||
// More than that, in at least one instance, there has been a TypeServer2MsType type
|
||||
// record that give the GUID, age, and name of the PDB file associated with the MSCOFF
|
||||
// file. At this point in time, these two sections of the MSCOFF are read (header and
|
||||
// raw data), but we do not interpret these sections any further. Suggest that we "might"
|
||||
// want to parse some of these records at load time? Maybe not. We could, at analysis
|
||||
// time, add the ability to process these two sections (as part of analysis (though we
|
||||
// will not be aware of a PDB file yet), and upon discovery of a TypeServer2MsType (or
|
||||
// perhaps other?), proceed to find the file (if possible) and also process that file.
|
||||
// We posit that if a record indicates a separate PDB for the types (Note: MSFT indicates
|
||||
// that only data types will be found in an MSCOFF PDB file), then that will likely be
|
||||
// the only record in the .debug$T section.
|
||||
// TODO: If the MSCOFF file is located in a MSCOFF ARCHIVE (*.lib), there can be a PDB
|
||||
// associated with the archive. We currently do not pass on this association of the
|
||||
// PDB archive to each underlying MSCOFF file. Moreover, we believe that we are not
|
||||
// currently discovering the associated MSCOFF ARCHIVE PDB file when processing the
|
||||
// MSCOFF ARCHIVE. Initial indication is that each MSCOFF within the archive will have
|
||||
// the PDB file that it needs listed, even if redundant for each MSCOFF within the
|
||||
// archive.
|
||||
// return executableFormat != null && (executableFormat.indexOf(PeLoader.PE_NAME) != -1 ||
|
||||
// executableFormat.indexOf(MSCoffLoader.MSCOFF_NAME) != -1);
|
||||
return PdbAnalyzerCommon.canAnalyzeProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOptions(Options options, Program program) {
|
||||
|
||||
symbolsRepositoryDir = PdbLocator.getDefaultPdbSymbolsDir();
|
||||
|
||||
// PDB file location information
|
||||
if (developerMode) {
|
||||
options.registerOption(OPTION_NAME_DO_FORCELOAD, Boolean.FALSE, null,
|
||||
@ -259,10 +204,9 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||
options.registerOption(OPTION_NAME_FORCELOAD_FILE, OptionType.FILE_TYPE,
|
||||
DEFAULT_FORCE_LOAD_FILE, null, OPTION_DESCRIPTION_FORCELOAD_FILE);
|
||||
}
|
||||
options.registerOption(OPTION_NAME_SYMBOLPATH, OptionType.FILE_TYPE, symbolsRepositoryDir,
|
||||
null, OPTION_DESCRIPTION_SYMBOLPATH);
|
||||
options.registerOption(OPTION_NAME_INCLUDE_PE_PDB_PATH, includePeSpecifiedPdbPath, null,
|
||||
OPTION_DESCRIPTION_INCLUDE_PE_PDB_PATH);
|
||||
options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS,
|
||||
searchRemoteLocations, null,
|
||||
PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS);
|
||||
|
||||
pdbReaderOptions.registerOptions(options);
|
||||
pdbApplicatorOptions.registerAnalyzerOptions(options);
|
||||
@ -279,14 +223,8 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||
forceLoadFile = options.getFile(OPTION_NAME_FORCELOAD_FILE, forceLoadFile);
|
||||
}
|
||||
|
||||
File symbolsDir = options.getFile(OPTION_NAME_SYMBOLPATH, symbolsRepositoryDir);
|
||||
if (!symbolsDir.equals(symbolsRepositoryDir)) {
|
||||
symbolsRepositoryDir = symbolsDir;
|
||||
PdbLocator.setDefaultPdbSymbolsDir(symbolsDir);
|
||||
}
|
||||
|
||||
includePeSpecifiedPdbPath =
|
||||
options.getBoolean(OPTION_NAME_INCLUDE_PE_PDB_PATH, includePeSpecifiedPdbPath);
|
||||
searchRemoteLocations = options.getBoolean(
|
||||
PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, searchRemoteLocations);
|
||||
|
||||
pdbReaderOptions.loadOptions(options);
|
||||
pdbApplicatorOptions.loadAnalyzerOptions(options);
|
||||
@ -294,51 +232,40 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||
|
||||
//==============================================================================================
|
||||
|
||||
private boolean failMissingFilename(PdbProgramAttributes attributes, MessageLog log) {
|
||||
if (doForceLoad) {
|
||||
return false; // PDB File property not used for forced load
|
||||
}
|
||||
if (StringUtils.isEmpty(attributes.getPdbFile())) {
|
||||
logFailure("Missing 'PDB File' program property, unable to locate PDB", log);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void logFailure(String msg, MessageLog log) {
|
||||
log.appendMsg(getName(), msg);
|
||||
log.appendMsg(getName(), "Skipping PDB processing");
|
||||
log.setStatus(msg);
|
||||
}
|
||||
|
||||
private boolean failMissingAttributes(PdbProgramAttributes attributes, MessageLog log) {
|
||||
if (doForceLoad) {
|
||||
return false; // Attributes not used for forced load
|
||||
}
|
||||
// RSDS version should only have GUID; non-RSDS version should only have Signature.
|
||||
String error;
|
||||
if ("RSDS".equals(attributes.getPdbVersion())) {
|
||||
if (!StringUtils.isEmpty(attributes.getPdbGuid())) {
|
||||
return false; // Don't fail.
|
||||
}
|
||||
error = "Missing 'PDB GUID' program property, unable to locate PDB.";
|
||||
}
|
||||
else {
|
||||
if (!StringUtils.isEmpty(attributes.getPdbSignature())) {
|
||||
return false; // Don't fail.
|
||||
}
|
||||
error = "Missing 'PDB Signature' program property, unable to locate PDB.";
|
||||
}
|
||||
logFailure(error, log);
|
||||
return true;
|
||||
/**
|
||||
* Sets the PDB file that will be used by the analyzer when it is next invoked
|
||||
* on the specified program.
|
||||
* <p>
|
||||
* Normally the analyzer would locate the PDB file on its own, but if a
|
||||
* headless script wishes to override the analyzer's behaivor, it can
|
||||
* use this method to specify a file.
|
||||
*
|
||||
* @param program {@link Program}
|
||||
* @param pdbFile the pdb file
|
||||
*/
|
||||
public static void setPdbFileOption(Program program, File pdbFile) {
|
||||
PdbAnalyzerCommon.setPdbFileOption(NAME, program, pdbFile);
|
||||
}
|
||||
|
||||
private boolean confirmDirectory(File path) {
|
||||
return path.isDirectory();
|
||||
/**
|
||||
* Sets the "allow remote" option that will be used by the analyzer when it is next invoked
|
||||
* on the specified program.
|
||||
* <p>
|
||||
* Normally when the analyzer attempts to locate a matching PDB file it
|
||||
* will default to NOT searching remote symbol servers. A headless script could
|
||||
* use this method to allow the analyzer to search remote symbol servers.
|
||||
*
|
||||
* @param program {@link Program}
|
||||
* @param allowRemote boolean flag, true means analyzer can search remote symbol
|
||||
* servers
|
||||
*/
|
||||
public static void setAllowRemoteOption(Program program, boolean allowRemote) {
|
||||
PdbAnalyzerCommon.setAllowRemoteOption(NAME, program, allowRemote);
|
||||
}
|
||||
|
||||
private boolean confirmFile(File path) {
|
||||
return path.isFile();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,90 +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.pdb;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.format.pe.debug.DebugCodeViewConstants;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class PdbInfo implements PdbInfoIface {
|
||||
public final static int MAGIC =
|
||||
DebugCodeViewConstants.SIGNATURE_NB << 16 |
|
||||
DebugCodeViewConstants.VERSION_10;
|
||||
|
||||
public static boolean isMatch(BinaryReader reader, int ptr) throws IOException {
|
||||
//read value out as big endian
|
||||
int value = reader.readByte(ptr ) << 24 |
|
||||
reader.readByte(ptr+1) << 16 |
|
||||
reader.readByte(ptr+2) << 8 |
|
||||
reader.readByte(ptr+3);
|
||||
return MAGIC == value;
|
||||
}
|
||||
|
||||
private byte [] magic;
|
||||
private int offset;
|
||||
private int sig;
|
||||
private int age;
|
||||
private String pdbName;
|
||||
|
||||
public PdbInfo(BinaryReader reader, int ptr) throws IOException {
|
||||
long origIndex = reader.getPointerIndex();
|
||||
reader.setPointerIndex(ptr);
|
||||
try {
|
||||
magic = reader.readNextByteArray(4);
|
||||
offset = reader.readNextInt();
|
||||
sig = reader.readNextInt();
|
||||
age = reader.readNextInt();
|
||||
pdbName = reader.readNextAsciiString();
|
||||
}
|
||||
finally {
|
||||
reader.setPointerIndex(origIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public byte [] getMagic() {
|
||||
return magic;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getSig() {
|
||||
return sig;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public String getPdbName() {
|
||||
return pdbName;
|
||||
}
|
||||
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
StructureDataType struct = new StructureDataType("PdbInfo", 0);
|
||||
struct.add(new StringDataType(), magic.length, "signature", null);
|
||||
struct.add(new DWordDataType(), "offset", null);
|
||||
struct.add(new DWordDataType(), "sig", null);
|
||||
struct.add(new DWordDataType(), "age", null);
|
||||
struct.add(new StringDataType(), pdbName.length(), "pdbname", null);
|
||||
struct.setCategoryPath(new CategoryPath("/PDB"));
|
||||
return struct;
|
||||
}
|
||||
}
|
@ -1,89 +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.pdb;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.format.pe.debug.DebugCodeViewConstants;
|
||||
import ghidra.app.util.datatype.microsoft.GUID;
|
||||
import ghidra.app.util.datatype.microsoft.GuidDataType;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class PdbInfoDotNet implements PdbInfoDotNetIface {
|
||||
public final static int MAGIC = DebugCodeViewConstants.SIGNATURE_DOT_NET << 16 |
|
||||
DebugCodeViewConstants.VERSION_DOT_NET;
|
||||
|
||||
public static boolean isMatch(BinaryReader reader, int ptr) throws IOException {
|
||||
//read value out as big endian
|
||||
int value =
|
||||
reader.readByte(ptr) << 24 | reader.readByte(ptr + 1) << 16 |
|
||||
reader.readByte(ptr + 2) << 8 | reader.readByte(ptr + 3);
|
||||
return MAGIC == value;
|
||||
}
|
||||
|
||||
private byte[] magic;
|
||||
private GUID guid;
|
||||
private int age;
|
||||
private String pdbName;
|
||||
|
||||
public PdbInfoDotNet(BinaryReader reader, int ptr) throws IOException {
|
||||
long origIndex = reader.getPointerIndex();
|
||||
reader.setPointerIndex(ptr);
|
||||
try {
|
||||
magic = reader.readNextByteArray(4);
|
||||
guid = new GUID(reader);
|
||||
age = reader.readNextInt();
|
||||
pdbName = reader.readNextAsciiString();
|
||||
}
|
||||
finally {
|
||||
reader.setPointerIndex(origIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public String getPdbName() {
|
||||
return pdbName;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public int getSignature() {
|
||||
return guid.getData1();
|
||||
}
|
||||
|
||||
public GUID getGUID() {
|
||||
return guid;
|
||||
}
|
||||
|
||||
public byte[] getMagic() {
|
||||
return magic;
|
||||
}
|
||||
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
StructureDataType struct = new StructureDataType("DotNetPdbInfo", 0);
|
||||
struct.add(new StringDataType(), magic.length, "signature", null);
|
||||
struct.add(new GuidDataType(), "guid", null);
|
||||
struct.add(new DWordDataType(), "age", null);
|
||||
if (pdbName.length() > 0) {
|
||||
struct.add(new StringDataType(), pdbName.length(), "pdbname", null);
|
||||
}
|
||||
struct.setCategoryPath(new CategoryPath("/PDB"));
|
||||
return struct;
|
||||
}
|
||||
}
|
@ -15,9 +15,10 @@
|
||||
*/
|
||||
package ghidra.app.util.bin.format.pdb;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
@ -27,9 +28,7 @@ import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility;
|
||||
import ghidra.app.services.DataTypeManagerService;
|
||||
import ghidra.app.util.NamespaceUtils;
|
||||
import ghidra.app.util.SymbolPath;
|
||||
import ghidra.app.util.importer.LibrarySearchPathManager;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.app.util.pdb.PdbLocator;
|
||||
import ghidra.app.util.pdb.PdbProgramAttributes;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.framework.options.Options;
|
||||
@ -83,6 +82,7 @@ public class PdbParser {
|
||||
private PdbErrorHandler errHandler;
|
||||
private PdbErrorReaderThread thread;
|
||||
private boolean parsed = false;
|
||||
private boolean allowNonExactMatch;
|
||||
|
||||
private CategoryPath pdbCategory;
|
||||
|
||||
@ -94,13 +94,40 @@ public class PdbParser {
|
||||
private PdbDataTypeParser dataTypeParser;
|
||||
private Map<SymbolPath, Boolean> namespaceMap = new TreeMap<>(); // false: simple namespace, true: class namespace
|
||||
|
||||
/**
|
||||
* Creates a PdbParser instance.
|
||||
*
|
||||
* @param pdbFile the pdb file to parse, either .pdb or .pdb.xml
|
||||
* @param program the {@link Program} to modify
|
||||
* @param service {@link DataTypeManagerService}
|
||||
* @param forceAnalysis boolean flag, currently always true, needs to be refactored out
|
||||
* @param allowNonExactMatch boolean flag, if true skips warning user about mismatch
|
||||
* between the program's PDB guid/id/age and the specified PDB file's guid/id/age, which
|
||||
* can terminate the pdb import in headless
|
||||
* @param monitor {@link TaskMonitor}, null ok
|
||||
*/
|
||||
public PdbParser(File pdbFile, Program program, DataTypeManagerService service,
|
||||
boolean forceAnalysis, TaskMonitor monitor) {
|
||||
this(pdbFile, program, service, getPdbAttributes(program), forceAnalysis, monitor);
|
||||
boolean forceAnalysis, boolean allowNonExactMatch, TaskMonitor monitor) {
|
||||
this(pdbFile, program, service, getPdbAttributes(program), forceAnalysis,
|
||||
allowNonExactMatch, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PdbParser instance.
|
||||
*
|
||||
* @param pdbFile the pdb file to parse, either .pdb or .pdb.xml
|
||||
* @param program the {@link Program} to modify
|
||||
* @param service {@link DataTypeManagerService}
|
||||
* @param programAttributes the PDB information specified by the program
|
||||
* @param forceAnalysis boolean flag, currently always true, needs to be refactored out
|
||||
* @param allowNonExactMatch boolean flag, if true skips warning user about mismatch
|
||||
* between the program's PDB guid/id/age and the specified PDB file's guid/id/age, which
|
||||
* can terminate the pdb import in headless
|
||||
* @param monitor {@link TaskMonitor}, null ok
|
||||
*/
|
||||
public PdbParser(File pdbFile, Program program, DataTypeManagerService service,
|
||||
PdbProgramAttributes programAttributes, boolean forceAnalysis, TaskMonitor monitor) {
|
||||
PdbProgramAttributes programAttributes, boolean forceAnalysis,
|
||||
boolean allowNonExactMatch, TaskMonitor monitor) {
|
||||
this.pdbFile = pdbFile;
|
||||
this.pdbCategory = new CategoryPath(CategoryPath.ROOT, pdbFile.getName());
|
||||
this.program = program;
|
||||
@ -108,8 +135,9 @@ public class PdbParser {
|
||||
this.service = service;
|
||||
this.forceAnalysis = forceAnalysis;
|
||||
this.monitor = monitor != null ? monitor : TaskMonitor.DUMMY;
|
||||
this.isXML = pdbFile.getAbsolutePath().endsWith(PdbFileType.XML.toString());
|
||||
this.isXML = pdbFile.getName().toLowerCase().endsWith(PdbFileType.XML.toString());
|
||||
this.programAttributes = programAttributes;
|
||||
this.allowNonExactMatch = allowNonExactMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,12 +212,12 @@ public class PdbParser {
|
||||
}
|
||||
|
||||
private void checkFileType() throws PdbException {
|
||||
String pdbFilename = pdbFile.getName();
|
||||
String pdbFilename = pdbFile.getName().toLowerCase();
|
||||
|
||||
if (!pdbFilename.endsWith(PdbFileType.PDB.toString()) &&
|
||||
!pdbFilename.endsWith(PdbFileType.XML.toString())) {
|
||||
throw new PdbException(
|
||||
"\nInvalid file type (expecting .pdb or .pdb.xml): '" + pdbFilename + "'");
|
||||
"\nInvalid file type (expecting .pdb or .pdb.xml): '" + pdbFile.getName() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@ -616,9 +644,11 @@ public class PdbParser {
|
||||
pdbGuid = pdbGuid.toUpperCase();
|
||||
pdbGuid = "{" + pdbGuid + "}";
|
||||
|
||||
if (!allowNonExactMatch) {
|
||||
if (!xmlGuid.equals(pdbGuid)) {
|
||||
warning = "PDB signature does not match.\n" + "Program GUID: " + pdbGuid +
|
||||
"\nXML GUID: " + xmlGuid; }
|
||||
"\nXML GUID: " + xmlGuid;
|
||||
}
|
||||
else {
|
||||
// Also check that PDB ages match, if they are both available
|
||||
if ((xmlAge != null) && (pdbAge != null)) {
|
||||
@ -632,6 +662,7 @@ public class PdbParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (warning.length() > 0) {
|
||||
if (SystemUtilities.isInHeadlessMode()) {
|
||||
@ -1042,17 +1073,6 @@ public class PdbParser {
|
||||
return new PdbProgramAttributes(program);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the PDB associated with the given program using its attributes.
|
||||
* The PDB path information within the program information will not be used.
|
||||
*
|
||||
* @param program program for which to find a matching PDB
|
||||
* @return matching PDB for program, or null
|
||||
*/
|
||||
public static File findPDB(Program program) {
|
||||
return findPDB(getPdbAttributes(program), false, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the PDB has previously been loaded for the specified program.
|
||||
* @param program program for which to find a matching PDB
|
||||
@ -1062,273 +1082,6 @@ public class PdbParser {
|
||||
return getPdbAttributes(program).isPdbLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the PDB associated with the given program using its attributes, specifying the
|
||||
* location where symbols are stored.
|
||||
*
|
||||
* @param program program for which to find a matching PDB
|
||||
* @param includePeSpecifiedPdbPath to also check the PE-header-specified PDB path
|
||||
* @param symbolsRepositoryDir location where downloaded symbols are stored
|
||||
* @return matching PDB for program, or null
|
||||
*/
|
||||
public static File findPDB(Program program, boolean includePeSpecifiedPdbPath,
|
||||
File symbolsRepositoryDir) {
|
||||
return findPDB(getPdbAttributes(program), includePeSpecifiedPdbPath, symbolsRepositoryDir,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a matching PDB file using attributes associated with the program. User can specify the
|
||||
* type of file to search from (.pdb or .pdb.xml).
|
||||
*
|
||||
* @param pdbAttributes PDB attributes associated with the program
|
||||
* @param includePeSpecifiedPdbPath to also check the PE-header-specified PDB path
|
||||
* @param symbolsRepositoryDir location of the local symbols repository (can be null)
|
||||
* @param fileType type of file to search for (can be null)
|
||||
* @return matching PDB file (or null, if not found)
|
||||
*/
|
||||
public static File findPDB(PdbProgramAttributes pdbAttributes,
|
||||
boolean includePeSpecifiedPdbPath, File symbolsRepositoryDir, PdbFileType fileType) {
|
||||
|
||||
// Store potential names of PDB files and potential locations of those files,
|
||||
// so that all possible combinations can be searched.
|
||||
// LinkedHashSet is used when we need to preserve order
|
||||
Set<String> guidSubdirPaths = new HashSet<>();
|
||||
|
||||
String guidAgeString = pdbAttributes.getGuidAgeCombo();
|
||||
if (guidAgeString == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> potentialPdbNames = pdbAttributes.getPotentialPdbFilenames();
|
||||
for (String potentialName : potentialPdbNames) {
|
||||
guidSubdirPaths.add(File.separator + potentialName + File.separator + guidAgeString);
|
||||
}
|
||||
|
||||
return checkPathsForPdb(symbolsRepositoryDir, guidSubdirPaths, potentialPdbNames, fileType,
|
||||
pdbAttributes, includePeSpecifiedPdbPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check potential paths in a specific order. If the symbolsRepositoryPath parameter is
|
||||
* supplied and the directory exists, that directory will be searched first for the
|
||||
* matching PDB file.
|
||||
*
|
||||
* If the file type is supplied, then only that file type will be searched for. Otherwise,
|
||||
* the search process depends on the current operating system that Ghidra is running from:
|
||||
*
|
||||
* - Windows: look in the symbolsRepositoryPath for a matching .pdb file. If one does not
|
||||
* exist, look for a .pdb.xml file in symbolsRepositoryPath. If not found, then
|
||||
* search for a matching .pdb file, then .pdb.xml file, in other directories.
|
||||
* - non-Windows: look in the symbolsRepositoryPath for a matching .pdb.xml file. If one does
|
||||
* not exist, look for a .pdb file. If a .pdb file is found, return an error saying
|
||||
* that it was found, but could not be processed. If no matches found in
|
||||
* symbolsRepositoryPath, then look for .pdb.xml file, then .pdb.xml file in other
|
||||
* directories.
|
||||
*
|
||||
* @param symbolsRepositoryDir location of the local symbols repository (can be null)
|
||||
* @param guidSubdirPaths subdirectory paths (that include the PDB's GUID) that may contain
|
||||
* a matching PDB
|
||||
* @param potentialPdbNames all potential filenames for the PDB file(s) that match the program
|
||||
* @param fileType file type to search for (can be null)
|
||||
* @param pdbAttributes PDB attributes associated with the program
|
||||
* @return matching PDB file, if found (else null)
|
||||
*/
|
||||
private static File checkPathsForPdb(File symbolsRepositoryDir, Set<String> guidSubdirPaths,
|
||||
List<String> potentialPdbNames, PdbFileType fileType,
|
||||
PdbProgramAttributes pdbAttributes, boolean includePeSpecifiedPdbPath) {
|
||||
|
||||
File foundPdb = null;
|
||||
Set<File> symbolsRepoPaths =
|
||||
getSymbolsRepositoryPaths(symbolsRepositoryDir, guidSubdirPaths);
|
||||
Set<File> predefinedPaths =
|
||||
getPredefinedPaths(guidSubdirPaths, pdbAttributes, includePeSpecifiedPdbPath);
|
||||
boolean fileTypeSpecified = (fileType != null);
|
||||
boolean checkForXml;
|
||||
|
||||
// If the file type is specified, look for that type of file only.
|
||||
if (fileTypeSpecified) {
|
||||
checkForXml = (fileType == PdbFileType.XML) ? true : false;
|
||||
|
||||
foundPdb = checkForPDBorXML(symbolsRepoPaths, potentialPdbNames, checkForXml);
|
||||
|
||||
if (foundPdb != null) {
|
||||
return foundPdb;
|
||||
}
|
||||
|
||||
foundPdb = checkForPDBorXML(predefinedPaths, potentialPdbNames, checkForXml);
|
||||
|
||||
return foundPdb;
|
||||
}
|
||||
|
||||
// If the file type is not specified, look for both file types, starting with the
|
||||
// file type that's most appropriate for the Operating System (PDB for Windows, XML for
|
||||
// non-Windows).
|
||||
checkForXml = onWindows ? false : true;
|
||||
|
||||
// Start by searching in symbolsRepositoryPath, if available.
|
||||
if (!symbolsRepoPaths.isEmpty()) {
|
||||
foundPdb = checkSpecificPathsForPdb(symbolsRepoPaths, potentialPdbNames, checkForXml);
|
||||
}
|
||||
|
||||
if (foundPdb != null) {
|
||||
return foundPdb;
|
||||
}
|
||||
|
||||
return checkSpecificPathsForPdb(predefinedPaths, potentialPdbNames, checkForXml);
|
||||
|
||||
}
|
||||
|
||||
private static File checkSpecificPathsForPdb(Set<File> paths, List<String> potentialPdbNames,
|
||||
boolean checkForXmlFirst) {
|
||||
|
||||
File foundPdb = checkForPDBorXML(paths, potentialPdbNames, checkForXmlFirst);
|
||||
|
||||
if (foundPdb != null) {
|
||||
return foundPdb;
|
||||
}
|
||||
|
||||
foundPdb = checkForPDBorXML(paths, potentialPdbNames, !checkForXmlFirst);
|
||||
|
||||
return foundPdb;
|
||||
}
|
||||
|
||||
private static Set<File> getSymbolsRepositoryPaths(File symbolsRepositoryDir,
|
||||
Set<String> guidSubdirPaths) {
|
||||
|
||||
Set<File> symbolsRepoPaths = new LinkedHashSet<>();
|
||||
|
||||
// Collect sub-directories of the symbol repository that exist
|
||||
if (symbolsRepositoryDir != null && symbolsRepositoryDir.isDirectory()) {
|
||||
|
||||
for (String guidSubdir : guidSubdirPaths) {
|
||||
File testDir = new File(symbolsRepositoryDir, guidSubdir);
|
||||
if (testDir.isDirectory()) {
|
||||
symbolsRepoPaths.add(testDir);
|
||||
}
|
||||
}
|
||||
|
||||
// Check outer folder last
|
||||
symbolsRepoPaths.add(symbolsRepositoryDir);
|
||||
}
|
||||
|
||||
return symbolsRepoPaths;
|
||||
}
|
||||
|
||||
// Get list of "paths we know about" to search for PDBs
|
||||
private static Set<File> getPredefinedPaths(Set<String> guidSubdirPaths,
|
||||
PdbProgramAttributes pdbAttributes, boolean includePeSpecifiedPdbPath) {
|
||||
|
||||
Set<File> predefinedPaths = new LinkedHashSet<>();
|
||||
|
||||
getPathsFromAttributes(pdbAttributes, includePeSpecifiedPdbPath, predefinedPaths);
|
||||
getSymbolPaths(PdbLocator.DEFAULT_SYMBOLS_DIR, guidSubdirPaths, predefinedPaths);
|
||||
getSymbolPaths(PdbLocator.WINDOWS_SYMBOLS_DIR, guidSubdirPaths, predefinedPaths);
|
||||
getLibraryPaths(guidSubdirPaths, predefinedPaths);
|
||||
|
||||
return predefinedPaths;
|
||||
}
|
||||
|
||||
private static void getLibraryPaths(Set<String> guidSubdirPaths, Set<File> predefinedPaths) {
|
||||
String[] libraryPaths = LibrarySearchPathManager.getLibraryPaths();
|
||||
|
||||
File libFile, subDir;
|
||||
|
||||
for (String path : libraryPaths) {
|
||||
|
||||
if ((libFile = new File(path)).isDirectory()) {
|
||||
predefinedPaths.add(libFile);
|
||||
|
||||
// Check alternate locations
|
||||
for (String guidSubdir : guidSubdirPaths) {
|
||||
if ((subDir = new File(path, guidSubdir)).isDirectory()) {
|
||||
predefinedPaths.add(subDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void getSymbolPaths(File symbolsDir, Set<String> guidSubdirPaths,
|
||||
Set<File> predefinedPaths) {
|
||||
// Don't have to call .exists(), since .isDirectory() does that already
|
||||
if (symbolsDir == null || !symbolsDir.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
predefinedPaths.add(symbolsDir);
|
||||
|
||||
// Check alternate locations
|
||||
String specialPdbPath = symbolsDir.getAbsolutePath();
|
||||
|
||||
for (String guidSubdir : guidSubdirPaths) {
|
||||
File testDir = new File(specialPdbPath + guidSubdir);
|
||||
if (testDir.isDirectory()) {
|
||||
predefinedPaths.add(testDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void getPathsFromAttributes(PdbProgramAttributes pdbAttributes,
|
||||
boolean includePeSpecifiedPdbPath, Set<File> predefinedPaths) {
|
||||
if (pdbAttributes != null) {
|
||||
|
||||
String currentPath = pdbAttributes.getPdbFile();
|
||||
|
||||
if (currentPath != null && includePeSpecifiedPdbPath) {
|
||||
File parentDir = new File(currentPath).getParentFile();
|
||||
|
||||
if (parentDir != null && parentDir.exists()) {
|
||||
predefinedPaths.add(parentDir);
|
||||
}
|
||||
}
|
||||
|
||||
currentPath = pdbAttributes.getExecutablePath();
|
||||
|
||||
if (currentPath != null && !currentPath.equals("unknown")) {
|
||||
File parentDir = new File(currentPath).getParentFile();
|
||||
|
||||
if (parentDir != null && parentDir.exists()) {
|
||||
predefinedPaths.add(parentDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first PDB-type file found. Assumes list of potentialPdbDirs is in the order
|
||||
* in which the directories should be searched.
|
||||
*
|
||||
* @param potentialPdbDirs potential PDB directories
|
||||
* @param potentialPdbNames potential PDB names
|
||||
* @param findXML - if true, only searches for the .pdb.xml version of the .pdb file
|
||||
* @return the first file found
|
||||
*/
|
||||
private static File checkForPDBorXML(Set<File> potentialPdbDirs, List<String> potentialPdbNames,
|
||||
boolean findXML) {
|
||||
|
||||
File pdb;
|
||||
|
||||
for (File pdbPath : potentialPdbDirs) {
|
||||
|
||||
for (String filename : potentialPdbNames) {
|
||||
|
||||
if (findXML) {
|
||||
pdb = new File(pdbPath, filename + PdbFileType.XML.toString());
|
||||
}
|
||||
else {
|
||||
pdb = new File(pdbPath, filename);
|
||||
}
|
||||
|
||||
// Note: isFile() also checks for existence
|
||||
if (pdb.isFile()) {
|
||||
return pdb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
PdbDataTypeParser getDataTypeParser() {
|
||||
if (program == null) {
|
||||
throw new AssertException("Parser was not constructed with program");
|
||||
|
@ -15,12 +15,14 @@
|
||||
*/
|
||||
package ghidra.app.util.bin.format.pdb2.pdbreader;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.app.util.datatype.microsoft.GUID;
|
||||
|
||||
/**
|
||||
* This class holds fields used to identify a PDB.
|
||||
* <P>
|
||||
* These are Version, Signature, Age, and GUID. Som identifiers can be null if not found in
|
||||
* These are Version, Signature, Age, and GUID. Some identifiers can be null if not found in
|
||||
* the specific version of the PDB.
|
||||
*/
|
||||
public class PdbIdentifiers {
|
||||
@ -38,7 +40,7 @@ public class PdbIdentifiers {
|
||||
* @param age age used to verify PDB against age stored in program
|
||||
* @param guid The GUID (can be null for older PDBs).
|
||||
*/
|
||||
PdbIdentifiers(int version, int signature, int age, GUID guid, Processor processor) {
|
||||
public PdbIdentifiers(int version, int signature, int age, GUID guid, Processor processor) {
|
||||
this.version = version;
|
||||
this.signature = signature;
|
||||
this.age = age;
|
||||
@ -78,4 +80,33 @@ public class PdbIdentifiers {
|
||||
return guid;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ((guid != null) ? guid.toString() : String.format("%08X", signature)) + ", " + age +
|
||||
", " + version + ", " + processor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(age, guid, processor, signature, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
PdbIdentifiers other = (PdbIdentifiers) obj;
|
||||
return age == other.age && Objects.equals(guid, other.guid) &&
|
||||
processor == other.processor && signature == other.signature &&
|
||||
version == other.version;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,128 +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 pdb;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.combobox.GComboBox;
|
||||
import ghidra.app.util.bin.format.pdb.PdbParser;
|
||||
import ghidra.app.util.pdb.pdbapplicator.PdbApplicatorControl;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
|
||||
class AskPdbOptionsDialog extends DialogComponentProvider {
|
||||
|
||||
private boolean isCanceled;
|
||||
|
||||
private boolean useMsDiaParser;
|
||||
private PdbApplicatorControl control = PdbApplicatorControl.ALL;
|
||||
|
||||
/**
|
||||
* Popup PDB loader options
|
||||
* @param parent parent component or null
|
||||
* @param isPdbFile true if file to be loaded is a PDB file, false
|
||||
* if MsDia XML file.
|
||||
*/
|
||||
AskPdbOptionsDialog(Component parent, boolean isPdbFile) {
|
||||
super("Load PDB Options", true, true, true, false);
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout(10, 10));
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
JPanel optionsPanel = new JPanel(new PairLayout(10, 10));
|
||||
|
||||
final GComboBox<PdbApplicatorControl> controlCombo =
|
||||
new GComboBox<>(PdbApplicatorControl.values());
|
||||
controlCombo.setSelectedItem(PdbApplicatorControl.ALL);
|
||||
controlCombo.addActionListener(e -> {
|
||||
control = (PdbApplicatorControl) controlCombo.getSelectedItem();
|
||||
});
|
||||
|
||||
optionsPanel.add(new JLabel("PDB Parser:"));
|
||||
|
||||
if (isPdbFile) {
|
||||
useMsDiaParser = false; // Use PDB Universal by default
|
||||
if (PdbParser.onWindows) {
|
||||
final GComboBox<String> combo =
|
||||
new GComboBox<>(new String[] { "PDB Universal", "PDB MSDIA" });
|
||||
combo.setSelectedIndex(0);
|
||||
controlCombo.setEnabled(!useMsDiaParser);
|
||||
combo.addActionListener(e -> {
|
||||
useMsDiaParser = (combo.getSelectedIndex() == 1);
|
||||
controlCombo.setEnabled(!useMsDiaParser);
|
||||
if (useMsDiaParser) {
|
||||
controlCombo.setSelectedItem(PdbApplicatorControl.ALL);
|
||||
}
|
||||
});
|
||||
optionsPanel.add(combo);
|
||||
}
|
||||
else {
|
||||
useMsDiaParser = false;
|
||||
JLabel label = new JLabel("PDB Universal");
|
||||
//label.setForeground(Color.red); // set color to emphasize prototype status
|
||||
optionsPanel.add(label);
|
||||
}
|
||||
}
|
||||
else {
|
||||
useMsDiaParser = true; // XML file only supported by MsDia parser
|
||||
return; // no interaction currently required
|
||||
}
|
||||
|
||||
optionsPanel.add(new JLabel("Control:"));
|
||||
optionsPanel.add(controlCombo);
|
||||
|
||||
panel.add(optionsPanel, BorderLayout.CENTER);
|
||||
|
||||
addWorkPanel(panel);
|
||||
|
||||
addApplyButton();
|
||||
addCancelButton();
|
||||
|
||||
setDefaultButton(applyButton);
|
||||
setRememberSize(false);
|
||||
|
||||
DockingWindowManager.showDialog(parent, AskPdbOptionsDialog.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyCallback() {
|
||||
isCanceled = false;
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
isCanceled = true;
|
||||
close();
|
||||
}
|
||||
|
||||
boolean isCanceled() {
|
||||
return isCanceled;
|
||||
}
|
||||
|
||||
boolean useMsDiaParser() {
|
||||
return useMsDiaParser;
|
||||
}
|
||||
|
||||
PdbApplicatorControl getApplicatorControl() {
|
||||
return control;
|
||||
}
|
||||
|
||||
}
|
@ -1,230 +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 pdb;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.dialogs.ObjectChooserDialog;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import generic.jar.ResourceFile;
|
||||
import generic.util.WindowUtilities;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.util.MessageType;
|
||||
|
||||
public class AskPdbUrlDialog extends DialogComponentProvider {
|
||||
|
||||
private boolean isCanceled;
|
||||
private JLabel label;
|
||||
private JTextField textField;
|
||||
private KeyListener keyListener;
|
||||
private List<URLChoice> choices = null;
|
||||
|
||||
protected AskPdbUrlDialog(String dialogTitle, String message) {
|
||||
this(null, dialogTitle, message, null);
|
||||
}
|
||||
|
||||
public AskPdbUrlDialog(String dialogTitle, String message, Object defaultValue) {
|
||||
this(null, dialogTitle, message, defaultValue);
|
||||
}
|
||||
|
||||
public AskPdbUrlDialog(Component parent, String title, String message) {
|
||||
this(parent, title, message, null);
|
||||
}
|
||||
|
||||
public AskPdbUrlDialog(final Component parent, String title, String message,
|
||||
Object defaultValue) {
|
||||
super(title, true, true, true, false);
|
||||
|
||||
// create the key listener all the text fields will use
|
||||
keyListener = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
int keyCode = e.getKeyCode();
|
||||
if (keyCode == KeyEvent.VK_ENTER) {
|
||||
okCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout(10, 10));
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
label = new GDLabel(message);
|
||||
panel.add(label, BorderLayout.WEST);
|
||||
|
||||
textField = new JTextField(40);
|
||||
textField.setName("JTextField");//for JUnits...
|
||||
textField.addKeyListener(keyListener);
|
||||
textField.setText(defaultValue == null ? "" : defaultValue.toString());
|
||||
textField.selectAll();
|
||||
panel.add(textField, BorderLayout.CENTER);
|
||||
|
||||
if (urlFileAvailable()) {
|
||||
JButton urlButton = new JButton("Choose from known URLs");
|
||||
urlButton.addActionListener(e -> urlCallback());
|
||||
|
||||
panel.add(urlButton, BorderLayout.EAST);
|
||||
}
|
||||
|
||||
addWorkPanel(panel);
|
||||
|
||||
addOKButton();
|
||||
addCancelButton();
|
||||
|
||||
setDefaultButton(okButton);
|
||||
setRememberSize(false);
|
||||
|
||||
DockingWindowManager.showDialog(parent, AskPdbUrlDialog.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addOKButton() {
|
||||
okButton = new JButton("Download from URL");
|
||||
okButton.setMnemonic('K');
|
||||
okButton.setName("OK");
|
||||
okButton.addActionListener(e -> okCallback());
|
||||
addButton(okButton);
|
||||
}
|
||||
|
||||
private boolean urlFileAvailable() {
|
||||
List<ResourceFile> urlFiles = Application.findFilesByExtensionInApplication(".pdburl");
|
||||
|
||||
if (urlFiles.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream urlFileContents = null;
|
||||
String currentLine;
|
||||
choices = new ArrayList<>();
|
||||
|
||||
for (ResourceFile urlFile : urlFiles) {
|
||||
urlFileContents = urlFile.getInputStream();
|
||||
|
||||
Scanner scanner = new Scanner(urlFileContents);
|
||||
try {
|
||||
while (scanner.hasNextLine()) {
|
||||
|
||||
currentLine = scanner.nextLine();
|
||||
|
||||
// Find first comma, split on that
|
||||
int commaIndex = currentLine.indexOf(',');
|
||||
|
||||
if (commaIndex > -1) {
|
||||
choices.add(new URLChoice(currentLine.substring(0, commaIndex).trim(),
|
||||
currentLine.substring(commaIndex + 1).trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveCurrentDimensions() {
|
||||
Rectangle bounds = getBounds();
|
||||
Window window = WindowUtilities.windowForComponent(getComponent());
|
||||
|
||||
if (window != null) {
|
||||
Point location = window.getLocation();
|
||||
bounds.x = location.x;
|
||||
bounds.y = location.y;
|
||||
}
|
||||
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.append(bounds.x).append(":");
|
||||
buffer.append(bounds.y).append(":");
|
||||
buffer.append(bounds.width).append(":");
|
||||
buffer.append(bounds.height).append(":");
|
||||
Preferences.setProperty("Ask Dialog Bounds", buffer.toString());
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return textField.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
isCanceled = false;
|
||||
if (textField.getText().length() == 0) {
|
||||
setStatusText("Please enter a valid URL.");
|
||||
return;
|
||||
}
|
||||
saveCurrentDimensions();
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
isCanceled = true;
|
||||
saveCurrentDimensions();
|
||||
close();
|
||||
}
|
||||
|
||||
private void urlCallback() {
|
||||
|
||||
ObjectChooserDialog<URLChoice> urlDialog = new ObjectChooserDialog<>("Choose a URL",
|
||||
URLChoice.class, choices, "getNetwork", "getUrl");
|
||||
|
||||
DockingWindowManager activeInstance = DockingWindowManager.getActiveInstance();
|
||||
activeInstance.showDialog(urlDialog);
|
||||
|
||||
URLChoice pickedUrl = urlDialog.getSelectedObject();
|
||||
|
||||
if (pickedUrl != null) {
|
||||
textField.setText(pickedUrl.getUrl());
|
||||
|
||||
if (pickedUrl.getNetwork().equalsIgnoreCase("internet")) {
|
||||
setStatusText(
|
||||
"WARNING: Check your organization's security policy before downloading files from the internet.",
|
||||
MessageType.ERROR);
|
||||
}
|
||||
else {
|
||||
setStatusText(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCanceled() {
|
||||
return isCanceled;
|
||||
}
|
||||
|
||||
public String getValueAsString() {
|
||||
Object val = getValue();
|
||||
if ("".equals(val)) {
|
||||
return null;
|
||||
}
|
||||
return val != null ? val.toString() : null;
|
||||
}
|
||||
|
||||
}
|
@ -20,7 +20,6 @@ import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.dialogs.MultiLineMessageDialog;
|
||||
import ghidra.app.plugin.core.analysis.*;
|
||||
import ghidra.app.plugin.core.datamgr.archive.DuplicateIdException;
|
||||
@ -29,8 +28,6 @@ import ghidra.app.util.bin.format.pdb.PdbException;
|
||||
import ghidra.app.util.bin.format.pdb.PdbParser;
|
||||
import ghidra.app.util.bin.format.pdb2.pdbreader.*;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.app.util.pdb.PdbLocator;
|
||||
import ghidra.app.util.pdb.PdbProgramAttributes;
|
||||
import ghidra.app.util.pdb.pdbapplicator.*;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
@ -45,19 +42,21 @@ class LoadPdbTask extends Task {
|
||||
private final Program program;
|
||||
private final boolean useMsDiaParser;
|
||||
private final PdbApplicatorControl control; // PDB Universal Parser only
|
||||
private boolean debugLogging;
|
||||
|
||||
LoadPdbTask(Program program, File pdbFile, boolean useMsDiaParser, PdbApplicatorControl control,
|
||||
DataTypeManagerService service) {
|
||||
boolean debugLogging, DataTypeManagerService service) {
|
||||
super("Load PDB", true, false, true, true);
|
||||
this.program = program;
|
||||
this.pdbFile = pdbFile;
|
||||
this.useMsDiaParser = useMsDiaParser;
|
||||
this.control = control;
|
||||
this.debugLogging = debugLogging;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(final TaskMonitor monitor) {
|
||||
public void run(TaskMonitor monitor) {
|
||||
|
||||
WrappingTaskMonitor wrappedMonitor = new WrappingTaskMonitor(monitor) {
|
||||
@Override
|
||||
@ -134,7 +133,7 @@ class LoadPdbTask extends Task {
|
||||
|
||||
private boolean parseWithMsDiaParser(MessageLog log, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
PdbParser parser = new PdbParser(pdbFile, program, service, true, monitor);
|
||||
PdbParser parser = new PdbParser(pdbFile, program, service, true, true, monitor);
|
||||
try {
|
||||
parser.parse();
|
||||
parser.openDataTypeArchives();
|
||||
@ -147,44 +146,19 @@ class LoadPdbTask extends Task {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: OptionDialog will not display an empty line
|
||||
private static final String BLANK_LINE = " \n";
|
||||
|
||||
private boolean parseWithNewParser(MessageLog log, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
PdbLog.setEnabled(debugLogging);
|
||||
|
||||
PdbReaderOptions pdbReaderOptions = new PdbReaderOptions(); // use defaults
|
||||
|
||||
PdbApplicatorOptions pdbApplicatorOptions = new PdbApplicatorOptions();
|
||||
|
||||
pdbApplicatorOptions.setProcessingControl(control);
|
||||
|
||||
PdbProgramAttributes programAttributes = new PdbProgramAttributes(program);
|
||||
|
||||
try (AbstractPdb pdb = ghidra.app.util.bin.format.pdb2.pdbreader.PdbParser.parse(
|
||||
pdbFile.getAbsolutePath(), pdbReaderOptions, monitor)) {
|
||||
|
||||
PdbIdentifiers identifiers = pdb.getIdentifiers();
|
||||
if (!PdbLocator.verifyPdbSignature(programAttributes, identifiers)) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("Selected PDB does not match program's PDB specification!\n");
|
||||
builder.append(BLANK_LINE);
|
||||
builder.append("Program's PDB specification:\n");
|
||||
builder.append(PdbLocator.formatPdbIdentifiers(programAttributes));
|
||||
builder.append(BLANK_LINE);
|
||||
builder.append("Selected PDB file specification:\n");
|
||||
builder.append(
|
||||
PdbLocator.formatPdbIdentifiers(pdbFile.getAbsolutePath(), identifiers));
|
||||
builder.append(BLANK_LINE);
|
||||
builder.append("Do you wish to force load this PDB?");
|
||||
|
||||
if (OptionDialog.YES_OPTION != OptionDialog.showYesNoDialog(null,
|
||||
"Confirm PDB Load", builder.toString())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
monitor.setMessage("PDB: Parsing " + pdbFile + "...");
|
||||
pdb.deserialize(monitor);
|
||||
PdbApplicator applicator = new PdbApplicator(pdbFile.getAbsolutePath(), pdb);
|
||||
|
@ -1,31 +0,0 @@
|
||||
/* ###
|
||||
* 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.
|
||||
* 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 pdb;
|
||||
|
||||
import ghidra.app.util.bin.format.pdb.*;
|
||||
import ghidra.framework.*;
|
||||
|
||||
public class PdbInitializer implements ModuleInitializer {
|
||||
public void run() {
|
||||
PluggableServiceRegistry.registerPluggableService(PdbFactory.class,
|
||||
new GhidraPdbFactory());
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return "PDB Support Module";
|
||||
}
|
||||
}
|
@ -16,30 +16,33 @@
|
||||
package pdb;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import docking.action.MenuData;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.tool.ToolConstants;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.context.ProgramActionContext;
|
||||
import ghidra.app.context.ProgramContextAction;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.app.plugin.core.analysis.PdbAnalyzerCommon;
|
||||
import ghidra.app.services.DataTypeManagerService;
|
||||
import ghidra.app.util.bin.format.pdb.PdbParser;
|
||||
import ghidra.app.util.pdb.pdbapplicator.PdbApplicatorControl;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.GhidraProgramUtilities;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.filechooser.ExtensionFileFilter;
|
||||
import ghidra.util.task.TaskBuilder;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.*;
|
||||
import pdb.symbolserver.*;
|
||||
import pdb.symbolserver.ui.ConfigPdbDialog;
|
||||
import pdb.symbolserver.ui.LoadPdbDialog;
|
||||
import pdb.symbolserver.ui.LoadPdbDialog.LoadPdbResults;
|
||||
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
@ -51,9 +54,14 @@ import ghidra.util.task.TaskLauncher;
|
||||
)
|
||||
//@formatter:on
|
||||
public class PdbPlugin extends Plugin {
|
||||
private static final String PDB_SYMBOL_SERVER_OPTIONS = "PdbSymbolServer";
|
||||
private static final String SYMBOL_STORAGE_DIR_OPTION =
|
||||
PDB_SYMBOL_SERVER_OPTIONS + ".Symbol_Storage_Directory";
|
||||
private static final String SYMBOL_SEARCH_PATH_OPTION =
|
||||
PDB_SYMBOL_SERVER_OPTIONS + ".Symbol_Search_Path";
|
||||
|
||||
private ProgramContextAction loadPdbAction;
|
||||
private GhidraFileChooser pdbChooser;
|
||||
// the name of the help directory under src/main/help/help/topics
|
||||
public static final String PDB_PLUGIN_HELP_TOPIC = "Pdb";
|
||||
|
||||
public PdbPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
@ -62,33 +70,34 @@ public class PdbPlugin extends Plugin {
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
loadPdbAction = new ProgramContextAction("Load PDB File", this.getName()) {
|
||||
new ActionBuilder("Load PDB File", this.getName())
|
||||
.supportsDefaultToolContext(true)
|
||||
.withContext(ProgramActionContext.class)
|
||||
.validContextWhen(pac -> pac.getProgram() != null &&
|
||||
PdbAnalyzerCommon.canAnalyzeProgram(pac.getProgram()))
|
||||
.menuPath(ToolConstants.MENU_FILE, "Load PDB File...")
|
||||
.menuGroup("Import PDB", "3")
|
||||
.helpLocation(new HelpLocation(PDB_PLUGIN_HELP_TOPIC, "Load PDB File"))
|
||||
.onAction(pac -> loadPDB(pac))
|
||||
.buildAndInstall(tool);
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ProgramActionContext context) {
|
||||
return context.getProgram() != null;
|
||||
new ActionBuilder("Symbol Server Config", this.getName())
|
||||
.menuPath(ToolConstants.MENU_EDIT, "Symbol Server Config")
|
||||
.menuGroup(ToolConstants.TOOL_OPTIONS_MENU_GROUP)
|
||||
.helpLocation(new HelpLocation(PDB_PLUGIN_HELP_TOPIC, "Symbol Server Config"))
|
||||
.onAction(ac -> configPDB())
|
||||
.buildAndInstall(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(ProgramActionContext programContext) {
|
||||
loadPDB();
|
||||
}
|
||||
};
|
||||
|
||||
MenuData menuData =
|
||||
new MenuData(new String[] { "&File", "Load PDB File..." }, null, "Import PDB");
|
||||
menuData.setMenuSubGroup("3"); // below the major actions in the "Import/Export" group
|
||||
loadPdbAction.setMenuBarData(menuData);
|
||||
|
||||
loadPdbAction.setEnabled(false);
|
||||
loadPdbAction.setHelpLocation(new HelpLocation("ImporterPlugin", loadPdbAction.getName()));
|
||||
tool.addAction(loadPdbAction);
|
||||
private void configPDB() {
|
||||
ConfigPdbDialog.showSymbolServerConfig();
|
||||
}
|
||||
|
||||
private void loadPDB() {
|
||||
Program program = GhidraProgramUtilities.getCurrentProgram(tool);
|
||||
AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(program);
|
||||
if (aam.isAnalyzing()) {
|
||||
private void loadPDB(ProgramActionContext pac) {
|
||||
Program program = pac.getProgram();
|
||||
AutoAnalysisManager currentAutoAnalysisManager =
|
||||
AutoAnalysisManager.getAnalysisManager(program);
|
||||
if (currentAutoAnalysisManager.isAnalyzing()) {
|
||||
Msg.showWarn(getClass(), null, "Load PDB",
|
||||
"Unable to load PDB file while analysis is running.");
|
||||
return;
|
||||
@ -110,26 +119,17 @@ public class PdbPlugin extends Plugin {
|
||||
}
|
||||
|
||||
try {
|
||||
File pdb = getPdbFile(program);
|
||||
if (pdb == null) {
|
||||
LoadPdbResults loadPdbResults = LoadPdbDialog.choosePdbForProgram(program);
|
||||
if (loadPdbResults == null) {
|
||||
tool.setStatusInfo("Loading PDB was cancelled.");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isPdbFile = pdb.getName().toLowerCase().endsWith(".pdb");
|
||||
|
||||
AskPdbOptionsDialog optionsDialog = new AskPdbOptionsDialog(null, isPdbFile);
|
||||
if (optionsDialog.isCanceled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean useMsDiaParser = optionsDialog.useMsDiaParser();
|
||||
PdbApplicatorControl control = optionsDialog.getApplicatorControl();
|
||||
|
||||
tool.setStatusInfo("");
|
||||
|
||||
DataTypeManagerService service = tool.getService(DataTypeManagerService.class);
|
||||
if (service == null) {
|
||||
DataTypeManagerService dataTypeManagerService =
|
||||
tool.getService(DataTypeManagerService.class);
|
||||
if (dataTypeManagerService == null) {
|
||||
Msg.showWarn(getClass(), null, "Load PDB",
|
||||
"Unable to locate DataTypeService in the current tool.");
|
||||
return;
|
||||
@ -138,34 +138,122 @@ public class PdbPlugin extends Plugin {
|
||||
// note: We intentionally use a 0-delay here. Our underlying task may show modal
|
||||
// dialog prompts. We want the task progress dialog to be showing before any
|
||||
// prompts appear.
|
||||
|
||||
LoadPdbTask task = new LoadPdbTask(program, pdb, useMsDiaParser, control, service);
|
||||
TaskBuilder.withTask(task)
|
||||
LoadPdbTask loadPdbTask = new LoadPdbTask(program, loadPdbResults.pdbFile,
|
||||
loadPdbResults.useMsDiaParser, loadPdbResults.control,
|
||||
loadPdbResults.debugLogging, dataTypeManagerService);
|
||||
TaskBuilder.withTask(loadPdbTask)
|
||||
.setStatusTextAlignment(SwingConstants.LEADING)
|
||||
.setLaunchDelay(0);
|
||||
new TaskLauncher(task, null, 0);
|
||||
new TaskLauncher(loadPdbTask, null, 0);
|
||||
}
|
||||
catch (Exception pe) {
|
||||
Msg.showError(getClass(), null, "Error Loading PDB", pe.getMessage(), pe);
|
||||
}
|
||||
}
|
||||
|
||||
private File getPdbFile(Program program) {
|
||||
File pdbFile = PdbParser.findPDB(program);
|
||||
if (pdbChooser == null) {
|
||||
pdbChooser = new GhidraFileChooser(tool.getToolFrame());
|
||||
pdbChooser.setTitle("Select PDB file to load:");
|
||||
pdbChooser.setApproveButtonText("Select PDB");
|
||||
pdbChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
|
||||
pdbChooser.setFileFilter(new ExtensionFileFilter(new String[] { "pdb", "xml" },
|
||||
"Program Database Files and PDB XML Representations"));
|
||||
//-------------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Searches the currently configured symbol server paths for a Pdb symbol file.
|
||||
*
|
||||
* @param program the program associated with the requested pdb file
|
||||
* @param findOptions options that control how to search for the symbol file
|
||||
* @param monitor a {@link TaskMonitor} that allows the user to cancel
|
||||
* @return a File that points to the found Pdb symbol file, or null if no file was found
|
||||
*/
|
||||
public static File findPdb(Program program, Set<FindOption> findOptions, TaskMonitor monitor) {
|
||||
|
||||
try {
|
||||
SymbolFileInfo symbolFileInfo = SymbolFileInfo.fromMetadata(program.getMetadata());
|
||||
if (symbolFileInfo == null) {
|
||||
return null;
|
||||
}
|
||||
// make a copy and add in the ONLY_FIRST_RESULT option
|
||||
findOptions = findOptions.isEmpty() ? EnumSet.noneOf(FindOption.class)
|
||||
: EnumSet.copyOf(findOptions);
|
||||
findOptions.add(FindOption.ONLY_FIRST_RESULT);
|
||||
|
||||
SymbolServerInstanceCreatorContext temporarySymbolServerInstanceCreatorContext =
|
||||
SymbolServerInstanceCreatorRegistry.getInstance().getContext(program);
|
||||
|
||||
SymbolServerService temporarySymbolServerService =
|
||||
getSymbolServerService(temporarySymbolServerInstanceCreatorContext);
|
||||
|
||||
List<SymbolFileLocation> results =
|
||||
temporarySymbolServerService.find(symbolFileInfo, findOptions, monitor);
|
||||
if (!results.isEmpty()) {
|
||||
return temporarySymbolServerService.getSymbolFile(results.get(0), monitor);
|
||||
}
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(PdbPlugin.class, "Error getting symbol file", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pdbFile != null) {
|
||||
pdbChooser.setSelectedFile(pdbFile);
|
||||
/**
|
||||
* Returns a new instance of a {@link SymbolServerService} configured with values from the
|
||||
* application's preferences, defaulting to a minimal instance if there is no config.
|
||||
*
|
||||
* @param symbolServerInstanceCreatorContext an object that provides the necessary context to
|
||||
* the SymbolServerInstanceCreatorRegistry to create the SymbolServers that are listed in the
|
||||
* config values
|
||||
* @return a new {@link SymbolServerService} instance, never null
|
||||
*/
|
||||
public static SymbolServerService getSymbolServerService(
|
||||
SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext) {
|
||||
SymbolServer temporarySymbolServer =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(Preferences.getProperty(SYMBOL_STORAGE_DIR_OPTION, "", true),
|
||||
symbolServerInstanceCreatorContext);
|
||||
SymbolStore symbolStore =
|
||||
(temporarySymbolServer instanceof SymbolStore) ? (SymbolStore) temporarySymbolServer
|
||||
: new SameDirSymbolStore(symbolServerInstanceCreatorContext.getRootDir());
|
||||
List<SymbolServer> symbolServers =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.createSymbolServersFromPathList(getSymbolSearchPaths(),
|
||||
symbolServerInstanceCreatorContext);
|
||||
return new SymbolServerService(symbolStore, symbolServers);
|
||||
}
|
||||
|
||||
File selectedFile = pdbChooser.getSelectedFile();
|
||||
return selectedFile;
|
||||
/**
|
||||
* Persists the {@link SymbolStore} and {@link SymbolServer}s contained in the
|
||||
* {@link SymbolServerService}.
|
||||
*
|
||||
* @param symbolServerService {@link SymbolServerService} to save, or null if clear p
|
||||
* reference values
|
||||
*/
|
||||
public static void saveSymbolServerServiceConfig(SymbolServerService symbolServerService) {
|
||||
if (symbolServerService != null) {
|
||||
Preferences.setProperty(SYMBOL_STORAGE_DIR_OPTION,
|
||||
symbolServerService.getSymbolStore().getName());
|
||||
|
||||
String path = symbolServerService.getSymbolServers()
|
||||
.stream()
|
||||
.map(SymbolServer::getName)
|
||||
.collect(Collectors.joining(";"));
|
||||
Preferences.setProperty(SYMBOL_SEARCH_PATH_OPTION, path);
|
||||
}
|
||||
else {
|
||||
Preferences.setProperty(SYMBOL_STORAGE_DIR_OPTION, null);
|
||||
Preferences.setProperty(SYMBOL_SEARCH_PATH_OPTION, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> getSymbolSearchPaths() {
|
||||
String searchPathStr = Preferences.getProperty(SYMBOL_SEARCH_PATH_OPTION, "", true);
|
||||
|
||||
String[] pathParts = searchPathStr.split(";");
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String part : pathParts) {
|
||||
part = part.trim();
|
||||
if (!part.isEmpty()) {
|
||||
result.add(part);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -1,856 +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 pdb;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import docking.action.MenuData;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.context.ProgramActionContext;
|
||||
import ghidra.app.context.ProgramContextAction;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.app.services.DataTypeManagerService;
|
||||
import ghidra.app.util.bin.format.pdb.PdbException;
|
||||
import ghidra.app.util.bin.format.pdb.PdbParser;
|
||||
import ghidra.app.util.bin.format.pdb.PdbParser.PdbFileType;
|
||||
import ghidra.app.util.pdb.PdbLocator;
|
||||
import ghidra.app.util.pdb.PdbProgramAttributes;
|
||||
import ghidra.app.util.pdb.pdbapplicator.PdbApplicatorControl;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.net.http.HttpUtil;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.GhidraProgramUtilities;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
|
||||
/**
|
||||
* Plugin that allows users to download PDB files from a Symbol Server URL.
|
||||
*
|
||||
* PDB files can be of type .pdb, .pdb.xml, and .cab:
|
||||
* - .pdb files are Microsoft's native representation of debug symbols
|
||||
* - .pdb.xml files are representations of .pdb files using XML. Ghidra provides a script
|
||||
* for users to transform .pdb files into .pdb.xml files.
|
||||
* - .cab (cabinet) files are compressed .pdb files. A Symbol Server set up using Microsoft
|
||||
* tools will allow download of .cab files, relying on the user to extract a .pdb from
|
||||
* the .cab file.
|
||||
*
|
||||
* The Symbol Server can be a URL to a hosted file system or a server that was set up using Microsoft
|
||||
* tools. This code will also take care of PKI authentication, if needed by the server.
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.COMMON,
|
||||
shortDescription = "Download PDB Files from a Symbol Server",
|
||||
description = "This plugin manages the downloading of PDB files from a Symbol Server."
|
||||
)
|
||||
//@formatter:on
|
||||
public class PdbSymbolServerPlugin extends Plugin {
|
||||
|
||||
private static final String symbolServerEnvVar = "_NT_SYMBOL_PATH";
|
||||
|
||||
private static final String PDB_URL_PROPERTY = "PDB Symbol Server";
|
||||
|
||||
private static String expectedPdbContentType = "application/octet-stream";
|
||||
private static String expectedXmlContentType = "text/xml";
|
||||
private static Properties urlProperties = null;
|
||||
|
||||
// Store last-selected value(s) for askXxx methods
|
||||
private static String serverUrl = null;
|
||||
private static File localDir = null;
|
||||
private PdbFileType fileType = PdbFileType.PDB;
|
||||
private boolean includePePdbPath = false;
|
||||
|
||||
enum RetrieveFileType {
|
||||
PDB, XML, CAB
|
||||
}
|
||||
|
||||
enum ReturnPdbStatus {
|
||||
DOWNLOADED, EXISTING, NOT_FOUND;
|
||||
}
|
||||
|
||||
public PdbSymbolServerPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
createActions();
|
||||
|
||||
urlProperties = new Properties();
|
||||
// Version # appears to be debugger version. 6.3.9600.17298
|
||||
urlProperties.setProperty("User-Agent", "Microsoft-Symbol-Server/6.3.9600.17298");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link PdbFileType}
|
||||
* @param fileType the {@link PdbFileType}
|
||||
*/
|
||||
public void setPdbFileType(PdbFileType fileType) {
|
||||
this.fileType = fileType;
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
ProgramContextAction downloadPdbAction =
|
||||
new ProgramContextAction("Download_PDB_File", this.getName()) {
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ProgramActionContext context) {
|
||||
return context.getProgram() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(ProgramActionContext programContext) {
|
||||
downloadPDB();
|
||||
}
|
||||
};
|
||||
|
||||
MenuData menuData =
|
||||
new MenuData(new String[] { "&File", "Download PDB File..." }, null, "Import PDB");
|
||||
menuData.setMenuSubGroup("4");
|
||||
downloadPdbAction.setMenuBarData(menuData);
|
||||
|
||||
downloadPdbAction.setEnabled(false);
|
||||
downloadPdbAction.setHelpLocation(new HelpLocation("Pdb", downloadPdbAction.getName()));
|
||||
tool.addAction(downloadPdbAction);
|
||||
}
|
||||
|
||||
private void downloadPDB() {
|
||||
Program program = GhidraProgramUtilities.getCurrentProgram(tool);
|
||||
|
||||
try {
|
||||
|
||||
PdbFileAndStatus returnPdb = getPdbFile(program);
|
||||
|
||||
File returnedPdbFile = returnPdb.getPdbFile();
|
||||
|
||||
switch (returnPdb.getPdbStatus()) {
|
||||
case NOT_FOUND:
|
||||
Msg.showInfo(getClass(), null, "Error", "Could not download the " + fileType +
|
||||
" file for this version of " + program.getName() + " from " + serverUrl);
|
||||
break;
|
||||
|
||||
case DOWNLOADED:
|
||||
Msg.showInfo(getClass(), null, "File Retrieved", "Downloaded and saved file '" +
|
||||
returnedPdbFile.getName() + "' to \n" + returnedPdbFile.getParent());
|
||||
// no break here, since we want it to continue
|
||||
|
||||
case EXISTING:
|
||||
tryToLoadPdb(returnedPdbFile, program);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (CancelledException ce) {
|
||||
tool.setStatusInfo("Downloading PDB from Symbol Server was cancelled.");
|
||||
return;
|
||||
}
|
||||
catch (PdbException pe) {
|
||||
Msg.showInfo(getClass(), null, "Error", "Error: " + pe.getMessage());
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
Msg.showInfo(getClass(), null, "Error",
|
||||
ioe.getClass().getSimpleName() + ": " + ioe.getMessage());
|
||||
|
||||
// If URL connection failed, then reset the dialog to show the default symbol server
|
||||
// (instead of the last one we attempted to connect to).
|
||||
if (ioe instanceof UnknownHostException) {
|
||||
serverUrl = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves PDB, using GUI to interact with user to get PDB and Symbol Server Information
|
||||
*
|
||||
* @param program program for which to retrieve the PDB file
|
||||
* @return the retrieved PDB file (could be in .pdb or .xml form)
|
||||
* @throws CancelledException upon user cancellation
|
||||
* @throws IOException if an I/O issue occurred
|
||||
* @throws PdbException if there was a problem with the PDB attributes
|
||||
*/
|
||||
private PdbFileAndStatus getPdbFile(Program program)
|
||||
throws CancelledException, IOException, PdbException {
|
||||
|
||||
try {
|
||||
PdbProgramAttributes pdbAttributes = PdbParser.getPdbAttributes(program);
|
||||
|
||||
if (pdbAttributes.getGuidAgeCombo() == null) {
|
||||
throw new PdbException(
|
||||
"Incomplete PDB information (GUID/Signature and/or age) associated with this program.\n" +
|
||||
"Either the program is not a PE, or it was not compiled with debug information.");
|
||||
}
|
||||
|
||||
// 1. Ask if user wants .pdb or .pdb.xml file
|
||||
fileType = askForFileExtension();
|
||||
|
||||
// 1.5 Ask if should search PE-specified PDB path.
|
||||
includePePdbPath = askIncludePeHeaderPdbPath();
|
||||
|
||||
String symbolEnv = System.getenv(symbolServerEnvVar);
|
||||
if (symbolEnv != null) {
|
||||
parseSymbolEnv(symbolEnv);
|
||||
}
|
||||
|
||||
// 2. Ask for local storage location
|
||||
localDir = askForLocalStorageLocation();
|
||||
|
||||
// 3. See if PDB can be found locally
|
||||
File pdbFile = PdbParser.findPDB(pdbAttributes, includePePdbPath, localDir, fileType);
|
||||
|
||||
// 4. If not found locally, ask if it should be retrieved
|
||||
if (pdbFile != null && pdbFile.getName().endsWith(fileType.toString())) {
|
||||
|
||||
String htmlString =
|
||||
HTMLUtilities.toWrappedHTML("Found potential* matching PDB at: \n " +
|
||||
pdbFile.getAbsolutePath() + "\n\n* Match determined by file name only; " +
|
||||
"not vetted for matching GUID/version." +
|
||||
"\n\nContinue with download?\n\n" +
|
||||
"<i>(downloaded file will be saved in a directory of the form " +
|
||||
localDir.getAbsolutePath() + File.separator + "<pdbFilename>" +
|
||||
File.separator + "<GUID>" + File.separator + ")</i>");
|
||||
|
||||
// Warn that there is already a matching file
|
||||
int response =
|
||||
OptionDialog.showYesNoDialog(null, "Potential Matching PDB Found", htmlString);
|
||||
|
||||
switch (response) {
|
||||
case 0:
|
||||
// User cancelled
|
||||
throw new CancelledException();
|
||||
|
||||
case 1:
|
||||
// Yes -- do nothing here
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// No
|
||||
return new PdbFileAndStatus(pdbFile, ReturnPdbStatus.EXISTING);
|
||||
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Ask for Symbol Server location
|
||||
serverUrl = askForSymbolServerUrl();
|
||||
|
||||
// Fix up URL
|
||||
if (!serverUrl.endsWith("/")) {
|
||||
serverUrl += "/";
|
||||
}
|
||||
|
||||
File downloadedPdb = attemptToDownloadPdb(pdbAttributes, serverUrl, localDir);
|
||||
|
||||
if (downloadedPdb != null) {
|
||||
return new PdbFileAndStatus(downloadedPdb, ReturnPdbStatus.DOWNLOADED);
|
||||
}
|
||||
|
||||
return new PdbFileAndStatus();
|
||||
}
|
||||
finally {
|
||||
// Store the dialog choices
|
||||
Preferences.store();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseSymbolEnv(String envString) {
|
||||
|
||||
// Expect the environment string to be of the form:
|
||||
// srv*[local cache]*[private symbol server]*https://msdl.microsoft.com/download/symbols
|
||||
// srv*c:\symbols*https://msdl.microsoft.com/download/symbols
|
||||
|
||||
if (!envString.startsWith("srv") && !envString.startsWith("SRV")) {
|
||||
return;
|
||||
}
|
||||
|
||||
String[] envParts = envString.split("\\*");
|
||||
|
||||
if (envParts.length < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
File storageDir = new File(envParts[1]);
|
||||
if (storageDir.isDirectory()) {
|
||||
localDir = storageDir;
|
||||
}
|
||||
|
||||
serverUrl = envParts[2];
|
||||
|
||||
Msg.info(getClass(), "Using server URL: " + serverUrl);
|
||||
}
|
||||
|
||||
private PdbFileType askForFileExtension() throws CancelledException {
|
||||
//@formatter:off
|
||||
int choice = OptionDialog.showOptionDialog(
|
||||
null,
|
||||
"pdb or pdb.xml",
|
||||
"Download a .pdb or .pdb.xml file?",
|
||||
"PDB",
|
||||
"XML");
|
||||
//@formatter:on
|
||||
|
||||
if (choice == OptionDialog.CANCEL_OPTION) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
return (choice == OptionDialog.OPTION_ONE) ? PdbFileType.PDB : PdbFileType.XML;
|
||||
}
|
||||
|
||||
private boolean askIncludePeHeaderPdbPath() throws CancelledException {
|
||||
//@formatter:off
|
||||
int choice = OptionDialog.showOptionDialog(
|
||||
null,
|
||||
"PE-specified PDB Path",
|
||||
"Unsafe: Include PE-specified PDB Path in search for existing PDB",
|
||||
"Yes",
|
||||
"No");
|
||||
//@formatter:on
|
||||
|
||||
if (choice == OptionDialog.CANCEL_OPTION) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
return (choice == OptionDialog.OPTION_ONE);
|
||||
}
|
||||
|
||||
String askForSymbolServerUrl() throws CancelledException {
|
||||
|
||||
AskPdbUrlDialog dialog;
|
||||
String dialogResponse = null;
|
||||
String storedURL;
|
||||
|
||||
if (serverUrl != null) {
|
||||
storedURL = serverUrl;
|
||||
}
|
||||
else {
|
||||
storedURL = Preferences.getProperty(PDB_URL_PROPERTY);
|
||||
|
||||
if (storedURL == null) {
|
||||
storedURL = "";
|
||||
}
|
||||
}
|
||||
|
||||
while (dialogResponse == null) {
|
||||
dialog = new AskPdbUrlDialog("Symbol Server URL", "What is the Symbol Server URL?",
|
||||
storedURL);
|
||||
|
||||
if (dialog.isCanceled()) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
|
||||
dialogResponse = dialog.getValueAsString();
|
||||
|
||||
// Make sure user has included either 'http' or 'https'
|
||||
if (!dialogResponse.startsWith("http")) {
|
||||
Msg.showInfo(getClass(), null, "Incomplete URL",
|
||||
"URL should start with either 'http' or 'https'.");
|
||||
dialogResponse = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure that URL has valid syntax
|
||||
try {
|
||||
new URL(dialogResponse);
|
||||
}
|
||||
catch (MalformedURLException malExc) {
|
||||
Msg.showInfo(getClass(), null, "Malformed URL", malExc.toString());
|
||||
dialogResponse = null;
|
||||
}
|
||||
}
|
||||
|
||||
Preferences.setProperty(PDB_URL_PROPERTY, dialogResponse);
|
||||
|
||||
return dialogResponse;
|
||||
}
|
||||
|
||||
private File askForLocalStorageLocation() throws CancelledException {
|
||||
|
||||
final GhidraFileChooser fileChooser = new GhidraFileChooser(tool.getActiveWindow());
|
||||
|
||||
// Need to store the variable in an array to allow the final variable to be reassigned.
|
||||
// Using an array prevents the compiler from warning about "The final local variable
|
||||
// cannot be assigned, since it is defined in an enclosing type."
|
||||
final File[] chosenDir = new File[1];
|
||||
|
||||
File testDirectory = null;
|
||||
|
||||
// localDir is not null if we already parsed the _NT_SYMBOL_PATH environment var
|
||||
if (localDir != null) {
|
||||
testDirectory = localDir;
|
||||
}
|
||||
else {
|
||||
testDirectory = PdbLocator.getDefaultPdbSymbolsDir();
|
||||
}
|
||||
|
||||
final File storedDirectory = testDirectory;
|
||||
|
||||
Runnable r = () -> {
|
||||
while (chosenDir[0] == null && !fileChooser.wasCancelled()) {
|
||||
fileChooser.setSelectedFile(storedDirectory);
|
||||
|
||||
fileChooser.setTitle("Select Location to Save Retrieved File");
|
||||
fileChooser.setApproveButtonText("OK");
|
||||
fileChooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||
chosenDir[0] = fileChooser.getSelectedFile();
|
||||
|
||||
if (chosenDir[0] != null) {
|
||||
if (!chosenDir[0].exists()) {
|
||||
Msg.showInfo(getClass(), null, "Directory does not exist",
|
||||
"The directory '" + chosenDir[0].getAbsolutePath() +
|
||||
"' does not exist. Please create it or choose a valid directory.");
|
||||
chosenDir[0] = null;
|
||||
}
|
||||
else if (chosenDir[0].isFile()) {
|
||||
Msg.showInfo(getClass(), null, "Invalid Directory",
|
||||
"The location '" + chosenDir[0].getAbsolutePath() +
|
||||
"' represents a file, not a directory. Please choose a directory.");
|
||||
chosenDir[0] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
SystemUtilities.runSwingNow(r);
|
||||
|
||||
if (fileChooser.wasCancelled()) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
|
||||
PdbLocator.setDefaultPdbSymbolsDir(chosenDir[0]);
|
||||
|
||||
return chosenDir[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to download a file from a URL and save it to the specified location.
|
||||
*
|
||||
* @param fileUrl URL from which to download the file
|
||||
* @param fileDestination location at which to save the downloaded file
|
||||
* @return whether download/save succeeded
|
||||
* @throws IOException if an I/O issue occurred
|
||||
* @throws PdbException if issue with PKI certificate
|
||||
*/
|
||||
boolean retrieveFile(String fileUrl, File fileDestination) throws IOException, PdbException {
|
||||
return retrieveFile(fileUrl, fileDestination, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to download a file from a URL and save it to the specified location.
|
||||
*
|
||||
* @param fileUrl URL from which to download the file
|
||||
* @param fileDestination location at which to save the downloaded file
|
||||
* @param retrieveProperties optional HTTP request header values to be included (may be null)
|
||||
* @return whether download/save succeeded
|
||||
* @throws IOException if an I/O issue occurred
|
||||
* @throws PdbException if issue with PKI certificate
|
||||
*/
|
||||
boolean retrieveFile(String fileUrl, File fileDestination, Properties retrieveProperties)
|
||||
throws IOException, PdbException {
|
||||
|
||||
String expectedContentType =
|
||||
(fileType == PdbFileType.PDB) ? expectedPdbContentType : expectedXmlContentType;
|
||||
|
||||
try {
|
||||
String contentType =
|
||||
HttpUtil.getFile(fileUrl, retrieveProperties, true, fileDestination);
|
||||
|
||||
if (contentType != null && !contentType.equals(expectedContentType)) {
|
||||
fileDestination.delete();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
|
||||
// No PKI Certificate installed
|
||||
if (ioe.getMessage().equals("Forbidden")) {
|
||||
throw new PdbException(
|
||||
"PKI Certificate needed for user authentication.\nTo set a " +
|
||||
"certificate, use the Project Window's 'Edit -> Set PKI Certificate' Action.");
|
||||
}
|
||||
|
||||
if (!ioe.getMessage().equals("Not Found")) {
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
return fileDestination.exists();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Take given file and move it to the specified destination folder in the location
|
||||
* <destination folder>/<pdbFilename>/>guidAgeString< (subfolders that do not
|
||||
* already exist will be created).
|
||||
*
|
||||
* @param destinationFolder root folder to which the given file will be moved
|
||||
* @param pdbFilename name of PDB file (subfolder with this name will be created under destination
|
||||
* folder, if it doesn't already exist)
|
||||
* @param guidAgeString guidAge string of the PDB (subfolder with this name will be created under
|
||||
* <destination folder>/<pdbFilename> folder, if it doesn't already exist)
|
||||
* @param downloadFilename name of final moved file (can be same as pdbFilename)
|
||||
* @param tempFile actual file to be moved
|
||||
* @return file that was moved (and optionally renamed) in its new location
|
||||
* @throws IOException if there was an IO-related problem making the directory or moving the file
|
||||
*/
|
||||
File createSubFoldersAndMoveFile(File destinationFolder, String pdbFilename,
|
||||
String guidAgeString, String downloadFilename, File tempFile) throws IOException {
|
||||
|
||||
File pdbOuterSaveDir = makeDirectory(destinationFolder, pdbFilename);
|
||||
File pdbInnerSaveDir = makeDirectory(pdbOuterSaveDir, guidAgeString);
|
||||
|
||||
File finalDestFile = new File(pdbInnerSaveDir, downloadFilename);
|
||||
|
||||
try {
|
||||
Files.move(tempFile.toPath(), finalDestFile.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
catch (IOException e) {
|
||||
tempFile.delete();
|
||||
throw new IOException("Could not save file: " + finalDestFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
return finalDestFile;
|
||||
}
|
||||
|
||||
private File makeDirectory(File parentFolder, String directoryName) throws IOException {
|
||||
File newDir = new File(parentFolder, directoryName);
|
||||
|
||||
if (newDir.isFile()) {
|
||||
throw new IOException("Trying to create folder " + newDir.getAbsolutePath() +
|
||||
",\nbut it shares the same name as an existing file.\n" +
|
||||
"Please try downloading PDB again, selecting a " +
|
||||
"non-conflicting destination folder.");
|
||||
}
|
||||
|
||||
if (!newDir.isDirectory()) {
|
||||
boolean madeDir = newDir.mkdir();
|
||||
if (!madeDir) {
|
||||
throw new IOException(
|
||||
"Trying to create parent folders to store PDB file. Could not create directory " +
|
||||
newDir.getAbsolutePath() + ".");
|
||||
}
|
||||
}
|
||||
|
||||
return newDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand cabinet (.cab) files (Windows compressed format).
|
||||
*
|
||||
* When on Windows, use the 'expand' command (should already be included with the OS).
|
||||
* When on Unix/Mac, use 'cabextract', which has been included with Ghidra.
|
||||
*
|
||||
* @param cabFile file to expand/uncompress
|
||||
* @param targetFilename file to save uncompressed *.pdb to
|
||||
* @return the file that was uncompressed
|
||||
* @throws PdbException if failure with cabinet extraction
|
||||
* @throws IOException if issue starting the {@link ProcessBuilder}
|
||||
*/
|
||||
File uncompressCabFile(File cabFile, String targetFilename) throws PdbException, IOException {
|
||||
|
||||
String cabextractPath = null;
|
||||
String[] cabextractCmdLine;
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
File cabextractExe = new File("C:\\Windows\\System32\\expand.exe");
|
||||
|
||||
if (!cabextractExe.exists()) {
|
||||
throw new PdbException(
|
||||
"Expected to find cabinet expansion utility 'expand.exe' in " +
|
||||
cabextractExe.getParent());
|
||||
}
|
||||
|
||||
cabextractPath = cabextractExe.getAbsolutePath();
|
||||
|
||||
// expand -R <source>.cab -F:<files> <destination>
|
||||
// -R renames from .cab to .pdb
|
||||
// -F specifies which files within cab to expand
|
||||
cabextractCmdLine = new String[] { cabextractPath, "-R", cabFile.getAbsolutePath(),
|
||||
"-F:" + targetFilename, cabFile.getParent() };
|
||||
}
|
||||
else {
|
||||
|
||||
// On Mac/Linux
|
||||
try {
|
||||
cabextractPath = Application.getOSFile("cabextract").getAbsolutePath();
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new PdbException("Unable to find 'cabextract' executable.");
|
||||
}
|
||||
|
||||
// -q for quiet
|
||||
// -d to specify where to extract to
|
||||
// -F to specify filter pattern of file(s) to extract
|
||||
cabextractCmdLine = new String[] { cabextractPath, "-q", "-d", cabFile.getParent(),
|
||||
"-F", targetFilename, cabFile.getAbsolutePath() };
|
||||
}
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder(cabextractCmdLine);
|
||||
Process currentProcess = builder.start();
|
||||
|
||||
try {
|
||||
int exitValue = currentProcess.waitFor();
|
||||
|
||||
if (exitValue != 0) {
|
||||
throw new PdbException("Abnormal termination of 'cabextract' process.");
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ie) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// Look for the file
|
||||
FilenameFilter pdbFilter = (dir, filename) -> {
|
||||
String lowercaseName = filename.toLowerCase();
|
||||
return (lowercaseName.endsWith(fileType.toString()));
|
||||
};
|
||||
|
||||
File[] files = cabFile.getParentFile().listFiles(pdbFilter);
|
||||
if (files != null) {
|
||||
for (File childFile : files) {
|
||||
if (childFile.getName().equals(targetFilename)) {
|
||||
return childFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file, then move it to its final destination. URL for download is created by
|
||||
* combining downloadURL and PDB file attributes. Final move destination is also determined
|
||||
* by the PDB file attributes.
|
||||
*
|
||||
* @param pdbAttributes PDB attributes (GUID, age, potential PDB locations, etc.)
|
||||
* @param downloadUrl Root URL to search for the PDB
|
||||
* @param saveToLocation Final root directory to save the file
|
||||
* @return the downloaded and moved file
|
||||
* @throws IOException if an I/O issue occurred
|
||||
* @throws PdbException if issue with PKI certificate or cabinet extraction
|
||||
*/
|
||||
private File attemptToDownloadPdb(PdbProgramAttributes pdbAttributes, String downloadUrl,
|
||||
File saveToLocation) throws PdbException, IOException {
|
||||
|
||||
// Get location of the user's 'temp' directory
|
||||
String tempDirPath = System.getProperty("java.io.tmpdir");
|
||||
File tempDir = new File(tempDirPath);
|
||||
|
||||
RetrieveFileType retrieveType =
|
||||
(fileType == PdbFileType.XML) ? RetrieveFileType.XML : RetrieveFileType.PDB;
|
||||
|
||||
// Attempt retrieval from connection (encrypted or non-encrypted are handled) by HttpUtil
|
||||
File createdFile = downloadExtractAndMoveFile(pdbAttributes, downloadUrl, tempDir,
|
||||
saveToLocation, retrieveType);
|
||||
|
||||
if (createdFile != null) {
|
||||
return createdFile;
|
||||
}
|
||||
|
||||
// If Microsoft-specific server, need to do more (i.e., filename will be named *.pd_ and in
|
||||
// .cab format). Need to change http connection properties to be able to pull back file.
|
||||
|
||||
// Attempt retrieval as if it was a Microsoft-specific URL
|
||||
if (retrieveType == RetrieveFileType.PDB) {
|
||||
return downloadExtractAndMoveFile(pdbAttributes, downloadUrl, tempDir, saveToLocation,
|
||||
RetrieveFileType.CAB);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file, then move it to its final destination. URL for download is created by
|
||||
* combining downloadURL and PDB file attributes. Final move destination is also determined
|
||||
* by the PDB file attributes.
|
||||
*
|
||||
* @param pdbAttributes PDB attributes (GUID, age, potential PDB locations, etc.)
|
||||
* @param downloadUrl Root URL to search for the PDB
|
||||
* @param tempSaveDirectory Temporary local directory to save downloaded file (which will be moved)
|
||||
* @param finalSaveDirectory Final root directory to save the file
|
||||
* @param retrieveFileType the {@link RetrieveFileType}
|
||||
* @return the downloaded and moved file
|
||||
* @throws IOException if an I/O issue occurred
|
||||
* @throws PdbException if issue with PKI certificate or cabinet extraction
|
||||
*/
|
||||
File downloadExtractAndMoveFile(PdbProgramAttributes pdbAttributes, String downloadUrl,
|
||||
File tempSaveDirectory, File finalSaveDirectory, RetrieveFileType retrieveFileType)
|
||||
throws IOException, PdbException {
|
||||
|
||||
// TODO: This should be performed by a monitored Task with ability to cancel
|
||||
|
||||
String guidAgeString = pdbAttributes.getGuidAgeCombo();
|
||||
List<String> potentialPdbFilenames = pdbAttributes.getPotentialPdbFilenames();
|
||||
File tempFile = null;
|
||||
String tempFileExtension = (retrieveFileType == RetrieveFileType.CAB) ? "cab" : "pdb";
|
||||
|
||||
File returnFile = null;
|
||||
|
||||
try {
|
||||
|
||||
tempFile = new File(tempSaveDirectory, "TempPDB." + tempFileExtension);
|
||||
|
||||
// Attempt retrieval from connection (encrypted or non-encrypted are handled)
|
||||
for (String pdbFilename : potentialPdbFilenames) {
|
||||
|
||||
String downloadFilename = pdbFilename;
|
||||
String currentUrl = downloadUrl + pdbFilename + "/" + guidAgeString + "/";
|
||||
|
||||
boolean retrieveSuccess = false;
|
||||
|
||||
switch (retrieveFileType) {
|
||||
case CAB:
|
||||
currentUrl += downloadFilename;
|
||||
currentUrl = currentUrl.substring(0, currentUrl.length() - 1) + "_";
|
||||
retrieveSuccess = retrieveFile(currentUrl, tempFile, urlProperties);
|
||||
|
||||
if (!retrieveSuccess) {
|
||||
continue;
|
||||
}
|
||||
|
||||
File extractedFile = uncompressCabFile(tempFile, pdbFilename);
|
||||
|
||||
if (extractedFile == null) {
|
||||
throw new IOException(
|
||||
"Unable to uncompress .cab file extracted for " + pdbFilename);
|
||||
}
|
||||
returnFile = extractedFile;
|
||||
|
||||
break;
|
||||
|
||||
case PDB:
|
||||
currentUrl += downloadFilename;
|
||||
retrieveSuccess = retrieveFile(currentUrl, tempFile);
|
||||
|
||||
if (!retrieveSuccess) {
|
||||
continue;
|
||||
}
|
||||
|
||||
returnFile = tempFile;
|
||||
break;
|
||||
|
||||
case XML:
|
||||
downloadFilename += ".xml";
|
||||
currentUrl += downloadFilename;
|
||||
retrieveSuccess = retrieveFile(currentUrl, tempFile);
|
||||
|
||||
if (!retrieveSuccess) {
|
||||
continue;
|
||||
}
|
||||
|
||||
returnFile = tempFile;
|
||||
break;
|
||||
}
|
||||
|
||||
return createSubFoldersAndMoveFile(finalSaveDirectory, pdbFilename, guidAgeString,
|
||||
downloadFilename, returnFile);
|
||||
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (tempFile != null && tempFile.exists()) {
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void tryToLoadPdb(File downloadedPdb, Program currentProgram) {
|
||||
|
||||
AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(currentProgram);
|
||||
if (aam.isAnalyzing()) {
|
||||
Msg.showWarn(getClass(), null, "Load PDB",
|
||||
"Unable to load PDB file while analysis is running.");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean analyzed =
|
||||
currentProgram.getOptions(Program.PROGRAM_INFO).getBoolean(Program.ANALYZED, false);
|
||||
|
||||
String message = "Would you like to apply the following PDB:\n\n" +
|
||||
downloadedPdb.getAbsolutePath() + "\n\n to " + currentProgram.getName() + "?";
|
||||
if (analyzed) {
|
||||
message += "\n \nWARNING: Loading PDB after analysis has been performed may produce" +
|
||||
"\npoor results. PDBs should generally be loaded prior to analysis or" +
|
||||
"\nautomatically during auto-analysis.";
|
||||
}
|
||||
|
||||
String htmlString = HTMLUtilities.toWrappedHTML(message);
|
||||
int response = OptionDialog.showYesNoDialog(null, "Load PDB?", htmlString);
|
||||
if (response != OptionDialog.YES_OPTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
AskPdbOptionsDialog optionsDialog =
|
||||
new AskPdbOptionsDialog(null, fileType == PdbFileType.PDB);
|
||||
if (optionsDialog.isCanceled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean useMsDiaParser = optionsDialog.useMsDiaParser();
|
||||
PdbApplicatorControl control = optionsDialog.getApplicatorControl();
|
||||
|
||||
tool.setStatusInfo("");
|
||||
|
||||
try {
|
||||
DataTypeManagerService service = tool.getService(DataTypeManagerService.class);
|
||||
if (service == null) {
|
||||
Msg.showWarn(getClass(), null, "Load PDB",
|
||||
"Unable to locate DataTypeService in the current tool.");
|
||||
return;
|
||||
}
|
||||
|
||||
TaskLauncher
|
||||
.launch(
|
||||
new LoadPdbTask(currentProgram, downloadedPdb, useMsDiaParser, control,
|
||||
service));
|
||||
}
|
||||
catch (Exception pe) {
|
||||
Msg.showError(getClass(), null, "Error", pe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
class PdbFileAndStatus {
|
||||
|
||||
private File pdbFile;
|
||||
private ReturnPdbStatus pdbStatus;
|
||||
|
||||
public PdbFileAndStatus() {
|
||||
pdbFile = null;
|
||||
pdbStatus = ReturnPdbStatus.NOT_FOUND;
|
||||
}
|
||||
|
||||
public PdbFileAndStatus(File pdbFile, ReturnPdbStatus pdbStatus) {
|
||||
this.pdbFile = pdbFile;
|
||||
this.pdbStatus = pdbStatus;
|
||||
}
|
||||
|
||||
public File getPdbFile() {
|
||||
return pdbFile;
|
||||
}
|
||||
|
||||
public ReturnPdbStatus getPdbStatus() {
|
||||
return pdbStatus;
|
||||
}
|
||||
}
|
||||
}
|
117
Ghidra/Features/PDB/src/main/java/pdb/PdbUtils.java
Normal file
@ -0,0 +1,117 @@
|
||||
/* ###
|
||||
* 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 pdb;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import ghidra.app.util.bin.format.pdb2.pdbreader.*;
|
||||
import ghidra.app.util.datatype.microsoft.GUID;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.xml.*;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
public class PdbUtils {
|
||||
|
||||
/**
|
||||
* Attempts to extract {@link PdbIdentifiers} from the specified file, which
|
||||
* can be either a pdb or pdb.xml file.
|
||||
* <p>
|
||||
*
|
||||
* @param file File to examine
|
||||
* @param monitor {@link TaskMonitor}to allow cancel and progress
|
||||
* @return new {@link PdbIdentifiers} instance with GUID/ID and age info, or null if
|
||||
* not a valid pdb or pdb.xml file
|
||||
*/
|
||||
public static PdbIdentifiers getPdbIdentifiers(File file, TaskMonitor monitor) {
|
||||
String extension = FilenameUtils.getExtension(file.getName()).toLowerCase();
|
||||
switch (extension) {
|
||||
case "pdb":
|
||||
try (AbstractPdb pdb =
|
||||
PdbParser.parse(file.getPath(), new PdbReaderOptions(), monitor)) {
|
||||
PdbIdentifiers identifiers = pdb.getIdentifiers();
|
||||
return identifiers;
|
||||
}
|
||||
catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
case "xml":
|
||||
XmlPullParser parser = null;
|
||||
try {
|
||||
parser = XmlPullParserFactory.create(file, null, false);
|
||||
|
||||
XmlElement xmlelem = parser.peek();
|
||||
|
||||
if (!"pdb".equals(xmlelem.getName())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String guidStr = xmlelem.getAttribute("guid");
|
||||
GUID guid = new GUID(guidStr);
|
||||
int age = Integer.parseInt(xmlelem.getAttribute("age"));
|
||||
|
||||
return new PdbIdentifiers(0, 0, age, guid, null);
|
||||
}
|
||||
catch (SAXException | IOException | RuntimeException e) {
|
||||
// don't care, return null
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
if (parser != null) {
|
||||
parser.dispose();
|
||||
}
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a singleton file from a cab file that only has 1 file
|
||||
*
|
||||
* @param cabFile Compressed cab file that only has 1 file embedded in it
|
||||
* @param destFile where to write the extracted file to
|
||||
* @param monitor {@link TaskMonitor} to allow canceling
|
||||
* @return original name of the file
|
||||
* @throws CancelledException if cancelled
|
||||
* @throws IOException if error reading / writing file or cab file has more than 1 file in it
|
||||
*/
|
||||
public static String extractSingletonCabToFile(File cabFile, File destFile, TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
FileSystemService fsService = FileSystemService.getInstance();
|
||||
FSRL cabFSRL = fsService.getLocalFSRL(cabFile);
|
||||
try (GFileSystem fs = fsService.openFileSystemContainer(cabFSRL, monitor)) {
|
||||
if (fs != null) {
|
||||
List<GFile> rootListing = fs.getListing(null);
|
||||
if (rootListing.size() == 1) {
|
||||
GFile f = rootListing.get(0);
|
||||
try (InputStream is = fs.getInputStream(f, monitor)) {
|
||||
FileUtilities.copyStreamToFile(is, destFile, false, monitor);
|
||||
return f.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IOException("Unable to find file to extract");
|
||||
}
|
||||
|
||||
}
|
@ -1,34 +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 pdb;
|
||||
|
||||
public class URLChoice {
|
||||
private String network;
|
||||
private String url;
|
||||
|
||||
public URLChoice(String network, String url) {
|
||||
this.network = network;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getNetwork() {
|
||||
return network;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Common functionality of File and Http symbol servers.
|
||||
*/
|
||||
public abstract class AbstractSymbolServer implements SymbolServer {
|
||||
protected static final String INDEX_TWO_FILENAME = "index2.txt";
|
||||
protected static final String PINGME_FILENAME = "pingme.txt"; // per MS custom
|
||||
|
||||
protected int storageLevel = -1;
|
||||
|
||||
@Override
|
||||
public List<SymbolFileLocation> find(SymbolFileInfo symbolFileInfo, Set<FindOption> options,
|
||||
TaskMonitor monitor) {
|
||||
initStorageLevelIfNeeded(monitor);
|
||||
|
||||
try {
|
||||
// "ke/kernelstuff.pdb/12345ABCFF0/"
|
||||
String uniqueFileDir = getUniqueFileDir(symbolFileInfo);
|
||||
|
||||
// "ke/kernelstuff.pdb/12345ABCFF0/kernelstuff.pdb" or
|
||||
// "ke/kernelstuff.pdb/12345ABCFF0/kernelstuff.pd_"
|
||||
String filePath = getFirstExists(uniqueFileDir, monitor, symbolFileInfo.getName(),
|
||||
getCompressedFilename(symbolFileInfo));
|
||||
|
||||
return (filePath != null)
|
||||
? List.of(new SymbolFileLocation(filePath, this, symbolFileInfo))
|
||||
: List.of();
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
Msg.warn(this, "Error searching for " + symbolFileInfo.getName(), ioe);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
protected int detectStorageLevel(TaskMonitor monitor) {
|
||||
return exists(INDEX_TWO_FILENAME, monitor) ? 2 : 1;
|
||||
}
|
||||
|
||||
protected void initStorageLevelIfNeeded(TaskMonitor monitor) {
|
||||
if (storageLevel < 0) {
|
||||
storageLevel = detectStorageLevel(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getFileDir(String filename) throws IOException {
|
||||
switch (storageLevel) {
|
||||
case 0:
|
||||
return "";
|
||||
case 1:
|
||||
return filename + "/";
|
||||
case 2:
|
||||
if (filename.length() <= 2) {
|
||||
throw new IOException(
|
||||
"Symbol filename too short to store in two-level index: " + filename);
|
||||
}
|
||||
return filename.substring(0, 2).toLowerCase() + "/" + filename + "/";
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported Symbol Server storage level: " + storageLevel);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getUniqueFileDir(SymbolFileInfo symbolFileInfo) throws IOException {
|
||||
switch (storageLevel) {
|
||||
case 0:
|
||||
return "";
|
||||
case 1:
|
||||
case 2:
|
||||
// "ke/kernelstuff.pdb/" or just "kernelstuff.pdb/"
|
||||
String fileRoot = getFileDir(symbolFileInfo.getName());
|
||||
|
||||
// "ke/kernelstuff.pdb/12345ABCFF0/"
|
||||
String uniqueFileDir = fileRoot + symbolFileInfo.getUniqueDirName() + "/";
|
||||
|
||||
return uniqueFileDir;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported Symbol Server storage level: " + storageLevel);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getFirstExists(String subDir, TaskMonitor monitor, String... filenames) {
|
||||
for (String filename : filenames) {
|
||||
String pathname = subDir + filename;
|
||||
if (exists(pathname, monitor)) {
|
||||
return pathname;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String makeCompressedExtension(String fileTypeExtension) {
|
||||
return (!fileTypeExtension.isEmpty()
|
||||
? fileTypeExtension.substring(0, fileTypeExtension.length() - 1)
|
||||
: "") +
|
||||
"_";
|
||||
}
|
||||
|
||||
static String getCompressedFilename(SymbolFileInfo symbolFileInfo) {
|
||||
return FilenameUtils.getBaseName(symbolFileInfo.getName()) + "." +
|
||||
makeCompressedExtension(FilenameUtils.getExtension(symbolFileInfo.getName()));
|
||||
}
|
||||
|
||||
static String getCompressedFilename(String filename) {
|
||||
return FilenameUtils.getBaseName(filename) + "." +
|
||||
makeCompressedExtension(FilenameUtils.getExtension(filename));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A wrapper around a real symbol server that indicates that the symbol server has been disabled.
|
||||
* <p>
|
||||
* Any find() operations will return an empty list, but file retrieval will still be passed through
|
||||
* to the original symbol server instance.
|
||||
*/
|
||||
public class DisabledSymbolServer implements SymbolServer {
|
||||
|
||||
private static String DISABLED_PREFIX = "disabled://";
|
||||
|
||||
/**
|
||||
* Predicate that tests if the location string is an instance of a disabled location.
|
||||
*
|
||||
* @param loc location string
|
||||
* @return boolean true if the string should be handled by the DisabledSymbolServer class
|
||||
*/
|
||||
public static boolean isDisabledSymbolServerLocation(String loc) {
|
||||
return loc.startsWith(DISABLED_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create new instances from a location string.
|
||||
*
|
||||
* @param locationString location string
|
||||
* @param context {@link SymbolServerInstanceCreatorContext}
|
||||
* @return new instance, or null if invalid location string
|
||||
*/
|
||||
public static SymbolServer createInstance(String locationString,
|
||||
SymbolServerInstanceCreatorContext context) {
|
||||
SymbolServer delegate =
|
||||
context.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(locationString.substring(DISABLED_PREFIX.length()), context);
|
||||
return (delegate != null) ? new DisabledSymbolServer(delegate) : null;
|
||||
}
|
||||
|
||||
private SymbolServer delegate;
|
||||
|
||||
/**
|
||||
* Creates a new instance, wrapping an existing SymbolServer.
|
||||
*
|
||||
* @param delegate the SymbolServer that is being disabled
|
||||
*/
|
||||
public DisabledSymbolServer(SymbolServer delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped (disabled) SymbolServer.
|
||||
*
|
||||
* @return wrapped / disabled SymbolServer
|
||||
*/
|
||||
public SymbolServer getSymbolServer() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return DISABLED_PREFIX + delegate.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescriptiveName() {
|
||||
return "Disabled - " + delegate.getDescriptiveName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(TaskMonitor monitor) {
|
||||
return delegate.isValid(monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String filename, TaskMonitor monitor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SymbolFileLocation> find(SymbolFileInfo fileInfo, Set<FindOption> findOptions,
|
||||
TaskMonitor monitor) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolServerInputStream getFileStream(String filename, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
return delegate.getFileStream(filename, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileLocation(String filename) {
|
||||
return delegate.getFileLocation(filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return delegate.isLocal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("DisabledSymbolServer: [ %s ]", delegate.toString());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pdb.symbolserver;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Options that control how Pdb files are searched for on a SymbolServer.
|
||||
*/
|
||||
public enum FindOption {
|
||||
/**
|
||||
* Allow connections to remote symbol servers
|
||||
*/
|
||||
ALLOW_REMOTE,
|
||||
/**
|
||||
* Only return the first result
|
||||
*/
|
||||
ONLY_FIRST_RESULT,
|
||||
/**
|
||||
* Match any Pdb with the same name, regardless of GUID / signature id / age.
|
||||
* (implies ANY_AGE)
|
||||
*/
|
||||
ANY_ID,
|
||||
/**
|
||||
* Match any Pdb with the same name and ID, regardless of age.
|
||||
*/
|
||||
ANY_AGE;
|
||||
|
||||
/**
|
||||
* Static constant empty set of no FindOptions.
|
||||
*/
|
||||
public static final Set<FindOption> NO_OPTIONS = Set.of();
|
||||
|
||||
/**
|
||||
* Create a container of FindOptions.
|
||||
*
|
||||
* @param findOptions varargs list of FindOption enum values
|
||||
* @return set of the specified FindOptions
|
||||
*/
|
||||
public static Set<FindOption> of(FindOption... findOptions) {
|
||||
EnumSet<FindOption> result = EnumSet.noneOf(FindOption.class);
|
||||
result.addAll(List.of(findOptions));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpRequest.BodyPublishers;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
import java.time.Duration;
|
||||
|
||||
import ghidra.net.HttpClients;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A {@link SymbolServer} that is accessed via HTTP.
|
||||
* <p>
|
||||
*
|
||||
*/
|
||||
public class HttpSymbolServer extends AbstractSymbolServer {
|
||||
private static final String GHIDRA_USER_AGENT = "Ghidra_HttpSymbolServer_client";
|
||||
private static final int HTTP_STATUS_OK = HttpURLConnection.HTTP_OK;
|
||||
private static final int HTTP_REQUEST_TIMEOUT_MS = 10000; // 10 seconds
|
||||
|
||||
/**
|
||||
* Predicate that tests if the location string is an instance of a HttpSymbolServer location.
|
||||
*
|
||||
* @param locationString symbol server location string
|
||||
* @return boolean true if the string should be handled by the HttpSymbolServer class
|
||||
*/
|
||||
public static boolean isHttpSymbolServerLocation(String locationString) {
|
||||
return locationString.startsWith("http://") || locationString.startsWith("https://");
|
||||
}
|
||||
|
||||
private final URI serverURI;
|
||||
|
||||
/**
|
||||
* Creates a new instance of a HttpSymbolServer.
|
||||
*
|
||||
* @param serverURI URI / URL of the symbol server
|
||||
*/
|
||||
public HttpSymbolServer(URI serverURI) {
|
||||
String path = serverURI.getPath();
|
||||
this.serverURI =
|
||||
path.endsWith("/") ? serverURI : serverURI.resolve(serverURI.getPath() + "/");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return serverURI.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(TaskMonitor monitor) {
|
||||
// NOTE: checking a http symbolserver's state by testing the
|
||||
// existence of a file is not 100% universally correct, as different
|
||||
// webserver implementations will handle this differently, but
|
||||
// no better options are apparent.
|
||||
// Just getting any HTTP response, including a 404 not found, isn't a
|
||||
// good indication that the symbol server is valid as it could be
|
||||
// a missing subtree of a parent web site.
|
||||
return exists("", monitor) || exists(PINGME_FILENAME, monitor);
|
||||
}
|
||||
|
||||
private HttpRequest.Builder request(String str) {
|
||||
return HttpRequest.newBuilder(serverURI.resolve(str))
|
||||
.timeout(Duration.ofMillis(HTTP_REQUEST_TIMEOUT_MS))
|
||||
.setHeader("User-Agent", GHIDRA_USER_AGENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String filename, TaskMonitor monitor) {
|
||||
try {
|
||||
HttpRequest request = request(filename).method("HEAD", BodyPublishers.noBody()).build();
|
||||
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": Checking exist for [" + filename + "]: " + request.toString());
|
||||
HttpResponse<Void> response =
|
||||
HttpClients.getHttpClient().send(request, BodyHandlers.discarding());
|
||||
int statusCode = response.statusCode();
|
||||
Msg.debug(this, logPrefix() + ": Response: " + response.statusCode());
|
||||
|
||||
return statusCode == HTTP_STATUS_OK;
|
||||
}
|
||||
catch (InterruptedException | IOException e) {
|
||||
// ignore, return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolServerInputStream getFileStream(String filename, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
try {
|
||||
HttpRequest request = request(filename).GET().build();
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": Getting file [" + filename + "]: " + request.toString());
|
||||
HttpResponse<InputStream> response =
|
||||
HttpClients.getHttpClient().send(request, BodyHandlers.ofInputStream());
|
||||
int statusCode = response.statusCode();
|
||||
Msg.debug(this, logPrefix() + ": Http response: " + response.statusCode());
|
||||
if (statusCode == HTTP_STATUS_OK) {
|
||||
long contentLen = response.headers().firstValueAsLong("Content-Length").orElse(-1);
|
||||
return new SymbolServerInputStream(response.body(), contentLen);
|
||||
}
|
||||
throw new IOException("Unable to get file: " + statusCode);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new IOException("Http get interrupted");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileLocation(String filename) {
|
||||
return serverURI.resolve(filename).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("HttpSymbolServer: [ url: %s, storageLevel: %d]", serverURI.toString(),
|
||||
storageLevel);
|
||||
}
|
||||
|
||||
private String logPrefix() {
|
||||
return getClass().getSimpleName() + "[" + serverURI + "]";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,389 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Stores Pdb symbol files in a local directory.
|
||||
* <p>
|
||||
* This is both a {@link SymbolServer} and a {@link SymbolStore}
|
||||
* <p>
|
||||
*/
|
||||
public class LocalSymbolStore extends AbstractSymbolServer implements SymbolStore {
|
||||
private static final String ADMIN_DIRNAME = "000admin"; // per MS custom
|
||||
|
||||
/**
|
||||
* Predicate that returns true if the location string is a LocalSymbolStore path
|
||||
*
|
||||
* @param locationString symbol server location string
|
||||
* @return boolean true if a LocalSymbolStore path
|
||||
*/
|
||||
public static boolean isLocalSymbolStoreLocation(String locationString) {
|
||||
if (locationString == null || locationString.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
File dir = new File(locationString);
|
||||
return dir.isAbsolute() && dir.isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (hopefully) MS-compatible symbol server directory location.
|
||||
* <p>
|
||||
*
|
||||
* @param rootDir Directory location of the new symbol store
|
||||
* @param indexLevel the 'level' of the storage directory. Typical directories
|
||||
* are either level 1, with pdb files stored directly under
|
||||
* the root directory, or level 2, using the first 2
|
||||
* characters of the pdb filename as a bucket to place each
|
||||
* pdb file-directory in. Level 0 indexLevel is a special
|
||||
* Ghidra construct that is just a user-friendlier plain
|
||||
* directory with a collection of Pdb files
|
||||
* @throws IOException if error creating directory or admin files
|
||||
*/
|
||||
public static void create(File rootDir, int indexLevel) throws IOException {
|
||||
FileUtilities.checkedMkdirs(rootDir);
|
||||
switch (indexLevel) {
|
||||
case 0:
|
||||
// don't have to do anything
|
||||
break;
|
||||
case 2:
|
||||
File index2File = new File(rootDir, INDEX_TWO_FILENAME);
|
||||
if (!index2File.exists()) {
|
||||
FileUtilities.writeStringToFile(index2File,
|
||||
"created by Ghidra LocalSymbolStore " + new Date());
|
||||
}
|
||||
// fall thru to create pingme and admin dir
|
||||
case 1:
|
||||
File pingmeFile = new File(rootDir, PINGME_FILENAME);
|
||||
if (!pingmeFile.exists()) {
|
||||
FileUtilities.writeStringToFile(pingmeFile,
|
||||
"created by Ghidra LocalSymbolStore " + new Date());
|
||||
}
|
||||
File adminDir = new File(rootDir, ADMIN_DIRNAME);
|
||||
if (!adminDir.isDirectory()) {
|
||||
FileUtilities.checkedMkdir(adminDir);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported storage index level: " + indexLevel);
|
||||
}
|
||||
}
|
||||
|
||||
private final File rootDir;
|
||||
|
||||
/**
|
||||
* Creates an instance of LocalSymbolStore.
|
||||
*
|
||||
* @param rootDir the root directory of the symbol storage
|
||||
*/
|
||||
public LocalSymbolStore(File rootDir) {
|
||||
this.rootDir = rootDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root directory of this symbol store.
|
||||
*
|
||||
* @return root directory of this symbol store
|
||||
*/
|
||||
public File getRootDir() {
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return rootDir.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getAdminDir() {
|
||||
return (storageLevel == 0) ? rootDir : new File(rootDir, ADMIN_DIRNAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(TaskMonitor monitor) {
|
||||
return isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-task monitor variant of {@link #isValid(TaskMonitor)}.
|
||||
*
|
||||
* @return boolean true if this is a valid symbol store
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return rootDir.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String filename, TaskMonitor monitor) {
|
||||
File f = new File(rootDir, filename);
|
||||
return f.isFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int detectStorageLevel(TaskMonitor monitor) {
|
||||
// if the PINGME files exists, it means this directory was initialized as
|
||||
// a real symbol server. If not, its probably just a normal directory
|
||||
// that contains files.
|
||||
File pingMeFile = new File(rootDir, PINGME_FILENAME);
|
||||
File adminDir = new File(rootDir, ADMIN_DIRNAME);
|
||||
if (pingMeFile.isFile() && adminDir.isDirectory()) {
|
||||
return super.detectStorageLevel(monitor);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SymbolFileLocation> find(SymbolFileInfo symbolFileInfo, Set<FindOption> options,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
initStorageLevelIfNeeded(monitor);
|
||||
|
||||
List<SymbolFileLocation> matches = new ArrayList<>();
|
||||
|
||||
// search for exact matches using the built-in logic in AbstractSymbolServer
|
||||
if (storageLevel != 0) {
|
||||
matches.addAll(super.find(symbolFileInfo, options, monitor));
|
||||
}
|
||||
|
||||
if (storageLevel == 0 || options.contains(FindOption.ANY_AGE) ||
|
||||
options.contains(FindOption.ANY_ID)) {
|
||||
|
||||
try {
|
||||
if (storageLevel == 0) {
|
||||
searchLevel0(rootDir, this, symbolFileInfo, options, matches, monitor);
|
||||
}
|
||||
else {
|
||||
searchLevelN(symbolFileInfo, options, matches, monitor);
|
||||
}
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
Msg.warn(this, "Error searching for " + symbolFileInfo.getName() + " in " + rootDir,
|
||||
ioe);
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
static void searchLevel0(File rootDir, SymbolStore symbolStore, SymbolFileInfo symbolFileInfo,
|
||||
Set<FindOption> options, List<SymbolFileLocation> matches, TaskMonitor monitor) {
|
||||
|
||||
// if its a "0 level" bag-of-files, we have to open each Pdb to find its UID and
|
||||
// AGE (after filtering for similar filenames as requested pdb file)
|
||||
for (File f : list(rootDir,
|
||||
ff -> ff.isFile() && isFilenameStartsWithMatch(symbolFileInfo, ff))) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
SymbolFileInfo fileInfo = SymbolFileInfo.fromFile(f, monitor);
|
||||
if (fileInfo != null) {
|
||||
if (hasSymbolFileInfoMatch(symbolFileInfo, fileInfo, options)) {
|
||||
matches.add(new SymbolFileLocation(f.getName(), symbolStore, fileInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void searchLevelN(SymbolFileInfo symbolFileInfo, Set<FindOption> options,
|
||||
List<SymbolFileLocation> matches,
|
||||
TaskMonitor monitor) throws IOException {
|
||||
|
||||
// enbiggen the search by grubing through our subdirectories.
|
||||
// "ke/kernelstuff.pdb/" or just "kernelstuff.pdb/"
|
||||
String fileDir = getFileDir(symbolFileInfo.getName());
|
||||
|
||||
// since its a normal 1 or 2 level, we can get UID and AGE info from the subpath
|
||||
// without opening the symbol file
|
||||
for (File subDir : list(new File(rootDir, fileDir), File::isDirectory)) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
searchSubDir(subDir, symbolFileInfo, fileDir, options, matches);
|
||||
}
|
||||
}
|
||||
|
||||
private void searchSubDir(File subDir, SymbolFileInfo symbolFileInfo, String relativeFileDir,
|
||||
Set<FindOption> options, List<SymbolFileLocation> results) {
|
||||
|
||||
String symbolFileName = symbolFileInfo.getName();
|
||||
SymbolFileInfo subDirSymbolFileInfo =
|
||||
SymbolFileInfo.fromSubdirectoryPath(symbolFileName, subDir.getName());
|
||||
|
||||
if (subDirSymbolFileInfo != null && !symbolFileInfo.isExactMatch(subDirSymbolFileInfo)) {
|
||||
// don't examine this subfolder if its fingerprints indicate its an exact match,
|
||||
// since exact matches will already have been added to the results
|
||||
|
||||
// "ke/kernelstuff.pdb/112233440/"
|
||||
String uniqueDir = relativeFileDir + subDir.getName() + "/";
|
||||
|
||||
if (hasSymbolFileInfoMatch(symbolFileInfo, subDirSymbolFileInfo, options)) {
|
||||
String matchingFile = getFirstExists(uniqueDir, null, symbolFileName,
|
||||
getCompressedFilename(symbolFileName));
|
||||
|
||||
if (matchingFile != null) {
|
||||
results.add(new SymbolFileLocation(matchingFile, this, subDirSymbolFileInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileLocation(String filename) {
|
||||
return getFile(filename).getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile(String path) {
|
||||
return new File(rootDir, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String giveFile(SymbolFileInfo symbolFileInfo, File file, String filename,
|
||||
TaskMonitor monitor) throws IOException {
|
||||
initStorageLevelIfNeeded(monitor);
|
||||
filename = FilenameUtils.getName(filename); // make sure no relative path shenanigans
|
||||
String relativeDestinationFilename = getUniqueFileDir(symbolFileInfo) + filename;
|
||||
File destinationFile = new File(rootDir, relativeDestinationFilename);
|
||||
FileUtilities.checkedMkdirs(destinationFile.getParentFile());
|
||||
if (destinationFile.isFile()) {
|
||||
Msg.info(this, logPrefix() + ": File already exists: " + destinationFile);
|
||||
if (!file.delete()) {
|
||||
Msg.warn(this, logPrefix() + ": Unable to delete source file: " + file);
|
||||
}
|
||||
return relativeDestinationFilename;
|
||||
}
|
||||
monitor.setMessage("Storing " + filename + " in local symbol store ");
|
||||
if (!file.renameTo(destinationFile)) {
|
||||
throw new IOException("Could not move " + file + " to " + destinationFile);
|
||||
}
|
||||
|
||||
return relativeDestinationFilename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String putStream(SymbolFileInfo symbolFileInfo,
|
||||
SymbolServerInputStream symbolServerInputStream, String filename, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
initStorageLevelIfNeeded(monitor);
|
||||
filename = FilenameUtils.getName(filename); // make sure no relative path shenanigans
|
||||
String relativeDestinationFilename = getUniqueFileDir(symbolFileInfo) + filename;
|
||||
File destinationFile = new File(rootDir, relativeDestinationFilename);
|
||||
FileUtilities.checkedMkdirs(destinationFile.getParentFile());
|
||||
if (destinationFile.isFile()) {
|
||||
Msg.info(this, logPrefix() + ": File already exists: " + destinationFile);
|
||||
return relativeDestinationFilename;
|
||||
}
|
||||
|
||||
File destinationFileTmp = new File(rootDir, relativeDestinationFilename + ".tmp");
|
||||
destinationFileTmp.delete();
|
||||
|
||||
monitor.setMessage("Storing " + filename + " in local symbol store ");
|
||||
|
||||
if (symbolServerInputStream.getExpectedLength() >= 0) {
|
||||
monitor.initialize(symbolServerInputStream.getExpectedLength());
|
||||
}
|
||||
try {
|
||||
long bytesCopied = FileUtilities.copyStreamToFile(
|
||||
symbolServerInputStream.getInputStream(), destinationFileTmp, false, monitor);
|
||||
if (symbolServerInputStream.getExpectedLength() >= 0 &&
|
||||
bytesCopied != symbolServerInputStream.getExpectedLength()) {
|
||||
throw new IOException("Copy length mismatch, expected " +
|
||||
symbolServerInputStream.getExpectedLength() + " bytes, got " + bytesCopied);
|
||||
}
|
||||
if (!destinationFileTmp.renameTo(destinationFile)) {
|
||||
throw new IOException(
|
||||
"Error renaming temp file " + destinationFileTmp + " to " + destinationFile);
|
||||
}
|
||||
return relativeDestinationFilename;
|
||||
}
|
||||
finally {
|
||||
destinationFileTmp.delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolServerInputStream getFileStream(String filename, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
File file = new File(rootDir, filename);
|
||||
return new SymbolServerInputStream(new FileInputStream(file), file.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("LocalSymbolStore: [ rootDir: %s, storageLevel: %d]",
|
||||
rootDir.getPath(), storageLevel);
|
||||
}
|
||||
|
||||
private String logPrefix() {
|
||||
return getClass().getSimpleName() + "[" + rootDir + "]";
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Static helpers
|
||||
|
||||
static File[] list(File dir, FileFilter filter) {
|
||||
File[] files = dir.listFiles(filter);
|
||||
return files != null ? files : new File[] {};
|
||||
}
|
||||
|
||||
static boolean isFilenameStartsWithMatch(SymbolFileInfo symbolFileInfo, File file) {
|
||||
String symbolFilenameNoExtension = FilenameUtils.getBaseName(symbolFileInfo.getName());
|
||||
String fileNoExtension = FilenameUtils.getBaseName(file.getName());
|
||||
|
||||
// use case-insensitive compare since these are PDB files, which
|
||||
// come from a Windows env
|
||||
if (!fileNoExtension.toLowerCase().startsWith(symbolFilenameNoExtension.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// match on ext ("pdb"), compressed ext ("pd_")
|
||||
String symbolFilenameExtension =
|
||||
FilenameUtils.getExtension(symbolFileInfo.getName()).toLowerCase();
|
||||
String fileExtension = FilenameUtils.getExtension(file.getName()).toLowerCase();
|
||||
return fileExtension.equals(symbolFilenameExtension) ||
|
||||
fileExtension.equals(makeCompressedExtension(symbolFilenameExtension));
|
||||
}
|
||||
|
||||
static boolean hasSymbolFileInfoMatch(SymbolFileInfo symbolFileInfo,
|
||||
SymbolFileInfo otherSymbolFileInfo, Set<FindOption> options) {
|
||||
boolean idMatches =
|
||||
symbolFileInfo.getUniqueName().equalsIgnoreCase(otherSymbolFileInfo.getUniqueName());
|
||||
boolean ageMatches = symbolFileInfo.getIdentifiers()
|
||||
.getAge() == otherSymbolFileInfo.getIdentifiers().getAge();
|
||||
|
||||
if (!options.contains(FindOption.ANY_ID)) {
|
||||
return idMatches && (ageMatches || options.contains(FindOption.ANY_AGE));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A Pdb symbol server / symbol store, similar to the {@link LocalSymbolStore},
|
||||
* but limited to searching just the single directory that the original executable is located in.
|
||||
* <p>
|
||||
* Matches symbol files that have a similar name to the requested symbol file (but the identifier
|
||||
* info - guid/id & age must still match as per the find options specified).
|
||||
*
|
||||
*/
|
||||
public class SameDirSymbolStore implements SymbolStore {
|
||||
|
||||
/**
|
||||
* Descriptive string
|
||||
*/
|
||||
public static String PROGRAMS_IMPORT_LOCATION_DESCRIPTION_STR = "Program's Import Location";
|
||||
|
||||
/**
|
||||
* Factory helper, indicates if the specified location is the special
|
||||
* magic string that indicates the location is the "same dir" symbol store.
|
||||
*
|
||||
* @param locationString Symbol server location string
|
||||
* @return boolean true if the location string is the special magic "same dir" string (".")
|
||||
*/
|
||||
public static boolean isSameDirLocation(String locationString) {
|
||||
return ".".equals(locationString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reuse / abuse the {@link SameDirSymbolStore} to be the container/wrapper for an already known
|
||||
* symbol file. Useful to wrap a file that was picked by the user in an
|
||||
* {@link SymbolFileLocation}.
|
||||
*
|
||||
* @param symbolFile symbol file
|
||||
* @param symbolFileInfo symbol file information
|
||||
* @return a new {@link SymbolFileLocation} with a {@link SameDirSymbolStore} parent
|
||||
*/
|
||||
public static SymbolFileLocation createManuallySelectedSymbolFileLocation(File symbolFile,
|
||||
SymbolFileInfo symbolFileInfo) {
|
||||
SameDirSymbolStore samedirSymbolStore = new SameDirSymbolStore(symbolFile.getParentFile());
|
||||
SymbolFileLocation symbolFileLocation =
|
||||
new SymbolFileLocation(symbolFile.getName(), samedirSymbolStore, symbolFileInfo);
|
||||
return symbolFileLocation;
|
||||
}
|
||||
|
||||
private final File rootDir;
|
||||
|
||||
/**
|
||||
* Create a new instance, based on the directory where the program was originally imported from.
|
||||
*
|
||||
* @param rootDir directory path where the program was originally imported from, or null if not
|
||||
* bound to an actual Program
|
||||
*/
|
||||
public SameDirSymbolStore(File rootDir) {
|
||||
this.rootDir = rootDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getAdminDir() {
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile(String path) {
|
||||
return new File(rootDir, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String giveFile(SymbolFileInfo symbolFileInfo, File f, String filename,
|
||||
TaskMonitor monitor) throws IOException {
|
||||
throw new IOException("Unsupported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String putStream(SymbolFileInfo symbolFileInfo, SymbolServerInputStream streamInfo,
|
||||
String filename, TaskMonitor monitor) throws IOException {
|
||||
throw new IOException("Unsupported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return ".";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescriptiveName() {
|
||||
return String.format(PROGRAMS_IMPORT_LOCATION_DESCRIPTION_STR + " - %s",
|
||||
isValid() ? rootDir.getPath() : "unspecified");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(TaskMonitor monitor) {
|
||||
return isValid();
|
||||
}
|
||||
|
||||
private boolean isValid() {
|
||||
return rootDir != null && rootDir.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String filename, TaskMonitor monitor) {
|
||||
return isValid() && getFile(filename).isFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SymbolFileLocation> find(SymbolFileInfo fileInfo, Set<FindOption> findOptions,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
List<SymbolFileLocation> results = new ArrayList<>();
|
||||
|
||||
if (isValid()) {
|
||||
LocalSymbolStore.searchLevel0(rootDir, this, fileInfo, findOptions, results, monitor);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolServerInputStream getFileStream(String filename, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
if (!isValid(monitor)) {
|
||||
throw new IOException("Unknown rootdir");
|
||||
}
|
||||
File file = getFile(filename);
|
||||
return new SymbolServerInputStream(new FileInputStream(file), file.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileLocation(String filename) {
|
||||
return getFile(filename).getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("SameDirSymbolStore: [ dir: %s ]", rootDir);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import ghidra.app.util.bin.format.pdb.PdbParserConstants;
|
||||
import ghidra.app.util.bin.format.pdb2.pdbreader.PdbIdentifiers;
|
||||
import ghidra.app.util.datatype.microsoft.GUID;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import pdb.PdbUtils;
|
||||
|
||||
/**
|
||||
* Information about a pdb symbol file: its filename and its
|
||||
* {@link PdbIdentifiers pdb guid/id fingerprints}
|
||||
*
|
||||
*/
|
||||
public class SymbolFileInfo {
|
||||
private static final int MIN_SIG_HEX_STR_LEN = 8;
|
||||
private static final int GUID_HEX_STR_LEN = 32;
|
||||
|
||||
/**
|
||||
* Create a SymbolFileInfo instance that represents an unknown / bad
|
||||
* file.
|
||||
*
|
||||
* @param path path string to use
|
||||
* @return new SymbolFileInfo with a PdbIdentifier with bogus / default values
|
||||
*/
|
||||
public static SymbolFileInfo unknown(String path) {
|
||||
return new SymbolFileInfo(path, new PdbIdentifiers(0, 0, 0, null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a SymbolFileInfo instance from the metadata found in a program
|
||||
*
|
||||
* @param metadata Map of String-to-String values taken from a program
|
||||
* @return new SymbolFileInfo instance, or null if no Pdb info found
|
||||
*/
|
||||
public static SymbolFileInfo fromMetadata(Map<String, String> metadata) {
|
||||
try {
|
||||
int sig =
|
||||
Integer.parseUnsignedInt(
|
||||
metadata.getOrDefault(PdbParserConstants.PDB_SIGNATURE, "0"), 16);
|
||||
String guidString = metadata.getOrDefault(PdbParserConstants.PDB_GUID, "");
|
||||
GUID guid = (guidString != null && !guidString.isBlank()) ? new GUID(guidString) : null;
|
||||
int age = Integer
|
||||
.parseUnsignedInt(metadata.getOrDefault(PdbParserConstants.PDB_AGE, "0"), 16);
|
||||
String path = metadata.getOrDefault(PdbParserConstants.PDB_FILE, "<unknown>");
|
||||
|
||||
PdbIdentifiers pdbIdentifiers = new PdbIdentifiers(0, sig, age, guid, null);
|
||||
|
||||
return new SymbolFileInfo(path, pdbIdentifiers);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SymbolFileInfo} instance using information scraped from a pdb symbol
|
||||
* server subdir path.
|
||||
*
|
||||
* @param path name of the pdb file
|
||||
* @param uniqueSubdir string that is a combo of 32_hexchar_GUID + age or
|
||||
* 8_hexchar_signature + age
|
||||
* @return new {@link SymbolFileInfo} instance, or null if invalid info in path
|
||||
* or subdir names
|
||||
*/
|
||||
public static SymbolFileInfo fromSubdirectoryPath(String path, String uniqueSubdir) {
|
||||
try {
|
||||
if (MIN_SIG_HEX_STR_LEN < uniqueSubdir.length() &&
|
||||
uniqueSubdir.length() < GUID_HEX_STR_LEN) {
|
||||
int sig = Integer.parseUnsignedInt(uniqueSubdir.substring(0, 8), 16);
|
||||
int age = Integer.parseUnsignedInt(uniqueSubdir.substring(8), 16);
|
||||
|
||||
return new SymbolFileInfo(path, new PdbIdentifiers(0, sig, age, null, null));
|
||||
}
|
||||
else if (uniqueSubdir.length() > GUID_HEX_STR_LEN) {
|
||||
String guidString = uniqueSubdir.substring(0, GUID_HEX_STR_LEN);
|
||||
GUID guid = new GUID(guidString);
|
||||
|
||||
int age = Integer.parseUnsignedInt(uniqueSubdir.substring(GUID_HEX_STR_LEN), 16);
|
||||
|
||||
return new SymbolFileInfo(path, new PdbIdentifiers(0, 0, age, guid, null));
|
||||
}
|
||||
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance using the specified path and guid/id string and age.
|
||||
*
|
||||
* @param path String pdb path filename
|
||||
* @param uid String GUID or signature id
|
||||
* @param age int value
|
||||
* @return new {@link SymbolFileInfo} instance made of specified path and identity info,
|
||||
* or null if bad GUID / signature id string
|
||||
*/
|
||||
public static SymbolFileInfo fromValues(String path, String uid, int age) {
|
||||
try {
|
||||
GUID guid = new GUID(uid);
|
||||
return new SymbolFileInfo(path, new PdbIdentifiers(0, 0, age, guid, null));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// ignore, try older codeview
|
||||
}
|
||||
try {
|
||||
int sig = Integer.parseUnsignedInt(uid, 16);
|
||||
return new SymbolFileInfo(path, new PdbIdentifiers(0, sig, age, null, null));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// fail
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance using the specified path and {@link PdbIdentifiers}.
|
||||
*
|
||||
* @param path String pdb path filename
|
||||
* @param pdbIdent {@link PdbIdentifiers}
|
||||
* @return new {@link SymbolFileInfo} instance made of specified path and ident info
|
||||
*/
|
||||
public static SymbolFileInfo fromPdbIdentifiers(String path, PdbIdentifiers pdbIdent) {
|
||||
return new SymbolFileInfo(path, pdbIdent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance using the information found inside the specified file.
|
||||
* <p>
|
||||
* The file will be opened and parsed to determine its GUID/ID and age.
|
||||
*
|
||||
* @param pdbFile pdb file to create a SymbolFileInfo for
|
||||
* @param monitor {@link TaskMonitor} for progress and cancel
|
||||
* @return new {@link SymbolFileInfo} instance or null if file is not a valid pdb or pdb.xml
|
||||
* file
|
||||
*/
|
||||
public static SymbolFileInfo fromFile(File pdbFile, TaskMonitor monitor) {
|
||||
PdbIdentifiers pdbIdentifiers = PdbUtils.getPdbIdentifiers(pdbFile, monitor);
|
||||
return (pdbIdentifiers != null) ? new SymbolFileInfo(pdbFile.getName(), pdbIdentifiers)
|
||||
: null;
|
||||
}
|
||||
|
||||
private final PdbIdentifiers pdbIdentifiers;
|
||||
private final String pdbPath;
|
||||
|
||||
private SymbolFileInfo(String pdbPath, PdbIdentifiers pdbIdentifiers) {
|
||||
this.pdbPath = pdbPath;
|
||||
this.pdbIdentifiers = pdbIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PdbIdentifiers} of this instance.
|
||||
*
|
||||
* @return {@link PdbIdentifiers} of this instance
|
||||
*/
|
||||
public PdbIdentifiers getIdentifiers() {
|
||||
return pdbIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the pdb file, derived from the {@link #getPath() path} value.
|
||||
*
|
||||
* @return String name of the pdb file
|
||||
*/
|
||||
public String getName() {
|
||||
return FilenameUtils.getName(pdbPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* The 'path' of the pdb file, which contains the full path and filename recovered from the
|
||||
* original binary's debug data. Typically, this is just a plain name string without any
|
||||
* path info.
|
||||
*
|
||||
* @return original pdb path string recovered from binary's debug data
|
||||
*/
|
||||
public String getPath() {
|
||||
return pdbPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* A string that represents the unique fingerprint of a Pdb file. Does not
|
||||
* include the age.
|
||||
*
|
||||
* @return either GUID str or signature hexstring
|
||||
*/
|
||||
public String getUniqueName() {
|
||||
return (pdbIdentifiers.getGuid() != null)
|
||||
? pdbIdentifiers.getGuid().toString().replace("-", "").toUpperCase()
|
||||
: String.format("%08X", pdbIdentifiers.getSignature());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string that is a combination of the GUID/ID and the age, in a format
|
||||
* used by symbol servers to create subdirectories in their directory structure.
|
||||
*
|
||||
* @return String combination of GUID/ID and age, e.g. "112233441"
|
||||
*/
|
||||
public String getUniqueDirName() {
|
||||
return getUniqueName() + Integer.toUnsignedString(pdbIdentifiers.getAge(), 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this SymbolFileInfo instance exactly matches the {@link PdbIdentifiers}
|
||||
* info of the other instance.
|
||||
*
|
||||
* @param other {@link SymbolFileInfo} to compare
|
||||
* @return boolean true if exact match of {@link PdbIdentifiers} info
|
||||
*/
|
||||
public boolean isExactMatch(SymbolFileInfo other) {
|
||||
return getUniqueName().equalsIgnoreCase(other.getUniqueName()) &&
|
||||
pdbIdentifiers.getAge() == other.getIdentifiers().getAge();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of this instance.
|
||||
*
|
||||
* @return String description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return getName() + ", " + getIdentifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("SymbolFileInfo: [ pdb: %s, uid: %s]", getName(),
|
||||
getIdentifiers().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(pdbIdentifiers, pdbPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SymbolFileInfo other = (SymbolFileInfo) obj;
|
||||
return Objects.equals(pdbIdentifiers, other.pdbIdentifiers) &&
|
||||
Objects.equals(pdbPath, other.pdbPath);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a symbol file on a {@link SymbolServer} or an associated file.
|
||||
*/
|
||||
public class SymbolFileLocation {
|
||||
private final SymbolFileInfo fileInfo;
|
||||
private final String path;
|
||||
private final SymbolServer symbolServer;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param path raw path to file (relative to the {@link SymbolServer})
|
||||
* @param symbolServer {@link SymbolServer} the file resides on
|
||||
* @param fileInfo the {@link SymbolFileInfo pdb file} that this file is associated with
|
||||
*/
|
||||
public SymbolFileLocation(String path, SymbolServer symbolServer, SymbolFileInfo fileInfo) {
|
||||
this.path = path;
|
||||
this.symbolServer = symbolServer;
|
||||
this.fileInfo = fileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw path inside the SymbolServer to the file.
|
||||
*
|
||||
* @return raw path inside the SymbolServer to the file
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link SymbolServer} that holds the file.
|
||||
*
|
||||
* @return the {@link SymbolServer} that holds the file
|
||||
*/
|
||||
public SymbolServer getSymbolServer() {
|
||||
return symbolServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link SymbolFileInfo pdb file} that this file is associated with.
|
||||
*
|
||||
* @return the {@link SymbolFileInfo pdb file} that this file is associated with
|
||||
*/
|
||||
public SymbolFileInfo getFileInfo() {
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this file is an 'exact match' for the
|
||||
* specified {@link SymbolFileInfo other pdb file}.
|
||||
*
|
||||
* @param otherSymbolFileInfo the other pdb file's info
|
||||
* @return boolean true if exact match (GUID & age match), false if not an exact match
|
||||
*/
|
||||
public boolean isExactMatch(SymbolFileInfo otherSymbolFileInfo) {
|
||||
return fileInfo.isExactMatch(otherSymbolFileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* The 'absolute' location of this file, including the symbol server's location.
|
||||
*
|
||||
* @return a string representing the 'absolute' location of this file
|
||||
*/
|
||||
public String getLocationStr() {
|
||||
return symbolServer.getFileLocation(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return path + " in " + symbolServer.getName() + " for " + fileInfo.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(fileInfo, path, symbolServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SymbolFileLocation other = (SymbolFileLocation) obj;
|
||||
return Objects.equals(fileInfo, other.fileInfo) && Objects.equals(path, other.path) &&
|
||||
symbolServer == other.symbolServer;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Represents the common functionality of different types of symbol servers: querying for
|
||||
* files containing symbol information and getting those files.
|
||||
*
|
||||
*/
|
||||
public interface SymbolServer {
|
||||
|
||||
/**
|
||||
* Name of the symbol server, suitable to use as the identity of this instance,
|
||||
* and which will allow the SymbolServerInstanceCreatorRegistry to recreate an instance.
|
||||
*
|
||||
* @return string name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Descriptive name of the symbol server, used in UI lists, etc.
|
||||
*
|
||||
* @return string descriptive name
|
||||
*/
|
||||
default String getDescriptiveName() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the symbol server is valid and can be queried.
|
||||
* @param monitor {@link TaskMonitor}
|
||||
*
|
||||
* @return boolean true if symbol server is working
|
||||
*/
|
||||
boolean isValid(TaskMonitor monitor);
|
||||
|
||||
/**
|
||||
* Returns true if the raw filename exists in the symbol server.
|
||||
*
|
||||
* @param filename raw path filename string
|
||||
* @param monitor {@link TaskMonitor}
|
||||
* @return boolean true if file exists
|
||||
*/
|
||||
boolean exists(String filename, TaskMonitor monitor);
|
||||
|
||||
/**
|
||||
* Searches for a symbol file on the server.
|
||||
* <p>
|
||||
* HttpSymbolServers only support exact matches, LocalSymbolStores can
|
||||
* possibly have fuzzy matches.
|
||||
*
|
||||
* @param fileInfo {@link SymbolFileInfo} bag of information about the file to search for
|
||||
* @param findOptions set of {@link FindOption} to control the search.
|
||||
* See {@link FindOption#NO_OPTIONS} or
|
||||
* {@link FindOption#of(FindOption...) FindOptions.of(option1, option2...)}
|
||||
* @param monitor {@link TaskMonitor}
|
||||
* @return list of {@link SymbolFileLocation location information instances} about matches
|
||||
*/
|
||||
List<SymbolFileLocation> find(SymbolFileInfo fileInfo, Set<FindOption> findOptions,
|
||||
TaskMonitor monitor);
|
||||
|
||||
/**
|
||||
* Returns a wrapped InputStream for the specified raw path filename.
|
||||
*
|
||||
* @param filename raw path filename
|
||||
* @param monitor {@link TaskMonitor}
|
||||
* @return {@link SymbolServerInputStream} wrapped {@link InputStream}, never null
|
||||
* @throws IOException if error or not found
|
||||
*/
|
||||
SymbolServerInputStream getFileStream(String filename, TaskMonitor monitor) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a location description string of a specific file contained in this symbol server.
|
||||
* <p>
|
||||
*
|
||||
* @param filename raw path and name of a file in this server
|
||||
* (typically from {@link SymbolFileLocation#getPath()}
|
||||
* @return a descriptive string with the 'absolute' location of this file
|
||||
*/
|
||||
String getFileLocation(String filename);
|
||||
|
||||
/**
|
||||
* Returns true if this {@link SymbolServer} is 'local', meaning
|
||||
* it can be searched without security issues / warning the user.
|
||||
*
|
||||
* @return boolean true if this symbolserver is 'local', false if remote
|
||||
*/
|
||||
boolean isLocal();
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* A {@link InputStream} wrapper returned from a {@link SymbolServer}
|
||||
* that also contains the expected length of the stream.
|
||||
*/
|
||||
public class SymbolServerInputStream implements Closeable {
|
||||
private final InputStream inputStream;
|
||||
private final long expectedLength;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*
|
||||
* @param inputStream {@link InputStream} to wrap
|
||||
* @param expectedLength the expected length of the input stream
|
||||
*/
|
||||
public SymbolServerInputStream(InputStream inputStream, long expectedLength) {
|
||||
this.inputStream = inputStream;
|
||||
this.expectedLength = expectedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped input stream
|
||||
* @return the wrapped input stream
|
||||
*/
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expected length of the input stream
|
||||
*
|
||||
* @return expected length of the input stream
|
||||
*/
|
||||
public long getExpectedLength() {
|
||||
return expectedLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Context for the {@link SymbolServerInstanceCreatorRegistry} when creating new
|
||||
* {@link SymbolServer} instances.
|
||||
* <p>
|
||||
* This allows the method that is creating a new SymbolServer to know the location the
|
||||
* Ghidra program was imported from, as well as to reach back to the registry itself and
|
||||
* use it to create other SymbolServer instances (if necessary).
|
||||
* <p>
|
||||
* Created via {@link SymbolServerInstanceCreatorRegistry#getContext()} or
|
||||
* {@link SymbolServerInstanceCreatorRegistry#getContext(ghidra.program.model.listing.Program)}
|
||||
*/
|
||||
public class SymbolServerInstanceCreatorContext {
|
||||
private final File rootDir;
|
||||
private final SymbolServerInstanceCreatorRegistry symbolServerInstanceCreatorRegistry;
|
||||
|
||||
SymbolServerInstanceCreatorContext(
|
||||
SymbolServerInstanceCreatorRegistry symbolServerInstanceCreatorRegistry) {
|
||||
this(null, symbolServerInstanceCreatorRegistry);
|
||||
}
|
||||
|
||||
SymbolServerInstanceCreatorContext(File rootDir,
|
||||
SymbolServerInstanceCreatorRegistry symbolServerInstanceCreatorRegistry) {
|
||||
this.rootDir = rootDir;
|
||||
this.symbolServerInstanceCreatorRegistry = symbolServerInstanceCreatorRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link SymbolServerInstanceCreatorRegistry} associated with this context.
|
||||
*
|
||||
* @return the {@link SymbolServerInstanceCreatorRegistry}
|
||||
*/
|
||||
public SymbolServerInstanceCreatorRegistry getSymbolServerInstanceCreatorRegistry() {
|
||||
return symbolServerInstanceCreatorRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* The root directory of the imported binary.
|
||||
*
|
||||
* @return directory of the binary, or null if no associated program
|
||||
*/
|
||||
public File getRootDir() {
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Registry of {@link SymbolServer} instance creators.
|
||||
*/
|
||||
public class SymbolServerInstanceCreatorRegistry {
|
||||
|
||||
private static final SymbolServerInstanceCreatorRegistry instance =
|
||||
new SymbolServerInstanceCreatorRegistry();
|
||||
|
||||
/**
|
||||
* A static singleton pre-configured with the default symbol server implementations.
|
||||
*
|
||||
* @return static singleton {@link SymbolServerInstanceCreatorRegistry} instance.
|
||||
*/
|
||||
public static SymbolServerInstanceCreatorRegistry getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final TreeMap<Integer, SymbolServerInstanceCreatorInfo> symbolServerInstanceCreatorsByPriority =
|
||||
new TreeMap<>();
|
||||
|
||||
private SymbolServerInstanceCreatorRegistry() {
|
||||
registerDefaultSymbolServerInstanceCreators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new SymbolServer implementation so that instances of
|
||||
* it can be created by the user and saved / restored from preferences.
|
||||
*
|
||||
* @param priority relative order of precedence of polling this
|
||||
* implementation's predicate to detect the specific SymbolServer
|
||||
* implementation from a locationString.
|
||||
* @param locationStringMatcher predicate that returns true / false if the specified String is
|
||||
* handled by this SymbolServer implementation
|
||||
* @param symbolServerInstanceCreator a method that creates a SymbolServer
|
||||
* instance based on the specified location string and context
|
||||
*/
|
||||
public void registerSymbolServerInstanceCreator(int priority,
|
||||
Predicate<String> locationStringMatcher,
|
||||
SymbolServerInstanceCreator symbolServerInstanceCreator) {
|
||||
SymbolServerInstanceCreatorInfo symbolServerInstanceCreatorInfo =
|
||||
new SymbolServerInstanceCreatorInfo(locationStringMatcher, symbolServerInstanceCreator);
|
||||
|
||||
symbolServerInstanceCreatorsByPriority.put(priority, symbolServerInstanceCreatorInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of symbol server location strings to a list of SymbolServer instances.
|
||||
*
|
||||
* @param locationStrings list of symbol server location strings
|
||||
* @param symbolServerInstanceCreatorContext a {@link SymbolServerInstanceCreatorContext}
|
||||
* - see {@link #getContext()} or {@link #getContext(Program)}
|
||||
* @return list of {@link SymbolServer}
|
||||
*/
|
||||
public List<SymbolServer> createSymbolServersFromPathList(List<String> locationStrings,
|
||||
SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext) {
|
||||
List<SymbolServer> result = new ArrayList<>();
|
||||
for (String locationString : locationStrings) {
|
||||
SymbolServer symbolServer =
|
||||
newSymbolServer(locationString, symbolServerInstanceCreatorContext);
|
||||
if (symbolServer != null) {
|
||||
result.add(symbolServer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SymbolServer instance, using the registered SymbolServer types.
|
||||
*
|
||||
* @param symbolServerLocationString SymbolServer location - see {@link SymbolServer#getName()}
|
||||
* @param symbolServerInstanceCreatorContext a {@link SymbolServerInstanceCreatorContext}
|
||||
* - see {@link #getContext()}
|
||||
* or {@link #getContext(Program)}
|
||||
* @return new SymbolServer instance, or null if bad location string
|
||||
*/
|
||||
public SymbolServer newSymbolServer(String symbolServerLocationString,
|
||||
SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext) {
|
||||
return newSymbolServer(symbolServerLocationString, symbolServerInstanceCreatorContext,
|
||||
SymbolServer.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SymbolServer instance, using the registered SymbolServer types.
|
||||
*
|
||||
* @param symbolServerLocationString SymbolServer location - see {@link SymbolServer#getName()}
|
||||
* @param symbolServerInstanceCreatorContext a {@link SymbolServerInstanceCreatorContext}
|
||||
* - see {@link #getContext()}
|
||||
* @param expectedSymbolServerClass expected class of the new symbol server being created
|
||||
* @return new SymbolServer instance, or null if bad location string
|
||||
*/
|
||||
public <T extends SymbolServer> T newSymbolServer(String symbolServerLocationString,
|
||||
SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext,
|
||||
Class<T> expectedSymbolServerClass) {
|
||||
if (symbolServerLocationString == null || symbolServerLocationString.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
for (SymbolServerInstanceCreatorInfo symbolServerInstanceCreatorInfo : symbolServerInstanceCreatorsByPriority
|
||||
.values()) {
|
||||
if (symbolServerInstanceCreatorInfo.getLocationStringMatcher()
|
||||
.test(symbolServerLocationString)) {
|
||||
SymbolServer result =
|
||||
symbolServerInstanceCreatorInfo.getSymbolServerInstanceCreator()
|
||||
.createSymbolServerFromLocationString(
|
||||
symbolServerLocationString, symbolServerInstanceCreatorContext);
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
if (!expectedSymbolServerClass.isInstance(result)) {
|
||||
Msg.debug(this, "SymbolServer location unexpected class type. Wanted " +
|
||||
expectedSymbolServerClass.getName() + ", got " +
|
||||
result.getClass().getName());
|
||||
return null;
|
||||
}
|
||||
return expectedSymbolServerClass.cast(result);
|
||||
}
|
||||
}
|
||||
Msg.debug(SymbolServerService.class,
|
||||
"Symbol server location [" + symbolServerLocationString + "] not valid, skipping.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SymbolServerInstanceCreatorContext} that is not bound to a Program.
|
||||
*
|
||||
* @return new {@link SymbolServerInstanceCreatorContext}
|
||||
*/
|
||||
public SymbolServerInstanceCreatorContext getContext() {
|
||||
return new SymbolServerInstanceCreatorContext(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SymbolServerInstanceCreatorContext} that is bound to a Program.
|
||||
*
|
||||
* @param program Ghidra program
|
||||
* @return new {@link SymbolServerInstanceCreatorContext}
|
||||
*/
|
||||
public SymbolServerInstanceCreatorContext getContext(Program program) {
|
||||
File exeLocation = new File(FilenameUtils.getFullPath(program.getExecutablePath()));
|
||||
return new SymbolServerInstanceCreatorContext(exeLocation, this);
|
||||
}
|
||||
|
||||
private void registerDefaultSymbolServerInstanceCreators() {
|
||||
registerSymbolServerInstanceCreator(0, DisabledSymbolServer::isDisabledSymbolServerLocation,
|
||||
DisabledSymbolServer::createInstance);
|
||||
registerSymbolServerInstanceCreator(100, HttpSymbolServer::isHttpSymbolServerLocation,
|
||||
(loc, context) -> new HttpSymbolServer(URI.create(loc)));
|
||||
registerSymbolServerInstanceCreator(200, SameDirSymbolStore::isSameDirLocation,
|
||||
(loc, context) -> new SameDirSymbolStore(context.getRootDir()));
|
||||
registerSymbolServerInstanceCreator(300, LocalSymbolStore::isLocalSymbolStoreLocation,
|
||||
(loc, context) -> new LocalSymbolStore(new File(loc)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface that creates a new {@link SymbolServer} instance using a
|
||||
* location string and a context instance.
|
||||
* <p>
|
||||
* See {@link #createSymbolServerFromLocationString(String, SymbolServerInstanceCreatorContext)}
|
||||
*/
|
||||
public interface SymbolServerInstanceCreator {
|
||||
/**
|
||||
* Creates a new {@link SymbolServer} instance using the specified location string
|
||||
* and the context available in the symbolServerInstanceCreatorContext.
|
||||
*
|
||||
* @param symbolServerLocationString location string
|
||||
* @param symbolServerInstanceCreatorContext context
|
||||
* @return new {@link SymbolServer} instance, null if error
|
||||
*/
|
||||
SymbolServer createSymbolServerFromLocationString(String symbolServerLocationString,
|
||||
SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext);
|
||||
}
|
||||
|
||||
private static class SymbolServerInstanceCreatorInfo {
|
||||
private Predicate<String> locationStringMatcher;
|
||||
private SymbolServerInstanceCreator symbolServerInstanceCreator;
|
||||
|
||||
SymbolServerInstanceCreatorInfo(Predicate<String> locationStringMatcher,
|
||||
SymbolServerInstanceCreator symbolServerInstanceCreator) {
|
||||
this.locationStringMatcher = locationStringMatcher;
|
||||
this.symbolServerInstanceCreator = symbolServerInstanceCreator;
|
||||
}
|
||||
|
||||
Predicate<String> getLocationStringMatcher() {
|
||||
return locationStringMatcher;
|
||||
}
|
||||
|
||||
SymbolServerInstanceCreator getSymbolServerInstanceCreator() {
|
||||
return symbolServerInstanceCreator;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,288 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import pdb.PdbUtils;
|
||||
|
||||
/**
|
||||
* A (lowercase-'S') service that searches for and fetches symbol files
|
||||
* from a set of local and remote {@link SymbolServer symbolservers}. (not to be
|
||||
* confused with a Plugin service)
|
||||
* <p>
|
||||
* Instances of this class are meant to be easily created when needed
|
||||
* and just as easily thrown away when not used or when the search
|
||||
* path configuration changes.
|
||||
* <p>
|
||||
* A SymbolServerService instance requires a {@link SymbolStore} and
|
||||
* optionally a list of {@link SymbolServer}s.
|
||||
*/
|
||||
public class SymbolServerService {
|
||||
|
||||
private SymbolStore symbolStore; // also the first element of the symbolServers list
|
||||
private List<SymbolServer> symbolServers;
|
||||
|
||||
/**
|
||||
* Creates a new SymbolServerService instance.
|
||||
* <p>
|
||||
* @param symbolStore a {@link SymbolStore} - where all
|
||||
* remote files are placed when downloaded. Also treated as a SymbolServer
|
||||
* and searched first
|
||||
* @param symbolServers a list of {@link SymbolServer symbol servers} - searched in order
|
||||
*/
|
||||
public SymbolServerService(SymbolStore symbolStore, List<SymbolServer> symbolServers) {
|
||||
this.symbolStore = symbolStore;
|
||||
this.symbolServers = new ArrayList<>();
|
||||
this.symbolServers.add(symbolStore);
|
||||
this.symbolServers.addAll(symbolServers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this SymbolServerService is fully valid.
|
||||
* Will be false if the symbol storage location isn't a {@link LocalSymbolStore}.
|
||||
*
|
||||
* @return boolean true if this instance is valid, false if not valid
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return symbolStore instanceof LocalSymbolStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link SymbolStore}, which is the primary / first location queried and
|
||||
* used to store any symbol files retrieved from a remote symbol server.
|
||||
*
|
||||
* @return the {@link SymbolStore}
|
||||
*/
|
||||
public SymbolStore getSymbolStore() {
|
||||
return symbolStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of {@link SymbolServer}s.
|
||||
*
|
||||
* @return the list of {@link SymbolServer}s
|
||||
*/
|
||||
public List<SymbolServer> getSymbolServers() {
|
||||
return new ArrayList<>(symbolServers.subList(1, symbolServers.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of configured symbol servers that are considered 'remote'.
|
||||
* @return number of remote symbol servers
|
||||
*/
|
||||
public int getRemoteSymbolServerCount() {
|
||||
int remoteSymbolServerCount = (int) getSymbolServers()
|
||||
.stream()
|
||||
.filter(ss -> !ss.isLocal())
|
||||
.count();
|
||||
|
||||
return remoteSymbolServerCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches all {@link SymbolServer symbol servers} for a matching pdb symbol file.
|
||||
*
|
||||
* @param symbolFileInfo {@link SymbolFileInfo} bag of information
|
||||
* about the file to search for
|
||||
* @param monitor {@link TaskMonitor} to update with search progress and to
|
||||
* allow the user to cancel the operation
|
||||
* @return a list of {@link SymbolFileLocation} instances
|
||||
* @throws CancelledException if cancelled
|
||||
*/
|
||||
public List<SymbolFileLocation> find(SymbolFileInfo symbolFileInfo, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
return find(symbolFileInfo, FindOption.NO_OPTIONS, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches all {@link SymbolServer symbol servers} for a matching pdb symbol file.
|
||||
* <p>
|
||||
* Returns a list of matches.
|
||||
* <p>
|
||||
* Use {@link SymbolFileLocation#isExactMatch(SymbolFileInfo)} to test elements in the
|
||||
* result list for exactness.
|
||||
* <p>
|
||||
*
|
||||
* @param symbolFileInfo Pdb file info to search for
|
||||
* @param findOptions set of {@link FindOption} to control the search.
|
||||
* See {@link FindOption#NO_OPTIONS} or
|
||||
* {@link FindOption#of(FindOption...) FindOptions.of(option1, option2...)}
|
||||
* @param monitor {@link TaskMonitor}
|
||||
* @return list of {@link SymbolFileLocation}s
|
||||
* @throws CancelledException if operation canceled by user
|
||||
*/
|
||||
public List<SymbolFileLocation> find(SymbolFileInfo symbolFileInfo,
|
||||
Set<FindOption> findOptions, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
List<SymbolFileLocation> allFindResults = new ArrayList<>();
|
||||
Set<String> uniqueSymbolFilePaths = new HashSet<>();
|
||||
|
||||
for_each_symbol_server_loop: for (SymbolServer symbolServer : symbolServers) {
|
||||
monitor.checkCanceled();
|
||||
if (!symbolServer.isLocal() && !findOptions.contains(FindOption.ALLOW_REMOTE)) {
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": skipping non-local symbol server " +
|
||||
symbolServer.getDescriptiveName());
|
||||
continue;
|
||||
}
|
||||
|
||||
Msg.debug(this, logPrefix() + ": querying " + symbolServer.getDescriptiveName() +
|
||||
" for " + symbolFileInfo.getDescription());
|
||||
|
||||
List<SymbolFileLocation> symbolServerFindResults =
|
||||
symbolServer.find(symbolFileInfo, findOptions, monitor);
|
||||
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": got " + symbolServerFindResults.size() + " results from " +
|
||||
symbolServer.getDescriptiveName());
|
||||
|
||||
// only add unique file locations
|
||||
for (SymbolFileLocation symbolFileLocation : symbolServerFindResults) {
|
||||
if (uniqueSymbolFilePaths.add(symbolFileLocation.getLocationStr())) {
|
||||
allFindResults.add(symbolFileLocation);
|
||||
if (findOptions.contains(FindOption.ONLY_FIRST_RESULT)) {
|
||||
break for_each_symbol_server_loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Msg.debug(this, logPrefix() + ": found " + allFindResults.size() + " matches");
|
||||
|
||||
return allFindResults;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local file path of the symbol file specified by symbolFileLocation.
|
||||
*
|
||||
* @param symbolFileLocation {@link SymbolFileLocation}, returned
|
||||
* by {@link #find(SymbolFileInfo, Set, TaskMonitor) find()}
|
||||
* @param monitor {@link TaskMonitor}
|
||||
* @return {@link File} path to the local pdb file, never null
|
||||
* @throws CancelledException if user cancels operation
|
||||
* @throws IOException if error or problem getting file
|
||||
*/
|
||||
public File getSymbolFile(SymbolFileLocation symbolFileLocation, TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": getting symbol file: " + symbolFileLocation.getLocationStr());
|
||||
|
||||
SymbolFileLocation localSymbolFileLocation =
|
||||
ensureLocalUncompressedFile(symbolFileLocation, monitor);
|
||||
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": local file now: " + localSymbolFileLocation.getLocationStr());
|
||||
|
||||
SymbolStore symbolStore = (SymbolStore) localSymbolFileLocation.getSymbolServer();
|
||||
|
||||
return symbolStore.getFile(localSymbolFileLocation.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a possibly remote {@link SymbolFileLocation} to a location that is local and
|
||||
* uncompressed.
|
||||
*
|
||||
* @param symbolFileLocation possibly remote {@link SymbolFileLocation}
|
||||
* @param monitor {@link TaskMonitor} to display progress and allow canceling
|
||||
* @return {@link SymbolFileLocation} that is local (possibly the same instance if already
|
||||
* local)
|
||||
* @throws CancelledException if canceled
|
||||
* @throws IOException if error
|
||||
*/
|
||||
public SymbolFileLocation getLocalSymbolFileLocation(SymbolFileLocation symbolFileLocation,
|
||||
TaskMonitor monitor) throws CancelledException, IOException {
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": getting symbol file: " + symbolFileLocation.getLocationStr());
|
||||
|
||||
SymbolFileLocation localSymbolFileLocation =
|
||||
ensureLocalUncompressedFile(symbolFileLocation, monitor);
|
||||
|
||||
return localSymbolFileLocation;
|
||||
}
|
||||
|
||||
private SymbolFileLocation ensureLocalUncompressedFile(SymbolFileLocation symbolFileLocation,
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
if (!(symbolFileLocation.getSymbolServer() instanceof SymbolStore)) {
|
||||
Msg.debug(this, logPrefix() + ": copying file " + symbolFileLocation.getLocationStr() +
|
||||
" from remote to local " + symbolStore.getName());
|
||||
|
||||
// copy from remote store to our main local symbol store
|
||||
String remoteFilename = FilenameUtils.getName(symbolFileLocation.getPath());
|
||||
try (SymbolServerInputStream symbolServerInputStream =
|
||||
symbolFileLocation.getSymbolServer()
|
||||
.getFileStream(symbolFileLocation.getPath(), monitor)) {
|
||||
String newPath =
|
||||
symbolStore.putStream(symbolFileLocation.getFileInfo(), symbolServerInputStream,
|
||||
remoteFilename, monitor);
|
||||
symbolFileLocation =
|
||||
new SymbolFileLocation(newPath, symbolStore, symbolFileLocation.getFileInfo());
|
||||
}
|
||||
}
|
||||
|
||||
// symbolFileLocation now must be on a SymbolStore, so safe to cast
|
||||
SymbolStore localSymbolStore = (SymbolStore) symbolFileLocation.getSymbolServer();
|
||||
|
||||
if (SymbolStore.isCompressedFilename(symbolFileLocation.getPath())) {
|
||||
File cabFile = localSymbolStore.getFile(symbolFileLocation.getPath());
|
||||
File temporaryExtractFile = new File(symbolStore.getAdminDir(),
|
||||
"ghidra_cab_extract_tmp_" + System.currentTimeMillis());
|
||||
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": decompressing file " + symbolFileLocation.getLocationStr());
|
||||
|
||||
String originalName =
|
||||
PdbUtils.extractSingletonCabToFile(cabFile, temporaryExtractFile, monitor);
|
||||
String uncompressedPath =
|
||||
symbolStore.giveFile(symbolFileLocation.getFileInfo(), temporaryExtractFile,
|
||||
originalName, monitor);
|
||||
|
||||
symbolFileLocation = new SymbolFileLocation(uncompressedPath, symbolStore,
|
||||
symbolFileLocation.getFileInfo());
|
||||
|
||||
Msg.debug(this,
|
||||
logPrefix() + ": new decompressed file " + symbolFileLocation.getLocationStr());
|
||||
}
|
||||
|
||||
return symbolFileLocation;
|
||||
}
|
||||
|
||||
private String logPrefix() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"SymbolServerService:\n\tsymbolStore: %s,\n\tsymbolServers:\n\t\t%s\n",
|
||||
symbolStore.toString(),
|
||||
symbolServers.subList(1, symbolServers.size())
|
||||
.stream()
|
||||
.map(SymbolServer::toString)
|
||||
.collect(Collectors.joining("\n\t\t")));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A local writable {@link SymbolServer}.
|
||||
*/
|
||||
public interface SymbolStore extends SymbolServer {
|
||||
|
||||
/**
|
||||
* Returns the 'admin' directory of this SymbolStore, which allows files created here
|
||||
* to be efficiently {@link #giveFile(SymbolFileInfo, File, String, TaskMonitor) given}
|
||||
* to the store.
|
||||
* <p>
|
||||
*
|
||||
* @return directory
|
||||
*/
|
||||
File getAdminDir();
|
||||
|
||||
/**
|
||||
* Returns an absolute {@link File} instance based on the specified relative path
|
||||
* to a file inside the symbol store.
|
||||
* <p>
|
||||
*
|
||||
* @param path relative local path
|
||||
* @return absolute {@link File} based on the specified relative path
|
||||
*/
|
||||
File getFile(String path);
|
||||
|
||||
/**
|
||||
* Offers the specified file to the SymbolStore. The file should be
|
||||
* located in the admin directory of the SymbolStore to ensure no problems
|
||||
* with ingesting the file.
|
||||
* <p>
|
||||
* The file will be 'consumed' by this SymbolStore, and the caller's
|
||||
* responsibility to the file ends.
|
||||
*
|
||||
* @param symbolFileInfo {@link SymbolFileInfo} bag of information about the file
|
||||
* @param file {@link File} to ingest
|
||||
* @param filename real name of the ingested file
|
||||
* @param monitor {@link TaskMonitor}
|
||||
* @return relative raw local path to the newly ingested file
|
||||
* @throws IOException if error
|
||||
*/
|
||||
String giveFile(SymbolFileInfo symbolFileInfo, File file, String filename, TaskMonitor monitor)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Places the contents of the stream into a file in this SymbolStore.
|
||||
* <p>
|
||||
*
|
||||
* @param symbolFileInfo {@link SymbolFileInfo} bag of information about the file
|
||||
* @param symbolServerInputStream the stream to ingest
|
||||
* @param filename real name of the ingested file
|
||||
* @param monitor {@link TaskMonitor}
|
||||
* @return relative raw local path to the newly ingested file
|
||||
* @throws IOException if error
|
||||
*/
|
||||
String putStream(SymbolFileInfo symbolFileInfo, SymbolServerInputStream symbolServerInputStream,
|
||||
String filename, TaskMonitor monitor) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the specified filename indicates that the file is a compressed
|
||||
* cab file.
|
||||
*
|
||||
* @param filename filename
|
||||
* @return boolean true if filename indicates that the file is compressed
|
||||
*/
|
||||
public static boolean isCompressedFilename(String filename) {
|
||||
return filename.endsWith("_");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver.ui;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.OptionDialog;
|
||||
import pdb.symbolserver.SymbolServerInstanceCreatorRegistry;
|
||||
import pdb.symbolserver.SymbolServerService;
|
||||
|
||||
/**
|
||||
* Dialog that allows the user to configure the Pdb search locations and symbol directory
|
||||
*/
|
||||
public class ConfigPdbDialog extends DialogComponentProvider {
|
||||
|
||||
public static void showSymbolServerConfig() {
|
||||
ConfigPdbDialog choosePdbDialog = new ConfigPdbDialog();
|
||||
DockingWindowManager.showDialog(choosePdbDialog);
|
||||
}
|
||||
|
||||
private SymbolServerPanel symbolServerConfigPanel;
|
||||
|
||||
public ConfigPdbDialog() {
|
||||
super("Configure Symbol Server Search", true, false, true, false);
|
||||
|
||||
build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
if (symbolServerConfigPanel.isConfigChanged() &&
|
||||
OptionDialog.showYesNoDialog(getComponent(),
|
||||
"Save Configuration",
|
||||
"Symbol server configuration changed. Save?") == OptionDialog.YES_OPTION) {
|
||||
symbolServerConfigPanel.saveConfig();
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
private void build() {
|
||||
symbolServerConfigPanel = new SymbolServerPanel(this::onSymbolServerServiceChange,
|
||||
SymbolServerInstanceCreatorRegistry.getInstance().getContext());
|
||||
|
||||
addButtons();
|
||||
addWorkPanel(symbolServerConfigPanel);
|
||||
setRememberSize(false);
|
||||
okButton.setEnabled(symbolServerConfigPanel.getSymbolServerService() != null);
|
||||
setMinimumSize(400, 250);
|
||||
}
|
||||
|
||||
private void onSymbolServerServiceChange(SymbolServerService newService) {
|
||||
okButton.setEnabled(newService != null);
|
||||
rootPanel.revalidate();
|
||||
}
|
||||
|
||||
private void addButtons() {
|
||||
addOKButton();
|
||||
addCancelButton();
|
||||
setDefaultButton(cancelButton);
|
||||
}
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pdb.symbolserver.ui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.io.File;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.options.editor.ButtonPanelFactory;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.label.GHtmlLabel;
|
||||
import ghidra.util.filechooser.GhidraFileFilter;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
|
||||
/**
|
||||
* Non-public, package-only dialog that prompts the user to enter a path
|
||||
* in a text field (similar to an {@link OptionDialog}) and allows them to click
|
||||
* a "..." browse button to pick the file and/or directory via a
|
||||
* {@link GhidraFileChooser} dialog.
|
||||
*/
|
||||
class FilePromptDialog extends DialogComponentProvider {
|
||||
|
||||
/**
|
||||
* Prompts the user to enter the path to a directory,
|
||||
* or to pick it using a browser dialog.
|
||||
*
|
||||
* @param title the dialog title
|
||||
* @param prompt HTML enabled prompt
|
||||
* @param initialValue initial value to pre-populate the input field with
|
||||
* @return the {@link File} the user entered / picked, or null if canceled
|
||||
*/
|
||||
public static File chooseDirectory(String title, String prompt, File initialValue) {
|
||||
return chooseFile(title, prompt, "Choose", null, initialValue,
|
||||
GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user to entry the path to a file and/or directory,
|
||||
* or to pick it using a browser dialog.
|
||||
* <p>
|
||||
*
|
||||
* @param title the dialog title
|
||||
* @param prompt HTML enabled prompt
|
||||
* @param chooseButtonText text of the choose button in the browser dialog
|
||||
* @param directory the initial directory of the browser dialog
|
||||
* @param initialFileValue the initial value to pre-populate the input field with
|
||||
* @param chooserMode {@link GhidraFileChooserMode} of the browser dialog
|
||||
* @param fileFilters optional {@link GhidraFileFilter filters}
|
||||
* @return the {@link File} the user entered / picked, or null if canceled
|
||||
*/
|
||||
public static File chooseFile(String title, String prompt, String chooseButtonText,
|
||||
File directory, File initialFileValue, GhidraFileChooserMode chooserMode,
|
||||
GhidraFileFilter... fileFilters) {
|
||||
FilePromptDialog filePromptDialog = new FilePromptDialog(title, prompt, chooseButtonText,
|
||||
directory, initialFileValue, chooserMode, fileFilters);
|
||||
DockingWindowManager.showDialog(filePromptDialog);
|
||||
return filePromptDialog.chosenValue;
|
||||
}
|
||||
|
||||
private GhidraFileChooser chooser;
|
||||
private GhidraFileFilter[] fileFilters;
|
||||
private File directory;
|
||||
private File file;
|
||||
private String approveButtonText;
|
||||
private JTextField filePathTextField;
|
||||
private GhidraFileChooserMode chooserMode;
|
||||
private File chosenValue;
|
||||
|
||||
protected FilePromptDialog(String title, String prompt, String approveButtonText,
|
||||
File directory, File file, GhidraFileChooserMode chooserMode,
|
||||
GhidraFileFilter... fileFilters) {
|
||||
super(title, true, false, true, false);
|
||||
|
||||
this.approveButtonText = approveButtonText;
|
||||
this.directory = directory;
|
||||
this.file = file;
|
||||
this.chooserMode = chooserMode;
|
||||
this.fileFilters = fileFilters;
|
||||
setRememberSize(false);
|
||||
|
||||
build(prompt);
|
||||
updateButtonEnablement();
|
||||
}
|
||||
|
||||
private void build(String prompt) {
|
||||
|
||||
GHtmlLabel promptLabel = new GHtmlLabel(prompt);
|
||||
filePathTextField = new JTextField(file != null ? file.getPath() : null, 40);
|
||||
filePathTextField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
updateButtonEnablement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
updateButtonEnablement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
updateButtonEnablement();
|
||||
}
|
||||
});
|
||||
JButton browseButton = ButtonPanelFactory.createButton(ButtonPanelFactory.BROWSE_TYPE);
|
||||
browseButton.addActionListener(e -> browse());
|
||||
|
||||
JPanel textFieldWithButtonPanel = new JPanel(new BorderLayout());
|
||||
textFieldWithButtonPanel.add(filePathTextField, BorderLayout.CENTER);
|
||||
textFieldWithButtonPanel.add(browseButton, BorderLayout.EAST);
|
||||
|
||||
JPanel mainPanel = new JPanel(new PairLayout());
|
||||
mainPanel.add(promptLabel);
|
||||
mainPanel.add(textFieldWithButtonPanel);
|
||||
Dimension size = mainPanel.getPreferredSize();
|
||||
size.width = Math.max(size.width, 500);
|
||||
mainPanel.setPreferredSize(size);
|
||||
mainPanel.setMinimumSize(size);
|
||||
JPanel newMain = new JPanel(new BorderLayout());
|
||||
newMain.add(mainPanel, BorderLayout.CENTER);
|
||||
|
||||
addWorkPanel(newMain);
|
||||
addOKButton();
|
||||
addCancelButton();
|
||||
}
|
||||
|
||||
private void updateButtonEnablement() {
|
||||
okButton.setEnabled(!filePathTextField.getText().isBlank());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
chosenValue = new File(filePathTextField.getText());
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
chosenValue = null;
|
||||
close();
|
||||
}
|
||||
|
||||
private void browse() {
|
||||
initChooser();
|
||||
String filePathText = filePathTextField.getText();
|
||||
filePathText = filePathText.isBlank() && file != null ? file.getPath() : "";
|
||||
if (!filePathText.isBlank()) {
|
||||
chooser.setSelectedFile(new File(filePathText));
|
||||
}
|
||||
File selectedFile = chooser.getSelectedFile();
|
||||
if (selectedFile != null) {
|
||||
filePathTextField.setText(selectedFile.getPath());
|
||||
}
|
||||
filePathTextField.requestFocusInWindow();
|
||||
}
|
||||
|
||||
private void initChooser() {
|
||||
|
||||
if (chooser == null) {
|
||||
chooser = new GhidraFileChooser(rootPanel);
|
||||
for (GhidraFileFilter gff : fileFilters) {
|
||||
chooser.addFileFilter(gff);
|
||||
}
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
chooser.setApproveButtonText(approveButtonText);
|
||||
chooser.setFileSelectionMode(chooserMode);
|
||||
chooser.setTitle(getTitle());
|
||||
|
||||
if (directory != null) {
|
||||
chooser.setCurrentDirectory(directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,942 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver.ui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.event.mouse.GMouseListenerAdapter;
|
||||
import docking.options.editor.ButtonPanelFactory;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.checkbox.GCheckBox;
|
||||
import docking.widgets.combobox.GComboBox;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.label.GIconLabel;
|
||||
import docking.widgets.label.GLabel;
|
||||
import docking.widgets.textfield.HintTextField;
|
||||
import docking.widgets.textfield.IntegerTextField;
|
||||
import ghidra.app.util.bin.format.pdb.PdbParser;
|
||||
import ghidra.app.util.pdb.pdbapplicator.PdbApplicatorControl;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.filechooser.ExtensionFileFilter;
|
||||
import ghidra.util.filechooser.GhidraFileFilter;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
import ghidra.util.task.*;
|
||||
import pdb.PdbPlugin;
|
||||
import pdb.symbolserver.*;
|
||||
import resources.Icons;
|
||||
import resources.ResourceManager;
|
||||
|
||||
/**
|
||||
* A dialog that allows the user to pick or search for a Pdb file for a program.
|
||||
*/
|
||||
public class LoadPdbDialog extends DialogComponentProvider {
|
||||
|
||||
private static final String LAST_PDBFILE_PREFERENCE_KEY = "Pdb.LastFile";
|
||||
static final Icon MATCH_OK_ICON =
|
||||
ResourceManager.loadImage("images/checkmark_green.gif", 16, 16);
|
||||
static final Icon MATCH_BAD_ICON =
|
||||
ResourceManager.loadImage("images/emblem-important.png", 16, 16);
|
||||
public static final GhidraFileFilter PDB_FILES_FILTER =
|
||||
ExtensionFileFilter.forExtensions("Microsoft Program Databases", "pdb", "pd_", "pdb.xml");
|
||||
|
||||
public static class LoadPdbResults {
|
||||
public File pdbFile;
|
||||
public PdbApplicatorControl control;
|
||||
public boolean useMsDiaParser;
|
||||
public boolean debugLogging;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a modal dialog to the user, allowing them to pick or search for a Pdb
|
||||
* file.<p>
|
||||
* The selected file and parser options are returned in a LoadPdbResults instance.
|
||||
*
|
||||
* @param program the Ghidra {@link Program} that has Pdb info
|
||||
* @return LoadPdbResults instance with the selected file and options, or null if canceled
|
||||
*/
|
||||
public static LoadPdbResults choosePdbForProgram(Program program) {
|
||||
LoadPdbDialog choosePdbDlg = new LoadPdbDialog(program);
|
||||
DockingWindowManager.showDialog(choosePdbDlg);
|
||||
File pdbFile = choosePdbDlg.getLocalSymbolFile(choosePdbDlg.selectedSymbolFile);
|
||||
if (pdbFile == null) {
|
||||
return null;
|
||||
}
|
||||
LoadPdbResults results = new LoadPdbResults();
|
||||
results.pdbFile = pdbFile;
|
||||
results.control =
|
||||
(PdbApplicatorControl) choosePdbDlg.restrictionsCombo.getSelectedItem();
|
||||
results.useMsDiaParser = choosePdbDlg.msdiaParserButton.isSelected();
|
||||
results.debugLogging = choosePdbDlg.debugLoggingCheckbox.isSelected();
|
||||
return results;
|
||||
}
|
||||
|
||||
private SymbolFileLocation selectedSymbolFile;
|
||||
|
||||
private SymbolServerService symbolServerService;
|
||||
private SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext;
|
||||
|
||||
private SymbolFileInfo programSymbolFileInfo;
|
||||
|
||||
private List<Supplier<StatusText>> statusTextSuppliers = new ArrayList<>();
|
||||
private boolean hasPerformedSearch;
|
||||
private boolean searchCanceled;
|
||||
|
||||
private Program program;
|
||||
|
||||
private SymbolServerPanel symbolServerConfigPanel;
|
||||
private SymbolFilePanel symbolFilePanel;
|
||||
|
||||
private JTextField programNameTextField;
|
||||
private JTextField pdbPathTextField;
|
||||
private GCheckBox overridePdbPathCheckBox;
|
||||
private JTextField pdbUniqueIdTextField;
|
||||
private GCheckBox overridePdbUniqueIdCheckBox;
|
||||
private IntegerTextField pdbAgeTextField;
|
||||
private GCheckBox overridePdbAgeCheckBox;
|
||||
private HintTextField pdbLocationTextField;
|
||||
private GIconLabel exactMatchIconLabel;
|
||||
|
||||
private JToggleButton advancedToggleButton;
|
||||
|
||||
private GhidraFileChooser chooser;
|
||||
|
||||
private JButton choosePdbLocationButton;
|
||||
private JButton loadPdbButton;
|
||||
|
||||
private JPanel pdbLocationPanel;
|
||||
private JPanel programPdbPanel;
|
||||
private JComponent workComp;
|
||||
|
||||
private JPanel parserOptionsPanel;
|
||||
private JRadioButton universalParserButton;
|
||||
private JRadioButton msdiaParserButton;
|
||||
private GComboBox<PdbApplicatorControl> restrictionsCombo;
|
||||
private GCheckBox debugLoggingCheckbox;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the LoadPdbDialog class.
|
||||
*
|
||||
* @param program the ghidra {@link Program} that is loading the Pdb
|
||||
*/
|
||||
public LoadPdbDialog(Program program) {
|
||||
super("Load PDB for " + program.getName(), true, true, true, true);
|
||||
setRememberSize(false);
|
||||
|
||||
this.program = program;
|
||||
this.programSymbolFileInfo = SymbolFileInfo.fromMetadata(program.getMetadata());
|
||||
if (programSymbolFileInfo == null) {
|
||||
programSymbolFileInfo = SymbolFileInfo.unknown("missing");
|
||||
}
|
||||
this.symbolServerInstanceCreatorContext =
|
||||
SymbolServerInstanceCreatorRegistry.getInstance().getContext(program);
|
||||
this.symbolServerService =
|
||||
PdbPlugin.getSymbolServerService(symbolServerInstanceCreatorContext);
|
||||
|
||||
build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dialogShown() {
|
||||
pdbPathTextField.setText(programSymbolFileInfo.getPath());
|
||||
pdbUniqueIdTextField.setText(programSymbolFileInfo.getUniqueName());
|
||||
pdbAgeTextField.setValue(programSymbolFileInfo.getIdentifiers().getAge());
|
||||
programNameTextField.setText(program.getName());
|
||||
cancelButton.requestFocusInWindow();
|
||||
|
||||
executeMonitoredRunnable("Search for PDB using built-in locations", true, true, 0,
|
||||
this::doInitialDefaultSearch);
|
||||
}
|
||||
|
||||
private void doInitialDefaultSearch(TaskMonitor monitor) {
|
||||
try {
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(programSymbolFileInfo, FindOption.NO_OPTIONS, monitor);
|
||||
if (!results.isEmpty()) {
|
||||
SymbolFileLocation symbolFileLocation =
|
||||
symbolServerService.getLocalSymbolFileLocation(results.get(0), monitor);
|
||||
File symbolFile = getLocalSymbolFile(symbolFileLocation);
|
||||
Swing.runLater(() -> {
|
||||
setSearchResults(results);
|
||||
setPdbLocationValue(symbolFileLocation, symbolFile);
|
||||
setSelectedPdbFile(symbolFileLocation);
|
||||
selectRowByLocation(symbolFileLocation);
|
||||
updateStatusText();
|
||||
updateButtonEnablement();
|
||||
updateParserOptionEnablement(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (CancelledException | IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
selectedSymbolFile = null;
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
* For screenshot use only
|
||||
*
|
||||
* @param options set of {@link FindOption} enum
|
||||
*/
|
||||
public void setSearchOptions(Set<FindOption> options) {
|
||||
symbolFilePanel.setFindOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* For screenshot use only
|
||||
*
|
||||
* @param pathStr path of symbol storage directory
|
||||
*/
|
||||
public void setSymbolStorageDirectoryTextOnly(String pathStr) {
|
||||
symbolServerConfigPanel.setSymbolStorageDirectoryTextOnly(pathStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* For screenshot use only
|
||||
*
|
||||
* @param symbolServers list of symbol servers
|
||||
*/
|
||||
public void setSymbolServers(List<SymbolServer> symbolServers) {
|
||||
symbolServerConfigPanel.setSymbolServers(symbolServers);
|
||||
}
|
||||
|
||||
/**
|
||||
* For screenshot use only
|
||||
*/
|
||||
public void pushAddLocationBution() {
|
||||
symbolServerConfigPanel.pushAddLocationButton();
|
||||
}
|
||||
|
||||
private void setSelectedPdbFile(SymbolFileLocation symbolFileLocation) {
|
||||
this.selectedSymbolFile = symbolFileLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the contents of the search results table.
|
||||
* <p>
|
||||
* Public only for screenshot usage, treat as private otherwise.
|
||||
*
|
||||
* @param results list of {@link SymbolFileLocation}s to add to results
|
||||
*/
|
||||
public void setSearchResults(List<SymbolFileLocation> results) {
|
||||
hasPerformedSearch = true;
|
||||
symbolFilePanel.getTableModel().setSearchResults(programSymbolFileInfo, results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a row in the results table.
|
||||
* <p>
|
||||
* Public only for screenshot usage. Treat as private.
|
||||
*
|
||||
* @param symbolFileLocation {@link SymbolFileLocation} to select in results table
|
||||
*/
|
||||
public void selectRowByLocation(SymbolFileLocation symbolFileLocation) {
|
||||
for (int i = 0; i < symbolFilePanel.getTableModel().getModelData().size(); i++) {
|
||||
SymbolFileRow symbolFileRow = symbolFilePanel.getTableModel().getModelData().get(i);
|
||||
if (symbolFileRow.getLocation().equals(symbolFileLocation)) {
|
||||
symbolFilePanel.getTable().selectRow(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
symbolFilePanel.getTable().clearSelection();
|
||||
}
|
||||
|
||||
private StatusText getSelectedPdbNoticeText() {
|
||||
if (selectedSymbolFile == null) {
|
||||
return null;
|
||||
}
|
||||
if (selectedSymbolFile.getFileInfo() == null) {
|
||||
return new StatusText("Unable to read Pdb information", MessageType.ERROR, false);
|
||||
}
|
||||
return !selectedSymbolFile.isExactMatch(programSymbolFileInfo)
|
||||
? new StatusText("WARNING: Selected PDB is not an exact match!",
|
||||
MessageType.WARNING, false)
|
||||
: null;
|
||||
}
|
||||
|
||||
private String getSymbolFileToolText(SymbolFileLocation symbolFileLocation) {
|
||||
return symbolFileLocation != null
|
||||
? String.format(
|
||||
"<html><table>" +
|
||||
"<tr><td>PDB Name:</td><td><b>%s</b></td></tr>" +
|
||||
"<tr><td>Path:</td><td><b>%s</b></td></tr>" +
|
||||
"<tr><td>GUID/ID:</td><td><b>%s</b></td></tr>" +
|
||||
"<tr><td>Age:</td><td><b>%x</b></td></tr>" +
|
||||
"<tr><td>Is Exact Match:</td><td><b>%b</b></td</tr>" +
|
||||
"</table>",
|
||||
HTMLUtilities.escapeHTML(symbolFileLocation.getFileInfo().getName()),
|
||||
HTMLUtilities.escapeHTML(symbolFileLocation.getLocationStr()),
|
||||
symbolFileLocation.getFileInfo().getUniqueName(),
|
||||
symbolFileLocation.getFileInfo().getIdentifiers().getAge(),
|
||||
symbolFileLocation.getFileInfo().isExactMatch(programSymbolFileInfo))
|
||||
: null;
|
||||
}
|
||||
|
||||
private void updateButtonEnablement() {
|
||||
boolean hasLocation = selectedSymbolFile != null;
|
||||
loadPdbButton.setEnabled(hasLocation);
|
||||
}
|
||||
|
||||
private void setSymbolServerService(SymbolServerService symbolServerService) {
|
||||
this.symbolServerService = symbolServerService;
|
||||
symbolFilePanel.setEnablement(symbolServerService != null);
|
||||
updateStatusText();
|
||||
}
|
||||
|
||||
private SymbolFileInfo getCurrentSymbolFileInfo() {
|
||||
String pdbPath = pdbPathTextField.getText();
|
||||
String uid = pdbUniqueIdTextField.getText();
|
||||
int age = pdbAgeTextField.getIntValue();
|
||||
|
||||
return SymbolFileInfo.fromValues(pdbPath, uid, age);
|
||||
}
|
||||
|
||||
private void searchForPdbs(ActionEvent e) {
|
||||
if (symbolServerService == null || !symbolServerService.isValid()) {
|
||||
return;
|
||||
}
|
||||
if (pdbAgeTextField.getText().isBlank()) {
|
||||
Msg.showWarn(this, null, "Bad PDB Age", "Invalid PDB Age value");
|
||||
return;
|
||||
}
|
||||
SymbolFileInfo symbolFileInfo = getCurrentSymbolFileInfo();
|
||||
if (symbolFileInfo == null) {
|
||||
Msg.showWarn(this, null, "Bad PDB GUID/Id",
|
||||
"Invalid PDB GUID / UID value: " + pdbUniqueIdTextField.getText());
|
||||
return;
|
||||
}
|
||||
Set<FindOption> findOptions = symbolFilePanel.getFindOptions();
|
||||
executeMonitoredRunnable("Search for PDBs", true, true, 0, monitor -> {
|
||||
try {
|
||||
searchCanceled = false;
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(symbolFileInfo, findOptions, monitor);
|
||||
Swing.runLater(() -> {
|
||||
setSearchResults(results);
|
||||
if (results.size() == 1) {
|
||||
selectRowByLocation(results.get(0));
|
||||
}
|
||||
updateStatusText();
|
||||
updateButtonEnablement();
|
||||
});
|
||||
}
|
||||
catch (CancelledException e1) {
|
||||
searchCanceled = true;
|
||||
Swing.runLater(() -> updateStatusText());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void build() {
|
||||
buildSymbolFilePanel();
|
||||
buildSSConfigPanel();
|
||||
buildPdbLocationPanel();
|
||||
buildProgramPdbPanel();
|
||||
buildParserOptionsPanel();
|
||||
setHelpLocation(new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "Load PDB File"));
|
||||
|
||||
addStatusTextSupplier(() -> hasPerformedSearch && advancedToggleButton.isSelected()
|
||||
? symbolServerConfigPanel.getSymbolServerWarnings()
|
||||
: null);
|
||||
addStatusTextSupplier(this::getSelectedPdbNoticeText);
|
||||
addStatusTextSupplier(this::getConfigChangedWarning);
|
||||
addStatusTextSupplier(this::getAllowRemoteWarning);
|
||||
addStatusTextSupplier(this::getFoundCountInfo);
|
||||
|
||||
addButtons();
|
||||
layoutSimple();
|
||||
|
||||
updateStatusText();
|
||||
updateButtonEnablement();
|
||||
// later dialogShow() will be called
|
||||
}
|
||||
|
||||
private void buildSSConfigPanel() {
|
||||
symbolServerConfigPanel =
|
||||
new SymbolServerPanel(this::setSymbolServerService, symbolServerInstanceCreatorContext);
|
||||
}
|
||||
|
||||
private void buildSymbolFilePanel() {
|
||||
symbolFilePanel = new SymbolFilePanel(this::searchForPdbs); // panel will be added in layoutAdvanced()
|
||||
|
||||
symbolFilePanel.getTable()
|
||||
.getSelectionModel()
|
||||
.addListSelectionListener(e -> updateSelectedRow());
|
||||
symbolFilePanel.addMouseListener(new GMouseListenerAdapter() {
|
||||
@Override
|
||||
public void doubleClickTriggered(MouseEvent e) {
|
||||
if (loadPdbButton.isEnabled()) {
|
||||
e.consume();
|
||||
loadPdbButton.doClick();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateSelectedRow() {
|
||||
SymbolFileRow row = symbolFilePanel.getSelectedRow();
|
||||
setSelectedPdbFile(row != null ? row.getLocation() : null);
|
||||
updateStatusText();
|
||||
updateButtonEnablement();
|
||||
updateParserOptionEnablement(true);
|
||||
}
|
||||
|
||||
private JPanel buildProgramPdbPanel() {
|
||||
|
||||
programNameTextField = new BetterNonEditableTextField(20);
|
||||
programNameTextField.setEditable(false);
|
||||
|
||||
pdbPathTextField = new BetterNonEditableTextField(20);
|
||||
pdbPathTextField.setEditable(false);
|
||||
|
||||
overridePdbPathCheckBox = new GCheckBox();
|
||||
overridePdbPathCheckBox.setVisible(false);
|
||||
overridePdbPathCheckBox.setToolTipText("Override PDB name (when searching).");
|
||||
overridePdbPathCheckBox.addItemListener(e -> {
|
||||
pdbPathTextField.setEditable(overridePdbPathCheckBox.isSelected());
|
||||
if (overridePdbPathCheckBox.isSelected()) {
|
||||
pdbPathTextField.requestFocusInWindow();
|
||||
}
|
||||
else {
|
||||
pdbPathTextField.setText(programSymbolFileInfo.getPath());
|
||||
}
|
||||
});
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(overridePdbPathCheckBox,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
SymbolFilePanel.SEARCH_OPTIONS_HELP_ANCHOR));
|
||||
|
||||
pdbUniqueIdTextField = new BetterNonEditableTextField(36);
|
||||
pdbUniqueIdTextField.setEditable(false);
|
||||
pdbUniqueIdTextField.setToolTipText(
|
||||
"<html>PDB GUID - either 36 or 32 hexadecimal characters:<br>" +
|
||||
" <b>'012345678-0123-0123-0123-0123456789ABC'</b> or <b>'0123456780123012301230123456789ABC'</b>, or<br>" +
|
||||
"PDB Signature Id - 8 hexadecimal character Id:<br>" +
|
||||
" <b>'11223344'</b>");
|
||||
|
||||
overridePdbUniqueIdCheckBox = new GCheckBox();
|
||||
overridePdbUniqueIdCheckBox.setVisible(false);
|
||||
overridePdbUniqueIdCheckBox.setToolTipText("Override PDB unique id (when searching).");
|
||||
overridePdbUniqueIdCheckBox.addItemListener(e -> {
|
||||
pdbUniqueIdTextField.setEditable(overridePdbUniqueIdCheckBox.isSelected());
|
||||
if (overridePdbUniqueIdCheckBox.isSelected()) {
|
||||
pdbUniqueIdTextField.requestFocusInWindow();
|
||||
}
|
||||
else {
|
||||
pdbUniqueIdTextField.setText(programSymbolFileInfo.getUniqueName());
|
||||
}
|
||||
});
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(overridePdbUniqueIdCheckBox,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
SymbolFilePanel.SEARCH_OPTIONS_HELP_ANCHOR));
|
||||
|
||||
pdbAgeTextField = new IntegerTextField(8);
|
||||
pdbAgeTextField.setAllowNegativeValues(false);
|
||||
pdbAgeTextField.setShowNumberMode(true);
|
||||
pdbAgeTextField.setHexMode();
|
||||
pdbAgeTextField.setEditable(false);
|
||||
|
||||
overridePdbAgeCheckBox = new GCheckBox();
|
||||
overridePdbAgeCheckBox.setVisible(false);
|
||||
overridePdbAgeCheckBox.setToolTipText("Override PDB age (when searching).");
|
||||
overridePdbAgeCheckBox.addItemListener(e -> {
|
||||
pdbAgeTextField.setEditable(overridePdbAgeCheckBox.isSelected());
|
||||
if (overridePdbAgeCheckBox.isSelected()) {
|
||||
pdbAgeTextField.requestFocus();
|
||||
}
|
||||
else {
|
||||
pdbAgeTextField.setValue(programSymbolFileInfo.getIdentifiers().getAge());
|
||||
}
|
||||
});
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(overridePdbAgeCheckBox,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
SymbolFilePanel.SEARCH_OPTIONS_HELP_ANCHOR));
|
||||
|
||||
programPdbPanel = new JPanel(new PairLayout(5, 5));
|
||||
programPdbPanel.setBorder(BorderFactory.createTitledBorder("Program PDB Information"));
|
||||
programPdbPanel.add(new GLabel("Program:", SwingConstants.RIGHT));
|
||||
programPdbPanel.add(programNameTextField);
|
||||
|
||||
programPdbPanel.add(
|
||||
join(null, new GLabel("PDB Name:", SwingConstants.RIGHT), overridePdbPathCheckBox));
|
||||
programPdbPanel.add(pdbPathTextField);
|
||||
|
||||
programPdbPanel.add(join(null, new GLabel("PDB Unique Id:", SwingConstants.RIGHT),
|
||||
overridePdbUniqueIdCheckBox));
|
||||
programPdbPanel.add(pdbUniqueIdTextField);
|
||||
|
||||
programPdbPanel.add(
|
||||
join(null, new GLabel("PDB Age:", SwingConstants.RIGHT), overridePdbAgeCheckBox));
|
||||
programPdbPanel.add(join(pdbAgeTextField.getComponent(), new JPanel(), null));
|
||||
|
||||
return programPdbPanel;
|
||||
}
|
||||
|
||||
private JPanel buildPdbLocationPanel() {
|
||||
pdbLocationTextField = new HintTextField("Browse [...] for PDB file or use 'Advanced'");
|
||||
pdbLocationTextField.setEditable(false);
|
||||
|
||||
choosePdbLocationButton = ButtonPanelFactory.createButton(ButtonPanelFactory.BROWSE_TYPE);
|
||||
choosePdbLocationButton.addActionListener(e -> choosePdbFile());
|
||||
|
||||
exactMatchIconLabel = new GIconLabel(Icons.EMPTY_ICON);
|
||||
|
||||
pdbLocationPanel = new JPanel(new PairLayout(5, 5));
|
||||
pdbLocationPanel.setBorder(BorderFactory.createTitledBorder("PDB Location"));
|
||||
pdbLocationPanel.add(new GLabel("PDB Location:", SwingConstants.RIGHT));
|
||||
pdbLocationPanel
|
||||
.add(join(exactMatchIconLabel, pdbLocationTextField, choosePdbLocationButton));
|
||||
return pdbLocationPanel;
|
||||
}
|
||||
|
||||
private void updateParserOptionEnablement(boolean trySetUniversal) {
|
||||
if (trySetUniversal) {
|
||||
universalParserButton.setSelected(true);
|
||||
msdiaParserButton.setSelected(false);
|
||||
}
|
||||
|
||||
boolean isXML = (selectedSymbolFile != null &&
|
||||
selectedSymbolFile.getPath().toLowerCase().endsWith(".pdb.xml"));
|
||||
boolean isWindows = PdbParser.onWindows;
|
||||
msdiaParserButton.setEnabled(isXML || isWindows);
|
||||
if (isXML) {
|
||||
msdiaParserButton.setSelected(true);
|
||||
}
|
||||
if (msdiaParserButton.isSelected() && !msdiaParserButton.isEnabled()) {
|
||||
msdiaParserButton.setSelected(false);
|
||||
}
|
||||
if (!isWindows && !isXML) {
|
||||
universalParserButton.setSelected(true);
|
||||
}
|
||||
universalParserButton.setEnabled(!isXML);
|
||||
if (universalParserButton.isSelected() && !universalParserButton.isEnabled()) {
|
||||
universalParserButton.setSelected(false);
|
||||
}
|
||||
restrictionsCombo.setEnabled(universalParserButton.isSelected());
|
||||
debugLoggingCheckbox.setEnabled(universalParserButton.isSelected());
|
||||
}
|
||||
|
||||
private JPanel buildParserOptionsPanel() {
|
||||
|
||||
ActionListener l = (e) -> updateParserOptionEnablement(false);
|
||||
universalParserButton = new JRadioButton("Universal");
|
||||
universalParserButton
|
||||
.setToolTipText("Platform-independent PDB analyzer (No PDB.XML support).");
|
||||
msdiaParserButton = new JRadioButton("MSDIA");
|
||||
msdiaParserButton.setToolTipText(
|
||||
"<html>Legacy PDB Analyzer.<br>" +
|
||||
"Requires MS DIA-SDK for raw PDB processing (Windows only), or preprocessed PDB.XML file.");
|
||||
universalParserButton.setSelected(true);
|
||||
universalParserButton.addActionListener(l);
|
||||
msdiaParserButton.addActionListener(l);
|
||||
|
||||
ButtonGroup buttonGroup = new ButtonGroup();
|
||||
buttonGroup.add(msdiaParserButton);
|
||||
buttonGroup.add(universalParserButton);
|
||||
|
||||
JPanel radioButtons = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
radioButtons.add(universalParserButton);
|
||||
radioButtons.add(msdiaParserButton);
|
||||
|
||||
restrictionsCombo = new GComboBox<>(PdbApplicatorControl.values());
|
||||
restrictionsCombo.setSelectedItem(PdbApplicatorControl.ALL);
|
||||
|
||||
debugLoggingCheckbox = new GCheckBox();
|
||||
debugLoggingCheckbox.setToolTipText(
|
||||
"If checked, logs information to the pdb.analyzer.log file for debug/development.");
|
||||
|
||||
parserOptionsPanel = new JPanel(new PairLayout(5, 5));
|
||||
parserOptionsPanel.setBorder(BorderFactory.createTitledBorder("PDB Parser"));
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(parserOptionsPanel,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
"PDB Parser Panel"));
|
||||
|
||||
parserOptionsPanel.add(new GLabel("Parser:"));
|
||||
parserOptionsPanel.add(radioButtons);
|
||||
|
||||
parserOptionsPanel.add(new GLabel("Control:"));
|
||||
parserOptionsPanel.add(restrictionsCombo);
|
||||
|
||||
parserOptionsPanel.add(new GLabel("[Dev] PDB Reader/Applicator Debug Logging:"));
|
||||
parserOptionsPanel.add(debugLoggingCheckbox);
|
||||
|
||||
return parserOptionsPanel;
|
||||
}
|
||||
|
||||
private void addButtons() {
|
||||
|
||||
loadPdbButton = new JButton("Load");
|
||||
loadPdbButton.setName("Load");
|
||||
|
||||
loadPdbButton.addActionListener(e -> {
|
||||
if (selectedSymbolFile == null ||
|
||||
(!selectedSymbolFile.isExactMatch(programSymbolFileInfo) &&
|
||||
OptionDialog.showYesNoDialog(loadPdbButton, "Mismatched Pdb File Warning",
|
||||
"<html>The selected file is not an exact match for the current program.<br>" +
|
||||
"Note: <b>Invalid disassembly may be produced!</b><br>Continue anyway?") != OptionDialog.YES_OPTION)) {
|
||||
return;
|
||||
}
|
||||
executeMonitoredRunnable("Prepare Selected Symbol File",
|
||||
true, true, 0, this::prepareSelectedSymbolFileAndClose);
|
||||
});
|
||||
addButton(loadPdbButton);
|
||||
|
||||
addCancelButton();
|
||||
setDefaultButton(cancelButton);
|
||||
|
||||
advancedToggleButton = new JToggleButton("Advanced >>");
|
||||
advancedToggleButton.addActionListener(e -> toggleAdvancedSearch());
|
||||
buttonPanel.add(advancedToggleButton);
|
||||
}
|
||||
|
||||
private void prepareSelectedSymbolFileAndClose(TaskMonitor monitor) {
|
||||
try {
|
||||
if (selectedSymbolFile != null && symbolServerService != null) {
|
||||
selectedSymbolFile =
|
||||
symbolServerService.getLocalSymbolFileLocation(selectedSymbolFile, monitor);
|
||||
}
|
||||
Swing.runLater(() -> close());
|
||||
return;
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
Msg.showError(this, getComponent(), "Error Getting Symbol File", ioe);
|
||||
}
|
||||
catch (CancelledException ce) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private StatusText getConfigChangedWarning() {
|
||||
return advancedToggleButton.isSelected() && symbolServerConfigPanel.isConfigChanged()
|
||||
? new StatusText(
|
||||
"Symbol Server Search Config Changed. Click \"Save Configuration\" button to save.",
|
||||
MessageType.INFO, false)
|
||||
: null;
|
||||
}
|
||||
|
||||
private StatusText getAllowRemoteWarning() {
|
||||
int remoteSymbolServerCount =
|
||||
symbolServerService != null ? symbolServerService.getRemoteSymbolServerCount() : 0;
|
||||
Set<FindOption> findOptions = symbolFilePanel.getFindOptions();
|
||||
return hasPerformedSearch && advancedToggleButton.isSelected() &&
|
||||
remoteSymbolServerCount != 0 && !findOptions.contains(FindOption.ALLOW_REMOTE)
|
||||
? new StatusText(
|
||||
"Remote servers were excluded. Select \"Allow remote\" checkbox to search remote servers.",
|
||||
MessageType.INFO, false)
|
||||
: null;
|
||||
}
|
||||
|
||||
private StatusText getFoundCountInfo() {
|
||||
if (advancedToggleButton.isSelected()) {
|
||||
if (searchCanceled) {
|
||||
return new StatusText("Search canceled", MessageType.INFO, false);
|
||||
}
|
||||
if (hasPerformedSearch) {
|
||||
int foundCount = symbolFilePanel.getTableModel().getModelData().size();
|
||||
return new StatusText(
|
||||
"Found " + foundCount + " file" + (foundCount != 1 ? "s" : ""),
|
||||
MessageType.INFO, false);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void toggleAdvancedSearch() {
|
||||
boolean isAdvanced = advancedToggleButton.isSelected();
|
||||
advancedToggleButton.setText("Advanced " + (isAdvanced ? "<<" : ">>"));
|
||||
|
||||
overridePdbAgeCheckBox.setVisible(isAdvanced);
|
||||
overridePdbPathCheckBox.setVisible(isAdvanced);
|
||||
overridePdbUniqueIdCheckBox.setVisible(isAdvanced);
|
||||
setPdbLocationValue(null, null);
|
||||
|
||||
if (isAdvanced) {
|
||||
if (symbolServerService == null || !symbolServerService.isValid()) {
|
||||
setSelectedPdbFile(null);
|
||||
}
|
||||
layoutAdvanced();
|
||||
}
|
||||
else {
|
||||
if (selectedSymbolFile != null) {
|
||||
File localSymbolFile = getLocalSymbolFile(selectedSymbolFile);
|
||||
if (localSymbolFile != null) {
|
||||
setPdbLocationValue(selectedSymbolFile, localSymbolFile);
|
||||
}
|
||||
}
|
||||
else {
|
||||
setSelectedPdbFile(null);
|
||||
}
|
||||
layoutSimple();
|
||||
}
|
||||
|
||||
updateStatusText();
|
||||
updateButtonEnablement();
|
||||
updateParserOptionEnablement(false);
|
||||
repack();
|
||||
}
|
||||
|
||||
private void layoutSimple() {
|
||||
Box box = Box.createVerticalBox();
|
||||
box.add(programPdbPanel);
|
||||
box.add(pdbLocationPanel);
|
||||
box.add(parserOptionsPanel);
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.add(box, BorderLayout.NORTH);
|
||||
|
||||
overrideWorkPanel(panel);
|
||||
}
|
||||
|
||||
private void overrideWorkPanel(JComponent workComp) {
|
||||
if (this.workComp != null && this.workComp.getParent() != null) {
|
||||
this.workComp.getParent().remove(this.workComp);
|
||||
}
|
||||
this.workComp = workComp;
|
||||
addWorkPanel(workComp);
|
||||
}
|
||||
|
||||
private void layoutAdvanced() {
|
||||
Box topPanel = Box.createHorizontalBox();
|
||||
topPanel.add(programPdbPanel);
|
||||
topPanel.add(symbolServerConfigPanel);
|
||||
|
||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||
mainPanel.add(symbolFilePanel, BorderLayout.CENTER);
|
||||
mainPanel.add(parserOptionsPanel, BorderLayout.SOUTH);
|
||||
|
||||
overrideWorkPanel(mainPanel);
|
||||
}
|
||||
|
||||
private void choosePdbFile() {
|
||||
File file = getChooser().getSelectedFile();
|
||||
if (file != null && file.isFile()) {
|
||||
Preferences.setProperty(LAST_PDBFILE_PREFERENCE_KEY, file.getPath());
|
||||
executeMonitoredRunnable("Get PDB Info", true, true, 0, monitor -> {
|
||||
SymbolFileInfo pdbSymbolFileInfo = SymbolFileInfo.fromFile(file, monitor);
|
||||
if (pdbSymbolFileInfo == null) {
|
||||
pdbSymbolFileInfo = SymbolFileInfo.unknown(file.getName());
|
||||
}
|
||||
SymbolFileLocation symbolFileLocation =
|
||||
SameDirSymbolStore.createManuallySelectedSymbolFileLocation(file,
|
||||
pdbSymbolFileInfo);
|
||||
Swing.runLater(() -> {
|
||||
setSearchResults(List.of(symbolFileLocation));
|
||||
setSelectedPdbFile(symbolFileLocation);
|
||||
setPdbLocationValue(symbolFileLocation, file);
|
||||
selectRowByLocation(symbolFileLocation);
|
||||
hasPerformedSearch = false;
|
||||
updateStatusText();
|
||||
updateButtonEnablement();
|
||||
updateParserOptionEnablement(true);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void setPdbLocationValue(SymbolFileLocation symbolFileLocation, File file) {
|
||||
boolean isExactMatch = symbolFileLocation != null
|
||||
? symbolFileLocation.isExactMatch(programSymbolFileInfo)
|
||||
: false;
|
||||
pdbLocationTextField.setText(file != null ? file.getPath() : "");
|
||||
pdbLocationTextField.setToolTipText(getSymbolFileToolText(symbolFileLocation));
|
||||
exactMatchIconLabel
|
||||
.setIcon(file == null ? null : isExactMatch ? MATCH_OK_ICON : MATCH_BAD_ICON);
|
||||
exactMatchIconLabel.setToolTipText(
|
||||
file == null ? null : isExactMatch ? "Exact match" : "Not exact match");
|
||||
|
||||
}
|
||||
|
||||
private GhidraFileChooser getChooser() {
|
||||
|
||||
if (chooser == null) {
|
||||
chooser = new GhidraFileChooser(getComponent());
|
||||
chooser.addFileFilter(PDB_FILES_FILTER);
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
chooser.setApproveButtonText("Choose");
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
|
||||
chooser.setTitle("Select PDB");
|
||||
|
||||
String lastFile = Preferences.getProperty(LAST_PDBFILE_PREFERENCE_KEY);
|
||||
if (lastFile != null) {
|
||||
chooser.setSelectedFile(new File(lastFile));
|
||||
}
|
||||
}
|
||||
|
||||
return chooser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a supplier of status text messages. The supplier will be polled
|
||||
* whenever the updateStatusText() method is called.
|
||||
* <p>
|
||||
* Use this status text scheme instead of {@link #setStatusText(String)} if
|
||||
* there are multiple locations that need to provide a status message at the
|
||||
* bottom of the dialog.
|
||||
*
|
||||
* @param supplier StatusText supplier
|
||||
*/
|
||||
private void addStatusTextSupplier(Supplier<StatusText> supplier) {
|
||||
statusTextSuppliers.remove(supplier);
|
||||
statusTextSuppliers.add(supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls all {@link #addStatusTextSupplier(Supplier) registered} StatusText suppliers and
|
||||
* sets the status message at the bottom of the dialog to the resulting message.
|
||||
* <p>
|
||||
* Not compatible with {@link #setStatusText(String)}. Either use it, or this.
|
||||
*/
|
||||
private void updateStatusText() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean alert = false;
|
||||
MessageType mt = MessageType.INFO;
|
||||
for (Supplier<StatusText> supplier : statusTextSuppliers) {
|
||||
StatusText statusText = supplier.get();
|
||||
if (statusText != null && statusText.message != null && !statusText.message.isEmpty()) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("<br>");
|
||||
}
|
||||
sb.append(HTMLUtilities.colorString(getStatusColor(statusText.messageType),
|
||||
statusText.message));
|
||||
alert |= statusText.alert;
|
||||
if (mt.ordinal() < statusText.messageType.ordinal()) {
|
||||
mt = statusText.messageType;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sb.length() != 0) {
|
||||
setStatusText("<html>" + sb.toString(), mt, alert);
|
||||
}
|
||||
else {
|
||||
clearStatusText();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private File getLocalSymbolFile(SymbolFileLocation symbolFileLocation) {
|
||||
if (symbolFileLocation == null) {
|
||||
return null;
|
||||
}
|
||||
SymbolServer symbolServer = symbolFileLocation.getSymbolServer();
|
||||
if (!(symbolServer instanceof SymbolStore)) {
|
||||
return null;
|
||||
}
|
||||
SymbolStore symbolStore = (SymbolStore) symbolServer;
|
||||
File file = symbolStore.getFile(symbolFileLocation.getPath());
|
||||
return SymbolStore.isCompressedFilename(file.getName()) ? null : file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a non-modal task that has progress and can be cancelled.
|
||||
* <p>
|
||||
* See {@link #executeProgressTask(Task, int)}.
|
||||
*
|
||||
* @param taskTitle String title of task
|
||||
* @param canCancel boolean flag, if true task can be canceled by the user
|
||||
* @param hasProgress boolean flag, if true the task has a progress meter
|
||||
* @param delay int number of milliseconds to delay before showing the task's
|
||||
* progress
|
||||
* @param runnable {@link MonitoredRunnable} to run
|
||||
*/
|
||||
private void executeMonitoredRunnable(String taskTitle, boolean canCancel,
|
||||
boolean hasProgress, int delay, MonitoredRunnable runnable) {
|
||||
Task task = new Task(taskTitle, canCancel, hasProgress, false) {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
runnable.monitoredRun(monitor);
|
||||
}
|
||||
};
|
||||
executeProgressTask(task, delay);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
static class StatusText {
|
||||
|
||||
public StatusText(String message, MessageType messageType, boolean alert) {
|
||||
this.message = message;
|
||||
this.messageType = messageType;
|
||||
this.alert = alert;
|
||||
}
|
||||
|
||||
public String message;
|
||||
public MessageType messageType;
|
||||
public boolean alert;
|
||||
}
|
||||
|
||||
static JPanel join(JComponent left, JComponent main, JComponent right) {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
if (left != null) {
|
||||
panel.add(left, BorderLayout.WEST);
|
||||
}
|
||||
panel.add(main, BorderLayout.CENTER);
|
||||
if (right != null) {
|
||||
panel.add(right, BorderLayout.EAST);
|
||||
}
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* A customized JTextField that changes the background of non-editable
|
||||
* text fields to be the same color as the parent container's background.
|
||||
*/
|
||||
static class BetterNonEditableTextField extends JTextField {
|
||||
|
||||
BetterNonEditableTextField(int columns) {
|
||||
super(columns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getBackground() {
|
||||
Container parent = getParent();
|
||||
if (parent != null && isEditable() == false) {
|
||||
Color bg = parent.getBackground();
|
||||
// mint a new Color object to avoid it being
|
||||
// ignored because the parent handed us a DerivedColor
|
||||
// instance
|
||||
return new Color(bg.getRGB());
|
||||
}
|
||||
return super.getBackground();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver.ui;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.TableColumn;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.checkbox.GCheckBox;
|
||||
import docking.widgets.label.GHtmlLabel;
|
||||
import docking.widgets.label.GLabel;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import pdb.PdbPlugin;
|
||||
import pdb.symbolserver.FindOption;
|
||||
|
||||
/**
|
||||
* Displays the results of a 'find' operation in a table.
|
||||
* Also allows the user to tweak search options.
|
||||
*/
|
||||
class SymbolFilePanel extends JPanel {
|
||||
static final String SEARCH_OPTIONS_HELP_ANCHOR = "PDB_Search_Search_Options";
|
||||
private SymbolFileTableModel tableModel;
|
||||
private GhidraTable table;
|
||||
|
||||
private JPanel tablePanel;
|
||||
private JPanel welcomePanel;
|
||||
|
||||
private JButton searchButton;
|
||||
private GCheckBox allowRemote;
|
||||
private GCheckBox ignorePdbUid;
|
||||
private GCheckBox ignorePdbAge;
|
||||
|
||||
SymbolFilePanel(ActionListener searchButtonActionListener) {
|
||||
super(new BorderLayout());
|
||||
|
||||
build();
|
||||
setEnablement(false);
|
||||
searchButton.addActionListener(searchButtonActionListener);
|
||||
}
|
||||
|
||||
SymbolFileTableModel getTableModel() {
|
||||
return tableModel;
|
||||
}
|
||||
|
||||
GhidraTable getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
Set<FindOption> getFindOptions() {
|
||||
Set<FindOption> findOptions = EnumSet.noneOf(FindOption.class);
|
||||
if (allowRemote.isSelected()) {
|
||||
findOptions.add(FindOption.ALLOW_REMOTE);
|
||||
}
|
||||
if (ignorePdbAge.isSelected()) {
|
||||
findOptions.add(FindOption.ANY_AGE);
|
||||
}
|
||||
if (ignorePdbUid.isSelected()) {
|
||||
findOptions.add(FindOption.ANY_ID);
|
||||
}
|
||||
return findOptions;
|
||||
}
|
||||
|
||||
void setFindOptions(Set<FindOption> findOptions) {
|
||||
allowRemote.setSelected(findOptions.contains(FindOption.ALLOW_REMOTE));
|
||||
ignorePdbAge.setSelected(findOptions.contains(FindOption.ANY_AGE));
|
||||
ignorePdbUid.setSelected(findOptions.contains(FindOption.ANY_ID));
|
||||
}
|
||||
|
||||
void setEnablement(boolean hasSymbolServerService) {
|
||||
searchButton.setEnabled(hasSymbolServerService);
|
||||
|
||||
if (welcomePanel != null && hasSymbolServerService) {
|
||||
remove(welcomePanel);
|
||||
welcomePanel = null;
|
||||
add(tablePanel, BorderLayout.CENTER);
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
SymbolFileRow getSelectedRow() {
|
||||
return table.getSelectedRow() != -1
|
||||
? tableModel.getRowObject(table.getSelectedRow())
|
||||
: null;
|
||||
}
|
||||
|
||||
int getSelectedRowIndex() {
|
||||
return table.getSelectedRow();
|
||||
}
|
||||
|
||||
private void build() {
|
||||
setBorder(BorderFactory.createTitledBorder("PDB Search"));
|
||||
add(buildButtonPanel(), BorderLayout.NORTH);
|
||||
buildTable(); // don't add it yet
|
||||
add(buildWelcomePanel(), BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private JPanel buildWelcomePanel() {
|
||||
welcomePanel = new JPanel();
|
||||
welcomePanel.add(new GHtmlLabel(
|
||||
"<html><br><center><font color=red>Local Symbol Storage location must be set first!"));
|
||||
welcomePanel.setPreferredSize(tablePanel.getPreferredSize());
|
||||
|
||||
return welcomePanel;
|
||||
}
|
||||
|
||||
private JPanel buildTable() {
|
||||
this.tableModel = new SymbolFileTableModel();
|
||||
this.table = new GhidraTable(tableModel);
|
||||
|
||||
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
|
||||
TableColumn isMatchColumn = table.getColumnModel().getColumn(0);
|
||||
isMatchColumn.setResizable(false);
|
||||
isMatchColumn.setPreferredWidth(32);
|
||||
isMatchColumn.setMaxWidth(32);
|
||||
isMatchColumn.setMinWidth(32);
|
||||
|
||||
// a few extra rows than needed since the table component
|
||||
// will be resized according to the number of warning text
|
||||
// lines at the bottom of the dialog
|
||||
table.setVisibleRowCount(8);
|
||||
table.setPreferredScrollableViewportSize(new Dimension(100, 100));
|
||||
|
||||
tablePanel = new JPanel(new BorderLayout());
|
||||
tablePanel.add(new JScrollPane(table), BorderLayout.CENTER);
|
||||
|
||||
return tablePanel;
|
||||
}
|
||||
|
||||
private JPanel buildButtonPanel() {
|
||||
searchButton = new JButton("Search");
|
||||
|
||||
allowRemote = new GCheckBox("Allow Remote");
|
||||
allowRemote.setToolTipText("Allow searching remote symbol servers.");
|
||||
|
||||
ignorePdbUid = new GCheckBox("Ignore GUID/ID");
|
||||
ignorePdbUid.setToolTipText("Find any PDB with same name (local locations only).");
|
||||
|
||||
ignorePdbAge = new GCheckBox("Ignore Age");
|
||||
ignorePdbAge.setToolTipText("Find PDB with any age value (local locations only).");
|
||||
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
|
||||
|
||||
panel.add(new GLabel("Search Options:"));
|
||||
panel.add(Box.createHorizontalStrut(10));
|
||||
panel.add(ignorePdbAge);
|
||||
panel.add(Box.createHorizontalStrut(10));
|
||||
panel.add(ignorePdbUid);
|
||||
panel.add(Box.createHorizontalStrut(10));
|
||||
panel.add(allowRemote);
|
||||
panel.add(Box.createHorizontalGlue());
|
||||
panel.add(searchButton);
|
||||
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(panel,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, SEARCH_OPTIONS_HELP_ANCHOR));
|
||||
|
||||
return panel;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver.ui;
|
||||
|
||||
import pdb.symbolserver.*;
|
||||
|
||||
/**
|
||||
* A row in the {@link SymbolFilePanel} find results table
|
||||
*/
|
||||
class SymbolFileRow {
|
||||
private SymbolFileLocation symbolFileLocation;
|
||||
private boolean isExactMatch;
|
||||
|
||||
SymbolFileRow(SymbolFileLocation symbolFileLocation, boolean isExactMatch) {
|
||||
this.symbolFileLocation = symbolFileLocation;
|
||||
this.isExactMatch = isExactMatch;
|
||||
}
|
||||
|
||||
SymbolFileInfo getSymbolFileInfo() {
|
||||
return symbolFileLocation.getFileInfo();
|
||||
}
|
||||
|
||||
SymbolFileLocation getLocation() {
|
||||
return symbolFileLocation;
|
||||
}
|
||||
|
||||
boolean isExactMatch() {
|
||||
return isExactMatch;
|
||||
}
|
||||
|
||||
boolean isAvailableLocal() {
|
||||
return symbolFileLocation.getSymbolServer() instanceof SymbolStore;
|
||||
}
|
||||
|
||||
void update(SymbolFileLocation newLocation, boolean newIsExactMatch) {
|
||||
this.symbolFileLocation = newLocation;
|
||||
this.isExactMatch = newIsExactMatch;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,289 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.widgets.table.*;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.framework.plugintool.ServiceProviderStub;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
import pdb.symbolserver.SymbolFileInfo;
|
||||
import pdb.symbolserver.SymbolFileLocation;
|
||||
|
||||
/**
|
||||
* Table model for the SymbolFilePanel table.
|
||||
*/
|
||||
class SymbolFileTableModel
|
||||
extends GDynamicColumnTableModel<SymbolFileRow, List<SymbolFileRow>> {
|
||||
|
||||
private List<SymbolFileRow> rows = new ArrayList<>();
|
||||
|
||||
SymbolFileTableModel() {
|
||||
super(new ServiceProviderStub());
|
||||
setDefaultTableSortState(null);
|
||||
}
|
||||
|
||||
void setRows(List<SymbolFileRow> rows) {
|
||||
this.rows = rows;
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
void setSearchResults(SymbolFileInfo symbolFileInfo, List<SymbolFileLocation> results) {
|
||||
List<SymbolFileRow> newRows = new ArrayList<>();
|
||||
for (SymbolFileLocation symbolFileLocation : results) {
|
||||
newRows.add(new SymbolFileRow(symbolFileLocation,
|
||||
symbolFileLocation.isExactMatch(symbolFileInfo)));
|
||||
}
|
||||
rows = newRows;
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Symbol Files";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SymbolFileRow> getModelData() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SymbolFileRow> getDataSource() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<SymbolFileRow> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<SymbolFileRow> descriptor = new TableColumnDescriptor<>();
|
||||
|
||||
descriptor.addVisibleColumn(new PdbExactMatchColumn());
|
||||
descriptor.addVisibleColumn(new PdbFileNameColumn());
|
||||
descriptor.addHiddenColumn(new PdbFilePathColumn());
|
||||
descriptor.addVisibleColumn(new GuidColumn());
|
||||
descriptor.addVisibleColumn(new PdbAgeColumn());
|
||||
descriptor.addHiddenColumn(new PdbVersionColumn());
|
||||
descriptor.addVisibleColumn(new PdbFileStatusColumn());
|
||||
descriptor.addVisibleColumn(new PdbFileLocationColumn());
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
private class PdbExactMatchColumn
|
||||
extends AbstractDynamicTableColumnStub<SymbolFileRow, Boolean> {
|
||||
|
||||
BooleanIconColumnRenderer renderer =
|
||||
new BooleanIconColumnRenderer(LoadPdbDialog.MATCH_OK_ICON,
|
||||
LoadPdbDialog.MATCH_BAD_ICON, null, "Exact Match", "Not Exact Match", null);
|
||||
|
||||
@Override
|
||||
public Boolean getValue(SymbolFileRow rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.isExactMatch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<Boolean> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Exact Match";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnDisplayName(Settings settings) {
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class PdbFileNameColumn extends AbstractDynamicTableColumnStub<SymbolFileRow, String> {
|
||||
|
||||
@Override
|
||||
public String getValue(SymbolFileRow rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.getSymbolFileInfo().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "PDB Filename";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 200;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class PdbFilePathColumn extends AbstractDynamicTableColumnStub<SymbolFileRow, String> {
|
||||
|
||||
@Override
|
||||
public String getValue(SymbolFileRow rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.getSymbolFileInfo().getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "PDB Filepath";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class GuidColumn extends AbstractDynamicTableColumnStub<SymbolFileRow, String> {
|
||||
|
||||
@Override
|
||||
public String getValue(SymbolFileRow rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.getSymbolFileInfo().getUniqueName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "GUID / Signature";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 300;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class PdbVersionColumn extends AbstractDynamicTableColumnStub<SymbolFileRow, String> {
|
||||
|
||||
@Override
|
||||
public String getValue(SymbolFileRow rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return Integer.toString(rowObject.getSymbolFileInfo().getIdentifiers().getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "PDB Version";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class PdbAgeColumn extends AbstractDynamicTableColumnStub<SymbolFileRow, Integer> {
|
||||
|
||||
@Override
|
||||
public Integer getValue(SymbolFileRow rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.getSymbolFileInfo().getIdentifiers().getAge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "PDB Age";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 120;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class PdbFileStatusColumn
|
||||
extends AbstractDynamicTableColumnStub<SymbolFileRow, String> {
|
||||
|
||||
@Override
|
||||
public String getValue(SymbolFileRow row, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return row.isAvailableLocal() ? "Local" : "Remote";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "PDB File Status";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 120;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class PdbFileLocationColumn
|
||||
extends AbstractDynamicTableColumnStub<SymbolFileRow, String> {
|
||||
|
||||
@Override
|
||||
public String getValue(SymbolFileRow row, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return row.getLocation().getLocationStr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "File Location";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Table column renderer to render a boolean value as an icon
|
||||
*/
|
||||
private static class BooleanIconColumnRenderer extends AbstractGColumnRenderer<Boolean> {
|
||||
|
||||
private Icon[] icons;
|
||||
private String[] toolTipStrings;
|
||||
|
||||
BooleanIconColumnRenderer(Icon trueIcon, Icon falseIcon, Icon missingIcon,
|
||||
String trueTooltip, String falseTooltip, String missingTooltip) {
|
||||
this.icons = new Icon[] { missingIcon, falseIcon, trueIcon };
|
||||
this.toolTipStrings = new String[] { missingTooltip, falseTooltip, trueTooltip };
|
||||
}
|
||||
|
||||
private int getValueOrdinal(GTableCellRenderingData data) {
|
||||
Boolean booleanValue = (Boolean) data.getValue();
|
||||
|
||||
return booleanValue == null ? 0 : booleanValue.booleanValue() ? 2 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
JLabel renderer = (JLabel) super.getTableCellRendererComponent(data);
|
||||
|
||||
int ordinal = getValueOrdinal(data);
|
||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
renderer.setText("");
|
||||
renderer.setIcon(icons[ordinal]);
|
||||
renderer.setToolTipText(toolTipStrings[ordinal]);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(Boolean booleanValue, Settings settings) {
|
||||
return booleanValue == null ? "" : booleanValue.toString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,594 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver.ui;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.TableColumn;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.options.editor.ButtonPanelFactory;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.label.GHtmlLabel;
|
||||
import docking.widgets.label.GLabel;
|
||||
import docking.widgets.table.GTable;
|
||||
import docking.widgets.textfield.HintTextField;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
import pdb.PdbPlugin;
|
||||
import pdb.symbolserver.*;
|
||||
import pdb.symbolserver.ui.LoadPdbDialog.StatusText;
|
||||
import resources.Icons;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Panel that allows the user to configure a SymbolServerService: a local
|
||||
* symbol storage directory and a list of search locations.
|
||||
*/
|
||||
class SymbolServerPanel extends JPanel {
|
||||
private static final String MS_SYMBOLSERVER_ENVVAR = "_NT_SYMBOL_PATH";
|
||||
|
||||
private List<WellKnownSymbolServerLocation> knownSymbolServers =
|
||||
WellKnownSymbolServerLocation.loadAll();
|
||||
|
||||
private SymbolStore localSymbolStore;
|
||||
private SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext;
|
||||
|
||||
private SymbolServerTableModel tableModel;
|
||||
private GTable table;
|
||||
private JPanel additionalSearchLocationsPanel;
|
||||
private JPanel defaultConfigNotice;
|
||||
private GhidraFileChooser chooser;
|
||||
private Consumer<SymbolServerService> changeCallback;
|
||||
|
||||
private JButton refreshSearchLocationsStatusButton;
|
||||
private JButton moveLocationUpButton;
|
||||
private JButton moveLocationDownButton;
|
||||
private JButton deleteLocationButton;
|
||||
private JButton addLocationButton;
|
||||
private JPanel symbolStorageLocationPanel;
|
||||
private HintTextField symbolStorageLocationTextField;
|
||||
private JButton chooseSymbolStorageLocationButton;
|
||||
private JButton saveSearchLocationsButton;
|
||||
private boolean configChanged;
|
||||
|
||||
SymbolServerPanel(Consumer<SymbolServerService> changeCallback,
|
||||
SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext) {
|
||||
this.symbolServerInstanceCreatorContext = symbolServerInstanceCreatorContext;
|
||||
|
||||
build();
|
||||
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(this,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "Symbol Server Config"));
|
||||
|
||||
SymbolServerService temporarySymbolServerService =
|
||||
PdbPlugin.getSymbolServerService(symbolServerInstanceCreatorContext);
|
||||
if (temporarySymbolServerService.getSymbolStore() instanceof LocalSymbolStore) {
|
||||
setSymbolStorageLocation(
|
||||
((LocalSymbolStore) temporarySymbolServerService.getSymbolStore()).getRootDir(),
|
||||
false);
|
||||
}
|
||||
tableModel.addSymbolServers(temporarySymbolServerService.getSymbolServers());
|
||||
setConfigChanged(false);
|
||||
|
||||
this.changeCallback = changeCallback;
|
||||
}
|
||||
|
||||
private void build() {
|
||||
setLayout(new BorderLayout());
|
||||
setBorder(BorderFactory.createTitledBorder("Symbol Server Search Config"));
|
||||
|
||||
buildSymbolStorageLocationPanel();
|
||||
JPanel buttonPanel = buildButtonPanel();
|
||||
JScrollPane tableScrollPane = buildTable();
|
||||
defaultConfigNotice = new JPanel();
|
||||
defaultConfigNotice.add(
|
||||
new GHtmlLabel(
|
||||
"<html><center><font color=red><br>" +
|
||||
"Missing / invalid configuration.<br><br>" +
|
||||
"Using default search location:<br>" +
|
||||
"Program's Import Location<br>",
|
||||
SwingConstants.CENTER));
|
||||
defaultConfigNotice.setPreferredSize(tableScrollPane.getPreferredSize());
|
||||
|
||||
additionalSearchLocationsPanel = new JPanel();
|
||||
additionalSearchLocationsPanel
|
||||
.setLayout(new BoxLayout(additionalSearchLocationsPanel, BoxLayout.Y_AXIS));
|
||||
additionalSearchLocationsPanel.add(buttonPanel);
|
||||
additionalSearchLocationsPanel.add(tableScrollPane);
|
||||
|
||||
add(symbolStorageLocationPanel, BorderLayout.NORTH);
|
||||
add(additionalSearchLocationsPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private void updateLayout(boolean showTable) {
|
||||
if (showTable == (additionalSearchLocationsPanel.getParent() != null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
remove(additionalSearchLocationsPanel);
|
||||
remove(defaultConfigNotice);
|
||||
add(showTable ? additionalSearchLocationsPanel : defaultConfigNotice, BorderLayout.CENTER);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link SymbolServerService} instance representing the currently
|
||||
* displayed configuration, or null if the displayed configuration is not valid.
|
||||
*
|
||||
* @return new {@link SymbolServerService} or null
|
||||
*/
|
||||
SymbolServerService getSymbolServerService() {
|
||||
return (localSymbolStore != null)
|
||||
? new SymbolServerService(localSymbolStore, tableModel.getSymbolServers())
|
||||
: null;
|
||||
}
|
||||
|
||||
void setSymbolServers(List<SymbolServer> symbolServers) {
|
||||
tableModel.setSymbolServers(symbolServers);
|
||||
}
|
||||
|
||||
/**
|
||||
* The union of the changed status of the local storage path and the additional
|
||||
* search paths table model changed status.
|
||||
*
|
||||
* @return boolean true if the config has changed
|
||||
*/
|
||||
boolean isConfigChanged() {
|
||||
return configChanged || tableModel.isDataChanged();
|
||||
}
|
||||
|
||||
void setConfigChanged(boolean configChanged) {
|
||||
this.configChanged = configChanged;
|
||||
tableModel.setDataChanged(configChanged);
|
||||
}
|
||||
|
||||
private JScrollPane buildTable() {
|
||||
tableModel = new SymbolServerTableModel();
|
||||
table = new GTable(tableModel);
|
||||
table.setVisibleRowCount(4);
|
||||
table.setUserSortingEnabled(false);
|
||||
table.getSelectionManager().addListSelectionListener(e -> {
|
||||
updateButtonEnablement();
|
||||
});
|
||||
tableModel.addTableModelListener(e -> {
|
||||
updateButtonEnablement();
|
||||
fireChanged();
|
||||
});
|
||||
|
||||
TableColumn enabledColumn = table.getColumnModel().getColumn(0);
|
||||
enabledColumn.setResizable(false);
|
||||
enabledColumn.setPreferredWidth(32);
|
||||
enabledColumn.setMaxWidth(32);
|
||||
enabledColumn.setMinWidth(32);
|
||||
|
||||
TableColumn statusColumn = table.getColumnModel().getColumn(1);
|
||||
statusColumn.setResizable(false);
|
||||
statusColumn.setPreferredWidth(32);
|
||||
statusColumn.setMaxWidth(32);
|
||||
statusColumn.setMinWidth(32);
|
||||
|
||||
table.setPreferredScrollableViewportSize(new Dimension(100, 100));
|
||||
|
||||
return new JScrollPane(table);
|
||||
}
|
||||
|
||||
private JPanel buildButtonPanel() {
|
||||
refreshSearchLocationsStatusButton =
|
||||
ButtonPanelFactory.createImageButton(Icons.REFRESH_ICON, "Refresh Status",
|
||||
ButtonPanelFactory.ARROW_SIZE);
|
||||
refreshSearchLocationsStatusButton.addActionListener(e -> refreshSearchLocationStatus());
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(refreshSearchLocationsStatusButton,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
"SymbolServerConfig Refresh Status"));
|
||||
|
||||
moveLocationUpButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_UP_TYPE);
|
||||
moveLocationUpButton.addActionListener(e -> moveLocation(-1));
|
||||
moveLocationUpButton.setToolTipText("Move location up");
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(moveLocationUpButton,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
"SymbolServerConfig MoveUpDown"));
|
||||
|
||||
moveLocationDownButton =
|
||||
ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_DOWN_TYPE);
|
||||
moveLocationDownButton.addActionListener(e -> moveLocation(1));
|
||||
moveLocationDownButton.setToolTipText("Move location down");
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(moveLocationDownButton,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
"SymbolServerConfig MoveUpDown"));
|
||||
|
||||
deleteLocationButton = ButtonPanelFactory.createImageButton(Icons.DELETE_ICON, "Delete",
|
||||
ButtonPanelFactory.ARROW_SIZE);
|
||||
deleteLocationButton.addActionListener(e -> deleteLocation());
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(deleteLocationButton,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
"SymbolServerConfig Delete"));
|
||||
|
||||
addLocationButton = ButtonPanelFactory.createImageButton(Icons.ADD_ICON, "Add",
|
||||
ButtonPanelFactory.ARROW_SIZE);
|
||||
addLocationButton.addActionListener(e -> addLocation());
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(addLocationButton,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
"SymbolServerConfig Add"));
|
||||
|
||||
saveSearchLocationsButton =
|
||||
ButtonPanelFactory.createImageButton(Icons.get("images/disk.png"),
|
||||
"Save Configuration", ButtonPanelFactory.ARROW_SIZE);
|
||||
saveSearchLocationsButton.addActionListener(e -> saveConfig());
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(saveSearchLocationsButton,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC,
|
||||
"SymbolServerConfig Save"));
|
||||
|
||||
JPanel buttonPanel = new JPanel();
|
||||
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
|
||||
buttonPanel.add(new GLabel("Additional Search Paths:"));
|
||||
buttonPanel.add(Box.createHorizontalGlue());
|
||||
buttonPanel.add(addLocationButton);
|
||||
buttonPanel.add(deleteLocationButton);
|
||||
buttonPanel.add(moveLocationUpButton);
|
||||
buttonPanel.add(moveLocationDownButton);
|
||||
buttonPanel.add(refreshSearchLocationsStatusButton);
|
||||
buttonPanel.add(saveSearchLocationsButton);
|
||||
|
||||
return buttonPanel;
|
||||
}
|
||||
|
||||
private JPanel buildSymbolStorageLocationPanel() {
|
||||
symbolStorageLocationTextField = new HintTextField(" Required ");
|
||||
symbolStorageLocationTextField.setEditable(false);
|
||||
|
||||
chooseSymbolStorageLocationButton =
|
||||
ButtonPanelFactory.createButton(ButtonPanelFactory.BROWSE_TYPE);
|
||||
chooseSymbolStorageLocationButton.addActionListener(e -> chooseSymbolStorageLocation());
|
||||
|
||||
symbolStorageLocationPanel = new JPanel(new PairLayout(5, 5));
|
||||
GLabel symbolStorageLocLabel = new GLabel("Local Symbol Storage:", SwingConstants.RIGHT);
|
||||
symbolStorageLocLabel
|
||||
.setToolTipText("User-specified directory where PDB files are stored. Required.");
|
||||
symbolStorageLocationPanel.add(symbolStorageLocLabel);
|
||||
symbolStorageLocationPanel.add(LoadPdbDialog.join(null, symbolStorageLocationTextField,
|
||||
chooseSymbolStorageLocationButton));
|
||||
return symbolStorageLocationPanel;
|
||||
}
|
||||
|
||||
private void updateButtonEnablement() {
|
||||
boolean hasLocalSymbolStore = localSymbolStore != null;
|
||||
boolean singleRow = table.getSelectedRowCount() == 1;
|
||||
boolean moreThanOneRow = table.getRowCount() > 1;
|
||||
|
||||
refreshSearchLocationsStatusButton.setEnabled(hasLocalSymbolStore && !tableModel.isEmpty());
|
||||
moveLocationUpButton.setEnabled(hasLocalSymbolStore && singleRow && moreThanOneRow);
|
||||
moveLocationDownButton.setEnabled(hasLocalSymbolStore && singleRow && moreThanOneRow);
|
||||
addLocationButton.setEnabled(hasLocalSymbolStore);
|
||||
deleteLocationButton.setEnabled(hasLocalSymbolStore && table.getSelectedRowCount() > 0);
|
||||
saveSearchLocationsButton.setEnabled(hasLocalSymbolStore && isConfigChanged());
|
||||
updateLayout(hasLocalSymbolStore);
|
||||
}
|
||||
|
||||
StatusText getSymbolServerWarnings() {
|
||||
Map<String, String> warningsByLocation = new HashMap<>();
|
||||
for (WellKnownSymbolServerLocation ssloc : knownSymbolServers) {
|
||||
if (ssloc.getWarning() != null && !ssloc.getWarning().isBlank()) {
|
||||
warningsByLocation.put(ssloc.getLocation(), ssloc.getWarning());
|
||||
}
|
||||
}
|
||||
String warning = tableModel.getDataSource()
|
||||
.stream()
|
||||
.map(row -> warningsByLocation.get(row.getSymbolServer().getName()))
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.joining("<br>\n"));
|
||||
|
||||
return !warning.isEmpty() ? new StatusText(warning, MessageType.WARNING, false) : null;
|
||||
}
|
||||
|
||||
private void setSymbolStorageLocation(File symbolStorageDir, boolean allowGUIPrompt) {
|
||||
if (symbolStorageDir == null) {
|
||||
return;
|
||||
}
|
||||
if (!symbolStorageDir.exists()) {
|
||||
if (!allowGUIPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
int opt = OptionDialog.showOptionDialog(this, "Create Local Symbol Storage Directory?",
|
||||
"<html>Symbol storage directory<br>" +
|
||||
HTMLUtilities.escapeHTML(symbolStorageDir.getPath()) +
|
||||
"<br>does not exist. Create?",
|
||||
"Yes", OptionDialog.QUESTION_MESSAGE);
|
||||
if (opt == OptionDialog.CANCEL_OPTION) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FileUtilities.checkedMkdirs(symbolStorageDir);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, this, "Failure", "Failed to create symbol storage directory " +
|
||||
symbolStorageDir + ": " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowGUIPrompt && isEmptyDirectory(symbolStorageDir)) {
|
||||
if (OptionDialog.showYesNoDialog(this,
|
||||
"Initialize Symbol Storage Directory?",
|
||||
"<html>Initialize new directory as Microsoft symbol storage directory?") == OptionDialog.YES_OPTION) {
|
||||
try {
|
||||
LocalSymbolStore.create(symbolStorageDir,
|
||||
1 /* level1 MS symbol storage directory */);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, this, "Initialize Failure",
|
||||
"Failed to initialize symbol storage directory " + symbolStorageDir, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localSymbolStore =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(symbolStorageDir.getPath(), symbolServerInstanceCreatorContext,
|
||||
SymbolStore.class);
|
||||
symbolStorageLocationTextField.setText(symbolStorageDir.getPath());
|
||||
fireChanged();
|
||||
}
|
||||
|
||||
private void fireChanged() {
|
||||
if (changeCallback != null) {
|
||||
changeCallback.accept(getSymbolServerService());
|
||||
}
|
||||
}
|
||||
|
||||
private void chooseSymbolStorageLocation() {
|
||||
configChanged = true;
|
||||
setSymbolStorageLocation(getChooser().getSelectedFile(), true);
|
||||
updateButtonEnablement();
|
||||
}
|
||||
|
||||
private void importLocations() {
|
||||
String envVar = (String) JOptionPane.showInputDialog(this,
|
||||
"<html>Enter value:<br><br>Example: SVR*c:\\symbols*https://msdl.microsoft.com/download/symbols/<br><br>",
|
||||
"Enter Symbol Server Search Path Value", JOptionPane.QUESTION_MESSAGE, null, null,
|
||||
Objects.requireNonNullElse(System.getenv(MS_SYMBOLSERVER_ENVVAR), ""));
|
||||
if (envVar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> symbolServerPaths = getSymbolPathsFromEnvStr(envVar);
|
||||
if (!symbolServerPaths.isEmpty()) {
|
||||
// if the first item in the path list looks like a local symbol storage path,
|
||||
// allow the user to set it as the storage dir (and remove it from the elements
|
||||
// that will be added to the search list)
|
||||
String firstSearchPath = symbolServerPaths.get(0);
|
||||
SymbolServer symbolServer =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(firstSearchPath, symbolServerInstanceCreatorContext);
|
||||
if (symbolServer instanceof LocalSymbolStore &&
|
||||
((LocalSymbolStore) symbolServer).isValid()) {
|
||||
int choice = OptionDialog.showYesNoCancelDialog(this, "Set Symbol Storage Location",
|
||||
"Set symbol storage location to " + firstSearchPath + "?");
|
||||
if (choice == OptionDialog.CANCEL_OPTION) {
|
||||
return;
|
||||
}
|
||||
if (choice == OptionDialog.YES_OPTION) {
|
||||
symbolServerPaths.remove(0);
|
||||
configChanged = true;
|
||||
setSymbolStorageLocation(((LocalSymbolStore) symbolServer).getRootDir(), true);
|
||||
symbolStorageLocationTextField.setText(symbolServer.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tableModel.addSymbolServers(
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.createSymbolServersFromPathList(symbolServerPaths,
|
||||
symbolServerInstanceCreatorContext));
|
||||
fireChanged();
|
||||
}
|
||||
|
||||
private List<String> getSymbolPathsFromEnvStr(String envString) {
|
||||
// Expect the environment string to be in the MS symbol server search path form:
|
||||
// srv*[local cache]*[private symbol server]*https://msdl.microsoft.com/download/symbols
|
||||
// srv*c:\symbols*https://msdl.microsoft.com/download/symbols;srv*c:\additional*https://symbol.server.tld/
|
||||
String[] envParts = envString.split("[*;]");
|
||||
List<String> results = new ArrayList<>();
|
||||
Set<String> locationStringDeduplicationSet = new HashSet<>();
|
||||
for (int i = 0; i < envParts.length; i++) {
|
||||
String locationString = envParts[i].trim();
|
||||
if (!locationString.isBlank() && !locationString.equalsIgnoreCase("srv") &&
|
||||
!locationStringDeduplicationSet.contains(locationString)) {
|
||||
results.add(locationString);
|
||||
locationStringDeduplicationSet.add(locationString);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private void addLocation() {
|
||||
JPopupMenu menu = createAddLocationPopupMenu();
|
||||
menu.show(addLocationButton, 0, 0);
|
||||
}
|
||||
|
||||
private JPopupMenu createAddLocationPopupMenu() {
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
JMenuItem addDirMenuItem = new JMenuItem("Directory");
|
||||
addDirMenuItem.addActionListener(e -> addDirectoryLocation());
|
||||
menu.add(addDirMenuItem);
|
||||
|
||||
JMenuItem addURLMenuItem = new JMenuItem("URL");
|
||||
addURLMenuItem.addActionListener(e -> addUrlLocation());
|
||||
menu.add(addURLMenuItem);
|
||||
|
||||
JMenuItem addProgLocMenuItem =
|
||||
new JMenuItem(SameDirSymbolStore.PROGRAMS_IMPORT_LOCATION_DESCRIPTION_STR);
|
||||
addProgLocMenuItem.addActionListener(e -> addSameDirLocation());
|
||||
menu.add(addProgLocMenuItem);
|
||||
|
||||
JMenuItem importEnvMenuItem = new JMenuItem("Import _NT_SYMBOL_PATH");
|
||||
importEnvMenuItem.addActionListener(e -> importLocations());
|
||||
menu.add(importEnvMenuItem);
|
||||
|
||||
if (!knownSymbolServers.isEmpty()) {
|
||||
menu.add(new JSeparator());
|
||||
for (WellKnownSymbolServerLocation ssloc : knownSymbolServers) {
|
||||
JMenuItem mi = new JMenuItem(ssloc.getLocation());
|
||||
mi.addActionListener(e -> addKnownLocation(ssloc));
|
||||
mi.setToolTipText(" [from " + ssloc.getFileOrigin() + "]");
|
||||
menu.add(mi);
|
||||
}
|
||||
}
|
||||
DockingWindowManager.getHelpService()
|
||||
.registerHelp(menu,
|
||||
new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig_Add"));
|
||||
return menu;
|
||||
}
|
||||
|
||||
private void addSameDirLocation() {
|
||||
SameDirSymbolStore sameDirSymbolStore =
|
||||
new SameDirSymbolStore(symbolServerInstanceCreatorContext.getRootDir());
|
||||
tableModel.addSymbolServer(sameDirSymbolStore);
|
||||
}
|
||||
|
||||
private void addKnownLocation(WellKnownSymbolServerLocation ssloc) {
|
||||
SymbolServer symbolServer =
|
||||
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
|
||||
.newSymbolServer(ssloc.getLocation(), symbolServerInstanceCreatorContext);
|
||||
if (symbolServer != null) {
|
||||
tableModel.addSymbolServer(symbolServer);
|
||||
}
|
||||
}
|
||||
|
||||
private void addUrlLocation() {
|
||||
String urlLocationString = OptionDialog.showInputSingleLineDialog(this, "Enter URL",
|
||||
"Enter the URL of a Symbol Server: ", "https://");
|
||||
if (urlLocationString == null || urlLocationString.isBlank()) {
|
||||
return;
|
||||
}
|
||||
urlLocationString = urlLocationString.toLowerCase();
|
||||
if (!(urlLocationString.startsWith("http://") ||
|
||||
urlLocationString.startsWith("https://"))) {
|
||||
Msg.showWarn(this, this, "Bad URL", "Invalid URL: " + urlLocationString);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
HttpSymbolServer httpSymbolServer = new HttpSymbolServer(URI.create(urlLocationString));
|
||||
tableModel.addSymbolServer(httpSymbolServer);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.showWarn(this, this, "Bad URL", "Invalid URL: " + urlLocationString);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDirectoryLocation() {
|
||||
File dir = FilePromptDialog.chooseDirectory("Enter Path", "Symbol Storage Location: ",
|
||||
null);
|
||||
if (dir == null) {
|
||||
return;
|
||||
}
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
Msg.showError(this, this, "Bad path", "Invalid path: " + dir);
|
||||
return;
|
||||
}
|
||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(dir);
|
||||
tableModel.addSymbolServer(localSymbolStore);
|
||||
}
|
||||
|
||||
private void deleteLocation() {
|
||||
int selectedRow = table.getSelectedRow();
|
||||
tableModel.deleteRows(table.getSelectedRows());
|
||||
if (selectedRow >= 0 && selectedRow < table.getRowCount()) {
|
||||
table.selectRow(selectedRow);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveLocation(int delta) {
|
||||
if (table.getSelectedRowCount() == 1) {
|
||||
tableModel.moveRow(table.getSelectedRow(), delta);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshSearchLocationStatus() {
|
||||
tableModel.refreshSymbolServerLocationStatus();
|
||||
updateButtonEnablement();
|
||||
}
|
||||
|
||||
/* package */ void saveConfig() {
|
||||
SymbolServerService temporarySymbolServerService = getSymbolServerService();
|
||||
if (temporarySymbolServerService != null) {
|
||||
PdbPlugin.saveSymbolServerServiceConfig(temporarySymbolServerService);
|
||||
Preferences.store();
|
||||
setConfigChanged(false);
|
||||
fireChanged();
|
||||
updateButtonEnablement();
|
||||
}
|
||||
}
|
||||
|
||||
private GhidraFileChooser getChooser() {
|
||||
|
||||
if (chooser == null) {
|
||||
chooser = new GhidraFileChooser(this);
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
chooser.setApproveButtonText("Choose");
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||
chooser.setTitle("Select Symbol Storage Dir");
|
||||
}
|
||||
|
||||
return chooser;
|
||||
}
|
||||
|
||||
/* screen shot usage */ void pushAddLocationButton() {
|
||||
addLocation();
|
||||
}
|
||||
|
||||
/* screen shot usage */ void setSymbolStorageDirectoryTextOnly(String pathStr) {
|
||||
symbolStorageLocationTextField.setText(pathStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given file path is a directory that contains no files.
|
||||
* <p>
|
||||
*
|
||||
* @param directory path to a location on the file system
|
||||
* @return true if is a directory and it contains no files
|
||||
*/
|
||||
private static boolean isEmptyDirectory(File directory) {
|
||||
if (directory.isDirectory()) {
|
||||
File[] dirContents = directory.listFiles();
|
||||
return dirContents != null && dirContents.length == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver.ui;
|
||||
|
||||
import pdb.symbolserver.DisabledSymbolServer;
|
||||
import pdb.symbolserver.SymbolServer;
|
||||
|
||||
/**
|
||||
* Represents a row in the {@link SymbolServerTableModel}
|
||||
*/
|
||||
class SymbolServerRow {
|
||||
|
||||
public enum LocationStatus {
|
||||
UNKNOWN, VALID, INVALID
|
||||
}
|
||||
|
||||
private SymbolServer symbolServer;
|
||||
private LocationStatus status = LocationStatus.UNKNOWN;
|
||||
|
||||
SymbolServerRow(SymbolServer symbolServer) {
|
||||
this.symbolServer = symbolServer;
|
||||
}
|
||||
|
||||
SymbolServer getSymbolServer() {
|
||||
return symbolServer;
|
||||
}
|
||||
|
||||
void setSymbolServer(SymbolServer symbolServer) {
|
||||
this.symbolServer = symbolServer;
|
||||
}
|
||||
|
||||
boolean isEnabled() {
|
||||
return !(symbolServer instanceof DisabledSymbolServer);
|
||||
}
|
||||
|
||||
void setEnabled(boolean enabled) {
|
||||
if (isEnabled() == enabled) {
|
||||
return;
|
||||
}
|
||||
if (enabled) {
|
||||
DisabledSymbolServer dss = (DisabledSymbolServer) symbolServer;
|
||||
symbolServer = dss.getSymbolServer();
|
||||
}
|
||||
else {
|
||||
symbolServer = new DisabledSymbolServer(symbolServer);
|
||||
}
|
||||
}
|
||||
|
||||
LocationStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
void setStatus(LocationStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("SymbolServerRow: [ status: %s, server: %s]", status.toString(),
|
||||
symbolServer.toString());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,309 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver.ui;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.INVALID;
|
||||
import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.VALID;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.widgets.table.*;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.framework.plugintool.ServiceProviderStub;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import pdb.symbolserver.SymbolServer;
|
||||
import resources.Icons;
|
||||
|
||||
/**
|
||||
* Table model for the {@link SymbolServerPanel} table
|
||||
*/
|
||||
class SymbolServerTableModel
|
||||
extends GDynamicColumnTableModel<SymbolServerRow, List<SymbolServerRow>> {
|
||||
|
||||
private List<SymbolServerRow> rows = new ArrayList<>();
|
||||
private boolean dataChanged;
|
||||
|
||||
SymbolServerTableModel() {
|
||||
super(new ServiceProviderStub());
|
||||
setDefaultTableSortState(null);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return rows.isEmpty();
|
||||
}
|
||||
|
||||
void setSymbolServers(List<SymbolServer> symbolServers) {
|
||||
rows.clear();
|
||||
for (SymbolServer symbolServer : symbolServers) {
|
||||
rows.add(new SymbolServerRow(symbolServer));
|
||||
}
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
List<SymbolServer> getSymbolServers() {
|
||||
return rows.stream()
|
||||
.map(SymbolServerRow::getSymbolServer)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
void addSymbolServer(SymbolServer ss) {
|
||||
SymbolServerRow row = new SymbolServerRow(ss);
|
||||
rows.add(row);
|
||||
dataChanged = true;
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
void addSymbolServers(List<SymbolServer> symbolServers) {
|
||||
for (SymbolServer symbolServer : symbolServers) {
|
||||
rows.add(new SymbolServerRow(symbolServer));
|
||||
}
|
||||
dataChanged = true;
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
void deleteRows(int[] rowIndexes) {
|
||||
for (int i = rowIndexes.length - 1; i >= 0; i--) {
|
||||
rows.remove(rowIndexes[i]);
|
||||
}
|
||||
dataChanged = true;
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
void refreshSymbolServerLocationStatus() {
|
||||
List<SymbolServerRow> rowsCopy = new ArrayList<>(this.rows);
|
||||
TaskLauncher.launchNonModal("Refresh Symbol Server Location Status", monitor -> {
|
||||
monitor.initialize(rowsCopy.size());
|
||||
monitor.setMessage("Refreshing symbol server status");
|
||||
try {
|
||||
for (SymbolServerRow row : rowsCopy) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
monitor.setMessage("Checking " + row.getSymbolServer().getName());
|
||||
row.setStatus(row.getSymbolServer().isValid(monitor) ? VALID : INVALID);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Swing.runLater(SymbolServerTableModel.this::fireTableDataChanged);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void moveRow(int rowIndex, int deltaIndex) {
|
||||
int destIndex = rowIndex + deltaIndex;
|
||||
if (rowIndex < 0 || rowIndex >= rows.size() || destIndex < 0 || destIndex >= rows.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SymbolServerRow symbolServerRow1 = rows.get(rowIndex);
|
||||
SymbolServerRow symbolServerRow2 = rows.get(destIndex);
|
||||
rows.set(destIndex, symbolServerRow1);
|
||||
rows.set(rowIndex, symbolServerRow2);
|
||||
|
||||
dataChanged = true;
|
||||
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
boolean isDataChanged() {
|
||||
return dataChanged;
|
||||
}
|
||||
|
||||
void setDataChanged(boolean b) {
|
||||
this.dataChanged = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Symbol Server Locations";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SymbolServerRow> getModelData() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SymbolServerRow> getDataSource() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSortable(int columnIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||
DynamicTableColumn<SymbolServerRow, ?, ?> column = getColumn(columnIndex);
|
||||
if (column instanceof EnabledColumn) {
|
||||
SymbolServerRow row = getRowObject(rowIndex);
|
||||
row.setEnabled((Boolean) aValue);
|
||||
dataChanged = true;
|
||||
fireTableDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||
DynamicTableColumn<SymbolServerRow, ?, ?> column = getColumn(columnIndex);
|
||||
return column instanceof EnabledColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<SymbolServerRow> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<SymbolServerRow> descriptor = new TableColumnDescriptor<>();
|
||||
|
||||
descriptor.addVisibleColumn(new EnabledColumn());
|
||||
descriptor.addVisibleColumn(new StatusColumn());
|
||||
descriptor.addVisibleColumn(new LocationColumn());
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------
|
||||
|
||||
private static class StatusColumn extends
|
||||
AbstractDynamicTableColumnStub<SymbolServerRow, SymbolServerRow.LocationStatus> {
|
||||
|
||||
private static final Icon VALID_ICON = Icons.get("images/checkmark_green.gif");
|
||||
private static final Icon INVALID_ICON = Icons.ERROR_ICON;
|
||||
|
||||
private static Icon[] icons = new Icon[] { null, VALID_ICON, INVALID_ICON };
|
||||
private static String[] toolTips = new String[] { null, "Status: Ok", "Status: Failed" };
|
||||
|
||||
EnumIconColumnRenderer<SymbolServerRow.LocationStatus> renderer =
|
||||
new EnumIconColumnRenderer<>(SymbolServerRow.LocationStatus.class, icons, toolTips);
|
||||
|
||||
@Override
|
||||
public SymbolServerRow.LocationStatus getValue(SymbolServerRow rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnDisplayName(Settings settings) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Status";
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<SymbolServerRow.LocationStatus> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class EnabledColumn
|
||||
extends AbstractDynamicTableColumnStub<SymbolServerRow, Boolean> {
|
||||
|
||||
@Override
|
||||
public String getColumnDisplayName(Settings settings) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getValue(SymbolServerRow rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Enabled";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class LocationColumn
|
||||
extends AbstractDynamicTableColumnStub<SymbolServerRow, String> {
|
||||
|
||||
@Override
|
||||
public String getValue(SymbolServerRow rowObject, Settings settings,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.getSymbolServer().getDescriptiveName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Location";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 250;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Table column renderer to render an enum value as a icon
|
||||
*
|
||||
* @param <E> enum type
|
||||
*/
|
||||
private static class EnumIconColumnRenderer<E extends Enum<E>>
|
||||
extends AbstractGColumnRenderer<E> {
|
||||
|
||||
private Icon[] icons;
|
||||
private String[] toolTips;
|
||||
|
||||
EnumIconColumnRenderer(Class<E> enumClass, Icon[] icons, String[] toolTips) {
|
||||
if (enumClass.getEnumConstants().length != icons.length ||
|
||||
icons.length != toolTips.length) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.icons = icons;
|
||||
this.toolTips = toolTips;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
JLabel renderer = (JLabel) super.getTableCellRendererComponent(data);
|
||||
|
||||
E e = (E) data.getValue();
|
||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
renderer.setText("");
|
||||
renderer.setIcon(e != null ? icons[e.ordinal()] : null);
|
||||
renderer.setToolTipText(e != null ? toolTips[e.ordinal()] : null);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getText(Object value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(E t, Settings settings) {
|
||||
return t == null ? "" : t.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.Msg;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Represents a well-known symbol server location.
|
||||
* <p>
|
||||
* See the PDB_SYMBOL_SERVER_URLS.pdburl file.
|
||||
*/
|
||||
class WellKnownSymbolServerLocation {
|
||||
private String locationCategory;
|
||||
private String location;
|
||||
private String warning;
|
||||
private String fileOrigin;
|
||||
|
||||
WellKnownSymbolServerLocation(String location, String locationCategory, String warning,
|
||||
String fileOrigin) {
|
||||
this.location = location;
|
||||
this.locationCategory = locationCategory;
|
||||
this.warning = warning;
|
||||
this.fileOrigin = fileOrigin;
|
||||
}
|
||||
|
||||
String getLocationCategory() {
|
||||
return locationCategory;
|
||||
}
|
||||
|
||||
String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
String getWarning() {
|
||||
return warning;
|
||||
}
|
||||
|
||||
String getFileOrigin() {
|
||||
return fileOrigin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(location, locationCategory, warning);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
WellKnownSymbolServerLocation other = (WellKnownSymbolServerLocation) obj;
|
||||
return Objects.equals(location, other.location) &&
|
||||
Objects.equals(locationCategory, other.locationCategory) &&
|
||||
Objects.equals(warning, other.warning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all symbol server location files (*.pdburl) and returns a list of entries.
|
||||
*
|
||||
* @return list of {@link WellKnownSymbolServerLocation} elements
|
||||
*/
|
||||
public static List<WellKnownSymbolServerLocation> loadAll() {
|
||||
List<ResourceFile> pdbUrlFiles = Application.findFilesByExtensionInApplication(".pdburl");
|
||||
|
||||
List<WellKnownSymbolServerLocation> results = new ArrayList<>();
|
||||
for (ResourceFile file : pdbUrlFiles) {
|
||||
try {
|
||||
List<String> lines = FileUtilities.getLines(file);
|
||||
for (String line : lines) {
|
||||
// format: location_category|location_string|warning_string
|
||||
// example: "Internet|https://msdl.microsoft.com/download/symbols|Warning: be careful!"
|
||||
String[] fields = line.split("\\|");
|
||||
if (fields.length > 1) {
|
||||
results.add(new WellKnownSymbolServerLocation(fields[1], fields[0],
|
||||
fields.length > 2 ? fields[2] : null, file.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.warn(WellKnownSymbolServerLocation.class, "Unable to read pdburl file: " + file);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
@ -15,17 +15,16 @@
|
||||
*/
|
||||
package ghidra.app.util.bin.format.pdb;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.app.services.DataTypeManagerService;
|
||||
import ghidra.app.util.bin.format.pdb.PdbParser.PdbFileType;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
@ -35,79 +34,34 @@ import ghidra.program.model.listing.FunctionManager;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
public class PdbParserTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
|
||||
private ProgramBuilder builder;
|
||||
|
||||
private static String notepadGUID = "36cfd5f9-888c-4483-b522-b9db242d8478";
|
||||
private static final String notepadGUID = "36cfd5f9-888c-4483-b522-b9db242d8478";
|
||||
private static final String programBasename = "notepad";
|
||||
|
||||
// Note: this is in hex. Code should translate it to decimal when creating GUID/Age folder name
|
||||
private static String notepadAge = "21";
|
||||
|
||||
// Name of subfolder that stores the actual PDB file
|
||||
private static String guidAgeCombo = "36CFD5F9888C4483B522B9DB242D847833";
|
||||
private String programBasename = "notepad";
|
||||
private static final String notepadAge = "21";
|
||||
|
||||
private File tempDir, fileLocation;
|
||||
private Program testProgram;
|
||||
//private static String guidAgeCombo = PdbParserNEW.getGuidAgeString(notepadGUID, notepadAge);
|
||||
|
||||
// Bogus symbol repository directory or null directory should not break anything
|
||||
private static final File noSuchSymbolsRepoDir = null;
|
||||
|
||||
private String pdbFilename, pdbXmlFilename;
|
||||
private String exeFolderName = "exe", pdbXmlFolderName = "pdb_xml",
|
||||
symbolsFolderName = "symbols";
|
||||
|
||||
private File pdbFile = null, pdbXmlFile = null, pdbXmlDir = null, symbolsFolder = null;
|
||||
|
||||
private List<File> createdFiles;
|
||||
|
||||
enum PdbLocation {
|
||||
NONE, SYMBOLS_SUBDIR, SYMBOLS_NO_SUBDIR, SAME_AS_EXE_SUBDIR, SAME_AS_EXE_NO_SUBDIR
|
||||
}
|
||||
|
||||
enum PdbXmlLocation {
|
||||
NONE, SAME_AS_PDB, OWN_DIR
|
||||
}
|
||||
|
||||
TestFunction[] programFunctions =
|
||||
new TestFunction[] { new TestFunction("function1", "0x110", "0x35") };
|
||||
|
||||
public PdbParserTest() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
// Get temp directory in which to store files
|
||||
String tempDirPath = getTestDirectoryPath();
|
||||
tempDir = new File(tempDirPath);
|
||||
|
||||
fileLocation = new File(tempDir, exeFolderName);
|
||||
|
||||
tempDir = createTempDirectory("pdb_parser");
|
||||
fileLocation = new File(tempDir, "exe");
|
||||
testProgram = buildProgram(fileLocation.getAbsolutePath());
|
||||
|
||||
pdbFilename = programBasename + ".pdb";
|
||||
pdbXmlFilename = pdbFilename + PdbFileType.XML.toString();
|
||||
|
||||
createdFiles = null;
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
|
||||
if (fileLocation != null) {
|
||||
FileUtilities.deleteDir(fileLocation);
|
||||
}
|
||||
if (createdFiles != null) {
|
||||
deleteCreatedFiles(createdFiles);
|
||||
}
|
||||
|
||||
System.gc();
|
||||
pdbXmlFilename = programBasename + ".pdb.xml";
|
||||
}
|
||||
|
||||
private Program buildProgram(String exeLocation) throws Exception {
|
||||
@ -138,838 +92,10 @@ public class PdbParserTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
return currentTestProgram;
|
||||
}
|
||||
|
||||
private List<File> createFiles(PdbLocation pdbLoc, PdbXmlLocation pdbXmlLoc) {
|
||||
|
||||
pdbFile = null;
|
||||
pdbXmlFile = null;
|
||||
|
||||
pdbXmlDir = new File(tempDir, pdbXmlFolderName);
|
||||
|
||||
List<File> filesCreated = new ArrayList<>();
|
||||
|
||||
createDirectory(fileLocation);
|
||||
filesCreated.add(fileLocation);
|
||||
|
||||
File subDir, subSubDir;
|
||||
|
||||
switch (pdbLoc) {
|
||||
// Put PDB file in the /symbols/ folder with subdirectories that
|
||||
// include the PDB name and GUID. I.e., the full path to the PDB is:
|
||||
// <temp>/symbols/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/notepad.pdb
|
||||
case SYMBOLS_SUBDIR:
|
||||
symbolsFolder = new File(tempDir, symbolsFolderName);
|
||||
createDirectory(symbolsFolder);
|
||||
filesCreated.add(symbolsFolder);
|
||||
|
||||
subDir = new File(symbolsFolder, pdbFilename);
|
||||
createDirectory(subDir);
|
||||
filesCreated.add(subDir);
|
||||
|
||||
subSubDir = new File(subDir, guidAgeCombo);
|
||||
createDirectory(subSubDir);
|
||||
filesCreated.add(subSubDir);
|
||||
|
||||
pdbFile = new File(subSubDir, pdbFilename);
|
||||
break;
|
||||
|
||||
// Put the PDB file directly into the /symbols folder.
|
||||
// I.e., <temp>/symbols/notepad.pdb
|
||||
case SYMBOLS_NO_SUBDIR:
|
||||
symbolsFolder = new File(tempDir, symbolsFolderName);
|
||||
createDirectory(symbolsFolder);
|
||||
filesCreated.add(symbolsFolder);
|
||||
|
||||
pdbFile = new File(symbolsFolder, pdbFilename);
|
||||
break;
|
||||
|
||||
// Put the PDB file in the same folder as the binary with subdirectories that
|
||||
// include the PDB name and GUID.
|
||||
// I.e., <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/notepad.pdb
|
||||
case SAME_AS_EXE_SUBDIR:
|
||||
subDir = new File(fileLocation, pdbFilename);
|
||||
createDirectory(subDir);
|
||||
filesCreated.add(subDir);
|
||||
|
||||
subSubDir = new File(subDir, guidAgeCombo);
|
||||
createDirectory(subSubDir);
|
||||
filesCreated.add(subSubDir);
|
||||
|
||||
pdbFile = new File(subSubDir, pdbFilename);
|
||||
break;
|
||||
|
||||
// Put the PDB file in the same folder as the binary
|
||||
// I.e., <temp>/exe/notepad.pdb
|
||||
case SAME_AS_EXE_NO_SUBDIR:
|
||||
pdbFile = new File(fileLocation, pdbFilename);
|
||||
break;
|
||||
|
||||
case NONE:
|
||||
// Do nothing
|
||||
break;
|
||||
|
||||
default:
|
||||
fail("Unrecognized pdbLocation choice: " + pdbLoc);
|
||||
}
|
||||
|
||||
if (pdbFile != null) {
|
||||
createFile(pdbFile);
|
||||
filesCreated.add(pdbFile);
|
||||
}
|
||||
|
||||
switch (pdbXmlLoc) {
|
||||
// Put the PDB XML file in the same location as the PDB file
|
||||
case SAME_AS_PDB:
|
||||
if (pdbFile != null) {
|
||||
pdbXmlFile = new File(pdbFile.getParentFile(), pdbXmlFilename);
|
||||
}
|
||||
else {
|
||||
fail("Does not make sense to create a .pdb.xml file in the same directory as " +
|
||||
"the .pdb file when there is no .pdb file!");
|
||||
}
|
||||
break;
|
||||
|
||||
// Put the PDB XML file in the <temp>/pdb_xml directory
|
||||
case OWN_DIR:
|
||||
// Create directory that will server as .pdb.xml location
|
||||
createDirectory(pdbXmlDir);
|
||||
filesCreated.add(pdbXmlDir);
|
||||
|
||||
pdbXmlFile = new File(pdbXmlDir, pdbXmlFilename);
|
||||
break;
|
||||
|
||||
// Do not create a PDB XML file
|
||||
case NONE:
|
||||
break;
|
||||
|
||||
default:
|
||||
fail("Unrecognized pdbXmlLocation choice: " + pdbXmlLoc);
|
||||
|
||||
}
|
||||
|
||||
if (pdbXmlFile != null) {
|
||||
createFile(pdbXmlFile);
|
||||
filesCreated.add(pdbXmlFile);
|
||||
}
|
||||
|
||||
verifyFilesCreated(pdbLoc, pdbXmlLoc);
|
||||
|
||||
return filesCreated;
|
||||
}
|
||||
|
||||
private void verifyFilesCreated(PdbLocation pdbLoc, PdbXmlLocation pdbXmlLoc) {
|
||||
|
||||
File expectedDir1, expectedDir2;
|
||||
|
||||
switch (pdbLoc) {
|
||||
case NONE:
|
||||
assertNull(pdbFile);
|
||||
break;
|
||||
|
||||
case SAME_AS_EXE_SUBDIR:
|
||||
assertNotNull(pdbFile);
|
||||
expectedDir1 = new File(fileLocation, pdbFilename);
|
||||
expectedDir2 = new File(expectedDir1, guidAgeCombo);
|
||||
assertEquals(expectedDir2, pdbFile.getParentFile());
|
||||
break;
|
||||
|
||||
case SAME_AS_EXE_NO_SUBDIR:
|
||||
assertNotNull(pdbFile);
|
||||
assertEquals(fileLocation, pdbFile.getParentFile());
|
||||
break;
|
||||
|
||||
case SYMBOLS_SUBDIR:
|
||||
assertNotNull(pdbFile);
|
||||
expectedDir1 = new File(symbolsFolder, pdbFilename);
|
||||
expectedDir2 = new File(expectedDir1, guidAgeCombo);
|
||||
assertEquals(expectedDir2, pdbFile.getParentFile());
|
||||
break;
|
||||
|
||||
case SYMBOLS_NO_SUBDIR:
|
||||
assertNotNull(pdbFile);
|
||||
assertEquals(symbolsFolder, pdbFile.getParentFile());
|
||||
break;
|
||||
|
||||
default:
|
||||
fail("Unrecognized pdbLocation choice: " + pdbLoc);
|
||||
}
|
||||
|
||||
switch (pdbXmlLoc) {
|
||||
case SAME_AS_PDB:
|
||||
assertNotNull(pdbXmlFile);
|
||||
assertEquals(pdbXmlFile.getParentFile(), pdbFile.getParentFile());
|
||||
break;
|
||||
|
||||
case OWN_DIR:
|
||||
assertNotNull(pdbXmlFile);
|
||||
assertEquals(pdbXmlFile.getParentFile(), pdbXmlDir);
|
||||
break;
|
||||
|
||||
case NONE:
|
||||
assertNull(pdbXmlFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteCreatedFiles(List<File> filesToDelete) {
|
||||
// Delete in the reverse order the files were added
|
||||
for (int i = filesToDelete.size() - 1; i >= 0; i--) {
|
||||
filesToDelete.get(i).delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/symbols/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* notepad.pdb.xml does not exist
|
||||
* Repo location left alone (default)
|
||||
*
|
||||
* PDB file should not be found, since default repo does not point to symbols folder.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb1() throws Exception {
|
||||
|
||||
createdFiles = null;
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SYMBOLS_SUBDIR, PdbXmlLocation.NONE);
|
||||
File pdb = PdbParser.findPDB(testProgram, false, noSuchSymbolsRepoDir);
|
||||
|
||||
// Should not find anything since repo is set to an invalid path
|
||||
assertNull(pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/symbols/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* notepad.pdb.xml does not exist
|
||||
* Repo location set to <temp>/symbols
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, Pdb file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb2() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SYMBOLS_SUBDIR, PdbXmlLocation.NONE);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, symbolsFolder);
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdbFile, pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/symbols/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* notepad.pdb.xml is here: <temp>/symbols/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* Repo location set to <temp>/symbols
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB XML file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb3() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SYMBOLS_SUBDIR, PdbXmlLocation.SAME_AS_PDB);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, symbolsFolder);
|
||||
|
||||
assertNotNull(pdb);
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
assertEquals(pdbFile, pdb);
|
||||
}
|
||||
else {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/symbols/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to <temp>/symbols
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB files should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb4() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SYMBOLS_SUBDIR, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, symbolsFolder);
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdb, pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/symbols/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to <temp>/pdb_xml
|
||||
*
|
||||
* On Windows, PDB XML file should be found.
|
||||
* On non-Windows, PDB XML file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb5() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SYMBOLS_SUBDIR, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbXmlDir);
|
||||
|
||||
assertNotNull(pdb);
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
else {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/symbols/
|
||||
* notepad.pdb.xml does not exist
|
||||
* Repo location set to <temp>/symbols
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb6() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SYMBOLS_NO_SUBDIR, PdbXmlLocation.NONE);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, symbolsFolder);
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdbFile, pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/symbols/
|
||||
* notepad.pdb.xml does not exist
|
||||
* Repo location set to <temp>/pdb_xml/
|
||||
*
|
||||
* PDB file should not be found, since default repo does not point to symbols folder.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb7() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SYMBOLS_NO_SUBDIR, PdbXmlLocation.NONE);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbXmlDir);
|
||||
|
||||
// Should not find anything since repo is set to an invalid path
|
||||
assertNull(pdb);
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/symbols/
|
||||
* notepad.pdb.xml is here: <temp>/symbols/
|
||||
* Repo location set to <temp>/symbols
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB XML file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb8() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SYMBOLS_NO_SUBDIR, PdbXmlLocation.SAME_AS_PDB);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, symbolsFolder);
|
||||
|
||||
assertNotNull(pdb);
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
assertEquals(pdbFile, pdb);
|
||||
}
|
||||
else {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/symbols/
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to <temp>/symbols
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb9() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SYMBOLS_NO_SUBDIR, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, symbolsFolder);
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdbFile, pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/symbols/
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to <temp>/pdb_xml
|
||||
*
|
||||
* On Windows, PDB XML file should be found.
|
||||
* On non-Windows, PDB XML file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb10() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SYMBOLS_NO_SUBDIR, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbXmlDir);
|
||||
|
||||
assertNotNull(pdb);
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
else {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* notepad.pdb.xml does not exist
|
||||
* Repo location set to <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
*
|
||||
* On Windows, PDB file should be found
|
||||
* On non-Windows, PDB file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb11() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_SUBDIR, PdbXmlLocation.NONE);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbFile.getParentFile());
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdbFile, pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* notepad.pdb.xml is here: <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* Repo location set to default location
|
||||
*
|
||||
* PDB file should not be found, since default repo does not point to exe folder.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb12() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_SUBDIR, PdbXmlLocation.SAME_AS_PDB);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, noSuchSymbolsRepoDir);
|
||||
|
||||
// Should not find anything since repo is set to an invalid path
|
||||
assertNull(pdb);
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* notepad.pdb.xml is here: <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* Repo location set to <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB XML file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb13() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_SUBDIR, PdbXmlLocation.SAME_AS_PDB);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbFile.getParentFile());
|
||||
|
||||
assertNotNull(pdb);
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
assertEquals(pdbFile, pdb);
|
||||
}
|
||||
else {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb14() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_SUBDIR, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbFile.getParentFile());
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdbFile, pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/notepad.pdb/36CFD5F9888C4483B522B9DB242D84782/
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to <temp>/pdb_xml/
|
||||
*
|
||||
* On Windows, PDB XML file should be found.
|
||||
* On non-Windows, PDB XML file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb15() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_SUBDIR, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbXmlFile.getParentFile());
|
||||
|
||||
assertNotNull(pdb);
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
else {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/
|
||||
* notepad.pdb.xml does not exist
|
||||
* Repo location set to default location
|
||||
*
|
||||
* Special case: Even if repo location is set to a location that doesn't contain a .pdb or
|
||||
* .pdb.xml file, it will still be found if there is a file in the same folder as the binary
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb16() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_NO_SUBDIR, PdbXmlLocation.NONE);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, noSuchSymbolsRepoDir);
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdbFile, pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/
|
||||
* notepad.pdb.xml does not exist
|
||||
* Repo location set to <temp>/exe/
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb17() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_NO_SUBDIR, PdbXmlLocation.NONE);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbFile.getParentFile());
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdbFile, pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/
|
||||
* notepad.pdb.xml does not exist
|
||||
* Repo location set to <temp>/pdb_xml/
|
||||
*
|
||||
* Special case: Even if repo location is set to a location that doesn't contain a .pdb or
|
||||
* .pdb.xml file, it will still be found if there is a file in the same folder as the binary
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb18() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_NO_SUBDIR, PdbXmlLocation.NONE);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbXmlDir);
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdbFile, pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/
|
||||
* notepad.pdb.xml is here: <temp>/exe/
|
||||
* Repo location set to the default location
|
||||
*
|
||||
* Special case: Even if repo location is set to a location that doesn't contain a .pdb or
|
||||
* .pdb.xml file, it will still be found if there is a file in the same folder as the binary
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB XML file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb19() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_NO_SUBDIR, PdbXmlLocation.SAME_AS_PDB);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, noSuchSymbolsRepoDir);
|
||||
|
||||
assertNotNull(pdb);
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
assertEquals(pdbFile, pdb);
|
||||
}
|
||||
else {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/
|
||||
* notepad.pdb.xml is here: <temp>/exe/
|
||||
* Repo location set to <temp>/exe/
|
||||
*
|
||||
* On Windows, PDB file should be found.
|
||||
* On non-Windows, PDB XML file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb20() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_NO_SUBDIR, PdbXmlLocation.SAME_AS_PDB);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbFile.getParentFile());
|
||||
|
||||
assertNotNull(pdb);
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
assertEquals(pdbFile, pdb);
|
||||
}
|
||||
else {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to default location
|
||||
*
|
||||
* On Windows, PDB file should be found (Repo path is invalid, so it looks in same location as exe).
|
||||
* On non-Windows, PDB file should be found (Repo path is invalid, so it looks in same location as exe).
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb21() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_NO_SUBDIR, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, noSuchSymbolsRepoDir);
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdbFile, pdb);
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to <temp>/exe/
|
||||
*
|
||||
* On Windows, PDB file should be found
|
||||
* On non-Windows, PDB file should be found.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb22() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_NO_SUBDIR, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbFile.getParentFile());
|
||||
|
||||
assertNotNull(pdb);
|
||||
assertEquals(pdbFile, pdb);
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb is here: <temp>/exe/
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to <temp>/pdb_xml/
|
||||
*
|
||||
* Special case: Even if repo location is set to a location that doesn't contain a .pdb or
|
||||
* .pdb.xml file, it will still be found if there is a file in the same folder as the binary
|
||||
*
|
||||
* On Windows, PDB XML file should be found (looks first in user-defined repo location)
|
||||
* On non-Windows, PDB XML file should be found
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb23() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.SAME_AS_EXE_NO_SUBDIR, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbXmlDir);
|
||||
|
||||
assertNotNull(pdb);
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
else {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb does not exist
|
||||
* notepad.pdb.xml does not exist
|
||||
* Repo location set to the default location
|
||||
*
|
||||
* On Windows, no file should be found
|
||||
* On non-Windows, no file should be found
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb24() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.NONE, PdbXmlLocation.NONE);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, noSuchSymbolsRepoDir);
|
||||
assertNull(pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb does not exist
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to the default location
|
||||
*
|
||||
* On Windows, no file should be found
|
||||
* On non-Windows, no file should be found
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb25() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.NONE, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, noSuchSymbolsRepoDir);
|
||||
assertNull(pdb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* notepad.pdb does not exist
|
||||
* notepad.pdb.xml is here: <temp>/pdb_xml/
|
||||
* Repo location set to <temp>/pdb_xml/
|
||||
*
|
||||
* On Windows, PDB XML file should be found
|
||||
* On non-Windows, PDB XML file should be found
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testFindPdb26() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.NONE, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbXmlDir);
|
||||
|
||||
assertNotNull(pdb);
|
||||
|
||||
if (PdbParser.onWindows) {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
else {
|
||||
assertEquals(pdbXmlFile, pdb);
|
||||
}
|
||||
}
|
||||
|
||||
private void createDirectory(File directory) {
|
||||
directory.mkdir();
|
||||
if (!directory.isDirectory()) {
|
||||
fail("Should have created directory: " + directory);
|
||||
}
|
||||
}
|
||||
|
||||
private void createFile(File file) {
|
||||
boolean createSuccess;
|
||||
|
||||
try {
|
||||
createSuccess = file.createNewFile();
|
||||
|
||||
if (!createSuccess) {
|
||||
fail("Failed creation of file: " + file);
|
||||
}
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
fail("Exception while creating: " + file);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildPdbXml() throws Exception {
|
||||
// Write to the pdb.xml file
|
||||
FileWriter xmlFileWriter;
|
||||
|
||||
try {
|
||||
xmlFileWriter = new FileWriter(pdbXmlFile);
|
||||
BufferedWriter xmlBuffWriter = new BufferedWriter(xmlFileWriter);
|
||||
private File buildPdbXml() throws IOException {
|
||||
File destFile = new File(tempDir, pdbXmlFilename);
|
||||
try (BufferedWriter xmlBuffWriter = new BufferedWriter(new FileWriter(destFile))) {
|
||||
|
||||
xmlBuffWriter.write("<pdb file=\"" + pdbFilename + "\" exe=\"" + programBasename +
|
||||
"\" guid=\"{" + notepadGUID.toUpperCase() + "}\" age=\"" +
|
||||
@ -993,28 +119,21 @@ public class PdbParserTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
// xmlBuffWriter.write("<tables></tables>");
|
||||
|
||||
xmlBuffWriter.write("</pdb>\n");
|
||||
xmlBuffWriter.close();
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
fail("IOException writing to temporary file (" + pdbXmlFile + "). " +
|
||||
ioe.toString());
|
||||
}
|
||||
|
||||
return destFile;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyFunctions() throws Exception {
|
||||
|
||||
createdFiles = createFiles(PdbLocation.NONE, PdbXmlLocation.OWN_DIR);
|
||||
|
||||
buildPdbXml();
|
||||
|
||||
File pdb = PdbParser.findPDB(testProgram, false, pdbXmlDir);
|
||||
File pdbXmlFile = buildPdbXml();
|
||||
|
||||
AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(testProgram);
|
||||
DataTypeManagerService dataTypeManagerService = mgr.getDataTypeManagerService();
|
||||
PdbParser parser =
|
||||
new PdbParser(pdb, testProgram, dataTypeManagerService, false, TaskMonitor.DUMMY);
|
||||
new PdbParser(pdbXmlFile, testProgram, dataTypeManagerService, false, false,
|
||||
TaskMonitor.DUMMY);
|
||||
|
||||
parser.openDataTypeArchives();
|
||||
parser.parse();
|
||||
|
@ -0,0 +1,85 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A "remote" symbol server that answers affirmatively for any query.
|
||||
*/
|
||||
public class DummySymbolServer implements SymbolServer {
|
||||
|
||||
private final byte[] dummyPayload;
|
||||
private final boolean returnCompressedFilenames;
|
||||
|
||||
public DummySymbolServer(String dummyPayload) {
|
||||
this(dummyPayload.getBytes(), false);
|
||||
}
|
||||
|
||||
public DummySymbolServer(byte[] dummyPayload, boolean returnCompressedFilenames) {
|
||||
this.dummyPayload = dummyPayload;
|
||||
this.returnCompressedFilenames = returnCompressedFilenames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "dummy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(TaskMonitor monitor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String filename, TaskMonitor monitor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SymbolFileLocation> find(SymbolFileInfo pdbInfo, Set<FindOption> findOptions,
|
||||
TaskMonitor monitor) {
|
||||
String name = pdbInfo.getName();
|
||||
if (returnCompressedFilenames) {
|
||||
name = name.substring(0, name.length() - 1) + "_";
|
||||
}
|
||||
SymbolFileLocation symLoc = new SymbolFileLocation(name, this, pdbInfo);
|
||||
return List.of(symLoc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolServerInputStream getFileStream(String filename, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
return new SymbolServerInputStream(new ByteArrayInputStream(dummyPayload),
|
||||
dummyPayload.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileLocation(String filename) {
|
||||
return "dummy-" + filename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class HttpSymbolServerTest {
|
||||
|
||||
//@Test
|
||||
public void test() {
|
||||
// This test is not enabled by default as it depends on an third-party resource
|
||||
HttpSymbolServer httpSymbolServer =
|
||||
new HttpSymbolServer(URI.create("http://msdl.microsoft.com/download/symbols/"));
|
||||
SymbolFileInfo pdbInfo =
|
||||
SymbolFileInfo.fromValues("kernelbase.pdb", "C1C44EDD93E1B8BA671874B5C1490C2D", 1);
|
||||
|
||||
List<SymbolFileLocation> results =
|
||||
httpSymbolServer.find(pdbInfo, FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
||||
assertEquals(1, results.size());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,225 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Test searching for symbol files in a local directory structure.
|
||||
* <p>
|
||||
* Directory level 1, 2 are MS compatible layouts of pdb symbol files.
|
||||
* Directory level 0 is a ghidra-ism where pdb symbol files can
|
||||
* be found in a un-organized directory with non-exact file names.
|
||||
* <p>
|
||||
* Testing level 0 searching is a TODO item because creating test
|
||||
* files that can be parsed isn't possible right now. (level 1, 2
|
||||
* directories can skip parsing the file since the guid/age is
|
||||
* in the path)
|
||||
*/
|
||||
public class LocalSymbolServerTest extends AbstractGenericTest {
|
||||
|
||||
private File temporaryDir;
|
||||
|
||||
private File mkFile(File file) throws IOException {
|
||||
FileUtilities.checkedMkdirs(file.getParentFile());
|
||||
FileUtilities.writeStringToFile(file, "test");
|
||||
return file;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
temporaryDir = createTempDirectory("localsymbolserver");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_Level0() throws IOException {
|
||||
File root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(root, 0);
|
||||
|
||||
assertTrue("Should not create files", root.list().length == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_Level1() throws IOException {
|
||||
File root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(root, 1);
|
||||
|
||||
assertTrue("Pingme should exist", new File(root, "pingme.txt").exists());
|
||||
assertTrue("Admin dir should exist", new File(root, "000admin").exists());
|
||||
assertFalse("Index2 should not exist", new File(root, "index2.txt").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate_Level2() throws IOException {
|
||||
File root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(root, 2);
|
||||
|
||||
assertTrue("Pingme should exist", new File(root, "pingme.txt").exists());
|
||||
assertTrue("Admin dir should exist", new File(root, "000admin").exists());
|
||||
assertTrue("Index2 should exist", new File(root, "index2.txt").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findExact_Level1() throws IOException {
|
||||
File root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(root, 1);
|
||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(root);
|
||||
|
||||
File pdbFile = mkFile(new File(root, "file1.pdb/112233445/file1.pdb"));
|
||||
mkFile(new File(root, "file1.pdb/112233446/file1.pdb"));
|
||||
|
||||
List<SymbolFileLocation> results =
|
||||
localSymbolStore.find(SymbolFileInfo.fromValues("file1.pdb", "11223344", 5),
|
||||
FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(1, results.size());
|
||||
|
||||
String resultLocation = localSymbolStore.getFileLocation(results.get(0).getPath());
|
||||
assertEquals(pdbFile.getPath(), resultLocation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAnyAges_Level1() throws IOException {
|
||||
// find pdbs with the same UID, but any AGE
|
||||
File root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(root, 1);
|
||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(root);
|
||||
|
||||
mkFile(new File(root, "file1.pdb/112233445/file1.pdb"));
|
||||
mkFile(new File(root, "file1.pdb/112233446/file1.pdb"));
|
||||
mkFile(new File(root, "file1.pdb/112233450/file1.pdb"));
|
||||
|
||||
List<SymbolFileLocation> results =
|
||||
localSymbolStore.find(SymbolFileInfo.fromValues("file1.pdb", "11223344", 0),
|
||||
FindOption.of(FindOption.ANY_AGE), TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(2, results.size());
|
||||
assertFalse(results.stream()
|
||||
.map(symbolFileLocation -> symbolFileLocation.getFileInfo().getUniqueName())
|
||||
.anyMatch(s -> !s.equals("11223344")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAnyUIDs_Level1() throws IOException {
|
||||
File root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(root, 1);
|
||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(root);
|
||||
|
||||
mkFile(new File(root, "file1.pdb/112233400/file1.pdb"));
|
||||
mkFile(new File(root, "file1.pdb/112233410/file1.pdb"));
|
||||
mkFile(new File(root, "file1.pdb/112233420/file1.pdb"));
|
||||
|
||||
List<SymbolFileLocation> results =
|
||||
localSymbolStore.find(SymbolFileInfo.fromValues("file1.pdb", "11223344", 0),
|
||||
FindOption.of(FindOption.ANY_ID), TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(3, results.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findExact_Level2() throws IOException {
|
||||
File root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(root, 2);
|
||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(root);
|
||||
|
||||
File similarPdbFile1 = mkFile(new File(root, "fi/file1.pdb/112233445/file1.pdb"));
|
||||
mkFile(new File(root, "fi/file1.pdb/112233446/file1.pdb"));
|
||||
|
||||
List<SymbolFileLocation> results =
|
||||
localSymbolStore.find(SymbolFileInfo.fromValues("file1.pdb", "11223344", 5),
|
||||
FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(1, results.size());
|
||||
|
||||
String resultLocation = localSymbolStore.getFileLocation(results.get(0).getPath());
|
||||
assertEquals(similarPdbFile1.getPath(), resultLocation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void giveFile_Level0() throws IOException {
|
||||
File root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(root, 0);
|
||||
|
||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(root);
|
||||
|
||||
File file1 = mkFile(new File(temporaryDir, "file1.pdb"));
|
||||
localSymbolStore.giveFile(SymbolFileInfo.fromValues("file1.pdb", "11223344", 0), file1,
|
||||
"file1.pdb", TaskMonitor.DUMMY);
|
||||
|
||||
assertFalse(file1.exists());
|
||||
|
||||
// can't search for the pdb file because a level0 LocalSymbolStore would
|
||||
// need to open up any 'pdb' files it finds to read the guid/id and age,
|
||||
// and we can't create good pdbs right now that would enable this.
|
||||
|
||||
File expectedFile = new File(root, "file1.pdb");
|
||||
assertTrue(expectedFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void giveFile_Level1() throws IOException {
|
||||
File root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(root, 1);
|
||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(root);
|
||||
|
||||
File file1 = mkFile(new File(temporaryDir, "file1.pdb"));
|
||||
localSymbolStore.giveFile(SymbolFileInfo.fromValues("file1.pdb", "11223344", 0), file1,
|
||||
"file1.pdb", TaskMonitor.DUMMY);
|
||||
|
||||
assertFalse(file1.exists());
|
||||
|
||||
List<SymbolFileLocation> results =
|
||||
localSymbolStore.find(SymbolFileInfo.fromValues("file1.pdb", "11223344", 0),
|
||||
FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
||||
assertEquals(1, results.size());
|
||||
assertEquals("file1.pdb/112233440/file1.pdb", results.get(0).getPath());
|
||||
assertEquals("11223344", results.get(0).getFileInfo().getUniqueName());
|
||||
assertEquals(0, results.get(0).getFileInfo().getIdentifiers().getAge());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void giveFile_Level2() throws IOException {
|
||||
File root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(root, 1);
|
||||
LocalSymbolStore localSymbolStore = new LocalSymbolStore(root);
|
||||
|
||||
File file1 = mkFile(new File(temporaryDir, "file1.pdb"));
|
||||
localSymbolStore.giveFile(SymbolFileInfo.fromValues("file1.pdb", "11223344", 0), file1,
|
||||
"file1.pdb", TaskMonitor.DUMMY);
|
||||
|
||||
assertFalse(file1.exists());
|
||||
|
||||
List<SymbolFileLocation> results =
|
||||
localSymbolStore.find(SymbolFileInfo.fromValues("file1.pdb", "11223344", 0),
|
||||
FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
||||
assertEquals(1, results.size());
|
||||
assertEquals("file1.pdb/112233440/file1.pdb", results.get(0).getPath());
|
||||
assertEquals("11223344", results.get(0).getFileInfo().getUniqueName());
|
||||
assertEquals(0, results.get(0).getFileInfo().getIdentifiers().getAge());
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
|
||||
public class SymbolServerInstanceCreatorRegistryTest extends AbstractGenericTest {
|
||||
|
||||
private SymbolServerInstanceCreatorRegistry symbolServerInstanceCreatorRegistry =
|
||||
SymbolServerInstanceCreatorRegistry.getInstance();
|
||||
private SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext =
|
||||
symbolServerInstanceCreatorRegistry.getContext();
|
||||
private File temporaryDir;
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
temporaryDir = createTempDirectory("localsymbolserver");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLocalSymbolStore() {
|
||||
SymbolServer symbolServer = symbolServerInstanceCreatorRegistry
|
||||
.newSymbolServer(temporaryDir.getPath(), symbolServerInstanceCreatorContext);
|
||||
assertNotNull(symbolServer);
|
||||
assertTrue(symbolServer instanceof LocalSymbolStore);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateHttpSymbolServer() {
|
||||
SymbolServer symbolServer = symbolServerInstanceCreatorRegistry
|
||||
.newSymbolServer("http://localhost/blah", symbolServerInstanceCreatorContext);
|
||||
assertNotNull(symbolServer);
|
||||
assertTrue(symbolServer instanceof HttpSymbolServer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateHttpsSymbolServer() {
|
||||
SymbolServer symbolServer = symbolServerInstanceCreatorRegistry
|
||||
.newSymbolServer("https://localhost/blah", symbolServerInstanceCreatorContext);
|
||||
assertNotNull(symbolServer);
|
||||
assertTrue(symbolServer instanceof HttpSymbolServer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSameDirSymbolStore() {
|
||||
SymbolServer symbolServer = symbolServerInstanceCreatorRegistry.newSymbolServer(".",
|
||||
symbolServerInstanceCreatorContext);
|
||||
assertNotNull(symbolServer);
|
||||
assertTrue(symbolServer instanceof SameDirSymbolStore);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDisabledSymbolServer() {
|
||||
SymbolServer symbolServer = symbolServerInstanceCreatorRegistry
|
||||
.newSymbolServer("disabled://.", symbolServerInstanceCreatorContext);
|
||||
assertNotNull(symbolServer);
|
||||
assertTrue(symbolServer instanceof DisabledSymbolServer);
|
||||
assertTrue(
|
||||
((DisabledSymbolServer) symbolServer).getSymbolServer() instanceof SameDirSymbolStore);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBogusLocation() {
|
||||
SymbolServer symbolServer = symbolServerInstanceCreatorRegistry.newSymbolServer("blah://",
|
||||
symbolServerInstanceCreatorContext);
|
||||
assertNull(symbolServer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPath() {
|
||||
List<SymbolServer> symbolServerResultList =
|
||||
symbolServerInstanceCreatorRegistry.createSymbolServersFromPathList(
|
||||
List.of(".", "http://localhost/blah"), symbolServerInstanceCreatorContext);
|
||||
assertEquals(2, symbolServerResultList.size());
|
||||
assertTrue(symbolServerResultList.get(0) instanceof SameDirSymbolStore);
|
||||
assertTrue(symbolServerResultList.get(1) instanceof HttpSymbolServer);
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Also see SymbolServerService2Test in the _Integration Tests module for tests that
|
||||
* decompress compressed pdb files.
|
||||
*/
|
||||
public class SymbolServerServiceTest extends AbstractGenericTest {
|
||||
private File temporaryDir;
|
||||
private File localSymbolStore1Root;
|
||||
private File localSymbolStore2Root;
|
||||
private LocalSymbolStore localSymbolStore1;
|
||||
private LocalSymbolStore localSymbolStore2;
|
||||
|
||||
private File mkFile(File file) throws IOException {
|
||||
FileUtilities.checkedMkdirs(file.getParentFile());
|
||||
FileUtilities.writeStringToFile(file, "test");
|
||||
return file;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
temporaryDir = createTempDirectory("symbolservers");
|
||||
localSymbolStore1Root = new File(temporaryDir, "symbols1");
|
||||
localSymbolStore2Root = new File(temporaryDir, "symbols2");
|
||||
LocalSymbolStore.create(localSymbolStore1Root, 1);
|
||||
LocalSymbolStore.create(localSymbolStore2Root, 1);
|
||||
|
||||
localSymbolStore1 = new LocalSymbolStore(localSymbolStore1Root);
|
||||
localSymbolStore2 = new LocalSymbolStore(localSymbolStore2Root);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Exact_AlreadyLocal() throws IOException, CancelledException {
|
||||
File pdbFile1 = mkFile(new File(localSymbolStore1Root, "file1.pdb/112233440/file1.pdb"));
|
||||
File pdbFile2 = mkFile(new File(localSymbolStore2Root, "file1.pdb/112233440/file1.pdb"));
|
||||
|
||||
SymbolServerService symbolServerService = new SymbolServerService(localSymbolStore1,
|
||||
List.of(localSymbolStore1, localSymbolStore2));
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(SymbolFileInfo.fromValues("file1.pdb", "11223344", 0),
|
||||
TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(2, results.size());
|
||||
|
||||
File foundPdbFile1 = symbolServerService.getSymbolFile(results.get(0), TaskMonitor.DUMMY);
|
||||
File foundPdbFile2 = symbolServerService.getSymbolFile(results.get(1), TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(pdbFile1, foundPdbFile1);
|
||||
assertEquals(pdbFile2, foundPdbFile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_AnyAge() throws IOException, CancelledException {
|
||||
// search for similar pdbs, across multiple storage servers
|
||||
mkFile(new File(localSymbolStore1Root, "file1.pdb/000000001/file1.pdb"));
|
||||
mkFile(new File(localSymbolStore1Root, "file1.pdb/112233441/file1.pdb"));
|
||||
mkFile(new File(localSymbolStore2Root, "file1.pdb/112233442/file1.pdb"));
|
||||
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1, List.of(localSymbolStore2));
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(SymbolFileInfo.fromValues("file1.pdb", "11223344", 0),
|
||||
FindOption.of(FindOption.ANY_AGE), TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(2, results.size());
|
||||
Set<String> uids = results.stream()
|
||||
.map(symbolFileLocation -> symbolFileLocation.getFileInfo().getUniqueName())
|
||||
.collect(Collectors.toSet());
|
||||
assertEquals(1, uids.size());
|
||||
assertTrue(uids.contains("11223344"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_AnyUID() throws IOException, CancelledException {
|
||||
// search for similar pdbs, across multiple storage servers
|
||||
mkFile(new File(localSymbolStore1Root, "file2.pdb/000000001/file2.pdb"));
|
||||
mkFile(new File(localSymbolStore1Root, "file1.pdb/000000001/file1.pdb"));
|
||||
mkFile(new File(localSymbolStore1Root, "file1.pdb/112233441/file1.pdb"));
|
||||
mkFile(new File(localSymbolStore2Root, "file1.pdb/112233442/file1.pdb"));
|
||||
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1, List.of(localSymbolStore2));
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(SymbolFileInfo.fromValues("file1.pdb", "11223344", 0),
|
||||
FindOption.of(FindOption.ANY_ID), TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(3, results.size());
|
||||
Set<String> uids = results.stream()
|
||||
.map(symbolFileLocation -> symbolFileLocation.getFileInfo().getUniqueName())
|
||||
.collect(Collectors.toSet());
|
||||
assertEquals(2, uids.size());
|
||||
assertTrue(uids.contains("11223344"));
|
||||
assertTrue(uids.contains("00000000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Remote() throws IOException, CancelledException {
|
||||
String payload = "testdummy";
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1,
|
||||
List.of(localSymbolStore2, new DummySymbolServer(payload)));
|
||||
SymbolFileInfo searchPdb = SymbolFileInfo.fromValues("file1.pdb", "11223344", 0);
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(searchPdb, FindOption.of(FindOption.ALLOW_REMOTE),
|
||||
TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(1, results.size());
|
||||
assertTrue(results.get(0).isExactMatch(searchPdb));
|
||||
|
||||
File pdbFile = symbolServerService.getSymbolFile(results.get(0), TaskMonitor.DUMMY);
|
||||
assertEquals(payload, Files.readString(pdbFile.toPath()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_NoRemote() throws CancelledException {
|
||||
String payload = "testdummy";
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1, List.of(new DummySymbolServer(payload)));
|
||||
SymbolFileInfo searchPdb = SymbolFileInfo.fromValues("file1.pdb", "11223344", 0);
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(searchPdb, FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(0, results.size());
|
||||
}
|
||||
|
||||
}
|
@ -15,9 +15,10 @@
|
||||
*/
|
||||
package docking;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Timer;
|
||||
@ -62,14 +63,14 @@ public class DialogComponentProvider
|
||||
protected JPanel rootPanel;
|
||||
private JPanel mainPanel;
|
||||
private JComponent workPanel;
|
||||
private JPanel buttonPanel;
|
||||
protected JPanel buttonPanel;
|
||||
private JPanel statusPanel;
|
||||
protected JButton okButton;
|
||||
protected JButton applyButton;
|
||||
protected JButton cancelButton;
|
||||
protected JButton dismissButton;
|
||||
private boolean isAlerting;
|
||||
private JLabel statusLabel;
|
||||
private GDHtmlLabel statusLabel;
|
||||
private JPanel statusProgPanel; // contains status panel and progress panel
|
||||
private Timer showTimer;
|
||||
private TaskScheduler taskScheduler;
|
||||
@ -697,7 +698,7 @@ public class DialogComponentProvider
|
||||
});
|
||||
}
|
||||
|
||||
private Color getStatusColor(MessageType type) {
|
||||
protected Color getStatusColor(MessageType type) {
|
||||
switch (type) {
|
||||
case ALERT:
|
||||
return Color.orange;
|
||||
|
@ -15,11 +15,12 @@
|
||||
*/
|
||||
package docking.widgets.textfield;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.*;
|
||||
@ -372,6 +373,15 @@ public class IntegerTextField {
|
||||
textField.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the editable mode for the JTextField component
|
||||
*
|
||||
* @param editable boolean flag, if true component is editable
|
||||
*/
|
||||
public void setEditable(boolean editable) {
|
||||
textField.setEditable(editable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests focus to the JTextField
|
||||
*/
|
||||
|
@ -0,0 +1,88 @@
|
||||
/* ###
|
||||
* 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.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpClient.Redirect;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
public class HttpClients {
|
||||
/**
|
||||
* Note: java.net.http.HttpClient instances can allocate system resources (file handles),
|
||||
* and frequently creating a new HttpClient could exhaust system resources.
|
||||
* <p>
|
||||
* There is no "close()" on a HttpClient to release resources. The system resources
|
||||
* allocated by HttpClient instances will be released when the instance is gc'd.
|
||||
* However, since the resources in question (filehandles) are not tied to memory pressure,
|
||||
* its possible a gc() won't happen before running out of file handles if a few hundred
|
||||
* HttpClient instances have been created / discarded.
|
||||
* <p>
|
||||
* Also note, there is no per-connection ability to disable hostname verification in a
|
||||
* SSL/TLS connection. There is a global flag:
|
||||
* -Djdk.internal.httpclient.disableHostnameVerification
|
||||
*
|
||||
*/
|
||||
private static HttpClient client;
|
||||
|
||||
/**
|
||||
* Creates a HttpClient Builder using Ghidra SSL/TLS context info.
|
||||
*
|
||||
* @return a new HttpClient Builder
|
||||
* @throws IOException if error in PKI settings or crypto configuration
|
||||
*/
|
||||
public static HttpClient.Builder newHttpClientBuilder() throws IOException {
|
||||
if (!ApplicationKeyManagerFactory.initialize()) {
|
||||
if (ApplicationKeyManagerFactory.getKeyStore() != null) {
|
||||
throw new IOException("Failed to initialize PKI certificate keystore");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return HttpClient.newBuilder()
|
||||
.sslContext(SSLContext.getDefault())
|
||||
.followRedirects(Redirect.NORMAL);
|
||||
}
|
||||
catch (NoSuchAlgorithmException nsae) {
|
||||
throw new IOException("Missing algorithm", nsae);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shared, plain (no special options) {@link HttpClient}.
|
||||
*
|
||||
* @return a {@link HttpClient}
|
||||
* @throws IOException if error in PKI settings or crypto configuration
|
||||
*/
|
||||
public static synchronized HttpClient getHttpClient() throws IOException {
|
||||
if (client == null) {
|
||||
client = newHttpClientBuilder().build();
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the currently cached {@link HttpClient}, forcing it to be
|
||||
* rebuilt during the next call to {@link #getHttpClient()}.
|
||||
*/
|
||||
public static synchronized void clearHttpClient() {
|
||||
client = null;
|
||||
}
|
||||
|
||||
}
|
@ -110,6 +110,10 @@ public class SSLContextInitializer implements ModuleInitializer {
|
||||
// Establish default HTTPS socket factory
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
|
||||
|
||||
// Force the HttpClient to be re-created by the next request to
|
||||
// HttpClients.getHttpClient() so that the new SSLContext is used
|
||||
HttpClients.clearHttpClient();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
@ -15,39 +15,15 @@
|
||||
*/
|
||||
package ghidra.net.http;
|
||||
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.Properties;
|
||||
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class HttpUtil {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("User-Agent", "Microsoft-Symbol-Server/6.3.9600.17298");
|
||||
|
||||
String urlStr =
|
||||
"http://msdl.microsoft.com/download/symbols/write.pdb/4FD8CA6696F445A7B969AB9BBD76E4591/write.pd_";
|
||||
|
||||
String homeDir = System.getProperty("user.home");
|
||||
File f = new File(homeDir + "/Downloads", "write.pdb.deleteme");
|
||||
|
||||
try {
|
||||
getFile(urlStr, properties, true, f);
|
||||
System.out.println("getFile completed: " + f);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an HTTP/HTTPS GET request and return the resulting HttpURLConnection.
|
||||
@ -155,4 +131,5 @@ public class HttpUtil {
|
||||
|
||||
return connection.getContentType();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,25 +15,23 @@
|
||||
*/
|
||||
package ghidra.util.filechooser;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* A convenience implementation of FileFilter that filters out
|
||||
* all files except for those type extensions that it knows about.
|
||||
*
|
||||
* Extensions are of the type ".foo", which is typically found on
|
||||
* Windows and Unix boxes, but not on Mac. Case is ignored.
|
||||
*
|
||||
* <p>
|
||||
* Extensions are of the type "foo" (no leading dot). Case is ignored.
|
||||
* <p>
|
||||
* Example - create a new filter that filters out all files
|
||||
* but gif and jpg image files:
|
||||
* <pre>
|
||||
* GhidraFileChooser chooser = new GhidraFileChooser();
|
||||
* ExtensionFileFilter filter = new ExtensionFileFilter(
|
||||
* new String{"gif", "jpg"}, "JPEG and GIF Images")
|
||||
* chooser.addFileFilter(filter);
|
||||
* chooser.addFileFilter(ExtensionFilFilter.forExtensions("JPEG and GIF Images", "gif", "jpg"));
|
||||
*</pre>
|
||||
*/
|
||||
public class ExtensionFileFilter implements GhidraFileFilter {
|
||||
@ -50,19 +48,16 @@ public class ExtensionFileFilter implements GhidraFileFilter {
|
||||
return eff;
|
||||
}
|
||||
|
||||
private Hashtable<String, ExtensionFileFilter> filters = null;
|
||||
private String description = null;
|
||||
private String fullDescription = null;
|
||||
private boolean useExtensionsInDescription = true;
|
||||
private List<String> extensions;
|
||||
private String description;
|
||||
private String fullDescription;
|
||||
|
||||
/**
|
||||
* Creates a file filter that accepts the given file type.
|
||||
* Example: new ExtensionFileFilter("jpg", "JPEG Image Images");
|
||||
* Example: new ExtensionFileFilter("jpg", "JPEG Images");
|
||||
*
|
||||
* Note that the "." before the extension is not needed. If
|
||||
* provided, it will be ignored.
|
||||
*
|
||||
* @see #addExtension
|
||||
* @param extension file extension to match, without leading dot
|
||||
* @param description descriptive string of the filter
|
||||
*/
|
||||
public ExtensionFileFilter(String extension, String description) {
|
||||
this(new String[] { extension }, description);
|
||||
@ -72,16 +67,15 @@ public class ExtensionFileFilter implements GhidraFileFilter {
|
||||
* Creates a file filter from the given string array and description.
|
||||
* Example: new ExtensionFileFilter(String {"gif", "jpg"}, "Gif and JPG Images");
|
||||
*
|
||||
* Note that the "." before the extension is not needed and will be ignored.
|
||||
*
|
||||
* @see #addExtension
|
||||
* @param filters array of file name extensions, each without a leading dot
|
||||
* @param description descriptive string of the filter
|
||||
*/
|
||||
public ExtensionFileFilter(String[] filters, String description) {
|
||||
this.filters = new Hashtable<String, ExtensionFileFilter>(filters.length);
|
||||
for (String filter : filters) {
|
||||
addExtension(filter);//add filters one by one
|
||||
}
|
||||
setDescription(description);
|
||||
this.extensions = Arrays.asList(filters)
|
||||
.stream()
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toList());
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,7 +84,6 @@ public class ExtensionFileFilter implements GhidraFileFilter {
|
||||
*
|
||||
* Files that begin with "." are ignored.
|
||||
*
|
||||
* @see #getExtension
|
||||
* @see FileFilter#accept
|
||||
*/
|
||||
@Override
|
||||
@ -101,133 +94,37 @@ public class ExtensionFileFilter implements GhidraFileFilter {
|
||||
if (model.isDirectory(f)) {
|
||||
return true;
|
||||
}
|
||||
if (filters.size() == 0) {
|
||||
if (extensions.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
String extension = getExtension(f);
|
||||
return extension != null && filters.get(extension) != null;
|
||||
String filename = f.getName().toLowerCase();
|
||||
if (filename.startsWith(".")) {
|
||||
return false;
|
||||
}
|
||||
int fnLen = filename.length();
|
||||
for (String ext : extensions) {
|
||||
int extLen = ext.length();
|
||||
int extStart = fnLen - extLen;
|
||||
if (extStart > 0 && filename.substring(extStart).equals(ext) &&
|
||||
filename.charAt(extStart - 1) == '.') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the extension portion of the file's name .
|
||||
*
|
||||
* @see #getExtension
|
||||
* @see FileFilter#accept
|
||||
*/
|
||||
public String getExtension(File f) {
|
||||
if (f != null) {
|
||||
String filename = f.getName();
|
||||
int i = filename.lastIndexOf('.');
|
||||
if (i > 0 && i < filename.length() - 1) {
|
||||
return filename.substring(i + 1).toLowerCase();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a filetype "dot" extension to filter against.
|
||||
*
|
||||
* For example: the following code will create a filter that filters
|
||||
* out all files except those that end in ".jpg" and ".tif":
|
||||
*
|
||||
* ExtensionFileFilter filter = new ExtensionFileFilter();
|
||||
* filter.addExtension("jpg");
|
||||
* filter.addExtension("tif");
|
||||
*
|
||||
* Note that the "." before the extension is not needed and will be ignored.
|
||||
*/
|
||||
public void addExtension(String extension) {
|
||||
if (filters == null) {
|
||||
filters = new Hashtable<String, ExtensionFileFilter>(5);
|
||||
}
|
||||
filters.put(extension.toLowerCase(), this);
|
||||
fullDescription = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the human readable description of this filter. For
|
||||
* example: "JPEG and GIF Image Files (*.jpg, *.gif)"
|
||||
*/
|
||||
@Override
|
||||
public String getDescription() {
|
||||
if (fullDescription == null) {
|
||||
fullDescription = "";
|
||||
if (description == null || isExtensionListInDescription()) {
|
||||
if (description != null) {
|
||||
fullDescription = description;
|
||||
}
|
||||
fullDescription += " (";
|
||||
// build the description from the extension list
|
||||
fullDescription = Objects.requireNonNullElse(description, "");
|
||||
|
||||
if (filters.size() == 0) {
|
||||
fullDescription += "*.*";
|
||||
}
|
||||
else {
|
||||
boolean firstExt = true;
|
||||
Enumeration<String> extensions = filters.keys();
|
||||
if (extensions != null) {
|
||||
while (extensions.hasMoreElements()) {
|
||||
if (!firstExt) {
|
||||
fullDescription += ",";
|
||||
}
|
||||
else {
|
||||
firstExt = false;
|
||||
}
|
||||
fullDescription += "*." + extensions.nextElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
// add prettified extensions to the description string
|
||||
fullDescription += " (";
|
||||
fullDescription += extensions.isEmpty()
|
||||
? "*.*"
|
||||
: extensions.stream().map(s -> "*." + s).collect(Collectors.joining(","));
|
||||
fullDescription += ")";
|
||||
}
|
||||
else {
|
||||
fullDescription = description;
|
||||
}
|
||||
}
|
||||
return fullDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the human readable description of this filter. For
|
||||
* example: filter.setDescription("Gif and JPG Images");
|
||||
*
|
||||
* @see #setDescription
|
||||
* @see #setExtensionListInDescription
|
||||
* @see #isExtensionListInDescription
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
fullDescription = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the extension list (.jpg, .gif, etc) should
|
||||
* show up in the human readable description.
|
||||
*
|
||||
* Only relevant if a description was provided in the constructor
|
||||
* or using setDescription();
|
||||
*
|
||||
* @see #getDescription
|
||||
* @see #setDescription
|
||||
* @see #isExtensionListInDescription
|
||||
*/
|
||||
public void setExtensionListInDescription(boolean b) {
|
||||
useExtensionsInDescription = b;
|
||||
fullDescription = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the extension list (.jpg, .gif, etc) should
|
||||
* show up in the human readable description.
|
||||
*
|
||||
* Only relevant if a description was provided in the constructor
|
||||
* or using setDescription();
|
||||
*
|
||||
* @see #getDescription
|
||||
* @see #setDescription
|
||||
* @see #setExtensionListInDescription
|
||||
*/
|
||||
public final boolean isExtensionListInDescription() {
|
||||
return useExtensionsInDescription;
|
||||
}
|
||||
}
|
||||
|
@ -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,12 +15,16 @@
|
||||
*/
|
||||
package ghidra.util.task;
|
||||
|
||||
/**
|
||||
* Similar to a {@link Runnable} except the {@link #monitoredRun(TaskMonitor) run}
|
||||
* method is given a monitor to report progress and check for cancellation.
|
||||
*/
|
||||
public interface MonitoredRunnable {
|
||||
|
||||
/**
|
||||
* Similar to a runnable except that is given a monitor to report progress and check for
|
||||
* cancellation.
|
||||
* Similar to a runnable except the run method is given a monitor
|
||||
* to report progress and check for cancellation.
|
||||
* @param monitor the TaskMonitor to use.
|
||||
*/
|
||||
public void monitoredRun(TaskMonitor monitor);
|
||||
void monitoredRun(TaskMonitor monitor);
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
/* ###
|
||||
* 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.net.http;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
public class HttpUtilTest {
|
||||
public static void main(String[] args) {
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("User-Agent", "Microsoft-Symbol-Server/6.3.9600.17298");
|
||||
|
||||
String urlStr =
|
||||
"http://msdl.microsoft.com/download/symbols/write.pdb/4FD8CA6696F445A7B969AB9BBD76E4591/write.pd_";
|
||||
|
||||
String homeDir = System.getProperty("user.home");
|
||||
File f = new File(homeDir + "/Downloads", "write.pdb.deleteme");
|
||||
|
||||
try {
|
||||
HttpUtil.getFile(urlStr, properties, true, f);
|
||||
System.out.println("getFile completed: " + f);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -15,102 +15,183 @@
|
||||
*/
|
||||
package help.screenshot;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Window;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import docking.widgets.dialogs.ObjectChooserDialog;
|
||||
import ghidra.app.util.pdb.PdbLocator;
|
||||
import ghidra.util.Msg;
|
||||
import pdb.URLChoice;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.junit.*;
|
||||
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfo;
|
||||
import ghidra.app.util.bin.format.pdb.PdbInfoDotNet;
|
||||
import ghidra.app.util.datatype.microsoft.GUID;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import pdb.PdbPlugin;
|
||||
import pdb.symbolserver.*;
|
||||
import pdb.symbolserver.ui.ConfigPdbDialog;
|
||||
import pdb.symbolserver.ui.LoadPdbDialog;
|
||||
|
||||
public class PdbScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
@Test
|
||||
public void testPdbOrXmlDialog() throws Exception {
|
||||
private static final String GUID1_STR = "012345670123012301230123456789AB";
|
||||
|
||||
performAction("Download_PDB_File", "PdbSymbolServerPlugin", false);
|
||||
private int tx;
|
||||
private File temporaryDir;
|
||||
|
||||
Window pdbDialog = waitForWindow("pdb or pdb.xml");
|
||||
pdbDialog.setSize(new Dimension(750, 200));
|
||||
captureWindow(pdbDialog);
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
pressButtonByText(pdbDialog, "Cancel");
|
||||
temporaryDir = createTempDirectory("example_pdb");
|
||||
tx = program.startTransaction("set analyzed flag");
|
||||
Options proplist = program.getOptions(Program.PROGRAM_INFO);
|
||||
proplist.setBoolean(Program.ANALYZED, false);
|
||||
PdbInfo pdbInfo = PdbInfoDotNet.fromValues("HelloWorld.pdb", 1, new GUID(GUID1_STR));
|
||||
pdbInfo.serializeToOptions(proplist);
|
||||
proplist.setString("Executable Location",
|
||||
new File(temporaryDir, program.getName()).getPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
program.endTransaction(tx, false);
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPeSpecifiedPathDialog() throws Exception {
|
||||
|
||||
performAction("Download_PDB_File", "PdbSymbolServerPlugin", false);
|
||||
|
||||
Window pdbDialog = waitForWindow("pdb or pdb.xml");
|
||||
pressButtonByText(pdbDialog, "PDB");
|
||||
|
||||
Window peSpecifiedPathDialog = waitForWindow("PE-specified PDB Path");
|
||||
captureWindow(peSpecifiedPathDialog);
|
||||
|
||||
pressButtonByText(peSpecifiedPathDialog, "Cancel");
|
||||
public void testSymbolServerConfig_Screenshot() throws IOException {
|
||||
PdbPlugin.saveSymbolServerServiceConfig(null);
|
||||
ConfigPdbDialog configPdbDialog = new ConfigPdbDialog();
|
||||
showDialogWithoutBlocking(tool, configPdbDialog);
|
||||
waitForSwing();
|
||||
captureDialog(ConfigPdbDialog.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSymbolServerURLDialog() throws Exception {
|
||||
|
||||
// Set up for local directory
|
||||
PdbLocator.setDefaultPdbSymbolsDir(getTestDataDirectory());
|
||||
|
||||
performAction("Download_PDB_File", "PdbSymbolServerPlugin", false);
|
||||
|
||||
Window pdbDialog = waitForWindow("pdb or pdb.xml");
|
||||
pressButtonByText(pdbDialog, "PDB");
|
||||
|
||||
Window peSpecifiedPathDialog = waitForWindow("PE-specified PDB Path");
|
||||
pressButtonByText(peSpecifiedPathDialog, "Yes");
|
||||
|
||||
Window saveLocationDialog = waitForWindow("Select Location to Save Retrieved File");
|
||||
pressButtonByText(saveLocationDialog, "OK");
|
||||
|
||||
Window urlDialog = waitForWindow("Symbol Server URL");
|
||||
urlDialog.setSize(new Dimension(850, 135));
|
||||
|
||||
captureWindow(urlDialog);
|
||||
|
||||
pressButtonByText(urlDialog, "Cancel");
|
||||
public void testLoadPdb_Initial_Screenshot() throws IOException {
|
||||
LoadPdbDialog loadPdbDialog = new LoadPdbDialog(program);
|
||||
showDialogWithoutBlocking(tool, loadPdbDialog);
|
||||
captureDialog(loadPdbDialog);
|
||||
pressButtonByText(loadPdbDialog, "Cancel");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKnownSymbolServerURLsDialog() throws Exception {
|
||||
|
||||
List<URLChoice> urlChoices = new ArrayList<>();
|
||||
urlChoices.add(new URLChoice("Internet", "https://msdl.microsoft.com/download/symbols"));
|
||||
urlChoices.add(new URLChoice("My Network", "https://my_symbol_server.my.org"));
|
||||
|
||||
final ObjectChooserDialog<URLChoice> urlDialog = new ObjectChooserDialog<>("Choose a URL",
|
||||
URLChoice.class, urlChoices, "getNetwork", "getUrl");
|
||||
public void testSymbolServerConfig_AddButtonMenu() throws IOException {
|
||||
File localSymbolStore1Root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(localSymbolStore1Root, 1);
|
||||
LocalSymbolStore localSymbolStore1 =
|
||||
new LocalSymbolStoreWithFakePath(localSymbolStore1Root, "/home/user/symbols");
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1, List.of());
|
||||
PdbPlugin.saveSymbolServerServiceConfig(symbolServerService);
|
||||
|
||||
LoadPdbDialog choosePdbDialog = new LoadPdbDialog(program);
|
||||
showDialogWithoutBlocking(tool, choosePdbDialog);
|
||||
waitForSwing();
|
||||
pressButtonByText(choosePdbDialog, "Advanced >>");
|
||||
runSwing(() -> {
|
||||
// Do nothing
|
||||
choosePdbDialog.pushAddLocationBution();
|
||||
});
|
||||
showDialogWithoutBlocking(tool, urlDialog);
|
||||
captureDialog();
|
||||
|
||||
pressButtonByText(urlDialog, "Cancel");
|
||||
waitForSwing();
|
||||
captureMenu();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessDialog() throws Exception {
|
||||
public void testLoadPdb_Advanced_NeedsConfig() throws IOException {
|
||||
PdbPlugin.saveSymbolServerServiceConfig(null);
|
||||
LoadPdbDialog choosePdbDialog = new LoadPdbDialog(program);
|
||||
showDialogWithoutBlocking(tool, choosePdbDialog);
|
||||
waitForSwing();
|
||||
pressButtonByText(choosePdbDialog, "Advanced >>");
|
||||
waitForSwing();
|
||||
captureDialog(LoadPdbDialog.class);
|
||||
pressButtonByText(choosePdbDialog, "Cancel");
|
||||
}
|
||||
|
||||
// Can't really get success message without actually downloading a file.
|
||||
// So, fake out the message by showing the same sort of dialog the user would see.
|
||||
Msg.showInfo(getClass(), null, "File Retrieved",
|
||||
"Downloaded and saved file 'example.pdb' to \n" +
|
||||
"C:\\Symbols\\example.pdb\\1123A456B7889012C3DDFA4556789B011");
|
||||
@Test
|
||||
public void testLoadPdb_Advanced_Screenshot() throws IOException {
|
||||
// Show the advanced side of the LoadPdbDialog, with
|
||||
// some faked search locations and search results so we
|
||||
// can have pretty paths
|
||||
File localSymbolStore1Root = new File(temporaryDir, "symbols");
|
||||
LocalSymbolStore.create(localSymbolStore1Root, 1);
|
||||
LocalSymbolStore localSymbolStore1 =
|
||||
new LocalSymbolStoreWithFakePath(localSymbolStore1Root, "/home/user/symbols");
|
||||
SameDirSymbolStoreWithFakePath sameDirSymbolStoreWithFakePath =
|
||||
new SameDirSymbolStoreWithFakePath(temporaryDir, "/home/user/examples");
|
||||
List<SymbolServer> symbolServers = List.of(sameDirSymbolStoreWithFakePath,
|
||||
new HttpSymbolServer(URI.create("https://msdl.microsoft.com/download/symbols/")));
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1, symbolServers);
|
||||
PdbPlugin.saveSymbolServerServiceConfig(symbolServerService);
|
||||
|
||||
Window successDialog = waitForWindow("File Retrieved");
|
||||
captureWindow(successDialog);
|
||||
LoadPdbDialog loadPdbDialog = new LoadPdbDialog(program);
|
||||
showDialogWithoutBlocking(tool, loadPdbDialog);
|
||||
waitForSwing();
|
||||
pressButtonByText(loadPdbDialog, "Advanced >>");
|
||||
List<SymbolFileLocation> symbolFileLocations = List.of(
|
||||
new SymbolFileLocation("HelloWorld.pdb/" + GUID1_STR + "1/HelloWorld.pdb",
|
||||
localSymbolStore1, SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 1)),
|
||||
new SymbolFileLocation("HelloWorld.pdb/" + GUID1_STR + "2/HelloWorld.pdb",
|
||||
localSymbolStore1, SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 2)),
|
||||
new SymbolFileLocation("HelloWorld.pdb", sameDirSymbolStoreWithFakePath,
|
||||
SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 1)),
|
||||
new SymbolFileLocation("HelloWorld_ver2.pdb", sameDirSymbolStoreWithFakePath,
|
||||
SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 2)));
|
||||
runSwing(() -> {
|
||||
loadPdbDialog
|
||||
.setSearchOptions(FindOption.of(FindOption.ALLOW_REMOTE, FindOption.ANY_AGE));
|
||||
loadPdbDialog.setSymbolServers(symbolServers);
|
||||
loadPdbDialog.setSymbolStorageDirectoryTextOnly("/home/user/symbols");
|
||||
loadPdbDialog.setSearchResults(symbolFileLocations);
|
||||
loadPdbDialog.selectRowByLocation(symbolFileLocations.get(0));
|
||||
});
|
||||
waitForSwing();
|
||||
captureDialog(LoadPdbDialog.class);
|
||||
pressButtonByText(loadPdbDialog, "Cancel");
|
||||
}
|
||||
|
||||
pressButtonByText(successDialog, "OK");
|
||||
private static class LocalSymbolStoreWithFakePath extends LocalSymbolStore {
|
||||
private String fakeRootDirPath;
|
||||
|
||||
public LocalSymbolStoreWithFakePath(File rootDir, String fakeRootDirPath) {
|
||||
super(rootDir);
|
||||
this.fakeRootDirPath = fakeRootDirPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescriptiveName() {
|
||||
return fakeRootDirPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileLocation(String filename) {
|
||||
return FilenameUtils.concat(fakeRootDirPath, filename);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SameDirSymbolStoreWithFakePath extends SameDirSymbolStore {
|
||||
private String fakeRootDirPath;
|
||||
|
||||
public SameDirSymbolStoreWithFakePath(File rootDir, String fakeRootDirPath) {
|
||||
super(rootDir);
|
||||
this.fakeRootDirPath = fakeRootDirPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescriptiveName() {
|
||||
return String.format(PROGRAMS_IMPORT_LOCATION_DESCRIPTION_STR + " - %s",
|
||||
fakeRootDirPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileLocation(String filename) {
|
||||
return FilenameUtils.concat(fakeRootDirPath, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,138 @@
|
||||
/* ###
|
||||
* 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 pdb.symbolserver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Tests for Pdb SymbolServer stuff that need to be in the Integration module because they depend
|
||||
* on FileFormat's file system stuff to decompress .cab files
|
||||
*/
|
||||
public class SymbolServerService2Test extends AbstractGhidraHeadedIntegrationTest {
|
||||
private File temporaryDir;
|
||||
private File localSymbolStore1Root;
|
||||
private LocalSymbolStore localSymbolStore1;
|
||||
|
||||
// Bytes for a very small .cab file that contains a singleton file named 'test.pdb' with
|
||||
// contents of "test"
|
||||
byte[] smallCabFileBytes = BaseEncoding.base16()
|
||||
.decode(("4d5343460000000055000000000000002c000000000000000301010001" +
|
||||
"00000000000000450000000100010004000000000000000000a248bc5c2000746573742e7064620" +
|
||||
"066652e4908000400434b2b492d2e0100").toUpperCase());
|
||||
|
||||
private File mkFile(File file, byte[] bytes) throws IOException {
|
||||
FileUtilities.checkedMkdirs(file.getParentFile());
|
||||
FileUtilities.writeBytes(file, bytes);
|
||||
return file;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
temporaryDir = createTempDirectory("symbolservers");
|
||||
localSymbolStore1Root = new File(temporaryDir, "symbols1");
|
||||
LocalSymbolStore.create(localSymbolStore1Root, 1);
|
||||
|
||||
localSymbolStore1 = new LocalSymbolStore(localSymbolStore1Root);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalCab() throws IOException, CancelledException {
|
||||
mkFile(new File(localSymbolStore1Root, "test.pdb/112233441/test.pd_"), smallCabFileBytes);
|
||||
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1, List.of());
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(SymbolFileInfo.fromValues("test.pdb", "11223344", 1),
|
||||
FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(1, results.size());
|
||||
assertEquals("test.pd_", FilenameUtils.getName(results.get(0).getPath()));
|
||||
|
||||
File pdbFile = symbolServerService.getSymbolFile(results.get(0), TaskMonitor.DUMMY);
|
||||
assertEquals("test\n" /* extra \n because FileUtilities.getText() adds it */,
|
||||
FileUtilities.getText(pdbFile));
|
||||
|
||||
// search again and we should only find the now decompressed pdb file
|
||||
List<SymbolFileLocation> results2 =
|
||||
symbolServerService.find(SymbolFileInfo.fromValues("test.pdb", "11223344", 1),
|
||||
FindOption.NO_OPTIONS, TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(1, results2.size());
|
||||
assertEquals("test.pdb", FilenameUtils.getName(results2.get(0).getPath()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoteCab() throws IOException, CancelledException {
|
||||
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1,
|
||||
List.of(new DummySymbolServer(smallCabFileBytes, true)));
|
||||
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(SymbolFileInfo.fromValues("test.pdb", "11223344", 1),
|
||||
FindOption.of(FindOption.ALLOW_REMOTE), TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(1, results.size());
|
||||
System.out.println(results.get(0).getLocationStr());
|
||||
|
||||
File pdbFile = symbolServerService.getSymbolFile(results.get(0), TaskMonitor.DUMMY);
|
||||
assertEquals("test\n" /* extra \n because FileUtilities.getText() adds it */,
|
||||
FileUtilities.getText(pdbFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoteCabAlreadyExistLocal() throws IOException, CancelledException {
|
||||
|
||||
SymbolServerService symbolServerService =
|
||||
new SymbolServerService(localSymbolStore1,
|
||||
List.of(new DummySymbolServer(smallCabFileBytes, true)));
|
||||
|
||||
List<SymbolFileLocation> results =
|
||||
symbolServerService.find(SymbolFileInfo.fromValues("test.pdb", "11223344", 1),
|
||||
FindOption.of(FindOption.ALLOW_REMOTE), TaskMonitor.DUMMY);
|
||||
|
||||
assertEquals(1, results.size());
|
||||
System.out.println(results.get(0).getLocationStr());
|
||||
|
||||
// cheese the file into the local symbol store after the remote file has been found
|
||||
// but before it has been downloaded
|
||||
mkFile(new File(localSymbolStore1Root, "test.pdb/112233441/test.pdb"),
|
||||
"nottest".getBytes());
|
||||
|
||||
// normally this would download the remote file and decompress it
|
||||
File pdbFile = symbolServerService.getSymbolFile(results.get(0), TaskMonitor.DUMMY);
|
||||
|
||||
// ensure that the original file wasn't overwritten by the new file
|
||||
assertEquals("nottest\n" /* extra \n because FileUtilities.getText() adds it */,
|
||||
FileUtilities.getText(pdbFile));
|
||||
}
|
||||
}
|