/* SPDX-License-Identifier: Unlicense */ #include "gdbremote_parser.hpp" #include "utils.hpp" #include #include #include using namespace GDBRemote; enum class TokenType: uint8_t { kUnknown = 0, kSeparatorComma, kSeparatorColon, kSeparatorSemicolon, kSeparatorEquals, kHexNumeric, kArbitrary, }; static inline bool isseparator(uint8_t byte) { return ':' == byte || ';' == byte || ',' == byte || '=' == byte; } static inline TokenType separatorTypeFromByte(uint8_t byte) { if (':' == byte) return TokenType::kSeparatorColon; if (';' == byte) return TokenType::kSeparatorSemicolon; if ('=' == byte) return TokenType::kSeparatorEquals; return TokenType::kSeparatorComma; } struct Token { TokenType type{}; std::string data{}; static std::vector Tokenize( const std::string& packet_data, const std::string& command) { if (packet_data.length() == 0) return std::vector{}; std::vector tokens{}; const size_t cmdlen = command.length(); if (cmdlen > 0) { tokens.push_back(Token{TokenType::kArbitrary, command}); } TokenType type{}; size_t i{command.length()}, offset{command.length()}; for (; i < packet_data.length(); i++) { assert(type != TokenType::kSeparatorColon); assert(type != TokenType::kSeparatorSemicolon); assert(type != TokenType::kSeparatorEquals); assert(type != TokenType::kSeparatorComma); const uint8_t byte = packet_data[i]; if (isseparator(byte)) { if (i > offset) { assert(type != TokenType::kUnknown); tokens.push_back(Token{type, packet_data.substr(offset, i - offset)}); offset = i; } tokens.push_back(Token{ separatorTypeFromByte(byte), packet_data.substr(offset, 1), }); offset++; type = TokenType::kUnknown; } else if (isxdigit(byte)) { if (type != TokenType::kArbitrary) { type = TokenType::kHexNumeric; } } else { type = TokenType::kArbitrary; } } if (i > offset) { assert(type != TokenType::kUnknown); tokens.push_back(Token{type, packet_data.substr(offset, i - offset)}); } return tokens; } }; static std::string DecodeBinaryHex(const std::string& data_hex) { std::string data(data_hex.size()/2, '\0'); for (size_t i = 0; i < data.size(); i++) { data[i] = Utils::ParseByteFromHexChars(data_hex[i * 2], data_hex[i * 2 + 1]); } return data; } struct Command { std::string name; Packet (*parse)(std::vector&&); static Packet parseNone(std::vector&&) { return Packet::None(); } static Packet parseQuerySupported(std::vector&&) { // TODO arguments return Packet{PacketType::kQuerySupported}; } static Packet parseQueryHaltReason(std::vector&&) { return Packet{PacketType::kQueryHaltReason}; } static Packet parseQueryC(std::vector&&) { // TODO arguments return Packet{PacketType::kQueryC}; } static Packet parseQueryFThreadInfo(std::vector&&) { return Packet{PacketType::kQueryFThreadInfo}; } static Packet parseQuerySThreadInfo(std::vector&&) { return Packet{PacketType::kQuerySThreadInfo}; } static Packet parseQueryRTOSThreadInfo(std::vector&&) { // TODO arguments return Packet{PacketType::kQueryRTOSThreadInfo}; } static Packet parseQueryAttached(std::vector&&) { // TODO arguments return Packet{PacketType::kQueryAttached}; } static Packet parseQueryTraceStatus(std::vector&&) { return Packet{PacketType::kQueryTraceStatus}; } static Packet parseQueryRcmd(std::vector&& tokens) { if (tokens.size() < 3) return Packet::None(); if (tokens[1].type != TokenType::kSeparatorComma) return Packet::None(); if (tokens[2].type != TokenType::kHexNumeric) return Packet::None(); const std::string& data_encoded = tokens[2].data; return Packet{ PacketType::kQueryRcmd, std::make_unique(std::move(DecodeBinaryHex(data_encoded))), }; } static Packet parseQStartNoAckMode(std::vector&&) { return Packet{PacketType::kQStartNoAckMode}; } static Packet parseSetThreadForCont(std::vector&&) { // TODO arguments return Packet{PacketType::kSetThreadForCont}; } static Packet parseSetThreadForOps(std::vector&&) { // TODO arguments return Packet{PacketType::kSetThreadForOps}; } static Packet parseMustReplyEmpty(std::vector&&) { return Packet{PacketType::kVMustReplyEmpty}; } static Packet parseEnableExtendedMode(std::vector&&) { return Packet{PacketType::kEnableExtendedMode}; } static Packet parseInterrupt(std::vector&&) { return Packet{PacketType::kInterrupt}; } static Packet parseContinueVCont(std::vector&& tokens) { const std::string first_arg = tokens.size() > 1 ? tokens[1].data : ""; // Check if it is "vCont?" command if (first_arg == "?") { return Packet{PacketType::kContinueAskSupported}; } // TODO arguments return Packet{PacketType::kContinue}; } static Packet parseContinue(std::vector&&) { return Packet{PacketType::kContinue}; } static Packet parseReadMemory(std::vector&& tokens) { if (tokens.size() < 4) return Packet::None(); if (tokens[1].type != TokenType::kHexNumeric) return Packet::None(); if (tokens[2].type != TokenType::kSeparatorComma) return Packet::None(); if (tokens[3].type != TokenType::kHexNumeric) return Packet::None(); const std::string& first = tokens[1].data; const std::string& second = tokens[3].data; // TODO handle number parsing error (ERANGE) const uint32_t offset = strtol(first.c_str(), nullptr, 16); const uint32_t lenght = strtol(second.c_str(), nullptr, 16); return Packet{ PacketType::kReadMemory, std::make_unique(offset, lenght), }; } static Packet parseWriteMemory(std::vector&& tokens) { if (tokens.size() < 6) return Packet::None(); if (tokens[1].type != TokenType::kHexNumeric) return Packet::None(); if (tokens[2].type != TokenType::kSeparatorComma) return Packet::None(); if (tokens[3].type != TokenType::kHexNumeric) return Packet::None(); // XXX Is data parameter always present or is it optional? if (tokens[4].type != TokenType::kSeparatorColon) return Packet::None(); if (tokens[5].type != TokenType::kHexNumeric) return Packet::None(); // TODO handle number parsing error (ERANGE) const std::string& first = tokens[1].data; const std::string& second = tokens[3].data; const std::string& data_raw = tokens[5].data; const size_t data_len = data_raw.length()/2; std::vector data(data_len); assert(data.size() == data_len); for (size_t i = 0; i < data_len; i++) { data[i] = Utils::ParseByteFromHexChars(data_raw[i*2], data_raw[i*2+1]); } const uint32_t offset = strtol(first.c_str(), nullptr, 16); const uint32_t lenght = strtol(second.c_str(), nullptr, 16); return Packet{ PacketType::kWriteMemory, std::make_unique(offset, lenght, std::move(data)), }; } static Packet parseReadGeneralRegisters(std::vector&&) { return Packet{PacketType::kReadGeneralRegisters}; } static Packet parseWriteGeneralRegisters(std::vector&& tokens) { if (tokens.size() < 2) return Packet::None(); if (tokens[1].type != TokenType::kHexNumeric) return Packet::None(); const std::string& first = tokens[1].data; constexpr size_t value_str_size = std::string_view("12345678").length(); std::vector registers(first.length()/value_str_size); for (size_t i = 0; i < first.length()/value_str_size; i++) { const std::string value_str(first.substr(i * value_str_size, value_str_size)); registers[i] = strtol(value_str.c_str(), nullptr, 16); } return Packet{ PacketType::kWriteGeneralRegisters, std::make_unique(std::move(registers)), }; } static Packet parseReadRegister(std::vector&& tokens) { if (tokens.size() < 2) return Packet::None(); if (tokens[1].type != TokenType::kHexNumeric) return Packet::None(); const std::string& first = tokens[1].data; const uint32_t reg_id = strtol(first.c_str(), nullptr, 16); return Packet{ PacketType::kReadRegister, std::make_unique(reg_id), }; } static Packet parseWriteRegister(std::vector&& tokens) { if (tokens.size() < 4) return Packet::None(); if (tokens[1].type != TokenType::kHexNumeric) return Packet::None(); if (tokens[2].type != TokenType::kSeparatorEquals) return Packet::None(); if (tokens[3].type != TokenType::kHexNumeric) return Packet::None(); const std::string& first = tokens[1].data; const std::string& second = tokens[3].data; const uint32_t reg_id = strtol(first.c_str(), nullptr, 16); const uint32_t value = strtol(second.c_str(), nullptr, 16); return Packet{ PacketType::kWriteRegister, std::make_unique(reg_id, value), }; } static Packet parseStep(std::vector&&) { return Packet{PacketType::kStep}; } static Packet parseSetOrDeleteBreakpoint(std::vector&& tokens) { if (tokens.size() < 6) return Packet::None(); if (tokens[1].type != TokenType::kHexNumeric) return Packet::None(); if (tokens[2].type != TokenType::kSeparatorComma) return Packet::None(); if (tokens[3].type != TokenType::kHexNumeric) return Packet::None(); // XXX Is third parameter always present or is it optional? if (tokens[4].type != TokenType::kSeparatorComma) return Packet::None(); if (tokens[5].type != TokenType::kHexNumeric) return Packet::None(); const std::string& first = tokens[1].data; const std::string& second = tokens[3].data; const std::string& third = tokens[5].data; const uint32_t type_num = strtol(first.c_str(), nullptr, 16); const auto type = (type_num >= static_cast(BreakpointType::kMin) && type_num < static_cast(BreakpointType::kUnsupported)) ? static_cast(type_num) : BreakpointType::kMin; const uint32_t offset = strtol(second.c_str(), nullptr, 16); const uint32_t length = strtol(third.c_str(), nullptr, 16); return Packet{ tokens[0].data[0] == 'Z' ? PacketType::kSetBreakpoint : PacketType::kDeleteBreakpoint, std::make_unique(type, offset, length), }; } }; static const Command commands[] = { { "qSupported", Command::parseQuerySupported }, { "?", Command::parseQueryHaltReason }, { "qC", Command::parseQueryC }, { "qfThreadInfo", Command::parseQueryFThreadInfo }, { "qsThreadInfo", Command::parseQuerySThreadInfo }, { "qL", Command::parseQueryRTOSThreadInfo }, { "qAttached", Command::parseQueryAttached }, { "qTStatus", Command::parseQueryTraceStatus }, { "qRcmd", Command::parseQueryRcmd }, { "QStartNoAckMode", Command::parseQStartNoAckMode }, { "Hc", Command::parseSetThreadForCont }, { "Hg", Command::parseSetThreadForOps }, { "vMustReplyEmpty", Command::parseMustReplyEmpty }, { "!", Command::parseEnableExtendedMode }, { "\x03", Command::parseInterrupt }, { "vCtrlC", Command::parseInterrupt }, { "vCont", Command::parseContinueVCont }, { "c", Command::parseContinue }, { "m", Command::parseReadMemory }, { "M", Command::parseWriteMemory }, { "g", Command::parseReadGeneralRegisters }, { "G", Command::parseWriteGeneralRegisters }, { "p", Command::parseReadRegister }, { "P", Command::parseWriteRegister }, { "s", Command::parseStep }, { "Z", Command::parseSetOrDeleteBreakpoint }, { "z", Command::parseSetOrDeleteBreakpoint }, }; Packet Packet::Parse(std::string packet_data) { for (const auto& command: commands) { const auto cmdlen = command.name.length(); const auto packetlen = packet_data.length(); if (cmdlen <= packetlen && 0 == memcmp(&command.name[0], &packet_data[0], cmdlen)) { return command.parse(std::move(Token::Tokenize(packet_data, command.name))); } } return Packet::None(); } std::unique_ptr ExchangeContext::Consume(uint8_t byte) { _last_packet += byte; switch (_parsing_state) { case ParsingState::kIdle: if (byte == '$') { transit(ParsingState::kPacketData); } else if (byte == 0x03) { _last_packet = std::string(1, 0x03); return ExchangeResult::Ack(_last_packet); } break; case ParsingState::kPacketData: if (byte == '#') { transit(ParsingState::kChecksum1); } else { _checksum = (_checksum + byte) & 0xff; _packet_data += byte; } break; case ParsingState::kPacketDataBinSecond: // TODO escaped and binary data break; case ParsingState::kChecksum1: if (isxdigit(byte)) { transit(ParsingState::kChecksum2); _given_checksum_first_byte = byte; } else { transit(ParsingState::kIdle); return ExchangeResult::Nak(); } break; case ParsingState::kChecksum2: const uint8_t checksum = _checksum; std::string packet_data(std::move(_packet_data)); transit(ParsingState::kIdle); if (isxdigit(byte)) { const uint8_t given_checksum = Utils::ParseByteFromHexChars(_given_checksum_first_byte, byte); if (given_checksum == checksum) { return ExchangeResult::Ack(packet_data); } else { printf( "Checksum mismatch: received=%02x, expected=%02x\n", given_checksum, checksum); printf("Packet data: \"%s\"\n", packet_data.c_str()); return ExchangeResult::Nak(); } } else { return ExchangeResult::Nak(); } break; } return nullptr; } static inline uint8_t CalcPacketDataChecksum(std::string packet) { uint8_t checksum{}; for (const auto c: packet) { checksum = (checksum + c) & 0xff; } return checksum; } std::string ExchangeContext::WrapDataToSend(std::string packet) { const uint8_t checksum = CalcPacketDataChecksum(packet); const uint8_t c1 = (checksum >> 4) & 0x0f; const uint8_t c2 = checksum& 0x0f; const char checksum_str[2] = { static_cast(c1 < 0xa ? c1 + '0' : c1 + ('a' - 0xa)), static_cast(c2 < 0xa ? c2 + '0' : c2 + ('a' - 0xa)), }; return "$" + packet + "#" + std::string(checksum_str, 2); } void ExchangeContext::transit(ParsingState new_state) { if (0) { printf( "GDBRemote ParsingState: %s -> %s\n", parsingStateToString(_parsing_state), parsingStateToString(new_state)); } switch (new_state) { case ParsingState::kIdle: _checksum = 0; _packet_data.clear(); break; case ParsingState::kPacketData: _last_packet = "$"; break; case ParsingState::kPacketDataBinSecond: break; case ParsingState::kChecksum1: break; case ParsingState::kChecksum2: break; } _parsing_state = new_state; } const char* ExchangeContext::parsingStateToString(ParsingState state) { switch (state) { case ParsingState::kIdle: return "Idle"; case ParsingState::kPacketData: return "PacketData"; case ParsingState::kPacketDataBinSecond: return "PacketDataBinSecond"; case ParsingState::kChecksum1: return "Checksum1"; case ParsingState::kChecksum2: return "Checksum2"; } return ""; }