mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-09-13 21:56:19 +00:00
Merge remote-tracking branch 'origin/GP-4561_ryanmkurtz_corrupt-macho'
(Closes #6271)
This commit is contained in:
commit
e6c8f5b53c
|
@ -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.macho.commands;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import ghidra.app.util.bin.BinaryReader;
|
||||||
|
import ghidra.app.util.bin.format.macho.MachConstants;
|
||||||
|
import ghidra.program.model.data.*;
|
||||||
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
|
|
||||||
|
public class CorruptLoadCommand extends LoadCommand {
|
||||||
|
|
||||||
|
private Throwable t;
|
||||||
|
|
||||||
|
public CorruptLoadCommand(BinaryReader reader, Throwable t) throws IOException {
|
||||||
|
super(reader);
|
||||||
|
this.t = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Throwable getProblem() {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommandName() {
|
||||||
|
return "corrupt_command";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||||
|
StructureDataType struct = new StructureDataType(getCommandName(), 0);
|
||||||
|
struct.add(DWORD, "cmd", null);
|
||||||
|
struct.add(DWORD, "cmdsize", null);
|
||||||
|
struct.setCategoryPath(new CategoryPath(MachConstants.DATA_TYPE_CATEGORY));
|
||||||
|
return struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,7 +26,6 @@ import ghidra.app.util.bin.format.macho.dyld.DyldCacheHeader;
|
||||||
import ghidra.app.util.bin.format.macho.dyld.DyldCacheMappingInfo;
|
import ghidra.app.util.bin.format.macho.dyld.DyldCacheMappingInfo;
|
||||||
import ghidra.app.util.bin.format.macho.threadcommand.ThreadCommand;
|
import ghidra.app.util.bin.format.macho.threadcommand.ThreadCommand;
|
||||||
import ghidra.app.util.opinion.DyldCacheUtils.SplitDyldCache;
|
import ghidra.app.util.opinion.DyldCacheUtils.SplitDyldCache;
|
||||||
import ghidra.util.Msg;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory used to create {@link LoadCommand}s
|
* A factory used to create {@link LoadCommand}s
|
||||||
|
@ -50,113 +49,119 @@ public class LoadCommandFactory {
|
||||||
*/
|
*/
|
||||||
public static LoadCommand getLoadCommand(BinaryReader reader, MachHeader header,
|
public static LoadCommand getLoadCommand(BinaryReader reader, MachHeader header,
|
||||||
SplitDyldCache splitDyldCache) throws IOException, MachException {
|
SplitDyldCache splitDyldCache) throws IOException, MachException {
|
||||||
|
long origIndex = reader.getPointerIndex();
|
||||||
int type = reader.peekNextInt();
|
int type = reader.peekNextInt();
|
||||||
switch (type) {
|
try {
|
||||||
case LC_SEGMENT:
|
return switch (type) {
|
||||||
return new SegmentCommand(reader, header.is32bit());
|
case LC_SEGMENT:
|
||||||
case LC_SYMTAB:
|
yield new SegmentCommand(reader, header.is32bit());
|
||||||
return new SymbolTableCommand(reader,
|
case LC_SYMTAB:
|
||||||
getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
|
yield new SymbolTableCommand(reader,
|
||||||
case LC_SYMSEG:
|
getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
|
||||||
return new SymbolCommand(reader);
|
case LC_SYMSEG:
|
||||||
case LC_THREAD:
|
yield new SymbolCommand(reader);
|
||||||
case LC_UNIXTHREAD:
|
case LC_THREAD:
|
||||||
return new ThreadCommand(reader, header);
|
case LC_UNIXTHREAD:
|
||||||
case LC_LOADFVMLIB:
|
yield new ThreadCommand(reader, header);
|
||||||
case LC_IDFVMLIB:
|
case LC_LOADFVMLIB:
|
||||||
return new FixedVirtualMemorySharedLibraryCommand(reader);
|
case LC_IDFVMLIB:
|
||||||
case LC_IDENT:
|
yield new FixedVirtualMemorySharedLibraryCommand(reader);
|
||||||
return new IdentCommand(reader);
|
case LC_IDENT:
|
||||||
case LC_FVMFILE:
|
yield new IdentCommand(reader);
|
||||||
return new FixedVirtualMemoryFileCommand(reader);
|
case LC_FVMFILE:
|
||||||
case LC_PREPAGE:
|
yield new FixedVirtualMemoryFileCommand(reader);
|
||||||
return new UnsupportedLoadCommand(reader, type);
|
case LC_PREPAGE:
|
||||||
case LC_DYSYMTAB:
|
yield new UnsupportedLoadCommand(reader);
|
||||||
return new DynamicSymbolTableCommand(reader,
|
case LC_DYSYMTAB:
|
||||||
getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
|
yield new DynamicSymbolTableCommand(reader,
|
||||||
case LC_LOAD_DYLIB:
|
getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
|
||||||
case LC_ID_DYLIB:
|
case LC_LOAD_DYLIB:
|
||||||
case LC_LOAD_UPWARD_DYLIB:
|
case LC_ID_DYLIB:
|
||||||
return new DynamicLibraryCommand(reader);
|
case LC_LOAD_UPWARD_DYLIB:
|
||||||
case LC_LOAD_DYLINKER:
|
yield new DynamicLibraryCommand(reader);
|
||||||
case LC_ID_DYLINKER:
|
case LC_LOAD_DYLINKER:
|
||||||
case LC_DYLD_ENVIRONMENT:
|
case LC_ID_DYLINKER:
|
||||||
return new DynamicLinkerCommand(reader);
|
case LC_DYLD_ENVIRONMENT:
|
||||||
case LC_PREBOUND_DYLIB:
|
yield new DynamicLinkerCommand(reader);
|
||||||
return new PreboundDynamicLibraryCommand(reader);
|
case LC_PREBOUND_DYLIB:
|
||||||
case LC_ROUTINES:
|
yield new PreboundDynamicLibraryCommand(reader);
|
||||||
return new RoutinesCommand(reader, header.is32bit());
|
case LC_ROUTINES:
|
||||||
case LC_SUB_FRAMEWORK:
|
yield new RoutinesCommand(reader, header.is32bit());
|
||||||
return new SubFrameworkCommand(reader);
|
case LC_SUB_FRAMEWORK:
|
||||||
case LC_SUB_UMBRELLA:
|
yield new SubFrameworkCommand(reader);
|
||||||
return new SubUmbrellaCommand(reader);
|
case LC_SUB_UMBRELLA:
|
||||||
case LC_SUB_CLIENT:
|
yield new SubUmbrellaCommand(reader);
|
||||||
return new SubClientCommand(reader);
|
case LC_SUB_CLIENT:
|
||||||
case LC_SUB_LIBRARY:
|
yield new SubClientCommand(reader);
|
||||||
return new SubLibraryCommand(reader);
|
case LC_SUB_LIBRARY:
|
||||||
case LC_TWOLEVEL_HINTS:
|
yield new SubLibraryCommand(reader);
|
||||||
return new TwoLevelHintsCommand(reader);
|
case LC_TWOLEVEL_HINTS:
|
||||||
case LC_PREBIND_CKSUM:
|
yield new TwoLevelHintsCommand(reader);
|
||||||
return new PrebindChecksumCommand(reader);
|
case LC_PREBIND_CKSUM:
|
||||||
case LC_LOAD_WEAK_DYLIB:
|
yield new PrebindChecksumCommand(reader);
|
||||||
return new DynamicLibraryCommand(reader);
|
case LC_LOAD_WEAK_DYLIB:
|
||||||
case LC_SEGMENT_64:
|
yield new DynamicLibraryCommand(reader);
|
||||||
return new SegmentCommand(reader, header.is32bit());
|
case LC_SEGMENT_64:
|
||||||
case LC_ROUTINES_64:
|
yield new SegmentCommand(reader, header.is32bit());
|
||||||
return new RoutinesCommand(reader, header.is32bit());
|
case LC_ROUTINES_64:
|
||||||
case LC_UUID:
|
yield new RoutinesCommand(reader, header.is32bit());
|
||||||
return new UuidCommand(reader);
|
case LC_UUID:
|
||||||
case LC_RPATH:
|
yield new UuidCommand(reader);
|
||||||
return new RunPathCommand(reader);
|
case LC_RPATH:
|
||||||
case LC_CODE_SIGNATURE:
|
yield new RunPathCommand(reader);
|
||||||
return new CodeSignatureCommand(reader,
|
case LC_CODE_SIGNATURE:
|
||||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
yield new CodeSignatureCommand(reader,
|
||||||
case LC_SEGMENT_SPLIT_INFO:
|
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||||
case LC_OPTIMIZATION_HINT:
|
case LC_SEGMENT_SPLIT_INFO:
|
||||||
case LC_DYLIB_CODE_SIGN_DRS:
|
case LC_OPTIMIZATION_HINT:
|
||||||
return new LinkEditDataCommand(reader,
|
case LC_DYLIB_CODE_SIGN_DRS:
|
||||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
yield new LinkEditDataCommand(reader,
|
||||||
case LC_REEXPORT_DYLIB:
|
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||||
return new DynamicLibraryCommand(reader);
|
case LC_REEXPORT_DYLIB:
|
||||||
case LC_ENCRYPTION_INFO:
|
yield new DynamicLibraryCommand(reader);
|
||||||
case LC_ENCRYPTION_INFO_64:
|
case LC_ENCRYPTION_INFO:
|
||||||
return new EncryptedInformationCommand(reader, header.is32bit());
|
case LC_ENCRYPTION_INFO_64:
|
||||||
case LC_DYLD_INFO:
|
yield new EncryptedInformationCommand(reader, header.is32bit());
|
||||||
case LC_DYLD_INFO_ONLY:
|
case LC_DYLD_INFO:
|
||||||
return new DyldInfoCommand(reader,
|
case LC_DYLD_INFO_ONLY:
|
||||||
getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
|
yield new DyldInfoCommand(reader,
|
||||||
case LC_VERSION_MIN_MACOSX:
|
getLinkerLoadCommandReader(reader, header, splitDyldCache), header);
|
||||||
case LC_VERSION_MIN_IPHONEOS:
|
case LC_VERSION_MIN_MACOSX:
|
||||||
case LC_VERSION_MIN_TVOS:
|
case LC_VERSION_MIN_IPHONEOS:
|
||||||
case LC_VERSION_MIN_WATCHOS:
|
case LC_VERSION_MIN_TVOS:
|
||||||
return new VersionMinCommand(reader);
|
case LC_VERSION_MIN_WATCHOS:
|
||||||
case LC_FUNCTION_STARTS:
|
yield new VersionMinCommand(reader);
|
||||||
return new FunctionStartsCommand(reader,
|
case LC_FUNCTION_STARTS:
|
||||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
yield new FunctionStartsCommand(reader,
|
||||||
case LC_MAIN:
|
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||||
return new EntryPointCommand(reader);
|
case LC_MAIN:
|
||||||
case LC_DATA_IN_CODE:
|
yield new EntryPointCommand(reader);
|
||||||
return new DataInCodeCommand(reader,
|
case LC_DATA_IN_CODE:
|
||||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
yield new DataInCodeCommand(reader,
|
||||||
case LC_SOURCE_VERSION:
|
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||||
return new SourceVersionCommand(reader);
|
case LC_SOURCE_VERSION:
|
||||||
case LC_LAZY_LOAD_DYLIB:
|
yield new SourceVersionCommand(reader);
|
||||||
return new DynamicLibraryCommand(reader);
|
case LC_LAZY_LOAD_DYLIB:
|
||||||
case LC_LINKER_OPTIONS:
|
yield new DynamicLibraryCommand(reader);
|
||||||
return new LinkerOptionCommand(reader);
|
case LC_LINKER_OPTIONS:
|
||||||
case LC_BUILD_VERSION:
|
yield new LinkerOptionCommand(reader);
|
||||||
return new BuildVersionCommand(reader);
|
case LC_BUILD_VERSION:
|
||||||
case LC_DYLD_EXPORTS_TRIE:
|
yield new BuildVersionCommand(reader);
|
||||||
return new DyldExportsTrieCommand(reader,
|
case LC_DYLD_EXPORTS_TRIE:
|
||||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
yield new DyldExportsTrieCommand(reader,
|
||||||
case LC_DYLD_CHAINED_FIXUPS:
|
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||||
return new DyldChainedFixupsCommand(reader,
|
case LC_DYLD_CHAINED_FIXUPS:
|
||||||
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
yield new DyldChainedFixupsCommand(reader,
|
||||||
case LC_FILESET_ENTRY:
|
getLinkerLoadCommandReader(reader, header, splitDyldCache));
|
||||||
return new FileSetEntryCommand(reader);
|
case LC_FILESET_ENTRY:
|
||||||
default:
|
yield new FileSetEntryCommand(reader);
|
||||||
Msg.warn(header, "Unsupported load command " + Integer.toHexString(type));
|
default:
|
||||||
return new UnsupportedLoadCommand(reader, type);
|
yield new UnsupportedLoadCommand(reader);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
reader.setPointerIndex(origIndex);
|
||||||
|
return new CorruptLoadCommand(reader, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -219,6 +219,6 @@ public final class LoadCommandTypes {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "Unknown load command type: " + Integer.toHexString(type);
|
return "LC_UNKNOWN_%X".formatted(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,16 +23,14 @@ import ghidra.program.model.data.*;
|
||||||
import ghidra.util.exception.DuplicateNameException;
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
|
|
||||||
public class UnsupportedLoadCommand extends LoadCommand {
|
public class UnsupportedLoadCommand extends LoadCommand {
|
||||||
private int type;
|
|
||||||
|
|
||||||
UnsupportedLoadCommand(BinaryReader reader, int type) throws IOException {
|
UnsupportedLoadCommand(BinaryReader reader) throws IOException {
|
||||||
super(reader);
|
super(reader);
|
||||||
this.type = type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCommandName() {
|
public String getCommandName() {
|
||||||
return "Unsupported Load Command Type = 0x" + Integer.toHexString(type);
|
return "unsupported_command";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,6 +19,8 @@ import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.apache.commons.collections4.map.LazySortedMap;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.analysis.rust.RustConstants;
|
import ghidra.app.plugin.core.analysis.rust.RustConstants;
|
||||||
import ghidra.app.plugin.core.analysis.rust.RustUtilities;
|
import ghidra.app.plugin.core.analysis.rust.RustUtilities;
|
||||||
import ghidra.app.util.MemoryBlockUtils;
|
import ghidra.app.util.MemoryBlockUtils;
|
||||||
|
@ -147,6 +149,7 @@ public class MachoProgramBuilder {
|
||||||
processLocalRelocations();
|
processLocalRelocations();
|
||||||
processEncryption();
|
processEncryption();
|
||||||
processUnsupportedLoadCommands();
|
processUnsupportedLoadCommands();
|
||||||
|
processCorruptLoadCommands();
|
||||||
|
|
||||||
// Markup structures
|
// Markup structures
|
||||||
markupHeaders(machoHeader, setupHeaderAddr(machoHeader.getAllSegments()));
|
markupHeaders(machoHeader, setupHeaderAddr(machoHeader.getAllSegments()));
|
||||||
|
@ -455,39 +458,58 @@ public class MachoProgramBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to discover and set the entry point.
|
* Attempts to discover and set the entry point.
|
||||||
|
* <p>
|
||||||
|
* A program may declare multiple entry points to, for example, confuse static analysis tools.
|
||||||
|
* We will sort the discovered entry points by priorities assigned to each type of load
|
||||||
|
* command, and only use the one with the highest priority.
|
||||||
*
|
*
|
||||||
* @throws Exception If there was a problem discovering or setting the entry point.
|
* @throws Exception If there was a problem discovering or setting the entry point.
|
||||||
*/
|
*/
|
||||||
protected void processEntryPoint() throws Exception {
|
protected void processEntryPoint() throws Exception {
|
||||||
monitor.setMessage("Processing entry point...");
|
monitor.setMessage("Processing entry point...");
|
||||||
Address entryPointAddr = null;
|
|
||||||
|
|
||||||
EntryPointCommand entryPointCommand =
|
final int LC_MAIN_PRIORITY = 1;
|
||||||
machoHeader.getFirstLoadCommand(EntryPointCommand.class);
|
final int LC_UNIX_THREAD_PRIORITY = 2;
|
||||||
if (entryPointCommand != null) {
|
final int LC_THREAD_PRIORITY = 3;
|
||||||
long offset = entryPointCommand.getEntryOffset();
|
SortedMap<Integer, List<Address>> priorityMap =
|
||||||
|
LazySortedMap.lazySortedMap(new TreeMap<>(), () -> new ArrayList<>());
|
||||||
|
|
||||||
|
for (EntryPointCommand cmd : machoHeader.getLoadCommands(EntryPointCommand.class)) {
|
||||||
|
long offset = cmd.getEntryOffset();
|
||||||
if (offset > 0) {
|
if (offset > 0) {
|
||||||
SegmentCommand segment = machoHeader.getSegment("__TEXT");
|
SegmentCommand segment = machoHeader.getSegment("__TEXT");
|
||||||
if (segment != null) {
|
if (segment != null) {
|
||||||
entryPointAddr = space.getAddress(segment.getVMaddress()).add(offset);
|
priorityMap.get(LC_MAIN_PRIORITY)
|
||||||
|
.add(space.getAddress(segment.getVMaddress()).add(offset));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entryPointAddr == null) {
|
for (ThreadCommand threadCommand : machoHeader.getLoadCommands(ThreadCommand.class)) {
|
||||||
ThreadCommand threadCommand = machoHeader.getFirstLoadCommand(ThreadCommand.class);
|
int priority = threadCommand.getCommandType() == LoadCommandTypes.LC_UNIXTHREAD
|
||||||
if (threadCommand != null) {
|
? LC_UNIX_THREAD_PRIORITY
|
||||||
long pointer = threadCommand.getInitialInstructionPointer();
|
: LC_THREAD_PRIORITY;
|
||||||
if (pointer != -1) {
|
long pointer = threadCommand.getInitialInstructionPointer();
|
||||||
entryPointAddr = space.getAddress(pointer);
|
if (pointer != -1) {
|
||||||
}
|
priorityMap.get(priority).add(space.getAddress(pointer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entryPointAddr != null) {
|
if (!priorityMap.isEmpty()) {
|
||||||
program.getSymbolTable().createLabel(entryPointAddr, "entry", SourceType.IMPORTED);
|
boolean realEntryFound = false;
|
||||||
program.getSymbolTable().addExternalEntryPoint(entryPointAddr);
|
for (List<Address> addrs : priorityMap.values()) {
|
||||||
createOneByteFunction("entry", entryPointAddr);
|
for (Address addr : addrs) {
|
||||||
|
if (!realEntryFound) {
|
||||||
|
program.getSymbolTable().createLabel(addr, "entry", SourceType.IMPORTED);
|
||||||
|
program.getSymbolTable().addExternalEntryPoint(addr);
|
||||||
|
createOneByteFunction("entry", addr);
|
||||||
|
realEntryFound = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.appendMsg("Ignoring entry point at: " + addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.appendMsg("Unable to determine entry point.");
|
log.appendMsg("Unable to determine entry point.");
|
||||||
|
@ -1341,9 +1363,27 @@ public class MachoProgramBuilder {
|
||||||
protected void processUnsupportedLoadCommands() throws CancelledException {
|
protected void processUnsupportedLoadCommands() throws CancelledException {
|
||||||
monitor.setMessage("Processing unsupported load commands...");
|
monitor.setMessage("Processing unsupported load commands...");
|
||||||
|
|
||||||
for (LoadCommand loadCommand : machoHeader.getLoadCommands(UnsupportedLoadCommand.class)) {
|
for (LoadCommand cmd : machoHeader.getLoadCommands(UnsupportedLoadCommand.class)) {
|
||||||
monitor.checkCancelled();
|
monitor.checkCancelled();
|
||||||
log.appendMsg(loadCommand.getCommandName());
|
log.appendMsg("Skipping unsupported load command: " +
|
||||||
|
LoadCommandTypes.getLoadCommandName(cmd.getCommandType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes {@link LoadCommand}s that appear to be corrupt.
|
||||||
|
*
|
||||||
|
* @throws CancelledException if the operation was cancelled.
|
||||||
|
*/
|
||||||
|
protected void processCorruptLoadCommands() throws CancelledException {
|
||||||
|
monitor.setMessage("Processing corrupt load commands...");
|
||||||
|
|
||||||
|
for (CorruptLoadCommand cmd : machoHeader.getLoadCommands(CorruptLoadCommand.class)) {
|
||||||
|
monitor.checkCancelled();
|
||||||
|
log.appendMsg("Skipping corrupt load command: %s (%s: %s)".formatted(
|
||||||
|
LoadCommandTypes.getLoadCommandName(cmd.getCommandType()),
|
||||||
|
cmd.getProblem().getClass().getSimpleName(),
|
||||||
|
cmd.getProblem().getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue