From ab67de9bcfe2aefe2c76bfe48417c5919ec0edc7 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Mon, 10 Jun 2024 06:38:19 +0800 Subject: [PATCH] feat: implement dwarf extraction --- .golangci.yaml | 6 +- cmd/gsa/command.go | 1 + cmd/gsa/entry.go | 1 + go.mod | 1 + go.sum | 2 + internal/analyze.go | 14 +- internal/disasm/interface_test.go | 2 +- internal/entity/knownaddr.go | 42 +++-- internal/entity/package.go | 60 +++++-- internal/entity/pcln_symbol.go | 6 + internal/knowninfo/collect.go | 8 +- internal/knowninfo/dependencies.go | 16 +- internal/knowninfo/dwarf.go | 260 ++++++++++++++++++++++++++--- internal/knowninfo/dwarf_info.go | 193 +++++++++++++++++++++ internal/knowninfo/knowninfo.go | 8 +- internal/knowninfo/section.go | 5 +- internal/knowninfo/symbol.go | 3 +- internal/section/section.go | 6 +- internal/section/section_test.go | 4 +- internal/wrapper/pe.go | 18 +- internal/wrapper/wrapper.go | 3 +- ui/common.ts | 11 +- ui/src/generated/schema.ts | 4 +- ui/src/schema/schema.ts | 2 +- ui/src/tool/utils.ts | 3 +- 25 files changed, 567 insertions(+), 112 deletions(-) create mode 100644 internal/knowninfo/dwarf_info.go diff --git a/.golangci.yaml b/.golangci.yaml index 9657e2fa6f..47424c22f4 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -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 diff --git a/cmd/gsa/command.go b/cmd/gsa/command.go index 15ca729199..fe80132152 100644 --- a/cmd/gsa/command.go +++ b/cmd/gsa/command.go @@ -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"` diff --git a/cmd/gsa/entry.go b/cmd/gsa/entry.go index 205cbcd05a..1e60a4209d 100644 --- a/cmd/gsa/entry.go +++ b/cmd/gsa/entry.go @@ -40,6 +40,7 @@ func entry() error { internal.Options{ SkipSymbol: Options.NoSymbol, SkipDisasm: Options.NoDisasm, + SkipDwarf: Options.NoDwarf, }) if err != nil { return err diff --git a/go.mod b/go.mod index ad7b1791d4..4f259cf856 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index d3e5492af2..e5a674877b 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/analyze.go b/internal/analyze.go index ac4ea81414..b3add63a6a 100644 --- a/internal/analyze.go +++ b/internal/analyze.go @@ -2,7 +2,6 @@ package internal import ( "errors" - "github.com/Zxilly/go-size-analyzer/internal/knowninfo" "io" "log/slog" "path" @@ -11,6 +10,7 @@ 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" ) @@ -18,6 +18,7 @@ import ( type Options struct { SkipSymbol bool SkipDisasm bool + SkipDwarf bool } func Analyze(name string, reader io.ReaderAt, size uint64, options Options) (*result.Result, error) { @@ -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() diff --git a/internal/disasm/interface_test.go b/internal/disasm/interface_test.go index d0ef514b9c..beb31203a5 100644 --- a/internal/disasm/interface_test.go +++ b/internal/disasm/interface_test.go @@ -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") } diff --git a/internal/entity/knownaddr.go b/internal/entity/knownaddr.go index ce7182142b..3383dddc56 100644 --- a/internal/entity/knownaddr.go +++ b/internal/entity/knownaddr.go @@ -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, @@ -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 { @@ -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) -} diff --git a/internal/entity/package.go b/internal/entity/package.go index c88151f98e..e231ee3c0c 100644 --- a/internal/entity/package.go +++ b/internal/entity/package.go @@ -1,6 +1,7 @@ package entity import ( + "debug/dwarf" "fmt" "runtime/debug" @@ -22,6 +23,7 @@ const ( PackageTypeVendor PackageType = "vendor" PackageTypeGenerated PackageType = "generated" PackageTypeUnknown PackageType = "unknown" + PackageTypeCGO PackageType = "cgo" ) type Package struct { @@ -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 { @@ -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), } } @@ -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) { @@ -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{ @@ -129,6 +162,7 @@ func (p *Package) getOrInitFile(s string) *File { } p.Files = append(p.Files, f) + p.filesCache[f.FilePath] = f return f } diff --git a/internal/entity/pcln_symbol.go b/internal/entity/pcln_symbol.go index 4967993cbb..e43de18b81 100644 --- a/internal/entity/pcln_symbol.go +++ b/internal/entity/pcln_symbol.go @@ -38,3 +38,9 @@ func NewPclnSymbolSize(s *gosym.Func) PclnSymbolSize { PCData: s.PCDataSize(), } } + +func NewEmptyPclnSymbolSize() PclnSymbolSize { + return PclnSymbolSize{ + PCData: make(map[string]int), + } +} diff --git a/internal/knowninfo/collect.go b/internal/knowninfo/collect.go index 124656f7ab..3cfb1890bf 100644 --- a/internal/knowninfo/collect.go +++ b/internal/knowninfo/collect.go @@ -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) @@ -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 } diff --git a/internal/knowninfo/dependencies.go b/internal/knowninfo/dependencies.go index 05fc4eb5aa..ac579a927a 100644 --- a/internal/knowninfo/dependencies.go +++ b/internal/knowninfo/dependencies.go @@ -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), @@ -131,27 +131,27 @@ 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 { @@ -159,8 +159,6 @@ func (k *KnownInfo) LoadPackages() error { pkgs.AddModules([]*debug.Module{&k.BuildInfo.ModInfo.Main}, entity.PackageTypeVendor) } - pkgs.FinishLoad() - slog.Info("Loading packages done") return nil diff --git a/internal/knowninfo/dwarf.go b/internal/knowninfo/dwarf.go index 18873fa967..79114d3c5f 100644 --- a/internal/knowninfo/dwarf.go +++ b/internal/knowninfo/dwarf.go @@ -2,12 +2,62 @@ package knowninfo import ( "debug/dwarf" + "errors" "fmt" "log/slog" - "os" - "strings" + + "github.com/ZxillyFork/gore" + "github.com/ZxillyFork/gosym" + "github.com/go-delve/delve/pkg/dwarf/op" + + "github.com/Zxilly/go-size-analyzer/internal/entity" + "github.com/Zxilly/go-size-analyzer/internal/utils" + "github.com/Zxilly/go-size-analyzer/internal/wrapper" ) +func (k *KnownInfo) AddrForDWARFVar(entry *dwarf.Entry) (uint64, error) { + arch := k.Wrapper.GoArch() + var ptrSize int + switch arch { + case "386", "arm": + ptrSize = 4 + default: + ptrSize = 8 + } + + instsAny := entry.Val(dwarf.AttrLocation) + if instsAny == nil { + return 0, errors.New("no location attribute") + } + insts, ok := instsAny.([]byte) + if !ok { + return 0, errors.New("failed to cast location attribute to []byte") + } + + imageBase := uint64(0) + if peWrapper, ok := k.Wrapper.(*wrapper.PeWrapper); ok { + imageBase = peWrapper.ImageBase + } + + addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{StaticBase: imageBase}, insts, ptrSize, nil) + if err != nil { + return 0, err + } + + return uint64(addr), nil +} + +func SizeForDWARFVar(d *dwarf.Data, entry *dwarf.Entry) (uint64, error) { + sizeOffset := entry.Val(dwarf.AttrType).(dwarf.Offset) + + typ, err := d.Type(sizeOffset) + if err != nil { + return 0, err + } + + return uint64(typ.Size()), nil +} + func (k *KnownInfo) TryLoadDwarf() bool { d, err := k.Wrapper.DWARF() if err != nil { @@ -15,38 +65,198 @@ func (k *KnownInfo) TryLoadDwarf() bool { return false } - out, err := os.OpenFile("dwarf.log", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - if err != nil { - panic(err) - } - defer out.Close() + k.HasDWARF = true r := d.Reader() - for { - entry, err := r.Next() - if entry == nil { - break + + langPackages := make(map[string]*entity.Package) + + loadCompilerUnit := func(cuEntry *dwarf.Entry, pendingEntry []*dwarf.Entry) { + var pkg *entity.Package + var filePath string + + cuLang, _ := cuEntry.Val(dwarf.AttrLanguage).(int64) + cuName, _ := cuEntry.Val(dwarf.AttrName).(string) + + if cuLang == DwLangGo { + // if we have load it with pclntab? + pkg = k.Deps.Trie.Get(cuName) + if pkg == nil { + pkg = entity.NewPackage() + pkg.Name = cuName + } + pkg.DwarfEntry = cuEntry + filePath = "" + typ := entity.PackageTypeVendor + if cuName == "main" { + typ = entity.PackageTypeMain + } else if gore.IsStandardLibrary(cuName) { + typ = entity.PackageTypeStd + } + pkg.Type = typ + } else { + pkgName := fmt.Sprintf("CGO %s", LanguageString(cuLang)) + pkg = langPackages[pkgName] + if pkg == nil { + pkg = entity.NewPackage() + pkg.Name = pkgName + pkg.Type = entity.PackageTypeCGO + langPackages[pkgName] = pkg + } + + compDirAny := cuEntry.Val(dwarf.AttrCompDir) + if compDirAny == nil { + slog.Warn("Failed to load DWARF: no compDir") + return + } + compDir := compDirAny.(string) + + filePath = fmt.Sprintf("%s/%s", compDir, cuName) } - if err != nil { - slog.Warn(fmt.Sprintf("Failed to read DWARF entry: %#v", err)) - break + + for _, subEntry := range pendingEntry { + subEntryName := subEntry.Val(dwarf.AttrName).(string) + + if subEntry.Tag == dwarf.TagVariable { + addr, err := k.AddrForDWARFVar(subEntry) + if err != nil { + slog.Warn(fmt.Sprintf("Failed to load DWARF var %s: %v", subEntryName, err)) + for _, field := range subEntry.Field { + slog.Debug(fmt.Sprintf("%#v", field)) + } + continue + } + size, err := SizeForDWARFVar(d, subEntry) + if err != nil { + slog.Warn(fmt.Sprintf("Failed to load DWARF var %s: %v", subEntryName, err)) + for _, field := range subEntry.Field { + slog.Debug(fmt.Sprintf("%#v", field)) + } + continue + } + + ap := k.KnownAddr.InsertSymbol(addr, size, pkg, entity.AddrTypeData, entity.SymbolMeta{ + SymbolName: utils.Deduplicate(subEntryName), + PackageName: utils.Deduplicate(pkg.Name), + }) + + pkg.AddSymbol(addr, size, entity.AddrTypeData, subEntryName, ap) + } else if subEntry.Tag == dwarf.TagSubprogram { + ranges, err := d.Ranges(subEntry) + if err != nil { + slog.Warn(fmt.Sprintf("Failed to load DWARF function size: %v", err)) + continue + } + + if len(ranges) == 0 { + // fixme: maybe compiler optimization it? + // example: sqlite3 simpleDestroy + continue + } + + addr := ranges[0][0] + size := ranges[0][1] - ranges[0][0] + + typ := entity.FuncTypeFunction + receiverName := "" + if cuLang == DwLangGo { + receiverName = (&gosym.Sym{Name: subEntryName}).ReceiverName() + if receiverName != "" { + typ = entity.FuncTypeMethod + } + } + + fn := &entity.Function{ + Name: subEntryName, + Addr: addr, + CodeSize: size, + Type: typ, + Receiver: receiverName, + PclnSize: entity.NewEmptyPclnSymbolSize(), + } + + added := pkg.AddFuncIfNotExists(filePath, fn) + + if added { + k.KnownAddr.Text.Insert(&entity.Addr{ + AddrPos: &entity.AddrPos{Addr: addr, Size: size, Type: entity.AddrTypeText}, + Pkg: pkg, + Function: fn, + SourceType: entity.AddrSourceDwarf, + Meta: entity.DwarfMeta{}, + }) + } + } else { + panic("unreachable") + } + } + } + + shouldIgnore := func(entry *dwarf.Entry) bool { + declaration := entry.Val(dwarf.AttrDeclaration) + if declaration != nil { + val := declaration.(bool) + if val { + return true + } } - fmt.Fprintf(out, "DWARF entry: %v\n", entryPrettyPrinter(entry)) + inline := entry.Val(dwarf.AttrInline) + if inline != nil { + val := inline.(int64) + if val > 0 { + return true + } + } + + abstractOrigin := entry.Val(dwarf.AttrAbstractOrigin) + return abstractOrigin != nil } - return true -} + var cuEntry *dwarf.Entry + var pendingEntry []*dwarf.Entry + depth := 1 + + for entry, err := r.Next(); entry != nil; entry, err = r.Next() { + if err != nil { + slog.Warn(fmt.Sprintf("Failed to load DWARF: %v", err)) + return false + } -func entryPrettyPrinter(entry *dwarf.Entry) string { - s := new(strings.Builder) + if entry.Tag == 0 { + depth-- + if depth <= 0 { + panic("broken DWARF") + } + if depth == 1 && cuEntry != nil { + loadCompilerUnit(cuEntry, pendingEntry) + cuEntry = nil + pendingEntry = nil + } + } + + switch entry.Tag { + case dwarf.TagCompileUnit: + cuEntry = entry + case dwarf.TagSubprogram: + if !shouldIgnore(entry) { + pendingEntry = append(pendingEntry, entry) + } + case dwarf.TagVariable: + if !shouldIgnore(entry) && depth == 2 { + pendingEntry = append(pendingEntry, entry) + } + } - fmt.Fprintf(s, "Offset: %v\n", entry.Offset) - fmt.Fprintf(s, "Tag: %v\n", entry.Tag.String()) - fmt.Fprintf(s, "Children: %v\n", entry.Children) - for _, field := range entry.Field { - fmt.Fprintf(s, "Field: %#v\n", field) + if entry.Children { + depth++ + } } - return s.String() + // add langPackages to knownPackages + for _, pkg := range langPackages { + k.Deps.Trie.Put(pkg.Name, pkg) + } + + return true } diff --git a/internal/knowninfo/dwarf_info.go b/internal/knowninfo/dwarf_info.go new file mode 100644 index 0000000000..da5c0f4975 --- /dev/null +++ b/internal/knowninfo/dwarf_info.go @@ -0,0 +1,193 @@ +package knowninfo + +import "strconv" + +type Language = int64 + +const ( + DwLangC89 Language = 0x0001 + DwLangC Language = 0x0002 + DwLangAda83 Language = 0x0003 + DwLangCPP Language = 0x0004 + DwLangCobol74 Language = 0x0005 + DwLangCobol85 Language = 0x0006 + DwLangFortran77 Language = 0x0007 + DwLangFortran90 Language = 0x0008 + DwLangPascal83 Language = 0x0009 + DwLangModula2 Language = 0x000a + DwLangJava Language = 0x000b + DwLangC99 Language = 0x000c + DwLangAda95 Language = 0x000d + DwLangFortran95 Language = 0x000e + DwLangPLI Language = 0x000f + DwLangObjC Language = 0x0010 + DwLangObjCPP Language = 0x0011 + DwLangUPC Language = 0x0012 + DwLangD Language = 0x0013 + DwLangPython Language = 0x0014 + DwLangOpenCL Language = 0x0015 + DwLangGo Language = 0x0016 + DwLangModula3 Language = 0x0017 + DwLangHaskell Language = 0x0018 + DwLangCPP03 Language = 0x0019 + DwLangCPP11 Language = 0x001a + DwLangOCaml Language = 0x001b + DwLangRust Language = 0x001c + DwLangC11 Language = 0x001d + DwLangSwift Language = 0x001e + DwLangJulia Language = 0x001f + DwLangDylan Language = 0x0020 + DwLangCPP14 Language = 0x0021 + DwLangFortran03 Language = 0x0022 + DwLangFortran08 Language = 0x0023 + DwLangRenderScript Language = 0x0024 + DwLangBLISS Language = 0x0025 + DwLangKotlin Language = 0x0026 + DwLangZig Language = 0x0027 + DwLangCrystal Language = 0x0028 + DwLangCPP17 Language = 0x002a + DwLangCPP20 Language = 0x002b + DwLangC17 Language = 0x002c + DwLangFortran18 Language = 0x002d + DwLangAda2005 Language = 0x002e + DwLangAda2012 Language = 0x002f + DwLangHIP Language = 0x0030 + DwLangAssembly Language = 0x0031 + DwLangCSharp Language = 0x0032 + DwLangMojo Language = 0x0033 + DwLangGLSL Language = 0x0034 + DwLangGLSLES Language = 0x0035 + DwLangHLSL Language = 0x0036 + DwLangOpenCLCPP Language = 0x0037 + DwLangCPPForOpenCL Language = 0x0038 + DwLangSYCL Language = 0x0039 + DwLangRuby Language = 0x0040 + DwLangMove Language = 0x0041 + DwLangHylo Language = 0x0042 +) + +// LanguageString returns the string representation of the Language constant. +func LanguageString(l Language) string { + switch l { + case DwLangC89: + return "C89" + case DwLangC: + return "C" + case DwLangAda83: + return "Ada83" + case DwLangCPP: + return "C++" + case DwLangCobol74: + return "Cobol74" + case DwLangCobol85: + return "Cobol85" + case DwLangFortran77: + return "Fortran77" + case DwLangFortran90: + return "Fortran90" + case DwLangPascal83: + return "Pascal83" + case DwLangModula2: + return "Modula2" + case DwLangJava: + return "Java" + case DwLangC99: + return "C99" + case DwLangAda95: + return "Ada95" + case DwLangFortran95: + return "Fortran95" + case DwLangPLI: + return "PLI" + case DwLangObjC: + return "ObjC" + case DwLangObjCPP: + return "ObjC++" + case DwLangUPC: + return "UPC" + case DwLangD: + return "D" + case DwLangPython: + return "Python" + case DwLangOpenCL: + return "OpenCL" + case DwLangGo: + return "Go" + case DwLangModula3: + return "Modula3" + case DwLangHaskell: + return "Haskell" + case DwLangCPP03: + return "C++03" + case DwLangCPP11: + return "C++11" + case DwLangOCaml: + return "OCaml" + case DwLangRust: + return "Rust" + case DwLangC11: + return "C11" + case DwLangSwift: + return "Swift" + case DwLangJulia: + return "Julia" + case DwLangDylan: + return "Dylan" + case DwLangCPP14: + return "C++14" + case DwLangFortran03: + return "Fortran03" + case DwLangFortran08: + return "Fortran08" + case DwLangRenderScript: + return "RenderScript" + case DwLangBLISS: + return "BLISS" + case DwLangKotlin: + return "Kotlin" + case DwLangZig: + return "Zig" + case DwLangCrystal: + return "Crystal" + case DwLangCPP17: + return "C++17" + case DwLangCPP20: + return "C++20" + case DwLangC17: + return "C17" + case DwLangFortran18: + return "Fortran18" + case DwLangAda2005: + return "Ada2005" + case DwLangAda2012: + return "Ada2012" + case DwLangHIP: + return "HIP" + case DwLangAssembly: + return "Assembly" + case DwLangCSharp: + return "C#" + case DwLangMojo: + return "Mojo" + case DwLangGLSL: + return "GLSL" + case DwLangGLSLES: + return "GLSLES" + case DwLangHLSL: + return "HLSL" + case DwLangOpenCLCPP: + return "OpenCL++" + case DwLangCPPForOpenCL: + return "C++ForOpenCL" + case DwLangSYCL: + return "SYCL" + case DwLangRuby: + return "Ruby" + case DwLangMove: + return "Move" + case DwLangHylo: + return "Hylo" + default: + return "Language(" + strconv.Itoa(int(l)) + ")" + } +} diff --git a/internal/knowninfo/knowninfo.go b/internal/knowninfo/knowninfo.go index 638c30bcfd..e914737d76 100644 --- a/internal/knowninfo/knowninfo.go +++ b/internal/knowninfo/knowninfo.go @@ -2,10 +2,12 @@ package knowninfo import ( "fmt" + + "github.com/ZxillyFork/gore" + "github.com/Zxilly/go-size-analyzer/internal/entity" "github.com/Zxilly/go-size-analyzer/internal/section" "github.com/Zxilly/go-size-analyzer/internal/wrapper" - "github.com/ZxillyFork/gore" ) type VersionFlag struct { @@ -16,7 +18,7 @@ type VersionFlag struct { type KnownInfo struct { Size uint64 BuildInfo *gore.BuildInfo - Sects *section.SectionMap + Sects *section.Store Deps *Dependencies KnownAddr *entity.KnownAddr @@ -26,6 +28,8 @@ type KnownInfo struct { Wrapper wrapper.RawFileWrapper VersionFlag VersionFlag + + HasDWARF bool } func (k *KnownInfo) UpdateVersionFlag() VersionFlag { diff --git a/internal/knowninfo/section.go b/internal/knowninfo/section.go index 02d553b1a2..7e12238180 100644 --- a/internal/knowninfo/section.go +++ b/internal/knowninfo/section.go @@ -1,8 +1,9 @@ package knowninfo import ( - "github.com/Zxilly/go-size-analyzer/internal/section" "log/slog" + + "github.com/Zxilly/go-size-analyzer/internal/section" ) func (k *KnownInfo) LoadSectionMap() error { @@ -12,7 +13,7 @@ func (k *KnownInfo) LoadSectionMap() error { slog.Info("Loading sections done") - k.Sects = §ion.SectionMap{ + k.Sects = §ion.Store{ Sections: sections, } return k.Sects.AssertSize(k.Size) diff --git a/internal/knowninfo/symbol.go b/internal/knowninfo/symbol.go index 33dd6e2f1f..d888e4b486 100644 --- a/internal/knowninfo/symbol.go +++ b/internal/knowninfo/symbol.go @@ -1,10 +1,11 @@ package knowninfo import ( - "github.com/ZxillyFork/gosym" "log/slog" "strings" + "github.com/ZxillyFork/gosym" + "github.com/Zxilly/go-size-analyzer/internal/entity" "github.com/Zxilly/go-size-analyzer/internal/utils" ) diff --git a/internal/section/section.go b/internal/section/section.go index c37968af4d..aeb80f345d 100644 --- a/internal/section/section.go +++ b/internal/section/section.go @@ -6,11 +6,11 @@ import ( "github.com/Zxilly/go-size-analyzer/internal/entity" ) -type SectionMap struct { +type Store struct { Sections map[string]*entity.Section } -func (s *SectionMap) FindSection(addr, size uint64) *entity.Section { +func (s *Store) FindSection(addr, size uint64) *entity.Section { for _, section := range s.Sections { if section.Debug { // we can't find things in debug sections @@ -24,7 +24,7 @@ func (s *SectionMap) FindSection(addr, size uint64) *entity.Section { return nil } -func (s *SectionMap) AssertSize(size uint64) error { +func (s *Store) AssertSize(size uint64) error { sectionsSize := uint64(0) for _, section := range s.Sections { if section.OnlyInMemory { diff --git a/internal/section/section_test.go b/internal/section/section_test.go index 523936ba74..ee467f105d 100644 --- a/internal/section/section_test.go +++ b/internal/section/section_test.go @@ -54,7 +54,7 @@ func TestSectionMap_AssertSize(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := &SectionMap{ + s := &Store{ Sections: tt.fields.Sections, } if err := s.AssertSize(tt.args.size); (err != nil) != tt.wantErr { @@ -97,7 +97,7 @@ func TestSectionMap_FindSection(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := &SectionMap{ + s := &Store{ Sections: tt.fields.Sections, } if got := s.FindSection(tt.args.addr, tt.args.size); !reflect.DeepEqual(got, tt.want) { diff --git a/internal/wrapper/pe.go b/internal/wrapper/pe.go index 5049bd19c1..f72425cc86 100644 --- a/internal/wrapper/pe.go +++ b/internal/wrapper/pe.go @@ -8,11 +8,11 @@ import ( "strings" "github.com/Zxilly/go-size-analyzer/internal/entity" - "github.com/Zxilly/go-size-analyzer/internal/utils" ) type PeWrapper struct { - file *pe.File + file *pe.File + ImageBase uint64 } func (p *PeWrapper) DWARF() (*dwarf.Data, error) { @@ -28,8 +28,6 @@ func (p *PeWrapper) LoadSymbols(marker func(name string, addr uint64, size uint6 return ErrNoSymbolTable } - imageBase := utils.GetImageBase(p.file) - const ( nUndef = 0 nAbs = -1 @@ -69,7 +67,7 @@ func (p *PeWrapper) LoadSymbols(marker func(name string, addr uint64, size uint6 sect := p.file.Sections[s.SectionNumber-1] ch := sect.Characteristics - a := uint64(s.Value) + imageBase + uint64(sect.VirtualAddress) + a := uint64(s.Value) + p.ImageBase + uint64(sect.VirtualAddress) var typ entity.AddrType switch { @@ -113,8 +111,6 @@ func (p *PeWrapper) LoadSymbols(marker func(name string, addr uint64, size uint6 } func (p *PeWrapper) LoadSections() map[string]*entity.Section { - imageBase := utils.GetImageBase(p.file) - ret := make(map[string]*entity.Section) for _, section := range p.file.Sections { d := strings.HasPrefix(section.Name, ".debug_") || strings.HasPrefix(section.Name, ".zdebug_") @@ -129,8 +125,8 @@ func (p *PeWrapper) LoadSections() map[string]*entity.Section { FileSize: uint64(section.Size), Offset: uint64(section.Offset), End: uint64(section.Offset + section.Size), - Addr: imageBase + uint64(section.VirtualAddress), - AddrEnd: imageBase + uint64(section.VirtualAddress+section.VirtualSize), + Addr: p.ImageBase + uint64(section.VirtualAddress), + AddrEnd: p.ImageBase + uint64(section.VirtualAddress+section.VirtualSize), OnlyInMemory: false, // pe file didn't have an only-in-memory section Debug: d, } @@ -153,13 +149,11 @@ func (p *PeWrapper) ReadAddr(addr, size uint64) ([]byte, error) { } func (p *PeWrapper) Text() (textStart uint64, text []byte, err error) { - imageBase := utils.GetImageBase(p.file) - sect := p.file.Section(".text") if sect == nil { return 0, nil, fmt.Errorf("text section not found") } - textStart = imageBase + uint64(sect.VirtualAddress) + textStart = p.ImageBase + uint64(sect.VirtualAddress) text, err = sect.Data() return textStart, text, err } diff --git a/internal/wrapper/wrapper.go b/internal/wrapper/wrapper.go index cfd922032e..1d5db9e2c3 100644 --- a/internal/wrapper/wrapper.go +++ b/internal/wrapper/wrapper.go @@ -8,6 +8,7 @@ import ( "errors" "github.com/Zxilly/go-size-analyzer/internal/entity" + "github.com/Zxilly/go-size-analyzer/internal/utils" ) var ErrNoSymbolTable = errors.New("no symbol table found") @@ -27,7 +28,7 @@ func NewWrapper(file any) RawFileWrapper { case *elf.File: return &ElfWrapper{f} case *pe.File: - return &PeWrapper{f} + return &PeWrapper{f, utils.GetImageBase(f)} case *macho.File: return &MachoWrapper{f} } diff --git a/ui/common.ts b/ui/common.ts index e15032b4b9..fea8de6442 100644 --- a/ui/common.ts +++ b/ui/common.ts @@ -28,12 +28,11 @@ export function getVersionTag(): HtmlTagDescriptor { return { tag: "script", - children: ` - console.info("Branch: ${branchName}"); - console.info("Commit: ${commitHash}"); - console.info("Date: ${commitDate}"); - console.info("Message: ${lastCommitMessage}"); - `.trim(), + children: + `console.info("Branch: ${branchName}");` + + `console.info("Commit: ${commitHash}");` + + `console.info("Date: ${commitDate}");` + + `console.info("Message: ${lastCommitMessage}");`, } } diff --git a/ui/src/generated/schema.ts b/ui/src/generated/schema.ts index 9f6a20c06b..f80d74d031 100644 --- a/ui/src/generated/schema.ts +++ b/ui/src/generated/schema.ts @@ -24,7 +24,7 @@ export interface FileSymbol { } export interface Package { name: string; - type: 'main' | 'std' | 'vendor' | 'generated' | 'unknown'; + type: 'main' | 'std' | 'vendor' | 'generated' | 'unknown' | 'cgo'; subPackages: { [key: string]: Package; }; @@ -48,7 +48,7 @@ export const parseResult = (input: any): import("typia").Primitive => { return true; return "object" === typeof value && null !== value && $io2(value); }); - const $io2 = (input: any): boolean => "string" === typeof input.name && ("main" === input.type || "std" === input.type || "vendor" === input.type || "generated" === input.type || "unknown" === input.type) && ("object" === typeof input.subPackages && null !== input.subPackages && false === Array.isArray(input.subPackages) && $io3(input.subPackages)) && (Array.isArray(input.files) && input.files.every((elem: any) => "object" === typeof elem && null !== elem && $io4(elem))) && (Array.isArray(input.symbols) && input.symbols.every((elem: any) => "object" === typeof elem && null !== elem && $io5(elem))) && ("number" === typeof input.size && (Math.floor(input.size) === input.size && 0 <= input.size && input.size <= 18446744073709552000)); + const $io2 = (input: any): boolean => "string" === typeof input.name && ("main" === input.type || "std" === input.type || "vendor" === input.type || "generated" === input.type || "unknown" === input.type || "cgo" === input.type) && ("object" === typeof input.subPackages && null !== input.subPackages && false === Array.isArray(input.subPackages) && $io3(input.subPackages)) && (Array.isArray(input.files) && input.files.every((elem: any) => "object" === typeof elem && null !== elem && $io4(elem))) && (Array.isArray(input.symbols) && input.symbols.every((elem: any) => "object" === typeof elem && null !== elem && $io5(elem))) && ("number" === typeof input.size && (Math.floor(input.size) === input.size && 0 <= input.size && input.size <= 18446744073709552000)); const $io3 = (input: any): boolean => Object.keys(input).every((key: any) => { const value = input[key]; if (undefined === value) diff --git a/ui/src/schema/schema.ts b/ui/src/schema/schema.ts index 00a5409712..9edd8eb8bb 100644 --- a/ui/src/schema/schema.ts +++ b/ui/src/schema/schema.ts @@ -28,7 +28,7 @@ export interface FileSymbol { export interface Package { name: string; - type: 'main' | 'std' | 'vendor' | 'generated' | 'unknown'; + type: 'main' | 'std' | 'vendor' | 'generated' | 'unknown' | 'cgo'; subPackages: { [key: string]: Package }; files: File[]; symbols: FileSymbol[]; diff --git a/ui/src/tool/utils.ts b/ui/src/tool/utils.ts index a3f0326394..5d27643bae 100644 --- a/ui/src/tool/utils.ts +++ b/ui/src/tool/utils.ts @@ -1,5 +1,4 @@ -import {Result} from "../schema/schema.ts"; -import {parseResult} from "../generated/schema.ts"; +import {parseResult, Result} from "../generated/schema.ts"; export function loadDataFromEmbed(): Result { const doc = document.querySelector("#data")!;