Skip to content

Commit

Permalink
add gdb to debugging chapter 4.2
Browse files Browse the repository at this point in the history
  • Loading branch information
danny committed Jun 9, 2023
1 parent f030069 commit 6b48fe0
Showing 1 changed file with 378 additions and 0 deletions.
378 changes: 378 additions & 0 deletions content/chapter 4/4.2-debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,381 @@ Entering interactive mode (type "help" for commands, "o" for options)



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

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

توجه داشته باشید که هنگام اشکال زدایی برنامه های Go که با _standard toolchain_ ساخته شده اند، _[Delve](https://github.com/go-delve/delve)_ جایگزین بهتری برای GDB است. زیرا _Go runtime_ را بهتر تشخیص می‌دهد و ساختارهای داده و عبارات را بهتر از GDB درک می کند. Delve در حال حاضر از Linux، OSX و Windows در amd64 پشتیبانی می کند. برای به روزترین لیست پلتفرم های پشتیبانی شده، لطفاً به _[Delve documentation](https://github.com/go-delve/delve/tree/master/Documentation/installation)_ مراجعه کنید.

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

به طور خلاصه، دستورالعمل‌های زیر باید تنها به‌عنوان راهنمای نحوه استفاده از GDB در هنگام کارکرد آن در نظر گرفته شود، نه به عنوان تضمین موفقیت اجرای درست برنامه. علاوه بر این نمای کلی، ممکن است بخواهید به [GDB manual](https://sourceware.org/gdb/current/onlinedocs/gdb/) مراجعه کنید.

### 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](https://go.dev/pkg/runtime/#hdr-Environment_Variables) مراجعه کنید).

### 4.2.4.2 Common Operations

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

```
(gdb) list
(gdb) list _line_
(gdb) list _file.go_:_line_
(gdb) break _line_
(gdb) break _file.go_:_line_
(gdb) disas
```

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

```
(gdb) bt
(gdb) frame _n_
```

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

```go
(gdb) info locals
(gdb) info args
(gdb) p variable
(gdb) whatis variable
```

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

```go
(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 استفاده می کند.

- Pretty printing a string, slice, map, channel or interface:

(gdb) **p _var_**

- A $len() and $cap() function for strings, slices and maps:

(gdb) **p $len(_var_)**

- A function to cast interfaces to their dynamic types:

(gdb) **p $dtype(_var_)**
(gdb) **iface _var_**

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

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

(gdb) **info goroutines**
(gdb) **goroutine _n_ _cmd_**
(gdb) **help goroutine**

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

(gdb) **goroutine 12 bt**

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

(gdb) **goroutine all bt**

اگر می‌خواهید ببینید که این حالت چگونه کار می‌کند یا می‌خواهید آن را گسترش دهید، به [src/runtime/runtime-gdb.py](https://go.dev/src/runtime/runtime-gdb.py) در توزیع Go source نگاهی بیندازید. این به برخی از type های جادویی خاص (`hash<T,U>`) و متغیرهایی (runtime.m و runtime.g) بستگی دارد کهlinker ([src/cmd/link/internal/ld/dwarf.go](https://go.dev/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](https://go.dev/cmd/go/#hdr-Environment_variables) قرار دهید تا مجبور نباشید هر بار آدرس آن را مشخص کنید.)

### 4.2.4.4 مثال های GDB

در این آموزش ما باینری unit tests پکیج [regexp](https://go.dev/pkg/regexp/) را بررسی می کنیم. برای ساخت باینری، به  `GOROOT/src/regexp$` تغییر دهید و `go test -c` را اجرا کنید. این باید یک فایل اجرایی به نام `regexp.test` تولید کند.

#### شروع دیباگ

Launch GDB, debugging `regexp.test`:

```shell
$ gdb regexp.test
GNU gdb (GDB) 7.2-gg8
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv 3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
Type "show copying" and "show warranty" for licensing/warranty details.
This GDB was configured as "x86_64-linux".

Reading symbols from /home/user/go/src/regexp/regexp.test...
done.
Loading Go Runtime support.
(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/ باشد):

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

#### بررسی کردن source

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

```
(gdb) l
```

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

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

```shell
(gdb) l regexp.go:1
(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 تنظیم کنید:
‍‍
```shell
(gdb) b 'regexp.TestFind'
Breakpoint 1 at 0x424908: file /home/user/go/src/regexp/find_test.go, line 148.
```

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

```shell
(gdb) run
Starting program: /home/user/go/src/regexp/regexp.test

Breakpoint 1, regexp.TestFind (t=0xf8404a89c0) at /home/user/go/src/regexp/find_test.go:148
148 func TestFind(t *testing.T) {
```
اجرا در breakpoint متوقف شده است. ببینید کدام گوروتین ها در حال اجرا هستند و چه کار می کنند:
```
(gdb) info goroutines
1 waiting runtime.gosched
* 13 running runtime.goexit
```
موردی که با * مشخص شده است، گوروتین فعلی است.
#### بررسی کردن stack
به خروجی از stack برای جایی که برنامه را متوقف کرده ایم نگاه کنید:
```shell
(gdb) bt _# backtrace_
#0 regexp.TestFind (t=0xf8404a89c0) at /home/user/go/src/regexp/find_test.go:148
#1 0x000000000042f60b in testing.tRunner (t=0xf8404a89c0, test=0x573720) at /home/user/go/src/testing/testing.go:156
#2 0x000000000040df64 in runtime.initdone () at /home/user/go/src/runtime/proc.c:242
#3 0x000000f8404a89c0 in ?? ()
#4 0x0000000000573720 in ?? ()
#5 0x0000000000000000 in ?? ()
```
گوروتین دیگر، شماره 1، در runtime.gosched گیر کرده و در channel receive مسدود شده است:
```shell
(gdb) goroutine 1 bt
#0 0x000000000040facb in runtime.gosched () at /home/user/go/src/runtime/proc.c:873
#1 0x00000000004031c9 in runtime.chanrecv (c=void, ep=void, selected=void, received=void)
at /home/user/go/src/runtime/chan.c:342
#2 0x0000000000403299 in runtime.chanrecv1 (t=void, c=void) at/home/user/go/src/runtime/chan.c:423
#3 0x000000000043075b in testing.RunTests (matchString={void (struct string, struct string, bool *, error *)}
0x7ffff7f9ef60, tests= []testing.InternalTest = {...}) at /home/user/go/src/testing/testing.go:201
#4 0x00000000004302b1 in testing.Main (matchString={void (struct string, struct string, bool *, error *)}
0x7ffff7f9ef80, tests= []testing.InternalTest = {...}, benchmarks= []testing.InternalBenchmark = {...})
at /home/user/go/src/testing/testing.go:168
#5 0x0000000000400dc1 in main.main () at /home/user/go/src/regexp/_testmain.go:98
#6 0x00000000004022e7 in runtime.mainstart () at /home/user/go/src/runtime/amd64/asm.s:78
#7 0x000000000040ea6f in runtime.initdone () at /home/user/go/src/runtime/proc.c:243
#8 0x0000000000000000 in ?? ()
```
همانطور که stack frame نشان می دهد، انتظار می رود الان باید در حال اجرای تابع regexp.TestFind باشیم.
```shell
(gdb) info frame
Stack level 0, frame at 0x7ffff7f9ff88:
rip = 0x425530 in regexp.TestFind (/home/user/go/src/regexp/find_test.go:148);
saved rip 0x430233
called by frame at 0x7ffff7f9ffa8
source language minimal.
Arglist at 0x7ffff7f9ff78, args: t=0xf840688b60
Locals at 0x7ffff7f9ff78, Previous frame's sp is 0x7ffff7f9ff88
Saved registers:
rip at 0x7ffff7f9ff80
```
دستور info locals همه متغیرهای محلی تابع و مقادیر آنها را فهرست می‌کند، اما استفاده از آن کمی خطرناک است، زیرا سعی می‌کند متغیرهای اولیه را نیز چاپ کند. برش‌های بدون مقدار اولیه ممکن است باعث شوند که gdb سعی کند آرایه‌های بزرگ دلخواه را چاپ کند.
آرگومان های تابع:
```shell
(gdb) info args
t = 0xf840688b60
```
هنگام چاپ آرگومان، توجه کنید که نشانگر یک مقدار Regexp است. توجه داشته باشید که GDB به اشتباه * را در سمت راست نام تایپ قرار داده و یک کلمه کلیدی 'struct' به سبک سنتی C ساخته است.
```
(gdb) p re
(gdb) p t
$1 = (struct testing.T *) 0xf840688b60
(gdb) p t
$1 = (struct testing.T *) 0xf840688b60
(gdb) p *t
$2 = {errors = "", failed = false, ch = 0xf8406f5690}
(gdb) p *t->ch
$3 = struct hchan<*testing.T>
```
که ساختار  `hchan<*testing.T>` نمایش  runtime-internal یک کانال است و در حال حاضر خالی است، وگرنه gdb محتویات آن را به زیبایی چاپ می کرد.
حالا به قسمت مهم پیمایش دیباگر میرسیم که با حرف n صورت می‌گیرد.
پیمایش دیباگر :
```shell
(gdb) n _# execute next line_
149 for _, test := range findTests {
(gdb) _# enter is repeat_
150 re := MustCompile(test.pat)
(gdb) p test.pat
$4 = ""
(gdb) p re
$5 = (struct regexp.Regexp *) 0xf84068d070
(gdb) p *re
$6 = {expr = "", prog = 0xf840688b80, prefix = "", prefixBytes = []uint8, prefixComplete = true,
prefixRune = 0, cond = 0 '\000', numSubexp = 0, longest = false, mu = {state = 0, sema = 0},
machine = []*regexp.machine}
(gdb) p *re->prog
$7 = {Inst = []regexp/syntax.Inst = {{Op = 5 '\005', Out = 0, Arg = 0, Rune = []int}, {Op =
6 '\006', Out = 2, Arg = 0, Rune = []int}, {Op = 4 '\004', Out = 0, Arg = 0, Rune = []int}},
Start = 1, NumCap = 2}
```
می توانیم با حرف 's' وارد فراخوانی Stringfunction شویم:
```
(gdb) s
regexp.(*Regexp).String (re=0xf84068d070, noname=void) at /home/user/go/src/regexp/regexp.go:97
97 func (re *Regexp) String() string {
```
یک stack trace بگیرید تا ببینید کجا هستیم:
```
(gdb) bt
#0 regexp.(*Regexp).String (re=0xf84068d070, noname=void)
at /home/user/go/src/regexp/regexp.go:97
#1 0x0000000000425615 in regexp.TestFind (t=0xf840688b60)
at /home/user/go/src/regexp/find_test.go:151
#2 0x0000000000430233 in testing.tRunner (t=0xf840688b60, test=0x5747b8)
at /home/user/go/src/testing/testing.go:156
#3 0x000000000040ea6f in runtime.initdone () at /home/user/go/src/runtime/proc.c:243
....
```
به source code نگاه کنید:
```
(gdb) l
92 mu sync.Mutex
93 machine []*machine
94 }
95
96 // String returns the source text used to compile the regular expression.
97 func (re *Regexp) String() string {
98 return re.expr
99 }
100
101 // Compile parses a regular expression and returns, if successful,
```
Pretty Printing
مکانیسم چاپ زیبا GDB توسط regexp matches به صورت زیر می‌باشد:
```
(gdb) p utf
$22 = []uint8 = {0 '\000', 0 '\000', 0 '\000', 0 '\000'}
```
از آنجایی که slice ها، آرایه ها و رشته ها اصلا اشاره‌گرهای C نیستند در نتیجه GDB نمی تواند عملیات subscripting را برای شما تفسیر کند، اما می توانید برای انجام این کار به نمایش runtime نگاه کنید (tab completion در اینجا کاربرد دارد):
```shell
(gdb) p slc
$11 = []int = {0, 0}
(gdb) p slc->* _<TAB>_
array slc len
(gdb) p slc->array
$12 = (int *) 0xf84057af00
(gdb) p slc->array[1]
$13 = 0
```
توابع extension یا افزونه `$len` و `$cap` روی strings, arrays , slices کار می‌کنند:
```shell
(gdb) p $len(utf)
$23 = 4
(gdb) p $cap(utf)
$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 گرفته شده‌اند.)
```shell
(gdb) p i
$4 = {str = "cbb"}
(gdb) whatis i
type = regexp.input
(gdb) p $dtype(i)
$26 = (struct regexp.inputBytes *) 0xf8400b4930
(gdb) iface i
regexp.input: struct regexp.inputBytes *
```
## 4.2.5 معرفی دیباگر DELVE
درحال تکمیل .....

0 comments on commit 6b48fe0

Please sign in to comment.