/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include #include #include #include "SandboxInfo.h" #include "SandboxProfilerChild.h" #include "SandboxProfiler.h" #include "mozilla/Atomics.h" #include "mozilla/StaticPtr.h" #include "mozilla/PodOperations.h" namespace mozilla { #if defined(DEBUG) thread_local Atomic sInSignalContext = Atomic(false); AutoForbidSignalContext::AutoForbidSignalContext() { sInSignalContext = true; } AutoForbidSignalContext::~AutoForbidSignalContext() { sInSignalContext = false; } #endif // defined(DEBUG) static StaticAutoPtr gProfiler; static StaticAutoPtr gSyscallsQueue; static StaticAutoPtr gLogsQueue; static Atomic isShutdown = Atomic(false); // Those function pointers are set by the main thread, and subsequently read by // all other thread, in particular in SIGSYS handlers. struct UprofilerFuncPtrs uprofiler; bool uprofiler_initted = false; static bool const SANDBOX_PROFILER_DEBUG = false; // Semaphores that we use to signal the SandboxProfilerEmitter thread when data // has been pushed to the SandboxProfilerQueue static sem_t gSyscallRequest; static sem_t gLogsRequest; // This is only be called on main thread, and not within SIGSYS context // // We might be called either from the profiler-started notification observer in // which case the !Active() call is not useful, but also directly from Sandbox' // SandboxLateInit where we want to verify if we are not already active: that // can happen if the user started the profiler via MOZ_PROFILER_STARTUP=1 /* static */ void SandboxProfiler::Create() { MOZ_ASSERT(!sInSignalContext, "SandboxProfiler::Create called in SIGSYS handler"); if (!Init()) { return; } if (!Active()) { return; } if (!gSyscallsQueue) { gSyscallsQueue = new SandboxProfilerQueue(15); } if (!gLogsQueue) { gLogsQueue = new SandboxProfilerQueue(15); } if (!gProfiler) { gProfiler = new SandboxProfiler(); } } SandboxProfiler::SandboxProfiler() { mThreadLogs = std::thread(&SandboxProfiler::ThreadMain, this, "SandboxProfilerEmitterLogs", gLogsQueue.get(), &gLogsRequest); mThreadSyscalls = std::thread(&SandboxProfiler::ThreadMain, this, "SandboxProfilerEmitterSyscalls", gSyscallsQueue.get(), &gSyscallRequest); } /* static */ void SandboxProfiler::Shutdown() { isShutdown = true; if (gProfiler) { // Unblock remaining SandboxProfiler::Signal(&gSyscallRequest); SandboxProfiler::Signal(&gLogsRequest); } gProfiler = nullptr; gSyscallsQueue = nullptr; gLogsQueue = nullptr; } SandboxProfiler::~SandboxProfiler() { if (mThreadLogs.joinable()) { mThreadLogs.join(); } if (mThreadSyscalls.joinable()) { mThreadSyscalls.join(); } } /* static */ bool SandboxProfiler::ActiveWithQueue(SandboxProfilerQueue* aQueue) { return !isShutdown && gProfiler && Active() && aQueue; } /* static */ void SandboxProfiler::Signal(sem_t* aSem) { if (sem_post(aSem) < 0) { if constexpr (SANDBOX_PROFILER_DEBUG) { fprintf(stderr, "[%d] %s SEM_POST errno=%d\n", getpid(), __PRETTY_FUNCTION__, errno); } } } /* static */ int SandboxProfiler::Wait(sem_t* aSem) { return sem_wait(aSem); } /* static */ void SandboxProfiler::ReportInit(const void* top) { if (!ActiveWithQueue(gSyscallsQueue)) { return; } SandboxProfilerPayload payload = { .mStack = NativeStack{.mCount = 0}, .mType = SandboxProfilerPayloadType::Init, }; uprofiler.native_backtrace(top, &payload.mStack); MOZ_ASSERT(gSyscallsQueue, "Queue is valid for Send() from ReportInit()"); if (!gSyscallsQueue) { if constexpr (SANDBOX_PROFILER_DEBUG) { fprintf(stderr, "[%d] WARNING: Hello PRODUCER: gSyscallsQueue disappeared\n", getpid()); } return; } int rv = gSyscallsQueue->Send(payload); if (rv == 0) { if constexpr (SANDBOX_PROFILER_DEBUG) { fprintf(stderr, "[%d] WARNING: Hello PRODUCER: one stack mCount=%zu DROPPED\n", getpid(), payload.mStack.mCount); } } SandboxProfiler::Signal(&gSyscallRequest); } void SandboxProfiler::ReportInitImpl(SandboxProfilerPayload& payload, ProfileChunkedBuffer& buffer) { const char buf[] = "uprofiler init"; std::array arg_names = {"init"}; std::array arg_types = { TRACE_VALUE_TYPE_STRING, }; std::array arg_values = {reinterpret_cast(buf)}; Report("SandboxBroker::InitWithStack", arg_names, arg_types, arg_values, &buffer); } /* static */ void SandboxProfiler::ReportLog(const char* aBuf) { if (!ActiveWithQueue(gLogsQueue)) { return; } if (!SandboxInfo::Get().Test(SandboxInfo::kVerbose) && !SandboxInfo::Get().Test(SandboxInfo::kVerboseTests)) { return; } SandboxProfilerPayload payload = { .mStack = NativeStack{.mCount = 0}, .mType = SandboxProfilerPayloadType::Log, }; const size_t bufLen = strnlen(aBuf, PATH_MAX); PodCopy(payload.mPath, aBuf, bufLen); MOZ_ASSERT(gLogsQueue, "Queue is valid for Send() from ReportLog()"); if (!gLogsQueue) { if constexpr (SANDBOX_PROFILER_DEBUG) { fprintf(stderr, "[%d] WARNING: Hello PRODUCER: gLogsQueue disappeared\n", getpid()); } return; } int rv = gLogsQueue->Send(payload); if (rv == 0) { if constexpr (SANDBOX_PROFILER_DEBUG) { fprintf(stderr, "[%d] WARNING: Hello PRODUCER: one log stack DROPPED\n", getpid()); } } SandboxProfiler::Signal(&gLogsRequest); } void SandboxProfiler::ReportLogImpl(SandboxProfilerPayload& payload) { std::array arg_names = {"log"}; std::array arg_types = { TRACE_VALUE_TYPE_STRING, }; std::array arg_values = { reinterpret_cast(payload.mPath), }; Report("SandboxBroker::Log", arg_names, arg_types, arg_values, nullptr); } /* static */ void SandboxProfiler::ReportRequest(const void* top, uint64_t aId, const char* aOp, int aFlags, const char* aPath, const char* aPath2, pid_t aPid) { if (!ActiveWithQueue(gSyscallsQueue)) { return; } // Take a stack, this should be safe to do in the context of SIGSYS SandboxProfilerPayload payload = { .mStack = NativeStack{.mCount = 0}, .mId = aId, .mOp = aOp, .mFlags = aFlags, .mPid = aPid, .mType = SandboxProfilerPayloadType::Request, }; if (aPath) { const size_t pathLen = strnlen(aPath, PATH_MAX); PodCopy(payload.mPath, aPath, pathLen); } else { payload.mPath[0] = '\0'; } if (aPath2) { const size_t path2Len = strnlen(aPath2, PATH_MAX); PodCopy(payload.mPath2, aPath2, path2Len); } else { payload.mPath2[0] = '\0'; } uprofiler.native_backtrace(top, &payload.mStack); MOZ_ASSERT(gSyscallsQueue, "Queue is valid for Send() from ReportRequest()"); if (!gSyscallsQueue) { if constexpr (SANDBOX_PROFILER_DEBUG) { fprintf(stderr, "[%d] WARNING: Hello PRODUCER: gSyscallsQueue disappeared\n", getpid()); } return; } int rv = gSyscallsQueue->Send(payload); if (rv == 0) { if constexpr (SANDBOX_PROFILER_DEBUG) { fprintf(stderr, "[%d] WARNING: Hello PRODUCER: one stack mCount=%zu DROPPED\n", getpid(), payload.mStack.mCount); } } SandboxProfiler::Signal(&gSyscallRequest); } void SandboxProfiler::ReportRequestImpl(SandboxProfilerPayload& payload, ProfileChunkedBuffer& buffer) { std::array arg_names = {"id", "op", "rflags", "path", "path2", "pid"}; std::array arg_types = { TRACE_VALUE_TYPE_UINT, // id TRACE_VALUE_TYPE_STRING, // op TRACE_VALUE_TYPE_UINT, // rflags TRACE_VALUE_TYPE_STRING, // path TRACE_VALUE_TYPE_STRING, // path2 TRACE_VALUE_TYPE_UINT // pid }; std::array arg_values = {static_cast(payload.mId), reinterpret_cast(payload.mOp), static_cast(payload.mFlags), reinterpret_cast(payload.mPath), reinterpret_cast(payload.mPath2), static_cast(payload.mPid)}; Report("SandboxBrokerClient", arg_names, arg_types, arg_values, &buffer); } void SandboxProfiler::ThreadMain(const char* aThreadName, SandboxProfilerQueue* aQueue, sem_t* aRequest) { uprofiler.register_thread(aThreadName, CallerPC()); SandboxProfilerPayload p; DebugOnly sem_init_rv = sem_init(aRequest, /* pshared */ 0, /* value */ 0); MOZ_ASSERT(sem_init_rv == 0, "Failure to initialize semaphore"); while (!isShutdown) { errno = 0; if (SandboxProfiler::Wait(aRequest) < 0) { int _errno = errno; MOZ_ASSERT(_errno != EINVAL, "sem_wait() returned EINVAL"); if (_errno == EAGAIN || _errno == EINTR) { continue; } } MOZ_ASSERT(aQueue, "Syscalls queue is valid for Recv()"); if (!aQueue) { if constexpr (SANDBOX_PROFILER_DEBUG) { fprintf(stderr, "[%d] WARNING: Hello CONSUMER [%s]: aQueue disappeared\n", getpid(), aThreadName); } continue; } int deq = aQueue->Recv(&p); if (deq > 0) { switch (p.mType) { case SandboxProfilerPayloadType::Init: case SandboxProfilerPayloadType::Request: { ProfileBufferChunkManagerSingle chunkManager{ mozilla::ProfileBufferChunkManager::scExpectedMaximumStackSize}; ProfileChunkedBuffer chunkedBuffer{ ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager}; uprofiler.backtrace_into_buffer(&p.mStack, &chunkedBuffer); switch (p.mType) { case SandboxProfilerPayloadType::Init: ReportInitImpl(p, chunkedBuffer); break; case SandboxProfilerPayloadType::Request: ReportRequestImpl(p, chunkedBuffer); break; default: // impossible? MOZ_ASSERT_UNREACHABLE("Should have been Init/Request"); break; } } break; case SandboxProfilerPayloadType::Log: ReportLogImpl(p); break; default: fprintf(stderr, "[%d] mType=%hhu\n", getpid(), static_cast(p.mType)); MOZ_CRASH("Unsupported type"); break; } } } sem_destroy(aRequest); uprofiler.unregister_thread(); } } // namespace mozilla