Skip to content

Commit

Permalink
feat: add support for parsing LC_NOTE data
Browse files Browse the repository at this point in the history
  • Loading branch information
blacktop committed May 31, 2024
1 parent 261acfb commit 129f268
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 58 deletions.
79 changes: 70 additions & 9 deletions cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ type Thread struct {
LoadBytes
types.ThreadCmd
bo binary.ByteOrder
IsArm bool
Threads []types.ThreadState
}

Expand Down Expand Up @@ -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, &regs)
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, &regs)
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, &regs)
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, &regs)
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, &regs)
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, &regs)
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"))
Expand Down Expand Up @@ -2010,6 +2017,7 @@ type VersionMinWatchOS struct {
type Note struct {
LoadBytes
types.NoteCmd
bo binary.ByteOrder
Data []byte
}

Expand All @@ -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 {
Expand Down
7 changes: 6 additions & 1 deletion file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
120 changes: 81 additions & 39 deletions types/commands.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
54 changes: 45 additions & 9 deletions types/commands_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 129f268

Please sign in to comment.