-
Notifications
You must be signed in to change notification settings - Fork 109
Reduce retained binary memory with range-reader groundwork #742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 4 commits
e058e18
bdf19b8
b4c5d72
658b1ba
9dcda8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.kyhsgeekcode.disassembler.files | ||
|
|
||
| enum class BinaryContainerFormat { | ||
| ELF, | ||
| PE, | ||
| RAW, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package com.kyhsgeekcode.disassembler.files | ||
|
|
||
| fun interface BinaryContentLoader { | ||
| fun load(): ByteArray | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.kyhsgeekcode.disassembler.files | ||
|
|
||
| import java.io.Closeable | ||
|
|
||
| interface BinaryRangeReader : Closeable { | ||
| val size: Long | ||
|
|
||
| fun read(offset: Long, length: Int): ByteArray | ||
|
|
||
| fun readFully(): ByteArray | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.kyhsgeekcode.disassembler.files | ||
|
|
||
| import java.io.File | ||
|
|
||
| class DeferredFileBackedBinaryContent( | ||
| private val file: File, | ||
| initialContent: ByteArray? = null, | ||
| private val loader: BinaryContentLoader? = null, | ||
| ) { | ||
| private var loadedContent: ByteArray? = initialContent | ||
|
|
||
| fun contents(): ByteArray { | ||
| val existing = loadedContent | ||
| if (existing != null) { | ||
| return existing | ||
| } | ||
| val loaded = loader?.load() ?: file.readBytes() | ||
| loadedContent = loaded | ||
| return loaded | ||
| } | ||
|
|
||
| fun length(): Long { | ||
| return loadedContent?.size?.toLong() ?: file.length() | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,40 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.kyhsgeekcode.disassembler.files | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.File | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.ByteBuffer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.channels.FileChannel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.file.StandardOpenOption | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlin.math.min | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class FileChannelBinaryRangeReader(file: File) : BinaryRangeReader { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val channel = FileChannel.open(file.toPath(), StandardOpenOption.READ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override val size: Long | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| get() = channel.size() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override fun read(offset: Long, length: Int): ByteArray { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| require(offset >= 0) { "offset must be non-negative" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| require(length >= 0) { "length must be non-negative" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (length == 0 || offset >= size) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ByteArray(0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val readableLength = min(length.toLong(), size - offset).toInt() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val buffer = ByteBuffer.allocate(readableLength) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| channel.read(buffer, offset) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return buffer.array() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override fun readFully(): ByteArray { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (size == 0L) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ByteArray(0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| require(size <= Int.MAX_VALUE) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "File is too large to fit into a ByteArray: $size bytes" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return read(offset = 0, length = size.toInt()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: No, java.nio.channels.FileChannel.read(ByteBuffer, long) does not guarantee that a single call fills the ByteBuffer, even for regular files. It can legally return fewer bytes than requested. Citations:
Handle short reads instead of assuming one The Use a loop to read until the buffer is full or EOF: Proposed fix override fun read(offset: Long, length: Int): ByteArray {
require(offset >= 0) { "offset must be non-negative" }
require(length >= 0) { "length must be non-negative" }
if (length == 0 || offset >= size) {
return ByteArray(0)
}
val readableLength = min(length.toLong(), size - offset).toInt()
val buffer = ByteBuffer.allocate(readableLength)
- channel.read(buffer, offset)
- return buffer.array()
+ var position = offset
+ while (buffer.hasRemaining()) {
+ val bytesRead = channel.read(buffer, position)
+ if (bytesRead <= 0) {
+ break
+ }
+ position += bytesRead
+ }
+ return buffer.array().copyOf(buffer.position())
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| override fun close() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| channel.close() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,9 +30,22 @@ | |
| public class PEFile extends AbstractFile { | ||
| PE pe; | ||
| ArrayList<TLS> tlss = new ArrayList<>(); | ||
| private final DeferredFileBackedBinaryContent binaryContent; | ||
| private boolean contentLoaded; | ||
|
|
||
| public PEFile(File file, byte[] filec) throws IOException, NotThisFormatException { | ||
| this(file, filec, null); | ||
| } | ||
|
|
||
| public PEFile(File file, byte[] filec, BinaryContentLoader deferredContentLoader) throws IOException, NotThisFormatException { | ||
| path = file.getPath(); | ||
| byte[] parsingBytes = filec != null ? filec : loadBinaryContents(file, deferredContentLoader); | ||
| binaryContent = new DeferredFileBackedBinaryContent( | ||
| file, | ||
| deferredContentLoader == null ? parsingBytes : null, | ||
| deferredContentLoader | ||
| ); | ||
| contentLoaded = deferredContentLoader == null; | ||
|
Comment on lines
36
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initialize When 🐛 Proposed fix byte[] parsingBytes = filec != null ? filec : loadBinaryContents(file, deferredContentLoader);
binaryContent = new DeferredFileBackedBinaryContent(
file,
deferredContentLoader == null ? parsingBytes : null,
deferredContentLoader
);
- contentLoaded = deferredContentLoader == null;
+ if (deferredContentLoader == null) {
+ fileContents = parsingBytes;
+ contentLoaded = true;
+ } else {
+ contentLoaded = false;
+ }🤖 Prompt for AI Agents |
||
| try { | ||
| pe = PEParser.parse(file); | ||
| } catch (NegativeArraySizeException e) { | ||
|
|
@@ -54,7 +67,6 @@ public PEFile(File file, byte[] filec) throws IOException, NotThisFormatExceptio | |
| setCodeSectionLimit(getCodeSectionBase() + oph.getSizeOfCode()); | ||
| setCodeVirtAddr(oph.getImageBase() + getCodeSectionBase()); | ||
| setEntryPoint(oph.getAddressOfEntryPoint()); | ||
| fileContents = filec; | ||
|
|
||
| getExportSymbols().clear(); | ||
| getImportSymbols().clear(); | ||
|
|
@@ -68,11 +80,11 @@ public PEFile(File file, byte[] filec) throws IOException, NotThisFormatExceptio | |
| if (ide == null) | ||
| continue;//null dll | ||
|
|
||
| String dllname = Elf.getZString(filec, rvc.convertVirtualAddressToRawDataPointer(ide.getNameRVA()));//idir.getName(i);//get dll name? !! Not implemented method!!!! | ||
| String dllname = Elf.getZString(parsingBytes, rvc.convertVirtualAddressToRawDataPointer(ide.getNameRVA()));//idir.getName(i);//get dll name? !! Not implemented method!!!! | ||
| Timber.v(dllname); | ||
| long originalFirstThunkRaw = rvc.convertVirtualAddressToRawDataPointer(ide.getImportLookupTableRVA());//OriginalFirstThunk | ||
| long firstThunkRaw = rvc.convertVirtualAddressToRawDataPointer(ide.getImportAddressTableRVA()); | ||
| ByteBuffer buf = ByteBuffer.wrap(filec, (int) originalFirstThunkRaw, (int) (filec.length - originalFirstThunkRaw)).order(ByteOrder.LITTLE_ENDIAN); | ||
| ByteBuffer buf = ByteBuffer.wrap(parsingBytes, (int) originalFirstThunkRaw, (int) (parsingBytes.length - originalFirstThunkRaw)).order(ByteOrder.LITTLE_ENDIAN); | ||
| int off = 0; | ||
| //Read by dword! | ||
| for (; ; ) { | ||
|
|
@@ -89,7 +101,7 @@ public PEFile(File file, byte[] filec) throws IOException, NotThisFormatExceptio | |
| //CHAR name[1]; | ||
| /*ByteBuffer INT=ByteBuffer.wrap(filec,(int)data,(int)(filec.length-data)); | ||
| INT.getShort();*/ | ||
| String funcname = Elf.getZString(filec, rvc.convertVirtualAddressToRawDataPointer((int) data) + 2); | ||
| String funcname = Elf.getZString(parsingBytes, rvc.convertVirtualAddressToRawDataPointer((int) data) + 2); | ||
| //Log.v(TAG,dllname+"."+funcname); | ||
| importSymbol.owner = dllname; | ||
| importSymbol.name = funcname; | ||
|
|
@@ -107,16 +119,16 @@ public PEFile(File file, byte[] filec) throws IOException, NotThisFormatExceptio | |
| long funcAddrRaw = rvc.convertVirtualAddressToRawDataPointer((int) edir.getExportAddressTableRVA()); | ||
| long funcNameRaw = rvc.convertVirtualAddressToRawDataPointer((int) edir.getNamePointerRVA()); | ||
| long funcOrdinalRaw = rvc.convertVirtualAddressToRawDataPointer((int) edir.getOrdinalTableRVA()); | ||
| ByteBuffer funcnamePointers = ByteBuffer.wrap(filec, (int) funcNameRaw, (int) (filec.length - funcNameRaw)).order(ByteOrder.LITTLE_ENDIAN);//len eq num of name | ||
| ByteBuffer funcOrdinalPointers = ByteBuffer.wrap(filec, (int) funcOrdinalRaw, (int) (filec.length - funcOrdinalRaw)).order(ByteOrder.LITTLE_ENDIAN);//len eq num of name | ||
| ByteBuffer funcnamePointers = ByteBuffer.wrap(parsingBytes, (int) funcNameRaw, (int) (parsingBytes.length - funcNameRaw)).order(ByteOrder.LITTLE_ENDIAN);//len eq num of name | ||
| ByteBuffer funcOrdinalPointers = ByteBuffer.wrap(parsingBytes, (int) funcOrdinalRaw, (int) (parsingBytes.length - funcOrdinalRaw)).order(ByteOrder.LITTLE_ENDIAN);//len eq num of name | ||
| int ordinalbase = (int) edir.getOrdinalBase(); | ||
| Timber.v("OrdinalBase=" + ordinalbase); | ||
| //RVAConverter rvc=pe.getSectionTable().getRVAConverter(); | ||
| for (int i = 0; i < numofExports; i++)//iterate over functions | ||
| { | ||
| Symbol sym = new Symbol(); | ||
| try { | ||
| sym.name = Elf.getZString(filec, rvc.convertVirtualAddressToRawDataPointer(funcnamePointers.getInt() & 0x7FFFFFFF)); | ||
| sym.name = Elf.getZString(parsingBytes, rvc.convertVirtualAddressToRawDataPointer(funcnamePointers.getInt() & 0x7FFFFFFF)); | ||
| } catch (StringIndexOutOfBoundsException e) { | ||
| Timber.e(e); | ||
| sym.name = "ordinal?"; | ||
|
|
@@ -126,7 +138,7 @@ public PEFile(File file, byte[] filec) throws IOException, NotThisFormatExceptio | |
| int ordinal = funcOrdinalPointers.getShort() & 0x7FFF; | ||
| long addraddr = funcAddrRaw + 4 * (ordinal - ordinalbase); | ||
| Timber.v("addraddr=" + addraddr); | ||
| sym.st_value = ByteBuffer.wrap(filec, (int) addraddr, (int) (filec.length - addraddr)).order(ByteOrder.LITTLE_ENDIAN).getInt() & 0x7FFFFFFF; | ||
| sym.st_value = ByteBuffer.wrap(parsingBytes, (int) addraddr, (int) (parsingBytes.length - addraddr)).order(ByteOrder.LITTLE_ENDIAN).getInt() & 0x7FFFFFFF; | ||
| Timber.v(sym.toString()); | ||
| sym.type = Symbol.Type.STT_FUNC; | ||
| sym.bind = Symbol.Bind.STB_GLOBAL; | ||
|
|
@@ -163,6 +175,27 @@ public PEFile(File file, byte[] filec) throws IOException, NotThisFormatExceptio | |
|
|
||
| } | ||
|
|
||
| private static byte[] loadBinaryContents(File file, BinaryContentLoader deferredContentLoader) throws IOException { | ||
| if (deferredContentLoader != null) { | ||
| return deferredContentLoader.load(); | ||
| } | ||
| return java.nio.file.Files.readAllBytes(file.toPath()); | ||
| } | ||
|
|
||
| @Override | ||
| public byte[] getBinaryContents() { | ||
| if (!contentLoaded) { | ||
| fileContents = binaryContent.contents(); | ||
| contentLoaded = true; | ||
| } | ||
| return fileContents; | ||
| } | ||
|
|
||
| @Override | ||
| public long getBinaryLength() { | ||
| return contentLoaded ? fileContents.length : binaryContent.length(); | ||
| } | ||
|
|
||
| //https://docs.microsoft.com/ko-kr/windows/desktop/api/winnt/ns-winnt-_image_file_header | ||
| private MachineType getMachineTypeFromPE(int machine) { | ||
| int h = org.boris.pecoff4j.constant.MachineType.IMAGE_FILE_MACHINE_I386; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't treat deferred bytes as "not configured".
Line 24 now returns the placeholder string whenever
fileContentsis still lazy. After this PR,RawFile,ElfFile, andPEFileintentionally leavefileContentsuninitialized until bytes are requested, sotoString()skips the size/address metadata even thoughgetBinaryLength()and the parsed fields are already available.🩹 Proposed fix
override fun toString(): String { - if (!::fileContents.isInitialized) { - return "The file has not been configured. You should setup manually in the first page before you can see the details." - } val builder = StringBuilder( if (this is RawFile) "The file has not been configured. You should setup manually in the first page before you can see the details." + System.lineSeparator() else "" )🤖 Prompt for AI Agents