diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java
index 74dc1726a1..daf75db464 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java
@@ -19,6 +19,8 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.util.*;
+import ghidra.plugin.importer.ProgramMappingService;
+import ghidra.program.model.listing.Program;
import ghidra.util.SystemUtilities;
/**
@@ -63,6 +65,27 @@ import ghidra.util.SystemUtilities;
public class FSRL {
public static final String PARAM_MD5 = "MD5";
+ /**
+ * Returns the {@link FSRL} stored in a {@link Program}'s properties, or null if not present
+ * or malformed.
+ *
+ * @param program {@link Program}
+ * @return {@link FSRL} from program's properties, or null if not present or invalid
+ */
+ public static FSRL fromProgram(Program program) {
+ String fsrlStr = program.getOptions(Program.PROGRAM_INFO)
+ .getString(ProgramMappingService.PROGRAM_SOURCE_FSRL, null);
+ if (fsrlStr != null) {
+ try {
+ return FSRL.fromString(fsrlStr);
+ }
+ catch (MalformedURLException e) {
+ // fall thru, return null
+ }
+ }
+ return null;
+ }
+
/**
* Creates a {@link FSRL} from a raw string. The parent portions of the FSRL
* are not intern()'d so will not be shared with other FSRL instances.
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/RefdInputStream.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/RefdInputStream.java
new file mode 100644
index 0000000000..a30401d854
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/RefdInputStream.java
@@ -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.formats.gfilesystem;
+
+import java.io.*;
+
+/**
+ * An {@link InputStream} wrapper that keeps a {@link FileSystemRef} pinned.
+ */
+public class RefdInputStream extends InputStream {
+
+ private FileSystemRef fsRef;
+ private InputStream delegate;
+
+ /**
+ * Creates a new {@link RefdInputStream}.
+ *
+ * @param fsRef {@link FileSystemRef}
+ * @param delegate the wrapped {@link InputStream}
+ */
+ public RefdInputStream(FileSystemRef fsRef, InputStream delegate) {
+ this.fsRef = fsRef;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (fsRef != null) {
+ fsRef.close();
+ fsRef = null;
+ }
+ delegate.close();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return delegate.available();
+ }
+
+ @Override
+ public int read() throws IOException {
+ return delegate.read();
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return delegate.read(b);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ return delegate.read(b, off, len);
+ }
+
+ @Override
+ public byte[] readAllBytes() throws IOException {
+ return delegate.readAllBytes();
+ }
+
+ @Override
+ public int readNBytes(byte[] b, int off, int len) throws IOException {
+ return delegate.readNBytes(b, off, len);
+ }
+
+ @Override
+ public byte[] readNBytes(int len) throws IOException {
+ return delegate.readNBytes(len);
+ }
+
+ @Override
+ public synchronized void mark(int readlimit) {
+ delegate.mark(readlimit);
+ }
+
+ @Override
+ public boolean markSupported() {
+ return delegate.markSupported();
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ return delegate.skip(n);
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ delegate.reset();
+ }
+
+ @Override
+ public long transferTo(OutputStream out) throws IOException {
+ return delegate.transferTo(out);
+ }
+
+ @Override
+ public void skipNBytes(long n) throws IOException {
+ delegate.skipNBytes(n);
+ }
+
+}
diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ContainerFileSymbolServer.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ContainerFileSymbolServer.java
new file mode 100644
index 0000000000..3a42b4a10b
--- /dev/null
+++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ContainerFileSymbolServer.java
@@ -0,0 +1,161 @@
+/* ###
+ * 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.*;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.io.FilenameUtils;
+
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.formats.gfilesystem.*;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * Companion to the {@link SameDirSymbolStore}, handles the case where the imported binary
+ * was located in a container file (eg. zip file).
+ *
+ * Instances of this class are conditionally created by the
+ * {@link SameDirSymbolStore#createInstance(String, SymbolServerInstanceCreatorContext) registry factory method}
+ * when it detects that the imported binary's {@link FSRL} isn't a simple local file.
+ */
+public class ContainerFileSymbolServer implements SymbolServer {
+
+ private final FileSystemService fsService;
+ private final FSRLRoot fsFSRL;
+ private final String subdir;
+
+ public ContainerFileSymbolServer(FSRL programFSRL) {
+ this.fsFSRL = programFSRL.getFS();
+ this.subdir = FilenameUtils.getFullPath(programFSRL.getPath());
+ this.fsService = FileSystemService.getInstance();
+ }
+
+ @Override
+ public String getName() {
+ return ".";
+ }
+
+ @Override
+ public String getDescriptiveName() {
+ return SameDirSymbolStore.PROGRAMS_IMPORT_LOCATION_DESCRIPTION_STR + " - " +
+ fsFSRL.toPrettyFullpathString();
+ }
+
+ @Override
+ public boolean isValid(TaskMonitor monitor) {
+ return true;
+ }
+
+ @Override
+ public boolean exists(String filename, TaskMonitor monitor) {
+ try (RefdFile file = getFile(filename, monitor)) {
+ return file != null;
+ }
+ catch (IOException e1) {
+ // fall thru
+ }
+ return false;
+ }
+
+ private RefdFile getFile(String filename, TaskMonitor monitor) {
+ try (FileSystemRef fsRef = fsService.getFilesystem(fsFSRL, monitor)) {
+ if (fsRef != null) {
+ GFileSystem fs = fsRef.getFilesystem();
+ String path = FSUtilities.appendPath(subdir, filename);
+ GFile file = fs.lookup(path);
+ if (file != null && !file.isDirectory()) {
+ return new RefdFile(fsRef.dup(), file);
+ }
+ }
+ }
+ catch (IOException | CancelledException e) {
+ // fall thru
+ }
+ return null;
+ }
+
+ @Override
+ public List find(SymbolFileInfo fileInfo, Set findOptions,
+ TaskMonitor monitor) {
+
+ try (RefdFile fref = getFile(fileInfo.getName(), monitor)) {
+ // TODO: need to be able to read info from candidate pdb file using some type of stream
+ if (fref != null) {
+ GFile file = fref.file;
+ GFileSystem fs = file.getFilesystem();
+ try (ByteProvider bp = fs.getByteProvider(file, monitor)) {
+ File tmpPdbFile = fsService.createPlaintextTempFile(bp, "temp_pdb", monitor);
+ File tmpPdbDir =
+ Files.createTempDirectory(tmpPdbFile.getParentFile().toPath(), "temp_pdb")
+ .toFile();
+ File destPdbFile = new File(tmpPdbDir, file.getName()).getCanonicalFile();
+ if (!destPdbFile.getParentFile().equals(tmpPdbDir)) {
+ throw new IOException("Bad filename: " + destPdbFile);
+ }
+ if (!tmpPdbFile.renameTo(destPdbFile)) {
+ throw new IOException(
+ "Unable to move file: " + tmpPdbFile + " to " + destPdbFile);
+ }
+ SymbolFileInfo foundInfo = SymbolFileInfo.fromFile(destPdbFile, monitor);
+ destPdbFile.delete();
+ tmpPdbDir.delete();
+ return List.of(new SymbolFileLocation(file.getName(), this, foundInfo));
+ }
+ }
+ }
+ catch (IOException | CancelledException e) {
+ // fall thru
+ }
+ return List.of();
+ }
+
+ @Override
+ public SymbolServerInputStream getFileStream(String filename, TaskMonitor monitor)
+ throws IOException {
+ try (RefdFile fref = getFile(filename, monitor)) {
+ if (fref != null) {
+ GFile file = fref.file;
+ InputStream is = file.getFilesystem().getInputStream(file, monitor);
+ is = new RefdInputStream(fref.fsRef.dup(), is);
+ return new SymbolServerInputStream(is, file.getLength());
+ }
+ }
+ catch (CancelledException e) {
+ // fall thru
+ }
+ throw new IOException();
+ }
+
+ @Override
+ public String getFileLocation(String filename) {
+ return fsFSRL.withPath(filename).toPrettyFullpathString();
+ }
+
+ @Override
+ public boolean isLocal() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "ContainerFileSymbolServer: [ fsrl: %s ]".formatted(fsFSRL);
+ }
+
+}
diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SameDirSymbolStore.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SameDirSymbolStore.java
index 0c1655d752..8567c87920 100644
--- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SameDirSymbolStore.java
+++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SameDirSymbolStore.java
@@ -18,6 +18,7 @@ package pdb.symbolserver;
import java.io.*;
import java.util.*;
+import ghidra.formats.gfilesystem.FSRL;
import ghidra.util.task.TaskMonitor;
/**
@@ -61,6 +62,23 @@ public class SameDirSymbolStore implements SymbolStore {
return symbolFileLocation;
}
+ /**
+ * Creates a {@link SymbolServer} for the "Program's Import Location" item. May return either
+ * a {@link SameDirSymbolStore} instance, or a {@link ContainerFileSymbolServer}.
+ *
+ * @param locationString will be "."
+ * @param context {@link SymbolServerInstanceCreatorContext}
+ * @return new {@link SymbolServer}
+ */
+ public static SymbolServer createInstance(String locationString,
+ SymbolServerInstanceCreatorContext context) {
+ FSRL programFSRL = context.getProgramFSRL();
+ if (programFSRL != null && programFSRL.getNestingDepth() != 1) {
+ return new ContainerFileSymbolServer(programFSRL);
+ }
+ return new SameDirSymbolStore(context.getRootDir());
+ }
+
private final File rootDir;
/**
diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorContext.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorContext.java
index 4e880ec480..363d9af1f1 100644
--- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorContext.java
+++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorContext.java
@@ -17,6 +17,11 @@ package pdb.symbolserver;
import java.io.File;
+import org.apache.commons.io.FilenameUtils;
+
+import ghidra.formats.gfilesystem.FSRL;
+import ghidra.program.model.listing.Program;
+
/**
* Context for the {@link SymbolServerInstanceCreatorRegistry} when creating new
* {@link SymbolServer} instances.
@@ -30,6 +35,7 @@ import java.io.File;
*/
public class SymbolServerInstanceCreatorContext {
private final File rootDir;
+ private final FSRL programFSRL;
private final SymbolServerInstanceCreatorRegistry symbolServerInstanceCreatorRegistry;
SymbolServerInstanceCreatorContext(
@@ -37,9 +43,16 @@ public class SymbolServerInstanceCreatorContext {
this(null, symbolServerInstanceCreatorRegistry);
}
- SymbolServerInstanceCreatorContext(File rootDir,
+ SymbolServerInstanceCreatorContext(Program program,
SymbolServerInstanceCreatorRegistry symbolServerInstanceCreatorRegistry) {
- this.rootDir = rootDir;
+ if (program != null) {
+ this.programFSRL = FSRL.fromProgram(program);
+ this.rootDir = new File(FilenameUtils.getFullPath(program.getExecutablePath()));
+ }
+ else {
+ this.programFSRL = null;
+ this.rootDir = null;
+ }
this.symbolServerInstanceCreatorRegistry = symbolServerInstanceCreatorRegistry;
}
@@ -61,4 +74,13 @@ public class SymbolServerInstanceCreatorContext {
return rootDir;
}
+ /**
+ * Returns the FSRL of imported binary.
+ *
+ * @return {@link FSRL} of the imported binary, or null if not present
+ */
+ public FSRL getProgramFSRL() {
+ return programFSRL;
+ }
+
}
diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorRegistry.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorRegistry.java
index 9905b4477a..b3b4417833 100644
--- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorRegistry.java
+++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorRegistry.java
@@ -15,13 +15,10 @@
*/
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 java.util.*;
+import java.util.function.Predicate;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
@@ -163,8 +160,7 @@ public class SymbolServerInstanceCreatorRegistry {
* @return new {@link SymbolServerInstanceCreatorContext}
*/
public SymbolServerInstanceCreatorContext getContext(Program program) {
- File exeLocation = new File(FilenameUtils.getFullPath(program.getExecutablePath()));
- return new SymbolServerInstanceCreatorContext(exeLocation, this);
+ return new SymbolServerInstanceCreatorContext(program, this);
}
private void registerDefaultSymbolServerInstanceCreators() {
@@ -173,7 +169,7 @@ public class SymbolServerInstanceCreatorRegistry {
registerSymbolServerInstanceCreator(100, HttpSymbolServer::isHttpSymbolServerLocation,
(loc, context) -> new HttpSymbolServer(URI.create(loc)));
registerSymbolServerInstanceCreator(200, SameDirSymbolStore::isSameDirLocation,
- (loc, context) -> new SameDirSymbolStore(context.getRootDir()));
+ SameDirSymbolStore::createInstance);
registerSymbolServerInstanceCreator(300, LocalSymbolStore::isLocalSymbolStoreLocation,
(loc, context) -> new LocalSymbolStore(new File(loc)));
}
diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/LoadPdbDialog.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/LoadPdbDialog.java
index 751c14a24b..a4b7c0040d 100644
--- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/LoadPdbDialog.java
+++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/LoadPdbDialog.java
@@ -787,10 +787,9 @@ public class LoadPdbDialog extends DialogComponentProvider {
return null;
}
SymbolServer symbolServer = symbolFileLocation.getSymbolServer();
- if (!(symbolServer instanceof SymbolStore)) {
+ if (!(symbolServer instanceof SymbolStore symbolStore)) {
return null;
}
- SymbolStore symbolStore = (SymbolStore) symbolServer;
File file = symbolStore.getFile(symbolFileLocation.getPath());
return SymbolStore.isCompressedFilename(file.getName()) ? null : file;
}
diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerPanel.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerPanel.java
index 724bffd0a1..a0e4de296d 100644
--- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerPanel.java
+++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerPanel.java
@@ -91,10 +91,9 @@ class SymbolServerPanel extends JPanel {
SymbolServerService temporarySymbolServerService =
PdbPlugin.getSymbolServerService(symbolServerInstanceCreatorContext);
- if (temporarySymbolServerService.getSymbolStore() instanceof LocalSymbolStore) {
- setSymbolStorageLocation(
- ((LocalSymbolStore) temporarySymbolServerService.getSymbolStore()).getRootDir(),
- false);
+ if (temporarySymbolServerService
+ .getSymbolStore() instanceof LocalSymbolStore tempLocalSymbolStore) {
+ setSymbolStorageLocation(tempLocalSymbolStore.getRootDir(), false);
}
tableModel.addSymbolServers(temporarySymbolServerService.getSymbolServers());
setConfigChanged(false);
@@ -368,8 +367,8 @@ class SymbolServerPanel extends JPanel {
SymbolServer symbolServer =
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
.newSymbolServer(firstSearchPath, symbolServerInstanceCreatorContext);
- if (symbolServer instanceof LocalSymbolStore &&
- ((LocalSymbolStore) symbolServer).isValid()) {
+ if (symbolServer instanceof LocalSymbolStore localSymbolStore &&
+ localSymbolStore.isValid()) {
int choice = OptionDialog.showYesNoCancelDialog(this, "Set Symbol Storage Location",
"Set symbol storage location to " + firstSearchPath + "?");
if (choice == OptionDialog.CANCEL_OPTION) {
@@ -378,7 +377,7 @@ class SymbolServerPanel extends JPanel {
if (choice == OptionDialog.YES_OPTION) {
symbolServerPaths.remove(0);
configChanged = true;
- setSymbolStorageLocation(((LocalSymbolStore) symbolServer).getRootDir(), true);
+ setSymbolStorageLocation(localSymbolStore.getRootDir(), true);
symbolStorageLocationTextField.setText(symbolServer.getName());
}
}