diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/RelocationException.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/RelocationException.java new file mode 100644 index 0000000000..fca2923092 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/RelocationException.java @@ -0,0 +1,52 @@ +/* ### + * 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; + +import java.util.Objects; + +/** + * RelocationException thrown when a supported relocation encounters an + * unexpected error during processing. + */ +public class RelocationException extends Exception { + + /** + * Constructs a new exception with the specified detail message. + * + * @param message the detail message (required). + */ + public RelocationException(String message) { + super(message); + Objects.requireNonNull(message); + } + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (required). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A {@code null} value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + RelocationException(String message, Exception cause) { + super(message, cause); + Objects.requireNonNull(message); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/relocation/CoffRelocationContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/relocation/CoffRelocationContext.java new file mode 100644 index 0000000000..0f2787c471 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/relocation/CoffRelocationContext.java @@ -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 ghidra.app.util.bin.format.coff.relocation; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import ghidra.app.util.bin.format.RelocationException; +import ghidra.app.util.bin.format.coff.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Symbol; + +/** + * CoffRelocationContext provide COFF relocation context data to be used by + * {@link CoffRelocationHandler} during processing of relocations. + */ +public class CoffRelocationContext { + + private final Program program; + private final CoffFileHeader header; + private final Map symbolsMap; + + private final Map contextMap = new HashMap<>(); + private CoffSectionHeader section; + + /** + * Construct COFF relocation context + * @param program program to which relocations are applied + * @param header COFF file header + * @param symbolsMap symbol lookup map + */ + public CoffRelocationContext(Program program, CoffFileHeader header, + Map symbolsMap) { + this.program = program; + this.header = header; + this.symbolsMap = symbolsMap; + } + + /** + * Reset context at start of COFF section relocation processing + * @param coffSection COFF section + */ + public void resetContext(CoffSectionHeader coffSection) { + this.section = coffSection; + contextMap.clear(); + } + + /** + * Get program to which relocations are being applied + * @return program + */ + public Program getProgram() { + return program; + } + + /** + * Get COFF section to which relocations are being applied + * @return COFF section + */ + public CoffSectionHeader getSection() { + return section; + } + + /** + * Get symbol required to process a relocation. Method should only be invoked + * when a symbol is required since some relocations may not require a symbol. + * @param relocation relocation whose related symbol should be returned + * @return relocation symbol + * @throws RelocationException if symbol not found + */ + public Symbol getSymbol(CoffRelocation relocation) throws RelocationException { + Symbol symbol = + symbolsMap.get(header.getSymbolAtIndex(relocation.getSymbolIndex())); + if (symbol == null) { + throw new RelocationException("missing required symbol"); + } + return symbol; + } + + /** + * Get address of symbol required to process a relocation. Method should only be invoked + * when a symbol is required since some relocations may not require a symbol. + * @param relocation relocation whose related symbol should be returned + * @return relocation symbol + * @throws RelocationException if symbol not found + */ + public Address getSymbolAddress(CoffRelocation relocation) throws RelocationException { + return getSymbol(relocation).getAddress(); + } + + /** + * Get and optionally compute context value for specified key + * @param key extension-specific context key + * @param mappingFunction function used to compute value if absent + * @return context value + */ + public Object computeContextValueIfAbsent(String key, + Function mappingFunction) { + return contextMap.computeIfAbsent(key, mappingFunction); + } + + /** + * Store context value for specified key + * @param key extension-specific context key + * @param value context value + */ + public void putContextValue(String key, Object value) { + contextMap.put(key, value); + } + + /** + * Get context value for specified key + * @param key extension-specific key + * @return context value or null if absent + */ + public Object getContextValue(String key) { + return contextMap.get(key); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/relocation/CoffRelocationHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/relocation/CoffRelocationHandler.java index 53f6a308a3..d00f2b18f2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/relocation/CoffRelocationHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/relocation/CoffRelocationHandler.java @@ -15,12 +15,11 @@ */ package ghidra.app.util.bin.format.coff.relocation; +import ghidra.app.util.bin.format.RelocationException; import ghidra.app.util.bin.format.coff.CoffFileHeader; import ghidra.app.util.bin.format.coff.CoffRelocation; import ghidra.program.model.address.Address; -import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryAccessException; -import ghidra.program.model.symbol.Symbol; import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.exception.NotFoundException; @@ -28,7 +27,7 @@ import ghidra.util.exception.NotFoundException; * An abstract class used to perform COFF relocations. Classes should extend this class to * provide relocations in a machine/processor specific way. */ -abstract public class CoffRelocationHandler implements ExtensionPoint { +public interface CoffRelocationHandler extends ExtensionPoint { /** * Checks to see whether or not an instance of this COFF relocation hander can handle @@ -37,18 +36,20 @@ abstract public class CoffRelocationHandler implements ExtensionPoint { * @param fileHeader The file header associated with the COFF to relocate. * @return True if this relocation handler can do the relocation; otherwise, false. */ - abstract public boolean canRelocate(CoffFileHeader fileHeader); + public boolean canRelocate(CoffFileHeader fileHeader); /** - * Performs a relocation. + * Performs a relocation at the specified address. * - * @param program The program to relocate. * @param address The address at which to perform the relocation. - * @param symbol The symbol used during relocation. * @param relocation The relocation information to use to perform the relocation. + * @param relocationContext relocation context data * @throws MemoryAccessException If there is a problem accessing memory during the relocation. * @throws NotFoundException If this handler didn't find a way to perform the relocation. + * @throws RelocationException if supported relocation encountered an error during processing. */ - abstract public void relocate(Program program, Address address, Symbol symbol, - CoffRelocation relocation) throws MemoryAccessException, NotFoundException; + public void relocate(Address address, CoffRelocation relocation, + CoffRelocationContext relocationContext) + throws MemoryAccessException, NotFoundException, RelocationException; + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/CoffLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/CoffLoader.java index 4601c1da8e..a9072e0d32 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/CoffLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/CoffLoader.java @@ -23,9 +23,9 @@ import java.util.Map.Entry; import ghidra.app.util.MemoryBlockUtils; import ghidra.app.util.Option; import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.format.RelocationException; import ghidra.app.util.bin.format.coff.*; -import ghidra.app.util.bin.format.coff.relocation.CoffRelocationHandler; -import ghidra.app.util.bin.format.coff.relocation.CoffRelocationHandlerFactory; +import ghidra.app.util.bin.format.coff.relocation.*; import ghidra.app.util.importer.MessageLog; import ghidra.framework.model.DomainObject; import ghidra.program.database.mem.FileBytes; @@ -128,7 +128,7 @@ public class CoffLoader extends AbstractLibrarySupportLoader { // Only one of the CoffLoader/MSCoffLoader will survive this check return loadSpecs; } - String secondary = isCLI(header) ? "cli" : null; + String secondary = isCLI(header) ? "cli" : Integer.toString(header.getFlags() & 0xffff); List results = QueryOpinionService.query(getName(), header.getMachineName(), secondary); for (QueryResult result : results) { @@ -645,6 +645,16 @@ public class CoffLoader extends AbstractLibrarySupportLoader { MessageLog log, TaskMonitor monitor) { CoffRelocationHandler handler = CoffRelocationHandlerFactory.getHandler(header); + if (handler == null) { + String msg = String.format("No COFF relocation handler for machine type 0x%x", + (Short) header.getMachine()); + log.appendMsg(msg); + Msg.error(this, program.getName() + ": " + msg); + } + + CoffRelocationContext relocationContext = + new CoffRelocationContext(program, header, symbolsMap); + int failureCount = 0; for (CoffSectionHeader section : header.getSections()) { if (monitor.isCancelled()) { @@ -653,69 +663,123 @@ public class CoffLoader extends AbstractLibrarySupportLoader { Address sectionStartAddr = sectionsMap.get(section); if (sectionStartAddr == null) { - if (section.getRelocationCount() > 0) { - log.appendMsg("Unable to process relocations for " + section.getName() + - ". No memory block was created."); + int relocCount = section.getRelocationCount(); + if (relocCount > 0) { + failureCount += relocCount; + String msg = "Unable to process " + relocCount + " relocations for section " + + section.getName() + ". No memory block was created."; + log.appendMsg(msg); + Msg.error(this, program.getName() + ": " + msg); } continue; } + relocationContext.resetContext(section); + + Address failedAddr = null; for (CoffRelocation relocation : section.getRelocations()) { if (monitor.isCancelled()) { break; } - Address address = sectionStartAddr.add(relocation.getAddress()); // assuming it's always a byte-offset + // Sections are defined with physical address while relocations use virtual address. + // Must adjust relocation address to physical. + // NOTE: Relocation address offset assumed to always be a byte-offset + Address address = + sectionStartAddr.add(relocation.getAddress() - section.getVirtualAddress()); + short relocationType = relocation.getType(); byte[] origBytes = new byte[0]; - Symbol symbol = - symbolsMap.get(header.getSymbolAtIndex(relocation.getSymbolIndex())); - if (handler == null) { - handleRelocationError(program, log, address, String.format( - "No relocation handler for machine type 0x%x to process relocation at %s with type 0x%x", - header.getMachine(), address, relocation.getType())); - } - else if (symbol == null) { - handleRelocationError(program, log, address, - String.format("No symbol to process relocation at %s with type 0x%x", - address, relocation.getType())); + ++failureCount; + handleRelocationError(program, address, relocationType, + "No COFF relocation handler", null); } else { try { origBytes = new byte[4]; - program.getMemory().getBytes(address, origBytes); - handler.relocate(program, address, symbol, relocation); + if (address.equals(failedAddr)) { + // skip relocation if previous failed relocation was at the same address + // since it is likely dependent on the previous failed relocation result + ++failureCount; + + String logMessage = + String.format("Skipped dependent COFF Relocation type 0x%x at %s", + relocationType, address.toString()); + Msg.error(this, program.getName() + ": " + logMessage); + + // TODO: once RelocationTable can retain all relocations at the same address + // this continue statement should be removed (see GP-2128) + continue; + } + //else { + program.getMemory().getBytes(address, origBytes); + handler.relocate(address, relocation, relocationContext); + //} } catch (MemoryAccessException e) { - handleRelocationError(program, log, address, String.format( - "Error accessing memory at address %s. Relocation failed.", address)); + ++failureCount; + failedAddr = address; + handleRelocationError(program, address, relocationType, + "Error accessing memory", null); } catch (NotFoundException e) { - handleRelocationError(program, log, address, - String.format("Relocation type 0x%x at address %s is not supported.", - relocation.getType(), address)); + ++failureCount; + failedAddr = address; + handleRelocationError(program, address, relocationType, + "Unsupported COFF relocation type", null); } - catch (AddressOutOfBoundsException e) { - handleRelocationError(program, log, address, - String.format("Error computing relocation at address %s.", address)); + catch (RelocationException e) { + ++failureCount; + failedAddr = address; + handleRelocationError(program, address, relocationType, e.getMessage(), + null); + } + catch (Exception e) { + ++failureCount; + failedAddr = address; + String msg = e.getMessage(); + if (msg == null) { + msg = e.toString(); + } + handleRelocationError(program, address, relocationType, msg, e); } } + // The relocation symbol may be null when either not required by a relocation or + // not found with symbol index + Symbol symbol = + symbolsMap.get(header.getSymbolAtIndex(relocation.getSymbolIndex())); + + // TODO: There may be multiple relocations at the same address. + // The RelocationTable for retaining relocations needs to be revised to handle + // this. At present only the last one will remain in the DB-backed address-based + // table. (see GP-2128) program.getRelocationTable() .add(address, relocation.getType(), new long[] { relocation.getSymbolIndex() }, origBytes, symbol != null ? symbol.getName() : ""); } } + + if (failureCount != 0) { + String msg = "Failed to process a total of " + failureCount + + " relocations. See log and error bookmarks for details."; + log.appendMsg(msg); + Msg.error(this, program.getName() + ": " + msg); + } } - private void handleRelocationError(Program program, MessageLog log, Address address, - String message) { + private void handleRelocationError(Program program, Address address, + Short relocationType, String message, Exception causeToReport) { + String bookmarkMessage = + String.format("Failed to apply COFF Relocation type 0x%x: %s", relocationType, message); program.getBookmarkManager() - .setBookmark(address, BookmarkType.ERROR, "Relocations", message); - log.appendMsg(message); + .setBookmark(address, BookmarkType.ERROR, "Relocations", bookmarkMessage); + String logMessage = String.format("Failed to apply COFF Relocation type 0x%x at %s: %s", + relocationType, address.toString(), message); + Msg.error(this, program.getName() + ": " + logMessage, causeToReport); } @Override diff --git a/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/coff/relocation/X86_32_CoffRelocationHandler.java b/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/coff/relocation/X86_32_CoffRelocationHandler.java index 710268731b..c45687b182 100644 --- a/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/coff/relocation/X86_32_CoffRelocationHandler.java +++ b/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/coff/relocation/X86_32_CoffRelocationHandler.java @@ -15,14 +15,15 @@ */ package ghidra.app.util.bin.format.coff.relocation; +import ghidra.app.util.bin.format.RelocationException; import ghidra.app.util.bin.format.coff.*; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryAccessException; -import ghidra.program.model.symbol.Symbol; import ghidra.util.exception.NotFoundException; -public class X86_32_CoffRelocationHandler extends CoffRelocationHandler { +public class X86_32_CoffRelocationHandler implements CoffRelocationHandler { @Override public boolean canRelocate(CoffFileHeader fileHeader) { @@ -30,27 +31,38 @@ public class X86_32_CoffRelocationHandler extends CoffRelocationHandler { } @Override - public void relocate(Program program, Address address, Symbol symbol, - CoffRelocation relocation) throws MemoryAccessException, NotFoundException { + public void relocate(Address address, CoffRelocation relocation, + CoffRelocationContext relocationContext) + throws MemoryAccessException, NotFoundException, RelocationException { - int addend = program.getMemory().getInt(address); + Program program = relocationContext.getProgram(); + Memory mem = program.getMemory(); + + int addend = mem.getInt(address); switch (relocation.getType()) { // We are implementing these types: case IMAGE_REL_I386_DIR32: { - program.getMemory().setInt(address, - (int) symbol.getAddress().add(addend).getOffset()); + int value = (int) relocationContext.getSymbolAddress(relocation) + .add(addend) + .getOffset(); + program.getMemory().setInt(address, value); break; } case IMAGE_REL_I386_DIR32NB: { - program.getMemory().setInt(address, - (int) symbol.getAddress().add(addend).subtract(program.getImageBase())); + int value = (int) relocationContext.getSymbolAddress(relocation) + .add(addend) + .subtract(program.getImageBase()); + mem.setInt(address, value); break; } case IMAGE_REL_I386_REL32: { - program.getMemory().setInt(address, - (int) symbol.getAddress().add(addend).subtract(address) - 4); + int value = (int) relocationContext.getSymbolAddress(relocation) + .add(addend) + .subtract(address); + value -= 4; + mem.setInt(address, value); break; } diff --git a/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/coff/relocation/X86_64_CoffRelocationHandler.java b/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/coff/relocation/X86_64_CoffRelocationHandler.java index 9b5ffc9f1e..891d8b371f 100644 --- a/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/coff/relocation/X86_64_CoffRelocationHandler.java +++ b/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/coff/relocation/X86_64_CoffRelocationHandler.java @@ -15,41 +15,55 @@ */ package ghidra.app.util.bin.format.coff.relocation; +import ghidra.app.util.bin.format.RelocationException; import ghidra.app.util.bin.format.coff.*; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryAccessException; -import ghidra.program.model.symbol.Symbol; import ghidra.util.exception.NotFoundException; -public class X86_64_CoffRelocationHandler extends CoffRelocationHandler { +public class X86_64_CoffRelocationHandler implements CoffRelocationHandler { @Override public boolean canRelocate(CoffFileHeader fileHeader) { return fileHeader.getMachine() == CoffMachineType.IMAGE_FILE_MACHINE_AMD64; } - + @Override - public void relocate(Program program, Address address, Symbol symbol, - CoffRelocation relocation) throws MemoryAccessException, NotFoundException { + public void relocate(Address address, CoffRelocation relocation, + CoffRelocationContext relocationContext) + throws MemoryAccessException, NotFoundException, RelocationException { + + Program program = relocationContext.getProgram(); + Memory mem = program.getMemory(); int distance = 0; - long addend = program.getMemory().getInt(address); + long addend = mem.getInt(address); switch (relocation.getType()) { // We are implementing these types: - case IMAGE_REL_AMD64_ADDR64: - addend = program.getMemory().getLong(address); // overwrite default 4-byte addend - program.getMemory().setLong(address, symbol.getAddress().add(addend).getOffset()); + case IMAGE_REL_AMD64_ADDR64: { + addend = mem.getLong(address); // overwrite default 4-byte addend + long value = relocationContext.getSymbolAddress(relocation) + .add(addend) + .getOffset(); + mem.setLong(address, value); break; - case IMAGE_REL_AMD64_ADDR32: - program.getMemory().setInt(address, - (int) symbol.getAddress().add(addend).getOffset()); + } + case IMAGE_REL_AMD64_ADDR32: { + int value = (int) relocationContext.getSymbolAddress(relocation) + .add(addend) + .getOffset(); + mem.setInt(address, value); break; + } case IMAGE_REL_AMD64_ADDR32NB: { - program.getMemory().setInt(address, - (int) symbol.getAddress().add(addend).subtract(program.getImageBase())); + int value = (int) relocationContext.getSymbolAddress(relocation) + .add(addend) + .subtract(program.getImageBase()); + mem.setInt(address, value); break; } case IMAGE_REL_AMD64_REL32_5: { // fallthrough to IMAGE_REL_AMD64_REL32 to get correct 'distance' @@ -68,8 +82,11 @@ public class X86_64_CoffRelocationHandler extends CoffRelocationHandler { distance++; } case IMAGE_REL_AMD64_REL32: { - program.getMemory().setInt(address, - (int) symbol.getAddress().add(addend).subtract(address) - 4 - distance); + int value = (int) relocationContext.getSymbolAddress(relocation) + .add(addend) + .subtract(address); + value -= (distance + 4); + mem.setInt(address, value); break; }