provider.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. //go:build windows
  2. // +build windows
  3. package etw
  4. import (
  5. "crypto/sha1" //nolint:gosec // not used for secure application
  6. "encoding/binary"
  7. "strings"
  8. "unicode/utf16"
  9. "github.com/Microsoft/go-winio/pkg/guid"
  10. "golang.org/x/sys/windows"
  11. )
  12. // Provider represents an ETW event provider. It is identified by a provider
  13. // name and ID (GUID), which should always have a 1:1 mapping to each other
  14. // (e.g. don't use multiple provider names with the same ID, or vice versa).
  15. type Provider struct {
  16. ID guid.GUID
  17. handle providerHandle
  18. metadata []byte
  19. callback EnableCallback
  20. index uint
  21. enabled bool
  22. level Level
  23. keywordAny uint64
  24. keywordAll uint64
  25. }
  26. // String returns the `provider`.ID as a string.
  27. func (provider *Provider) String() string {
  28. if provider == nil {
  29. return "<nil>"
  30. }
  31. return provider.ID.String()
  32. }
  33. type providerHandle uint64
  34. // ProviderState informs the provider EnableCallback what action is being
  35. // performed.
  36. type ProviderState uint32
  37. const (
  38. // ProviderStateDisable indicates the provider is being disabled.
  39. ProviderStateDisable ProviderState = iota
  40. // ProviderStateEnable indicates the provider is being enabled.
  41. ProviderStateEnable
  42. // ProviderStateCaptureState indicates the provider is having its current
  43. // state snap-shotted.
  44. ProviderStateCaptureState
  45. )
  46. type eventInfoClass uint32
  47. //nolint:deadcode,varcheck // keep unused constants for potential future use
  48. const (
  49. eventInfoClassProviderBinaryTrackInfo eventInfoClass = iota
  50. eventInfoClassProviderSetReserved1
  51. eventInfoClassProviderSetTraits
  52. eventInfoClassProviderUseDescriptorType
  53. )
  54. // EnableCallback is the form of the callback function that receives provider
  55. // enable/disable notifications from ETW.
  56. type EnableCallback func(guid.GUID, ProviderState, Level, uint64, uint64, uintptr)
  57. func providerCallback(
  58. sourceID guid.GUID,
  59. state ProviderState,
  60. level Level,
  61. matchAnyKeyword uint64,
  62. matchAllKeyword uint64,
  63. filterData uintptr,
  64. i uintptr,
  65. ) {
  66. provider := providers.getProvider(uint(i))
  67. switch state {
  68. case ProviderStateCaptureState:
  69. case ProviderStateDisable:
  70. provider.enabled = false
  71. case ProviderStateEnable:
  72. provider.enabled = true
  73. provider.level = level
  74. provider.keywordAny = matchAnyKeyword
  75. provider.keywordAll = matchAllKeyword
  76. }
  77. if provider.callback != nil {
  78. provider.callback(sourceID, state, level, matchAnyKeyword, matchAllKeyword, filterData)
  79. }
  80. }
  81. // providerIDFromName generates a provider ID based on the provider name. It
  82. // uses the same algorithm as used by .NET's EventSource class, which is based
  83. // on RFC 4122. More information on the algorithm can be found here:
  84. // https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/
  85. //
  86. // The algorithm is roughly the RFC 4122 algorithm for a V5 UUID, but differs in
  87. // the following ways:
  88. // - The input name is first upper-cased, UTF16-encoded, and converted to
  89. // big-endian.
  90. // - No variant is set on the result UUID.
  91. // - The result UUID is treated as being in little-endian format, rather than
  92. // big-endian.
  93. func providerIDFromName(name string) guid.GUID {
  94. buffer := sha1.New() //nolint:gosec // not used for secure application
  95. namespace := guid.GUID{
  96. Data1: 0x482C2DB2,
  97. Data2: 0xC390,
  98. Data3: 0x47C8,
  99. Data4: [8]byte{0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB},
  100. }
  101. namespaceBytes := namespace.ToArray()
  102. buffer.Write(namespaceBytes[:])
  103. _ = binary.Write(buffer, binary.BigEndian, utf16.Encode([]rune(strings.ToUpper(name))))
  104. sum := buffer.Sum(nil)
  105. sum[7] = (sum[7] & 0xf) | 0x50
  106. a := [16]byte{}
  107. copy(a[:], sum)
  108. return guid.FromWindowsArray(a)
  109. }
  110. type providerOpts struct {
  111. callback EnableCallback
  112. id guid.GUID
  113. group guid.GUID
  114. }
  115. // ProviderOpt allows the caller to specify provider options to
  116. // NewProviderWithOptions.
  117. type ProviderOpt func(*providerOpts)
  118. // WithCallback is used to provide a callback option to NewProviderWithOptions.
  119. func WithCallback(callback EnableCallback) ProviderOpt {
  120. return func(opts *providerOpts) {
  121. opts.callback = callback
  122. }
  123. }
  124. // WithID is used to provide a provider ID option to NewProviderWithOptions.
  125. func WithID(id guid.GUID) ProviderOpt {
  126. return func(opts *providerOpts) {
  127. opts.id = id
  128. }
  129. }
  130. // WithGroup is used to provide a provider group option to NewProviderWithOptions.
  131. func WithGroup(group guid.GUID) ProviderOpt {
  132. return func(opts *providerOpts) {
  133. opts.group = group
  134. }
  135. }
  136. // NewProviderWithID creates and registers a new ETW provider, allowing the
  137. // provider ID to be manually specified. This is most useful when there is an
  138. // existing provider ID that must be used to conform to existing diagnostic
  139. // infrastructure.
  140. func NewProviderWithID(name string, id guid.GUID, callback EnableCallback) (provider *Provider, err error) {
  141. return NewProviderWithOptions(name, WithID(id), WithCallback(callback))
  142. }
  143. // NewProvider creates and registers a new ETW provider. The provider ID is
  144. // generated based on the provider name.
  145. func NewProvider(name string, callback EnableCallback) (provider *Provider, err error) {
  146. return NewProviderWithOptions(name, WithCallback(callback))
  147. }
  148. // Close unregisters the provider.
  149. func (provider *Provider) Close() error {
  150. if provider == nil {
  151. return nil
  152. }
  153. providers.removeProvider(provider)
  154. return eventUnregister(provider.handle)
  155. }
  156. // IsEnabled calls IsEnabledForLevelAndKeywords with LevelAlways and all
  157. // keywords set.
  158. func (provider *Provider) IsEnabled() bool {
  159. return provider.IsEnabledForLevelAndKeywords(LevelAlways, ^uint64(0))
  160. }
  161. // IsEnabledForLevel calls IsEnabledForLevelAndKeywords with the specified level
  162. // and all keywords set.
  163. func (provider *Provider) IsEnabledForLevel(level Level) bool {
  164. return provider.IsEnabledForLevelAndKeywords(level, ^uint64(0))
  165. }
  166. // IsEnabledForLevelAndKeywords allows event producer code to check if there are
  167. // any event sessions that are interested in an event, based on the event level
  168. // and keywords. Although this check happens automatically in the ETW
  169. // infrastructure, it can be useful to check if an event will actually be
  170. // consumed before doing expensive work to build the event data.
  171. func (provider *Provider) IsEnabledForLevelAndKeywords(level Level, keywords uint64) bool {
  172. if provider == nil {
  173. return false
  174. }
  175. if !provider.enabled {
  176. return false
  177. }
  178. // ETW automatically sets the level to 255 if it is specified as 0, so we
  179. // don't need to worry about the level=0 (all events) case.
  180. if level > provider.level {
  181. return false
  182. }
  183. if keywords != 0 && (keywords&provider.keywordAny == 0 || keywords&provider.keywordAll != provider.keywordAll) {
  184. return false
  185. }
  186. return true
  187. }
  188. // WriteEvent writes a single ETW event from the provider. The event is
  189. // constructed based on the EventOpt and FieldOpt values that are passed as
  190. // opts.
  191. func (provider *Provider) WriteEvent(name string, eventOpts []EventOpt, fieldOpts []FieldOpt) error {
  192. if provider == nil {
  193. return nil
  194. }
  195. options := eventOptions{descriptor: newEventDescriptor()}
  196. em := &eventMetadata{}
  197. ed := &eventData{}
  198. // We need to evaluate the EventOpts first since they might change tags, and
  199. // we write out the tags before evaluating FieldOpts.
  200. for _, opt := range eventOpts {
  201. opt(&options)
  202. }
  203. if !provider.IsEnabledForLevelAndKeywords(options.descriptor.level, options.descriptor.keyword) {
  204. return nil
  205. }
  206. em.writeEventHeader(name, options.tags)
  207. for _, opt := range fieldOpts {
  208. opt(em, ed)
  209. }
  210. // Don't pass a data blob if there is no event data. There will always be
  211. // event metadata (e.g. for the name) so we don't need to do this check for
  212. // the metadata.
  213. dataBlobs := [][]byte{}
  214. if len(ed.toBytes()) > 0 {
  215. dataBlobs = [][]byte{ed.toBytes()}
  216. }
  217. return provider.writeEventRaw(
  218. options.descriptor,
  219. options.activityID,
  220. options.relatedActivityID,
  221. [][]byte{em.toBytes()},
  222. dataBlobs,
  223. )
  224. }
  225. // writeEventRaw writes a single ETW event from the provider. This function is
  226. // less abstracted than WriteEvent, and presents a fairly direct interface to
  227. // the event writing functionality. It expects a series of event metadata and
  228. // event data blobs to be passed in, which must conform to the TraceLogging
  229. // schema. The functions on EventMetadata and EventData can help with creating
  230. // these blobs. The blobs of each type are effectively concatenated together by
  231. // the ETW infrastructure.
  232. func (provider *Provider) writeEventRaw(
  233. descriptor *eventDescriptor,
  234. activityID guid.GUID,
  235. relatedActivityID guid.GUID,
  236. metadataBlobs [][]byte,
  237. dataBlobs [][]byte) error {
  238. dataDescriptorCount := uint32(1 + len(metadataBlobs) + len(dataBlobs))
  239. dataDescriptors := make([]eventDataDescriptor, 0, dataDescriptorCount)
  240. dataDescriptors = append(dataDescriptors,
  241. newEventDataDescriptor(eventDataDescriptorTypeProviderMetadata, provider.metadata))
  242. for _, blob := range metadataBlobs {
  243. dataDescriptors = append(dataDescriptors,
  244. newEventDataDescriptor(eventDataDescriptorTypeEventMetadata, blob))
  245. }
  246. for _, blob := range dataBlobs {
  247. dataDescriptors = append(dataDescriptors,
  248. newEventDataDescriptor(eventDataDescriptorTypeUserData, blob))
  249. }
  250. return eventWriteTransfer(provider.handle,
  251. descriptor,
  252. (*windows.GUID)(&activityID),
  253. (*windows.GUID)(&relatedActivityID),
  254. dataDescriptorCount,
  255. &dataDescriptors[0])
  256. }