diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java index aa9777a9c6..d15e87818c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java @@ -215,8 +215,8 @@ public class DWARFAnalyzer extends AbstractAnalyzer { "Manually re-run the DWARF analyzer after adjusting the options or start it via Dwarf_ExtractorScript"); } catch (DWARFException | IOException e) { - log.appendMsg("Error during DWARFAnalyzer import: " + e.getMessage()); - Msg.error(this, "Error during DWARFAnalyzer import: " + e.getMessage(), e); + log.appendMsg("Error during DWARFAnalyzer import: " + e); + Msg.error(this, "Error during DWARFAnalyzer import: ", e); } return false; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/FaultTolerantInputStream.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/FaultTolerantInputStream.java new file mode 100644 index 0000000000..2efe8ccac3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/FaultTolerantInputStream.java @@ -0,0 +1,103 @@ +/* ### + * 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; + +import java.util.Arrays; +import java.util.function.BiConsumer; + +import java.io.IOException; +import java.io.InputStream; + +import ghidra.formats.gfilesystem.FSUtilities; +import ghidra.util.Msg; + +/** + * An InputStream wrapper that suppresses any {@link IOException}s thrown by the wrapped stream + * and starts returning 0 value bytes for all subsequent reads. + */ +public class FaultTolerantInputStream extends InputStream { + + private InputStream delegate; + private long currentPosition; + private long totalLength; + private Throwable error; + private long faultPosition; + private long faultByteCount; + private BiConsumer errorConsumer; + + /** + * Creates instance. + * + * @param delegate {@link InputStream} to wrap + * @param length expected length of the stream + * @param errorConsumer consumer that will accept errors, if null Msg.error() will be used + */ + public FaultTolerantInputStream(InputStream delegate, long length, + BiConsumer errorConsumer) { + this.delegate = delegate; + this.totalLength = length; + this.errorConsumer = errorConsumer != null ? errorConsumer : this::defaultErrorHandler; + } + + @Override + public void close() throws IOException { + FSUtilities.uncheckedClose(delegate, null); + if (error != null) { + errorConsumer.accept( + "Errors encountered when reading at position %d, %d bytes faulted, replaced with 0's" + .formatted(faultPosition, faultByteCount), + error); + } + } + + private void defaultErrorHandler(String msg, Throwable th) { + Msg.error(this, msg, th); + } + + @Override + public int read() throws IOException { + byte[] buffer = new byte[1]; + int bytesRead = read(buffer, 0, 1); + return bytesRead == 1 ? Byte.toUnsignedInt(buffer[0]) : -1; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (error == null) { + // haven't hit an error yet, try to read normally + try { + int bytesRead = delegate.read(b, off, len); + currentPosition += bytesRead; + return bytesRead; + } + catch (IOException e) { + faultPosition = currentPosition; + error = e; + } + } + + // there was an error, return 0's from now on + long remaining = totalLength - currentPosition; + if (remaining <= 0) { + return 0; + } + len = (int) Math.min(len, remaining); + Arrays.fill(b, off, off + len, (byte) 0); + currentPosition += len; + faultByteCount += len; + return len; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/MemoryLoadable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/MemoryLoadable.java index 6b58256c8d..fbe3b1ad97 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/MemoryLoadable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/MemoryLoadable.java @@ -15,10 +15,53 @@ */ package ghidra.app.util.bin.format; +import java.io.IOException; +import java.io.InputStream; +import java.util.Hashtable; +import java.util.function.BiConsumer; + +import ghidra.app.util.bin.format.elf.ElfLoadHelper; +import ghidra.program.model.address.Address; +import ghidra.program.model.mem.MemoryBlock; + /** - * MemoryLoadable is a marker interface which identifies a file format - * object which uniquely identifies a memory loadable portion of a binary file. + * MemoryLoadable serves as both a marker interface which identifies a memory + * loadable portion of a binary file (supports use as a {@link Hashtable} key). In addition, + * it serves to supply the neccessary input stream to create a {@link MemoryBlock}. + * */ public interface MemoryLoadable { + /** + * Determine if the use of input stream decompression or filtering via an extension is neccessary. + * If this method returns true and a + * {@link #getFilteredLoadInputStream(ElfLoadHelper, Address, long, BiConsumer) filtered stream} + * is required and will prevent the use of a direct mapping to file bytes for affected memory + * regions. + * @param elfLoadHelper ELF load helper + * @param start memory load address + * @return true if the use of a filtered input stream is required + */ + public boolean hasFilteredLoadInputStream(ElfLoadHelper elfLoadHelper, Address start); + + /** + * Return filtered InputStream for loading a memory block (includes non-loaded OTHER blocks). + * See {@link #hasFilteredLoadInputStream(ElfLoadHelper, Address)}. + * @param elfLoadHelper ELF load helper + * @param start memory load address + * @param dataLength the in-memory data length in bytes (actual bytes read from dataInput may be more) + * @param errorConsumer consumer that will accept errors which may occur during stream + * decompression, if null Msg.error() will be used. + * @return filtered input stream or original input stream + * @throws IOException if error initializing filtered input stream + */ + public InputStream getFilteredLoadInputStream(ElfLoadHelper elfLoadHelper, Address start, + long dataLength, BiConsumer errorConsumer) throws IOException; + + /** + * {@return raw data input stream associated with this loadable object.} + * @throws IOException if error initializing input stream + */ + public InputStream getRawInputStream() throws IOException; + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfCompressedSectionHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfCompressedSectionHeader.java new file mode 100644 index 0000000000..fdcb9efe83 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfCompressedSectionHeader.java @@ -0,0 +1,124 @@ +/* ### + * 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.elf; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; + +/** + * Header at the beginning of an ELF compressed section. + *

+ * See https://docs.oracle.com/cd/E53394_01/html/E54813/section_compression.html + *

+ *

+ * typedef struct {
+ *      Elf32_Word      ch_type;
+ *      Elf32_Word      ch_size;
+ *      Elf32_Word      ch_addralign;
+ * } Elf32_Chdr;
+ * 
+ * typedef struct {
+ *      Elf64_Word      ch_type;
+ *      Elf64_Word      ch_reserved;
+ *      Elf64_Xword     ch_size;
+ *      Elf64_Xword     ch_addralign;
+ * } Elf64_Chdr;
+ * 
+ */ +public class ElfCompressedSectionHeader { + public static final int ELFCOMPRESS_ZLIB = 1; + //public static final int ELFCOMPRESS_LOOS = 0x60000000; + //public static final int ELFCOMPRESS_HIOS = 0x6fffffff; + //public static final int ELFCOMPRESS_LOPROC = 0x70000000; + //public static final int ELFCOMPRESS_HIPROC = 0x7fffffff; + private static final int SIZEOF_HEADER_32 = 12; // sizeof(word)*3 fields; + private static final int SIZEOF_HEADER_64 = 24; // sizeof(word)*2 fields + sizeof(xword)*2 fields + + /** + * Reads an Elf(32|64)_Chdr from the current position in the supplied stream. + * + * @param reader stream to read from + * @param elf ElfHeader that defines the format of the binary + * @return new {@link ElfCompressedSectionHeader} instance, never null + * @throws IOException if error reading the header + */ + public static ElfCompressedSectionHeader read(BinaryReader reader, ElfHeader elf) + throws IOException { + return elf.is32Bit() ? read32(reader) : read64(reader); + } + + private int ch_type; // compression algo + private long ch_size; // size, in bytes, of uncompressed data + private long ch_addralign; // alignment of the uncompressed data, sh_addralign + private int headerSize; // metadata about this header, used to skip the header when re-reading + + private ElfCompressedSectionHeader(int type, long size, long align, int headerSize) { + this.ch_type = type; + this.ch_size = size; + this.ch_addralign = align; + this.headerSize = headerSize; + } + + /** + * {@return the compression type, see ELFCOMPRESS_ZLIB} + */ + public int getCh_type() { + return ch_type; + } + + /** + * {@return the uncompressed size} + */ + public long getCh_size() { + return ch_size; + } + + /** + * {@return the address alignment value}. + *

+ * See {@link ElfSectionHeader#getAddressAlignment()} + */ + public long getCh_addralign() { + return ch_addralign; + } + + /** + * {@return the size of this header struct} + */ + public int getHeaderSize() { + return headerSize; + } + + //--------------------------------------------------------------------------------------------- + + private static ElfCompressedSectionHeader read32(BinaryReader reader) throws IOException { + int type = reader.readNextInt(); + long size = reader.readNextUnsignedInt(); + long align = reader.readNextUnsignedInt(); + + return new ElfCompressedSectionHeader(type, size, align, SIZEOF_HEADER_32); + } + + private static ElfCompressedSectionHeader read64(BinaryReader reader) throws IOException { + int type = reader.readNextInt(); + /*long unused_reserved = */ reader.readNextUnsignedInt(); + long size = reader.readNextLong(); + long align = reader.readNextLong(); + + return new ElfCompressedSectionHeader(type, size, align, SIZEOF_HEADER_64); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfHeader.java index f12a969eca..685a480a61 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfHeader.java @@ -15,10 +15,11 @@ */ package ghidra.app.util.bin.format.elf; -import java.io.IOException; import java.util.*; import java.util.function.Consumer; +import java.io.IOException; + import ghidra.app.util.bin.*; import ghidra.app.util.bin.format.elf.ElfRelocationTable.TableFormat; import ghidra.app.util.bin.format.elf.extend.ElfExtensionFactory; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfProgramHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfProgramHeader.java index e45341b763..d2f7dd80b9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfProgramHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfProgramHeader.java @@ -16,11 +16,13 @@ package ghidra.app.util.bin.format.elf; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; +import java.util.function.BiConsumer; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.StructConverter; +import ghidra.app.util.bin.*; import ghidra.app.util.bin.format.MemoryLoadable; +import ghidra.program.model.address.Address; import ghidra.program.model.data.*; import ghidra.util.StringUtilities; @@ -264,6 +266,34 @@ public class ElfProgramHeader return header.getLoadAdapter().getAdjustedLoadSize(this); } + @Override + public boolean hasFilteredLoadInputStream(ElfLoadHelper elfLoadHelper, Address start) { + return header.getLoadAdapter().hasFilteredLoadInputStream(elfLoadHelper, this, start); + } + + @Override + public InputStream getFilteredLoadInputStream(ElfLoadHelper elfLoadHelper, Address start, + long dataLength, BiConsumer errorConsumer) throws IOException { + return header.getLoadAdapter() + .getFilteredLoadInputStream(elfLoadHelper, this, start, dataLength, + getRawInputStream()); + } + + @Override + public InputStream getRawInputStream() throws IOException { + return getRawByteProvider().getInputStream(0); + } + + private ByteProvider getRawByteProvider() { + if (reader == null) { + throw new UnsupportedOperationException("This ElfProgramHeader does not have a reader"); + } + if (p_filesz <= 0) { + return ByteProvider.EMPTY_BYTEPROVIDER; + } + return new ByteProviderWrapper(reader.getByteProvider(), p_offset, p_filesz); + } + /** * Returns the binary reader. * @return the binary reader diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeader.java index f537bab9b0..7d0895375e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeader.java @@ -18,13 +18,16 @@ package ghidra.app.util.bin.format.elf; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; +import java.util.function.BiConsumer; +import java.util.zip.InflaterInputStream; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.StructConverter; +import ghidra.app.util.bin.*; import ghidra.app.util.bin.format.MemoryLoadable; +import ghidra.program.model.address.Address; import ghidra.program.model.data.*; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryBlock; +import ghidra.util.Msg; import ghidra.util.StringUtilities; /** @@ -89,6 +92,8 @@ public class ElfSectionHeader implements StructConverter, MemoryLoadable { private boolean modified = false; private boolean bytesChanged = false; + private ElfCompressedSectionHeader compressedHeader; + public ElfSectionHeader(BinaryReader reader, ElfHeader header) throws IOException { this.reader = reader; @@ -121,9 +126,40 @@ public class ElfSectionHeader implements StructConverter, MemoryLoadable { sh_addralign = reader.readNextLong(); sh_entsize = reader.readNextLong(); } + + if ((sh_flags & ElfSectionHeaderConstants.SHF_COMPRESSED) != 0) { + compressedHeader = readCompressedSectionHeader(); + } //checkSize(); } + private ElfCompressedSectionHeader readCompressedSectionHeader() { + try { + if (!isValidForCompressed(reader.length())) { + throw new IOException( + "Invalid compressed section: %s".formatted(getNameAsString())); + } + ElfCompressedSectionHeader result = + ElfCompressedSectionHeader.read(getRawSectionReader(), header); + if (!isSupportedCompressionType(result.getCh_type())) { + throw new IOException("Unknown ELF section compression type 0x%x for section %s" + .formatted(compressedHeader.getCh_type(), getNameAsString())); + } + return result; + } + catch (IOException e) { + Msg.warn(this, "Error reading compressed section information: " + e); + Msg.debug(this, "Error reading compressed section information", e); + } + return null; + } + + private boolean isValidForCompressed(long streamLength) { + long endOffset = sh_offset + sh_size; + return !isAlloc() && sh_offset >= 0 && sh_size > 0 && endOffset > 0 && + endOffset <= streamLength; + } + ElfSectionHeader(ElfHeader header, MemoryBlock block, int sh_name, long imageBase) throws MemoryAccessException { @@ -201,7 +237,9 @@ public class ElfSectionHeader implements StructConverter, MemoryLoadable { * @return the section address alignment constraints */ public long getAddressAlignment() { - return sh_addralign; + return compressedHeader == null + ? sh_addralign + : compressedHeader.getCh_addralign(); } /** @@ -247,6 +285,28 @@ public class ElfSectionHeader implements StructConverter, MemoryLoadable { return header.getLoadAdapter().isSectionAllocated(this); } + /** + * Returns true if this section is compressed in a supported manner. This does NOT include + * sections that carry compressed data, such as ".zdebuginfo" type sections. + * + * @return true if the section was compressed and needs to be decompressed, false if normal + * section + */ + public boolean isCompressed() { + return compressedHeader != null; + } + + private boolean isSupportedCompressionType(int compressionType) { + return switch ( compressionType ) { + case ElfCompressedSectionHeader.ELFCOMPRESS_ZLIB -> true; + default -> false; + }; + } + + private boolean isNoBits() { + return sh_type == ElfSectionHeaderConstants.SHT_NOBITS; + } + /** * This member holds extra information, whose interpretation * depends on the section type. @@ -382,14 +442,35 @@ public class ElfSectionHeader implements StructConverter, MemoryLoadable { } /** - * Get the adjusted size of the section in bytes (i.e., memory block) which relates to this section header; it may be zero - * if no block should be created. The returned value reflects any adjustment the ElfExtension may require - * based upon the specific processor/language implementation which may require filtering of file bytes - * as read into memory. - * @return the number of bytes in the resulting memory block + * Returns the logical size of this section, possibly affected by compression. + * + * @return logical size of this section, see {@link #getSize()} */ - public long getAdjustedSize() { - return header.getLoadAdapter().getAdjustedSize(this); + public long getLogicalSize() { + return compressedHeader == null + ? sh_size + : compressedHeader.getCh_size(); + } + + @Override + public boolean hasFilteredLoadInputStream(ElfLoadHelper elfLoadHelper, Address start) { + return isCompressed() || + header.getLoadAdapter().hasFilteredLoadInputStream(elfLoadHelper, this, start); + } + + @Override + public InputStream getFilteredLoadInputStream(ElfLoadHelper elfLoadHelper, Address start, + long dataLength, BiConsumer errorConsumer) throws IOException { + InputStream is = isCompressed() + ? getDecompressedDataStream(dataLength, errorConsumer) + : getRawInputStream(); + return header.getLoadAdapter() + .getFilteredLoadInputStream(elfLoadHelper, this, start, dataLength, is); + } + + @Override + public InputStream getRawInputStream() throws IOException { + return getRawSectionByteProvider().getInputStream(0); } /** @@ -413,36 +494,46 @@ public class ElfSectionHeader implements StructConverter, MemoryLoadable { return "SHT_0x" + StringUtilities.pad(Integer.toHexString(sh_type), '0', 8); } - /** - * Returns the actual data bytes from the file for this section - * @return the actual data bytes from the file for this section - * @throws IOException if an I/O error occurs while reading the file - */ - public byte[] getData() throws IOException { - if (sh_type == ElfSectionHeaderConstants.SHT_NOBITS) { - return new byte[0]; + private InputStream getDecompressedDataStream(long dataLength, + BiConsumer errorConsumer) throws IOException { + if (compressedHeader == null || dataLength != compressedHeader.getCh_size()) { + throw new UnsupportedOperationException(); } - if (data != null) { - return data; - } - if (reader == null) { - throw new UnsupportedOperationException("This ElfSectionHeader does not have a reader"); - } - return reader.readByteArray(sh_offset, (int) sh_size); + + int skip = compressedHeader.getHeaderSize(); + InputStream is = getRawSectionByteProvider().getInputStream(skip); + + is = getDecompressionStream(is); + + return new FaultTolerantInputStream(is, compressedHeader.getCh_size(), errorConsumer); } - /** - * Returns an input stream starting at offset into - * the byte provider. - * NOTE: Do not use this method if you have called setData(). - * @return the input stream - * @throws IOException if an I/O error occurs - */ - public InputStream getDataStream() throws IOException { + private ByteProvider getRawSectionByteProvider() { if (reader == null) { throw new UnsupportedOperationException("This ElfSectionHeader does not have a reader"); } - return reader.getByteProvider().getInputStream(sh_offset); + if (isNoBits()) { + return ByteProvider.EMPTY_BYTEPROVIDER; + } + return new ByteProviderWrapper(reader.getByteProvider(), sh_offset, sh_size); + } + + private BinaryReader getRawSectionReader() throws IOException { + return new BinaryReader(getRawSectionByteProvider(), header.isLittleEndian()); + } + + private InputStream getDecompressionStream(InputStream compressedStream) throws IOException { + switch (compressedHeader.getCh_type()) { + case ElfCompressedSectionHeader.ELFCOMPRESS_ZLIB: + Msg.debug(this, + "Decompressing ELF section %s, original/decompressed size: 0x%x/0x%x" + .formatted(getNameAsString(), sh_size, compressedHeader.getCh_size())); + return new InflaterInputStream(compressedStream); + default: + throw new IOException("Unknown ELF section compression type 0x%x for section %s" + .formatted(compressedHeader.getCh_type(), getNameAsString())); + } + } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeaderConstants.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeaderConstants.java index 5929bcaa82..821380a18f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeaderConstants.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeaderConstants.java @@ -141,6 +141,8 @@ public class ElfSectionHeaderConstants { public static final int SHF_GROUP = (1 << 9); /**The section that holds thread-local data.*/ public static final int SHF_TLS = (1 << 10); + /**The bytes of the section are compressed */ + public static final int SHF_COMPRESSED = (1 << 11); /**This section is excluded from the final executable or shared library.*/ public static final int SHF_EXCLUDE = 0x80000000; /**The section contains OS-specific data.*/ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/extend/ElfLoadAdapter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/extend/ElfLoadAdapter.java index 3cce2bcd0a..5f317e56ee 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/extend/ElfLoadAdapter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/extend/ElfLoadAdapter.java @@ -15,6 +15,7 @@ */ package ghidra.app.util.bin.format.elf.extend; +import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -463,13 +464,19 @@ public class ElfLoadAdapter { } /** - * Return the memory section size in bytes for the specified section header. - * The returned value will be consistent with any byte filtering which may be required. + * Returns the memory section size in bytes for the specified section header. + *

+ * The returned value will be consistent with any byte filtering and decompression which + * may be required. + *

+ * The default implementation returns the section's + * {@link ElfSectionHeader#getLogicalSize() logical size} + * * @param section the section header * @return preferred memory block size in bytes which corresponds to the specified section header */ public long getAdjustedSize(ElfSectionHeader section) { - return section.getSize(); + return section.getLogicalSize(); } /** @@ -482,9 +489,11 @@ public class ElfLoadAdapter { * @param dataLength the in-memory data length in bytes (actual bytes read from dataInput may be more) * @param dataInput the source input stream * @return filtered input stream or original input stream + * @throws IOException if error initializing filtered stream */ public InputStream getFilteredLoadInputStream(ElfLoadHelper elfLoadHelper, - MemoryLoadable loadable, Address start, long dataLength, InputStream dataInput) { + MemoryLoadable loadable, Address start, long dataLength, InputStream dataInput) + throws IOException { return dataInput; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java index 3f1e3bf2cf..c02d9b080c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java @@ -329,18 +329,18 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { return false; } - if (elf.getLoadAdapter().hasFilteredLoadInputStream(this, loadable, start)) { + byte[] bytes = new byte[(int) length]; + int bytesRead; + if (loadable.hasFilteredLoadInputStream(this, start)) { // block is unable to map directly to file bytes - read from filtered input stream - try (InputStream dataInput = - getInitializedBlockInputStream(loadable, start, fileOffset, length)) { - byte[] bytes = new byte[(int) length]; - return dataInput.read(bytes) == bytes.length && isZeroedArray(bytes, bytes.length); + try (InputStream is = loadable.getFilteredLoadInputStream(this, start, length, null)) { + bytesRead = is.read(bytes); } } - - byte[] bytes = new byte[(int) length]; - return fileBytes.getModifiedBytes(fileOffset, bytes) == bytes.length && - isZeroedArray(bytes, bytes.length); + else { + bytesRead = fileBytes.getModifiedBytes(fileOffset, bytes); + } + return bytesRead == length && isZeroedArray(bytes, bytes.length); } @Override @@ -3332,7 +3332,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { if (elfSectionToLoad.isAlloc() && addr != 0) { AddressSpace loadSpace = getSectionAddressSpace(elfSectionToLoad); if (loadSpace.equals(space)) { - long sectionByteLength = elfSectionToLoad.getAdjustedSize(); // size in bytes + long sectionByteLength = elf.getLoadAdapter().getAdjustedSize(elfSectionToLoad); // size in bytes long sectionLength = sectionByteLength / space.getAddressableUnitSize(); relocStartAddr = Math.max(relocStartAddr, addr + sectionLength); } @@ -3399,7 +3399,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { throws AddressOutOfBoundsException { long addr = elfSectionToLoad.getAddress(); - long sectionByteLength = elfSectionToLoad.getAdjustedSize(); // size in bytes + long sectionByteLength = elf.getLoadAdapter().getAdjustedSize(elfSectionToLoad); // size in bytes long loadOffset = elfSectionToLoad.getOffset(); // file offset in bytes Long nextRelocOffset = null; @@ -3569,22 +3569,6 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { return sym; } - /** - * Get a suitable input stream for loading a memory block defined by a specified loadable. - * @param loadable Corresponding ElfSectionHeader or ElfProgramHeader for the memory block to be created. - * @param start memory load address - * @param fileOffset byte provider offset - * @param dataLength the in-memory data length in bytes (actual bytes read from dataInput may be more) - * @return input stream for loading memory block - * @throws IOException if failed to obtain input stream - */ - private InputStream getInitializedBlockInputStream(MemoryLoadable loadable, Address start, - long fileOffset, long dataLength) throws IOException { - InputStream dataInput = elf.getByteProvider().getInputStream(fileOffset); - return elf.getLoadAdapter() - .getFilteredLoadInputStream(this, loadable, start, dataLength, dataInput); - } - private String formatFloat(float value, int maxDecimalPlaces) { NumberFormat format = NumberFormat.getNumberInstance(); format.setMaximumIntegerDigits(maxDecimalPlaces); @@ -3642,26 +3626,40 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { // Msg.debug(this, // "Loading block " + name + " at " + start + " from file offset " + fileOffset); - long endOffset = fileOffset + revisedLength - 1; - if (endOffset >= fileBytes.getSize()) { + long compSectOrigSize = loadable instanceof ElfSectionHeader header && header.isCompressed() + ? header.getSize() + : -1; + + String blockComment = comment; + if (compSectOrigSize >= 0) { + blockComment += + " (decompressed, original length: 0x%x)".formatted(compSectOrigSize); + } + else if ((fileOffset + revisedLength - 1) >= fileBytes.getSize()) { + // ensure valid length for non-compressed items revisedLength = fileBytes.getSize() - fileOffset; log("Truncating block load for " + name + " which exceeds file length"); } - - String blockComment = comment; if (dataLength != revisedLength) { + // either gt MAX_BINARY_SIZE or gt fileBytes size blockComment += " (section truncated)"; } MemoryBlock block = null; try { - if (elf.getLoadAdapter().hasFilteredLoadInputStream(this, loadable, start)) { + if (loadable != null && loadable.hasFilteredLoadInputStream(this, start)) { // block is unable to map directly to file bytes - load from input stream - try (InputStream dataInput = - getInitializedBlockInputStream(loadable, start, fileOffset, revisedLength)) { + try (InputStream is = + loadable.getFilteredLoadInputStream(this, start, revisedLength, + (errorMsg, th) -> { + String loadableTypeStr = + compSectOrigSize >= 0 ? "compressed section " : ""; + log.appendMsg("Error when reading %s[%s]: %s".formatted(loadableTypeStr, + name, errorMsg)); + Msg.error(this, errorMsg, th); + })) { block = MemoryBlockUtils.createInitializedBlock(program, isOverlay, name, start, - dataInput, revisedLength, blockComment, BLOCK_SOURCE_NAME, r, w, x, log, - monitor); + is, revisedLength, blockComment, BLOCK_SOURCE_NAME, r, w, x, log, monitor); } } else { @@ -3674,8 +3672,8 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { finally { if (block == null) { Address end = start.addNoWrap(revisedLength - 1); - log("Unexpected ELF memory bock load conflict when creating '" + name + "' at " + - start.toString(true) + "-" + end.toString(true)); + log("Unexpected ELF memory block load conflict when creating '" + name + + "' at " + start.toString(true) + "-" + end.toString(true)); } } return block;