-
Notifications
You must be signed in to change notification settings - Fork 131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GdbServer: Implement new netstream that can be interrupted #4223
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,7 +64,7 @@ namespace FEX { | |
#ifndef _WIN32 | ||
void GdbServer::Break(FEXCore::Core::InternalThreadState* Thread, int signal) { | ||
std::lock_guard lk(sendMutex); | ||
if (!CommsStream) { | ||
if (!CommsStream.HasSocket()) { | ||
return; | ||
} | ||
|
||
|
@@ -73,7 +73,7 @@ void GdbServer::Break(FEXCore::Core::InternalThreadState* Thread, int signal) { | |
CurrentDebuggingThread = ThreadObject->ThreadInfo.TID.load(); | ||
|
||
const auto str = fextl::fmt::format("T{:02x}thread:{:x};", signal, CurrentDebuggingThread); | ||
SendPacket(*CommsStream, str); | ||
SendPacket(str); | ||
} | ||
|
||
void GdbServer::WaitForThreadWakeup() { | ||
|
@@ -178,7 +178,7 @@ static fextl::string encodeHex(std::string_view str) { | |
// Takes a serial stream and reads a single packet | ||
// Un-escapes chars, checks the checksum and request a retransmit if it fails. | ||
// Once the checksum is validated, it acknowledges and returns the packet in a string | ||
fextl::string GdbServer::ReadPacket(std::iostream& stream) { | ||
fextl::string GdbServer::ReadPacket() { | ||
fextl::string packet {}; | ||
|
||
// The GDB "Remote Serial Protocal" was originally 7bit clean for use on serial ports. | ||
|
@@ -190,9 +190,9 @@ fextl::string GdbServer::ReadPacket(std::iostream& stream) { | |
// where any $ or # in the packet body are escaped ('}' followed by the char XORed with 0x20) | ||
// The checksum is a single unsigned byte sum of the data, hex encoded. | ||
|
||
int c; | ||
while ((c = stream.get()) > 0) { | ||
switch (c) { | ||
Utils::NetStream::ReturnGet c; | ||
while ((c = CommsStream.get()).HasData()) { | ||
switch (c.GetData()) { | ||
case '$': // start of packet | ||
if (packet.size() != 0) { | ||
LogMan::Msg::EFmt("Dropping unexpected data: \"{}\"", packet); | ||
|
@@ -203,15 +203,18 @@ fextl::string GdbServer::ReadPacket(std::iostream& stream) { | |
break; | ||
case '}': // escape char | ||
{ | ||
char escaped; | ||
stream >> escaped; | ||
packet.push_back(escaped ^ 0x20); | ||
auto escaped = CommsStream.get(); | ||
if (escaped.HasData()) { | ||
packet.push_back(escaped.GetData() ^ 0x20); | ||
} else { | ||
LogMan::Msg::EFmt("Received Invalid escape char: ${}", packet); | ||
} | ||
break; | ||
} | ||
case '#': // end of packet | ||
{ | ||
char hexString[3] = {0, 0, 0}; | ||
stream.read(hexString, 2); | ||
CommsStream.read(hexString, 2); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly to the above, this doesn't seem to handle hangup events properly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
int expected_checksum = std::strtoul(hexString, nullptr, 16); | ||
|
||
if (calculateChecksum(packet) == expected_checksum) { | ||
|
@@ -221,7 +224,7 @@ fextl::string GdbServer::ReadPacket(std::iostream& stream) { | |
} | ||
break; | ||
} | ||
default: packet.push_back((char)c); break; | ||
default: packet.push_back(c.GetData()); break; | ||
} | ||
} | ||
|
||
|
@@ -248,22 +251,22 @@ static fextl::string escapePacket(const fextl::string& packet) { | |
return ss.str(); | ||
} | ||
|
||
void GdbServer::SendPacket(std::ostream& stream, const fextl::string& packet) { | ||
void GdbServer::SendPacket(const fextl::string& packet) { | ||
const auto escaped = escapePacket(packet); | ||
const auto str = fextl::fmt::format("${}#{:02x}", escaped, calculateChecksum(escaped)); | ||
|
||
stream << str << std::flush; | ||
CommsStream.SendPacket(str); | ||
} | ||
|
||
void GdbServer::SendACK(std::ostream& stream, bool NACK) { | ||
void GdbServer::SendACK(bool NACK) { | ||
if (NoAckMode) { | ||
return; | ||
} | ||
|
||
if (NACK) { | ||
stream << "-" << std::flush; | ||
CommsStream.SendPacket("-"); | ||
} else { | ||
stream << "+" << std::flush; | ||
CommsStream.SendPacket("+"); | ||
} | ||
|
||
if (SettingNoAckMode) { | ||
|
@@ -1341,16 +1344,16 @@ GdbServer::HandledPacketType GdbServer::ProcessPacket(const fextl::string& packe | |
void GdbServer::SendPacketPair(const HandledPacketType& response) { | ||
std::lock_guard lk(sendMutex); | ||
if (response.TypeResponse == HandledPacketType::TYPE_ACK || response.TypeResponse == HandledPacketType::TYPE_ONLYACK) { | ||
SendACK(*CommsStream, false); | ||
SendACK(false); | ||
} else if (response.TypeResponse == HandledPacketType::TYPE_NACK || response.TypeResponse == HandledPacketType::TYPE_ONLYNACK) { | ||
SendACK(*CommsStream, true); | ||
SendACK(true); | ||
} | ||
|
||
if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) { | ||
SendPacket(*CommsStream, ""); | ||
SendPacket(""); | ||
} else if (response.TypeResponse != HandledPacketType::TYPE_ONLYNACK && response.TypeResponse != HandledPacketType::TYPE_ONLYACK && | ||
response.TypeResponse != HandledPacketType::TYPE_NONE) { | ||
SendPacket(*CommsStream, response.Response); | ||
SendPacket(response.Response); | ||
} | ||
} | ||
|
||
|
@@ -1362,7 +1365,7 @@ GdbServer::WaitForConnectionResult GdbServer::WaitForConnection() { | |
int Result = ppoll(&PollFD, 1, nullptr, nullptr); | ||
if (Result > 0) { | ||
if (PollFD.revents & POLLIN) { | ||
CommsStream = OpenSocket(); | ||
OpenSocket(); | ||
return WaitForConnectionResult::CONNECTION; | ||
} else if (PollFD.revents & (POLLHUP | POLLERR | POLLNVAL)) { | ||
// Listen socket error or shutting down | ||
|
@@ -1393,12 +1396,11 @@ void GdbServer::GdbServerLoop() { | |
HandledPacketType response {}; | ||
|
||
// Outer server loop. Handles packet start, ACK/NAK and break | ||
|
||
int c; | ||
while ((c = CommsStream->get()) >= 0) { | ||
switch (c) { | ||
Utils::NetStream::ReturnGet c; | ||
while (!CoreShuttingDown.load() && (c = CommsStream.get()).HasData()) { | ||
switch (c.GetData()) { | ||
case '$': { | ||
auto packet = ReadPacket(*CommsStream); | ||
auto packet = ReadPacket(); | ||
response = ProcessPacket(packet); | ||
SendPacketPair(response); | ||
if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) { | ||
|
@@ -1413,7 +1415,7 @@ void GdbServer::GdbServerLoop() { | |
// NAK, Resend requested | ||
{ | ||
std::lock_guard lk(sendMutex); | ||
SendPacket(*CommsStream, response.Response); | ||
SendPacket(response.Response); | ||
} | ||
break; | ||
case '\x03': { // ASCII EOT | ||
|
@@ -1426,13 +1428,17 @@ void GdbServer::GdbServerLoop() { | |
SendPacketPair({std::move(str), HandledPacketType::TYPE_ACK}); | ||
break; | ||
} | ||
default: LogMan::Msg::DFmt("GdbServer: Unexpected byte {} ({:02x})", static_cast<char>(c), c); | ||
default: LogMan::Msg::DFmt("GdbServer: Unexpected byte {} ({:02x})", c.GetData(), c.GetData()); | ||
} | ||
} | ||
|
||
if (c.HasHangup()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The control flow has become weird here (maybe since the latest changes?). Why do we skip the Why is there still an outer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, that was a bug introduced due to the refactoring request to delete an outer loop. Added the loop back because it's required to allow gdb connections to disconnect and reconnect at a later time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's three levels of nesting loops now, two of them on the same variable... while (!CoreShuttingDown.load()) {
// ...
while (!CoreShuttingDown.load()) {
// ...
while ((c = CommsStream.get()).HasData()) { |
||
break; | ||
} | ||
|
||
{ | ||
std::lock_guard lk(sendMutex); | ||
CommsStream.reset(); | ||
CommsStream.InvalidateSocket(); | ||
} | ||
} | ||
|
||
|
@@ -1504,14 +1510,14 @@ void GdbServer::CloseListenSocket() { | |
unlink(GdbUnixSocketPath.c_str()); | ||
} | ||
|
||
fextl::unique_ptr<std::iostream> GdbServer::OpenSocket() { | ||
void GdbServer::OpenSocket() { | ||
// Block until a connection arrives | ||
struct sockaddr_storage their_addr {}; | ||
socklen_t addr_size {}; | ||
|
||
int new_fd = accept(ListenSocket, (struct sockaddr*)&their_addr, &addr_size); | ||
|
||
return fextl::make_unique<FEXCore::Utils::NetStream>(new_fd); | ||
CommsStream.OpenSocket(new_fd); | ||
} | ||
|
||
#endif | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there a potential race condition here? An interrupt exactly after
}
would be caught here but silently be skipped.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now it will just permanently hang trying to receive more data, won't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless a hangup occurs, similar to previous behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you fix it? Not sure it makes sense to add the capability for interruptions and then only use it half of the time...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no fix in this case. We need to wait until gdb has finished sending the message, otherwise the command stream is borked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a scenario that can happen in practice or must other FEX code somehow ensure no interruptions in this specific part?
How do you currently plan to deal with it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gdb is likely to have already sent the message and its in our socket's buffer. The extreme case will be gdb has crashed which we notice as a hangup and early terminate.
When interrupted, it will continue waiting for its next message from gdb and handle whatever the interruption was about afterwards. There's nothing more to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not seeing it, which I think relates back to my unanswered questions at the end of #4223 (comment) . I need to see an actual rundown of the problem scenario(s) you're addressing. It's unclear where interruptions happen and why, and which elements of this PR are mandated by GDB and which ones are pending on implementation choices.
Normally I just wouldn't expect async-aware code to be able to enter a permanently hanging state. It looks like a design flaw in the current implementation, but I can't pin it down more specifically without more context.