compat / win32 / trace2_win32_process_info.con commit Merge branch 'nd/diff-parseopt-2' (4ab0f13)
   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 */
  16static 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 */
  71static 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 */
 106static 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 */
 133static void get_is_being_debugged(void)
 134{
 135        if (IsDebuggerPresent())
 136                trace2_data_intmax("process", the_repository,
 137                                   "windows/debugger_present", 1);
 138}
 139
 140void trace2_collect_process_info(void)
 141{
 142        if (!trace2_is_enabled())
 143                return;
 144
 145        get_is_being_debugged();
 146        get_ancestry();
 147}