Merge remote-tracking branch 'origin/GP-4017_dev747368_pdb_symbolserver_fsrl'

This commit is contained in:
Ryan Kurtz 2023-11-09 12:08:37 -05:00
commit 7fa03c9719
8 changed files with 350 additions and 19 deletions

View file

@ -19,6 +19,8 @@ import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.util.*; import java.util.*;
import ghidra.plugin.importer.ProgramMappingService;
import ghidra.program.model.listing.Program;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
/** /**
@ -63,6 +65,27 @@ import ghidra.util.SystemUtilities;
public class FSRL { public class FSRL {
public static final String PARAM_MD5 = "MD5"; 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 * 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. * are not intern()'d so will not be shared with other FSRL instances.

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

View file

@ -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).
* <p>
* 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<SymbolFileLocation> find(SymbolFileInfo fileInfo, Set<FindOption> 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);
}
}

View file

@ -18,6 +18,7 @@ package pdb.symbolserver;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
@ -61,6 +62,23 @@ public class SameDirSymbolStore implements SymbolStore {
return symbolFileLocation; 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; private final File rootDir;
/** /**

View file

@ -17,6 +17,11 @@ package pdb.symbolserver;
import java.io.File; 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 * Context for the {@link SymbolServerInstanceCreatorRegistry} when creating new
* {@link SymbolServer} instances. * {@link SymbolServer} instances.
@ -30,6 +35,7 @@ import java.io.File;
*/ */
public class SymbolServerInstanceCreatorContext { public class SymbolServerInstanceCreatorContext {
private final File rootDir; private final File rootDir;
private final FSRL programFSRL;
private final SymbolServerInstanceCreatorRegistry symbolServerInstanceCreatorRegistry; private final SymbolServerInstanceCreatorRegistry symbolServerInstanceCreatorRegistry;
SymbolServerInstanceCreatorContext( SymbolServerInstanceCreatorContext(
@ -37,9 +43,16 @@ public class SymbolServerInstanceCreatorContext {
this(null, symbolServerInstanceCreatorRegistry); this(null, symbolServerInstanceCreatorRegistry);
} }
SymbolServerInstanceCreatorContext(File rootDir, SymbolServerInstanceCreatorContext(Program program,
SymbolServerInstanceCreatorRegistry symbolServerInstanceCreatorRegistry) { 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; this.symbolServerInstanceCreatorRegistry = symbolServerInstanceCreatorRegistry;
} }
@ -61,4 +74,13 @@ public class SymbolServerInstanceCreatorContext {
return rootDir; 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;
}
} }

View file

@ -15,13 +15,10 @@
*/ */
package pdb.symbolserver; package pdb.symbolserver;
import java.util.*;
import java.util.function.Predicate;
import java.io.File; import java.io.File;
import java.net.URI; import java.net.URI;
import java.util.*;
import org.apache.commons.io.FilenameUtils; import java.util.function.Predicate;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -163,8 +160,7 @@ public class SymbolServerInstanceCreatorRegistry {
* @return new {@link SymbolServerInstanceCreatorContext} * @return new {@link SymbolServerInstanceCreatorContext}
*/ */
public SymbolServerInstanceCreatorContext getContext(Program program) { public SymbolServerInstanceCreatorContext getContext(Program program) {
File exeLocation = new File(FilenameUtils.getFullPath(program.getExecutablePath())); return new SymbolServerInstanceCreatorContext(program, this);
return new SymbolServerInstanceCreatorContext(exeLocation, this);
} }
private void registerDefaultSymbolServerInstanceCreators() { private void registerDefaultSymbolServerInstanceCreators() {
@ -173,7 +169,7 @@ public class SymbolServerInstanceCreatorRegistry {
registerSymbolServerInstanceCreator(100, HttpSymbolServer::isHttpSymbolServerLocation, registerSymbolServerInstanceCreator(100, HttpSymbolServer::isHttpSymbolServerLocation,
(loc, context) -> new HttpSymbolServer(URI.create(loc))); (loc, context) -> new HttpSymbolServer(URI.create(loc)));
registerSymbolServerInstanceCreator(200, SameDirSymbolStore::isSameDirLocation, registerSymbolServerInstanceCreator(200, SameDirSymbolStore::isSameDirLocation,
(loc, context) -> new SameDirSymbolStore(context.getRootDir())); SameDirSymbolStore::createInstance);
registerSymbolServerInstanceCreator(300, LocalSymbolStore::isLocalSymbolStoreLocation, registerSymbolServerInstanceCreator(300, LocalSymbolStore::isLocalSymbolStoreLocation,
(loc, context) -> new LocalSymbolStore(new File(loc))); (loc, context) -> new LocalSymbolStore(new File(loc)));
} }

View file

@ -787,10 +787,9 @@ public class LoadPdbDialog extends DialogComponentProvider {
return null; return null;
} }
SymbolServer symbolServer = symbolFileLocation.getSymbolServer(); SymbolServer symbolServer = symbolFileLocation.getSymbolServer();
if (!(symbolServer instanceof SymbolStore)) { if (!(symbolServer instanceof SymbolStore symbolStore)) {
return null; return null;
} }
SymbolStore symbolStore = (SymbolStore) symbolServer;
File file = symbolStore.getFile(symbolFileLocation.getPath()); File file = symbolStore.getFile(symbolFileLocation.getPath());
return SymbolStore.isCompressedFilename(file.getName()) ? null : file; return SymbolStore.isCompressedFilename(file.getName()) ? null : file;
} }

View file

@ -91,10 +91,9 @@ class SymbolServerPanel extends JPanel {
SymbolServerService temporarySymbolServerService = SymbolServerService temporarySymbolServerService =
PdbPlugin.getSymbolServerService(symbolServerInstanceCreatorContext); PdbPlugin.getSymbolServerService(symbolServerInstanceCreatorContext);
if (temporarySymbolServerService.getSymbolStore() instanceof LocalSymbolStore) { if (temporarySymbolServerService
setSymbolStorageLocation( .getSymbolStore() instanceof LocalSymbolStore tempLocalSymbolStore) {
((LocalSymbolStore) temporarySymbolServerService.getSymbolStore()).getRootDir(), setSymbolStorageLocation(tempLocalSymbolStore.getRootDir(), false);
false);
} }
tableModel.addSymbolServers(temporarySymbolServerService.getSymbolServers()); tableModel.addSymbolServers(temporarySymbolServerService.getSymbolServers());
setConfigChanged(false); setConfigChanged(false);
@ -368,8 +367,8 @@ class SymbolServerPanel extends JPanel {
SymbolServer symbolServer = SymbolServer symbolServer =
symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry() symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry()
.newSymbolServer(firstSearchPath, symbolServerInstanceCreatorContext); .newSymbolServer(firstSearchPath, symbolServerInstanceCreatorContext);
if (symbolServer instanceof LocalSymbolStore && if (symbolServer instanceof LocalSymbolStore localSymbolStore &&
((LocalSymbolStore) symbolServer).isValid()) { localSymbolStore.isValid()) {
int choice = OptionDialog.showYesNoCancelDialog(this, "Set Symbol Storage Location", int choice = OptionDialog.showYesNoCancelDialog(this, "Set Symbol Storage Location",
"Set symbol storage location to " + firstSearchPath + "?"); "Set symbol storage location to " + firstSearchPath + "?");
if (choice == OptionDialog.CANCEL_OPTION) { if (choice == OptionDialog.CANCEL_OPTION) {
@ -378,7 +377,7 @@ class SymbolServerPanel extends JPanel {
if (choice == OptionDialog.YES_OPTION) { if (choice == OptionDialog.YES_OPTION) {
symbolServerPaths.remove(0); symbolServerPaths.remove(0);
configChanged = true; configChanged = true;
setSymbolStorageLocation(((LocalSymbolStore) symbolServer).getRootDir(), true); setSymbolStorageLocation(localSymbolStore.getRootDir(), true);
symbolStorageLocationTextField.setText(symbolServer.getName()); symbolStorageLocationTextField.setText(symbolServer.getName());
} }
} }