This is the mail archive of the
gdb@sourceware.org
mailing list for the GDB project.
Design of common agent library
- From: Yao Qi <yao at codesourcery dot com>
- To: gdb at sourceware dot org
- Date: Mon, 05 Dec 2011 16:40:47 +0800
- Subject: Design of common agent library
Hi,
This is a follow-up work to Stan's description on common agent here
Toward multicore GDB - Target agent
http://sourceware.org/ml/gdb/2011-11/msg00014.html
This design is going to describe the components of "common agent", and
their interactions. We can start from 2.7 - 2.10, and target platforms
are both 32-bit and 64-bit x86-linux. Comments, thoughts and questions
are welcome.
I didn't name this agent, so I still use term "common agent" in this
design. Personally, I name it as "next-generation debugging agent,
NGDA", and your thoughts on its name is welcome as well.
Table of Contents
=================
1 Overview
2 modules of common agent library
2.1 configuration
2.2 signal and signal handler
2.2.1 signal translation
2.2.2 signal handlers
2.3 debug thread
2.4 thread
2.5 itset
2.6 point
2.7 tracepoint runtime
2.7.1 trace frame related operations
2.7.2 trace buffer management
2.7.3 while stepping state
2.7.4 data collection
2.7.5 control
2.7.6 maintenance
2.7.7 misc
2.8 byte code compiler
2.9 backend
2.9.1 byte code compiler backend
2.9.2 fast tracepoint backend
2.9.3 signal context backend and trap point backend
2.9.4 misc
2.10 inter-operation with gdb/gdbserver
2.10.1 trace control and buffer management
2.10.2 static tracepoint
3 inter-operation with in-process debugging
1 Overview
~~~~~~~~~~~
The overview of common agent library is described [here] by Stan. It is
quite general, and here are some of my thoughts,
- common agent library should work with gdb and gdbserver,
- common agent library lays a foundation for in-process debugging and
out-of-process debugging,
- An agent byte code interpreter,
- Evaluation of breakpoint or tracepoint's condition,
- Matching thread or core specific breakpoint or tracepoint,
- common agent library should accept connections from multiple types of
clients,
[here]: http://sourceware.org/ml/gdb/2011-11/msg00014.html
For the design, it is made as much as it can be, taking different cases
into account, and leaving enough room for the work in the future.
2 modules of common agent library
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This chapter can be treated as the main body of API design. Since
common agent library provide basic stuffs, so its functionalities should
be partitioned into different modules, so that different tools can use
just the parts they need. We can divide agent into several modules as
below,
2.1 configuration
==================
Common agent library can work in multiple configurations with different
capabilities. In each session, only one configuration is set in common
agent library.
Different configurations may _request_ different capabilities of agent.
For example, one client needs to access inferior registers, while the
other client doesn't.
struct agent_config
{
/* Each slot presents one capability of agent. */
int capas[AGENT_CAPA_LAST];
/* Initialization routine. */
void (* init) (void);
};
Set agent's configuration,
/* Set agent's configuration via CONFIG. */
void
agent_set_config (struct agent_config *config)
For example, when common agent library is used with gdb/gdbserver, it
should be configured as not to handle signal, however, when common agent
library is used in in-proc-debugging, common agent library should be
configured to handle signal.
2.2 signal and signal handler
==============================
In agent, we can process signals by dedicated signal handlers, so
- breakpoint and slow (or trap) tracepoint can be supported, which
replies on handling /SIGTRAP/ properly,
- thread control and management relies on signals as well,
- accessing inferior register contents is done by accessing registers
on stack in signal handler.
First of all, here are two functions to translate native signals back
and forth to signals that agent knows,
2.2.1 signal translation
-------------------------
/* Translate our native signal number into one of the ones that agent
knows about. */
gdb_signal_t
translate_from_native_signal (int sig)
/* Translate a agent signal SIG into its native counterpart. */
int
translate_to_native_signal (gdb_signal_t sig)
2.2.2 signal handlers
----------------------
Dedicated signal handlers are registered to handle signals.
/* This is the main signal handler for the agent. It has two main
uses; first, to implement thread stop/resume behavior, and second,
to catch program signals like SIGTRAP so they can be reported to
GDB. Stopped threads will sit in here indefinitely, only
continuing when the dedicated resume signal is received.
SIG is the signal being received. */
void
agent_signal_handler (int sig, siginfo_t *info, void *ptr)
/* The resumption handler doesn't have anything to do; the signal is
just a way to get a suspended thread running again. SIG is the signal
being received. */
void
agent_resumption_handler (int sig, siginfo_t *info, void *ptr)
/* This function rejiggers the application's signal handling so that
the debug thread can get control when signals are raised. It is
only needed if the debugger wants to use traps or interrupt the
application. */
void
agent_init_signal_handlers ()
2.3 debug thread
=================
If signals are handled in agent, a dedicated thread is created, as a
server, to process or consume the *stop events* sent from or produced by
signal handler. We call this thread ~debug thread~ . Here are the
producer and consumer of stop event,
/* Report that TINFO stopped. Used by the the inferior thread's
signal handlers. */
void
stop_event_enqueue (gdb_stub_thread_info_t *tinfo)
/* Pop a stop event from the queue. Used by the debug thread. */
gdb_stub_thread_info_t *
stop_event_dequeue (void)
and when signal handler pushes events into pipe, and notify ~debug
thread~ via
/* Put something in the pipe, so the debug thread wakes up. */
void
debug_thread_notify (void)
and then ~debug thread~ is waken up, picks up event from queue and
process it.
One major task of ~debug thread~ is to control (stop or resume) threads
managed by agent.
/* Stop all threads that are currently being managed by the agent. */
void
agent_stop_all (void)
/* Unsuspend any threads that were stopped by the agent. */
void
agent_resume_all (void)
/* Resume TINFO from the current PC, taking into account a step
request from GDB, the need to step over any tracepoint, or to do
'while-stepping' processing. */
void
agent_proceed (gdb_stub_thread_info_t *tinfo)
2.4 thread
===========
In agent, we have to store and query information for a given thread, and
these information are stored in ~agent_thread_info_t~
/* Have pthread_t PTH_ID added to the list of threads being
debugged. Return the info for new thread. */
agent_thread_info_t *
agent_thread_info_add (pthread_t pth_id)
/* Given a thread id, return the thread info for it. */
agent_thread_info_t *
agent_thread_info_find (gdb_thread_t t);
/* Given a Posix thread id PTH_ID, return the stub's thread info for
it. */
agent_thread_info_t *
agent_thread_info_find_from_pthread (pthread_t pth_id)
/* Remove a thread info TINFO and associated data structures. */
void
agent_thread_info_remove (agent_thread_info_t *tinfo)
2.5 itset
==========
~itset~ means _inferior thread set_ , which presets a set of process
(inferior), threads and cores. Some times, a specific ~itset~ is
assigned to a tracepoint or breakpoint, so tracepoint or breakpoint is
hit conditionally if current process, thread and core of matches with
~itset~ or ~ptc set~ in breakpoint or tracepoint.
/* Returns true if ITSET contains THREAD. */
int
itset_contains_thread (struct itset *itset,
struct agent_thread_info_t *thread)
2.6 point
==========
Both tracepoint and breakpoint have some in common, and we move them
into a struct named ~base_point_t~ , which can be regarded as a base
class of tracepoint and breakpoint.
typedef struct base_point_t
{
/* Pointer to next in linked-list. */
struct base_point_t *next;
/* The point type, such as breakpoint, fast tracepoint, tracepoint,
and etc. */
gdb_point_t type;
/* The breakpoint address. */
gdb_addr_t addr;
/* Pointer to the agent expression that is the point's or
breakpoint's conditional, or NULL if the tracepoint is
unconditional. */
agent_expr_t *cond;
/* Compile condition. */
void (*compile_condition) (struct base_point_t *point, void *data);
/* Test condition of point is true. */
int (*condition_is_true) (struct base_point_t *point,
gdb_stub_thread_info_t *tinfo, void *data);
/* Pointer to the itset of this point, and it is used for thread/core
specific breakpoint or tracepoint. */
struct itset *itset;
} base_point_t;
- Evaluate condition for point, see ~condition_is_true~ and
~compile_condition~ in ~base_point_t~ tracepoint's or breakpoint's
condition can be evaluated in target side,
- Check itset, and match with current process, thread and core.
* point insertion to and removal from inferior
- breakpoint and tracepoint insertion and removal
- jump pad installation for fast tracepoint
/* Add a breakpoint or tracepoint. TYPE is the type of breakpoint/
watchpoint/tracepoint to remove. ADDR is the address of the point.
LENGTH is the length in bytes to write. Return 0 for sucess
otherwise
return non-zero. */
int
agent_addpoint (gdb_point_t type, gdb_addr_t addr, gdb_size_t length,
uint64_t *handle_p)
/* Remove a breakpoint or tracepoint. TYPE is the type of breakpoint/
watchpoint/tracepoint to remove. ADDR is the address of the point.
LENGTH is the length in bytes to write. */
int
agent_rmpoint (gdb_point_t type, gdb_addr_t addr, gdb_size_t length,
uint64_t handle)
2.7 tracepoint runtime
=======================
One task of agent is to manage tracepoint related contents correctly in
runtime,
2.7.1 trace frame related operations
-------------------------------------
/* Add a raw traceframe for the given tracepoint TPOINT, and return
this raw
traceframe. */
static traceframe_t* traceframe_add (tracepoint_t *tpoint);
/* Add a block of desired block size AMT to the traceframe TFRAME
currently
being worked on. */
unsigned char * traceframe_add_block (traceframe_t *tframe, int amt);
/* Given a traceframe number NUM, find the NUMth traceframe in the
buffer. */
traceframe_t* traceframe_find (int num);
/* Look for the block of type TYPE_WANTED in the trameframe starting
at DATABASE of DATASIZE bytes long. TFNUM is the traceframe
number. */
static unsigned char *
traceframe_find_block_type (tracerun_t *trun,
unsigned char *database, unsigned int
datasize,
int tfnum, char type_wanted);
/* Get a block of registers from the given traceframe TFRAME. Return
the pointer to register block. */
static unsigned char *
traceframe_find_regblock (tracerun_t *trun,
traceframe_t *tframe, int tfnum);
/* Get the value of the PC from the given traceframe TFRAME. */
static gdb_addr_t
traceframe_get_pc (traceframe_t *tframe);
/* Get the address (tracepoint or collected PC) of a traceframe
TFRAME. */
gdb_addr_t traceframe_get_address (traceframe_t *tframe);
2.7.2 trace buffer management
------------------------------
static void
agent_trace_buffer_init (char *buf, int bufsize)
/* Empty out an already-existing trace buffer. */
void
agent_trace_buffer_clear ()
/* Return the total free space. This is not necessarily the largest
block we can allocate, because of the two-part case. */
int
agent_trace_buffer_free_space (tracerun_t *trun)
2.7.3 while stepping state
---------------------------
/* Call this when thread TINFO has hit the tracepoint defined by
TP_NUMBER and TP_ADDRESS, and that tracepoint has a while-stepping
action. */
static void
while_stepping_state_add (gdb_stub_thread_info_t *tinfo,
int tp_number, gdb_addr_t tp_address)
/* Release the while-stepping collecting state WSTEP. */
static void
while_stepping_state_release (tracepoint_wstep_state_t *wstep)
/* Release all while-stepping collecting states associated currently
associated with thread TINFO. */
void
while_stepping_state_release_list (gdb_stub_thread_info_t *tinfo)
2.7.4 data collection
----------------------
/* This routine is designed to be called from the jump pads of fast
tracepoint TPOINT and RAW_REGS is a pointer to the saved register
block. */
void
gdb_agent_gdb_collect (tracepoint_t *tpoint, unsigned char *raw_regs);
/* Do tracepoint data collection at a step in the while-stepping loop. */
static void
collect_data_at_step (gdb_stub_thread_info_t *tinfo,
tracepoint_t *tpoint, int current_step);
/* Create a trace frame for the hit of the given tracepoint TPOINT in the
given thread TINFO. */
static void
tracepoint_collect_data (gdb_stub_thread_info_t *tinfo,
tracepoint_t *tpoint,
unsigned char *raw_regs)
/* If TINFO was handling a 'while-stepping' action, the step has
finished, so collect any step data needed, and check if any more
steps are required. Return true if the thread was indeed
collecting tracepoint data, false otherwise. */
int
tracepoint_finished_step (gdb_stub_thread_info_t *tinfo);
There are multiple types of actions, and we define base action, and concrete
actions inherit this.
typedef struct tracepoint_action_t {
char type;
/* Run action SELF. */
void (*run) (struct tracepoint_action_t *self,
gdb_stub_thread_info_t *tinfo,
struct tracepoint_t *tpoint, struct traceframe_t *tframe);
} tracepoint_action_t;
Different concrete types of action implement different ~run~ function.
2.7.5 control
--------------
/* Stop the tracing run. DISCONN is 1 if stopping due to disconnection,
0 if stopping due to tstop. */
void
gdb_stop_tracing (int disconn)
2.7.6 maintainance
-------------------
/* Restore the program to its pre-tracing state. */
void
tracepoints_clear_installed ();
2.7.7 misc
-----------
/* Return true if TINFO just hit a tracepoint. Collect data if so. */
int
tracepoint_was_hit (gdb_stub_thread_info_t *tinfo, int
gdb_stub_nonstop_mode);
2.8 byte code compiler
=======================
Agent can compile byte code to native code or interpret/evaluate byte code.
/* Given an agent expression AEXPR, turn it into native code. */
enum eval_result_type
agent_expr_compile_bytecodes (agent_expr_t *aexpr,
int64_t (*get_tsv_value_pself) (int num),
void (*set_tsv_value) (int num, int64_t
val))
/* translate string to agent expression back and forth. */
agent_expr_t *
agent_expr_parse (const char **actparm)
char *
agent_expr_unparse (agent_expr_t *aexpr)
/* The agent expression evaluator, as specified by the GDB docs. Evaluate
agent expression AEXPR for thread TINFO. RAW_REGS points to block of
registers. TFRAME is a traceframe to get any trace data. Evaluation
result value is put in RSLT. It returns 0 if everything went OK, and
a nonzero error code otherwise. */
enum eval_result_type
agent_expr_eval (gdb_stub_thread_info_t *tinfo,
unsigned char *raw_regs,
traceframe_t *tframe,
agent_expr_t *aexpr,
uint64_t *rslt)
2.9 backend
============
Generally speaking, backend can be regarded as a combination of ~OS~ and
~target~ processor. ~backend~ is composed by the following parts,
- byte code compiler backend
- fast tracepoint backend
- trap point backend
- signal context backend,
- misc
2.9.1 byte code compiler backend
---------------------------------
It is to emit native instructions for each byte code instruction.
struct bytecode_compiler_emit_backend
{
void (*emit_prologue) (void);
void (*emit_epilogue) (void);
void (*emit_add) (void);
void (*emit_sub) (void);
void (*emit_mul) (void);
void (*emit_lsh) (void);
void (*emit_rsh_signed) (void);
void (*emit_rsh_unsigned) (void);
void (*emit_ext) (int arg);
void (*emit_log_not) (void);
void (*emit_bit_and) (void);
void (*emit_bit_or) (void);
void (*emit_bit_xor) (void);
void (*emit_bit_not) (void);
void (*emit_equal) (void);
void (*emit_less_signed) (void);
void (*emit_less_unsigned) (void);
void (*emit_ref) (int size,
int (*agent_mem_read_to) (unsigned char *,
gdb_addr_t from,
gdb_size_t));
void (*emit_if_goto) (int *offset_p, int *size_p);
void (*emit_goto) (int *offset_p, int *size_p);
void (*write_goto_address) (unsigned char *from, unsigned char *to,
int size);
void (*emit_const) (int64_t num);
void (*emit_call) (void *fn);
void (*emit_reg) (int reg);
void (*emit_pop) (void);
void (*emit_stack_flush) (void);
void (*emit_zero_ext) (int arg);
void (*emit_swap) (void);
void (*emit_stack_adjust) (int n);
void (*emit_int_call_1) (int64_t (*fn) (int), int arg1);
void (*emit_void_call_2) (void (*fn) (int, int64_t), int arg1);
void (*emit_eq_goto) (int *offset_p, int *size_p);
void (*emit_ne_goto) (int *offset_p, int *size_p);
void (*emit_lt_goto) (int *offset_p, int *size_p);
void (*emit_le_goto) (int *offset_p, int *size_p);
void (*emit_gt_goto) (int *offset_p, int *size_p);
void (*emit_ge_goto) (int *offset_p, int *size_p);
};
2.9.2 fast tracepoint backend
------------------------------
struct fast_tracepoint_backend
{
/* Determine needs trampoline buffer or not. */
int need_tramploline_buffer_p;
/* Move a block of saved registers REGS (typically located on the stack)
into a thread TINFO's register block. */
void (*get_fast_tracepoint_regs) (gdb_stub_thread_info_t *tinfo,
unsigned char *regs);
};
2.9.3 signal context backend and trap point backend
----------------------------------------------------
So far, breakpoint and slow tracepoint can be named as ~trap point~ ,
because they reply on _SIGTRAP_ . Agent should insert special
instructions into inferior to trigger _SIGTRAP_ .
struct trap_point_backend
{
/* Length in bytes of breakpoint instruction. */
int breakpoint_size;
int decr_pc_after_break;
/* Breakpoint instruction. */
unsigned char *breakpoint_insn;
};
struct signal_context_backend
{
/* Extract register contents from CONTEXT and store them into TINFO. */
void (*get_signal_context_regs) (gdb_stub_thread_info_t *tinfo,
ucontext_t *context);
/* Store register contentes from TINFO to CONTEXT. */
void (*put_signal_context_regs) (gdb_stub_thread_info_t *tinfo,
ucontext_t *context);
uint64_t (*get_reg) (gdb_stub_thread_info_t *tinfo, int regnum);
void (*set_reg) (gdb_stub_thread_info_t *tinfo, int regnum, uint64_t
val);
/* Get the register number of PC. */
int (*get_pc_reg_num) ();
struct trap_point_backend breakpoint;
}
2.9.4 misc
-----------
/* Maximum length of instruction. */
int max_insn_length;
/* Get the core number of thread TINFO is running on. */
int (*core_of_thread) (gdb_stub_thread_info_t *);
2.10 inter-operation with gdb/gdbserver
========================================
The common agent library design above doesn't take interaction with
gdb/gdbserver into account. The interaction is mainly done by some
control variables and sockets, which is quite independent of gdb or
gdbserver. Common agent library doesn't bind itself to gdb or
gdbserver. Any tools can talk with common agent library by means of
accessing these control variables. This part should be put into a
separate module.
2.10.1 trace control and buffer management
-------------------------------------------
The tasks of this module are
+ inter-operate with gdbserver on some shared control variables and
functions,
+ gdb_agent_trace_buffer_lo, gdb_agent_trace_buffer_hi,
gdb_agent_trace_buffer_is_full,
+ gdb_agent_expr_eval_result
+ gdb_agent_error_tracepoint, gdb_agent_tracepoints,
+ gdb_agent_tracing,
+ gdb_agent_trace_state_variables
+ gdb_agent_traceframes_created
+ gdb_agent_gdb_jump_pad_buffer, gdb_agent_gdb_jump_pad_buffer_end,
+ follow the mechanism to control trace buffer between libinproctrace.so
and gdbserver
+ gdb_agent_trace_buffer_ctrl_curr, gdb_agent_trace_buffer_ctrl
+ gdb_agent_traceframe_write_count, gdb_agent_traceframe_read_count,
+ gdb_agent_stop_tracing, gdb_agent_flush_trace_buffer,
gdb_agent_about_to_request_buffer_space,
+ keep the same struct layout of tracepoint for both gdbserver side and
agent site.
2.10.2 static tracepoint
-------------------------
This module adds static tracepoint support for LTTng/UST based static
tracepoints. This module includes
+ Synchronization with ~gdbserver~ . The command exchange and
synchronization between gdbserver and common agent is done by a socket
communication and a piece of shared buffer.
+ Translate commands from gdbserver, and invoke corresponding
~libust.so~ APIs.
Shared control variables are
+ gdb_agent_ust_loaded,
+ gdb_agent_helper_thread_id,
+ gdb_agent_cmd_buf,
3 inter-operation with in-process debugging
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Think a little bit further, more operations can be done in the
inferior's process, such as breakpoint condition evaluation, and
debugging overhead can be reduced further. It is a long-term goal, but
we can think of it on the module granularity.
+ a module to handle RSP, and translate packets into commands,
+ a module to manage in-process trace buffer, which is more flexible
than out-of-process trace buffer management,
--
Yao (éå)