GP-42 Initial implementation of Pdb symbol store / symbol servers

This commit is contained in:
dev747368 2020-07-21 18:34:35 -04:00
parent df72f24b58
commit 425667e640
93 changed files with 7715 additions and 3874 deletions

View file

@ -17,6 +17,10 @@ eclipse.project.name = 'GPL CabExtract'
project.ext.cabextract = "cabextract-1.6"
/*********************************************************************************
* Deprecated - will be removed
*********************************************************************************/
/*********************************************************************************
* CabExtract platform specific tasks
*

View file

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

View file

@ -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>&lt;ghidra install root&gt;/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>&nbsp;&nbsp;&nbsp;&nbsp;createPdbXmlFiles.bat C:\Symbols\samplePdb.pdb</CODE>
<br>
<CODE>&nbsp;&nbsp;&nbsp;&nbsp;createPdbXmlFiles.bat C:\Symbols</CODE>
<br>
</LI>
<br>
<LI>Run the included <I>pdb.exe</I> executable (found in the <I>&lt;ghidra install
root&gt;/Ghidra/Features/PDB/os/win64</I> directory) and redirect (save) its output to an
XML file as shown below:
<br><br>
<CODE>&nbsp;&nbsp;&nbsp;&nbsp;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 &quot;Unable to locate the DIA SDK,&quot;
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>

View file

@ -66,11 +66,13 @@ public class BinaryReader {
this.provider = provider;
setLittleEndian(isLittleEndian);
}
/**
* 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

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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);

View file

@ -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);

View file

@ -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();
}
/**

View file

@ -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();

View file

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

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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 &rarr; Load PDB File...</I></B> action.
<H2><a name="DIASDK">DIA SDK Dependency</a></H2>

View file

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

View file

@ -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 &rarr; 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 &rarr; 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">&nbsp;(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">&nbsp;(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">&nbsp;(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">&nbsp;(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">&nbsp;(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>

View file

@ -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 &rarr; <a href="LoadPDBNew.html#Load_PDB_File">Load PDB File</a></B></LI>
<LI>PDB Analyzer via <B>Analysis &rarr;
<A HREF="help/topics/AutoAnalysisPlugin/AutoAnalysis.htm#Auto_Analyze">Auto Analyze</A></B> or
<B>Analysis &rarr; <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>&lt;ghidra install root&gt;/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>&nbsp;&nbsp;&nbsp;&nbsp;createPdbXmlFiles.bat C:\Symbols\samplePdb.pdb</CODE>
<br>
<CODE>&nbsp;&nbsp;&nbsp;&nbsp;createPdbXmlFiles.bat C:\Symbols</CODE>
<br>
</LI>
<br>
<LI>Run the included <I>pdb.exe</I> executable (found in the <I>&lt;ghidra install
root&gt;/Ghidra/Features/PDB/os/win64</I> directory) and redirect (save) its output to an
XML file as shown below:
<br><br>
<CODE>&nbsp;&nbsp;&nbsp;&nbsp;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 &quot;Unable to locate the DIA SDK,&quot;
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>

View file

@ -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\&lt;pdbfilename&gt;\&lt;GUID&gt;</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>&lt;download location&gt;/&lt;pdbfilename&gt;/
&lt;GUID&gt;</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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

View file

@ -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);
}
includePeSpecifiedPdbPath =
options.getBoolean(OPTION_NAME_INCLUDE_PE_PDB_PATH, includePeSpecifiedPdbPath);
searchRemoteLocations = options.getBoolean(
PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, searchRemoteLocations);
}
/**
* 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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
Msg.info(this, "PDB analyzer failed to locate PDB file");
return false;
}
pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchRemoteLocations, monitor);
}
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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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,18 +644,21 @@ public class PdbParser {
pdbGuid = pdbGuid.toUpperCase();
pdbGuid = "{" + pdbGuid + "}";
if (!xmlGuid.equals(pdbGuid)) {
warning = "PDB signature does not match.\n" + "Program GUID: " + pdbGuid +
"\nXML GUID: " + xmlGuid; }
else {
// Also check that PDB ages match, if they are both available
if ((xmlAge != null) && (pdbAge != null)) {
if (!allowNonExactMatch) {
if (!xmlGuid.equals(pdbGuid)) {
warning = "PDB signature does not match.\n" + "Program GUID: " + pdbGuid +
"\nXML GUID: " + xmlGuid;
}
else {
// Also check that PDB ages match, if they are both available
if ((xmlAge != null) && (pdbAge != null)) {
int pdbAgeDecimal = Integer.parseInt(pdbAge, 16);
int xmlAgeDecimal = Integer.parseInt(xmlAge);
int pdbAgeDecimal = Integer.parseInt(pdbAge, 16);
int xmlAgeDecimal = Integer.parseInt(xmlAge);
if (xmlAgeDecimal != pdbAgeDecimal) {
warning = "PDB ages do not match.";
if (xmlAgeDecimal != pdbAgeDecimal) {
warning = "PDB ages do not match.";
}
}
}
}
@ -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");

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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";
}
}

View file

@ -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;
}
@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);
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);
}
private void loadPDB() {
Program program = GhidraProgramUtilities.getCurrentProgram(tool);
AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(program);
if (aam.isAnalyzing()) {
private void configPDB() {
ConfigPdbDialog.showSymbolServerConfig();
}
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"));
}
//-------------------------------------------------------------------------------------------------------
if (pdbFile != null) {
pdbChooser.setSelectedFile(pdbFile);
}
/**
* 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) {
File selectedFile = pdbChooser.getSelectedFile();
return selectedFile;
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;
}
/**
* 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);
}
/**
* 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;
}
}

View file

@ -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 + "&lt;pdbFilename&gt;" +
File.separator + "&lt;GUID&gt;" + 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
* &lt;destination folder&gt;/&lt;pdbFilename&gt;/&gt;guidAgeString&lt; (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
* &lt;destination folder&gt;/&lt;pdbFilename&gt; 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;
}
}
}

View 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");
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -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());
}
}

View file

@ -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;
}
}

View file

@ -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 + "]";
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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")));
}
}

View file

@ -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("_");
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}
}

View file

@ -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>" +
"&nbsp;&nbsp;<b>'012345678-0123-0123-0123-0123456789ABC'</b> or <b>'0123456780123012301230123456789ABC'</b>, or<br>" +
"PDB Signature Id - 8 hexadecimal character Id:<br>" +
"&nbsp;&nbsp;<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();
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

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

View file

@ -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
*/

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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();
}
}

View file

@ -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;
}
/**
* 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();
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 null;
return false;
}
/**
* 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();
}
}
}
fullDescription += ")";
}
else {
fullDescription = description;
}
// add prettified extensions to the description string
fullDescription += " (";
fullDescription += extensions.isEmpty()
? "*.*"
: extensions.stream().map(s -> "*." + s).collect(Collectors.joining(","));
fullDescription += ")";
}
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;
}
}

View file

@ -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.
* @param monitor the TaskMonitor to use.
*/
public void monitoredRun(TaskMonitor monitor);
* 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.
*/
void monitoredRun(TaskMonitor monitor);
}

View file

@ -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();
}
}
}

View file

@ -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);
}
}
}

View file

@ -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));
}
}