1//===-- copyprof.cpp ------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// This file implements the core CopyProf runtime initialization and callback
10/// functions inserted by the instrumentation passes.
11///
12//===----------------------------------------------------------------------===//
13
14#include "copyprof_interface_internal.h"
15#include "copyprof_reporting.h"
16#include "copyprof_shadow.h"
17#include "copyprof_state.h"
18#include "sanitizer_common/sanitizer_common.h"
19#include "sanitizer_common/sanitizer_flags.h"
20#include "sanitizer_common/sanitizer_internal_defs.h"
21
22using namespace __copyprof;
23
24namespace __copyprof {
25
26// Whether CopyProf has been initialized.
27bool copyprof_is_initialized;
28// Whether CopyProf is currently initializing.
29bool copyprof_init_is_running;
30
31static void CheckUnwind() {
32 UNINITIALIZED BufferedStackTrace trace;
33 trace.Unwind(pc: StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(),
34 /*context=*/nullptr, request_fast: common_flags()->fast_unwind_on_check);
35 trace.Print();
36}
37
38static void Initialize() {
39 if (LIKELY(copyprof_is_initialized))
40 return;
41 CHECK(!copyprof_init_is_running &&
42 "BUG: CopyProf Initialize() must not call itself.");
43 copyprof_init_is_running = true;
44 CacheBinaryName();
45 SetCheckUnwindCallback(&CheckUnwind);
46 InitializePlatformEarly();
47 SetCommonFlagsDefaults();
48 InitializeCommonFlags();
49 InitializeShadowMemory();
50 copyprof_init_is_running = false;
51 copyprof_is_initialized = true;
52}
53
54static void MaybeUpdateSmfContext(SmfContext context) {
55 // Only entering a top level special member function changes the current
56 // context. The context logically remains the same until control flow leaves
57 // the top level function.
58 if (__copyprof_state.construct_nesting_level == 0 &&
59 __copyprof_state.copy_nesting_level == 0 &&
60 __copyprof_state.destruct_nesting_level == 0) {
61 __copyprof_state.smf_context = context;
62 }
63}
64
65// Updates shadow memory for `[addr, addr + size)` according to the current SMF
66// context: no update in `DTOR` context (objects may mutate their memory during
67// destruction, but that must not invalidate its classification as an
68// unnecessary copy), marked as copy in `COPY` context, and marked as non-copy
69// otherwise.
70static void UpdateShadow(const void* addr, uptr size) {
71 if (UNLIKELY(__copyprof_state.smf_context == SmfContext::DTOR))
72 return;
73 MarkApplicationMemory(app_addr: addr, num_bytes: size,
74 is_copy: __copyprof_state.smf_context == SmfContext::COPY);
75}
76
77static void CopyMemberFunctionEnter(const void* this_ptr, uptr obj_size) {
78 MaybeUpdateSmfContext(context: SmfContext::COPY);
79 if (__copyprof_state.copy_nesting_level++ == 0) {
80 __copyprof_state.current_this_ptr = this_ptr;
81 }
82 UpdateShadow(addr: this_ptr, size: obj_size);
83}
84
85static void CopyMemberFunctionExit(const void* this_ptr, uptr obj_size) {
86 --__copyprof_state.copy_nesting_level;
87 MaybeUpdateSmfContext(context: SmfContext::NONE);
88}
89
90} // namespace __copyprof
91
92void __copyprof_init_once() { Initialize(); }
93
94void __copyprof_ctor_enter_callback(const void* this_ptr, uptr obj_size) {
95 MaybeUpdateSmfContext(context: SmfContext::CTOR);
96 ++__copyprof_state.construct_nesting_level;
97 UpdateShadow(addr: this_ptr, size: obj_size);
98}
99
100void __copyprof_ctor_exit_callback(const void* this_ptr, uptr obj_size) {
101 --__copyprof_state.construct_nesting_level;
102 MaybeUpdateSmfContext(context: SmfContext::NONE);
103}
104
105void __copyprof_copy_ctor_enter_callback(const void* this_ptr,
106 const void* other_ptr, uptr obj_size) {
107 CopyMemberFunctionEnter(this_ptr, obj_size);
108}
109
110void __copyprof_copy_ctor_exit_callback(const void* this_ptr,
111 const void* other_ptr, uptr obj_size) {
112 CopyMemberFunctionExit(this_ptr, obj_size);
113}
114
115void __copyprof_copy_assign_op_enter_callback(const void* this_ptr,
116 const void* other_ptr,
117 uptr obj_size) {
118 CopyMemberFunctionEnter(this_ptr, obj_size);
119}
120
121void __copyprof_copy_assign_op_exit_callback(const void* this_ptr,
122 const void* other_ptr,
123 uptr obj_size) {
124 CopyMemberFunctionExit(this_ptr, obj_size);
125}
126
127void __copyprof_dtor_enter_callback(const void* this_ptr, uptr obj_size) {
128 MaybeUpdateSmfContext(context: SmfContext::DTOR);
129 if (__copyprof_state.destruct_nesting_level++ == 0) {
130 // Control flow just entered the top-level d'tor. Optimistically mark
131 // `is_transitive_copy` as `true`. If any subsequent d'tor observes object
132 // memory marked as not copy, then the flag will be set to `false`.
133 __copyprof_state.is_transitive_copy = true;
134 }
135}
136
137void __copyprof_dtor_exit_callback(const void* this_ptr, uptr obj_size) {
138 --__copyprof_state.destruct_nesting_level;
139 MaybeUpdateSmfContext(context: SmfContext::NONE);
140 // If this is the top level-dtor and `this` is transitively marked as copy,
141 // then an object has been found whose transitively owned memory is marked as
142 // copy, so a report is logged.
143 __copyprof_state.is_transitive_copy &= IsMarkedAsCopy(app_addr: this_ptr, num_bytes: obj_size);
144 if (__copyprof_state.destruct_nesting_level == 0 &&
145 __copyprof_state.is_transitive_copy) {
146 // TODO: Dynamic allocation tracking via malloc/new interception will be
147 // added in a subsequent patch. For now, did_allocate is set to true.
148 LogCopyProfReport(GET_CALLER_PC(), GET_CURRENT_FRAME(), obj_size,
149 /*did_allocate=*/true);
150 }
151}
152
153void __copyprof_store_callback(const void* addr, uptr size) {
154 UpdateShadow(addr, size);
155}
156