server: add 'services' command to list service information

This addes a 'services' command which outputs a TCL list
where each entry is a TCL dict containing information about
the service.

This allows obtaining the actual TCP port used when the
requested port is 0. Tested with the following script:

adapter driver dummy
gdb port 0
jtag newtap tap cpu -irlen 5
target create tap.cpu riscv -chain-position tap.cpu
init
echo [services]
shutdown

Output is:

...
Info : [tap.cpu] starting gdb server on 0
Info : Listening on port 53811 for gdb connections
{
    name gdb
    port 53811
    target tap.cpu
}

Change-Id: I29bc2d352c0058f8b6a1c4296345f9ece4fbe03f
Signed-off-by: Tim Hutt <[email protected]>
Reviewed-on: https://review.openocd.org/c/openocd/+/9535
Tested-by: jenkins
Reviewed-by: Evgeniy Naydanov <[email protected]>
Reviewed-by: Antonio Borneo <[email protected]>
This commit is contained in:
Tim Hutt
2026-07-25 10:47:12 +00:00
committed by Antonio Borneo
parent c064a1999f
commit 43441cd83b
3 changed files with 78 additions and 0 deletions
+16
View File
@@ -27,6 +27,7 @@
#include "config.h"
#endif
#include <helper/tcl-common.h>
#include <target/breakpoints.h>
#include <target/target_request.h>
#include <target/register.h>
@@ -3913,6 +3914,20 @@ static void gdb_keep_client_alive(struct connection *connection)
}
}
static COMMAND_HELPER(gdb_service_info, const struct service *service)
{
struct gdb_service *gdb_service = service->priv;
char *cmd_name = tcl_escape_alloc(CMD_CTX->interp, gdb_service->target->cmd_name);
if (!cmd_name) {
LOG_ERROR("Unable to escape Tcl string");
return ERROR_FAIL;
}
command_print(cmd, " target %s", cmd_name);
free(cmd_name);
return ERROR_OK;
}
static const struct service_driver gdb_service_driver = {
.name = "gdb",
.new_connection_during_keep_alive_handler = NULL,
@@ -3920,6 +3935,7 @@ static const struct service_driver gdb_service_driver = {
.input_handler = gdb_input,
.connection_closed_handler = gdb_connection_closed,
.keep_client_alive_handler = gdb_keep_client_alive,
.service_info_handler = gdb_service_info,
};
static int gdb_target_start(struct target *target, const char *port)
+54
View File
@@ -16,6 +16,7 @@
#endif
#include "server.h"
#include <helper/tcl-common.h>
#include <helper/time_support.h>
#include <target/target.h>
#include <target/target_request.h>
@@ -231,6 +232,7 @@ int add_service(const struct service_driver *driver, const char *port,
c->connection_closed = driver->connection_closed_handler;
c->keep_client_alive = driver->keep_client_alive_handler;
c->service_dtor = driver->service_dtor_handler;
c->service_info = driver->service_info_handler;
c->priv = priv;
c->next = NULL;
@@ -873,6 +875,51 @@ COMMAND_HANDLER(handle_bindto_command)
return ERROR_OK;
}
COMMAND_HANDLER(handle_services_command)
{
if (CMD_ARGC != 0)
return ERROR_COMMAND_SYNTAX_ERROR;
for (const struct service *s = services; s; s = s->next) {
command_print(CMD, "{");
/* Escape the name in case it contains special characters. */
char *escaped_name = tcl_escape_alloc(CMD_CTX->interp, s->name);
if (!escaped_name) {
command_print(CMD, "Unable to escape Tcl string");
return ERROR_FAIL;
}
command_print(CMD, " name %s", escaped_name);
free(escaped_name);
struct sockaddr_in addr_in;
addr_in.sin_port = 0;
socklen_t addr_in_size = sizeof(addr_in);
/* If it's a TCP connection and they specified port 0 try to get the real port. */
if (s->type == CONNECTION_TCP &&
s->portnumber == 0 &&
getsockname(s->fd, (struct sockaddr *)&addr_in, &addr_in_size) == 0) {
command_print(CMD, " port %hu", ntohs(addr_in.sin_port));
} else {
/* Need to escape port because it could be a FIFO path which is
* allowed to contain basically any character. */
char *escaped_port = tcl_escape_alloc(CMD_CTX->interp, s->port);
if (!escaped_port) {
command_print(CMD, "Unable to escape Tcl string");
return ERROR_FAIL;
}
command_print(CMD, " port %s", escaped_port);
free(escaped_port);
}
if (s->service_info)
CALL_COMMAND_HANDLER(s->service_info, s);
command_print(CMD, "}");
}
return ERROR_OK;
}
static const struct command_registration server_command_handlers[] = {
{
.name = "shutdown",
@@ -903,6 +950,13 @@ static const struct command_registration server_command_handlers[] = {
.help = "Specify address by name on which to listen for "
"incoming TCP/IP connections",
},
{
.name = "services",
.handler = &handle_services_command,
.mode = COMMAND_ANY,
.usage = "",
.help = "return information about running services"
},
COMMAND_REGISTRATION_DONE
};
+8
View File
@@ -59,6 +59,8 @@ struct service_driver {
/** callback to handle incoming data */
int (*input_handler)(struct connection *connection);
void (*service_dtor_handler)(struct service *service);
/** Callback to provide more details for the `services` command. */
COMMAND_HELPER((*service_info_handler), const struct service *service);
/** callback to tear down the connection */
int (*connection_closed_handler)(struct connection *connection);
/** called periodically to send keep-alive messages on the connection */
@@ -68,7 +70,12 @@ struct service_driver {
struct service {
char *name;
enum connection_type type;
/**
* The 'port', which can be an integer for TCP, or 'disabled',
* 'pipe' (stdin/out) or a FIFO path.
*/
char *port;
/** If port is an integer it is parsed and saved here. */
unsigned short portnumber;
int fd;
struct sockaddr_in sin;
@@ -78,6 +85,7 @@ struct service {
int (*new_connection)(struct connection *connection);
int (*input)(struct connection *connection);
void (*service_dtor)(struct service *service);
COMMAND_HELPER((*service_info), const struct service *service);
int (*connection_closed)(struct connection *connection);
void (*keep_client_alive)(struct connection *connection);
void *priv;