4.2 آموزش کار با Debugging

4.2 آموزش کار با Debugging

برای بحث debugging در زبان گو روش های مختلفی وجود دارد که محبوبترین روش ها به شرح زیر می باشد :

  1. با استفاده پکیج fmt : شما با استفاده از تابع fmt.Println می توانید مقادیر برخی از متغیرها را چاپ کنید و یکی از روش های ساده برای بحث دیباگ هستش.
  2. با استفاده از پکیج log : یکی از کاربردی ترین روش ها بحث لاگ کردن هستش که شما می توانید هر بخش از کدهای خود را لاگ کنید و بصورت زنده در console یا داخل فایل ببینید و از همه مهمتر شما می توانید لاگ های خود را سطح بندی کنید تا بهتر بتوانید دیباگ کنید.
  3. استفاده از پکیج pprof : پکیج pprof به شما کمک می کند تا راحتر مشکلات عملکردی برنامه خود را بیابید و همچنین خروجی profile از عملکرد برنامه با CPU و Memory را آنالیز کنید.
  4. با استفاده از IDE ها: اکثر IDE هایی (Goland, Vscode) که قبلا معرفی کردیم دارای دیباگر داخلی می باشند که شما می توانید در هر بخش از کد خود breakpoint بزارید و خط به خط کد را دیباگ کنید و مشکلات را برطرف کنید.
  5. با استفاده از دیباگر dlv : یکی از محبوبترین دیباگرهایی بصورت متن باز در حال توسعه است delve می باشد که به شما برای دیباگ کردن بصورت تعاملی هنگام اجرا کمک می کند.

حال در ادامه ما با مثال هایی نحوه کار با این شیوه هایی که معرفی کردیم آموزش می دهیم.

4.2.1 نحوه دیباگ با fmt #

شما با استفاده از تابع fmt.Println می توانید مقادیر برخی از متغیرها را چاپ کنید و برای اینکار به مثال ساده زیر توجه کنید :

 1package main
 2
 3import "fmt"
 4
 5func fibonacci(n uint) uint {
 6	if n < 2 {
 7		return n
 8	}
 9
10	var a, b uint
11
12	b = 1
13
14	for n--; n > 0; n-- {
15		a += b
16		a, b = b, a
17		fmt.Println("this is example debugging value a is ", a, " value b is ", b) 
18	}
19
20	return b
21}
22
23func main() {
24	fmt.Println(fibonacci(100))
25}
 1$ go run main.go
 2this is example debugging value a is  1  value b is  1
 3this is example debugging value a is  1  value b is  2
 4this is example debugging value a is  2  value b is  3
 5this is example debugging value a is  3  value b is  5
 6this is example debugging value a is  5  value b is  8
 7...
 8this is example debugging value a is  6174643828739884737  value b is  16008811023750101250
 9this is example debugging value a is  16008811023750101250  value b is  3736710778780434371
103736710778780434371

در کد فوق برای اینکه مقدار a, b را ببینیم از تابع Println استفاده کردیم تا مقدار داخل این دو متغیر را ببینیم.

4.2.2 نحوه دیباگ با استفاده log #

یکی از کاربردی ترین روش ها بحث لاگ کردن هستش که شما می توانید هر بخش از کدهای خود را لاگ کنید و بصورت زنده در console یا داخل فایل ببینید و از همه مهمتر شما می توانید لاگ های خود را سطح بندی کنید تا بهتر بتوانید دیباگ کنید. حال برای اینکه با log دیباگ کنید یک پکیج استاندارد به نام log داریم که می توانید بسته به نیازتان تغییرات دهید و هچنین پکیج هایی نظیر zap, logrus و … هست برای بحث لاگ به شما خیلی کمک می کنند.

 1package main
 2
 3import (
 4	"log"
 5	"os"
 6)
 7
 8func main() {
 9	// Set the log level to Info
10	log.SetFlags(0)
11	log.SetPrefix("[Info] ")
12	log.SetOutput(os.Stdout)
13	log.Println("This is an informational message")
14
15	// Set the log level to Warning
16	log.SetPrefix("[Warn] ")
17	log.SetOutput(os.Stdout)
18	log.Println("This is a warning message")
19
20	// Set the log level to Error
21	log.SetPrefix("[Error] ")
22	log.SetOutput(os.Stderr)
23	log.Println("This is an error message")
24}
1$ go run main.go
2[Info] This is an informational message
3[Warn] This is a warning message
4[Error] This is an error message

در کد فوق ما با استفاده از تابع SetPrefix یک پیشوند برای لاگ ها اضافه کردیم تا بتوانیم لاگ با سطح های مختلف ایجاد کنیم. و با استفاده از log.Println لاگ را چاپ کردیم. در ادامه این فصل به بحث آموزش کار به log میپردازیم.

4.2.3 دیباگ عملکرد با استفاده از pprof #

زبان گو یک ابزار داخلی دارد جهت آنالیز و دیباگ عملکرد به نام pprof که با استفاده از آن می توانید یکسری اطلاعات در خصوص عملکرد برنامه تهیه کنید و براساس آنالیز اطلاعات مشکلات عملکردی را می توانید برطرف کنید.

 1package main
 2
 3import (
 4	"log"
 5	"net/http"
 6	_ "net/http/pprof"
 7)
 8
 9func main() {
10	log.Println(http.ListenAndServe("localhost:6060", nil))
11}
1$ go run main.go

در کد فوق ما از پکیج مسیر net/http/pprof استفاده کردیم تا عملکرد برنامه را در وب سرور زبان گو را آنالیز و دیباگ کنیم. حال اگر به آدرس http://localhost:6060/debug/pprof/ بروید می توانید وضعیت عملکرد وب سرور را در لحظه ببینید و آنالیز کنید.

pprof

4.2.3.1 آنالیز وضعیت CPU #

برای آنالیز وضعیت CPU کافیه کامند زیر را بزنید :

1go tool pprof http://localhost:6060/debug/pprof/profile

4.2.3.2 آنالیز وضعیت heap memory #

برای آنالیز حافظه heap کافیه کامند زیر را بزنید :

1go tool pprof http://localhost:6060/debug/pprof/heap
2
3Fetching profile over HTTP from http://localhost:6060/debug/pprof/heap
4Saved profile in /home/javad/pprof/pprof.main.alloc_objects.alloc_space.inuse_objects.inuse_space.001.pb.gz
5File: main
6Type: inuse_space
7Time: Jan 27, 2023 at 6:46pm (+0330)
8Entering interactive mode (type "help" for commands, "o" for options)
9(pprof) pdf

زمانیکه کامند فوق را بزنید وارد شل pprof خواهید شد که می توانید با زدن help کامندهای کاربردی را جهت آنالیز ببینید. به عنوان مثال pdf را بزنید یک خروجی pdf بصورت گراف از وضعیت حافظه heap ارائه می دهد که می توانید وضعیت را آنالیز کنید (جهت خواندن گراف این آموزش را مطالعه کنید).

4.2.4 دیباگ با استفاده از GDB #

این قسمت با استفاده از مستندات رسمی GO در مورد 1. Debugging Go Code with GDB نوشته شده است. دستورالعمل های زیر برای استاندارد toolchain (کامپایلر و ابزارهای gc Go) اعمال می شود. Gccgo دارای پشتیبانی از native gdb به صورت پیش فرض است.

توجه داشته باشید که هنگام اشکال زدایی برنامه های Go که باstandard toolchain ساخته شده اند، Delve جایگزین بهتری برای GDB است. زیرا Go runtime را بهتر تشخیص می‌دهد و ساختارهای داده و عبارات را بهتر از GDB درک می کند. Delve در حال حاضر از Linux، OSX و Windows در amd64 پشتیبانی می کند. برای به روزترین لیست پلتفرم های پشتیبانی شده، لطفاً به Delve documentation مراجعه کنید.

GDB برنامه های Go را به خوبی درک نمی کند. مدیریت stack و threading و runtime شامل جنبه هایی هستند که به اندازه کافی با مدل اجرایی متفاوت است که GDB انتظار دارد که می توانند debugger را اشتباه گرفته و نتایج نادرستی را حتی زمانی که برنامه با gccgo کامپایل می شود ایجاد کنند. در نتیجه، اگرچه GDB می‌تواند در برخی موقعیت‌ها مفید باشد (به عنوان مثال، اشکال‌زدایی کد Cgo، یا اشکال‌زدایی خود زمان اجرا)، اما برای برنامه‌های Go، به‌ویژه برنامه‌های به‌شدت همزمان هستند، اشکال‌زدایی با این روش چندان قابل اعتمادی نیست. علاوه بر این، پرداختن به این مسائل که دشوار هستند، برای پروژه Go در اولویت نیست.

به طور خلاصه، دستورالعمل‌های زیر باید تنها به‌عنوان راهنمای نحوه استفاده از GDB در هنگام کارکرد آن در نظر گرفته شود، نه به عنوان تضمین موفقیت اجرای درست برنامه. علاوه بر این نمای کلی، ممکن است بخواهید به GDB manual مراجعه کنید.

4.2.4.1 مقدمه اولیه GDB #

وقتی برنامه‌های Go را با toolchain مربوط gc در Linux، macOS، FreeBSD یا NetBSD کامپایل و link می‌دهید، باینری‌های به دست آمده حاوی اطلاعات اشکال‌زدایی DWARFv4 هستند که نسخه‌های اخیر (≥7.5) اشکال‌زدای GDB می‌توانند از آن برای بازرسی یک live process یا یک core dump استفاده کنند. .

پرچم ‘-w’ را به linker ارسال کنید تا اطلاعات debug را حذف کنید (به عنوان مثال، go build -ldflags=-w prog.go).

کد تولید شده توسط کامپایلر gc شامل درون خطی کردن فراخوانی تابع و ثبت متغیرها است. این بهینه سازی ها گاهی اوقات می تواند اشکال زدایی با gdb را سخت تر کند. اگر متوجه شدید که باید این بهینه سازی ها را غیرفعال کنید، برنامه خود را با استفاده از go build -gcflags=all=-N -l بسازید.

اگر می‌خواهید از gdb برای بررسی یک core dump استفاده کنید، می توانید یک Dump را در یک program crash راه اندازی کنید و برای این کار باید GOTRACEBACK=crash در environment تنظیم کنید (برای اطلاعات بیشتر به  runtime package documentation مراجعه کنید).

4.2.4.2 Common Operations #

نمایش فایل و شماره خط برای کد، تعیین breakpoints و disassemble:

1(gdb)  list 
2(gdb)  list _line_ 
3(gdb)  list _file.go_:_line_ 
4(gdb)  break _line_ 
5(gdb)  break _file.go_:_line_ 
6(gdb)  disas 

نمایش backtraces و باز کردن stack frames:

1(gdb)  bt 
2(gdb)  frame _n_ 

نمایش نام، type و location در stack frame و local variables، آرگومان ها و مقادیر بازگشتی:

1(gdb)  info locals 
2(gdb)  info args 
3(gdb)  p variable 
4(gdb)  whatis variable 

نمایش نام، type و location و global variables:

1(gdb)  info variables _regexp_

4.2.4.3 Go Extensions #

اخیرا یک نوع extension به GDB اجازه می دهد تا extension scripts را برای یک باینری معین load کند. این toolchain برای extend GDB با تعداد انگشت شماری از command ها برای بررسی داخلی runtime code (مانند گوروتین ها) و pretty print the built-in map و slice وchannel types استفاده می کند.

1Pretty printing a string, slice, map, channel or interface:(gdb) p var
2A $len() and $cap() function for strings, slices and maps: (gdb) p $len(var)
3A function to cast interfaces to their dynamic types:
4(gdb) p $dtype(var)
5(gdb) iface var

مشکلات شناخته شده: GDB نمی تواند به طور خودکار dynamic type یک interface value را پیدا کند اگر نام طولانی آن با نام کوتاه آن متفاوت باشد (در هنگام printing stacktraces آزاردهنده است، pretty printer به نمایش نام short type و اشاره گر بازمی گردد).

بررسی گوروتین ها:

1(gdb) info goroutines
2(gdb) goroutine _n_ _cmd_
3(gdb) help goroutine

به عنوان مثال:

1(gdb) goroutine 12 bt

می توانید همه گوروتین ها را با pass کردن همه به جای goroutine’s ID خاص بررسی کنید. مثلا:

1 (gdb) goroutine all bt

اگر می‌خواهید ببینید که این حالت چگونه کار می‌کند یا می‌خواهید آن را گسترش دهید، به src/runtime/runtime-gdb.py در توزیع Go source نگاهی بیندازید. این به برخی از type های جادویی خاص (hash<T,U>) و متغیرهایی (runtime.m و runtime.g) بستگی دارد کهlinker (src/cmd/link/internal/ld/dwarf.go) اطمینان حاصل می کند که در کد DWARF توضیح داده شده اند.

اگر به debugging information علاقه دارید، objdump -W a.out را اجرا کنید و در بخش های مرتبط با .debug_* مرور کنید.

مشکلات شناخته شده: #

۱- حالت String pretty printing فقط برای type string فعال می شود، نه برای انواع مشتق شده از آن. ۲-Type information برای قسمت های C که در runtime library هستند، وجود ندارد.
۳- GDB شرایط Go’s name را نمی‌فهمد و «fmt.Print» را به‌عنوان یک کلمه بدون ساختار با یک «» در نظر می‌گیرد. که باید نقل شود. حتی با شدت بیشتری به method names فرم pkg.(*MyType).Meth. برخورد می کند.
۴- از Go 1.11، قسمت debug information به طور پیش فرض فشرده شده است. نسخه‌های قدیمی‌تر gdb، مانند نسخه‌ای که به‌طور پیش‌فرض در MacOS موجود است، فشرده‌سازی را درک نمی‌کنند. شما می توانید با استفاده از go build -ldflags=-compressdwarf=false‍ اطلاعات اشکال زدایی فشرده نشده تولید کنید. (برای راحتی می توانید گزینه -ldflags را در  GOFLAGS environment variable قرار دهید تا مجبور نباشید هر بار آدرس آن را مشخص کنید.)

4.2.4.4 مثال های GDB #

در این آموزش ما باینری unit tests پکیج regexp را بررسی می کنیم. برای ساخت باینری، به  GOROOT/src/regexp$ تغییر دهید و go test -c را اجرا کنید. این باید یک فایل اجرایی به نام regexp.test تولید کند.

شروع دیباگ #

Launch GDB, debugging regexp.test:

 1$  gdb regexp.test 
 2GNU gdb (GDB) 7.2-gg8
 3Copyright (C) 2010 Free Software Foundation, Inc.
 4License GPLv  3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
 5Type "show copying" and "show warranty" for licensing/warranty details.
 6This GDB was configured as "x86_64-linux".
 7
 8Reading symbols from  /home/user/go/src/regexp/regexp.test...
 9done.
10Loading Go Runtime support.
11(gdb)

پیام ‘Loading Go Runtime Support’ به این معنی است که GDB برنامه extension را از مسیر GOROOT/src/runtime/runtime-gdb.py$ بارگیری کرده است.

برای کمک به GDB در یافتن آدرس Go runtime sources و سایر اسکریپت‌های همراه، $GOROOT را با پرچم ‘-d’ ارسال کنید:

gdb regexp.test -d $GOROOT$

اگر به دلایلی هنوز GDB نمی تواند آن دایرکتوری یا آن اسکریپت را پیدا کند، می توانید آن را دستی load کنید (با فرض اینکه  go sources در آدرس ~/go/ باشد):

1(gdb)  source ~/go/src/runtime/runtime-gdb.py 
2Loading Go Runtime support.

بررسی کردن source #

از دستور ’l’ یا ’list’ برای بررسی source code استفاده کنید.

1(gdb) l 

بخش خاصی از منبع را که ’list’ را پارامتر می کند با نام تابع فهرست کنید (باید با نام بسته آن مرتبط باشد).

1(gdb)  l main.main 

یک file خاص و line number را فهرست کنید:

1(gdb) l regexp.go:1 
2(gdb) _# Hit enter to repeat last command. Here, this lists next 10 lines._

Naming #

نام متغیرها و توابع باید با نام package هایی که به آنها تعلق دارند قابل بازیابی باشند. به عنوان مثال تابع Compile از بسته regexp برای GDB به عنوان ‘regexp.Compile’ شناخته می شود.

متدها باید با نام receiver types خود قابل بازیابی باشند. به عنوان مثال، *Regexp type’s String به عنوان 'regexp.(*Regexp).String' شناخته می شود.

متغیرهایی که سایر متغیرها را تحت shadow قرار می دهند، به صورت جادویی با یک عدد در debug info پسوند می شوند. متغیرهایی که توسط بسته‌ها ارجاع می‌شوند به‌عنوان اشاره‌گرهایی با پیشوند جادویی «&» ظاهر می‌شوند.

قراردادن breakpoints #

یک breakpoint در تابع TestFind تنظیم کنید: ‍‍

1(gdb)  b 'regexp.TestFind' 
2Breakpoint 1 at 0x424908: file /home/user/go/src/regexp/find_test.go, line 148.

اجرا کردن برنامه:

1(gdb)  run 
2Starting program: /home/user/go/src/regexp/regexp.test
3
4Breakpoint 1, regexp.TestFind (t=0xf8404a89c0) at /home/user/go/src/regexp/find_test.go:148
5148	func TestFind(t *testing.T) {

اجرا در breakpoint متوقف شده است. ببینید کدام گوروتین ها در حال اجرا هستند و چه کار می کنند:

1(gdb)  info goroutines 
2  1  waiting runtime.gosched
3* 13  running runtime.goexit

موردی که با * مشخص شده است، گوروتین فعلی است.

بررسی کردن stack #

به خروجی از stack برای جایی که برنامه را متوقف کرده ایم نگاه کنید:

1(gdb)  bt   _# backtrace_
2#0  regexp.TestFind (t=0xf8404a89c0) at /home/user/go/src/regexp/find_test.go:148
3#1  0x000000000042f60b in testing.tRunner (t=0xf8404a89c0, test=0x573720) at /home/user/go/src/testing/testing.go:156
4#2  0x000000000040df64 in runtime.initdone () at /home/user/go/src/runtime/proc.c:242
5#3  0x000000f8404a89c0 in ?? ()
6#4  0x0000000000573720 in ?? ()
7#5  0x0000000000000000 in ?? ()

گوروتین دیگر، شماره 1، در runtime.gosched گیر کرده و در channel receive مسدود شده است:

 1(gdb)  goroutine 1 bt 
 2#0  0x000000000040facb in runtime.gosched () at /home/user/go/src/runtime/proc.c:873
 3#1  0x00000000004031c9 in runtime.chanrecv (c=void, ep=void, selected=void, received=void)
 4 at  /home/user/go/src/runtime/chan.c:342
 5#2  0x0000000000403299 in runtime.chanrecv1 (t=void, c=void) at/home/user/go/src/runtime/chan.c:423
 6#3  0x000000000043075b in testing.RunTests (matchString={void (struct string, struct string, bool *, error *)}
 7 0x7ffff7f9ef60, tests=  []testing.InternalTest = {...}) at /home/user/go/src/testing/testing.go:201
 8#4  0x00000000004302b1 in testing.Main (matchString={void (struct string, struct string, bool *, error *)}
 9 0x7ffff7f9ef80, tests= []testing.InternalTest = {...}, benchmarks= []testing.InternalBenchmark = {...})
10at /home/user/go/src/testing/testing.go:168
11#5  0x0000000000400dc1 in main.main () at /home/user/go/src/regexp/_testmain.go:98
12#6  0x00000000004022e7 in runtime.mainstart () at /home/user/go/src/runtime/amd64/asm.s:78
13#7  0x000000000040ea6f in runtime.initdone () at /home/user/go/src/runtime/proc.c:243
14#8  0x0000000000000000 in ?? ()

همانطور که stack frame نشان می دهد، انتظار می رود الان باید در حال اجرای تابع regexp.TestFind باشیم.

 1(gdb) info frame 
 2Stack level 0, frame at 0x7ffff7f9ff88:
 3 rip = 0x425530 in regexp.TestFind (/home/user/go/src/regexp/find_test.go:148);
 4    saved rip 0x430233
 5 called by frame at 0x7ffff7f9ffa8
 6 source language minimal.
 7 Arglist at 0x7ffff7f9ff78, args: t=0xf840688b60
 8 Locals at 0x7ffff7f9ff78, Previous frame's sp is 0x7ffff7f9ff88
 9 Saved registers:
10  rip at 0x7ffff7f9ff80

دستور info locals همه متغیرهای محلی تابع و مقادیر آنها را فهرست می‌کند، اما استفاده از آن کمی خطرناک است، زیرا سعی می‌کند متغیرهای اولیه را نیز چاپ کند. برش‌های بدون مقدار اولیه ممکن است باعث شوند که gdb سعی کند آرایه‌های بزرگ دلخواه را چاپ کند.

آرگومان های تابع:

1(gdb)  info args 
2t = 0xf840688b60

هنگام چاپ آرگومان، توجه کنید که نشانگر یک مقدار Regexp است. توجه داشته باشید که GDB به اشتباه * را در سمت راست نام تایپ قرار داده و یک کلمه کلیدی ‘struct’ به سبک سنتی C ساخته است.

1(gdb)  p re 
2(gdb) p t
3$1 = (struct testing.T *) 0xf840688b60
4(gdb) p t
5$1 = (struct testing.T *) 0xf840688b60
6(gdb) p *t
7$2 = {errors = "", failed = false, ch = 0xf8406f5690}
8(gdb) p *t->ch
9$3 = struct hchan<*testing.T>

که ساختار  hchan<*testing.T> نمایش  runtime-internal یک کانال است و در حال حاضر خالی است، وگرنه gdb محتویات آن را به زیبایی چاپ می کرد.
حالا به قسمت مهم پیمایش دیباگر میرسیم که با حرف n صورت می‌گیرد.

پیمایش دیباگر :

 1(gdb)  n   _# execute next line_
 2149             for _, test := range findTests {
 3(gdb)    _# enter is repeat_
 4150                     re := MustCompile(test.pat)
 5(gdb)  p test.pat 
 6$4 = ""
 7(gdb) p re 
 8$5 = (struct regexp.Regexp *) 0xf84068d070
 9(gdb)  p *re 
10$6 = {expr = "", prog = 0xf840688b80, prefix = "", prefixBytes =  []uint8, prefixComplete = true,
11  prefixRune = 0, cond = 0 '\000', numSubexp = 0, longest = false, mu = {state = 0, sema = 0},
12  machine =  []*regexp.machine}
13(gdb)  p *re->prog 
14$7 = {Inst =  []regexp/syntax.Inst = {{Op = 5 '\005', Out = 0, Arg = 0, Rune =  []int}, {Op =
15    6 '\006', Out = 2, Arg = 0, Rune =  []int}, {Op = 4 '\004', Out = 0, Arg = 0, Rune =  []int}},
16  Start = 1, NumCap = 2}

می توانیم با حرف ’s’ وارد فراخوانی Stringfunction شویم:

1(gdb)  s 
2regexp.(*Regexp).String (re=0xf84068d070, noname=void) at /home/user/go/src/regexp/regexp.go:97
397      func (re *Regexp) String() string {

یک stack trace بگیرید تا ببینید کجا هستیم:

1(gdb)  bt 
2#0  regexp.(*Regexp).String (re=0xf84068d070, noname=void)
3    at /home/user/go/src/regexp/regexp.go:97
4#1  0x0000000000425615 in regexp.TestFind (t=0xf840688b60)
5    at /home/user/go/src/regexp/find_test.go:151
6#2  0x0000000000430233 in testing.tRunner (t=0xf840688b60, test=0x5747b8)
7    at /home/user/go/src/testing/testing.go:156
8#3  0x000000000040ea6f in runtime.initdone () at /home/user/go/src/runtime/proc.c:243
9....

به source code نگاه کنید:

 1(gdb)  l 
 292              mu      sync.Mutex
 393              machine []*machine
 494      }
 595
 696      // String returns the source text used to compile the regular expression.
 797      func (re *Regexp) String() string {
 898              return re.expr
 999      }
10100
11101     // Compile parses a regular expression and returns, if successful,

Pretty Printing

مکانیسم چاپ زیبا GDB توسط regexp matches به صورت زیر می‌باشد:

1(gdb)  p utf 
2$22 =  []uint8 = {0 '\000', 0 '\000', 0 '\000', 0 '\000'}

از آنجایی که slice ها، آرایه ها و رشته ها اصلا اشاره‌گرهای C نیستند در نتیجه GDB نمی تواند عملیات subscripting را برای شما تفسیر کند، اما می توانید برای انجام این کار به نمایش runtime نگاه کنید (tab completion در اینجا کاربرد دارد):

1(gdb)  p slc 
2$11 =  []int = {0, 0}
3(gdb)  p slc->* _<TAB>_
4array  slc    len
5(gdb)  p slc->array 
6$12 = (int *) 0xf84057af00
7(gdb)  p slc->array[1] 
8$13 = 0

توابع extension یا افزونه $len و $cap روی strings, arrays , slices کار می‌کنند:

1(gdb)  p $len(utf) 
2$23 = 4
3(gdb)  p $cap(utf) 
4$24 = 4

Channelها و mapها در واقع typeهایی از جنس «reference» هستند که gdb آن‌ها را به‌عنوان اشاره‌گر به C++ like types مانند <hash<int,string>* نشان می‌دهد. Interface ها در runtime به عنوان یک اشاره گر به یک توصیفگر(descriptor) و یک اشاره گر به یک مقدار نشان داده می شوند. پسوند Go GDB در runtime این را رمزگشایی می کند و به طور خودکار pretty printing را برای runtime type ایجاد می کند. تابع افزونه dtype$ درنهایت یک  dynamic type را برای شما رمزگشایی می‌کند (مثال‌هایی از یک breakpoint در خط 293 regexp.go گرفته شده‌اند.)

1(gdb)  p i 
2$4 = {str = "cbb"}
3(gdb) whatis i 
4type = regexp.input
5(gdb)  p $dtype(i) 
6$26 = (struct regexp.inputBytes *) 0xf8400b4930
7(gdb)  iface i 
8regexp.input: struct regexp.inputBytes *

4.2.5 معرفی دیباگر DELVE #

delve یک debugger برای زبان برنامه نویسی GO است. هدف این پروژه ارائه یک ابزار ساده و کامل debugger برای GO است. DeLve باید به راحتی استفاده می‌شود و استفاده از آن آسان بسیار آسان می‌باشد.

4.2.5.1 راهنمای نصب DELVE #

دستورالعمل های زیر برای کار بر روی Linux، macOS، Windows و FreeBSD مورد استفاده قرار می‌گیرد.

برای Clone و build دستورات زیر رو داریم

1 git clone https://github.com/go-delve/delve
2 cd delve
3 go install github.com/go-delve/delve/cmd/dlv

برای Go version 1.16 و بالاتر هم داریم:

1# Install the latest release:
2$ go install github.com/go-delve/delve/cmd/dlv@latest
3
4# Install at tree head:
5$ go install github.com/go-delve/delve/cmd/dlv@master
6
7# Install at a specific version or pseudo-version:
8$ go install github.com/go-delve/delve/cmd/dlv@v1.7.3
9$ go install github.com/go-delve/delve/cmd/dlv@v1.7.4-0.20211208103735-2f13672765fe

برای جزئیات در مورد محل ذخیره فایل اجرایی  dlv به راهنمای نصب یا دستور go help install مراجعه کنید.

اگر در مرحله نصب با خطای مشابه زیر مواجه شدید: 2

1found packages native (proc.go) and your_operating_system_and_architecture_combination_is_not_supported_by_delve (support_sentinel.go) in /home/pi/go/src/github.com/go-delve/delve/pkg/proc/native

یعنی از سیستم عامل یا معماری CPU شما پشتیبانی نمی شود، خروجی go version را بررسی کنید.

برای نصب در macOS از لینک macOS استفاده کنید.

4.2.5.2 شروع استفاده از DELVE #

هدف Delve یک ابزار بسیار ساده و قدرتمند است، اما اگر به استفاده از source level debugger در یک زبان کامپایل شده عادت ندارید این ابزار می‌تواند گیج‌کننده باشد. البته این سند تمام اطلاعاتی را که برای شروع اشکال زدایی برنامه های Go خود نیاز دارید را ارائه می دهد.

4.2.5.3 Debugging ‘main’ packages #

اولین CLI subcommand که بررسی خواهیم کرد کلیدواژه debug است. این subcommand را می توان بدون آرگومان اجرا کرد اگر شما در همان دایرکتوری main package خود هستید، در غیر این صورت به صورت اختیاری یک package path را می پذیرد.

به عنوان مثال با توجه به این  project layout داریم:

1github.com/me/foo
2├── cmd
3│   └── foo
4│       └── main.go
5└── pkg
6    └── baz
7        ├── bar.go
8        └── bar_test.go

اگر در دایرکتوری github.com/me/foo/cmd/foo هستید، می توانید به سادگی dlv debug را از command line اجرا کنید. از هر جای دیگری، مثلاً project root، می توانید به سادگی package را معرفی کنید، به عنوان مثال: dlv debug github.com/me/foo/cmd/foo . برای ارسال flag‌ها به برنامه خود، آنها را به صورت زیر جدا سازی کنید:

--dlv debug github.com/me/foo/cmd/foo -- -arg1 value

فراخوانی آن دستور باعث می شود که Delve برنامه را به روشی مناسب برای اشکال زدایی کامپایل کند و سپس برنامه اجرا شده و دیباگر به برنامه attach می شود و debug session را شروع می کند. حالا، هنگامی که جلسه  debug session برای اولین بار شروع شده است، شما در ابتدای شروع اولیه برنامه هستید. برای رسیدن به جایی که مورد نظر هست باید یک یا دو breakpoint تعیین کنید و اجرا را تا آن نقطه ادامه دهید.

به عنوان مثال، برای ادامه اجرا به تابع  main برنامه:

 1$ dlv debug github.com/me/foo/cmd/foo
 2Type 'help' for list of commands.
 3(dlv) break main.main
 4Breakpoint 1 set at 0x49ecf3 for main.main() ./test.go:5
 5(dlv) continue
 6> main.main() ./test.go:5 (hits goroutine(1):1 total:1) (PC: 0x49ecf3)
 7     1:	package main
 8     2:	
 9     3:	import "fmt"
10     4:	
11=>   5:	func main() {
12     6:		fmt.Println("delve test")
13     7:	}
14(dlv) 

4.2.5.4 Debugging tests #

با توجه به ساختار دایرکتوری مشابه با بالا، می توانید کد خود را با اجرای مجموعه آزمایشی خود اشکال زدایی کنید. برای این کار می توانید از subcommand یا دستور dlv test استفاده کنید، که همان package path اختیاری را به عنوان  dlv debug طی می کند و در صورت عدم ارائه آرگومان، package فعلی را نیز می سازد.

 1$ dlv test github.com/me/foo/pkg/baz
 2Type 'help' for list of commands.
 3(dlv) funcs test.Test*
 4/home/me/go/src/github.com/me/foo/pkg/baz/test.TestHi
 5(dlv) break TestHi
 6Breakpoint 1 set at 0x536513 for /home/me/go/src/github.com/me/foo/pkg/baz/test.TestHi() ./test_test.go:5
 7(dlv) continue
 8> /home/me/go/src/github.com/me/foo/pkg/baz/test.TestHi() ./bar_test.go:5 (hits goroutine(5):1 total:1) (PC: 0x536513)
 9     1:	package baz
10     2:	
11     3:	import "testing"
12     4:	
13=>   5:	func TestHi(t *testing.T) {
14     6:		t.Fatal("implement me!")
15     7:	}
16(dlv) 

همانطور که می بینید، ما شروع به دیباگ کردن یک test binary کردیم، تابع تست خود را از طریق دستور  funcs پیدا کردیم که یک regexp برای فیلتر کردن لیست توابع می گیرد، حالا یک breakpoint تعیین می کنیم و سپس اجرا را ادامه می دهیم تا زمانی که به آن breakpoint رسیدیم.

برای اطلاعات بیشتر در مورد subcommands که می‌توانید استفاده کنید،  dlv help را تایپ کنید، و یک بار در debug session می‌توانید با تایپ help در هر زمانی، تمام دستورات موجود را مشاهده کنید.

4.2.5.5 Synopsis و گزینه های Command line options #

Delve شما را قادر می سازد تا با کنترل اجرای فرآیند، ارزیابی متغیرها و ارائه اطلاعات state thread / goroutine، همینطور CPU register state و موارد دیگر، با برنامه خود تعامل داشته باشید.

هدف این ابزار ارائه یک رابط ساده و در عین حال قدرتمند برای اشکال زدایی برنامه های Go است. flagبرای استفاده از قابلیت های delve باید flagها را به برنامه‌ای که با استفاده از آن اشکال‌زدایی می‌کنید ارسال کنید، به کمک دستور  -- برای مثال:

dlv exec ./hello -- server --config conf/config.toml

در سایر گزینه ها داریم:

 1
 2
 3      --accept-multiclient               Allows a headless server to accept multiple client connections via JSON-RPC or DAP.
 4      --allow-non-terminal-interactive   Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
 5      --api-version int                  Selects JSON-RPC API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
 6      --backend string                   Backend selection (see 'dlv help backend'). (default "default")
 7      --build-flags string               Build flags, to be passed to the compiler. For example: --build-flags="-tags=integration -mod=vendor -cover -v"
 8      --check-go-version                 Exits if the version of Go in use is not compatible (too old or too new) with the version of Delve. (default true)
 9      --disable-aslr                     Disables address space randomization
10      --headless                         Run debug server only, in headless mode. Server will accept both JSON-RPC or DAP client connections.
11  -h, --help                             help for dlv
12      --init string                      Init file, executed by the terminal client.
13  -l, --listen string                    Debugging server listen address. (default "127.0.0.1:0")
14      --log                              Enable debugging server logging.
15      --log-dest string                  Writes logs to the specified file or file descriptor (see 'dlv help log').
16      --log-output string                Comma separated list of components that should produce debug output (see 'dlv help log')
17      --only-same-user                   Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
18  -r, --redirect stringArray             Specifies redirect rules for target process (see 'dlv help redirect')
19      --wd string                        Working directory for running the program.

همینطور دستورات زیر را داریم:

  • dlv attach - Attach to running process and begin debugging.

  • dlv connect - Connect to a headless debug server with a terminal client.

  • dlv core - Examine a core dump.

  • dlv dap - Starts a headless TCP server communicating via Debug Adaptor Protocol (DAP).

  • dlv debug - Compile and begin debugging main package in current directory, or the package specified.

  • dlv exec - Execute a precompiled binary, and begin a debug session.

  • dlv replay - Replays a rr trace.

  • dlv run - Deprecated command. Use ‘debug’ instead.

  • dlv test - Compile test binary and begin debugging program.

  • dlv trace - Compile and begin tracing program.

  • dlv version - Prints version.

  • dlv log - Help about logging flags

  • dlv backend - Help about the --backend flag