DDD and GDB breakpoints having multiple locations (C++ constructors, templates etc.)

Peter Wainwright <[email protected]> Sat, 14 Feb 2009 20:06:12 +0000
Newsgroups gmane.comp.debugging.ddd.bugs
Message-ID <[email protected]>
I noticed that recent versions of GDB have a modified "info breakpoints"
display which can display multiple addresses for one breakpoint.  This
occurs, for example, in constructors, templates, inline functions (since
I use a lot of C++ this is an itch I have to scratch).


https://savannah.gnu.org/bugs/?25510


I've produced a patch which addresses some of these issues.  If anyone
can improve it, I'd like to hear from you.

Peter

_______________________________________________
bug-ddd mailing list
[email protected]
http://lists.gnu.org/mailman/listinfo/bug-ddd
ddd-multi-bp.patch (text/x-patch, 28.9 KB)
Index: ddd/SourceView.C
===================================================================
--- ddd/SourceView.C	(revision 8026)
+++ ddd/SourceView.C	(working copy)
@@ -967,6 +967,10 @@
     {
 	if (address.contains('*', 0))
 	{
+	    if (bp->n_locations() > 1) {
+		post_warning("Cannot move multiple BP in code window");
+		return false;
+	    }
 	    if (compare_address(address.after('*'), bp->address()) == 0)
 		return false;	// Breakpoint already at address
 	}
@@ -1194,7 +1198,8 @@
 	{
 	    BreakPoint *bp = bp_map.get(nrs[i]);
 	    if (bp != 0)
-		gdb_command(clear_command(bp->pos()));
+		for (int j = 0; j < bp->n_locations(); j++)
+		    gdb_command(clear_command(bp->get_location(j).pos()));
 	}
     }
     else if (gdb->has_delete_command())
@@ -1203,26 +1208,31 @@
     }
     else
     {
-        for (int i = 0; i < nrs.size(); i++)
-	    gdb_command(delete_command(nrs[i]));
+        for (int i = 0; i < nrs.size(); i++) {
+	    std::vector<string> delcmds = delete_commands(nrs[i]);
+	    for (unsigned j = 0; j < delcmds.size(); j++)
+		gdb_command(delcmds[j]);
+	}
     }
 }
 
 // A generic deletion command for breakpoint BP_NR - either `clear' or `delete'
-string SourceView::delete_command(int bp_nr)
+std::vector<string> SourceView::delete_commands(int bp_nr)
 {
+    std::vector<string> cmds;
     if (gdb->has_delete_command())
     {
-	return gdb->delete_command(itostring(bp_nr));
+	cmds.push_back(gdb->delete_command(itostring(bp_nr)));
     }
     else if (gdb->has_clear_command())
     {
 	BreakPoint *bp = bp_map.get(bp_nr);
-	if (bp != 0)
-	    return clear_command(bp->pos());
+	if (bp != 0) {
+	    for (int j = 0; j < bp->n_locations(); j++)
+		cmds.push_back(clear_command(bp->get_location(j).pos()));
+	}
     }
-
-    return "";			// No way to delete a breakpoint (*sigh*)
+    return cmds;
 }
 
 // Return `clear ARG' command.  If CLEAR_NEXT is set, attempt to guess
@@ -1366,10 +1376,19 @@
 {
     int bp_nr = *((int *)client_data);
     BreakPoint *bp = bp_map.get(bp_nr);
-    if (bp != 0 && !bp->address().empty())
+    if (bp != 0)
     {
-	string address = string('*') + bp->address();
-	line_popup_set_pcCB(w, XtPointer(&address), call_data);
+	if (bp->n_locations() > 1)
+	{
+	    post_warning("BP has multiple locations, PC is ambiguous");
+	    return;
+	}
+	string addr = bp->address();
+	if (!addr.empty())
+	{
+	    string address = string('*') + addr;
+	    line_popup_set_pcCB(w, XtPointer(&address), call_data);
+	}
     }
 }
 
@@ -1581,14 +1600,17 @@
 
 bool SourceView::bp_matches(BreakPoint *bp, const string& file, int line)
 {
+    int i;
     switch (bp->type())
     {
     case BREAKPOINT:
     case ACTIONPOINT:
     case TRACEPOINT:
-	return (line == 0 || bp->line_nr() == line) &&
-	    (bp->file_name().empty() || file_matches(bp->file_name(), file));
-
+	for (i = 0; i < bp->n_locations(); i++) {
+	    BreakPointLocn &locn = bp->get_location(i);
+	    if (bp_matches(locn, file, line)) return true;
+	}
+	return false;
     case WATCHPOINT:
 	return false;
     }
@@ -1596,6 +1618,19 @@
     return false;		// Never reached
 }
 
+// Check if BP occurs in the current source text
+bool SourceView::bp_matches(BreakPointLocn &locn, int line)
+{
+    return bp_matches(locn, current_source_name(), line) || 
+	bp_matches(locn, current_file_name, line);
+}
+
+bool SourceView::bp_matches(BreakPointLocn &locn, const string& file, int line)
+{
+    return ((line == 0 || locn.line_nr() == line) &&
+	    (locn.file_name().empty() || file_matches(locn.file_name(), file)));
+}
+
 // ***************************************************************************
 //
 
@@ -1747,9 +1782,16 @@
 		     bp != 0;
 		     bp = bp_map.next(ref))
 		{
-		    bp->selected() = 
-			(bp->type() == BREAKPOINT && 
-			 compare_address(pos, bp->address()) == 0);
+		    int i;
+		    bp->selected() = false;
+		    for (i = 0; i < bp->n_locations(); i++) {
+			BreakPointLocn &locn = bp->get_location(i);
+			if (bp->type() == BREAKPOINT && 
+			    compare_address(pos, locn.address()) == 0) {
+			    bp->selected() = true;
+			    break;
+			}
+		    }
 		}
 	    }
 	}
@@ -2844,6 +2886,7 @@
 	if ((bp->type() == BREAKPOINT || bp->type() == TRACEPOINT) && 
 	    bp_matches(bp))
 	{
+	    // ASSUME: multi-location breakpoints all have same source line
 	    bps_in_line[bp->line_nr()] += bp->number();
 	}
     }
@@ -2931,7 +2974,8 @@
 	if (bp->type() != BREAKPOINT)
 	    continue;
 
-	bp_addresses += bp->address();
+	for (i = 0; i < bp->n_locations(); i++)
+	    bp_addresses += bp->get_location(i).address();
     }
 
     // Process all bp_addresses
@@ -2948,8 +2992,9 @@
 	     bp != 0;
 	     bp = bp_map.next(ref))
 	{
-	    if (bp->address() == address)
-		insert_string += bp->symbol();
+	    for (int j = 0; j < bp->n_locations(); j++)
+		if (bp->get_location(j).address() == address)
+		    insert_string += bp->symbol();
 	}
 
 	int indent = indent_amount(code_text_w, pos);
@@ -3005,14 +3050,17 @@
 	     bp != 0;
 	     bp = bp_map.next(ref))
 	{
-	    if (w == bp->source_glyph() || w == bp->code_glyph())
-	    {
-		// Breakpoint glyph found
-		line_nr = bp->line_nr();
-		address = bp->address();
-		in_text = false;
-		bp_nr   = bp->number();
-		return true;
+	    for (int i = 0; i < bp->n_locations(); i++) {
+		BreakPointLocn &locn = bp->get_location(i);
+		if (w == locn.source_glyph() || w == locn.code_glyph())
+		{
+		    // Breakpoint glyph found
+		    line_nr = locn.line_nr();
+		    address = locn.address();
+		    in_text = false;
+		    bp_nr   = bp->number();
+		    return true;
+		}
 	    }
 	}
     }
@@ -3127,8 +3175,9 @@
 		     bp != 0;
 		     bp = bp_map.next(ref))
 		{
-		    if (compare_address(address, bp->address()) == 0)
-			bps += bp->number();
+		    for (int i = 0; i < bp->n_locations(); i++)
+			if (compare_address(address, bp->get_location(i).address()) == 0)
+			    bps += bp->number();
 		}
 		if (bps.size() == 1)
 		{
@@ -4185,8 +4234,10 @@
 		{
 		    // To undo this change, we must delete the old
 		    // breakpoint and create a new one.
-		    undo_commands << delete_command(bp->number()) << "\n"
-				  << string(old_state);
+		    std::vector<string> delcmds = delete_commands(bp->number());
+		    for (unsigned i = 0; i < delcmds.size(); i++)
+			undo_commands << delcmds[i] << "\n";
+		    undo_commands << string(old_state);
 		}
 		else
 		{
@@ -4210,7 +4261,9 @@
 	    }
 	    else
 	    {
-		undo_commands << delete_command(bp_nr) << '\n';
+		std::vector<string> delcmds = delete_commands(bp_nr);
+		for (unsigned i = 0; i < delcmds.size(); i++)
+		    undo_commands << delcmds[i] << '\n';
 	    }
 
 	    if (!added)
@@ -5487,9 +5540,12 @@
 		 bp != 0;
 		 bp = bp_map.next(ref))
 	    {
-		if (bp->type() == BREAKPOINT && 
-		    compare_address(address, bp->address()) == 0)
-		    bps += bp->number();
+		for (int i = 0; i < bp->n_locations(); i++) {
+		    BreakPointLocn &locn = bp->get_location(i);
+		    if (bp->type() == BREAKPOINT && 
+			compare_address(address, locn.address()) == 0)
+			bps += bp->number();
+		}
 	    }
 	}
 
@@ -6594,7 +6650,8 @@
 	{
 	    // Replace breakpoint by new one with command.
 	    const string cmd = "b " + 
-		bp->file_name() + ":" + itostring(bp->line_nr()) + 
+		bp->file_name() + ":" + 
+		itostring(bp->line_nr()) + 
 		" {" + action + "}";
 	    gdb_command(cmd, origin);
 	    delete_bp(bp->number(), origin);
@@ -6847,6 +6904,7 @@
 	return;
 
     strip_space(info_breakpoints_output);
+    info_breakpoints_output.gsub("\t", "        ");
     if (info_breakpoints_output.empty())
     {
 	if (gdb->has_watch_command())
@@ -8815,60 +8873,63 @@
 		 bp != 0;
 		 bp = bp_map.next(ref))
 	    {
-		if (bp->type() != BREAKPOINT)
-		    continue;
-
-		Widget& bp_glyph = k ? bp->code_glyph() : bp->source_glyph();
-		Widget text_w    = k ? code_text_w      : source_text_w;
-		bp_glyph = 0;
-
-		XmTextPosition pos;
-		if (k == 0)
-		{
-		    // Find source position
-		    if (!bp_matches(bp)
-			|| line_count <= 0
-			|| bp->line_nr() <= 0
-			|| bp->line_nr() > line_count)
+		for (int i = 0; i < bp->n_locations(); i++) {
+		    BreakPointLocn &locn = bp->get_location(i);
+		    if (bp->type() != BREAKPOINT)
 			continue;
 
-		    pos = pos_of_line(bp->line_nr());
-		}
-		else
-		{
-		    // Find code position
-		    pos = find_pc(bp->address());
-		}
+		    Widget& bp_glyph = k ? locn.code_glyph() : locn.source_glyph();
+		    Widget text_w    = k ? code_text_w      : source_text_w;
+		    bp_glyph = 0;
 
-		if (bp->dispo() != BPKEEP)
-		{
-		    // Temporary breakpoint
-		    if (bp->enabled())
-			bp_glyph = map_stop_at(text_w, pos, plain_temps[k],
-					       plain_temps_count, positions);
-		    else
-			bp_glyph = map_stop_at(text_w, pos, grey_temps[k],
-					       grey_temps_count, positions);
-		}
-		else if (!bp->condition().empty() || bp->ignore_count() != 0)
-		{
-		    // Conditional breakpoint
-		    if (bp->enabled())
-			bp_glyph = map_stop_at(text_w, pos, plain_conds[k],
-					       plain_conds_count, positions);
+		    XmTextPosition pos;
+		    if (k == 0)
+		    {
+			// Find source position
+			if (!bp_matches(bp)
+			    || line_count <= 0
+			    || locn.line_nr() <= 0
+			    || locn.line_nr() > line_count)
+			    continue;
+
+			pos = pos_of_line(locn.line_nr());
+		    }
 		    else
-			bp_glyph = map_stop_at(text_w, pos, grey_conds[k],
-					       grey_conds_count, positions);
-		}
-		else
-		{
-		    // Ordinary breakpoint
-		    if (bp->enabled())
-			bp_glyph = map_stop_at(text_w, pos, plain_stops[k],
-					       plain_stops_count, positions);
+		    {
+			// Find code position
+			pos = find_pc(locn.address());
+		    }
+
+		    if (bp->dispo() != BPKEEP)
+		    {
+			// Temporary breakpoint
+			if (bp->enabled())
+			    bp_glyph = map_stop_at(text_w, pos, plain_temps[k],
+						   plain_temps_count, positions);
+			else
+			    bp_glyph = map_stop_at(text_w, pos, grey_temps[k],
+						   grey_temps_count, positions);
+		    }
+		    else if (!bp->condition().empty() || bp->ignore_count() != 0)
+		    {
+			// Conditional breakpoint
+			if (bp->enabled())
+			    bp_glyph = map_stop_at(text_w, pos, plain_conds[k],
+						   plain_conds_count, positions);
+			else
+			    bp_glyph = map_stop_at(text_w, pos, grey_conds[k],
+						   grey_conds_count, positions);
+		    }
 		    else
-			bp_glyph = map_stop_at(text_w, pos, grey_stops[k],
-					       grey_stops_count, positions);
+		    {
+			// Ordinary breakpoint
+			if (bp->enabled())
+			    bp_glyph = map_stop_at(text_w, pos, plain_stops[k],
+						   plain_stops_count, positions);
+			else
+			    bp_glyph = map_stop_at(text_w, pos, grey_stops[k],
+						   grey_stops_count, positions);
+		    }
 		}
 	    }
 	}
@@ -9167,19 +9228,30 @@
     unmap_drag_stop(text_w);
     unmap_drag_arrow(text_w);
 
-    current_drag_origin     = glyph;
-    current_drag_breakpoint = 0;
-
     // Check for breakpoint
     MapRef ref;
     for (BreakPoint *bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
     {
-	if (glyph == bp->source_glyph() || glyph == bp->code_glyph())
-	{
-	    current_drag_breakpoint = bp->number();
-	    break;
+	for (int i = 0; i < bp->n_locations(); i++) {
+	    BreakPointLocn &locn = bp->get_location(i);
+	    if (glyph == locn.source_glyph() || glyph == locn.code_glyph())
+	    {
+		if (glyph == locn.code_glyph() && bp->n_locations() > 1) {
+		    // Cannot drag a breakpoint in code window if it has
+		    // multiple locations.  FIXME: such glyphs should be
+		    // visually distinguished.
+		    current_drag_origin     = NULL;
+		    current_drag_breakpoint = 0;
+		    return;
+		}
+		current_drag_origin     = glyph;
+		current_drag_breakpoint = bp->number();
+		return;
+	    }
 	}
     }
+    current_drag_origin     = glyph;
+    current_drag_breakpoint = 0;
 }
 
 void SourceView::followGlyphAct(Widget glyph, XEvent *e, String *, Cardinal *)
@@ -9421,9 +9493,15 @@
     MapRef ref;
     for (BreakPoint *bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
     {
-	if (glyph == bp->source_glyph() || glyph == bp->code_glyph())
-	{
-	    bps += bp->number();
+	for (int i = 0; i < bp->n_locations(); i++) {
+	    BreakPointLocn &locn = bp->get_location(i);
+	    if (glyph == locn.source_glyph() || glyph == locn.code_glyph())
+	    {
+		// Cannot delete individual locations.
+		if (glyph == locn.code_glyph() && bp->n_locations() > 1)
+		    continue;
+		bps += bp->number();
+	    }
 	}
     }
 
@@ -9874,8 +9952,10 @@
     BreakPoint *bp = bp_map.get(num);
     if (bp == 0)
 	return "";
-    else
+    else {
+	std::cerr << "FIXME: SourceView::bp_pos: only returns first pos\n";
 	return bp->pos();
+    }
 }
 
 
@@ -10017,6 +10097,8 @@
 	for (BreakPoint *bp = bp_map.first(ref); bp != 0; 
 	     bp = bp_map.next(ref))
 	{
+	    // For gdb we use the delete command.
+	    // So if we get here this is a simple breakpoint.
 	    Command c(clear_command(bp->pos()));
 	    c.verbose  = false;
 	    c.prompt   = false;
Index: ddd/SourceView.h
===================================================================
--- ddd/SourceView.h	(revision 8026)
+++ ddd/SourceView.h	(working copy)
@@ -46,6 +47,8 @@
 #ifndef _DDD_SourceView_h
 #define _DDD_SourceView_h
 
+#include <vector>
+
 // Motif includes
 #include <Xm/Xm.h>
 
@@ -328,6 +331,12 @@
     // True iff breakpoint BP is in FILE (at LINE, if given)
     static bool bp_matches(BreakPoint *bp, const string& file, int line = 0);
 
+    // True iff breakpoint location locn is in current file (at LINE, if given)
+    static bool bp_matches(BreakPointLocn &locn, int line = 0);
+
+    // True iff breakpoint location locn is in FILE (at LINE, if given)
+    static bool bp_matches(BreakPointLocn &locn, const string& file, int line = 0);
+
     // True iff FILE1 is equal to FILE2
     static bool file_matches(const string& file1, const string& file2);
 
@@ -829,7 +838,7 @@
     static string clear_command(string arg, bool clear_next = false,
 				int first_bp = 0);
     // Return `delete N' command.
-    static string delete_command(int bp_nr);
+    static std::vector<string> delete_commands(int bp_nr);
 
     // Return `{ COMMAND; }'
     static string command_list(const string& cmd);
Index: ddd/BreakPoint.C
===================================================================
--- ddd/BreakPoint.C	(revision 8026)
+++ ddd/BreakPoint.C	(working copy)
@@ -51,10 +52,12 @@
 #include "regexps.h"
 #include "index.h"
 #include "value-read.h"
+#include "post.h"
 
 #if RUNTIME_REGEX
 static regex rxnl_int ("\n[1-9]");
 static regex rxname_colon_int_nl ("[^ ]+:[0-9]+\n");
+static regex rxint_dot_int ("[0-9]+\.[0-9]+");
 #endif
 
 // Create new breakpoint from INFO_OUTPUT
@@ -64,10 +67,6 @@
       mytype(BREAKPOINT),
       mydispo(BPKEEP),
       myenabled(true),
-      myfile_name(file),
-      myline_nr(0),
-      myaddress(""),
-      myfunc(""),
       myexpr(""),
       myinfos(""),
       myignore_count(0),
@@ -79,10 +78,9 @@
       myfile_changed(true),
       myposition_changed(true),
       myaddress_changed(true),
-      myselected(false),
-      mysource_glyph(0),
-      mycode_glyph(0)
+      myselected(false)
 {
+    locn.resize(1);
     if (gdb->has_numbered_breakpoints())
     {
 	// Read leading breakpoint number
@@ -199,15 +197,23 @@
     }
     info_output = info_output.after(rxblanks_or_tabs);
 
-    string new_info = "";
-    if (mytype == BREAKPOINT) 
+    // Check for multiple breakpoints
+
+    bool multiple = false;
+    if (info_output.contains("<MULTIPLE>", 0)) {
+	info_output = info_output.after('\n');
+	multiple = true;
+    }
+
+    if (mytype == BREAKPOINT && !multiple) 
     {
+	locn.resize(1);
         if (MAKE != gdb->type() && BASH != gdb->type())
 	{
 	    // Read address
-	    myaddress = info_output.through(rxalphanum);
+	    locn[0].myaddress = info_output.through(rxalphanum);
 	  
-	    info_output = info_output.after(myaddress);
+	    info_output = info_output.after(locn[0].myaddress);
 	    strip_leading_space(info_output);
 	    
 	}
@@ -223,7 +229,7 @@
 		  func = func.before(" at ");
 	      strip_space(func);
 
-	      myfunc = func;
+	      locn[0].myfunc = func;
 	  }
 	}
 
@@ -240,11 +246,11 @@
 	}
 
  	remainder = remainder.from(rxname_colon_int_nl);
- 	myfile_name = remainder.before(":");
+ 	locn[0].myfile_name = remainder.before(":");
 
  	remainder = remainder.after(":");
  	if (!remainder.empty() && isdigit(remainder[0]))
- 	    myline_nr = get_positive_nr(remainder);
+ 	    locn[0].myline_nr = get_positive_nr(remainder);
     }
     else if (mytype == WATCHPOINT)
     {
@@ -256,6 +262,7 @@
     int ignore_count = 0;
     string cond      = "";
     StringArray commands;
+    string new_info = "";
 
     if (!info_output.empty() && !isdigit(info_output[0]))
     {
@@ -324,6 +331,72 @@
 	delete[] lines;
     }
 
+    if (mytype == BREAKPOINT && multiple) 
+    {
+	if (GDB != gdb->type()) {
+	    post_warning("Detected multiple breakpoint, but this is not GDB");
+	    return;
+	}
+	int numlocs = 0;
+	while (!info_output.empty() && info_output.contains(rxint_dot_int, 0)) {
+	    locn.resize(numlocs+1);
+
+	    // Read address
+	    info_output = info_output.after(rxint_dot_int);
+	    strip_leading_space(info_output);
+
+	    // Read enabled flag (`y' or `n')
+	    // We discard this: I don't think GDB allows these flags to
+	    // be set individually.
+	    bool myenabled2;
+	    if (info_output.contains('y', 0))
+		myenabled2 = true;
+	    else if (info_output.contains('n', 0))
+		myenabled2 = false;
+	    info_output = info_output.after(rxblanks_or_tabs);
+
+	    // Read address
+	    locn[numlocs].myaddress = info_output.through(rxalphanum);
+	    info_output = info_output.after(locn[numlocs].myaddress);
+	    strip_leading_space(info_output);
+
+	    // Read function name
+	    if (info_output.contains("in ", 0))
+	    {
+		// Function name
+		string func2 = info_output.after("in ");
+		if (func2.contains('\n'))
+		    func2 = func2.before('\n');
+		if (func2.contains(" at "))
+		    func2 = func2.before(" at ");
+		strip_space(func2);
+		locn[numlocs].myfunc = func2;
+	    }
+
+	    // Read location
+	    string remainder = info_output.through('\n');
+	    info_output = info_output.after('\n');
+
+	    // GDB 5.0 may issue an (indented) file name in the following line
+	    if (!remainder.contains(rxname_colon_int_nl))
+	    {
+		remainder += info_output.through('\n');
+		if (remainder.contains(rxname_colon_int_nl))
+		    info_output = info_output.after('\n');
+	    }
+
+	    remainder = remainder.from(rxname_colon_int_nl);
+	    locn[numlocs].myfile_name = remainder.before(":");
+
+	    remainder = remainder.after(":");
+	    if (!remainder.empty() && isdigit(remainder[0]))
+		locn[numlocs].myline_nr = get_positive_nr(remainder);
+
+	    numlocs++;
+	}
+    }
+
+
     myinfos = new_info;
     myignore_count = ignore_count;
     mycondition = cond;
@@ -381,13 +454,13 @@
 		new_line_nr = get_positive_nr(info_output);
 
 	    if (!file_name.empty())
-		myfile_name = file_name;
+		locn[0].myfile_name = file_name;
 
 	    if (new_line_nr != 0)
-		myline_nr = new_line_nr;
+		locn[0].myline_nr = new_line_nr;
 
 	    // DBX issues either locations or functions
-	    myfunc = "";
+	    locn[0].myfunc = "";
 	}
 	else if (info_output.contains ("in ", 0))
 	{
@@ -400,37 +473,37 @@
 		// Ladebug output:
 		// `PC==x in TYPE FUNC(ARGS...) "FILE":LINE { COMMANDS }
 
-		myfile_name = line.after("\"");
-		myfile_name = myfile_name.before("\"");
-		myline_nr   = get_positive_nr(line.after("\":"));
-		myfunc      = line.before("\"");
-		strip_space(myfunc);
+		locn[0].myfile_name = line.after("\"");
+		locn[0].myfile_name = locn[0].myfile_name.before("\"");
+		locn[0].myline_nr   = get_positive_nr(line.after("\":"));
+		locn[0].myfunc      = line.before("\"");
+		strip_space(locn[0].myfunc);
 
 		// Be sure to remove TYPE
-		while (myfunc.contains(" "))
-		    myfunc = myfunc.after(" ");
+		while (locn[0].myfunc.contains(" "))
+		    locn[0].myfunc = locn[0].myfunc.after(" ");
 	    }
 	    else
 	    {
 		// DBX output:
 		// `stop in FUNC'
-		myfunc = line.before(rxblanks_or_tabs);
-		strip_space(myfunc);
+		locn[0].myfunc = line.before(rxblanks_or_tabs);
+		strip_space(locn[0].myfunc);
 
-		myfile_name = "";
-		myline_nr = 0;
+		locn[0].myfile_name = "";
+		locn[0].myline_nr = 0;
 
 		// Attempt to get exact position of FUNC
-		const string pos = dbx_lookup(myfunc);
+		const string pos = dbx_lookup(locn[0].myfunc);
 		if (!pos.empty())
 		{
 		    const string file_name = pos.before(":");
 		    const string line_s    = pos.after(":");
 		    int new_line_nr  = get_positive_nr(line_s);
 
-		    myfile_name = file_name;
+		    locn[0].myfile_name = file_name;
 		    if (new_line_nr != 0)
-			myline_nr = new_line_nr;
+			locn[0].myline_nr = new_line_nr;
 		}
 	    }
 	}
@@ -523,16 +596,16 @@
 
     // Get function name and position
     info_output = info_output.after(rxblanks_or_tabs);
-    myfunc = info_output.before(": ");
+    locn[0].myfunc = info_output.before(": ");
 
-    const string pos = dbx_lookup(myfunc);
+    const string pos = dbx_lookup(locn[0].myfunc);
     if (!pos.empty())
     {
-	myfile_name = pos.before(":");
+	locn[0].myfile_name = pos.before(":");
     }
 
     info_output = info_output.after(": ");
-    myline_nr = get_positive_nr(info_output);
+    locn[0].myline_nr = get_positive_nr(info_output);
 
     info_output = info_output.after('\n');
 
@@ -570,8 +643,8 @@
 	    if (last_space > 0)
 		class_name = class_name.after(last_space);
 
-	    myfile_name = class_name;
-	    myline_nr   = line_no;
+	    locn[0].myfile_name = class_name;
+	    locn[0].myline_nr   = line_no;
 
 	    // Kill this line
 	    int beginning_of_line = colon;
@@ -603,14 +676,14 @@
 	if (first_line.contains(':', -1))
 	{
 	    // Get leading file name
-	    myfile_name = first_line.before(':');
+	    locn[0].myfile_name = first_line.before(':');
 	    info_output = info_output.after('\n');
 	}
     }
 
     static const StringArray empty;
     mycommands = empty;
-    myline_nr = atoi(info_output.chars());
+    locn[0].myline_nr = atoi(info_output.chars());
     info_output = info_output.after('\n');
     bool break_seen = false;
     while (info_output.contains("  ", 0))
@@ -740,28 +813,35 @@
 
     if (type() == BREAKPOINT)
     {
-	if (new_bp.address() != address())
+	// FIXME: I don't believe any of these can be reached for GDB.
+	// If I'm wrong then we will need to be more careful because
+	// the breakpoint could have multiple locations.
+	if (new_bp.locn[0].address() != locn[0].address())
 	{
+	    std::cerr << "\007**** BREAKPOINT ADDRESS CHANGED\007\n";
 	    changed = myaddress_changed = true;
-	    myaddress = new_bp.address();
+	    locn[0].myaddress = new_bp.locn[0].address();
 	}
 
-	if (new_bp.func() != func())
+	if (new_bp.locn[0].func() != locn[0].func())
 	{
+	    std::cerr << "\007**** BREAKPOINT FUNCTION CHANGED\007\n";
 	    changed = myposition_changed = true;
-	    myfunc = new_bp.func();
+	    locn[0].myfunc = new_bp.locn[0].func();
 	}
 
-	if (new_bp.file_name() != file_name())
+	if (new_bp.locn[0].file_name() != locn[0].file_name())
 	{
+	    std::cerr << "\007**** BREAKPOINT FILENAME CHANGED\007\n";
 	    changed = myposition_changed = myfile_changed = true;
-	    myfile_name = new_bp.file_name();
+	    locn[0].myfile_name = new_bp.locn[0].file_name();
 	}
 
-	if (new_bp.line_nr() != line_nr())
+	if (new_bp.locn[0].line_nr() != locn[0].line_nr())
 	{
+	    std::cerr << "\007**** BREAKPOINT LINE CHANGED\007\n";
 	    changed = myposition_changed = true;
-	    myline_nr = new_bp.line_nr();
+	    locn[0].myline_nr = new_bp.locn[0].line_nr();
 	}
     }
     else if (type() == WATCHPOINT)
@@ -823,7 +903,7 @@
 // Resources
 //-----------------------------------------------------------------------------
 
-string BreakPoint::pos() const
+string BreakPointLocn::pos() const
 {
     if (line_nr() == 0)
 	return "*" + address();
@@ -833,6 +913,11 @@
 	return file_name() + ":" + itostring(line_nr());
 }
 
+string BreakPoint::pos() const
+{
+    return locn[0].pos();
+}
+
 string BreakPoint::symbol() const
 {
     char c;
@@ -1032,10 +1117,10 @@
 {
     if (pos.empty())
     { 
-	if (line_nr() > 0)
-	    pos = file_name() + ":" + itostring(line_nr());
+	if (locn[0].line_nr() > 0)
+	    pos = locn[0].file_name() + ":" + itostring(locn[0].line_nr());
 	else
-	    pos = string('*') + address();
+	    pos = string('*') + locn[0].address();
     }
 
     if (cond == char(-1))
@@ -1118,9 +1203,9 @@
 	switch (type())
 	{
 	case BREAKPOINT:
-	    if (!func().empty())
+	    if (!locn[0].func().empty())
 	    {
-		os << "stop in " << func() << "\n";
+		os << "stop in " << locn[0].func() << "\n";
 	    }
 	    else if (pos.contains('*', 0))
 	    {
Index: ddd/BreakPoint.h
===================================================================
--- ddd/BreakPoint.h	(revision 8026)
+++ ddd/BreakPoint.h	(working copy)
@@ -32,6 +33,8 @@
 // A `BreakPoint' stores information about an existing debugger breakpoint.
 //-----------------------------------------------------------------------------
 
+#include <vector>
+
 #include <X11/Intrinsic.h>
 
 // Misc includes
@@ -70,15 +73,43 @@
     BPDIS			// Disable (`enable once' in GDB)
 };
 
+class BreakPoint;
+
+class BreakPointLocn {
+    string  myfile_name;	// File name
+    int     myline_nr;		// Line number
+    string  myaddress;		// Address in memory
+    string  myfunc;		// Function name
+    Widget  mysource_glyph;	// Associated glyph in source
+    Widget  mycode_glyph;	// Associated glyph in code
+public:
+    BreakPointLocn():
+	myfile_name(""),
+	myline_nr(0),
+	myaddress(""),
+	myfunc(""),
+	mysource_glyph(0),
+	mycode_glyph(0)
+    {
+    }
+    // Breakpoint position
+    const string& file_name() const { return myfile_name; }
+    int line_nr() const             { return myline_nr; }
+    const string& address() const   { return myaddress; }
+    string pos() const;
+    const string& func() const      { return myfunc; }
+    // Associated glyphs in source and machine code
+    Widget& source_glyph() { return mysource_glyph; }
+    Widget& code_glyph()   { return mycode_glyph; }
+    friend class BreakPoint;
+};
+
 class BreakPoint {
     int     mynumber;		// Breakpoint number
     BPType  mytype;		// Type, as above
     BPDispo mydispo;		// Disposition, as above
     bool    myenabled;		// Is breakpoint enabled?
-    string  myfile_name;	// File name
-    int     myline_nr;		// Line number
-    string  myaddress;		// Address in memory
-    string  myfunc;		// Function name
+    std::vector<BreakPointLocn> locn;
     string  myexpr;		// Expression to watch (for watchpoints)
     string  myinfos;		// Additional information (human-readable)
     int     myignore_count;	// Ignore count
@@ -93,9 +124,6 @@
     bool    myaddress_changed;	// True if address changed
     bool    myselected;		// True if selected
 
-    Widget  mysource_glyph;	// Associated glyph in source
-    Widget  mycode_glyph;	// Associated glyph in code
-
 private:
     BreakPoint(const BreakPoint&);
     BreakPoint& operator = (const BreakPoint&);
@@ -140,15 +168,21 @@
     // What to do when breakpoint is reached.
     BPDispo dispo() const { return mydispo; }
 
+    // Number of items (GDB has breakpoints at multiple locations)
+    int n_locations() const { return locn.size(); }
+
+    // Get numbered locn
+    BreakPointLocn &get_location(int i) { return locn[i]; }
+
     // Whether breakpoint is enabled
     bool enabled() const;
 
-    // Breakpoint position
-    const string& file_name() const { return myfile_name; }
-    int line_nr() const             { return myline_nr; }
-    const string& address() const   { return myaddress; }
+    // Breakpoint position (for simple breakpoints)
+    const string& file_name() const { return locn[0].myfile_name; }
+    int line_nr() const             { return locn[0].myline_nr; }
+    const string& address() const   { return locn[0].myaddress; }
     string pos() const;
-    const string& func() const      { return myfunc; }
+    const string& func() const      { return locn[0].myfunc; }
 
     // Watchpoint info
     const string& expr() const   { return myexpr; }
@@ -167,10 +201,6 @@
     // Selection state
     bool& selected() { return myselected; }
 
-    // Associated glyphs in source and machine code
-    Widget& source_glyph() { return mysource_glyph; }
-    Widget& code_glyph()   { return mycode_glyph; }
-
     // True iff `enabled' status changed
     bool enabled_changed () const { return myenabled_changed; }