//---------------------------------------------------------------------------- // // GdbSrvRspClient.cpp // // This class implements the RSP communication protocol used to communicate with the // GdbServer stub. This class can be instantiated by using a TCPIP communication class // or serial protocol class for connecting, sending and receiving data over the // link layer (TCP/IP or RS-232). // // Copyright (c) Microsoft. All rights reserved. //---------------------------------------------------------------------------- #include "stdafx.h" #include #include #include "ExceptionHelpers.h" #include "GdbSrvRspclient.h" #include using namespace GdbSrvControllerLib; //============================================================================= // Private defines and typedefs //============================================================================= // Calculate the RSP packet length #define CALC_RSP_PACKET_LENGTH(inputLenth) (strlen("$") + inputLenth + strlen("#nn")) // Detect if the passed in character needs to be escaped #define HANDLE_ESCAPE_SEQUENCE(ch) ((ch == '$' || ch == '#' || ch == '{') ? true : false) // Return the status of the particular feature #define IS_FEATURE_ENABLED(feature) (GdbSrvRspClient::s_RspProtocolFeatures[feature].isEnabled) #define GET_FEATURE_VALUE(feature) (GdbSrvRspClient::s_RspProtocolFeatures[feature].featureDefaultValue) #define GET_FEATURE_ENTRY(feature) (GdbSrvRspClient::s_RspProtocolFeatures[feature]) #define GET_FEATURE_NAME(feature) (GdbSrvRspClient::s_RspProtocolFeatures[feature].name) // Returns true if the interrupt event has been set #define IS_INTERRUPT_EVENT_SET(interruptEvent) (WaitForSingleObject(interruptEvent, 0) == WAIT_OBJECT_0) // Returns true if we need to continue resending the command request packet #define IS_SEND_PACKET_DONE(ch, event) ((ch != '+') && !IS_INTERRUPT_EVENT_SET(event)) // Check if there is a NAK/Start data packet #define IS_NAK_OR_START_PACKET(ch) ((ch == '-') || (ch == '$')) // This type indicates the error structure used for displaying errors typedef struct { int errorVal; LPCSTR description; LPCSTR actionHelper; } ConnectStreamErrorStruct; //============================================================================= // Private data definitions //============================================================================= // Configuration default packet structure template PacketConfig GdbSrvRspClient::s_RspProtocolFeatures[MAX_FEATURES] = { {false, 0, "VCont"}, {false, 0, "QStartNoAckMode"}, {false, 2048, "PacketSize"}, {false, 0, "qtrace32.memory"}, {false, 0, "Qtrace32.memory"}, }; // List of command packets that do not require Acknowledgment packet const char * listOfNotRequiredAckPackets[] = { // Set thread/processor packet "H" }; // Interrupt Packet const char interruptPacket[] = {0x03}; // Link Layer Configuration options template RSP_CONFIG_COMM_SESSION GdbSrvRspClient::s_LinkLayerConfigOptions = {0}; // List of socket stream connection errors static const ConnectStreamErrorStruct tcpStreamErrors[] = { {ERROR_HOST_DOWN, "The remote system is not available or the system is not ready for accepting commands.\n" "The Target GdbServer is probably down or the target system is running.\n"}, {WSAETIMEDOUT, "The connection has been dropped because of a network failure or\n" "because the peer system failed to respond (The GdbServer did not response in time)\n", "The GdbServer could not stop the target or we could lost the connection.\n"}, {WSAEMSGSIZE, "The message was too large to fit into the specified buffer and was truncated.", ""}, {WSAESHUTDOWN, "A request to send or receive data was disallowed because the socket had already \n" "been shut down in that direction with a previous shutdown call.\n", ""}, {WSAENETRESET, "the connection has been broken due to keep-alive activity that detected a failure \n" "while the operation was in progress.\n", ""}, {WSAENOTCONN, "The socket is not connected.\n", ""}, {WSAENOTSOCK, "The descriptor is not a socket.\n", ""}, {WSANOTINITIALISED, "A successful WSAStartup call must occur before using this function.\n", ""}, {WSAENETDOWN, "The network subsystem has failed.\n", ""}, {WSAEINTR, "The socket was closed.\n", ""}, {WSAEINPROGRESS, "A blocking Winsock call is in progress, or the service provider is still processing a callback function.\n", ""}, {WSAEALREADY, "A nonblocking connect call is in progress on the specified socket.\n", ""}, {WSAEADDRNOTAVAIL, "The remote address is not a valid address (such as ADDR_ANY).\n", ""}, {WSAECONNABORTED, "The virtual circuit was terminated due to a time-out or other failure.\n", "The application should close the socket as it is no longer usable."}, {WSAECONNRESET, "The virtual circuit was reset by the remote side executing a hard or abortive close.\n", "The application should close the socket as it is no longer usable."}, {WSAEAFNOSUPPORT, "Addresses in the specified family cannot be used with this socket.\n", ""}, {WSAECONNREFUSED, "The attempt to connect was forcefully rejected.\n", ""}, {WSAEFAULT, "The name or namelen parameter is not a valid part of the user address space, the namelen parameter\n" "is too small, or the name parameter contains incorrect address format for the associated address family.\n", ""}, {WSAEINVAL, "The parameter s is a listening socket.\n", ""}, {WSAEISCONN, "The socket is already connected (connection-oriented sockets only).\n", ""}, {WSAENETUNREACH, "The network cannot be reached from this host at this time.\n", ""}, }; //============================================================================= // Private function definitions //============================================================================= // FindErrorEntry Finds an entry in the error table // // Parameter: // errorCode Error code to find // // Return: // Pointer to the error code array entry. // const ConnectStreamErrorStruct * FindErrorEntry(_In_ int errorCode) { const ConnectStreamErrorStruct * pEntry = nullptr; for (size_t index = 0; index < ARRAYSIZE(tcpStreamErrors); ++index) { if (errorCode == tcpStreamErrors[index].errorVal) { // Display the error pEntry = &tcpStreamErrors[index]; break; } } return pEntry; } // GetErrorDescription Returnd the error description. // // Parameter: // pEntry Pointer to the entry. // // Return: // Pointer to description string. // LPCSTR GetErrorDescription(_In_ const ConnectStreamErrorStruct * pEntry) { assert(pEntry != nullptr); return pEntry->description; } // EscapePacket This function will escape the original string when the characters // '#' or '$' appear in the packet data. The escape character // is ASCII 0x7d ('}'), and is followed by the original character // XORed with 0x20. The character '}' itself must also be escaped. // // Parameter: // command String command to check for any escape character. // // Return: // The processed string for escape characters. // string EscapePacket(_In_ const string & command) { string escapedOrigCommand(command); smatch characterMatch; bool found = regex_search(command, characterMatch, regex("[$#}]")); if (found) { for (size_t pos = 0; pos < characterMatch.size(); ++pos) { string escapedOriginalCh(1, (command[characterMatch.position(pos)] ^ 0x20)); string temp = "{" + escapedOriginalCh; escapedOrigCommand.replace(characterMatch.position(pos), 1, temp); } } return escapedOrigCommand; } // // MakeRunLengthEncoding Implements the run-length encoding algorithm used by the RSP protocol. // // Parameters: // pCommand Pointer to the start of the string to check // remaining Number of remaining characters in the string to check // pCheckSum Pointer to the checksum variable // ppBuffer Pointer to the pointer pointing to the processed character buffer // // Return: // The number of characters that have been encoded. // // Note. // The run-length encoding info can be found here: // http://www.embecosm.com/appnotes/ean4/embecosm-howto-rsp-server-ean4-issue-2.html // int MakeRunLengthEncoding(_In_reads_(remaining) const char * pCommand, _In_ int remaining, _Inout_ unsigned int * pCheckSum, _Out_ char ** ppBuffer) { assert(pCommand != nullptr && pCheckSum != nullptr && ppBuffer != nullptr); // We cannot pass past '~' as this feature is suitable // for run-lengths of 4, 5 and 8-97 int maxLen = (remaining > 97) ? 97 : remaining; int runLength; for (runLength = 1; runLength < maxLen; ++runLength) { if (pCommand[0] != pCommand[runLength]) { break; } } // Calculate the checksum and update the output buffer for the current character *pCheckSum += pCommand[0]; *(*ppBuffer)++ = pCommand[0]; if (--runLength >= 3) { // yes, so there are characters that need to be encoded // Skip the start & end data packets characters. while (((runLength + 29) == '$') || ((runLength + 29) == '#')) { runLength--; } *pCheckSum += '*'; *(*ppBuffer)++ = '*'; *pCheckSum += static_cast(runLength) + 29; *(*ppBuffer)++ = static_cast(runLength) + 29; runLength++; } else { runLength = 1; } return runLength; } // // ReceiveInternal Reads a maximum packet length from the link layer and then // returns each received character from the stored buffer. // Indirectly returns the current character in the stored buffer. // If the link layer function fails then it returns the // error code rather than the number of remaining characters // (this is similar to the Bekerly socket recv() function). // // Parameters: // packetLength Length of the expected packet. // pStream Pointer to the TcpIpStream object. // resetBuffer Flag indicating if we need to flush the current buffer. // pCurrentChar Pointer to the current output character. // // Return: // Number of remaining characters in the stored input buffer or the error // returned from the stream receive function (SOCKET_ERROR). // // Note. // This function will execute a read by requesting to receive a maximum number of characters // and then it'll dispatch the received characters until the buffer is empty or the // caller requests reseting the input buffer. // int ReceiveInternal(_In_ int packetLength, _In_ TcpIpStream * const pStream, _In_ bool resetBuffer, _Out_ char * pCurrentChar) { assert(pStream != nullptr && pCurrentChar != nullptr); static unique_ptr pReadInputStream = nullptr; static int readInputStreamCharCounter = 0; static char * pReadInputStreamBuffer = nullptr; static int maximumPacketLength = 0; if (resetBuffer || pReadInputStreamBuffer == nullptr) { maximumPacketLength = static_cast(CALC_RSP_PACKET_LENGTH(packetLength)); pReadInputStream = unique_ptr (new (nothrow) char[maximumPacketLength]); assert(pReadInputStream != nullptr); readInputStreamCharCounter = 0; } if (readInputStreamCharCounter == 0) { pReadInputStreamBuffer = pReadInputStream.get(); memset(pReadInputStreamBuffer, 0x00, maximumPacketLength); readInputStreamCharCounter = pStream->Receive(pReadInputStreamBuffer, maximumPacketLength); if (readInputStreamCharCounter == SOCKET_ERROR) { pReadInputStreamBuffer = nullptr; } } if (pReadInputStreamBuffer != nullptr) { readInputStreamCharCounter--; *pCurrentChar = *pReadInputStreamBuffer; pReadInputStreamBuffer++; } return readInputStreamCharCounter; } // // BuildRspPacket Builds the RSP packet // // Parameters: // pStream Pointer to the TcpIpStream object. // outData Reference to the built packet. // checkSum Reference to the checksum of the built packet data // // Return: // If there is no any error then it returns the packet length // Otherwise the error. // int BuildRspPacket(_In_ TcpIpStream * const pStream, _Out_ string & outData, _Out_ unsigned int & checkSum) { assert(pStream != nullptr); int readStatus; char currentChar; checkSum = 0; for(;;) { readStatus = ReceiveInternal(0, pStream, false, ¤tChar); if (readStatus == SOCKET_ERROR) { break; } if (currentChar == '#') { checkSum %= 256; break; } outData += currentChar; checkSum += currentChar; } return readStatus; } // // IsValidRspPacket Validates the RSP packet checksum. // It compares the received packet checksum fields with the calculated checksum. // // Parameters: // pStream Pointer to the TcpIpStream object. // outData The calculated checksum of the received packet // isNoAckModeEnabled Flag indicating if the ACK mode is not enabled // inputRspData Reference to the received data packet without the checksum. // outRspData If the checksum field is correct then it contains the outputed string. // // Return: // true If both checksums match. // false Otherwise. // bool IsValidRspPacket(_In_ TcpIpStream * const pStream, _In_ unsigned int checkSum, _In_ bool isNoAckModeEnabled, _In_ const string & inputRspData, _Out_ string & outRspData) { assert(pStream != nullptr); bool isDone = false; unsigned char checkSumL; unsigned char checkSumR; // Verify the checksum int readStatus = ReceiveInternal(0, pStream, false, reinterpret_cast(&checkSumL)); if (readStatus != SOCKET_ERROR) { readStatus = ReceiveInternal(0, pStream, false, reinterpret_cast(&checkSumR)); if (readStatus != SOCKET_ERROR) { checkSumL = ((AciiHexToNumber(checkSumL) << 4) & 0xf0); unsigned int packetCheckSum = (AciiHexToNumber(checkSumR) & 0x0f) | checkSumL; if (checkSum == packetCheckSum) { // Checksum matched, so try to send an ACK if (!isNoAckModeEnabled) { pStream->Send("+", 1); } // Build the output response outRspData.assign(inputRspData); isDone = true; } else { if (!isNoAckModeEnabled) { // Send the NAK pStream->Send("-", 1); } } } } return isDone; } #if 0 // CreateSendRspPacketWithRunLengthEncoding Createa a RSP send packet with run-length encoding. // Note. // This function has not been fully tested, so please ensure testing before using it. // string GdbSrvRspClient::CreateSendRspPacketWithRunLengthEncoding(_In_ const string & command) { // Try to see if we need to escape the $/#/} characters in the request. // As far as RSP states so far there is no a RSP command having $/#/} as part of the request command. string packetToSend = EscapePacket(command); // Set the processing buffer int commandLength = static_cast(packetToSend.length()); unique_ptr outPacketToSend(new char[commandLength + 1]); char * pOutPacketToSend = outPacketToSend.get(); memset(pOutPacketToSend, 0x00, commandLength + 1); // Try to add a run-length encoding to the packet data. I'm not sure if this is required // for the RSP client, but it's one more issue that is not clear from the RSP doc. const char * pCommand = packetToSend.c_str(); unsigned int checksum = 0; for (int index = 0, encodedLength = 0; index < commandLength; index += encodedLength) { // If the encoding is required the add the encoding length to the current proccessed buffer character, // otherwise update the output buffer and calculate the checksum for the current processed character. encodedLength = MakeRunLengthEncoding(&pCommand[index], commandLength - index, &checksum, &pOutPacketToSend); } string outPacketData(outPacketToSend.get(), strlen(outPacketToSend.get())); // Put the start marker of the data packet outPacketData.insert(0, "$"); // Put the end marker of the data packet outPacketData.insert(outPacketData.end(), 1, '#'); // Put the checksum as two ascii hex digits outPacketData.insert(outPacketData.end(), 1, NumberToAciiHex(((checksum >> 4) & 0xf))); outPacketData.insert(outPacketData.end(), 1, NumberToAciiHex((checksum & 0xf))); return outPacketData; } #endif // // IsReceiveInterrupt Check if we need to interrupt the ongoing receiving sequence. // // Parameters: // readStatus The status of the last read action. // isRspWaitNeeded Flag is true when we are inside of the no receive timeout session. // interruptEvent Handle of the interrupt event // // Return: // true If we need to interrupt the waiting sequence because the user cancel // or a link layer error ocurred. // false Otherwise. // inline bool IsReceiveInterrupt(_In_ int readStatus, _In_ bool isRspWaitNeeded, _In_ HANDLE interruptEvent) { bool isInterrupted = false; if (readStatus == SOCKET_ERROR && isRspWaitNeeded) { isInterrupted = true; } else if(IS_INTERRUPT_EVENT_SET(interruptEvent)) { isInterrupted = true; } return isInterrupted; } // // WaitForRspPacketStart Waits for the start packet character to arrive from the server. // // Parameters: // maxPacketLength Expected packet length // pStream Pointer to the TcpIpStream object. // isRspWaitNeeded Flag true if we need to wait until the packet arrive (ignore timeout). // IsPollingChannelMode Flag set if the current mode requires polling all channels. // fResetBuffer Flag indicates if we need to reset any pending data in the local cached buffer. // // Return: // The number of received characters. // int GdbSrvRspClient::WaitForRspPacketStart(_In_ int maxPacketLength, _In_ TcpIpStream * const pStream, _In_ bool isRspWaitNeeded, _Inout_ bool & IsPollingChannelMode, _In_ bool fResetBuffer) { assert(pStream != nullptr && maxPacketLength != 0); char currentChar; int readStatus; bool reset = fResetBuffer; // Wait for the packet start character to arrive. do { readStatus = ReceiveInternal(maxPacketLength, pStream, reset, ¤tChar); // Do we need to exit the receiving sequence? if (IsReceiveInterrupt(readStatus, isRspWaitNeeded, m_interruptEvent.Get())) { IsPollingChannelMode = false; break; } reset = false; } while (currentChar != '$' && !IsPollingChannelMode); return readStatus; } // // CreateSendRspPacket Creates a Rsp request packet. // // Parameters: // command Reference to the request command data. // // Return: // The Rsp formated string. // string GdbSrvRspClient::CreateSendRspPacket(_In_ const string & command) { // Try to see if we need to escape the $/#/} characters in the request. // As far as RSP states so far there is no request command having $/#/} characters. string packetToSend = EscapePacket(command); unsigned int checkSum = 0; for (auto pos = packetToSend.begin(); pos != packetToSend.end(); ++pos) { // Calculate the checksum and update the output buffer for the current character checkSum += *pos; } checkSum %= 256; // Put the start marker of the data packet packetToSend.insert(0, "$"); // Put the end marker of the data packet packetToSend.insert(packetToSend.end(), 1, '#'); // Put the checksum as two ascii hex digits packetToSend.insert(packetToSend.end(), 1, NumberToAciiHex(((checkSum >> 4) & 0xf))); packetToSend.insert(packetToSend.end(), 1, NumberToAciiHex((checkSum & 0xf))); return packetToSend; } // SetProtocolFeatureValue Set the Protocol feature value field inline void GdbSrvRspClient::SetProtocolFeatureValue(_In_ size_t index, _In_ int value) { (GET_FEATURE_ENTRY(index)).featureDefaultValue = value; } // SetProtocolFeatureFlag Set the Protocol feature flag field inline void GdbSrvRspClient::SetProtocolFeatureFlag(_In_ size_t index, _In_ bool value) { (GET_FEATURE_ENTRY(index)).isEnabled = value; } // // GetNoAckModeRequired Check if the command needs ACK mode. // // command command to check. // // Returns // true The command does not need ack mode. // false Otherwise. // inline bool GdbSrvRspClient::GetNoAckModeRequired(_In_ const string & command) { bool isNoAckMode = false; // Is ACK packet mode enabled? if (!IS_FEATURE_ENABLED(PACKET_QSTART_NO_ACKMODE)) { size_t numberOfElements = ARRAYSIZE(listOfNotRequiredAckPackets); for (size_t index = 0; index < numberOfElements; ++index) { if (strcmp(listOfNotRequiredAckPackets[index], command.c_str()) == 0) { isNoAckMode = true; break; } } } else { isNoAckMode = true; } return isNoAckMode; } //============================================================================= // Public function definitions //============================================================================= // // SendRspPacket Sends a RSP packet to the GdbServer. // // Parameters: // command Reference to a string containing the request data (command) // // Return: // true if succeeded sending the RSp packet. // false Otherwise. // activeCore Current active processor core. // // Note. // This function will create and send a packet with the following format: // $#<2 bytes digits checksum> // // The GdbServer will ACK/NAK the packet, so If the checksum validation succeeded // then the GdbServer will send an ACK packet ('+'). // If GdbServer NAK the packet ('-') then we will resend the packet until we get // the ACK or the user cancel the sending sequence. // bool GdbSrvRspClient::SendRspPacket(_In_ const string & command, _In_ unsigned activeCore) { assert(m_pConnector != nullptr); try { scoped_lock packetGuard(m_gdbSrvRspLock); bool isDone = true; // Create the packet to send string packetToSend = CreateSendRspPacket(command); // Does we require ACK? bool isNoAckMode = GetNoAckModeRequired(command); TcpIpStream * pTcpStream = m_pConnector->GetLinkLayerStreamEntry(activeCore); assert(pTcpStream != nullptr); char ackCharacter[1] = {0}; int sendResult = 0; int retryCounter = 0; bool isSendPacket = true; do { // Send it over the Link layer until we receive an ACK from GdbServer if (isSendPacket) { sendResult = pTcpStream->Send(packetToSend.c_str(), static_cast(packetToSend.length())); if (sendResult == SOCKET_ERROR) { isDone = false; break; } isSendPacket = false; } // Is no ACK packet mode enabled? if (isNoAckMode) { // We do not need + packet, so exit here break; } // Try to read the +/- (ACK/NAK) packet sendResult = pTcpStream->Receive(ackCharacter, sizeof(ackCharacter)); if (sendResult == SOCKET_ERROR) { // Did we reach the maximum retry attempts? if (IS_MAX_ATTEMPTS(++retryCounter)) { isDone = false; break; } else { isSendPacket = true; } } // Is it a start data packet? // The start packet ($) checking is due to a GdbServer error, so force retrying again as the packet // has not been Acked yet. isSendPacket = IS_NAK_OR_START_PACKET(ackCharacter[0]); // Should we check here for the '\03' interrupt request from the GdbServer? // It's not clear if the GdbServer can send us break request provoked by the target. // If yes then we should add the break checking (\x03) here and send/read then // the stop reason package that will continue this break before exiting from this function. } while (IS_SEND_PACKET_DONE(ackCharacter[0], m_interruptEvent.Get())); return isDone; } CATCH_AND_RETURN_BOOLEAN } // // ReceiveRspPacketEx Receives a RSP packet from the GdbServer. // // Parameters: // isRspWaitNeeded Flag indicates if we need to wait forever for the incoming Gdbserver packet. // response Reference to the ouputed RSP packet string if the function succeed. // activeCore Current active processor core. // IsPollingChannelMode Flag set if the current mode requires polling all channels // fResetBuffer Flag indicates if we need to reset any pending data in the cached buffer. // // Return: // true The received RSP packet is correct. // false Otherwise. // // Note. // The RSP packet is expected in the format: // $#<2 bytes digits checksum> // The function validates the checksum and sends a CK/NAK (+/-) (if the ackmode is enabled). // If we receive a valid packet then it disables polling mode. // bool GdbSrvRspClient::ReceiveRspPacketEx(_Out_ string & response, _In_ unsigned activeCore, _In_ bool isRspWaitNeeded, _Inout_ bool & IsPollingChannelMode, _In_ bool fResetBuffer) { assert(m_pConnector != nullptr); try { bool isDone = false; scoped_lock packetGuard(m_gdbSrvRspLock); // Verify if we have set the maximum response packet, if so then use // this value as the maximum response int maxPacketLength = GET_FEATURE_VALUE(PACKET_SIZE); // Get the current active core tcp stream object. TcpIpStream * pTcpStream = m_pConnector->GetLinkLayerStreamEntry(activeCore); assert(pTcpStream != nullptr); // Wait for the first packet character '$' to arrive if (WaitForRspPacketStart(maxPacketLength, pTcpStream, isRspWaitNeeded, IsPollingChannelMode, fResetBuffer) != SOCKET_ERROR) { string replyPacket; replyPacket.reserve(response.length()); unsigned int checkSum = 0; // Build the data packet if (BuildRspPacket(pTcpStream, replyPacket, checkSum) != SOCKET_ERROR) { // Verify if the RSP checksum is valid, if so, then output the response string if (IsValidRspPacket(pTcpStream, checkSum, IS_FEATURE_ENABLED(PACKET_QSTART_NO_ACKMODE), replyPacket, response)) { isDone = true; // Disable polling mode IsPollingChannelMode = false; } } } return isDone; } CATCH_AND_RETURN_BOOLEAN } // // ConfigRspSession Set the RSP session communication parameters. // Also, it sets the TCP stream link layer options. // // Parameters: // pConfigData Pointer to the configuration session data. // core if C_ALLCORES, then configure all cores, otherwise only specific core. // // Return: // true if we succeeded setting up the stream connection. // false Otherwise. // bool GdbSrvRspClient::ConfigRspSession(_In_ const RSP_CONFIG_COMM_SESSION * pConfigData, _In_ unsigned core) { assert(m_pConnector != nullptr && pConfigData != nullptr); scoped_lock packetGuard(m_gdbSrvRspLock); bool configDone = true; bool isAllCores = (core == C_ALLCORES) ? true : false; memcpy(&s_LinkLayerConfigOptions, pConfigData, sizeof(s_LinkLayerConfigOptions)); size_t totalNumberOfProcessorCores = m_pConnector->GetNumberOfConnections(); for (size_t coreNumber = 0; coreNumber < totalNumberOfProcessorCores; ++coreNumber) { if (isAllCores || core == coreNumber) { TcpIpStream * pStream = m_pConnector->GetLinkLayerStreamEntry(coreNumber); assert(pStream != nullptr); // Set the call back function if (pConfigData->pDisplayCommDataFunc != nullptr && pConfigData->pTextHandler != nullptr) { pStream->SetCallBackDisplayFunc(pConfigData->pDisplayCommDataFunc, pConfigData->pTextHandler); } // Disable Nagle algorithm bool isNagleAlgorithmDisabled = true; if (pStream->SetOptions(IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&isNagleAlgorithmDisabled), sizeof(isNagleAlgorithmDisabled)) == SOCKET_ERROR) { configDone = false; break; } // Enable TCP keep alive packets, so we can check if the GdbServer is alive DWORD isKeepAlive = 1; if (pStream->SetOptions(SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&isKeepAlive), sizeof(isKeepAlive)) == SOCKET_ERROR) { configDone = false; break; } int resultRecv = 0; if (pConfigData->recvTimeout != 0) { resultRecv = pStream->SetOptions(SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&pConfigData->recvTimeout), sizeof(pConfigData->recvTimeout)); } int resultSend = 0; if (pConfigData->sendTimeout != 0) { resultSend = pStream->SetOptions(SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&pConfigData->sendTimeout), sizeof(pConfigData->sendTimeout)); } if (resultRecv == SOCKET_ERROR || resultSend == SOCKET_ERROR) { configDone = false; break; } } } return configDone; } // // GetRspSessionStatus Get the connection status of the link layer session. // This can be used by the high layers for implementing keep alive mechanism. // // Parameters: // error A reference to the error code returned by the link layer. // If this function detected that we lost connection then // it contains an injected abort error code. // core Processor core to check for RspConnection. If C_ALLCORES, then check all cores. // // Return: // true If this function succeeded retrieving the link layer status. // // false If this function failed retrieving the link layer status as // we do not have a physical connection yet. // // Note. // If there is an error then it returns indirectly it. // bool GdbSrvRspClient::GetRspSessionStatus(_Out_ HRESULT & error, _In_ unsigned core) { assert(m_pConnector != nullptr); bool isDone = false; bool isAllCores = (core == C_ALLCORES) ? true : false; size_t totalNumberOfProcessorCores = m_pConnector->GetNumberOfConnections(); for (size_t coreNumber = 0; coreNumber < totalNumberOfProcessorCores; ++coreNumber) { if (isAllCores || coreNumber == core) { TcpIpStream * pStream = m_pConnector->GetLinkLayerStreamEntry(coreNumber); assert(pStream != nullptr); if (m_pConnector->IsConnected()) { fd_set fdSetRead; fd_set fdSetError; struct timeval selectTimeout; selectTimeout.tv_sec = 0; selectTimeout.tv_usec = 500; int numberOfSockets = pStream->Select(&fdSetRead, nullptr, &fdSetError, &selectTimeout); if (numberOfSockets != SOCKET_ERROR && numberOfSockets != 0) { if (pStream->IsFDSet(&fdSetRead) != 0) { // Ensure that the connection has been closed/terminated by trying to see // if this is not a close/reset/terminate false positive event, so we will try peeking the data // If there is a pending data then this is not caused by a close/terminating event. // Change the socket mode to non-blocking. u_long mode = 1; int modeStatus = pStream->Ioctlsocket(FIONBIO, &mode); if (modeStatus != SOCKET_ERROR) { // Look for any pending data char tempBuffer[1]; if (pStream->Peek(tempBuffer, sizeof(tempBuffer), MSG_PEEK) != NO_ERROR) { // We got an error, so find out if this is a lost socket connection type of error. int peekerror = m_pConnector->GetLastError(); if (m_pConnector->IsConnectionLost(peekerror)) { // We lost connection, so abort the GDB RSP session. error = ERROR_OPERATION_ABORTED; } } if (error != ERROR_OPERATION_ABORTED) { // Change back to blocking mode mode = 0; if (pStream->Ioctlsocket(FIONBIO, &mode) == SOCKET_ERROR) { // This should be a fatal error as we cannot set back the previous socket mode. error = ERROR_OPERATION_ABORTED; } } } else { error = HRESULT_FROM_WIN32(m_pConnector->GetLastError()); } } // Is there a socket error? else if (pStream->IsFDSet(&fdSetError) != 0) { DWORD socketError = 0; DWORD socketErrorLength = sizeof(socketError); int errorStatus = pStream->GetOptions(SOL_SOCKET, SO_ERROR, reinterpret_cast(&socketError), reinterpret_cast(&socketErrorLength)); if (errorStatus != SOCKET_ERROR) { error = HRESULT_FROM_WIN32(socketError); } else { error = HRESULT_FROM_WIN32(m_pConnector->GetLastError()); } } } else { error = HRESULT_FROM_WIN32(m_pConnector->GetLastError()); } isDone = true; } } } return isDone; } // // UpdateRspPacketFeatures Updates the local cache for the GdbServer supported features, so // this function will parse the response and // stores the features in the local packetEnableFeatures array. // Parameters: // reply Reference to the string containing the feature reply (see below the format) // // Return: // true always. // // Note. // Reply format can be found here: // https://sourceware.org/gdb/onlinedocs/gdb/General-Query-Packets.html#qSupported // bool GdbSrvRspClient::UpdateRspPacketFeatures(_In_ const string & reply) { try { scoped_lock packetGuard(m_gdbSrvRspLock); if (!reply.empty()) { for (int index = 0; index < MAX_FEATURES; ++index) { string::size_type pos = reply.find(GET_FEATURE_NAME(index)); if (pos != string::npos) { // Looking for featureName= pos += (GET_FEATURE_NAME(index)).length(); if (reply[pos] == '=') { // This feature contains a value, so extract it SetProtocolFeatureValue(index, stoi(reply.substr(pos + 1), nullptr, 16)); } else if (reply[pos] == '+') { // This feature contains an enabled flag, so set the flag. SetProtocolFeatureFlag(index, true); } else if (reply[pos] == '-') { SetProtocolFeatureFlag(index, false); } } } } return true; } CATCH_AND_RETURN_BOOLEAN } // // GetRspPacketFeatures Retrieves the current stored features. // // Parameters: // pConfig Pointer to the output buffer stored configuration. // index Feature index. // // Returns: // Nothing. // void GdbSrvRspClient::GetRspPacketFeatures(_Out_ PacketConfig * pConfig, _In_ RSP_FEATURES index) { assert(pConfig != nullptr); scoped_lock packetGuard(m_gdbSrvRspLock); pConfig->isEnabled = s_RspProtocolFeatures[index].isEnabled; pConfig->featureDefaultValue = s_RspProtocolFeatures[index].featureDefaultValue; pConfig->name += s_RspProtocolFeatures[index].name; } // // ConnectRsp Connects to the remote GdbServer // // Returns: // true if the connection succeeded. // false Otherwise. // bool GdbSrvRspClient::ConnectRsp() { assert(m_pConnector != nullptr); scoped_lock packetGuard(m_gdbSrvRspLock); unsigned int retries = (s_LinkLayerConfigOptions.connectAttempts == 0) ? 1 : s_LinkLayerConfigOptions.connectAttempts; return m_pConnector->Connect(retries); } // // AttachRspToCore Create a new core TCP stream channel and connect to it. // // Parameters: // connectionStr Connection string. // core Processor core to attach. // // Returns: // true if the attach operation succeeded (create a new channel and connect to it). // false Otherwise. // bool GdbSrvRspClient::AttachRspToCore(_In_ const wstring &connectionStr, _In_ unsigned core) { assert(m_pConnector != nullptr); bool isAttached = false; scoped_lock packetGuard(m_gdbSrvRspLock); if (connectionStr.empty() || core == C_ALLCORES || core > m_pConnector->GetNumberOfConnections()) { return isAttached; } isAttached = m_pConnector->TcpOpenStreamCore(connectionStr, core); if (isAttached) { unsigned int retries = (s_LinkLayerConfigOptions.connectAttempts == 0) ? 1 : s_LinkLayerConfigOptions.connectAttempts; isAttached = m_pConnector->TcpConnectCore(retries, core); } return isAttached; } // // ConnectRspToCore Connect to specific TCP stream channel // // Parameters: // connectionStr Connection string // core Processor core to connect. // // Returns: // true if the TCT stream connect succeeded. // false Otherwise. // bool GdbSrvRspClient::ConnectRspToCore(_In_ const wstring &connectionStr, _In_ unsigned core) { assert(m_pConnector != nullptr); scoped_lock packetGuard(m_gdbSrvRspLock); if (connectionStr.empty() || core == C_ALLCORES || core > m_pConnector->GetNumberOfConnections()) { return false; } unsigned int retries = (s_LinkLayerConfigOptions.connectAttempts == 0) ? 1 : s_LinkLayerConfigOptions.connectAttempts; return m_pConnector->TcpConnectCore(retries, core); } // // CloseRspCore Close the RSP protocol for the passed in processor channel. // // Parameters: // core Processor core to close. // // Returns: // true if the close operation succeeded. // false Otherwise. // bool GdbSrvRspClient::CloseRspCore(_In_ const wstring &closeStr, _In_ unsigned core) { UNREFERENCED_PARAMETER(closeStr); assert(m_pConnector != nullptr); scoped_lock packetGuard(m_gdbSrvRspLock); bool isClosed = false; if (core == C_ALLCORES || core > m_pConnector->GetNumberOfConnections()) { return isClosed; } return m_pConnector->TcpCloseCore(core); } // // GetRspLastError Retrieves the last error from the link layer // // Returns: // The stream class stored error. // int GdbSrvRspClient::GetRspLastError() { assert(m_pConnector != nullptr); return m_pConnector->GetLastError(); } // // ShutDownRsp Shutdown the RSP protocol session by requesting closing the connection.. // // Returns: // true if no error during closing the connection. // false otherwise. // bool GdbSrvRspClient::ShutDownRsp() { assert(m_pConnector != nullptr); return m_pConnector->Close(); } // // SendRspInterruptEx Interrupt the target by sending a RSP interrup packet. // This packet contains only one character 0x03 // without a RSP protocol packet structure // (it does not have any response). // // Parameters: // fResetAllCores Flag if true, then we need to reset all cores // if false, then the second parameter (activeCore) will // have the core value that needs to be skipped. // activeCore Contains the core that will be skipped from being interrupted. // // Return: // true if we succeeded sending the interrupt break sequence. // false Otherwise. // // Request: // 0x03 // Response: // Nothing. // // Note. // !!! The interrupt command does not have a response, but we should not try // reading characters without using the critical section read internal buffer protection !!! // The interrupt command will generate sending a stop reason reply packet by the GdbServer and // this packet will be received from the main working command thread. // bool GdbSrvRspClient::SendRspInterruptEx(_In_ bool fResetAllCores, _In_ unsigned activeCore) { assert(m_pConnector != nullptr); bool isDone = false; size_t totalNumberOfProcessorCores = m_pConnector->GetNumberOfConnections(); for (size_t coreNumber = 0; coreNumber < totalNumberOfProcessorCores; ++coreNumber) { if (fResetAllCores || (coreNumber != activeCore)) { TcpIpStream * pStream = m_pConnector->GetLinkLayerStreamEntry(coreNumber); assert(pStream != nullptr); int sendResult = pStream->Send(interruptPacket, static_cast(strlen(interruptPacket))); if (sendResult != SOCKET_ERROR) { // Set the interrupt event SetEvent(m_interruptEvent.Get()); // Wait a little bit for the GdbServer to process the break request packet. Sleep(200); isDone = true; } } } return isDone; } // // HandleRspErrors Finds the RSP communication errors. // // Parameters: // testxType Text type (command/output/error) // // Return: // Nothing. // void GdbSrvRspClient::HandleRspErrors(_In_ GdbSrvTextType textType) { assert(m_pConnector != nullptr); int errorCode = m_pConnector->GetLastError(); const ConnectStreamErrorStruct * pEntry = nullptr; size_t totalNumberOfProcessorCores = m_pConnector->GetNumberOfConnections(); for (size_t coreNumber = 0; coreNumber < totalNumberOfProcessorCores; ++coreNumber) { TcpIpStream * pStream = m_pConnector->GetLinkLayerStreamEntry(coreNumber); assert(pStream != nullptr); pEntry = FindErrorEntry(errorCode); if (pEntry != nullptr) { pStream->CallDisplayFunction(pEntry->description, textType); break; } } if (pEntry == nullptr) { char errorString[128] = {0}; _snprintf_s(errorString, _TRUNCATE, "The socket error 0x%x ocurred", errorCode); TcpIpStream * pStream = m_pConnector->GetLinkLayerStream(); assert(pStream != nullptr); pStream->CallDisplayFunction(errorString, textType); } } // // DiscardResponse Discard any pending response. // // Parameters: // activeCore The processor number that has to be skipped from discarding the response // // Return: // Nothing. // void GdbSrvRspClient::DiscardResponse(_In_ unsigned activeCore) { assert(m_pConnector != nullptr); scoped_lock packetGuard(m_gdbSrvRspLock); bool IsPollingChannelMode = true; size_t totalNumberOfProcessorCores = m_pConnector->GetNumberOfConnections(); for (unsigned coreNumber = 0; coreNumber < totalNumberOfProcessorCores; ++coreNumber) { if (coreNumber != activeCore) { TcpIpStream * pStream = m_pConnector->GetLinkLayerStreamEntry(coreNumber); assert(pStream != nullptr); string result; bool isRecvDone = ReceiveRspPacketEx(result, coreNumber, false, IsPollingChannelMode, true); if ((!isRecvDone && IsPollingChannelMode) || result.empty()) { // Try to interrupt pStream->Send(interruptPacket, static_cast(strlen(interruptPacket))); } else { // Is the target running/power down packet? if (result.find("S00") != string::npos) { pStream->CallDisplayFunction(GetErrorDescription(FindErrorEntry(ERROR_HOST_DOWN)), GdbSrvTextType::CommandError); } } } } } // // IsFeatureEnabled Check if the feature is enabled. // // Parameters: // feature Feature to check // // Return: // true The feature is enabled // false The feature is disabled // bool GdbSrvRspClient::IsFeatureEnabled(_In_ unsigned feature) { return IS_FEATURE_ENABLED(feature); } // // GetNumberOfStreamConnections Get the number of ongoing connections // // Parameters: // // Return: // Number of channel stream connections. // size_t GdbSrvRspClient::GetNumberOfStreamConnections() { assert(m_pConnector != nullptr); scoped_lock packetGuard(m_gdbSrvRspLock); return m_pConnector->GetNumberOfConnections(); } GdbSrvRspClient::GdbSrvRspClient(_In_ const vector &coreConnectionParameters) : m_interruptEvent(CreateEvent(nullptr, FALSE, FALSE, nullptr)), m_pConnector(unique_ptr(new (nothrow) TConnectStream(coreConnectionParameters))) { InitializeCriticalSection(&m_gdbSrvRspLock); } GdbSrvRspClient::~GdbSrvRspClient() { ShutDownRsp(); DeleteCriticalSection(&m_gdbSrvRspLock); m_interruptEvent.Close(); }