| //===-- copyprof.cpp ------------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// This file implements the core CopyProf runtime initialization and callback |
| /// functions inserted by the instrumentation passes. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "copyprof_interface_internal.h" |
| #include "copyprof_reporting.h" |
| #include "copyprof_shadow.h" |
| #include "copyprof_state.h" |
| #include "sanitizer_common/sanitizer_common.h" |
| #include "sanitizer_common/sanitizer_flags.h" |
| #include "sanitizer_common/sanitizer_internal_defs.h" |
| |
| using namespace __copyprof; |
| |
| namespace __copyprof { |
| |
| // Whether CopyProf has been initialized. |
| bool copyprof_is_initialized; |
| // Whether CopyProf is currently initializing. |
| bool copyprof_init_is_running; |
| |
| static void CheckUnwind() { |
| UNINITIALIZED BufferedStackTrace trace; |
| trace.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), |
| /*context=*/nullptr, common_flags()->fast_unwind_on_check); |
| trace.Print(); |
| } |
| |
| static void Initialize() { |
| if (LIKELY(copyprof_is_initialized)) |
| return; |
| CHECK(!copyprof_init_is_running && |
| "BUG: CopyProf Initialize() must not call itself."); |
| copyprof_init_is_running = true; |
| CacheBinaryName(); |
| SetCheckUnwindCallback(&CheckUnwind); |
| InitializePlatformEarly(); |
| SetCommonFlagsDefaults(); |
| InitializeCommonFlags(); |
| InitializeShadowMemory(); |
| copyprof_init_is_running = false; |
| copyprof_is_initialized = true; |
| } |
| |
| static void MaybeUpdateSmfContext(SmfContext context) { |
| // Only entering a top level special member function changes the current |
| // context. The context logically remains the same until control flow leaves |
| // the top level function. |
| if (__copyprof_state.construct_nesting_level == 0 && |
| __copyprof_state.copy_nesting_level == 0 && |
| __copyprof_state.destruct_nesting_level == 0) { |
| __copyprof_state.smf_context = context; |
| } |
| } |
| |
| // Updates shadow memory for `[addr, addr + size)` according to the current SMF |
| // context: no update in `DTOR` context (objects may mutate their memory during |
| // destruction, but that must not invalidate its classification as an |
| // unnecessary copy), marked as copy in `COPY` context, and marked as non-copy |
| // otherwise. |
| static void UpdateShadow(const void* addr, uptr size) { |
| if (UNLIKELY(__copyprof_state.smf_context == SmfContext::DTOR)) |
| return; |
| MarkApplicationMemory(addr, size, |
| __copyprof_state.smf_context == SmfContext::COPY); |
| } |
| |
| static void CopyMemberFunctionEnter(const void* this_ptr, uptr obj_size) { |
| MaybeUpdateSmfContext(SmfContext::COPY); |
| if (__copyprof_state.copy_nesting_level++ == 0) { |
| __copyprof_state.current_this_ptr = this_ptr; |
| } |
| UpdateShadow(this_ptr, obj_size); |
| } |
| |
| static void CopyMemberFunctionExit(const void* this_ptr, uptr obj_size) { |
| --__copyprof_state.copy_nesting_level; |
| MaybeUpdateSmfContext(SmfContext::NONE); |
| } |
| |
| } // namespace __copyprof |
| |
| void __copyprof_init_once() { Initialize(); } |
| |
| void __copyprof_ctor_enter_callback(const void* this_ptr, uptr obj_size) { |
| MaybeUpdateSmfContext(SmfContext::CTOR); |
| ++__copyprof_state.construct_nesting_level; |
| UpdateShadow(this_ptr, obj_size); |
| } |
| |
| void __copyprof_ctor_exit_callback(const void* this_ptr, uptr obj_size) { |
| --__copyprof_state.construct_nesting_level; |
| MaybeUpdateSmfContext(SmfContext::NONE); |
| } |
| |
| void __copyprof_copy_ctor_enter_callback(const void* this_ptr, |
| const void* other_ptr, uptr obj_size) { |
| CopyMemberFunctionEnter(this_ptr, obj_size); |
| } |
| |
| void __copyprof_copy_ctor_exit_callback(const void* this_ptr, |
| const void* other_ptr, uptr obj_size) { |
| CopyMemberFunctionExit(this_ptr, obj_size); |
| } |
| |
| void __copyprof_copy_assign_op_enter_callback(const void* this_ptr, |
| const void* other_ptr, |
| uptr obj_size) { |
| CopyMemberFunctionEnter(this_ptr, obj_size); |
| } |
| |
| void __copyprof_copy_assign_op_exit_callback(const void* this_ptr, |
| const void* other_ptr, |
| uptr obj_size) { |
| CopyMemberFunctionExit(this_ptr, obj_size); |
| } |
| |
| void __copyprof_dtor_enter_callback(const void* this_ptr, uptr obj_size) { |
| MaybeUpdateSmfContext(SmfContext::DTOR); |
| if (__copyprof_state.destruct_nesting_level++ == 0) { |
| // Control flow just entered the top-level d'tor. Optimistically mark |
| // `is_transitive_copy` as `true`. If any subsequent d'tor observes object |
| // memory marked as not copy, then the flag will be set to `false`. |
| __copyprof_state.is_transitive_copy = true; |
| } |
| } |
| |
| void __copyprof_dtor_exit_callback(const void* this_ptr, uptr obj_size) { |
| --__copyprof_state.destruct_nesting_level; |
| MaybeUpdateSmfContext(SmfContext::NONE); |
| // If this is the top level-dtor and `this` is transitively marked as copy, |
| // then an object has been found whose transitively owned memory is marked as |
| // copy, so a report is logged. |
| __copyprof_state.is_transitive_copy &= IsMarkedAsCopy(this_ptr, obj_size); |
| if (__copyprof_state.destruct_nesting_level == 0 && |
| __copyprof_state.is_transitive_copy) { |
| // TODO: Dynamic allocation tracking via malloc/new interception will be |
| // added in a subsequent patch. For now, did_allocate is set to true. |
| LogCopyProfReport(GET_CALLER_PC(), GET_CURRENT_FRAME(), obj_size, |
| /*did_allocate=*/true); |
| } |
| } |
| |
| void __copyprof_store_callback(const void* addr, uptr size) { |
| UpdateShadow(addr, size); |
| } |