Instruksi dalam dokumen ini hanya berlaku untuk perkakas toolchain standar
(perkakas dan compiler Go gc
).
Gccgo memiliki dukungan gdb langsung.
Perlu diketahui bahwa
Delve
adalah alternatif lain dari GDB untuk melakukan debugging pada program Go
yang dibangun dengan toolchain standar.
Delve mengenal runtime, struktur data, dan ekspresi Go lebih baik
daripada GDB.
Delve saat ini mendukung Linux, OSX, dan Windows pada amd64
.
Untuk daftar platform yang didukung, lihatlah
dokumentasi Delve.
GDB tidak begitu baik mengenali program Go. Managemen stack, threading, dan runtime memiliki aspek-aspek yang cukup berbeda dari model eksekusi yang diharapkan GDB yang dapat membingungkan debugger dan menyebabkan hasil yang tidak tepat bahkan untuk program yang dibuat dengan gccgo. Akibatnya, walaupun GDB dapat berguna pada situasi tertentu (misalnya, melakukan debug pada kode Cgo, atau debug pada runtime itu sendiri), ia bukanlah debugger yang handal untuk program Go, terutama program yang bergantung pada konkurensi. Lebih lanjut lagi, saat ini bukanlah prioritas dari proyek Go untuk mengatasi masalah ini.
Instruksi-instruksi di bawah ini sebaiknya digunakan sebagai panduan tentang bagaimana menggunakan GDB, tidak menjamin selalu sukses. Selain artikel ini Anda mungkin bisa membaca lebih lanjut pada manual GDB.
Pendahuluan
Saat Anda mengompilasi kode Go dengan toolchain gc
pada Linux, macOS,
FreeBSD, atau NetBSD, program yang dihasilkan berisi informasi debug dengan
format DWARFv4 yang mana versi terbaru (≥7.5) dari GDB dapat gunakan untuk
menginspeksi sebuah proses secara langsung atau sebuah core dump.
Gunakan opsi '-w’ untuk menghilangkan informasi debug (misalnya, go build
-ldflags=-w prog.go
).
Kode yang dibangkitkan oleh compiler gc mengikutkan baris pemanggilan fungsi
dan registrasi variabel-variabel.
Optimisasi ini terkadang membuat debugging dengan gdb menjadi sukar.
Jika Anda butuh mematikan optimasi ini, bangun program Anda menggunakan go
build -gcflags=all="-N -l"
.
Jika Anda ingin menggunakan gdb untuk menginspeksi sebuah core dump, Anda
dapat memicu dump pada saat program crash, pada sistem yang membolehkan,
dengan menset GOTRACEBACK=crash
di lingkungan sistem (lihat
dokumentasi paket
runtime
untuk informasi lebih lanjut).
Operasi umum
-
Tampilkan berkas dan nomor baris dari kode, set breakpoint dan uraikan baris perintah kode,
(gdb) list (gdb) list line (gdb) list file.go:line (gdb) break line (gdb) break file.go:line (gdb) disas
-
Tampilkan backtrace dan stack frame:
(gdb) bt (gdb) frame n
-
Tampilkan nama, tipe, dan lokasi dari variabel lokal pada stack frame, argumen, dan nilai kembalian:
(gdb) info locals (gdb) info args (gdb) p variable (gdb) whatis variable
-
Tampilkan nama, tipe, dan lokasi dari variabel global:
(gdb) info variables regexp
Ekstensi Go
Mekanisme ekstensi pada GDB membolehkan memuat skrip ekstensi untuk program tertentu. Perkakas Go menggunakan ekstensi ini untuk mengembangkan GDB dengan beberapa perintah yang berguna untuk menginspeksi internal dari kode runtime (seperti goroutine) dan untuk mencetak tipe map, slice, dan channel.
-
Cetak sebuah string, slice, map, channel, atau interface,
(gdb) p var
-
Fungsi
$len()
dan$cap()
untuk string, slice, dan map:(gdb) p $len(var)
-
Fungsi untuk mengganti interface menjadi tipe dinamisnya:
(gdb) p $dtype(var) (gdb) iface var
Masalah diketahui: GDB tidak bisa secara otomatis mencari tipe dinamis dari sebuah nilai interface jika nama panjangya berbeda dengan nama pendeknya (cukup mengganggu saat mencetak stack trace, pencetak GDB balik lagi menggunakan nama tipe yang pendek dan sebuah pointer).
-
Memeriksa goroutine:
(gdb) info goroutines (gdb) goroutine n cmd (gdb) help goroutine
Sebagai contohnya,
(gdb) goroutine 12 bt
Anda dapat menginspeksi semua goroutine dengan mengirim
all
bukan ID dari goroutine tertentu. Misalnya:(gdb) goroutine all bt
Jika Anda ingin melihat bagaimana ia bekerja, atau ingin mengubahnya, lihatlah
src/runtime/runtime-gdb.py
di dalam distribusi sumber Go.
Skrip ekstensi tersebut bergantung pada tipe-tipe khusus (hash<T,U>
) dan
variabel (runtime.m
dan runtime.g
) yang linker
(
src/cmd/link/internal/ld/dwarf.go)
pastikan didescripsikan dalam format DWARF.
Jika Anda tertarik pada bentuk informasi debug, jalankan objdump -W a.out
dan lihatlah pada bagian .debug_*
.
Masalah yang diketahui
-
Pencetakan string hanya bisa digunakan pada tipe string, bukan pada tipe yang diturunkan dari string.
-
Informasi tipe hilang pada bagian C dari pustaka runtime
-
GDB tidak mengenal kualifikasi nama Go dan memperlakukan "fmt.Print" sebagai string harfiah dengan "." harus diberi tanda kutip.
-
Pada Go 1.11, informasi debug selalu dikompres. Versi terdahulu dari gdb, seperti yang tersedia pada macOS, tidak mengenal kompresi. Anda dapat membangkitkan informasi debug tanpa kompresi dengan menggunakan
go build -ldflags=-compressdwarf=false
. (Supaya lebih gampang Anda dapat menyimpan opsi-ldflags
dalam variabel lingkungan GOFLAGS supaya Anda tidak perlu mengulangi penulisannya lagi).
Tutorial
Dalam tutorial ini kita akan menginspeksi binari dari unit test pada paket
regexp.
Untuk membuat binari tersebut, pindahlah ke $GOROOT/src/regexp
dan jalankan
go test -c
.
Perintah tersebut seharusnya menghasilkan sebuah berkas program bernama
regepx.test
.
Memulai
Jalankan GDB untuk men-debug regexp.test
:
$ 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)
Pesan "Loading Go Runtime support" berarti GDB memuat ekstensi dari
$GOROOT/src/runtime/runtime-gdb.py
.
Untuk membantu GDB menemukan sumber runtime Go dan skrip pendukung lainnya,
kirimkan $GOROOT
dengan opsi -d
:
$ gdb regexp.test -d $GOROOT
Jika GDB masih tetap tidak bisa menemukan direktori atau skrip tersebut, Anda bisa memuatnya secara manual dengan memberitahu gdb (dengan asumsi Anda memiliki sumber kode Go di ~/go/):
(gdb) source ~/go/src/runtime/runtime-gdb.py Loading Go Runtime support.
Menginspeksi sumber
Gunakan perintah “l
” atau “list
” untuk menginspeksi kode sumber.
(gdb) l
Tampilkan bagian tertentu dari sumber dengan mengirim parameter pada
“list
” dengan nama fungsi (harus disertai dengan nama paketnya).
(gdb) l main.main
Tampilkan isi berkas tertentu dan nomor baris kode:
(gdb) l regexp.go:1 (gdb) # Tekan enter untuk mengulangi perintah sebelumnya.
Penamaan
Nama variabel dan fungsi harus disertai dengan nama paket di mana mereka
berada.
Fungsi Compile
yang ada dalam paket regexp
dikenal oleh GDB sebagai
regexp.Compile
.
Method harus disertai dengan tipe penerimanya.
Misalnya, method String
pada tipe Regexp
dikenal oleh GDB dengan
regexp.(*Regexp).String
.
Variabel yang menimpa variabel dengan nama yang sama secara otomatis diberi sufiks dengan nomor dalam informasi debug. Variabel yang diacu oleh sebuah closure akan muncul sebagai pointer dengan prefiks '&'.
Menset breakpoint
Set sebuah breakpoint pada fungsi TestFind
:
(gdb) b 'regexp.TestFind' Breakpoint 1 at 0x424908: file /home/user/go/src/regexp/find_test.go, line 148.
Jalankan program:
(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) {
Eksekusi telah berhenti pada breakpoint. Lihatlah goroutine mana saja yang sedang berjalan, dan apa yang mereka lakukan:
(gdb) info goroutines 1 waiting runtime.gosched * 13 running runtime.goexit
Baris yang diawali dengan `*` adalah goroutine yang aktif sekarang.
Menginspeksi stack
Untuk melihat stack trace di posisi program kita berhenti:
(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 ?? ()
Goroutine yang lain, nomor 1, tersendat dalam runtime.gosched
, ditahan pada
penerima channel:
(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 memperlihatkan kita sekarang sedang mengeksekusi fungsi
regexp.TestFind
, seperti yang diharapkan.
(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
Perintah info locals
menampilkan semua variabel lokal terhadap fungsi dan
nilainya, namun sedikit berbahaya untuk digunakan, secara ia mencoba mencetak
variabel yang belum diinisiasi.
Slice yang belum diinisiasi bisa menyebabkan gdb mencetak array yang sangat
besar.
Untuk melihat argumen fungsi:
(gdb) info args t = 0xf840688b60
Saat mencetak argumen, perhatikan bahwa ia adalah sebuah pointer ke nilai
Regexp.
GDB secara tidak tepat menaruh *
pada sisi kanan dari nama tipe dan
mencetak kata 'struct’, dalam gaya tradisional 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>
Struct hchan<*testing.T>
adalah representasi runtime-internal dari sebuah
channel.
Saat ini kosong, kalau tidak gdb akan mencetak isinya.
Melangkah ke perintah selanjutnya:
(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}
Kita dapat melangkah ke pemanggilan Stringfunction
dengan "s":
(gdb) s regexp.(*Regexp).String (re=0xf84068d070, noname=void) at /home/user/go/src/regexp/regexp.go:97 97 func (re *Regexp) String() string {
Ambil stack trace untuk melihat posisi kita sekarang:
(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 ....
Lihat pada kode sumber:
(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,
Pencetakan
Mekanisme pencetakan pada GDB dipicu oleh kecocokan regexp pada nama tipe. Sebagai contoh pada slice:
(gdb) p utf $22 = []uint8 = {0 '\000', 0 '\000', 0 '\000', 0 '\000'}
Secara slice, array, dan string bukanlah C pointer, GDB tidak dapat menerjemahkan operasi tersebut untuk Anda, namun Anda dapat melihat ke dalam representasi runtime:
(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
Fungsi ekstensi $len
dan $cap
bekerja pada string, array, dan slice:
(gdb) p $len(utf) $23 = 4 (gdb) p $cap(utf) $24 = 4
Channel dan map adalah tipe "reference", yang mana gdb tampilkan sebagai
pointer ke tipe bentukan-C++ seperti hash<int,string>*
.
Interface direpresentasikan dalam runtime sebagai sebuah pointer ke tipe
pen-deskripsi dan sebuah pointer ke nilai.
Ekstensi runtime pada GDB menerjemahkan ini dan secara otomatis memicu
pencetakan untuk tipe runtime.
Fungsi ekstensi $dtype
menerjemahkan tipe dinamis untuk Anda (contoh ini
diambil dari breakpoint pada regexp.go baris 293.)
(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 * ---