1320384Sdim//===-- MainThreadCheckerRuntime.cpp ----------------------------*- C++ -*-===//
2320384Sdim//
3353358Sdim// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4353358Sdim// See https://llvm.org/LICENSE.txt for license information.
5353358Sdim// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6320384Sdim//
7320384Sdim//===----------------------------------------------------------------------===//
8320384Sdim
9320384Sdim#include "MainThreadCheckerRuntime.h"
10320384Sdim
11320384Sdim#include "lldb/Breakpoint/StoppointCallbackContext.h"
12320384Sdim#include "lldb/Core/Module.h"
13320384Sdim#include "lldb/Core/PluginManager.h"
14320384Sdim#include "lldb/Symbol/Symbol.h"
15320384Sdim#include "lldb/Symbol/SymbolContext.h"
16320384Sdim#include "lldb/Symbol/Variable.h"
17320384Sdim#include "lldb/Symbol/VariableList.h"
18320384Sdim#include "lldb/Target/InstrumentationRuntimeStopInfo.h"
19320384Sdim#include "lldb/Target/RegisterContext.h"
20320384Sdim#include "lldb/Target/SectionLoadList.h"
21320384Sdim#include "lldb/Target/StopInfo.h"
22320384Sdim#include "lldb/Target/Target.h"
23320384Sdim#include "lldb/Target/Thread.h"
24320384Sdim#include "lldb/Utility/RegularExpression.h"
25320384Sdim#include "Plugins/Process/Utility/HistoryThread.h"
26320384Sdim
27353358Sdim#include <memory>
28353358Sdim
29320384Sdimusing namespace lldb;
30320384Sdimusing namespace lldb_private;
31320384Sdim
32320384SdimMainThreadCheckerRuntime::~MainThreadCheckerRuntime() {
33320384Sdim  Deactivate();
34320384Sdim}
35320384Sdim
36320384Sdimlldb::InstrumentationRuntimeSP
37320384SdimMainThreadCheckerRuntime::CreateInstance(const lldb::ProcessSP &process_sp) {
38320384Sdim  return InstrumentationRuntimeSP(new MainThreadCheckerRuntime(process_sp));
39320384Sdim}
40320384Sdim
41320384Sdimvoid MainThreadCheckerRuntime::Initialize() {
42320384Sdim  PluginManager::RegisterPlugin(
43320384Sdim      GetPluginNameStatic(), "MainThreadChecker instrumentation runtime plugin.",
44320384Sdim      CreateInstance, GetTypeStatic);
45320384Sdim}
46320384Sdim
47320384Sdimvoid MainThreadCheckerRuntime::Terminate() {
48320384Sdim  PluginManager::UnregisterPlugin(CreateInstance);
49320384Sdim}
50320384Sdim
51320384Sdimlldb_private::ConstString MainThreadCheckerRuntime::GetPluginNameStatic() {
52320384Sdim  return ConstString("MainThreadChecker");
53320384Sdim}
54320384Sdim
55320384Sdimlldb::InstrumentationRuntimeType MainThreadCheckerRuntime::GetTypeStatic() {
56320384Sdim  return eInstrumentationRuntimeTypeMainThreadChecker;
57320384Sdim}
58320384Sdim
59320384Sdimconst RegularExpression &
60320384SdimMainThreadCheckerRuntime::GetPatternForRuntimeLibrary() {
61320384Sdim  static RegularExpression regex(llvm::StringRef("libMainThreadChecker.dylib"));
62320384Sdim  return regex;
63320384Sdim}
64320384Sdim
65320384Sdimbool MainThreadCheckerRuntime::CheckIfRuntimeIsValid(
66320384Sdim    const lldb::ModuleSP module_sp) {
67320384Sdim  static ConstString test_sym("__main_thread_checker_on_report");
68320384Sdim  const Symbol *symbol =
69320384Sdim      module_sp->FindFirstSymbolWithNameAndType(test_sym, lldb::eSymbolTypeAny);
70320384Sdim  return symbol != nullptr;
71320384Sdim}
72320384Sdim
73320384SdimStructuredData::ObjectSP
74320384SdimMainThreadCheckerRuntime::RetrieveReportData(ExecutionContextRef exe_ctx_ref) {
75320384Sdim  ProcessSP process_sp = GetProcessSP();
76320384Sdim  if (!process_sp)
77320384Sdim    return StructuredData::ObjectSP();
78320384Sdim
79320384Sdim  ThreadSP thread_sp = exe_ctx_ref.GetThreadSP();
80320384Sdim  StackFrameSP frame_sp = thread_sp->GetSelectedFrame();
81320384Sdim  ModuleSP runtime_module_sp = GetRuntimeModuleSP();
82320384Sdim  Target &target = process_sp->GetTarget();
83320384Sdim
84320384Sdim  if (!frame_sp)
85320384Sdim    return StructuredData::ObjectSP();
86320384Sdim
87320384Sdim  RegisterContextSP regctx_sp = frame_sp->GetRegisterContext();
88320384Sdim  if (!regctx_sp)
89320384Sdim    return StructuredData::ObjectSP();
90320384Sdim
91320384Sdim  const RegisterInfo *reginfo = regctx_sp->GetRegisterInfoByName("arg1");
92320384Sdim  if (!reginfo)
93320384Sdim    return StructuredData::ObjectSP();
94320384Sdim
95320384Sdim  uint64_t apiname_ptr = regctx_sp->ReadRegisterAsUnsigned(reginfo, 0);
96320384Sdim  if (!apiname_ptr)
97320384Sdim    return StructuredData::ObjectSP();
98320384Sdim
99320384Sdim  std::string apiName = "";
100320384Sdim  Status read_error;
101320384Sdim  target.ReadCStringFromMemory(apiname_ptr, apiName, read_error);
102320384Sdim  if (read_error.Fail())
103320384Sdim    return StructuredData::ObjectSP();
104320384Sdim
105320384Sdim  std::string className = "";
106320384Sdim  std::string selector = "";
107320384Sdim  if (apiName.substr(0, 2) == "-[") {
108320384Sdim    size_t spacePos = apiName.find(" ");
109320384Sdim    if (spacePos != std::string::npos) {
110320384Sdim      className = apiName.substr(2, spacePos - 2);
111320384Sdim      selector = apiName.substr(spacePos + 1, apiName.length() - spacePos - 2);
112320384Sdim    }
113320384Sdim  }
114320384Sdim
115320384Sdim  // Gather the PCs of the user frames in the backtrace.
116320384Sdim  StructuredData::Array *trace = new StructuredData::Array();
117320384Sdim  auto trace_sp = StructuredData::ObjectSP(trace);
118320384Sdim  StackFrameSP responsible_frame;
119320384Sdim  for (unsigned I = 0; I < thread_sp->GetStackFrameCount(); ++I) {
120320384Sdim    StackFrameSP frame = thread_sp->GetStackFrameAtIndex(I);
121320384Sdim    Address addr = frame->GetFrameCodeAddress();
122320384Sdim    if (addr.GetModule() == runtime_module_sp) // Skip PCs from the runtime.
123320384Sdim      continue;
124320384Sdim
125320384Sdim    // The first non-runtime frame is responsible for the bug.
126320384Sdim    if (!responsible_frame)
127320384Sdim      responsible_frame = frame;
128320384Sdim
129320384Sdim    // First frame in stacktrace should point to a real PC, not return address.
130320384Sdim    if (I != 0 && trace->GetSize() == 0) {
131320384Sdim      addr.Slide(-1);
132320384Sdim    }
133320384Sdim
134320384Sdim    lldb::addr_t PC = addr.GetLoadAddress(&target);
135320384Sdim    trace->AddItem(StructuredData::ObjectSP(new StructuredData::Integer(PC)));
136320384Sdim  }
137320384Sdim
138320384Sdim  auto *d = new StructuredData::Dictionary();
139320384Sdim  auto dict_sp = StructuredData::ObjectSP(d);
140320384Sdim  d->AddStringItem("instrumentation_class", "MainThreadChecker");
141320384Sdim  d->AddStringItem("api_name", apiName);
142320384Sdim  d->AddStringItem("class_name", className);
143320384Sdim  d->AddStringItem("selector", selector);
144320384Sdim  d->AddStringItem("description",
145320970Sdim                   apiName + " must be used from main thread only");
146320384Sdim  d->AddIntegerItem("tid", thread_sp->GetIndexID());
147320384Sdim  d->AddItem("trace", trace_sp);
148320384Sdim  return dict_sp;
149320384Sdim}
150320384Sdim
151320384Sdimbool MainThreadCheckerRuntime::NotifyBreakpointHit(
152320384Sdim    void *baton, StoppointCallbackContext *context, user_id_t break_id,
153320384Sdim    user_id_t break_loc_id) {
154320384Sdim  assert(baton && "null baton");
155320384Sdim  if (!baton)
156360784Sdim    return false; ///< false => resume execution.
157320384Sdim
158320384Sdim  MainThreadCheckerRuntime *const instance =
159320384Sdim      static_cast<MainThreadCheckerRuntime *>(baton);
160320384Sdim
161320384Sdim  ProcessSP process_sp = instance->GetProcessSP();
162320384Sdim  ThreadSP thread_sp = context->exe_ctx_ref.GetThreadSP();
163320384Sdim  if (!process_sp || !thread_sp ||
164320384Sdim      process_sp != context->exe_ctx_ref.GetProcessSP())
165320384Sdim    return false;
166320384Sdim
167320970Sdim  if (process_sp->GetModIDRef().IsLastResumeForUserExpression())
168320970Sdim    return false;
169320970Sdim
170320384Sdim  StructuredData::ObjectSP report =
171320384Sdim      instance->RetrieveReportData(context->exe_ctx_ref);
172320384Sdim
173320384Sdim  if (report) {
174320384Sdim    std::string description = report->GetAsDictionary()
175320384Sdim                                ->GetValueForKey("description")
176320384Sdim                                ->GetAsString()
177320384Sdim                                ->GetValue();
178320384Sdim    thread_sp->SetStopInfo(
179320384Sdim        InstrumentationRuntimeStopInfo::CreateStopReasonWithInstrumentationData(
180320384Sdim            *thread_sp, description, report));
181320384Sdim    return true;
182320384Sdim  }
183320384Sdim
184320384Sdim  return false;
185320384Sdim}
186320384Sdim
187320384Sdimvoid MainThreadCheckerRuntime::Activate() {
188320384Sdim  if (IsActive())
189320384Sdim    return;
190320384Sdim
191320384Sdim  ProcessSP process_sp = GetProcessSP();
192320384Sdim  if (!process_sp)
193320384Sdim    return;
194320384Sdim
195320384Sdim  ModuleSP runtime_module_sp = GetRuntimeModuleSP();
196320384Sdim
197320384Sdim  ConstString symbol_name("__main_thread_checker_on_report");
198320384Sdim  const Symbol *symbol = runtime_module_sp->FindFirstSymbolWithNameAndType(
199320384Sdim      symbol_name, eSymbolTypeCode);
200320384Sdim
201320384Sdim  if (symbol == nullptr)
202320384Sdim    return;
203320384Sdim
204320384Sdim  if (!symbol->ValueIsAddress() || !symbol->GetAddressRef().IsValid())
205320384Sdim    return;
206320384Sdim
207320384Sdim  Target &target = process_sp->GetTarget();
208320384Sdim  addr_t symbol_address = symbol->GetAddressRef().GetOpcodeLoadAddress(&target);
209320384Sdim
210320384Sdim  if (symbol_address == LLDB_INVALID_ADDRESS)
211320384Sdim    return;
212320384Sdim
213320384Sdim  Breakpoint *breakpoint =
214320384Sdim      process_sp->GetTarget()
215320384Sdim          .CreateBreakpoint(symbol_address, /*internal=*/true,
216320384Sdim                            /*hardware=*/false)
217320384Sdim          .get();
218320384Sdim  breakpoint->SetCallback(MainThreadCheckerRuntime::NotifyBreakpointHit, this,
219320384Sdim                          true);
220320384Sdim  breakpoint->SetBreakpointKind("main-thread-checker-report");
221320384Sdim  SetBreakpointID(breakpoint->GetID());
222320384Sdim
223320384Sdim  SetActive(true);
224320384Sdim}
225320384Sdim
226320384Sdimvoid MainThreadCheckerRuntime::Deactivate() {
227320384Sdim  SetActive(false);
228320384Sdim
229320384Sdim  auto BID = GetBreakpointID();
230320384Sdim  if (BID == LLDB_INVALID_BREAK_ID)
231320384Sdim    return;
232320384Sdim
233320384Sdim  if (ProcessSP process_sp = GetProcessSP()) {
234320384Sdim    process_sp->GetTarget().RemoveBreakpointByID(BID);
235320384Sdim    SetBreakpointID(LLDB_INVALID_BREAK_ID);
236320384Sdim  }
237320384Sdim}
238320384Sdim
239320384Sdimlldb::ThreadCollectionSP
240320384SdimMainThreadCheckerRuntime::GetBacktracesFromExtendedStopInfo(
241320384Sdim    StructuredData::ObjectSP info) {
242320384Sdim  ThreadCollectionSP threads;
243353358Sdim  threads = std::make_shared<ThreadCollection>();
244360784Sdim
245320384Sdim  ProcessSP process_sp = GetProcessSP();
246360784Sdim
247320384Sdim  if (info->GetObjectForDotSeparatedPath("instrumentation_class")
248320384Sdim      ->GetStringValue() != "MainThreadChecker")
249320384Sdim    return threads;
250360784Sdim
251320384Sdim  std::vector<lldb::addr_t> PCs;
252320384Sdim  auto trace = info->GetObjectForDotSeparatedPath("trace")->GetAsArray();
253320384Sdim  trace->ForEach([&PCs](StructuredData::Object *PC) -> bool {
254320384Sdim    PCs.push_back(PC->GetAsInteger()->GetValue());
255320384Sdim    return true;
256320384Sdim  });
257360784Sdim
258320384Sdim  if (PCs.empty())
259320384Sdim    return threads;
260360784Sdim
261320384Sdim  StructuredData::ObjectSP thread_id_obj =
262320384Sdim      info->GetObjectForDotSeparatedPath("tid");
263320384Sdim  tid_t tid = thread_id_obj ? thread_id_obj->GetIntegerValue() : 0;
264353358Sdim
265353358Sdim  HistoryThread *history_thread = new HistoryThread(*process_sp, tid, PCs);
266320384Sdim  ThreadSP new_thread_sp(history_thread);
267360784Sdim
268341825Sdim  // Save this in the Process' ExtendedThreadList so a strong pointer retains
269341825Sdim  // the object
270320384Sdim  process_sp->GetExtendedThreadList().AddThread(new_thread_sp);
271320384Sdim  threads->AddThread(new_thread_sp);
272320384Sdim
273320384Sdim  return threads;
274320384Sdim}
275