Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement dwarf extraction #65

Merged
merged 8 commits into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ Terminal interface options
## TODO

- [ ] Add more pattern for disassembling the binary
- [ ] Extract the information from the DWARF section
- [x] Extract the information from the DWARF section
- [x] Count the symbol size itself to package
- [ ] Add other charts like flame graph, pie chart, etc.
- [ ] Support C++/Rust symbol demangling in cgo
Expand Down
2 changes: 1 addition & 1 deletion README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ Terminal interface options
## TODO

- [ ] 添加更多用于反汇编二进制文件的模式
- [ ] 从 DWARF 段提取信息
- [x] 从 DWARF 段提取信息
- [x] 计算符号本身的大小到包中
- [ ] 添加其他图表,如火焰图、饼图等
- [ ] 支持 demangle cgo 中的 C++/Rust 符号
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
43 changes: 28 additions & 15 deletions internal/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
"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 All @@ -30,15 +32,15 @@
slog.Info("Parsing binary done")
slog.Info("Finding build info...")

k := &KnownInfo{
k := &knowninfo.KnownInfo{
Size: size,
BuildInfo: file.BuildInfo,

gore: file,
wrapper: wrapper.NewWrapper(file.GetParsedFile()),
Gore: file,
Wrapper: wrapper.NewWrapper(file.GetParsedFile()),
}
k.KnownAddr = entity.NewKnownAddr()
k.UpdateVersionFlag()
k.VersionFlag = k.UpdateVersionFlag()

slog.Info("Build info found")

Expand All @@ -52,21 +54,32 @@
return nil, err
}

if !options.SkipSymbol {
err = k.AnalyzeSymbol()
if err != nil {
if !errors.Is(err, wrapper.ErrNoSymbolTable) {
return nil, err
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()
if err != nil {
if !errors.Is(err, wrapper.ErrNoSymbolTable) {
return nil, err

Check warning on line 71 in internal/analyze.go

View check run for this annotation

Codecov / codecov/patch

internal/analyze.go#L71

Added line #L71 was not covered by tests

}

Check warning on line 73 in internal/analyze.go

View check run for this annotation

Codecov / codecov/patch

internal/analyze.go#L73

Added line #L73 was not covered by tests
slog.Warn("Warning: no symbol table found, this can lead to inaccurate results")
}
slog.Warn("Warning: no symbol table found, this can lead to inaccurate results")
}
}

if !options.SkipDisasm {
err = k.Disasm()
if err != nil {
return nil, err
if !options.SkipDisasm {
err = k.Disasm()
if err != nil {
return nil, err
}

Check warning on line 82 in internal/analyze.go

View check run for this annotation

Codecov / codecov/patch

internal/analyze.go#L81-L82

Added lines #L81 - L82 were not covered by tests
}
}

Expand Down
13 changes: 9 additions & 4 deletions internal/disasm/interface_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package disasm

import (
"debug/dwarf"
"errors"
"testing"

Expand All @@ -17,6 +18,10 @@ type TestFileWrapper struct {
textErr error
}

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

func (t TestFileWrapper) Text() (textStart uint64, text []byte, err error) {
return t.textStart, t.text, t.textErr
}
Expand All @@ -26,19 +31,19 @@ func (t TestFileWrapper) GoArch() string {
}

func (TestFileWrapper) ReadAddr(_, _ uint64) ([]byte, error) {
panic("implement me")
panic("not reachable")
}

func (TestFileWrapper) LoadSymbols(_ func(name string, addr uint64, size uint64, typ entity.AddrType) error) error {
panic("implement me")
panic("not reachable")
}

func (TestFileWrapper) LoadSections() map[string]*entity.Section {
panic("implement me")
panic("not reachable")
}

func (TestFileWrapper) PclntabSections() []string {
panic("implement me")
panic("not reachable")
}

func TestNewExtractorNoText(t *testing.T) {
Expand Down
24 changes: 20 additions & 4 deletions internal/entity/knownaddr.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@
)

type KnownAddr struct {
Pclntab AddrSpace
Text AddrSpace

Symbol AddrSpace
SymbolCoverage AddrCoverage
}

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 @@ -33,7 +33,23 @@

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)

Check warning on line 52 in internal/entity/knownaddr.go

View check run for this annotation

Codecov / codecov/patch

internal/entity/knownaddr.go#L39-L52

Added lines #L39 - L52 were not covered by tests
}

func (f *KnownAddr) InsertSymbol(entry uint64, size uint64, p *Package, typ AddrType, meta SymbolMeta) *Addr {
Expand Down
3 changes: 3 additions & 0 deletions internal/entity/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ type SymbolMeta struct {
type DisasmMeta struct {
Value string
}

type DwarfMeta struct {
}
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),
}
}
1 change: 1 addition & 0 deletions internal/entity/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ const (
AddrSourceGoPclntab AddrSourceType = "pclntab"
AddrSourceSymbol AddrSourceType = "symbol"
AddrSourceDisasm AddrSourceType = "disasm"
AddrSourceDwarf AddrSourceType = "dwarf"
)
Loading
Loading