Skip to content
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

IEP-1265: Debug Process Hangs #1023

Merged
merged 16 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*******************************************************************************/
package com.espressif.idf.core.util;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import com.espressif.idf.core.logging.Logger;
Expand All @@ -24,13 +26,15 @@

public static boolean isPortAvailable(int port)
{
try (Socket ignored = new Socket("localhost", port)) //$NON-NLS-1$
try (ServerSocket serverSocket = new ServerSocket(port))
{
return false;
serverSocket.setReuseAddress(true);
return true;
}
catch (Exception e)

Check warning on line 34 in bundles/com.espressif.idf.core/src/com/espressif/idf/core/util/PortChecker.java

View workflow job for this annotation

GitHub Actions / spotbugs

REC_CATCH_EXCEPTION

Exception is caught when Exception is not thrown in com.espressif.idf.core.util.PortChecker.isPortAvailable(int)
Raw output
This method uses a try-catch block that catches Exception objects, but Exception is not thrown within the try block, and RuntimeException is not explicitly caught. It is a common bug pattern to say try { ... } catch (Exception e) { something } as a shorthand for catching a number of types of exception each of whose catch blocks is identical, but this construct also accidentally catches RuntimeException as well, masking potential bugs.

A better approach is to either explicitly catch the specific exceptions that are thrown, or to explicitly catch RuntimeException exception, rethrow it, and then catch all non-Runtime Exceptions, as shown below:

try {
    ...
} catch (RuntimeException e) {
    throw e;
} catch (Exception e) {
    ... deal with all non-runtime exceptions ...
}
{
return true;
Logger.log("Port: " + port + " is not available"); //$NON-NLS-1$ //$NON-NLS-2$
return false;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Catch Specific Exceptions Instead of Generic Exception

Catching the generic Exception can mask other unintended exceptions and make debugging harder. It's better to catch the more specific IOException, which is the expected exception type for socket operations.

Apply this diff to catch IOException specifically:

-} catch (Exception e) {
+} catch (IOException e) {
    Logger.log("Port: " + port + " is not available"); //$NON-NLS-1$ //$NON-NLS-2$
    return false;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try (ServerSocket serverSocket = new ServerSocket(port))
{
return false;
serverSocket.setReuseAddress(true);
return true;
}
catch (Exception e)
{
return true;
Logger.log("Port: " + port + " is not available"); //$NON-NLS-1$ //$NON-NLS-2$
return false;
try (ServerSocket serverSocket = new ServerSocket(port))
{
serverSocket.setReuseAddress(true);
return true;
}
catch (IOException e)
{
Logger.log("Port: " + port + " is not available"); //$NON-NLS-1$ //$NON-NLS-2$
return false;

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ public static String[] getGdbServerCommandLineArray(ILaunchConfiguration configu
lst.add(executable);

int port = PortChecker
.getAvailablePort(configuration.getAttribute(ConfigurationAttributes.GDB_SERVER_GDB_PORT_NUMBER,
DefaultPreferences.GDB_SERVER_GDB_PORT_NUMBER_DEFAULT));
.getAvailablePort(DefaultPreferences.GDB_SERVER_GDB_PORT_NUMBER_DEFAULT);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Dynamic port selection improved, but Telnet port requires attention

The implementation of PortChecker.getAvailablePort with a retry limit enhances the robustness of dynamic port selection for the GDB and TCL ports, effectively addressing potential port conflicts and preventing endless retries.

However, the telnet_port still does not utilize PortChecker.getAvailablePort. To ensure consistency and prevent similar issues with the Telnet port, please apply the dynamic port selection logic with retry limits to the telnet_port as well.

  • Apply PortChecker.getAvailablePort with retry limits to the Telnet port configuration.
🔗 Analysis chain

Approve the dynamic port selection, but suggest improvements

The introduction of PortChecker.getAvailablePort() for the GDB port is a good step towards resolving port conflicts and addressing the debugging process hangs. However, there are a few points to consider:

  1. This change is only applied to the GDB port. For consistency and to fully address potential conflicts, consider applying the same logic to the Telnet and TCL ports as well.

  2. To prevent the endless retry issue reported by AndriiFilippov, consider adding a retry limit or a timeout mechanism to the port selection process.

  3. Verify if this change resolves the reported debugging hangs across different platforms (Windows, Linux, macOS) and with various ESP-IDF versions.

Here's a suggested implementation for applying the dynamic port selection to all ports:

private static int getAvailablePort(int defaultPort, int maxRetries) {
    for (int i = 0; i < maxRetries; i++) {
        int port = PortChecker.getAvailablePort(defaultPort);
        if (port != defaultPort) {
            return port;
        }
    }
    throw new RuntimeException("Unable to find an available port after " + maxRetries + " attempts");
}

// In getGdbServerCommandLineArray method:
int gdbPort = getAvailablePort(DefaultPreferences.GDB_SERVER_GDB_PORT_NUMBER_DEFAULT, 10);
lst.add("-c");
lst.add("gdb_port " + gdbPort);

int telnetPort = getAvailablePort(DefaultPreferences.GDB_SERVER_TELNET_PORT_NUMBER_DEFAULT, 10);
lst.add("-c");
lst.add("telnet_port " + telnetPort);

int tclPort = getAvailablePort(DefaultPreferences.GDB_SERVER_TCL_PORT_NUMBER_DEFAULT, 10);
lst.add("-c");
lst.add("tcl_port " + tclPort);

This implementation applies the dynamic port selection to all ports and includes a retry limit to prevent endless loops.

To ensure this change resolves the reported issues, please run the following verification steps:

Please run these tests and provide the results to ensure the changes work as expected across different environments.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the changes across different platforms and ESP-IDF versions

# Test on different platforms
platforms=("windows" "linux" "macos")
for platform in "${platforms[@]}"; do
    echo "Testing on $platform"
    # Add platform-specific test commands here
done

# Test with different ESP-IDF versions
versions=("4.4" "5.0" "5.1")
for version in "${versions[@]}"; do
    echo "Testing with ESP-IDF v$version"
    # Add version-specific test commands here
done

# Verify port selection logic
echo "Verifying port selection logic"
rg "PortChecker.getAvailablePort" -A 10

Length of output: 2411


Script:

#!/bin/bash
# Verify dynamic port selection for telnet_port

echo "Searching for PortChecker.getAvailablePort usage in telnet_port"
rg "telnet_port" -A 10 | rg "PortChecker.getAvailablePort"

# Check implementation of getAvailablePort for retry limits
echo "Checking implementation of getAvailablePort for retry limits"
rg "getAvailablePort" -A 10 bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/PortChecker.java

Length of output: 761


Script:

#!/bin/bash
# Locate the PortChecker.java file in the repository
echo "Searching for PortChecker.java in the repository"
fd "PortChecker.java" . -type f

# If the file is found, check for the implementation of getAvailablePort with retry limits
# Replace <path_to_PortChecker.java> with the actual path from the previous command's output
# Example:
# rg "def getAvailablePort" <path_to_PortChecker.java> -A 10

# Verify if PortChecker.getAvailablePort is used for telnet_port
echo "Searching for PortChecker.getAvailablePort usage in telnet_port"
rg "telnet_port" -A 10 | rg "PortChecker.getAvailablePort"

Length of output: 654


Script:

#!/bin/bash
# Corrected script to locate PortChecker.java and verify its implementation

# Locate the PortChecker.java file in the repository
echo "Searching for PortChecker.java in the repository"
fd "PortChecker.java" . --type f

# If the file is found, check for the implementation of getAvailablePort with retry limits
# Replace <path_to_PortChecker.java> with the actual path from the previous command's output
# Example:
# rg "getAvailablePort" <path_to_PortChecker.java> -A 10

# Assuming the file was found at bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/PortChecker.java
echo "Inspecting getAvailablePort method for retry limits"
rg "getAvailablePort" -A 10 bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/PortChecker.java

# Verify if PortChecker.getAvailablePort is used for telnet_port
echo "Searching for PortChecker.getAvailablePort usage in telnet_port"
rg "telnet_port" -A 10 | rg "PortChecker.getAvailablePort"

Length of output: 965


Script:

#!/bin/bash
# Corrected script to locate PortChecker.java and verify its implementation

# Locate the PortChecker.java file in the repository
echo "Searching for PortChecker.java in the repository"
fd "PortChecker.java" . --type file

# Check implementation of getAvailablePort for retry limits in the correct PortChecker.java
echo "Inspecting getAvailablePort method for retry limits"
rg "getAvailablePort" -A 10 bundles/com.espressif.idf.core/src/com/espressif/idf/core/util/PortChecker.java

# Verify if PortChecker.getAvailablePort is used for telnet_port
echo "Searching for PortChecker.getAvailablePort usage in telnet_port"
rg "telnet_port" -A 10 | rg "PortChecker.getAvailablePort"

Length of output: 1095

lst.add("-c"); //$NON-NLS-1$
lst.add("gdb_port " + port); //$NON-NLS-1$
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*******************************************************************************

Check warning on line 1 in bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/Launch.java

View workflow job for this annotation

GitHub Actions / spotbugs

THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION

Method lists Exception in its throws clause.
Raw output
Method lists Exception in its throws clause.
When declaring a method, the types of exceptions in the throws clause should be the most specific. Therefore, using Exception in the throws clause would force the caller to either use it in its own throws clause, or use it in a try-catch block (when it does not necessarily contain any meaningful information about the thrown exception).

For more information, see the SEI CERT ERR07-J rule [https://wiki.sei.cmu.edu/confluence/display/java/ERR07-J.+Do+not+throw+RuntimeException%2C+Exception%2C+or+Throwable].
* Copyright (c) 2013 Liviu Ionescu.
*
* This program and the accompanying materials
Expand Down Expand Up @@ -70,7 +70,7 @@
System.out.println("openocd.Launch.launch(" + launchConfiguration.getName() + "," + mode + ") " + this);
}

fConfig = launchConfiguration;

Check warning on line 73 in bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/Launch.java

View workflow job for this annotation

GitHub Actions / spotbugs

EI_EXPOSE_REP2

new com.espressif.idf.debug.gdbjtag.openocd.dsf.Launch(ILaunchConfiguration, String, ISourceLocator) may expose internal representation by storing an externally mutable object into Launch.fConfig
Raw output
This code stores a reference to an externally mutable object into the internal representation of the object.  If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Storing a copy of the object is better approach in many situations.
fExecutor = (DefaultDsfExecutor) getDsfExecutor();
fSession = getSession();
}
Expand Down Expand Up @@ -147,9 +147,10 @@
fDefaultPreferences.getGdbClientExecutable());
}

int availableRemotePort = PortChecker.getAvailablePort(config.getAttribute(IGDBJtagConstants.ATTR_PORT_NUMBER,
DefaultPreferences.GDB_SERVER_GDB_PORT_NUMBER_DEFAULT));
config.setAttribute(IGDBJtagConstants.ATTR_PORT_NUMBER, availableRemotePort);
if (Configuration.getDoStartGdbServer(config))
{
config.setAttribute(IGDBJtagConstants.ATTR_PORT_NUMBER, DefaultPreferences.GDB_SERVER_GDB_PORT_NUMBER_DEFAULT);
}
Comment on lines +166 to +169
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Port handling logic needs improvement

The current implementation sets a fixed default port when the GDB server starts, but according to PR feedback, this doesn't handle port conflicts effectively. When the default port (3333) is occupied, the system should retry with alternative ports.

Consider implementing a more robust port selection mechanism:

 if (Configuration.getDoStartGdbServer(config))
 {
-    config.setAttribute(IGDBJtagConstants.ATTR_PORT_NUMBER, DefaultPreferences.GDB_SERVER_GDB_PORT_NUMBER_DEFAULT);
+    int port = PortChecker.findAvailablePort(DefaultPreferences.GDB_SERVER_GDB_PORT_NUMBER_DEFAULT);
+    if (port == -1) {
+        throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
+            "Unable to find available port for GDB server"));
+    }
+    config.setAttribute(IGDBJtagConstants.ATTR_PORT_NUMBER, port);
 }

Committable suggestion skipped: line range outside the PR's diff.


config.setAttribute(DebugPlugin.ATTR_PROCESS_FACTORY_ID, CustomIdfProcessFactory.ID);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@

private Text fTargetIpAddress;
private Text fTargetPortNumber;
private Group gdbClientGroup;

protected Button fUpdateThreadlistOnSuspend;

Expand Down Expand Up @@ -470,6 +471,7 @@
public void widgetSelected(SelectionEvent e)
{
doStartGdbServerChanged();
gdbClientGroup.setEnabled(!fDoStartGdbServer.getSelection());
if (fDoStartGdbServer.getSelection())
{
fTargetIpAddress.setText(DefaultPreferences.REMOTE_IP_ADDRESS_LOCALHOST);
Expand Down Expand Up @@ -572,16 +574,16 @@
private void createGdbClientControls(Composite parent)
{

Group group = new Group(parent, SWT.NONE);
gdbClientGroup = new Group(parent, SWT.NONE);
{
GridLayout layout = new GridLayout();
group.setLayout(layout);
gdbClientGroup.setLayout(layout);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
group.setLayoutData(gd);
group.setText(Messages.getString("DebuggerTab.gdbSetupGroup_Text")); //$NON-NLS-1$
gdbClientGroup.setLayoutData(gd);
gdbClientGroup.setText(Messages.getString("DebuggerTab.gdbSetupGroup_Text")); //$NON-NLS-1$
}

Composite comp = new Composite(group, SWT.NONE);
Composite comp = new Composite(gdbClientGroup, SWT.NONE);
{
GridLayout layout = new GridLayout();
layout.numColumns = 5;
Expand Down Expand Up @@ -865,7 +867,7 @@
System.out.println("openocd.TabDebugger.initializeFrom() " + configuration.getName()); //$NON-NLS-1$
}

fConfiguration = configuration;

Check warning on line 870 in bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/TabDebugger.java

View workflow job for this annotation

GitHub Actions / spotbugs

EI_EXPOSE_REP2

com.espressif.idf.debug.gdbjtag.openocd.ui.TabDebugger.initializeFrom(ILaunchConfiguration) may expose internal representation by storing an externally mutable object into TabDebugger.fConfiguration
Raw output
This code stores a reference to an externally mutable object into the internal representation of the object.  If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Storing a copy of the object is better approach in many situations.

try
{
Expand Down Expand Up @@ -1009,8 +1011,8 @@
// Allocate server console
if (EclipseUtils.isWindows())
{
fDoGdbServerAllocateConsole.setSelection(true);
}

Check warning on line 1015 in bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/TabDebugger.java

View workflow job for this annotation

GitHub Actions / spotbugs

DB_DUPLICATE_BRANCHES

com.espressif.idf.debug.gdbjtag.openocd.ui.TabDebugger.initializeFromDefaults() uses the same code for two branches
Raw output
This method uses the same code to implement two branches of a conditional branch. Check to ensure that this isn't a coding mistake.
else
{
fDoGdbServerAllocateConsole.setSelection(DefaultPreferences.DO_GDB_SERVER_ALLOCATE_CONSOLE_DEFAULT);
Expand Down Expand Up @@ -1299,7 +1301,7 @@
{
try
{
int port = Integer.valueOf(str).intValue();

Check warning on line 1304 in bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/ui/TabDebugger.java

View workflow job for this annotation

GitHub Actions / spotbugs

DM_BOXED_PRIMITIVE_FOR_PARSING

Boxing/unboxing to parse a primitive com.espressif.idf.debug.gdbjtag.openocd.ui.TabDebugger.performApply(ILaunchConfigurationWorkingCopy)
Raw output
A boxed primitive is created from a String, just to extract the unboxed primitive value. It is more efficient to just call the static parseXXX method.
configuration.setAttribute(IGDBJtagConstants.ATTR_PORT_NUMBER, port);
}
catch (NumberFormatException e)
Expand Down
Loading