RFC-0074: Expose Process Start Time

RFC-0074: Expose Process Start Time
StatusAccepted
Areas
  • Kernel
Description

Make process start time queryable from userspace.

Gerrit change
Authors
Reviewers
Date submitted (year-month-day)2021-02-19
Date reviewed (year-month-day)2021-03-11

Summary

We refactor ZX_INFO_PROCESS to expose a process's start time and also to clean up the flags describing its runtime. We say a process has a valid start time when a ZX_INFO_PROCESS_FLAG_STARTED flag is present and define it to be the monotonic time at which zx_process_start() was called, when valid.

Motivation

First filed at crbug.com/726484 and then https://fxbug.dev/42105649, Chromium requested the feature in order to give a base time for trace events, extending a platform-agnostic interface already supporting Linux, macOS, and Windows. Further, https://fxbug.dev/42119376 was filed to leverage this same feature in order to include uptime in crash reports.

Design

In a small, straightforward extension of ProcessDispatcher, we would record the time in ProcessDispatcher::AddInitializedThread() for the initial thread and then pass it along in ProcessDispatcher::GetInfo(); the former method is the main "starting" routine of zx_process_start(), while the latter is that of zx_get_object_info() for a process handle with ZX_INFO_PROCESS.

As for the struct populated by zx_object_get_info(), currently we have:

typedef struct zx_info_process {
    // The process's return code; only valid if |exited| is true.
    // If the process was killed, it will be one of the ZX_TASK_RETCODE values.
    int64_t return_code;
    // True if the process has ever left the initial creation state,
    // even if it has exited as well.
    bool started;
    // If true, the process has exited and |return_code| is valid.
    bool exited;
    // True if a debugger is attached to the process.
    bool debugger_attached;
    uint8_t padding1[5];
} zx_info_process_t;

We would evolve it to be:

typedef struct zx_info_process {
    // The process's return code; only valid if the
    // |ZX_PROCESS_INFO_FLAG_EXITED| flag is set. If the process was killed, it
    // will be one of the |ZX_TASK_RETCODE| values.
    int64_t return_code;
    // The monotonic time at which zx_process_start() was called, only valid
    // if the |ZX_INFO_PROCESS_FLAG_STARTED| flag is set.
    zx_time_t start_time;
    // Bitwise OR of ZX_INFO_PROCESS_FLAG_* values.
    uint32_t flags;
    uint8_t padding1[4];
} zx_info_process_t;

For which the following flag values would be introduced:

// Whether the process has started. zx_process_info_t::start_time is only
// valid if this flag is set.
#define ZX_INFO_PROCESS_FLAG_STARTED (1u << 0)

// Whether the process has exited.
#define ZX_INFO_PROCESS_FLAG_EXITED (1u << 1)

// Whether a debugger is attached to the process.
#define ZX_INFO_PROCESS_FLAG_DEBUGGER_ATTACHED (1u << 2)

The boolean-to-flag refactor is not strictly necessary, but it would bring zx_info_process_t in line with current syscall struct policy, save a byte on padding, and would not increase the amount of work needed to carry out this proposal (and also would save the next engineer from a fair amount of hassle).

Implementation

  1. Rename the old struct to zx_info_process_v1_t and create an alias to refer to it by its old name (zx_info_process_t -> zx_info_process_v1_t). Rename the topic ZX_INFO_PROCESS to ZX_INFO_PROCESS_V1 and create an alias to the old name (ZX_INFO_PROCESS -> ZX_INFO_PROCESS_V1).

  2. Add the new struct (zx_info_process_v2_t) and topic (ZX_INFO_PROCESS_V2). Change the kernel to track process start time and recognize the new topic and struct.

  3. Update all code (in-tree and out-of-tree) to use ZX_INFO_PROCESS_V2.

  4. Change the ZX_INFO_PROCESS and zx_info_process_t alias to refer to the v2 topic and struct.

  5. Wait until the previous change has been fully rolled out.

  6. Update all code (in-tree and out-of-tree) to use ZX_INFO_PROCESS again.

  7. Remove the v1 topic and struct, as well as the v2 alias.

The Rust and Go versions of these types will be similarly updated.

Performance

The added logic should incur a negligible runtime cost, especially as it would be amortized over the lifetime of an individual process.

Security considerations

If a program already has a handle to a process, it can do and glean much more than the time at which that process started. Moreover, start time is already exposed - without seeming concern - in a number of other operating systems.

Privacy considerations

See Security considerations above.

Testing

Zircon's process-related core tests will be extended to assert, for example, that time samples taken before and after a zx_process_start() do indeed sandwich the recorded start time, and that a not-yet-started process has ZX_INFO_PROCESS_FLAG_STARTED unset.

Documentation

ZX_INFO_PROCESS documentation will be updated accordingly.

Drawbacks, alternatives, and unknowns

Also considered was exposing the "start" time as a more general, task-level concept (exposed under, say, ZX_INFO_TASK_RUNTIME). For threads, things extend indentically: a thread's start time is the point at which zx_thread_start() is called. For a job, it would then be most natural to say that it "starts" when its first child starts. Since jobs can be nested, however, this would introduce walking task subtrees, which is relatively more complicated logic than dealing with the other two task types. Ultimately the extensions were decided against, as they would not meet a current need and would allow us to sidestep the need for task hierarchy walking.

Prior art and references

Linux: /proc/[pid]/stat exposes starttime. FreeBSD: /proc/[pid]/status exposes start time in a space-separated list of statistics. macOS: The proc_pidinfo() syscalls seems to expose this.