1 | //===-- SourcePrinter.cpp - source interleaving utilities ----------------===// |
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 | #include "llvm/DebugInfo/BTF/BTFContext.h" |
10 | #include "llvm/ObjectYAML/YAML.h" |
11 | #include "llvm/ObjectYAML/yaml2obj.h" |
12 | #include "llvm/Support/SwapByteOrder.h" |
13 | #include "llvm/Testing/Support/Error.h" |
14 | |
15 | using namespace llvm; |
16 | using namespace llvm::object; |
17 | |
18 | #define LC(Line, Col) ((Line << 10u) | Col) |
19 | #define ASSERT_SUCCEEDED(E) ASSERT_THAT_ERROR((E), Succeeded()) |
20 | |
21 | const char BTFEndOfData[] = |
22 | "error while reading .BTF section: unexpected end of data" ; |
23 | const char BTFExtEndOfData[] = |
24 | "error while reading .BTF.ext section: unexpected end of data" ; |
25 | |
26 | static raw_ostream &operator<<(raw_ostream &OS, const yaml::BinaryRef &Ref) { |
27 | Ref.writeAsHex(OS); |
28 | return OS; |
29 | } |
30 | |
31 | template <typename T> |
32 | static yaml::BinaryRef makeBinRef(const T *Ptr, size_t Size = sizeof(T)) { |
33 | return yaml::BinaryRef(ArrayRef<uint8_t>((const uint8_t *)Ptr, Size)); |
34 | } |
35 | |
36 | namespace { |
37 | // This is a mockup for an ELF file containing .BTF and .BTF.ext sections. |
38 | // Binary content of these sections corresponds to the value of |
39 | // MockData1::BTF and MockData1::Ext fields. |
40 | // |
41 | // The yaml::yaml2ObjectFile() is used to generate actual ELF, |
42 | // see MockData1::makeObj(). |
43 | // |
44 | // The `BTF` and `Ext` fields are initialized with correct values |
45 | // valid for a small example with a few sections, fields could be |
46 | // modified before a call to `makeObj()` to test parser with invalid |
47 | // input, etc. |
48 | |
49 | struct MockData1 { |
50 | // Use "pragma pack" to model .BTF & .BTF.ext sections content using |
51 | // 'struct' objects. This pragma is supported by CLANG, GCC & MSVC, |
52 | // which matters for LLVM CI. |
53 | #pragma pack(push, 1) |
54 | struct B { |
55 | BTF::Header = {}; |
56 | // No types. |
57 | struct S { |
58 | char Foo[4] = "foo" ; |
59 | char Bar[4] = "bar" ; |
60 | char Buz[4] = "buz" ; |
61 | char Line1[11] = "first line" ; |
62 | char Line2[12] = "second line" ; |
63 | char File1[4] = "a.c" ; |
64 | char File2[4] = "b.c" ; |
65 | } Strings; |
66 | |
67 | B() { |
68 | Header.Magic = BTF::MAGIC; |
69 | Header.Version = 1; |
70 | Header.HdrLen = sizeof(Header); |
71 | Header.StrOff = offsetof(B, Strings) - sizeof(Header); |
72 | Header.StrLen = sizeof(Strings); |
73 | } |
74 | } BTF; |
75 | |
76 | struct E { |
77 | BTF::ExtHeader = {}; |
78 | // No func info. |
79 | struct { |
80 | uint32_t LineRecSize = sizeof(BTF::BPFLineInfo); |
81 | struct { |
82 | BTF::SecLineInfo Sec = {offsetof(B::S, Foo), .NumLineInfo: 2}; |
83 | BTF::BPFLineInfo Lines[2] = { |
84 | {.InsnOffset: 16, offsetof(B::S, File1), offsetof(B::S, Line1), LC(7, 1)}, |
85 | {.InsnOffset: 32, offsetof(B::S, File1), offsetof(B::S, Line2), LC(14, 5)}, |
86 | }; |
87 | } Foo; |
88 | struct { |
89 | BTF::SecLineInfo Sec = {offsetof(B::S, Bar), .NumLineInfo: 1}; |
90 | BTF::BPFLineInfo Lines[1] = { |
91 | {.InsnOffset: 0, offsetof(B::S, File2), offsetof(B::S, Line1), LC(42, 4)}, |
92 | }; |
93 | } Bar; |
94 | } Lines; |
95 | |
96 | E() { |
97 | Header.Magic = BTF::MAGIC; |
98 | Header.Version = 1; |
99 | Header.HdrLen = sizeof(Header); |
100 | Header.LineInfoOff = offsetof(E, Lines) - sizeof(Header); |
101 | Header.LineInfoLen = sizeof(Lines); |
102 | } |
103 | } Ext; |
104 | #pragma pack(pop) |
105 | |
106 | int BTFSectionLen = sizeof(BTF); |
107 | int ExtSectionLen = sizeof(Ext); |
108 | |
109 | SmallString<0> Storage; |
110 | std::unique_ptr<ObjectFile> Obj; |
111 | |
112 | ObjectFile &makeObj() { |
113 | std::string Buffer; |
114 | raw_string_ostream Yaml(Buffer); |
115 | Yaml << R"( |
116 | !ELF |
117 | FileHeader: |
118 | Class: ELFCLASS64)" ; |
119 | if (sys::IsBigEndianHost) |
120 | Yaml << "\n Data: ELFDATA2MSB" ; |
121 | else |
122 | Yaml << "\n Data: ELFDATA2LSB" ; |
123 | Yaml << R"( |
124 | Type: ET_REL |
125 | Machine: EM_BPF |
126 | Sections: |
127 | - Name: foo |
128 | Type: SHT_PROGBITS |
129 | Size: 0x0 |
130 | - Name: bar |
131 | Type: SHT_PROGBITS |
132 | Size: 0x0)" ; |
133 | |
134 | if (BTFSectionLen >= 0) |
135 | Yaml << R"( |
136 | - Name: .BTF |
137 | Type: SHT_PROGBITS |
138 | Content: )" |
139 | << makeBinRef(Ptr: &BTF, Size: BTFSectionLen); |
140 | |
141 | if (ExtSectionLen >= 0) |
142 | Yaml << R"( |
143 | - Name: .BTF.ext |
144 | Type: SHT_PROGBITS |
145 | Content: )" |
146 | << makeBinRef(Ptr: &Ext, Size: ExtSectionLen); |
147 | |
148 | Obj = yaml::yaml2ObjectFile(Storage, Yaml: Buffer, |
149 | ErrHandler: [](const Twine &Err) { errs() << Err; }); |
150 | return *Obj.get(); |
151 | } |
152 | }; |
153 | |
154 | TEST(BTFParserTest, simpleCorrectInput) { |
155 | BTFParser BTF; |
156 | MockData1 Mock; |
157 | Error Err = BTF.parse(Obj: Mock.makeObj()); |
158 | EXPECT_FALSE(Err); |
159 | |
160 | EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Foo)), "foo" ); |
161 | EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Bar)), "bar" ); |
162 | EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Line1)), "first line" ); |
163 | EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Line2)), "second line" ); |
164 | EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, File1)), "a.c" ); |
165 | EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, File2)), "b.c" ); |
166 | |
167 | // Invalid offset. |
168 | EXPECT_EQ(BTF.findString(sizeof(MockData1::B::S)), StringRef()); |
169 | |
170 | const BTF::BPFLineInfo *I1 = BTF.findLineInfo(Address: {.Address: 16, .SectionIndex: 1}); |
171 | ASSERT_TRUE(I1); |
172 | EXPECT_EQ(I1->getLine(), 7u); |
173 | EXPECT_EQ(I1->getCol(), 1u); |
174 | EXPECT_EQ(BTF.findString(I1->FileNameOff), "a.c" ); |
175 | EXPECT_EQ(BTF.findString(I1->LineOff), "first line" ); |
176 | |
177 | const BTF::BPFLineInfo *I2 = BTF.findLineInfo(Address: {.Address: 32, .SectionIndex: 1}); |
178 | ASSERT_TRUE(I2); |
179 | EXPECT_EQ(I2->getLine(), 14u); |
180 | EXPECT_EQ(I2->getCol(), 5u); |
181 | EXPECT_EQ(BTF.findString(I2->FileNameOff), "a.c" ); |
182 | EXPECT_EQ(BTF.findString(I2->LineOff), "second line" ); |
183 | |
184 | const BTF::BPFLineInfo *I3 = BTF.findLineInfo(Address: {.Address: 0, .SectionIndex: 2}); |
185 | ASSERT_TRUE(I3); |
186 | EXPECT_EQ(I3->getLine(), 42u); |
187 | EXPECT_EQ(I3->getCol(), 4u); |
188 | EXPECT_EQ(BTF.findString(I3->FileNameOff), "b.c" ); |
189 | EXPECT_EQ(BTF.findString(I3->LineOff), "first line" ); |
190 | |
191 | // No info for insn address. |
192 | EXPECT_FALSE(BTF.findLineInfo({24, 1})); |
193 | EXPECT_FALSE(BTF.findLineInfo({8, 2})); |
194 | // No info for section number. |
195 | EXPECT_FALSE(BTF.findLineInfo({16, 3})); |
196 | } |
197 | |
198 | TEST(BTFParserTest, badSectionNameOffset) { |
199 | BTFParser BTF; |
200 | MockData1 Mock; |
201 | // "foo" is section #1, corrupting it's name offset will make impossible |
202 | // to match section name with section index when BTF is parsed. |
203 | Mock.Ext.Lines.Foo.Sec.SecNameOff = 100500; |
204 | Error Err = BTF.parse(Obj: Mock.makeObj()); |
205 | EXPECT_FALSE(Err); |
206 | // "foo" line info should be corrupted. |
207 | EXPECT_FALSE(BTF.findLineInfo({16, 1})); |
208 | // "bar" line info should be ok. |
209 | EXPECT_TRUE(BTF.findLineInfo({0, 2})); |
210 | } |
211 | |
212 | // Keep this as macro to preserve line number info. |
213 | #define EXPECT_PARSE_ERROR(Mock, Message) \ |
214 | do { \ |
215 | BTFParser BTF; \ |
216 | EXPECT_THAT_ERROR(BTF.parse((Mock).makeObj()), \ |
217 | FailedWithMessage(testing::HasSubstr(Message))); \ |
218 | } while (false) |
219 | |
220 | TEST(BTFParserTest, badBTFMagic) { |
221 | MockData1 Mock; |
222 | Mock.BTF.Header.Magic = 42; |
223 | EXPECT_PARSE_ERROR(Mock, "invalid .BTF magic: 2a" ); |
224 | } |
225 | |
226 | TEST(BTFParserTest, badBTFVersion) { |
227 | MockData1 Mock; |
228 | Mock.BTF.Header.Version = 42; |
229 | EXPECT_PARSE_ERROR(Mock, "unsupported .BTF version: 42" ); |
230 | } |
231 | |
232 | TEST(BTFParserTest, badBTFHdrLen) { |
233 | MockData1 Mock; |
234 | Mock.BTF.Header.HdrLen = 5; |
235 | EXPECT_PARSE_ERROR(Mock, "unexpected .BTF header length: 5" ); |
236 | } |
237 | |
238 | TEST(BTFParserTest, badBTFSectionLen) { |
239 | MockData1 Mock1, Mock2; |
240 | |
241 | // Cut-off string section by one byte. |
242 | Mock1.BTFSectionLen = |
243 | offsetof(MockData1::B, Strings) + sizeof(MockData1::B::S) - 1; |
244 | EXPECT_PARSE_ERROR(Mock1, "invalid .BTF section size" ); |
245 | |
246 | // Cut-off header. |
247 | Mock2.BTFSectionLen = offsetof(BTF::Header, StrOff); |
248 | EXPECT_PARSE_ERROR(Mock2, BTFEndOfData); |
249 | } |
250 | |
251 | TEST(BTFParserTest, badBTFExtMagic) { |
252 | MockData1 Mock; |
253 | Mock.Ext.Header.Magic = 42; |
254 | EXPECT_PARSE_ERROR(Mock, "invalid .BTF.ext magic: 2a" ); |
255 | } |
256 | |
257 | TEST(BTFParserTest, badBTFExtVersion) { |
258 | MockData1 Mock; |
259 | Mock.Ext.Header.Version = 42; |
260 | EXPECT_PARSE_ERROR(Mock, "unsupported .BTF.ext version: 42" ); |
261 | } |
262 | |
263 | TEST(BTFParserTest, badBTFExtHdrLen) { |
264 | MockData1 Mock1, Mock2; |
265 | |
266 | Mock1.Ext.Header.HdrLen = 5; |
267 | EXPECT_PARSE_ERROR(Mock1, "unexpected .BTF.ext header length: 5" ); |
268 | |
269 | Mock2.Ext.Header.HdrLen = sizeof(Mock2.Ext); |
270 | EXPECT_PARSE_ERROR(Mock2, BTFExtEndOfData); |
271 | } |
272 | |
273 | TEST(BTFParserTest, badBTFExtSectionLen) { |
274 | MockData1 Mock1, Mock2, Mock3; |
275 | |
276 | // Cut-off header before HdrLen. |
277 | Mock1.ExtSectionLen = offsetof(BTF::ExtHeader, HdrLen); |
278 | EXPECT_PARSE_ERROR(Mock1, BTFExtEndOfData); |
279 | |
280 | // Cut-off header before LineInfoLen. |
281 | Mock2.ExtSectionLen = offsetof(BTF::ExtHeader, LineInfoLen); |
282 | EXPECT_PARSE_ERROR(Mock2, BTFExtEndOfData); |
283 | |
284 | // Cut-off line-info section somewhere in the middle. |
285 | Mock3.ExtSectionLen = offsetof(MockData1::E, Lines) + 4; |
286 | EXPECT_PARSE_ERROR(Mock3, BTFExtEndOfData); |
287 | } |
288 | |
289 | TEST(BTFParserTest, badBTFExtLineInfoRecSize) { |
290 | MockData1 Mock1, Mock2; |
291 | |
292 | Mock1.Ext.Lines.LineRecSize = 2; |
293 | EXPECT_PARSE_ERROR(Mock1, "unexpected .BTF.ext line info record length: 2" ); |
294 | |
295 | Mock2.Ext.Lines.LineRecSize = sizeof(Mock2.Ext.Lines.Foo.Lines[0]) + 1; |
296 | EXPECT_PARSE_ERROR(Mock2, BTFExtEndOfData); |
297 | } |
298 | |
299 | TEST(BTFParserTest, badBTFExtLineSectionName) { |
300 | MockData1 Mock1; |
301 | |
302 | Mock1.Ext.Lines.Foo.Sec.SecNameOff = offsetof(MockData1::B::S, Buz); |
303 | EXPECT_PARSE_ERROR( |
304 | Mock1, "can't find section 'buz' while parsing .BTF.ext line info" ); |
305 | } |
306 | |
307 | TEST(BTFParserTest, missingSections) { |
308 | MockData1 Mock1, Mock2, Mock3; |
309 | |
310 | Mock1.BTFSectionLen = -1; |
311 | EXPECT_PARSE_ERROR(Mock1, "can't find .BTF section" ); |
312 | EXPECT_FALSE(BTFParser::hasBTFSections(Mock1.makeObj())); |
313 | |
314 | Mock2.ExtSectionLen = -1; |
315 | EXPECT_PARSE_ERROR(Mock2, "can't find .BTF.ext section" ); |
316 | EXPECT_FALSE(BTFParser::hasBTFSections(Mock2.makeObj())); |
317 | |
318 | EXPECT_TRUE(BTFParser::hasBTFSections(Mock3.makeObj())); |
319 | } |
320 | |
321 | // Check that BTFParser instance is reset when BTFParser::parse() is |
322 | // called several times. |
323 | TEST(BTFParserTest, parserReset) { |
324 | BTFParser BTF; |
325 | MockData1 Mock1, Mock2; |
326 | |
327 | EXPECT_FALSE(BTF.parse(Mock1.makeObj())); |
328 | EXPECT_TRUE(BTF.findLineInfo({16, 1})); |
329 | EXPECT_TRUE(BTF.findLineInfo({0, 2})); |
330 | |
331 | // Break the reference to "bar" section name, thus making |
332 | // information about "bar" line numbers unavailable. |
333 | Mock2.Ext.Lines.Bar.Sec.SecNameOff = 100500; |
334 | |
335 | EXPECT_FALSE(BTF.parse(Mock2.makeObj())); |
336 | EXPECT_TRUE(BTF.findLineInfo({16, 1})); |
337 | // Make sure that "bar" no longer available (its index is 2). |
338 | EXPECT_FALSE(BTF.findLineInfo({0, 2})); |
339 | } |
340 | |
341 | TEST(BTFParserTest, btfContext) { |
342 | MockData1 Mock; |
343 | BTFParser BTF; |
344 | std::unique_ptr<BTFContext> Ctx = BTFContext::create(Obj: Mock.makeObj()); |
345 | |
346 | DILineInfo I1 = Ctx->getLineInfoForAddress(Address: {.Address: 16, .SectionIndex: 1}); |
347 | EXPECT_EQ(I1.Line, 7u); |
348 | EXPECT_EQ(I1.Column, 1u); |
349 | EXPECT_EQ(I1.FileName, "a.c" ); |
350 | EXPECT_EQ(I1.LineSource, "first line" ); |
351 | |
352 | DILineInfo I2 = Ctx->getLineInfoForAddress(Address: {.Address: 24, .SectionIndex: 1}); |
353 | EXPECT_EQ(I2.Line, 0u); |
354 | EXPECT_EQ(I2.Column, 0u); |
355 | EXPECT_EQ(I2.FileName, DILineInfo::BadString); |
356 | EXPECT_EQ(I2.LineSource, std::nullopt); |
357 | } |
358 | |
359 | static uint32_t mkInfo(uint32_t Kind) { return Kind << 24; } |
360 | |
361 | template <typename T> static void append(std::string &S, const T &What) { |
362 | S.append(s: (const char *)&What, n: sizeof(What)); |
363 | } |
364 | |
365 | class MockData2 { |
366 | SmallString<0> ObjStorage; |
367 | std::unique_ptr<ObjectFile> Obj; |
368 | std::string Types; |
369 | std::string Strings; |
370 | std::string Relocs; |
371 | std::string Lines; |
372 | unsigned TotalTypes; |
373 | int LastRelocSecIdx; |
374 | unsigned NumRelocs; |
375 | int LastLineSecIdx; |
376 | unsigned NumLines; |
377 | |
378 | public: |
379 | MockData2() { reset(); } |
380 | |
381 | unsigned totalTypes() const { return TotalTypes; } |
382 | |
383 | uint32_t addString(StringRef S) { |
384 | uint32_t Off = Strings.size(); |
385 | Strings.append(s: S.data(), n: S.size()); |
386 | Strings.append(s: "\0" , n: 1); |
387 | return Off; |
388 | }; |
389 | |
390 | uint32_t addType(const BTF::CommonType &Tp) { |
391 | append(S&: Types, What: Tp); |
392 | return ++TotalTypes; |
393 | } |
394 | |
395 | template <typename T> void addTail(const T &Tp) { append(Types, Tp); } |
396 | |
397 | void resetTypes() { |
398 | Types.resize(n: 0); |
399 | TotalTypes = 0; |
400 | } |
401 | |
402 | void reset() { |
403 | ObjStorage.clear(); |
404 | Types.resize(n: 0); |
405 | Strings.resize(n: 0); |
406 | Relocs.resize(n: 0); |
407 | Lines.resize(n: 0); |
408 | TotalTypes = 0; |
409 | LastRelocSecIdx = -1; |
410 | NumRelocs = 0; |
411 | LastLineSecIdx = -1; |
412 | NumLines = 0; |
413 | } |
414 | |
415 | void finishRelocSec() { |
416 | if (LastRelocSecIdx == -1) |
417 | return; |
418 | |
419 | BTF::SecFieldReloc *SecInfo = |
420 | (BTF::SecFieldReloc *)&Relocs[LastRelocSecIdx]; |
421 | SecInfo->NumFieldReloc = NumRelocs; |
422 | LastRelocSecIdx = -1; |
423 | NumRelocs = 0; |
424 | } |
425 | |
426 | void finishLineSec() { |
427 | if (LastLineSecIdx == -1) |
428 | return; |
429 | |
430 | BTF::SecLineInfo *SecInfo = (BTF::SecLineInfo *)&Lines[LastLineSecIdx]; |
431 | SecInfo->NumLineInfo = NumLines; |
432 | NumLines = 0; |
433 | LastLineSecIdx = -1; |
434 | } |
435 | |
436 | void addRelocSec(const BTF::SecFieldReloc &R) { |
437 | finishRelocSec(); |
438 | LastRelocSecIdx = Relocs.size(); |
439 | append(S&: Relocs, What: R); |
440 | } |
441 | |
442 | void addReloc(const BTF::BPFFieldReloc &R) { |
443 | append(S&: Relocs, What: R); |
444 | ++NumRelocs; |
445 | } |
446 | |
447 | void addLinesSec(const BTF::SecLineInfo &R) { |
448 | finishLineSec(); |
449 | LastLineSecIdx = Lines.size(); |
450 | append(S&: Lines, What: R); |
451 | } |
452 | |
453 | void addLine(const BTF::BPFLineInfo &R) { |
454 | append(S&: Lines, What: R); |
455 | ++NumLines; |
456 | } |
457 | |
458 | ObjectFile &makeObj() { |
459 | finishRelocSec(); |
460 | finishLineSec(); |
461 | |
462 | BTF::Header = {}; |
463 | BTFHeader.Magic = BTF::MAGIC; |
464 | BTFHeader.Version = 1; |
465 | BTFHeader.HdrLen = sizeof(BTFHeader); |
466 | BTFHeader.StrOff = 0; |
467 | BTFHeader.StrLen = Strings.size(); |
468 | BTFHeader.TypeOff = Strings.size(); |
469 | BTFHeader.TypeLen = Types.size(); |
470 | |
471 | std::string BTFSec; |
472 | append(S&: BTFSec, What: BTFHeader); |
473 | BTFSec.append(str: Strings); |
474 | BTFSec.append(str: Types); |
475 | |
476 | BTF::ExtHeader = {}; |
477 | ExtHeader.Magic = BTF::MAGIC; |
478 | ExtHeader.Version = 1; |
479 | ExtHeader.HdrLen = sizeof(ExtHeader); |
480 | ExtHeader.FieldRelocOff = 0; |
481 | ExtHeader.FieldRelocLen = Relocs.size() + sizeof(uint32_t); |
482 | ExtHeader.LineInfoOff = ExtHeader.FieldRelocLen; |
483 | ExtHeader.LineInfoLen = Lines.size() + sizeof(uint32_t); |
484 | |
485 | std::string ExtSec; |
486 | append(S&: ExtSec, What: ExtHeader); |
487 | append(S&: ExtSec, What: (uint32_t)sizeof(BTF::BPFFieldReloc)); |
488 | ExtSec.append(str: Relocs); |
489 | append(S&: ExtSec, What: (uint32_t)sizeof(BTF::BPFLineInfo)); |
490 | ExtSec.append(str: Lines); |
491 | |
492 | std::string YamlBuffer; |
493 | raw_string_ostream Yaml(YamlBuffer); |
494 | Yaml << R"( |
495 | !ELF |
496 | FileHeader: |
497 | Class: ELFCLASS64)" ; |
498 | if (sys::IsBigEndianHost) |
499 | Yaml << "\n Data: ELFDATA2MSB" ; |
500 | else |
501 | Yaml << "\n Data: ELFDATA2LSB" ; |
502 | Yaml << R"( |
503 | Type: ET_REL |
504 | Machine: EM_BPF |
505 | Sections: |
506 | - Name: foo |
507 | Type: SHT_PROGBITS |
508 | Size: 0x80 |
509 | - Name: bar |
510 | Type: SHT_PROGBITS |
511 | Size: 0x80 |
512 | - Name: .BTF |
513 | Type: SHT_PROGBITS |
514 | Content: )" |
515 | << makeBinRef(Ptr: BTFSec.data(), Size: BTFSec.size()); |
516 | Yaml << R"( |
517 | - Name: .BTF.ext |
518 | Type: SHT_PROGBITS |
519 | Content: )" |
520 | << makeBinRef(Ptr: ExtSec.data(), Size: ExtSec.size()); |
521 | |
522 | Obj = yaml::yaml2ObjectFile(Storage&: ObjStorage, Yaml: YamlBuffer, |
523 | ErrHandler: [](const Twine &Err) { errs() << Err; }); |
524 | return *Obj.get(); |
525 | } |
526 | }; |
527 | |
528 | TEST(BTFParserTest, allTypeKinds) { |
529 | MockData2 D; |
530 | D.addType(Tp: {.NameOff: D.addString(S: "1" ), .Info: mkInfo(Kind: BTF::BTF_KIND_INT), {.Size: 4}}); |
531 | D.addTail(Tp: (uint32_t)0); |
532 | D.addType(Tp: {.NameOff: D.addString(S: "2" ), .Info: mkInfo(Kind: BTF::BTF_KIND_PTR), {.Size: 1}}); |
533 | D.addType(Tp: {.NameOff: D.addString(S: "3" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ARRAY), {.Size: 0}}); |
534 | D.addTail(Tp: BTF::BTFArray({.ElemType: 1, .IndexType: 1, .Nelems: 2})); |
535 | D.addType(Tp: {.NameOff: D.addString(S: "4" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT) | 2, {.Size: 8}}); |
536 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "a" ), .Type: 1, .Offset: 0})); |
537 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "b" ), .Type: 1, .Offset: 0})); |
538 | D.addType(Tp: {.NameOff: D.addString(S: "5" ), .Info: mkInfo(Kind: BTF::BTF_KIND_UNION) | 3, {.Size: 8}}); |
539 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "a" ), .Type: 1, .Offset: 0})); |
540 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "b" ), .Type: 1, .Offset: 0})); |
541 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "c" ), .Type: 1, .Offset: 0})); |
542 | D.addType(Tp: {.NameOff: D.addString(S: "6" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ENUM) | 2, {.Size: 4}}); |
543 | D.addTail(Tp: BTF::BTFEnum({.NameOff: D.addString(S: "U" ), .Val: 1})); |
544 | D.addTail(Tp: BTF::BTFEnum({.NameOff: D.addString(S: "V" ), .Val: 2})); |
545 | D.addType(Tp: {.NameOff: D.addString(S: "7" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ENUM64) | 1, {.Size: 4}}); |
546 | D.addTail(Tp: BTF::BTFEnum64({.NameOff: D.addString(S: "W" ), .Val_Lo32: 0, .Val_Hi32: 1})); |
547 | D.addType( |
548 | Tp: {.NameOff: D.addString(S: "8" ), .Info: BTF::FWD_UNION_FLAG | mkInfo(Kind: BTF::BTF_KIND_FWD), {.Size: 0}}); |
549 | D.addType(Tp: {.NameOff: D.addString(S: "9" ), .Info: mkInfo(Kind: BTF::BTF_KIND_TYPEDEF), {.Size: 1}}); |
550 | D.addType(Tp: {.NameOff: D.addString(S: "10" ), .Info: mkInfo(Kind: BTF::BTF_KIND_VOLATILE), {.Size: 1}}); |
551 | D.addType(Tp: {.NameOff: D.addString(S: "11" ), .Info: mkInfo(Kind: BTF::BTF_KIND_CONST), {.Size: 1}}); |
552 | D.addType(Tp: {.NameOff: D.addString(S: "12" ), .Info: mkInfo(Kind: BTF::BTF_KIND_RESTRICT), {.Size: 1}}); |
553 | D.addType(Tp: {.NameOff: D.addString(S: "13" ), .Info: mkInfo(Kind: BTF::BTF_KIND_FUNC_PROTO) | 1, {.Size: 1}}); |
554 | D.addTail(Tp: BTF::BTFParam({.NameOff: D.addString(S: "P" ), .Type: 2})); |
555 | D.addType(Tp: {.NameOff: D.addString(S: "14" ), .Info: mkInfo(Kind: BTF::BTF_KIND_FUNC), {.Size: 13}}); |
556 | D.addType(Tp: {.NameOff: D.addString(S: "15" ), .Info: mkInfo(Kind: BTF::BTF_KIND_VAR), {.Size: 2}}); |
557 | D.addTail(Tp: (uint32_t)0); |
558 | D.addType(Tp: {.NameOff: D.addString(S: "16" ), .Info: mkInfo(Kind: BTF::BTF_KIND_DATASEC) | 3, {.Size: 0}}); |
559 | D.addTail(Tp: BTF::BTFDataSec({.Type: 1, .Offset: 0, .Size: 4})); |
560 | D.addTail(Tp: BTF::BTFDataSec({.Type: 1, .Offset: 4, .Size: 4})); |
561 | D.addTail(Tp: BTF::BTFDataSec({.Type: 1, .Offset: 8, .Size: 4})); |
562 | D.addType(Tp: {.NameOff: D.addString(S: "17" ), .Info: mkInfo(Kind: BTF::BTF_KIND_FLOAT), {.Size: 4}}); |
563 | D.addType(Tp: {.NameOff: D.addString(S: "18" ), .Info: mkInfo(Kind: BTF::BTF_KIND_DECL_TAG), {.Size: 0}}); |
564 | D.addTail(Tp: (uint32_t)-1); |
565 | D.addType(Tp: {.NameOff: D.addString(S: "19" ), .Info: mkInfo(Kind: BTF::BTF_KIND_TYPE_TAG), {.Size: 0}}); |
566 | |
567 | BTFParser BTF; |
568 | Error Err = BTF.parse(Obj: D.makeObj()); |
569 | EXPECT_FALSE(Err); |
570 | |
571 | EXPECT_EQ(D.totalTypes() + 1 /* +1 for void */, BTF.typesCount()); |
572 | for (unsigned Id = 1; Id < D.totalTypes() + 1; ++Id) { |
573 | const BTF::CommonType *Tp = BTF.findType(Id); |
574 | ASSERT_TRUE(Tp); |
575 | std::string IdBuf; |
576 | raw_string_ostream IdBufStream(IdBuf); |
577 | IdBufStream << Id; |
578 | EXPECT_EQ(BTF.findString(Tp->NameOff), IdBuf); |
579 | } |
580 | } |
581 | |
582 | TEST(BTFParserTest, bigStruct) { |
583 | const uint32_t N = 1000u; |
584 | MockData2 D; |
585 | uint32_t FStr = D.addString(S: "f" ); |
586 | D.addType(Tp: {.NameOff: D.addString(S: "foo" ), .Info: mkInfo(Kind: BTF::BTF_KIND_INT), {.Size: 4}}); |
587 | D.addTail(Tp: (uint32_t)0); |
588 | D.addType(Tp: {.NameOff: D.addString(S: "big" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT) | N, {.Size: 8}}); |
589 | for (unsigned I = 0; I < N; ++I) |
590 | D.addTail(Tp: BTF::BTFMember({.NameOff: FStr, .Type: 1, .Offset: 0})); |
591 | D.addType(Tp: {.NameOff: D.addString(S: "bar" ), .Info: mkInfo(Kind: BTF::BTF_KIND_INT), {.Size: 4}}); |
592 | D.addTail(Tp: (uint32_t)0); |
593 | |
594 | BTFParser BTF; |
595 | ASSERT_SUCCEEDED(BTF.parse(D.makeObj())); |
596 | ASSERT_EQ(BTF.typesCount(), 4u /* +1 for void */); |
597 | const BTF::CommonType *Foo = BTF.findType(Id: 1); |
598 | const BTF::CommonType *Big = BTF.findType(Id: 2); |
599 | const BTF::CommonType *Bar = BTF.findType(Id: 3); |
600 | ASSERT_TRUE(Foo); |
601 | ASSERT_TRUE(Big); |
602 | ASSERT_TRUE(Bar); |
603 | EXPECT_EQ(BTF.findString(Foo->NameOff), "foo" ); |
604 | EXPECT_EQ(BTF.findString(Big->NameOff), "big" ); |
605 | EXPECT_EQ(BTF.findString(Bar->NameOff), "bar" ); |
606 | EXPECT_EQ(Big->getVlen(), N); |
607 | } |
608 | |
609 | TEST(BTFParserTest, incompleteTypes) { |
610 | MockData2 D; |
611 | auto IncompleteType = [&](const BTF::CommonType &Tp) { |
612 | D.resetTypes(); |
613 | D.addType(Tp); |
614 | EXPECT_PARSE_ERROR(D, "incomplete type definition in .BTF section" ); |
615 | }; |
616 | |
617 | // All kinds that need tail. |
618 | IncompleteType({.NameOff: D.addString(S: "a" ), .Info: mkInfo(Kind: BTF::BTF_KIND_INT), {.Size: 4}}); |
619 | IncompleteType({.NameOff: D.addString(S: "b" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ARRAY), {.Size: 0}}); |
620 | IncompleteType({.NameOff: D.addString(S: "c" ), .Info: mkInfo(Kind: BTF::BTF_KIND_VAR), {.Size: 0}}); |
621 | IncompleteType({.NameOff: D.addString(S: "d" ), .Info: mkInfo(Kind: BTF::BTF_KIND_DECL_TAG), {.Size: 0}}); |
622 | |
623 | // All kinds with vlen. |
624 | IncompleteType({.NameOff: D.addString(S: "a" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT) | 2, {.Size: 8}}); |
625 | IncompleteType({.NameOff: D.addString(S: "b" ), .Info: mkInfo(Kind: BTF::BTF_KIND_UNION) | 3, {.Size: 8}}); |
626 | IncompleteType({.NameOff: D.addString(S: "c" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ENUM) | 2, {.Size: 4}}); |
627 | IncompleteType({.NameOff: D.addString(S: "d" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ENUM64) | 1, {.Size: 4}}); |
628 | IncompleteType({.NameOff: D.addString(S: "e" ), .Info: mkInfo(Kind: BTF::BTF_KIND_FUNC_PROTO) | 1, {.Size: 1}}); |
629 | IncompleteType({.NameOff: D.addString(S: "f" ), .Info: mkInfo(Kind: BTF::BTF_KIND_DATASEC) | 3, {.Size: 0}}); |
630 | |
631 | // An unexpected tail. |
632 | D.resetTypes(); |
633 | D.addTail(Tp: (uint32_t)0); |
634 | EXPECT_PARSE_ERROR(D, "incomplete type definition in .BTF section" ); |
635 | } |
636 | |
637 | // Use macro to preserve line number in error message. |
638 | #define SYMBOLIZE(SecAddr, Expected) \ |
639 | do { \ |
640 | const BTF::BPFFieldReloc *R = BTF.findFieldReloc((SecAddr)); \ |
641 | ASSERT_TRUE(R); \ |
642 | SmallString<64> Symbolized; \ |
643 | BTF.symbolize(R, Symbolized); \ |
644 | EXPECT_EQ(Symbolized, (Expected)); \ |
645 | } while (false) |
646 | |
647 | // Shorter name for initializers below. |
648 | using SA = SectionedAddress; |
649 | |
650 | TEST(BTFParserTest, typeRelocs) { |
651 | MockData2 D; |
652 | uint32_t Zero = D.addString(S: "0" ); |
653 | // id 1: struct foo {} |
654 | // id 2: union bar; |
655 | // id 3: struct buz; |
656 | D.addType(Tp: {.NameOff: D.addString(S: "foo" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT), {.Size: 0}}); |
657 | D.addType(Tp: {.NameOff: D.addString(S: "bar" ), |
658 | .Info: mkInfo(Kind: BTF::BTF_KIND_FWD) | BTF::FWD_UNION_FLAG, |
659 | {.Size: 0}}); |
660 | D.addType(Tp: {.NameOff: D.addString(S: "buz" ), .Info: mkInfo(Kind: BTF::BTF_KIND_FWD), {.Size: 0}}); |
661 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 7}); |
662 | // List of all possible correct type relocations for type id #1. |
663 | D.addReloc(R: {.InsnOffset: 0, .TypeID: 1, .OffsetNameOff: Zero, .RelocKind: BTF::BTF_TYPE_ID_LOCAL}); |
664 | D.addReloc(R: {.InsnOffset: 8, .TypeID: 1, .OffsetNameOff: Zero, .RelocKind: BTF::BTF_TYPE_ID_REMOTE}); |
665 | D.addReloc(R: {.InsnOffset: 16, .TypeID: 1, .OffsetNameOff: Zero, .RelocKind: BTF::TYPE_EXISTENCE}); |
666 | D.addReloc(R: {.InsnOffset: 24, .TypeID: 1, .OffsetNameOff: Zero, .RelocKind: BTF::TYPE_MATCH}); |
667 | D.addReloc(R: {.InsnOffset: 32, .TypeID: 1, .OffsetNameOff: Zero, .RelocKind: BTF::TYPE_SIZE}); |
668 | // Forward declarations. |
669 | D.addReloc(R: {.InsnOffset: 40, .TypeID: 2, .OffsetNameOff: Zero, .RelocKind: BTF::TYPE_SIZE}); |
670 | D.addReloc(R: {.InsnOffset: 48, .TypeID: 3, .OffsetNameOff: Zero, .RelocKind: BTF::TYPE_SIZE}); |
671 | // Incorrect type relocation: bad type id. |
672 | D.addReloc(R: {.InsnOffset: 56, .TypeID: 42, .OffsetNameOff: Zero, .RelocKind: BTF::TYPE_SIZE}); |
673 | // Incorrect type relocation: spec should be '0'. |
674 | D.addReloc(R: {.InsnOffset: 64, .TypeID: 1, .OffsetNameOff: D.addString(S: "10" ), .RelocKind: BTF::TYPE_SIZE}); |
675 | |
676 | BTFParser BTF; |
677 | Error E = BTF.parse(Obj: D.makeObj()); |
678 | EXPECT_FALSE(E); |
679 | |
680 | SYMBOLIZE(SA({0, 1}), "<local_type_id> [1] struct foo" ); |
681 | SYMBOLIZE(SA({8, 1}), "<target_type_id> [1] struct foo" ); |
682 | SYMBOLIZE(SA({16, 1}), "<type_exists> [1] struct foo" ); |
683 | SYMBOLIZE(SA({24, 1}), "<type_matches> [1] struct foo" ); |
684 | SYMBOLIZE(SA({32, 1}), "<type_size> [1] struct foo" ); |
685 | SYMBOLIZE(SA({40, 1}), "<type_size> [2] fwd union bar" ); |
686 | SYMBOLIZE(SA({48, 1}), "<type_size> [3] fwd struct buz" ); |
687 | SYMBOLIZE(SA({56, 1}), "<type_size> [42] '0' <unknown type id: 42>" ); |
688 | SYMBOLIZE(SA({64, 1}), |
689 | "<type_size> [1] '10' " |
690 | "<unexpected type-based relocation spec: should be '0'>" ); |
691 | } |
692 | |
693 | TEST(BTFParserTest, enumRelocs) { |
694 | MockData2 D; |
695 | // id 1: enum { U, V } |
696 | D.addType(Tp: {.NameOff: D.addString(S: "foo" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ENUM) | 2, {.Size: 4}}); |
697 | D.addTail(Tp: BTF::BTFEnum({.NameOff: D.addString(S: "U" ), .Val: 1})); |
698 | D.addTail(Tp: BTF::BTFEnum({.NameOff: D.addString(S: "V" ), .Val: 2})); |
699 | // id 2: int |
700 | D.addType(Tp: {.NameOff: D.addString(S: "int" ), .Info: mkInfo(Kind: BTF::BTF_KIND_INT), {.Size: 4}}); |
701 | D.addTail(Tp: (uint32_t)0); |
702 | // id 3: enum: uint64_t { A, B } |
703 | D.addType(Tp: {.NameOff: D.addString(S: "bar" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ENUM64) | 2, {.Size: 8}}); |
704 | D.addTail(Tp: BTF::BTFEnum64({.NameOff: D.addString(S: "A" ), .Val_Lo32: 1, .Val_Hi32: 0})); |
705 | D.addTail(Tp: BTF::BTFEnum64({.NameOff: D.addString(S: "B" ), .Val_Lo32: 2, .Val_Hi32: 0})); |
706 | |
707 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 5}); |
708 | // An ok relocation accessing value #1: U. |
709 | D.addReloc(R: {.InsnOffset: 0, .TypeID: 1, .OffsetNameOff: D.addString(S: "0" ), .RelocKind: BTF::ENUM_VALUE_EXISTENCE}); |
710 | // An ok relocation accessing value #2: V. |
711 | D.addReloc(R: {.InsnOffset: 8, .TypeID: 1, .OffsetNameOff: D.addString(S: "1" ), .RelocKind: BTF::ENUM_VALUE}); |
712 | // Incorrect relocation: too many elements in string "1:0". |
713 | D.addReloc(R: {.InsnOffset: 16, .TypeID: 1, .OffsetNameOff: D.addString(S: "1:0" ), .RelocKind: BTF::ENUM_VALUE}); |
714 | // Incorrect relocation: type id "2" is not an enum. |
715 | D.addReloc(R: {.InsnOffset: 24, .TypeID: 2, .OffsetNameOff: D.addString(S: "1" ), .RelocKind: BTF::ENUM_VALUE}); |
716 | // Incorrect relocation: value #42 does not exist for enum "foo". |
717 | D.addReloc(R: {.InsnOffset: 32, .TypeID: 1, .OffsetNameOff: D.addString(S: "42" ), .RelocKind: BTF::ENUM_VALUE}); |
718 | // An ok relocation accessing value #1: A. |
719 | D.addReloc(R: {.InsnOffset: 40, .TypeID: 3, .OffsetNameOff: D.addString(S: "0" ), .RelocKind: BTF::ENUM_VALUE_EXISTENCE}); |
720 | // An ok relocation accessing value #2: B. |
721 | D.addReloc(R: {.InsnOffset: 48, .TypeID: 3, .OffsetNameOff: D.addString(S: "1" ), .RelocKind: BTF::ENUM_VALUE}); |
722 | |
723 | BTFParser BTF; |
724 | Error E = BTF.parse(Obj: D.makeObj()); |
725 | EXPECT_FALSE(E); |
726 | |
727 | SYMBOLIZE(SA({0, 1}), "<enumval_exists> [1] enum foo::U = 1" ); |
728 | SYMBOLIZE(SA({8, 1}), "<enumval_value> [1] enum foo::V = 2" ); |
729 | SYMBOLIZE( |
730 | SA({16, 1}), |
731 | "<enumval_value> [1] '1:0' <unexpected enumval relocation spec size>" ); |
732 | SYMBOLIZE( |
733 | SA({24, 1}), |
734 | "<enumval_value> [2] '1' <unexpected type kind for enum relocation: 1>" ); |
735 | SYMBOLIZE(SA({32, 1}), "<enumval_value> [1] '42' <bad value index: 42>" ); |
736 | SYMBOLIZE(SA({40, 1}), "<enumval_exists> [3] enum bar::A = 1" ); |
737 | SYMBOLIZE(SA({48, 1}), "<enumval_value> [3] enum bar::B = 2" ); |
738 | } |
739 | |
740 | TEST(BTFParserTest, enumRelocsMods) { |
741 | MockData2 D; |
742 | // id 1: enum { U, V } |
743 | D.addType(Tp: {.NameOff: D.addString(S: "foo" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ENUM) | 2, {.Size: 4}}); |
744 | D.addTail(Tp: BTF::BTFEnum({.NameOff: D.addString(S: "U" ), .Val: 1})); |
745 | D.addTail(Tp: BTF::BTFEnum({.NameOff: D.addString(S: "V" ), .Val: 2})); |
746 | // id 2: typedef enum foo a; |
747 | D.addType(Tp: {.NameOff: D.addString(S: "a" ), .Info: mkInfo(Kind: BTF::BTF_KIND_TYPEDEF), {.Size: 1}}); |
748 | // id 3: const enum foo; |
749 | D.addType(Tp: {.NameOff: D.addString(S: "" ), .Info: mkInfo(Kind: BTF::BTF_KIND_CONST), {.Size: 1}}); |
750 | |
751 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0}); |
752 | D.addReloc(R: {.InsnOffset: 0, .TypeID: 2, .OffsetNameOff: D.addString(S: "0" ), .RelocKind: BTF::ENUM_VALUE}); |
753 | D.addReloc(R: {.InsnOffset: 8, .TypeID: 3, .OffsetNameOff: D.addString(S: "1" ), .RelocKind: BTF::ENUM_VALUE}); |
754 | |
755 | BTFParser BTF; |
756 | Error E = BTF.parse(Obj: D.makeObj()); |
757 | EXPECT_FALSE(E); |
758 | |
759 | SYMBOLIZE(SA({0, 1}), "<enumval_value> [2] typedef a::U = 1" ); |
760 | SYMBOLIZE(SA({8, 1}), "<enumval_value> [3] const enum foo::V = 2" ); |
761 | } |
762 | |
763 | TEST(BTFParserTest, fieldRelocs) { |
764 | MockData2 D; |
765 | // id 1: int |
766 | D.addType(Tp: {.NameOff: D.addString(S: "int" ), .Info: mkInfo(Kind: BTF::BTF_KIND_INT), {.Size: 4}}); |
767 | D.addTail(Tp: (uint32_t)0); |
768 | // id 2: struct foo { int a; int b; } |
769 | D.addType(Tp: {.NameOff: D.addString(S: "foo" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT) | 2, {.Size: 8}}); |
770 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "a" ), .Type: 1, .Offset: 0})); |
771 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "b" ), .Type: 1, .Offset: 0})); |
772 | // id 3: array of struct foo. |
773 | D.addType(Tp: {.NameOff: D.addString(S: "" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ARRAY), {.Size: 0}}); |
774 | D.addTail(Tp: BTF::BTFArray({.ElemType: 2, .IndexType: 1, .Nelems: 2})); |
775 | // id 4: struct bar { struct foo u[2]; int v; } |
776 | D.addType(Tp: {.NameOff: D.addString(S: "bar" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT) | 2, {.Size: 8}}); |
777 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "u" ), .Type: 3, .Offset: 0})); |
778 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "v" ), .Type: 1, .Offset: 0})); |
779 | // id 5: array with bad element type id. |
780 | D.addType(Tp: {.NameOff: D.addString(S: "" ), .Info: mkInfo(Kind: BTF::BTF_KIND_ARRAY), {.Size: 0}}); |
781 | D.addTail(Tp: BTF::BTFArray({.ElemType: 42, .IndexType: 1, .Nelems: 2})); |
782 | // id 6: struct buz { <bad type> u[2]; <bad type> v; } |
783 | D.addType(Tp: {.NameOff: D.addString(S: "bar" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT) | 2, {.Size: 8}}); |
784 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "u" ), .Type: 5, .Offset: 0})); |
785 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "v" ), .Type: 42, .Offset: 0})); |
786 | |
787 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0 /* patched automatically */}); |
788 | // All field relocations kinds for struct bar::v. |
789 | D.addReloc(R: {.InsnOffset: 0, .TypeID: 4, .OffsetNameOff: D.addString(S: "0:1" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
790 | D.addReloc(R: {.InsnOffset: 8, .TypeID: 4, .OffsetNameOff: D.addString(S: "0:1" ), .RelocKind: BTF::FIELD_BYTE_SIZE}); |
791 | D.addReloc(R: {.InsnOffset: 16, .TypeID: 4, .OffsetNameOff: D.addString(S: "0:1" ), .RelocKind: BTF::FIELD_EXISTENCE}); |
792 | D.addReloc(R: {.InsnOffset: 24, .TypeID: 4, .OffsetNameOff: D.addString(S: "0:1" ), .RelocKind: BTF::FIELD_SIGNEDNESS}); |
793 | D.addReloc(R: {.InsnOffset: 32, .TypeID: 4, .OffsetNameOff: D.addString(S: "0:1" ), .RelocKind: BTF::FIELD_LSHIFT_U64}); |
794 | D.addReloc(R: {.InsnOffset: 40, .TypeID: 4, .OffsetNameOff: D.addString(S: "0:1" ), .RelocKind: BTF::FIELD_RSHIFT_U64}); |
795 | // Non-zero first idx. |
796 | D.addReloc(R: {.InsnOffset: 48, .TypeID: 4, .OffsetNameOff: D.addString(S: "7:1" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
797 | // Access through array and struct: struct bar::u[1].a. |
798 | D.addReloc(R: {.InsnOffset: 56, .TypeID: 4, .OffsetNameOff: D.addString(S: "0:0:1:0" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
799 | // Access through array and struct: struct bar::u[1].b. |
800 | D.addReloc(R: {.InsnOffset: 64, .TypeID: 4, .OffsetNameOff: D.addString(S: "0:0:1:1" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
801 | // Incorrect relocation: empty access string. |
802 | D.addReloc(R: {.InsnOffset: 72, .TypeID: 4, .OffsetNameOff: D.addString(S: "" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
803 | // Incorrect relocation: member index out of range (only two members in bar). |
804 | D.addReloc(R: {.InsnOffset: 80, .TypeID: 4, .OffsetNameOff: D.addString(S: "0:2" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
805 | // Incorrect relocation: unknown element type id (buz::u[0] access). |
806 | D.addReloc(R: {.InsnOffset: 88, .TypeID: 6, .OffsetNameOff: D.addString(S: "0:0:0" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
807 | // Incorrect relocation: unknown member type id (buz::v access). |
808 | D.addReloc(R: {.InsnOffset: 96, .TypeID: 6, .OffsetNameOff: D.addString(S: "0:1:0" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
809 | // Incorrect relocation: non structural type in the middle of access string |
810 | // struct bar::v.<something>. |
811 | D.addReloc(R: {.InsnOffset: 104, .TypeID: 4, .OffsetNameOff: D.addString(S: "0:1:0" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
812 | |
813 | BTFParser BTF; |
814 | Error E = BTF.parse(Obj: D.makeObj()); |
815 | EXPECT_FALSE(E); |
816 | |
817 | SYMBOLIZE(SA({0, 1}), "<byte_off> [4] struct bar::v (0:1)" ); |
818 | SYMBOLIZE(SA({8, 1}), "<byte_sz> [4] struct bar::v (0:1)" ); |
819 | SYMBOLIZE(SA({16, 1}), "<field_exists> [4] struct bar::v (0:1)" ); |
820 | SYMBOLIZE(SA({24, 1}), "<signed> [4] struct bar::v (0:1)" ); |
821 | SYMBOLIZE(SA({32, 1}), "<lshift_u64> [4] struct bar::v (0:1)" ); |
822 | SYMBOLIZE(SA({40, 1}), "<rshift_u64> [4] struct bar::v (0:1)" ); |
823 | SYMBOLIZE(SA({48, 1}), "<byte_off> [4] struct bar::[7].v (7:1)" ); |
824 | SYMBOLIZE(SA({56, 1}), "<byte_off> [4] struct bar::u[1].a (0:0:1:0)" ); |
825 | SYMBOLIZE(SA({64, 1}), "<byte_off> [4] struct bar::u[1].b (0:0:1:1)" ); |
826 | SYMBOLIZE(SA({72, 1}), "<byte_off> [4] '' <field spec too short>" ); |
827 | SYMBOLIZE(SA({80, 1}), |
828 | "<byte_off> [4] '0:2' " |
829 | "<member index 2 for spec sub-string 1 is out of range>" ); |
830 | SYMBOLIZE(SA({88, 1}), "<byte_off> [6] '0:0:0' " |
831 | "<unknown element type id 42 for spec sub-string 2>" ); |
832 | SYMBOLIZE(SA({96, 1}), "<byte_off> [6] '0:1:0' " |
833 | "<unknown member type id 42 for spec sub-string 1>" ); |
834 | SYMBOLIZE(SA({104, 1}), "<byte_off> [4] '0:1:0' " |
835 | "<unexpected type kind 1 for spec sub-string 2>" ); |
836 | } |
837 | |
838 | TEST(BTFParserTest, fieldRelocsMods) { |
839 | MockData2 D; |
840 | // struct foo { |
841 | // int u; |
842 | // } |
843 | // typedef struct foo bar; |
844 | // struct buz { |
845 | // const bar v; |
846 | // } |
847 | // typedef buz quux; |
848 | // const volatile restrict quux <some-var>; |
849 | uint32_t Int = |
850 | D.addType(Tp: {.NameOff: D.addString(S: "int" ), .Info: mkInfo(Kind: BTF::BTF_KIND_INT), {.Size: 4}}); |
851 | D.addTail(Tp: (uint32_t)0); |
852 | uint32_t Foo = |
853 | D.addType(Tp: {.NameOff: D.addString(S: "foo" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT) | 1, {.Size: 4}}); |
854 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "u" ), .Type: Int, .Offset: 0})); |
855 | uint32_t Bar = |
856 | D.addType(Tp: {.NameOff: D.addString(S: "bar" ), .Info: mkInfo(Kind: BTF::BTF_KIND_TYPEDEF), {.Size: Foo}}); |
857 | uint32_t CBar = |
858 | D.addType(Tp: {.NameOff: D.addString(S: "bar" ), .Info: mkInfo(Kind: BTF::BTF_KIND_CONST), {.Size: Bar}}); |
859 | uint32_t Buz = |
860 | D.addType(Tp: {.NameOff: D.addString(S: "buz" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT) | 1, {.Size: 4}}); |
861 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "v" ), .Type: CBar, .Offset: 0})); |
862 | uint32_t Quux = |
863 | D.addType(Tp: {.NameOff: D.addString(S: "quux" ), .Info: mkInfo(Kind: BTF::BTF_KIND_TYPEDEF), {.Size: Buz}}); |
864 | uint32_t RQuux = |
865 | D.addType(Tp: {.NameOff: D.addString(S: "" ), .Info: mkInfo(Kind: BTF::BTF_KIND_RESTRICT), {.Size: Quux}}); |
866 | uint32_t VRQuux = |
867 | D.addType(Tp: {.NameOff: D.addString(S: "" ), .Info: mkInfo(Kind: BTF::BTF_KIND_VOLATILE), {.Size: RQuux}}); |
868 | uint32_t CVRQuux = |
869 | D.addType(Tp: {.NameOff: D.addString(S: "" ), .Info: mkInfo(Kind: BTF::BTF_KIND_CONST), {.Size: VRQuux}}); |
870 | uint32_t CUnknown = |
871 | D.addType(Tp: {.NameOff: D.addString(S: "" ), .Info: mkInfo(Kind: BTF::BTF_KIND_CONST), {.Size: 77}}); |
872 | uint32_t CVUnknown = |
873 | D.addType(Tp: {.NameOff: D.addString(S: "" ), .Info: mkInfo(Kind: BTF::BTF_KIND_VOLATILE), {.Size: CUnknown}}); |
874 | |
875 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0}); |
876 | D.addReloc(R: {.InsnOffset: 0, .TypeID: Bar, .OffsetNameOff: D.addString(S: "0:0" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
877 | D.addReloc(R: {.InsnOffset: 8, .TypeID: CVRQuux, .OffsetNameOff: D.addString(S: "0:0:0" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
878 | D.addReloc(R: {.InsnOffset: 16, .TypeID: CVUnknown, .OffsetNameOff: D.addString(S: "0:1:2" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
879 | |
880 | BTFParser BTF; |
881 | Error E = BTF.parse(Obj: D.makeObj()); |
882 | EXPECT_FALSE(E); |
883 | |
884 | // Should show modifiers / name of typedef. |
885 | SYMBOLIZE(SA({0, 1}), "<byte_off> [3] typedef bar::u (0:0)" ); |
886 | SYMBOLIZE(SA({8, 1}), |
887 | "<byte_off> [9] const volatile restrict typedef quux::v.u (0:0:0)" ); |
888 | SYMBOLIZE(SA({16, 1}), |
889 | "<byte_off> [11] '0:1:2' <unknown type id: 77 in modifiers chain>" ); |
890 | } |
891 | |
892 | TEST(BTFParserTest, relocTypeTagAndVoid) { |
893 | MockData2 D; |
894 | // __attribute__((type_tag("tag"))) void |
895 | uint32_t Tag = |
896 | D.addType(Tp: {.NameOff: D.addString(S: "tag" ), .Info: mkInfo(Kind: BTF::BTF_KIND_TYPE_TAG), {.Size: 0}}); |
897 | |
898 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0}); |
899 | D.addReloc(R: {.InsnOffset: 0, .TypeID: Tag, .OffsetNameOff: D.addString(S: "0" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
900 | D.addReloc(R: {.InsnOffset: 8, .TypeID: 0 /* void */, .OffsetNameOff: D.addString(S: "0" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
901 | |
902 | BTFParser BTF; |
903 | Error E = BTF.parse(Obj: D.makeObj()); |
904 | EXPECT_FALSE(E); |
905 | |
906 | SYMBOLIZE(SA({0, 1}), "<type_exists> [1] type_tag(\"tag\") void" ); |
907 | SYMBOLIZE(SA({8, 1}), "<type_exists> [0] void" ); |
908 | } |
909 | |
910 | TEST(BTFParserTest, longRelocModifiersCycle) { |
911 | MockData2 D; |
912 | |
913 | D.addType( |
914 | Tp: {.NameOff: D.addString(S: "" ), .Info: mkInfo(Kind: BTF::BTF_KIND_CONST), {.Size: 1 /* ourselves */}}); |
915 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0}); |
916 | D.addReloc(R: {.InsnOffset: 0, .TypeID: 1, .OffsetNameOff: D.addString(S: "" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
917 | |
918 | BTFParser BTF; |
919 | Error E = BTF.parse(Obj: D.makeObj()); |
920 | EXPECT_FALSE(E); |
921 | |
922 | SYMBOLIZE(SA({0, 1}), "<type_exists> [1] '' <modifiers chain is too long>" ); |
923 | } |
924 | |
925 | TEST(BTFParserTest, relocAnonFieldsAndTypes) { |
926 | MockData2 D; |
927 | |
928 | // struct { |
929 | // int :32; |
930 | // } v; |
931 | uint32_t Int = |
932 | D.addType(Tp: {.NameOff: D.addString(S: "int" ), .Info: mkInfo(Kind: BTF::BTF_KIND_INT), {.Size: 4}}); |
933 | D.addTail(Tp: (uint32_t)0); |
934 | uint32_t Anon = |
935 | D.addType(Tp: {.NameOff: D.addString(S: "" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT) | 1, {.Size: 4}}); |
936 | D.addTail(Tp: BTF::BTFMember({.NameOff: D.addString(S: "" ), .Type: Int, .Offset: 0})); |
937 | |
938 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0}); |
939 | D.addReloc(R: {.InsnOffset: 0, .TypeID: Anon, .OffsetNameOff: D.addString(S: "0" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
940 | D.addReloc(R: {.InsnOffset: 8, .TypeID: Anon, .OffsetNameOff: D.addString(S: "0:0" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
941 | |
942 | BTFParser BTF; |
943 | Error E = BTF.parse(Obj: D.makeObj()); |
944 | EXPECT_FALSE(E); |
945 | |
946 | SYMBOLIZE(SA({0, 1}), "<type_exists> [2] struct <anon 2>" ); |
947 | SYMBOLIZE(SA({8, 1}), "<byte_off> [2] struct <anon 2>::<anon 0> (0:0)" ); |
948 | } |
949 | |
950 | TEST(BTFParserTest, miscBadRelos) { |
951 | MockData2 D; |
952 | |
953 | uint32_t S = D.addType(Tp: {.NameOff: D.addString(S: "S" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT), {.Size: 0}}); |
954 | |
955 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0}); |
956 | D.addReloc(R: {.InsnOffset: 0, .TypeID: 0, .OffsetNameOff: D.addString(S: "" ), .RelocKind: 777}); |
957 | D.addReloc(R: {.InsnOffset: 8, .TypeID: S, .OffsetNameOff: D.addString(S: "abc" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
958 | D.addReloc(R: {.InsnOffset: 16, .TypeID: S, .OffsetNameOff: D.addString(S: "0#" ), .RelocKind: BTF::FIELD_BYTE_OFFSET}); |
959 | |
960 | BTFParser BTF; |
961 | Error E = BTF.parse(Obj: D.makeObj()); |
962 | EXPECT_FALSE(E); |
963 | |
964 | SYMBOLIZE(SA({0, 1}), |
965 | "<reloc kind #777> [0] '' <unknown relocation kind: 777>" ); |
966 | SYMBOLIZE(SA({8, 1}), "<byte_off> [1] 'abc' <spec string is not a number>" ); |
967 | SYMBOLIZE(SA({16, 1}), |
968 | "<byte_off> [1] '0#' <unexpected spec string delimiter: '#'>" ); |
969 | } |
970 | |
971 | TEST(BTFParserTest, relocsMultipleSections) { |
972 | MockData2 D; |
973 | |
974 | uint32_t S = D.addType(Tp: {.NameOff: D.addString(S: "S" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT), {.Size: 0}}); |
975 | uint32_t T = D.addType(Tp: {.NameOff: D.addString(S: "T" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT), {.Size: 0}}); |
976 | |
977 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0}); |
978 | D.addReloc(R: {.InsnOffset: 0, .TypeID: S, .OffsetNameOff: D.addString(S: "" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
979 | D.addReloc(R: {.InsnOffset: 8, .TypeID: S, .OffsetNameOff: D.addString(S: "" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
980 | |
981 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "bar" ), .NumFieldReloc: 0}); |
982 | D.addReloc(R: {.InsnOffset: 8, .TypeID: T, .OffsetNameOff: D.addString(S: "" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
983 | D.addReloc(R: {.InsnOffset: 16, .TypeID: T, .OffsetNameOff: D.addString(S: "" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
984 | |
985 | BTFParser BTF; |
986 | Error E = BTF.parse(Obj: D.makeObj()); |
987 | EXPECT_FALSE(E); |
988 | |
989 | EXPECT_TRUE(BTF.findFieldReloc({0, 1})); |
990 | EXPECT_TRUE(BTF.findFieldReloc({8, 1})); |
991 | EXPECT_FALSE(BTF.findFieldReloc({16, 1})); |
992 | |
993 | EXPECT_FALSE(BTF.findFieldReloc({0, 2})); |
994 | EXPECT_TRUE(BTF.findFieldReloc({8, 2})); |
995 | EXPECT_TRUE(BTF.findFieldReloc({16, 2})); |
996 | |
997 | EXPECT_FALSE(BTF.findFieldReloc({0, 3})); |
998 | EXPECT_FALSE(BTF.findFieldReloc({8, 3})); |
999 | EXPECT_FALSE(BTF.findFieldReloc({16, 3})); |
1000 | |
1001 | auto AssertReloType = [&](const SectionedAddress &A, const char *Name) { |
1002 | const BTF::BPFFieldReloc *Relo = BTF.findFieldReloc(Address: A); |
1003 | ASSERT_TRUE(Relo); |
1004 | const BTF::CommonType *Type = BTF.findType(Id: Relo->TypeID); |
1005 | ASSERT_TRUE(Type); |
1006 | EXPECT_EQ(BTF.findString(Type->NameOff), Name); |
1007 | }; |
1008 | |
1009 | AssertReloType({.Address: 8, .SectionIndex: 1}, "S" ); |
1010 | AssertReloType({.Address: 8, .SectionIndex: 2}, "T" ); |
1011 | } |
1012 | |
1013 | TEST(BTFParserTest, parserResetReloAndTypes) { |
1014 | BTFParser BTF; |
1015 | MockData2 D; |
1016 | |
1017 | // First time: two types, two relocations. |
1018 | D.addType(Tp: {.NameOff: D.addString(S: "foo" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT), {.Size: 0}}); |
1019 | D.addType(Tp: {.NameOff: D.addString(S: "bar" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT), {.Size: 0}}); |
1020 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0}); |
1021 | D.addReloc(R: {.InsnOffset: 0, .TypeID: 1, .OffsetNameOff: D.addString(S: "" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
1022 | D.addReloc(R: {.InsnOffset: 8, .TypeID: 2, .OffsetNameOff: D.addString(S: "" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
1023 | |
1024 | Error E1 = BTF.parse(Obj: D.makeObj()); |
1025 | EXPECT_FALSE(E1); |
1026 | |
1027 | ASSERT_TRUE(BTF.findType(1)); |
1028 | EXPECT_EQ(BTF.findString(BTF.findType(1)->NameOff), "foo" ); |
1029 | EXPECT_TRUE(BTF.findType(2)); |
1030 | EXPECT_TRUE(BTF.findFieldReloc({0, 1})); |
1031 | EXPECT_TRUE(BTF.findFieldReloc({8, 1})); |
1032 | |
1033 | // Second time: one type, one relocation. |
1034 | D.reset(); |
1035 | D.addType(Tp: {.NameOff: D.addString(S: "buz" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT), {.Size: 0}}); |
1036 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0}); |
1037 | D.addReloc(R: {.InsnOffset: 0, .TypeID: 1, .OffsetNameOff: D.addString(S: "" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
1038 | |
1039 | Error E2 = BTF.parse(Obj: D.makeObj()); |
1040 | EXPECT_FALSE(E2); |
1041 | |
1042 | ASSERT_TRUE(BTF.findType(1)); |
1043 | EXPECT_EQ(BTF.findString(BTF.findType(1)->NameOff), "buz" ); |
1044 | EXPECT_FALSE(BTF.findType(2)); |
1045 | EXPECT_TRUE(BTF.findFieldReloc({0, 1})); |
1046 | EXPECT_FALSE(BTF.findFieldReloc({8, 1})); |
1047 | } |
1048 | |
1049 | TEST(BTFParserTest, selectiveLoad) { |
1050 | BTFParser BTF1, BTF2, BTF3; |
1051 | MockData2 D; |
1052 | |
1053 | D.addType(Tp: {.NameOff: D.addString(S: "foo" ), .Info: mkInfo(Kind: BTF::BTF_KIND_STRUCT), {.Size: 0}}); |
1054 | D.addRelocSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumFieldReloc: 0}); |
1055 | D.addReloc(R: {.InsnOffset: 0, .TypeID: 1, .OffsetNameOff: D.addString(S: "" ), .RelocKind: BTF::TYPE_EXISTENCE}); |
1056 | D.addLinesSec(R: {.SecNameOff: D.addString(S: "foo" ), .NumLineInfo: 0}); |
1057 | D.addLine(R: {.InsnOffset: 0, .FileNameOff: D.addString(S: "file.c" ), .LineOff: D.addString(S: "some line" ), LC(2, 3)}); |
1058 | |
1059 | BTFParser::ParseOptions Opts; |
1060 | |
1061 | ObjectFile &Obj1 = D.makeObj(); |
1062 | Opts = {}; |
1063 | Opts.LoadLines = true; |
1064 | ASSERT_SUCCEEDED(BTF1.parse(Obj1, Opts)); |
1065 | |
1066 | Opts = {}; |
1067 | Opts.LoadTypes = true; |
1068 | ASSERT_SUCCEEDED(BTF2.parse(Obj1, Opts)); |
1069 | |
1070 | Opts = {}; |
1071 | Opts.LoadRelocs = true; |
1072 | ASSERT_SUCCEEDED(BTF3.parse(Obj1, Opts)); |
1073 | |
1074 | EXPECT_TRUE(BTF1.findLineInfo({0, 1})); |
1075 | EXPECT_FALSE(BTF2.findLineInfo({0, 1})); |
1076 | EXPECT_FALSE(BTF3.findLineInfo({0, 1})); |
1077 | |
1078 | EXPECT_FALSE(BTF1.findType(1)); |
1079 | EXPECT_TRUE(BTF2.findType(1)); |
1080 | EXPECT_FALSE(BTF3.findType(1)); |
1081 | |
1082 | EXPECT_FALSE(BTF1.findFieldReloc({0, 1})); |
1083 | EXPECT_FALSE(BTF2.findFieldReloc({0, 1})); |
1084 | EXPECT_TRUE(BTF3.findFieldReloc({0, 1})); |
1085 | } |
1086 | |
1087 | } // namespace |
1088 | |