summaryrefslogtreecommitdiff
path: root/Kernel/Syscalls/profiling.cpp
blob: f8a3a13f1dff011974594cad482794b66e95ba02 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <Kernel/Tasks/Coredump.h>
#include <Kernel/Tasks/PerformanceManager.h>
#include <Kernel/Tasks/Process.h>
#include <Kernel/Tasks/Scheduler.h>
#include <Kernel/Time/TimeManagement.h>

namespace Kernel {

bool g_profiling_all_threads;
PerformanceEventBuffer* g_global_perf_events;
u64 g_profiling_event_mask;

// NOTE: event_mask needs to be passed as a pointer as u64
//       does not fit into a register on 32bit architectures.
ErrorOr<FlatPtr> Process::sys$profiling_enable(pid_t pid, Userspace<u64 const*> userspace_event_mask)
{
    VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this);
    TRY(require_no_promises());

    auto const event_mask = TRY(copy_typed_from_user(userspace_event_mask));
    return profiling_enable(pid, event_mask);
}

// NOTE: This second entrypoint exists to allow the kernel to invoke the syscall to enable boot profiling.
ErrorOr<FlatPtr> Process::profiling_enable(pid_t pid, u64 event_mask)
{
    VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this);

    if (pid == -1) {
        auto credentials = this->credentials();
        if (!credentials->is_superuser())
            return EPERM;
        ScopedCritical critical;
        g_profiling_event_mask = PERF_EVENT_PROCESS_CREATE | PERF_EVENT_THREAD_CREATE | PERF_EVENT_MMAP;
        if (g_global_perf_events) {
            g_global_perf_events->clear();
        } else {
            g_global_perf_events = PerformanceEventBuffer::try_create_with_size(32 * MiB).leak_ptr();
            if (!g_global_perf_events) {
                g_profiling_event_mask = 0;
                return ENOMEM;
            }
        }

        SpinlockLocker lock(g_profiling_lock);
        if (!TimeManagement::the().enable_profile_timer())
            return ENOTSUP;
        g_profiling_all_threads = true;
        PerformanceManager::add_process_created_event(*Scheduler::colonel());
        TRY(Process::for_each_in_same_jail([](auto& process) -> ErrorOr<void> {
            PerformanceManager::add_process_created_event(process);
            return {};
        }));
        g_profiling_event_mask = event_mask;
        return 0;
    }

    auto process = Process::from_pid_in_same_jail(pid);
    if (!process)
        return ESRCH;
    if (process->is_dead())
        return ESRCH;
    auto credentials = this->credentials();
    auto profile_process_credentials = process->credentials();
    if (!credentials->is_superuser() && profile_process_credentials->uid() != credentials->euid())
        return EPERM;
    SpinlockLocker lock(g_profiling_lock);
    g_profiling_event_mask = PERF_EVENT_PROCESS_CREATE | PERF_EVENT_THREAD_CREATE | PERF_EVENT_MMAP;
    process->set_profiling(true);
    if (!process->create_perf_events_buffer_if_needed()) {
        process->set_profiling(false);
        return ENOMEM;
    }
    g_profiling_event_mask = event_mask;
    if (!TimeManagement::the().enable_profile_timer()) {
        process->set_profiling(false);
        return ENOTSUP;
    }
    return 0;
}

ErrorOr<FlatPtr> Process::sys$profiling_disable(pid_t pid)
{
    VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this);
    TRY(require_no_promises());

    if (pid == -1) {
        auto credentials = this->credentials();
        if (!credentials->is_superuser())
            return EPERM;
        ScopedCritical critical;
        if (!TimeManagement::the().disable_profile_timer())
            return ENOTSUP;
        g_profiling_all_threads = false;
        return 0;
    }

    auto process = Process::from_pid_in_same_jail(pid);
    if (!process)
        return ESRCH;
    auto credentials = this->credentials();
    auto profile_process_credentials = process->credentials();
    if (!credentials->is_superuser() && profile_process_credentials->uid() != credentials->euid())
        return EPERM;
    SpinlockLocker lock(g_profiling_lock);
    if (!process->is_profiling())
        return EINVAL;
    // FIXME: If we enabled the profile timer and it's not supported, how do we disable it now?
    if (!TimeManagement::the().disable_profile_timer())
        return ENOTSUP;
    process->set_profiling(false);
    return 0;
}

ErrorOr<FlatPtr> Process::sys$profiling_free_buffer(pid_t pid)
{
    VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this);
    TRY(require_no_promises());

    if (pid == -1) {
        auto credentials = this->credentials();
        if (!credentials->is_superuser())
            return EPERM;

        OwnPtr<PerformanceEventBuffer> perf_events;

        {
            ScopedCritical critical;

            perf_events = adopt_own_if_nonnull(g_global_perf_events);
            g_global_perf_events = nullptr;
        }

        return 0;
    }

    auto process = Process::from_pid_in_same_jail(pid);
    if (!process)
        return ESRCH;
    auto credentials = this->credentials();
    auto profile_process_credentials = process->credentials();
    if (!credentials->is_superuser() && profile_process_credentials->uid() != credentials->euid())
        return EPERM;
    SpinlockLocker lock(g_profiling_lock);
    if (process->is_profiling())
        return EINVAL;
    process->delete_perf_events_buffer();
    return 0;
}
}