Go語言編程通過dwarf獲取內(nèi)聯(lián)函數(shù)
dwarf組成
dwarf 由 The Debugging Information Entry 。
type Entry struct {
Offset Offset
Tag Tag // 描述其類型
Children bool
Field []Field // 包含的字段
}
不同的 entry 有不同的類型:
- tag compile unit, 在 go 中就表示一個 package 下的所有源代碼文件。
- tag sub program, 表示函數(shù)
一個 entry 有不同的 attr:
AT_low_pc,AT_high_pc分別代表函數(shù)的 起始/結(jié)束 PC地址- AttrName 表示名字
對于函數(shù):
package s
func Leaf(lx, ly int) int {
return (lx << 7) ^ (ly >> uint32(lx&7))
}
func Top(tq int) int {
var tv [10]int
tr := Leaf(tq-13, tq+13)
return tr + tv[tr&3]
}
對應(yīng)的 entry:
DW_TAG_complication_unit{ // package s
DW_TAG_subprogram {
DW_AT_name: s.Top
DW_TAG_formal_parameter {
DW_AT_name: tq // 參數(shù)名
DW_AT_type: ... // 參數(shù)類型
}
}
}
如何將 addr 轉(zhuǎn)換為行號
- seekpc 返回該 pc 對應(yīng)的 complication unit。(類似于線性搜索,并且下一次調(diào)用 seekpc,會在上一次的之后開始搜索,所以 pc 最好需要排序)
- dwarf.Reader.Next() 將會循環(huán)讀取 entry,如果是函數(shù)并且地址在范圍內(nèi),就認(rèn)為找到了對應(yīng) address 的函數(shù)名。
- dwarf line reader 將會返回該 complication unit 對應(yīng)的 line 信息。
// go1.19/src/cmd/pprof/pprof.go:300
func pctoLine(f *elf.File, pc uint64) []driver.Frame {
dwarf, _ := f.DWARF()
r := dwarf.Reader()
unit, _ := r.SeekPC(pc)
lines, _ := dwarf.LineReader(unit)
var lentry godwarf.LineEntry
if err := lines.SeekPC(pc, &lentry); err != nil {
log.Fatal(err)
}
// Try to find the function name.
name := ""
FindName:
for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {
if entry.Tag == godwarf.TagSubprogram {
ranges, err := dwarf.Ranges(entry)
if err != nil {
log.Fatal(err)
}
for _, pcs := range ranges {
if pcs[0] <= pc && pc < pcs[1] {
var ok bool
// TODO: AT_linkage_name, AT_MIPS_linkage_name.
name, ok = entry.Val(godwarf.AttrName).(string)
if ok {
break FindName
}
}
}
}
}
frames := []driver.Frame{
{
Func: name,
File: lentry.File.Name,
Line: lentry.Line,
},
}
return frames
}
內(nèi)聯(lián)函數(shù)
使用 pprof 獲取地址:
其中 simplify1 是被內(nèi)聯(lián)的函數(shù)。
2152: 0x5a51a8 M=1 regexp/syntax.simplify1 /usr/local/go1.18/go/src/regexp/syntax/simplify.go:148 s=0
regexp/syntax.(*Regexp).Simplify /usr/local/go1.18/go/src/regexp/syntax/simplify.go:100 s=0
調(diào)用棧:

使用正常的方式獲取地址:
被內(nèi)聯(lián)的函數(shù)消失了,但是行號還是正確的。
regexp/syntax.(*Regexp).Simplify /usr/local/go1.18/go/src/regexp/syntax/simplify.go 148
如何展開內(nèi)聯(lián)函數(shù)
inline 內(nèi)聯(lián)設(shè)計:go.googlesource.com/proposal/+/…
如果我們有一個函數(shù):
package s
func Leaf(lx, ly int) int {
return (lx << 7) ^ (ly >> uint32(lx&7))
}
func Top(tq int) int {
var tv [10]int
tr := Leaf(tq-13, tq+13)
return tr + tv[tr&3]
}
那么對于 top 這個程序,我們會包含以下 entry:
- tag_subprogram: 表示 top 這個函數(shù)
- tag_subprogram: 表示 leaf 這個內(nèi)聯(lián)函數(shù)的抽象(含有函數(shù)名,不含有地址范圍)
- TAG_inlined_subroutine: 表示 leaf 這個內(nèi)聯(lián)函數(shù)的實體。(包含地址范圍等信息)
DW_TAG_subprogram {
DW_AT_name: s.Top
DW_TAG_formal_parameter {
DW_AT_name: tq
DW_AT_type: ...
}
// abstract inline function
DW_TAG_subprogram { // offset: D1
DW_AT_name: s.Leaf
DW_AT_inline : DW_INL_inlined (not declared as inline but inlined)
...
DW_TAG_formal_parameter { // offset: D2
DW_AT_name: lx
DW_AT_type: ...
}
DW_TAG_formal_parameter { // offset: D3
DW_AT_name: ly
DW_AT_type: ...
}
...
}
// inlined body of 'Leaf'
DW_TAG_inlined_subroutine {
DW_AT_abstract_origin: // reference to D1 above
DW_AT_call_file: 1
DW_AT_call_line: 15
DW_AT_ranges : ...
DW_TAG_formal_parameter {
DW_AT_abstract_origin: // reference to D2 above
DW_AT_location: ...
}
DW_TAG_formal_parameter {
DW_AT_abstract_origin: // reference to D3 above
DW_AT_location: ...
}
}
}
因此,通過 pc 地址不斷的循環(huán)遍歷 inline_subroutine 這種類型的 entry,我們就可以獲取所有的內(nèi)聯(lián)函數(shù)。
func inlineStackInternal(stack []*godwarf.Tree, n *godwarf.Tree, pc uint64) []*godwarf.Tree {
switch n.Tag {
case dwarf.TagSubprogram, dwarf.TagInlinedSubroutine, dwarf.TagLexDwarfBlock:
if pc == 0 || n.ContainsPC(pc) {
for _, child := range n.Children {
stack = inlineStackInternal(stack, child, pc)
}
if n.Tag == dwarf.TagInlinedSubroutine {
stack = append(stack, n)
}
}
}
return stack
}
然后,我們通過該 entry 的 AbstractOrigin 字段,獲取 abstract function,然后就可以得到函數(shù)名。
abstractOrigin := f.abstractSubprograms[e.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)]
使用 parca 展開內(nèi)聯(lián)函數(shù)
使用 parca 獲取內(nèi)聯(lián):
package main
import (
"debug/elf"
"log"
"os"
"strconv"
parcadwarf "github.com/parca-dev/parca/pkg/symbol/addr2line"
"github.com/parca-dev/parca/pkg/symbol/demangle"
)
func main() {
f, _ := elf.Open(os.Args[1])
debug, _ := parcadwarf.DWARF(nil, f, demangle.NewDemangler("simple", false))
pc, _ := strconv.ParseUint(os.Args[2], 16, 64)
log.Println(debug.PCToLines(pc))
}
pprof raw 的輸出,該 address fe1475 總共代表三個函數(shù):
1951: 0xfe1475 M=1 google.golang.org/grpc/metadata.Join /home/gitlab-runner/go/pkg/mod/google.golang.org/grpc@v1.48.0/metadata/metadata.go:141 s=0
google.golang.org/grpc/metadata.MD.Copy /home/gitlab-runner/go/pkg/mod/google.golang.org/grpc@v1.48.0/metadata/metadata.go:92 s=0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc.UnaryServerInterceptor.func1 /home/gitlab-runner/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc@v0.33.0/interceptor.go:304 s=0
輸出:
./dwarf/dwarf /home/data/server/otel-collector/data/otelcol-contrib fe1475
[{297 name:"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc.UnaryServerInterceptor.func1" filename:"/home/gitlab-runner/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc@v0.33.0/interceptor.go"} {138 name:"?" filename:"/home/gitlab-runner/go/pkg/mod/google.golang.org/grpc@v1.48.0/metadata/metadata.go"} {92 name:"?" filename:"/home/gitlab-runner/go/pkg/mod/google.golang.org/grpc@v1.48.0/metadata/metadata.go"}]
parca 輸出有以下問題
- 無法正確的獲取內(nèi)聯(lián)的函數(shù)名
- 內(nèi)聯(lián)函數(shù)的行號不正確。
- 內(nèi)聯(lián)函數(shù)順序不對
對于第一個問題,其實是 parca 只會將 pc 地址表示的當(dāng)前 complication unit 進行內(nèi)聯(lián)函數(shù)映射。
對于途中就是 interceptor 這個庫。
而內(nèi)聯(lián)的函數(shù)在 meatadata 這個庫,所以無法正確的獲取函數(shù)名。
對于第二個問題,由于內(nèi)聯(lián)函數(shù)展開后,獲取的是 DW_TAG_subprogram,它映射一個范圍內(nèi)的地址,自然也無法精確的獲取行號。
對于第三個問題,parca 寫錯了。
以上就是Go語言編程通過dwarf獲取內(nèi)聯(lián)函數(shù)的詳細(xì)內(nèi)容,更多關(guān)于Go dwarf獲取內(nèi)聯(lián)函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
gin解析json格式的數(shù)據(jù)出錯的處理方案
這篇文章主要介紹了gin解析json格式的數(shù)據(jù)出錯的處理方案,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03
使用VSCODE配置GO語言開發(fā)環(huán)境的完整步驟
Go語言是采用UTF8編碼的,理論上使用任何文本編輯器都能做Go語言開發(fā),大家可以根據(jù)自己的喜好自行選擇,下面這篇文章主要給大家介紹了關(guān)于使用VSCODE配置GO語言開發(fā)環(huán)境的完整步驟,需要的朋友可以參考下2022-11-11
go語言實現(xiàn)的memcache協(xié)議服務(wù)的方法
這篇文章主要介紹了go語言實現(xiàn)的memcache協(xié)議服務(wù)的方法,實例分析了Go語言使用memcache的技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-03-03
go?sync包中的互斥鎖Mutex和等待組WaitGroup使用詳解
這篇文章主要為大家介紹了go?sync包中的互斥鎖Mutex和等待組WaitGroup使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08

