Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions internal/windows/api/pdh.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,15 +476,20 @@ func PdhGetRawCounterArray(hCounter PDH_HCOUNTER, lpdwBufferSize *uint32, lpdwBu
return uint32(ret)
}

// UTF16PtrToString converts a UTF16 pointer to a Go string
func UTF16PtrToString(ptr *uint16) string {
if ptr == nil {
// UTF16PtrToString converts a UTF16 pointer to a Go string, reading at most maxLen uint16s.
// It stops at the first null terminator or when maxLen is reached.
func UTF16PtrToString(ptr *uint16, maxLen uint32) string {
if ptr == nil || maxLen == 0 {
return ""
}

// Find the length of the null-terminated wide string
// Find the length of the null-terminated wide string, up to maxLen
length := 0
for p := ptr; *p != 0; {
p := ptr
for i := uint32(0); i < maxLen; i++ {
if *p == 0 {
break
}
length++
p = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + 2))
}
Expand Down
48 changes: 37 additions & 11 deletions internal/windows/api/pdh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ func TestUTF16PtrToString(t *testing.T) {
utf16Ptr, err := syscall.UTF16PtrFromString(tt.input)
require.NoError(t, err, "Failed to create UTF16 pointer from string")

// Test the function
result := UTF16PtrToString(utf16Ptr)
// Test the function with a large enough maxLen
result := UTF16PtrToString(utf16Ptr, 1024)

// Verify the result
assert.Equal(t, tt.expected, result)
Expand All @@ -85,7 +85,7 @@ func TestUTF16PtrToString_NilPointer(t *testing.T) {
t.Parallel()

// Test with nil pointer
result := UTF16PtrToString(nil)
result := UTF16PtrToString(nil, 100)
assert.Equal(t, "", result, "Should return empty string for nil pointer")
}

Expand All @@ -96,7 +96,7 @@ func TestUTF16PtrToString_ZeroLengthString(t *testing.T) {
nullTerminator := uint16(0)
ptr := &nullTerminator

result := UTF16PtrToString(ptr)
result := UTF16PtrToString(ptr, 100)
assert.Equal(t, "", result, "Should return empty string for zero-length UTF16 string")
}

Expand All @@ -115,10 +115,36 @@ func TestUTF16PtrToString_ManualUTF16Array(t *testing.T) {
// Get pointer to first element
ptr := &utf16Array[0]

result := UTF16PtrToString(ptr)
result := UTF16PtrToString(ptr, 100)
assert.Equal(t, "test", result, "Should correctly parse manually created UTF16 array")
}

func TestUTF16PtrToString_BoundsCheck(t *testing.T) {
t.Parallel()

// Manually create a UTF16 array: "test" WITHOUT null terminator (simulating buffer end)
// We'll put some garbage after it to ensure we don't read it
utf16Array := []uint16{
0x0074, // 't'
0x0065, // 'e'
0x0073, // 's'
0x0074, // 't'
0xFFFF, // Garbage
0xFFFF, // Garbage
}

// Get pointer to first element
ptr := &utf16Array[0]

// Tell it to read only 4 chars
result := UTF16PtrToString(ptr, 4)
assert.Equal(t, "test", result, "Should stop reading at maxLen even without null terminator")

// Tell it to read only 2 chars
result = UTF16PtrToString(ptr, 2)
assert.Equal(t, "te", result, "Should stop reading at maxLen")
}

func TestUTF16PtrToString_EdgeCases(t *testing.T) {
t.Parallel()

Expand All @@ -128,7 +154,7 @@ func TestUTF16PtrToString_EdgeCases(t *testing.T) {
utf16Ptr, err := syscall.UTF16PtrFromString("A")
require.NoError(t, err)

result := UTF16PtrToString(utf16Ptr)
result := UTF16PtrToString(utf16Ptr, 100)
assert.Equal(t, "A", result)
})

Expand All @@ -139,7 +165,7 @@ func TestUTF16PtrToString_EdgeCases(t *testing.T) {
utf16Ptr, err := syscall.UTF16PtrFromString(input)
require.NoError(t, err)

result := UTF16PtrToString(utf16Ptr)
result := UTF16PtrToString(utf16Ptr, 100)
assert.Equal(t, input, result)
})

Expand All @@ -150,7 +176,7 @@ func TestUTF16PtrToString_EdgeCases(t *testing.T) {
utf16Ptr, err := syscall.UTF16PtrFromString(input)
require.NoError(t, err)

result := UTF16PtrToString(utf16Ptr)
result := UTF16PtrToString(utf16Ptr, 100)
assert.Equal(t, input, result)
})
}
Expand All @@ -165,7 +191,7 @@ func BenchmarkUTF16PtrToString(b *testing.B) {
b.ResetTimer()

for range b.N {
_ = UTF16PtrToString(utf16Ptr)
_ = UTF16PtrToString(utf16Ptr, 1024)
}
}

Expand All @@ -180,7 +206,7 @@ func BenchmarkUTF16PtrToString_LongString(b *testing.B) {
b.ResetTimer()

for range b.N {
_ = UTF16PtrToString(utf16Ptr)
_ = UTF16PtrToString(utf16Ptr, 1024)
}
}

Expand Down Expand Up @@ -211,7 +237,7 @@ func TestUTF16PtrToString_CompareWithSyscall(t *testing.T) {
ptr := &utf16Slice[0]

// Test our function
ourResult := UTF16PtrToString(ptr)
ourResult := UTF16PtrToString(ptr, 1024)

// Compare with syscall's implementation
syscallResult := syscall.UTF16ToString(utf16Slice[:len(utf16Slice)-1]) // Remove null terminator for syscall
Expand Down
17 changes: 16 additions & 1 deletion internal/windows/pdh_raw_poll.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,22 @@ func (pdh *PdhRawPoll) PollRawArray() (map[string][]CPUGroupInfo, error) {
item := (*winapi.PDH_RAW_COUNTER_ITEM)(unsafe.Pointer(uintptr(unsafe.Pointer(itemBuffer)) + offset))

if item.SzName != nil {
name := winapi.UTF16PtrToString(item.SzName)
var name string

// Calculate remaining buffer size from the pointer to the end of the buffer
bufferStart := uintptr(unsafe.Pointer(&buffer[0]))
stringStart := uintptr(unsafe.Pointer(item.SzName))

// Calculate offset of string within buffer
if stringStart >= bufferStart && stringStart < bufferStart+uintptr(bufferSize) {
stringOffset := stringStart - bufferStart
remainingBytes := uintptr(bufferSize) - stringOffset
// Convert bytes to uint16 count (divide by 2)
maxLen := uint32(remainingBytes / 2)

name = winapi.UTF16PtrToString(item.SzName, maxLen)
}

// Use constant instead of magic number 32
timestamp := uint64(item.RawValue.TimeStamp.HighDateTime)<<timestampShiftBits | uint64(item.RawValue.TimeStamp.LowDateTime)

Expand Down