Skip to content

Commit 0bf0761

Browse files
authored
feat(filter): Callstack filter fields (#234)
1 parent 1ada2fa commit 0bf0761

12 files changed

Lines changed: 526 additions & 94 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ require (
4343
require (
4444
github.com/rivo/uniseg v0.4.2 // indirect
4545
github.com/rogpeppe/go-internal v1.11.0 // indirect
46+
golang.org/x/arch v0.6.0 // indirect
4647
)
4748

4849
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
249249
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
250250
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
251251
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
252+
golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
253+
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
252254
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
253255
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
254256
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

pkg/filter/accessor_windows.go

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
psnap "github.com/rabbitstack/fibratus/pkg/ps"
2525
"github.com/rabbitstack/fibratus/pkg/util/cmdline"
2626
"github.com/rabbitstack/fibratus/pkg/util/loldrivers"
27+
"golang.org/x/sys/windows"
2728
"path/filepath"
2829
"strconv"
2930
"strings"
@@ -433,8 +434,12 @@ func (ps *psAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e
433434
}
434435

435436
const (
436-
rootAncestor = "root" // represents the root ancestor
437-
anyAncestor = "any" // represents any ancestor in the hierarchy
437+
rootAncestor = "root" // represents the root ancestor
438+
anyAncestor = "any" // represents any ancestor in the hierarchy
439+
frameUEnd = "uend" // represents the last user space stack frame
440+
frameUStart = "ustart" // represents the first user space stack frame
441+
frameKEnd = "kend" // represents the last kernel space stack frame
442+
frameKStart = "kstart" // represents the first kernel space stack frame
438443
)
439444

440445
// ancestorFields recursively walks the process ancestors and extracts
@@ -566,6 +571,112 @@ func (t *threadAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value
566571
return nil, nil
567572
}
568573
return kevt.GetParamAsString(kparams.NTStatus), nil
574+
case fields.ThreadCallstackSummary:
575+
return kevt.Callstack.Summary(), nil
576+
case fields.ThreadCallstackDetail:
577+
return kevt.Callstack.String(), nil
578+
case fields.ThreadCallstackModules:
579+
return kevt.Callstack.Modules(), nil
580+
case fields.ThreadCallstackSymbols:
581+
return kevt.Callstack.Symbols(), nil
582+
case fields.ThreadCallstackAllocationSizes:
583+
return kevt.Callstack.AllocationSizes(kevt.PID), nil
584+
case fields.ThreadCallstackProtections:
585+
return kevt.Callstack.Protections(kevt.PID), nil
586+
case fields.ThreadCallstackCallsiteLeadingAssembly:
587+
return kevt.Callstack.CallsiteInsns(kevt.PID, true), nil
588+
case fields.ThreadCallstackCallsiteTrailingAssembly:
589+
return kevt.Callstack.CallsiteInsns(kevt.PID, false), nil
590+
case fields.ThreadCallstackIsUnbacked:
591+
return kevt.Callstack.ContainsUnbacked(), nil
592+
default:
593+
if f.IsCallstackMap() {
594+
return callstackFields(f.String(), kevt)
595+
}
596+
}
597+
return nil, nil
598+
}
599+
600+
// callstackFields is responsible for extracting
601+
// the stack frame data for the specified frame
602+
// index. The index 0 represents the least-recent
603+
// frame, usually the base thread initialization
604+
// frames.
605+
func callstackFields(field string, kevt *kevent.Kevent) (kparams.Value, error) {
606+
if kevt.Callstack.IsEmpty() {
607+
return nil, nil
608+
}
609+
key, segment := captureInBrackets(field)
610+
if key == "" || segment == "" {
611+
return nil, nil
612+
}
613+
var i int
614+
switch key {
615+
case frameUStart:
616+
i = 0
617+
case frameUEnd:
618+
for ; i < kevt.Callstack.Depth()-1 && !kevt.Callstack[i].Addr.InSystemRange(); i++ {
619+
}
620+
i--
621+
case frameKStart:
622+
for i = kevt.Callstack.Depth() - 1; i >= 0 && kevt.Callstack[i].Addr.InSystemRange(); i-- {
623+
}
624+
i++
625+
case frameKEnd:
626+
i = kevt.Callstack.Depth() - 1
627+
default:
628+
if strings.HasSuffix(key, ".dll") {
629+
for n, frame := range kevt.Callstack {
630+
if strings.EqualFold(filepath.Base(frame.Module), key) {
631+
i = n
632+
break
633+
}
634+
}
635+
} else {
636+
var err error
637+
i, err = strconv.Atoi(key)
638+
if err != nil {
639+
return nil, err
640+
}
641+
}
642+
}
643+
644+
if i > kevt.Callstack.Depth() || i < 0 {
645+
i = 0
646+
}
647+
f := kevt.Callstack[i]
648+
649+
switch segment {
650+
case fields.FrameAddress:
651+
return f.Addr.String(), nil
652+
case fields.FrameSymbolOffset:
653+
return f.Offset, nil
654+
case fields.FrameModule:
655+
return f.Module, nil
656+
case fields.FrameSymbol:
657+
return f.Symbol, nil
658+
case fields.FrameProtection, fields.FrameAllocationSize:
659+
proc, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, kevt.PID)
660+
if err != nil {
661+
return nil, err
662+
}
663+
defer windows.Close(proc)
664+
if segment == fields.FrameProtection {
665+
return f.Protection(proc), nil
666+
}
667+
return f.AllocationSize(proc), nil
668+
case fields.FrameCallsiteLeadingAssembly, fields.FrameCallsiteTrailingAssembly:
669+
proc, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, kevt.PID)
670+
if err != nil {
671+
return nil, err
672+
}
673+
defer windows.Close(proc)
674+
if segment == fields.FrameCallsiteLeadingAssembly {
675+
return f.CallsiteAssembly(proc, true), nil
676+
}
677+
return f.CallsiteAssembly(proc, false), nil
678+
case fields.FrameIsUnbacked:
679+
return f.IsUnbacked(), nil
569680
}
570681
return nil, nil
571682
}

pkg/filter/fields/fields_windows.go

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727

2828
// pathRegexp splits the provided path into different components. The first capture
2929
// contains the indexed field name. Next is the indexed key and, finally the segment.
30-
var pathRegexp = regexp.MustCompile(`(pe.sections|pe.resources|ps.envs|ps.modules|ps.ancestor|kevt.arg)\[(.+\s*)].?(.*)`)
30+
var pathRegexp = regexp.MustCompile(`(pe.sections|pe.resources|ps.envs|ps.modules|ps.ancestor|kevt.arg|thread.callstack)\[(.+\s*)].?(.*)`)
3131

3232
// Field represents the type alias for the field
3333
type Field string
@@ -177,6 +177,26 @@ const (
177177
ThreadAccessMaskNames Field = "thread.access.mask.names"
178178
// ThreadAccessStatus represents the thread access status field
179179
ThreadAccessStatus Field = "thread.access.status"
180+
// ThreadCallstack represents the field that provides access to stack frames
181+
ThreadCallstack Field = "thread.callstack"
182+
// ThreadCallstackSummary represents the thread callstack summary field
183+
ThreadCallstackSummary Field = "thread.callstack.summary"
184+
// ThreadCallstackDetail represents the thread callstack detail field
185+
ThreadCallstackDetail Field = "thread.callstack.detail"
186+
// ThreadCallstackModules represents the callstack modules field
187+
ThreadCallstackModules Field = "thread.callstack.modules"
188+
// ThreadCallstackSymbols represents the callstack symbols field
189+
ThreadCallstackSymbols Field = "thread.callstack.symbols"
190+
// ThreadCallstackProtections represents the callstack region protections field
191+
ThreadCallstackProtections Field = "thread.callstack.protections"
192+
// ThreadCallstackAllocationSizes represents the private region page sizes field
193+
ThreadCallstackAllocationSizes Field = "thread.callstack.allocation_sizes"
194+
// ThreadCallstackCallsiteLeadingAssembly represents the callsite prelude opcodes field
195+
ThreadCallstackCallsiteLeadingAssembly Field = "thread.callstack.callsite_leading_assembly"
196+
// ThreadCallstackCallsiteTrailingAssembly represents the callsite postlude opcodes field
197+
ThreadCallstackCallsiteTrailingAssembly Field = "thread.callstack.callsite_trailing_assembly"
198+
// ThreadCallstackIsUnbacked represents the field that indicates if there is an unbacked stack frame
199+
ThreadCallstackIsUnbacked Field = "thread.callstack.is_unbacked"
180200

181201
// PeNumSections represents the number of sections
182202
PeNumSections Field = "pe.nsections"
@@ -506,14 +526,47 @@ const (
506526
ProcessSID Segment = "sid"
507527
// ProcessSessionID represents the session id bound to the process
508528
ProcessSessionID Segment = "sessionid"
529+
530+
// FrameAddress represents the stack frame return address
531+
FrameAddress Segment = "address"
532+
// FrameSymbolOffset represents the symbol offset
533+
FrameSymbolOffset Segment = "offset"
534+
// FrameSymbol represents the symbol name
535+
FrameSymbol Segment = "symbol"
536+
// FrameModule represents the symbol module
537+
FrameModule Segment = "module"
538+
// FrameAllocationSize represents the frame region allocation size
539+
FrameAllocationSize Segment = "allocation_size"
540+
// FrameProtection represents the region page protection where the frame instruction lives
541+
FrameProtection Segment = "protection"
542+
// FrameIsUnbacked determines if the frame is unbacked
543+
FrameIsUnbacked Segment = "is_unbacked"
544+
// FrameCallsiteLeadingAssembly represents the leading callsite opcodes
545+
FrameCallsiteLeadingAssembly = "callsite_leading_assembly"
546+
// FrameCallsiteTrailingAssembly represents the trailing callsite opcodes
547+
FrameCallsiteTrailingAssembly = "callsite_trailing_assembly"
509548
)
510549

550+
func (s Segment) IsSection() bool {
551+
return s == SectionEntropy || s == SectionSize || s == SectionMD5Hash
552+
}
553+
func (s Segment) IsModule() bool {
554+
return s == ModuleChecksum || s == ModuleLocation || s == ModuleBaseAddress || s == ModuleDefaultAddress || s == ModuleSize
555+
}
556+
func (s Segment) IsProcess() bool {
557+
return s == ProcessID || s == ProcessName || s == ProcessCmdline || s == ProcessExe || s == ProcessArgs || s == ProcessCwd || s == ProcessSID || s == ProcessSessionID
558+
}
559+
func (s Segment) IsCallstack() bool {
560+
return s == FrameAddress || s == FrameSymbolOffset || s == FrameSymbol || s == FrameModule || s == FrameAllocationSize || s == FrameProtection || s == FrameIsUnbacked || s == FrameCallsiteLeadingAssembly || s == FrameCallsiteTrailingAssembly
561+
}
562+
511563
func (f Field) IsEnvsMap() bool { return strings.HasPrefix(f.String(), "ps.envs[") }
512564
func (f Field) IsModsMap() bool { return strings.HasPrefix(f.String(), "ps.modules[") }
513565
func (f Field) IsAncestorMap() bool { return strings.HasPrefix(f.String(), "ps.ancestor[") }
514566
func (f Field) IsPeSectionsMap() bool { return strings.HasPrefix(f.String(), "pe.sections[") }
515567
func (f Field) IsPeResourcesMap() bool { return strings.HasPrefix(f.String(), "pe.resources[") }
516568
func (f Field) IsKevtArgMap() bool { return strings.HasPrefix(f.String(), "kevt.arg[") }
569+
func (f Field) IsCallstackMap() bool { return strings.HasPrefix(f.String(), "thread.callstack[") }
517570

518571
var fields = map[Field]FieldInfo{
519572
KevtSeq: {KevtSeq, "event sequence number", kparams.Uint64, []string{"kevt.seq > 666"}, nil},
@@ -595,18 +648,27 @@ var fields = map[Field]FieldInfo{
595648
PsParentUUID: {PsParentUUID, "unique parent process identifier", kparams.Uint64, []string{"ps.parent.uuid > 6000054355"}, nil},
596649
PsChildUUID: {PsChildUUID, "unique child process identifier", kparams.Uint64, []string{"ps.child.uuid > 6000054355"}, nil},
597650

598-
ThreadBasePrio: {ThreadBasePrio, "scheduler priority of the thread", kparams.Int8, []string{"thread.prio = 5"}, nil},
599-
ThreadIOPrio: {ThreadIOPrio, "I/O priority hint for scheduling I/O operations", kparams.Int8, []string{"thread.io.prio = 4"}, nil},
600-
ThreadPagePrio: {ThreadPagePrio, "memory page priority hint for memory pages accessed by the thread", kparams.Int8, []string{"thread.page.prio = 12"}, nil},
601-
ThreadKstackBase: {ThreadKstackBase, "base address of the thread's kernel space stack", kparams.Address, []string{"thread.kstack.base = 'a65d800000'"}, nil},
602-
ThreadKstackLimit: {ThreadKstackLimit, "limit of the thread's kernel space stack", kparams.Address, []string{"thread.kstack.limit = 'a85d800000'"}, nil},
603-
ThreadUstackBase: {ThreadUstackBase, "base address of the thread's user space stack", kparams.Address, []string{"thread.ustack.base = '7ffe0000'"}, nil},
604-
ThreadUstackLimit: {ThreadUstackLimit, "limit of the thread's user space stack", kparams.Address, []string{"thread.ustack.limit = '8ffe0000'"}, nil},
605-
ThreadEntrypoint: {ThreadEntrypoint, "starting address of the function to be executed by the thread", kparams.Address, []string{"thread.entrypoint = '7efe0000'"}, nil},
606-
ThreadPID: {ThreadPID, "the process identifier where the thread is created", kparams.Uint32, []string{"kevt.pid != thread.pid"}, nil},
607-
ThreadAccessMask: {ThreadAccessMask, "thread desired access rights", kparams.AnsiString, []string{"thread.access.mask = '0x1fffff'"}, nil},
608-
ThreadAccessMaskNames: {ThreadAccessMaskNames, "thread desired access rights as a string list", kparams.Slice, []string{"thread.access.mask.names in ('IMPERSONATE')"}, nil},
609-
ThreadAccessStatus: {ThreadAccessStatus, "thread access status", kparams.UnicodeString, []string{"thread.access.status = 'success'"}, nil},
651+
ThreadBasePrio: {ThreadBasePrio, "scheduler priority of the thread", kparams.Int8, []string{"thread.prio = 5"}, nil},
652+
ThreadIOPrio: {ThreadIOPrio, "I/O priority hint for scheduling I/O operations", kparams.Int8, []string{"thread.io.prio = 4"}, nil},
653+
ThreadPagePrio: {ThreadPagePrio, "memory page priority hint for memory pages accessed by the thread", kparams.Int8, []string{"thread.page.prio = 12"}, nil},
654+
ThreadKstackBase: {ThreadKstackBase, "base address of the thread's kernel space stack", kparams.Address, []string{"thread.kstack.base = 'a65d800000'"}, nil},
655+
ThreadKstackLimit: {ThreadKstackLimit, "limit of the thread's kernel space stack", kparams.Address, []string{"thread.kstack.limit = 'a85d800000'"}, nil},
656+
ThreadUstackBase: {ThreadUstackBase, "base address of the thread's user space stack", kparams.Address, []string{"thread.ustack.base = '7ffe0000'"}, nil},
657+
ThreadUstackLimit: {ThreadUstackLimit, "limit of the thread's user space stack", kparams.Address, []string{"thread.ustack.limit = '8ffe0000'"}, nil},
658+
ThreadEntrypoint: {ThreadEntrypoint, "starting address of the function to be executed by the thread", kparams.Address, []string{"thread.entrypoint = '7efe0000'"}, nil},
659+
ThreadPID: {ThreadPID, "the process identifier where the thread is created", kparams.Uint32, []string{"kevt.pid != thread.pid"}, nil},
660+
ThreadAccessMask: {ThreadAccessMask, "thread desired access rights", kparams.AnsiString, []string{"thread.access.mask = '0x1fffff'"}, nil},
661+
ThreadAccessMaskNames: {ThreadAccessMaskNames, "thread desired access rights as a string list", kparams.Slice, []string{"thread.access.mask.names in ('IMPERSONATE')"}, nil},
662+
ThreadAccessStatus: {ThreadAccessStatus, "thread access status", kparams.UnicodeString, []string{"thread.access.status = 'success'"}, nil},
663+
ThreadCallstackSummary: {ThreadCallstackSummary, "callstack summary", kparams.UnicodeString, []string{"thread.callstack.summary contains 'ntdll.dll|KERNELBASE.dll'"}, nil},
664+
ThreadCallstackDetail: {ThreadCallstackDetail, "detailed information of each stack frame", kparams.UnicodeString, []string{"thread.callstack.detail contains 'KERNELBASE.dll!CreateProcessW'"}, nil},
665+
ThreadCallstackModules: {ThreadCallstackModules, "list of modules comprising the callstack", kparams.Slice, []string{"thread.callstack.modules in ('C:\\WINDOWS\\System32\\KERNELBASE.dll')"}, nil},
666+
ThreadCallstackSymbols: {ThreadCallstackSymbols, "list of symbols comprising the callstack", kparams.Slice, []string{"thread.callstack.symbols in ('ntdll.dll!NtCreateProcess')"}, nil},
667+
ThreadCallstackAllocationSizes: {ThreadCallstackAllocationSizes, "allocation sizes of private pages", kparams.Slice, []string{"thread.callstack.allocation_sizes > 10000"}, nil},
668+
ThreadCallstackProtections: {ThreadCallstackProtections, "page protections masks of each frame", kparams.Slice, []string{"thread.callstack.protections in ('RWX', 'WX')"}, nil},
669+
ThreadCallstackCallsiteLeadingAssembly: {ThreadCallstackCallsiteLeadingAssembly, "callsite leading assembly instructions", kparams.Slice, []string{"thread.callstack.callsite_leading_assembly in ('mov r10,rcx', 'syscall')"}, nil},
670+
ThreadCallstackCallsiteTrailingAssembly: {ThreadCallstackCallsiteTrailingAssembly, "callsite trailing assembly instructions", kparams.Slice, []string{"thread.callstack.callsite_trailing_assembly in ('add esp, 0xab')"}, nil},
671+
ThreadCallstackIsUnbacked: {ThreadCallstackIsUnbacked, "indicates if the callstack contains unbacked regions", kparams.Bool, []string{"thread.callstack.is_unbacked"}, nil},
610672

611673
ImageName: {ImageName, "full image name", kparams.UnicodeString, []string{"image.name contains 'advapi32.dll'"}, nil},
612674
ImageBase: {ImageBase, "the base address of process in which the image is loaded", kparams.Address, []string{"image.base.address = 'a65d800000'"}, nil},
@@ -733,28 +795,14 @@ func Lookup(name string) Field {
733795
if segment == "" {
734796
return None
735797
}
736-
switch Segment(segment) {
737-
case SectionEntropy:
738-
return Field(name)
739-
case SectionMD5Hash:
740-
return Field(name)
741-
case SectionSize:
798+
if Segment(segment).IsSection() {
742799
return Field(name)
743800
}
744801
case PsModules:
745802
if segment == "" {
746803
return None
747804
}
748-
switch Segment(segment) {
749-
case ModuleSize:
750-
return Field(name)
751-
case ModuleChecksum:
752-
return Field(name)
753-
case ModuleDefaultAddress:
754-
return Field(name)
755-
case ModuleBaseAddress:
756-
return Field(name)
757-
case ModuleLocation:
805+
if Segment(segment).IsModule() {
758806
return Field(name)
759807
}
760808
case PsAncestor:
@@ -769,30 +817,38 @@ func Lookup(name string) Field {
769817
// we can also get the `any` keyword
770818
// that collects the fields of all
771819
// ancestor processes
772-
var keyRegexp = regexp.MustCompile(`[1-9]+|root|any`)
820+
var keyRegexp = regexp.MustCompile(`^[1-9]+$|^root$|^any$`)
773821
if !keyRegexp.MatchString(key) {
774822
return None
775823
}
776-
switch Segment(segment) {
777-
case ProcessName:
778-
return Field(name)
779-
case ProcessID:
780-
return Field(name)
781-
case ProcessArgs:
824+
if Segment(segment).IsProcess() {
782825
return Field(name)
783-
case ProcessCmdline:
784-
return Field(name)
785-
case ProcessCwd:
786-
return Field(name)
787-
case ProcessExe:
788-
return Field(name)
789-
case ProcessSID:
826+
}
827+
case PeResources:
828+
if key != "" && segment == "" {
790829
return Field(name)
791-
case ProcessSessionID:
830+
}
831+
case PsEnvs, KevtArg:
832+
if key != "" {
792833
return Field(name)
793834
}
794-
case PeResources, PsEnvs, KevtArg:
795-
if key != "" && segment == "" {
835+
case ThreadCallstack:
836+
if segment == "" {
837+
return None
838+
}
839+
// the key can be the stack frame
840+
// index with 0 being the bottom
841+
// userspace frame. u/k start/end
842+
// keys identify the start/end
843+
// user and kernel space frames.
844+
// Lastly, it is possible to specify
845+
// the name of the module from which
846+
// the call was originated
847+
var keyRegexp = regexp.MustCompile(`^[0-9]+$|^uend$|^ustart$|^kend$|^kstart$|^[a-zA-Z0-9]+\.dll$`)
848+
if !keyRegexp.MatchString(key) {
849+
return None
850+
}
851+
if Segment(segment).IsCallstack() {
796852
return Field(name)
797853
}
798854
}

0 commit comments

Comments
 (0)