mirror of
https://github.com/Zenithsiz/ftmemsim-valgrind.git
synced 2026-02-10 21:47:06 +00:00
Note that catch syscall implies to use the soon to be released gdb 7.11 version. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@15770
1566 lines
51 KiB
C
1566 lines
51 KiB
C
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- Handle remote gdb protocol. m_gdbserver.c ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|
|
/*
|
|
This file is part of Valgrind, a dynamic binary instrumentation
|
|
framework.
|
|
|
|
Copyright (C) 2011-2015 Philippe Waroquiers
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
02111-1307, USA.
|
|
|
|
The GNU General Public License is contained in the file COPYING.
|
|
*/
|
|
|
|
#include "pub_core_basics.h"
|
|
#include "pub_core_vki.h"
|
|
#include "pub_core_debuglog.h"
|
|
#include "pub_core_libcproc.h"
|
|
#include "pub_core_libcprint.h"
|
|
#include "pub_core_mallocfree.h"
|
|
#include "pub_core_threadstate.h"
|
|
#include "pub_core_gdbserver.h"
|
|
#include "pub_core_options.h"
|
|
#include "pub_core_transtab.h"
|
|
#include "pub_core_hashtable.h"
|
|
#include "pub_core_xarray.h"
|
|
#include "pub_core_libcassert.h"
|
|
#include "pub_core_libcbase.h"
|
|
#include "pub_core_libcsignal.h"
|
|
#include "pub_core_signals.h"
|
|
#include "pub_core_machine.h" // VG_(fnptr_to_fnentry)
|
|
#include "pub_core_debuginfo.h"
|
|
#include "pub_core_scheduler.h"
|
|
#include "pub_core_syswrap.h"
|
|
|
|
#include "server.h"
|
|
|
|
Int VG_(dyn_vgdb_error);
|
|
|
|
/* forward declarations */
|
|
VG_REGPARM(1)
|
|
void VG_(helperc_CallDebugger) ( HWord iaddr );
|
|
VG_REGPARM(1)
|
|
void VG_(helperc_invalidate_if_not_gdbserved) ( Addr addr );
|
|
static void invalidate_current_ip (ThreadId tid, const HChar *who);
|
|
|
|
/* reasons of call to call_gdbserver. */
|
|
typedef
|
|
enum {
|
|
init_reason, // initialises gdbserver resources
|
|
vgdb_reason, // gdbserver invocation by vgdb doing ptrace
|
|
core_reason, // gdbserver invocation by core (e.g. error encountered)
|
|
break_reason, // break encountered
|
|
watch_reason, // watchpoint detected by tool
|
|
signal_reason, // signal encountered
|
|
exit_reason} // process terminated
|
|
CallReason;
|
|
|
|
static const HChar* ppCallReason(CallReason reason)
|
|
{
|
|
switch (reason) {
|
|
case init_reason: return "init_reason";
|
|
case vgdb_reason: return "vgdb_reason";
|
|
case core_reason: return "core_reason";
|
|
case break_reason: return "break_reason";
|
|
case watch_reason: return "watch_reason";
|
|
case signal_reason: return "signal_reason";
|
|
case exit_reason: return "exit_reason";
|
|
default: vg_assert (0);
|
|
}
|
|
}
|
|
|
|
/* An instruction instrumented for gdbserver looks like this:
|
|
1. Ist_Mark (0x1234)
|
|
2. Put (IP, 0x1234)
|
|
3. helperc_CallDebugger (0x1234)
|
|
This will give control to gdb if there is a break at 0x1234
|
|
or if we are single stepping
|
|
4. ... here the real IR for the instruction at 0x1234
|
|
|
|
When there is a break at 0x1234:
|
|
if user does "continue" or "step" or similar,
|
|
then - the call to debugger returns
|
|
- valgrind executes at 3. the real IR(s) for 0x1234
|
|
|
|
if as part of helperc_CallDebugger, the user calls
|
|
some code in gdb e.g print hello_world()
|
|
then - gdb prepares a dummy stack frame with a specific
|
|
return address (typically it uses _start) and
|
|
inserts a break at this address
|
|
- gdb then puts in EIP the address of hello_world()
|
|
- gdb then continues (so the helperc_CallDebugger
|
|
returns)
|
|
- call_gdbserver() function will then return the
|
|
control to the scheduler (using VG_MINIMAL_LONGJMP)
|
|
to allow the block of the new EIP
|
|
to be executed.
|
|
- hello_world code is executed.
|
|
- when hello_world() returns, it returns to
|
|
_start and encounters the break at _start.
|
|
- gdb then removes this break, put 0x1234 in EIP
|
|
and does a "step". This causes to jump from
|
|
_start to 0x1234, where the call to
|
|
helperc_CallDebugger is redone.
|
|
- This is all ok, the user can then give new gdb
|
|
commands.
|
|
|
|
However, when continue is given, address 0x1234 is to
|
|
be executed: gdb gives a single step, which must not
|
|
report again the break at 0x1234. To avoid a 2nd report
|
|
of the same break, the below tells that the next
|
|
helperc_CallDebugger call must ignore a break/stop at
|
|
this address.
|
|
*/
|
|
static Addr ignore_this_break_once = 0;
|
|
|
|
|
|
static void call_gdbserver ( ThreadId tid , CallReason reason);
|
|
|
|
/* Describes the address addr (for debugging/printing purposes).
|
|
Last two results are kept. A third call will replace the
|
|
oldest result. */
|
|
static HChar* sym (Addr addr, Bool is_code)
|
|
{
|
|
static HChar *buf[2];
|
|
static int w = 0;
|
|
PtrdiffT offset;
|
|
if (w == 2) w = 0;
|
|
|
|
if (is_code) {
|
|
const HChar *name;
|
|
name = VG_(describe_IP) (addr, NULL);
|
|
if (buf[w]) VG_(free)(buf[w]);
|
|
buf[w] = VG_(strdup)("gdbserver sym", name);
|
|
} else {
|
|
const HChar *name;
|
|
VG_(get_datasym_and_offset) (addr, &name, &offset);
|
|
if (buf[w]) VG_(free)(buf[w]);
|
|
buf[w] = VG_(strdup)("gdbserver sym", name);
|
|
}
|
|
return buf[w++];
|
|
}
|
|
|
|
/* Each time gdbserver is called, gdbserver_called is incremented
|
|
gdbserver_exited is incremented when gdbserver is asked to exit */
|
|
static int gdbserver_called = 0;
|
|
static int gdbserver_exited = 0;
|
|
|
|
/* alloc and free functions for xarray and similar. */
|
|
static void* gs_alloc (const HChar* cc, SizeT sz)
|
|
{
|
|
return VG_(malloc)(cc, sz);
|
|
}
|
|
static void gs_free (void* ptr)
|
|
{
|
|
VG_(free)(ptr);
|
|
}
|
|
|
|
typedef
|
|
enum {
|
|
GS_break,
|
|
GS_jump
|
|
}
|
|
GS_Kind;
|
|
|
|
typedef
|
|
struct _GS_Address {
|
|
struct _GS_Address* next;
|
|
Addr addr;
|
|
GS_Kind kind;
|
|
}
|
|
GS_Address;
|
|
|
|
/* gs_addresses contains a list of all addresses that have been invalidated
|
|
because they have been (or must be) instrumented for gdbserver.
|
|
An entry is added in this table when there is a break at this
|
|
address (kind == GS_break) or if this address is the jump target of an
|
|
exit of a block that has been instrumented for gdbserver while
|
|
single stepping (kind == GS_jump).
|
|
When gdbserver is not single stepping anymore, all GS_jump entries
|
|
are removed, their translations are invalidated.
|
|
|
|
Note for ARM: addr in GS_Address is the value without the thumb bit set.
|
|
*/
|
|
static VgHashTable *gs_addresses = NULL;
|
|
|
|
// Transform addr in the form stored in the list of addresses.
|
|
// For the ARM architecture, we store it with the thumb bit set to 0.
|
|
static Addr HT_addr ( Addr addr )
|
|
{
|
|
#if defined(VGA_arm)
|
|
return addr & ~(Addr)1;
|
|
#else
|
|
return addr;
|
|
#endif
|
|
}
|
|
|
|
static void add_gs_address (Addr addr, GS_Kind kind, const HChar* from)
|
|
{
|
|
GS_Address *p;
|
|
|
|
p = VG_(malloc)(from, sizeof(GS_Address));
|
|
p->addr = HT_addr (addr);
|
|
p->kind = kind;
|
|
VG_(HT_add_node)(gs_addresses, p);
|
|
/* It should be sufficient to discard a range of 1.
|
|
We use 2 to ensure the below is not sensitive to the presence
|
|
of thumb bit in the range of addresses to discard.
|
|
No need to discard translations for Vg_VgdbFull as all
|
|
instructions are in any case vgdb-instrumented. */
|
|
if (VG_(clo_vgdb) != Vg_VgdbFull)
|
|
VG_(discard_translations) (addr, 2, from);
|
|
}
|
|
|
|
static void remove_gs_address (GS_Address* g, const HChar* from)
|
|
{
|
|
VG_(HT_remove) (gs_addresses, g->addr);
|
|
// See add_gs_address for the explanation for condition and the range 2 below.
|
|
if (VG_(clo_vgdb) != Vg_VgdbFull)
|
|
VG_(discard_translations) (g->addr, 2, from);
|
|
VG_(free) (g);
|
|
}
|
|
|
|
const HChar* VG_(ppPointKind) (PointKind kind)
|
|
{
|
|
switch(kind) {
|
|
case software_breakpoint: return "software_breakpoint";
|
|
case hardware_breakpoint: return "hardware_breakpoint";
|
|
case write_watchpoint: return "write_watchpoint";
|
|
case read_watchpoint: return "read_watchpoint";
|
|
case access_watchpoint: return "access_watchpoint";
|
|
default: return "???wrong PointKind";
|
|
}
|
|
}
|
|
|
|
typedef
|
|
struct _GS_Watch {
|
|
Addr addr;
|
|
SizeT len;
|
|
PointKind kind;
|
|
}
|
|
GS_Watch;
|
|
|
|
/* gs_watches contains a list of all addresses+len+kind that are being
|
|
watched. */
|
|
static XArray* gs_watches = NULL;
|
|
|
|
static inline GS_Watch* index_gs_watches(Word i)
|
|
{
|
|
return *(GS_Watch **) VG_(indexXA) (gs_watches, i);
|
|
}
|
|
|
|
/* Returns the GS_Watch matching addr/len/kind and sets *g_ix to its
|
|
position in gs_watches.
|
|
If no matching GS_Watch is found, returns NULL and sets g_ix to -1. */
|
|
static GS_Watch* lookup_gs_watch (Addr addr, SizeT len, PointKind kind,
|
|
Word* g_ix)
|
|
{
|
|
const Word n_elems = VG_(sizeXA) (gs_watches);
|
|
Word i;
|
|
GS_Watch *g;
|
|
|
|
/* Linear search. If we have many watches, this might be optimised
|
|
by having the array sorted and using VG_(lookupXA) */
|
|
for (i = 0; i < n_elems; i++) {
|
|
g = index_gs_watches(i);
|
|
if (g->addr == addr && g->len == len && g->kind == kind) {
|
|
// Found.
|
|
*g_ix = i;
|
|
return g;
|
|
}
|
|
}
|
|
|
|
// Not found.
|
|
*g_ix = -1;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* protocol spec tells the below must be idempotent. */
|
|
static void breakpoint (Bool insert, CORE_ADDR addr)
|
|
{
|
|
GS_Address *g;
|
|
|
|
g = VG_(HT_lookup) (gs_addresses, (UWord)HT_addr(addr));
|
|
if (insert) {
|
|
/* insert a breakpoint at addr or upgrade its kind */
|
|
if (g == NULL) {
|
|
add_gs_address (addr, GS_break, "m_gdbserver breakpoint insert");
|
|
} else {
|
|
/* already gdbserved. Normally, it must be because of a jump.
|
|
However, due to idempotent or if connection with gdb was
|
|
lost (kept breaks from the previous gdb), if already existing,
|
|
we just upgrade its kind. */
|
|
g->kind = GS_break;
|
|
}
|
|
} else {
|
|
/* delete a breakpoint at addr or downgrade its kind */
|
|
if (g != NULL && g->kind == GS_break) {
|
|
if (valgrind_single_stepping()) {
|
|
/* keep gdbserved instrumentation while single stepping */
|
|
g->kind = GS_jump;
|
|
} else {
|
|
remove_gs_address (g, "m_gdbserver breakpoint remove");
|
|
}
|
|
} else {
|
|
dlog (1, "remove break addr %p %s\n",
|
|
C2v(addr), (g == NULL ?
|
|
"NULL" :
|
|
(g->kind == GS_jump ? "GS_jump" : "GS_break")));
|
|
}
|
|
}
|
|
}
|
|
|
|
static Bool (*tool_watchpoint) (PointKind kind,
|
|
Bool insert,
|
|
Addr addr,
|
|
SizeT len) = NULL;
|
|
void VG_(needs_watchpoint) (Bool (*watchpoint) (PointKind kind,
|
|
Bool insert,
|
|
Addr addr,
|
|
SizeT len))
|
|
{
|
|
tool_watchpoint = watchpoint;
|
|
}
|
|
|
|
Bool VG_(gdbserver_point) (PointKind kind, Bool insert,
|
|
CORE_ADDR addr, int len)
|
|
{
|
|
Bool res;
|
|
GS_Watch *g;
|
|
Word g_ix;
|
|
Bool is_code = kind == software_breakpoint || kind == hardware_breakpoint;
|
|
|
|
dlog(1, "%s %s at addr %p %s\n",
|
|
(insert ? "insert" : "remove"),
|
|
VG_(ppPointKind) (kind),
|
|
C2v(addr),
|
|
sym(addr, is_code));
|
|
|
|
if (is_code) {
|
|
breakpoint (insert, addr);
|
|
return True;
|
|
}
|
|
|
|
vg_assert (kind == access_watchpoint
|
|
|| kind == read_watchpoint
|
|
|| kind == write_watchpoint);
|
|
|
|
if (tool_watchpoint == NULL)
|
|
return False;
|
|
|
|
res = (*tool_watchpoint) (kind, insert, addr, len);
|
|
if (!res)
|
|
return False; /* error or unsupported */
|
|
|
|
// Protocol says insert/remove must be idempotent.
|
|
// So, we just ignore double insert or (supposed) double delete.
|
|
|
|
g = lookup_gs_watch (addr, len, kind, &g_ix);
|
|
if (insert) {
|
|
if (g == NULL) {
|
|
g = VG_(malloc)("gdbserver_point watchpoint", sizeof(GS_Watch));
|
|
g->addr = addr;
|
|
g->len = len;
|
|
g->kind = kind;
|
|
VG_(addToXA)(gs_watches, &g);
|
|
} else {
|
|
dlog(1,
|
|
"VG_(gdbserver_point) addr %p len %d kind %s already inserted\n",
|
|
C2v(addr), len, VG_(ppPointKind) (kind));
|
|
}
|
|
} else {
|
|
if (g != NULL) {
|
|
VG_(removeIndexXA) (gs_watches, g_ix);
|
|
VG_(free) (g);
|
|
} else {
|
|
dlog(1,
|
|
"VG_(gdbserver_point) addr %p len %d kind %s already deleted?\n",
|
|
C2v(addr), len, VG_(ppPointKind) (kind));
|
|
}
|
|
}
|
|
return True;
|
|
}
|
|
|
|
Bool VG_(has_gdbserver_breakpoint) (Addr addr)
|
|
{
|
|
GS_Address *g;
|
|
if (!gdbserver_called)
|
|
return False;
|
|
g = VG_(HT_lookup) (gs_addresses, (UWord)HT_addr(addr));
|
|
return (g != NULL && g->kind == GS_break);
|
|
}
|
|
|
|
Bool VG_(is_watched)(PointKind kind, Addr addr, Int szB)
|
|
{
|
|
Word n_elems;
|
|
GS_Watch* g;
|
|
Word i;
|
|
Bool watched = False;
|
|
const ThreadId tid = VG_(running_tid);
|
|
|
|
if (!gdbserver_called)
|
|
return False;
|
|
|
|
n_elems = VG_(sizeXA) (gs_watches);
|
|
|
|
Addr to = addr + szB; // semi-open interval [addr, to[
|
|
|
|
vg_assert (kind == access_watchpoint
|
|
|| kind == read_watchpoint
|
|
|| kind == write_watchpoint);
|
|
dlog(1, "tid %u VG_(is_watched) %s addr %p szB %d\n",
|
|
tid, VG_(ppPointKind) (kind), C2v(addr), szB);
|
|
|
|
for (i = 0; i < n_elems; i++) {
|
|
g = index_gs_watches(i);
|
|
switch (g->kind) {
|
|
case software_breakpoint:
|
|
case hardware_breakpoint:
|
|
break;
|
|
case access_watchpoint:
|
|
case read_watchpoint:
|
|
case write_watchpoint:
|
|
if (to <= g->addr || addr >= (g->addr + g->len))
|
|
/* If no overlap, examine next watchpoint: */
|
|
continue;
|
|
|
|
watched = True; /* We have an overlap */
|
|
|
|
/* call gdbserver if access kind reported by the tool
|
|
matches the watchpoint kind. */
|
|
if (kind == access_watchpoint
|
|
|| g->kind == access_watchpoint
|
|
|| g->kind == kind) {
|
|
/* Watchpoint encountered.
|
|
If this is a read watchpoint, we directly call gdbserver
|
|
to report it to gdb.
|
|
Otherwise, for a write watchpoint, we have to finish
|
|
the instruction so as to modify the value.
|
|
If we do not finish the instruction, then gdb sees no
|
|
value change and continues.
|
|
For a read watchpoint, we better call gdbserver directly:
|
|
in case the current block is not gdbserved, Valgrind
|
|
will execute instructions till the next block. */
|
|
|
|
/* set the watchpoint stop address to the first read or written. */
|
|
if (g->addr <= addr) {
|
|
VG_(set_watchpoint_stop_address) (addr);
|
|
} else {
|
|
VG_(set_watchpoint_stop_address) (g->addr);
|
|
}
|
|
|
|
if (kind == write_watchpoint) {
|
|
/* Let Valgrind stop as early as possible after this instruction
|
|
by switching to Single Stepping mode. */
|
|
valgrind_set_single_stepping (True);
|
|
invalidate_current_ip (tid, "m_gdbserver write watchpoint");
|
|
} else {
|
|
call_gdbserver (tid, watch_reason);
|
|
VG_(set_watchpoint_stop_address) ((Addr) 0);
|
|
}
|
|
return True; // we are watched here.
|
|
}
|
|
break;
|
|
default:
|
|
vg_assert (0);
|
|
}
|
|
}
|
|
return watched;
|
|
}
|
|
|
|
/* Returns the reason for which gdbserver instrumentation is needed */
|
|
static VgVgdb VG_(gdbserver_instrumentation_needed) (const VexGuestExtents* vge)
|
|
{
|
|
GS_Address* g;
|
|
int e;
|
|
|
|
if (!gdbserver_called)
|
|
return Vg_VgdbNo;
|
|
|
|
if (valgrind_single_stepping()) {
|
|
dlog(2, "gdbserver_instrumentation_needed due to single stepping\n");
|
|
return Vg_VgdbYes;
|
|
}
|
|
|
|
if (VG_(clo_vgdb) == Vg_VgdbYes && VG_(HT_count_nodes) (gs_addresses) == 0)
|
|
return Vg_VgdbNo;
|
|
|
|
/* We assume we do not have a huge nr of breakpoints.
|
|
Otherwise, we need something more efficient e.g.
|
|
a sorted list of breakpoints or associate extents to it or ...
|
|
*/
|
|
VG_(HT_ResetIter) (gs_addresses);
|
|
while ((g = VG_(HT_Next) (gs_addresses))) {
|
|
for (e = 0; e < vge->n_used; e++) {
|
|
if (g->addr >= HT_addr(vge->base[e])
|
|
&& g->addr < HT_addr(vge->base[e]) + vge->len[e]) {
|
|
dlog(2,
|
|
"gdbserver_instrumentation_needed %p %s reason %s\n",
|
|
C2v(g->addr), sym(g->addr, /* is_code */ True),
|
|
(g->kind == GS_jump ? "GS_jump" : "GS_break"));
|
|
return Vg_VgdbYes;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (VG_(clo_vgdb) == Vg_VgdbFull) {
|
|
dlog(4, "gdbserver_instrumentation_needed"
|
|
" due to VG_(clo_vgdb) == Vg_VgdbFull\n");
|
|
return Vg_VgdbFull;
|
|
}
|
|
|
|
|
|
return Vg_VgdbNo;
|
|
}
|
|
|
|
// Clear gdbserved_addresses in gs_addresses.
|
|
// If clear_only_jumps, clears only the addresses that are served
|
|
// for jump reasons.
|
|
// Otherwise, clear all the addresses.
|
|
// Cleared addresses are invalidated so as to have them re-translated.
|
|
static void clear_gdbserved_addresses(Bool clear_only_jumps)
|
|
{
|
|
GS_Address** ag;
|
|
UInt n_elems;
|
|
int i;
|
|
|
|
dlog(1,
|
|
"clear_gdbserved_addresses: scanning hash table nodes %u\n",
|
|
VG_(HT_count_nodes) (gs_addresses));
|
|
ag = (GS_Address**) VG_(HT_to_array) (gs_addresses, &n_elems);
|
|
for (i = 0; i < n_elems; i++)
|
|
if (!clear_only_jumps || ag[i]->kind == GS_jump)
|
|
remove_gs_address (ag[i], "clear_gdbserved_addresses");
|
|
VG_(free) (ag);
|
|
}
|
|
|
|
// Clear watched addressed in gs_watches, delete gs_watches.
|
|
static void clear_watched_addresses(void)
|
|
{
|
|
GS_Watch* g;
|
|
const Word n_elems = VG_(sizeXA) (gs_watches);
|
|
Word i;
|
|
|
|
dlog(1,
|
|
"clear_watched_addresses: %ld elements\n",
|
|
n_elems);
|
|
|
|
for (i = 0; i < n_elems; i++) {
|
|
g = index_gs_watches(i);
|
|
if (!VG_(gdbserver_point) (g->kind,
|
|
/* insert */ False,
|
|
g->addr,
|
|
g->len)) {
|
|
vg_assert (0);
|
|
}
|
|
}
|
|
|
|
VG_(deleteXA) (gs_watches);
|
|
gs_watches = NULL;
|
|
}
|
|
|
|
static void invalidate_if_jump_not_yet_gdbserved (Addr addr, const HChar* from)
|
|
{
|
|
if (VG_(HT_lookup) (gs_addresses, (UWord)HT_addr(addr)))
|
|
return;
|
|
add_gs_address (addr, GS_jump, from);
|
|
}
|
|
|
|
static void invalidate_current_ip (ThreadId tid, const HChar *who)
|
|
{
|
|
invalidate_if_jump_not_yet_gdbserved (VG_(get_IP) (tid), who);
|
|
}
|
|
|
|
Bool VG_(gdbserver_init_done) (void)
|
|
{
|
|
return gdbserver_called > 0;
|
|
}
|
|
|
|
Bool VG_(gdbserver_stop_at) (VgdbStopAt stopat)
|
|
{
|
|
return gdbserver_called > 0 && VgdbStopAtiS(stopat, VG_(clo_vgdb_stop_at));
|
|
}
|
|
|
|
void VG_(gdbserver_prerun_action) (ThreadId tid)
|
|
{
|
|
// Using VG_(dyn_vgdb_error) allows the user to control if gdbserver
|
|
// stops after a fork.
|
|
if (VG_(dyn_vgdb_error) == 0
|
|
|| VgdbStopAtiS(VgdbStopAt_Startup, VG_(clo_vgdb_stop_at))) {
|
|
/* The below call allows gdb to attach at startup
|
|
before the first guest instruction is executed. */
|
|
VG_(umsg)("(action at startup) vgdb me ... \n");
|
|
VG_(gdbserver)(tid);
|
|
} else {
|
|
/* User has activated gdbserver => initialize now the FIFOs
|
|
to let vgdb/gdb contact us either via the scheduler poll
|
|
mechanism or via vgdb ptrace-ing valgrind. */
|
|
if (VG_(gdbserver_activity) (tid))
|
|
VG_(gdbserver) (tid);
|
|
}
|
|
}
|
|
|
|
/* when fork is done, various cleanup is needed in the child process.
|
|
In particular, child must have its own connection to avoid stealing
|
|
data from its parent */
|
|
static void gdbserver_cleanup_in_child_after_fork(ThreadId me)
|
|
{
|
|
dlog(1, "thread %u gdbserver_cleanup_in_child_after_fork pid %d\n",
|
|
me, VG_(getpid) ());
|
|
|
|
/* finish connection inheritated from parent */
|
|
remote_finish(reset_after_fork);
|
|
|
|
/* ensure next call to gdbserver will be considered as a brand
|
|
new call that will initialize a fresh gdbserver. */
|
|
if (gdbserver_called) {
|
|
gdbserver_called = 0;
|
|
vg_assert (gs_addresses != NULL);
|
|
vg_assert (gs_watches != NULL);
|
|
clear_gdbserved_addresses(/* clear only jumps */ False);
|
|
VG_(HT_destruct) (gs_addresses, VG_(free));
|
|
gs_addresses = NULL;
|
|
clear_watched_addresses();
|
|
} else {
|
|
vg_assert (gs_addresses == NULL);
|
|
vg_assert (gs_watches == NULL);
|
|
}
|
|
|
|
|
|
if (VG_(clo_trace_children)) {
|
|
VG_(gdbserver_prerun_action) (me);
|
|
}
|
|
}
|
|
|
|
/* If reason is init_reason, creates the connection resources (e.g.
|
|
the FIFOs) to allow a gdb connection to be detected by polling
|
|
using remote_desc_activity.
|
|
Otherwise (other reasons):
|
|
If connection with gdb not yet opened, opens the connection with gdb.
|
|
reads gdb remote protocol packets and executes the requested commands.
|
|
*/
|
|
static void call_gdbserver ( ThreadId tid , CallReason reason)
|
|
{
|
|
ThreadState* tst = VG_(get_ThreadState)(tid);
|
|
int stepping;
|
|
Addr saved_pc;
|
|
|
|
dlog(1,
|
|
"entering call_gdbserver %s ... pid %d tid %u status %s "
|
|
"sched_jmpbuf_valid %d\n",
|
|
ppCallReason (reason),
|
|
VG_(getpid) (), tid, VG_(name_of_ThreadStatus)(tst->status),
|
|
tst->sched_jmpbuf_valid);
|
|
|
|
/* If we are about to die, then just run server_main() once to get
|
|
the resume reply out and return immediately because most of the state
|
|
of this tid and process is about to be torn down. */
|
|
if (reason == exit_reason) {
|
|
server_main();
|
|
return;
|
|
}
|
|
|
|
vg_assert(VG_(is_valid_tid)(tid));
|
|
saved_pc = VG_(get_IP) (tid);
|
|
|
|
if (gdbserver_exited) {
|
|
dlog(0, "call_gdbserver called when gdbserver_exited %d\n",
|
|
gdbserver_exited);
|
|
return;
|
|
}
|
|
|
|
if (gdbserver_called == 0) {
|
|
vg_assert (gs_addresses == NULL);
|
|
vg_assert (gs_watches == NULL);
|
|
gs_addresses = VG_(HT_construct)( "gdbserved_addresses" );
|
|
gs_watches = VG_(newXA)(gs_alloc,
|
|
"gdbserved_watches",
|
|
gs_free,
|
|
sizeof(GS_Watch*));
|
|
VG_(atfork)(NULL, NULL, gdbserver_cleanup_in_child_after_fork);
|
|
}
|
|
vg_assert (gs_addresses != NULL);
|
|
vg_assert (gs_watches != NULL);
|
|
|
|
gdbserver_called++;
|
|
|
|
/* call gdbserver_init if this is the first call to gdbserver. */
|
|
if (gdbserver_called == 1)
|
|
gdbserver_init();
|
|
|
|
if (reason == init_reason || gdbserver_called == 1)
|
|
remote_open(VG_(clo_vgdb_prefix));
|
|
|
|
/* if the call reason is to initialize, then return control to
|
|
valgrind. After this initialization, gdbserver will be called
|
|
again either if there is an error detected by valgrind or
|
|
if vgdb sends data to the valgrind process. */
|
|
if (reason == init_reason) {
|
|
return;
|
|
}
|
|
|
|
stepping = valgrind_single_stepping();
|
|
|
|
server_main();
|
|
|
|
ignore_this_break_once = valgrind_get_ignore_break_once();
|
|
if (ignore_this_break_once)
|
|
dlog(1, "!!! will ignore_this_break_once %s\n",
|
|
sym(ignore_this_break_once, /* is_code */ True));
|
|
|
|
|
|
if (valgrind_single_stepping()) {
|
|
/* we are single stepping. If we were not stepping on entry,
|
|
then invalidate the current program counter so as to properly
|
|
do single step. In case the program counter was changed by
|
|
gdb, this will also invalidate the target address we will
|
|
jump to. */
|
|
if (!stepping && tid != 0) {
|
|
invalidate_current_ip (tid, "m_gdbserver single step");
|
|
}
|
|
} else {
|
|
/* We are not single stepping. If we were stepping on entry,
|
|
then clear the gdbserved addresses. This will cause all
|
|
these gdbserved blocks to be invalidated so that they can be
|
|
re-translated without being gdbserved. */
|
|
if (stepping)
|
|
clear_gdbserved_addresses(/* clear only jumps */ True);
|
|
}
|
|
|
|
/* can't do sanity check at beginning. At least the stack
|
|
check is not yet possible. */
|
|
if (gdbserver_called > 1)
|
|
VG_(sanity_check_general) (/* force_expensive */ False);
|
|
|
|
/* If the PC has been changed by gdb, then we VG_MINIMAL_LONGJMP to
|
|
the scheduler to execute the block of the new PC.
|
|
Otherwise we just return to continue executing the
|
|
current block. */
|
|
if (VG_(get_IP) (tid) != saved_pc) {
|
|
dlog(1, "tid %u %s PC changed from %s to %s\n",
|
|
tid, VG_(name_of_ThreadStatus) (tst->status),
|
|
sym(saved_pc, /* is_code */ True),
|
|
sym(VG_(get_IP) (tid), /* is_code */ True));
|
|
if (tst->status == VgTs_Yielding) {
|
|
SysRes sres;
|
|
VG_(memset)(&sres, 0, sizeof(SysRes));
|
|
VG_(acquire_BigLock)(tid, "gdbsrv VG_MINIMAL_LONGJMP");
|
|
}
|
|
if (tst->sched_jmpbuf_valid) {
|
|
/* resume scheduler */
|
|
VG_MINIMAL_LONGJMP(tst->sched_jmpbuf);
|
|
}
|
|
/* else continue to run */
|
|
}
|
|
/* continue to run */
|
|
}
|
|
|
|
/* busy > 0 when gdbserver is currently being called.
|
|
busy is used to avoid vgdb invoking gdbserver
|
|
while gdbserver by Valgrind. */
|
|
static volatile int busy = 0;
|
|
|
|
void VG_(gdbserver) ( ThreadId tid )
|
|
{
|
|
busy++;
|
|
/* called by the rest of valgrind for
|
|
--vgdb-error=0 reason
|
|
or by scheduler "poll/debug/interrupt" reason
|
|
or to terminate. */
|
|
if (tid != 0) {
|
|
call_gdbserver (tid, core_reason);
|
|
} else {
|
|
if (gdbserver_called == 0) {
|
|
dlog(1, "VG_(gdbserver) called to terminate, nothing to terminate\n");
|
|
} else if (gdbserver_exited) {
|
|
dlog(1, "VG_(gdbserver) called to terminate again %d\n",
|
|
gdbserver_exited);
|
|
} else {
|
|
gdbserver_terminate();
|
|
gdbserver_exited++;
|
|
}
|
|
}
|
|
busy--;
|
|
}
|
|
|
|
// nr of invoke_gdbserver while gdbserver is already executing.
|
|
static int interrupts_while_busy = 0;
|
|
|
|
// nr of invoke_gdbserver while gdbserver is not executing.
|
|
static int interrupts_non_busy = 0;
|
|
|
|
// nr of invoke_gdbserver when some threads are not interruptible.
|
|
static int interrupts_non_interruptible = 0;
|
|
|
|
/* When all threads are blocked in a system call, the Valgrind
|
|
scheduler cannot poll the shared memory for gdbserver activity. In
|
|
such a case, vgdb will force the invokation of gdbserver using
|
|
ptrace. To do that, vgdb 'pushes' a call to invoke_gdbserver
|
|
on the stack using ptrace. invoke_gdbserver must not return.
|
|
Instead, it must call give_control_back_to_vgdb.
|
|
vgdb expects to receive a SIGSTOP, which this function generates.
|
|
When vgdb gets this SIGSTOP, it knows invoke_gdbserver call
|
|
is finished and can reset the Valgrind process in the state prior to
|
|
the 'pushed call' (using ptrace again).
|
|
This all works well. However, the user must avoid
|
|
'kill-9ing' vgdb during such a pushed call, otherwise
|
|
the SIGSTOP generated below will be seen by the Valgrind core,
|
|
instead of being handled by vgdb. The OS will then handle the SIGSTOP
|
|
by stopping the Valgrind process.
|
|
We use SIGSTOP as this process cannot be masked. */
|
|
|
|
static void give_control_back_to_vgdb(void)
|
|
{
|
|
#if !defined(VGO_solaris)
|
|
/* cause a SIGSTOP to be sent to ourself, so that vgdb takes control.
|
|
vgdb will then restore the stack so as to resume the activity
|
|
before the ptrace (typically do_syscall_WRK). */
|
|
if (VG_(kill)(VG_(getpid)(), VKI_SIGSTOP) != 0)
|
|
vg_assert2(0, "SIGSTOP for vgdb could not be generated\n");
|
|
|
|
/* If we arrive here, it means a call was pushed on the stack
|
|
by vgdb, but during this call, vgdb and/or connection
|
|
died. Alternatively, it is a bug in the vgdb<=>Valgrind gdbserver
|
|
ptrace handling. */
|
|
vg_assert2(0,
|
|
"vgdb did not took control. Did you kill vgdb ?\n"
|
|
"busy %d vgdb_interrupted_tid %u\n",
|
|
busy, vgdb_interrupted_tid);
|
|
#else /* defined(VGO_solaris) */
|
|
/* On Solaris, this code is run within the context of an agent thread
|
|
(see vgdb-invoker-solaris.c and "PCAGENT" control message in
|
|
proc(4)). Exit the agent thread now.
|
|
*/
|
|
SysRes sres = VG_(do_syscall0)(SYS_lwp_exit);
|
|
if (sr_isError(sres))
|
|
vg_assert2(0, "The agent thread could not be exited\n");
|
|
#endif /* !defined(VGO_solaris) */
|
|
}
|
|
|
|
/* Using ptrace calls, vgdb will force an invocation of gdbserver.
|
|
VG_(invoke_gdbserver) is the entry point called through the
|
|
vgdb ptrace technique. */
|
|
void VG_(invoke_gdbserver) ( int check )
|
|
{
|
|
/* ******* Avoid non-reentrant function call from here .....
|
|
till the ".... till here" below. */
|
|
|
|
/* We need to determine the state of the various threads to decide
|
|
if we directly invoke gdbserver or if we rather indicate to the
|
|
scheduler to invoke the gdbserver. To decide that, it is
|
|
critical to avoid any "coregrind" function call as the ptrace
|
|
might have stopped the process in the middle of this (possibly)
|
|
non-rentrant function. So, it is only when all threads are in
|
|
an "interruptible" state that we can safely invoke
|
|
gdbserver. Otherwise, we let the valgrind scheduler invoke
|
|
gdbserver at the next poll. This poll will be made very soon
|
|
thanks to a call to VG_(force_vgdb_poll). */
|
|
int n_tid;
|
|
|
|
vg_assert (check == 0x8BADF00D);
|
|
|
|
if (busy) {
|
|
interrupts_while_busy++;
|
|
give_control_back_to_vgdb();
|
|
}
|
|
interrupts_non_busy++;
|
|
|
|
/* check if all threads are in an "interruptible" state. If yes,
|
|
we invoke gdbserver. Otherwise, we tell the scheduler to wake up
|
|
asap. */
|
|
for (n_tid = 1; n_tid < VG_N_THREADS; n_tid++) {
|
|
switch (VG_(threads)[n_tid].status) {
|
|
/* interruptible states. */
|
|
case VgTs_WaitSys:
|
|
case VgTs_Yielding:
|
|
if (vgdb_interrupted_tid == 0) vgdb_interrupted_tid = n_tid;
|
|
break;
|
|
|
|
case VgTs_Empty:
|
|
case VgTs_Zombie:
|
|
break;
|
|
|
|
/* non interruptible states. */
|
|
case VgTs_Init:
|
|
case VgTs_Runnable:
|
|
interrupts_non_interruptible++;
|
|
VG_(force_vgdb_poll) ();
|
|
give_control_back_to_vgdb();
|
|
|
|
default: vg_assert(0);
|
|
}
|
|
}
|
|
|
|
/* .... till here.
|
|
From here onwards, function calls are ok: it is
|
|
safe to call valgrind core functions: all threads are blocked in
|
|
a system call or are yielding or ... */
|
|
dlog(1, "invoke_gdbserver running_tid %u vgdb_interrupted_tid %u\n",
|
|
VG_(running_tid), vgdb_interrupted_tid);
|
|
call_gdbserver (vgdb_interrupted_tid, vgdb_reason);
|
|
vgdb_interrupted_tid = 0;
|
|
dlog(1,
|
|
"exit invoke_gdbserver running_tid %u\n", VG_(running_tid));
|
|
give_control_back_to_vgdb();
|
|
|
|
vg_assert2(0, "end of invoke_gdbserver reached");
|
|
|
|
}
|
|
|
|
Bool VG_(gdbserver_activity) (ThreadId tid)
|
|
{
|
|
Bool ret;
|
|
busy++;
|
|
if (!gdbserver_called)
|
|
call_gdbserver (tid, init_reason);
|
|
switch (remote_desc_activity("VG_(gdbserver_activity)")) {
|
|
case 0: ret = False; break;
|
|
case 1: ret = True; break;
|
|
case 2:
|
|
remote_finish(reset_after_error);
|
|
call_gdbserver (tid, init_reason);
|
|
ret = False;
|
|
break;
|
|
default: vg_assert (0);
|
|
}
|
|
busy--;
|
|
return ret;
|
|
}
|
|
|
|
static void dlog_signal (const HChar *who, const vki_siginfo_t *info,
|
|
ThreadId tid)
|
|
{
|
|
dlog(1, "VG core calling %s "
|
|
"vki_nr %d %s gdb_nr %u %s tid %u\n",
|
|
who,
|
|
info->si_signo, VG_(signame)(info->si_signo),
|
|
target_signal_from_host (info->si_signo),
|
|
target_signal_to_name(target_signal_from_host (info->si_signo)),
|
|
tid);
|
|
|
|
}
|
|
|
|
void VG_(gdbserver_report_fatal_signal) (const vki_siginfo_t *info,
|
|
ThreadId tid)
|
|
{
|
|
dlog_signal("VG_(gdbserver_report_fatal_signal)", info, tid);
|
|
|
|
if (remote_connected()) {
|
|
dlog(1, "already connected, assuming already reported\n");
|
|
return;
|
|
}
|
|
|
|
VG_(umsg)("(action on fatal signal) vgdb me ... \n");
|
|
|
|
/* indicate to gdbserver that there is a signal */
|
|
gdbserver_signal_encountered (info);
|
|
|
|
/* let gdbserver do some work, e.g. show the signal to the user */
|
|
call_gdbserver (tid, signal_reason);
|
|
|
|
}
|
|
|
|
Bool VG_(gdbserver_report_signal) (vki_siginfo_t *info, ThreadId tid)
|
|
{
|
|
dlog_signal("VG_(gdbserver_report_signal)", info, tid);
|
|
|
|
/* if gdbserver is currently not connected, then signal
|
|
is to be given to the process */
|
|
if (!remote_connected()) {
|
|
dlog(1, "not connected => pass\n");
|
|
return True;
|
|
}
|
|
/* if gdb has informed gdbserver that this signal can be
|
|
passed directly without informing gdb, then signal is
|
|
to be given to the process. */
|
|
if (pass_signals[target_signal_from_host(info->si_signo)]) {
|
|
dlog(1, "pass_signals => pass\n");
|
|
return True;
|
|
}
|
|
|
|
/* indicate to gdbserver that there is a signal */
|
|
gdbserver_signal_encountered (info);
|
|
|
|
/* let gdbserver do some work, e.g. show the signal to the user.
|
|
User can also decide to ignore the signal or change the signal. */
|
|
call_gdbserver (tid, signal_reason);
|
|
|
|
/* ask gdbserver what is the final decision */
|
|
if (gdbserver_deliver_signal (info)) {
|
|
dlog(1, "gdbserver deliver signal\n");
|
|
return True;
|
|
} else {
|
|
dlog(1, "gdbserver ignore signal\n");
|
|
return False;
|
|
}
|
|
}
|
|
|
|
Bool catching_syscalls = False; // True if catching all or some syscalls.
|
|
/* If catching_syscalls is True, then syscalls_to_catch_size == 0 means
|
|
to catch all syscalls. Otherwise, it is the size of the syscalls_to_catch
|
|
array. */
|
|
Int syscalls_to_catch_size = 0;
|
|
Int *syscalls_to_catch;
|
|
static Bool catch_this_syscall (Int sysno)
|
|
{
|
|
Int i;
|
|
|
|
if (syscalls_to_catch_size == 0)
|
|
return True;
|
|
|
|
for (i = 0; i < syscalls_to_catch_size; i++)
|
|
if (syscalls_to_catch[i] == sysno)
|
|
return True;
|
|
|
|
return False;
|
|
}
|
|
|
|
void VG_(gdbserver_report_syscall) (Bool before, UWord sysno, ThreadId tid)
|
|
{
|
|
dlog(4, "VG_(gdbserver_report_syscall) before %d sysno %lu tid %d\n",
|
|
before, sysno, tid);
|
|
|
|
if (UNLIKELY(catching_syscalls)) {
|
|
if (!remote_connected()) {
|
|
dlog(2, "not connected => no report\n");
|
|
}
|
|
|
|
if (catch_this_syscall ((Int)sysno)) {
|
|
/* let gdbserver do some work */
|
|
gdbserver_syscall_encountered (before, (Int)sysno);
|
|
call_gdbserver (tid, signal_reason);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VG_(gdbserver_exit) (ThreadId tid, VgSchedReturnCode tids_schedretcode)
|
|
{
|
|
dlog(1, "VG core calling VG_(gdbserver_exit) tid %u will exit\n", tid);
|
|
if (remote_connected()) {
|
|
/* Make sure vgdb knows we are about to die and why. */
|
|
switch(tids_schedretcode) {
|
|
case VgSrc_None:
|
|
vg_assert (0);
|
|
case VgSrc_ExitThread:
|
|
case VgSrc_ExitProcess:
|
|
gdbserver_process_exit_encountered
|
|
('W', VG_(threads)[tid].os_state.exitcode);
|
|
call_gdbserver (tid, exit_reason);
|
|
break;
|
|
case VgSrc_FatalSig:
|
|
gdbserver_process_exit_encountered
|
|
('X', VG_(threads)[tid].os_state.fatalsig);
|
|
call_gdbserver (tid, exit_reason);
|
|
break;
|
|
default:
|
|
vg_assert(0);
|
|
}
|
|
} else {
|
|
dlog(1, "not connected\n");
|
|
}
|
|
|
|
/* Tear down the connection if it still exists. */
|
|
VG_(gdbserver) (0);
|
|
}
|
|
|
|
// Check if single_stepping or if there is a break requested at iaddr.
|
|
// If yes, call debugger
|
|
VG_REGPARM(1)
|
|
void VG_(helperc_CallDebugger) ( HWord iaddr )
|
|
{
|
|
GS_Address* g;
|
|
|
|
// For Vg_VgdbFull, after a fork, we might have calls to this helper
|
|
// while gdbserver is not yet initialized.
|
|
if (!gdbserver_called)
|
|
return;
|
|
|
|
if (valgrind_single_stepping() ||
|
|
((g = VG_(HT_lookup) (gs_addresses, (UWord)HT_addr(iaddr))) &&
|
|
(g->kind == GS_break))) {
|
|
if (iaddr == HT_addr(ignore_this_break_once)) {
|
|
dlog(1, "ignoring ignore_this_break_once %s\n",
|
|
sym(ignore_this_break_once, /* is_code */ True));
|
|
ignore_this_break_once = 0;
|
|
} else {
|
|
call_gdbserver (VG_(get_running_tid)(), break_reason);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* software_breakpoint support --------------------------------------*/
|
|
/* When a block is instrumented for gdbserver, single step and breaks
|
|
will be obeyed in this block. However, if a jump to another block
|
|
is executed while single_stepping is active, we must ensure that
|
|
this block is also instrumented. For this, when a block is
|
|
instrumented for gdbserver while single_stepping, the target of all
|
|
the Jump instructions in this block will be checked to verify if
|
|
the block is already instrumented for gdbserver. The below will
|
|
ensure that if not already instrumented for gdbserver, the target
|
|
block translation containing addr will be invalidated. The list of
|
|
gdbserved Addr will also be kept so that translations can be
|
|
dropped automatically by gdbserver when going out of single step
|
|
mode.
|
|
|
|
Call the below at translation time if the jump target is a constant.
|
|
Otherwise, rather use VG_(add_stmt_call_invalidate_if_not_gdbserved).
|
|
|
|
To instrument the target exit statement, you can call
|
|
VG_(add_stmt_call_invalidate_exit_target_if_not_gdbserved) rather
|
|
than check the kind of target exit. */
|
|
static void VG_(invalidate_if_not_gdbserved) (Addr addr)
|
|
{
|
|
if (valgrind_single_stepping())
|
|
invalidate_if_jump_not_yet_gdbserved
|
|
(addr, "gdbserver target jump (instrument)");
|
|
}
|
|
|
|
// same as VG_(invalidate_if_not_gdbserved) but is intended to be called
|
|
// at runtime (only difference is the invalidate reason which traces
|
|
// it is at runtime)
|
|
VG_REGPARM(1)
|
|
void VG_(helperc_invalidate_if_not_gdbserved) ( Addr addr )
|
|
{
|
|
if (valgrind_single_stepping())
|
|
invalidate_if_jump_not_yet_gdbserved
|
|
(addr, "gdbserver target jump (runtime)");
|
|
}
|
|
|
|
static void VG_(add_stmt_call_invalidate_if_not_gdbserved)
|
|
( IRSB* sb_in,
|
|
const VexGuestLayout* layout,
|
|
const VexGuestExtents* vge,
|
|
IRTemp jmp,
|
|
IRSB* irsb)
|
|
{
|
|
|
|
void* fn;
|
|
const HChar* nm;
|
|
IRExpr** args;
|
|
Int nargs;
|
|
IRDirty* di;
|
|
|
|
fn = &VG_(helperc_invalidate_if_not_gdbserved);
|
|
nm = "VG_(helperc_invalidate_if_not_gdbserved)";
|
|
args = mkIRExprVec_1(IRExpr_RdTmp (jmp));
|
|
nargs = 1;
|
|
|
|
di = unsafeIRDirty_0_N( nargs/*regparms*/, nm,
|
|
VG_(fnptr_to_fnentry)( fn ), args );
|
|
|
|
di->nFxState = 0;
|
|
|
|
addStmtToIRSB(irsb, IRStmt_Dirty(di));
|
|
}
|
|
|
|
/* software_breakpoint support --------------------------------------*/
|
|
/* If a tool wants to allow gdbserver to do something at Addr, then
|
|
VG_(add_stmt_call_gdbserver) will add in IRSB a call to a helper
|
|
function. This helper function will check if the process must be
|
|
stopped at the instruction Addr: either there is a break at Addr or
|
|
the process is being single-stepped. Typical usage of the below is to
|
|
instrument an Ist_IMark to allow the debugger to interact at any
|
|
instruction being executed. As soon as there is one break in a block,
|
|
then to allow single stepping in this block (and possible insertions
|
|
of other breaks in the same sb_in while the process is stopped), a
|
|
debugger statement will be inserted for all instructions of a block. */
|
|
static void VG_(add_stmt_call_gdbserver)
|
|
(IRSB* sb_in, /* block being translated */
|
|
const VexGuestLayout* layout,
|
|
const VexGuestExtents* vge,
|
|
IRType gWordTy, IRType hWordTy,
|
|
Addr iaddr, /* Addr of instruction being instrumented */
|
|
UChar delta, /* delta to add to iaddr to obtain IP */
|
|
IRSB* irsb) /* irsb block to which call is added */
|
|
{
|
|
void* fn;
|
|
const HChar* nm;
|
|
IRExpr** args;
|
|
Int nargs;
|
|
IRDirty* di;
|
|
|
|
/* first store the address in the program counter so that the check
|
|
done by VG_(helperc_CallDebugger) will be based on the correct
|
|
program counter. We might make this more efficient by rather
|
|
searching for assignement to program counter and instrumenting
|
|
that but the below is easier and I guess that the optimiser will
|
|
remove the redundant store. And in any case, when debugging a
|
|
piece of code, the efficiency requirement is not critical: very
|
|
few blocks will be instrumented for debugging. */
|
|
|
|
/* For platforms on which the IP can differ from the addr of the instruction
|
|
being executed, we need to add the delta to obtain the IP.
|
|
This IP will be given to gdb (e.g. if a breakpoint is put at iaddr).
|
|
|
|
For ARM, this delta will ensure that the thumb bit is set in the
|
|
IP when executing thumb code. gdb uses this thumb bit a.o.
|
|
to properly guess the next IP for the 'step' and 'stepi' commands. */
|
|
vg_assert(delta <= 1);
|
|
addStmtToIRSB(irsb, IRStmt_Put(layout->offset_IP ,
|
|
mkIRExpr_HWord(iaddr + (Addr)delta)));
|
|
|
|
fn = &VG_(helperc_CallDebugger);
|
|
nm = "VG_(helperc_CallDebugger)";
|
|
args = mkIRExprVec_1(mkIRExpr_HWord (iaddr));
|
|
nargs = 1;
|
|
|
|
di = unsafeIRDirty_0_N( nargs/*regparms*/, nm,
|
|
VG_(fnptr_to_fnentry)( fn ), args );
|
|
|
|
/* Note: in fact, a debugger call can read whatever register
|
|
or memory. It can also write whatever register or memory.
|
|
So, in theory, we have to indicate the whole universe
|
|
can be read and modified. It is however not critical
|
|
to indicate precisely what is being read/written
|
|
as such indications are needed for tool error detection
|
|
and we do not want to have errors being detected for
|
|
gdb interactions. */
|
|
|
|
di->nFxState = 2;
|
|
di->fxState[0].fx = Ifx_Read;
|
|
di->fxState[0].offset = layout->offset_SP;
|
|
di->fxState[0].size = layout->sizeof_SP;
|
|
di->fxState[0].nRepeats = 0;
|
|
di->fxState[0].repeatLen = 0;
|
|
di->fxState[1].fx = Ifx_Modify;
|
|
di->fxState[1].offset = layout->offset_IP;
|
|
di->fxState[1].size = layout->sizeof_IP;
|
|
di->fxState[1].nRepeats = 0;
|
|
di->fxState[1].repeatLen = 0;
|
|
|
|
addStmtToIRSB(irsb, IRStmt_Dirty(di));
|
|
|
|
}
|
|
|
|
|
|
/* Invalidate the target of the exit if needed:
|
|
If target is constant, it is invalidated at translation time.
|
|
Otherwise, a call to a helper function is generated to invalidate
|
|
the translation at run time.
|
|
The below is thus calling either VG_(invalidate_if_not_gdbserved)
|
|
or VG_(add_stmt_call_invalidate_if_not_gdbserved). */
|
|
static void VG_(add_stmt_call_invalidate_exit_target_if_not_gdbserved)
|
|
(IRSB* sb_in,
|
|
const VexGuestLayout* layout,
|
|
const VexGuestExtents* vge,
|
|
IRType gWordTy,
|
|
IRSB* irsb)
|
|
{
|
|
if (sb_in->next->tag == Iex_Const) {
|
|
VG_(invalidate_if_not_gdbserved) (gWordTy == Ity_I64 ?
|
|
sb_in->next->Iex.Const.con->Ico.U64
|
|
: sb_in->next->Iex.Const.con->Ico.U32);
|
|
} else if (sb_in->next->tag == Iex_RdTmp) {
|
|
VG_(add_stmt_call_invalidate_if_not_gdbserved)
|
|
(sb_in, layout, vge, sb_in->next->Iex.RdTmp.tmp, irsb);
|
|
} else {
|
|
vg_assert (0); /* unexpected expression tag in exit. */
|
|
}
|
|
}
|
|
|
|
IRSB* VG_(instrument_for_gdbserver_if_needed)
|
|
(IRSB* sb_in,
|
|
const VexGuestLayout* layout,
|
|
const VexGuestExtents* vge,
|
|
IRType gWordTy, IRType hWordTy)
|
|
{
|
|
IRSB* sb_out;
|
|
Int i;
|
|
const VgVgdb instr_needed = VG_(gdbserver_instrumentation_needed) (vge);
|
|
|
|
if (instr_needed == Vg_VgdbNo)
|
|
return sb_in;
|
|
|
|
|
|
/* here, we need to instrument for gdbserver */
|
|
sb_out = deepCopyIRSBExceptStmts(sb_in);
|
|
|
|
for (i = 0; i < sb_in->stmts_used; i++) {
|
|
IRStmt* st = sb_in->stmts[i];
|
|
|
|
if (!st || st->tag == Ist_NoOp) continue;
|
|
|
|
if (st->tag == Ist_Exit && instr_needed == Vg_VgdbYes) {
|
|
VG_(invalidate_if_not_gdbserved)
|
|
(hWordTy == Ity_I64 ?
|
|
st->Ist.Exit.dst->Ico.U64 :
|
|
st->Ist.Exit.dst->Ico.U32);
|
|
}
|
|
addStmtToIRSB( sb_out, st );
|
|
if (st->tag == Ist_IMark) {
|
|
/* For an Ist_Mark, add a call to debugger. */
|
|
switch (instr_needed) {
|
|
case Vg_VgdbNo: vg_assert (0);
|
|
case Vg_VgdbYes:
|
|
case Vg_VgdbFull:
|
|
VG_(add_stmt_call_gdbserver) ( sb_in, layout, vge,
|
|
gWordTy, hWordTy,
|
|
st->Ist.IMark.addr,
|
|
st->Ist.IMark.delta,
|
|
sb_out);
|
|
/* There is an optimisation possible here for Vg_VgdbFull:
|
|
Put a guard ensuring we only call gdbserver if 'FullCallNeeded'.
|
|
FullCallNeeded would be set to 1 we have just switched on
|
|
Single Stepping or have just encountered a watchpoint
|
|
or have just inserted a breakpoint.
|
|
(as gdb by default removes and re-insert breakpoints), we would
|
|
need to also implement the notion of 'breakpoint pending removal'
|
|
to remove at the next 'continue/step' packet. */
|
|
break;
|
|
default: vg_assert (0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (instr_needed == Vg_VgdbYes) {
|
|
VG_(add_stmt_call_invalidate_exit_target_if_not_gdbserved) (sb_in,
|
|
layout, vge,
|
|
gWordTy,
|
|
sb_out);
|
|
}
|
|
|
|
return sb_out;
|
|
}
|
|
|
|
struct mon_out_buf {
|
|
HChar buf[DATASIZ+1];
|
|
int next;
|
|
UInt ret;
|
|
};
|
|
|
|
static void mon_out (HChar c, void *opaque)
|
|
{
|
|
struct mon_out_buf *b = (struct mon_out_buf *) opaque;
|
|
b->ret++;
|
|
b->buf[b->next] = c;
|
|
b->next++;
|
|
if (b->next == DATASIZ) {
|
|
b->buf[b->next] = '\0';
|
|
monitor_output(b->buf);
|
|
b->next = 0;
|
|
}
|
|
}
|
|
UInt VG_(gdb_printf) ( const HChar *format, ... )
|
|
{
|
|
struct mon_out_buf b;
|
|
|
|
b.next = 0;
|
|
b.ret = 0;
|
|
|
|
va_list vargs;
|
|
va_start(vargs, format);
|
|
VG_(vcbprintf) (mon_out, &b, format, vargs);
|
|
va_end(vargs);
|
|
|
|
if (b.next > 0) {
|
|
b.buf[b.next] = '\0';
|
|
monitor_output(b.buf);
|
|
}
|
|
return b.ret;
|
|
}
|
|
|
|
Int VG_(keyword_id) (const HChar* keywords, const HChar* input_word,
|
|
kwd_report_error report)
|
|
{
|
|
const Int il = (input_word == NULL ? 0 : VG_(strlen) (input_word));
|
|
HChar iw[il+1];
|
|
HChar kwds[VG_(strlen)(keywords)+1];
|
|
HChar *kwdssaveptr;
|
|
|
|
HChar* kw; /* current keyword, its length, its position */
|
|
Int kwl;
|
|
Int kpos = -1;
|
|
|
|
Int pass;
|
|
/* pass 0 = search, optional pass 1 = output message multiple matches */
|
|
|
|
Int pass1needed = 0;
|
|
|
|
Int partial_match = -1;
|
|
Int full_match = -1;
|
|
|
|
if (input_word == NULL) {
|
|
iw[0] = 0;
|
|
partial_match = 0; /* to force an empty string to cause an error */
|
|
} else {
|
|
VG_(strcpy) (iw, input_word);
|
|
}
|
|
|
|
for (pass = 0; pass < 2; pass++) {
|
|
VG_(strcpy) (kwds, keywords);
|
|
if (pass == 1)
|
|
VG_(gdb_printf) ("%s can match",
|
|
(il == 0 ? "<empty string>" : iw));
|
|
for (kw = VG_(strtok_r) (kwds, " ", &kwdssaveptr);
|
|
kw != NULL;
|
|
kw = VG_(strtok_r) (NULL, " ", &kwdssaveptr)) {
|
|
kwl = VG_(strlen) (kw);
|
|
kpos++;
|
|
|
|
if (il > kwl) {
|
|
; /* ishtar !~ is */
|
|
} else if (il == kwl) {
|
|
if (VG_(strcmp) (kw, iw) == 0) {
|
|
/* exact match */
|
|
if (pass == 1)
|
|
VG_(gdb_printf) (" %s", kw);
|
|
if (full_match != -1)
|
|
pass1needed++;
|
|
full_match = kpos;
|
|
}
|
|
} else {
|
|
/* il < kwl */
|
|
if (VG_(strncmp) (iw, kw, il) == 0) {
|
|
/* partial match */
|
|
if (pass == 1)
|
|
VG_(gdb_printf) (" %s", kw);
|
|
if (partial_match != -1)
|
|
pass1needed++;
|
|
partial_match = kpos;
|
|
}
|
|
}
|
|
}
|
|
/* check for success or for no match at all */
|
|
if (pass1needed == 0) {
|
|
if (full_match != -1) {
|
|
return full_match;
|
|
} else {
|
|
if (report == kwd_report_all && partial_match == -1) {
|
|
VG_(gdb_printf) ("%s does not match any of '%s'\n",
|
|
iw, keywords);
|
|
}
|
|
return partial_match;
|
|
}
|
|
}
|
|
|
|
/* here we have duplicated match error */
|
|
if (pass == 1 || report == kwd_report_none) {
|
|
if (report != kwd_report_none) {
|
|
VG_(gdb_printf) ("\n");
|
|
}
|
|
if (partial_match != -1 || full_match != -1)
|
|
return -2;
|
|
else
|
|
return -1;
|
|
}
|
|
}
|
|
/* UNREACHED */
|
|
vg_assert (0);
|
|
}
|
|
|
|
/* True if string can be a 0x number */
|
|
static Bool is_zero_x (const HChar *s)
|
|
{
|
|
if (strlen (s) >= 3 && s[0] == '0' && s[1] == 'x')
|
|
return True;
|
|
else
|
|
return False;
|
|
}
|
|
|
|
/* True if string can be a 0b number */
|
|
static Bool is_zero_b (const HChar *s)
|
|
{
|
|
if (strlen (s) >= 3 && s[0] == '0' && s[1] == 'b')
|
|
return True;
|
|
else
|
|
return False;
|
|
}
|
|
|
|
Bool VG_(strtok_get_address_and_size) (Addr* address,
|
|
SizeT* szB,
|
|
HChar **ssaveptr)
|
|
{
|
|
HChar* wa;
|
|
HChar* ws;
|
|
HChar* endptr;
|
|
const HChar *ppc;
|
|
|
|
wa = VG_(strtok_r) (NULL, " ", ssaveptr);
|
|
ppc = wa;
|
|
if (ppc == NULL || !VG_(parse_Addr) (&ppc, address)) {
|
|
VG_(gdb_printf) ("missing or malformed address\n");
|
|
*address = (Addr) 0;
|
|
*szB = 0;
|
|
return False;
|
|
}
|
|
ws = VG_(strtok_r) (NULL, " ", ssaveptr);
|
|
if (ws == NULL) {
|
|
/* Do nothing, i.e. keep current value of szB. */ ;
|
|
} else if (is_zero_x (ws)) {
|
|
*szB = VG_(strtoull16) (ws, &endptr);
|
|
} else if (is_zero_b (ws)) {
|
|
Int j;
|
|
HChar *parsews = ws;
|
|
Int n_bits = VG_(strlen) (ws) - 2;
|
|
*szB = 0;
|
|
ws = NULL; // assume the below loop gives a correct nr.
|
|
for (j = 0; j < n_bits; j++) {
|
|
if ('0' == parsews[j+2]) { /* do nothing */ }
|
|
else if ('1' == parsews[j+2]) *szB |= (1 << (n_bits-j-1));
|
|
else {
|
|
/* report malformed binary integer */
|
|
ws = parsews;
|
|
endptr = ws + j + 2;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
*szB = VG_(strtoull10) (ws, &endptr);
|
|
}
|
|
|
|
if (ws != NULL && *endptr != '\0') {
|
|
VG_(gdb_printf) ("malformed integer, expecting "
|
|
"hex 0x..... or dec ...... or binary .....b\n");
|
|
*address = (Addr) 0;
|
|
*szB = 0;
|
|
return False;
|
|
}
|
|
return True;
|
|
}
|
|
|
|
void VG_(gdbserver_status_output)(void)
|
|
{
|
|
const int nr_gdbserved_addresses
|
|
= (gs_addresses == NULL ? -1 : VG_(HT_count_nodes) (gs_addresses));
|
|
const int nr_watchpoints
|
|
= (gs_watches == NULL ? -1 : (int) VG_(sizeXA) (gs_watches));
|
|
remote_utils_output_status();
|
|
VG_(umsg)
|
|
("nr of calls to gdbserver: %d\n"
|
|
"single stepping %d\n"
|
|
"interrupts intr_tid %u gs_non_busy %d gs_busy %d tid_non_intr %d\n"
|
|
"gdbserved addresses %d (-1 = not initialized)\n"
|
|
"watchpoints %d (-1 = not initialized)\n"
|
|
"vgdb-error %d\n"
|
|
"hostvisibility %s\n",
|
|
gdbserver_called,
|
|
valgrind_single_stepping(),
|
|
|
|
vgdb_interrupted_tid,
|
|
interrupts_non_busy,
|
|
interrupts_while_busy,
|
|
interrupts_non_interruptible,
|
|
|
|
nr_gdbserved_addresses,
|
|
nr_watchpoints,
|
|
VG_(dyn_vgdb_error),
|
|
hostvisibility ? "yes" : "no");
|
|
}
|