This patch carries forward our aim to remove offset field from qRegisterInfo packets and XML register description. I have created a new function which returns if offset fields are dynamic meaning client can calculate offset on its own based on register number sequence and register size. For now this function only returns true for NativeRegisterContextLinux_arm64 but we can test this for other architectures and make it standard later. As a consequence we do not send offset field from lldb-server (arm64 for now) while other stubs dont have an offset field so it wont effect them for now. On the client side we have replaced previous offset calculation algorithm with a new scheme, where we sort all primary registers in increasing order of remote regnum and then calculate offset incrementally. This committ also includes a test to verify all of above functionality on Arm64. Reviewed By: labath Differential Revision: https://reviews.llvm.org/D91241
376 lines
14 KiB
C++
376 lines
14 KiB
C++
//===-- MessageObjects.cpp ------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "MessageObjects.h"
|
|
#include "lldb/Utility/Args.h"
|
|
#include "lldb/Utility/StringExtractor.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace lldb_private;
|
|
using namespace lldb;
|
|
using namespace llvm;
|
|
namespace llgs_tests {
|
|
|
|
Expected<ProcessInfo> ProcessInfo::create(StringRef response) {
|
|
ProcessInfo process_info;
|
|
auto elements_or_error = SplitUniquePairList("ProcessInfo", response);
|
|
if (!elements_or_error)
|
|
return elements_or_error.takeError();
|
|
|
|
auto &elements = *elements_or_error;
|
|
if (elements["pid"].getAsInteger(16, process_info.m_pid))
|
|
return make_parsing_error("ProcessInfo: pid");
|
|
if (elements["parent-pid"].getAsInteger(16, process_info.m_parent_pid))
|
|
return make_parsing_error("ProcessInfo: parent-pid");
|
|
if (elements["real-uid"].getAsInteger(16, process_info.m_real_uid))
|
|
return make_parsing_error("ProcessInfo: real-uid");
|
|
if (elements["real-gid"].getAsInteger(16, process_info.m_real_gid))
|
|
return make_parsing_error("ProcessInfo: real-uid");
|
|
if (elements["effective-uid"].getAsInteger(16, process_info.m_effective_uid))
|
|
return make_parsing_error("ProcessInfo: effective-uid");
|
|
if (elements["effective-gid"].getAsInteger(16, process_info.m_effective_gid))
|
|
return make_parsing_error("ProcessInfo: effective-gid");
|
|
if (elements["ptrsize"].getAsInteger(10, process_info.m_ptrsize))
|
|
return make_parsing_error("ProcessInfo: ptrsize");
|
|
|
|
process_info.m_triple = fromHex(elements["triple"]);
|
|
StringRef endian_str = elements["endian"];
|
|
if (endian_str == "little")
|
|
process_info.m_endian = support::little;
|
|
else if (endian_str == "big")
|
|
process_info.m_endian = support::big;
|
|
else
|
|
return make_parsing_error("ProcessInfo: endian");
|
|
|
|
return process_info;
|
|
}
|
|
|
|
lldb::pid_t ProcessInfo::GetPid() const { return m_pid; }
|
|
|
|
support::endianness ProcessInfo::GetEndian() const { return m_endian; }
|
|
|
|
//====== ThreadInfo ============================================================
|
|
ThreadInfo::ThreadInfo(StringRef name, StringRef reason, RegisterMap registers,
|
|
unsigned int)
|
|
: m_name(name.str()), m_reason(reason.str()),
|
|
m_registers(std::move(registers)) {}
|
|
|
|
const RegisterValue *ThreadInfo::ReadRegister(unsigned int Id) const {
|
|
auto Iter = m_registers.find(Id);
|
|
return Iter == m_registers.end() ? nullptr : &Iter->getSecond();
|
|
}
|
|
|
|
//====== JThreadsInfo ==========================================================
|
|
|
|
Expected<RegisterMap>
|
|
JThreadsInfo::parseRegisters(const StructuredData::Dictionary &Dict,
|
|
ArrayRef<RegisterInfo> RegInfos) {
|
|
RegisterMap Result;
|
|
|
|
auto KeysObj = Dict.GetKeys();
|
|
auto Keys = KeysObj->GetAsArray();
|
|
for (size_t i = 0; i < Keys->GetSize(); i++) {
|
|
StringRef KeyStr, ValueStr;
|
|
Keys->GetItemAtIndexAsString(i, KeyStr);
|
|
Dict.GetValueForKeyAsString(KeyStr, ValueStr);
|
|
unsigned int Register;
|
|
if (!llvm::to_integer(KeyStr, Register, 10))
|
|
return make_parsing_error("JThreadsInfo: register key[{0}]", i);
|
|
|
|
auto RegValOr =
|
|
parseRegisterValue(RegInfos[Register], ValueStr, support::big);
|
|
if (!RegValOr)
|
|
return RegValOr.takeError();
|
|
Result[Register] = std::move(*RegValOr);
|
|
}
|
|
return std::move(Result);
|
|
}
|
|
|
|
Expected<JThreadsInfo> JThreadsInfo::create(StringRef Response,
|
|
ArrayRef<RegisterInfo> RegInfos) {
|
|
JThreadsInfo jthreads_info;
|
|
|
|
StructuredData::ObjectSP json =
|
|
StructuredData::ParseJSON(std::string(Response));
|
|
StructuredData::Array *array = json->GetAsArray();
|
|
if (!array)
|
|
return make_parsing_error("JThreadsInfo: JSON array");
|
|
|
|
for (size_t i = 0; i < array->GetSize(); i++) {
|
|
StructuredData::Dictionary *thread_info;
|
|
array->GetItemAtIndexAsDictionary(i, thread_info);
|
|
if (!thread_info)
|
|
return make_parsing_error("JThreadsInfo: JSON obj at {0}", i);
|
|
|
|
StringRef name, reason;
|
|
thread_info->GetValueForKeyAsString("name", name);
|
|
thread_info->GetValueForKeyAsString("reason", reason);
|
|
uint64_t signal;
|
|
thread_info->GetValueForKeyAsInteger("signal", signal);
|
|
uint64_t tid;
|
|
thread_info->GetValueForKeyAsInteger("tid", tid);
|
|
|
|
StructuredData::Dictionary *register_dict;
|
|
thread_info->GetValueForKeyAsDictionary("registers", register_dict);
|
|
if (!register_dict)
|
|
return make_parsing_error("JThreadsInfo: registers JSON obj");
|
|
|
|
auto RegsOr = parseRegisters(*register_dict, RegInfos);
|
|
if (!RegsOr)
|
|
return RegsOr.takeError();
|
|
jthreads_info.m_thread_infos[tid] =
|
|
ThreadInfo(name, reason, std::move(*RegsOr), signal);
|
|
}
|
|
|
|
return jthreads_info;
|
|
}
|
|
|
|
const ThreadInfoMap &JThreadsInfo::GetThreadInfos() const {
|
|
return m_thread_infos;
|
|
}
|
|
|
|
Expected<RegisterInfo> RegisterInfoParser::create(StringRef Response) {
|
|
auto ElementsOr = SplitUniquePairList("RegisterInfoParser", Response);
|
|
if (!ElementsOr)
|
|
return ElementsOr.takeError();
|
|
auto &Elements = *ElementsOr;
|
|
|
|
RegisterInfo Info = {
|
|
nullptr, // Name
|
|
nullptr, // Alt name
|
|
0, // byte size
|
|
0, // offset
|
|
eEncodingUint, // encoding
|
|
eFormatHex, // format
|
|
{
|
|
LLDB_INVALID_REGNUM, // eh_frame reg num
|
|
LLDB_INVALID_REGNUM, // DWARF reg num
|
|
LLDB_INVALID_REGNUM, // generic reg num
|
|
LLDB_INVALID_REGNUM, // process plugin reg num
|
|
LLDB_INVALID_REGNUM // native register number
|
|
},
|
|
nullptr,
|
|
nullptr,
|
|
nullptr, // Dwarf expression opcode bytes pointer
|
|
0 // Dwarf expression opcode bytes length
|
|
};
|
|
Info.name = ConstString(Elements["name"]).GetCString();
|
|
if (!Info.name)
|
|
return make_parsing_error("qRegisterInfo: name");
|
|
|
|
Info.alt_name = ConstString(Elements["alt-name"]).GetCString();
|
|
|
|
if (!to_integer(Elements["bitsize"], Info.byte_size, 10))
|
|
return make_parsing_error("qRegisterInfo: bit-size");
|
|
Info.byte_size /= CHAR_BIT;
|
|
|
|
if (!to_integer(Elements["offset"], Info.byte_offset, 10))
|
|
Info.byte_offset = LLDB_INVALID_INDEX32;
|
|
|
|
Info.encoding = Args::StringToEncoding(Elements["encoding"]);
|
|
if (Info.encoding == eEncodingInvalid)
|
|
return make_parsing_error("qRegisterInfo: encoding");
|
|
|
|
Info.format = StringSwitch<Format>(Elements["format"])
|
|
.Case("binary", eFormatBinary)
|
|
.Case("decimal", eFormatDecimal)
|
|
.Case("hex", eFormatHex)
|
|
.Case("float", eFormatFloat)
|
|
.Case("vector-sint8", eFormatVectorOfSInt8)
|
|
.Case("vector-uint8", eFormatVectorOfUInt8)
|
|
.Case("vector-sint16", eFormatVectorOfSInt16)
|
|
.Case("vector-uint16", eFormatVectorOfUInt16)
|
|
.Case("vector-sint32", eFormatVectorOfSInt32)
|
|
.Case("vector-uint32", eFormatVectorOfUInt32)
|
|
.Case("vector-float32", eFormatVectorOfFloat32)
|
|
.Case("vector-uint64", eFormatVectorOfUInt64)
|
|
.Case("vector-uint128", eFormatVectorOfUInt128)
|
|
.Default(eFormatInvalid);
|
|
if (Info.format == eFormatInvalid)
|
|
return make_parsing_error("qRegisterInfo: format");
|
|
|
|
Info.kinds[eRegisterKindGeneric] =
|
|
Args::StringToGenericRegister(Elements["generic"]);
|
|
|
|
return std::move(Info);
|
|
}
|
|
|
|
Expected<RegisterValue> parseRegisterValue(const RegisterInfo &Info,
|
|
StringRef HexValue,
|
|
llvm::support::endianness Endian,
|
|
bool ZeroPad) {
|
|
SmallString<128> Storage;
|
|
if (ZeroPad && HexValue.size() < Info.byte_size * 2) {
|
|
Storage.insert(Storage.begin(), Info.byte_size * 2 - HexValue.size(), '0');
|
|
Storage += HexValue;
|
|
HexValue = Storage;
|
|
}
|
|
|
|
SmallVector<uint8_t, 64> Bytes(HexValue.size() / 2);
|
|
StringExtractor(HexValue).GetHexBytes(Bytes, '\xcc');
|
|
RegisterValue Value;
|
|
Status ST;
|
|
Value.SetFromMemoryData(
|
|
&Info, Bytes.data(), Bytes.size(),
|
|
Endian == support::little ? eByteOrderLittle : eByteOrderBig, ST);
|
|
if (ST.Fail())
|
|
return ST.ToError();
|
|
return Value;
|
|
}
|
|
|
|
//====== StopReply =============================================================
|
|
Expected<std::unique_ptr<StopReply>>
|
|
StopReply::create(StringRef Response, llvm::support::endianness Endian,
|
|
ArrayRef<RegisterInfo> RegInfos) {
|
|
if (Response.size() < 3)
|
|
return make_parsing_error("StopReply: Invalid packet");
|
|
if (Response.consume_front("T"))
|
|
return StopReplyStop::create(Response, Endian, RegInfos);
|
|
if (Response.consume_front("W"))
|
|
return StopReplyExit::create(Response);
|
|
return make_parsing_error("StopReply: Invalid packet");
|
|
}
|
|
|
|
Expected<RegisterMap> StopReplyStop::parseRegisters(
|
|
const StringMap<SmallVector<StringRef, 2>> &Elements,
|
|
support::endianness Endian, ArrayRef<lldb_private::RegisterInfo> RegInfos) {
|
|
|
|
RegisterMap Result;
|
|
for (const auto &E : Elements) {
|
|
StringRef Key = E.getKey();
|
|
const auto &Val = E.getValue();
|
|
if (Key.size() != 2)
|
|
continue;
|
|
|
|
unsigned int Reg;
|
|
if (!to_integer(Key, Reg, 16))
|
|
continue;
|
|
|
|
if (Val.size() != 1)
|
|
return make_parsing_error(
|
|
"StopReplyStop: multiple entries for register field [{0:x}]", Reg);
|
|
|
|
auto RegValOr = parseRegisterValue(RegInfos[Reg], Val[0], Endian);
|
|
if (!RegValOr)
|
|
return RegValOr.takeError();
|
|
Result[Reg] = std::move(*RegValOr);
|
|
}
|
|
return std::move(Result);
|
|
}
|
|
|
|
Expected<std::unique_ptr<StopReplyStop>>
|
|
StopReplyStop::create(StringRef Response, support::endianness Endian,
|
|
ArrayRef<RegisterInfo> RegInfos) {
|
|
unsigned int Signal;
|
|
StringRef SignalStr = Response.take_front(2);
|
|
Response = Response.drop_front(2);
|
|
if (!to_integer(SignalStr, Signal, 16))
|
|
return make_parsing_error("StopReply: stop signal");
|
|
|
|
auto Elements = SplitPairList(Response);
|
|
for (StringRef Field :
|
|
{"name", "reason", "thread", "threads", "thread-pcs"}) {
|
|
// This will insert an empty field if there is none. In the future, we
|
|
// should probably differentiate between these fields not being present and
|
|
// them being empty, but right now no tests depends on this.
|
|
if (Elements.insert({Field, {""}}).first->second.size() != 1)
|
|
return make_parsing_error(
|
|
"StopReply: got multiple responses for the {0} field", Field);
|
|
}
|
|
StringRef Name = Elements["name"][0];
|
|
StringRef Reason = Elements["reason"][0];
|
|
|
|
lldb::tid_t Thread;
|
|
if (!to_integer(Elements["thread"][0], Thread, 16))
|
|
return make_parsing_error("StopReply: thread");
|
|
|
|
SmallVector<StringRef, 20> Threads;
|
|
SmallVector<StringRef, 20> Pcs;
|
|
Elements["threads"][0].split(Threads, ',');
|
|
Elements["thread-pcs"][0].split(Pcs, ',');
|
|
if (Threads.size() != Pcs.size())
|
|
return make_parsing_error("StopReply: thread/PC count mismatch");
|
|
|
|
RegisterMap ThreadPcs;
|
|
const RegisterInfo *PcInfo = find_if(RegInfos, [](const RegisterInfo &Info) {
|
|
return Info.kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_PC;
|
|
});
|
|
assert(PcInfo);
|
|
|
|
for (auto ThreadPc : zip(Threads, Pcs)) {
|
|
lldb::tid_t Id;
|
|
if (!to_integer(std::get<0>(ThreadPc), Id, 16))
|
|
return make_parsing_error("StopReply: Thread id '{0}'",
|
|
std::get<0>(ThreadPc));
|
|
|
|
auto PcOr = parseRegisterValue(*PcInfo, std::get<1>(ThreadPc), Endian,
|
|
/*ZeroPad*/ true);
|
|
if (!PcOr)
|
|
return PcOr.takeError();
|
|
ThreadPcs[Id] = std::move(*PcOr);
|
|
}
|
|
|
|
auto RegistersOr = parseRegisters(Elements, Endian, RegInfos);
|
|
if (!RegistersOr)
|
|
return RegistersOr.takeError();
|
|
|
|
return std::make_unique<StopReplyStop>(Signal, Thread, Name,
|
|
std::move(ThreadPcs),
|
|
std::move(*RegistersOr), Reason);
|
|
}
|
|
|
|
Expected<std::unique_ptr<StopReplyExit>>
|
|
StopReplyExit::create(StringRef Response) {
|
|
uint8_t Status;
|
|
if (!to_integer(Response, Status, 16))
|
|
return make_parsing_error("StopReply: exit status");
|
|
return std::make_unique<StopReplyExit>(Status);
|
|
}
|
|
|
|
//====== Globals ===============================================================
|
|
Expected<StringMap<StringRef>> SplitUniquePairList(StringRef caller,
|
|
StringRef str) {
|
|
SmallVector<StringRef, 20> elements;
|
|
str.split(elements, ';');
|
|
|
|
StringMap<StringRef> pairs;
|
|
for (StringRef s : elements) {
|
|
std::pair<StringRef, StringRef> pair = s.split(':');
|
|
if (pairs.count(pair.first))
|
|
return make_parsing_error("{0}: Duplicate Key: {1}", caller, pair.first);
|
|
|
|
pairs.insert(pair);
|
|
}
|
|
|
|
return pairs;
|
|
}
|
|
|
|
StringMap<SmallVector<StringRef, 2>> SplitPairList(StringRef str) {
|
|
SmallVector<StringRef, 20> elements;
|
|
str.split(elements, ';');
|
|
|
|
StringMap<SmallVector<StringRef, 2>> pairs;
|
|
for (StringRef s : elements) {
|
|
std::pair<StringRef, StringRef> pair = s.split(':');
|
|
pairs[pair.first].push_back(pair.second);
|
|
}
|
|
|
|
return pairs;
|
|
}
|
|
} // namespace llgs_tests
|
|
|
|
std::ostream &lldb_private::operator<<(std::ostream &OS,
|
|
const RegisterValue &RegVal) {
|
|
ArrayRef<uint8_t> Bytes(static_cast<const uint8_t *>(RegVal.GetBytes()),
|
|
RegVal.GetByteSize());
|
|
return OS << formatv("RegisterValue[{0}]: {1:@[x-2]}", RegVal.GetByteSize(),
|
|
make_range(Bytes.begin(), Bytes.end()))
|
|
.str();
|
|
}
|