ETWDUMP: Update to support live capture

etwdump will read events from a live session with the params as the filter, also the params can be used as filter when reading from a etl file.
This commit is contained in:
Odysseus Yang 2021-01-11 10:45:24 -08:00 committed by Guy Harris
parent 40a988533f
commit c4b32c8f46
6 changed files with 422 additions and 50 deletions

View File

@ -21,6 +21,7 @@ S<[ B<--capture> ]>
S<[ B<--fifo>=E<lt>path to file or pipeE<gt> ]>
S<[ B<--iue>=E<lt>Should undecidable events be includedE<gt> ]>
S<[ B<--etlfile>=E<lt>etl fileE<gt> ]>
S<[ B<--params>=E<lt>filter parametersE<gt> ]>
=head1 DESCRIPTION
@ -71,6 +72,10 @@ Choose if the undecidable event is included.
Select etl file to display in Wireshark.
=item --params=E<lt>filter parametersE<gt>
Input providers, keyword and level filters for the etl file and live session.
=back
=head1 EXAMPLES
@ -102,12 +107,13 @@ To see interface configuration options:
etwdump --extcap-interface=etwdump --extcap-config
Example output:
arg {number=0}{call=--iue}{display=Should undecidable events be included}{type=boolflag}{default=false}{tooltip=Choose if the undecidable event is included}{group=Capture}
arg {number=1}{call=--etlfile}{display=etl file}{type=fileselect}{tooltip=Select etl file to display in Wireshark}{required=true}{group=Capture}
arg {number=0}{call=--etlfile}{display=etl file}{type=fileselect}{tooltip=Select etl file to display in Wireshark}{group=Capture}
arg {number=1}{call=--params}{display=filter parmeters}{type=string}{tooltip=Input providers, keyword and level filters for the etl file and live session}{group=Capture}
arg {number=2}{call=--iue}{display=Should undecidable events be included}{type=boolflag}{default=false}{tooltip=Choose if the undecidable event is included}{group=Capture}
To capture:
etwdump --extcap-interface=etwdump --fifo=/tmp/etw.pcapng --capture
etwdump --extcap-interface etwdump --fifo=/tmp/etw.pcapng --capture --params "--p=Microsoft-Windows-Wmbclass-Opn --p=Microsoft-Windows-wmbclass --k=0xff --l=4"
NOTE: To stop capturing CTRL+C/kill/terminate application.

View File

@ -255,6 +255,8 @@ if(BUILD_etwdump AND WIN32)
wiretap
wsutil
tdh
wevtapi
rpcrt4
${GLIB2_LIBRARIES}
${ZLIB_LIBRARIES}
${CMAKE_DL_LIBS}

View File

@ -17,9 +17,13 @@
#include "config.h"
#include "etl.h"
#include "wsutil/wsgetopt.h"
#include "wsutil/strtoi.h"
#include "etw_message.h"
#include <rpc.h>
#include <winevt.h>
#define MAX_PACKET_SIZE 0xFFFF
#define G_NSEC_PER_SEC 1000000000
#define ADD_OFFSET_TO_POINTER(buffer, offset) (((PBYTE)buffer) + offset)
@ -34,6 +38,7 @@ EXTERN_C const GUID DECLSPEC_SELECTANY EventTraceGuid = { 0x68fdd900, 0x4a3e, 0x
EXTERN_C const GUID DECLSPEC_SELECTANY ImageIdGuid = { 0xb3e675d7, 0x2554, 0x4f18, { 0x83, 0xb, 0x27, 0x62, 0x73, 0x25, 0x60, 0xde } };
EXTERN_C const GUID DECLSPEC_SELECTANY SystemConfigExGuid = { 0x9b79ee91, 0xb5fd, 0x41c0, { 0xa2, 0x43, 0x42, 0x48, 0xe2, 0x66, 0xe9, 0xd0 } };
EXTERN_C const GUID DECLSPEC_SELECTANY EventMetadataGuid = { 0xbbccf6c1, 0x6cd1, 0x48c4, {0x80, 0xff, 0x83, 0x94, 0x82, 0xe3, 0x76, 0x71 } };
EXTERN_C const GUID DECLSPEC_SELECTANY ZeroGuid = { 0 };
typedef struct _WTAP_ETL_RECORD {
EVENT_HEADER EventHeader; // Event header
@ -43,10 +48,31 @@ typedef struct _WTAP_ETL_RECORD {
ULONG ProviderLength;
} WTAP_ETL_RECORD;
enum {
OPT_PROVIDER,
OPT_KEYWORD,
OPT_LEVEL,
};
static struct option longopts[] = {
{ "p", required_argument, NULL, OPT_PROVIDER},
{ "k", required_argument, NULL, OPT_KEYWORD},
{ "l", required_argument, NULL, OPT_LEVEL},
{ 0, 0, 0, 0 }
};
typedef struct _PROVIDER_FILTER {
GUID ProviderId;
ULONG64 Keyword;
UCHAR Level;
} PROVIDER_FILTER;
static gchar g_err_info[FILENAME_MAX] = { 0 };
static int g_err = ERROR_SUCCESS;
static wtap_dumper* g_pdh = NULL;
extern ULONGLONG g_num_events;
static PROVIDER_FILTER g_provider_filters[32] = { 0 };
static BOOL g_is_live_session = FALSE;
static void WINAPI event_callback(PEVENT_RECORD ev);
void etw_dump_write_opn_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp);
@ -55,33 +81,263 @@ void etw_dump_write_event_head_only(PEVENT_RECORD ev, ULARGE_INTEGER timestamp);
void wtap_etl_rec_dump(ULARGE_INTEGER timestamp, WTAP_ETL_RECORD* etl_record, ULONG total_packet_length, BOOLEAN is_inbound);
wtap_dumper* etw_dump_open(const char* pcapng_filename, int* err, gchar** err_info);
wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filename, int* err, gchar** err_info)
DWORD GetPropertyValue(WCHAR* ProviderId, EVT_PUBLISHER_METADATA_PROPERTY_ID PropertyId, PEVT_VARIANT* Value)
{
EVENT_TRACE_LOGFILE log_file;
TRACEHANDLE trace_handle = INVALID_PROCESSTRACE_HANDLE;
BOOL bRet;
DWORD err = ERROR_SUCCESS;
PEVT_VARIANT value = NULL;
DWORD bufSize = 0;
DWORD bufUsedOrReqd = 0;
EVT_HANDLE pubHandle = EvtOpenPublisherMetadata(NULL, ProviderId, NULL, GetThreadLocale(), 0);
if (pubHandle == NULL)
{
return GetLastError();
}
/*
* Get required size for property
*/
bRet = EvtGetPublisherMetadataProperty(
pubHandle,
PropertyId,
0,
bufSize,
value,
&bufUsedOrReqd);
if (!bRet && ((err = GetLastError()) != ERROR_INSUFFICIENT_BUFFER))
{
return err;
}
else if (bRet) /* Didn't expect this to succeed */
{
return ERROR_INVALID_STATE;
}
value = (PEVT_VARIANT)g_malloc(bufUsedOrReqd);
if (!value)
{
return ERROR_INSUFFICIENT_BUFFER;
}
bufSize = bufUsedOrReqd;
/*
* Get the property value
*/
bRet = EvtGetPublisherMetadataProperty(
pubHandle,
PropertyId,
0,
bufSize,
value,
&bufUsedOrReqd);
if (!bRet)
{
g_free(value);
return GetLastError();
}
*Value = value;
return ERROR_SUCCESS;
}
wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filename, const char* params, int* err, gchar** err_info)
{
EVENT_TRACE_LOGFILE log_file = { 0 };
WCHAR w_etl_filename[FILENAME_MAX] = { 0 };
WCHAR w_pcapng_filename[FILENAME_MAX] = { 0 };
ULONG trace_error;
wtap_open_return_val returnVal = WTAP_OPEN_MINE;
SUPER_EVENT_TRACE_PROPERTIES super_trace_properties = { 0 };
super_trace_properties.prop.Wnode.BufferSize = sizeof(SUPER_EVENT_TRACE_PROPERTIES);
super_trace_properties.prop.Wnode.ClientContext = 2;
super_trace_properties.prop.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
super_trace_properties.prop.LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
super_trace_properties.prop.LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
TRACEHANDLE traceControllerHandle = (TRACEHANDLE)INVALID_HANDLE_VALUE;
TRACEHANDLE trace_handle = INVALID_PROCESSTRACE_HANDLE;
SecureZeroMemory(g_provider_filters, sizeof(g_provider_filters));
SecureZeroMemory(g_err_info, FILENAME_MAX);
g_err = ERROR_SUCCESS;
g_num_events = 0;
g_is_live_session = FALSE;
if (params)
{
int opt_result = 0;
int option_idx = 0;
int provider_idx = 0;
char** params_array = NULL;
int params_array_num = 0;
WCHAR provider_id[FILENAME_MAX] = { 0 };
ULONG convert_level = 0;
params_array = g_strsplit(params, " ", -1);
while (params_array[params_array_num])
{
params_array_num++;
}
optind = 0;
while ((opt_result = getopt_long(params_array_num, params_array, ":", longopts, &option_idx)) != -1) {
switch (opt_result) {
case OPT_PROVIDER:
mbstowcs(provider_id, optarg, FILENAME_MAX);
if (UuidFromString(provider_id, &g_provider_filters[provider_idx].ProviderId) == RPC_S_INVALID_STRING_UUID)
{
PEVT_VARIANT value = NULL;
*err = GetPropertyValue(
provider_id,
EvtPublisherMetadataPublisherGuid,
&value);
/*
* Copy returned GUID locally
*/
if (*err == ERROR_SUCCESS)
{
if (value->Type == EvtVarTypeGuid && value->GuidVal)
{
g_provider_filters[provider_idx].ProviderId = *(value->GuidVal);
}
else
{
*err = ERROR_INVALID_DATA;
}
}
else
{
*err_info = g_strdup_printf("Cannot convert provider %s to a GUID, err is 0x%x", optarg, *err);
return WTAP_OPEN_ERROR;
}
g_free(value);
}
if (IsEqualGUID(&g_provider_filters[0].ProviderId, &ZeroGuid))
{
*err = ERROR_INVALID_PARAMETER;
*err_info = g_strdup_printf("Provider %s is zero, err is 0x%x", optarg, *err);
return WTAP_OPEN_ERROR;
}
provider_idx++;
break;
case OPT_KEYWORD:
if (provider_idx == 0)
{
*err = ERROR_INVALID_PARAMETER;
*err_info = g_strdup_printf("-k parameter must follow -p, err is 0x%x", *err);
return WTAP_OPEN_ERROR;
}
g_provider_filters[provider_idx - 1].Keyword = _strtoui64(optarg, NULL, 0);
if (!g_provider_filters[provider_idx - 1].Keyword)
{
*err = ERROR_INVALID_PARAMETER;
*err_info = g_strdup_printf("Keyword %s cannot be converted, err is 0x%x", optarg, *err);
return WTAP_OPEN_ERROR;
}
break;
case OPT_LEVEL:
if (provider_idx == 0)
{
*err = ERROR_INVALID_PARAMETER;
*err_info = g_strdup_printf("-l parameter must follow -p, err is 0x%x", *err);
return WTAP_OPEN_ERROR;
}
convert_level = strtoul(optarg, NULL, 0);
if (convert_level > UCHAR_MAX)
{
*err = ERROR_INVALID_PARAMETER;
*err_info = g_strdup_printf("Level %s is bigger than 0xff, err is 0x%x", optarg, *err);
return WTAP_OPEN_ERROR;
}
if (!convert_level)
{
*err = ERROR_INVALID_PARAMETER;
*err_info = g_strdup_printf("Level %s cannot be converted, err is 0x%x", optarg, *err);
return WTAP_OPEN_ERROR;
}
g_provider_filters[provider_idx - 1].Level = (UCHAR)convert_level;
break;
}
}
g_strfreev(params_array);
}
/* do/while(FALSE) is used to jump out of loop so no complex nested if/else is needed */
do
{
mbstowcs(w_etl_filename, etl_filename, FILENAME_MAX);
mbstowcs(w_pcapng_filename, pcapng_filename, FILENAME_MAX);
/* Read ETW from an etl file */
if (etl_filename)
{
mbstowcs(w_etl_filename, etl_filename, FILENAME_MAX);
SecureZeroMemory(&log_file, sizeof(log_file));
log_file.LogFileName = w_etl_filename;
log_file.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD;
log_file.EventRecordCallback = event_callback;
log_file.Context = NULL;
log_file.LogFileName = w_etl_filename;
log_file.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD;
log_file.EventRecordCallback = event_callback;
log_file.Context = NULL;
}
else
{
/*
* Try the best to stop the leftover session since extcap has no way to cleanup when stop capturing. See issue
* https://gitlab.com/wireshark/wireshark/-/issues/17131
*/
ControlTrace((TRACEHANDLE)NULL, LOGGER_NAME, &super_trace_properties.prop, EVENT_TRACE_CONTROL_STOP);
g_is_live_session = TRUE;
log_file.LoggerName = LOGGER_NAME;
log_file.LogFileName = NULL;
log_file.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
log_file.EventRecordCallback = event_callback;
log_file.BufferCallback = NULL;
log_file.Context = NULL;
*err = StartTrace(
&traceControllerHandle,
log_file.LoggerName,
&super_trace_properties.prop);
if (*err != ERROR_SUCCESS)
{
*err_info = g_strdup_printf("StartTrace failed with %u", *err);
returnVal = WTAP_OPEN_ERROR;
break;
}
for (int i = 0; i < ARRAYSIZE(g_provider_filters); i++)
{
if (IsEqualGUID(&g_provider_filters[i].ProviderId, &ZeroGuid))
{
break;
}
*err = EnableTraceEx(
&g_provider_filters[i].ProviderId,
NULL,
traceControllerHandle,
TRUE,
g_provider_filters[i].Level,
g_provider_filters[i].Keyword,
0,
0,
NULL);
if (*err != ERROR_SUCCESS)
{
*err_info = g_strdup_printf("EnableTraceEx failed with %u", *err);
returnVal = WTAP_OPEN_ERROR;
break;
}
}
}
trace_handle = OpenTrace(&log_file);
if (trace_handle == INVALID_PROCESSTRACE_HANDLE) {
*err = GetLastError();
*err_info = g_strdup_printf("OpenTrace failed with %u", err);
returnVal = WTAP_OPEN_NOT_MINE;
break;
@ -94,9 +350,10 @@ wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filen
break;
}
trace_error = ProcessTrace(&trace_handle, 1, 0, 0);
if (trace_error != ERROR_SUCCESS) {
*err = ProcessTrace(&trace_handle, 1, 0, 0);
if (*err != ERROR_SUCCESS) {
returnVal = WTAP_OPEN_ERROR;
*err_info = g_strdup_printf("ProcessTrace failed with %u", err);
break;
}
@ -109,6 +366,7 @@ wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filen
}
if (!g_num_events) {
*err = ERROR_NO_DATA;
*err_info = g_strdup_printf("Didn't find any etw event");
returnVal = WTAP_OPEN_NOT_MINE;
break;
@ -121,18 +379,64 @@ wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filen
}
if (g_pdh != NULL)
{
if (!wtap_dump_close(g_pdh, err, err_info))
if (*err == ERROR_SUCCESS)
{
returnVal = WTAP_OPEN_ERROR;
if (!wtap_dump_close(g_pdh, err, err_info))
{
returnVal = WTAP_OPEN_ERROR;
}
}
else
{
int err_ignore;
gchar* err_info_ignore = NULL;
if (!wtap_dump_close(g_pdh, &err_ignore, &err_info_ignore))
{
returnVal = WTAP_OPEN_ERROR;
g_free(err_info_ignore);
}
}
}
return returnVal;
}
BOOL is_event_filtered_out(PEVENT_RECORD ev)
{
if (g_is_live_session)
{
return FALSE;
}
if (IsEqualGUID(&g_provider_filters[0].ProviderId, &ZeroGuid))
{
return FALSE;
}
for (int i = 0; i < ARRAYSIZE(g_provider_filters); i++)
{
if (IsEqualGUID(&g_provider_filters[i].ProviderId, &ev->EventHeader.ProviderId))
{
return FALSE;
}
if (IsEqualGUID(&g_provider_filters[i].ProviderId, &ZeroGuid))
{
break;
}
}
return TRUE;
}
static void WINAPI event_callback(PEVENT_RECORD ev)
{
ULARGE_INTEGER timestamp;
g_num_events++;
if (is_event_filtered_out(ev))
{
return;
}
/*
* 100ns since 1/1/1601 -> usec since 1/1/1970.
* The offset of 11644473600 seconds can be calculated with a couple of calls to SystemTimeToFileTime.
@ -295,6 +599,12 @@ void wtap_etl_rec_dump(ULARGE_INTEGER timestamp, WTAP_ETL_RECORD* etl_record, UL
sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, %s", err_info);
g_free(err_info);
}
/* Only flush when live session */
if (g_is_live_session && !wtap_dump_flush(g_pdh, &err)) {
g_err = err;
sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, %d", err);
}
wtap_rec_cleanup(&rec);
}

View File

@ -18,12 +18,18 @@
#include <glib.h>
#include <stdlib.h>
#include <windows.h>
#include <strsafe.h>
#include <tdh.h>
#include <guiddef.h>
wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filename, int* err, gchar** err_info);
#define LOGGER_NAME L"wireshark etwdump"
typedef struct
{
EVENT_TRACE_PROPERTIES prop;
char padding[64];
} SUPER_EVENT_TRACE_PROPERTIES;
wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filename, const char* params, int* err, gchar** err_info);
#endif

View File

@ -23,6 +23,8 @@
#include <ui/cmdarg_err.h>
#include "etl.h"
#include <signal.h>
/* extcap-interface has to be unique, or it may use wrong option output by a different extcapbin */
#define ETW_EXTCAP_INTERFACE "etwdump"
#define ETWDUMP_VERSION_MAJOR "1"
@ -34,7 +36,8 @@ enum {
OPT_HELP,
OPT_VERSION,
OPT_INCLUDE_UNDECIDABLE_EVENT,
OPT_ETLFILE
OPT_ETLFILE,
OPT_PARAMS
};
static struct option longopts[] = {
@ -43,11 +46,24 @@ static struct option longopts[] = {
{ "version", no_argument, NULL, OPT_VERSION},
{ "iue", optional_argument, NULL, OPT_INCLUDE_UNDECIDABLE_EVENT},
{ "etlfile", required_argument, NULL, OPT_ETLFILE},
{ "params", required_argument, NULL, OPT_PARAMS},
{ 0, 0, 0, 0 }
};
int g_include_undecidable_event = FALSE;
void SignalHandler(_U_ int signal)
{
SUPER_EVENT_TRACE_PROPERTIES super_trace_properties = { 0 };
super_trace_properties.prop.Wnode.BufferSize = sizeof(SUPER_EVENT_TRACE_PROPERTIES);
super_trace_properties.prop.Wnode.ClientContext = 2;
super_trace_properties.prop.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
super_trace_properties.prop.LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
super_trace_properties.prop.LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
/* Close trace when press CONTROL+C when running this console alone */
ControlTrace((TRACEHANDLE)NULL, LOGGER_NAME, &super_trace_properties.prop, EVENT_TRACE_CONTROL_STOP);
}
static void help(extcap_parameters* extcap_conf)
{
extcap_help_print(extcap_conf);
@ -66,30 +82,26 @@ static int list_config(char* interface)
g_warning("Interface must be %s", ETW_EXTCAP_INTERFACE);
return EXIT_FAILURE;
}
/* Saved for later live capture support */
#if 0
printf("arg {number=%u}{call=--type}{display=Capture type}"
"{type=selector}{tooltip=Choose the type of capture}{group=Capture}\n",
inc);
printf("value {arg=%u}{value=etl}{display=From a etl file}\n", inc);
printf("value {arg=%u}{value=live}{display=From a live session}\n", inc);
inc++;
#endif
/*
* required=true agu will be displayed before required=false on UI
*
* Empty etlfile and unempty params, read etw events from a live session with the params as the filter
* Unempty etlfile and empty params, read etw events from the etl file without filter
* Unempty etlfile and unemtpy params, read etw events from the etl file with the params as the filter
* Empty eltfile and empty params, invalid
*/
printf("arg {number=%u}{call=--etlfile}{display=etl file}"
"{type=fileselect}{tooltip=Select etl file to display in Wireshark}{required=false}{group=Capture}\n",
inc++);
printf("arg {number=%u}{call=--params}{display=filter parmeters}"
"{type=string}{tooltip=Input providers, keyword and level filters for the etl file and live session}{group=Capture}\n",
inc++);
/*
* The undecidable events are those that either don't have sub-dissector or don't have anthing meaningful to display except for the EVENT_HEADER.
*/
printf("arg {number=%u}{call=--iue}{display=Should undecidable events be included}"
"{type=boolflag}{default=false}{tooltip=Choose if the undecidable event is included}{group=Capture}\n",
inc++);
printf("arg {number=%u}{call=--etlfile}{display=etl file}"
"{type=fileselect}{tooltip=Select etl file to display in Wireshark}{required=true}{group=Capture}\n",
inc++);
/* Saved for later live capture support */
#if 0
printf("arg {number=%u}{call=--session-params}{display=Live session parameters}"
"{type=string}{tooltip=providers, keyword and level}{group=Capture}\n",
inc++);
#endif
extcap_config_debug(&inc);
return EXIT_SUCCESS;
@ -103,6 +115,7 @@ int main(int argc, char* argv[])
int ret = EXIT_FAILURE;
char* etlfile = NULL;
char* params = NULL;
extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
char* help_url;
@ -166,6 +179,11 @@ int main(int argc, char* argv[])
etlfile = g_strdup(optarg);
break;
case OPT_PARAMS:
/* Add params as the prefix since getopt_long will ignore the first argument always */
params = g_strdup_printf("params %s", optarg);
break;
case OPT_INCLUDE_UNDECIDABLE_EVENT:
g_include_undecidable_event = TRUE;
break;
@ -204,9 +222,17 @@ int main(int argc, char* argv[])
goto end;
}
if (etlfile == NULL && params == NULL)
{
g_warning("ERROR: Both --etlfile and --params arguments are empty");
goto end;
}
wtap_init(FALSE);
switch(etw_dump(etlfile, extcap_conf->fifo, &ret, &err_msg))
signal(SIGINT, SignalHandler);
switch(etw_dump(etlfile, extcap_conf->fifo, params, &ret, &err_msg))
{
case WTAP_OPEN_ERROR:
if (err_msg != NULL) {
@ -220,14 +246,29 @@ int main(int argc, char* argv[])
}
break;
case WTAP_OPEN_NOT_MINE:
if (err_msg != NULL) {
g_warning("The file %s is not etl format. Error message: %s.",
etlfile, err_msg);
g_free(err_msg);
if (etlfile == NULL)
{
if (err_msg != NULL) {
g_warning("The live session didn't caputre any event. Error message: %s.",
err_msg);
g_free(err_msg);
}
else
{
g_warning("The live session didn't caputre any event");
}
}
else
{
g_warning("The file %s is not etl format");
if (err_msg != NULL) {
g_warning("The file %s is not etl format. Error message: %s.",
etlfile, err_msg);
g_free(err_msg);
}
else
{
g_warning("The file %s is not etl format", etlfile);
}
}
break;
case WTAP_OPEN_MINE:
@ -244,6 +285,10 @@ end:
{
g_free(etlfile);
}
if (params != NULL)
{
g_free(params);
}
return ret;
}

View File

@ -33,8 +33,11 @@ for FILE in $COMMIT_FILES; do
then
continue
fi
# extcap/etl.c: that compiles, and is compiled, only on Windows
if test "$FILE_BASENAME" = "etl.c"
# extcap/{etwdump.c,etl.c,etw_message.c}: those compile, and are compiled,
# only on Windows
if test \( "$FILE_BASENAME" = "etwdump.c" -o \
"$FILE_BASENAME" = "etl.c" -o \
"$FILE_BASENAME" = "etw_message.c" \)
then
continue
fi