diff --git a/README.md b/README.md index d7f77f7..c4f28f0 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | Halo Tag || [`patterns/hinf_tag.hexpat`](patterns/hinf_tag.hexpat) | Halo Infinite Tag Files | | Halo Module || [`patterns/hinf_module.hexpat`](patterns/hinf_module.hexpat) | Halo Infinite Module Archive Files | | Halo HavokScript || [`patterns/hinf_luas.hexpat`](patterns/hinf_luas.hexpat) | Halo Infinite HavokScript 5.1 Bytecode | +| HPROF || [`patterns/hprof.hexpat`](patterns/hprof.hexpat) | Java HPROF Profiler Data Format | | HSDT || [`patterns/hsdt.hexpat`](patterns/hsdt.hexpat) | HiSilicon device-tree table images | | ICO | | [`patterns/ico.hexpat`](patterns/ico.hexpat) | Icon (.ico) or Cursor (.cur) files | | ID3 | `audio/mpeg` | [`patterns/id3.hexpat`](patterns/id3.hexpat) | ID3 tags in MP3 files | diff --git a/patterns/hprof.hexpat b/patterns/hprof.hexpat new file mode 100644 index 0000000..80c3d8d --- /dev/null +++ b/patterns/hprof.hexpat @@ -0,0 +1,362 @@ +#pragma author WerWolv +#pragma description Java HPROF Profiler Data Format +#pragma endian big +#pragma magic [ "JAVA PROFILE" ] @ 0x00 + +#pragma array_limit 0 +#pragma pattern_limit 0 + +import std.mem; +import std.io; +import std.sys; +import std.core; + +enum Tag : u8 { + STRING_IN_UTF8 = 0x01, + LOAD_CLASS = 0x02, + UNLOAD_CLASS = 0x03, + STACK_FRAME = 0x04, + STACK_TRACE = 0x05, + ALLOC_SITES = 0x06, + HEAP_SUMMARY = 0x07, + START_THREAD = 0x0A, + END_THREAD = 0x0B, + HEAP_DUMP = 0x0C, + HEAP_DUMP_SEGMENT = 0x1C, + HEAP_DUMP_END = 0x2C, + CPU_SAMPLES = 0x0D, + CONTROL_SETTINGS = 0x0E, +}; + +u32 id_size = 0; +struct ID { + match (id_size) { + (1): u8 value; + (2): u16 value; + (4): u32 value; + (8): u64 value; + (_): std::error("Invalid ID size"); + } +} [[sealed, format("format_id")]]; + +fn format_id(ref auto id) { + return std::format("0x{:0{}X}", id.value, id_size * 2); +}; + +struct StringInUTF8 { + ID id; + char string[parent.length - sizeof(id)]; +}; + +struct LoadClass { + u32 classSerialNumber; + ID classObjectId; + u32 stackTraceSerialNumber; + ID classNameStringID; +}; + +struct StackFrame { + ID stackFrameId; + ID methodNameStringId; + ID methodSignatureStringId; + ID sourceFileNameStringId; + u32 classSerialNumber; + u32 lineInformation; +}; + +struct StackTrace { + u32 stackTraceSerialNumber; + u32 threadSerialNumber; + u32 numberOfFrames; + ID stackFrameIds[while(!std::mem::reached(addressof(this) + parent.length))]; +}; + +enum SubTag : u8 { + RootUnknown = 0xFF, + RootJNIGlobal = 0x01, + RootJNILocal = 0x02, + RootJavaFrame = 0x03, + RootNativeStack = 0x04, + RootStickyClass = 0x05, + RootThreadBlock = 0x06, + RootMonitorUsed = 0x07, + RootThreadObject = 0x08, + ClassDump = 0x20, + InstanceDump = 0x21, + ObjectArrayDump = 0x22, + PrimitiveArrayDump = 0x23 +}; + +enum EntryType : u8 { + Object = 2, + Boolean = 4, + Char = 5, + Float = 6, + Double = 7, + Byte = 8, + Short = 9, + Int = 10, + Long = 11 +}; + +struct BasicType { + match (Tag) { + (EntryType::Object): ID value; + (EntryType::Boolean): bool value; + (EntryType::Char): { padding[1]; char value; } + (EntryType::Float): float value; + (EntryType::Double): double value; + (EntryType::Byte): u8 value; + (EntryType::Short): u16 value; + (EntryType::Int): u32 value; + (EntryType::Long): u64 value; + (_): std::error("Invalid BasicType type"); + } +} [[sealed, format("format_basic_type")]]; + +fn format_basic_type(ref auto basicType) { + return std::format("{}", basicType.value); +}; + +struct ConstantPoolEntry { + u16 constantPoolIndex; + EntryType type; + BasicType value; +}; + +struct ConstantPoolArray { + u16 size; + ConstantPoolEntry entries[size]; +}; + +struct StaticFieldEntry { + ID staticFieldNameStringId; + EntryType type; + BasicType value; +}; + +struct StaicFieldArray { + u16 size; + StaticFieldEntry entries[size]; +}; + +struct InstanceField { + ID fieldNameStringId; + EntryType fieldType; +}; + +struct InstanceFieldsArray { + u16 size; + InstanceField instanceFields[size]; +}; + +struct HeapDump { + SubTag subTag; + match (subTag) { + (SubTag::RootUnknown): { + ID objectId; + } + (SubTag::RootJNIGlobal): { + ID objectId; + ID jniGlobalRefId; + } + (SubTag::RootJNILocal): { + ID objectId; + u32 threadSerialNumber; + u32 frameNumberInStackTrace; + } + (SubTag::RootJavaFrame): { + ID objectId; + u32 threadSerialNumber; + u32 frameNumberInStackTrace; + } + (SubTag::RootNativeStack): { + ID objectId; + u32 threadSerialNumber; + } + (SubTag::RootStickyClass): { + ID objectId; + } + (SubTag::RootThreadBlock): { + ID objectId; + u32 threadSerialNumber; + } + (SubTag::RootMonitorUsed): { + ID objectId; + } + (SubTag::RootThreadObject): { + ID threadObjectId; + u32 threadSerialNumber; + u32 stackTraceSerialNumber; + } + (SubTag::ClassDump): { + ID classObjectId; + u32 stackTraceSerialNumber; + ID superClassObjectId; + ID classLoaderObjectId; + ID signersObjectId; + ID protectionDomainObjectId; + ID reserved[2]; + u32 instanceSize; + ConstantPoolArray constantPool; + StaicFieldArray staticFields; + InstanceFieldsArray instanceFields; + } + (SubTag::InstanceDump): { + ID objectId; + u32 stackTraceSerialNumber; + ID classObjectId; + u32 numBytes; + std::mem::Bytes instanceFieldValues; + } + (SubTag::ObjectArrayDump): { + ID arrayObjectId; + u32 stackTraceSerialNumber; + u32 numberOfElements; + ID arrayClassObjectId; + ID elements[numberOfElements]; + } + (SubTag::PrimitiveArrayDump): { + ID arrayObjectId; + u32 stackTraceSerialNumber; + u32 numberOfElements; + EntryType type; + BasicType value[numberOfElements]; + } + (_): std::error(std::format("Heap Dump Sub Tag {:02X} at 0x{:08X} {}", u32(subTag), $, std::core::array_index())); + } + + if ($ - addressof(parent.length) + sizeof(parent.length) >= parent.length) + break; + + u8 endTag [[no_unique_address, hidden]]; + if (endTag == 0x2C) { + padding[1]; + break; + } +}; + +struct UnloadClass { + u32 classSerialNumber; +}; + +bitfield AllocSitesFlags { + completeInsteadOfIncremental : 1; + sortedByLineInsteadOfAllocation : 1; + forceGc : 1; + padding : 13; +}; + +struct AllocSite { + bool arrayIndicator; + u32 classSerialNumber; + u32 stackTraceSerialNumber; + u32 numberOfLiveBytes; + u32 numberOfLiveInstances; + u32 numberOfBytesAllocated; + u32 numberOfInstancesAllocated; +}; + +struct AllocSites { + AllocSitesFlags flags; + float cutoffRatio; + u32 totalLiveBytes; + u32 totalLiveInstances; + u64 totalBytesAllocated; + u64 totalInstancesAllocated; + u32 numSites; + AllocSite sites[numSites]; +}; + +struct HeapSummary { + u32 totalLiveBytes; + u32 totalLiveInstances; + u64 totalBytesAllocated; + u64 totalInstancesAllocated; +}; + +struct StartThread { + u32 threadSerialNumber; + ID threadObjectId; + u32 stackTraceSerialNumber; + ID threadNameStringId; + ID threadGroupNameId; + ID threadParentGroupNameId; +}; + +struct EndThread { + u32 threadSerialNumber; +}; + +struct Sample { + u32 numberOfSamples; + u32 stackTraceSerialNumber; +}; + +struct CpuSamples { + u32 numSamples; + Sample samples[numSamples]; +}; + +bitfield ControlSettingsFlags { + allocTraces : 1; + cpuSampling : 1; + padding : 30; +}; + +struct ControlSettings { + ControlSettingsFlags flags; + u16 stackTraceDepth; +}; + +struct Record { + Tag tag; + u32 time; + u32 length; + + match (tag) { + (Tag::STRING_IN_UTF8): + StringInUTF8 body; + (Tag::LOAD_CLASS): + LoadClass body; + (Tag::UNLOAD_CLASS): + UnloadClass body; + (Tag::STACK_FRAME): + StackFrame body; + (Tag::STACK_TRACE): + StackTrace body; + (Tag::ALLOC_SITES): + AllocSites body; + (Tag::HEAP_SUMMARY): + HeapSummary body; + (Tag::START_THREAD): + StartThread body; + (Tag::END_THREAD): + EndThread body; + (Tag::HEAP_DUMP | Tag::HEAP_DUMP_SEGMENT): + HeapDump heapDumps[while(true)]; + (Tag::HEAP_DUMP_END): + std::error("HEAP_DUMP_END Tag without previous HEAP_DUMP or HEAP_DUMP_SEGMENT Tag"); + (Tag::CPU_SAMPLES): + CpuSamples body; + (Tag::CONTROL_SETTINGS): + ControlSettings body; + (_): + std::error(std::format("Unknown record type {:02X} at address 0x{:08X}, index {}", u8(tag), addressof(tag), std::core::array_index())); + } +}; + +struct Header { + char format_name[]; + u32 identifier_size; + id_size = identifier_size; + u32 timestamp_high; + u32 time_stamp_low; +}; + +struct HPROF { + Header header; + Record records[while(!std::mem::eof())]; +}; + +HPROF hprof @ 0x00; \ No newline at end of file diff --git a/tests/patterns/test_data/hprof.hexpat.bin b/tests/patterns/test_data/hprof.hexpat.bin new file mode 100644 index 0000000..a8f6f7d Binary files /dev/null and b/tests/patterns/test_data/hprof.hexpat.bin differ