Writes an ELF program that was imported with the ELF loader back to its original file
+ layout. Any file-backed bytes that were modified by the user in the program database will
+ be reflected in the new file.
+
+
Writing back a modified Memory
+ Map is not supported.
+
+
Relocation bytes are always
+ restored to their original values, even if the user modifies them.
+
Ghidra Zip File (.gzf)
@@ -320,7 +334,7 @@
Address Space - Specifies which address space to export as Intel Hex format
- only supports one address space. This option will be intialized to the "default"
+ only supports one address space. This option will be initialized to the "default"
address space.
Record Size - Specifies the size (in bytes) of each record in the
output file. The default 16.
@@ -330,6 +344,20 @@
2 bytes will be dropped.
+
+
PE
+
+
+
Writes a PE program that was imported with the PE loader back to its original file
+ layout. Any file-backed bytes that were modified by the user in the program database will
+ be reflected in the new file.
+
+
Writing back a modified Memory
+ Map is not supported.
+
+
Relocation bytes are always
+ restored to their original values, even if the user modifies them.
+
XML
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java
new file mode 100644
index 0000000000..4645369886
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/AbstractLoaderExporter.java
@@ -0,0 +1,167 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.util.exporter;
+
+import java.io.*;
+import java.nio.file.*;
+import java.util.List;
+
+import ghidra.app.util.DomainObjectService;
+import ghidra.app.util.Option;
+import ghidra.app.util.opinion.Loader;
+import ghidra.framework.model.DomainObject;
+import ghidra.program.database.mem.AddressSourceInfo;
+import ghidra.program.database.mem.FileBytes;
+import ghidra.program.model.address.AddressSetView;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.Memory;
+import ghidra.program.model.reloc.Relocation;
+import ghidra.util.Conv;
+import ghidra.util.HelpLocation;
+import ghidra.util.task.TaskMonitor;
+import utilities.util.FileUtilities;
+
+/**
+ * An {@link Exporter} that can export programs imported with a particular {@link Loader}
+ */
+public abstract class AbstractLoaderExporter extends Exporter {
+
+ /**
+ * Creates a new {@link AbstractLoaderExporter}
+ *
+ * @param name The display name of this exporter
+ * @param extension The default extension for this exporter
+ * @param help The {@link HelpLocation} for this exporter
+ */
+ protected AbstractLoaderExporter(String name, String extension, HelpLocation help) {
+ super(name, extension, help);
+ }
+
+ /**
+ * Checks to see if the given file format is supported by this exporter
+ *
+ * @param fileFormat The file format (loader name) of the program to export
+ * @return True if the given file format is supported by this exporter; otherwise, false
+ */
+ protected abstract boolean supportsFileFormat(String fileFormat);
+
+ @Override
+ public boolean export(File file, DomainObject domainObj, AddressSetView addrSet,
+ TaskMonitor monitor) throws IOException, ExporterException {
+
+ if (!(domainObj instanceof Program)) {
+ log.appendMsg("Unsupported type: " + domainObj.getClass().getSimpleName());
+ return false;
+ }
+
+ Program program = (Program) domainObj;
+ Memory memory = program.getMemory();
+
+ String fileFormat = program.getExecutableFormat();
+ if (!supportsFileFormat(fileFormat)) {
+ log.appendMsg("Unsupported file format: " + fileFormat);
+ return false;
+ }
+
+ // Write source program's file bytes to a temp file
+ File tempFile = File.createTempFile("ghidra_export_", null);
+ try (OutputStream out = new FileOutputStream(tempFile, false)) {
+ FileBytes[] fileBytes = memory.getAllFileBytes()
+ .stream()
+ .filter(fb -> program.getExecutablePath().endsWith(fb.getFilename()))
+ .toArray(FileBytes[]::new);
+ for (FileBytes bytes : fileBytes) {
+ FileUtilities.copyStreamToStream(new FileBytesInputStream(bytes), out, monitor);
+ }
+ }
+
+ // Undo relocations in the temp file
+ String error = null;
+ try (RandomAccessFile fout = new RandomAccessFile(tempFile, "rw")) {
+ Iterable relocs = () -> program.getRelocationTable().getRelocations();
+ for (Relocation reloc : relocs) {
+ AddressSourceInfo info = memory.getAddressSourceInfo(reloc.getAddress());
+ if (info == null) {
+ error = "Failed to get relocation address source";
+ break;
+ }
+ if (info.getFileOffset() < 0) {
+ error = "Failed to get relocation file offset";
+ break;
+ }
+ if (info.getFileOffset() >= fout.length()) {
+ error = "Relocation file offset exceeds file length";
+ break;
+ }
+ fout.seek(info.getFileOffset());
+ fout.write(reloc.getBytes());
+ }
+ }
+
+ // If errors occurred, log them and delete the malformed temp file
+ if (error != null) {
+ log.appendMsg(error);
+ if (!tempFile.delete()) {
+ log.appendMsg("Failed to delete malformed file: " + tempFile);
+ }
+ return false;
+ }
+
+ // Move temp file to desired output file
+ Path from = Paths.get(tempFile.toURI());
+ Path to = Paths.get(file.toURI());
+ Files.move(from, to, StandardCopyOption.REPLACE_EXISTING);
+ return true;
+ }
+
+ @Override
+ public List