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 */