Files
ipsw/pkg/crashlog/ips.go

2436 lines
92 KiB
Go

package crashlog
import (
"bytes"
"compress/gzip"
_ "embed"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"slices"
"sort"
"strings"
"text/tabwriter"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/apex/log"
"github.com/blacktop/arm64-cgo/disassemble"
"github.com/blacktop/go-macho"
"github.com/blacktop/go-macho/pkg/swift"
"github.com/blacktop/go-macho/types"
"github.com/blacktop/ipsw/internal/commands/dsc"
"github.com/blacktop/ipsw/internal/commands/extract"
"github.com/blacktop/ipsw/internal/demangle"
"github.com/blacktop/ipsw/internal/search"
"github.com/blacktop/ipsw/internal/syms/server"
"github.com/blacktop/ipsw/pkg/disass"
"github.com/blacktop/ipsw/pkg/dyld"
"github.com/blacktop/ipsw/pkg/info"
"github.com/blacktop/ipsw/pkg/signature"
"github.com/fatih/color"
"github.com/go-viper/mapstructure/v2"
)
// REFERENCES:
// - https://developer.apple.com/documentation/xcode/interpreting-the-json-format-of-a-crash-report
// - https://github.com/zed-industries/zed/blob/main/crates/collab/src/api/ips_file.rs
//go:embed data/log_type.gz
var logTypeData []byte
var colorTime = color.New(color.Bold, color.FgHiGreen).SprintFunc()
var colorError = color.New(color.Bold, color.FgHiRed).SprintFunc()
var colorAddr = color.New(color.Faint).SprintfFunc()
var colorBold = color.New(color.Bold).SprintfFunc()
var colorImage = color.New(color.Bold, color.FgHiMagenta).SprintfFunc()
var colorField = color.New(color.Bold, color.FgHiBlue).SprintFunc()
var ErrDone = errors.New("done")
type LogType struct {
Name string `json:"name"`
Comment string `json:"comment,omitempty"`
Extention string `json:"ext,omitempty"`
EOS bool `json:"eOS,omitempty"`
Gm bool `json:"gm,omitempty"`
Legacy bool `json:"legacy,omitempty"`
MacOS bool `json:"macOS,omitempty"`
Disabled bool `json:"disabled,omitempty"`
WatchSync bool `json:"watchSync,omitempty"`
Limit int `json:"limit,omitempty"`
Radar string `json:"radar,omitempty"`
Seed bool `json:"seed,omitempty"`
Routing string `json:"routing,omitempty"`
Subrouting string `json:"subrouting,omitempty"`
}
// LogTypes from /System/Library/PrivateFrameworks/OSAnalytics.framework/Versions/A/Resources/submissionConfig.plist
type LogTypes map[string]LogType
type DB struct {
LogTypes LogTypes `json:"log_types"`
SubmissionParams map[string]any `json:"submission_params"`
WhitelistedDomains map[string]any `json:"whitelisted_domains"`
}
func GetLogTypes() (*LogTypes, error) {
var db DB
zr, err := gzip.NewReader(bytes.NewReader(logTypeData))
if err != nil {
return nil, err
}
defer zr.Close()
if err := json.NewDecoder(zr).Decode(&db); err != nil {
return nil, fmt.Errorf("failed unmarshaling ipsw_db data: %w", err)
}
return &db.LogTypes, nil
}
type Config struct {
All bool
Running bool
Process string
Unslid bool
KernelSlide uint64 // Custom KASLR slide to apply to kernel frames for live debugging
DSCSlide uint64 // Custom slide to apply to dyld_shared_cache frames for live debugging
Demangle bool
Hex bool
Verbose bool
Peek bool
PeekCount int // Number of instructions to show with --peek (default 5)
PemDB string
SignaturesDir string
ExtrasDir string
IDAScript bool // Generate IDAPython script to mark panic frames
CrashlogPath string // Path to the crashlog file (set automatically by OpenIPS)
}
type Ips struct {
Header IpsMetadata
Payload IPSPayload
Config *Config
KernelSymbols signature.SymbolMap // Symbols discovered while analyzing the kernelcache
}
type Platform int
const (
PlatformMacOS Platform = 1
PlatformIOS Platform = 2 // (includes iOS apps running under macOS on Apple silicon)
PlatformTVOS Platform = 3
PlatformWatch Platform = 4
PlatformMacCatalyst Platform = 6
PlatformIOSSimulator Platform = 7
PlatformTVOSSimulator Platform = 8
PlatformWatchOSSimulator Platform = 9
)
func (p Platform) String() string {
switch p {
case PlatformMacOS:
return "macOS"
case PlatformIOS:
return "iOS"
case PlatformTVOS:
return "tvOS"
case PlatformWatch:
return "watchOS"
case PlatformMacCatalyst:
return "Mac Catalyst"
case PlatformIOSSimulator:
return "iOS Simulator"
case PlatformTVOSSimulator:
return "tvOS Simulator"
case PlatformWatchOSSimulator:
return "watchOS Simulator"
default:
return "Unknown"
}
}
type Timestamp time.Time
func (ts *Timestamp) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), "\"")
if s == "null" || s == "" {
return nil
}
// 2023-08-04 19:10:03.00 +0200
t, err := time.Parse("2006-01-02 15:04:05 -0700", s)
if err != nil {
return err
}
*ts = Timestamp(t)
return nil
}
func (r Timestamp) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(r))
}
func (r Timestamp) Format(s string) string {
t := time.Time(r)
return t.Format(s)
}
type IpsMetadata struct {
Name string `json:"name,omitempty"`
AppName string `json:"app_name,omitempty"`
AppVersion string `json:"app_version,omitempty"`
BugType string `json:"bug_type,omitempty"`
BugTypeDesc string `json:"bug_type_desc,omitempty"`
OsVersion string `json:"os_version,omitempty"`
BundleID string `json:"bundleID,omitempty"`
BuildVersion string `json:"build_version,omitempty"`
IncidentID string `json:"incident_id,omitempty"`
Platform Platform `json:"platform,omitempty"`
Timestamp Timestamp `json:"timestamp"`
SliceUUID string `json:"slice_uuid,omitempty"`
ShareWithAppDevs int `json:"share_with_app_devs,omitempty"`
IsFirstParty int `json:"is_first_party,omitempty"`
RootsInstalled int `json:"roots_installed,omitempty"`
}
func (m IpsMetadata) Version() string {
re := regexp.MustCompile(`(?P<version>[0-9.]+) \((?P<build>\w+)\)$`)
matches := re.FindStringSubmatch(m.OsVersion)
if len(matches) != 3 {
return m.OsVersion
}
return matches[1]
}
func (m IpsMetadata) Build() string {
re := regexp.MustCompile(`(?P<version>[0-9.]+) \((?P<build>\w+)\)$`)
matches := re.FindStringSubmatch(m.OsVersion)
if len(matches) != 3 {
return m.OsVersion
}
return matches[2]
}
type MemoryStatus struct {
BusyBufferCount int64 `json:"busyBufferCount,omitempty"`
Compressions int64 `json:"compressions,omitempty"`
CompressorSize int64 `json:"compressorSize,omitempty"`
Decompressions int64 `json:"decompressions,omitempty"`
MemoryPages struct {
Active int64 `json:"active,omitempty"`
FileBacked int64 `json:"fileBacked,omitempty"`
Free int64 `json:"free,omitempty"`
Inactive int64 `json:"inactive,omitempty"`
Purgeable int64 `json:"purgeable,omitempty"`
Speculative int64 `json:"speculative,omitempty"`
Throttled int64 `json:"throttled,omitempty"`
Wired int64 `json:"wired,omitempty"`
} `json:"memoryPages"`
MemoryPressure bool `json:"memoryPressure,omitempty"`
MemoryPressureDetails struct {
PagesReclaimed int64 `json:"pagesReclaimed,omitempty"`
PagesWanted int64 `json:"pagesWanted,omitempty"`
} `json:"memoryPressureDetails"`
PageSize int64 `json:"pageSize,omitempty"`
}
func (ms MemoryStatus) String() string {
return fmt.Sprintf(
"%s:\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s:\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s: %t\n"+
" %s:\n"+
" %s: %d\n"+
" %s: %d\n"+
" %s: %d\n",
colorField("Memory Status"),
colorField("Busy Buffer Count"), ms.BusyBufferCount,
colorField("Compressions"), ms.Compressions,
colorField("Compressor Size"), ms.CompressorSize,
colorField("Decompressions"), ms.Decompressions,
colorField("Memory Pages"),
colorField("Active"), ms.MemoryPages.Active,
colorField("File Backed"), ms.MemoryPages.FileBacked,
colorField("Free"), ms.MemoryPages.Free,
colorField("Inactive"), ms.MemoryPages.Inactive,
colorField("Purgeable"), ms.MemoryPages.Purgeable,
colorField("Speculative"), ms.MemoryPages.Speculative,
colorField("Throttled"), ms.MemoryPages.Throttled,
colorField("Wired"), ms.MemoryPages.Wired,
colorField("Memory Pressure"), ms.MemoryPressure,
colorField("Memory Pressure Details"),
colorField("Pages Reclaimed"), ms.MemoryPressureDetails.PagesReclaimed,
colorField("Pages Wanted"), ms.MemoryPressureDetails.PagesWanted,
colorField("Page Size"), ms.PageSize,
)
}
type Process struct {
ID int `json:"pid"`
Name string `json:"procname"`
TimesThrottled int `json:"timesThrottled"`
TurnstileInfo []string `json:"turnstileInfo"`
PageIns int `json:"pageIns"`
WaitInfo []string `json:"waitInfo"`
TimesDidThrottle int `json:"timesDidThrottle"`
CopyOnWriteFaults int `json:"copyOnWriteFaults"`
PageFaults int `json:"pageFaults"`
UserTimeTask float64 `json:"userTimeTask"`
SystemTimeTask float64 `json:"systemTimeTask"`
Flags []string `json:"flags"`
ResidentMemoryBytes int `json:"residentMemoryBytes"`
ThreadByID map[int]Thread `json:"threadById,omitempty"`
}
type Thread struct {
ID int `json:"id"`
Name string `json:"name,omitempty"`
State []string `json:"state"`
Continuation []int `json:"continuation,omitempty"`
Queue string `json:"queue,omitempty"`
DispatchQueueLabel string `json:"dispatch_queue_label,omitempty"`
SchedFlags []string `json:"schedFlags,omitempty"`
BasePriority int `json:"basePriority"`
UserFrames []PanicFrame `json:"userFrames"`
KernelFrames []PanicFrame `json:"kernelFrames,omitempty"`
WaitEvent []float64 `json:"waitEvent,omitempty"`
QosRequested string `json:"qosRequested,omitempty"`
QosEffective string `json:"qosEffective,omitempty"`
UserTime float64 `json:"userTime"`
UserUsec int `json:"user_usec"`
SystemTime float64 `json:"systemTime"`
SystemUsec int `json:"system_usec"`
SchedPriority int `json:"schedPriority"`
}
type BinaryImage struct {
Arch string `json:"arch,omitempty"`
Base uint64 `json:"base,omitempty"`
Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
Size uint64 `json:"size,omitempty"`
Source string `json:"source,omitempty"`
UUID string `json:"uuid,omitempty"`
Slide uint64 `json:"slide,omitempty"`
}
// FactorPackIDs accepts both the current string-list rollout encoding and the legacy object encoding.
type FactorPackIDs []string
func (ids *FactorPackIDs) UnmarshalJSON(data []byte) error {
if bytes.Equal(data, []byte("null")) {
*ids = nil
return nil
}
var values []string
arrayErr := json.Unmarshal(data, &values)
if arrayErr == nil {
*ids = values
return nil
}
var legacyObject struct{}
if err := json.Unmarshal(data, &legacyObject); err == nil {
*ids = nil
return nil
}
return arrayErr
}
func (bi *BinaryImage) UnmarshalJSON(b []byte) error {
var raw []json.RawMessage
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
if err := json.Unmarshal(raw[0], &bi.UUID); err != nil {
return err
}
if err := json.Unmarshal(raw[1], &bi.Base); err != nil {
return err
}
if err := json.Unmarshal(raw[2], &bi.Source); err != nil {
return err
}
switch bi.Source {
case "P":
bi.Source = "Process"
case "S":
bi.Source = "SharedCache"
case "C":
bi.Source = "SharedCacheLibrary"
case "K":
bi.Source = "Kernel"
case "U":
bi.Source = "KernelCache"
case "T":
bi.Source = "KernelTextExec"
case "A":
bi.Source = "Absolute"
default:
return fmt.Errorf("invalid binary image source: %v", bi.Source)
}
return nil
}
type OsVersion struct {
ReleaseType string `json:"releaseType,omitempty"`
Build string `json:"build,omitempty"`
Train string `json:"train,omitempty"`
IsEmbedded bool `json:"isEmbedded,omitempty"`
}
type BundleInfo struct {
CFBundleIdentifier string `json:"CFBundleIdentifier,omitempty"`
CFBundleShortVersionString string `json:"CFBundleShortVersionString,omitempty"`
CFBundleVersion string `json:"CFBundleVersion,omitempty"`
DTAppStoreToolsBuild string `json:"DTAppStoreToolsBuild,omitempty"`
}
type StoreInfo struct {
ItemID string `json:"itemID,omitempty"`
StoreCohortMetadata string `json:"storeCohortMetadata,omitempty"`
DistributorID string `json:"distributorID,omitempty"`
ApplicationVariant string `json:"applicationVariant,omitempty"`
DeviceIdentifierForVendor string `json:"deviceIdentifierForVendor,omitempty"`
SoftwareVersionExternalIdentifier string `json:"softwareVersionExternalIdentifier,omitempty"`
ThirdParty bool `json:"thirdParty,omitempty"`
}
type Exception struct {
Codes string `json:"codes,omitempty"`
RawCodes []int `json:"rawCodes,omitempty"`
Message string `json:"message,omitempty"`
Signal string `json:"signal,omitempty"`
Type string `json:"type,omitempty"`
Subtype string `json:"subtype,omitempty"`
}
func fmtAddr(val uint64) string {
if val == 0 {
return colorAddr("%#016x", val)
}
return fmt.Sprintf("%#016x", val)
}
func fmtAddrSmol(val uint64) string {
if val == 0 {
return colorAddr("%#08x", val)
}
return fmt.Sprintf("%#08x", val)
}
type Register struct {
Value uint64 `json:"value,omitempty"`
SymbolLocation uint64 `json:"symbolLocation,omitempty"`
Symbol string `json:"symbol,omitempty"`
Description string `json:"description,omitempty"`
MatchesCrashFrame int `json:"matchesCrashFrame,omitempty"`
}
func (r Register) String() string {
var out strings.Builder
out.WriteString(fmtAddr(r.Value))
if r.SymbolLocation != 0 {
out.WriteString(fmt.Sprintf(" %s", colorAddr("%#016x", r.SymbolLocation)))
}
if len(r.Symbol) > 0 {
out.WriteString(fmt.Sprintf(" %s", colorImage(r.Symbol)))
}
if len(r.Description) > 0 {
out.WriteString(" " + colorField(r.Description))
}
if r.MatchesCrashFrame != 0 {
out.WriteString(colorBold(" matches_crash_frame=%d", r.MatchesCrashFrame))
}
return out.String()
}
type ThreadState struct {
X []Register `json:"x,omitempty"`
Flavor string `json:"flavor,omitempty"`
LR Register `json:"lr"`
CPSR Register `json:"cpsr"`
FP Register `json:"fp"`
SP Register `json:"sp"`
ESR Register `json:"esr"`
PC Register `json:"pc"`
FAR Register `json:"far"`
}
func (s ThreadState) HasSymbols() bool {
for _, reg := range s.X {
if len(reg.Symbol) > 0 {
return true
}
}
if len(s.LR.Symbol) > 0 {
return true
}
if len(s.FP.Symbol) > 0 {
return true
}
if len(s.SP.Symbol) > 0 {
return true
}
if len(s.PC.Symbol) > 0 {
return true
}
if len(s.ESR.Symbol) > 0 {
return true
}
return false
}
func (s ThreadState) String() string {
if s.HasSymbols() {
var out strings.Builder
for i, reg := range s.X {
out.WriteString(fmt.Sprintf(" %3s: %s\n", fmt.Sprintf("x%d", i), reg))
}
out.WriteString(fmt.Sprintf(" fp: %s\n", s.FP))
out.WriteString(fmt.Sprintf(" lr: %s\n", s.LR))
out.WriteString(fmt.Sprintf(" sp: %s\n", s.SP))
out.WriteString(fmt.Sprintf(" pc: %s\n", s.PC))
out.WriteString(fmt.Sprintf(" far: %s\n", s.FAR))
out.WriteString(fmt.Sprintf(" cpsr: %s\n", s.CPSR))
out.WriteString(fmt.Sprintf(" esr: %s\n", s.ESR))
return out.String()
} else {
return fmt.Sprintf(
" x0: %s x1: %s x2: %s x3: %s\n"+
" x4: %s x5: %s x6: %s x7: %s\n"+
" x8: %s x9: %s x10: %s x11: %s\n"+
" x12: %s x13: %s x14: %s x15: %s\n"+
" x16: %s x17: %s x18: %s x19: %s\n"+
" x20: %s x21: %s x22: %s x23: %s\n"+
" x24: %s x25: %s x26: %s x27: %s\n"+
" x28: %s fp: %s lr: %s\n"+
" sp: %s pc: %s cpsr: %s\n"+
" esr: %s %s\n",
fmtAddr(s.X[0].Value), fmtAddr(s.X[1].Value), fmtAddr(s.X[2].Value), fmtAddr(s.X[3].Value),
fmtAddr(s.X[4].Value), fmtAddr(s.X[5].Value), fmtAddr(s.X[6].Value), fmtAddr(s.X[7].Value),
fmtAddr(s.X[8].Value), fmtAddr(s.X[9].Value), fmtAddr(s.X[10].Value), fmtAddr(s.X[11].Value),
fmtAddr(s.X[12].Value), fmtAddr(s.X[13].Value), fmtAddr(s.X[14].Value), fmtAddr(s.X[15].Value),
fmtAddr(s.X[16].Value), fmtAddr(s.X[17].Value), fmtAddr(s.X[18].Value), fmtAddr(s.X[19].Value),
fmtAddr(s.X[20].Value), fmtAddr(s.X[21].Value), fmtAddr(s.X[22].Value), fmtAddr(s.X[23].Value),
fmtAddr(s.X[24].Value), fmtAddr(s.X[25].Value), fmtAddr(s.X[26].Value), fmtAddr(s.X[27].Value),
fmtAddr(s.X[28].Value), fmtAddr(s.FP.Value), fmtAddr(s.LR.Value),
fmtAddr(s.SP.Value), fmtAddr(s.PC.Value), fmtAddrSmol(s.CPSR.Value),
fmtAddrSmol(s.ESR.Value), colorField(s.ESR.Description))
}
}
type UserThread struct {
Frames []Frame `json:"frames,omitempty"`
ID int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Queue string `json:"queue,omitempty"`
ThreadState ThreadState `json:"threadState"`
Triggered bool `json:"triggered,omitempty"`
}
type Frame struct {
ImageIndex uint64 `json:"imageIndex,omitempty"`
ImageOffset uint64 `json:"imageOffset,omitempty"`
Symbol string `json:"symbol,omitempty"`
SymbolLocation uint64 `json:"symbolLocation,omitempty"`
Slide uint64 `json:"slide,omitempty"`
}
type PanicFrame struct {
ImageIndex uint64 `json:"imageIndex,omitempty"`
ImageName string `json:"imageName,omitempty"`
ImageOffset uint64 `json:"imageOffset,omitempty"`
Symbol string `json:"symbol,omitempty"`
SymbolLocation uint64 `json:"symbolLocation,omitempty"`
Slide uint64 `json:"slide,omitempty"`
// PeekBytes holds ARM64 instructions around the frame address for --peek disassembly
PeekBytes []byte `json:"-"`
// PeekAddr is the starting address of PeekBytes
PeekAddr uint64 `json:"-"`
// PeekFrameIdx is the index of the frame instruction within PeekBytes (in instructions, not bytes)
PeekFrameIdx int `json:"-"`
// PeekSymbols maps addresses to symbol names for enriching branch target disassembly
PeekSymbols map[uint64]string `json:"-"`
}
func (pf *PanicFrame) UnmarshalJSON(b []byte) error {
var s [2]uint64
if err := json.Unmarshal(b, &s); err != nil {
return err
}
*pf = PanicFrame{
ImageIndex: s[0],
ImageName: fmt.Sprintf("image_%d", s[0]),
ImageOffset: s[1],
}
return nil
}
type Termination struct {
ByPid int `json:"byPid,omitempty"`
ByProc string `json:"byProc,omitempty"`
Code int `json:"code,omitempty"`
Flags int `json:"flags,omitempty"` // https://opensource.apple.com/source/xnu/xnu-3789.21.4/bsd/sys/reason.h.auto.html
Indicator string `json:"indicator,omitempty"`
Namespace string `json:"namespace,omitempty"`
}
type PostSampleVMStats struct {
Faults int `json:"faults,omitempty"`
ActiveCount int `json:"active_count,omitempty"`
CompressorPageCount int `json:"compressorPageCount,omitempty"`
TotalUncompressedPagesInCompressor int `json:"total_uncompressed_pages_in_compressor,omitempty"`
Hits int `json:"hits,omitempty"`
Decompressions int `json:"decompressions,omitempty"`
Swapouts int `json:"swapouts,omitempty"`
WireCount int `json:"wire_count,omitempty"`
Lookups int `json:"lookups,omitempty"`
Purges int `json:"purges,omitempty"`
Pageouts int `json:"pageouts,omitempty"`
Pageins int `json:"pageins,omitempty"`
ExternalPageCount int `json:"external_page_count,omitempty"`
ZeroFillCount int `json:"zero_fill_count,omitempty"`
FreeCount int `json:"free_count,omitempty"`
CowFaults int `json:"cow_faults,omitempty"`
SpeculativeCount int `json:"speculative_count,omitempty"`
Swapins int `json:"swapins,omitempty"`
Compressions int `json:"compressions,omitempty"`
Reactivations int `json:"reactivations,omitempty"`
InactiveCount int `json:"inactive_count,omitempty"`
ThrottledCount int `json:"throttled_count,omitempty"`
PurgeableCount int `json:"purgeable_count,omitempty"`
InternalPageCount int `json:"internal_page_count,omitempty"`
}
type AdditionalDetails struct {
StackshotDurationOuterNsec int `json:"stackshot_duration_outer_nsec,omitempty"`
StackshotInFlags int `json:"stackshot_in_flags,omitempty"`
StackshotThreadsCount int `json:"stackshot_threads_count,omitempty"`
StackshotDurationPriorNsec int `json:"stackshot_duration_prior_nsec,omitempty"`
StackshotSizeEstimateAdj int `json:"stackshot_size_estimate_adj,omitempty"`
StackshotOutFlags int `json:"stackshot_out_flags,omitempty"`
StackshotTasksCount int `json:"stackshot_tasks_count,omitempty"`
StackshotInPid int `json:"stackshot_in_pid,omitempty"`
StackshotSizeEstimate int `json:"stackshot_size_estimate,omitempty"`
StackshotTries int `json:"stackshot_tries,omitempty"`
StackshotDurationNsec int `json:"stackshot_duration_nsec,omitempty"`
SystemStateFlags int `json:"system_state_flags,omitempty"`
}
type IPSPayload struct {
/* Kernelspace Fields */
Build string `json:"build,omitempty"`
Product string `json:"product,omitempty"`
SocID string `json:"socId,omitempty"`
Kernel string `json:"kernel,omitempty"`
Incident string `json:"incident,omitempty"`
CrashReporterKey string `json:"crashReporterKey,omitempty"`
Date string `json:"date,omitempty"`
PanicString string `json:"panicString,omitempty"`
MacOSPanicString string `json:"macOSPanicString,omitempty"`
PanicFlags string `json:"panicFlags,omitempty"`
OtherString string `json:"otherString,omitempty"`
MemoryStatus MemoryStatus `json:"memoryStatus"`
ProcessByPid map[int]Process `json:"processByPid,omitempty"`
BinaryImages []BinaryImage `json:"binaryImages,omitempty"`
Notes []string `json:"notes,omitempty"`
/* Userspace Fields */
Asi map[string][]string `json:"asi,omitempty"` // Additional application-specific logging. The properties of this object include an array of log strings.
// For more information, see Diagnostic messages. This appears in a translated report under Application Specific Information.
// https://developer.apple.com/documentation/xcode/examining-the-fields-in-a-crash-report#Diagnostic-messages
IsCorpse any `json:"isCorpse,omitempty"`
IsNonFatal string `json:"isNonFatal,omitempty"`
IsSimulated string `json:"isSimulated,omitempty"`
Uptime int `json:"uptime,omitempty"`
Translated bool `json:"translated,omitempty"`
ProcName string `json:"procName,omitempty"`
ProcPath string `json:"procPath,omitempty"`
ProcRole string `json:"procRole,omitempty"`
ProcLaunch string `json:"procLaunch,omitempty"`
ProcStartAbsTime int64 `json:"procStartAbsTime,omitempty"`
ProcExitAbsTime int64 `json:"procExitAbsTime,omitempty"`
UserID int `json:"userID,omitempty"`
DeployVersion int `json:"deployVersion,omitempty"`
ModelCode string `json:"modelCode,omitempty"`
CoalitionID int `json:"coalitionID,omitempty"`
OsVersion OsVersion `json:"osVersion"`
CaptureTime string `json:"captureTime,omitempty"`
PID int `json:"pid,omitempty"`
CPUType string `json:"cpuType,omitempty"`
RootsInstalled int `json:"roots_installed,omitempty"`
BugType string `json:"bug_type,omitempty"`
BundleInfo BundleInfo `json:"bundleInfo"`
StoreInfo StoreInfo `json:"storeInfo"`
ParentProc string `json:"parentProc,omitempty"`
ParentPid int `json:"parentPid,omitempty"`
CoalitionName string `json:"coalitionName,omitempty"`
LockdownMode int `json:"ldm,omitempty"`
WasUnlockedSinceBoot int `json:"wasUnlockedSinceBoot,omitempty"`
IsLocked int `json:"isLocked,omitempty"`
InstructionByteStream struct {
BeforePC string `json:"beforePC,omitempty"`
AtPC string `json:"atPC,omitempty"`
} `json:"instructionByteStream"`
CodeSigningID string `json:"codeSigningID,omitempty"`
CodeSigningTeamID string `json:"codeSigningTeamID,omitempty"`
CodeSigningFlags int `json:"codeSigningFlags,omitempty"`
CodeSigningValidationCategory int `json:"codeSigningValidationCategory,omitempty"`
CodeSigningTrustLevel int `json:"codeSigningTrustLevel,omitempty"`
CodeSigningMonitor int `json:"codeSigningMonitor,omitempty"`
ThrottleTimeout int64 `json:"throttleTimeout,omitempty"`
BasebandVersion string `json:"basebandVersion,omitempty"`
Exception any `json:"exception,omitempty"`
LastExceptionBacktrace []Frame `json:"lastExceptionBacktrace,omitempty"`
FaultingThread int `json:"faultingThread,omitempty"`
Threads []UserThread `json:"threads,omitempty"`
UsedImages []struct {
Arch string `json:"arch,omitempty"`
Base uint64 `json:"base,omitempty"`
Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
Size uint64 `json:"size,omitempty"`
Source string `json:"source,omitempty"`
UUID string `json:"uuid,omitempty"`
} `json:"usedImages,omitempty"`
SharedCache struct {
Base uint64 `json:"base,omitempty"`
Size uint64 `json:"size,omitempty"`
UUID string `json:"uuid,omitempty"`
} `json:"sharedCache"`
LegacyInfo struct {
ThreadTriggered struct {
Queue string `json:"queue,omitempty"`
} `json:"threadTriggered"`
} `json:"legacyInfo"`
TrialInfo struct {
Rollouts []struct {
RolloutID string `json:"rolloutId,omitempty"`
FactorPackIds FactorPackIDs `json:"factorPackIds"`
DeploymentID int `json:"deploymentId,omitempty"`
} `json:"rollouts,omitempty"`
Experiments []struct {
TreatmentID string `json:"treatmentId,omitempty"`
ExperimentID string `json:"experimentId,omitempty"`
DeploymentID int `json:"deploymentId,omitempty"`
} `json:"experiments,omitempty"`
} `json:"trialInfo"`
DTAppStoreToolsBuild string `json:"DTAppStoreToolsBuild,omitempty"`
Version int `json:"version,omitempty"`
VMSummary string `json:"vmSummary,omitempty"`
VmRegionInfo string `json:"vmregioninfo,omitempty"`
Termination Termination `json:"termination"`
AbsoluteTime int `json:"absoluteTime,omitempty"`
PostSampleVMStats PostSampleVMStats `json:"postSampleVMStats"`
AdditionalDetails AdditionalDetails `json:"additionalDetails"`
panic210 *Panic210
}
func ParseHeader(in string) (hdr *IpsMetadata, err error) {
f, err := os.Open(in)
if err != nil {
return nil, err
}
defer f.Close()
if err := json.NewDecoder(f).Decode(&hdr); err != nil {
return nil, fmt.Errorf("failed to decode JSON header (possibly unsupported .ips format): %w", err)
}
db, err := GetLogTypes()
if err != nil {
return nil, fmt.Errorf("failed to get log types database: %w", err)
}
if bt, ok := (*db)[hdr.BugType]; ok {
hdr.BugTypeDesc = bt.Name
if len(bt.Comment) > 0 {
hdr.BugTypeDesc = hdr.BugType + " (" + bt.Comment + ")"
}
}
return hdr, nil
}
func OpenIPS(in string, conf *Config) (*Ips, error) {
ips := Ips{Config: conf}
// Store the crashlog path for later use (e.g., IDA script generation)
conf.CrashlogPath = in
f, err := os.Open(in)
if err != nil {
return nil, err
}
defer f.Close()
// parse multi-line JSON
dec := json.NewDecoder(f)
if err := dec.Decode(&ips.Header); err != nil {
return nil, err
}
if err := dec.Decode(&ips.Payload); err != nil {
return nil, err
}
if len(ips.Payload.Product) == 0 {
ips.Payload.Product = ips.Payload.ModelCode
}
db, err := GetLogTypes()
if err != nil {
return nil, fmt.Errorf("failed to get log types database: %w", err)
}
if bt, ok := (*db)[ips.Header.BugType]; ok {
ips.Header.BugTypeDesc = bt.Name
if len(bt.Comment) > 0 {
ips.Header.BugTypeDesc = ips.Header.BugType + " (" + bt.Comment + ")"
}
}
return &ips, nil
}
func demangleSym(do bool, in string) string {
if do {
if strings.HasPrefix(in, "__Z") || strings.HasPrefix(in, "_Z") {
return demangle.Do(in, false, false)
}
if strings.HasPrefix(in, "_$s") || strings.HasPrefix(in, "$s") {
in, _ = swift.Demangle(in)
}
}
return in
}
// peekBytesReader is an interface for reading bytes at a virtual address
type peekBytesReader interface {
ReadAtAddr(p []byte, addr uint64) (n int, err error)
}
// readPeekBytes reads ARM64 instructions around a frame address for --peek disassembly.
// count is the total number of instructions to show (centered on frame).
// funcStart is the function's start address (0 if unknown) - used to avoid reading before the function.
// Returns the bytes, the starting address, and the index of the frame instruction within the buffer.
//
// Centering logic:
// - odd count (e.g. 5): equal before/after (2 before, frame, 2 after)
// - even count (e.g. 4): fewer before (1 before, frame, 2 after)
//
// If frame is at/near function start, we shift instructions after to compensate.
func readPeekBytes(m peekBytesReader, frameAddr uint64, count int, funcStart, funcEnd uint64) ([]byte, uint64, int) {
const instrSize = 4 // ARM64 instruction size
if count < 1 {
count = 1
}
// Calculate ideal number of instructions before/after
// For odd count: equal split (5 -> 2 before, 1 frame, 2 after)
// For even count: fewer before (4 -> 1 before, 1 frame, 2 after)
numBefore := (count - 1) / 2
numAfter := count - 1 - numBefore
// Clamp numBefore if we'd read before function start
if funcStart > 0 && frameAddr >= funcStart {
maxBefore := int((frameAddr - funcStart) / instrSize)
if numBefore > maxBefore {
// Shift extra instructions to after
extra := numBefore - maxBefore
numBefore = maxBefore
numAfter += extra
}
}
// Also ensure we don't underflow the start address
maxPossibleBefore := int(frameAddr / instrSize)
if numBefore > maxPossibleBefore {
extra := numBefore - maxPossibleBefore
numBefore = maxPossibleBefore
numAfter += extra
}
// Clamp numAfter if we'd read past function end
if funcEnd > 0 && funcEnd > frameAddr {
// funcEnd points to the first byte AFTER the function, so last valid instr starts at funcEnd-instrSize
maxAfter := max(0, int((funcEnd-frameAddr)/instrSize)-1)
if numAfter > maxAfter {
numAfter = maxAfter
}
}
totalInstrs := numBefore + 1 + numAfter
startAddr := frameAddr - uint64(numBefore*instrSize)
// Read the bytes
buf := make([]byte, totalInstrs*instrSize)
n, err := m.ReadAtAddr(buf, startAddr)
if err != nil || n < len(buf) {
return nil, 0, 0
}
return buf, startAddr, numBefore
}
// symbolLookup is an interface for looking up symbols by address
type symbolLookup interface {
FindAddressSymbols(addr uint64) ([]macho.Symbol, error)
}
// extractPeekSymbols extracts symbol names for branch targets in peek bytes.
// peekAddr is the address to disassemble at (slid for user frames).
// slide is subtracted from branch targets before symbol lookup (for user frames with slide).
// The returned map uses slid addresses as keys to match formatPeekDisassembly lookups.
func extractPeekSymbols(peekBytes []byte, peekAddr uint64, slide uint64, lookup symbolLookup) map[uint64]string {
if len(peekBytes) == 0 || lookup == nil {
return nil
}
instructions, err := disassemble.GetInstructions(peekAddr, peekBytes)
if err != nil {
return nil
}
result := make(map[uint64]string)
for _, block := range instructions.Blocks() {
for _, instr := range block {
// Check for branch instructions (B, BL)
if instr.Operation == disassemble.ARM64_BL || instr.Operation == disassemble.ARM64_B {
for _, operand := range instr.Operands {
if operand.Class == disassemble.LABEL {
target := uint64(operand.Immediate) // slid address
if _, exists := result[target]; !exists {
// Unslide target for symbol lookup (macho has unslid addresses)
lookupAddr := target
if slide > 0 && target >= slide {
lookupAddr = target - slide
}
if syms, err := lookup.FindAddressSymbols(lookupAddr); err == nil && len(syms) > 0 {
// Store with slid key for lookup in formatPeekDisassembly
result[target] = syms[0].Name
}
}
break
}
}
}
}
}
if len(result) == 0 {
return nil
}
return result
}
// formatPeekDisassembly formats the peek bytes as disassembly output.
// peekFrameIdx is the index of the frame instruction within peekBytes (in instructions, not bytes).
// peekAddr is the start address of peekBytes.
// slide is used to adjust displayed addresses if unslid is true.
// customSlide is added to kernel frame addresses for live debugging (--kc-slide flag).
// dscSlide is added to DSC frame addresses for live debugging (--dsc-slide flag).
// isDSCFrame indicates if this is a dyld_shared_cache frame (Source == "SharedCache" or "SharedCacheLibrary").
// peekSymbols optionally maps addresses to symbol names for enriching branch targets.
// doMangle indicates whether to demangle symbol names.
//
// NOTE: For kernel frames, peekAddr is already an unslid address (BinaryImage.Base + ImageOffset).
// For user frames, peekAddr is a slid address. The unslid flag should only affect user frames.
func formatPeekDisassembly(peekBytes []byte, peekAddr uint64, peekFrameIdx int, slide, customSlide, dscSlide uint64, isDSCFrame, unslid bool, peekSymbols map[uint64]string, doMangle bool) string {
if len(peekBytes) == 0 {
return ""
}
instructions, err := disassemble.GetInstructions(peekAddr, peekBytes)
if err != nil {
return ""
}
// Detect if this is a kernel frame by checking if peekAddr is in kernel address space.
// Kernel addresses are in high memory (0xfffffff0_00000000 and above).
isKernelFrame := peekAddr >= 0xfffffff000000000
var out strings.Builder
instrIdx := 0
for _, block := range instructions.Blocks() {
for _, instr := range block {
// Determine display address
displayAddr := instr.Address
if isKernelFrame && customSlide != 0 {
// Apply custom slide to kernel frames for live debugging
displayAddr += customSlide
} else if isDSCFrame && dscSlide != 0 {
// Apply DSC slide to shared cache frames for live debugging
displayAddr += dscSlide
} else if !isKernelFrame && unslid && slide != 0 && displayAddr >= slide {
// Only apply unslide logic to user frames (not kernel frames)
displayAddr -= slide
}
// Mark the frame instruction with --> using the frame index
pad := " "
if instrIdx == peekFrameIdx {
pad = " --> "
}
opStr := strings.TrimSpace(strings.TrimPrefix(instr.String(), instr.Operation.String()))
// Enrich branch instructions (B/BL) with symbol names if available
comment := ""
if (instr.Operation == disassemble.ARM64_BL || instr.Operation == disassemble.ARM64_B) && len(peekSymbols) > 0 {
for _, operand := range instr.Operands {
if operand.Class == disassemble.LABEL {
target := uint64(operand.Immediate)
if symName, ok := peekSymbols[target]; ok {
display := symName
if doMangle {
display = demangle.Do(symName, false, false)
}
comment = fmt.Sprintf(" ; %s", display)
}
break
}
}
}
out.WriteString(fmt.Sprintf("%s%s: %s %s %s%s\n",
pad,
colorAddr("%#08x", displayAddr),
disassemble.GetOpCodeByteString(instr.Raw),
colorImage("%-7s", instr.Operation),
disass.ColorOperands(opStr),
colorField(comment),
))
instrIdx++
}
}
return out.String()
}
// isDSCFrame returns true if the frame is from dyld_shared_cache (Source == "SharedCache" or "SharedCacheLibrary").
func (i *Ips) isDSCFrame(frame PanicFrame) bool {
if int(frame.ImageIndex) < len(i.Payload.BinaryImages) {
src := i.Payload.BinaryImages[frame.ImageIndex].Source
return src == "SharedCache" || src == "SharedCacheLibrary"
}
return false
}
// panicFrameAddr computes the display address for a panic frame.
// After Symbolicate210: ImageOffset already has Base added:
// - User frames: Base is slid, so ImageOffset is the slid runtime address
// - Kernel frames: Base + raw offset = unslid address (kernel frames are already KASLR-unslid in crashlog JSON)
//
// The --unslide flag subtracts the slide to show unslid addresses (only affects user frames).
// The --slide flag adds a custom KASLR slide to kernel frames for live debugging.
// The --dsc-slide flag adds a custom slide to dyld_shared_cache frames for live debugging.
// Returns: (address, slide, wasSlid)
func (i *Ips) panicFrameAddr(frame PanicFrame) (addr, slide uint64, wasSlid bool) {
// After symbolication, ImageOffset = BinaryImage.Base + raw offset from JSON
// (Symbolicate210 does: frame.ImageOffset += BinaryImage.Base)
addr = frame.ImageOffset
// Get slide - frame.Slide is set during Symbolicate210 from BinaryImage.Slide
if frame.Slide != 0 {
slide = frame.Slide
} else if int(frame.ImageIndex) < len(i.Payload.BinaryImages) {
slide = i.Payload.BinaryImages[frame.ImageIndex].Slide
}
// Detect frame source type
var isKernel, isDSC bool
if int(frame.ImageIndex) < len(i.Payload.BinaryImages) {
src := i.Payload.BinaryImages[frame.ImageIndex].Source
switch src {
case "Kernel", "KernelCache", "KernelTextExec":
isKernel = true
case "SharedCache", "SharedCacheLibrary":
isDSC = true
}
}
// Apply custom slides for live debugging:
// - Kernel frames are stored unslid, so we ADD the custom slide to get runtime addresses
// - DSC frames are stored with the static cache address, ADD custom slide to get runtime addresses
if isKernel && i.Config.KernelSlide != 0 {
addr += i.Config.KernelSlide
slide = i.Config.KernelSlide
wasSlid = true
} else if isDSC && i.Config.DSCSlide != 0 {
addr += i.Config.DSCSlide
slide = i.Config.DSCSlide
wasSlid = true
} else if i.Config.Unslid {
// Unslide user frames (kernel frames are already unslid, so skip them)
if !isKernel && slide != 0 && addr >= slide {
addr -= slide
}
}
return addr, slide, wasSlid
}
// openDSCs opens the dyld_shared_caches used for userspace symbolication. With
// an IPSW it mounts and opens the IPSW's caches; otherwise it opens the supplied
// paths (e.g. an Xcode DeviceSupport dump). The returned func releases the
// caches (and unmounts the IPSW when applicable).
func (i *Ips) openDSCs(ipswPath string, dscPaths []string) ([]*dyld.File, func(), error) {
if ipswPath != "" {
ctx, fs, err := dsc.OpenFromIPSW(ipswPath, i.Config.PemDB, false, true)
if err != nil {
return nil, nil, fmt.Errorf("failed to open DSC from IPSW: %w", err)
}
return fs, func() {
for _, f := range fs {
f.Close()
}
ctx.Unmount()
}, nil
}
var fs []*dyld.File
for _, p := range dscPaths {
f, err := dyld.Open(p)
if err != nil {
for _, opened := range fs {
opened.Close()
}
return nil, nil, fmt.Errorf("failed to open DeviceSupport DSC %s: %w", p, err)
}
fs = append(fs, f)
}
return fs, func() {
for _, f := range fs {
f.Close()
}
}, nil
}
// hasSharedCacheImage reports whether any binary image is the shared cache,
// i.e. whether there are userspace frames a DSC/loose-dylib source could resolve.
func (i *Ips) hasSharedCacheImage() bool {
for _, img := range i.Payload.BinaryImages {
if img.Name == "dyld_shared_cache" {
return true
}
}
return false
}
// Symbolicate210 symbolicates a 210/288 panic crashlog. When ipswPath is set,
// it extracts the matching kernelcache and opens the IPSW's DSC for full
// kernel + userspace symbolication. When ipswPath is empty, only userspace
// frames are symbolicated (kernel frames are left raw) using, in order of
// preference: the dyld_shared_caches at dscPaths, or — when those are absent —
// the loose extracted dylibs under looseDir (a modern Xcode DeviceSupport
// "Symbols" dump, which no longer ships the cache itself).
func (i *Ips) Symbolicate210(ipswPath string, dscPaths []string, looseDir string) (err error) {
i.Payload.panic210, err = parsePanicString210(i.Payload.PanicString)
if err != nil {
return fmt.Errorf("failed to parse panic string: %w", err)
}
total := len(i.Payload.BinaryImages)
// add default binary image names
for idx, img := range i.Payload.BinaryImages {
switch img.Source {
case "Absolute":
i.Payload.BinaryImages[idx].Name = "absolute"
total--
case "Kernel":
i.Payload.BinaryImages[idx].Name = "kernel"
if i.Payload.panic210 != nil && i.Payload.panic210.KernelSlide != nil {
i.Payload.BinaryImages[idx].Slide = i.Payload.panic210.KernelSlide.Value.(uint64)
}
total--
case "KernelCache":
i.Payload.BinaryImages[idx].Name = "kernelcache"
if i.Payload.panic210 != nil {
switch {
case i.Payload.panic210.KernelCacheSlide != nil:
i.Payload.BinaryImages[idx].Slide = i.Payload.panic210.KernelCacheSlide.Value.(uint64)
case i.Payload.panic210.KernelSlide != nil:
i.Payload.BinaryImages[idx].Slide = i.Payload.panic210.KernelSlide.Value.(uint64)
}
}
total--
case "KernelTextExec":
i.Payload.BinaryImages[idx].Name = "kernelcache (__TEXT_EXEC)"
if i.Payload.panic210 != nil && i.Payload.panic210.KernelTextExecSlide != nil {
i.Payload.BinaryImages[idx].Slide = i.Payload.panic210.KernelTextExecSlide.Value.(uint64)
}
total--
case "SharedCache":
i.Payload.BinaryImages[idx].Name = "dyld_shared_cache"
total--
case "SharedCacheLibrary":
i.Payload.BinaryImages[idx].Name = "dyld_shared_cache (library)"
total--
}
}
machoFuncMap := make(map[string][]types.Function)
uuidFuncMap := make(map[string][]types.Function)
/* SYMBOLICATE KERNELCACHE */
var kc *macho.File
if ipswPath != "" {
// If crashlog has no device identifier, prompt user to select from available devices
device := i.Payload.Product
log.WithField("device", device).Debug("Looking for kernelcache matching crashlog device")
if device == "" {
ipswInfo, err := info.Parse(ipswPath)
if err != nil {
return fmt.Errorf("crashlog has no device identifier and failed to parse IPSW info: %w", err)
}
var devices []string
for _, dtree := range ipswInfo.DeviceTrees {
if dt, err := dtree.Summary(); err == nil {
devices = append(devices, dt.ProductType)
}
}
if len(devices) == 0 {
return fmt.Errorf("crashlog has no device identifier and no devices found in IPSW")
}
sort.Strings(devices)
if len(devices) == 1 {
device = devices[0]
log.Warnf("crashlog has no device identifier; using only available device: %s", device)
} else {
var choice string
prompt := &survey.Select{
Message: "Crashlog has no device identifier. Select target device:",
Options: devices,
PageSize: 15,
}
if err := survey.AskOne(prompt, &choice); err != nil {
if err == terminal.InterruptErr {
return fmt.Errorf("user cancelled device selection")
}
return fmt.Errorf("failed to select device: %w", err)
}
device = choice
}
}
out, err := extract.Kernelcache(&extract.Config{
IPSW: ipswPath,
KernelDevice: device,
Output: os.TempDir(),
})
if err != nil {
return fmt.Errorf("failed to extract kernelcache: %w", err)
}
if len(out) == 0 {
return fmt.Errorf("no kernelcache found for device %s in IPSW (multi-device IPSW may not contain this device)", device)
}
// Log which kernelcache(s) were found for the device
for k := range out {
log.WithFields(log.Fields{
"device": device,
"kernelcache": filepath.Base(k),
}).Debug("Found kernelcache for device")
}
defer func() {
for k := range out {
os.Remove(k)
}
}()
// Collect all kernel-related UUIDs from crashlog for validation
// kc.UUID() returns the LC_UUID of the kernelcache container
type kernelUUIDInfo struct {
name string
uuid string
}
var crashlogKernelUUIDs []kernelUUIDInfo
if i.Payload.panic210 != nil {
if i.Payload.panic210.DebugHdrKernelCacheUUID != nil {
crashlogKernelUUIDs = append(crashlogKernelUUIDs, kernelUUIDInfo{
name: "Debug Header KernelCache UUID",
uuid: i.Payload.panic210.DebugHdrKernelCacheUUID.Value.(string),
})
}
if i.Payload.panic210.FilesetKernelCacheUUID != nil {
crashlogKernelUUIDs = append(crashlogKernelUUIDs, kernelUUIDInfo{
name: "Fileset KernelCache UUID",
uuid: i.Payload.panic210.FilesetKernelCacheUUID.Value.(string),
})
}
if i.Payload.panic210.KernelCacheUUID != nil {
crashlogKernelUUIDs = append(crashlogKernelUUIDs, kernelUUIDInfo{
name: "KernelCache UUID",
uuid: i.Payload.panic210.KernelCacheUUID.Value.(string),
})
}
if i.Payload.panic210.KernelUUID != nil {
crashlogKernelUUIDs = append(crashlogKernelUUIDs, kernelUUIDInfo{
name: "Kernel UUID",
uuid: i.Payload.panic210.KernelUUID.Value.(string),
})
}
}
var sigs []signature.Symbolicator
if i.Config.SignaturesDir != "" {
sigs, err = signature.Parse(i.Config.SignaturesDir)
if err != nil {
return fmt.Errorf("failed to parse signatures: %v", err)
}
}
for k := range out {
smap := signature.NewSymbolMap()
log.WithField("kernelcache", filepath.Base(k)).Info("Symbolicating...")
if err := smap.Symbolicate(k, sigs, !i.Config.Verbose); err != nil {
return fmt.Errorf("failed to symbolicate kernelcache: %v", err)
}
// Store symbols for IDA script generation.
i.KernelSymbols = smap
kc, err = macho.Open(k)
if err != nil {
return fmt.Errorf("failed to open kernelcache: %v", err)
}
defer kc.Close()
// Validate kernelcache UUID matches any of the crashlog's kernel UUIDs
if len(crashlogKernelUUIDs) > 0 {
if kcUUID := kc.UUID(); kcUUID != nil {
// Log the IPSW kernelcache UUID we extracted
log.WithField("uuid", kcUUID.UUID.String()).Info("IPSW kernelcache UUID")
// Log all crashlog kernel UUIDs for comparison
for _, info := range crashlogKernelUUIDs {
log.WithFields(log.Fields{
"field": info.name,
"uuid": info.uuid,
}).Debug("Crashlog kernel UUID")
}
// Normalize IPSW UUID: uppercase and no dashes for comparison
actualUUID := strings.ToUpper(strings.ReplaceAll(kcUUID.UUID.String(), "-", ""))
// Check against all kernel-related UUIDs from crashlog
matched := false
var matchedField string
for _, info := range crashlogKernelUUIDs {
expectedUUID := strings.ToUpper(strings.ReplaceAll(info.uuid, "-", ""))
if actualUUID == expectedUUID {
matched = true
matchedField = info.name
break
}
}
if matched {
log.WithFields(log.Fields{
"field": matchedField,
"uuid": kcUUID.UUID.String(),
}).Info("Kernelcache UUID validated")
} else {
// Log all the UUIDs we tried to match against
var uuidList []string
for _, info := range crashlogKernelUUIDs {
uuidList = append(uuidList, fmt.Sprintf("%s=%s", info.name, info.uuid))
}
log.WithFields(log.Fields{
"ipsw_uuid": kcUUID.UUID.String(),
"crashlog_uuids": strings.Join(uuidList, ", "),
}).Warn("Kernelcache UUID mismatch: IPSW UUID doesn't match any crashlog kernel UUID")
}
}
} else {
log.Debug("No kernelcache UUID in crashlog, skipping validation")
}
if kc.FileTOC.FileHeader.Type == types.MH_FILESET {
for _, fe := range kc.FileSets() {
mfe, err := kc.GetFileSetFileByName(fe.EntryID)
if err != nil {
return fmt.Errorf("failed to parse entry %s: %v", fe.EntryID, err)
}
for _, fn := range mfe.GetFunctions() {
if syms, err := mfe.FindAddressSymbols(fn.StartAddr); err == nil {
for _, sym := range syms {
fn.Name = sym.Name
}
} else {
if sym, ok := smap[fn.StartAddr]; ok {
fn.Name = sym
}
}
if fn.Name == "" {
fn.Name = fmt.Sprintf("func_%x", fn.StartAddr)
}
machoFuncMap["kernelcache"] = append(machoFuncMap["kernelcache"], fn)
// Also map under "kernel" for Source=Kernel frames
machoFuncMap["kernel"] = append(machoFuncMap["kernel"], fn)
// Also map under "kernelcache (__TEXT_EXEC)" for Source=KernelTextExec frames
machoFuncMap["kernelcache (__TEXT_EXEC)"] = append(machoFuncMap["kernelcache (__TEXT_EXEC)"], fn)
}
}
} else { // non-fileset kernelcache
for _, fn := range kc.GetFunctions() {
if syms, err := kc.FindAddressSymbols(fn.StartAddr); err == nil {
for _, sym := range syms {
fn.Name = sym.Name
}
} else {
if sym, ok := smap[fn.StartAddr]; ok {
fn.Name = sym
}
if fn.Name == "" {
fn.Name = fmt.Sprintf("func_%x", fn.StartAddr)
}
}
machoFuncMap["kernelcache"] = append(machoFuncMap["kernelcache"], fn)
// Also map under "kernel" for Source=Kernel frames
machoFuncMap["kernel"] = append(machoFuncMap["kernel"], fn)
// Also map under "kernelcache (__TEXT_EXEC)" for Source=KernelTextExec frames
machoFuncMap["kernelcache (__TEXT_EXEC)"] = append(machoFuncMap["kernelcache (__TEXT_EXEC)"], fn)
}
}
}
}
/* SYMBOLICATE EXTRA MACHOS */
if i.Config.ExtrasDir != "" {
if err := search.ForEachMacho(i.Config.ExtrasDir, func(path string, m *macho.File) error {
for idx, img := range i.Payload.BinaryImages {
if m.UUID() != nil && strings.EqualFold(img.UUID, m.UUID().UUID.String()) {
i.Payload.BinaryImages[idx].Path = path
i.Payload.BinaryImages[idx].Name = path
// i.Payload.BinaryImages[idx].Name = filepath.Base(path)
i.Payload.BinaryImages[idx].Slide = i.Payload.BinaryImages[idx].Base - m.GetBaseAddress()
for _, fn := range m.GetFunctions() {
if syms, err := m.FindAddressSymbols(fn.StartAddr); err == nil {
for _, sym := range syms {
fn.Name = sym.Name
}
fn.StartAddr += i.Payload.BinaryImages[idx].Slide
fn.EndAddr += i.Payload.BinaryImages[idx].Slide
machoFuncMap[i.Payload.BinaryImages[idx].Name] = append(machoFuncMap[i.Payload.BinaryImages[idx].Name], fn)
uuidFuncMap[strings.ToUpper(img.UUID)] = append(uuidFuncMap[strings.ToUpper(img.UUID)], fn)
} else {
fn.StartAddr += i.Payload.BinaryImages[idx].Slide
fn.EndAddr += i.Payload.BinaryImages[idx].Slide
fn.Name = fmt.Sprintf("func_%x", fn.StartAddr)
machoFuncMap[i.Payload.BinaryImages[idx].Name] = append(machoFuncMap[i.Payload.BinaryImages[idx].Name], fn)
uuidFuncMap[strings.ToUpper(img.UUID)] = append(uuidFuncMap[strings.ToUpper(img.UUID)], fn)
}
}
total--
}
}
return nil
}); err != nil {
if !errors.Is(err, ErrDone) {
return fmt.Errorf("failed to symbolicate: %w", err)
}
}
}
/* SYMBOLICATE FILESYSTEM MACHOS */
if ipswPath != "" {
if err := search.ForEachMachoInIPSW(ipswPath, i.Config.PemDB, func(path string, m *macho.File) error {
if total == 0 {
return ErrDone // break
}
for idx, img := range i.Payload.BinaryImages {
if m.UUID() != nil && strings.EqualFold(img.UUID, m.UUID().UUID.String()) {
i.Payload.BinaryImages[idx].Path = path
i.Payload.BinaryImages[idx].Name = path
// i.Payload.BinaryImages[idx].Name = filepath.Base(path)
i.Payload.BinaryImages[idx].Slide = i.Payload.BinaryImages[idx].Base - m.GetBaseAddress()
// Read peek bytes for panicked thread user frames from this binary (--peek flag)
if i.Config.Peek && i.Payload.panic210 != nil {
panicPID := i.Payload.panic210.PanickedTask.PID
panicTID := i.Payload.panic210.PanickedThread.TID
if proc, ok := i.Payload.ProcessByPid[panicPID]; ok {
if thread, ok := proc.ThreadByID[panicTID]; ok {
for frameIdx := range thread.UserFrames {
frame := &i.Payload.ProcessByPid[panicPID].ThreadByID[panicTID].UserFrames[frameIdx]
if frame.ImageIndex == uint64(idx) {
// Compute the VM address relative to MachO base (not runtime base)
// frame.ImageOffset is raw offset within image
// m.GetBaseAddress() is MachO's preferred load address
vmAddr := m.GetBaseAddress() + frame.ImageOffset
// Get function boundaries for boundary checking
var funcStart, funcEnd uint64
if fn, err := m.GetFunctionForVMAddr(vmAddr); err == nil {
funcStart = fn.StartAddr
funcEnd = fn.EndAddr
}
if peekBytes, startAddr, frameIdx := readPeekBytes(m, vmAddr, i.Config.PeekCount, funcStart, funcEnd); peekBytes != nil {
frame.PeekBytes = peekBytes
// Store the runtime (slid) address for display
slide := i.Payload.BinaryImages[idx].Slide
slidAddr := startAddr + slide
frame.PeekAddr = slidAddr
frame.PeekFrameIdx = frameIdx
// Extract symbols for branch targets in peek bytes
// Use slid address so keys match what formatPeekDisassembly will look up
frame.PeekSymbols = extractPeekSymbols(peekBytes, slidAddr, slide, m)
}
}
}
}
}
}
for _, fn := range m.GetFunctions() {
if syms, err := m.FindAddressSymbols(fn.StartAddr); err == nil {
for _, sym := range syms {
fn.Name = sym.Name
}
fn.StartAddr += i.Payload.BinaryImages[idx].Slide
fn.EndAddr += i.Payload.BinaryImages[idx].Slide
machoFuncMap[i.Payload.BinaryImages[idx].Name] = append(machoFuncMap[i.Payload.BinaryImages[idx].Name], fn)
uuidFuncMap[strings.ToUpper(img.UUID)] = append(uuidFuncMap[strings.ToUpper(img.UUID)], fn)
} else {
fn.StartAddr += i.Payload.BinaryImages[idx].Slide
fn.EndAddr += i.Payload.BinaryImages[idx].Slide
fn.Name = fmt.Sprintf("func_%x", fn.StartAddr)
machoFuncMap[i.Payload.BinaryImages[idx].Name] = append(machoFuncMap[i.Payload.BinaryImages[idx].Name], fn)
uuidFuncMap[strings.ToUpper(img.UUID)] = append(uuidFuncMap[strings.ToUpper(img.UUID)], fn)
}
}
total--
}
}
return nil
}); err != nil {
if !errors.Is(err, ErrDone) {
return fmt.Errorf("failed to symbolicate: %w", err)
}
}
}
// print any binary images that are missing names
// for idx, img := range i.Payload.BinaryImages {
// if len(img.Name) == 0 {
// fmt.Printf("idx: %d - %v\n", idx, img)
// }
// }
fs, closeDSCs, err := i.openDSCs(ipswPath, dscPaths)
if err != nil {
return err
}
defer closeDSCs()
// When no dyld_shared_cache is available, fall back to the loose extracted
// dylibs from an Xcode DeviceSupport dump for userspace symbolication. Only
// build the (expensive) index if the crash actually has shared-cache frames.
var lc *looseCache
if len(fs) == 0 && looseDir != "" && i.hasSharedCacheImage() {
if lc, err = newLooseCache(looseDir); err != nil {
log.WithError(err).Warn("failed to index DeviceSupport dylibs")
lc = nil
} else {
defer lc.Close()
}
}
for pid, proc := range i.Payload.ProcessByPid {
for tid, thread := range proc.ThreadByID {
for idx, frame := range thread.UserFrames {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset += i.Payload.BinaryImages[frame.ImageIndex].Base
if i.Payload.BinaryImages[frame.ImageIndex].Slide != 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Slide = i.Payload.BinaryImages[frame.ImageIndex].Slide
}
if len(i.Payload.BinaryImages[frame.ImageIndex].Name) > 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName = i.Payload.BinaryImages[frame.ImageIndex].Name
} else {
if strings.HasPrefix(i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName, "image_") {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName += fmt.Sprintf(" (probably %s)", proc.Name)
}
}
if i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName == "absolute" {
continue // skip absolute
} else if i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName == "dyld_shared_cache" {
for _, f := range fs {
if !strings.EqualFold(i.Payload.BinaryImages[frame.ImageIndex].UUID, f.UUID.String()) {
continue // skip to next DSC
}
if f.Headers[f.UUID].SharedRegionStart != i.Payload.BinaryImages[frame.ImageIndex].Base {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Slide = i.Payload.BinaryImages[frame.ImageIndex].Base - f.Headers[f.UUID].SharedRegionStart
}
// lookup symbol in DSC dylib
if img, err := f.GetImageContainingVMAddr(i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset); err == nil {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName = filepath.Base(img.Name)
img.ParsePublicSymbols(false)
img.ParseLocalSymbols(false)
m, err := img.GetMacho()
if err != nil {
return fmt.Errorf("failed to get macho from image: %w", err)
}
if fn, err := m.GetFunctionForVMAddr(i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset); err == nil {
if sym, ok := f.AddressToSymbol.Get(fn.StartAddr); ok {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Symbol = demangleSym(i.Config.Demangle, sym)
if i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset-fn.StartAddr != 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].SymbolLocation = i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset - fn.StartAddr
}
} else {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Symbol = fmt.Sprintf("func_%x", fn.StartAddr)
}
}
}
}
if lc != nil {
uf := &i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx]
if name, sym, symStart, ok := lc.symbolicate(uf.ImageOffset); ok {
if name != "" {
uf.ImageName = name
}
if sym != "" {
uf.Symbol = demangleSym(i.Config.Demangle, sym)
if uf.ImageOffset-symStart != 0 {
uf.SymbolLocation = uf.ImageOffset - symStart
}
}
}
}
} else { // "Process"
// lookup symbol in MachO symbol map (by name, then fall back to UUID)
funcs, ok := machoFuncMap[i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName]
if !ok {
uuid := strings.ToUpper(i.Payload.BinaryImages[frame.ImageIndex].UUID)
funcs, ok = uuidFuncMap[uuid]
}
if ok {
found := false
for _, fn := range funcs {
if fn.StartAddr <= i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset && i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset < fn.EndAddr {
found = true
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Symbol = demangleSym(i.Config.Demangle, fn.Name)
if i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset-fn.StartAddr != 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].SymbolLocation = i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset - fn.StartAddr
}
break
}
}
if !found {
// sometimes a frame will report using `dyld` but the offset is not in any function or in the binary image (e.g. in memory macho)
if i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName == "/usr/lib/dyld" {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName += " (??? in memory macho)"
}
log.WithFields(log.Fields{
"proc": fmt.Sprintf("%s [%d]", proc.Name, pid),
"thread": tid,
"frame": idx,
"img": i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName,
}).Debugf("failed to find function for process offset %#x", i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset)
}
} else {
log.WithFields(log.Fields{
"proc": fmt.Sprintf("%s [%d]", proc.Name, pid),
"thread": tid,
"frame": idx,
"img": i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName,
}).Debugf("failed to find function for process offset %#x", i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset)
}
}
}
/* KernelFrames */
if kc != nil {
for idx, frame := range thread.KernelFrames {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset += i.Payload.BinaryImages[frame.ImageIndex].Base
if i.Payload.BinaryImages[frame.ImageIndex].Slide != 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].Slide = i.Payload.BinaryImages[frame.ImageIndex].Slide
}
if len(i.Payload.BinaryImages[frame.ImageIndex].Name) > 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName = i.Payload.BinaryImages[frame.ImageIndex].Name
} else {
if strings.HasPrefix(i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName, "image_") {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName += " (maybe a kext)"
}
}
if i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName == "absolute" {
continue // skip absolute
} else if funcs, ok := machoFuncMap[i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName]; ok {
found := false
for _, fn := range funcs {
if fn.StartAddr <= i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset &&
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset < fn.EndAddr {
found = true
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].Symbol = demangleSym(i.Config.Demangle, fn.Name)
if i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset-fn.StartAddr != 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].SymbolLocation = i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset - fn.StartAddr
}
break
}
}
if !found {
// if i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName == "??" {
// i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName += " (??? maybe kext?)"
// }
if seg := kc.FindSegmentForVMAddr(i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset); seg != nil {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].Symbol = seg.Name
if sec := kc.FindSectionForVMAddr(i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset); sec != nil {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].Symbol += "." + sec.Name
}
} else {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].Symbol = "???"
log.WithFields(log.Fields{
"proc": fmt.Sprintf("%s [%d]", proc.Name, pid),
"thread": tid,
"frame": idx,
"img": i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName,
}).Debugf("failed to find function for process offset %#x", i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset)
}
}
} else {
log.WithFields(log.Fields{
"proc": fmt.Sprintf("%s [%d]", proc.Name, pid),
"thread": tid,
"frame": idx,
"img": i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName,
"offset": fmt.Sprintf("%#x", i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset),
}).Errorf("unexpected image")
}
}
}
// Read peek bytes for panicked thread frames (--peek flag)
if i.Config.Peek && i.Payload.panic210 != nil &&
pid == i.Payload.panic210.PanickedTask.PID &&
tid == i.Payload.panic210.PanickedThread.TID {
// cache already-opened machos so we don't reopen for each frame
machoCache := make(map[int]*macho.File)
defer func() {
for _, m := range machoCache {
m.Close()
}
}()
// Read peek bytes for kernel frames from kernelcache
if kc != nil {
for idx := range i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames {
frame := &i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx]
if frame.ImageName == "absolute" {
continue
}
if frame.ImageOffset > 0 {
// Get function boundaries for boundary checking
var funcStart, funcEnd uint64
if fn, err := kc.GetFunctionForVMAddr(frame.ImageOffset); err == nil {
funcStart = fn.StartAddr
funcEnd = fn.EndAddr
}
// Use the runtime (slid) address for reading; kc segments are already in slid VM space.
if peekBytes, startAddr, frameIdx := readPeekBytes(kc, frame.ImageOffset, i.Config.PeekCount, funcStart, funcEnd); peekBytes != nil {
frame.PeekBytes = peekBytes
frame.PeekAddr = startAddr
frame.PeekFrameIdx = frameIdx
// Extract symbols for branch targets in peek bytes (kernel has no slide)
frame.PeekSymbols = extractPeekSymbols(peekBytes, startAddr, 0, kc)
}
}
}
}
// Read peek bytes for user frames from DSC (process binaries already handled during ForEachMachoInIPSW)
for idx := range i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames {
frame := &i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx]
if frame.ImageName == "absolute" || len(frame.PeekBytes) > 0 {
continue // skip absolute or frames already populated from filesystem
}
if frame.ImageOffset > 0 {
frameSlide := frame.Slide
if frameSlide == 0 && int(frame.ImageIndex) < len(i.Payload.BinaryImages) {
frameSlide = i.Payload.BinaryImages[frame.ImageIndex].Slide
}
unslidAddr := frame.ImageOffset
if frameSlide != 0 && unslidAddr >= frameSlide {
unslidAddr -= frameSlide
}
// 1) Try using a located MachO on disk (extras/IPSW/symbol server)
if bi := i.Payload.BinaryImages[frame.ImageIndex]; len(bi.Path) > 0 {
if m, ok := machoCache[int(frame.ImageIndex)]; ok {
var funcStart, funcEnd uint64
if fn, err := m.GetFunctionForVMAddr(unslidAddr); err == nil {
funcStart = fn.StartAddr
funcEnd = fn.EndAddr
}
if peekBytes, startAddr, peekFrameIdx := readPeekBytes(m, unslidAddr, i.Config.PeekCount, funcStart, funcEnd); peekBytes != nil {
frame.PeekBytes = peekBytes
slidAddr := startAddr + frameSlide
frame.PeekAddr = slidAddr
frame.PeekFrameIdx = peekFrameIdx
// Extract symbols for branch targets in peek bytes
frame.PeekSymbols = extractPeekSymbols(peekBytes, slidAddr, frameSlide, m)
continue
}
} else if m, err := macho.Open(bi.Path); err == nil {
machoCache[int(frame.ImageIndex)] = m
var funcStart, funcEnd uint64
if fn, err := m.GetFunctionForVMAddr(unslidAddr); err == nil {
funcStart = fn.StartAddr
funcEnd = fn.EndAddr
}
if peekBytes, startAddr, peekFrameIdx := readPeekBytes(m, unslidAddr, i.Config.PeekCount, funcStart, funcEnd); peekBytes != nil {
frame.PeekBytes = peekBytes
slidAddr := startAddr + frameSlide
frame.PeekAddr = slidAddr
frame.PeekFrameIdx = peekFrameIdx
// Extract symbols for branch targets in peek bytes
frame.PeekSymbols = extractPeekSymbols(peekBytes, slidAddr, frameSlide, m)
continue
}
} else {
log.WithError(err).Debugf("failed to open macho for peek: %s", bi.Path)
}
}
// 2) Try reading from DSC as a fallback
for _, f := range fs {
if img, err := f.GetImageContainingVMAddr(frame.ImageOffset); err == nil {
if m, err := img.GetMacho(); err == nil {
unslidAddr := frame.ImageOffset
if frameSlide != 0 && unslidAddr >= frameSlide {
unslidAddr -= frameSlide
}
var funcStart, funcEnd uint64
if fn, err := m.GetFunctionForVMAddr(unslidAddr); err == nil {
funcStart = fn.StartAddr
funcEnd = fn.EndAddr
}
if peekBytes, startAddr, peekFrameIdx := readPeekBytes(m, unslidAddr, i.Config.PeekCount, funcStart, funcEnd); peekBytes != nil {
frame.PeekBytes = peekBytes
slidAddr := startAddr + frameSlide
frame.PeekAddr = slidAddr
frame.PeekFrameIdx = peekFrameIdx
// Extract symbols for branch targets in peek bytes
frame.PeekSymbols = extractPeekSymbols(peekBytes, slidAddr, frameSlide, m)
}
}
break
}
}
}
}
}
}
}
// Generate IDA Python scripts if requested
if i.Config.IDAScript {
if err := i.generateIDAScripts(); err != nil {
return fmt.Errorf("failed to generate IDA scripts: %w", err)
}
}
return nil
}
func (i *Ips) Symbolicate210WithDatabase(dbURL string) (err error) {
db := server.NewServer(dbURL)
if err := db.Ping(); err != nil {
return fmt.Errorf("failed symbolicate panic 210: %w", err)
}
if ok, err := db.HasIPSW(i.Header.Version(), i.Header.Build(), i.Payload.Product); err != nil {
return fmt.Errorf("failed symbolicate panic 210: %w", err)
} else {
if !ok {
need := fmt.Sprintf("%s (%s) for %s", i.Header.Version(), i.Header.Build(), i.Payload.Product)
return fmt.Errorf("failed symbolicate panic 210: required IPSW not found in symbol server database; need %s", need)
}
}
i.Payload.panic210, err = parsePanicString210(i.Payload.PanicString)
if err != nil {
return fmt.Errorf("failed to parse panic string: %w", err)
}
total := len(i.Payload.BinaryImages)
// add default binary image names
for idx, img := range i.Payload.BinaryImages {
switch img.Source {
case "Absolute":
i.Payload.BinaryImages[idx].Name = "absolute"
total--
case "Kernel":
i.Payload.BinaryImages[idx].Name = "kernel"
if i.Payload.panic210 != nil && i.Payload.panic210.KernelSlide != nil {
i.Payload.BinaryImages[idx].Slide = i.Payload.panic210.KernelSlide.Value.(uint64)
}
total--
case "KernelCache":
i.Payload.BinaryImages[idx].Name = "kernelcache"
if i.Payload.panic210 != nil {
// if i.Payload.panic210.KernelTextBase != nil {
// i.Payload.BinaryImages[idx].Base = i.Payload.panic210.KernelTextBase.Value.(uint64)
// }
if i.Payload.panic210.KernelCacheSlide != nil {
i.Payload.BinaryImages[idx].Slide = i.Payload.panic210.KernelCacheSlide.Value.(uint64)
} else if i.Payload.panic210.KernelSlide != nil {
i.Payload.BinaryImages[idx].Slide = i.Payload.panic210.KernelSlide.Value.(uint64)
}
if i.Payload.panic210.KernelUUID != nil {
i.Payload.BinaryImages[idx].UUID = i.Payload.panic210.KernelUUID.Value.(string)
}
}
total--
case "KernelTextExec":
i.Payload.BinaryImages[idx].Name = "kernelcache (__TEXT_EXEC)"
if i.Payload.panic210 != nil && i.Payload.panic210.KernelTextExecSlide != nil {
i.Payload.BinaryImages[idx].Slide = i.Payload.panic210.KernelTextExecSlide.Value.(uint64)
}
total--
case "SharedCache":
i.Payload.BinaryImages[idx].Name = "dyld_shared_cache"
total--
case "SharedCacheLibrary":
i.Payload.BinaryImages[idx].Name = "dyld_shared_cache (library)"
total--
}
}
for idx, img := range i.Payload.BinaryImages {
if m, err := db.GetMachO(strings.ToUpper(img.UUID)); err == nil {
if i.Payload.BinaryImages[idx].Name != "kernelcache" {
i.Payload.BinaryImages[idx].Path = m.GetPath()
i.Payload.BinaryImages[idx].Name = m.GetPath()
i.Payload.BinaryImages[idx].Slide = i.Payload.BinaryImages[idx].Base - m.TextStart
}
total--
} else {
log.WithFields(log.Fields{
"uuid": img.UUID,
"name": img.Name,
}).Debug("failed to find macho for uuid")
}
}
if total > 0 {
log.WithFields(log.Fields{
"total": total,
}).Debug("missing binary images")
}
for pid, proc := range i.Payload.ProcessByPid {
for tid, thread := range proc.ThreadByID {
for idx, frame := range thread.UserFrames {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset += i.Payload.BinaryImages[frame.ImageIndex].Base
if i.Payload.BinaryImages[frame.ImageIndex].Slide != 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Slide = i.Payload.BinaryImages[frame.ImageIndex].Slide
}
if len(i.Payload.BinaryImages[frame.ImageIndex].Name) > 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName = i.Payload.BinaryImages[frame.ImageIndex].Name
} else {
if strings.HasPrefix(i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName, "image_") {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName += fmt.Sprintf(" (probably %s)", proc.Name)
}
}
if i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName == "absolute" {
continue // skip absolute
} else if i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName == "dyld_shared_cache" {
dsc, err := db.GetDSC(strings.ToUpper(i.Payload.BinaryImages[frame.ImageIndex].UUID))
if err != nil {
return fmt.Errorf("failed to get DSC for uuid: %w", err)
}
if dsc.SharedRegionStart != i.Payload.BinaryImages[frame.ImageIndex].Base {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Slide = i.Payload.BinaryImages[frame.ImageIndex].Base - dsc.SharedRegionStart
}
// lookup symbol in DSC dylib
if img, err := db.GetDSCImage(dsc.UUID, i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset); err == nil {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName = filepath.Base(img.GetPath())
if sym, err := db.GetSymbol(img.UUID, i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset); err == nil {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Symbol = demangleSym(i.Config.Demangle, sym.GetName())
if i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset-sym.Start != 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].SymbolLocation = i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset - sym.Start
}
} else {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Symbol = fmt.Sprintf("func_%x", i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset)
}
} else {
log.WithFields(log.Fields{
"proc": fmt.Sprintf("%s [%d]", proc.Name, pid),
"thread": tid,
"frame": idx,
"img": i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName,
}).Debugf("failed to find DSC image containing offset %#x", i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset)
}
} else { // "Process"
if sym, err := db.GetSymbol(
strings.ToUpper(i.Payload.BinaryImages[frame.ImageIndex].UUID),
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset-
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Slide,
); err == nil {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Symbol = demangleSym(i.Config.Demangle, sym.GetName())
if i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset-sym.Start != 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].SymbolLocation =
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset -
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Slide -
sym.Start
}
} else {
// sometimes a frame will report using `dyld` but the offset is not in any function or in the binary image (e.g. in memory macho)
if i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName == "/usr/lib/dyld" {
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName += " (??? in memory macho)"
}
log.WithFields(log.Fields{
"proc": fmt.Sprintf("%s [%d]", proc.Name, pid),
"thread": tid,
"frame": idx,
"img": i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageName,
}).Debugf("failed to find symbol for process offset %#x",
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].ImageOffset-
i.Payload.ProcessByPid[pid].ThreadByID[tid].UserFrames[idx].Slide)
}
}
}
for idx, frame := range thread.KernelFrames {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset += i.Payload.BinaryImages[frame.ImageIndex].Base
if i.Payload.BinaryImages[frame.ImageIndex].Slide != 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].Slide = i.Payload.BinaryImages[frame.ImageIndex].Slide
}
if len(i.Payload.BinaryImages[frame.ImageIndex].Name) > 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName = i.Payload.BinaryImages[frame.ImageIndex].Name
} else {
if strings.HasPrefix(i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName, "image_") {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName += " (maybe a kext)"
}
}
if i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName == "absolute" {
continue // skip absolute
} else if i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName == "kernelcache" {
if sym, err := db.GetSymbol(
strings.ToUpper(i.Payload.BinaryImages[frame.ImageIndex].UUID),
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset & ^uint64(1<<63),
); err == nil {
sym.Start |= uint64(1 << 63)
sym.End |= uint64(1 << 63)
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].Symbol = demangleSym(i.Config.Demangle, sym.GetName())
if i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset-sym.Start != 0 {
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].SymbolLocation =
i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset - sym.Start
}
} else {
log.WithFields(log.Fields{
"proc": fmt.Sprintf("%s [%d]", proc.Name, pid),
"thread": tid,
"frame": idx,
"img": i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName,
}).Debugf("failed to find symbol for kernel frame image offset %#x", i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset)
}
} else {
log.WithFields(log.Fields{
"proc": fmt.Sprintf("%s [%d]", proc.Name, pid),
"thread": tid,
"frame": idx,
"img": i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageName,
"offset": fmt.Sprintf("%#x", i.Payload.ProcessByPid[pid].ThreadByID[tid].KernelFrames[idx].ImageOffset),
}).Errorf("unexpected image")
}
}
}
}
// Generate IDA scripts if requested
if i.Config.IDAScript {
if err := i.generateIDAScripts(); err != nil {
return fmt.Errorf("failed to generate IDA scripts: %w", err)
}
}
return nil
}
func fmtState(states []string) string {
var out []string
for _, s := range states {
switch s {
case "TH_WAIT", "TH_UNINT":
out = append(out, colorAddr(s))
case "TH_RUN", "TH_IDLE":
out = append(out, colorBold(s))
default:
out = append(out, s)
}
}
return strings.Join(out, ", ")
}
func colorVMSummary(in string) string {
separatorMatch := regexp.MustCompile(`=+`)
in = separatorMatch.ReplaceAllStringFunc(in, func(s string) string {
return colorField(s)
})
return in
}
func (i *Ips) String() string {
var out string
switch i.Header.BugType {
case "Panic", "210", "288":
out = fmt.Sprintf("[%s] - %s - %s %s\n\n", colorTime(i.Header.Timestamp.Format("02Jan2006 15:04:05")), colorError(i.Header.BugTypeDesc), i.Payload.Product, i.Payload.Build)
if i.Payload.panic210 == nil {
var err error
i.Payload.panic210, err = parsePanicString210(i.Payload.PanicString)
if err != nil {
log.Errorf("failed to parse panic string: %w", err)
}
}
if i.Config.Verbose {
if len(i.Payload.PanicString) > 0 {
out += fmt.Sprintf("%s: %s\n", colorField("Panic String"), i.Payload.PanicString)
}
} else {
out += i.Payload.panic210.String()
}
if len(i.Payload.PanicFlags) > 0 {
out += fmt.Sprintf("%s: %s\n", colorField("Panic Flags"), i.Payload.PanicFlags)
}
out += "\n" + i.Payload.MemoryStatus.String()
out += i.Payload.OtherString + "\n"
var pids []int
for pid := range i.Payload.ProcessByPid {
pids = append(pids, pid)
}
sort.Ints(pids)
for _, pid := range pids {
p := i.Payload.ProcessByPid[pid]
paniced := ""
if p.ID == i.Payload.panic210.PanickedTask.PID {
paniced = colorError(" (Panicked)")
} else {
/* filter procs */
if !i.Config.All {
if i.Config.Running {
notRunning := true
// check if any thread is running
for _, t := range p.ThreadByID {
if slices.Contains(t.State, "TH_RUN") {
notRunning = false
break
}
}
if notRunning {
continue
}
} else if len(i.Config.Process) > 0 {
if p.Name != i.Config.Process {
continue
}
} else {
continue
}
}
}
out += fmt.Sprintf(colorField("Process")+": %s [%s]%s\n", colorImage(p.Name), colorBold("%d", p.ID), paniced)
for _, t := range p.ThreadByID {
paniced = ""
if t.ID == i.Payload.panic210.PanickedThread.TID {
paniced = colorError(" (Panicked)")
} else {
/* filter threads */
if !i.Config.All {
if i.Config.Running {
if !slices.Contains(t.State, "TH_RUN") {
continue
}
} else if len(i.Config.Process) > 0 {
if p.Name != i.Config.Process {
continue
}
} else {
continue
}
}
}
out += fmt.Sprintf(colorField(" Thread")+": %s%s\n", colorBold("%d", t.ID), paniced)
if len(t.Name) > 0 {
out += fmt.Sprintf(" Name: %s\n", colorTime(t.Name))
}
if len(t.DispatchQueueLabel) > 0 {
out += fmt.Sprintf(" Queue: %s\n", t.DispatchQueueLabel)
}
out += fmt.Sprintf(" State: %s\n", fmtState(t.State))
out += fmt.Sprintf(" Base Priority: %d\n", t.BasePriority)
out += fmt.Sprintf(" Sched Priority: %d\n", t.SchedPriority)
out += fmt.Sprintf(" User Time: %d usec\n", t.UserUsec)
out += fmt.Sprintf(" System Time: %d usec\n", t.SystemUsec)
if len(t.UserFrames) > 0 {
out += " User Frames:\n"
isPanickedThread := i.Payload.panic210 != nil &&
p.ID == i.Payload.panic210.PanickedTask.PID &&
t.ID == i.Payload.panic210.PanickedThread.TID
buf := bytes.NewBufferString("")
w := tabwriter.NewWriter(buf, 0, 0, 1, ' ', 0)
for idx, f := range t.UserFrames {
if f.ImageName == "absolute" {
continue
}
addr, slideVal, _ := i.panicFrameAddr(f)
slideInfo := ""
if slideVal > 0 {
slideInfo = fmt.Sprintf(" (slide=%#x)", slideVal)
}
symloc := ""
if f.SymbolLocation > 0 {
symloc = fmt.Sprintf(" + %d", f.SymbolLocation)
if i.Config.Hex {
symloc = fmt.Sprintf(" + %#x", f.SymbolLocation)
}
}
fmt.Fprintf(w, " %02d: %s\t%s%s %s%s\n", idx, colorImage(f.ImageName), colorAddr("%#x", addr), slideInfo, colorField(f.Symbol), symloc)
// Add peek disassembly for panicked thread frames
if i.Config.Peek && isPanickedThread && len(f.PeekBytes) > 0 {
w.Flush()
out += buf.String()
buf.Reset()
isDSC := i.isDSCFrame(f)
out += formatPeekDisassembly(f.PeekBytes, f.PeekAddr, f.PeekFrameIdx, slideVal, i.Config.KernelSlide, i.Config.DSCSlide, isDSC, i.Config.Unslid, f.PeekSymbols, i.Config.Demangle)
}
}
w.Flush()
out += buf.String()
}
if len(t.KernelFrames) > 0 {
out += " Kernel Frames:\n"
isPanickedThread := i.Payload.panic210 != nil &&
p.ID == i.Payload.panic210.PanickedTask.PID &&
t.ID == i.Payload.panic210.PanickedThread.TID
buf := bytes.NewBufferString("")
w := tabwriter.NewWriter(buf, 0, 0, 1, ' ', 0)
for idx, f := range t.KernelFrames {
if f.ImageName == "absolute" {
continue
}
addr, slideVal, _ := i.panicFrameAddr(f)
slideInfo := ""
if slideVal > 0 {
slideInfo = fmt.Sprintf(" (slide=%#x)", slideVal)
}
symloc := ""
if f.SymbolLocation > 0 {
symloc = fmt.Sprintf(" + %d", f.SymbolLocation)
if i.Config.Hex {
symloc = fmt.Sprintf(" + %#x", f.SymbolLocation)
}
}
fmt.Fprintf(w, " %02d: %s\t%s%s %s%s\n", idx, colorImage(f.ImageName), colorAddr("%#x", addr), slideInfo, colorField(f.Symbol), symloc)
// Add peek disassembly for panicked thread frames
if i.Config.Peek && isPanickedThread && len(f.PeekBytes) > 0 {
w.Flush()
out += buf.String()
buf.Reset()
// Kernel frames are never DSC frames, so pass false for isDSCFrame
out += formatPeekDisassembly(f.PeekBytes, f.PeekAddr, f.PeekFrameIdx, slideVal, i.Config.KernelSlide, i.Config.DSCSlide, false, i.Config.Unslid, f.PeekSymbols, i.Config.Demangle)
}
}
w.Flush()
out += buf.String()
}
}
out += "\n"
}
if len(i.Payload.Notes) > 0 {
out += colorField("NOTES") + ":\n"
for _, n := range i.Payload.Notes {
out += fmt.Sprintf(" - %s\n", n)
}
}
case "Crash", "309":
out = fmt.Sprintf("[%s] - %s\n\n", colorTime(i.Header.Timestamp.Format("02Jan2006 15:04:05")), colorError(i.Header.BugTypeDesc))
out += fmt.Sprintf(
colorField("Process")+": %s [%d]\n"+
colorField("Path")+": %s\n"+
colorField("Parent")+": %s [%d]\n"+
colorField("Hardware Model")+": %s\n"+
colorField("OS Version")+": %s\n"+
colorField("BuildID")+": %s\n"+
colorField("LockdownMode")+": %d\n"+
colorField("Was Unlocked Since Boot")+": %d\n"+
colorField("Is Locked")+": %d\n",
i.Payload.ProcName, i.Payload.PID,
i.Payload.ProcPath,
i.Payload.ParentProc, i.Payload.ParentPid,
i.Payload.ModelCode,
i.Payload.OsVersion.Train,
i.Payload.OsVersion.Build,
i.Payload.LockdownMode,
i.Payload.WasUnlockedSinceBoot,
i.Payload.IsLocked,
)
if i.Payload.SharedCache.Size > 0 {
out += fmt.Sprintf(colorField("Shared Cache")+": %s %s: %#x %s: %d\n", i.Payload.SharedCache.UUID, colorField("base"), i.Payload.SharedCache.Base, colorField("size"), i.Payload.SharedCache.Size)
}
var exception Exception
if err := mapstructure.Decode(i.Payload.Exception, &exception); err == nil {
out += fmt.Sprintf("\n%s: %s (%s) %s\n", colorField("Exception Type"), colorBold(exception.Type), exception.Signal, exception.Message)
if len(exception.Subtype) > 0 {
out += fmt.Sprintf(colorField("Exception Subtype")+": %s\n", exception.Subtype)
}
} else {
out += fmt.Sprintf(colorField("Exception")+": %s\n", i.Payload.Exception)
}
if len(i.Payload.VmRegionInfo) > 0 {
out += fmt.Sprintf(colorField("VM Region Info")+": %s\n", i.Payload.VmRegionInfo)
}
if i.Payload.Asi != nil {
out += colorField("ASI") + ":\n"
for k, v := range i.Payload.Asi {
out += fmt.Sprintf(" %s:\n", k)
for _, s := range v {
out += fmt.Sprintf(" - %s\n", s)
}
}
out += "\n"
}
for _, t := range i.Payload.Threads {
out += fmt.Sprintf(colorField("Thread")+" %s:", colorBold("%d", t.ID))
if len(t.Name) > 0 {
out += colorField(" name") + ": " + t.Name
if len(t.Queue) > 0 {
out += ","
}
}
if len(t.Queue) > 0 {
out += colorField(" queue") + ": " + t.Queue
}
if t.Triggered {
out += colorError(" (Crashed)\n")
} else {
out += "\n"
}
if len(t.Frames) > 0 {
buf := bytes.NewBufferString("")
w := tabwriter.NewWriter(buf, 0, 0, 1, ' ', 0)
for idx, f := range t.Frames {
addr := i.Payload.UsedImages[f.ImageIndex].Base + f.ImageOffset
slideInfo := ""
if i.Config.Unslid && f.Slide != 0 && addr >= f.Slide {
addr -= f.Slide
slideInfo = fmt.Sprintf(" (unslid %#x)", f.Slide)
}
symloc := ""
if f.SymbolLocation > 0 {
symloc = fmt.Sprintf(" + %d", f.SymbolLocation)
if i.Config.Hex {
symloc = fmt.Sprintf(" + %#x", f.SymbolLocation)
}
}
fmt.Fprintf(w, " %02d: %s\t%s%s %s%s\n", idx, colorImage(i.Payload.UsedImages[f.ImageIndex].Name), colorAddr("%#x", addr), slideInfo, colorField(f.Symbol), symloc)
}
w.Flush()
out += buf.String()
}
if t.Triggered || i.Config.All {
out += colorField("Thread") + colorBold(" %d ", t.ID) + colorField(t.ThreadState.Flavor) + "\n" + t.ThreadState.String()
if t.Triggered {
if len(i.Payload.InstructionByteStream.BeforePC) > 0 {
out += colorField("Instructions") + "\n"
if b64data, err := base64.StdEncoding.WithPadding(base64.StdPadding).DecodeString(i.Payload.InstructionByteStream.BeforePC); err != nil {
log.WithError(err).Errorf("failed to decode BeforePC instruction byte stream")
} else {
if instructions, err := disassemble.GetInstructions(t.ThreadState.PC.Value-(10*4), b64data); err != nil {
log.WithError(err).Errorf("failed to disassemble BeforePC instructions")
} else {
pad := " "
for _, block := range instructions.Blocks() {
for _, i := range block {
opStr := strings.TrimSpace(strings.TrimPrefix(i.String(), i.Operation.String()))
out += fmt.Sprintf("%s%s: %s %s %s\n",
pad,
colorAddr("%#08x", i.Address),
disassemble.GetOpCodeByteString(i.Raw),
colorImage("%-7s", i.Operation),
disass.ColorOperands(opStr),
)
}
out += "\n"
}
}
}
}
if len(i.Payload.InstructionByteStream.AtPC) > 0 {
out = strings.TrimSuffix(out, "\n")
if b64data, err := base64.StdEncoding.WithPadding(base64.StdPadding).DecodeString(i.Payload.InstructionByteStream.AtPC); err != nil {
log.WithError(err).Errorf("failed to decode AtPC instruction byte stream")
} else {
if instructions, err := disassemble.GetInstructions(t.ThreadState.PC.Value, b64data); err != nil {
log.WithError(err).Errorf("failed to disassemble AtPC instructions")
} else {
for idx, block := range instructions.Blocks() {
for jdx, i := range block {
pad := " "
if idx == 0 && jdx == 0 {
pad = colorError("PC=>")
}
opStr := strings.TrimSpace(strings.TrimPrefix(i.String(), i.Operation.String()))
out += fmt.Sprintf("%s%s: %s %s %s\n",
pad,
colorAddr("%#08x", i.Address),
disassemble.GetOpCodeByteString(i.Raw),
colorImage("%-7s", i.Operation),
disass.ColorOperands(opStr),
)
}
out += "\n"
}
}
}
}
}
}
out += "\n"
}
if len(i.Payload.LastExceptionBacktrace) > 0 {
out += colorField("Last Exception Backtrace") + ":\n"
buf := bytes.NewBufferString("")
w := tabwriter.NewWriter(buf, 0, 0, 1, ' ', 0)
for idx, f := range i.Payload.LastExceptionBacktrace {
addr := i.Payload.UsedImages[f.ImageIndex].Base + f.ImageOffset
slideInfo := ""
if i.Config.Unslid && f.Slide != 0 && addr >= f.Slide {
addr -= f.Slide
slideInfo = fmt.Sprintf(" (unslid %#x)", f.Slide)
}
symloc := ""
if f.SymbolLocation > 0 {
symloc = fmt.Sprintf(" + %d", f.SymbolLocation)
if i.Config.Hex {
symloc = fmt.Sprintf(" + %#x", f.SymbolLocation)
}
}
fmt.Fprintf(w, " %02d: %s\t%s%s %s%s\n", idx, colorImage(i.Payload.UsedImages[f.ImageIndex].Name), colorAddr("%#x", addr), slideInfo, colorField(f.Symbol), symloc)
}
w.Flush()
out += buf.String() + "\n"
}
if len(i.Payload.VMSummary) > 0 {
out += colorField("VM Summary") + ":\n" + colorVMSummary(i.Payload.VMSummary)
}
default:
return fmt.Sprintf("%s: (unsupported, notify author)", i.Header.BugType)
}
return out
}