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/Object/ObjectFile.h"
7 #include "llvm/ProfileData/InstrProf.h"
8 #include "llvm/ProfileData/MemProfData.inc"
9 #include "llvm/ProfileData/RawMemProfReader.h"
10 #include "llvm/Support/Error.h"
11 #include "llvm/Support/MD5.h"
12 #include "llvm/Support/raw_ostream.h"
13 #include "gmock/gmock.h"
14 #include "gtest/gtest.h"
15 
16 #include <initializer_list>
17 
18 namespace {
19 
20 using ::llvm::DIGlobal;
21 using ::llvm::DIInliningInfo;
22 using ::llvm::DILineInfo;
23 using ::llvm::DILineInfoSpecifier;
24 using ::llvm::DILocal;
25 using ::llvm::memprof::CallStackMap;
26 using ::llvm::memprof::MemInfoBlock;
27 using ::llvm::memprof::MemProfRecord;
28 using ::llvm::memprof::MemProfSchema;
29 using ::llvm::memprof::Meta;
30 using ::llvm::memprof::PortableMemInfoBlock;
31 using ::llvm::memprof::RawMemProfReader;
32 using ::llvm::memprof::SegmentEntry;
33 using ::llvm::object::SectionedAddress;
34 using ::llvm::symbolize::SymbolizableModule;
35 using ::testing::Return;
36 
37 class MockSymbolizer : public SymbolizableModule {
38 public:
39   MOCK_CONST_METHOD3(symbolizeInlinedCode,
40                      DIInliningInfo(SectionedAddress, DILineInfoSpecifier,
41                                     bool));
42   // Most of the methods in the interface are unused. We only mock the
43   // method that we expect to be called from the memprof reader.
44   virtual DILineInfo symbolizeCode(SectionedAddress, DILineInfoSpecifier,
45                                    bool) const {
46     llvm_unreachable("unused");
47   }
48   virtual DIGlobal symbolizeData(SectionedAddress) const {
49     llvm_unreachable("unused");
50   }
51   virtual std::vector<DILocal> symbolizeFrame(SectionedAddress) const {
52     llvm_unreachable("unused");
53   }
54   virtual bool isWin32Module() const { llvm_unreachable("unused"); }
55   virtual uint64_t getModulePreferredBase() const {
56     llvm_unreachable("unused");
57   }
58 };
59 
60 struct MockInfo {
61   std::string FunctionName;
62   uint32_t Line;
63   uint32_t StartLine;
64   uint32_t Column;
65   std::string FileName = "valid/path.cc";
66 };
67 DIInliningInfo makeInliningInfo(std::initializer_list<MockInfo> MockFrames) {
68   DIInliningInfo Result;
69   for (const auto &Item : MockFrames) {
70     DILineInfo Frame;
71     Frame.FunctionName = Item.FunctionName;
72     Frame.Line = Item.Line;
73     Frame.StartLine = Item.StartLine;
74     Frame.Column = Item.Column;
75     Frame.FileName = Item.FileName;
76     Result.addFrame(Frame);
77   }
78   return Result;
79 }
80 
81 llvm::SmallVector<SegmentEntry, 4> makeSegments() {
82   llvm::SmallVector<SegmentEntry, 4> Result;
83   // Mimic an entry for a non position independent executable.
84   Result.emplace_back(0x0, 0x40000, 0x0);
85   return Result;
86 }
87 
88 const DILineInfoSpecifier specifier() {
89   return DILineInfoSpecifier(
90       DILineInfoSpecifier::FileLineInfoKind::RawValue,
91       DILineInfoSpecifier::FunctionNameKind::LinkageName);
92 }
93 
94 MATCHER_P4(FrameContains, FunctionName, LineOffset, Column, Inline, "") {
95   const uint64_t ExpectedHash = llvm::Function::getGUID(FunctionName);
96   if (arg.Function != ExpectedHash) {
97     *result_listener << "Hash mismatch";
98     return false;
99   }
100   if (arg.LineOffset == LineOffset && arg.Column == Column &&
101       arg.IsInlineFrame == Inline) {
102     return true;
103   }
104   *result_listener << "LineOffset, Column or Inline mismatch";
105   return false;
106 }
107 
108 MATCHER_P(EqualsRecord, Want, "") {
109   if (arg == Want)
110     return true;
111 
112   std::string Explanation;
113   llvm::raw_string_ostream OS(Explanation);
114   OS << "\n Want: \n";
115   Want.print(OS);
116   OS << "\n Got: \n";
117   arg.print(OS);
118   OS.flush();
119 
120   *result_listener << Explanation;
121   return false;
122 }
123 
124 MemProfSchema getFullSchema() {
125   MemProfSchema Schema;
126 #define MIBEntryDef(NameTag, Name, Type) Schema.push_back(Meta::Name);
127 #include "llvm/ProfileData/MIBEntryDef.inc"
128 #undef MIBEntryDef
129   return Schema;
130 }
131 
132 TEST(MemProf, FillsValue) {
133   std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
134 
135   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
136                                                 specifier(), false))
137       .Times(1) // Only once since we cache the result for future lookups.
138       .WillRepeatedly(Return(makeInliningInfo({
139           {"foo", 10, 5, 30},
140           {"bar", 201, 150, 20},
141       })));
142 
143   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x6000},
144                                                 specifier(), false))
145       .Times(1)
146       .WillRepeatedly(Return(makeInliningInfo({
147           {"baz", 10, 5, 30},
148           {"qux.llvm.12345", 75, 70, 10},
149       })));
150 
151   CallStackMap CSM;
152   CSM[0x1] = {0x2000};
153   CSM[0x2] = {0x6000, 0x2000};
154 
155   llvm::MapVector<uint64_t, MemInfoBlock> Prof;
156   Prof[0x1].AllocCount = 1;
157   Prof[0x2].AllocCount = 2;
158 
159   auto Seg = makeSegments();
160 
161   RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM);
162 
163   std::vector<MemProfRecord> Records;
164   for (const MemProfRecord &R : Reader) {
165     Records.push_back(R);
166   }
167   EXPECT_EQ(Records.size(), 2U);
168 
169   EXPECT_EQ(Records[0].Info.getAllocCount(), 1U);
170   EXPECT_EQ(Records[1].Info.getAllocCount(), 2U);
171   EXPECT_THAT(Records[0].CallStack[0], FrameContains("foo", 5U, 30U, false));
172   EXPECT_THAT(Records[0].CallStack[1], FrameContains("bar", 51U, 20U, true));
173 
174   EXPECT_THAT(Records[1].CallStack[0], FrameContains("baz", 5U, 30U, false));
175   EXPECT_THAT(Records[1].CallStack[1], FrameContains("qux", 5U, 10U, true));
176   EXPECT_THAT(Records[1].CallStack[2], FrameContains("foo", 5U, 30U, false));
177   EXPECT_THAT(Records[1].CallStack[3], FrameContains("bar", 51U, 20U, true));
178 }
179 
180 TEST(MemProf, PortableWrapper) {
181   MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
182                     /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
183                     /*dealloc_cpu=*/4);
184 
185   const auto Schema = getFullSchema();
186   PortableMemInfoBlock WriteBlock(Info);
187 
188   std::string Buffer;
189   llvm::raw_string_ostream OS(Buffer);
190   WriteBlock.serialize(Schema, OS);
191   OS.flush();
192 
193   PortableMemInfoBlock ReadBlock(
194       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()));
195 
196   EXPECT_EQ(ReadBlock, WriteBlock);
197   // Here we compare directly with the actual counts instead of MemInfoBlock
198   // members. Since the MemInfoBlock struct is packed and the EXPECT_EQ macros
199   // take a reference to the params, this results in unaligned accesses.
200   EXPECT_EQ(1UL, ReadBlock.getAllocCount());
201   EXPECT_EQ(7ULL, ReadBlock.getTotalAccessCount());
202   EXPECT_EQ(3UL, ReadBlock.getAllocCpuId());
203 }
204 
205 TEST(MemProf, RecordSerializationRoundTrip) {
206   const MemProfSchema Schema = getFullSchema();
207 
208   llvm::SmallVector<MemProfRecord, 3> Records;
209   MemProfRecord MR;
210 
211   MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
212                     /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
213                     /*dealloc_cpu=*/4);
214 
215   MR.Info = PortableMemInfoBlock(Info);
216   MR.CallStack.push_back({0x123, 1, 2, false});
217   MR.CallStack.push_back({0x345, 3, 4, false});
218   Records.push_back(MR);
219 
220   MR.clear();
221   MR.Info = PortableMemInfoBlock(Info);
222   MR.CallStack.push_back({0x567, 5, 6, false});
223   MR.CallStack.push_back({0x789, 7, 8, false});
224   Records.push_back(MR);
225 
226   std::string Buffer;
227   llvm::raw_string_ostream OS(Buffer);
228   serializeRecords(Records, Schema, OS);
229   OS.flush();
230 
231   const llvm::SmallVector<MemProfRecord, 4> GotRecords = deserializeRecords(
232       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()));
233 
234   ASSERT_TRUE(!GotRecords.empty());
235   EXPECT_EQ(GotRecords.size(), Records.size());
236   EXPECT_THAT(GotRecords[0], EqualsRecord(Records[0]));
237   EXPECT_THAT(GotRecords[1], EqualsRecord(Records[1]));
238 }
239 
240 TEST(MemProf, SymbolizationFilter) {
241   std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
242 
243   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
244                                                 specifier(), false))
245       .Times(1) // once since we don't lookup invalid PCs repeatedly.
246       .WillRepeatedly(Return(makeInliningInfo({
247           {"malloc", 70, 57, 3, "memprof/memprof_malloc_linux.cpp"},
248       })));
249 
250   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
251                                                 specifier(), false))
252       .Times(1) // once since we don't lookup invalid PCs repeatedly.
253       .WillRepeatedly(Return(makeInliningInfo({
254           {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
255       })));
256 
257   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
258                                                 specifier(), false))
259       .Times(1) // once since we don't lookup invalid PCs repeatedly.
260       .WillRepeatedly(Return(makeInliningInfo({
261           {DILineInfo::BadString, 0, 0, 0},
262       })));
263 
264   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x4000},
265                                                 specifier(), false))
266       .Times(1)
267       .WillRepeatedly(Return(makeInliningInfo({
268           {"foo", 10, 5, 30},
269       })));
270 
271   CallStackMap CSM;
272   CSM[0x1] = {0x1000, 0x2000, 0x3000, 0x4000};
273   // This entry should be dropped since all PCs are either not
274   // symbolizable or belong to the runtime.
275   CSM[0x2] = {0x1000, 0x2000};
276 
277   llvm::MapVector<uint64_t, MemInfoBlock> Prof;
278   Prof[0x1].AllocCount = 1;
279   Prof[0x2].AllocCount = 1;
280 
281   auto Seg = makeSegments();
282 
283   RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM);
284 
285   std::vector<MemProfRecord> Records;
286   for (const MemProfRecord &R : Reader) {
287     Records.push_back(R);
288   }
289   ASSERT_EQ(Records.size(), 1U);
290   ASSERT_EQ(Records[0].CallStack.size(), 1U);
291   EXPECT_THAT(Records[0].CallStack[0], FrameContains("foo", 5U, 30U, false));
292 }
293 } // namespace
294