A friend asked me to read the source code, how to debug? This time we will briefly take a look at how to compile and debug the Go Runtime source code, interested friends can manually operate.

Compile and modify Go source code for debugging

First download compilation

Yum -y install GCC yum -y install GCC

Then download the go source:

[root@localhost src]# git clone https://github.com/golang/go.git

Go to the SRC directory and execute
[root@localhost src]# ./all.bash

[root@localhost src]# ./all.bash
Building Go cmd/dist using /usr/local/go. (go1.15.8 Linux /amd64) Building go Toolchain1 using /usr/local/go.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building packages and commands for linux/amd64.
...

##### API check
Go version is "go1.15.10", ignoring -next /data/learn/go/api/next.txt

ALL TESTS PASSED
---
Installed Go for linux/amd64 in /data/learn/go
Installed commands in /data/learn/go/bin
*** You need to add /data/learn/go/bin to your PATH.
Copy the code

Compiled go and gofmt are in the bin directory:

[root@localhost src]# cd .. /bin/
[root@localhost bin]# ls
go  gofmt
Copy the code

To prevent conflicts between our modified GO and the previously installed GO, create a myGo soft connection pointing to the modified GO:

[root@localhost bin]# mkdir -p ~/mygo/bin

[root@localhost bin]# cd ~/testgo/bin

[root@localhost bin]# ln -sf /data/learn/go/bin/go mygo
Copy the code

Finally, add ~/testgo/bin to PATH:

[root@localhost bin]# vim /etc/profile

export PATH=$PATH:/data/learn/mygo/bin

source /etc/profile
Copy the code

Run myGo and check out the version:

[root@localhost bin]# mygo versionGo version go1.15.10 Linux/amd64Copy the code

GODEBUG

When we modify the source code, we can use the GODEBUG variable to print debugging information.

schedtrace

schedtrace: setting schedtrace=X causes the scheduler to emit a single line to standard error every X milliseconds, summarizing the scheduler state.

Schedtrace =X indicates that the run time prints a scheduler summary line every X milliseconds to the standard ERR output.

Setting schedTrace =1000 prints a scheduler profile every second after the program starts:

[root@localhost gotest]# GOMAXPROCS=1 GODEBUG=schedtrace=1000 mygo run main.go SCHED 0ms: gomaxprocs=1 idleprocs=0 threads=4 spinningthreads=0 idlethreads=0 runqueue=0 [2]
# command-line-arguments
SCHED 0ms: gomaxprocs=1 idleprocs=0 threads=3 spinningthreads=0 idlethreads=1 runqueue=0 [2]
SCHED 0ms: gomaxprocs=1 idleprocs=0 threads=3 spinningthreads=0 idlethreads=0 runqueue=0 [2]
Copy the code

0ms: number of milliseconds since the program began;

Gomaxprocs =1: indicates the number of configured processors.

Idleprocs =0: number of idle P (processors);

Threads =3: Number of threads managed at runtime, currently 6;

Spinningthreads =0: number of threads to execute preemption;

Idlethreads =1: number of idlethreads;

Runqueue =0: The number of goroutines in the global runqueue;

[2] : Number of goroutines in the local RUN queue, indicating that two are waiting;

scheddetail

scheddetail: setting schedtrace=X and scheddetail=1 causes the scheduler to emit detailed multiline info every X milliseconds, describing state of the scheduler, processors, threads and goroutines.

Schedtrace and schedDetail are set together to provide details for processor P, thread M, and Goroutine G.

Such as:

[root@localhost gotest]# GOMAXPROCS=1 GODEBUG=schedtrace=1000,scheddetail=1 mygo run main.go 
SCHED 0ms: gomaxprocs=1 idleprocs=0 threads=4 spinningthreads=0 idlethreads=0 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0
  P0: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=2 gfreecnt=0 timerslen=0
  M3: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
  M2: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=2 dying=0 spinning=false blocked=false lockedg=-1
  M1: p=-1 curg=17 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=17
  M0: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=1
  G1: status=1(chan receive) m=-1 lockedm=0
  G17: status=6() m=1 lockedm=1
  G2: status=1() m=-1 lockedm=-1
  G3: status=1() m=-1 lockedm=-1
  G4: status=4(GC scavenge wait) m=-1 lockedm=-1
...
Copy the code

Let’s first look at what G stands for:

  • Status: indicates the running status of G.

  • M: belongs to which M;

  • Lockedm: is there a lock M;

The running state of G has the following meanings:

const (
	// Has just been allocated and has not yet been initialized
	_Gidle = iota / / 0
	// No execution code, no stack ownership, stored in the run queue
	_Grunnable / / 1
	// Can execute code, has the ownership of the stack, is given to the kernel thread M and processor P
	_Grunning / / 2
	// System call executing, owning stack, no user code executing,
	// The kernel thread M is assigned but is not on the run queue
	_Gsyscall / / 3
	// User code is not executed and is not on the run queue due to run-time blocking,
	// But it may exist on the Channel wait queue
	_Gwaiting / / 4
	// Indicates that the current goroutine is not in use, no code is being executed, and there may be an allocated stack
	_Gdead / / 6
	// The stack is being copied, no code is being executed, not on the run queue
	_Gcopystack / / 8
	// Blocked due to preemption, not executing user code and not on the run queue, waiting to wake up
	_Gpreempted / / 9
	// GC is scanning stack space, no code is being executed and can coexist with other states
	_Gscan          = 0x1000...).Copy the code

If you don’t understand, you need to Go and see my scheduling cycle analysis: the explanation scheduling loop source code of the Go language www.luozhiyun.com/archives/44…

M stands for:

M0: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=1
Copy the code
  • P: belongs to which P;
  • Curg: which G is currently in use;
  • Mallocing: Whether memory is being allocated;
  • Throwing: Whether an exception is thrown;
  • Preemptoff: keep curg running on m if not empty string (“”);
  • Runqsize: the number of G’s in the runqueue;
  • Spinning: Is grabbing G;

What P stands for:

P0: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=2 gfreecnt=0 timerslen=0
Copy the code
  • Status: indicates the running status of P.
  • Schedtick: P times of scheduling.
  • Syscalltick: P Number of system calls.
  • M: Belongs to which M.
  • Runqsize: the number of G’s in the runqueue.
  • Gfreecnt: available G (in Gdead state).

The status of P indicates the following:

const ( 
	// indicates that P is not running user code or scheduler
	_Pidle = iota 
	// held by thread M and executing user code or scheduler
	_Prunning 
	// No user code is executed, the current thread is stuck in a system call
	_Psyscall
	// held by thread M, current processor stopped due to garbage collection STW
	_Pgcstop 
	// The current processor is not in use
	_Pdead
)
Copy the code

Modify compile

Let’s say we modify the channel and add a print:

func makechan(t *chantype, size int) *hchan{...if debug.schedtrace > 0 {
		print("bearluo makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, "\n")}...return c
}
Copy the code

Then go to the SRC directory and recompile:

[root@localhost src]# ./make.bash
Building Go cmd/dist using /usr/local/go. (go1.15.8 Linux /amd64) Building go Toolchain1 using /usr/local/go.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building packages and commands for linux/amd64.
---
Installed Go for linux/amd64 in /data/learn/go
Installed commands in /data/learn/go/bin
Copy the code

Write a simple demo (no simpler) :

package main

import (
	"fmt"
)

func main(a) {

	c := make(chan int.10)
	fmt.Println(c)
}
Copy the code

Perform:

[root@localhost gotest]# GODEBUG=schedtrace=1000 mygo run main.go 
bearluo makechan: chan=0xc000036070; elemsize=8; dataqsiz=2
SCHED 0ms: gomaxprocs=16 idleprocs=13 threads=6 spinningthreads=1 idlethreads=0 runqueue=0 [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
bearluo makechan: chan=0xc00010e000; elemsize=1; dataqsiz=0
bearluo makechan: chan=0xc00010e060; elemsize=0; dataqsiz=0
bearluo makechan: chan=0xc00010e180; elemsize=0; dataqsiz=0
bearluo makechan: chan=0xc0006a8000; elemsize=1; dataqsiz=35
bearluo makechan: chan=0xc0003f6660; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc000226540; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc0001381e0; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc0005043c0; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc00049c420; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc000594300; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc000090360; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc000220000; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc00075e000; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc000138840; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc000226780; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc0003ea420; elemsize=16; dataqsiz=2
bearluo makechan: chan=0xc00049d320; elemsize=16; dataqsiz=1
...
Copy the code

Delve debugging

Currently, Go supports several debuggers including GDB, LLDB and Delve. Delve is the only debugging tool designed and developed specifically for the Go language. Delve itself is developed in the Go language and supports the Windows platform as well. In this section we briefly explain how to debug the Go Runtime code and assembler based on Delve.

Project address: github.com/go-delve/de…

Installation:

go get github.com/go-delve/delve/cmd/dlv
Copy the code

Start by writing an example of test.go:

package main

import "fmt"

type A struct {
	test string
}
func main(a) {
	a := new(A)
	fmt.Println(a)
}
Copy the code

Then run the DLV debug command to go to the directory where the package resides.

PS C:\document\code\test_go\src> dlv debug
Type 'help' for list of commands.
Copy the code

You can then use the break command to set a breakpoint on the main method of the main package:

(dlv) break main.main
Breakpoint 1 set at 0x4bd30a for main.main() c:/document/code/test_go/src/test.go:8
Copy the code

View all breakpoints that have been set by breakpoints:

(dlv) breakpoints
Breakpoint runtime-fatal-throw at 0x4377e0 for runtime.fatalthrow() c:/software/go/src/runtime/panic.go:1162 (0)
Breakpoint unrecovered-panic at 0x437860 for runtime.fatalpanic() c:/software/go/src/runtime/panic.go:1189 (0)
        print runtime.curg._panic.arg
Breakpoint 1 at 0x4bd30a for main.main() c:/document/code/test_go/src/test.go:8 (0)
Copy the code

Run the program to the next breakpoint with the continue command:

(dlv) continue
> main.main() c:/document/code/test_go/src/test.go:8 (hits goroutine(1) :1 total:1) (PC: 0x4bd30a)
     3: import "fmt"
     4:
     5: type A struct {
     6:         test string
     7: }
=>   8: func main() {
     9:         a := new(A)
    10:         fmt.Println(a)
    11:}12:
    13:
Copy the code

Disassemble the assemble code for main:

(dlv) disassemble
TEXT main.main(SB) C:/document/code/test_go/src/test.go
        test.go:8       0x4bd2f0        65488b0c2528000000      mov rcx, qword ptr gs:[0x28]
        test.go:8       0x4bd2f9        488b8900000000          mov rcx, qword ptr [rcx]
        test.go:8       0x4bd300        483b6110                cmp rsp, qword ptr [rcx+0x10]
        test.go:8       0x4bd304        0f8697000000            jbe 0x4bd3a1
=>      test.go:8       0x4bd30a*       4883ec78                sub rsp, 0x78
        test.go:8       0x4bd30e        48896c2470              mov qword ptr [rsp+0x70].rbp
        test.go:8       0x4bd313        488d6c2470              lea rbp, ptr [rsp+0x70]
        test.go:9       0x4bd318        488d0581860100          lea rax, ptr [__image_base__+874912]
        test.go:9       0x4bd31f        48890424                mov qword ptr [rsp], rax
        test.go:9       0x4bd323        e8e800f5ff              call $runtime.newobject
        test.go:9       0x4bd328        488b442408              mov rax, qword ptr [rsp+0x8]
        test.go:9       0x4bd32d        4889442430              mov qword ptr [rsp+0x30], rax
        test.go:10      0x4bd332        4889442440              mov qword ptr [rsp+0x40], rax
        test.go:10      0x4bd337        0f57c0                  xorps xmm0, xmm0
        test.go:10      0x4bd33a        0f11442448              movups xmmword ptr [rsp+0x48], xmm0
        test.go:10      0x4bd33f        488d442448              lea rax, ptr [rsp+0x48]
        test.go:10      0x4bd344        4889442438              mov qword ptr [rsp+0x38], rax
        test.go:10      0x4bd349        8400                    test byte ptr [rax], al
        test.go:10      0x4bd34b        488b4c2440              mov rcx, qword ptr [rsp+0x40]
        test.go:10      0x4bd350        488d15099f0000          lea rdx, ptr [__image_base__+815712]
        test.go:10      0x4bd357        4889542448              mov qword ptr [rsp+0x48], rdx
        test.go:10      0x4bd35c        48894c2450              mov qword ptr [rsp+0x50], rcx
        test.go:10      0x4bd361        8400                    test byte ptr [rax], al
        test.go:10      0x4bd363        eb00                    jmp 0x4bd365
        test.go:10      0x4bd365        4889442458              mov qword ptr [rsp+0x58], rax
        test.go:10      0x4bd36a        48c744246001000000      mov qword ptr [rsp+0x60].0x1
        test.go:10      0x4bd373        48c744246801000000      mov qword ptr [rsp+0x68].0x1
        test.go:10      0x4bd37c        48890424                mov qword ptr [rsp], rax
        test.go:10      0x4bd380        48c744240801000000      mov qword ptr [rsp+0x8].0x1
        test.go:10      0x4bd389        48c744241001000000      mov qword ptr [rsp+0x10].0x1
        test.go:10      0x4bd392        e869a0ffff              call $fmt.Println
        test.go:11      0x4bd397        488b6c2470              mov rbp, qword ptr [rsp+0x70]
        test.go:11      0x4bd39c        4883c478                add rsp, 0x78
        test.go:11      0x4bd3a0        c3                      ret
        test.go:8       0x4bd3a1        e82a50faff              call $runtime.morestack_noctxt
        .:0             0x4bd3a6        e945ffffff              jmp $main.main
Copy the code

Now we can use break to call runtime. newObject:

(dlv) break runtime.newobject
Breakpoint 2 set at 0x40d426 for runtime.newobject() c:/software/go/src/runtime/malloc.go:1164
Copy the code

Type continue to skip to the location of the breakpoint:

(dlv) continue
> runtime.newobject() c:/software/go/src/runtime/malloc.go:1164 (hits goroutine(1) :1 total:1) (PC: 0x40d426)
Warning: debugging optimized function
  1159:}1160:
  1161: // implementation of new builtin
  1162: // compiler (both frontend and SSA backend) knows the signature
  1163: // of this function= >1164: func newobject(typ *_type) unsafe.Pointer {
  1165:         return mallocgc(typ.size, typ, true)
  1166:}1167:
  1168: //go:linkname reflect_unsafe_New reflect.unsafe_New
  1169: func reflect_unsafe_New(typ *_type) unsafe.Pointer {
Copy the code

Print to view tyP data:

(dlv) print typ
*runtime._type {size: 16, ptrdata: 8, hash: 875453117, tflag: tflagUncommon|tflagExtraStar|tflagNamed (7), align: 8, fieldAlign: 8, kind: 25, equal: runtime.strequal, gcdata: *1, str: 5418, ptrToThis: 37472}
Copy the code

You can see that the size printed here is 16bytes, because we only have A string field in our A structure.

After entering the mallocGC method, view the function parameters and local variables using the args and locals commands:

(dlv) args
size = (unreadable could not find loclist entry at 0x8b40 for address 0x40ca73)
typ = (*runtime._type)(0x4d59a0)
needzero = true
~r3 = (unreadable empty OP stack)
(dlv) locals
(no locals)
Copy the code

Reference

Installing Go from source golang.org/doc/install…

The Scheduler Tracing In the Go www.ardanlabs.com/blog/2015/0…

GODEBUG golang.org/pkg/runtime…

Watch schedule tracking with GODEBUG eddycjy.com/posts/go/to…