Go语言编程通过dwarf获取内联函数

   2023-02-08 学习力0
核心提示:目录dwarf组成如何将 addr 转换为行号内联函数如何展开内联函数使用 parca 展开内联函数parca 输出有以下问题dwarf组成dwarf 由 The Debugging Information Entry 。type Entry struct {Offset OffsetTagTag // 描述其类型Children boolField[]Field // 包含

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, 表示函数

一个 entry 有不同的 attr:

  • AT_low_pc, AT_high_pc 分别代表函数的 起始/结束 PC地址
  • AttrName 表示名字

对于函数:

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]
    }

对应的 entry:

DW_TAG_complication_unit{ // package s
DW_TAG_subprogram {
      DW_AT_name:            s.Top
      DW_TAG_formal_parameter {
         DW_AT_name:         tq  // 参数名
         DW_AT_type:         ... // 参数类型
      }  
   }
}   

如何将 addr 转换为行号

  • seekpc 返回该 pc 对应的 complication unit。(类似于线性搜索,并且下一次调用 seekpc,会在上一次的之后开始搜索,所以 pc 最好需要排序)
  • dwarf.Reader.Next() 将会循环读取 entry,如果是函数并且地址在范围内,就认为找到了对应 address 的函数名。
  • dwarf line reader 将会返回该 complication unit 对应的 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
}

内联函数

使用 pprof 获取地址:

其中 simplify1 是被内联的函数。

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

调用栈:

Go语言编程通过dwarf获取内联函数

使用正常的方式获取地址:

被内联的函数消失了,但是行号还是正确的。

regexp/syntax.(*Regexp).Simplify /usr/local/go1.18/go/src/regexp/syntax/simplify.go 148

如何展开内联函数

inline 内联设计:go.googlesource.com/proposal/+/…

如果我们有一个函数:

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 这个函数
  • tag_subprogram: 表示 leaf 这个内联函数的抽象(含有函数名,不含有地址范围)
  • TAG_inlined_subroutine: 表示 leaf 这个内联函数的实体。(包含地址范围等信息)
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 地址不断的循环遍历 inline_subroutine 这种类型的 entry,我们就可以获取所有的内联函数。

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,然后就可以得到函数名。

abstractOrigin := f.abstractSubprograms[e.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)]

使用 parca 展开内联函数

使用 parca 获取内联:

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 总共代表三个函数:

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 输出有以下问题

  • 无法正确的获取内联的函数名
  • 内联函数的行号不正确。
  • 内联函数顺序不对

对于第一个问题,其实是 parca 只会将 pc 地址表示的当前 complication unit 进行内联函数映射。

对于途中就是 interceptor 这个库。

而内联的函数在 meatadata 这个库,所以无法正确的获取函数名。

对于第二个问题,由于内联函数展开后,获取的是 DW_TAG_subprogram,它映射一个范围内的地址,自然也无法精确的获取行号。

对于第三个问题,parca 写错了。

以上就是Go语言编程通过dwarf获取内联函数的详细内容,更多关于Go dwarf获取内联函数的资料请关注其它相关文章!

原文地址:https://juejin.cn/post/7168807606146826254
 
标签: Go dwarf 内联函数
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 《黑马程序员》 category分类的使用(Objective
    分类的作用:在不改变原来类的基础上,可以给类增加一些方法。使用注意 : ①  分类只能增加方法,不可以增加成员变量                ②  分类的方法在实现中可以访问成员变量,不过成员变量必须手动实现。               
    03-16
  • intellij idea go go go!
    安装如下:1. intellij idea2. jdk-8u31-windows-x643.1. scala:Scala是一门多范式的编程语言,一种类似java的编程语言[1]  ,设计初衷是实现可伸缩的语言[2]  、并集成面向对象编程和函数式编程的各种特性。 2.IdeaVimEmulates Vim editorVim是一个类似
    03-08
  • 用 VSCode 搭建 Go 语言开发环境
    用 VSCode 搭建 Go 语言开发环境
    介绍这是来自 Funai Research Institute Digital 的 osw。由于我决定使用 Go 语言进行工作,所以我会在学习时将其记录在备忘录中。如果您可以参考,我将不胜感激。目标听众从现在开始学习 Go 语言的人我正在考虑使用 VSCode本次使用的环境这次,我们将基于以
    03-08
  • 我比较了 Go 和 C# 的速度
    我比较了 Go 和 C# 的速度
    我在 Go 和 C# 之间进行了速度比较。我通常使用 C#,但我有机会使用 Go,并且由于传闻 Go 速度很快,所以我实际测量了它。测量内容我在 Go 和 C# 中执行了一个简单的循环和判断过程,以查看整数 2 到 N 是否为质数。来源是Github参考。测量模式 逻辑内核 8 Wi
    03-08
  • Nginx动态路由的新姿势:使用Go取代lua nginx路由规则
    Nginx动态路由的新姿势:使用Go取代lua nginx路
    导语: 在Nitro 中, 我们需要一款专业的负载均衡器。 经过一番研究之后,Mihai Todor和我使用Go构建了基于Nginx、Redis 协议的路由器解决方案,其中nginx负责所有繁重工作,路由器本身并不承载流量。 这个解决方案过去一年在生产环境中运行顺畅。 以下是我
    03-08
  • 《zw版·Halcon-delphi系列原创教程》 Halcon分
    《zw版·Halcon-delphi系列原创教程》 Halcon分类函数012,polygon,多边形为方便阅读,在不影响说明的前提下,笔者对函数进行了简化::: 用符号“**”,替换:“procedure”:: 用大写字母“X”,替换:“IHUntypedObjectX”:: 省略了字符:“const”、“OleVa
    02-09
  • 设计模式之开放-封闭原则(引申出Objective-C中
    开放封闭原则(OCP原则The Open-Closed Principle)是面向对象的核心设计所在。它是说,软件开发实体(类、模块、函数等)应该可以扩展,但是不能修改。这个原则有两个特征,一个是说“对于扩展是开放的”,另一个是说“对于更改是封闭的”。我们在编写任何ap
    02-09
  • Objective-C——消息、Category和Protocol Objective-C
    Objective-C——消息、Category和Protocol Obje
    面向对象永远是个可以吐槽的话题,从开始提出到推崇备至,到充满质疑,一路走来让人唏嘘不已。面向对象的思想可谓历史悠久,20世纪70年代的Smalltalk可以说是面向对象语言的经典,直到今天我们依然将这门语言视为面向对象语言的基础。面向对象是大部分编程语
    02-09
  • Go语言使用goroutine及通道实现并发详解
    Go语言使用goroutine及通道实现并发详解
    目录使用通道接收数据阻塞接收数据非阻塞接收数据接收任意数据,忽略掉接收的数据循环接收数据使用通道接收数据在上一篇文章中介绍了通道以及使用通道发送数据,本篇接着了解通道的基本内容,如何使用通道接收数据;通道的接收同样使用"-"操作符;使用通道接
  • Go 数据结构之堆排序示例详解
    Go 数据结构之堆排序示例详解
    目录堆排序堆排序过程动画显示开始堆排序代码实现总结堆排序堆排序是一种树形选择排序算法。简单选择排序算法每次选择一个关键字最小的记录需要 O(n) 的时间,而堆排序选择一个关键字最小的记录需要 O(nlogn)的时间。堆可以看作一棵完全二叉树的顺序存储结构
点击排行