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