Joseph Swaminathan
Joseph Swaminathan

Reputation: 115

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

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
}

Upvotes: 3

Views: 379

Answers (2)

nemo
nemo

Reputation: 57729

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{'A','B','C'})
main.Test1(0x513350, 0x4) // Test1("AAAA")

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 (
    "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(")")
}

Test:

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)
}

Output:

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

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

go get github.com/githubnemo/pdump

Upvotes: 3

Gordon Childs
Gordon Childs

Reputation: 36139

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

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

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

Upvotes: 0

Related Questions