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  }