Чтение поставщиков ETW с помощью go

Я пытаюсь получить доступ к функции EnumerateTraceGuids из Advapi32.dll на ходу. Я на очень ранней стадии и все еще пытаюсь понять, что я должен делать. У меня есть следующий код, который постоянно выдает ошибку: 87, что означает ERROR_INVALID_PARAMETER.

Я использовал этот файл в качестве отправной точки, хотя он только пишет, а не читает: https://github.com/moby/moby/blob/master/daemon/logger/etwlogs/etwlogs_windows.go

Официальная документация для функции, которую я пытаюсь вызвать, находится здесь: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363713(v=vs.85).aspx

Требуется GuidPropertiesArray [in, out] Массив указателей на структуры TRACE_GUID_PROPERTIES. Эта структура выглядит следующим образом (https://msdn.microsoft.com/en-us/library/windows/desktop/aa364143(v=vs.85).aspx)

typedef struct _TRACE_GUID_PROPERTIES {
  GUID    Guid;
  ULONG   GuidType;
  ULONG   LoggerId;
  ULONG   EnableLevel;
  ULONG   EnableFlags;
  BOOLEAN IsEnable;
} TRACE_GUID_PROPERTIES, *PTRACE_GUID_PROPERTIES;

У меня есть следующий код, чтобы попытаться сделать это:

package main
import (
    "errors"
    "fmt"
    "syscall"
    "unsafe"
    "github.com/sirupsen/logrus"
    "golang.org/x/sys/windows"
)

const (
    win32CallSuccess = 0
    MaxProv = 50
    nbProviders = 50
)

var (
    modAdvapi32          = windows.NewLazySystemDLL("Advapi32.dll")
    procEnumerateTraceGuids = modAdvapi32.NewProc("EnumerateTraceGuids")
)

type ulong int32

type TRACE_GUID_PROPERTIES struct {
    Guid syscall.GUID
    GuidType ulong
    LoggerId ulong
    EnableLevel ulong
    EnableFlags ulong
    IsEnable bool
}

func callEnumerateTraceGuids() error {
    GuidPropertiesArray:= make([]TRACE_GUID_PROPERTIES, 1)
    ptr := &GuidPropertiesArray[0]
    ret, _, _ := procEnumerateTraceGuids.Call(uintptr(unsafe.Pointer(&ptr)), MaxProv, nbProviders)
    if ret != win32CallSuccess {
        errorMessage := fmt.Sprintf("Failed to register ETW provider. Error: %d", ret)
        logrus.Error(errorMessage)
        return errors.New(errorMessage)
    }
    return nil
}

func main() {
    callEnumerateTraceGuids()
}

На данный момент я не уверен, что я должен делать. Я пробовал много вариантов инициализации массива без успеха. Надеясь, что кто-то может указать мне в правильном направлении. Спасибо !

Изменить: измененный код на основе комментариев, но по-прежнему возникает та же ошибка.

PS: Это моя первая публикация в stackoverflow, и мне уже сказали, что я ленив менее чем через 12 часов после публикации моего вопроса (ура!), Так что не уверен, что правильно спрашиваю... Я не слишком знаком с go и никогда раньше не вызывал Windows DLL из go, и, поскольку я продолжаю сталкиваться с этой ERROR_INVALID_PARAMETER, я подумал о том, чтобы попытаться пройти эту первую стену, чтобы одновременно понять некоторые концепции. Надеюсь, это поможет понять мою просьбу (т.е. я пришел с миром).


person EmFl    schedule 01.05.2018    source источник
comment
Обратите внимание, что эта функция API принимает указатель на указатель, то есть принимает адрес области памяти, содержащей адрес массива. Вместо этого вы передаете адрес массива.   -  person kostix    schedule 02.05.2018
comment
Я пробовал это: GuidPropertiesArray:= make([]*TRACE_GUID_PROPERTIES, 50) ptr:= &GuidPropertiesArray[0] ret, _, _ := procEnumerateTraceGuids.Call(uintptr(unsafe.Pointer(&ptr)), MaxProv, nbProviders)   -  person EmFl    schedule 02.05.2018
comment
Итак, вы попробовали это, и?… Тем не менее, я предполагаю, что вы 1) должны выделить массив структур (не указателей); 2) взять адрес его 1-го элемента и поместить его в переменную ; 3) взять его адрес и передать этой функции.   -  person kostix    schedule 02.05.2018
comment
В общем, такие проблемы не решаются. Вы ищете, например, использование функций API win32 на C или C++, а затем переводите их на Go (конечно, если вам лень просто внимательно читать страницу документации по API).   -  person kostix    schedule 02.05.2018
comment
Кстати, пример находится прямо в документации по функциям. Ключ к переводу этого C в Go заключается в том, что malloc и realloc C возвращают (нетипизированный) указатель, даже если вы обычно выделяете память для массива значений, тип которых не является указателем.   -  person kostix    schedule 02.05.2018


Ответы (1)


Хорошо, у меня было немного свободного времени и доступ к Windows XP, поэтому я решил стряхнуть пыль со своих навыков программирования для Windows и написал работающее решение:

package main

import (
    "golang.org/x/sys/windows"

    "log"
    "syscall"
    "unsafe"
)

var (
    modAdvapi32             = windows.NewLazySystemDLL("advapi32")
    procEnumerateTraceGuids = modAdvapi32.NewProc("EnumerateTraceGuids")
)

type traceGuidProperties struct {
    guid        syscall.GUID
    guidType    uint32
    loggerId    uint32
    enableLevel uint32
    enableFlags uint32
    isEnable    uint32
}

func enumerateTraceGuids(ptr **traceGuidProperties, count uint32, out *uint32) error {
    rc, _, _ := procEnumerateTraceGuids.Call(uintptr(unsafe.Pointer(ptr)),
        uintptr(count), uintptr(unsafe.Pointer(out)))
    if rc != 0 {
        return syscall.Errno(rc)
    }
    return nil
}

func enumTraceGuids() ([]*traceGuidProperties, error) {
    var errMoreData = syscall.Errno(234)

    var (
        dummyProps traceGuidProperties
        dummyPtr   = &dummyProps

        count uint32
    )

    err := enumerateTraceGuids(&dummyPtr, 0, &count)
    if err != errMoreData {
        return nil, err
    }

    items := make([]*traceGuidProperties, count)
    for i := range items {
        items[i] = new(traceGuidProperties)
    }

    for {
        err = enumerateTraceGuids(&items[0], count, &count)
        if err == nil {
            break
        }
        if err != errMoreData {
            return nil, err
        }
        for i := 0; i < int(count)-len(items); i++ {
            items = append(items, new(traceGuidProperties))
        }
    }

    return items[:count], nil
}

func main() {
    log.SetFlags(0)

    data, err := enumTraceGuids()
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("len(data)=%d\n", len(data))
    for i := range data {
        log.Println(*(data[i]))
    }
}

Ключевые моменты:

  • Я был неправ, когда сказал вам, что «вы… должны выделить массив структур (не указателей)» — на самом деле EnumerateTraceGuids действительно ожидает массив указателей.

  • Как указано здесь, в работе EnumerateTraceGuids есть две тонкости:

    • Contrary to what its documentation states, it actually supports being called with its PropertyArrayCount parameter set to 0, in which case it's expected to return ERROR_MORE_DATA while having set GuidCount to the number of elements of the input array required for the (next) call to complete successfully. IOW, that way we know how many trace GUIDs the system currently "knows about".
    • Тем не менее, даже в этом случае функция выполняет проверку правильности входного массива (см. ниже).
  • Оказывается, функция ожидает массив указателей на TRACE_GUID_PROPERTIES блоков, выделенных вами.

    Другими словами, если он говорит, что знает около 10 трассировочных идентификаторов GUID, вам нужно выделить 10 значений типа TRACE_GUID_PROPERTIES, затем создать массив из 10 указателей на эти значения и передать указатель на 1-й элемент этого массива в функцию.

  • Обратите внимание, что между изменениями, происходящими в системе (эти трассировки добавляются или удаляются по ряду причин), и вызовами EnumerateTraceGuids существует неотъемлемая гонка.

    Это означает, что если первый вызов этой функции сообщил вам, что она «знает» около 10 GUID трассировки, при следующем вызове может оказаться, что уже имеется 20 GUID трассировки или 5 GUID (или любое другое их количество FWIW).

    Таким образом, мы учитываем обе эти возможности следующим образом:

    1. Сначала мы делаем вызов с указателем на одно (но допустимое) значение TRACE_GUID_PROPERTIES, выделенное статически (поэтому функция «видит» то, что выглядит как массив из одного элемента), при этом сообщая функции, что входной «массив» имеет нулевые элементы .

      Мы ожидаем, что функция потерпит неудачу с ERROR_MORE_DATA, и поместим фактическое количество трассировочных GUID, о которых она «знает», в переменную, на которую мы предоставили ей указатель.

    2. Мы выделяем столько TRACE_GUID_PROPERTIES памяти, сколько блокирует функция, указанная при первом вызове. Для этого мы используем встроенную функцию new(), которая ведет себя примерно так же, как malloc() в стандартной библиотеке C — она выделяет память для значения указанного типа и возвращает указатель на выделенный блок памяти.

    3. Мы создаем массив указателей на эти выделенные блоки памяти и снова вызываем EnumerateTraceGuids.

    4. Если это удается, мы обрабатываем возможность того, что он вернул меньше элементов, чем мы выделили, и повторно нарезаем наш срез.

    5. Если это не удается с ERROR_MORE_DATA, мы расширяем наш слайс любым необходимым количеством элементов (сначала выделяя память для их TRACE_GUID_PROPERTIES блоков) и пытаемся снова вызвать функцию.

  • «Магическое число» 234 — это фактический код значения ERROR_MORE_DATA.

Извините за первоначальную путаницу.

person kostix    schedule 02.05.2018
comment
Спасибо большое за это. У меня еще не было времени сравнивать, но я очень быстро проверил ваше решение, и, конечно же, оно сработало :). Огромная благодарность за потраченное время. - person EmFl; 04.05.2018
comment
Кстати, проверка это намекает на пару вещей: 1) нет нужно использовать golang.org/x/sys/windows — на складе syscall уже должно быть то же самое; 2) этот пакет, кажется, уже экспортирует значение ERROR_MODE_DATA, поэтому нет необходимости делать это явно, как я. Я бы попробовал эти предложения и посмотрел, сработают ли они. - person kostix; 04.05.2018