mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
2436 lines
92 KiB
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
|
|
}
|