From c2cd7d5ff84d0a9f1435909fff03fe4a4a3360f9 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Fri, 7 Jun 2024 12:20:29 +0800 Subject: [PATCH 1/8] chore: start implement dwarf extraction --- README.md | 2 +- README_zh-CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index be9240ff48..3e8ce686d4 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README_zh-CN.md b/README_zh-CN.md index 09b27ad1f2..be9b749b90 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -176,7 +176,7 @@ Terminal interface options ## TODO - [ ] 添加更多用于反汇编二进制文件的模式 -- [ ] 从 DWARF 段提取信息 +- [x] 从 DWARF 段提取信息 - [x] 计算符号本身的大小到包中 - [ ] 添加其他图表,如火焰图、饼图等 - [ ] 支持 demangle cgo 中的 C++/Rust 符号 From 3686aad8a6ed87aea6b8d3a386a077db828a361d Mon Sep 17 00:00:00 2001 From: Zxilly Date: Fri, 7 Jun 2024 12:44:04 +0800 Subject: [PATCH 2/8] test: format import --- scripts/tests.py | 5 ++++- scripts/tool/merge.py | 4 +++- scripts/tool/remote.py | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/tests.py b/scripts/tests.py index dbbbd0cea7..22444175a4 100644 --- a/scripts/tests.py +++ b/scripts/tests.py @@ -2,12 +2,15 @@ from argparse import ArgumentParser import requests +import subprocess +import time from tool.gsa import build_gsa from tool.junit import generate_junit from tool.merge import merge_covdata from tool.remote import load_remote_binaries, load_remote_for_tui_test, TestType, get_flag_str -from tool.utils import * +from tool.utils import log, get_project_root, ensure_dir, format_time, load_skip, get_covdata_integration_dir, \ + find_unused_port, assert_html_valid, init_dirs def run_unit_tests(): diff --git a/scripts/tool/merge.py b/scripts/tool/merge.py index e3455043f5..3a4ce7f7ae 100644 --- a/scripts/tool/merge.py +++ b/scripts/tool/merge.py @@ -1,4 +1,6 @@ -from .utils import * +import subprocess + +from .utils import dir_is_empty, get_project_root, log, get_covdata_unit_dir, get_covdata_integration_dir def merge_covdata(): diff --git a/scripts/tool/remote.py b/scripts/tool/remote.py index a2da20eb23..9fc60f7846 100644 --- a/scripts/tool/remote.py +++ b/scripts/tool/remote.py @@ -4,12 +4,13 @@ import tarfile import zipfile from enum import Flag, Enum, auto +from threading import Thread from urllib.parse import urlparse import requests from tqdm import tqdm -from .utils import * +from .utils import get_project_root, ensure_dir, run_process, get_bin_path, log, get_binaries_path class TestType(Flag): From 9b57f2f8cbf49b277f049db6c56cc78ff81e3151 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Fri, 7 Jun 2024 16:04:27 +0800 Subject: [PATCH 3/8] chore: rename components --- ui/src/explorer/{app.tsx => Explorer.tsx} | 4 ++-- .../{file_selector.test.tsx => FileSelector.test.tsx} | 2 +- ui/src/explorer/{file_selector.tsx => FileSelector.tsx} | 0 ui/src/explorer_main.tsx | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) rename ui/src/explorer/{app.tsx => Explorer.tsx} (97%) rename ui/src/explorer/{file_selector.test.tsx => FileSelector.test.tsx} (98%) rename ui/src/explorer/{file_selector.tsx => FileSelector.tsx} (100%) diff --git a/ui/src/explorer/app.tsx b/ui/src/explorer/Explorer.tsx similarity index 97% rename from ui/src/explorer/app.tsx rename to ui/src/explorer/Explorer.tsx index ae32b85d9f..0f659138ea 100644 --- a/ui/src/explorer/app.tsx +++ b/ui/src/explorer/Explorer.tsx @@ -3,7 +3,7 @@ import {useAsync} from "react-use"; import gsa from "../../gsa.wasm?init"; import {createEntry} from "../tool/entry.ts"; import {Dialog, DialogContent, DialogContentText, DialogTitle} from "@mui/material"; -import {FileSelector} from "./file_selector.tsx"; +import {FileSelector} from "./FileSelector.tsx"; import TreeMap from "../TreeMap.tsx"; type ModalState = { @@ -16,7 +16,7 @@ type ModalState = { declare function gsa_analyze(name: string, data: Uint8Array): import("../generated/schema.ts").Result; -export const App: React.FC = () => { +export const Explorer: React.FC = () => { const go = useMemo(() => new Go(), []) const {value: inst, loading, error: loadError} = useAsync(async () => { diff --git a/ui/src/explorer/file_selector.test.tsx b/ui/src/explorer/FileSelector.test.tsx similarity index 98% rename from ui/src/explorer/file_selector.test.tsx rename to ui/src/explorer/FileSelector.test.tsx index ef004aebbf..e65d44fc3c 100644 --- a/ui/src/explorer/file_selector.test.tsx +++ b/ui/src/explorer/FileSelector.test.tsx @@ -1,5 +1,5 @@ import {fireEvent, render} from '@testing-library/react'; -import {FileSelector} from './file_selector.tsx'; +import {FileSelector} from './FileSelector.tsx'; import {expect, test, vi} from 'vitest'; test('FileSelector should render correctly', () => { diff --git a/ui/src/explorer/file_selector.tsx b/ui/src/explorer/FileSelector.tsx similarity index 100% rename from ui/src/explorer/file_selector.tsx rename to ui/src/explorer/FileSelector.tsx diff --git a/ui/src/explorer_main.tsx b/ui/src/explorer_main.tsx index fcd0bfff1b..1fbfdab94d 100644 --- a/ui/src/explorer_main.tsx +++ b/ui/src/explorer_main.tsx @@ -2,10 +2,10 @@ import ReactDOM from "react-dom/client"; import React from "react"; import "./tool/wasm_exec.js" -import {App} from "./explorer/app.tsx"; +import {Explorer} from "./explorer/Explorer.tsx"; ReactDOM.createRoot(document.getElementById('root')!).render( - + , ) From bdc1f204134c7646c3b4a82770ab7fb9d4574c51 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Fri, 7 Jun 2024 16:06:51 +0800 Subject: [PATCH 4/8] chore: remove global var --- ui/src/vite-env.d.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ui/src/vite-env.d.ts b/ui/src/vite-env.d.ts index 563eab67c6..11f02fe2a0 100644 --- a/ui/src/vite-env.d.ts +++ b/ui/src/vite-env.d.ts @@ -1,11 +1 @@ /// - -interface ImportMetaEnv { - readonly PACKAGE_DATA: string -} - -interface ImportMeta { - readonly env: ImportMetaEnv -} - -declare const PACKAGE_DATA: string; From 3797871393d551f31966eb38b4f8ae331a42a45a Mon Sep 17 00:00:00 2001 From: Zxilly Date: Sat, 8 Jun 2024 14:19:18 +0800 Subject: [PATCH 5/8] refactor: move known info files --- internal/analyze.go | 37 ++-- internal/entity/knownaddr.go | 18 ++ internal/entity/meta.go | 3 + internal/entity/type.go | 1 + internal/knowninfo.go | 229 --------------------- internal/knowninfo/collect.go | 89 ++++++++ internal/{ => knowninfo}/dependencies.go | 76 +++++-- internal/{ => knowninfo}/disasm.go | 4 +- internal/{ => knowninfo}/disasm_js_wasm.go | 6 +- internal/knowninfo/dwarf.go | 52 +++++ internal/knowninfo/knowninfo.go | 52 +++++ internal/knowninfo/section.go | 19 ++ internal/{ => knowninfo}/symbol.go | 37 +++- internal/{ => section}/section.go | 2 +- internal/{ => section}/section_test.go | 2 +- internal/wrapper/elf.go | 5 + internal/wrapper/macho.go | 5 + internal/wrapper/pe.go | 5 + internal/wrapper/wrapper.go | 2 + 19 files changed, 380 insertions(+), 264 deletions(-) delete mode 100644 internal/knowninfo.go create mode 100644 internal/knowninfo/collect.go rename internal/{ => knowninfo}/dependencies.go (60%) rename internal/{ => knowninfo}/disasm.go (96%) rename internal/{ => knowninfo}/disasm_js_wasm.go (73%) create mode 100644 internal/knowninfo/dwarf.go create mode 100644 internal/knowninfo/knowninfo.go create mode 100644 internal/knowninfo/section.go rename internal/{ => knowninfo}/symbol.go (60%) rename internal/{ => section}/section.go (97%) rename internal/{ => section}/section_test.go (99%) diff --git a/internal/analyze.go b/internal/analyze.go index cc85521f30..ac4ea81414 100644 --- a/internal/analyze.go +++ b/internal/analyze.go @@ -2,6 +2,7 @@ package internal import ( "errors" + "github.com/Zxilly/go-size-analyzer/internal/knowninfo" "io" "log/slog" "path" @@ -30,15 +31,15 @@ func Analyze(name string, reader io.ReaderAt, size uint64, options Options) (*re 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") @@ -52,21 +53,25 @@ func Analyze(name string, reader io.ReaderAt, size uint64, options Options) (*re return nil, err } - if !options.SkipSymbol { - err = k.AnalyzeSymbol() - if err != nil { - if !errors.Is(err, wrapper.ErrNoSymbolTable) { - return nil, err - + ok := k.TryLoadDwarf() + if !ok { + // fallback to symbol and disasm + if !options.SkipSymbol { + err = k.AnalyzeSymbol() + if err != nil { + if !errors.Is(err, wrapper.ErrNoSymbolTable) { + return nil, err + + } + 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 + } } } diff --git a/internal/entity/knownaddr.go b/internal/entity/knownaddr.go index 43c97a26bc..ce7182142b 100644 --- a/internal/entity/knownaddr.go +++ b/internal/entity/knownaddr.go @@ -10,6 +10,8 @@ type KnownAddr struct { Symbol AddrSpace SymbolCoverage AddrCoverage + + Dwarf AddrSpace } func NewKnownAddr() *KnownAddr { @@ -104,3 +106,19 @@ 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/meta.go b/internal/entity/meta.go index f96c4d5658..61f3091762 100644 --- a/internal/entity/meta.go +++ b/internal/entity/meta.go @@ -16,3 +16,6 @@ type SymbolMeta struct { type DisasmMeta struct { Value string } + +type DwarfMeta struct { +} diff --git a/internal/entity/type.go b/internal/entity/type.go index 5d119be2b3..4e8b3a2bcb 100644 --- a/internal/entity/type.go +++ b/internal/entity/type.go @@ -14,4 +14,5 @@ const ( AddrSourceGoPclntab AddrSourceType = "pclntab" AddrSourceSymbol AddrSourceType = "symbol" AddrSourceDisasm AddrSourceType = "disasm" + AddrSourceDwarf AddrSourceType = "dwarf" ) diff --git a/internal/knowninfo.go b/internal/knowninfo.go deleted file mode 100644 index b987b10e0d..0000000000 --- a/internal/knowninfo.go +++ /dev/null @@ -1,229 +0,0 @@ -package internal - -import ( - "fmt" - "log/slog" - "math" - "runtime/debug" - - "github.com/ZxillyFork/gore" - "github.com/ZxillyFork/gosym" - - "github.com/Zxilly/go-size-analyzer/internal/entity" - "github.com/Zxilly/go-size-analyzer/internal/utils" - "github.com/Zxilly/go-size-analyzer/internal/wrapper" -) - -type KnownInfo struct { - Size uint64 - BuildInfo *gore.BuildInfo - Sects *SectionMap - Deps *Dependencies - KnownAddr *entity.KnownAddr - - Coverage entity.AddrCoverage - - gore *gore.GoFile - wrapper wrapper.RawFileWrapper - - VersionFlag struct { - Leq118 bool - Meq120 bool - } -} - -func (k *KnownInfo) LoadSectionMap() error { - slog.Info("Loading sections...") - - sections := k.wrapper.LoadSections() - - slog.Info("Loading sections done") - - k.Sects = &SectionMap{ - Sections: sections, - } - return k.Sects.AssertSize(k.Size) -} - -func (k *KnownInfo) AnalyzeSymbol() error { - slog.Info("Analyzing symbols...") - - err := k.wrapper.LoadSymbols(k.MarkSymbol) - if err != nil { - return err - } - - k.KnownAddr.BuildSymbolCoverage() - - slog.Info("Analyzing symbols done") - - return nil -} - -func (k *KnownInfo) UpdateVersionFlag() { - ver, err := k.gore.GetCompilerVersion() - if err != nil { - // if we can't get build info, we assume it's go1.20 plus - k.VersionFlag.Meq120 = true - } else { - k.VersionFlag.Leq118 = gore.GoVersionCompare(ver.Name, "go1.18.10") <= 0 - k.VersionFlag.Meq120 = gore.GoVersionCompare(ver.Name, "go1.20rc1") >= 0 - } -} - -// ExtractPackageFromSymbol copied from debug/gosym/symtab.go -func (k *KnownInfo) ExtractPackageFromSymbol(s string) string { - var ver gosym.Version - if k.VersionFlag.Meq120 { - ver = gosym.Ver120 // ver120 - } else if k.VersionFlag.Leq118 { - ver = gosym.Ver118 // ver118 - } - - sym := &gosym.Sym{ - Name: s, - GoVersion: ver, - } - - packageName := sym.PackageName() - - return utils.UglyGuess(packageName) -} - -func (k *KnownInfo) LoadPackages() error { - slog.Info("Loading packages...") - - pkgs := NewDependencies(k) - k.Deps = pkgs - - pclntab, err := k.gore.PCLNTab() - if err != nil { - return err - } - - self, err := k.gore.GetPackages() - if err != nil { - return err - } - for _, p := range self { - pkgs.Add(p, entity.PackageTypeMain, pclntab) - } - - grStd, _ := k.gore.GetSTDLib() - for _, p := range grStd { - pkgs.Add(p, entity.PackageTypeStd, pclntab) - } - - grVendor, _ := k.gore.GetVendors() - for _, p := range grVendor { - pkgs.Add(p, entity.PackageTypeVendor, pclntab) - } - - grGenerated, _ := k.gore.GetGeneratedPackages() - for _, p := range grGenerated { - pkgs.Add(p, entity.PackageTypeGenerated, pclntab) - } - - grUnknown, _ := k.gore.GetUnknown() - for _, p := range grUnknown { - pkgs.Add(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 -} - -func (k *KnownInfo) RequireModInfo() error { - if k.BuildInfo == nil { - return fmt.Errorf("no build info") - } - return nil -} - -func (k *KnownInfo) CollectCoverage() error { - // load coverage for pclntab and symbol - pclntabCov := k.KnownAddr.Pclntab.ToDirtyCoverage() - - // merge all - covs := make([]entity.AddrCoverage, 0) - - // collect packages coverage - _ = k.Deps.trie.Walk(func(_ string, p *entity.Package) error { - covs = append(covs, p.GetPackageCoverage()) - return nil - }) - - covs = append(covs, pclntabCov, k.KnownAddr.SymbolCoverage) - - var err error - k.Coverage, err = entity.MergeAndCleanCoverage(covs) - return err -} - -func (k *KnownInfo) CalculateSectionSize() error { - t := make(map[*entity.Section]uint64) - // minus coverage part - for _, cp := range k.Coverage { - section := k.Sects.FindSection(cp.Pos.Addr, cp.Pos.Size) - if section == nil { - slog.Debug(fmt.Sprintf("section not found for coverage part %s", cp)) - continue - } - t[section] += cp.Pos.Size - } - - pclntabSize := uint64(0) - _ = k.Deps.trie.Walk(func(_ string, p *entity.Package) error { - for _, fn := range p.GetFunctions() { - pclntabSize += fn.PclnSize.Size() - } - return nil - }) - - // minus pclntab size - possibleNames := k.wrapper.PclntabSections() - for name, section := range k.Sects.Sections { - for _, possibleName := range possibleNames { - if possibleName == name { - t[section] += pclntabSize - goto foundPclntab - } - } - } - return fmt.Errorf("pclntab section not found when calculate known size") -foundPclntab: - - // linear map virtual size to file size - for section, size := range t { - mapper := 1.0 - if section.Size != section.FileSize { - // need to map to file size - mapper = float64(section.FileSize) / float64(section.Size) - } - section.KnownSize = uint64(math.Floor(float64(size) * mapper)) - - if section.KnownSize > section.FileSize { - // fixme: pclntab size calculation is not accurate - slog.Warn(fmt.Sprintf("section %s known size %d > file size %d, this is a known issue", section.Name, section.KnownSize, section.FileSize)) - section.KnownSize = section.FileSize - } - } - return nil -} - -// CalculatePackageSize calculate the size of each package -// Happens after disassembly -func (k *KnownInfo) CalculatePackageSize() { - _ = k.Deps.trie.Walk(func(_ string, p *entity.Package) error { - p.AssignPackageSize() - return nil - }) -} diff --git a/internal/knowninfo/collect.go b/internal/knowninfo/collect.go new file mode 100644 index 0000000000..124656f7ab --- /dev/null +++ b/internal/knowninfo/collect.go @@ -0,0 +1,89 @@ +package knowninfo + +import ( + "fmt" + "log/slog" + "math" + + "github.com/Zxilly/go-size-analyzer/internal/entity" +) + +func (k *KnownInfo) CollectCoverage() error { + // load coverage for pclntab and symbol + pclntabCov := k.KnownAddr.Pclntab.ToDirtyCoverage() + + // merge all + covs := make([]entity.AddrCoverage, 0) + + // collect packages coverage + _ = k.Deps.Trie.Walk(func(_ string, p *entity.Package) error { + covs = append(covs, p.GetPackageCoverage()) + return nil + }) + + covs = append(covs, pclntabCov, k.KnownAddr.SymbolCoverage) + + var err error + k.Coverage, err = entity.MergeAndCleanCoverage(covs) + return err +} + +func (k *KnownInfo) CalculateSectionSize() error { + t := make(map[*entity.Section]uint64) + // minus coverage part + for _, cp := range k.Coverage { + s := k.Sects.FindSection(cp.Pos.Addr, cp.Pos.Size) + if s == nil { + slog.Debug(fmt.Sprintf("section not found for coverage part %s", cp)) + continue + } + t[s] += cp.Pos.Size + } + + pclntabSize := uint64(0) + _ = k.Deps.Trie.Walk(func(_ string, p *entity.Package) error { + for _, fn := range p.GetFunctions() { + pclntabSize += fn.PclnSize.Size() + } + return nil + }) + + // minus pclntab size + possibleNames := k.Wrapper.PclntabSections() + for name, s := range k.Sects.Sections { + for _, possibleName := range possibleNames { + if possibleName == name { + t[s] += pclntabSize + goto foundPclntab + } + } + } + return fmt.Errorf("pclntab section not found when calculate known size") +foundPclntab: + + // linear map virtual size to file size + for s, size := range t { + mapper := 1.0 + if s.Size != s.FileSize { + // need to map to file size + mapper = float64(s.FileSize) / float64(s.Size) + } + s.KnownSize = uint64(math.Floor(float64(size) * mapper)) + + if s.KnownSize > s.FileSize { + // 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 + } + } + return nil +} + +// CalculatePackageSize calculate the size of each package +// Happens after disassembly +func (k *KnownInfo) CalculatePackageSize() { + _ = k.Deps.Trie.Walk(func(_ string, p *entity.Package) error { + p.AssignPackageSize() + return nil + }) +} diff --git a/internal/dependencies.go b/internal/knowninfo/dependencies.go similarity index 60% rename from internal/dependencies.go rename to internal/knowninfo/dependencies.go index 845c6ddbb9..05fc4eb5aa 100644 --- a/internal/dependencies.go +++ b/internal/knowninfo/dependencies.go @@ -1,6 +1,7 @@ -package internal +package knowninfo import ( + "log/slog" "runtime/debug" "github.com/ZxillyFork/gore" @@ -16,19 +17,19 @@ type Dependencies struct { k *KnownInfo TopPkgs entity.PackageMap - trie *trie.PathTrie[*entity.Package] + Trie *trie.PathTrie[*entity.Package] } func NewDependencies(k *KnownInfo) *Dependencies { return &Dependencies{ TopPkgs: make(entity.PackageMap), k: k, - trie: trie.NewPathTrie[*entity.Package](), + Trie: trie.NewPathTrie[*entity.Package](), } } func (m *Dependencies) GetPackage(name string) (*entity.Package, bool) { - p := m.trie.Get(name) + p := m.Trie.Get(name) if p == nil { return nil, false } @@ -37,7 +38,7 @@ func (m *Dependencies) GetPackage(name string) (*entity.Package, bool) { func (m *Dependencies) GetFunctions() []*entity.Function { funcs := make([]*entity.Function, 0) - _ = m.trie.Walk(func(_ string, p *entity.Package) error { + _ = m.Trie.Walk(func(_ string, p *entity.Package) error { funcs = append(funcs, p.GetFunctions()...) return nil }) @@ -46,7 +47,7 @@ func (m *Dependencies) GetFunctions() []*entity.Function { func (m *Dependencies) AddModules(mods []*debug.Module, typ entity.PackageType) { for _, mod := range mods { - old := m.trie.Get(mod.Path) + old := m.Trie.Get(mod.Path) if old != nil { old.DebugMod = mod continue @@ -55,7 +56,7 @@ func (m *Dependencies) AddModules(mods []*debug.Module, typ entity.PackageType) p.Name = utils.Deduplicate(mod.Path) p.Type = typ p.DebugMod = mod - m.trie.Put(mod.Path, p) + m.Trie.Put(mod.Path, p) } } @@ -66,11 +67,11 @@ func (m *Dependencies) FinishLoad() { } // load generated packages, they don't have a path - if m.trie.Value != nil { - m.TopPkgs[""] = *m.trie.Value + if m.Trie.Value != nil { + m.TopPkgs[""] = *m.Trie.Value } - pending := []pair{{m.TopPkgs, m.trie}} + pending := []pair{{m.TopPkgs, m.Trie}} load := func(packageMap entity.PackageMap, p *trie.PathTrie[*entity.Package]) { for part, nxt := range p.RecursiveDirectChildren() { @@ -106,10 +107,61 @@ func (m *Dependencies) Add(gp *gore.Package, typ entity.PackageType, pclntab *go } // we need merge since the gore relies on the broken std PackageName() function - old := m.trie.Get(name) + old := m.Trie.Get(name) if old != nil { // merge the old one p.Merge(old) } - m.trie.Put(name, p) + m.Trie.Put(name, p) +} + +func (k *KnownInfo) LoadPackages() error { + slog.Info("Loading packages...") + + pkgs := NewDependencies(k) + k.Deps = pkgs + + pclntab, err := k.Gore.PCLNTab() + if err != nil { + return err + } + + self, err := k.Gore.GetPackages() + if err != nil { + return err + } + for _, p := range self { + pkgs.Add(p, entity.PackageTypeMain, pclntab) + } + + grStd, _ := k.Gore.GetSTDLib() + for _, p := range grStd { + pkgs.Add(p, entity.PackageTypeStd, pclntab) + } + + grVendor, _ := k.Gore.GetVendors() + for _, p := range grVendor { + pkgs.Add(p, entity.PackageTypeVendor, pclntab) + } + + grGenerated, _ := k.Gore.GetGeneratedPackages() + for _, p := range grGenerated { + pkgs.Add(p, entity.PackageTypeGenerated, pclntab) + } + + grUnknown, _ := k.Gore.GetUnknown() + for _, p := range grUnknown { + pkgs.Add(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 } diff --git a/internal/disasm.go b/internal/knowninfo/disasm.go similarity index 96% rename from internal/disasm.go rename to internal/knowninfo/disasm.go index 4eeb1aa3a1..9fafc2fb73 100644 --- a/internal/disasm.go +++ b/internal/knowninfo/disasm.go @@ -1,6 +1,6 @@ //go:build !wasm -package internal +package knowninfo import ( "context" @@ -24,7 +24,7 @@ func (k *KnownInfo) Disasm() error { fns := k.Deps.GetFunctions() - e, err := disasm.NewExtractor(k.wrapper, k.Size) + e, err := disasm.NewExtractor(k.Wrapper, k.Size) if err != nil { if errors.Is(err, disasm.ErrArchNotSupported) { slog.Warn("Warning: disassembler not supported for this architecture") diff --git a/internal/disasm_js_wasm.go b/internal/knowninfo/disasm_js_wasm.go similarity index 73% rename from internal/disasm_js_wasm.go rename to internal/knowninfo/disasm_js_wasm.go index 2d1efad6d2..cb50d7c5f8 100644 --- a/internal/disasm_js_wasm.go +++ b/internal/knowninfo/disasm_js_wasm.go @@ -1,8 +1,10 @@ //go:build wasm -package internal +package knowninfo -import "log/slog" +import ( + "log/slog" +) func (k *KnownInfo) Disasm() error { slog.Info("disassembler disabled for wasm") diff --git a/internal/knowninfo/dwarf.go b/internal/knowninfo/dwarf.go new file mode 100644 index 0000000000..2f916cd8a5 --- /dev/null +++ b/internal/knowninfo/dwarf.go @@ -0,0 +1,52 @@ +package knowninfo + +import ( + "debug/dwarf" + "fmt" + "log/slog" + "os" + "strings" +) + +func (k *KnownInfo) TryLoadDwarf() bool { + dwarf, err := k.Wrapper.DWARF() + if err != nil { + slog.Warn(fmt.Sprintf("Failed to load DWARF: %v", err)) + 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() + + r := dwarf.Reader() + for { + entry, err := r.Next() + if entry == nil { + break + } + if err != nil { + slog.Warn(fmt.Sprintf("Failed to read DWARF entry: %#v", err)) + break + } + + fmt.Fprintf(out, "DWARF entry: %v\n", entryPrettyPrinter(entry)) + } + + return true +} + +func entryPrettyPrinter(entry *dwarf.Entry) string { + s := new(strings.Builder) + + 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) + } + + return s.String() +} diff --git a/internal/knowninfo/knowninfo.go b/internal/knowninfo/knowninfo.go new file mode 100644 index 0000000000..638c30bcfd --- /dev/null +++ b/internal/knowninfo/knowninfo.go @@ -0,0 +1,52 @@ +package knowninfo + +import ( + "fmt" + "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 { + Leq118 bool + Meq120 bool +} + +type KnownInfo struct { + Size uint64 + BuildInfo *gore.BuildInfo + Sects *section.SectionMap + Deps *Dependencies + KnownAddr *entity.KnownAddr + + Coverage entity.AddrCoverage + + Gore *gore.GoFile + Wrapper wrapper.RawFileWrapper + + VersionFlag VersionFlag +} + +func (k *KnownInfo) UpdateVersionFlag() VersionFlag { + ver, err := k.Gore.GetCompilerVersion() + if err != nil { + // if we can't get build info, we assume it's go1.20 plus + return VersionFlag{ + Leq118: false, + Meq120: true, + } + } + + return VersionFlag{ + Leq118: gore.GoVersionCompare(ver.Name, "go1.18.10") <= 0, + Meq120: gore.GoVersionCompare(ver.Name, "go1.20rc1") >= 0, + } +} + +func (k *KnownInfo) RequireModInfo() error { + if k.BuildInfo == nil { + return fmt.Errorf("no build info") + } + return nil +} diff --git a/internal/knowninfo/section.go b/internal/knowninfo/section.go new file mode 100644 index 0000000000..02d553b1a2 --- /dev/null +++ b/internal/knowninfo/section.go @@ -0,0 +1,19 @@ +package knowninfo + +import ( + "github.com/Zxilly/go-size-analyzer/internal/section" + "log/slog" +) + +func (k *KnownInfo) LoadSectionMap() error { + slog.Info("Loading sections...") + + sections := k.Wrapper.LoadSections() + + slog.Info("Loading sections done") + + k.Sects = §ion.SectionMap{ + Sections: sections, + } + return k.Sects.AssertSize(k.Size) +} diff --git a/internal/symbol.go b/internal/knowninfo/symbol.go similarity index 60% rename from internal/symbol.go rename to internal/knowninfo/symbol.go index 3834477d45..33dd6e2f1f 100644 --- a/internal/symbol.go +++ b/internal/knowninfo/symbol.go @@ -1,6 +1,7 @@ -package internal +package knowninfo import ( + "github.com/ZxillyFork/gosym" "log/slog" "strings" @@ -8,6 +9,25 @@ import ( "github.com/Zxilly/go-size-analyzer/internal/utils" ) +// ExtractPackageFromSymbol copied from debug/gosym/symtab.go +func (k *KnownInfo) ExtractPackageFromSymbol(s string) string { + var ver gosym.Version + if k.VersionFlag.Meq120 { + ver = gosym.Ver120 // ver120 + } else if k.VersionFlag.Leq118 { + ver = gosym.Ver118 // ver118 + } + + sym := &gosym.Sym{ + Name: s, + GoVersion: ver, + } + + packageName := sym.PackageName() + + return utils.UglyGuess(packageName) +} + func (k *KnownInfo) MarkSymbol(name string, addr, size uint64, typ entity.AddrType) error { if typ != entity.AddrTypeData { // todo: support text symbols, cross check with pclntab @@ -42,3 +62,18 @@ func (k *KnownInfo) MarkSymbol(name string, addr, size uint64, typ entity.AddrTy return nil } + +func (k *KnownInfo) AnalyzeSymbol() error { + slog.Info("Analyzing symbols...") + + err := k.Wrapper.LoadSymbols(k.MarkSymbol) + if err != nil { + return err + } + + k.KnownAddr.BuildSymbolCoverage() + + slog.Info("Analyzing symbols done") + + return nil +} diff --git a/internal/section.go b/internal/section/section.go similarity index 97% rename from internal/section.go rename to internal/section/section.go index bb322a746d..c37968af4d 100644 --- a/internal/section.go +++ b/internal/section/section.go @@ -1,4 +1,4 @@ -package internal +package section import ( "fmt" diff --git a/internal/section_test.go b/internal/section/section_test.go similarity index 99% rename from internal/section_test.go rename to internal/section/section_test.go index 604ac492fa..523936ba74 100644 --- a/internal/section_test.go +++ b/internal/section/section_test.go @@ -1,4 +1,4 @@ -package internal +package section import ( "reflect" diff --git a/internal/wrapper/elf.go b/internal/wrapper/elf.go index 7f436f759a..539a986030 100644 --- a/internal/wrapper/elf.go +++ b/internal/wrapper/elf.go @@ -1,6 +1,7 @@ package wrapper import ( + "debug/dwarf" "debug/elf" "encoding/binary" "errors" @@ -14,6 +15,10 @@ type ElfWrapper struct { file *elf.File } +func (e *ElfWrapper) DWARF() (*dwarf.Data, error) { + return e.file.DWARF() +} + func (*ElfWrapper) PclntabSections() []string { return []string{".gopclntab", ".data.rel.ro.gopclntab", ".data.rel.ro"} } diff --git a/internal/wrapper/macho.go b/internal/wrapper/macho.go index 76545862cf..8d7d8c033f 100644 --- a/internal/wrapper/macho.go +++ b/internal/wrapper/macho.go @@ -1,6 +1,7 @@ package wrapper import ( + "debug/dwarf" "debug/macho" "fmt" "slices" @@ -13,6 +14,10 @@ type MachoWrapper struct { file *macho.File } +func (m *MachoWrapper) DWARF() (*dwarf.Data, error) { + return m.file.DWARF() +} + func (*MachoWrapper) PclntabSections() []string { return []string{"__gopclntab __TEXT", "__gopclntab __DATA_CONST"} } diff --git a/internal/wrapper/pe.go b/internal/wrapper/pe.go index 8c419e2773..5049bd19c1 100644 --- a/internal/wrapper/pe.go +++ b/internal/wrapper/pe.go @@ -1,6 +1,7 @@ package wrapper import ( + "debug/dwarf" "debug/pe" "fmt" "slices" @@ -14,6 +15,10 @@ type PeWrapper struct { file *pe.File } +func (p *PeWrapper) DWARF() (*dwarf.Data, error) { + return p.file.DWARF() +} + func (*PeWrapper) PclntabSections() []string { return []string{".rdata"} // FIXME: get real position from gore, can be .text } diff --git a/internal/wrapper/wrapper.go b/internal/wrapper/wrapper.go index 49dfec0732..cfd922032e 100644 --- a/internal/wrapper/wrapper.go +++ b/internal/wrapper/wrapper.go @@ -1,6 +1,7 @@ package wrapper import ( + "debug/dwarf" "debug/elf" "debug/macho" "debug/pe" @@ -18,6 +19,7 @@ type RawFileWrapper interface { LoadSymbols(marker func(name string, addr, size uint64, typ entity.AddrType) error) error LoadSections() map[string]*entity.Section PclntabSections() []string + DWARF() (*dwarf.Data, error) } func NewWrapper(file any) RawFileWrapper { From 7c5910b6d5791823c03969bcb55b7df864b3b560 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Sat, 8 Jun 2024 14:55:50 +0800 Subject: [PATCH 6/8] test: fix handler --- internal/disasm/interface_test.go | 6 ++++++ internal/knowninfo/dwarf.go | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/disasm/interface_test.go b/internal/disasm/interface_test.go index d9eb0faf77..eae5c38ecb 100644 --- a/internal/disasm/interface_test.go +++ b/internal/disasm/interface_test.go @@ -1,6 +1,7 @@ package disasm import ( + "debug/dwarf" "errors" "testing" @@ -17,6 +18,11 @@ type TestFileWrapper struct { textErr error } +func (t TestFileWrapper) DWARF() (*dwarf.Data, error) { + //TODO implement me + panic("implement me") +} + func (t TestFileWrapper) Text() (textStart uint64, text []byte, err error) { return t.textStart, t.text, t.textErr } diff --git a/internal/knowninfo/dwarf.go b/internal/knowninfo/dwarf.go index 2f916cd8a5..18873fa967 100644 --- a/internal/knowninfo/dwarf.go +++ b/internal/knowninfo/dwarf.go @@ -9,7 +9,7 @@ import ( ) func (k *KnownInfo) TryLoadDwarf() bool { - dwarf, err := k.Wrapper.DWARF() + d, err := k.Wrapper.DWARF() if err != nil { slog.Warn(fmt.Sprintf("Failed to load DWARF: %v", err)) return false @@ -21,7 +21,7 @@ func (k *KnownInfo) TryLoadDwarf() bool { } defer out.Close() - r := dwarf.Reader() + r := d.Reader() for { entry, err := r.Next() if entry == nil { From d115ffb7136df4a0601614bb032ec6109b570563 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Sat, 8 Jun 2024 15:00:08 +0800 Subject: [PATCH 7/8] chore: remove todo --- internal/disasm/interface_test.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/disasm/interface_test.go b/internal/disasm/interface_test.go index eae5c38ecb..d0ef514b9c 100644 --- a/internal/disasm/interface_test.go +++ b/internal/disasm/interface_test.go @@ -19,8 +19,7 @@ type TestFileWrapper struct { } func (t TestFileWrapper) DWARF() (*dwarf.Data, error) { - //TODO implement me - panic("implement me") + panic("not reachable") } func (t TestFileWrapper) Text() (textStart uint64, text []byte, err error) { @@ -32,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) { From 32839731144d0dc43d770665474e352aa3cb7504 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Mon, 10 Jun 2024 06:38:19 +0800 Subject: [PATCH 8/8] 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")!;