diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ExtentsByteProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ExtentsByteProvider.java new file mode 100644 index 0000000000..4e72eda7c4 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/ExtentsByteProvider.java @@ -0,0 +1,175 @@ +/* ### + * 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.io.*; +import java.util.Map.Entry; +import java.util.TreeMap; + +import ghidra.formats.gfilesystem.FSRL; + +/** + * A {@link ByteProvider} that is a concatenation of sub-ranges of another ByteProvider, also + * allowing for non-initialized (sparse) regions. + *

+ * Not thread-safe when extents are being added. + */ +public class ExtentsByteProvider implements ByteProvider { + + private ByteProvider delegate; + /** + * TreeMap of this-provider offsets to the delegate-provider's offsets. + *

+ * Each extent/region in the delegate provider is defined by the gap between + * adjacent offsetMap entries. The last entry is bounded by the total + * length of the provider as specified by the length field. + */ + private TreeMap offsetMap = new TreeMap<>(); // this-provider offset -> delegate-provider offset + private long length; + private FSRL fsrl; + + /** + * Creates a new {@link ExtentsByteProvider}. + * + * @param provider {@link ByteProvider} to wrap + * @param fsrl {@link FSRL} of the byte provider + */ + public ExtentsByteProvider(ByteProvider provider, FSRL fsrl) { + this.delegate = provider; + this.fsrl = fsrl; + } + + /** + * Adds an extent to the current end of this instance. + * + * @param offset long byte offset in the delegate ByteProvider + * @param extentLen long length of the extent region in the delegate ByteProvider + */ + public void addExtent(long offset, long extentLen) { + if (extentLen <= 0) { + throw new IllegalArgumentException(); + } + offsetMap.put(length, offset); + length += extentLen; + } + + /** + * Adds a sparse extent to the current end of this instance. + * + * @param extentLen long length of the sparse extent region + */ + public void addSparseExtent(long extentLen) { + addExtent(-1, extentLen); + } + + @Override + public File getFile() { + return null; + } + + @Override + public String getName() { + return null; + } + + @Override + public FSRL getFSRL() { + return fsrl; + } + + @Override + public String getAbsolutePath() { + return null; + } + + @Override + public long length() throws IOException { + return length; + } + + @Override + public boolean isValidIndex(long index) { + return 0 <= index && index < length; + } + + @Override + public void close() throws IOException { + delegate.close(); + } + + @Override + public byte readByte(long index) throws IOException { + ensureBounds(index, 1); + + Entry entry = offsetMap.floorEntry(index); + long extentStart = entry.getKey(); + long extentOffset = index - extentStart; + long delegateExtentStart = entry.getValue(); + + return (delegateExtentStart != -1) + ? delegate.readByte(delegateExtentStart + extentOffset) + : 0; + } + + @Override + public byte[] readBytes(long index, long longCount) throws IOException { + if (longCount >= Integer.MAX_VALUE) { + throw new IOException("Unable to read " + longCount + " bytes at once"); + } + ensureBounds(index, longCount); + + int count = (int) longCount; + byte[] result = new byte[count]; + int bytesRead = 0; + while (bytesRead < count) { + long offsetToRead = index + bytesRead; + Entry entry = offsetMap.floorEntry(offsetToRead); + Entry nextEntry = offsetMap.higherEntry(entry.getKey()); + + long extentStart = entry.getKey(); + long extentOffset = offsetToRead - extentStart; + long extentEnd = (nextEntry != null) ? nextEntry.getKey() : length; + long delegateExtentStart = entry.getValue(); + int bytesToRead = + (int) Math.min(count - bytesRead, extentEnd - extentStart - extentOffset); + if (delegateExtentStart != -1) { + long delegateOffsetToRead = delegateExtentStart + extentOffset; + byte[] extentBytes = delegate.readBytes(delegateOffsetToRead, bytesToRead); + System.arraycopy(extentBytes, 0, result, bytesRead, bytesToRead); + } + else { + // the extent was not present, result will be 0's + } + bytesRead += bytesToRead; + } + return result; + } + + @Override + public InputStream getInputStream(long index) throws IOException { + return new ByteProviderInputStream(this, 0, length); + } + + private void ensureBounds(long index, long count) throws IOException { + if (index < 0 || index > length) { + throw new IOException("Invalid index: " + index); + } + if (index + count > length) { + throw new IOException("Unable to read past EOF: " + index + ", " + count); + } + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/ExtentsByteProviderTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/ExtentsByteProviderTest.java new file mode 100644 index 0000000000..8bb53f67f0 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/ExtentsByteProviderTest.java @@ -0,0 +1,214 @@ +/* ### + * 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 static org.junit.Assert.*; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Test; + +public class ExtentsByteProviderTest { + private ByteArrayProvider bap(int... values) { + byte[] bytes = new byte[values.length]; + for (int i = 0; i < values.length; i++) { + bytes[i] = (byte) values[i]; + } + return new ByteArrayProvider(bytes); + } + + /* + * "NN 01 NN 03 NN 05 NN 07 NN 09"... (NN = blockNumber, 00-FF = offset in block) + */ + private ByteArrayProvider patternedBAP(int bs, int count) { + byte[] bytes = new byte[bs * count]; + for (int blockNum = 0; blockNum < count; blockNum++) { + int blockStart = blockNum * bs; + Arrays.fill(bytes, blockStart, blockStart + bs, (byte) blockNum); + for (int i = 1; i < bs; i += 2) { + bytes[i + blockStart] = (byte) (i % 256); + } + } + return new ByteArrayProvider(bytes); + } + + @Test(expected = IOException.class) + public void testEmptyExtentBP() throws IOException { + try (ExtentsByteProvider ebp = new ExtentsByteProvider(bap(55), null)) { + ebp.readByte(0); + } + } + + @Test + public void testExtentBP_SingleByteRead() throws IOException { + try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) { + ebp.addExtent(10, 10); + ebp.addExtent(0, 1); + ebp.addExtent(0, 10); + + assertEquals(21, ebp.length()); + + assertEquals(0x01, ebp.readByte(0)); + assertEquals(0x01, ebp.readByte(1)); + assertEquals(0x01, ebp.readByte(2)); + assertEquals(0x03, ebp.readByte(3)); + assertEquals(0x09, ebp.readByte(9)); + + assertEquals(0x00, ebp.readByte(11)); + assertEquals(0x01, ebp.readByte(12)); + assertEquals(0x00, ebp.readByte(13)); + assertEquals(0x03, ebp.readByte(14)); + assertEquals(0x09, ebp.readByte(20)); + + try { + ebp.readByte(21); + fail(); + } + catch (IOException e) { + // good + } + } + } + + @Test + public void testExtentBP_MultiByteRead() throws IOException { + try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) { + ebp.addExtent(10, 10); + ebp.addExtent(0, 1); + ebp.addExtent(0, 10); + + assertEquals(21, ebp.length()); + + byte[] bytes = ebp.readBytes(0, 21); + assertEquals(0x01, bytes[0]); + assertEquals(0x01, bytes[1]); + assertEquals(0x01, bytes[2]); + assertEquals(0x03, bytes[3]); + assertEquals(0x09, bytes[9]); + + assertEquals(0x00, bytes[11]); + assertEquals(0x01, bytes[12]); + assertEquals(0x00, bytes[13]); + assertEquals(0x03, bytes[14]); + assertEquals(0x09, bytes[20]); + } + } + + @Test + public void testExtentBP_MisalignedMultiByteRead() throws IOException { + try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) { + ebp.addExtent(10, 10); + ebp.addExtent(0, 10); + + assertEquals(20, ebp.length()); + + byte[] bytes = ebp.readBytes(5, 10); + assertEquals(0x05, bytes[0]); + assertEquals(0x01, bytes[1]); + assertEquals(0x07, bytes[2]); + assertEquals(0x01, bytes[3]); + assertEquals(0x09, bytes[4]); + assertEquals(0x00, bytes[5]); + assertEquals(0x01, bytes[6]); + assertEquals(0x00, bytes[7]); + assertEquals(0x03, bytes[8]); + assertEquals(0x00, bytes[9]); + } + } + + @Test + public void testSmallExtentBP() throws IOException { + try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) { + ebp.addExtent(10, 1); + ebp.addExtent(0, 1); + + assertEquals(2, ebp.length()); + + assertEquals(0x01, ebp.readByte(0)); + assertEquals(0x00, ebp.readByte(1)); + + try { + ebp.readByte(3); + fail(); + } + catch (IOException e) { + // good + } + } + } + + @Test + public void testExtentBP_SparseMultiByteRead() throws IOException { + try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) { + ebp.addExtent(-1, 5); + + assertEquals(5, ebp.length()); + + byte[] bytes = ebp.readBytes(0, 5); + assertEquals(0x00, bytes[0]); + assertEquals(0x00, bytes[1]); + assertEquals(0x00, bytes[2]); + assertEquals(0x00, bytes[3]); + assertEquals(0x00, bytes[4]); + } + } + + @Test + public void testExtentBP_SparseSingleByteRead() throws IOException { + try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) { + ebp.addExtent(-1, 5); + + assertEquals(5, ebp.length()); + + assertEquals(0x00, ebp.readByte(0)); + assertEquals(0x00, ebp.readByte(1)); + assertEquals(0x00, ebp.readByte(2)); + assertEquals(0x00, ebp.readByte(3)); + assertEquals(0x00, ebp.readByte(4)); + } + } + + @Test + public void testExtentBP_MixedSparseMultiByteRead() throws IOException { + try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) { + ebp.addExtent(10, 10); + ebp.addExtent(-1, 5); + ebp.addExtent(0, 10); + + assertEquals(25, ebp.length()); + + byte[] bytes = ebp.readBytes(0, 25); + assertEquals(0x01, bytes[0]); + assertEquals(0x01, bytes[1]); + assertEquals(0x01, bytes[2]); + assertEquals(0x03, bytes[3]); + assertEquals(0x09, bytes[9]); + + assertEquals(0x00, bytes[10]); + assertEquals(0x00, bytes[11]); + assertEquals(0x00, bytes[12]); + assertEquals(0x00, bytes[13]); + assertEquals(0x00, bytes[14]); + + assertEquals(0x00, bytes[15]); + assertEquals(0x01, bytes[16]); + assertEquals(0x00, bytes[17]); + assertEquals(0x03, bytes[18]); + assertEquals(0x09, bytes[24]); + } + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Analyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Analyzer.java index f55510ba29..42283008f4 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Analyzer.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Analyzer.java @@ -198,10 +198,7 @@ public class Ext4Analyzer extends FileFormatAnalyzer { List entries = i_block.getExtentEntries(); for( int i = 0; i < numEntries; i++ ) { Ext4Extent extent = entries.get(i); - int low = extent.getEe_start_lo(); - int high = extent.getEe_start_hi(); - int blockNumber = (high << 16) | low; - long offset = blockNumber * blockSize; + long offset = extent.getExtentStartBlockNumber() * blockSize; reader.setPointerIndex(offset); Address address = toAddr(program, offset); if( isDirEntry2 ) { diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Extent.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Extent.java index e1e2d0dceb..93b247122a 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Extent.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Extent.java @@ -15,16 +15,12 @@ */ package ghidra.file.formats.ext4; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.ByteProvider; -import ghidra.app.util.bin.StructConverter; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.Structure; -import ghidra.program.model.data.StructureDataType; -import ghidra.util.exception.DuplicateNameException; - import java.io.IOException; +import ghidra.app.util.bin.*; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + public class Ext4Extent implements StructConverter { private int ee_block; @@ -59,6 +55,33 @@ public class Ext4Extent implements StructConverter { return ee_start_lo; } + /** + * Returns the number of blocks this extent contains. + * + * @return number of blocks in this extent + */ + public int getExtentBlockCount() { + return Short.toUnsignedInt(ee_len); + } + + /** + * Returns the stream block number of where this extent starts. + * + * @return block number (in the constructed stream) of this extent + */ + public long getStreamBlockNumber() { + return Integer.toUnsignedLong(ee_block); + } + + /** + * Returns the block number of where the data for this extent is stored. + * + * @return starting block number of where data for this extent is stored + */ + public long getExtentStartBlockNumber() { + return Short.toUnsignedLong(ee_start_hi) << 32 | Integer.toUnsignedLong(ee_start_lo); + } + @Override public DataType toDataType() throws DuplicateNameException, IOException { Structure structure = new StructureDataType("ext4_extent", 0); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java index 92c131e247..2bc33985d7 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java @@ -15,13 +15,13 @@ */ package ghidra.file.formats.ext4; -import java.io.*; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.*; import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; import ghidra.util.NumericUtilities; @@ -113,8 +113,8 @@ public class Ext4FileSystem implements GFileSystem { if (parent == null) { parent = fsih.getRootDir(); } - parent = fsih.storeFileWithParent(name, parent, -1, true, - (inode.getI_size_high() << 32) | inode.getI_size_lo(), new Ext4File(name, inode)); + parent = fsih.storeFileWithParent(name, parent, -1, true, inode.getSize(), + new Ext4File(name, inode)); } if ((inode.getI_flags() & Ext4Constants.EXT4_EXTENTS_FL) == 0) { return; @@ -142,10 +142,7 @@ public class Ext4FileSystem implements GFileSystem { for (int i = 0; i < numEntries; i++) { monitor.checkCanceled(); Ext4Extent extent = entries.get(i); - long low = extent.getEe_start_lo() & 0xffffffffL; - long high = extent.getEe_start_hi() & 0xffffffffL; - long blockNumber = (high << 16) | low; - long offset = blockNumber * blockSize; + long offset = extent.getExtentStartBlockNumber() * blockSize; reader.setPointerIndex(offset); if (isDirEntry2) { processDirEntry2(reader, superBlock, inodes, parent, monitor, extent, offset); @@ -241,10 +238,9 @@ public class Ext4FileSystem implements GFileSystem { private void storeFile(Ext4Inode[] inodes, Ext4DirEntry dirEnt, GFile parent) { int fileInodeNum = dirEnt.getInode(); Ext4Inode fileInode = inodes[fileInodeNum]; - long fileSize = (fileInode.getI_size_high() << 32) | fileInode.getI_size_lo(); fsih.storeFileWithParent(dirEnt.getName(), parent, -1, - (fileInode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR, fileSize, - new Ext4File(dirEnt.getName(), fileInode)); + (fileInode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR, + fileInode.getSize(), new Ext4File(dirEnt.getName(), fileInode)); inodes[fileInodeNum] = null; } @@ -254,9 +250,8 @@ public class Ext4FileSystem implements GFileSystem { if (fileInode == null) { return;//TODO } - long fileSize = (fileInode.getI_size_high() << 32) | fileInode.getI_size_lo(); fsih.storeFileWithParent(dirEnt2.getName(), parent, -1, - dirEnt2.getFile_type() == Ext4Constants.FILE_TYPE_DIRECTORY, fileSize, + dirEnt2.getFile_type() == Ext4Constants.FILE_TYPE_DIRECTORY, fileInode.getSize(), new Ext4File(dirEnt2.getName(), fileInode)); inodes[fileInodeNum] = null; } @@ -284,7 +279,7 @@ public class Ext4FileSystem implements GFileSystem { } Ext4Inode inode = ext4File.getInode(); String info = ""; - long size = (inode.getI_size_high() << 32) | inode.getI_size_lo(); + long size = inode.getSize(); if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFLNK) { Ext4IBlock block = inode.getI_block(); byte[] extra = block.getExtra(); @@ -299,27 +294,8 @@ public class Ext4FileSystem implements GFileSystem { @Override public InputStream getInputStream(GFile file, TaskMonitor monitor) throws IOException, CancelledException { - Ext4File extFile = fsih.getMetadata(file); - if (extFile == null) { - return null; - } - Ext4Inode inode = extFile.getInode(); - if (inode == null) { - return null; - } - - if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR) { - throw new IOException(extFile.getName() + " is a directory."); - } - - if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFLNK) { - inode = resolveSymLink(file); - if (inode == null) { - throw new IOException(extFile.getName() + " is a broken symlink."); - } - } - - return getInputStream(inode); + ByteProvider bp = getByteProvider(file, monitor); + return (bp != null) ? new ByteProviderInputStream(bp, 0, bp.length()) : null; } private static final int MAX_SYMLINK_LOOKUP_COUNT = 100; @@ -351,57 +327,76 @@ public class Ext4FileSystem implements GFileSystem { return null; } - private InputStream getInputStream(Ext4Inode inode) throws IOException { - int i_size_lo = inode.getI_size_lo(); - int i_size_high = inode.getI_size_high(); - long size = (i_size_high << 32) | i_size_lo; + private Ext4Inode getInodeFor(GFile file) throws IOException { + Ext4File extFile = fsih.getMetadata(file); + if (extFile == null) { + return null; + } + Ext4Inode inode = extFile.getInode(); + if (inode == null) { + return null; + } - boolean usesExtents = (inode.getI_flags() & Ext4Constants.EXT4_EXTENTS_FL) != 0; - if (usesExtents) { - Ext4IBlock i_block = inode.getI_block(); - Ext4ExtentHeader header = i_block.getHeader(); - if (header.getEh_depth() == 0) { - List entries = i_block.getExtentEntries(); - return concatenateExtents(entries, size); + if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFLNK) { + inode = resolveSymLink(file); + if (inode == null) { + throw new IOException(extFile.getName() + " is a broken symlink."); } } - return null; + return inode; } /** - * The file is spread throughout the ext4 file, so - * concatenate each extent into one contiguous stream. + * Returns a {@link ByteProvider} that supplies the bytes of the requested file. * - * TODO better memory management? currently loads entire file into memory. + * @param file {@link GFile} to get + * @param monitor {@link TaskMonitor} to cancel + * @return {@link ByteProvider} containing the bytes of the requested file, caller is + * responsible for closing the ByteProvider + * @throws IOException if error */ - private InputStream concatenateExtents(List entries, long actualSize) - throws IOException { - if (actualSize > Integer.MAX_VALUE) { - throw new IOException( - "File is >2GB, too large to extract. Please report to Ghidra team."); + public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) throws IOException { + Ext4Inode inode = getInodeFor(file); + if (inode == null) { + return null; } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - for (int i = 0; i < entries.size(); ++i) { - Ext4Extent extent = entries.get(i); - - long low = extent.getEe_start_lo() & 0xffffffffL; - long high = extent.getEe_start_hi() & 0xffffffffL; - long blockNumber = (high << 16) | low; - long extentOffset = blockNumber * blockSize; - long extentSize = (extent.getEe_len() & 0xffffL) * blockSize; - - try { - byte[] extentBytes = provider.readBytes(extentOffset, extentSize); - baos.write(extentBytes); - } - catch (IOException e) { - // ignore - } + if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR) { + throw new IOException(file.getName() + " is a directory."); } - return new ByteArrayInputStream(baos.toByteArray(), 0, (int) actualSize); + boolean usesExtents = (inode.getI_flags() & Ext4Constants.EXT4_EXTENTS_FL) != 0; + if (!usesExtents) { + throw new IOException("Unsupported file storage: not EXT4_EXTENTS: " + file.getPath()); + } + + Ext4IBlock i_block = inode.getI_block(); + Ext4ExtentHeader header = i_block.getHeader(); + if (header.getEh_depth() != 0) { + throw new IOException("Unsupported file storage: eh_depth: " + file.getPath()); + } + + long fileSize = inode.getSize(); + ExtentsByteProvider ebp = new ExtentsByteProvider(provider, file.getFSRL()); + for (Ext4Extent extent : i_block.getExtentEntries()) { + long startPos = extent.getStreamBlockNumber() * blockSize; + long providerOfs = extent.getExtentStartBlockNumber() * blockSize; + long extentLen = extent.getExtentBlockCount() * blockSize; + if (ebp.length() < startPos) { + ebp.addSparseExtent(startPos - ebp.length()); + } + if (ebp.length() + extentLen > fileSize) { + // the last extent may have a trailing partial block + extentLen = fileSize - ebp.length(); + } + + ebp.addExtent(providerOfs, extentLen); + } + if (ebp.length() < fileSize) { + // trailing sparse. not sure if possible. + ebp.addSparseExtent(fileSize - ebp.length()); + } + return ebp; } private Ext4Inode[] getInodes(BinaryReader reader, Ext4SuperBlock superBlock, diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Inode.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Inode.java index bd3e0b21bf..494b5f9449 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Inode.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4Inode.java @@ -15,17 +15,12 @@ */ package ghidra.file.formats.ext4; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.ByteProvider; -import ghidra.app.util.bin.StructConverter; -import ghidra.program.model.data.ArrayDataType; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.Structure; -import ghidra.program.model.data.StructureDataType; -import ghidra.util.exception.DuplicateNameException; - import java.io.IOException; +import ghidra.app.util.bin.*; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + public class Ext4Inode implements StructConverter { private short i_mode; @@ -199,6 +194,15 @@ public class Ext4Inode implements StructConverter { return i_projid; } + /** + * Returns the size of this file. + * + * @return size of this file + */ + public long getSize() { + return Integer.toUnsignedLong(i_size_high) << 32 | Integer.toUnsignedLong(i_size_lo); + } + @Override public DataType toDataType() throws DuplicateNameException, IOException { DataType iBlockDataType = i_block.toDataType(); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java index f44b1eb215..663aecff16 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java @@ -21,17 +21,13 @@ import java.util.List; import ghidra.app.cmd.comments.SetCommentCmd; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.services.ProgramManager; -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.ByteProvider; -import ghidra.app.util.bin.MemoryByteProvider; +import ghidra.app.util.bin.*; import ghidra.app.util.importer.MessageLog; import ghidra.file.analyzers.FileFormatAnalyzer; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.data.DataType; -import ghidra.program.model.listing.CodeUnit; -import ghidra.program.model.listing.Data; -import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.*; import ghidra.program.model.symbol.SourceType; import ghidra.util.exception.DuplicateNameException; import ghidra.util.task.TaskMonitor; @@ -260,10 +256,7 @@ public class NewExt4Analyzer extends FileFormatAnalyzer { if ( iBlock != null ) { for ( Ext4Extent extent : iBlock.getExtentEntries( ) ) { monitor.checkCanceled( ); - long lo = extent.getEe_start_lo( ) & 0xffffffffL; - long hi = extent.getEe_start_hi( ) & 0xffffffffL; - long value = ( hi << 32 ) | lo; - long destination = value * blockSize; + long destination = extent.getExtentStartBlockNumber() * blockSize; comment += "Extent: 0x" + Long.toHexString( destination ) + "\n"; } } @@ -346,10 +339,7 @@ public class NewExt4Analyzer extends FileFormatAnalyzer { List entries = i_block.getExtentEntries(); for ( int i = 0; i < numEntries; i++ ) { Ext4Extent extent = entries.get( i ); - long lo = extent.getEe_start_lo( ) & 0xffffffffL; - long hi = extent.getEe_start_hi( ) & 0xffffffffL; - long value = ( hi << 32 ) | lo; - long offset = value * blockSize; + long offset = extent.getExtentStartBlockNumber() * blockSize; reader.setPointerIndex(offset); Address address = toAddr( program, offset ); if ( isDirEntry2 ) {