1 //===-- BreakpadRecords.cpp -----------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "Plugins/ObjectFile/Breakpad/BreakpadRecords.h"
10 #include "lldb/lldb-defines.h"
11 #include "llvm/ADT/StringExtras.h"
12 #include "llvm/ADT/StringSwitch.h"
13 #include "llvm/Support/Endian.h"
14 #include "llvm/Support/FormatVariadic.h"
17 using namespace lldb_private
;
18 using namespace lldb_private::breakpad
;
39 static T
stringTo(llvm::StringRef Str
);
41 template <> Token stringTo
<Token
>(llvm::StringRef Str
) {
42 return llvm::StringSwitch
<Token
>(Str
)
43 .Case("MODULE", Token::Module
)
44 .Case("INFO", Token::Info
)
45 .Case("CODE_ID", Token::CodeID
)
46 .Case("FILE", Token::File
)
47 .Case("FUNC", Token::Func
)
48 .Case("INLINE", Token::Inline
)
49 .Case("INLINE_ORIGIN", Token::InlineOrigin
)
50 .Case("PUBLIC", Token::Public
)
51 .Case("STACK", Token::Stack
)
52 .Case("CFI", Token::CFI
)
53 .Case("INIT", Token::Init
)
54 .Case("WIN", Token::Win
)
55 .Default(Token::Unknown
);
59 llvm::Triple::OSType stringTo
<llvm::Triple::OSType
>(llvm::StringRef Str
) {
61 return llvm::StringSwitch
<Triple::OSType
>(Str
)
62 .Case("Linux", Triple::Linux
)
63 .Case("mac", Triple::MacOSX
)
64 .Case("windows", Triple::Win32
)
65 .Default(Triple::UnknownOS
);
69 llvm::Triple::ArchType stringTo
<llvm::Triple::ArchType
>(llvm::StringRef Str
) {
71 return llvm::StringSwitch
<Triple::ArchType
>(Str
)
72 .Case("arm", Triple::arm
)
73 .Cases("arm64", "arm64e", Triple::aarch64
)
74 .Case("mips", Triple::mips
)
75 .Case("msp430", Triple::msp430
)
76 .Case("ppc", Triple::ppc
)
77 .Case("ppc64", Triple::ppc64
)
78 .Case("s390", Triple::systemz
)
79 .Case("sparc", Triple::sparc
)
80 .Case("sparcv9", Triple::sparcv9
)
81 .Case("x86", Triple::x86
)
82 .Cases("x86_64", "x86_64h", Triple::x86_64
)
83 .Default(Triple::UnknownArch
);
87 static T
consume(llvm::StringRef
&Str
) {
88 llvm::StringRef Token
;
89 std::tie(Token
, Str
) = getToken(Str
);
90 return stringTo
<T
>(Token
);
93 /// Return the number of hex digits needed to encode an (POD) object of a given
95 template <typename T
> static constexpr size_t hex_digits() {
99 static UUID
parseModuleId(llvm::Triple::OSType os
, llvm::StringRef str
) {
101 using uuid_t
= uint8_t[16];
103 llvm::support::ubig32_t age
;
105 static_assert(sizeof(data
) == 20);
106 // The textual module id encoding should be between 33 and 40 bytes long,
107 // depending on the size of the age field, which is of variable length.
108 // The first three chunks of the id are encoded in big endian, so we need to
110 if (str
.size() <= hex_digits
<data_t::uuid_t
>() ||
111 str
.size() > hex_digits
<data_t
>())
113 if (!all_of(str
, llvm::isHexDigit
))
116 llvm::StringRef uuid_str
= str
.take_front(hex_digits
<data_t::uuid_t
>());
117 llvm::StringRef age_str
= str
.drop_front(hex_digits
<data_t::uuid_t
>());
119 llvm::copy(fromHex(uuid_str
), data
.uuid
);
121 bool success
= to_integer(age_str
, age
, 16);
123 UNUSED_IF_ASSERT_DISABLED(success
);
126 // On non-windows, the age field should always be zero, so we don't include to
127 // match the native uuid format of these platforms.
128 return UUID(&data
, os
== llvm::Triple::Win32
? sizeof(data
)
129 : sizeof(data
.uuid
));
132 std::optional
<Record::Kind
> Record::classify(llvm::StringRef Line
) {
133 Token Tok
= consume
<Token
>(Line
);
136 return Record::Module
;
144 return Record::Public
;
146 Tok
= consume
<Token
>(Line
);
149 return Record::StackCFI
;
151 return Record::StackWin
;
156 return Record::Inline
;
157 case Token::InlineOrigin
:
158 return Record::InlineOrigin
;
160 // Optimistically assume that any unrecognised token means this is a line
161 // record, those don't have a special keyword and start directly with a
169 // These should never appear at the start of a valid record.
172 llvm_unreachable("Fully covered switch above!");
175 std::optional
<ModuleRecord
> ModuleRecord::parse(llvm::StringRef Line
) {
176 // MODULE Linux x86_64 E5894855C35DCCCCCCCCCCCCCCCCCCCC0 a.out
177 if (consume
<Token
>(Line
) != Token::Module
)
180 llvm::Triple::OSType OS
= consume
<llvm::Triple::OSType
>(Line
);
181 if (OS
== llvm::Triple::UnknownOS
)
184 llvm::Triple::ArchType Arch
= consume
<llvm::Triple::ArchType
>(Line
);
185 if (Arch
== llvm::Triple::UnknownArch
)
189 std::tie(Str
, Line
) = getToken(Line
);
190 UUID ID
= parseModuleId(OS
, Str
);
194 return ModuleRecord(OS
, Arch
, std::move(ID
));
197 llvm::raw_ostream
&breakpad::operator<<(llvm::raw_ostream
&OS
,
198 const ModuleRecord
&R
) {
199 return OS
<< "MODULE " << llvm::Triple::getOSTypeName(R
.OS
) << " "
200 << llvm::Triple::getArchTypeName(R
.Arch
) << " "
201 << R
.ID
.GetAsString();
204 std::optional
<InfoRecord
> InfoRecord::parse(llvm::StringRef Line
) {
205 // INFO CODE_ID 554889E55DC3CCCCCCCCCCCCCCCCCCCC [a.exe]
206 if (consume
<Token
>(Line
) != Token::Info
)
209 if (consume
<Token
>(Line
) != Token::CodeID
)
213 std::tie(Str
, Line
) = getToken(Line
);
214 // If we don't have any text following the code ID (e.g. on linux), we should
215 // use this as the UUID. Otherwise, we should revert back to the module ID.
217 if (Line
.trim().empty()) {
218 if (Str
.empty() || !ID
.SetFromStringRef(Str
))
221 return InfoRecord(std::move(ID
));
224 llvm::raw_ostream
&breakpad::operator<<(llvm::raw_ostream
&OS
,
225 const InfoRecord
&R
) {
226 return OS
<< "INFO CODE_ID " << R
.ID
.GetAsString();
229 template <typename T
>
230 static std::optional
<T
> parseNumberName(llvm::StringRef Line
, Token TokenType
) {
232 if (consume
<Token
>(Line
) != TokenType
)
237 std::tie(Str
, Line
) = getToken(Line
);
238 if (!to_integer(Str
, Number
))
241 llvm::StringRef Name
= Line
.trim();
245 return T(Number
, Name
);
248 std::optional
<FileRecord
> FileRecord::parse(llvm::StringRef Line
) {
250 return parseNumberName
<FileRecord
>(Line
, Token::File
);
253 llvm::raw_ostream
&breakpad::operator<<(llvm::raw_ostream
&OS
,
254 const FileRecord
&R
) {
255 return OS
<< "FILE " << R
.Number
<< " " << R
.Name
;
258 std::optional
<InlineOriginRecord
>
259 InlineOriginRecord::parse(llvm::StringRef Line
) {
260 // INLINE_ORIGIN number name
261 return parseNumberName
<InlineOriginRecord
>(Line
, Token::InlineOrigin
);
264 llvm::raw_ostream
&breakpad::operator<<(llvm::raw_ostream
&OS
,
265 const InlineOriginRecord
&R
) {
266 return OS
<< "INLINE_ORIGIN " << R
.Number
<< " " << R
.Name
;
269 static bool parsePublicOrFunc(llvm::StringRef Line
, bool &Multiple
,
270 lldb::addr_t
&Address
, lldb::addr_t
*Size
,
271 lldb::addr_t
&ParamSize
, llvm::StringRef
&Name
) {
272 // PUBLIC [m] address param_size name
274 // FUNC [m] address size param_size name
276 Token Tok
= Size
? Token::Func
: Token::Public
;
278 if (consume
<Token
>(Line
) != Tok
)
282 std::tie(Str
, Line
) = getToken(Line
);
283 Multiple
= Str
== "m";
286 std::tie(Str
, Line
) = getToken(Line
);
287 if (!to_integer(Str
, Address
, 16))
290 if (Tok
== Token::Func
) {
291 std::tie(Str
, Line
) = getToken(Line
);
292 if (!to_integer(Str
, *Size
, 16))
296 std::tie(Str
, Line
) = getToken(Line
);
297 if (!to_integer(Str
, ParamSize
, 16))
307 std::optional
<FuncRecord
> FuncRecord::parse(llvm::StringRef Line
) {
309 lldb::addr_t Address
, Size
, ParamSize
;
310 llvm::StringRef Name
;
312 if (parsePublicOrFunc(Line
, Multiple
, Address
, &Size
, ParamSize
, Name
))
313 return FuncRecord(Multiple
, Address
, Size
, ParamSize
, Name
);
318 bool breakpad::operator==(const FuncRecord
&L
, const FuncRecord
&R
) {
319 return L
.Multiple
== R
.Multiple
&& L
.Address
== R
.Address
&&
320 L
.Size
== R
.Size
&& L
.ParamSize
== R
.ParamSize
&& L
.Name
== R
.Name
;
322 llvm::raw_ostream
&breakpad::operator<<(llvm::raw_ostream
&OS
,
323 const FuncRecord
&R
) {
324 return OS
<< llvm::formatv("FUNC {0}{1:x-} {2:x-} {3:x-} {4}",
325 R
.Multiple
? "m " : "", R
.Address
, R
.Size
,
326 R
.ParamSize
, R
.Name
);
329 std::optional
<InlineRecord
> InlineRecord::parse(llvm::StringRef Line
) {
330 // INLINE inline_nest_level call_site_line call_site_file_num origin_num
332 if (consume
<Token
>(Line
) != Token::Inline
)
335 llvm::SmallVector
<llvm::StringRef
> Tokens
;
336 SplitString(Line
, Tokens
, " ");
337 if (Tokens
.size() < 6 || Tokens
.size() % 2 == 1)
340 size_t InlineNestLevel
;
341 uint32_t CallSiteLineNum
;
342 size_t CallSiteFileNum
;
344 if (!(to_integer(Tokens
[0], InlineNestLevel
) &&
345 to_integer(Tokens
[1], CallSiteLineNum
) &&
346 to_integer(Tokens
[2], CallSiteFileNum
) &&
347 to_integer(Tokens
[3], OriginNum
)))
350 InlineRecord Record
= InlineRecord(InlineNestLevel
, CallSiteLineNum
,
351 CallSiteFileNum
, OriginNum
);
352 for (size_t i
= 4; i
< Tokens
.size(); i
+= 2) {
353 lldb::addr_t Address
;
354 if (!to_integer(Tokens
[i
], Address
, 16))
357 if (!to_integer(Tokens
[i
+ 1].trim(), Size
, 16))
359 Record
.Ranges
.emplace_back(Address
, Size
);
364 bool breakpad::operator==(const InlineRecord
&L
, const InlineRecord
&R
) {
365 return L
.InlineNestLevel
== R
.InlineNestLevel
&&
366 L
.CallSiteLineNum
== R
.CallSiteLineNum
&&
367 L
.CallSiteFileNum
== R
.CallSiteFileNum
&& L
.OriginNum
== R
.OriginNum
&&
368 L
.Ranges
== R
.Ranges
;
371 llvm::raw_ostream
&breakpad::operator<<(llvm::raw_ostream
&OS
,
372 const InlineRecord
&R
) {
373 OS
<< llvm::formatv("INLINE {0} {1} {2} {3}", R
.InlineNestLevel
,
374 R
.CallSiteLineNum
, R
.CallSiteFileNum
, R
.OriginNum
);
375 for (const auto &range
: R
.Ranges
) {
376 OS
<< llvm::formatv(" {0:x-} {1:x-}", range
.first
, range
.second
);
381 std::optional
<LineRecord
> LineRecord::parse(llvm::StringRef Line
) {
382 lldb::addr_t Address
;
384 std::tie(Str
, Line
) = getToken(Line
);
385 if (!to_integer(Str
, Address
, 16))
389 std::tie(Str
, Line
) = getToken(Line
);
390 if (!to_integer(Str
, Size
, 16))
394 std::tie(Str
, Line
) = getToken(Line
);
395 if (!to_integer(Str
, LineNum
))
399 std::tie(Str
, Line
) = getToken(Line
);
400 if (!to_integer(Str
, FileNum
))
403 return LineRecord(Address
, Size
, LineNum
, FileNum
);
406 bool breakpad::operator==(const LineRecord
&L
, const LineRecord
&R
) {
407 return L
.Address
== R
.Address
&& L
.Size
== R
.Size
&& L
.LineNum
== R
.LineNum
&&
408 L
.FileNum
== R
.FileNum
;
410 llvm::raw_ostream
&breakpad::operator<<(llvm::raw_ostream
&OS
,
411 const LineRecord
&R
) {
412 return OS
<< llvm::formatv("{0:x-} {1:x-} {2} {3}", R
.Address
, R
.Size
,
413 R
.LineNum
, R
.FileNum
);
416 std::optional
<PublicRecord
> PublicRecord::parse(llvm::StringRef Line
) {
418 lldb::addr_t Address
, ParamSize
;
419 llvm::StringRef Name
;
421 if (parsePublicOrFunc(Line
, Multiple
, Address
, nullptr, ParamSize
, Name
))
422 return PublicRecord(Multiple
, Address
, ParamSize
, Name
);
427 bool breakpad::operator==(const PublicRecord
&L
, const PublicRecord
&R
) {
428 return L
.Multiple
== R
.Multiple
&& L
.Address
== R
.Address
&&
429 L
.ParamSize
== R
.ParamSize
&& L
.Name
== R
.Name
;
431 llvm::raw_ostream
&breakpad::operator<<(llvm::raw_ostream
&OS
,
432 const PublicRecord
&R
) {
433 return OS
<< llvm::formatv("PUBLIC {0}{1:x-} {2:x-} {3}",
434 R
.Multiple
? "m " : "", R
.Address
, R
.ParamSize
,
438 std::optional
<StackCFIRecord
> StackCFIRecord::parse(llvm::StringRef Line
) {
439 // STACK CFI INIT address size reg1: expr1 reg2: expr2 ...
441 // STACK CFI address reg1: expr1 reg2: expr2 ...
442 // No token in exprN ends with a colon.
444 if (consume
<Token
>(Line
) != Token::Stack
)
446 if (consume
<Token
>(Line
) != Token::CFI
)
450 std::tie(Str
, Line
) = getToken(Line
);
452 bool IsInitRecord
= stringTo
<Token
>(Str
) == Token::Init
;
454 std::tie(Str
, Line
) = getToken(Line
);
456 lldb::addr_t Address
;
457 if (!to_integer(Str
, Address
, 16))
460 std::optional
<lldb::addr_t
> Size
;
463 std::tie(Str
, Line
) = getToken(Line
);
464 if (!to_integer(Str
, *Size
, 16))
468 return StackCFIRecord(Address
, Size
, Line
.trim());
471 bool breakpad::operator==(const StackCFIRecord
&L
, const StackCFIRecord
&R
) {
472 return L
.Address
== R
.Address
&& L
.Size
== R
.Size
&&
473 L
.UnwindRules
== R
.UnwindRules
;
476 llvm::raw_ostream
&breakpad::operator<<(llvm::raw_ostream
&OS
,
477 const StackCFIRecord
&R
) {
481 OS
<< llvm::formatv("{0:x-} ", R
.Address
);
483 OS
<< llvm::formatv("{0:x-} ", *R
.Size
);
484 return OS
<< " " << R
.UnwindRules
;
487 std::optional
<StackWinRecord
> StackWinRecord::parse(llvm::StringRef Line
) {
488 // STACK WIN type rva code_size prologue_size epilogue_size parameter_size
489 // saved_register_size local_size max_stack_size has_program_string
490 // program_string_OR_allocates_base_pointer
492 if (consume
<Token
>(Line
) != Token::Stack
)
494 if (consume
<Token
>(Line
) != Token::Win
)
499 std::tie(Str
, Line
) = getToken(Line
);
500 // Right now we only support the "FrameData" frame type.
501 if (!to_integer(Str
, Type
) || FrameType(Type
) != FrameType::FrameData
)
505 std::tie(Str
, Line
) = getToken(Line
);
506 if (!to_integer(Str
, RVA
, 16))
509 lldb::addr_t CodeSize
;
510 std::tie(Str
, Line
) = getToken(Line
);
511 if (!to_integer(Str
, CodeSize
, 16))
514 // Skip fields which we aren't using right now.
515 std::tie(Str
, Line
) = getToken(Line
); // prologue_size
516 std::tie(Str
, Line
) = getToken(Line
); // epilogue_size
518 lldb::addr_t ParameterSize
;
519 std::tie(Str
, Line
) = getToken(Line
);
520 if (!to_integer(Str
, ParameterSize
, 16))
523 lldb::addr_t SavedRegisterSize
;
524 std::tie(Str
, Line
) = getToken(Line
);
525 if (!to_integer(Str
, SavedRegisterSize
, 16))
528 lldb::addr_t LocalSize
;
529 std::tie(Str
, Line
) = getToken(Line
);
530 if (!to_integer(Str
, LocalSize
, 16))
533 std::tie(Str
, Line
) = getToken(Line
); // max_stack_size
535 uint8_t HasProgramString
;
536 std::tie(Str
, Line
) = getToken(Line
);
537 if (!to_integer(Str
, HasProgramString
))
539 // FrameData records should always have a program string.
540 if (!HasProgramString
)
543 return StackWinRecord(RVA
, CodeSize
, ParameterSize
, SavedRegisterSize
,
544 LocalSize
, Line
.trim());
547 bool breakpad::operator==(const StackWinRecord
&L
, const StackWinRecord
&R
) {
548 return L
.RVA
== R
.RVA
&& L
.CodeSize
== R
.CodeSize
&&
549 L
.ParameterSize
== R
.ParameterSize
&&
550 L
.SavedRegisterSize
== R
.SavedRegisterSize
&&
551 L
.LocalSize
== R
.LocalSize
&& L
.ProgramString
== R
.ProgramString
;
554 llvm::raw_ostream
&breakpad::operator<<(llvm::raw_ostream
&OS
,
555 const StackWinRecord
&R
) {
556 return OS
<< llvm::formatv(
557 "STACK WIN 4 {0:x-} {1:x-} ? ? {2} {3} {4} ? 1 {5}", R
.RVA
,
558 R
.CodeSize
, R
.ParameterSize
, R
.SavedRegisterSize
, R
.LocalSize
,
562 llvm::StringRef
breakpad::toString(Record::Kind K
) {
574 case Record::InlineOrigin
:
575 return "INLINE_ORIGIN";
580 case Record::StackCFI
:
582 case Record::StackWin
:
585 llvm_unreachable("Unknown record kind!");