cli frontend: fix 100% cpu when using gdb stub

the emulator thread was consuming 100% cpu even when the debugger was
active and execution paused.

a second pipe was added to gdb stub, which allows communication in
direction stub -> emulator/frontend, and also to infinitely block
in the frontend until the debugger returns control, for example
by typing "c" (continue) in gdb.

the other frontends use an inefficient method of running usleep(1000)
or similar in a loop, which will cause high cpu usage too, albeit not
a full 100% but more like 10-20%.

in order not to fill up the pipe with data for frontends that don't use
this mechanism, the functionality needs to be explicitly enabled.
(see functions added to gdbstub.h)

the functions added could in theory also be used to communicate
other data to the frontend, and optimally even replace all the locking
between the 2 sides.
This commit is contained in:
rofl0r 2021-10-24 04:10:24 +00:00
parent dbc1f06662
commit d5693c54cf
4 changed files with 93 additions and 36 deletions

View File

@ -63,6 +63,21 @@
#ifdef GDB_STUB
#include "../armcpu.h"
#include "../gdbstub.h"
class CliDriver : public BaseDriver
{
private:
gdbstub_handle_t __stubs[2];
public:
virtual void EMU_DebugIdleUpdate() {
gdbstub_wait(__stubs);
}
virtual void setStubs(gdbstub_handle_t stubs[2]) {
this->__stubs[0] = stubs[0];
this->__stubs[1] = stubs[1];
}
};
#else
class CliDriver : public BaseDriver {};
#endif
volatile bool execute = false;
@ -478,6 +493,19 @@ static void desmume_cycle(struct ctrls_event_config * cfg)
SPU_Emulate_user();
}
#ifdef GDB_STUB
static gdbstub_handle_t setup_gdb_stub(u16 port, armcpu_t *cpu, const armcpu_memory_iface *memio, const char* desc) {
gdbstub_handle_t stub = createStub_gdb(port, cpu, memio);
if ( stub == NULL) {
fprintf( stderr, "Failed to create %s gdbstub on port %d\n", desc, port);
exit( 1);
} else {
activateStub_gdb(stub);
}
return stub;
}
#endif
int main(int argc, char ** argv) {
class configured_features my_config;
struct ctrls_event_config ctrls_cfg;
@ -568,8 +596,8 @@ int main(int argc, char ** argv) {
slot2_Init();
slot2_Change((NDS_SLOT2_TYPE)slot2_device_type);
driver = new BaseDriver();
driver = new CliDriver();
#ifdef GDB_STUB
gdbstub_mutex_init();
@ -577,37 +605,22 @@ int main(int argc, char ** argv) {
* Activate the GDB stubs
* This has to come after NDS_Init() where the CPUs are set up.
*/
gdbstub_handle_t arm9_gdb_stub = NULL;
gdbstub_handle_t arm7_gdb_stub = NULL;
gdbstub_handle_t stubs[2] = {};
if ( my_config.arm9_gdb_port > 0) {
arm9_gdb_stub = createStub_gdb( my_config.arm9_gdb_port,
stubs[0] = setup_gdb_stub(my_config.arm9_gdb_port,
&NDS_ARM9,
&arm9_direct_memory_iface);
if ( arm9_gdb_stub == NULL) {
fprintf( stderr, "Failed to create ARM9 gdbstub on port %d\n",
my_config.arm9_gdb_port);
exit( 1);
}
else {
activateStub_gdb( arm9_gdb_stub);
}
&arm9_direct_memory_iface, "ARM9");
}
if ( my_config.arm7_gdb_port > 0) {
arm7_gdb_stub = createStub_gdb( my_config.arm7_gdb_port,
stubs[1] = setup_gdb_stub(my_config.arm7_gdb_port,
&NDS_ARM7,
&arm7_base_memory_iface);
if ( arm7_gdb_stub == NULL) {
fprintf( stderr, "Failed to create ARM7 gdbstub on port %d\n",
my_config.arm7_gdb_port);
exit( 1);
}
else {
activateStub_gdb( arm7_gdb_stub);
}
&arm7_base_memory_iface, "ARM7");
}
((CliDriver*)driver)->setStubs(stubs);
gdbstub_wait_set_enabled(stubs[0], 1);
gdbstub_wait_set_enabled(stubs[1], 1);
#endif
if ( !my_config.disable_sound) {
@ -795,15 +808,12 @@ int main(int argc, char ** argv) {
uninit_joy();
#ifdef GDB_STUB
destroyStub_gdb( arm9_gdb_stub);
arm9_gdb_stub = NULL;
destroyStub_gdb( arm7_gdb_stub);
arm7_gdb_stub = NULL;
destroyStub_gdb( stubs[0]);
destroyStub_gdb( stubs[1]);
gdbstub_mutex_destroy();
#endif
SDL_Quit();
NDS_DeInit();

View File

@ -45,6 +45,19 @@ destroyStub_gdb( gdbstub_handle_t stub);
void
activateStub_gdb( gdbstub_handle_t stub);
/* wait until either of 2 gdb stubs gives control back to the emulator.
pass a stubs[2], one of them may be NULL.
return value: response from stub | (stub number<<31),
i.e. if stub 1 responded, the high bit is set (and so the result negative).
the primary usecase for this is to do a blocking wait until the stub returns
control to the emulator, in order to not waste cpu cycles.
returns 0 on failure or if no response was available. */
int gdbstub_wait( gdbstub_handle_t *stubs);
/* enable or disable use of the pipe for gdbstub_wait() */
void gdbstub_wait_set_enabled(gdbstub_handle_t stub, int on);
/*
* An implementation of the following functions is required
* for the GDB stub to function.

View File

@ -304,9 +304,35 @@ indicateCPUStop_gdb( struct gdb_stub_state *stub) {
SEND( stub->ctl_pipe[1], &command, 1);
}
int gdbstub_wait(gdbstub_handle_t *stubs) {
struct gdb_stub_state* g[2];
g[0] = (struct gdb_stub_state *) stubs[0];
g[1] = (struct gdb_stub_state *) stubs[1];
fd_set set;
unsigned i;
FD_ZERO(&set);
for (i = 0; i < 2; ++i)
if(g[i]) FD_SET( g[i]->info_pipe[0], &set);
int res = select( FD_SETSIZE, &set, NULL, NULL, NULL);
if (res <= 0) return 0;
for (i = 0; i < 2; ++i)
if ( g[i] && FD_ISSET( g[i]->info_pipe[0], &set)) {
RECV( g[i]->info_pipe[0], &res, 4);
return res | (i << 31);
}
return 0;
}
void gdbstub_wait_set_enabled(gdbstub_handle_t stub, int on) {
struct gdb_stub_state* g = (struct gdb_stub_state *) stub;
if(g) g->info_pipe_enabled = on;
}
static void infopipe_send(struct gdb_stub_state* g, int status) {
int resp = status;
if (g->info_pipe_enabled)
SEND(g->info_pipe[1], &resp, 4);
}
/*
*
*
@ -701,6 +727,7 @@ processPacket_gdb( SOCKET_TYPE sock, const uint8_t *packet,
/* remove the cpu stall */
stub->cpu_ctrl->unstall( stub->cpu_ctrl->data);
NDS_debug_continue();
infopipe_send(stub, 1);
break;
case 's': {
@ -725,6 +752,7 @@ processPacket_gdb( SOCKET_TYPE sock, const uint8_t *packet,
stub->cpu_ctrl->unstall( stub->cpu_ctrl->data);
//NDS_debug_step();
NDS_debug_continue();
infopipe_send(stub, 2);
break;
}
@ -1537,7 +1565,7 @@ createStub_gdb( uint16_t port,
stub->access_breakpoints = NULL;
if ( INIT_SOCKETS() != 0) return NULL;
if ( (res = INIT_PIPE(stub->ctl_pipe)) == 0) {
if ( (res = INIT_PIPE(stub->ctl_pipe)) == 0 && INIT_PIPE(stub->info_pipe) == 0) {
stub->active = 1;
stub->emu_stub_state = gdb_stub_state::RUNNING_EMU_GDB_STATE;
stub->ctl_stub_state = gdb_stub_state::STOPPED_GDB_STATE;

View File

@ -171,8 +171,14 @@ struct gdb_stub_state {
/** the free breakpoint descriptor list */
struct breakpoint_gdb *free_breakpoints;
/** the control pipe (or socket) to the gdb stub */
/** the control pipe (or socket) to the gdb stub. this allows to send commands to the stub. */
SOCKET_TYPE ctl_pipe[2];
/** this pipe allows the stub to communicate to the controlling program. */
SOCKET_TYPE info_pipe[2];
/** whether above pipe is enabled */
int info_pipe_enabled;
};