/* SPDX-License-Identifier: Unlicense */ #include "gdbremote_parser.hpp" #include #include #include using namespace GDBRemote; enum class TokenType: uint8_t { kUnknown = 0, kSeparatorComma, kSeparatorColon, kSeparatorSemicolon, kNumeric, kHexNumeric, kArbitrary, }; static inline bool isseparator(uint8_t byte) { return ':' == byte || ';' == byte || ',' == byte; } static inline TokenType separatorTypeFromByte(uint8_t byte) { if (':' == byte) return TokenType::kSeparatorColon; if (';' == byte) return TokenType::kSeparatorSemicolon; return TokenType::kSeparatorComma; } struct Token { TokenType type{}; std::string data{}; static std::vector Tokenize(std::string packet_data) { if (packet_data.length() == 0) return std::vector{}; std::vector tokens{}; TokenType type{}; size_t i, offset; for (i = 0, offset = 0; i < packet_data.length(); i++) { assert(type != TokenType::kSeparatorColon); assert(type != TokenType::kSeparatorSemicolon); 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 (isdigit(byte)) { if (type == TokenType::kUnknown) { type = TokenType::kNumeric; } } 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; } }; struct Command { std::string name; Packet (*parse)(const std::vector&&); static Packet parseNone(const std::vector&&) { return Packet::None(); } static Packet parseQuerySupported(const std::vector&&) { // TODO arguments return Packet{PacketType::kQuerySupported}; } static Packet parseQueryHaltReason(const std::vector&&) { return Packet{PacketType::kQueryHaltReason}; } static Packet parseQueryC(const std::vector&&) { return Packet{PacketType::kQueryC}; } static Packet parseQueryFThreadInfo(const std::vector&&) { return Packet{PacketType::kQueryFThreadInfo}; } static Packet parseQuerySThreadInfo(const std::vector&&) { return Packet{PacketType::kQuerySThreadInfo}; } static Packet parseQueryRTOSThreadInfo(const std::vector&&) { // TODO arguments return Packet{PacketType::kQueryRTOSThreadInfo}; } static Packet parseQueryAttached(const std::vector&&) { return Packet{PacketType::kQueryAttached}; } static Packet parseQueryTraceStatus(const std::vector&&) { return Packet{PacketType::kQueryTraceStatus}; } static Packet parseSetThreadForCont(const std::vector&&) { // TODO arguments return Packet{PacketType::kSetThreadForCont}; } static Packet parseSetThreadForOps(const std::vector&&) { // TODO arguments return Packet{PacketType::kSetThreadForOps}; } static Packet parseMustReplyEmpty(const std::vector&&) { return Packet{PacketType::kVMustReplyEmpty}; } static Packet parseEnableExtendedMode(const std::vector&&) { return Packet{PacketType::kEnableExtendedMode}; } }; 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 }, { "Hc", Command::parseSetThreadForCont }, { "Hg", Command::parseSetThreadForOps }, { "vMustReplyEmpty", Command::parseMustReplyEmpty }, { "!", Command::parseEnableExtendedMode }, }; Packet Packet::Parse(std::string packet_data) { const auto tokens = Token::Tokenize(packet_data); if (tokens.size() == 0) return Packet::None(); const auto first_token = tokens[0]; if (first_token.type != TokenType::kArbitrary) return Packet::None(); for (const auto& command: commands) { const auto cmdlen = command.name.length(); const auto toklen = first_token.data.length(); const auto len = std::min(cmdlen, cmdlen); // Make sure first token fully includes the command from the beginning // and not necessary fully equals to it. if (cmdlen <= toklen && 0 == memcmp( &command.name[0], &first_token.data[0], len)) { return command.parse(std::move(tokens)); } } return Packet::None(); } static inline uint8_t parseChecksumFromHexChars(uint8_t first, uint8_t second) { // Assume, that given bytes are valid hex digits in ASCII first = ((first & 0x40) ? (first + 9) : first) & 0x0f; second = ((second & 0x40) ? (second + 9) : second) & 0x0f; return ((first << 4) | second) & 0xff; } std::unique_ptr ExchangeContext::Consume(uint8_t byte) { _last_packet += byte; switch (_parsing_state) { case ParsingState::kIdle: if (byte == '$') { transit(ParsingState::kPacketData); } 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 = parseChecksumFromHexChars(_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; } std::string ExchangeContext::WrapDataToSend(Packet packet) { switch (packet.type) { case PacketType::kNone: case PacketType::kVMustReplyEmpty: case PacketType::kQuerySupported: case PacketType::kQueryHaltReason: case PacketType::kQueryC: case PacketType::kQueryFThreadInfo: case PacketType::kQuerySThreadInfo: case PacketType::kQueryRTOSThreadInfo: case PacketType::kQueryAttached: case PacketType::kQueryTraceStatus: case PacketType::kSetThreadForCont: case PacketType::kSetThreadForOps: case PacketType::kEnableExtendedMode: return "$#00"; break; } return std::string{}; } 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 ""; }