| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // It is __imperative__ that the functions in this file are __not__ included in |
| // release or profile builds. |
| // |
| // They call into the "private" ptrace() API to ensure that the current process |
| // is being ptrace()-d. Only debug builds rely on ptrace(), and the ptrace() API |
| // is not allowed for use in the App Store, so we must exclude it from profile- |
| // and release-builds. |
| // |
| // When an app is launched from a host workstation (e.g. via Xcode or |
| // "ios-deploy"), the process is already ptrace()-d by debugserver. However, |
| // when an app is launched from the home screen, it is not, so for debug builds |
| // we initialize the ptrace() relationship via PT_TRACE_ME if necessary. |
| // |
| // Please see the following documents for more details: |
| // - go/decommissioning-dbc |
| // - go/decommissioning-dbc-engine |
| // - go/decommissioning-dbc-tools |
| |
| #include "flutter/runtime/ptrace_check.h" |
| |
| #if TRACING_CHECKS_NECESSARY |
| |
| #include <sys/sysctl.h> |
| #include <sys/types.h> |
| |
| #include <mutex> |
| |
| #include "flutter/fml/build_config.h" |
| |
| // Being extra careful and adding additional landmines that will prevent |
| // compilation of this TU in an incorrect runtime mode. |
| static_assert(OS_IOS, "This translation unit is iOS specific."); |
| static_assert(FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG, |
| "This translation unit must only be compiled in the debug " |
| "runtime mode as it " |
| "contains private API usage."); |
| |
| #define PT_TRACE_ME 0 |
| #define PT_SIGEXC 12 |
| extern "C" int ptrace(int request, pid_t pid, caddr_t addr, int data); |
| |
| namespace flutter { |
| |
| static bool IsLaunchedByFlutterCLI(const Settings& vm_settings) { |
| // Only the Flutter CLI passes "--enable-checked-mode". Therefore, if the flag |
| // is present, we have been launched by "ios-deploy" via "debugserver". |
| // |
| // We choose this flag because it is always passed to launch debug builds. |
| return vm_settings.enable_checked_mode; |
| } |
| |
| static bool IsLaunchedByXcode() { |
| // Use "sysctl()" to check if we're currently being debugged (e.g. by Xcode). |
| // We could also check "getppid() != 1" (launchd), but this is more direct. |
| const pid_t self = getpid(); |
| int mib[5] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, self, 0}; |
| |
| auto proc = std::make_unique<struct kinfo_proc>(); |
| size_t proc_size = sizeof(struct kinfo_proc); |
| if (::sysctl(mib, 4, proc.get(), &proc_size, nullptr, 0) < 0) { |
| FML_LOG(ERROR) << "Could not execute sysctl() to get current process info: " |
| << strerror(errno); |
| return false; |
| } |
| |
| return proc->kp_proc.p_flag & P_TRACED; |
| } |
| |
| static bool EnableTracingManually(const Settings& vm_settings) { |
| if (::ptrace(PT_TRACE_ME, 0, nullptr, 0) == -1) { |
| FML_LOG(ERROR) << "Could not call ptrace(PT_TRACE_ME): " << strerror(errno); |
| // No use trying PT_SIGEXC -- it's only needed if PT_TRACE_ME succeeds. |
| return false; |
| } |
| |
| if (::ptrace(PT_SIGEXC, 0, nullptr, 0) == -1) { |
| FML_LOG(ERROR) << "Could not call ptrace(PT_SIGEXC): " << strerror(errno); |
| return false; |
| } |
| |
| // The previous operation causes this process to not be reaped after it |
| // terminates (even if PT_SIGEXC fails). Issue a warning to the console every |
| // (approximiately) maxproc/10 leaks. See the links above for an explanation |
| // of this issue. |
| size_t maxproc = 0; |
| size_t maxproc_size = sizeof(size_t); |
| const int sysctl_result = |
| ::sysctlbyname("kern.maxproc", &maxproc, &maxproc_size, nullptr, 0); |
| if (sysctl_result < 0) { |
| FML_LOG(ERROR) |
| << "Could not execute sysctl() to determine process count limit: " |
| << strerror(errno); |
| return false; |
| } |
| |
| const char* warning = |
| "Launching a debug-mode app from the home screen may cause problems.\n" |
| "Please compile a profile-/release-build, launch your app via \"flutter " |
| "run\", or see https://github.com/flutter/flutter/wiki/" |
| "PID-leak-in-iOS-debug-builds-launched-from-home-screen for details."; |
| |
| if (vm_settings.verbose_logging // used for testing and also informative |
| || sysctl_result < 0 // could not determine maximum process count |
| || maxproc / 10 == 0 // avoid division (%) by 0 |
| || getpid() % (maxproc / 10) == 0) // warning every ~maxproc/10 leaks |
| { |
| FML_LOG(ERROR) << warning; |
| } |
| |
| return true; |
| } |
| |
| static bool EnableTracingIfNecessaryOnce(const Settings& vm_settings) { |
| if (IsLaunchedByFlutterCLI(vm_settings)) { |
| return true; |
| } |
| |
| if (IsLaunchedByXcode()) { |
| return true; |
| } |
| |
| return EnableTracingManually(vm_settings); |
| } |
| |
| static TracingResult sTracingResult = TracingResult::kNotAttempted; |
| |
| bool EnableTracingIfNecessaryImpl(const Settings& vm_settings) { |
| static std::once_flag tracing_flag; |
| |
| std::call_once(tracing_flag, [&vm_settings]() { |
| sTracingResult = EnableTracingIfNecessaryOnce(vm_settings) |
| ? TracingResult::kEnabled |
| : TracingResult::kDisabled; |
| }); |
| return sTracingResult != TracingResult::kDisabled; |
| } |
| |
| TracingResult GetTracingResultImpl() { |
| return sTracingResult; |
| } |
| |
| } // namespace flutter |
| |
| #endif // TRACING_CHECKS_NECESSARY |