From 129f26825ac9f36055bcbfe9b76253c979f2200d Mon Sep 17 00:00:00 2001 From: blacktop Date: Thu, 30 May 2024 19:48:59 -0600 Subject: [PATCH] feat: add support for parsing LC_NOTE data --- cmds.go | 79 +++++++++++++++++++++++--- file.go | 7 ++- types/commands.go | 120 ++++++++++++++++++++++++++------------- types/commands_string.go | 54 +++++++++++++++--- 4 files changed, 202 insertions(+), 58 deletions(-) diff --git a/cmds.go b/cmds.go index c9a373f..4ec550f 100644 --- a/cmds.go +++ b/cmds.go @@ -553,6 +553,7 @@ type Thread struct { LoadBytes types.ThreadCmd bo binary.ByteOrder + IsArm bool Threads []types.ThreadState } @@ -581,33 +582,39 @@ func (t *Thread) String() string { padding := strings.Repeat(" ", 7) var out []string for _, thread := range t.Threads { - switch thread.Flavor { + var flavor any + if t.IsArm { + flavor = types.ArmThreadFlavor(thread.Flavor) + } else { + flavor = types.X86ThreadFlavor(thread.Flavor) + } + switch flavor { case types.X86_THREAD_STATE32: var regs Regs386 binary.Read(bytes.NewReader(thread.Data), t.bo, ®s) - out = append(out, fmt.Sprintf("%s%s EntryPoint: %#08x\n%s", padding, thread.Flavor, regs.IP, regs.String(regPadding))) + out = append(out, fmt.Sprintf("%s%s EntryPoint: %#08x\n%s", padding, flavor, regs.IP, regs.String(regPadding))) case types.X86_THREAD_STATE64: var regs RegsAMD64 binary.Read(bytes.NewReader(thread.Data), t.bo, ®s) - out = append(out, fmt.Sprintf("%s%s EntryPoint: %#016x\n%s", padding, thread.Flavor, regs.IP, regs.String(regPadding))) + out = append(out, fmt.Sprintf("%s%s EntryPoint: %#016x\n%s", padding, flavor, regs.IP, regs.String(regPadding))) case types.ARM_THREAD_STATE32: var regs RegsARM binary.Read(bytes.NewReader(thread.Data), t.bo, ®s) - out = append(out, fmt.Sprintf("%s%s EntryPoint: %#08x\n%s", padding, thread.Flavor, regs.PC, regs.String(regPadding))) + out = append(out, fmt.Sprintf("%s%s EntryPoint: %#08x\n%s", padding, flavor, regs.PC, regs.String(regPadding))) case types.ARM_THREAD_STATE64: var regs RegsARM64 binary.Read(bytes.NewReader(thread.Data), t.bo, ®s) - out = append(out, fmt.Sprintf("%s%s EntryPoint: %#016x\n%s", padding, thread.Flavor, regs.PC, regs.String(regPadding))) + out = append(out, fmt.Sprintf("%s%s EntryPoint: %#016x\n%s", padding, flavor, regs.PC, regs.String(regPadding))) case types.ARM_EXCEPTION_STATE: var regs ArmExceptionState binary.Read(bytes.NewReader(thread.Data), t.bo, ®s) - out = append(out, fmt.Sprintf("%s%s:\n%s", padding, thread.Flavor, regs.String(regPadding))) + out = append(out, fmt.Sprintf("%s%s:\n%s", padding, flavor, regs.String(regPadding))) case types.ARM_EXCEPTION_STATE64: var regs ArmExceptionState64 binary.Read(bytes.NewReader(thread.Data), t.bo, ®s) - out = append(out, fmt.Sprintf("%s%s:\n%s", padding, thread.Flavor, regs.String(regPadding))) + out = append(out, fmt.Sprintf("%s%s:\n%s", padding, flavor, regs.String(regPadding))) default: - out = append(out, fmt.Sprintf("%s%s", padding, thread.Flavor)) + out = append(out, fmt.Sprintf("%s%s", padding, flavor)) } } return fmt.Sprintf("Threads: %d\n%s", len(t.Threads), strings.Join(out, "\n")) @@ -2010,6 +2017,7 @@ type VersionMinWatchOS struct { type Note struct { LoadBytes types.NoteCmd + bo binary.ByteOrder Data []byte } @@ -2023,7 +2031,60 @@ func (n *Note) Write(buf *bytes.Buffer, o binary.ByteOrder) error { return nil } func (n *Note) String() string { - return fmt.Sprintf("DataOwner: \"%s\", offset=0x%08x-0x%08x size=%5d", string(n.DataOwner[:]), n.Offset, n.Offset+n.Size, n.Size) + var note string + padding := strings.Repeat(" ", 7) + switch string(bytes.Trim(n.DataOwner[:], "\x00")) { + case "addrable bits": + var version uint32 + if err := binary.Read(bytes.NewReader(n.Data), n.bo, &version); err == nil { + switch version { + case 3: + var addrableBits types.NoteAddrableBitsV3 + if err := binary.Read(bytes.NewReader(n.Data), n.bo, &addrableBits); err == nil { + note = fmt.Sprintf("%sAddrableBits: version=%d, num_bits=%d", padding, version, addrableBits.NumAddrBits) + } + case 4: + var addrableBits types.NoteAddrableBitsV4 + if err := binary.Read(bytes.NewReader(n.Data), n.bo, &addrableBits); err == nil { + note = fmt.Sprintf("%sAddrableBits: version=%d, lo_bits=%d, hi_bits=%d", padding, version, addrableBits.LoAddrBits, addrableBits.HiAddrBits) + } + } + } + case "all image infos": + var imgs []types.NoteAllImageInfosImage + r := bytes.NewReader(n.Data) + var allImageInfos types.NoteAllImageInfos + if err := binary.Read(r, n.bo, &allImageInfos); err == nil { + note = fmt.Sprintf("%sAllImageInfos: version=%d img_count=%d", padding, allImageInfos.Version, allImageInfos.InfoArrayCount) + entries := make([]types.NoteAllImageInfosImageEntry, allImageInfos.InfoArrayCount) + if err := binary.Read(r, n.bo, &entries); err == nil { + for _, entry := range entries { + var img types.NoteAllImageInfosImage + img.Entry = entry + segs := make([]types.NoteAllImageInfosSegmentVmaddr, entry.SegmentCount) + if err := binary.Read(r, n.bo, &segs); err == nil { + img.Segments = segs + } + imgs = append(imgs, img) + } + for i := 0; i < int(allImageInfos.InfoArrayCount); i++ { + if name, err := readString(r); err == nil { + imgs[i].Name = name + } + } + note += "\n" + } + } + for _, img := range imgs { + note += fmt.Sprintf("%s%#x: %s\n", strings.Repeat(" ", 9), img.Entry.LoadAddress, img.Name) + for _, seg := range img.Segments { + note += fmt.Sprintf("%s%#x %s\n", strings.Repeat(" ", 11), seg.VmAddr, string(bytes.Trim(seg.Name[:], "\x00"))) + } + } + note = strings.TrimSuffix(note, "\n") + } + + return fmt.Sprintf("DataOwner: \"%s\", offset=0x%08x-0x%08x size=%5d\n%s", string(n.DataOwner[:]), n.Offset, n.Offset+n.Size, n.Size, note) } func (n *Note) MarshalJSON() ([]byte, error) { return json.Marshal(struct { diff --git a/file.go b/file.go index 5a14c9d..036bb7b 100644 --- a/file.go +++ b/file.go @@ -368,6 +368,9 @@ func NewFile(r io.ReaderAt, config ...FileConfig) (*File, error) { l.LoadCmd = cmd l.Len = siz l.bo = bo + if f.isArm() || f.isArm64() || f.isArm64e() { + l.IsArm = true + } for { var thread types.ThreadState err := binary.Read(b, bo, &thread.Flavor) @@ -1161,6 +1164,7 @@ func NewFile(r io.ReaderAt, config ...FileConfig) (*File, error) { l.DataOwner = n.DataOwner l.Offset = n.Offset l.Size = n.Size + l.bo = bo l.Data = make([]byte, l.Size) if _, err := f.cr.ReadAt(l.Data, int64(l.Offset)); err != nil { return nil, fmt.Errorf("failed to read Note data at offset=%#x; %v", int64(l.Offset), err) @@ -1399,7 +1403,8 @@ func readString(r io.Reader) (string, error) { } func (f *File) is64bit() bool { return f.FileHeader.Magic == types.Magic64 } -func (f *File) isArm64() bool { return f.CPU == types.CPUArm64 } +func (f *File) isArm() bool { return f.CPU == types.CPUArm } +func (f *File) isArm64() bool { return f.CPU == types.CPUArm64 || f.CPU == types.CPUArm6432 } func (f *File) isArm64e() bool { return f.isArm64() && (f.SubCPU&types.CpuSubtypeMask) == types.CPUSubtypeArm64E } diff --git a/types/commands.go b/types/commands.go index 8ab9642..d8fbc45 100644 --- a/types/commands.go +++ b/types/commands.go @@ -1,6 +1,6 @@ package types -//go:generate stringer -type=LoadCmd,ThreadFlavor -output commands_string.go +//go:generate stringer -type=LoadCmd,X86ThreadFlavor,ArmThreadFlavor -output commands_string.go import ( "encoding/json" @@ -367,56 +367,58 @@ type IDDylinkerCmd DylinkerCmd // LC_ID_DYLINKER type DyldEnvironmentCmd DylinkerCmd // LC_DYLD_ENVIRONMENT type ThreadFlavor uint32 +type X86ThreadFlavor ThreadFlavor +type ArmThreadFlavor ThreadFlavor const ( // // x86 flavors // - X86_THREAD_STATE32 ThreadFlavor = 1 - X86_FLOAT_STATE32 ThreadFlavor = 2 - X86_EXCEPTION_STATE32 ThreadFlavor = 3 - X86_THREAD_STATE64 ThreadFlavor = 4 - X86_FLOAT_STATE64 ThreadFlavor = 5 - X86_EXCEPTION_STATE64 ThreadFlavor = 6 - X86_THREAD_STATE ThreadFlavor = 7 - X86_FLOAT_STATE ThreadFlavor = 8 - X86_EXCEPTION_STATE ThreadFlavor = 9 - X86_DEBUG_STATE32 ThreadFlavor = 10 - X86_DEBUG_STATE64 ThreadFlavor = 11 - X86_DEBUG_STATE ThreadFlavor = 12 - X86_THREAD_STATE_NONE ThreadFlavor = 13 + X86_THREAD_STATE32 X86ThreadFlavor = 1 + X86_FLOAT_STATE32 X86ThreadFlavor = 2 + X86_EXCEPTION_STATE32 X86ThreadFlavor = 3 + X86_THREAD_STATE64 X86ThreadFlavor = 4 + X86_FLOAT_STATE64 X86ThreadFlavor = 5 + X86_EXCEPTION_STATE64 X86ThreadFlavor = 6 + X86_THREAD_STATE X86ThreadFlavor = 7 + X86_FLOAT_STATE X86ThreadFlavor = 8 + X86_EXCEPTION_STATE X86ThreadFlavor = 9 + X86_DEBUG_STATE32 X86ThreadFlavor = 10 + X86_DEBUG_STATE64 X86ThreadFlavor = 11 + X86_DEBUG_STATE X86ThreadFlavor = 12 + X86_THREAD_STATE_NONE X86ThreadFlavor = 13 /* 14 and 15 are used for the internal X86_SAVED_STATE flavours */ /* Arrange for flavors to take sequential values, 32-bit, 64-bit, non-specific */ - X86_AVX_STATE32 ThreadFlavor = 16 - X86_AVX_STATE64 ThreadFlavor = (X86_AVX_STATE32 + 1) - X86_AVX_STATE ThreadFlavor = (X86_AVX_STATE32 + 2) - X86_AVX512_STATE32 ThreadFlavor = 19 - X86_AVX512_STATE64 ThreadFlavor = (X86_AVX512_STATE32 + 1) - X86_AVX512_STATE ThreadFlavor = (X86_AVX512_STATE32 + 2) - X86_PAGEIN_STATE ThreadFlavor = 22 - X86_THREAD_FULL_STATE64 ThreadFlavor = 23 - X86_INSTRUCTION_STATE ThreadFlavor = 24 - X86_LAST_BRANCH_STATE ThreadFlavor = 25 + X86_AVX_STATE32 X86ThreadFlavor = 16 + X86_AVX_STATE64 X86ThreadFlavor = (X86_AVX_STATE32 + 1) + X86_AVX_STATE X86ThreadFlavor = (X86_AVX_STATE32 + 2) + X86_AVX512_STATE32 X86ThreadFlavor = 19 + X86_AVX512_STATE64 X86ThreadFlavor = (X86_AVX512_STATE32 + 1) + X86_AVX512_STATE X86ThreadFlavor = (X86_AVX512_STATE32 + 2) + X86_PAGEIN_STATE X86ThreadFlavor = 22 + X86_THREAD_FULL_STATE64 X86ThreadFlavor = 23 + X86_INSTRUCTION_STATE X86ThreadFlavor = 24 + X86_LAST_BRANCH_STATE X86ThreadFlavor = 25 // // arm flavors // - ARM_THREAD_STATE ThreadFlavor = 1 - ARM_UNIFIED_THREAD_STATE ThreadFlavor = ARM_THREAD_STATE - ARM_VFP_STATE ThreadFlavor = 2 - ARM_EXCEPTION_STATE ThreadFlavor = 3 - ARM_DEBUG_STATE ThreadFlavor = 4 /* pre-armv8 */ - ARM_THREAD_STATE_NONE ThreadFlavor = 5 - ARM_THREAD_STATE64 ThreadFlavor = 6 - ARM_EXCEPTION_STATE64 ThreadFlavor = 7 + ARM_THREAD_STATE ArmThreadFlavor = 1 + ARM_UNIFIED_THREAD_STATE ArmThreadFlavor = ARM_THREAD_STATE + ARM_VFP_STATE ArmThreadFlavor = 2 + ARM_EXCEPTION_STATE ArmThreadFlavor = 3 + ARM_DEBUG_STATE ArmThreadFlavor = 4 /* pre-armv8 */ + ARM_THREAD_STATE_NONE ArmThreadFlavor = 5 + ARM_THREAD_STATE64 ArmThreadFlavor = 6 + ARM_EXCEPTION_STATE64 ArmThreadFlavor = 7 // ARM_THREAD_STATE_LAST 8 /* legacy */ - ARM_THREAD_STATE32 ThreadFlavor = 9 + ARM_THREAD_STATE32 ArmThreadFlavor = 9 /* API */ - ARM_DEBUG_STATE32 ThreadFlavor = 14 - ARM_DEBUG_STATE64 ThreadFlavor = 15 - ARM_NEON_STATE ThreadFlavor = 16 - ARM_NEON_STATE64 ThreadFlavor = 17 - ARM_CPMU_STATE64 ThreadFlavor = 18 - ARM_PAGEIN_STATE ThreadFlavor = 27 + ARM_DEBUG_STATE32 ArmThreadFlavor = 14 + ARM_DEBUG_STATE64 ArmThreadFlavor = 15 + ARM_NEON_STATE ArmThreadFlavor = 16 + ARM_NEON_STATE64 ArmThreadFlavor = 17 + ARM_CPMU_STATE64 ArmThreadFlavor = 18 + ARM_PAGEIN_STATE ArmThreadFlavor = 27 ) type ThreadState struct { @@ -1128,6 +1130,46 @@ type NoteCmd struct { Size uint64 // length of data region } +/* Note commands DataOwner = "addrable bits" */ +type NoteAddrableBitsV3 struct { + Version uint32 + NumAddrBits uint32 + Reserved uint64 +} +type NoteAddrableBitsV4 struct { + Version uint32 + LoAddrBits uint32 + HiAddrBits uint32 + Reserved uint32 +} +type NoteAllImageInfosImageEntry struct { + FilepathOffset uint64 // offset in corefile to c-string of the file path, + // UINT64_MAX if unavailable. + UUID UUID // uint8_t[16]. should be set to all zeroes if + // uuid is unknown. + LoadAddress uint64 // UINT64_MAX if unknown. + SegAddrsOffset uint64 // offset to the array of struct segment_vmaddr's. + SegmentCount uint32 // The number of segments for this binary. + Unused uint32 +} +type NoteAllImageInfosSegmentVmaddr struct { + Name [16]byte + VmAddr uint64 + Unused uint64 +} +type NoteAllImageInfos struct { + Version uint32 + InfoArrayCount uint32 + EntriesFileoff uint64 + EntriesSize uint32 + _ uint32 +} +type NoteAllImageInfosImage struct { + Name string + Entry NoteAllImageInfosImageEntry + Segments []NoteAllImageInfosSegmentVmaddr +} + // FilesetEntryCmd commands describe constituent Mach-O files that are part // of a fileset. In one implementation, entries are dylibs with individual // mach headers and repositionable text and data segments. Each entry is diff --git a/types/commands_string.go b/types/commands_string.go index 6dfe1dc..633197f 100644 --- a/types/commands_string.go +++ b/types/commands_string.go @@ -1,4 +1,4 @@ -// Code generated by "stringer -type=LoadCmd,ThreadFlavor -output commands_string.go"; DO NOT EDIT. +// Code generated by "stringer -type=LoadCmd,X86ThreadFlavor,ArmThreadFlavor -output commands_string.go"; DO NOT EDIT. package types @@ -160,6 +160,34 @@ func _() { _ = x[X86_THREAD_FULL_STATE64-23] _ = x[X86_INSTRUCTION_STATE-24] _ = x[X86_LAST_BRANCH_STATE-25] +} + +const ( + _X86ThreadFlavor_name_0 = "X86_THREAD_STATE32X86_FLOAT_STATE32X86_EXCEPTION_STATE32X86_THREAD_STATE64X86_FLOAT_STATE64X86_EXCEPTION_STATE64X86_THREAD_STATEX86_FLOAT_STATEX86_EXCEPTION_STATEX86_DEBUG_STATE32X86_DEBUG_STATE64X86_DEBUG_STATEX86_THREAD_STATE_NONE" + _X86ThreadFlavor_name_1 = "X86_AVX_STATE32X86_AVX_STATE64X86_AVX_STATEX86_AVX512_STATE32X86_AVX512_STATE64X86_AVX512_STATEX86_PAGEIN_STATEX86_THREAD_FULL_STATE64X86_INSTRUCTION_STATEX86_LAST_BRANCH_STATE" +) + +var ( + _X86ThreadFlavor_index_0 = [...]uint8{0, 18, 35, 56, 74, 91, 112, 128, 143, 162, 179, 196, 211, 232} + _X86ThreadFlavor_index_1 = [...]uint8{0, 15, 30, 43, 61, 79, 95, 111, 134, 155, 176} +) + +func (i X86ThreadFlavor) String() string { + switch { + case 1 <= i && i <= 13: + i -= 1 + return _X86ThreadFlavor_name_0[_X86ThreadFlavor_index_0[i]:_X86ThreadFlavor_index_0[i+1]] + case 16 <= i && i <= 25: + i -= 16 + return _X86ThreadFlavor_name_1[_X86ThreadFlavor_index_1[i]:_X86ThreadFlavor_index_1[i+1]] + default: + return "X86ThreadFlavor(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} _ = x[ARM_THREAD_STATE-1] _ = x[ARM_UNIFIED_THREAD_STATE-1] _ = x[ARM_VFP_STATE-2] @@ -178,22 +206,30 @@ func _() { } const ( - _ThreadFlavor_name_0 = "X86_THREAD_STATE32X86_FLOAT_STATE32X86_EXCEPTION_STATE32X86_THREAD_STATE64X86_FLOAT_STATE64X86_EXCEPTION_STATE64X86_THREAD_STATEX86_FLOAT_STATEX86_EXCEPTION_STATEX86_DEBUG_STATE32X86_DEBUG_STATE64X86_DEBUG_STATEX86_THREAD_STATE_NONEARM_DEBUG_STATE32ARM_DEBUG_STATE64X86_AVX_STATE32X86_AVX_STATE64X86_AVX_STATEX86_AVX512_STATE32X86_AVX512_STATE64X86_AVX512_STATEX86_PAGEIN_STATEX86_THREAD_FULL_STATE64X86_INSTRUCTION_STATEX86_LAST_BRANCH_STATE" - _ThreadFlavor_name_1 = "ARM_PAGEIN_STATE" + _ArmThreadFlavor_name_0 = "ARM_THREAD_STATEARM_VFP_STATEARM_EXCEPTION_STATEARM_DEBUG_STATEARM_THREAD_STATE_NONEARM_THREAD_STATE64ARM_EXCEPTION_STATE64" + _ArmThreadFlavor_name_1 = "ARM_THREAD_STATE32" + _ArmThreadFlavor_name_2 = "ARM_DEBUG_STATE32ARM_DEBUG_STATE64ARM_NEON_STATEARM_NEON_STATE64ARM_CPMU_STATE64" + _ArmThreadFlavor_name_3 = "ARM_PAGEIN_STATE" ) var ( - _ThreadFlavor_index_0 = [...]uint16{0, 18, 35, 56, 74, 91, 112, 128, 143, 162, 179, 196, 211, 232, 249, 266, 281, 296, 309, 327, 345, 361, 377, 400, 421, 442} + _ArmThreadFlavor_index_0 = [...]uint8{0, 16, 29, 48, 63, 84, 102, 123} + _ArmThreadFlavor_index_2 = [...]uint8{0, 17, 34, 48, 64, 80} ) -func (i ThreadFlavor) String() string { +func (i ArmThreadFlavor) String() string { switch { - case 1 <= i && i <= 25: + case 1 <= i && i <= 7: i -= 1 - return _ThreadFlavor_name_0[_ThreadFlavor_index_0[i]:_ThreadFlavor_index_0[i+1]] + return _ArmThreadFlavor_name_0[_ArmThreadFlavor_index_0[i]:_ArmThreadFlavor_index_0[i+1]] + case i == 9: + return _ArmThreadFlavor_name_1 + case 14 <= i && i <= 18: + i -= 14 + return _ArmThreadFlavor_name_2[_ArmThreadFlavor_index_2[i]:_ArmThreadFlavor_index_2[i+1]] case i == 27: - return _ThreadFlavor_name_1 + return _ArmThreadFlavor_name_3 default: - return "ThreadFlavor(" + strconv.FormatInt(int64(i), 10) + ")" + return "ArmThreadFlavor(" + strconv.FormatInt(int64(i), 10) + ")" } }