【问题标题】:Is there an equivalent of os.Args() for functions?函数是否有等效的 os.Args() ?
【发布时间】:2015-10-07 21:28:31
【问题描述】:

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

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

函数是否有与os.Args() 等效的函数?看了下runtime包,没有找到这样的功能。

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

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

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

我希望能够做到以下几点

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
}

【问题讨论】:

    标签: go


    【解决方案1】:

    您不能在 Go 中执行此操作,因为您无法在当前 goroutine 中获取当前活动函数的堆栈帧。这样做并非不可能,我将在下面进一步展示,但问题是没有公共 API 可以可靠地完成这项工作。可以在引发panic 时打印的堆栈跟踪中看到它可以完成:在这种情况下,堆栈上的所有值都被转储。

    如果您对堆栈跟踪的实际生成方式感兴趣,请查看运行时包中的genstacktrace

    至于您的问题的解决方案,您可以将源代码解析路由为already suggested。如果您喜欢冒险,可以解析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"
    )
    
    // Parses the second call'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 == '\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",)
    

    可以在on github找到一个稍微高级的版本:

    go get github.com/githubnemo/pdump
    

    【讨论】:

    • 非常感谢。我在 github 上试过这个版本和一个。除了字符串参数外,它工作正常。注意:我不得不将我的 go 从 1.4.2 升级到 1.5.1,因为 go1.4.2 不支持 reflect.ArraOf()
    • @JosephSwaminathan 请在 github 上发布问题或修复它并打开拉取请求。谢谢。
    • 发布了一个问题。该代码似乎还可以,目前无法找到修复程序。
    • @nimo 打开了一个拉取请求
    【解决方案2】:

    要一般地打印函数的参数,您可以这样做:

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

    printInputParametersa variadic functioninput[]interface{} 类型。

    【讨论】:

    • @RhytmicFistman 谢谢。但我不想重新定义函数定义。我只想能够调试。 (我编辑了我的问题以澄清)
    • 对不起,有反射,但我只能看到参数的类型,而不是它们的值。您还需要指定函数的名称...
    猜你喜欢
    • 2020-03-10
    • 2019-09-29
    • 1970-01-01
    • 1970-01-01
    • 2012-04-01
    • 2012-06-05
    • 2021-01-20
    • 1970-01-01
    • 2022-01-09
    相关资源
    最近更新 更多