概要
在工作中有时要用到go dlv 工具做代码调试或追踪的事情,本文专门记录下相关指令的用法。
dlv调试代码命令主要有三个:
debug:调试未编译源码;
exec:调试编译后的但未运行的可执行文件;
attach:调试正在运行的程序。
但三者进入调试阶段后的操作都一样的。
示例调试代码如下:
//main.go文件内容
1 package main
2
3 import (
4 "fmt"
5 "runtime"
6 "time"
7 )
8
9 func deadloop() {
10 for {
11 }
12 }
13
14 func worker() {
15 for {
16 fmt.Println("worker is running")
17 time.Sleep(time.Second * 1)
18 }
19 }
20
21 func main() {
22 fmt.Printf("There are %d cores.\n", runtime.NumCPU())
23 readFileLine()
24 x:=9
25 go worker()
26
27 go deadloop()
28
29 i := 3
30 for {
31 fmt.Printf("main is running, i=%d\n", i)
32 i--
33 if i == x {
34 runtime.GC()
35 }
36
37 time.Sleep(time.Second * 1)
38 }
39 }
//file.go文件内容
1 package main
2 import (
3 "fmt"
4 //"gopkg.in/yaml.v2"
5 "io"
6 "os"
7 //"unsafe"
8 )
9 func readFileLine() {
10 file, err := os.Open("/home/fpf/public/a.txt")
11 defer file.Close()
12 if err != nil {
13 fmt.Println("open file err", err.Error())
14 return
15 }
16 // 使用bufio读取
17 //r := bufio.NewReader(file)
18 data := make([]byte, 3)
19 for {
20 //按字节读
21 _, err = file.Read(data)
22 // 分行读取文件 ReadLine返回单个行,不包括行尾字节(\n 或 \r\n)
23 //data, _, err := r.ReadLine()
24
25 // 以分隔符形式读取,比如此处设置的分割符是\n,则遇到\n就返回,且包括\n本身 直接返回字符串
26 //str, err := r.ReadString('\n')
27
28 // 以分隔符形式读取,比如此处设置的分割符是\n,则遇到\n就返回,且包括\n本身 直接返回字节数数组
29 //data, err := r.ReadBytes('\n')
30
31 // 读取到末尾退出
32 if err == io.EOF {
33 fmt.Println("read over!!!")
34 break
35 }
36
37 if err != nil {
38 fmt.Println("read err", err.Error())
39 break
40 }
41
42 // 打印出内容
43 fmt.Printf("%s\n", data)
44 }
45 }
调试的服务器信息:Centos Linux 7 ,CPU AMD x86_64
Go version 1.23.4
指令说明
[root@name goschedule]# dlv debug main.go file.go
(dlv) help
The following commands are available:
Running the program:
call -------------------------------- 恢复进程,注入一个函数调用(还在实验阶段!!) 本人没用过
continue (alias: c) ----------------- 运行到断点或程序终止
next (alias: n) --------------------- 转到下行代码
next-instruction (alias: ni | nexti) Single step a single cpu instruction, skipping function calls
rebuild ----------------------------- Rebuild the target executable and restarts it. It does not work if the executable was not built by delve.
restart (alias: r) ------------------ 重启进程,调试进度重新开始.
step (alias: s) --------------------- 单步执行,遇到函数调用,会进入函数,并停止
step-instruction (alias: si | stepi) Single step a single cpu instruction. 单步执行提条cpu指令
stepout (alias: so) ----------------- 跳出当前函数,回到入口处.
Manipulating breakpoints:
break (alias: b) ------- 设置一个断点,格式:b 文件路径:行数,比如 b ./main.go:31
breakpoints (alias: bp) 打印活跃的断点信息.
clear ------------------ 删除某个断点,格式:clear 断点ID
clearall --------------- 删除所有断点
condition (alias: cond) Set breakpoint condition.设置断点条件,只有满足条件时断点才会停止,比如:cond 2 i==-1
on --------------------- Executes a command when a breakpoint is hit.在命中断点时执行命令,比如:on 2 print i,即遇到断点2时打印变量i的值
toggle ----------------- Toggles on or off a breakpoint.停用/启用某个断点,格式:toggle 断点ID
trace (alias: t) ------- Set tracepoint. 设置一个追踪点,和断点不同,执行到追踪点程序不会暂定,只是打印一定信息后继续执行,格式: t 文件路径:行数
watch ------------------ Set watchpoint. 设置一个监听点,监听某个地址被读写了就会暂停程序 格式:watch [-r|-w|-rw] <expr>,但是**目前本人没搞明白这个地址怎么设置,哪位大神指导评论区说一下**
Viewing program variables and memory:
args ----------------- 打印当前函数的参数
display -------------- Print value of an expression every time the program stops.
examinemem (alias: x) Examine raw memory at the given address.
locals --------------- 打印当前函数的本地(局部)变量
print (alias: p) ----- 计算一个表达式,可以打印某个变量的值,格式: p 变量名
regs ----------------- Print contents of CPU registers. 打印CPU寄存器的内容
set ------------------ 改变一个变量的值,格式:set 变量名=xxx
vars ----------------- 打印当前包的所有变量
whatis --------------- 打印表达式的类型,可以打印某个变量的类型,格式:whatis int32(x)
Listing and switching between threads and goroutines:
goroutine (alias: gr) -- Shows or changes current goroutine 打印当前所在协程信息,或切换指定协程,格式:gr 协程id
goroutines (alias: grs) List program goroutines. 打印所有协程信息
thread (alias: tr) ----- Switch to the specified thread. 切换指定线程
threads ---------------- Print out info for every traced thread. 打印所有线程信息
Viewing the call stack and selecting frames:
deferred --------- Executes command in the context of a deferred call. 在延迟调用的上下文中执行命令
down ------------- Move the current frame down. 将当前帧向下移动
frame ------------ Set the current frame, or execute command on a different frame. 设置当前帧,或在不同的帧上执行命令,格式:frame 帧id,这个id通过 bt打印就可以看到
stack (alias: bt) Print stack trace. 打印堆栈跟踪信息
up --------------- Move the current frame up. 将当前帧向上移动
Other commands:
config --------------------- Changes configuration parameters.
disassemble (alias: disass) Disassembler.打印汇编代码
dump ----------------------- Creates a core dump from the current process state
edit (alias: ed) ----------- Open where you are in $DELVE_EDITOR or $EDITOR
exit (alias: quit | q) ----- Exit the debugger.
funcs ---------------------- Print list of functions.
help (alias: h) ------------ Prints the help message.
libraries ------------------ List loaded dynamic libraries
list (alias: ls | l) ------- Show source code. 打印部分代码
packages ------------------- Print list of packages.
source --------------------- Executes a file containing a list of delve commands
sources -------------------- Print list of source files.
target --------------------- Manages child process debugging.
transcript ----------------- Appends command output to a file.
types ---------------------- Print list of types
(dlv) help display #看一下display具体用法
Print value of an expression every time the program stops.
display -a [%format] <expression>
display -d <number>
实战
以追踪os.Open函数调用链路为例。在项目编译目录下执行dlv debug main.go file.go
[root@name goschedule]# dlv debug main.go file.go
Type 'help' for list of commands.
(dlv) b ./main.go:22
Breakpoint 1 set at 0x4cad5a for main.main() ./main.go:22
(dlv) c
> [Breakpoint 1] main.main() ./main.go:22 (hits goroutine(1):1 total:1) (PC: 0x4cad5a)
17: time.Sleep(time.Second * 1)
18: }
19: }
20:
21: func main() {
=> 22: fmt.Printf("There are %d cores.\n", runtime.NumCPU())
23: readFileLine()
24:
25: go worker()
26:
27: go deadloop()
(dlv) n
There are 12 cores.
> main.main() ./main.go:23 (PC: 0x4cadfa)
18: }
19: }
20:
21: func main() {
22: fmt.Printf("There are %d cores.\n", runtime.NumCPU())
=> 23: readFileLine()
24:
25: go worker()
26:
27: go deadloop()
28:
(dlv) s
> main.readFileLine() ./file.go:9 (PC: 0x4caf16)
4: //"gopkg.in/yaml.v2"
5: "io"
6: "os"
7: //"unsafe"
8: )
=> 9: func readFileLine() {
10: file, err := os.Open("/home/fpf/public/a.txt")
11: defer file.Close()
12: if err != nil {
13: fmt.Println("open file err", err.Error())
14: return
(dlv) n
> main.readFileLine() ./file.go:10 (PC: 0x4caf1d)
5: "io"
6: "os"
7: //"unsafe"
8: )
9: func readFileLine() {
=> 10: file, err := os.Open("/home/fpf/public/a.txt")
11: defer file.Close()
12: if err != nil {
13: fmt.Println("open file err", err.Error())
14: return
15: }
(dlv) s
> os.Open() /usr/local/go/src/os/file.go:364 (PC: 0x4bc8ae)
359:
360: // Open opens the named file for reading. If successful, methods on
361: // the returned file can be used for reading; the associated file
362: // descriptor has mode O_RDONLY.
363: // If there is an error, it will be of type *PathError.
=> 364: func Open(name string) (*File, error) {
365: return OpenFile(name, O_RDONLY, 0)
366: }
367:
368: // Create creates or truncates the named file. If the file already exists,
369: // it is truncated. If the file does not exist, it is created with mode 0o666
(dlv) n
> os.Open() /usr/local/go/src/os/file.go:365 (PC: 0x4bc8ce)
360: // Open opens the named file for reading. If successful, methods on
361: // the returned file can be used for reading; the associated file
362: // descriptor has mode O_RDONLY.
363: // If there is an error, it will be of type *PathError.
364: func Open(name string) (*File, error) {
=> 365: return OpenFile(name, O_RDONLY, 0)
366: }
367:
368: // Create creates or truncates the named file. If the file already exists,
369: // it is truncated. If the file does not exist, it is created with mode 0o666
370: // (before umask). If successful, methods on the returned File can
(dlv) s
> os.OpenFile() /usr/local/go/src/os/file.go:383 (PC: 0x4bc993)
378: // or Create instead. It opens the named file with specified flag
379: // (O_RDONLY etc.). If the file does not exist, and the O_CREATE flag
380: // is passed, it is created with mode perm (before umask). If successful,
381: // methods on the returned File can be used for I/O.
382: // If there is an error, it will be of type *PathError.
=> 383: func OpenFile(name string, flag int, perm FileMode) (*File, error) {
384: testlog.Open(name)
385: f, err := openFileNolog(name, flag, perm)
386: if err != nil {
387: return nil, err
388: }
(dlv) n
> os.OpenFile() /usr/local/go/src/os/file.go:384 (PC: 0x4bc9c5)
379: // (O_RDONLY etc.). If the file does not exist, and the O_CREATE flag
380: // is passed, it is created with mode perm (before umask). If successful,
381: // methods on the returned File can be used for I/O.
382: // If there is an error, it will be of type *PathError.
383: func OpenFile(name string, flag int, perm FileMode) (*File, error) {
=> 384: testlog.Open(name)
385: f, err := openFileNolog(name, flag, perm)
386: if err != nil {
387: return nil, err
388: }
389: f.appendMode = flag&O_APPEND != 0
(dlv) n
> os.OpenFile() /usr/local/go/src/os/file.go:385 (PC: 0x4bc9da)
380: // is passed, it is created with mode perm (before umask). If successful,
381: // methods on the returned File can be used for I/O.
382: // If there is an error, it will be of type *PathError.
383: func OpenFile(name string, flag int, perm FileMode) (*File, error) {
384: testlog.Open(name)
=> 385: f, err := openFileNolog(name, flag, perm)
386: if err != nil {
387: return nil, err
388: }
389: f.appendMode = flag&O_APPEND != 0
390:
(dlv) s
> os.openFileNolog() /usr/local/go/src/os/file_unix.go:264 (PC: 0x4bdd33)
259: // On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
260: const DevNull = "/dev/null"
261:
262: // openFileNolog is the Unix implementation of OpenFile.
263: // Changes here should be reflected in openDirAt and openDirNolog, if relevant.
=> 264: func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
265: setSticky := false
266: if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
267: if _, err := Stat(name); IsNotExist(err) {
268: setSticky = true
269: }
(dlv) n
> os.openFileNolog() /usr/local/go/src/os/file_unix.go:265 (PC: 0x4bdd68)
260: const DevNull = "/dev/null"
261:
262: // openFileNolog is the Unix implementation of OpenFile.
263: // Changes here should be reflected in openDirAt and openDirNolog, if relevant.
264: func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
=> 265: setSticky := false
266: if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
267: if _, err := Stat(name); IsNotExist(err) {
268: setSticky = true
269: }
270: }
(dlv) n
> os.openFileNolog() /usr/local/go/src/os/file_unix.go:273 (PC: 0x4bdd6d)
268: setSticky = true
269: }
270: }
271:
272: var (
=> 273: r int
274: s poll.SysFile
275: e error
276: )
277: // We have to check EINTR here, per issues 11180 and 39237.
278: ignoringEINTR(func() error {
(dlv) n
> os.openFileNolog() /usr/local/go/src/os/file_unix.go:274 (PC: 0x4bdd76)
269: }
270: }
271:
272: var (
273: r int
=> 274: s poll.SysFile
275: e error
276: )
277: // We have to check EINTR here, per issues 11180 and 39237.
278: ignoringEINTR(func() error {
279: r, s, e = open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
(dlv) n
> os.openFileNolog() /usr/local/go/src/os/file_unix.go:275 (PC: 0x4bdd7f)
270: }
271:
272: var (
273: r int
274: s poll.SysFile
=> 275: e error
276: )
277: // We have to check EINTR here, per issues 11180 and 39237.
278: ignoringEINTR(func() error {
279: r, s, e = open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
280: return e
(dlv) n
> os.openFileNolog() /usr/local/go/src/os/file_unix.go:278 (PC: 0x4bdd85)
273: r int
274: s poll.SysFile
275: e error
276: )
277: // We have to check EINTR here, per issues 11180 and 39237.
=> 278: ignoringEINTR(func() error {
279: r, s, e = open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
280: return e
281: })
282: if e != nil {
283: return nil, &PathError{Op: "open", Path: name, Err: e}
(dlv) s
> os.ignoringEINTR() /usr/local/go/src/os/file_posix.go:249 (PC: 0x4bd62e)
244: // signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
245: // Also #20400 and #36644 are issues in which a signal handler is
246: // installed without setting SA_RESTART. None of these are the common case,
247: // but there are enough of them that it seems that we can't avoid
248: // an EINTR loop.
=> 249: func ignoringEINTR(fn func() error) error {
250: for {
251: err := fn()
252: if err != syscall.EINTR {
253: return err
254: }
(dlv) n
> os.ignoringEINTR() /usr/local/go/src/os/file_posix.go:250 (PC: 0x4bd63d)
245: // Also #20400 and #36644 are issues in which a signal handler is
246: // installed without setting SA_RESTART. None of these are the common case,
247: // but there are enough of them that it seems that we can't avoid
248: // an EINTR loop.
249: func ignoringEINTR(fn func() error) error {
=> 250: for {
251: err := fn()
252: if err != syscall.EINTR {
253: return err
254: }
255: }
(dlv) n
> os.ignoringEINTR() /usr/local/go/src/os/file_posix.go:251 (PC: 0x4bd640)
246: // installed without setting SA_RESTART. None of these are the common case,
247: // but there are enough of them that it seems that we can't avoid
248: // an EINTR loop.
249: func ignoringEINTR(fn func() error) error {
250: for {
=> 251: err := fn()
252: if err != syscall.EINTR {
253: return err
254: }
255: }
256: }
(dlv) s
> os.openFileNolog.func1() /usr/local/go/src/os/file_unix.go:278 (PC: 0x4be0d3)
273: r int
274: s poll.SysFile
275: e error
276: )
277: // We have to check EINTR here, per issues 11180 and 39237.
=> 278: ignoringEINTR(func() error {
279: r, s, e = open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
280: return e
281: })
282: if e != nil {
283: return nil, &PathError{Op: "open", Path: name, Err: e}
(dlv) s
> os.openFileNolog.func1() /usr/local/go/src/os/file_unix.go:279 (PC: 0x4be126)
274: s poll.SysFile
275: e error
276: )
277: // We have to check EINTR here, per issues 11180 and 39237.
278: ignoringEINTR(func() error {
=> 279: r, s, e = open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
280: return e
281: })
282: if e != nil {
283: return nil, &PathError{Op: "open", Path: name, Err: e}
284: }
(dlv) s
> os.syscallMode() /usr/local/go/src/os/file_posix.go:60 (PC: 0x4bd2aa)
55: runtime.KeepAlive(f)
56: return n, err
57: }
58:
59: // syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
=> 60: func syscallMode(i FileMode) (o uint32) {
61: o |= uint32(i.Perm())
62: if i&ModeSetuid != 0 {
63: o |= syscall.S_ISUID
64: }
65: if i&ModeSetgid != 0 {
(dlv) so
> os.openFileNolog.func1() /usr/local/go/src/os/file_unix.go:279 (PC: 0x4be12f)
Values returned:
o: 0
274: s poll.SysFile
275: e error
276: )
277: // We have to check EINTR here, per issues 11180 and 39237.
278: ignoringEINTR(func() error {
=> 279: r, s, e = open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
280: return e
281: })
282: if e != nil {
283: return nil, &PathError{Op: "open", Path: name, Err: e}
284: }
(dlv) s
> os.open() /usr/local/go/src/os/file_open_unix.go:14 (PC: 0x4bceb3)
9: import (
10: "internal/poll"
11: "syscall"
12: )
13:
=> 14: func open(path string, flag int, perm uint32) (int, poll.SysFile, error) {
15: fd, err := syscall.Open(path, flag, perm)
16: return fd, poll.SysFile{}, err
17: }
(dlv) s
> syscall.Open() /usr/local/go/src/syscall/syscall_linux.go:283 (PC: 0x4a052e)
278:
279: func Mknod(path string, mode uint32, dev int) (err error) {
280: return Mknodat(_AT_FDCWD, path, mode, dev)
281: }
282:
=> 283: func Open(path string, mode int, perm uint32) (fd int, err error) {
284: return openat(_AT_FDCWD, path, mode|O_LARGEFILE, perm)
285: }
286:
287: //sys openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
288:
(dlv) n
> syscall.Open() /usr/local/go/src/syscall/syscall_linux.go:284 (PC: 0x4a0560)
279: func Mknod(path string, mode uint32, dev int) (err error) {
280: return Mknodat(_AT_FDCWD, path, mode, dev)
281: }
282:
283: func Open(path string, mode int, perm uint32) (fd int, err error) {
=> 284: return openat(_AT_FDCWD, path, mode|O_LARGEFILE, perm)
285: }
286:
287: //sys openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
288:
289: func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
(dlv) s
> syscall.openat() /usr/local/go/src/syscall/zsyscall_linux_amd64.go:92 (PC: 0x4a1153)
87: return
88: }
89:
90: // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
91:
=> 92: func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
93: var _p0 *byte
94: _p0, err = BytePtrFromString(path)
95: if err != nil {
96: return
97: }
(dlv) n
> syscall.openat() /usr/local/go/src/syscall/zsyscall_linux_amd64.go:93 (PC: 0x4a1193)
88: }
89:
90: // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
91:
92: func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
=> 93: var _p0 *byte
94: _p0, err = BytePtrFromString(path)
95: if err != nil {
96: return
97: }
98: r0, _, e1 := Syscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), uintptr(mode), 0, 0)
(dlv) n
> syscall.openat() /usr/local/go/src/syscall/zsyscall_linux_amd64.go:94 (PC: 0x4a119f)
89:
90: // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
91:
92: func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
93: var _p0 *byte
=> 94: _p0, err = BytePtrFromString(path)
95: if err != nil {
96: return
97: }
98: r0, _, e1 := Syscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), uintptr(mode), 0, 0)
99: fd = int(r0)
(dlv) n
> syscall.openat() /usr/local/go/src/syscall/zsyscall_linux_amd64.go:95 (PC: 0x4a1241)
90: // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
91:
92: func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
93: var _p0 *byte
94: _p0, err = BytePtrFromString(path)
=> 95: if err != nil {
96: return
97: }
98: r0, _, e1 := Syscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), uintptr(mode), 0, 0)
99: fd = int(r0)
100: if e1 != 0 {
(dlv) n
> syscall.openat() /usr/local/go/src/syscall/zsyscall_linux_amd64.go:98 (PC: 0x4a1256)
93: var _p0 *byte
94: _p0, err = BytePtrFromString(path)
95: if err != nil {
96: return
97: }
=> 98: r0, _, e1 := Syscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), uintptr(mode), 0, 0)
99: fd = int(r0)
100: if e1 != 0 {
101: err = errnoErr(e1)
102: }
103: return
(dlv) s
> syscall.Syscall6() /usr/local/go/src/syscall/syscall_linux.go:94 (PC: 0x4a2604)
89: }
90:
91: //go:uintptrkeepalive
92: //go:nosplit
93: //go:linkname Syscall6
=> 94: func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) {
95: runtime_entersyscall()
96: r1, r2, err = RawSyscall6(trap, a1, a2, a3, a4, a5, a6)
97: runtime_exitsyscall()
98: return
99: }
(dlv) n
> syscall.Syscall6() /usr/local/go/src/syscall/syscall_linux.go:96 (PC: 0x4a2665)
91: //go:uintptrkeepalive
92: //go:nosplit
93: //go:linkname Syscall6
94: func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) {
95: runtime_entersyscall()
=> 96: r1, r2, err = RawSyscall6(trap, a1, a2, a3, a4, a5, a6)
97: runtime_exitsyscall()
98: return
99: }
100:
101: func rawSyscallNoError(trap, a1, a2, a3 uintptr) (r1, r2 uintptr)
(dlv) s
> syscall.RawSyscall6() /usr/local/go/src/syscall/syscall_linux.go:62 (PC: 0x4a2444)
57:
58: //go:uintptrkeepalive
59: //go:nosplit
60: //go:norace
61: //go:linkname RawSyscall6
=> 62: func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) {
63: var errno uintptr
64: r1, r2, errno = runtimesyscall.Syscall6(trap, a1, a2, a3, a4, a5, a6)
65: err = Errno(errno)
66: return
67: }
(dlv) n
> syscall.RawSyscall6() /usr/local/go/src/syscall/syscall_linux.go:63 (PC: 0x4a249e)
58: //go:uintptrkeepalive
59: //go:nosplit
60: //go:norace
61: //go:linkname RawSyscall6
62: func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) {
=> 63: var errno uintptr
64: r1, r2, errno = runtimesyscall.Syscall6(trap, a1, a2, a3, a4, a5, a6)
65: err = Errno(errno)
66: return
67: }
68:
(dlv) n
> syscall.RawSyscall6() /usr/local/go/src/syscall/syscall_linux.go:64 (PC: 0x4a24a7)
59: //go:nosplit
60: //go:norace
61: //go:linkname RawSyscall6
62: func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) {
63: var errno uintptr
=> 64: r1, r2, errno = runtimesyscall.Syscall6(trap, a1, a2, a3, a4, a5, a6)
65: err = Errno(errno)
66: return
67: }
68:
69: //go:uintptrkeepalive
(dlv) s
> internal/runtime/syscall.Syscall6() /usr/local/go/src/internal/runtime/syscall/asm_linux_amd64.s:30 (PC: 0x478e80)
Warning: debugging optimized function
25: // Note that this differs from "standard" ABI convention, which would pass 4th
26: // arg in CX, not R10.
27: TEXT ·Syscall6<ABIInternal>(SB),NOSPLIT,$0
28: // a6 already in R9.
29: // a5 already in R8.
=> 30: MOVQ SI, R10 // a4
31: MOVQ DI, DX // a3
32: MOVQ CX, SI // a2
33: MOVQ BX, DI // a1
34: // num already in AX.
35: SYSCALL
可以看到,通过b、c、n、s、so这几个指令轻松追踪到了os.Open的go源码在Linux系统下的调用链路:
os.Open->OpenFile->openFileNolog->os.open->syscall.Open->syscall.openat->syscall.Syscall6->syscall.RawSyscall6->runtimesyscall.Syscall6[汇编]
有兴趣可以多深入了解下syscall.Syscall6
函数:
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) {
runtime_entersyscall() //进入系统调用,这里会将M和P进行解绑,这样P上的其他G可以被其他空闲的M执行,不至于其他G也等着
r1, r2, err = RawSyscall6(trap, a1, a2, a3, a4, a5, a6)//进行系统调用,此时M会一直等待系统返回,无法运行其他协程,被当前协程任务独占了
runtime_exitsyscall() //结束系统调用,M可以干其他任务了
return
}