X-Git-Url: http://drtracing.org/?a=blobdiff_plain;f=gdb%2Fgdbserver%2Fmem-break.c;h=c796948ec88d06a7ac0f74faecd574e60e4bae53;hb=505106cdc7c816a44bbfee11daf500f4e5e14072;hp=977b0e3f3d3a2d3fa4903e143224379d8c9b3d8c;hpb=1c0a559ec88f81e3f66ed3c0295d852af1cbf23a;p=deliverable%2Fbinutils-gdb.git diff --git a/gdb/gdbserver/mem-break.c b/gdb/gdbserver/mem-break.c index 977b0e3f3d..c796948ec8 100644 --- a/gdb/gdbserver/mem-break.c +++ b/gdb/gdbserver/mem-break.c @@ -1,5 +1,5 @@ /* Memory breakpoint operations for the remote server for GDB. - Copyright 2002 + Copyright (C) 2002, 2003, 2005, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. Contributed by MontaVista Software. @@ -8,7 +8,7 @@ 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 + the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, @@ -17,203 +17,612 @@ 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. */ + along with this program. If not, see . */ #include "server.h" -const char *breakpoint_data; +const unsigned char *breakpoint_data; int breakpoint_len; #define MAX_BREAKPOINT_LEN 8 -struct breakpoint +/* GDB will never try to install multiple breakpoints at the same + address. But, we need to keep track of internal breakpoints too, + and so we do need to be able to install multiple breakpoints at the + same address transparently. We keep track of two different, and + closely related structures. A raw breakpoint, which manages the + low level, close to the metal aspect of a breakpoint. It holds the + breakpoint address, and a buffer holding a copy of the instructions + that would be in memory had not been a breakpoint there (we call + that the shadow memory of the breakpoint). We occasionally need to + temporarilly uninsert a breakpoint without the client knowing about + it (e.g., to step over an internal breakpoint), so we keep an + `inserted' state associated with this low level breakpoint + structure. There can only be one such object for a given address. + Then, we have (a bit higher level) breakpoints. This structure + holds a callback to be called whenever a breakpoint is hit, a + high-level type, and a link to a low level raw breakpoint. There + can be many high-level breakpoints at the same address, and all of + them will point to the same raw breakpoint, which is reference + counted. */ + +/* The low level, physical, raw breakpoint. */ +struct raw_breakpoint { - struct breakpoint *next; + struct raw_breakpoint *next; + + /* A reference count. Each high level breakpoint referencing this + raw breakpoint accounts for one reference. */ + int refcount; + + /* The breakpoint's insertion address. There can only be one raw + breakpoint for a given PC. */ CORE_ADDR pc; + + /* The breakpoint's shadow memory. */ unsigned char old_data[MAX_BREAKPOINT_LEN]; - /* Non-zero iff we are stepping over this breakpoint. */ - int reinserting; + /* Non-zero if this breakpoint is currently inserted in the + inferior. */ + int inserted; + + /* Non-zero if this breakpoint is currently disabled because we no + longer detect it as inserted. */ + int shlib_disabled; +}; + +/* The type of a breakpoint. */ +enum bkpt_type + { + /* A GDB breakpoint, requested with a Z0 packet. */ + gdb_breakpoint, + + /* A basic-software-single-step breakpoint. */ + reinsert_breakpoint, + + /* Any other breakpoint type that doesn't require specific + treatment goes here. E.g., an event breakpoint. */ + other_breakpoint, + }; + +/* A high level (in gdbserver's perspective) breakpoint. */ +struct breakpoint +{ + struct breakpoint *next; - /* Non-NULL iff this breakpoint was inserted to step over - another one. Points to the other breakpoint (which is also - in the *next chain somewhere). */ - struct breakpoint *breakpoint_to_reinsert; + /* The breakpoint's type. */ + enum bkpt_type type; - /* Function to call when we hit this breakpoint. */ - void (*handler) (CORE_ADDR); + /* Link to this breakpoint's raw breakpoint. This is always + non-NULL. */ + struct raw_breakpoint *raw; + + /* Function to call when we hit this breakpoint. If it returns 1, + the breakpoint shall be deleted; 0 or if this callback is NULL, + it will be left inserted. */ + int (*handler) (CORE_ADDR); }; -struct breakpoint *breakpoints; +static struct raw_breakpoint * +find_raw_breakpoint_at (CORE_ADDR where) +{ + struct process_info *proc = current_process (); + struct raw_breakpoint *bp; + + for (bp = proc->raw_breakpoints; bp != NULL; bp = bp->next) + if (bp->pc == where) + return bp; -void -set_breakpoint_at (CORE_ADDR where, void (*handler) (CORE_ADDR)) + return NULL; +} + +static struct raw_breakpoint * +set_raw_breakpoint_at (CORE_ADDR where) { - struct breakpoint *bp; + struct process_info *proc = current_process (); + struct raw_breakpoint *bp; + int err; if (breakpoint_data == NULL) error ("Target does not support breakpoints."); - bp = malloc (sizeof (struct breakpoint)); - memset (bp, 0, sizeof (struct breakpoint)); - - (*the_target->read_memory) (where, bp->old_data, - breakpoint_len); - (*the_target->write_memory) (where, breakpoint_data, - breakpoint_len); + bp = find_raw_breakpoint_at (where); + if (bp != NULL) + { + bp->refcount++; + return bp; + } + bp = xcalloc (1, sizeof (*bp)); bp->pc = where; - bp->handler = handler; + bp->refcount = 1; - bp->next = breakpoints; - breakpoints = bp; + err = (*the_target->read_memory) (where, bp->old_data, + breakpoint_len); + if (err != 0) + { + if (debug_threads) + fprintf (stderr, + "Failed to read shadow memory of" + " breakpoint at 0x%s (%s).\n", + paddress (where), strerror (err)); + free (bp); + return NULL; + } + + err = (*the_target->write_memory) (where, breakpoint_data, + breakpoint_len); + if (err != 0) + { + if (debug_threads) + fprintf (stderr, + "Failed to insert breakpoint at 0x%s (%s).\n", + paddress (where), strerror (err)); + free (bp); + return NULL; + } + + /* Link the breakpoint in. */ + bp->inserted = 1; + bp->next = proc->raw_breakpoints; + proc->raw_breakpoints = bp; + return bp; } -static void -delete_breakpoint (struct breakpoint *bp) +struct breakpoint * +set_breakpoint_at (CORE_ADDR where, int (*handler) (CORE_ADDR)) { - struct breakpoint *cur; + struct process_info *proc = current_process (); + struct breakpoint *bp; + struct raw_breakpoint *raw; + + raw = set_raw_breakpoint_at (where); - if (breakpoints == bp) + if (raw == NULL) { - breakpoints = bp->next; - (*the_target->write_memory) (bp->pc, bp->old_data, - breakpoint_len); - free (bp); - return; + /* warn? */ + return NULL; } - cur = breakpoints; - while (cur->next) + + bp = xcalloc (1, sizeof (struct breakpoint)); + bp->type = other_breakpoint; + + bp->raw = raw; + bp->handler = handler; + + bp->next = proc->breakpoints; + proc->breakpoints = bp; + + return bp; +} + +static int +delete_raw_breakpoint (struct process_info *proc, struct raw_breakpoint *todel) +{ + struct raw_breakpoint *bp, **bp_link; + int ret; + + bp = proc->raw_breakpoints; + bp_link = &proc->raw_breakpoints; + + while (bp) { - if (cur->next == bp) + if (bp == todel) { - cur->next = bp->next; - (*the_target->write_memory) (bp->pc, bp->old_data, - breakpoint_len); + if (bp->inserted) + { + struct raw_breakpoint *prev_bp_link = *bp_link; + + *bp_link = bp->next; + + ret = (*the_target->write_memory) (bp->pc, bp->old_data, + breakpoint_len); + if (ret != 0) + { + /* Something went wrong, relink the breakpoint. */ + *bp_link = prev_bp_link; + + if (debug_threads) + fprintf (stderr, + "Failed to uninsert raw breakpoint " + "at 0x%s (%s) while deleting it.\n", + paddress (bp->pc), strerror (ret)); + return ret; + } + + } + else + *bp_link = bp->next; + free (bp); - return; + return 0; + } + else + { + bp_link = &bp->next; + bp = *bp_link; } } - warning ("Could not find breakpoint in list."); + + warning ("Could not find raw breakpoint in list."); + return ENOENT; } -static struct breakpoint * -find_breakpoint_at (CORE_ADDR where) +static int +release_breakpoint (struct process_info *proc, struct breakpoint *bp) { - struct breakpoint *bp = breakpoints; + int newrefcount; + int ret; - while (bp != NULL) + newrefcount = bp->raw->refcount - 1; + if (newrefcount == 0) { - if (bp->pc == where) - return bp; - bp = bp->next; + ret = delete_raw_breakpoint (proc, bp->raw); + if (ret != 0) + return ret; } + else + bp->raw->refcount = newrefcount; - return NULL; + free (bp); + + return 0; } -static void -reinsert_breakpoint_handler (CORE_ADDR stop_pc) +static int +delete_breakpoint_1 (struct process_info *proc, struct breakpoint *todel) { - struct breakpoint *stop_bp, *orig_bp; + struct breakpoint *bp, **bp_link; + int err; + + bp = proc->breakpoints; + bp_link = &proc->breakpoints; - stop_bp = find_breakpoint_at (stop_pc); - if (stop_bp == NULL) - error ("lost the stopping breakpoint."); + while (bp) + { + if (bp == todel) + { + *bp_link = bp->next; + + err = release_breakpoint (proc, bp); + if (err != 0) + return err; + + bp = *bp_link; + return 0; + } + else + { + bp_link = &bp->next; + bp = *bp_link; + } + } - orig_bp = stop_bp->breakpoint_to_reinsert; - if (orig_bp == NULL) - error ("no breakpoint to reinsert"); + warning ("Could not find breakpoint in list."); + return ENOENT; +} - (*the_target->write_memory) (orig_bp->pc, breakpoint_data, - breakpoint_len); - orig_bp->reinserting = 0; - delete_breakpoint (stop_bp); +int +delete_breakpoint (struct breakpoint *todel) +{ + struct process_info *proc = current_process (); + return delete_breakpoint_1 (proc, todel); } -void -reinsert_breakpoint_by_bp (CORE_ADDR stop_pc, CORE_ADDR stop_at) +static struct breakpoint * +find_gdb_breakpoint_at (CORE_ADDR where) +{ + struct process_info *proc = current_process (); + struct breakpoint *bp; + + for (bp = proc->breakpoints; bp != NULL; bp = bp->next) + if (bp->type == gdb_breakpoint && bp->raw->pc == where) + return bp; + + return NULL; +} + +int +set_gdb_breakpoint_at (CORE_ADDR where) { - struct breakpoint *bp, *orig_bp; + struct breakpoint *bp; - set_breakpoint_at (stop_at, reinsert_breakpoint_handler); + if (breakpoint_data == NULL) + return 1; + + /* If we see GDB inserting a second breakpoint at the same address, + then the first breakpoint must have disappeared due to a shared + library unload. On targets where the shared libraries are + handled by userspace, like SVR4, for example, GDBserver can't + tell if a library was loaded or unloaded. Since we refcount + breakpoints, if we didn't do this, we'd just increase the + refcount of the previous breakpoint at this address, but the trap + was not planted in the inferior anymore, thus the breakpoint + would never be hit. */ + bp = find_gdb_breakpoint_at (where); + if (bp != NULL) + { + delete_gdb_breakpoint_at (where); - orig_bp = find_breakpoint_at (stop_pc); - if (orig_bp == NULL) - error ("Could not find original breakpoint in list."); + /* Might as well validate all other breakpoints. */ + validate_breakpoints (); + } - bp = find_breakpoint_at (stop_at); + bp = set_breakpoint_at (where, NULL); if (bp == NULL) - error ("Could not find breakpoint in list (reinserting by breakpoint)."); - bp->breakpoint_to_reinsert = orig_bp; + return -1; - (*the_target->write_memory) (orig_bp->pc, orig_bp->old_data, - breakpoint_len); - orig_bp->reinserting = 1; + bp->type = gdb_breakpoint; + return 0; } -void -uninsert_breakpoint (CORE_ADDR stopped_at) +int +delete_gdb_breakpoint_at (CORE_ADDR addr) { struct breakpoint *bp; + int err; + + if (breakpoint_data == NULL) + return 1; - bp = find_breakpoint_at (stopped_at); + bp = find_gdb_breakpoint_at (addr); if (bp == NULL) - error ("Could not find breakpoint in list (uninserting)."); + return -1; + + err = delete_breakpoint (bp); + if (err) + return -1; + + return 0; +} - (*the_target->write_memory) (bp->pc, bp->old_data, - breakpoint_len); - bp->reinserting = 1; +int +gdb_breakpoint_here (CORE_ADDR where) +{ + struct breakpoint *bp = find_gdb_breakpoint_at (where); + + return (bp != NULL); } void -reinsert_breakpoint (CORE_ADDR stopped_at) +set_reinsert_breakpoint (CORE_ADDR stop_at) { struct breakpoint *bp; - bp = find_breakpoint_at (stopped_at); + bp = set_breakpoint_at (stop_at, NULL); + bp->type = reinsert_breakpoint; +} + +void +delete_reinsert_breakpoints (void) +{ + struct process_info *proc = current_process (); + struct breakpoint *bp, **bp_link; + + bp = proc->breakpoints; + bp_link = &proc->breakpoints; + + while (bp) + { + if (bp->type == reinsert_breakpoint) + { + *bp_link = bp->next; + release_breakpoint (proc, bp); + bp = *bp_link; + } + else + { + bp_link = &bp->next; + bp = *bp_link; + } + } +} + +static void +uninsert_raw_breakpoint (struct raw_breakpoint *bp) +{ + if (bp->inserted) + { + int err; + + bp->inserted = 0; + err = (*the_target->write_memory) (bp->pc, bp->old_data, + breakpoint_len); + if (err != 0) + { + bp->inserted = 1; + + if (debug_threads) + fprintf (stderr, + "Failed to uninsert raw breakpoint at 0x%s (%s).\n", + paddress (bp->pc), strerror (err)); + } + } +} + +void +uninsert_breakpoints_at (CORE_ADDR pc) +{ + struct raw_breakpoint *bp; + + bp = find_raw_breakpoint_at (pc); if (bp == NULL) - error ("Could not find breakpoint in list (uninserting)."); - if (! bp->reinserting) + { + /* This can happen when we remove all breakpoints while handling + a step-over. */ + if (debug_threads) + fprintf (stderr, + "Could not find breakpoint at 0x%s " + "in list (uninserting).\n", + paddress (pc)); + return; + } + + if (bp->inserted) + uninsert_raw_breakpoint (bp); +} + +static void +reinsert_raw_breakpoint (struct raw_breakpoint *bp) +{ + int err; + + if (bp->inserted) error ("Breakpoint already inserted at reinsert time."); - (*the_target->write_memory) (bp->pc, breakpoint_data, - breakpoint_len); - bp->reinserting = 0; + err = (*the_target->write_memory) (bp->pc, breakpoint_data, + breakpoint_len); + if (err == 0) + bp->inserted = 1; + else if (debug_threads) + fprintf (stderr, + "Failed to reinsert breakpoint at 0x%s (%s).\n", + paddress (bp->pc), strerror (err)); } -int -check_breakpoints (CORE_ADDR stop_pc) +void +reinsert_breakpoints_at (CORE_ADDR pc) { - struct breakpoint *bp; + struct raw_breakpoint *bp; - bp = find_breakpoint_at (stop_pc); + bp = find_raw_breakpoint_at (pc); if (bp == NULL) - return 0; - if (bp->reinserting) { - warning ("Hit a removed breakpoint?"); - return 0; + /* This can happen when we remove all breakpoints while handling + a step-over. */ + if (debug_threads) + fprintf (stderr, + "Could not find raw breakpoint at 0x%s " + "in list (reinserting).\n", + paddress (pc)); + return; } - (*bp->handler) (bp->pc); - return 1; + reinsert_raw_breakpoint (bp); +} + +void +check_breakpoints (CORE_ADDR stop_pc) +{ + struct process_info *proc = current_process (); + struct breakpoint *bp, **bp_link; + + bp = proc->breakpoints; + bp_link = &proc->breakpoints; + + while (bp) + { + if (bp->raw->pc == stop_pc) + { + if (!bp->raw->inserted) + { + warning ("Hit a removed breakpoint?"); + return; + } + + if (bp->handler != NULL && (*bp->handler) (stop_pc)) + { + *bp_link = bp->next; + + release_breakpoint (proc, bp); + + bp = *bp_link; + continue; + } + } + + bp_link = &bp->next; + bp = *bp_link; + } } void -set_breakpoint_data (const char *bp_data, int bp_len) +set_breakpoint_data (const unsigned char *bp_data, int bp_len) { breakpoint_data = bp_data; breakpoint_len = bp_len; } +int +breakpoint_here (CORE_ADDR addr) +{ + return (find_raw_breakpoint_at (addr) != NULL); +} + +int +breakpoint_inserted_here (CORE_ADDR addr) +{ + struct raw_breakpoint *bp; + + bp = find_raw_breakpoint_at (addr); + + return (bp != NULL && bp->inserted); +} + +static int +validate_inserted_breakpoint (struct raw_breakpoint *bp) +{ + unsigned char *buf; + int err; + + gdb_assert (bp->inserted); + + buf = alloca (breakpoint_len); + err = (*the_target->read_memory) (bp->pc, buf, breakpoint_len); + if (err || memcmp (buf, breakpoint_data, breakpoint_len) != 0) + { + /* Tag it as gone. */ + bp->inserted = 0; + bp->shlib_disabled = 1; + return 0; + } + + return 1; +} + +static void +delete_disabled_breakpoints (void) +{ + struct process_info *proc = current_process (); + struct breakpoint *bp, *next; + + for (bp = proc->breakpoints; bp != NULL; bp = next) + { + next = bp->next; + if (bp->raw->shlib_disabled) + delete_breakpoint_1 (proc, bp); + } +} + +/* Check if breakpoints we inserted still appear to be inserted. They + may disappear due to a shared library unload, and worse, a new + shared library may be reloaded at the same address as the + previously unloaded one. If that happens, we should make sure that + the shadow memory of the old breakpoints isn't used when reading or + writing memory. */ + +void +validate_breakpoints (void) +{ + struct process_info *proc = current_process (); + struct breakpoint *bp; + + for (bp = proc->breakpoints; bp != NULL; bp = bp->next) + { + if (bp->raw->inserted) + validate_inserted_breakpoint (bp->raw); + } + + delete_disabled_breakpoints (); +} + void -check_mem_read (CORE_ADDR mem_addr, char *buf, int mem_len) +check_mem_read (CORE_ADDR mem_addr, unsigned char *buf, int mem_len) { - struct breakpoint *bp = breakpoints; + struct process_info *proc = current_process (); + struct raw_breakpoint *bp = proc->raw_breakpoints; CORE_ADDR mem_end = mem_addr + mem_len; + int disabled_one = 0; for (; bp != NULL; bp = bp->next) { @@ -238,15 +647,26 @@ check_mem_read (CORE_ADDR mem_addr, char *buf, int mem_len) copy_offset = start - bp->pc; buf_offset = start - mem_addr; - memcpy (buf + buf_offset, bp->old_data + copy_offset, copy_len); + if (bp->inserted) + { + if (validate_inserted_breakpoint (bp)) + memcpy (buf + buf_offset, bp->old_data + copy_offset, copy_len); + else + disabled_one = 1; + } } + + if (disabled_one) + delete_disabled_breakpoints (); } void -check_mem_write (CORE_ADDR mem_addr, char *buf, int mem_len) +check_mem_write (CORE_ADDR mem_addr, unsigned char *buf, int mem_len) { - struct breakpoint *bp = breakpoints; + struct process_info *proc = current_process (); + struct raw_breakpoint *bp = proc->raw_breakpoints; CORE_ADDR mem_end = mem_addr + mem_len; + int disabled_one = 0; for (; bp != NULL; bp = bp->next) { @@ -272,7 +692,45 @@ check_mem_write (CORE_ADDR mem_addr, char *buf, int mem_len) buf_offset = start - mem_addr; memcpy (bp->old_data + copy_offset, buf + buf_offset, copy_len); - if (bp->reinserting == 0) - memcpy (buf + buf_offset, breakpoint_data + copy_offset, copy_len); + if (bp->inserted) + { + if (validate_inserted_breakpoint (bp)) + memcpy (buf + buf_offset, breakpoint_data + copy_offset, copy_len); + else + disabled_one = 1; + } } + + if (disabled_one) + delete_disabled_breakpoints (); +} + +/* Delete all breakpoints, and un-insert them from the inferior. */ + +void +delete_all_breakpoints (void) +{ + struct process_info *proc = current_process (); + + while (proc->breakpoints) + delete_breakpoint_1 (proc, proc->breakpoints); +} + +/* Release all breakpoints, but do not try to un-insert them from the + inferior. */ + +void +free_all_breakpoints (struct process_info *proc) +{ + struct raw_breakpoint *raw_bp; + + for (raw_bp = proc->raw_breakpoints; raw_bp != NULL; raw_bp = raw_bp->next) + raw_bp->inserted = 0; + + /* Note: use PROC explicitly instead of deferring to + delete_all_breakpoints --- CURRENT_INFERIOR may already have been + released when we get here. There should be no call to + current_process from here on. */ + while (proc->breakpoints) + delete_breakpoint_1 (proc, proc->breakpoints); }