Skip to content

Commit

Permalink
fixed some data races and styling. (bi-zone#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmcder02 authored Nov 9, 2023
1 parent d02877b commit fc78d03
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 36 deletions.
14 changes: 10 additions & 4 deletions event.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//+build windows
//go:build windows
// +build windows

package etw

Expand Down Expand Up @@ -88,9 +89,9 @@ type EventDescriptor struct {
// EventProperties returns a map that could be interpreted as "structure that
// fit inside a map". Map keys is a event data field names, map values is field
// values rendered to strings. So map values could be one of the following:
// - `[]string` for arrays of any types;
// - `map[string]interface{}` for fields that are structures;
// - `string` for any other values.
// - `[]string` for arrays of any types;
// - `map[string]interface{}` for fields that are structures;
// - `string` for any other values.
//
// Take a look at `TestParsing` for possible EventProperties values.
func (e *Event) EventProperties() (map[string]interface{}, error) {
Expand Down Expand Up @@ -515,6 +516,11 @@ func stampToTime(quadPart C.LONGLONG) time.Time {
// with a following slicing.
// Ref: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
func createUTF16String(ptr uintptr, len int) string {
// Race detector doesn't like this cast, but it's safe.
// ptr is represented as a kernel address > 0xC0'0000'0000
if !AllowKernelAccess && ptr > 0x7FFFFFFFFF {
return ""
}
if len == 0 {
return ""
}
Expand Down
6 changes: 6 additions & 0 deletions memory_norace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//go:build !race
// +build !race

package etw

const AllowKernelAccess = true
6 changes: 6 additions & 0 deletions memory_race.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//go:build race
// +build race

package etw

const AllowKernelAccess = false
4 changes: 2 additions & 2 deletions session.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ void WINAPI stdcallHandleEvent(PEVENT_RECORD e) {

// OpenTraceHelper helps to access EVENT_TRACE_LOGFILEW union fields and pass
// pointer to C not warning CGO checker.
TRACEHANDLE OpenTraceHelper(LPWSTR name, PVOID ctx) {
TRACEHANDLE OpenTraceHelper(LPWSTR name, UINT64 ctx) {
EVENT_TRACE_LOGFILEW trace = {0};
trace.LoggerName = name;
trace.Context = ctx;
trace.Context = (PVOID)ctx;
trace.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
trace.EventRecordCallback = stdcallHandleEvent;

Expand Down
73 changes: 44 additions & 29 deletions session.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ func (e ExistsError) Error() string {
// Session should be closed via `.Close` call to free obtained OS resources
// even if `.Process` has never been called.
type Session struct {
mu sync.Mutex

name string
config []SessionOptions
callback EventCallback
Expand Down Expand Up @@ -103,24 +105,29 @@ func NewSession(sessionName string) (*Session, error) {
// for more info about events processing.
//
// N.B. Process blocks until `.Close` being called!
func (s *Session) Process(cb EventCallback) error {
s.callback = cb

if s.config == nil {
func (self *Session) Process(cb EventCallback) error {
self.mu.Lock()
self.callback = cb
hSession := self.hSession
config := []SessionOptions{}
copy(config, self.config)
self.mu.Unlock()

if config == nil || len(config) == 0 {
return fmt.Errorf("no providers to subscribe to;")
}

for _, cfg := range s.config {
if err := s.subscribeToProvider(cfg); err != nil {
for _, cfg := range config {
if err := subscribeToProvider(cfg, hSession); err != nil {
return fmt.Errorf("failed to subscribe to provider; %w", err)
}
}

cgoKey := newCallbackKey(s)
cgoKey := newCallbackKey(self)
defer freeCallbackKey(cgoKey)

// Will block here until being closed.
if err := s.processEvents(cgoKey); err != nil {
if err := self.processEvents(cgoKey); err != nil {
return fmt.Errorf("error processing events; %w", err)
}
return nil
Expand All @@ -129,15 +136,19 @@ func (s *Session) Process(cb EventCallback) error {
// UpdateOptions changes subscription parameters in runtime. The only option
// that can't be updated is session name. To change session name -- stop and
// recreate a session with new desired name.
func (s *Session) UpdateOptions(providerGUID windows.GUID, options ...Option) error {
for i, cfg := range s.config {
func (self *Session) UpdateOptions(providerGUID windows.GUID, options ...Option) error {
self.mu.Lock()
defer self.mu.Unlock()
hSession := self.hSession

for i, cfg := range self.config {
if cfg.Guid == providerGUID {
for _, opt := range options {
opt(&cfg)
}
s.config[i] = cfg
self.config[i] = cfg

if err := s.subscribeToProvider(cfg); err != nil {
if err := subscribeToProvider(cfg, hSession); err != nil {
return fmt.Errorf("failed to enable provider; %w", err)
}
return nil
Expand All @@ -149,24 +160,28 @@ func (s *Session) UpdateOptions(providerGUID windows.GUID, options ...Option) er
opt(&cfg)
}
cfg.Guid = providerGUID
s.config = append(s.config, cfg)
self.config = append(self.config, cfg)

if err := s.subscribeToProvider(cfg); err != nil {
if err := subscribeToProvider(cfg, hSession); err != nil {
return err
}
return nil
}

// Close stops trace session and frees associated resources.
func (s *Session) Close() error {
func (self *Session) Close() error {
self.mu.Lock()
defer self.mu.Unlock()
hSession := self.hSession

// "Be sure to disable all providers before stopping the session."
// https://docs.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-an-event-tracing-session
for _, cfg := range s.config {
if err := s.unsubscribeFromProvider(cfg); err != nil {
for _, cfg := range self.config {
if err := unsubscribeFromProvider(cfg, hSession); err != nil {
return fmt.Errorf("failed to disable provider; %w", err)
}
}
if err := s.stopSession(); err != nil {
if err := self.stopSession(); err != nil {
return fmt.Errorf("failed to stop session; %w", err)
}
return nil
Expand Down Expand Up @@ -223,14 +238,14 @@ func KillSession(name string) error {
}

// createETWSession wraps StartTraceW.
func (s *Session) createETWSession() error {
func (self *Session) createETWSession() error {
// We need to allocate a sequential buffer for a structure and a session name
// which will be placed there by an API call (for the future calls).
//
// (Ref: https://docs.microsoft.com/en-us/windows/win32/etw/wnode-header#members)
//
// The only way to do it in go -- unsafe cast of the allocated memory.
sessionNameSize := len(s.etwSessionName) * int(unsafe.Sizeof(s.etwSessionName[0]))
sessionNameSize := len(self.etwSessionName) * int(unsafe.Sizeof(self.etwSessionName[0]))
bufSize := int(unsafe.Sizeof(C.EVENT_TRACE_PROPERTIES{})) + sessionNameSize
propertiesBuf := make([]byte, bufSize)

Expand All @@ -248,23 +263,23 @@ func (s *Session) createETWSession() error {
pProperties.LogFileMode = C.EVENT_TRACE_REAL_TIME_MODE

ret := C.StartTraceW(
&s.hSession,
C.LPWSTR(unsafe.Pointer(&s.etwSessionName[0])),
&self.hSession,
C.LPWSTR(unsafe.Pointer(&self.etwSessionName[0])),
pProperties,
)
switch err := windows.Errno(ret); err {
case windows.ERROR_ALREADY_EXISTS:
return ExistsError{SessionName: s.name}
return ExistsError{SessionName: self.name}
case windows.ERROR_SUCCESS:
s.propertiesBuf = propertiesBuf
self.propertiesBuf = propertiesBuf
return nil
default:
return fmt.Errorf("StartTraceW failed; %w", err)
}
}

// subscribeToProvider wraps EnableTraceEx2 with EVENT_CONTROL_CODE_ENABLE_PROVIDER.
func (s *Session) subscribeToProvider(config SessionOptions) error {
func subscribeToProvider(config SessionOptions, hSession C.ULONGLONG) error {
// https://docs.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-an-event-tracing-session
params := C.ENABLE_TRACE_PARAMETERS{
Version: 2, // ENABLE_TRACE_PARAMETERS_VERSION_2
Expand All @@ -286,7 +301,7 @@ func (s *Session) subscribeToProvider(config SessionOptions) error {
//
// Ref: https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2
ret := C.EnableTraceEx2(
s.hSession,
hSession,
(*C.GUID)(unsafe.Pointer(&config.Guid)),
C.EVENT_CONTROL_CODE_ENABLE_PROVIDER,
C.UCHAR(config.Level),
Expand All @@ -303,7 +318,7 @@ func (s *Session) subscribeToProvider(config SessionOptions) error {
}

// unsubscribeFromProvider wraps EnableTraceEx2 with EVENT_CONTROL_CODE_DISABLE_PROVIDER.
func (s *Session) unsubscribeFromProvider(cfg SessionOptions) error {
func unsubscribeFromProvider(cfg SessionOptions, hSession C.ULONGLONG) error {
// ULONG WMIAPI EnableTraceEx2(
// TRACEHANDLE TraceHandle,
// LPCGUID ProviderId,
Expand All @@ -315,7 +330,7 @@ func (s *Session) unsubscribeFromProvider(cfg SessionOptions) error {
// PENABLE_TRACE_PARAMETERS EnableParameters
// );
ret := C.EnableTraceEx2(
s.hSession,
hSession,
(*C.GUID)(unsafe.Pointer(&cfg.Guid)),
C.EVENT_CONTROL_CODE_DISABLE_PROVIDER,
0,
Expand All @@ -336,7 +351,7 @@ func (s *Session) processEvents(callbackContextKey uintptr) error {
// Ref: https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-opentracew
traceHandle := C.OpenTraceHelper(
(C.LPWSTR)(unsafe.Pointer(&s.etwSessionName[0])),
(C.PVOID)(callbackContextKey),
(C.UINT64)(callbackContextKey),
)
if C.INVALID_PROCESSTRACE_HANDLE == traceHandle {
return fmt.Errorf("OpenTraceW failed; %w", windows.GetLastError())
Expand Down
2 changes: 1 addition & 1 deletion session.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

// OpenTraceHelper helps to access EVENT_TRACE_LOGFILEW union fields and pass
// pointer to C not warning CGO checker.
TRACEHANDLE OpenTraceHelper(LPWSTR name, PVOID ctx);
TRACEHANDLE OpenTraceHelper(LPWSTR name, UINT64 ctx);

// GetArraySize extracts a size of array located at property @i.
ULONG GetArraySize(PEVENT_RECORD event, PTRACE_EVENT_INFO info, int idx, UINT32* count);
Expand Down

0 comments on commit fc78d03

Please sign in to comment.