587 lines · cpp
1//===-- BreakpadRecords.cpp -----------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 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"15#include <optional>16 17using namespace lldb_private;18using namespace lldb_private::breakpad;19 20namespace {21enum class Token {22 Unknown,23 Module,24 Info,25 CodeID,26 File,27 Func,28 Inline,29 InlineOrigin,30 Public,31 Stack,32 CFI,33 Init,34 Win,35};36}37 38template<typename T>39static T stringTo(llvm::StringRef Str);40 41template <> 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);56}57 58template <>59llvm::Triple::OSType stringTo<llvm::Triple::OSType>(llvm::StringRef Str) {60 using llvm::Triple;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);66}67 68template <>69llvm::Triple::ArchType stringTo<llvm::Triple::ArchType>(llvm::StringRef Str) {70 using llvm::Triple;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);84}85 86template<typename T>87static T consume(llvm::StringRef &Str) {88 llvm::StringRef Token;89 std::tie(Token, Str) = getToken(Str);90 return stringTo<T>(Token);91}92 93/// Return the number of hex digits needed to encode an (POD) object of a given94/// type.95template <typename T> static constexpr size_t hex_digits() {96 return 2 * sizeof(T);97}98 99static UUID parseModuleId(llvm::Triple::OSType os, llvm::StringRef str) {100 struct data_t {101 using uuid_t = uint8_t[16];102 uuid_t uuid;103 llvm::support::ubig32_t age;104 } data;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 to109 // byte-swap those.110 if (str.size() <= hex_digits<data_t::uuid_t>() ||111 str.size() > hex_digits<data_t>())112 return UUID();113 if (!all_of(str, llvm::isHexDigit))114 return UUID();115 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>());118 119 llvm::copy(fromHex(uuid_str), data.uuid);120 uint32_t age;121 bool success = to_integer(age_str, age, 16);122 assert(success);123 UNUSED_IF_ASSERT_DISABLED(success);124 data.age = age;125 126 // On non-windows, the age field should always be zero, so we don't include to127 // match the native uuid format of these platforms.128 return UUID(&data, os == llvm::Triple::Win32 ? sizeof(data)129 : sizeof(data.uuid));130}131 132std::optional<Record::Kind> Record::classify(llvm::StringRef Line) {133 Token Tok = consume<Token>(Line);134 switch (Tok) {135 case Token::Module:136 return Record::Module;137 case Token::Info:138 return Record::Info;139 case Token::File:140 return Record::File;141 case Token::Func:142 return Record::Func;143 case Token::Public:144 return Record::Public;145 case Token::Stack:146 Tok = consume<Token>(Line);147 switch (Tok) {148 case Token::CFI:149 return Record::StackCFI;150 case Token::Win:151 return Record::StackWin;152 default:153 return std::nullopt;154 }155 case Token::Inline:156 return Record::Inline;157 case Token::InlineOrigin:158 return Record::InlineOrigin;159 case Token::Unknown:160 // Optimistically assume that any unrecognised token means this is a line161 // record, those don't have a special keyword and start directly with a162 // hex number.163 return Record::Line;164 165 case Token::CodeID:166 case Token::CFI:167 case Token::Init:168 case Token::Win:169 // These should never appear at the start of a valid record.170 return std::nullopt;171 }172 llvm_unreachable("Fully covered switch above!");173}174 175std::optional<ModuleRecord> ModuleRecord::parse(llvm::StringRef Line) {176 // MODULE Linux x86_64 E5894855C35DCCCCCCCCCCCCCCCCCCCC0 a.out177 if (consume<Token>(Line) != Token::Module)178 return std::nullopt;179 180 llvm::Triple::OSType OS = consume<llvm::Triple::OSType>(Line);181 if (OS == llvm::Triple::UnknownOS)182 return std::nullopt;183 184 llvm::Triple::ArchType Arch = consume<llvm::Triple::ArchType>(Line);185 if (Arch == llvm::Triple::UnknownArch)186 return std::nullopt;187 188 llvm::StringRef Str;189 std::tie(Str, Line) = getToken(Line);190 UUID ID = parseModuleId(OS, Str);191 if (!ID)192 return std::nullopt;193 194 return ModuleRecord(OS, Arch, std::move(ID));195}196 197llvm::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();202}203 204std::optional<InfoRecord> InfoRecord::parse(llvm::StringRef Line) {205 // INFO CODE_ID 554889E55DC3CCCCCCCCCCCCCCCCCCCC [a.exe]206 if (consume<Token>(Line) != Token::Info)207 return std::nullopt;208 209 if (consume<Token>(Line) != Token::CodeID)210 return std::nullopt;211 212 llvm::StringRef Str;213 std::tie(Str, Line) = getToken(Line);214 // If we don't have any text following the code ID (e.g. on linux), we should215 // use this as the UUID. Otherwise, we should revert back to the module ID.216 UUID ID;217 if (Line.trim().empty()) {218 if (Str.empty() || !ID.SetFromStringRef(Str))219 return std::nullopt;220 }221 return InfoRecord(std::move(ID));222}223 224llvm::raw_ostream &breakpad::operator<<(llvm::raw_ostream &OS,225 const InfoRecord &R) {226 return OS << "INFO CODE_ID " << R.ID.GetAsString();227}228 229template <typename T>230static std::optional<T> parseNumberName(llvm::StringRef Line, Token TokenType) {231 // TOKEN number name232 if (consume<Token>(Line) != TokenType)233 return std::nullopt;234 235 llvm::StringRef Str;236 size_t Number;237 std::tie(Str, Line) = getToken(Line);238 if (!to_integer(Str, Number))239 return std::nullopt;240 241 llvm::StringRef Name = Line.trim();242 if (Name.empty())243 return std::nullopt;244 245 return T(Number, Name);246}247 248std::optional<FileRecord> FileRecord::parse(llvm::StringRef Line) {249 // FILE number name250 return parseNumberName<FileRecord>(Line, Token::File);251}252 253llvm::raw_ostream &breakpad::operator<<(llvm::raw_ostream &OS,254 const FileRecord &R) {255 return OS << "FILE " << R.Number << " " << R.Name;256}257 258std::optional<InlineOriginRecord>259InlineOriginRecord::parse(llvm::StringRef Line) {260 // INLINE_ORIGIN number name261 return parseNumberName<InlineOriginRecord>(Line, Token::InlineOrigin);262}263 264llvm::raw_ostream &breakpad::operator<<(llvm::raw_ostream &OS,265 const InlineOriginRecord &R) {266 return OS << "INLINE_ORIGIN " << R.Number << " " << R.Name;267}268 269static 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 name273 // or274 // FUNC [m] address size param_size name275 276 Token Tok = Size ? Token::Func : Token::Public;277 278 if (consume<Token>(Line) != Tok)279 return false;280 281 llvm::StringRef Str;282 std::tie(Str, Line) = getToken(Line);283 Multiple = Str == "m";284 285 if (Multiple)286 std::tie(Str, Line) = getToken(Line);287 if (!to_integer(Str, Address, 16))288 return false;289 290 if (Tok == Token::Func) {291 std::tie(Str, Line) = getToken(Line);292 if (!to_integer(Str, *Size, 16))293 return false;294 }295 296 std::tie(Str, Line) = getToken(Line);297 if (!to_integer(Str, ParamSize, 16))298 return false;299 300 Name = Line.trim();301 if (Name.empty())302 return false;303 304 return true;305}306 307std::optional<FuncRecord> FuncRecord::parse(llvm::StringRef Line) {308 bool Multiple;309 lldb::addr_t Address, Size, ParamSize;310 llvm::StringRef Name;311 312 if (parsePublicOrFunc(Line, Multiple, Address, &Size, ParamSize, Name))313 return FuncRecord(Multiple, Address, Size, ParamSize, Name);314 315 return std::nullopt;316}317 318bool 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;321}322llvm::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);327}328 329std::optional<InlineRecord> InlineRecord::parse(llvm::StringRef Line) {330 // INLINE inline_nest_level call_site_line call_site_file_num origin_num331 // [address size]+332 if (consume<Token>(Line) != Token::Inline)333 return std::nullopt;334 335 llvm::SmallVector<llvm::StringRef> Tokens;336 SplitString(Line, Tokens, " ");337 if (Tokens.size() < 6 || Tokens.size() % 2 == 1)338 return std::nullopt;339 340 size_t InlineNestLevel;341 uint32_t CallSiteLineNum;342 size_t CallSiteFileNum;343 size_t OriginNum;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)))348 return std::nullopt;349 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))355 return std::nullopt;356 lldb::addr_t Size;357 if (!to_integer(Tokens[i + 1].trim(), Size, 16))358 return std::nullopt;359 Record.Ranges.emplace_back(Address, Size);360 }361 return Record;362}363 364bool 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;369}370 371llvm::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);377 }378 return OS;379}380 381std::optional<LineRecord> LineRecord::parse(llvm::StringRef Line) {382 lldb::addr_t Address;383 llvm::StringRef Str;384 std::tie(Str, Line) = getToken(Line);385 if (!to_integer(Str, Address, 16))386 return std::nullopt;387 388 lldb::addr_t Size;389 std::tie(Str, Line) = getToken(Line);390 if (!to_integer(Str, Size, 16))391 return std::nullopt;392 393 uint32_t LineNum;394 std::tie(Str, Line) = getToken(Line);395 if (!to_integer(Str, LineNum))396 return std::nullopt;397 398 size_t FileNum;399 std::tie(Str, Line) = getToken(Line);400 if (!to_integer(Str, FileNum))401 return std::nullopt;402 403 return LineRecord(Address, Size, LineNum, FileNum);404}405 406bool 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;409}410llvm::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);414}415 416std::optional<PublicRecord> PublicRecord::parse(llvm::StringRef Line) {417 bool Multiple;418 lldb::addr_t Address, ParamSize;419 llvm::StringRef Name;420 421 if (parsePublicOrFunc(Line, Multiple, Address, nullptr, ParamSize, Name))422 return PublicRecord(Multiple, Address, ParamSize, Name);423 424 return std::nullopt;425}426 427bool 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;430}431llvm::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,435 R.Name);436}437 438std::optional<StackCFIRecord> StackCFIRecord::parse(llvm::StringRef Line) {439 // STACK CFI INIT address size reg1: expr1 reg2: expr2 ...440 // or441 // STACK CFI address reg1: expr1 reg2: expr2 ...442 // No token in exprN ends with a colon.443 444 if (consume<Token>(Line) != Token::Stack)445 return std::nullopt;446 if (consume<Token>(Line) != Token::CFI)447 return std::nullopt;448 449 llvm::StringRef Str;450 std::tie(Str, Line) = getToken(Line);451 452 bool IsInitRecord = stringTo<Token>(Str) == Token::Init;453 if (IsInitRecord)454 std::tie(Str, Line) = getToken(Line);455 456 lldb::addr_t Address;457 if (!to_integer(Str, Address, 16))458 return std::nullopt;459 460 std::optional<lldb::addr_t> Size;461 if (IsInitRecord) {462 Size.emplace();463 std::tie(Str, Line) = getToken(Line);464 if (!to_integer(Str, *Size, 16))465 return std::nullopt;466 }467 468 return StackCFIRecord(Address, Size, Line.trim());469}470 471bool breakpad::operator==(const StackCFIRecord &L, const StackCFIRecord &R) {472 return L.Address == R.Address && L.Size == R.Size &&473 L.UnwindRules == R.UnwindRules;474}475 476llvm::raw_ostream &breakpad::operator<<(llvm::raw_ostream &OS,477 const StackCFIRecord &R) {478 OS << "STACK CFI ";479 if (R.Size)480 OS << "INIT ";481 OS << llvm::formatv("{0:x-} ", R.Address);482 if (R.Size)483 OS << llvm::formatv("{0:x-} ", *R.Size);484 return OS << " " << R.UnwindRules;485}486 487std::optional<StackWinRecord> StackWinRecord::parse(llvm::StringRef Line) {488 // STACK WIN type rva code_size prologue_size epilogue_size parameter_size489 // saved_register_size local_size max_stack_size has_program_string490 // program_string_OR_allocates_base_pointer491 492 if (consume<Token>(Line) != Token::Stack)493 return std::nullopt;494 if (consume<Token>(Line) != Token::Win)495 return std::nullopt;496 497 llvm::StringRef Str;498 uint8_t Type;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)502 return std::nullopt;503 504 lldb::addr_t RVA;505 std::tie(Str, Line) = getToken(Line);506 if (!to_integer(Str, RVA, 16))507 return std::nullopt;508 509 lldb::addr_t CodeSize;510 std::tie(Str, Line) = getToken(Line);511 if (!to_integer(Str, CodeSize, 16))512 return std::nullopt;513 514 // Skip fields which we aren't using right now.515 std::tie(Str, Line) = getToken(Line); // prologue_size516 std::tie(Str, Line) = getToken(Line); // epilogue_size517 518 lldb::addr_t ParameterSize;519 std::tie(Str, Line) = getToken(Line);520 if (!to_integer(Str, ParameterSize, 16))521 return std::nullopt;522 523 lldb::addr_t SavedRegisterSize;524 std::tie(Str, Line) = getToken(Line);525 if (!to_integer(Str, SavedRegisterSize, 16))526 return std::nullopt;527 528 lldb::addr_t LocalSize;529 std::tie(Str, Line) = getToken(Line);530 if (!to_integer(Str, LocalSize, 16))531 return std::nullopt;532 533 std::tie(Str, Line) = getToken(Line); // max_stack_size534 535 uint8_t HasProgramString;536 std::tie(Str, Line) = getToken(Line);537 if (!to_integer(Str, HasProgramString))538 return std::nullopt;539 // FrameData records should always have a program string.540 if (!HasProgramString)541 return std::nullopt;542 543 return StackWinRecord(RVA, CodeSize, ParameterSize, SavedRegisterSize,544 LocalSize, Line.trim());545}546 547bool 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;552}553 554llvm::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,559 R.ProgramString);560}561 562llvm::StringRef breakpad::toString(Record::Kind K) {563 switch (K) {564 case Record::Module:565 return "MODULE";566 case Record::Info:567 return "INFO";568 case Record::File:569 return "FILE";570 case Record::Func:571 return "FUNC";572 case Record::Inline:573 return "INLINE";574 case Record::InlineOrigin:575 return "INLINE_ORIGIN";576 case Record::Line:577 return "LINE";578 case Record::Public:579 return "PUBLIC";580 case Record::StackCFI:581 return "STACK CFI";582 case Record::StackWin:583 return "STACK WIN";584 }585 llvm_unreachable("Unknown record kind!");586}587