This is a creation in Article, where the information may have evolved or changed.
by chaishushan{at}gmail.com
Note: The first draft of this article was sent to Golang China blog, where the content is partially modified.
The call information of a function is the important run-time information in the program, which is used in many situations (such as debugging or logging).
runtime
runtime.Caller
runtime.Callers
Several functions, such as the Go Language pack, runtime.FuncForPC
provide a way to get information about a function caller.
The documentation links for these several functions:
- http://docs.studygolang.com/pkg/runtime/#Caller
- http://docs.studygolang.com/pkg/runtime/#Callers
- http://docs.studygolang.com/pkg/runtime/#FuncForPC
This article mainly describes the use of these functions.
runtime.Caller
Usage of
The signature of the function is as follows:
func runtime.Caller(skip int) (pc uintptr, file string, line int, ok bool)
runtime.Caller
Returns the goroutine
function call information on the current stack. There is pc
information such as the current value and the called File and line number. If the information is not available, the ok
value returned is false
.
The input parameter skip
is the number of stack frames to skip, 0
or the caller if it is runtime.Caller
.
Note: Because of historical reasons, runtime.Caller
and runtime.Callers
in the skip
meaning is not the same, will be discussed later.
Here is a simple example that prints the stack frame information for a function call:
func main() { for skip := 0; ; skip++ { pc, file, line, ok := runtime.Caller(skip) if !ok { break } fmt.Printf("skip = %v, pc = %v, file = %v, line = %v\n", skip, pc, file, line) } // Output: // skip = 0, pc = 4198453, file = caller.go, line = 10 // skip = 1, pc = 4280066, file = $(GOROOT)/src/pkg/runtime/proc.c, line = 220 // skip = 2, pc = 4289712, file = $(GOROOT)/src/pkg/runtime/proc.c, line = 1394}
skip = 0
this is the function of the current file ("Caller.go") main.main
, along with the corresponding line number. The extraneous code is omitted here, so the line number of the output and the location of the page display are somewhat different.
The additional skip = 1
and skip = 2
also correspond to 2 function calls respectively. By looking runtime/proc.c
up the code of the file, we can know that the corresponding function is the runtime.main
and runtime.goexit
.
After finishing, you can know that the boot sequence of Go's normal program is as follows:
runtime.goexit
For the real function entry (not main.main
)
- Then
runtime.goexit
call the runtime.main
function
- Final
runtime.main
call to user-written main.main
functions
runtime.Callers
Usage of
The signature of the function is as follows:
func runtime.Callers(skip int, pc []uintptr) int
runtime.Callers
Functions and runtime.Caller
functions have a similar name (one suffix s
), but the function's parameter/return value differs greatly from the parameter's meaning.
runtime.Callers
Fill in the slices with the program counter on the go stacks that invokes it pc
. The parameter skip
is the number of stack frames to skip before starting to record in the PC, and if 0 represents runtime.Callers
its own stack frame, 1 represents the caller's stack frame. The function returns a write to pc
the The number of items in the slice (limited by the capacity of the tile).
Here is runtime.Callers
An example for outputting information for each stack frame pc
:
func main() { pc := make([]uintptr, 1024) for skip := 0; ; skip++ { n := runtime.Callers(skip, pc) if n <= 0 { break } fmt.Printf("skip = %v, pc = %v\n", skip, pc[:n]) } // Output: // skip = 0, pc = [4304486 4198562 4280114 4289760] // skip = 1, pc = [4198562 4280114 4289760] // skip = 2, pc = [4280114 4289760] // skip = 3, pc = [4289760]}
The output new pc
length and skip
size have inverse correlation. skip = 0
runtime.Callers
information for itself.
This example outputs a stack frame more than the previous example, because runtime.Callers
there is more information on a stack frame (the previous example is no runtime.Caller
information ( Note: no s
suffix )).
So runtime.Callers
runtime.Caller
What are the correlations and differences?
runtime.Callers
and runtime.Caller
the similarities and differences
Because the previous 2 examples are different programs, the output pc
values are not reference. Now let's look at how the output in the same example looks:
func Main () {for Skip: = 0;; skip++ {pc, file, line, OK: = runtime. Caller (Skip) if!ok {break} fmt. Printf ("skip =%v, PC =%v, file =%v, line =%v\n", skip, PC, file, line)}//Output://skip = 0, PC = 4198456 , file = caller.go, line = ten//skip = 1, PC = 4280962, File = $ (goroot)/src/pkg/runtime/proc.c, line =//Skip = 2, PC = 4290608, File = $ (goroot)/src/pkg/runtime/proc.c, line = 1394 PC: = Make ([]uintptr, 1024x768) for Skip: = 0; ; skip++ {n: = runtime. Callers (skip, PC) if n <= 0 {break} fmt. Printf ("skip =%v, PC =%v\n", skip, Pc[:n])}//Output://skip = 0, PC = [4305334 4198635 4280962 4290608] Skip = 1, PC = [4198635 4280962 4290608]//skip = 2, PC = [4280962 4290608]//skip = 3, PC = [4290608]}
/pre>
For example, the output can be found, 4280962
and 4290608
two pc
values are the same. They correspond runtime.main
and runtime.goexit
function respectively.
runtime.Caller
The output is not the same as the 4198456
runtime.Callers
output 4198635
. This is because the call locations of the two functions are not the same, resulting in a value that is pc
not exactly the same.
The last is runtime.Callers
a multiple output 4305334
value, corresponding to runtime.Callers
the internal call location.
Because the Go language (Go1.2) uses a segmented stack, the pc
size relationship between the different is not obvious.
runtime.FuncForPC
The use of
The signature of the function is as follows:
func runtime.FuncForPC(pc uintptr) *runtime.Funcfunc (f *runtime.Func) FileLine(pc uintptr) (file string, line int)func (f *runtime.Func) Entry() uintptrfunc (f *runtime.Func) Name() string
Returns runtime.FuncForPC
pc
the function that contains the given address, if it is not valid pc
nil
.
runtime.Func.FileLine
Returns the pc
corresponding source file name and line number. The description of the installation document, if pc
not within the function frame range, the result is indeterminate.
runtime.Func.Entry
The address of the corresponding function. runtime.Func.Name
returns the name of the function.
Here's runtime.FuncForPC
an example:
Func Main () {for Skip: = 0;; skip++ {PC, _, _, OK: Runtime. Caller (Skip) if!ok {break} P: = Runtime. FUNCFORPC (PC) file, line: = P.fileline (0) fmt. Printf ("skip =%v, PC =%v\n", skip, PC) FMT. Printf ("file =%v, line =%d\n", file, line) FMT. Printf ("entry =%v\n", P.entry ()) fmt. Printf ("name =%v\n", P.name ())}//Output://skip = 0, PC = 4198456//File = Caller.go, line = 8// Entry = 4198400//name = Main.main//skip = 1, PC = 4282882//File = $ (goroot)/SRC/PKG/RUNTIME/PROC.C, line = 179//Entry = 4282576//name = Runtime.main//skip = 2, PC = 4292528//File = $ (goroot)/src/p KG/RUNTIME/PROC.C, line = 1394//Entry = 4292528//name = Runtime.goexit PC: = Make ([]uintptr, 1024x768) for Skip: = 0;; skip++ {n: = runtime. Callers (skip, PC) if n <= 0 {break} fmt. Printf ("skip =%v, PC =%v\ n ", skip, Pc[:n]) for J: = 0; J < N; J + + {p: = runtime. FUNCFORPC (Pc[j]) file, line: = P.fileline (0) fmt. Printf ("skip =%v, PC =%v\n", skip, Pc[j]) fmt. Printf ("file =%v, line =%d\n", file, line) FMT. Printf ("entry =%v\n", P.entry ()) fmt. Printf ("name =%v\n", P.name ())} break}//Output://skip = 0, PC = [4307254 4198586 4282882 4292528]//skip = 0, PC = 4307254//File = $ (goroot)/src/pkg/runtime/runtime.c, line = 315//Entry = 4307168//name = runtime. Callers//skip = 0, PC = 4198586//File = Caller.go, line = 8//Entry = 4198400//name = Mai N.main//skip = 0, PC = 4282882//File = $ (goroot)/src/pkg/runtime/proc.c, line = 179//Entry = 4282 576//Name = Runtime.main//skip = 0, PC = 4292528//File = $ (goroot)/src/pkg/runtime/proc.c, line = 1394//Entry = 4292528//name = Runtime.goexit}
Depending on the test, if it is not valid pc
(for example 0
), the runtime.Func.FileLine
start line number of the current function is generally output. However, in practice, it is common runtime.Caller
to get the file name and line number information, runtime.Func.FileLine
seldom used (how to get pc
parameters independently?).
Custom CallerName
functions
Based on the previous functions, we can easily customize a CallerName
function. The function CallerName
returns user-friendly information such as the caller's letter/file name/line number.
The function is implemented as follows:
func CallerName(skip int) (name, file string, line int, ok bool) { var pc uintptr if pc, file, line, ok = runtime.Caller(skip + 1); !ok { return } name = runtime.FuncForPC(pc).Name() return}
Where the call is executed runtime.Caller
, the parameter is skip + 1
used to counteract CallerName
the call of the function itself.
Here is an CallerName
example of the output based on:
func main() { for skip := 0; ; skip++ { name, file, line, ok := CallerName(skip) if !ok { break } fmt.Printf("skip = %v\n", skip) fmt.Printf(" file = %v, line = %d\n", file, line) fmt.Printf(" name = %v\n", name) } // Output: // skip = 0 // file = caller.go, line = 19 // name = main.main // skip = 1 // file = $(GOROOT)/src/pkg/runtime/proc.c, line = 220 // name = runtime.main // skip = 2 // file = $(GOROOT)/src/pkg/runtime/proc.c, line = 1394 // name = runtime.goexit}
This makes it easy to output the information of the function caller.
Types of functions in the Go language
In the go language, in addition to the normal function calls defined by the language, there are different function invocation types such as the/init function/global variable initialization of the closure function.
To facilitate testing of different types of function calls, we wrap a PrintCallerName
function. This function is used to output the caller's information.
func PrintCallerName(skip int, comment string) bool { name, file, line, ok := CallerName(skip + 1) if !ok { return false } fmt.Printf("skip = %v, comment = %s\n", skip, comment) fmt.Printf(" file = %v, line = %d\n", file, line) fmt.Printf(" name = %v\n", name) return true}
Then write the following test code (function closure call/global variable initialization/init function, etc.):
var a = PrintCallerName(0, "main.a")var b = PrintCallerName(0, "main.b")func init() { a = PrintCallerName(0, "main.init.a")}func init() { b = PrintCallerName(0, "main.init.b") func() { b = PrintCallerName(0, "main.init.b[1]") }()}func main() { a = PrintCallerName(0, "main.main.a") b = PrintCallerName(0, "main.main.b") func() { b = PrintCallerName(0, "main.main.b[1]") func() { b = PrintCallerName(0, "main.main.b[1][1]") }() b = PrintCallerName(0, "main.main.b[2]") }()}
The output results are as follows:
// Output:// skip = 0, comment = main.a// file = caller.go, line = 8// name = main.init// skip = 0, comment = main.b// file = caller.go, line = 9// name = main.init// skip = 0, comment = main.init.a// file = caller.go, line = 12// name = main.init·1// skip = 0, comment = main.init.b// file = caller.go, line = 16// name = main.init·2// skip = 0, comment = main.init.b[1]// file = caller.go, line = 18// name = main.func·001// skip = 0, comment = main.main.a// file = caller.go, line = 23// name = main.main// skip = 0, comment = main.main.b// file = caller.go, line = 24// name = main.main// skip = 0, comment = main.main.b[1]// file = caller.go, line = 26// name = main.func·003// skip = 0, comment = main.main.b[1][1]// file = caller.go, line = 28// name = main.func·002// skip = 0, comment = main.main.b[2]// file = caller.go, line = 30// name = main.func·003
Observing the output results, we can find the following laws:
- The initialization of a global variable is called by a
main.init
function
- The custom
init
function has a numeric suffix that is numbered according to the order in which it appears. For example, main.init·1
and main.init·2
so on.
- The closure function is
main.func·001
named in the format and the position of the end of the installation closure definition is numbered sequentially.
For example, the following global variables initialize the caller as a main.init
function:
var a = PrintCallerName(0, "main.a")var b = PrintCallerName(0, "main.b")
The following two init
functions correspond respectively according to the order main.init·1
of occurrence and main.init·2
:
func init() { // main.init·1 //}func init() { // main.init·2 //}
The following three closures are based on the defined ending order 001
, respectively/ 002
/ 003
:
func init() { func(){ // }() // main.func·001}func main() { func() { func(){ // }() // main.func·002 }() // main.func·003}
Because of the existence of these special function invocation methods, we need to further refine the CallerName
function.
Improved CallerName
functions
Two special types of calls are init
class function calls and closure function calls.
The improved CallerName
function init
uniformly handles the caller of the class function as a init
function. Call the closure function This process is the name of the function of the caller.
// caller types:// runtime.goexit// runtime.main// main.init// main.init·1// main.main// main.func·001// code.google.com/p/gettext-go/gettext.TestCallerName// ...func CallerName(skip int) (name, file string, line int, ok bool) { var ( reInit = regexp.MustCompile(`init·\d+$`) // main.init·1 reClosure = regexp.MustCompile(`func·\d+$`) // main.func·001 ) for { var pc uintptr if pc, file, line, ok = runtime.Caller(skip + 1); !ok { return } name = runtime.FuncForPC(pc).Name() if reInit.MatchString(name) { name = reInit.ReplaceAllString(name, "init") return } if reClosure.MatchString(name) { skip++ continue } return } return}
Treatment of the idea:
- If it is a
init
type of function call (matching regular expression "init·\d+$"
), return directly as a init
function fan
- If it is a
func
closure type (match regular expression "func·\d+$"
), skip the current stack frame and continue with recursion
- Returns the normal function call type
CallerName
The shortcomings of the function
The following code is available:
func init() { var _ = myInit("1")}func main() { var _ = myInit("2")}var myInit = func(name string) { b = PrintCallerName(0, name + ":main.myInit.b")}
myInit
is a global variable and is assigned a closure function. The init
main
myInit
result of this closure function output is then called in the and function separately
Vary depending on the calling environment.
Intuitively, it is myInit
best to output function names when the closure functions are executed main.myInit
. But it's main.myInit
just a variable bound to a closure function, and the real name of the closure is main.func·???
. This name is not available at run time main.myInit
.
Therefore, the functions used internally in Gettext-go are callerName
main.func·???
treated uniformly as main.func
the context of the gettext.Gettext
translation function.
The Gettext-go callerName
function is implemented here: Caller.go. test file here: Caller_test.go.
Different Go program START process
Based on the function caller information, it is easy to verify the program initiation process in various environments.
We need to create a separate caller
directory with three test codes.
caller/main.go
Main program:
package mainimport ( "fmt" "regexp" "runtime")func main() { _ = PrintCallerName(0, "main.main._")}func PrintCallerName(skip int, comment string) bool { // 实现和前面的例子相同}func CallerName(skip int) (name, file string, line int, ok bool) { // 实现和前面的例子相同}
caller/main_test.go
Test files for the main program (same as in a main
package):
package mainimport ( "fmt" "testing")func TestPrintCallerName(t *testing.T) { for skip := 0; ; skip++ { name, file, line, ok := CallerName(skip) if !ok { break } fmt.Printf("skip = %v, name = %v, file = %v, line = %v\n", skip, name, file, line) } t.Fail()}
caller/example_test.go
The caller of the main program's package (in the new main_test
package):
package main_testimport ( myMain "." "fmt")func Example() { for skip := 0; ; skip++ { name, file, line, ok := myMain.CallerName(skip) if !ok { break } fmt.Printf("skip = %v, name = %v, file = %v, line = %v\n", skip, name, file, line) } // Output: ?}
Then go to the caller
directory, run go run test
can get the following output results:
Skip = 0, name = caller. Testprintcallername, file = caller/main_test.go, line = 10skip = 1, name = Testing.trunner, File = $ (goroot)/src/pkg/testi Ng/testing.go, line = 391skip = 2, name = runtime.goexit, File = $ (goroot)/src/pkg/runtime/proc.c, line = 1394---fail:te Stprintcallername (0.00 seconds)---fail:example (2.0001ms) got:skip = 0, name = caller_test. Example, file = caller/example_test.go, line = 10skip = 1, name = testing.runexample, File = $ (goroot)/src/pkg/testing/exa Mple.go, line = 98skip = 2, name = testing. Runexamples, File = $ (goroot)/src/pkg/testing/example.go, line = 36skip = 3, name = testing. Main, File = $ (goroot)/src/pkg/testing/testing.go, line = 404skip = 4, name = Main.main, File = $ (TEMP)/go-build365033523/ Caller/_test/_testmain.go, line = 51skip = 5, name = Runtime.main, File = $ (goroot)/src/pkg/runtime/proc.c, line = 220skip = 6, name = runtime.goexit, File = $ (goroot)/src/pkg/runtime/proc.c, line = 1394want:? Failexit Status 1FAIL caller 0.254s
Analyzing the output data we can see that the start-up process for the test code and the example code is not the same as the normal program flow.
To test the startup process of the code:
runtime.goexit
or the entrance.
- But
runtime.goexit
runtime.main
instead of calling the function, it calls the testing.tRunner
function
testing.tRunner
Functions go test
are generated by commands and are used to execute individual test functions
Example code to start the process:
runtime.goexit
or the entrance.
- Then
runtime.goexit
call the runtime.main
function
- The final
runtime.main
call to go test
the command-generated main.main
function , in the _test/_testmain.go
file
- Then call
testing.Main
, change function to execute each example function
In addition, we can see from this example that the packages we write ourselves with main.main
main
can also be imported by other packages. But the function in the package after the import of the other package main
main
is no longer a main.main
function. So the entrance to the program is not written by itself. The c22/> function.
Summarize
The Go language runtime
Pack's runtime.Caller
/ runtime.Callers
/functions, runtime.FuncForPC
while seemingly simple, are very powerful.
These functions can not only solve some practical engineering problems (such as the context information used in gettext-go to get the translation), but also are well suited for debugging and analyzing the runtime information of various go programs.