trace2: collect Windows-specific process information
Add platform-specific interface to log information about the current process. On Windows, this interface is used to indicate whether the git process is running under a debugger and list names of the process ancestors. Information for other platforms is left for a future effort. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jeff Hostetler committed
Feb 22, 2019 at 14:25 UTC
353d3d77f4fdd1582e3b60824deebca27c7b2790
4 files changed
+164
common-main.c
+1
@@ -37,6 +37,7 @@ int main(int argc, const char **argv)
37
38
trace2_initialize();
39
trace2_cmd_start(argv);
40
+ trace2_collect_process_info();
41
42
git_resolve_executable_dir(argv[0]);
43
compat/win32/trace2_win32_process_info.c
new
+147
@@ -0,0 +1,147 @@
1
+#include "../../cache.h"
2
+#include "../../json-writer.h"
3
+#include <Psapi.h>
4
+#include <tlHelp32.h>
5
+
6
+/*
7
+ * An arbitrarily chosen value to limit the size of the ancestor
8
+ * array built in git_processes().
9
+ */
10
+#define NR_PIDS_LIMIT 10
11
+
12
+/*
13
+ * Find the process data for the given PID in the given snapshot
14
+ * and update the PROCESSENTRY32 data.
15
+ */
16
+static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)
17
+{
18
+ pe32->dwSize = sizeof(PROCESSENTRY32);
19
+
20
+ if (Process32First(hSnapshot, pe32)) {
21
+ do {
22
+ if (pe32->th32ProcessID == pid)
23
+ return 1;
24
+ } while (Process32Next(hSnapshot, pe32));
25
+ }
26
+ return 0;
27
+}
28
+
29
+/*
30
+ * Accumulate JSON array of our parent processes:
31
+ * [
32
+ * exe-name-parent,
33
+ * exe-name-grand-parent,
34
+ * ...
35
+ * ]
36
+ *
37
+ * Note: we only report the filename of the process executable; the
38
+ * only way to get its full pathname is to use OpenProcess()
39
+ * and GetModuleFileNameEx() or QueryfullProcessImageName()
40
+ * and that seems rather expensive (on top of the cost of
41
+ * getting the snapshot).
42
+ *
43
+ * Note: we compute the set of parent processes by walking the PPID
44
+ * link in each visited PROCESSENTRY32 record. This search
45
+ * stops when an ancestor process is not found in the snapshot
46
+ * (because it exited before the current or intermediate parent
47
+ * process exited).
48
+ *
49
+ * This search may compute an incorrect result if the PPID link
50
+ * refers to the PID of an exited parent and that PID has been
51
+ * recycled and given to a new unrelated process.
52
+ *
53
+ * Worse, it is possible for a child or descendant of the
54
+ * current process to be given the recycled PID and cause a
55
+ * PPID-cycle. This would cause an infinite loop building our
56
+ * parent process array.
57
+ *
58
+ * Note: for completeness, the "System Idle" process has PID=0 and
59
+ * PPID=0 and could cause another PPID-cycle. We don't expect
60
+ * Git to be a descendant of the idle process, but because of
61
+ * PID recycling, it might be possible to get a PPID link value
62
+ * of 0. This too would cause an infinite loop.
63
+ *
64
+ * Therefore, we keep an array of the visited PPIDs to guard against
65
+ * cycles.
66
+ *
67
+ * We use a fixed-size array rather than ALLOC_GROW to keep things
68
+ * simple and avoid the alloc/realloc overhead. It is OK if we
69
+ * truncate the search and return a partial answer.
70
+ */
71
+static void get_processes(struct json_writer *jw, HANDLE hSnapshot)
72
+{
73
+ PROCESSENTRY32 pe32;
74
+ DWORD pid;
75
+ DWORD pid_list[NR_PIDS_LIMIT];
76
+ int k, nr_pids = 0;
77
+
78
+ pid = GetCurrentProcessId();
79
+ while (find_pid(pid, hSnapshot, &pe32)) {
80
+ /* Only report parents. Omit self from the JSON output. */
81
+ if (nr_pids)
82
+ jw_array_string(jw, pe32.szExeFile);
83
+
84
+ /* Check for cycle in snapshot. (Yes, it happened.) */
85
+ for (k = 0; k < nr_pids; k++)
86
+ if (pid == pid_list[k]) {
87
+ jw_array_string(jw, "(cycle)");
88
+ return;
89
+ }
90
+
91
+ if (nr_pids == NR_PIDS_LIMIT) {
92
+ jw_array_string(jw, "(truncated)");
93
+ return;
94
+ }
95
+
96
+ pid_list[nr_pids++] = pid;
97
+
98
+ pid = pe32.th32ParentProcessID;
99
+ }
100
+}
101
+
102
+/*
103
+ * Emit JSON data for the current and parent processes. Individual
104
+ * trace2 targets can decide how to actually print it.
105
+ */
106
+static void get_ancestry(void)
107
+{
108
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
109
+
110
+ if (hSnapshot != INVALID_HANDLE_VALUE) {
111
+ struct json_writer jw = JSON_WRITER_INIT;
112
+
113
+ jw_array_begin(&jw, 0);
114
+ get_processes(&jw, hSnapshot);
115
+ jw_end(&jw);
116
+
117
+ trace2_data_json("process", the_repository, "windows/ancestry",
118
+ &jw);
119
+
120
+ jw_release(&jw);
121
+ CloseHandle(hSnapshot);
122
+ }
123
+}
124
+
125
+/*
126
+ * Is a debugger attached to the current process?
127
+ *
128
+ * This will catch debug runs (where the debugger started the process).
129
+ * This is the normal case. Since this code is called during our startup,
130
+ * it will not report instances where a debugger is attached dynamically
131
+ * to a running git process, but that is relatively rare.
132
+ */
133
+static void get_is_being_debugged(void)
134
+{
135
+ if (IsDebuggerPresent())
136
+ trace2_data_intmax("process", the_repository,
137
+ "windows/debugger_present", 1);
138
+}
139
+
140
+void trace2_collect_process_info(void)
141
+{
142
+ if (!trace2_is_enabled())
143
+ return;
144
+
145
+ get_is_being_debugged();
146
+ get_ancestry();
147
+}
config.mak.uname
+2
@@ -393,6 +393,7 @@ ifeq ($(uname_S),Windows)
393
BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
394
COMPAT_OBJS = compat/msvc.o compat/winansi.o \
395
compat/win32/pthread.o compat/win32/syslog.o \
396
+ compat/win32/trace2_win32_process_info.o \
397
compat/win32/dirent.o
398
COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
399
BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE
@@ -546,6 +547,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
547
COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
548
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
549
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
550
+ compat/win32/trace2_win32_process_info.o \
551
compat/win32/path-utils.o \
552
compat/win32/pthread.o compat/win32/syslog.o \
553
compat/win32/dirent.o
trace2.h
+14
@@ -368,4 +368,18 @@ void trace2_printf(const char *fmt, ...);
368
/* clang-format on */
369
#endif
370
371
+/*
372
+ * Optional platform-specific code to dump information about the
373
+ * current and any parent process(es). This is intended to allow
374
+ * post-processors to know who spawned this git instance and anything
375
+ * else the platform may be able to tell us about the current process.
376
+ */
377
+#if defined(GIT_WINDOWS_NATIVE)
378
+void trace2_collect_process_info(void);
379
+#else
380
+#define trace2_collect_process_info() \
381
+ do { \
382
+ } while (0)
383
+#endif
384
+
385
#endif /* TRACE2_H */