Skip to content

Commit

Permalink
feat: implement dwarf extraction
Browse files Browse the repository at this point in the history
  • Loading branch information
Zxilly committed Jun 9, 2024
1 parent 31d5e30 commit ab67de9
Show file tree
Hide file tree
Showing 25 changed files with 567 additions and 112 deletions.
6 changes: 3 additions & 3 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ linters-settings:

- name: cognitive-complexity
severity: warning
arguments: [ 50 ]
arguments: [ 300 ]

- name: cyclomatic
arguments: [ 30 ]
arguments: [ 100 ]

- name: call-to-gc
- name: unchecked-type-assertion
disabled: true
- name: struct-tag
disabled: true
Expand Down
1 change: 1 addition & 0 deletions cmd/gsa/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var Options struct {

NoDisasm bool `help:"Skip disassembly pass"`
NoSymbol bool `help:"Skip symbol pass"`
NoDwarf bool `help:"Skip dwarf pass"`

HideSections bool `help:"Hide sections" group:"text"`
HideMain bool `help:"Hide main package" group:"text"`
Expand Down
1 change: 1 addition & 0 deletions cmd/gsa/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func entry() error {
internal.Options{
SkipSymbol: Options.NoSymbol,
SkipDisasm: Options.NoDisasm,
SkipDwarf: Options.NoDwarf,
})
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/charmbracelet/x/exp/teatest v0.0.0-20240525152034-77596eb8760e
github.com/charmbracelet/x/term v0.1.1
github.com/dustin/go-humanize v1.0.1
github.com/go-delve/delve v1.22.1
github.com/go-json-experiment/json v0.0.0-20240524174822-2d9f40f7385b
github.com/jedib0t/go-pretty/v6 v6.5.9
github.com/knadh/profiler v0.1.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/go-delve/delve v1.22.1 h1:LQSF2sv+lP3mmOzMkadl5HGQGgSS2bFg2tbyALqHu8Y=
github.com/go-delve/delve v1.22.1/go.mod h1:TfOb+G5H6YYKheZYAmA59ojoHbOimGfs5trbghHdLbM=
github.com/go-json-experiment/json v0.0.0-20240524174822-2d9f40f7385b h1:IM96IiRXFcd7l+mU8Sys9pcggoBLbH/dEgzOESrS8F8=
github.com/go-json-experiment/json v0.0.0-20240524174822-2d9f40f7385b/go.mod h1:uDEMZSTQMj7V6Lxdrx4ZwchmHEGdICbjuY+GQd7j9LM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
Expand Down
14 changes: 11 additions & 3 deletions internal/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package internal

import (
"errors"
"github.com/Zxilly/go-size-analyzer/internal/knowninfo"
"io"
"log/slog"
"path"
Expand All @@ -11,13 +10,15 @@ import (
"golang.org/x/exp/maps"

"github.com/Zxilly/go-size-analyzer/internal/entity"
"github.com/Zxilly/go-size-analyzer/internal/knowninfo"
"github.com/Zxilly/go-size-analyzer/internal/result"
"github.com/Zxilly/go-size-analyzer/internal/wrapper"
)

type Options struct {
SkipSymbol bool
SkipDisasm bool
SkipDwarf bool
}

func Analyze(name string, reader io.ReaderAt, size uint64, options Options) (*result.Result, error) {
Expand Down Expand Up @@ -53,8 +54,15 @@ func Analyze(name string, reader io.ReaderAt, size uint64, options Options) (*re
return nil, err
}

ok := k.TryLoadDwarf()
if !ok {
dwarfOk := false
if !options.SkipDwarf {
dwarfOk = k.TryLoadDwarf()
}

// DWARF can still add new package
k.Deps.FinishLoad()

if !dwarfOk {
// fallback to symbol and disasm
if !options.SkipSymbol {
err = k.AnalyzeSymbol()
Expand Down
2 changes: 1 addition & 1 deletion internal/disasm/interface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type TestFileWrapper struct {
textErr error
}

func (t TestFileWrapper) DWARF() (*dwarf.Data, error) {
func (TestFileWrapper) DWARF() (*dwarf.Data, error) {
panic("not reachable")
}

Expand Down
42 changes: 20 additions & 22 deletions internal/entity/knownaddr.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,21 @@ import (
)

type KnownAddr struct {
Pclntab AddrSpace
Text AddrSpace

Symbol AddrSpace
SymbolCoverage AddrCoverage

Dwarf AddrSpace
}

func NewKnownAddr() *KnownAddr {
return &KnownAddr{
Pclntab: make(map[uint64]*Addr),
Text: make(map[uint64]*Addr),
Symbol: make(map[uint64]*Addr),
SymbolCoverage: make(AddrCoverage, 0),
}
}

func (f *KnownAddr) InsertPclntab(entry uint64, size uint64, fn *Function, meta GoPclntabMeta) {
func (f *KnownAddr) InsertTextFromPclnTab(entry uint64, size uint64, fn *Function, meta GoPclntabMeta) {
cur := Addr{
AddrPos: &AddrPos{
Addr: entry,
Expand All @@ -35,7 +33,23 @@ func (f *KnownAddr) InsertPclntab(entry uint64, size uint64, fn *Function, meta

Meta: meta,
}
f.Pclntab.Insert(&cur)
f.Text.Insert(&cur)
}

func (f *KnownAddr) InsertTextFromDWARF(entry uint64, size uint64, fn *Function, meta DwarfMeta) {
cur := Addr{
AddrPos: &AddrPos{
Addr: entry,
Size: size,
Type: AddrTypeText,
},
Pkg: fn.pkg,
Function: fn,
SourceType: AddrSourceDwarf,

Meta: meta,
}
f.Text.Insert(&cur)
}

func (f *KnownAddr) InsertSymbol(entry uint64, size uint64, p *Package, typ AddrType, meta SymbolMeta) *Addr {
Expand Down Expand Up @@ -106,19 +120,3 @@ func (f *KnownAddr) InsertDisasm(entry uint64, size uint64, fn *Function, meta D

fn.disasm.Insert(&cur)
}

func (f *KnownAddr) InsertDwarf(entry uint64, size uint64, typ AddrType, pkg *Package, meta DwarfMeta) {
cur := Addr{
AddrPos: &AddrPos{
Addr: entry,
Size: size,
Type: typ,
},
Pkg: pkg,
Function: nil,
SourceType: AddrSourceDwarf,
Meta: meta,
}

f.Dwarf.Insert(&cur)
}
60 changes: 47 additions & 13 deletions internal/entity/package.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package entity

import (
"debug/dwarf"
"fmt"
"runtime/debug"

Expand All @@ -22,6 +23,7 @@ const (
PackageTypeVendor PackageType = "vendor"
PackageTypeGenerated PackageType = "generated"
PackageTypeUnknown PackageType = "unknown"
PackageTypeCGO PackageType = "cgo"
)

type Package struct {
Expand All @@ -33,20 +35,23 @@ type Package struct {

Size uint64 `json:"size"` // late filled

filesCache map[string]*File
funcsCache map[string]*Function

loaded bool // mean it comes from gore

// should not be used to calculate size,
// since linker can create overlapped symbols.
// relies on coverage.
// currently only data symbol
Symbols []*Symbol `json:"symbols"`

symbolAddrSpace AddrSpace
coverage *utils.ValueOnce[AddrCoverage]

// should have at least one of them
GorePkg *gore.Package `json:"-"`
DebugMod *debug.Module `json:"-"`
// should have at least one of them, for cgo pesudo package all nil
GorePkg *gore.Package `json:"-"`
DebugMod *debug.Module `json:"-"`
DwarfEntry *dwarf.Entry `json:"-"`
}

func NewPackage() *Package {
Expand All @@ -56,6 +61,8 @@ func NewPackage() *Package {
Symbols: make([]*Symbol, 0),
coverage: utils.NewOnce[AddrCoverage](),
symbolAddrSpace: AddrSpace{},
filesCache: make(map[string]*File),
funcsCache: make(map[string]*Function),
}
}

Expand Down Expand Up @@ -96,15 +103,34 @@ func NewPackageWithGorePackage(gp *gore.Package, name string, typ PackageType, p
}

func (p *Package) fileEnsureUnique() {
seen := make(map[string]*File)
fileSeen := make(map[string]*File)

for _, f := range p.Files {
if old, ok := seen[f.FilePath]; ok {
old.Functions = append(old.Functions, f.Functions...)
if old, ok := fileSeen[f.FilePath]; ok {
funcSeen := make(map[string]*Function)
for _, fn := range old.Functions {
funcSeen[fn.Name] = fn
}

for _, fn := range f.Functions {
if _, ok := funcSeen[fn.Name]; !ok {
old.Functions = append(old.Functions, fn)
}
}
} else {
seen[f.FilePath] = f
fileSeen[f.FilePath] = f
}
}

p.Files = maps.Values(fileSeen)
p.filesCache = fileSeen

p.funcsCache = make(map[string]*Function)
for _, f := range p.Files {
for _, fn := range f.Functions {
p.funcsCache[fn.Name] = fn
}
}
p.Files = maps.Values(seen)
}

func (p *Package) addFunction(path string, fn *Function) {
Expand All @@ -115,11 +141,18 @@ func (p *Package) addFunction(path string, fn *Function) {
file.Functions = append(file.Functions, fn)
}

func (p *Package) AddFuncIfNotExists(path string, fn *Function) bool {
if _, ok := p.funcsCache[fn.Name]; !ok {
p.addFunction(path, fn)
p.funcsCache[fn.Name] = fn
return true
}
return false
}

func (p *Package) getOrInitFile(s string) *File {
for _, f := range p.Files {
if f.FilePath == s {
return f
}
if f, ok := p.filesCache[s]; ok {
return f
}

f := &File{
Expand All @@ -129,6 +162,7 @@ func (p *Package) getOrInitFile(s string) *File {
}

p.Files = append(p.Files, f)
p.filesCache[f.FilePath] = f
return f
}

Expand Down
6 changes: 6 additions & 0 deletions internal/entity/pcln_symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ func NewPclnSymbolSize(s *gosym.Func) PclnSymbolSize {
PCData: s.PCDataSize(),
}
}

func NewEmptyPclnSymbolSize() PclnSymbolSize {
return PclnSymbolSize{
PCData: make(map[string]int),
}
}
8 changes: 6 additions & 2 deletions internal/knowninfo/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func (k *KnownInfo) CollectCoverage() error {
// load coverage for pclntab and symbol
pclntabCov := k.KnownAddr.Pclntab.ToDirtyCoverage()
pclntabCov := k.KnownAddr.Text.ToDirtyCoverage()

// merge all
covs := make([]entity.AddrCoverage, 0)
Expand Down Expand Up @@ -70,11 +70,15 @@ foundPclntab:
}
s.KnownSize = uint64(math.Floor(float64(size) * mapper))

if s.KnownSize > s.FileSize {
if s.KnownSize > s.FileSize && s.FileSize != 0 {
// fixme: pclntab size calculation is not accurate
slog.Warn(fmt.Sprintf("section %s known size %d > file size %d, this is a known issue", s.Name, s.KnownSize, s.FileSize))
s.KnownSize = s.FileSize
}

if s.FileSize == 0 {
s.KnownSize = 0
}
}
return nil
}
Expand Down
16 changes: 7 additions & 9 deletions internal/knowninfo/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ func (m *Dependencies) FinishLoad() {
}
}

func (m *Dependencies) Add(gp *gore.Package, typ entity.PackageType, pclntab *gosym.Table) {
func (m *Dependencies) AddFromPclntab(gp *gore.Package, typ entity.PackageType, pclntab *gosym.Table) {
name := utils.UglyGuess(gp.Name)

p := entity.NewPackageWithGorePackage(gp, name, typ, pclntab)

// update addrs
for _, f := range p.GetFunctions() {
m.k.KnownAddr.InsertPclntab(f.Addr, f.CodeSize, f, entity.GoPclntabMeta{
m.k.KnownAddr.InsertTextFromPclnTab(f.Addr, f.CodeSize, f, entity.GoPclntabMeta{
FuncName: utils.Deduplicate(f.Name),
PackageName: utils.Deduplicate(p.Name),
Type: utils.Deduplicate(f.Type),
Expand Down Expand Up @@ -131,36 +131,34 @@ func (k *KnownInfo) LoadPackages() error {
return err
}
for _, p := range self {
pkgs.Add(p, entity.PackageTypeMain, pclntab)
pkgs.AddFromPclntab(p, entity.PackageTypeMain, pclntab)
}

grStd, _ := k.Gore.GetSTDLib()
for _, p := range grStd {
pkgs.Add(p, entity.PackageTypeStd, pclntab)
pkgs.AddFromPclntab(p, entity.PackageTypeStd, pclntab)
}

grVendor, _ := k.Gore.GetVendors()
for _, p := range grVendor {
pkgs.Add(p, entity.PackageTypeVendor, pclntab)
pkgs.AddFromPclntab(p, entity.PackageTypeVendor, pclntab)
}

grGenerated, _ := k.Gore.GetGeneratedPackages()
for _, p := range grGenerated {
pkgs.Add(p, entity.PackageTypeGenerated, pclntab)
pkgs.AddFromPclntab(p, entity.PackageTypeGenerated, pclntab)
}

grUnknown, _ := k.Gore.GetUnknown()
for _, p := range grUnknown {
pkgs.Add(p, entity.PackageTypeUnknown, pclntab)
pkgs.AddFromPclntab(p, entity.PackageTypeUnknown, pclntab)
}

if err = k.RequireModInfo(); err == nil {
pkgs.AddModules(k.BuildInfo.ModInfo.Deps, entity.PackageTypeVendor)
pkgs.AddModules([]*debug.Module{&k.BuildInfo.ModInfo.Main}, entity.PackageTypeVendor)
}

pkgs.FinishLoad()

slog.Info("Loading packages done")

return nil
Expand Down
Loading

0 comments on commit ab67de9

Please sign in to comment.