github.com/zchee/zap-cloudlogging@v0.0.0-20220819025602-19b026d3900e/caller.go (about)

     1  // Copyright 2022 The zap-cloudlogging Authors
     2  // SPDX-License-Identifier: BSD-3-Clause
     3  
     4  //go:build go1.13
     5  
     6  package zapcloudlogging
     7  
     8  import (
     9  	"runtime"
    10  	"unsafe"
    11  )
    12  
    13  // Token from https://github.com/golang/go/blob/go1.19/src/runtime/symtab.go#L799-L802
    14  type funcInfo struct {
    15  	Func *runtime.Func  // *_func
    16  	_    unsafe.Pointer // datap *moduledata
    17  }
    18  
    19  // Token from https://github.com/golang/go/blob/go1.19/src/runtime/symtab.go#L828
    20  //
    21  //go:linkname findfunc runtime.findfunc
    22  //go:noescape
    23  func findfunc(pc uintptr) funcInfo
    24  
    25  // FuncForPC is a drop-in replacement for runtime.FuncForPC.
    26  //
    27  //go:nosplit
    28  func FuncForPC(pc uintptr) *runtime.Func {
    29  	return findfunc(pc).Func
    30  }
    31  
    32  // frame is the information returned by Frames for each call frame.
    33  //
    34  // Token from: https://github.com/golang/go/blob/go1.19/src/runtime/symtab.go#L26-L61
    35  type frame struct {
    36  	// PC is the program counter for the location in this frame.
    37  	// For a frame that calls another frame, this will be the
    38  	// program counter of a call instruction. Because of inlining,
    39  	// multiple frames may have the same PC value, but different
    40  	// symbolic information.
    41  	PC uintptr
    42  
    43  	// Func is the Func value of this call frame. This may be nil
    44  	// for non-Go code or fully inlined functions.
    45  	Func *runtime.Func
    46  
    47  	// Function is the package path-qualified function name of
    48  	// this call frame. If non-empty, this string uniquely
    49  	// identifies a single function in the program.
    50  	// This may be the empty string if not known.
    51  	// If Func is not nil then Function == Func.Name().
    52  	Function string
    53  
    54  	// File and Line are the file name and line number of the
    55  	// location in this frame. For non-leaf frames, this will be
    56  	// the location of a call. These may be the empty string and
    57  	// zero, respectively, if not known.
    58  	File string
    59  	Line int
    60  
    61  	// Entry point program counter for the function; may be zero
    62  	// if not known. If Func is not nil then Entry ==
    63  	// Func.Entry().
    64  	Entry uintptr
    65  
    66  	// The runtime's internal view of the function. This field
    67  	// is set (funcInfo.valid() returns true) only for Go functions,
    68  	// not for C functions.
    69  	funcInfo funcInfo
    70  }
    71  
    72  // frames may be used to get function/file/line information for a
    73  // slice of PC values returned by Callers.
    74  //
    75  // Token from: https://github.com/golang/go/blob/go1.19/src/runtime/symtab.go#L16-L23
    76  type frames struct {
    77  	// callers is a slice of PCs that have not yet been expanded to frames.
    78  	callers []uintptr
    79  
    80  	// frames is a slice of Frames that have yet to be returned.
    81  	frames     []frame
    82  	frameStore [2]frame
    83  }
    84  
    85  // CallersFrames is a drop-in replacement for runtime.CallersFrames.
    86  //
    87  // CallersFrames takes a slice of PC values returned by Callers and
    88  // prepares to return function/file/line information.
    89  // Do not change the slice until you are done with the Frames.
    90  //
    91  // Token from: https://github.com/golang/go/blob/go1.19/src/runtime/symtab.go#L66
    92  func CallersFrames(callers []uintptr) *frames {
    93  	f := &frames{callers: callers}
    94  	f.frames = f.frameStore[:0]
    95  	return f
    96  }
    97  
    98  // Token from: https://github.com/golang/go/blob/go1.19/src/runtime/symtab.go#L81
    99  //
   100  //go:linkname next runtime.(*Frames).Next
   101  //go:noescape
   102  func next(*frames) (frame frame, more bool)
   103  
   104  // Next returns a Frame representing the next call frame in the slice
   105  // of PC values. If it has already returned all call frames, Next
   106  // returns a zero Frame.
   107  //
   108  // The more result indicates whether the next call to Next will return
   109  // a valid Frame. It does not necessarily indicate whether this call
   110  // returned one.
   111  //
   112  // See the Frames example for idiomatic usage.
   113  //
   114  //go:nosplit
   115  func (ci *frames) Next() (frame frame, more bool) {
   116  	return next(ci)
   117  }
   118  
   119  // Caller is a drop-in replacement for runtime.Caller.
   120  //
   121  // Token from: https://github.com/golang/go/blob/go1.19/src/runtime/extern.go#L217-L225
   122  func Caller(skip int) (pc uintptr, file string, line int, ok bool) {
   123  	rpc := make([]uintptr, 1)
   124  	n := callers(skip+1, rpc)
   125  	if n < 1 {
   126  		return
   127  	}
   128  	frame, _ := CallersFrames(rpc).Next()
   129  
   130  	return frame.PC, frame.File, frame.Line, frame.PC != 0
   131  }
   132  
   133  // Callers is a drop-in replacement for runtime.Callers that uses frame
   134  // pointers for fast and simple stack unwinding.
   135  //
   136  // Based by: https://github.com/golang/go/blob/go1.19/src/runtime/extern.go#L240-L248
   137  //
   138  //go:noinline
   139  func Callers(skip int, pcs []uintptr) int {
   140  	return callers(skip+1, pcs)
   141  }
   142  
   143  //go:noinline
   144  //go:nosplit
   145  func callers(skip int, pcs []uintptr) int {
   146  	fp := uintptr(unsafe.Pointer(&skip)) - 16
   147  
   148  	i := 0
   149  	for i < len(pcs) {
   150  		pc := deref(fp + 8)
   151  		if skip == 0 {
   152  			pcs[i] = pc
   153  			i++
   154  		} else {
   155  			skip--
   156  		}
   157  		fp = deref(fp)
   158  		if fp == 0 {
   159  			break
   160  		}
   161  	}
   162  
   163  	return i
   164  }
   165  
   166  //go:nosplit
   167  func deref(addr uintptr) uintptr {
   168  	return uintptr(**(**unsafe.Pointer)(unsafe.Pointer(&addr)))
   169  }