1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "platform/globals.h" |
6 | |
7 | #if defined(DART_USE_TCMALLOC) && !defined(PRODUCT) |
8 | |
9 | #include "platform/assert.h" |
10 | #include "vm/globals.h" |
11 | #include "vm/malloc_hooks.h" |
12 | #include "vm/os.h" |
13 | #include "vm/profiler.h" |
14 | #include "vm/profiler_service.h" |
15 | #include "vm/unit_test.h" |
16 | |
17 | namespace dart { |
18 | |
19 | static void MallocHookTestBufferInitializer(volatile char* buffer, |
20 | uintptr_t size) { |
21 | // Run through the buffer and do something. If we don't do this and the memory |
22 | // in buffer isn't touched, the tcmalloc hooks won't be called. |
23 | for (uintptr_t i = 0; i < size; ++i) { |
24 | buffer[i] = i; |
25 | } |
26 | } |
27 | |
28 | class EnableMallocHooksScope : public ValueObject { |
29 | public: |
30 | EnableMallocHooksScope() { |
31 | OSThread::Current(); // Ensure not allocated during test. |
32 | saved_enable_malloc_hooks_ = FLAG_profiler_native_memory; |
33 | FLAG_profiler_native_memory = true; |
34 | MallocHooks::Init(); |
35 | MallocHooks::ResetStats(); |
36 | } |
37 | |
38 | ~EnableMallocHooksScope() { |
39 | MallocHooks::Cleanup(); |
40 | FLAG_profiler_native_memory = saved_enable_malloc_hooks_; |
41 | } |
42 | |
43 | private: |
44 | bool saved_enable_malloc_hooks_; |
45 | }; |
46 | |
47 | class EnableMallocHooksAndStacksScope : public EnableMallocHooksScope { |
48 | public: |
49 | EnableMallocHooksAndStacksScope() { |
50 | OSThread::Current(); // Ensure not allocated during test. |
51 | saved_enable_stack_traces_ = MallocHooks::stack_trace_collection_enabled(); |
52 | MallocHooks::set_stack_trace_collection_enabled(true); |
53 | if (!FLAG_profiler) { |
54 | FLAG_profiler = true; |
55 | Profiler::Init(); |
56 | } |
57 | MallocHooks::ResetStats(); |
58 | } |
59 | |
60 | ~EnableMallocHooksAndStacksScope() { |
61 | MallocHooks::set_stack_trace_collection_enabled(saved_enable_stack_traces_); |
62 | } |
63 | |
64 | private: |
65 | bool saved_enable_stack_traces_; |
66 | }; |
67 | |
68 | UNIT_TEST_CASE(BasicMallocHookTest) { |
69 | EnableMallocHooksScope scope; |
70 | |
71 | EXPECT_EQ(0L, MallocHooks::allocation_count()); |
72 | EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes()); |
73 | const intptr_t buffer_size = 10; |
74 | char* buffer = new char[buffer_size]; |
75 | MallocHookTestBufferInitializer(buffer, buffer_size); |
76 | |
77 | EXPECT_EQ(1L, MallocHooks::allocation_count()); |
78 | EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size), |
79 | MallocHooks::heap_allocated_memory_in_bytes()); |
80 | |
81 | delete[] buffer; |
82 | EXPECT_EQ(0L, MallocHooks::allocation_count()); |
83 | EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes()); |
84 | } |
85 | |
86 | UNIT_TEST_CASE(FreeUnseenMemoryMallocHookTest) { |
87 | EnableMallocHooksScope scope; |
88 | |
89 | const intptr_t pre_hook_buffer_size = 3; |
90 | char* pre_hook_buffer = new char[pre_hook_buffer_size]; |
91 | MallocHookTestBufferInitializer(pre_hook_buffer, pre_hook_buffer_size); |
92 | |
93 | MallocHooks::ResetStats(); |
94 | EXPECT_EQ(0L, MallocHooks::allocation_count()); |
95 | EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes()); |
96 | |
97 | const intptr_t buffer_size = 10; |
98 | char* buffer = new char[buffer_size]; |
99 | MallocHookTestBufferInitializer(buffer, buffer_size); |
100 | |
101 | EXPECT_EQ(1L, MallocHooks::allocation_count()); |
102 | EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size), |
103 | MallocHooks::heap_allocated_memory_in_bytes()); |
104 | |
105 | delete[] pre_hook_buffer; |
106 | EXPECT_EQ(1L, MallocHooks::allocation_count()); |
107 | EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size), |
108 | MallocHooks::heap_allocated_memory_in_bytes()); |
109 | |
110 | delete[] buffer; |
111 | EXPECT_EQ(0L, MallocHooks::allocation_count()); |
112 | EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes()); |
113 | } |
114 | |
115 | VM_UNIT_TEST_CASE(StackTraceMallocHookSimpleTest) { |
116 | EnableMallocHooksAndStacksScope scope; |
117 | |
118 | char* var = static_cast<char*>(malloc(16 * sizeof(char))); |
119 | Sample* sample = MallocHooks::GetSample(var); |
120 | EXPECT(sample != NULL); |
121 | |
122 | free(var); |
123 | sample = MallocHooks::GetSample(var); |
124 | EXPECT(sample == NULL); |
125 | } |
126 | |
127 | static char* DART_NOINLINE StackTraceLengthHelper(uintptr_t* end_address) { |
128 | char* var = static_cast<char*>(malloc(16 * sizeof(char))); |
129 | *end_address = OS::GetProgramCounter(); |
130 | return var; |
131 | } |
132 | |
133 | VM_UNIT_TEST_CASE(StackTraceMallocHookLengthTest) { |
134 | EnableMallocHooksAndStacksScope scope; |
135 | |
136 | uintptr_t test_start_address = |
137 | reinterpret_cast<uintptr_t>(Dart_TestStackTraceMallocHookLengthTest); |
138 | uintptr_t helper_start_address = |
139 | reinterpret_cast<uintptr_t>(StackTraceLengthHelper); |
140 | uintptr_t helper_end_address = 0; |
141 | |
142 | char* var = StackTraceLengthHelper(&helper_end_address); |
143 | Sample* sample = MallocHooks::GetSample(var); |
144 | EXPECT(sample != NULL); |
145 | uintptr_t test_end_address = OS::GetProgramCounter(); |
146 | |
147 | // Ensure that all stack frames are where we expect them to be in the sample. |
148 | // If they aren't, the kSkipCount constant in malloc_hooks.cc is likely |
149 | // incorrect. |
150 | uword address = sample->At(0); |
151 | bool first_result = |
152 | (helper_start_address <= address) && (helper_end_address >= address); |
153 | EXPECT(first_result); |
154 | address = sample->At(1); |
155 | bool second_result = |
156 | (test_start_address <= address) && (test_end_address >= address); |
157 | EXPECT(second_result); |
158 | |
159 | if (!(first_result && second_result)) { |
160 | OS::PrintErr( |
161 | "If this test is failing, it's likely that the value set for" |
162 | " the number of frames to skip in malloc_hooks.cc is " |
163 | "incorrect for this configuration/platform. This value can be" |
164 | " found in malloc_hooks.cc in the AllocationInfo class, and " |
165 | "is stored in the kSkipCount constant.\n" ); |
166 | OS::PrintErr("First result: %d Second Result: %d\n" , first_result, |
167 | second_result); |
168 | OS::PrintErr("Dumping sample stack trace:\n" ); |
169 | sample->DumpStackTrace(); |
170 | } |
171 | |
172 | free(var); |
173 | } |
174 | |
175 | ISOLATE_UNIT_TEST_CASE(StackTraceMallocHookSimpleJSONTest) { |
176 | EnableMallocHooksAndStacksScope scope; |
177 | |
178 | ClearProfileVisitor cpv(Isolate::Current()); |
179 | Profiler::sample_buffer()->VisitSamples(&cpv); |
180 | |
181 | char* var = static_cast<char*>(malloc(16 * sizeof(char))); |
182 | JSONStream js; |
183 | ProfilerService::PrintNativeAllocationJSON(&js, -1, -1, false); |
184 | const char* json = js.ToCString(); |
185 | |
186 | // Check that all the stack frames from the current down to main are actually |
187 | // present in the profile. This is just a simple sanity check to make sure |
188 | // that the ProfileTrie has a representation of the stack trace collected when |
189 | // var is allocated. More intense testing is already done in profiler_test.cc. |
190 | // This is brittle: inlining and ICF in the C compiler and linker will affect |
191 | // the frames we see. |
192 | EXPECT_SUBSTRING("\"dart::Dart_TestStackTraceMallocHookSimpleJSONTest()\"" , |
193 | json); |
194 | EXPECT_SUBSTRING("\"dart::TestCase::Run()\"" , json); |
195 | EXPECT_SUBSTRING("\"dart::TestCaseBase::RunTest()\"" , json); |
196 | EXPECT_SUBSTRING("\"main\"" , json); |
197 | |
198 | free(var); |
199 | } |
200 | |
201 | }; // namespace dart |
202 | |
203 | #endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT) |
204 | |