280 lines
9.7 KiB
Go
280 lines
9.7 KiB
Go
package etw
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha1"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"unicode/utf16"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// Provider represents an ETW event provider. It is identified by a provider
|
|
// name and ID (GUID), which should always have a 1:1 mapping to each other
|
|
// (e.g. don't use multiple provider names with the same ID, or vice versa).
|
|
type Provider struct {
|
|
ID *windows.GUID
|
|
handle providerHandle
|
|
metadata []byte
|
|
callback EnableCallback
|
|
index uint
|
|
enabled bool
|
|
level Level
|
|
keywordAny uint64
|
|
keywordAll uint64
|
|
}
|
|
|
|
// String returns the `provider`.ID as a string
|
|
func (provider *Provider) String() string {
|
|
data1 := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(data1, provider.ID.Data1)
|
|
data2 := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(data2, provider.ID.Data2)
|
|
data3 := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(data3, provider.ID.Data3)
|
|
return fmt.Sprintf(
|
|
"%s-%s-%s-%s-%s",
|
|
hex.EncodeToString(data1),
|
|
hex.EncodeToString(data2),
|
|
hex.EncodeToString(data3),
|
|
hex.EncodeToString(provider.ID.Data4[:2]),
|
|
hex.EncodeToString(provider.ID.Data4[2:]))
|
|
}
|
|
|
|
type providerHandle windows.Handle
|
|
|
|
// ProviderState informs the provider EnableCallback what action is being
|
|
// performed.
|
|
type ProviderState uint32
|
|
|
|
const (
|
|
// ProviderStateDisable indicates the provider is being disabled.
|
|
ProviderStateDisable ProviderState = iota
|
|
// ProviderStateEnable indicates the provider is being enabled.
|
|
ProviderStateEnable
|
|
// ProviderStateCaptureState indicates the provider is having its current
|
|
// state snap-shotted.
|
|
ProviderStateCaptureState
|
|
)
|
|
|
|
type eventInfoClass uint32
|
|
|
|
const (
|
|
eventInfoClassProviderBinaryTrackInfo eventInfoClass = iota
|
|
eventInfoClassProviderSetReserved1
|
|
eventInfoClassProviderSetTraits
|
|
eventInfoClassProviderUseDescriptorType
|
|
)
|
|
|
|
// EnableCallback is the form of the callback function that receives provider
|
|
// enable/disable notifications from ETW.
|
|
type EnableCallback func(*windows.GUID, ProviderState, Level, uint64, uint64, uintptr)
|
|
|
|
func providerCallback(sourceID *windows.GUID, state ProviderState, level Level, matchAnyKeyword uint64, matchAllKeyword uint64, filterData uintptr, i uintptr) {
|
|
provider := providers.getProvider(uint(i))
|
|
|
|
switch state {
|
|
case ProviderStateDisable:
|
|
provider.enabled = false
|
|
case ProviderStateEnable:
|
|
provider.enabled = true
|
|
provider.level = level
|
|
provider.keywordAny = matchAnyKeyword
|
|
provider.keywordAll = matchAllKeyword
|
|
}
|
|
|
|
if provider.callback != nil {
|
|
provider.callback(sourceID, state, level, matchAnyKeyword, matchAllKeyword, filterData)
|
|
}
|
|
}
|
|
|
|
// providerCallbackAdapter acts as the first-level callback from the C/ETW side
|
|
// for provider notifications. Because Go has trouble with callback arguments of
|
|
// different size, it has only pointer-sized arguments, which are then cast to
|
|
// the appropriate types when calling providerCallback.
|
|
func providerCallbackAdapter(sourceID *windows.GUID, state uintptr, level uintptr, matchAnyKeyword uintptr, matchAllKeyword uintptr, filterData uintptr, i uintptr) uintptr {
|
|
providerCallback(sourceID, ProviderState(state), Level(level), uint64(matchAnyKeyword), uint64(matchAllKeyword), filterData, i)
|
|
return 0
|
|
}
|
|
|
|
// providerIDFromName generates a provider ID based on the provider name. It
|
|
// uses the same algorithm as used by .NET's EventSource class, which is based
|
|
// on RFC 4122. More information on the algorithm can be found here:
|
|
// https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/
|
|
// The algorithm is roughly:
|
|
// Hash = Sha1(namespace + arg.ToUpper().ToUtf16be())
|
|
// Guid = Hash[0..15], with Hash[7] tweaked according to RFC 4122
|
|
func providerIDFromName(name string) *windows.GUID {
|
|
buffer := sha1.New()
|
|
|
|
namespace := []byte{0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8, 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB}
|
|
buffer.Write(namespace)
|
|
|
|
binary.Write(buffer, binary.BigEndian, utf16.Encode([]rune(strings.ToUpper(name))))
|
|
|
|
sum := buffer.Sum(nil)
|
|
sum[7] = (sum[7] & 0xf) | 0x50
|
|
|
|
return &windows.GUID{
|
|
Data1: binary.LittleEndian.Uint32(sum[0:4]),
|
|
Data2: binary.LittleEndian.Uint16(sum[4:6]),
|
|
Data3: binary.LittleEndian.Uint16(sum[6:8]),
|
|
Data4: [8]byte{sum[8], sum[9], sum[10], sum[11], sum[12], sum[13], sum[14], sum[15]},
|
|
}
|
|
}
|
|
|
|
// NewProvider creates and registers a new ETW provider. The provider ID is
|
|
// generated based on the provider name.
|
|
func NewProvider(name string, callback EnableCallback) (provider *Provider, err error) {
|
|
return NewProviderWithID(name, providerIDFromName(name), callback)
|
|
}
|
|
|
|
// NewProviderWithID creates and registers a new ETW provider, allowing the
|
|
// provider ID to be manually specified. This is most useful when there is an
|
|
// existing provider ID that must be used to conform to existing diagnostic
|
|
// infrastructure.
|
|
func NewProviderWithID(name string, id *windows.GUID, callback EnableCallback) (provider *Provider, err error) {
|
|
providerCallbackOnce.Do(func() {
|
|
globalProviderCallback = windows.NewCallback(providerCallbackAdapter)
|
|
})
|
|
|
|
provider = providers.newProvider()
|
|
defer func() {
|
|
if err != nil {
|
|
providers.removeProvider(provider)
|
|
}
|
|
}()
|
|
provider.ID = id
|
|
provider.callback = callback
|
|
|
|
if err := eventRegister(provider.ID, globalProviderCallback, uintptr(provider.index), &provider.handle); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
metadata := &bytes.Buffer{}
|
|
binary.Write(metadata, binary.LittleEndian, uint16(0)) // Write empty size for buffer (to update later)
|
|
metadata.WriteString(name)
|
|
metadata.WriteByte(0) // Null terminator for name
|
|
binary.LittleEndian.PutUint16(metadata.Bytes(), uint16(metadata.Len())) // Update the size at the beginning of the buffer
|
|
provider.metadata = metadata.Bytes()
|
|
|
|
if err := eventSetInformation(
|
|
provider.handle,
|
|
eventInfoClassProviderSetTraits,
|
|
uintptr(unsafe.Pointer(&provider.metadata[0])),
|
|
uint32(len(provider.metadata))); err != nil {
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return provider, nil
|
|
}
|
|
|
|
// Close unregisters the provider.
|
|
func (provider *Provider) Close() error {
|
|
providers.removeProvider(provider)
|
|
return eventUnregister(provider.handle)
|
|
}
|
|
|
|
// IsEnabled calls IsEnabledForLevelAndKeywords with LevelAlways and all
|
|
// keywords set.
|
|
func (provider *Provider) IsEnabled() bool {
|
|
return provider.IsEnabledForLevelAndKeywords(LevelAlways, ^uint64(0))
|
|
}
|
|
|
|
// IsEnabledForLevel calls IsEnabledForLevelAndKeywords with the specified level
|
|
// and all keywords set.
|
|
func (provider *Provider) IsEnabledForLevel(level Level) bool {
|
|
return provider.IsEnabledForLevelAndKeywords(level, ^uint64(0))
|
|
}
|
|
|
|
// IsEnabledForLevelAndKeywords allows event producer code to check if there are
|
|
// any event sessions that are interested in an event, based on the event level
|
|
// and keywords. Although this check happens automatically in the ETW
|
|
// infrastructure, it can be useful to check if an event will actually be
|
|
// consumed before doing expensive work to build the event data.
|
|
func (provider *Provider) IsEnabledForLevelAndKeywords(level Level, keywords uint64) bool {
|
|
if !provider.enabled {
|
|
return false
|
|
}
|
|
|
|
// ETW automatically sets the level to 255 if it is specified as 0, so we
|
|
// don't need to worry about the level=0 (all events) case.
|
|
if level > provider.level {
|
|
return false
|
|
}
|
|
|
|
if keywords != 0 && (keywords&provider.keywordAny == 0 || keywords&provider.keywordAll != provider.keywordAll) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// WriteEvent writes a single ETW event from the provider. The event is
|
|
// constructed based on the EventOpt and FieldOpt values that are passed as
|
|
// opts.
|
|
func (provider *Provider) WriteEvent(name string, eventOpts []EventOpt, fieldOpts []FieldOpt) error {
|
|
options := eventOptions{descriptor: newEventDescriptor()}
|
|
em := &eventMetadata{}
|
|
ed := &eventData{}
|
|
|
|
// We need to evaluate the EventOpts first since they might change tags, and
|
|
// we write out the tags before evaluating FieldOpts.
|
|
for _, opt := range eventOpts {
|
|
opt(&options)
|
|
}
|
|
|
|
if !provider.IsEnabledForLevelAndKeywords(options.descriptor.level, options.descriptor.keyword) {
|
|
return nil
|
|
}
|
|
|
|
em.writeEventHeader(name, options.tags)
|
|
|
|
for _, opt := range fieldOpts {
|
|
opt(em, ed)
|
|
}
|
|
|
|
// Don't pass a data blob if there is no event data. There will always be
|
|
// event metadata (e.g. for the name) so we don't need to do this check for
|
|
// the metadata.
|
|
dataBlobs := [][]byte{}
|
|
if len(ed.bytes()) > 0 {
|
|
dataBlobs = [][]byte{ed.bytes()}
|
|
}
|
|
|
|
return provider.writeEventRaw(options.descriptor, nil, nil, [][]byte{em.bytes()}, dataBlobs)
|
|
}
|
|
|
|
// writeEventRaw writes a single ETW event from the provider. This function is
|
|
// less abstracted than WriteEvent, and presents a fairly direct interface to
|
|
// the event writing functionality. It expects a series of event metadata and
|
|
// event data blobs to be passed in, which must conform to the TraceLogging
|
|
// schema. The functions on EventMetadata and EventData can help with creating
|
|
// these blobs. The blobs of each type are effectively concatenated together by
|
|
// the ETW infrastructure.
|
|
func (provider *Provider) writeEventRaw(
|
|
descriptor *eventDescriptor,
|
|
activityID *windows.GUID,
|
|
relatedActivityID *windows.GUID,
|
|
metadataBlobs [][]byte,
|
|
dataBlobs [][]byte) error {
|
|
|
|
dataDescriptorCount := uint32(1 + len(metadataBlobs) + len(dataBlobs))
|
|
dataDescriptors := make([]eventDataDescriptor, 0, dataDescriptorCount)
|
|
|
|
dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeProviderMetadata, provider.metadata))
|
|
for _, blob := range metadataBlobs {
|
|
dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeEventMetadata, blob))
|
|
}
|
|
for _, blob := range dataBlobs {
|
|
dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeUserData, blob))
|
|
}
|
|
|
|
return eventWriteTransfer(provider.handle, descriptor, activityID, relatedActivityID, dataDescriptorCount, &dataDescriptors[0])
|
|
}
|