diff --git a/pkg/symbolize/symbolizer.go b/pkg/symbolize/symbolizer.go index 41587fbc2..bb13b3faa 100644 --- a/pkg/symbolize/symbolizer.go +++ b/pkg/symbolize/symbolizer.go @@ -65,6 +65,9 @@ var ( // symModulesCount counts the number of loaded module exports symModulesCount = expvar.NewInt("symbolizer.modules.count") + // symEnumModulesHits counts the number of hits from enumerated modules + symEnumModulesHits = expvar.NewInt("symbolizer.enum.modules.hits") + // debugHelpFallbacks counts how many times we Debug Help API was called // to resolve symbol information since we fail to do this from process // modules and PE export directory data @@ -462,6 +465,23 @@ func (s *Symbolizer) produceFrame(addr va.Address, e *kevent.Kevent) callstack.F if mod == nil && e.PS.Parent != nil { mod = e.PS.Parent.FindModuleByVa(addr) } + if mod == nil { + // our last resort is to enumerate process modules + modules := sys.EnumProcessModules(e.PID) + for _, m := range modules { + b := va.Address(m.BaseOfDll) + size := uint64(m.SizeOfImage) + if addr >= b && addr <= b.Inc(size) { + mod = &pstypes.Module{ + Name: m.Name, + BaseAddress: b, + Size: size, + } + symEnumModulesHits.Add(1) + break + } + } + } if mod != nil { frame.Module = mod.Name frame.ModuleAddress = mod.BaseAddress diff --git a/pkg/sys/process.go b/pkg/sys/process.go index 22ddf4458..b4db01b09 100644 --- a/pkg/sys/process.go +++ b/pkg/sys/process.go @@ -117,3 +117,59 @@ func IsWindowsService() bool { isSvc, err := svc.IsWindowsService() return isSvc && err == nil } + +// ProcessModule describes the process loaded module. +type ProcessModule struct { + windows.ModuleInfo + Name string +} + +// EnumProcessModules returns all loaded modules in the process address space. +func EnumProcessModules(pid uint32) []ProcessModule { + n := uint32(1024) + mods := make([]windows.Handle, n) + proc, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, pid) + if err != nil { + return nil + } + defer windows.Close(proc) + + if err := windows.EnumProcessModules(proc, &mods[0], 1024, &n); err != nil { + // needs bigger buffer + if n > 1024 { + mods = make([]windows.Handle, n) + if err := windows.EnumProcessModules(proc, &mods[0], n, &n); err != nil { + return nil + } + } + return nil + } + + modules := make([]ProcessModule, 0) + for _, mod := range mods { + if mod == 0 { + continue + } + var moduleInfo windows.ModuleInfo + if err := windows.GetModuleInformation(proc, mod, &moduleInfo, uint32(unsafe.Sizeof(moduleInfo))); err != nil { + continue + } + module := ProcessModule{ModuleInfo: moduleInfo} + if name, err := getModuleFileName(proc, mod); err == nil { + module.Name = name + } + modules = append(modules, module) + } + + return modules +} + +func getModuleFileName(proc, mod windows.Handle) (string, error) { + n := uint32(1024) + buf := make([]uint16, n) + err := windows.GetModuleFileNameEx(proc, mod, &buf[0], n) + if err != nil { + return "", err + } + return windows.UTF16ToString(buf), nil +} diff --git a/pkg/sys/process_test.go b/pkg/sys/process_test.go new file mode 100644 index 000000000..89f1503b1 --- /dev/null +++ b/pkg/sys/process_test.go @@ -0,0 +1,37 @@ +/* + * 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 sys + +import ( + "github.com/stretchr/testify/assert" + "golang.org/x/sys/windows" + "path/filepath" + "strings" + "testing" +) + +func TestEnumProcessModules(t *testing.T) { + mods := EnumProcessModules(windows.GetCurrentProcessId()) + assert.True(t, len(mods) > 0) + names := make([]string, 0, len(mods)) + for _, mod := range mods { + names = append(names, filepath.Base(strings.ToLower(mod.Name))) + } + assert.Contains(t, names, "ntdll.dll") +}