函数是否有类似于os.Args()的等效函数?

huangapple go评论73阅读模式
英文:

Is there an equivalent of os.Args() for functions?

问题

为了帮助调试GO程序,我想编写两个通用函数,分别在进入和退出时调用,用于打印输入和输出参数的值:

printInputParameters(input ...interface{})
printOutputParameters(output ...interface{})

是否有类似于os.Args()的函数可以用于函数?我查看了runtime包,但没有找到这样的函数。

例如,假设我有两个具有不同输入参数和输出参数的函数:

func f1(i int, f float64) (e error) {
    ...一些代码
}

func f2(s string, b []byte) (u uint64, e error) {
    ...一些代码
}

我希望能够做到以下操作:

func f1(i int, f float64) (e error) {
    printInputParameters( ? )
    defer func() {
        printOutputParameters( ? )
    }()

    ...一些代码
}

func f2(s string, b []byte) (u uint64, e error) {
    printInputParameters( ? )
    defer func() {
        printOutputParameters( ? )
    }()

    ...一些代码
}
英文:

To help debug GO programs, I want to write two generic functions that will be called on entry and exit, which will print the values of input and output parameters respectively:

printInputParameters(input ...interface{})
printOutputParameters(output ...interface{})

Is there an equivalent of os.Args() for functions? I looked at runtime package and didn't find such functions.

For example lets say I have two functions with different input parameters and output parameters

func f1(int i, float f) (e error) {
    ... some code here
}

func f2(s string, b []byte) (u uint64, e error) {
     .. some code here
}

I want to be able to do the following

func f1(int i, float f) (e error) {
     printInputparameters( ? )
     defer func() {
          printOutputParameters( ? )
     }()

     ... some code here
}

func f2(s string, b []byte) (u uint64, e error) {
     printInputparameters( ? )
     defer func() {
          printOutputParameters( ? )
     }()

     ... some code here
}

答案1

得分: 3

在Go语言中,你无法获取当前goroutine中当前活动函数的堆栈帧,因此无法实现这一点。虽然在下面我将展示一种方法,但问题在于没有公共API可靠地完成此操作。可以在引发panic时打印的堆栈跟踪中看到可以实现这一点:在这种情况下,堆栈上的所有值都被转储出来。

如果你对堆栈跟踪的生成方式感兴趣,可以查看运行时包中的genstacktrace

至于解决你的问题,你可以采用已经建议的源代码解析方法。如果你感兴趣,你可以解析由runtime.Stack提供的堆栈跟踪。但要注意,这种方法有很多缺点,你很快就会意识到任何其他解决方案都比这个更好。

要解析堆栈跟踪,只需获取之前调用函数的行(从printInputParameters的视角),获取该函数的名称,并根据反射提供的参数类型解析参数值。以下是各种函数调用的堆栈跟踪输出示例:

main.Test1(0x2) // Test1(int64(2))
main.Test1(0xc820043ed5, 0x3, 0x3) // Test1([]byte{'A','B','C'})
main.Test1(0x513350, 0x4) // Test1("AAAA")

你可以看到复杂类型(那些无法适应寄存器的类型)可能会使用多个'参数'。例如,字符串是指向数据和长度的指针。因此,你必须使用unsafe包来访问这些指针,并使用反射从这些数据创建值。

如果你想自己尝试,这里有一些示例代码:

import (
    "fmt"
    "math"
    "reflect"
    "runtime"
    "strconv"
    "strings"
    "unsafe"
)

// 解析形如以下堆栈跟踪中第二个调用的参数:
//
// goroutine 1 [running]:
// main.printInputs(0x4c4c60, 0x539038)
//    /.../go/src/debug/main.go:16 +0xe0
// main.Test1(0x2)
//    /.../go/src/debug/main.go:23
//
func parseParams(st string) (string, []uintptr) {

    line := 1
    start, stop := 0, 0
    for i, c := range st {
        if c == '\n' {
            line++
        }
        if line == 4 && c == '\n' {
            start = i + 1
        }
        if line == 5 && c == '\n' {
            stop = i
        }
    }

    call := st[start:stop]
    fname := call[0:strings.IndexByte(call, '(')]
    param := call[strings.IndexByte(call, '(')+1 : strings.IndexByte(call, ')')]
    params := strings.Split(param, ", ")
    parsedParams := make([]uintptr, len(params))

    for i := range params {
        iv, err := strconv.ParseInt(params[i], 0, 64)

        if err != nil {
            panic(err.Error())
        }

        parsedParams[i] = uintptr(iv)
    }

    return fname, parsedParams
}

func fromAddress(t reflect.Type, addr uintptr) reflect.Value {
    return reflect.NewAt(t, unsafe.Pointer(&addr)).Elem()
}

func printInputs(fn interface{}) {
    v := reflect.ValueOf(fn)
    vt := v.Type()
    b := make([]byte, 500)

    if v.Kind() != reflect.Func {
        return
    }

    runtime.Stack(b, false)

    name, params := parseParams(string(b))
    pidx := 0

    fmt.Print(name + "(")
    for i := 0; i < vt.NumIn(); i++ {
        t := vt.In(i)
        switch t.Kind() {
        case reflect.Int64:
        case reflect.Int:
            // Just use the value from the stack
            fmt.Print(params[pidx], ",")
            pidx++
        case reflect.Float64:
            fmt.Print(math.Float64frombits(uint64(params[pidx])), ",")
            pidx++
        case reflect.Slice:
            // create []T pointing to slice content
            data := reflect.ArrayOf(int(params[pidx+2]), t.Elem())
            svp := reflect.NewAt(data, unsafe.Pointer(params[pidx]))
            fmt.Printf("%v,", svp.Elem())
            pidx += 3
        case reflect.String:
            sv := fromAddress(t, params[pidx])
            fmt.Printf("%v,", sv)
            pidx += 2
        case reflect.Map:
            // points to hmap struct
            mv := fromAddress(t,params[pidx])
            fmt.Printf("%v,", mv)
            pidx++
        } /* switch */
    }
    fmt.Println(")")
}

测试:

func Test1(in int, b []byte, in2 int, m string) {
    printInputs(Test1)
}

func main() {
    b := []byte{'A', 'B', 'C'}
    s := "AAAA"
    Test1(2, b, 9, s)
}

输出:

main.Test1(2,[65 66 67],9,"AAAA",)

这是一个稍微高级一点的版本,可以在github上找到

go get github.com/githubnemo/pdump
英文:

You cannot do this in Go since there is no way you can get the stack frame of the currently active function in the current goroutine. It is not impossible to do this as I'll show further below but the problem is that there is no public API to get this done reliably. That it can be done can be seen in the stack traces printed when a panic is raised: all values on the stack are dumped in that case.

Should you be interested in how the stack trace is actually generated then have a look at genstacktrace in the runtime package.

As for a solution to your problem, you can the source code parsing route as already suggested. If you feel adventurous, you can parse the stack trace provided by runtime.Stack. But beware, there are so many drawbacks that you will quickly realize that any solution is better than this one.

To parse the stack trace, just get the line of the previously called function (from the viewpoint of printInputParameters), get the name of that function and parse the parameter values according to the parameter types provided by reflection. Some examples of stack trace outputs of various function invocations:

main.Test1(0x2) // Test1(int64(2))
main.Test1(0xc820043ed5, 0x3, 0x3) // Test1([]byte{&#39;A&#39;,&#39;B&#39;,&#39;C&#39;})
main.Test1(0x513350, 0x4) // Test1(&quot;AAAA&quot;)

You can see that complex types (those which do not fit into a register) may use more than one 'parameter'. A string for example is a pointer to the data and the length. So you have to use the unsafe package to access these pointers and reflection to create values from this data.

If you want to try yourself, here's some example code:

import (
	&quot;fmt&quot;
	&quot;math&quot;
	&quot;reflect&quot;
	&quot;runtime&quot;
	&quot;strconv&quot;
	&quot;strings&quot;
	&quot;unsafe&quot;
)

// Parses the second call&#39;s parameters in a stack trace of the form:
//
// goroutine 1 [running]:
// main.printInputs(0x4c4c60, 0x539038)
//	/.../go/src/debug/main.go:16 +0xe0
// main.Test1(0x2)
//	/.../go/src/debug/main.go:23
//
func parseParams(st string) (string, []uintptr) {

	line := 1
	start, stop := 0, 0
	for i, c := range st {
		if c == &#39;\n&#39; {
			line++
		}
		if line == 4 &amp;&amp; c == &#39;\n&#39; {
			start = i + 1
		}
		if line == 5 &amp;&amp; c == &#39;\n&#39; {
			stop = i
		}
	}

	call := st[start:stop]
	fname := call[0:strings.IndexByte(call, &#39;(&#39;)]
	param := call[strings.IndexByte(call, &#39;(&#39;)+1 : strings.IndexByte(call, &#39;)&#39;)]
	params := strings.Split(param, &quot;, &quot;)
	parsedParams := make([]uintptr, len(params))

	for i := range params {
		iv, err := strconv.ParseInt(params[i], 0, 64)

		if err != nil {
			panic(err.Error())
		}

		parsedParams[i] = uintptr(iv)
	}

	return fname, parsedParams
}

func fromAddress(t reflect.Type, addr uintptr) reflect.Value {
	return reflect.NewAt(t, unsafe.Pointer(&amp;addr)).Elem()
}

func printInputs(fn interface{}) {
	v := reflect.ValueOf(fn)
	vt := v.Type()
	b := make([]byte, 500)

	if v.Kind() != reflect.Func {
		return
	}

	runtime.Stack(b, false)

	name, params := parseParams(string(b))
	pidx := 0

	fmt.Print(name + &quot;(&quot;)
	for i := 0; i &lt; vt.NumIn(); i++ {
		t := vt.In(i)
		switch t.Kind() {
		case reflect.Int64:
		case reflect.Int:
			// Just use the value from the stack
			fmt.Print(params[pidx], &quot;,&quot;)
			pidx++
		case reflect.Float64:
			fmt.Print(math.Float64frombits(uint64(params[pidx])), &quot;,&quot;)
			pidx++
		case reflect.Slice:
			// create []T pointing to slice content
			data := reflect.ArrayOf(int(params[pidx+2]), t.Elem())
			svp := reflect.NewAt(data, unsafe.Pointer(params[pidx]))
			fmt.Printf(&quot;%v,&quot;, svp.Elem())
			pidx += 3
		case reflect.String:
			sv := fromAddress(t, params[pidx])
			fmt.Printf(&quot;%v,&quot;, sv)
			pidx += 2
		case reflect.Map:
			// points to hmap struct
			mv := fromAddress(t,params[pidx])
			fmt.Printf(&quot;%v,&quot;, mv)
			pidx++
        } /* switch */
	}
	fmt.Println(&quot;)&quot;)
}

Test:

func Test1(in int, b []byte, in2 int, m string) {
	printInputs(Test1)
}

func main() {
	b := []byte{&#39;A&#39;, &#39;B&#39;, &#39;C&#39;}
	s := &quot;AAAA&quot;
	Test1(2, b, 9, s)
}

Output:

main.Test1(2,[65 66 67],9,&quot;AAAA&quot;,)

A slightly advanced version of this can be found on github:

go get github.com/githubnemo/pdump

答案2

得分: 0

要通用地打印函数的参数,你可以这样做:

func printInputParameters(input ...interface{}) {
    fmt.Printf("Args: %v", input)
}

printInputParameters 是一个可变参数函数input 的类型是 []interface{}

英文:

To generically print your functions' arguments, you can do this:

func printInputParameters(input ...interface{}) {
    fmt.Printf(&quot;Args: %v&quot;, input)
}

printInputParameters is a variadic function, and input is of type []interface{}.

huangapple
  • 本文由 发表于 2015年10月8日 05:28:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/33002720.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定