github.com/rsc/tmp@v0.0.0-20240517235954-6deaab19748b/macpanic/main.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Macpanic summarizes macOS panic logs. 6 // 7 // Usage: 8 // 9 // macpanic [-k kernel] [file...] 10 // 11 // Macpanic reads each of the named panic logs and summarizes the panic. 12 // With no arguments it reads /Library/Logs/DiagnosticReports/Kernel*panic. 13 // To add symbol information to the panic summary, macpanic uses symbols 14 // from kernel (default /System/Library/Kernels/kernel) and also inspects 15 // installed kernel modules. 16 package main 17 18 import ( 19 "bytes" 20 "flag" 21 "fmt" 22 "io/ioutil" 23 "log" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "sort" 28 "strconv" 29 "strings" 30 "unicode/utf8" 31 32 "github.com/ianlancetaylor/demangle" 33 ) 34 35 func usage() { 36 fmt.Fprintf(os.Stderr, "usage: macpanic [-k kernel] [file...]\n") 37 os.Exit(2) 38 } 39 40 var kernel = flag.String("k", "/System/Library/Kernels/kernel", "kernel binary") 41 var version string 42 43 type sym struct { 44 addr uint64 45 name string 46 } 47 48 var syms []sym 49 50 func main() { 51 log.SetPrefix("macpanic: ") 52 log.SetFlags(0) 53 flag.Usage = usage 54 flag.Parse() 55 56 data, err := ioutil.ReadFile(*kernel) 57 if err != nil { 58 log.Fatal(err) 59 } 60 i := bytes.Index(data, []byte("Darwin Kernel Version")) 61 if i < 0 { 62 log.Fatalf("cannot find 'Darwin Kernel Version' in kernel") 63 } 64 data = data[i:] 65 i = bytes.IndexByte(data, 0) 66 if i < 0 || !utf8.Valid(data[:i]) { 67 log.Fatalf("found malformed 'Darwin Kernel Version' in kernel") 68 } 69 version = string(data[:i]) 70 fmt.Printf("kernel %s: %s\n", *kernel, version) 71 72 syms, err = nm(*kernel) 73 if err != nil { 74 log.Fatal(err) 75 } 76 77 args := flag.Args() 78 if len(args) == 0 { 79 list, err := filepath.Glob("/Library/Logs/DiagnosticReports/Kernel*panic") 80 if err != nil { 81 log.Fatal(err) 82 } 83 args = list 84 } 85 for _, arg := range args { 86 process(arg) 87 } 88 } 89 90 func nm(file string) ([]sym, error) { 91 var syms []sym 92 data, err := exec.Command("nm", file).Output() 93 if err != nil { 94 return nil, fmt.Errorf("nm %s: %v", file, err) 95 } 96 for _, line := range bytes.Split(data, []byte("\n")) { 97 i := bytes.IndexByte(line, ' ') 98 if i < 0 { 99 continue 100 } 101 j := bytes.IndexByte(line[i+1:], ' ') 102 if i < 0 { 103 continue 104 } 105 j += i + 1 106 addr, err := strconv.ParseUint(string(line[:i]), 16, 64) 107 if err != nil { 108 continue 109 } 110 name := string(line[j+1:]) 111 syms = append(syms, sym{addr, name}) 112 } 113 sort.Slice(syms, func(i, j int) bool { 114 return syms[i].addr < syms[j].addr 115 }) 116 return syms, nil 117 } 118 119 func process(file string) { 120 data, err := ioutil.ReadFile(file) 121 if err != nil { 122 log.Print(err) 123 return 124 } 125 126 i := bytes.Index(data, []byte("Kernel slide:")) 127 if i < 0 { 128 log.Printf("%s: cannot find kernel slide", file) 129 return 130 } 131 j := bytes.IndexByte(data[i:], '\n') 132 if j < 0 { 133 log.Printf("%s: cannot find kernel slide", file) 134 return 135 } 136 j += i 137 138 s := strings.TrimSpace(string(data[i+len("Kernel slide:") : j])) 139 slide, err := strconv.ParseUint(s, 0, 64) 140 if err != nil { 141 log.Printf("%s: cannot parse kernel slide %q", file, s) 142 return 143 } 144 145 i = bytes.Index(data, []byte("Kernel text base:")) 146 if i < 0 { 147 log.Printf("%s: cannot find kernel slide", file) 148 return 149 } 150 j = bytes.IndexByte(data[i:], '\n') 151 if j < 0 { 152 log.Printf("%s: cannot find kernel text base", file) 153 return 154 } 155 j += i 156 s = strings.TrimSpace(string(data[i+len("Kernel text base:") : j])) 157 base, err := strconv.ParseUint(s, 0, 64) 158 if err != nil { 159 log.Printf("%s: cannot parse kernel text base %q", file, s) 160 return 161 } 162 163 i = bytes.Index(data, []byte("Kernel version:\n")) 164 if i < 0 { 165 log.Printf("%s: cannot find kernel version", file) 166 return 167 } 168 j = bytes.IndexByte(data[i+len("Kernel version:\n"):], '\n') 169 if j < 0 { 170 log.Printf("%s: cannot find kernel version", file) 171 return 172 } 173 j += i + len("Kernel version:\n") 174 v := string(data[i+len("Kernel version:\n") : j]) 175 if v != version { 176 log.Printf("%s: mismatched kernel version %q != %q", file, v, version) 177 return 178 } 179 180 i = bytes.Index(data, []byte("\npanic")) 181 if i < 0 { 182 log.Printf("%s: cannot find panic", file) 183 return 184 } 185 i++ 186 j = bytes.Index(data[i:], []byte("\n")) 187 if j < 0 { 188 log.Printf("%s: cannot find panic", file) 189 return 190 } 191 p := string(data[i : i+j]) 192 193 i = bytes.Index(data, []byte("\nBacktrace")) 194 if i < 0 { 195 log.Printf("%s: cannot find backtrace", file) 196 return 197 } 198 199 var trace [][2]uint64 200 var exts []sym 201 lines := bytes.Split(data[i+1:], []byte("\n"))[1:] 202 for len(lines) > 0 { 203 line := strings.TrimSpace(string(lines[0])) 204 if line == "" { 205 break 206 } 207 lines = lines[1:] 208 if strings.HasPrefix(line, "Kernel Extensions in backtrace") { 209 for len(lines) > 0 { 210 line := strings.TrimSpace(string(lines[0])) 211 if line == "" { 212 break 213 } 214 lines = lines[1:] 215 i := strings.Index(line, "(") 216 if i < 0 { 217 break 218 } 219 j := strings.LastIndex(line, "@") 220 if j < 0 { 221 break 222 } 223 name := line[:i] 224 addr := line[j+1:] 225 if i := strings.Index(addr, "->"); i >= 0 { 226 addr = addr[:i] 227 } 228 a, err := strconv.ParseUint(addr, 0, 64) 229 if err != nil { 230 log.Printf("%s: cannot parse extension address: %s", file, line) 231 continue 232 } 233 exts = append(exts, sym{a, name}) 234 } 235 break 236 } 237 i := strings.Index(line, " : ") 238 if i < 0 { 239 log.Printf("%s: cannot parse backtrace line: %s", file, line) 240 break 241 } 242 a, err := strconv.ParseUint(line[:i], 0, 64) 243 b, err1 := strconv.ParseUint(line[i+3:], 0, 64) 244 if err != nil || err1 != nil { 245 log.Printf("%s: cannot parse backtrace line: %s", file, line) 246 break 247 } 248 trace = append(trace, [2]uint64{a, b}) 249 } 250 251 sort.Slice(exts, func(i, j int) bool { 252 return exts[i].addr < exts[j].addr 253 }) 254 255 fmt.Printf("\n%s\n", file) 256 fmt.Printf("\t%s\n", p) 257 for _, t := range trace { 258 var desc string 259 if t[1] < base { 260 desc = translate(t[1], exts, true) 261 } else { 262 desc = translate(t[1]-slide, syms, false) 263 } 264 fmt.Printf("\t%#x : %#x : %s\n", t[0], t[1], desc) 265 } 266 } 267 268 func translate(pc uint64, syms []sym, exts bool) string { 269 i := sort.Search(len(syms), func(i int) bool { 270 return i+1 >= len(syms) || syms[i+1].addr > pc 271 }) 272 if i >= len(syms) { 273 return "???" 274 } 275 name := syms[i].name 276 n, err := demangle.ToString(name) 277 if err != nil { 278 n, err = demangle.ToString(strings.TrimPrefix(name, "_")) 279 } 280 if err == nil { 281 name = n 282 } 283 desc := fmt.Sprintf("%s + %#x", name, pc-syms[i].addr) 284 if exts { 285 name := strings.TrimSuffix(syms[i].name, ".kext") 286 elem := name[strings.LastIndex(name, ".")+1:] 287 esyms, err := nm("/System/Library/Extensions/" + elem + ".kext/Contents/MacOS/" + elem) 288 if err != nil { 289 esyms, err = nm("/Library/Extensions/" + elem + ".kext/Contents/MacOS/" + elem) 290 } 291 if err == nil { 292 d := translate(pc-syms[i].addr, esyms, false) 293 if d != "???" { 294 desc += " (" + d + ")" 295 } 296 } 297 } 298 return desc 299 }