diff --git a/internal/etw/source_test.go b/internal/etw/source_test.go index 37a8ca838..c1d4b328c 100644 --- a/internal/etw/source_test.go +++ b/internal/etw/source_test.go @@ -805,6 +805,7 @@ func (s *NoopPsSnapshotter) GetSnapshot() []*pstypes.PS func (s *NoopPsSnapshotter) AddThread(evt *event.Event) error { return nil } func (s *NoopPsSnapshotter) AddModule(evt *event.Event) error { return nil } func (s *NoopPsSnapshotter) FindModule(addr va.Address) (bool, *pstypes.Module) { return false, nil } +func (s *NoopPsSnapshotter) FindAllModules() map[string]pstypes.Module { return nil } func (s *NoopPsSnapshotter) RemoveThread(pid uint32, tid uint32) error { return nil } func (s *NoopPsSnapshotter) RemoveModule(pid uint32, addr va.Address) error { return nil } func (s *NoopPsSnapshotter) WriteFromCapture(evt *event.Event) error { return nil } diff --git a/pkg/ps/snapshotter.go b/pkg/ps/snapshotter.go index 1eec7f18e..f58f45403 100644 --- a/pkg/ps/snapshotter.go +++ b/pkg/ps/snapshotter.go @@ -55,6 +55,8 @@ type Snapshotter interface { // FindModule traverses loaded modules of all processes in the snapshot and // if there is module with the specified base address, it returns its metadata. FindModule(addr va.Address) (bool, *pstypes.Module) + // FindAllModules finds all unique modules across the snapshotter state. + FindAllModules() map[string]pstypes.Module // FindAndPut attempts to retrieve process' state for the specified process identifier. // If the process is found, the snapshotter state is updated with the new process. FindAndPut(pid uint32) *pstypes.PS diff --git a/pkg/ps/snapshotter_mock.go b/pkg/ps/snapshotter_mock.go index f0268da93..2b3dade65 100644 --- a/pkg/ps/snapshotter_mock.go +++ b/pkg/ps/snapshotter_mock.go @@ -57,6 +57,11 @@ func (s *SnapshotterMock) FindModule(addr va.Address) (bool, *pstypes.Module) { return args.Bool(0), nil } +func (s *SnapshotterMock) FindAllModules() map[string]pstypes.Module { + args := s.Called() + return args.Get(0).(map[string]pstypes.Module) +} + // FindAndPut method func (s *SnapshotterMock) FindAndPut(pid uint32) *pstypes.PS { args := s.Called(pid) diff --git a/pkg/ps/snapshotter_windows.go b/pkg/ps/snapshotter_windows.go index 1412d3aa4..9b1c9b472 100644 --- a/pkg/ps/snapshotter_windows.go +++ b/pkg/ps/snapshotter_windows.go @@ -320,6 +320,21 @@ func (s *snapshotter) FindModule(addr va.Address) (bool, *pstypes.Module) { return false, nil } +func (s *snapshotter) FindAllModules() map[string]pstypes.Module { + s.mu.RLock() + defer s.mu.RUnlock() + mods := make(map[string]pstypes.Module) + for _, proc := range s.procs { + for _, mod := range proc.Modules { + if _, ok := mods[mod.Name]; ok { + continue + } + mods[mod.Name] = mod + } + } + return mods +} + func (s *snapshotter) AddMmap(e *event.Event) error { s.mu.Lock() defer s.mu.Unlock() diff --git a/pkg/symbolize/exports.go b/pkg/symbolize/exports.go new file mode 100644 index 000000000..a84b921de --- /dev/null +++ b/pkg/symbolize/exports.go @@ -0,0 +1,137 @@ +/* + * Copyright 2021-present by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package symbolize + +import ( + "sync" + "time" + + "github.com/rabbitstack/fibratus/pkg/ps" + "github.com/rabbitstack/fibratus/pkg/util/va" + log "github.com/sirupsen/logrus" +) + +// ModuleExports contains exports for the specific module +// indexed by RVA (Relative Virtual Address). +type ModuleExports struct { + exps map[uint32]string +} + +// SymbolFromRVA finds the closest export address before RVA. +func (m *ModuleExports) SymbolFromRVA(rva va.Address) string { + var exp uint32 + for f := range m.exps { + if uint64(f) <= rva.Uint64() { + if exp < f { + exp = f + } + } + } + if exp != 0 { + sym, ok := m.exps[exp] + if ok && sym == "" { + return "?" + } + return sym + } + return "" +} + +// ExportsDirectoryCache stores the cached module exports extracted +// from the PE export directory. +type ExportsDirectoryCache struct { + sync.RWMutex + exports map[string]*ModuleExports + + purger *time.Ticker + quit chan struct{} + + psnap ps.Snapshotter +} + +// NewExportsDirectoryCache returns a fresh instance of the exports directory cache. +func NewExportsDirectoryCache(psnap ps.Snapshotter) *ExportsDirectoryCache { + c := &ExportsDirectoryCache{ + exports: make(map[string]*ModuleExports), + purger: time.NewTicker(time.Minute * 20), + quit: make(chan struct{}, 1), + psnap: psnap, + } + return c +} + +// Exports returns the exports for the given module path. If +// the exports can't be find, then the module PE is parsed +// and the exports cache updated. +func (c *ExportsDirectoryCache) Exports(mod string) (*ModuleExports, bool) { + c.RLock() + exports, ok := c.exports[mod] + c.RUnlock() + if ok { + return exports, true + } + pe, err := parsePeFile(mod) + if err != nil { + return nil, false + } + c.Lock() + defer c.Unlock() + exports = &ModuleExports{exps: pe.Exports} + c.exports[mod] = exports + return exports, true +} + +// Clear removes all module exports from the directory cache. +func (c *ExportsDirectoryCache) Clear() { + c.Lock() + defer c.Unlock() + c.exports = make(map[string]*ModuleExports) +} + +// RemoveExports removes all exports associated with the module. +func (c *ExportsDirectoryCache) RemoveExports(mod string) { + c.Lock() + defer c.Unlock() + delete(c.exports, mod) +} + +func (c *ExportsDirectoryCache) purge() { + for { + select { + case <-c.purger.C: + c.clearExports() + case <-c.quit: + return + } + } +} + +// clearExports purges all module exports that +// don't exist in the global snapshotter state. +func (c *ExportsDirectoryCache) clearExports() { + mods := c.psnap.FindAllModules() + c.Lock() + defer c.Unlock() + for exp := range c.exports { + if _, ok := mods[exp]; !ok { + log.Debugf("removing stale export %s from directory cache", exp) + delete(c.exports, exp) + } + } +} diff --git a/pkg/symbolize/exports_test.go b/pkg/symbolize/exports_test.go new file mode 100644 index 000000000..0eb5f7352 --- /dev/null +++ b/pkg/symbolize/exports_test.go @@ -0,0 +1,91 @@ +/* + * Copyright 2021-present by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package symbolize + +import ( + "testing" + + "github.com/rabbitstack/fibratus/pkg/util/va" + "github.com/stretchr/testify/assert" +) + +func TestSymbolFromRVA(t *testing.T) { + var tests = []struct { + rva va.Address + exports map[uint32]string + expectedSymbol string + }{ + {va.Address(317949), map[uint32]string{ + 9824: "SHCreateScopeItemFromShellItem", + 23248: "SHCreateScopeItemFromIDList", + 165392: "DllGetClassObject", + 186368: "SHCreateSearchIDListFromAutoList", + 238048: "DllCanUnloadNow", + 240112: "IsShellItemInSearchIndex", + 240304: "IsMSSearchEnabled", + 272336: "SHSaveBinaryAutoListToStream", + 310672: "DllMain", + 317920: "", + 320864: "", + 434000: "SHCreateAutoList", + 434016: "SHCreateAutoListWithID", + 555040: "CreateDefaultProviderResolver", + 571136: "GetGatherAdmin", + 572592: "SEARCH_RemoteLocationsCscStateCache_IsRemoteLocationInCsc"}, + "?", + }, + {va.Address(434011), map[uint32]string{ + 9824: "SHCreateScopeItemFromShellItem", + 23248: "SHCreateScopeItemFromIDList", + 165392: "DllGetClassObject", + 186368: "SHCreateSearchIDListFromAutoList", + 238048: "DllCanUnloadNow", + 240112: "IsShellItemInSearchIndex", + 240304: "IsMSSearchEnabled", + 272336: "SHSaveBinaryAutoListToStream", + 310672: "DllMain", + 317920: "", + 320864: "", + 434000: "SHCreateAutoList", + 434016: "SHCreateAutoListWithID", + 555040: "CreateDefaultProviderResolver", + 571136: "GetGatherAdmin", + 572592: "SEARCH_RemoteLocationsCscStateCache_IsRemoteLocationInCsc"}, + "SHCreateAutoList", + }, + {va.Address(4532), map[uint32]string{ + 9824: "SHCreateScopeItemFromShellItem", + 23248: "SHCreateScopeItemFromIDList", + 165392: "DllGetClassObject", + 186368: "SHCreateSearchIDListFromAutoList", + 238048: "DllCanUnloadNow", + 240112: "IsShellItemInSearchIndex", + 240304: "IsMSSearchEnabled", + 572592: "SEARCH_RemoteLocationsCscStateCache_IsRemoteLocationInCsc"}, + "", + }, + } + + for _, tt := range tests { + t.Run(tt.expectedSymbol, func(t *testing.T) { + exps := &ModuleExports{exps: tt.exports} + assert.Equal(t, tt.expectedSymbol, exps.SymbolFromRVA(tt.rva)) + }) + } +} diff --git a/pkg/symbolize/symbolizer.go b/pkg/symbolize/symbolizer.go index e2146f29c..1531270ee 100644 --- a/pkg/symbolize/symbolizer.go +++ b/pkg/symbolize/symbolizer.go @@ -53,8 +53,6 @@ var ( // symCleanups counts the number of symbol cleanups symCleanups = expvar.NewInt("symbolizer.symbol.cleanups") - // modCleanups counts the number of module cleanups - modCleanups = expvar.NewInt("symbolizer.module.cleanups") // symCacheHits counts the number of cache hits in the symbols cache symCacheHits = expvar.NewInt("symbolizer.cache.hits") @@ -85,10 +83,6 @@ var parsePeFile = func(name string, option ...pe.Option) (*pe.PE, error) { // its handle and symbol resources are disposed var procTTL = 15 * time.Second -// modTTL maximum time for the module to remain in -// the state until all its exports are removed -var modTTL = 8 * time.Minute - type process struct { pid uint32 handle windows.Handle @@ -102,22 +96,11 @@ func (p *process) keepalive() { } type module struct { - exports map[uint32]string - accessed time.Time + exports *ModuleExports minExportRVA, maxExportRVA uint32 hasExports bool } -type syminfo struct { - module string - symbol string - moduleAddress va.Address // base module address -} - -func (m *module) keepalive() { - m.accessed = time.Now() -} - func (m *module) isUnexported(rva va.Address) bool { if m.minExportRVA == 0 || m.maxExportRVA == 0 { return false @@ -125,13 +108,19 @@ func (m *module) isUnexported(rva va.Address) bool { return rva.Uint64() < uint64(m.minExportRVA) || rva.Uint64() > uint64(m.maxExportRVA) } +type syminfo struct { + module string + symbol string + moduleAddress va.Address // base module address +} + // Symbolizer is responsible for converting raw addresses // into symbol names and modules with the assistance of the -// symbol resolver. +// export directory or symbol resolver. type Symbolizer struct { config *config.Config procs map[uint32]*process - mods map[va.Address]*module + mods map[uint32]map[va.Address]*module mu sync.Mutex // symbols stores the mapping of stack @@ -143,6 +132,9 @@ type Symbolizer struct { // resolution symbols map[uint32]map[va.Address]syminfo + // exps stores resolved export directories + exps *ExportsDirectoryCache + r Resolver psnap ps.Snapshotter @@ -161,12 +153,13 @@ func NewSymbolizer(r Resolver, psnap ps.Snapshotter, config *config.Config, enqu sym := &Symbolizer{ config: config, procs: make(map[uint32]*process), - mods: make(map[va.Address]*module), + mods: make(map[uint32]map[va.Address]*module), symbols: make(map[uint32]map[va.Address]syminfo), cleaner: time.NewTicker(time.Second * 2), purger: time.NewTicker(time.Minute * 5), quit: make(chan struct{}, 1), enqueue: enqueue, + exps: NewExportsDirectoryCache(psnap), r: r, psnap: psnap, } @@ -184,6 +177,7 @@ func NewSymbolizer(r Resolver, psnap ps.Snapshotter, config *config.Config, enqu } go sym.housekeep() + go sym.exps.purge() return sym } @@ -194,6 +188,7 @@ func (s *Symbolizer) Close() { s.cleaner.Stop() s.purger.Stop() s.quit <- struct{}{} + s.exps.quit <- struct{}{} s.cleanAllSyms() if s.config.SymbolizeKernelAddresses { for _, dev := range sys.EnumDevices() { @@ -208,11 +203,11 @@ func (s *Symbolizer) ProcessEvent(e *event.Event) (bool, error) { pid := e.Params.MustGetPid() s.mu.Lock() defer s.mu.Unlock() - if _, ok := s.symbols[pid]; !ok { - return true, nil - } - symCachedSymbols.Add(-int64(len(s.symbols[pid]))) + delete(s.mods, pid) delete(s.symbols, pid) + // remove exports for this process + s.exps.RemoveExports(e.GetParamAsString(params.Exe)) + symCachedSymbols.Add(-int64(len(s.symbols[pid]))) proc, ok := s.procs[pid] if !ok { return true, nil @@ -240,9 +235,7 @@ func (s *Symbolizer) ProcessEvent(e *event.Event) (bool, error) { } } - // remove module if it has been unmapped from - // all process VAS. If the new module is loaded - // populate its export directory entries + // remove module if it has been unmapped from the process VAS err := s.syncModules(e) if err != nil { log.Error(err) @@ -263,49 +256,36 @@ func (s *Symbolizer) ProcessEvent(e *event.Event) (bool, error) { } // syncModules reconciles the state of loaded modules. -// When the module is unloaded from all processes in -// the snapshost state, its exports map is pruned. If -// the new module is loaded and not already present in -// the map, we parse its export directory and insert +// When the module is unloaded from the process address +// space, its information is pruned from the cache. +// If the new module is loaded and not already present in +// the map, we get its export directory and insert // into the map. func (s *Symbolizer) syncModules(e *event.Event) error { - filename := e.GetParamAsString(params.ImagePath) - addr := e.Params.TryGetAddress(params.ImageBase) - s.mu.Lock() - defer s.mu.Unlock() + base := e.Params.TryGetAddress(params.ImageBase) + size := e.Params.TryGetUint64(params.ImageSize) if e.IsUnloadImage() { - ok, _ := s.psnap.FindModule(addr) - if !ok { + s.mu.Lock() + defer s.mu.Unlock() + if _, ok := s.mods[e.PID][base]; ok { symModulesCount.Add(-1) - delete(s.mods, addr) - } - // remove executable images - if strings.EqualFold(filepath.Ext(filename), ".exe") { - delete(s.mods, addr) + delete(s.mods[e.PID], base) + // prune symbol entry from the cache if + // the symbol address falls within the + // unmapped module + syms, ok := s.symbols[e.PID] + if !ok { + return nil + } + for addr := range syms { + if addr >= base && addr <= base.Inc(size) { + delete(s.symbols[e.PID], base) + } + } } - return nil } - if s.mods[addr] != nil { - return nil - } - px, err := parsePeFile(filename, pe.WithSections(), pe.WithExports()) - if err != nil { - return fmt.Errorf("unable to parse PE exports for module [%s]: %v", filename, err) - } - - symModulesCount.Add(1) - - m := &module{exports: px.Exports, accessed: time.Now(), hasExports: true} - exportRVAs := convert.MapKeysToSlice(m.exports) - if len(exportRVAs) > 0 { - m.minExportRVA, m.maxExportRVA = slices.Min(exportRVAs), slices.Max(exportRVAs) - } else { - m.hasExports = false - } - s.mods[addr] = m - return nil } @@ -485,31 +465,34 @@ func (s *Symbolizer) produceFrame(addr va.Address, e *event.Event) callstack.Fra } } } + if mod != nil { frame.Module = mod.Name frame.ModuleAddress = mod.BaseAddress - m, ok := s.mods[mod.BaseAddress] + m, ok := s.mods[pid][mod.BaseAddress] peOK := true if !ok { - // parse export directory to resolve symbols - m = &module{exports: make(map[uint32]string), accessed: time.Now(), hasExports: true} - px, err := parsePeFile(mod.Name, pe.WithSections(), pe.WithExports()) - if err != nil { - peOK = false - m.hasExports = false - } else { - m.exports = px.Exports - m.hasExports = len(m.exports) > 0 - exportRVAs := convert.MapKeysToSlice(m.exports) + var exports *ModuleExports + exports, peOK = s.exps.Exports(mod.Name) + m = &module{ + hasExports: true, + exports: &ModuleExports{exps: make(map[uint32]string)}, + } + if exports != nil { + m.exports = exports + m.hasExports = len(m.exports.exps) > 0 + exportRVAs := convert.MapKeysToSlice(m.exports.exps) if m.hasExports { m.minExportRVA, m.maxExportRVA = slices.Min(exportRVAs), slices.Max(exportRVAs) } + } else { + m.hasExports = false } symModulesCount.Add(1) - s.mods[mod.BaseAddress] = m + s.cacheModule(e.PID, mod.BaseAddress, m) } rva := addr.Dec(mod.BaseAddress.Uint64()) - frame.Symbol = symbolFromRVA(rva, m.exports) + frame.Symbol = m.exports.SymbolFromRVA(rva) // permit unknown symbols for executable modules if frame.Symbol == "" && strings.EqualFold(filepath.Ext(mod.Name), ".exe") { frame.Symbol = "?" @@ -523,9 +506,8 @@ func (s *Symbolizer) produceFrame(addr va.Address, e *event.Event) callstack.Fra if frame.Symbol == "" && (m.isUnexported(rva) || (!m.hasExports && peOK)) { frame.Symbol = "?" } - // keep to module alive from purger - m.keepalive() } + if frame.Module != "" && frame.Symbol != "" { // store resolved symbol information in cache s.cacheSymbol(pid, addr, &frame) @@ -571,33 +553,6 @@ func (s *Symbolizer) produceFrame(addr va.Address, e *event.Event) callstack.Fra return frame } -func (s *Symbolizer) cacheSymbol(pid uint32, addr va.Address, frame *callstack.Frame) { - if sym, ok := s.symbols[pid]; ok { - if _, ok := sym[addr]; !ok { - symCachedSymbols.Add(1) - s.symbols[pid][addr] = syminfo{module: frame.Module, symbol: frame.Symbol, moduleAddress: frame.ModuleAddress} - } - } else { - symCachedSymbols.Add(1) - s.symbols[pid] = map[va.Address]syminfo{addr: {module: frame.Module, symbol: frame.Symbol, moduleAddress: frame.ModuleAddress}} - } -} - -// resolveSymbolFromExportDirectory parses the module PE -// export directory and attempts to locate the closest -// symbol before the relative virtual callstack address. -func (s *Symbolizer) resolveSymbolFromExportDirectory(addr va.Address, mod *pstypes.Module) string { - if mod == nil { - return "" - } - px, err := parsePeFile(mod.Name, pe.WithSections(), pe.WithExports()) - if err != nil { - return "" - } - rva := addr.Dec(mod.BaseAddress.Uint64()) - return symbolFromRVA(rva, px.Exports) -} - // symbolizeAddress resolves the given address to a symbol. If the symbol // for this address was resolved previously, we fetch it from the cache. // On the contrary, the symbol is first consulted in the export directory. @@ -610,9 +565,15 @@ func (s *Symbolizer) symbolizeAddress(pid uint32, addr va.Address, mod *pstypes. symbol, ok := s.symbols[pid][addr] if !ok && mod != nil { // resolve symbol from the export directory - symbol.symbol = s.resolveSymbolFromExportDirectory(addr, mod) + exports, ok := s.exps.Exports(mod.Name) + if !ok { + goto fallback + } + rva := addr.Dec(mod.BaseAddress.Uint64()) + symbol.symbol = exports.SymbolFromRVA(rva) } +fallback: // try to get the symbol via Debug Help API if symbol.symbol == "" { proc, ok := s.procs[pid] @@ -658,37 +619,31 @@ func (s *Symbolizer) symbolizeAddress(pid uint32, addr va.Address, mod *pstypes. return symbol.symbol } -// symbolFromRVA finds the closest export address before RVA. -func symbolFromRVA(rva va.Address, exports map[uint32]string) string { - var exp uint32 - for f := range exports { - if uint64(f) <= rva.Uint64() { - if exp < f { - exp = f - } +func (s *Symbolizer) cacheModule(pid uint32, addr va.Address, m *module) { + if mod, ok := s.mods[pid]; ok { + if _, ok := mod[addr]; !ok { + s.mods[pid][addr] = m } + } else { + s.mods[pid] = map[va.Address]*module{addr: m} } - if exp != 0 { - sym, ok := exports[exp] - if ok && sym == "" { - return "?" +} + +func (s *Symbolizer) cacheSymbol(pid uint32, addr va.Address, frame *callstack.Frame) { + if sym, ok := s.symbols[pid]; ok { + if _, ok := sym[addr]; !ok { + symCachedSymbols.Add(1) + s.symbols[pid][addr] = syminfo{module: frame.Module, symbol: frame.Symbol, moduleAddress: frame.ModuleAddress} } - return sym + } else { + symCachedSymbols.Add(1) + s.symbols[pid] = map[va.Address]syminfo{addr: {module: frame.Module, symbol: frame.Symbol, moduleAddress: frame.ModuleAddress}} } - return "" } func (s *Symbolizer) cleanSym() { s.mu.Lock() defer s.mu.Unlock() - for addr, m := range s.mods { - if time.Since(m.accessed) > modTTL { - modCleanups.Add(1) - symModulesCount.Add(-1) - log.Debugf("removing module exports for addr [%s]", addr) - delete(s.mods, addr) - } - } for _, proc := range s.procs { if time.Since(proc.accessed) > procTTL { symCleanups.Add(1) diff --git a/pkg/symbolize/symbolizer_test.go b/pkg/symbolize/symbolizer_test.go index 163d8b4e9..f624e752d 100644 --- a/pkg/symbolize/symbolizer_test.go +++ b/pkg/symbolize/symbolizer_test.go @@ -21,7 +21,6 @@ package symbolize import ( "math/rand" "os" - "path/filepath" "testing" "time" @@ -192,53 +191,33 @@ func TestProcessCallstackPeExports(t *testing.T) { assert.True(t, e.Callstack.ContainsUnbacked()) // check internal state - assert.Len(t, s.mods, 3) + assert.Len(t, s.mods, 1) + assert.Len(t, s.mods[e.PID], 3) // should have populated the symbols cache assert.Len(t, s.symbols, 1) assert.Equal(t, syminfo{module: "unbacked", symbol: "?"}, s.symbols[e.PID][0x2638e59e0a5]) - // image load event should add module exports - // and when the image is unloaded and there are - // no processes with the image section mapped - // inside their VAS, we can remove the module - e2 := &event.Event{ - Type: event.LoadImage, - Tid: 2484, - PID: uint32(12328), - CPU: 1, - Seq: 2, - Name: "LoadImage", - Timestamp: time.Now(), - Category: event.Image, - Params: event.Params{ - params.ImageBase: {Name: params.ImageBase, Type: params.Address, Value: uint64(0x12345f)}, - params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\System32\\bcrypt32.dll"}, - }, - PS: proc, - } - _, err = s.ProcessEvent(e2) - require.NoError(t, err) - assert.Len(t, s.mods, 4) - e3 := &event.Event{ Type: event.UnloadImage, Tid: 2484, - PID: uint32(12328), + PID: uint32(os.Getpid()), CPU: 1, Seq: 2, Name: "UnloadImage", Timestamp: time.Now(), Category: event.Image, Params: event.Params{ - params.ImageBase: {Name: params.ImageBase, Type: params.Address, Value: uint64(0x12345f)}, - params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: filepath.Join(os.Getenv("SystemRoot"), "System32", "bcrypt32.dll")}, + params.ImageBase: {Name: params.ImageBase, Type: params.Address, Value: uint64(0x7ffb5d8e11c4)}, + params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: `C:\Windows\System32\user32.dll`}, }, PS: proc, } + + // dll is unloaded, the number of modules should decrement _, err = s.ProcessEvent(e3) require.NoError(t, err) - assert.Len(t, s.mods, 3) + assert.Len(t, s.mods[e.PID], 2) } func TestProcessCallstack(t *testing.T) { @@ -481,67 +460,3 @@ func TestProcessCallstackProcsTTL(t *testing.T) { r.AssertNumberOfCalls(t, "Cleanup", 1) assert.Equal(t, 0, s.procsSize()) } - -func TestSymbolFromRVA(t *testing.T) { - var tests = []struct { - rva va.Address - exports map[uint32]string - expectedSymbol string - }{ - {va.Address(317949), map[uint32]string{ - 9824: "SHCreateScopeItemFromShellItem", - 23248: "SHCreateScopeItemFromIDList", - 165392: "DllGetClassObject", - 186368: "SHCreateSearchIDListFromAutoList", - 238048: "DllCanUnloadNow", - 240112: "IsShellItemInSearchIndex", - 240304: "IsMSSearchEnabled", - 272336: "SHSaveBinaryAutoListToStream", - 310672: "DllMain", - 317920: "", - 320864: "", - 434000: "SHCreateAutoList", - 434016: "SHCreateAutoListWithID", - 555040: "CreateDefaultProviderResolver", - 571136: "GetGatherAdmin", - 572592: "SEARCH_RemoteLocationsCscStateCache_IsRemoteLocationInCsc"}, - "?", - }, - {va.Address(434011), map[uint32]string{ - 9824: "SHCreateScopeItemFromShellItem", - 23248: "SHCreateScopeItemFromIDList", - 165392: "DllGetClassObject", - 186368: "SHCreateSearchIDListFromAutoList", - 238048: "DllCanUnloadNow", - 240112: "IsShellItemInSearchIndex", - 240304: "IsMSSearchEnabled", - 272336: "SHSaveBinaryAutoListToStream", - 310672: "DllMain", - 317920: "", - 320864: "", - 434000: "SHCreateAutoList", - 434016: "SHCreateAutoListWithID", - 555040: "CreateDefaultProviderResolver", - 571136: "GetGatherAdmin", - 572592: "SEARCH_RemoteLocationsCscStateCache_IsRemoteLocationInCsc"}, - "SHCreateAutoList", - }, - {va.Address(4532), map[uint32]string{ - 9824: "SHCreateScopeItemFromShellItem", - 23248: "SHCreateScopeItemFromIDList", - 165392: "DllGetClassObject", - 186368: "SHCreateSearchIDListFromAutoList", - 238048: "DllCanUnloadNow", - 240112: "IsShellItemInSearchIndex", - 240304: "IsMSSearchEnabled", - 572592: "SEARCH_RemoteLocationsCscStateCache_IsRemoteLocationInCsc"}, - "", - }, - } - - for _, tt := range tests { - t.Run(tt.expectedSymbol, func(t *testing.T) { - assert.Equal(t, tt.expectedSymbol, symbolFromRVA(tt.rva, tt.exports)) - }) - } -}