網(wǎng)站首頁(yè) 編程語言 正文
dwarf組成
dwarf 由 The Debugging Information Entry 。
type Entry struct { Offset Offset Tag Tag // 描述其類型 Children bool Field []Field // 包含的字段 }
不同的 entry 有不同的類型:
- tag compile unit, 在 go 中就表示一個(gè) package 下的所有源代碼文件。
- tag sub program, 表示函數(shù)
一個(gè) entry 有不同的 attr:
-
AT_low_pc
,AT_high_pc
分別代表函數(shù)的 起始/結(jié)束 PC地址 - AttrName 表示名字
對(duì)于函數(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] }
對(duì)應(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)換為行號(hào)
- seekpc 返回該 pc 對(duì)應(yīng)的 complication unit。(類似于線性搜索,并且下一次調(diào)用 seekpc,會(huì)在上一次的之后開始搜索,所以 pc 最好需要排序)
- dwarf.Reader.Next() 將會(huì)循環(huán)讀取 entry,如果是函數(shù)并且地址在范圍內(nèi),就認(rèn)為找到了對(duì)應(yīng) address 的函數(shù)名。
- dwarf line reader 將會(huì)返回該 complication unit 對(duì)應(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ù)消失了,但是行號(hào)還是正確的。
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è)計(jì):go.googlesource.com/proposal/+/…
如果我們有一個(gè)函數(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] }
那么對(duì)于 top 這個(gè)程序,我們會(huì)包含以下 entry:
- tag_subprogram: 表示 top 這個(gè)函數(shù)
- tag_subprogram: 表示 leaf 這個(gè)內(nèi)聯(lián)函數(shù)的抽象(含有函數(shù)名,不含有地址范圍)
- TAG_inlined_subroutine: 表示 leaf 這個(gè)內(nèi)聯(lián)函數(shù)的實(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 總共代表三個(gè)函數(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ù)的行號(hào)不正確。
- 內(nèi)聯(lián)函數(shù)順序不對(duì)
對(duì)于第一個(gè)問題,其實(shí)是 parca 只會(huì)將 pc 地址表示的當(dāng)前 complication unit 進(jìn)行內(nèi)聯(lián)函數(shù)映射。
對(duì)于途中就是 interceptor 這個(gè)庫(kù)。
而內(nèi)聯(lián)的函數(shù)在 meatadata 這個(gè)庫(kù),所以無法正確的獲取函數(shù)名。
對(duì)于第二個(gè)問題,由于內(nèi)聯(lián)函數(shù)展開后,獲取的是 DW_TAG_subprogram,它映射一個(gè)范圍內(nèi)的地址,自然也無法精確的獲取行號(hào)。
對(duì)于第三個(gè)問題,parca 寫錯(cuò)了。
原文鏈接:https://juejin.cn/post/7168807606146826254
相關(guān)推薦
- 2024-01-11 String數(shù)組轉(zhuǎn)List的三種方式
- 2023-01-10 Qt實(shí)現(xiàn)可以計(jì)算大數(shù)的簡(jiǎn)單計(jì)算器_C 語言
- 2022-08-19 存儲(chǔ)引擎的應(yīng)用場(chǎng)景
- 2023-01-17 關(guān)于最大池化層和平均池化層圖解_python
- 2022-03-07 Go?container包的介紹_Golang
- 2022-05-28 Entity?Framework?Core表名映射_實(shí)用技巧
- 2023-01-18 Python中的裝飾器使用_python
- 2022-07-22 定時(shí)任務(wù)和延時(shí)任務(wù)詳解
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支