1 #include "llvm/ProfileData/MemProf.h"
2 #include "llvm/ADT/DenseMap.h"
3 #include "llvm/ADT/MapVector.h"
4 #include "llvm/DebugInfo/DIContext.h"
5 #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
6 #include "llvm/IR/Function.h"
7 #include "llvm/IR/Value.h"
8 #include "llvm/Object/ObjectFile.h"
9 #include "llvm/ProfileData/InstrProf.h"
10 #include "llvm/ProfileData/MemProfData.inc"
11 #include "llvm/ProfileData/RawMemProfReader.h"
12 #include "llvm/Support/Error.h"
13 #include "llvm/Support/MD5.h"
14 #include "llvm/Support/raw_ostream.h"
15 #include "gmock/gmock.h"
16 #include "gtest/gtest.h"
17 
18 #include <initializer_list>
19 
20 namespace {
21 
22 using ::llvm::DIGlobal;
23 using ::llvm::DIInliningInfo;
24 using ::llvm::DILineInfo;
25 using ::llvm::DILineInfoSpecifier;
26 using ::llvm::DILocal;
27 using ::llvm::memprof::CallStackMap;
28 using ::llvm::memprof::MemInfoBlock;
29 using ::llvm::memprof::MemProfRecord;
30 using ::llvm::memprof::MemProfSchema;
31 using ::llvm::memprof::Meta;
32 using ::llvm::memprof::PortableMemInfoBlock;
33 using ::llvm::memprof::RawMemProfReader;
34 using ::llvm::memprof::SegmentEntry;
35 using ::llvm::object::SectionedAddress;
36 using ::llvm::symbolize::SymbolizableModule;
37 using ::testing::Return;
38 
39 class MockSymbolizer : public SymbolizableModule {
40 public:
41   MOCK_CONST_METHOD3(symbolizeInlinedCode,
42                      DIInliningInfo(SectionedAddress, DILineInfoSpecifier,
43                                     bool));
44   // Most of the methods in the interface are unused. We only mock the
45   // method that we expect to be called from the memprof reader.
46   virtual DILineInfo symbolizeCode(SectionedAddress, DILineInfoSpecifier,
47                                    bool) const {
48     llvm_unreachable("unused");
49   }
50   virtual DIGlobal symbolizeData(SectionedAddress) const {
51     llvm_unreachable("unused");
52   }
53   virtual std::vector<DILocal> symbolizeFrame(SectionedAddress) const {
54     llvm_unreachable("unused");
55   }
56   virtual bool isWin32Module() const { llvm_unreachable("unused"); }
57   virtual uint64_t getModulePreferredBase() const {
58     llvm_unreachable("unused");
59   }
60 };
61 
62 struct MockInfo {
63   std::string FunctionName;
64   uint32_t Line;
65   uint32_t StartLine;
66   uint32_t Column;
67   std::string FileName = "valid/path.cc";
68 };
69 DIInliningInfo makeInliningInfo(std::initializer_list<MockInfo> MockFrames) {
70   DIInliningInfo Result;
71   for (const auto &Item : MockFrames) {
72     DILineInfo Frame;
73     Frame.FunctionName = Item.FunctionName;
74     Frame.Line = Item.Line;
75     Frame.StartLine = Item.StartLine;
76     Frame.Column = Item.Column;
77     Frame.FileName = Item.FileName;
78     Result.addFrame(Frame);
79   }
80   return Result;
81 }
82 
83 llvm::SmallVector<SegmentEntry, 4> makeSegments() {
84   llvm::SmallVector<SegmentEntry, 4> Result;
85   // Mimic an entry for a non position independent executable.
86   Result.emplace_back(0x0, 0x40000, 0x0);
87   return Result;
88 }
89 
90 const DILineInfoSpecifier specifier() {
91   return DILineInfoSpecifier(
92       DILineInfoSpecifier::FileLineInfoKind::RawValue,
93       DILineInfoSpecifier::FunctionNameKind::LinkageName);
94 }
95 
96 MATCHER_P4(FrameContains, FunctionName, LineOffset, Column, Inline, "") {
97   const uint64_t ExpectedHash = llvm::Function::getGUID(FunctionName);
98   if (arg.Function != ExpectedHash) {
99     *result_listener << "Hash mismatch";
100     return false;
101   }
102   if (arg.LineOffset == LineOffset && arg.Column == Column &&
103       arg.IsInlineFrame == Inline) {
104     return true;
105   }
106   *result_listener << "LineOffset, Column or Inline mismatch";
107   return false;
108 }
109 
110 MATCHER_P(EqualsRecord, Want, "") {
111   if (arg == Want)
112     return true;
113 
114   std::string Explanation;
115   llvm::raw_string_ostream OS(Explanation);
116   OS << "\n Want: \n";
117   Want.print(OS);
118   OS << "\n Got: \n";
119   arg.print(OS);
120   OS.flush();
121 
122   *result_listener << Explanation;
123   return false;
124 }
125 
126 MemProfSchema getFullSchema() {
127   MemProfSchema Schema;
128 #define MIBEntryDef(NameTag, Name, Type) Schema.push_back(Meta::Name);
129 #include "llvm/ProfileData/MIBEntryDef.inc"
130 #undef MIBEntryDef
131   return Schema;
132 }
133 
134 TEST(MemProf, FillsValue) {
135   std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
136 
137   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
138                                                 specifier(), false))
139       .Times(1) // Only once since we remember invalid PCs.
140       .WillRepeatedly(Return(makeInliningInfo({
141           {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
142       })));
143 
144   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
145                                                 specifier(), false))
146       .Times(1) // Only once since we cache the result for future lookups.
147       .WillRepeatedly(Return(makeInliningInfo({
148           {"foo", 10, 5, 30},
149           {"bar", 201, 150, 20},
150       })));
151 
152   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
153                                                 specifier(), false))
154       .Times(1)
155       .WillRepeatedly(Return(makeInliningInfo({
156           {"xyz", 10, 5, 30},
157           {"abc", 10, 5, 30},
158       })));
159 
160   CallStackMap CSM;
161   CSM[0x1] = {0x1000, 0x2000, 0x3000};
162 
163   llvm::MapVector<uint64_t, MemInfoBlock> Prof;
164   Prof[0x1].AllocCount = 1;
165 
166   auto Seg = makeSegments();
167 
168   RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM);
169 
170   llvm::DenseMap<llvm::GlobalValue::GUID, MemProfRecord> Records;
171   for (const auto &Pair : Reader) {
172     Records.insert({Pair.first, Pair.second});
173   }
174 
175   // Mock program psuedocode and expected memprof record contents.
176   //
177   //                              AllocSite       CallSite
178   // inline foo() { new(); }         Y               N
179   // bar() { foo(); }                Y               Y
180   // inline xyz() { bar(); }         N               Y
181   // abc() { xyz(); }                N               Y
182 
183   // We expect 4 records. We attach alloc site data to foo and bar, i.e.
184   // all frames bottom up until we find a non-inline frame. We attach call site
185   // data to bar, xyz and abc.
186   ASSERT_EQ(Records.size(), 4U);
187 
188   // Check the memprof record for foo.
189   const llvm::GlobalValue::GUID FooId = MemProfRecord::getGUID("foo");
190   ASSERT_EQ(Records.count(FooId), 1U);
191   const MemProfRecord &Foo = Records[FooId];
192   ASSERT_EQ(Foo.AllocSites.size(), 1U);
193   EXPECT_EQ(Foo.AllocSites[0].Info.getAllocCount(), 1U);
194   EXPECT_THAT(Foo.AllocSites[0].CallStack[0],
195               FrameContains("foo", 5U, 30U, true));
196   EXPECT_THAT(Foo.AllocSites[0].CallStack[1],
197               FrameContains("bar", 51U, 20U, false));
198   EXPECT_THAT(Foo.AllocSites[0].CallStack[2],
199               FrameContains("xyz", 5U, 30U, true));
200   EXPECT_THAT(Foo.AllocSites[0].CallStack[3],
201               FrameContains("abc", 5U, 30U, false));
202   EXPECT_TRUE(Foo.CallSites.empty());
203 
204   // Check the memprof record for bar.
205   const llvm::GlobalValue::GUID BarId = MemProfRecord::getGUID("bar");
206   ASSERT_EQ(Records.count(BarId), 1U);
207   const MemProfRecord &Bar = Records[BarId];
208   ASSERT_EQ(Bar.AllocSites.size(), 1U);
209   EXPECT_EQ(Bar.AllocSites[0].Info.getAllocCount(), 1U);
210   EXPECT_THAT(Bar.AllocSites[0].CallStack[0],
211               FrameContains("foo", 5U, 30U, true));
212   EXPECT_THAT(Bar.AllocSites[0].CallStack[1],
213               FrameContains("bar", 51U, 20U, false));
214   EXPECT_THAT(Bar.AllocSites[0].CallStack[2],
215               FrameContains("xyz", 5U, 30U, true));
216   EXPECT_THAT(Bar.AllocSites[0].CallStack[3],
217               FrameContains("abc", 5U, 30U, false));
218 
219   ASSERT_EQ(Bar.CallSites.size(), 1U);
220   ASSERT_EQ(Bar.CallSites[0].size(), 2U);
221   EXPECT_THAT(Bar.CallSites[0][0], FrameContains("foo", 5U, 30U, true));
222   EXPECT_THAT(Bar.CallSites[0][1], FrameContains("bar", 51U, 20U, false));
223 
224   // Check the memprof record for xyz.
225   const llvm::GlobalValue::GUID XyzId = MemProfRecord::getGUID("xyz");
226   ASSERT_EQ(Records.count(XyzId), 1U);
227   const MemProfRecord &Xyz = Records[XyzId];
228   ASSERT_EQ(Xyz.CallSites.size(), 1U);
229   ASSERT_EQ(Xyz.CallSites[0].size(), 2U);
230   // Expect the entire frame even though in practice we only need the first
231   // entry here.
232   EXPECT_THAT(Xyz.CallSites[0][0], FrameContains("xyz", 5U, 30U, true));
233   EXPECT_THAT(Xyz.CallSites[0][1], FrameContains("abc", 5U, 30U, false));
234 
235   // Check the memprof record for abc.
236   const llvm::GlobalValue::GUID AbcId = MemProfRecord::getGUID("abc");
237   ASSERT_EQ(Records.count(AbcId), 1U);
238   const MemProfRecord &Abc = Records[AbcId];
239   EXPECT_TRUE(Abc.AllocSites.empty());
240   ASSERT_EQ(Abc.CallSites.size(), 1U);
241   ASSERT_EQ(Abc.CallSites[0].size(), 2U);
242   EXPECT_THAT(Abc.CallSites[0][0], FrameContains("xyz", 5U, 30U, true));
243   EXPECT_THAT(Abc.CallSites[0][1], FrameContains("abc", 5U, 30U, false));
244 }
245 
246 TEST(MemProf, PortableWrapper) {
247   MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
248                     /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
249                     /*dealloc_cpu=*/4);
250 
251   const auto Schema = getFullSchema();
252   PortableMemInfoBlock WriteBlock(Info);
253 
254   std::string Buffer;
255   llvm::raw_string_ostream OS(Buffer);
256   WriteBlock.serialize(Schema, OS);
257   OS.flush();
258 
259   PortableMemInfoBlock ReadBlock(
260       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()));
261 
262   EXPECT_EQ(ReadBlock, WriteBlock);
263   // Here we compare directly with the actual counts instead of MemInfoBlock
264   // members. Since the MemInfoBlock struct is packed and the EXPECT_EQ macros
265   // take a reference to the params, this results in unaligned accesses.
266   EXPECT_EQ(1UL, ReadBlock.getAllocCount());
267   EXPECT_EQ(7ULL, ReadBlock.getTotalAccessCount());
268   EXPECT_EQ(3UL, ReadBlock.getAllocCpuId());
269 }
270 
271 TEST(MemProf, RecordSerializationRoundTrip) {
272   const MemProfSchema Schema = getFullSchema();
273 
274   MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
275                     /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
276                     /*dealloc_cpu=*/4);
277 
278   llvm::SmallVector<llvm::SmallVector<MemProfRecord::Frame>> AllocCallStacks = {
279       {{0x123, 1, 2, false}, {0x345, 3, 4, false}},
280       {{0x123, 1, 2, false}, {0x567, 5, 6, false}}};
281 
282   llvm::SmallVector<llvm::SmallVector<MemProfRecord::Frame>> CallSites = {
283       {{0x333, 1, 2, false}, {0x777, 3, 4, true}}};
284 
285   MemProfRecord Record;
286   for (const auto &ACS : AllocCallStacks) {
287     // Use the same info block for both allocation sites.
288     Record.AllocSites.emplace_back(ACS, Info);
289   }
290   Record.CallSites.assign(CallSites);
291 
292   std::string Buffer;
293   llvm::raw_string_ostream OS(Buffer);
294   Record.serialize(Schema, OS);
295   OS.flush();
296 
297   const MemProfRecord GotRecord = MemProfRecord::deserialize(
298       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()));
299 
300   EXPECT_THAT(GotRecord, EqualsRecord(Record));
301 }
302 
303 TEST(MemProf, SymbolizationFilter) {
304   std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
305 
306   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
307                                                 specifier(), false))
308       .Times(1) // once since we don't lookup invalid PCs repeatedly.
309       .WillRepeatedly(Return(makeInliningInfo({
310           {"malloc", 70, 57, 3, "memprof/memprof_malloc_linux.cpp"},
311       })));
312 
313   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
314                                                 specifier(), false))
315       .Times(1) // once since we don't lookup invalid PCs repeatedly.
316       .WillRepeatedly(Return(makeInliningInfo({
317           {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
318       })));
319 
320   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
321                                                 specifier(), false))
322       .Times(1) // once since we don't lookup invalid PCs repeatedly.
323       .WillRepeatedly(Return(makeInliningInfo({
324           {DILineInfo::BadString, 0, 0, 0},
325       })));
326 
327   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x4000},
328                                                 specifier(), false))
329       .Times(1)
330       .WillRepeatedly(Return(makeInliningInfo({
331           {"foo", 10, 5, 30},
332       })));
333 
334   CallStackMap CSM;
335   CSM[0x1] = {0x1000, 0x2000, 0x3000, 0x4000};
336   // This entry should be dropped since all PCs are either not
337   // symbolizable or belong to the runtime.
338   CSM[0x2] = {0x1000, 0x2000};
339 
340   llvm::MapVector<uint64_t, MemInfoBlock> Prof;
341   Prof[0x1].AllocCount = 1;
342   Prof[0x2].AllocCount = 1;
343 
344   auto Seg = makeSegments();
345 
346   RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM);
347 
348   llvm::SmallVector<MemProfRecord, 1> Records;
349   for (const auto &KeyRecordPair : Reader) {
350     Records.push_back(KeyRecordPair.second);
351   }
352 
353   ASSERT_EQ(Records.size(), 1U);
354   ASSERT_EQ(Records[0].AllocSites.size(), 1U);
355   ASSERT_EQ(Records[0].AllocSites[0].CallStack.size(), 1U);
356   EXPECT_THAT(Records[0].AllocSites[0].CallStack[0],
357               FrameContains("foo", 5U, 30U, false));
358 }
359 } // namespace
360