diff --git a/.github/workflows/built-tests.yml b/.github/workflows/built-tests.yml index c4964d2d97..061e05db01 100644 --- a/.github/workflows/built-tests.yml +++ b/.github/workflows/built-tests.yml @@ -1,4 +1,4 @@ -name: Tests +name: Tests and build on: push: diff --git a/cmd/gsa/command.go b/cmd/gsa/command.go index fe80132152..01b50c10af 100644 --- a/cmd/gsa/command.go +++ b/cmd/gsa/command.go @@ -3,9 +3,12 @@ package main import ( + "log/slog" + "github.com/alecthomas/kong" gsv "github.com/Zxilly/go-size-analyzer" + "github.com/Zxilly/go-size-analyzer/internal/utils" "github.com/Zxilly/go-size-analyzer/internal/webui" ) @@ -90,4 +93,10 @@ func init() { return nil }), ) + + if Options.Verbose { + utils.InitLogger(slog.LevelDebug) + } else { + utils.InitLogger(slog.LevelWarn) + } } diff --git a/cmd/gsa/entry.go b/cmd/gsa/entry.go index f64d88c26f..8590c0a6f1 100644 --- a/cmd/gsa/entry.go +++ b/cmd/gsa/entry.go @@ -21,12 +21,6 @@ import ( ) func entry() error { - if Options.Verbose { - utils.InitLogger(slog.LevelDebug) - } else { - utils.InitLogger(slog.LevelWarn) - } - utils.ApplyMemoryLimit() reader, err := mmap.Open(Options.Binary) diff --git a/internal/dwarf/dwarf.go b/internal/dwarf/dwarf.go index 638672ace3..77cec432f9 100644 --- a/internal/dwarf/dwarf.go +++ b/internal/dwarf/dwarf.go @@ -4,6 +4,7 @@ import ( "debug/dwarf" "fmt" "log/slog" + "strconv" "strings" ) @@ -86,17 +87,13 @@ func EntryShouldIgnore(entry *dwarf.Entry) bool { declaration := entry.Val(dwarf.AttrDeclaration) if declaration != nil { val, ok := declaration.(bool) - if ok && val { - return true - } + return !ok || val } inline := entry.Val(dwarf.AttrInline) if inline != nil { val, ok := inline.(bool) - if ok && val { - return true - } + return !ok || val } abstractOrigin := entry.Val(dwarf.AttrAbstractOrigin) @@ -124,12 +121,12 @@ func EntryFileReader(cu *dwarf.Entry, d *dwarf.Data) func(entry *dwarf.Entry) st if entry.Val(dwarf.AttrTrampoline) == nil { fileIndexAny := entry.Val(dwarf.AttrDeclFile) if fileIndexAny == nil { - slog.Warn(fmt.Sprintf("Failed to load DWARF function file: %s", EntryPrettyPrinter(entry))) + slog.Warn(fmt.Sprintf("Failed to load DWARF function file: %s", EntryPrettyPrint(entry))) return defaultName } fileIndex, ok := fileIndexAny.(int64) if !ok || fileIndex < 0 || int(fileIndex) >= len(files) { - slog.Warn(fmt.Sprintf("Failed to load DWARF function file: %s", EntryPrettyPrinter(entry))) + slog.Warn(fmt.Sprintf("Failed to load DWARF function file: %s", EntryPrettyPrint(entry))) return defaultName } @@ -140,10 +137,14 @@ func EntryFileReader(cu *dwarf.Entry, d *dwarf.Data) func(entry *dwarf.Entry) st } } -func EntryPrettyPrinter(entry *dwarf.Entry) string { +func EntryPrettyPrint(entry *dwarf.Entry) string { ret := new(strings.Builder) + ret.WriteString(entry.Tag.String()) + ret.WriteString(" ") + ret.WriteString(strconv.Itoa(int(entry.Offset))) + ret.WriteString(" ") for _, field := range entry.Field { - ret.WriteString(fmt.Sprintf("%#v", field)) + ret.WriteString(fmt.Sprintf("%#v ", field)) } return ret.String() diff --git a/internal/entity/addr.go b/internal/entity/addr.go index 8c28b08cbf..c3790b6a88 100644 --- a/internal/entity/addr.go +++ b/internal/entity/addr.go @@ -20,8 +20,10 @@ func (a *AddrPos) String() string { type Addr struct { *AddrPos - Pkg *Package // package can be nil for cgo symbols + Pkg *Package // package can be nil for cgo symbols + Function *Function // for symbol source it will be a nil + Symbol *Symbol // for function source it will be a nil SourceType AddrSourceType @@ -29,14 +31,19 @@ type Addr struct { } func (a *Addr) String() string { - var pkgName, funcName string + ret := new(strings.Builder) + _, _ = fmt.Fprintf(ret, "AddrPos: %s", a.AddrPos) if a.Pkg != nil { - pkgName = a.Pkg.Name + _, _ = fmt.Fprintf(ret, " Pkg: %s", a.Pkg.Name) } if a.Function != nil { - funcName = a.Function.Name + _, _ = fmt.Fprintf(ret, " Function: %s", a.Function.Name) + } + if a.Symbol != nil { + _, _ = fmt.Fprintf(ret, " Symbol: %s", a.Symbol.Name) } - return fmt.Sprintf("AddrPos: %s Pkg: %s Function: %s SourceType: %s", a.AddrPos, pkgName, funcName, a.SourceType) + _, _ = fmt.Fprintf(ret, " SourceType: %s", a.SourceType) + return ret.String() } // AddrCoverage is a list of AddrPos, describe the coverage of the address space diff --git a/internal/entity/addr_test.go b/internal/entity/addr_test.go index 884a9848f7..1c031418b6 100644 --- a/internal/entity/addr_test.go +++ b/internal/entity/addr_test.go @@ -135,7 +135,7 @@ func TestCoveragePartStringWithMultipleAddrs(t *testing.T) { } expected := "Pos: Addr: 1000 CodeSize: 100 Type: data\n" + - "AddrPos: Addr: 1000 CodeSize: 100 Type: data Pkg: Function: SourceType: disasm\n" + + "AddrPos: Addr: 1000 CodeSize: 100 Type: data SourceType: disasm\n" + "AddrPos: Addr: 1000 CodeSize: 100 Type: data Pkg: main Function: main SourceType: symbol" result := coveragePart.String() @@ -178,7 +178,7 @@ func TestErrorReturnsExpectedErrorMessage(t *testing.T) { } expected := "addr 1000 pos Pos: Addr: 1000 CodeSize: 100 Type: data\n" + - "AddrPos: Pkg: Function: SourceType: and Pos: Addr: 10ff CodeSize: 100 Type: data\n" + - "AddrPos: Pkg: Function: SourceType: conflict" + "AddrPos: SourceType: and Pos: Addr: 10ff CodeSize: 100 Type: data\n" + + "AddrPos: SourceType: conflict" assert.Equal(t, expected, err.Error()) } diff --git a/internal/entity/knownaddr.go b/internal/entity/knownaddr.go index 2a4ce1140c..248eb592b5 100644 --- a/internal/entity/knownaddr.go +++ b/internal/entity/knownaddr.go @@ -6,17 +6,17 @@ import ( ) type KnownAddr struct { - Text AddrSpace + TextAddrSpace AddrSpace - Symbol AddrSpace - SymbolCoverage AddrCoverage + SymbolAddrSpace AddrSpace + SymbolCoverage AddrCoverage } func NewKnownAddr() *KnownAddr { return &KnownAddr{ - Text: make(map[uint64]*Addr), - Symbol: make(map[uint64]*Addr), - SymbolCoverage: make(AddrCoverage, 0), + TextAddrSpace: make(map[uint64]*Addr), + SymbolAddrSpace: make(map[uint64]*Addr), + SymbolCoverage: make(AddrCoverage, 0), } } @@ -33,7 +33,7 @@ func (f *KnownAddr) InsertTextFromPclnTab(entry uint64, size uint64, fn *Functio Meta: meta, } - f.Text.Insert(&cur) + f.TextAddrSpace.Insert(&cur) } func (f *KnownAddr) InsertTextFromDWARF(entry uint64, size uint64, fn *Function, meta DwarfMeta) { @@ -49,7 +49,7 @@ func (f *KnownAddr) InsertTextFromDWARF(entry uint64, size uint64, fn *Function, Meta: meta, } - f.Text.Insert(&cur) + f.TextAddrSpace.Insert(&cur) } func (f *KnownAddr) InsertSymbol(entry uint64, size uint64, p *Package, typ AddrType, meta SymbolMeta) *Addr { @@ -60,12 +60,13 @@ func (f *KnownAddr) InsertSymbol(entry uint64, size uint64, p *Package, typ Addr Type: typ, }, Pkg: p, - Function: nil, // TODO: try to find the function? + Function: nil, + Symbol: NewSymbol(meta.SymbolName, entry, size, typ), SourceType: AddrSourceSymbol, Meta: meta, } - f.Symbol.Insert(cur) + f.SymbolAddrSpace.Insert(cur) return cur } @@ -77,17 +78,18 @@ func (f *KnownAddr) InsertSymbolFromDWARF(entry uint64, size uint64, p *Package, Type: typ, }, Pkg: p, - Function: nil, // TODO: try to find the function? + Function: nil, + Symbol: NewSymbol(meta.SymbolName, entry, size, typ), SourceType: AddrSourceDwarf, Meta: meta, } - f.Symbol.Insert(cur) + f.SymbolAddrSpace.Insert(cur) return cur } func (f *KnownAddr) BuildSymbolCoverage() { - f.SymbolCoverage = f.Symbol.ToDirtyCoverage() + f.SymbolCoverage = f.SymbolAddrSpace.ToDirtyCoverage() } func (f *KnownAddr) SymbolCovHas(entry uint64, size uint64) (AddrType, bool) { diff --git a/internal/knowninfo/collect.go b/internal/knowninfo/collect.go index 3cfb1890bf..34f76ae904 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.Text.ToDirtyCoverage() + pclntabCov := k.KnownAddr.TextAddrSpace.ToDirtyCoverage() // merge all covs := make([]entity.AddrCoverage, 0) @@ -34,7 +34,7 @@ func (k *KnownInfo) CalculateSectionSize() error { 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)) + slog.Debug(fmt.Sprintf("possible bss addr %s", cp)) continue } t[s] += cp.Pos.Size diff --git a/internal/knowninfo/dwarf.go b/internal/knowninfo/dwarf.go index f2007d4209..2bb494f770 100644 --- a/internal/knowninfo/dwarf.go +++ b/internal/knowninfo/dwarf.go @@ -1,7 +1,9 @@ package knowninfo import ( + "context" "debug/dwarf" + "errors" "fmt" "log/slog" "math" @@ -18,18 +20,28 @@ import ( func (k *KnownInfo) AddDwarfVariable(entry *dwarf.Entry, d *dwarf.Data, pkg *entity.Package, ptrSize int) { instsAny := entry.Val(dwarf.AttrLocation) if instsAny == nil { - slog.Warn(fmt.Sprintf("no location attribute for %s", dwarfutil.EntryPrettyPrinter(entry))) + slog.Warn(fmt.Sprintf("no location attribute for %s", dwarfutil.EntryPrettyPrint(entry))) return } insts, ok := instsAny.([]byte) if !ok { - slog.Warn(fmt.Sprintf("location attribute is not []byte for %s", dwarfutil.EntryPrettyPrinter(entry))) + slog.Warn(fmt.Sprintf("location attribute is not []byte for %s", dwarfutil.EntryPrettyPrint(entry))) return } addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{StaticBase: k.Wrapper.ImageBase()}, insts, ptrSize, nil) if err != nil { - slog.Warn(fmt.Sprintf("Failed to execute location attribute for %s: %v", dwarfutil.EntryPrettyPrinter(entry), err)) + level := slog.LevelDebug + if !errors.Is(err, op.ErrMemoryReadUnavailable) { + level = slog.LevelWarn + } + slog.Log(context.Background(), + level, + fmt.Sprintf( + "Failed to execute location attribute for %s: %v", + dwarfutil.EntryPrettyPrint(entry), err, + ), + ) return } @@ -41,7 +53,7 @@ func (k *KnownInfo) AddDwarfVariable(entry *dwarf.Entry, d *dwarf.Data, pkg *ent return k.Wrapper.ReadAddr(addrCb, size) }) if err != nil { - slog.Warn(fmt.Sprintf("Failed to load DWARF var %s: %v", dwarfutil.EntryPrettyPrinter(entry), err)) + slog.Warn(fmt.Sprintf("Failed to load DWARF var %s: %v", dwarfutil.EntryPrettyPrint(entry), err)) return } @@ -86,7 +98,7 @@ func (k *KnownInfo) AddDwarfSubProgram( ) { subEntryName, ok := subEntry.Val(dwarf.AttrName).(string) if !ok { - slog.Warn(fmt.Sprintf("Failed to load DWARF function name: %s", dwarfutil.EntryPrettyPrinter(subEntry))) + slog.Warn(fmt.Sprintf("Failed to load DWARF function name: %s", dwarfutil.EntryPrettyPrint(subEntry))) return } @@ -129,7 +141,7 @@ func (k *KnownInfo) AddDwarfSubProgram( added := pkg.AddFuncIfNotExists(filename, fn) if added { - k.KnownAddr.Text.Insert(&entity.Addr{ + k.KnownAddr.TextAddrSpace.Insert(&entity.Addr{ AddrPos: &entity.AddrPos{Addr: addr, Size: size, Type: entity.AddrTypeText}, Pkg: pkg, Function: fn, @@ -142,12 +154,12 @@ func (k *KnownInfo) AddDwarfSubProgram( func (k *KnownInfo) GetPackageFromDwarfCompileUnit(cuEntry *dwarf.Entry) *entity.Package { cuLang, ok := cuEntry.Val(dwarf.AttrLanguage).(int64) if !ok { - slog.Warn(fmt.Sprintf("Failed to load DWARF compile unit language: %s", dwarfutil.EntryPrettyPrinter(cuEntry))) + slog.Warn(fmt.Sprintf("Failed to load DWARF compile unit language: %s", dwarfutil.EntryPrettyPrint(cuEntry))) return nil } cuName, ok := cuEntry.Val(dwarf.AttrName).(string) if !ok { - slog.Warn(fmt.Sprintf("Failed to load DWARF compile unit name: %s", dwarfutil.EntryPrettyPrinter(cuEntry))) + slog.Warn(fmt.Sprintf("Failed to load DWARF compile unit name: %s", dwarfutil.EntryPrettyPrint(cuEntry))) return nil } @@ -185,7 +197,7 @@ func (k *KnownInfo) GetPackageFromDwarfCompileUnit(cuEntry *dwarf.Entry) *entity func (k *KnownInfo) LoadDwarfCompileUnit(d *dwarf.Data, cuEntry *dwarf.Entry, pendingEntry []*dwarf.Entry, ptrSize int) { cuLang, ok := cuEntry.Val(dwarf.AttrLanguage).(int64) if !ok { - slog.Warn(fmt.Sprintf("Failed to load DWARF compile unit language: %s", dwarfutil.EntryPrettyPrinter(cuEntry))) + slog.Warn(fmt.Sprintf("Failed to load DWARF compile unit language: %s", dwarfutil.EntryPrettyPrint(cuEntry))) return } diff --git a/scripts/tests.py b/scripts/tests.py index 501c6611ee..9b417b7c2b 100644 --- a/scripts/tests.py +++ b/scripts/tests.py @@ -1,6 +1,7 @@ import os.path import platform import subprocess +import threading import time from argparse import ArgumentParser @@ -176,8 +177,9 @@ def run_integration_tests(typ: str, gsa_path: str): else: timeout = 60 - if typ == "example": + if typ == "web": run_web_test(gsa_path) + return all_tests = len(targets) completed_tests = 0 @@ -242,14 +244,27 @@ def run_web_test(entry: str): log(f"Waiting for the server to start on port {port}...") - for line in iter(p.stdout.readline, ""): - if "localhost" in line: - break - stdout_data += line + timeout_seconds = 5 + timeout_occurred = False + + def timeout_handler(): + nonlocal timeout_occurred + timeout_occurred = True + p.kill() # 强制终止进程 + + timer = threading.Timer(timeout_seconds, timeout_handler) + timer.start() + try: + for line in iter(p.stdout.readline, ""): + if "localhost" in line: + break + stdout_data += line + finally: + timer.cancel() - time.sleep(1) + if p.poll() is not None or timeout_occurred: + log(f"args: {p.args}") - if p.poll() is not None: stdout_data += p.stdout.read() stderr_data = p.stderr.read() @@ -313,6 +328,7 @@ def get_parser() -> ArgumentParser: if args.integration_example or args.integration_real: with build_gsa() as gsa: if args.integration_example: + run_integration_tests("web", gsa) run_integration_tests("example", gsa) if args.integration_real: run_integration_tests("real", gsa) diff --git a/scripts/tool/gsa.py b/scripts/tool/gsa.py index e716cd6694..a191177b59 100644 --- a/scripts/tool/gsa.py +++ b/scripts/tool/gsa.py @@ -35,7 +35,8 @@ def build_gsa(): log("Built gsa.") - shutil.copyfile(temp_binary, os.path.join(get_project_root(), "results", "gsa")) + ext = ".exe" if os.name == "nt" else "" + shutil.copyfile(temp_binary, os.path.join(get_project_root(), "results", "gsa"+ext)) yield temp_binary