github.com/Aoi-hosizora/ahlib@v1.5.1-0.20230404072829-241b93cf91c7/xruntime/xruntime.go (about)

     1  package xruntime
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"net"
     9  	"os"
    10  	"path/filepath"
    11  	"reflect"
    12  	"runtime"
    13  	"strconv"
    14  	"strings"
    15  	"syscall"
    16  	"unsafe"
    17  )
    18  
    19  // ================
    20  // function related
    21  // ================
    22  
    23  // NameOfFunction returns given function's name by searching runtime.Func by PC.
    24  func NameOfFunction(f interface{}) string {
    25  	return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
    26  }
    27  
    28  // ===================
    29  // trace stack related
    30  // ===================
    31  
    32  // RawStack returns the raw debug trace stack of the calling goroutine from runtime.Stack, if all is true, it will also return other
    33  // goroutines' trace stack. Also see debug.Stack and runtime.Stack for more information.
    34  //
    35  // Returned value is just like:
    36  // 	goroutine 19 [running]:
    37  // 	github.com/Aoi-hosizora/ahlib/xruntime.RawStack(0x7?)
    38  // 		.../xruntime/xruntime.go:46 +0x6a
    39  // 	github.com/Aoi-hosizora/ahlib/xruntime.TestRawStack(0x0?)
    40  // 		.../xruntime/xruntime_test.go:65 +0x30
    41  // 	testing.tRunner(0xc000085380, 0xf682b0)
    42  // 		.../src/testing/testing.go:1439 +0x102
    43  // 	created by testing.(*T).Run
    44  // 		.../src/testing/testing.go:1486 +0x35f
    45  func RawStack(all bool) []byte {
    46  	buf := make([]byte, 1024)
    47  	for {
    48  		n := runtime.Stack(buf, all)
    49  		if n < len(buf) {
    50  			return buf[:n]
    51  		}
    52  		buf = make([]byte, 2*len(buf))
    53  	}
    54  }
    55  
    56  // TraceFrame represents a frame of the runtime trace stack, also see RuntimeTraceStack.
    57  type TraceFrame struct {
    58  	// Index represents the index of the frame in stack, 0 identifying the caller of this xruntime package.
    59  	Index int
    60  
    61  	// FuncPC represents the function's program count.
    62  	FuncPC uintptr
    63  
    64  	// FuncFullName represents the function's fill name, including the package full name and the function name.
    65  	FuncFullName string
    66  
    67  	// FuncName represents the function's name, including the package short name and the function name.
    68  	FuncName string
    69  
    70  	// Filename represents the file's full name.
    71  	Filename string
    72  
    73  	// LineIndex represents the line number in the file, starts from 1.
    74  	LineIndex int
    75  
    76  	// LineText represents the line text in the fileļ¼Œ"?" if the text cannot be got.
    77  	LineText string
    78  }
    79  
    80  // String returns the formatted TraceFrame.
    81  //
    82  // Returned value is just like:
    83  // 	File: .../xruntime/xruntime_test.go:145
    84  // 	Func: github.com/Aoi-hosizora/ahlib/xruntime.TestTraceStack.func1
    85  // 		stack := RuntimeTraceStack(0)
    86  func (t *TraceFrame) String() string {
    87  	return fmt.Sprintf("File: %s:%d\nFunc: %s\n\t%s", t.Filename, t.LineIndex, t.FuncFullName, t.LineText)
    88  }
    89  
    90  // TraceStack represents the runtime trace stack, consists of some TraceFrame, also see RuntimeTraceStack.
    91  type TraceStack []*TraceFrame
    92  
    93  // String returns the formatted TraceStack.
    94  //
    95  // Returned value is just like:
    96  // 	File: .../xruntime/xruntime_test.go:145
    97  // 	Func: github.com/Aoi-hosizora/ahlib/xruntime.TestTraceStack.func1
    98  // 		stack := RuntimeTraceStack(0)
    99  // 	File: .../xruntime/xruntime_test.go:147
   100  // 	Func: github.com/Aoi-hosizora/ahlib/xruntime.TestTraceStack
   101  // 		}()
   102  // 	File: .../src/testing/testing.go:1439
   103  // 	Func: testing.tRunner
   104  // 		fn(t)
   105  // 	File: .../src/runtime/asm_amd64.s:1571
   106  // 	Func: runtime.goexit
   107  // 		BYTE	$0x90	// NOP
   108  func (t TraceStack) String() string {
   109  	l := len(t)
   110  	sb := strings.Builder{}
   111  	for i, frame := range t {
   112  		sb.WriteString(frame.String())
   113  		if i != l-1 {
   114  			sb.WriteString("\n")
   115  		}
   116  	}
   117  	return sb.String()
   118  }
   119  
   120  // RuntimeTraceStack returns TraceStack (a slice of TraceFrame) from runtime.Caller using given skip (0 identifying the caller of RuntimeTraceStack).
   121  func RuntimeTraceStack(skip uint) TraceStack {
   122  	frames := make([]*TraceFrame, 0)
   123  	for i := skip + 1; ; i++ {
   124  		funcPC, filename, lineIndex, ok := runtime.Caller(int(i))
   125  		if !ok {
   126  			break
   127  		}
   128  
   129  		// func
   130  		funcObj := runtime.FuncForPC(funcPC)
   131  		funcFullName := funcObj.Name()
   132  		_, funcName := filepath.Split(funcFullName)
   133  
   134  		// file
   135  		lineText := "?"
   136  		if filename != "" {
   137  			if data, err := ioutil.ReadFile(filename); err == nil {
   138  				lines := bytes.Split(data, []byte{'\n'})
   139  				if lineIndex > 0 && lineIndex <= len(lines) {
   140  					lineText = string(bytes.TrimSpace(lines[lineIndex-1]))
   141  				}
   142  			}
   143  		}
   144  
   145  		// out
   146  		frame := &TraceFrame{Index: int(i), FuncPC: funcPC, FuncFullName: funcFullName, FuncName: funcName, Filename: filename, LineIndex: lineIndex, LineText: lineText}
   147  		frames = append(frames, frame)
   148  	}
   149  
   150  	return frames
   151  }
   152  
   153  // RuntimeTraceStackWithInfo returns TraceStack (a slice of TraceFrame) from runtime.Caller using given skip, with some information of the first TraceFrame's.
   154  func RuntimeTraceStackWithInfo(skip uint) (stack TraceStack, filename string, funcName string, lineIndex int, lineText string) {
   155  	stack = RuntimeTraceStack(skip + 1)
   156  	if len(stack) == 0 {
   157  		return []*TraceFrame{}, "", "", 0, ""
   158  	}
   159  	top := stack[0]
   160  	return stack, top.Filename, top.FuncName, top.LineIndex, top.LineText
   161  }
   162  
   163  // ========================
   164  // hack hide string related
   165  // ========================
   166  
   167  var (
   168  	// _magicBytes represents a slice of bytes, and are used in HackHideString and HackGetHiddenString.
   169  	_magicBytes = []byte{0, 'h', 'i', 0, 'd', 'e'}
   170  
   171  	_magicLength = len(_magicBytes)
   172  )
   173  
   174  // HackHideString hides given hidden string after given data address (in heap space) and returns the new data address. Note that this is an unsafe function.
   175  //
   176  // Example:
   177  // 	handler := func() {}
   178  // 	funcSize := int(reflect.TypeOf(func() {}).Size()) // which always equals to 8 in x86-64 machine
   179  // 	handlerFn := (*struct{fn uintptr})(unsafe.Pointer(&handler)).fn
   180  // 	hackedFn := HackHideString(handlerFn, funcSize, "handlerName")
   181  // 	hackedHandler := *(*func())(unsafe.Pointer(&struct{fn uintptr}{fn: hackedFn}))
   182  // 	realHandlerName := HackGetHiddenString(hackedFn, funcSize) // got "handlerName"
   183  // 	hackedHandler() // hackedHandler can be invoked normally
   184  //go:nosplit
   185  func HackHideString(given uintptr, givenLength int, hidden string) (dataAddr uintptr) {
   186  	hiddenHeader := (*reflect.StringHeader)(unsafe.Pointer(&hidden))
   187  	hiddenLen := int32(hiddenHeader.Len) // only store int32 length
   188  	hiddenBs := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{Data: hiddenHeader.Data, Len: int(hiddenLen), Cap: int(hiddenLen)}))
   189  	bs := bytes.Buffer{}
   190  	bs.Grow(givenLength + _magicLength + 4 + int(hiddenLen))
   191  
   192  	// given
   193  	for i := 0; i < givenLength; i++ {
   194  		bs.WriteByte(*(*byte)(unsafe.Pointer(given + uintptr(i))))
   195  	}
   196  	bs.Write(_magicBytes) // \x00, 'h', 'i', \x00, 'd', 'e', # = 6
   197  
   198  	// hidden
   199  	bs.Write([]byte{byte(hiddenLen), byte(hiddenLen >> 8), byte(hiddenLen >> 16), byte(hiddenLen >> 24)}) // lo -> hi, # = 4
   200  	bs.Write(hiddenBs)
   201  
   202  	fakeSlice := bs.Bytes() // its Data part will be used to interpret as other types
   203  	return (*reflect.SliceHeader)(unsafe.Pointer(&fakeSlice)).Data
   204  }
   205  
   206  // HackHideStringAfterString hides given hidden string after given string (in heap space) and returns the new string. Note that this is an unsafe function.
   207  //
   208  // Example:
   209  // 	httpMethod := "GET"
   210  // 	hackedMethod := xruntime.HackHideStringAfterString(httpMethod, "handlerName")
   211  // 	realHandlerName := xruntime.HackGetHiddenStringAfterString(hackedMethod) // got "handlerName"
   212  // 	fmt.Println(hackedMethod) // hackedMethod can be printed normally
   213  //go:nosplit
   214  func HackHideStringAfterString(given, hidden string) string {
   215  	givenHeader := (*reflect.StringHeader)(unsafe.Pointer(&given))
   216  	fakeData := HackHideString(givenHeader.Data, givenHeader.Len, hidden)
   217  	fakeHeader := &reflect.StringHeader{Data: fakeData, Len: givenHeader.Len}
   218  	return *(*string)(unsafe.Pointer(fakeHeader))
   219  }
   220  
   221  // HackGetHiddenString get the hidden string from given unsafe.Pointer, see HackHideString for example. Note that this is an unsafe function.
   222  //go:nosplit
   223  func HackGetHiddenString(given uintptr, givenLength int) (hidden string) {
   224  	// check magic
   225  	for i, ch := range _magicBytes {
   226  		if *(*byte)(unsafe.Pointer(given + uintptr(givenLength+i))) != ch {
   227  			return ""
   228  		}
   229  	}
   230  
   231  	// get hidden data
   232  	digits := *(*[4]byte)(unsafe.Pointer(given + uintptr(givenLength+_magicLength))) // lo -> hi
   233  	hiddenData := given + uintptr(givenLength+_magicLength+4)
   234  	hiddenLen := int32(digits[0]) + int32(digits[1])<<8 + int32(digits[2])<<16 + int32(digits[3])<<24
   235  
   236  	// construct string
   237  	fakeHeader := &reflect.StringHeader{Data: hiddenData, Len: int(hiddenLen)}
   238  	return *(*string)(unsafe.Pointer(fakeHeader))
   239  }
   240  
   241  // HackGetHiddenStringAfterString get the hidden string from given string, see HackHideStringAfterString for example. Note that this is an unsafe function.
   242  //go:nosplit
   243  func HackGetHiddenStringAfterString(given string) (hidden string) {
   244  	givenHeader := (*reflect.StringHeader)(unsafe.Pointer(&given))
   245  	return HackGetHiddenString(givenHeader.Data, givenHeader.Len)
   246  }
   247  
   248  // ============================
   249  // mass constants and functions
   250  // ============================
   251  
   252  // Pprof profile names, see pprof.Lookup (runtime/pprof) and pprof.Handler (net/http/pprof) for more details.
   253  const (
   254  	PprofGoroutineProfile    = "goroutine"
   255  	PprofThreadcreateProfile = "threadcreate"
   256  	PprofHeapProfile         = "heap"
   257  	PprofAllocsProfile       = "allocs"
   258  	PprofBlockProfle         = "block"
   259  	PprofMutexProfile        = "mutex"
   260  )
   261  
   262  // signalNames is used by SignalName.
   263  var signalNames = [...]string{
   264  	1:  "SIGHUP",
   265  	2:  "SIGINT",
   266  	3:  "SIGQUIT",
   267  	4:  "SIGILL",
   268  	5:  "SIGTRAP",
   269  	6:  "SIGABRT",
   270  	7:  "SIGBUS",
   271  	8:  "SIGFPE",
   272  	9:  "SIGKILL",
   273  	10: "SIGUSR1",
   274  	11: "SIGSEGV",
   275  	12: "SIGUSR2",
   276  	13: "SIGPIPE",
   277  	14: "SIGALRM",
   278  	15: "SIGTERM",
   279  }
   280  
   281  // signalReadableNames is used by SignalReadableName.
   282  var signalReadableNames = [...]string{
   283  	1:  "hangup",
   284  	2:  "interrupt",
   285  	3:  "quit",
   286  	4:  "illegal instruction",
   287  	5:  "trace/breakpoint trap",
   288  	6:  "aborted",
   289  	7:  "bus error",
   290  	8:  "floating point exception",
   291  	9:  "killed",
   292  	10: "user defined signal 1",
   293  	11: "segmentation fault",
   294  	12: "user defined signal 2",
   295  	13: "broken pipe",
   296  	14: "alarm clock",
   297  	15: "terminated",
   298  }
   299  
   300  // SignalName returns the SIGXXX string from given syscall.Signal. Note that syscall.Signal.String() and xruntime.SignalReadableName
   301  // will return the human-readable string value.
   302  func SignalName(sig syscall.Signal) string {
   303  	if sig >= 1 && int(sig) < len(signalNames) {
   304  		return signalNames[sig]
   305  	}
   306  	return "signal " + strconv.Itoa(int(sig))
   307  }
   308  
   309  // SignalReadableName returns the human-readable name from given syscall.Signal, this function has the same result with syscall.Signal.String().
   310  func SignalReadableName(sig syscall.Signal) string {
   311  	if sig >= 1 && int(sig) < len(signalReadableNames) {
   312  		return signalReadableNames[sig]
   313  	}
   314  	return "signal " + strconv.Itoa(int(sig))
   315  }
   316  
   317  // ProxyEnv represents some proxy environment variables, returned by GetProxyEnv.
   318  type ProxyEnv struct {
   319  	NoProxy    string
   320  	HttpProxy  string
   321  	HttpsProxy string
   322  	SocksProxy string
   323  }
   324  
   325  // GetProxyEnv lookups and returns four proxy environments, including no_proxy, http_proxy, https_proxy and socks_proxy.
   326  func GetProxyEnv() *ProxyEnv {
   327  	np := strings.TrimSpace(os.Getenv("no_proxy"))
   328  	hp := strings.TrimSpace(os.Getenv("http_proxy"))
   329  	hsp := strings.TrimSpace(os.Getenv("https_proxy"))
   330  	ssp := strings.TrimSpace(os.Getenv("socks_proxy"))
   331  	return &ProxyEnv{NoProxy: np, HttpProxy: hp, HttpsProxy: hsp, SocksProxy: ssp}
   332  }
   333  
   334  // PrintLog prints each proxy variables if it is not empty, using given logFunc (default to log.Println) and prefix (default to empty).
   335  //
   336  // Example:
   337  // 	proxyEnv := xruntime.GetProxyEnv()
   338  // 	proxyEnv.PrintLog(nil, "[Gin] ")
   339  func (p *ProxyEnv) PrintLog(logFunc func(string), prefix string) {
   340  	if logFunc == nil {
   341  		logFunc = func(s string) {
   342  			log.Println(s)
   343  		}
   344  	}
   345  
   346  	if p.NoProxy != "" {
   347  		logFunc(fmt.Sprintf("%sUsing no_proxy: %s", prefix, p.NoProxy))
   348  	}
   349  	if p.HttpProxy != "" {
   350  		logFunc(fmt.Sprintf("%sUsing http_proxy: %s", prefix, p.HttpProxy))
   351  	}
   352  	if p.HttpsProxy != "" {
   353  		logFunc(fmt.Sprintf("%sUsing https_proxy: %s", prefix, p.HttpsProxy))
   354  	}
   355  	if p.SocksProxy != "" {
   356  		logFunc(fmt.Sprintf("%sUsing socks_proxy: %s", prefix, p.SocksProxy))
   357  	}
   358  }
   359  
   360  // NetAddrType describes a concrete address type, including TCPAddrType, UDPAddrType, IPAddrType and UnixAddrType.
   361  type NetAddrType string
   362  
   363  const (
   364  	// TCPAddrType represents a net.TCPAddr concrete address type.
   365  	TCPAddrType NetAddrType = "TCPAddr"
   366  
   367  	// UDPAddrType represents a net.UDPAddr concrete address type.
   368  	UDPAddrType NetAddrType = "UDPAddr"
   369  
   370  	// IPAddrType represents a net.IPAddr concrete address type.
   371  	IPAddrType NetAddrType = "IPAddr"
   372  
   373  	// UnixAddrType represents a net.UnixAddr concrete address type.
   374  	UnixAddrType NetAddrType = "UnixAddr"
   375  )
   376  
   377  // String returns the string value of NetAddrType.
   378  func (n NetAddrType) String() string {
   379  	return string(n)
   380  }
   381  
   382  // ConcreteNetAddr represents a concrete net.Addr type, which includes implementation of net.TCPAddr, net.UDPAddr, net.IPAddr and net.UnixAddr.
   383  type ConcreteNetAddr struct {
   384  	Type NetAddrType
   385  
   386  	TCPAddr  *net.TCPAddr
   387  	UDPAddr  *net.UDPAddr
   388  	IPAddr   *net.IPAddr
   389  	UnixAddr *net.UnixAddr
   390  
   391  	// for TCP, UDP, IP
   392  	IP   net.IP
   393  	Zone string
   394  
   395  	// for TCP, UDP
   396  	Port int
   397  
   398  	// for Unix
   399  	Name string
   400  	Net  string
   401  }
   402  
   403  // ParseNetAddr parses given net.Addr value to ConcreteNetAddr, returns error if given address is not TCP, UDP, IP, Unix address.
   404  func ParseNetAddr(addr net.Addr) (*ConcreteNetAddr, bool) {
   405  	if addr == nil {
   406  		return nil, false
   407  	}
   408  
   409  	var out *ConcreteNetAddr
   410  	switch addr := addr.(type) {
   411  	case *net.TCPAddr:
   412  		out = &ConcreteNetAddr{Type: TCPAddrType, TCPAddr: addr, IP: addr.IP, Zone: addr.Zone, Port: addr.Port}
   413  	case *net.UDPAddr:
   414  		out = &ConcreteNetAddr{Type: UDPAddrType, UDPAddr: addr, IP: addr.IP, Zone: addr.Zone, Port: addr.Port}
   415  	case *net.IPAddr:
   416  		out = &ConcreteNetAddr{Type: IPAddrType, IPAddr: addr, IP: addr.IP, Zone: addr.Zone}
   417  	case *net.UnixAddr:
   418  		out = &ConcreteNetAddr{Type: UnixAddrType, UnixAddr: addr, Name: addr.Name, Net: addr.Net}
   419  	default:
   420  		return nil, false
   421  	}
   422  	return out, true
   423  }