gdbstub: Implement catching syscalls
GDB supports stopping on syscall entry and exit using the "catch syscall" command. It relies on 3 packets, which are currently not supported by QEMU: * qSupported:QCatchSyscalls+ [1] * QCatchSyscalls: [2] * T05syscall_entry: and T05syscall_return: [3] Implement generation and handling of these packets. [1] https://sourceware.org/gdb/current/onlinedocs/gdb.html/General-Query-Packets.html#qSupported [2] https://sourceware.org/gdb/current/onlinedocs/gdb.html/General-Query-Packets.html#QCatchSyscalls [3] https://sourceware.org/gdb/current/onlinedocs/gdb.html/Stop-Reply-Packets.html Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com> Message-Id: <20240202152506.279476-5-iii@linux.ibm.com> [AJB: GString -> g_strdup_printf] Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-Id: <20240207163812.3231697-14-alex.bennee@linaro.org>
This commit is contained in:
parent
0a0d87c9b8
commit
046f143c51
@ -1617,6 +1617,7 @@ static void handle_query_supported(GArray *params, void *user_ctx)
|
|||||||
if (gdbserver_state.c_cpu->opaque) {
|
if (gdbserver_state.c_cpu->opaque) {
|
||||||
g_string_append(gdbserver_state.str_buf, ";qXfer:auxv:read+");
|
g_string_append(gdbserver_state.str_buf, ";qXfer:auxv:read+");
|
||||||
}
|
}
|
||||||
|
g_string_append(gdbserver_state.str_buf, ";QCatchSyscalls+");
|
||||||
#endif
|
#endif
|
||||||
g_string_append(gdbserver_state.str_buf, ";qXfer:exec-file:read+");
|
g_string_append(gdbserver_state.str_buf, ";qXfer:exec-file:read+");
|
||||||
#endif
|
#endif
|
||||||
@ -1810,6 +1811,14 @@ static const GdbCmdParseEntry gdb_gen_set_table[] = {
|
|||||||
.schema = "l0"
|
.schema = "l0"
|
||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(CONFIG_USER_ONLY)
|
||||||
|
{
|
||||||
|
.handler = gdb_handle_set_catch_syscalls,
|
||||||
|
.cmd = "CatchSyscalls:",
|
||||||
|
.cmd_startswith = 1,
|
||||||
|
.schema = "s0",
|
||||||
|
},
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
static void handle_gen_query(GArray *params, void *user_ctx)
|
static void handle_gen_query(GArray *params, void *user_ctx)
|
||||||
|
@ -195,6 +195,7 @@ void gdb_handle_v_file_close(GArray *params, void *user_ctx); /* user */
|
|||||||
void gdb_handle_v_file_pread(GArray *params, void *user_ctx); /* user */
|
void gdb_handle_v_file_pread(GArray *params, void *user_ctx); /* user */
|
||||||
void gdb_handle_v_file_readlink(GArray *params, void *user_ctx); /* user */
|
void gdb_handle_v_file_readlink(GArray *params, void *user_ctx); /* user */
|
||||||
void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx); /* user */
|
void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx); /* user */
|
||||||
|
void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx); /* user */
|
||||||
|
|
||||||
void gdb_handle_query_attached(GArray *params, void *user_ctx); /* both */
|
void gdb_handle_query_attached(GArray *params, void *user_ctx); /* both */
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
#include "qemu/osdep.h"
|
||||||
|
#include "qemu/bitops.h"
|
||||||
#include "qemu/cutils.h"
|
#include "qemu/cutils.h"
|
||||||
#include "qemu/sockets.h"
|
#include "qemu/sockets.h"
|
||||||
#include "exec/hwaddr.h"
|
#include "exec/hwaddr.h"
|
||||||
@ -21,11 +22,20 @@
|
|||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "internals.h"
|
#include "internals.h"
|
||||||
|
|
||||||
|
#define GDB_NR_SYSCALLS 1024
|
||||||
|
typedef unsigned long GDBSyscallsMask[BITS_TO_LONGS(GDB_NR_SYSCALLS)];
|
||||||
|
|
||||||
/* User-mode specific state */
|
/* User-mode specific state */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int fd;
|
int fd;
|
||||||
char *socket_path;
|
char *socket_path;
|
||||||
int running_state;
|
int running_state;
|
||||||
|
/*
|
||||||
|
* Store syscalls mask without memory allocation in order to avoid
|
||||||
|
* implementing synchronization.
|
||||||
|
*/
|
||||||
|
bool catch_all_syscalls;
|
||||||
|
GDBSyscallsMask catch_syscalls_mask;
|
||||||
} GDBUserState;
|
} GDBUserState;
|
||||||
|
|
||||||
static GDBUserState gdbserver_user_state;
|
static GDBUserState gdbserver_user_state;
|
||||||
@ -503,10 +513,91 @@ void gdb_syscall_handling(const char *syscall_packet)
|
|||||||
gdb_handlesig(gdbserver_state.c_cpu, 0);
|
gdb_handlesig(gdbserver_state.c_cpu, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool should_catch_syscall(int num)
|
||||||
|
{
|
||||||
|
if (gdbserver_user_state.catch_all_syscalls) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (num < 0 || num >= GDB_NR_SYSCALLS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return test_bit(num, gdbserver_user_state.catch_syscalls_mask);
|
||||||
|
}
|
||||||
|
|
||||||
void gdb_syscall_entry(CPUState *cs, int num)
|
void gdb_syscall_entry(CPUState *cs, int num)
|
||||||
{
|
{
|
||||||
|
if (should_catch_syscall(num)) {
|
||||||
|
g_autofree char *reason = g_strdup_printf("syscall_entry:%x;", num);
|
||||||
|
gdb_handlesig_reason(cs, gdb_target_sigtrap(), reason);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void gdb_syscall_return(CPUState *cs, int num)
|
void gdb_syscall_return(CPUState *cs, int num)
|
||||||
{
|
{
|
||||||
|
if (should_catch_syscall(num)) {
|
||||||
|
g_autofree char *reason = g_strdup_printf("syscall_return:%x;", num);
|
||||||
|
gdb_handlesig_reason(cs, gdb_target_sigtrap(), reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx)
|
||||||
|
{
|
||||||
|
const char *param = get_param(params, 0)->data;
|
||||||
|
GDBSyscallsMask catch_syscalls_mask;
|
||||||
|
bool catch_all_syscalls;
|
||||||
|
unsigned int num;
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
/* "0" means not catching any syscalls. */
|
||||||
|
if (strcmp(param, "0") == 0) {
|
||||||
|
gdbserver_user_state.catch_all_syscalls = false;
|
||||||
|
memset(gdbserver_user_state.catch_syscalls_mask, 0,
|
||||||
|
sizeof(gdbserver_user_state.catch_syscalls_mask));
|
||||||
|
gdb_put_packet("OK");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "1" means catching all syscalls. */
|
||||||
|
if (strcmp(param, "1") == 0) {
|
||||||
|
gdbserver_user_state.catch_all_syscalls = true;
|
||||||
|
gdb_put_packet("OK");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "1;..." means catching only the specified syscalls.
|
||||||
|
* The syscall list must not be empty.
|
||||||
|
*/
|
||||||
|
if (param[0] == '1' && param[1] == ';') {
|
||||||
|
catch_all_syscalls = false;
|
||||||
|
memset(catch_syscalls_mask, 0, sizeof(catch_syscalls_mask));
|
||||||
|
for (p = ¶m[2];; p++) {
|
||||||
|
if (qemu_strtoui(p, &p, 16, &num) || (*p && *p != ';')) {
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
if (num >= GDB_NR_SYSCALLS) {
|
||||||
|
/*
|
||||||
|
* Fall back to reporting all syscalls. Reporting extra
|
||||||
|
* syscalls is inefficient, but the spec explicitly allows it.
|
||||||
|
* Keep parsing in case there is a syntax error ahead.
|
||||||
|
*/
|
||||||
|
catch_all_syscalls = true;
|
||||||
|
} else {
|
||||||
|
set_bit(num, catch_syscalls_mask);
|
||||||
|
}
|
||||||
|
if (!*p) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gdbserver_user_state.catch_all_syscalls = catch_all_syscalls;
|
||||||
|
if (!catch_all_syscalls) {
|
||||||
|
memcpy(gdbserver_user_state.catch_syscalls_mask,
|
||||||
|
catch_syscalls_mask, sizeof(catch_syscalls_mask));
|
||||||
|
}
|
||||||
|
gdb_put_packet("OK");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err:
|
||||||
|
gdb_put_packet("E00");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user