1// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5#include "platform/globals.h"
6#if defined(HOST_OS_FUCHSIA)
7
8#include "vm/thread_interrupter.h"
9
10#include <zircon/process.h>
11#include <zircon/status.h>
12#include <zircon/syscalls.h>
13#include <zircon/syscalls/debug.h>
14#include <zircon/syscalls/object.h>
15#include <zircon/types.h>
16
17#include "vm/flags.h"
18#include "vm/instructions.h"
19#include "vm/os.h"
20#include "vm/profiler.h"
21
22namespace dart {
23
24#ifndef PRODUCT
25
26DECLARE_FLAG(bool, trace_thread_interrupter);
27
28// TODO(ZX-430): Currently, CPU profiling for Fuchsia is arranged very similarly
29// to our Windows profiling. That is, the interrupter thread iterates over
30// all threads, suspends them, samples various things, and then resumes them.
31// When ZX-430 is resolved, the code below should be rewritten to use whatever
32// feature is added for it.
33
34// A scope within which a target thread is suspended. When the scope is exited,
35// the thread is resumed and its handle is closed.
36class ThreadSuspendScope {
37 public:
38 explicit ThreadSuspendScope(zx_handle_t thread_handle)
39 : thread_handle_(thread_handle), suspend_token_(ZX_HANDLE_INVALID) {
40 zx_status_t status = zx_task_suspend_token(thread_handle, &suspend_token_);
41 // If a thread is somewhere where suspend is impossible, zx_task_suspend()
42 // can return ZX_ERR_NOT_SUPPORTED.
43 if (status != ZX_OK) {
44 if (FLAG_trace_thread_interrupter) {
45 OS::PrintErr("ThreadInterrupter: zx_task_suspend failed: %s\n",
46 zx_status_get_string(status));
47 }
48 }
49 }
50
51 ~ThreadSuspendScope() {
52 if (suspend_token_ != ZX_HANDLE_INVALID) {
53 zx_handle_close(suspend_token_);
54 }
55 zx_handle_close(thread_handle_);
56 }
57
58 bool suspended() const { return suspend_token_ != ZX_HANDLE_INVALID; }
59
60 private:
61 zx_handle_t thread_handle_;
62 zx_handle_t suspend_token_; // ZX_HANDLE_INVALID when not suspended.
63
64 DISALLOW_ALLOCATION();
65 DISALLOW_COPY_AND_ASSIGN(ThreadSuspendScope);
66};
67
68class ThreadInterrupterFuchsia : public AllStatic {
69 public:
70#if defined(TARGET_ARCH_X64)
71 static bool GrabRegisters(zx_handle_t thread, InterruptedThreadState* state) {
72 zx_thread_state_general_regs regs;
73 zx_status_t status = zx_thread_read_state(
74 thread, ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
75 if (status != ZX_OK) {
76 if (FLAG_trace_thread_interrupter) {
77 OS::PrintErr("ThreadInterrupter failed to get registers: %s\n",
78 zx_status_get_string(status));
79 }
80 return false;
81 }
82 state->pc = static_cast<uintptr_t>(regs.rip);
83 state->fp = static_cast<uintptr_t>(regs.rbp);
84 state->csp = static_cast<uintptr_t>(regs.rsp);
85 state->dsp = static_cast<uintptr_t>(regs.rsp);
86 return true;
87 }
88#elif defined(TARGET_ARCH_ARM64)
89 static bool GrabRegisters(zx_handle_t thread, InterruptedThreadState* state) {
90 zx_thread_state_general_regs regs;
91 zx_status_t status = zx_thread_read_state(
92 thread, ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
93 if (status != ZX_OK) {
94 if (FLAG_trace_thread_interrupter) {
95 OS::PrintErr("ThreadInterrupter failed to get registers: %s\n",
96 zx_status_get_string(status));
97 }
98 return false;
99 }
100 state->pc = static_cast<uintptr_t>(regs.pc);
101 state->fp = static_cast<uintptr_t>(regs.r[FPREG]);
102 state->csp = static_cast<uintptr_t>(regs.sp);
103 state->dsp = static_cast<uintptr_t>(regs.r[SPREG]);
104 state->lr = static_cast<uintptr_t>(regs.lr);
105 return true;
106 }
107#else
108#error "Unsupported architecture"
109#endif
110
111 static void Interrupt(OSThread* os_thread) {
112 ASSERT(os_thread->id() != ZX_KOID_INVALID);
113 ASSERT(!OSThread::Compare(OSThread::GetCurrentThreadId(), os_thread->id()));
114 zx_status_t status;
115
116 // Get a handle on the target thread.
117 const zx_koid_t target_thread_koid = os_thread->id();
118 if (FLAG_trace_thread_interrupter) {
119 OS::PrintErr("ThreadInterrupter: interrupting thread with koid=%ld\n",
120 target_thread_koid);
121 }
122 zx_handle_t target_thread_handle;
123 status = zx_object_get_child(zx_process_self(), target_thread_koid,
124 ZX_RIGHT_SAME_RIGHTS, &target_thread_handle);
125 if (status != ZX_OK) {
126 if (FLAG_trace_thread_interrupter) {
127 OS::PrintErr("ThreadInterrupter: zx_object_get_child failed: %s\n",
128 zx_status_get_string(status));
129 }
130 return;
131 }
132 if (target_thread_handle == ZX_HANDLE_INVALID) {
133 if (FLAG_trace_thread_interrupter) {
134 OS::PrintErr(
135 "ThreadInterrupter: zx_object_get_child gave an invalid "
136 "thread handle!");
137 }
138 return;
139 }
140
141 // This scope suspends the thread. When we exit the scope, the thread is
142 // resumed, and the thread handle is closed.
143 ThreadSuspendScope tss(target_thread_handle);
144 if (!tss.suspended()) {
145 return;
146 }
147
148 // Check that the thread is suspended.
149 status = PollThreadUntilSuspended(target_thread_handle);
150 if (status != ZX_OK) {
151 return;
152 }
153
154 // Grab the target thread's registers.
155 InterruptedThreadState its;
156 if (!GrabRegisters(target_thread_handle, &its)) {
157 return;
158 }
159 // Currently we sample only threads that are associated
160 // with an isolate. It is safe to call 'os_thread->thread()'
161 // here as the thread which is being queried is suspended.
162 Thread* thread = static_cast<Thread*>(os_thread->thread());
163 if (thread != NULL) {
164 Profiler::SampleThread(thread, its);
165 }
166 }
167
168 private:
169 static const char* ThreadStateGetString(uint32_t state) {
170// TODO(dje): This #ifdef is temporary to handle the transition.
171// It can be deleted once the new version of zircon rolls out.
172#ifdef ZX_THREAD_STATE_BASIC
173 state = ZX_THREAD_STATE_BASIC(state);
174#endif
175 switch (state) {
176 case ZX_THREAD_STATE_NEW:
177 return "ZX_THREAD_STATE_NEW";
178 case ZX_THREAD_STATE_RUNNING:
179 return "ZX_THREAD_STATE_RUNNING";
180 case ZX_THREAD_STATE_SUSPENDED:
181 return "ZX_THREAD_STATE_SUSPENDED";
182 case ZX_THREAD_STATE_BLOCKED:
183 return "ZX_THREAD_STATE_BLOCKED";
184 case ZX_THREAD_STATE_DYING:
185 return "ZX_THREAD_STATE_DYING";
186 case ZX_THREAD_STATE_DEAD:
187 return "ZX_THREAD_STATE_DEAD";
188 default:
189 return "<Unknown>";
190 }
191 }
192
193 static zx_status_t PollThreadUntilSuspended(zx_handle_t thread_handle) {
194 const intptr_t kMaxPollAttempts = 10;
195 intptr_t poll_tries = 0;
196 while (poll_tries < kMaxPollAttempts) {
197 zx_info_thread_t thread_info;
198 zx_status_t status =
199 zx_object_get_info(thread_handle, ZX_INFO_THREAD, &thread_info,
200 sizeof(thread_info), NULL, NULL);
201 poll_tries++;
202 if (status != ZX_OK) {
203 if (FLAG_trace_thread_interrupter) {
204 OS::PrintErr("ThreadInterrupter: zx_object_get_info failed: %s\n",
205 zx_status_get_string(status));
206 }
207 return status;
208 }
209 if (thread_info.state == ZX_THREAD_STATE_SUSPENDED) {
210 // Success.
211 return ZX_OK;
212 }
213 if (thread_info.state == ZX_THREAD_STATE_RUNNING) {
214 // Poll.
215 continue;
216 }
217 if (FLAG_trace_thread_interrupter) {
218 OS::PrintErr("ThreadInterrupter: Thread is not suspended: %s\n",
219 ThreadStateGetString(thread_info.state));
220 }
221 return ZX_ERR_BAD_STATE;
222 }
223 if (FLAG_trace_thread_interrupter) {
224 OS::PrintErr("ThreadInterrupter: Exceeded max suspend poll tries\n");
225 }
226 return ZX_ERR_BAD_STATE;
227 }
228};
229
230bool ThreadInterrupter::IsDebuggerAttached() {
231 return false;
232}
233
234void ThreadInterrupter::InterruptThread(OSThread* thread) {
235 if (FLAG_trace_thread_interrupter) {
236 OS::PrintErr("ThreadInterrupter suspending %p\n",
237 reinterpret_cast<void*>(thread->id()));
238 }
239 ThreadInterrupterFuchsia::Interrupt(thread);
240 if (FLAG_trace_thread_interrupter) {
241 OS::PrintErr("ThreadInterrupter resuming %p\n",
242 reinterpret_cast<void*>(thread->id()));
243 }
244}
245
246void ThreadInterrupter::InstallSignalHandler() {
247 // Nothing to do on Fuchsia.
248}
249
250void ThreadInterrupter::RemoveSignalHandler() {
251 // Nothing to do on Fuchsia.
252}
253
254#endif // !PRODUCT
255
256} // namespace dart
257
258#endif // defined(HOST_OS_FUCHSIA)
259