Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/etw/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
2 changes: 2 additions & 0 deletions pkg/ps/snapshotter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions pkg/ps/snapshotter_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions pkg/ps/snapshotter_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
137 changes: 137 additions & 0 deletions pkg/symbolize/exports.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
91 changes: 91 additions & 0 deletions pkg/symbolize/exports_test.go
Original file line number Diff line number Diff line change
@@ -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))
})
}
}
Loading
Loading