Debugger stepping does not understand recursion (bug 38025)
authorAndi McClure <andi.mcclure@xamarin.com>
Mon, 23 May 2016 23:04:06 +0000 (19:04 -0400)
committerAndi McClure <andi.mcclure@xamarin.com>
Mon, 23 May 2016 23:04:06 +0000 (19:04 -0400)
Testing with the debugger showed multiple failures around recursive
functions:

1. Step-out breakpoints can trigger on inner frames.
Solution: Extend existing inner-frame test from step-over to step out
also.

2. "Same source line, continuing" test triggers even on wrong frame
   (affects both step over and step out)
Solution: Check frame as part of this test

3. When step breakpoints pick next sequence points, they can register
   multiple breakpoints at the same place if a recursive call happens to
   exit there. (Possibly harmless but may have resulted in duplicate
   debugger events being generated in one test.)
Solution: Check for duplicates in step-request breakpoint list before
adding new breakpoints

4. Mono.Debugging.Soft in the debugger-libs repo sometimes resumes
   after breakpoints if it decides Mono.Debugger.Soft has not stepped
   far enough, but the check to trigger this was not recursion-aware.
Solution: This one is not addressed in this commit, but a fix was
checked in to debugger-libs. Any code using this commit therefore
needs to be run in conjunction with debugger-libs dfbf6d8b13.

Tests were also added for issues 1-3 above.

mcs/class/Mono.Debugger.Soft/Test/dtest-app.cs
mcs/class/Mono.Debugger.Soft/Test/dtest.cs
mono/mini/debugger-agent.c

index 150738e6633c1a6e9473953105245bb0872e069f..e86615c1c3055d8cbb50cf52d5ddd90e9b4bcd3d 100644 (file)
@@ -419,6 +419,9 @@ public class Tests : TestsBase, ITest2
                ss_step_through ();
                ss_non_user_code ();
                ss_recursive (1);
+               ss_recursive2 (1);
+               ss_recursive2 (1);
+               ss_recursive_chaotic ();
                ss_fp_clobber ();
        }
 
@@ -568,6 +571,92 @@ public class Tests : TestsBase, ITest2
                ss_recursive (n + 1);
        }
 
+       // Breakpoint will be placed here
+       [MethodImplAttribute (MethodImplOptions.NoInlining)]
+       public static void ss_recursive2_trap ()
+       {
+       }
+
+       public static void ss_recursive2_at (string s)
+       {
+               // Console.WriteLine (s);
+       }
+
+       // This method is used both for a step over and step out test.
+       [MethodImplAttribute (MethodImplOptions.NoInlining)]
+       public static void ss_recursive2 (int x)
+       {
+               ss_recursive2_at ( "ss_recursive2 in " + x);
+               if (x < 10) {
+                       int next = x + 1;
+                       ss_recursive2_at ("ss_recursive2 descend " + x);
+                       ss_recursive2_trap ();
+                       ss_recursive2 (next);
+               }
+               ss_recursive2_at ("ss_recursive2 out " + x);
+       }
+
+       // Breakpoint will be placed here
+       [MethodImplAttribute (MethodImplOptions.NoInlining)]
+       public static void ss_recursive_chaotic_trap ()
+       {
+       }
+
+       [MethodImplAttribute (MethodImplOptions.NoInlining)]
+       public static void ss_recursive_chaotic_at (bool exiting, string at, int n)
+       {
+//             string indent = "";
+//             for (int count = 12 - n; count > 0; count--)
+//                     indent += "\t";
+//             Console.WriteLine (indent + (exiting ? "<--" : "-->") + " " + at + " " + n);
+       }
+
+       [MethodImplAttribute (MethodImplOptions.NoInlining)]
+       public static void ss_recursive_chaotic_fizz (int n)
+       {
+               ss_recursive_chaotic_at (false, "fizz", n);
+               if (n > 0) {
+                       int next = n - 1;
+                       ss_recursive_chaotic_buzz (next);
+                       ss_recursive_chaotic_fizzbuzz (next);
+               } else {
+                       ss_recursive_chaotic_trap ();
+               }
+               ss_recursive_chaotic_at (true, "fizz", n);
+       }
+
+       [MethodImplAttribute (MethodImplOptions.NoInlining)]
+       public static void ss_recursive_chaotic_buzz (int n)
+       {
+               ss_recursive_chaotic_at (false, "buzz", n);
+               if (n > 0) {
+                       int next = n - 1;
+                       ss_recursive_chaotic_fizz (next);
+                       ss_recursive_chaotic_fizzbuzz (next);
+               }
+               ss_recursive_chaotic_at (true, "buzz", n);
+       }
+
+       [MethodImplAttribute (MethodImplOptions.NoInlining)]
+       public static void ss_recursive_chaotic_fizzbuzz (int n)
+       {
+               ss_recursive_chaotic_at (false, "fizzbuzz", n);
+               if (n > 0) {
+                       int next = n - 1;
+                       ss_recursive_chaotic_fizz (next);
+                       ss_recursive_chaotic_buzz (next);
+                       ss_recursive_chaotic_fizzbuzz (next);
+               }
+               ss_recursive_chaotic_at (true, "fizzbuzz", n);
+       }
+
+       // Call a complex tree of recursive calls that has tripped up "step out" in the past.
+       [MethodImplAttribute (MethodImplOptions.NoInlining)]
+       public static void ss_recursive_chaotic ()
+       {
+               ss_recursive_chaotic_fizz (12);
+       }
+
        [MethodImplAttribute (MethodImplOptions.NoInlining)]
        public static void ss_fp_clobber () {
                double v = ss_fp_clobber_1 (5.0);
index 086c6f5f9aa92214c1c9e00bf2ee9d8b98968484..81f91de103c66de18c6db9443f07b9d8e3948364 100644 (file)
@@ -42,6 +42,17 @@ public class DebuggerTests
        public static string runtime = Environment.GetEnvironmentVariable ("DBG_RUNTIME");
        public static string agent_args = Environment.GetEnvironmentVariable ("DBG_AGENT_ARGS");
 
+       // Not currently used, but can be useful when debugging individual tests.
+       void StackTraceDump (Event e)
+       {
+               int i = 0;
+               foreach (var frame in e.Thread.GetFrames ())
+               {
+                       i++;
+                       Console.WriteLine ("Frame " + i + ", " + frame.Method.Name);
+               }
+       }
+
        Event GetNextEvent () {
                var es = vm.GetNextEventSet ();
                Assert.AreEqual (1, es.Events.Length);
@@ -124,6 +135,140 @@ public class DebuggerTests
                return (e as BreakpointEvent);
        }
 
+       class ReusableBreakpoint {
+               DebuggerTests owner;
+               public string method_name;
+               public BreakpointEventRequest req;
+               public BreakpointEvent lastEvent = null;
+               public ReusableBreakpoint (DebuggerTests owner, string method_name)
+               {
+                       this.owner = owner;
+                       this.method_name = method_name;
+                       MethodMirror m = owner.entry_point.DeclaringType.GetMethod (method_name);
+                       Assert.IsNotNull (m);
+                       req = owner.vm.SetBreakpoint (m, m.ILOffsets [0]);
+               }
+
+               public void Continue ()
+               {
+                       bool survived = false;
+
+                       try {
+                               Event e = null;
+
+                               while (true) {
+                                       owner.vm.Resume ();
+                                       e = owner.GetNextEvent ();
+                                       if (e is BreakpointEvent)
+                                               break;
+                               }
+
+                               Assert.IsInstanceOfType (typeof (BreakpointEvent), e);
+                               Assert.AreEqual (method_name, (e as BreakpointEvent).Method.Name);
+
+                               lastEvent = e as BreakpointEvent;
+
+                               survived = true;
+                       } finally {
+                               if (!survived) { // Ensure cleanup if we triggered an assert
+                                       Disable ();
+                               }
+                       }
+               }
+
+               public void Disable ()
+               {
+                       req.Disable ();
+               }
+       }
+
+       /* One of the tests executes a complex tree of recursive functions.
+          The only good way to specify how its behavior should appear from this side
+          is to just run the function tree once over here and record what it does. */
+       public struct RecursiveChaoticPoint
+       {
+               public bool breakpoint;
+               public string name;
+               public int depth;
+
+               public RecursiveChaoticPoint (bool breakpoint, string name, int depth)
+               {
+                       this.breakpoint = breakpoint;
+                       this.name = name;
+                       this.depth = depth;
+               }
+       }
+
+       // The breakpoint is placed here in dtest-app.cs
+       public static void ss_recursive_chaotic_trap (int n, List<RecursiveChaoticPoint> trace, ref bool didLast, ref bool didAny)
+       {
+               // Depth is calculated as:
+               // Main + single_stepping + ss_recursive_chaotic + (n is 12 at outermost frame and 0 at innermost frame) + ss_recursive_chaotic_trap
+               trace.Add (new RecursiveChaoticPoint (true, "ss_recursive_chaotic_trap", 12 - n + 5));
+               didLast = true;
+       }
+
+       public static void ss_recursive_chaotic_at (string at, int n, List<RecursiveChaoticPoint> trace, ref bool didLast, ref bool didAny)
+       {
+               // This will be called after every return from a function. The other function will return whether "step out" is currently active, and it will be passed in here as didLast.
+               if (didLast) {
+                       // Depth is calculated as:
+                       // Main + single_stepping + ss_recursive_chaotic + (n is 12 at outermost frame and 0 at innermost frame)
+                       trace.Add (new RecursiveChaoticPoint (false, "ss_recursive_chaotic_" + at, 12 - n + 4));
+                       didAny = true;
+                       didLast = false;
+               }
+       }
+
+       public static bool ss_recursive_chaotic_fizz (int n, List<RecursiveChaoticPoint> trace)
+       {
+               bool didLast = false, didAny = false;
+               if (n > 0) {
+                       int next = n - 1;
+                       didLast = ss_recursive_chaotic_buzz (next, trace);
+                       ss_recursive_chaotic_at ("fizz", n, trace, ref didLast, ref didAny);
+                       didLast = ss_recursive_chaotic_fizzbuzz (next, trace);
+                       ss_recursive_chaotic_at ("fizz", n, trace, ref didLast, ref didAny);
+               } else {
+                       ss_recursive_chaotic_trap (n, trace, ref didLast, ref didAny);
+                       ss_recursive_chaotic_at ("fizz", n, trace, ref didLast, ref didAny);
+               }
+               return didAny;
+       }
+
+       public static bool ss_recursive_chaotic_buzz (int n, List<RecursiveChaoticPoint> trace)
+       {
+               bool didLast = false, didAny = false;
+               if (n > 0) {
+                       int next = n - 1;
+                       didLast = ss_recursive_chaotic_fizz (next, trace);
+                       ss_recursive_chaotic_at ("buzz", n, trace, ref didLast, ref didAny);
+                       didLast = ss_recursive_chaotic_fizzbuzz (next, trace);
+                       ss_recursive_chaotic_at ("buzz", n, trace, ref didLast, ref didAny);
+               }
+               return didAny;
+       }
+
+       public static bool ss_recursive_chaotic_fizzbuzz (int n, List<RecursiveChaoticPoint> trace)
+       {
+               bool didLast = false, didAny = false;
+               if (n > 0) {
+                       int next = n - 1;
+                       didLast = ss_recursive_chaotic_fizz (next, trace);
+                       ss_recursive_chaotic_at ("fizzbuzz", n, trace, ref didLast, ref didAny);
+                       didLast = ss_recursive_chaotic_buzz (next, trace);
+                       ss_recursive_chaotic_at ("fizzbuzz", n, trace, ref didLast, ref didAny);
+                       didLast = ss_recursive_chaotic_fizzbuzz (next, trace);
+                       ss_recursive_chaotic_at ("fizzbuzz", n, trace, ref didLast, ref didAny);
+               }
+               return didAny;
+       }
+
+       public static void trace_ss_recursive_chaotic (List<RecursiveChaoticPoint> trace)
+       {
+               ss_recursive_chaotic_fizz (12, trace);
+       }
+
        Event single_step (ThreadMirror t) {
                var req = vm.CreateStepRequest (t);
                req.Enable ();
@@ -372,11 +517,28 @@ public class DebuggerTests
                Assert.AreEqual (m2.Name, (e as BreakpointEvent).Method.Name);
        }
 
+       // Assert we have stepped to a location
        void assert_location (Event e, string method) {
                Assert.IsTrue (e is StepEvent);
                Assert.AreEqual (method, (e as StepEvent).Method.Name);
        }
 
+       // Assert we have breakpointed at a location
+       void assert_location_at_breakpoint (Event e, string method) {
+               Assert.IsTrue (e is BreakpointEvent);
+               Assert.AreEqual (method, (e as BreakpointEvent).Method.Name);
+       }
+
+       // Assert we have stepped to or breakpointed at a location
+       void assert_location_allow_breakpoint (Event e, string method) {
+               if (e is StepEvent)
+                       Assert.AreEqual (method, (e as StepEvent).Method.Name);
+               else if (e is BreakpointEvent)
+                       Assert.AreEqual (method, (e as BreakpointEvent).Method.Name);
+               else
+                       Assert.Fail ("Neither step nor breakpoint event");
+       }
+
        StepEventRequest create_step (Event e) {
                var req = vm.CreateStepRequest (e.Thread);
                step_req = req;
@@ -624,6 +786,95 @@ public class DebuggerTests
                AssertValue (1, f.GetValue (f.Method.GetLocal ("n")));
                req.Disable ();
 
+               // Check that step-over stops correctly when inner frames with recursive functions contain breakpoints
+               e = run_until ("ss_recursive2");
+               ReusableBreakpoint breakpoint = new ReusableBreakpoint (this, "ss_recursive2_trap");
+               try {
+                       breakpoint.Continue ();
+                       e = breakpoint.lastEvent;
+                       req = create_step (e);
+                       for (int c = 1; c <= 9; c++) {
+                               // The first ten times we try to step over this function, the breakpoint will stop us
+                               assert_location_at_breakpoint (e, "ss_recursive2_trap");
+
+                               req.Disable ();
+                               req = create_step (e);
+                               req.Size = StepSize.Line;
+
+                               e = step_out ();
+                               assert_location (e, "ss_recursive2");
+
+                               // Stack should consist of Main + single_stepping + (1 ss_recursive2 frame per loop iteration)
+                               Assert.AreEqual (c+2, e.Thread.GetFrames ().Length);
+                               e = step_over_or_breakpoint ();
+                       }
+                       // At this point we should have escaped the breakpoints and this will be a normal step stop
+                       assert_location (e, "ss_recursive2");
+                       Assert.AreEqual (11, e.Thread.GetFrames ().Length);
+               } finally {
+                       req.Disable ();
+                       breakpoint.Disable ();
+               }
+
+               // Check that step-out stops correctly when inner frames with recursive functions contain breakpoints
+               e = run_until ("ss_recursive2");
+               breakpoint = new ReusableBreakpoint (this, "ss_recursive2_trap");
+               try {
+                       breakpoint.Continue ();
+                       e = breakpoint.lastEvent;
+                       req = create_step (e);
+                       for (int c = 1; c <= 9; c++) {
+                               // The first ten times we try to step over this function, the breakpoint will stop us
+                               assert_location_at_breakpoint (e, "ss_recursive2_trap");
+
+                               req.Disable ();
+                               req = create_step (e);
+                               req.Size = StepSize.Line;
+
+                               e = step_out ();
+                               assert_location (e, "ss_recursive2");
+
+                               // Stack should consist of Main + single_stepping + (1 ss_recursive2 frame per loop iteration)
+                               Assert.AreEqual (c+2, e.Thread.GetFrames ().Length);
+                               e = step_out_or_breakpoint ();
+                       }
+                       for (int c = 8; c >= 1; c--) {
+                               assert_location (e, "ss_recursive2");
+                               Assert.AreEqual (c + 2, e.Thread.GetFrames ().Length);
+
+                               e = step_out ();
+                       }
+               } finally {
+                       req.Disable ();
+                       breakpoint.Disable ();
+               }
+
+               // Test step out with a really complicated call tree
+               List<RecursiveChaoticPoint> trace = new List<RecursiveChaoticPoint>();
+               trace_ss_recursive_chaotic (trace);
+               e = run_until ("ss_recursive_chaotic");
+               try {
+                       breakpoint = new ReusableBreakpoint (this, "ss_recursive_chaotic_trap");
+                       breakpoint.Continue ();
+                       e = breakpoint.lastEvent;
+                       foreach (RecursiveChaoticPoint point in trace)
+                       {
+                               if (point.breakpoint)
+                                       assert_location_at_breakpoint (e, point.name);
+                               else
+                                       assert_location (e, point.name);
+                               Assert.AreEqual (point.depth, e.Thread.GetFrames ().Length);
+
+                               req.Disable ();
+                               req = create_step (e);
+                               req.Size = StepSize.Line;
+                               e = step_out_or_breakpoint ();
+                       }
+               } finally {
+                       req.Disable ();
+                       breakpoint.Disable ();
+               }
+
                // Check that single stepping doesn't clobber fp values
                e = run_until ("ss_fp_clobber");
                req = create_step (e);
@@ -1625,6 +1876,27 @@ public class DebuggerTests
                return step_once ();
        }
 
+       Event step_once_or_breakpoint () {
+               vm.Resume ();
+               var e = GetNextEvent ();
+               Assert.IsTrue (e is StepEvent || e is BreakpointEvent);
+               return e;
+       }
+
+       Event step_over_or_breakpoint () {
+               step_req.Disable ();
+               step_req.Depth = StepDepth.Over;
+               step_req.Enable ();
+               return step_once_or_breakpoint ();
+       }
+
+       Event step_out_or_breakpoint () {
+               step_req.Disable ();
+               step_req.Depth = StepDepth.Out;
+               step_req.Enable ();
+               return step_once_or_breakpoint ();
+       }
+
        [Test]
        public void Locals () {
                var be = run_until ("locals1");
index e5906b18d89aeee6d1819fd5a1693030d00b9ccd..336e20d3bdab7e3ac941d60e0b91cf747aba06a2 100644 (file)
@@ -4505,6 +4505,18 @@ clear_breakpoints_for_domain (MonoDomain *domain)
        mono_loader_unlock ();
 }
 
+/*
+ * ss_calculate_framecount:
+ *
+ * Ensure DebuggerTlsData fields are filled out.
+ */
+static void ss_calculate_framecount(DebuggerTlsData *tls, MonoContext *ctx)
+{
+       if (!tls->context.valid)
+               mono_thread_state_init_from_monoctx (&tls->context, ctx);
+       compute_frame_info (tls->thread, tls);
+}
+
 /*
  * ss_update:
  *
@@ -4526,22 +4538,24 @@ ss_update (SingleStepReq *req, MonoJitInfo *ji, SeqPoint *sp, DebuggerTlsData *t
                return FALSE;
        }
 
-       if (req->depth == STEP_DEPTH_OVER && hit) {
-               if (!tls->context.valid)
-                       mono_thread_state_init_from_monoctx (&tls->context, ctx);
-               compute_frame_info (tls->thread, tls);
-               if (req->nframes && tls->frame_count && tls->frame_count > req->nframes) {
-                       /* Hit the breakpoint in a recursive call */
-                       DEBUG_PRINTF (1, "[%p] Breakpoint at lower frame while stepping over, continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get ());
+       if ((req->depth == STEP_DEPTH_OVER || req->depth == STEP_DEPTH_OUT) && hit) {
+               gboolean is_step_out = req->depth == STEP_DEPTH_OUT;
+
+               ss_calculate_framecount(tls, ctx);
+
+               // Because functions can call themselves recursively, we need to make sure we're stopping at the right stack depth.
+               // In case of step out, the target is the frame *enclosing* the one where the request was made.
+               int target_frames = req->nframes + (is_step_out ? -1 : 0);
+               if (req->nframes > 0 && tls->frame_count > 0 && tls->frame_count > target_frames) {
+                       /* Hit the breakpoint in a recursive call, don't halt */
+                       DEBUG_PRINTF (1, "[%p] Breakpoint at lower frame while stepping %s, continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), is_step_out ? "out" : "over");
                        return FALSE;
                }
        }
 
        if (req->depth == STEP_DEPTH_INTO && req->size == STEP_SIZE_MIN && (sp->flags & MONO_SEQ_POINT_FLAG_NONEMPTY_STACK) && ss_req->start_method){
                method = jinfo_get_method (ji);
-               if (!tls->context.valid)
-                       mono_thread_state_init_from_monoctx (&tls->context, ctx);
-               compute_frame_info (tls->thread, tls);
+               ss_calculate_framecount(tls, ctx);
                if (ss_req->start_method == method && req->nframes && tls->frame_count == req->nframes) {//Check also frame count(could be recursion)
                        DEBUG_PRINTF (1, "[%p] Seq point at nonempty stack %x while stepping in, continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), sp->il_offset);
                        return FALSE;
@@ -4563,8 +4577,11 @@ ss_update (SingleStepReq *req, MonoJitInfo *ji, SeqPoint *sp, DebuggerTlsData *t
                ss_req->last_method = method;
                hit = FALSE;
        } else if (loc && method == ss_req->last_method && loc->row == ss_req->last_line) {
-               DEBUG_PRINTF (1, "[%p] Same source line (%d), continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), loc->row);
-               hit = FALSE;
+               ss_calculate_framecount(tls, ctx);
+               if (tls->frame_count == req->nframes) { // If the frame has changed we're clearly not on the same source line.
+                       DEBUG_PRINTF (1, "[%p] Same source line (%d), continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), loc->row);
+                       hit = FALSE;
+               }
        }
                                
        if (loc) {
@@ -5070,6 +5087,25 @@ ss_stop (SingleStepReq *ss_req)
        }
 }
 
+/*
+ * ss_start:
+ *
+ * Check if breakpoint would collide with one already in list. Print debug trace before returning if so.
+ */
+static gboolean ss_bp_is_duplicate(GSList *bps, MonoMethod *method, guint32 il_offset)
+{
+       GSList *l;
+       for (l = bps; l; l = l->next) {
+               MonoBreakpoint *bp = (MonoBreakpoint *)l->data;
+               if (bp->method == method && bp->il_offset == il_offset) {
+                       DEBUG_PRINTF (1, "[dbg] Candidate breakpoint at %s:[il=0x%x] is a duplicate for this step request, will not add.\n", mono_method_full_name (method, TRUE), (int)il_offset);
+                       return TRUE;
+               }
+       }
+       return FALSE;
+}
+
+
 /*
  * ss_start:
  *
@@ -5167,8 +5203,10 @@ ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint* sp, MonoSeqPointI
                        for (i = 0; i < sp->next_len; i++) {
                                next_sp = &next[i];
 
-                               bp = set_breakpoint (method, next_sp->il_offset, ss_req->req, NULL);
-                               ss_req->bps = g_slist_append (ss_req->bps, bp);
+                               if (!ss_bp_is_duplicate(ss_req->bps, method, next_sp->il_offset)) {
+                                       bp = set_breakpoint (method, next_sp->il_offset, ss_req->req, NULL);
+                                       ss_req->bps = g_slist_append (ss_req->bps, bp);
+                               }
                        }
                        g_free (next);
                }
@@ -5180,8 +5218,10 @@ ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint* sp, MonoSeqPointI
                        for (i = 0; i < parent_sp->next_len; i++) {
                                next_sp = &next[i];
 
-                               bp = set_breakpoint (parent_sp_method, next_sp->il_offset, ss_req->req, NULL);
-                               ss_req->bps = g_slist_append (ss_req->bps, bp);
+                               if (!ss_bp_is_duplicate(ss_req->bps, parent_sp_method, next_sp->il_offset)) {
+                                       bp = set_breakpoint (parent_sp_method, next_sp->il_offset, ss_req->req, NULL);
+                                       ss_req->bps = g_slist_append (ss_req->bps, bp);
+                               }
                        }
                        g_free (next);
                }
@@ -5212,7 +5252,7 @@ ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint* sp, MonoSeqPointI
 
                                                found_sp = mono_find_next_seq_point_for_native_offset (frame->domain, frame->method, (char*)ei->handler_start - (char*)jinfo->code_start, NULL, &local_sp);
                                                sp = (found_sp)? &local_sp : NULL;
-                                               if (sp) {
+                                               if (sp && !ss_bp_is_duplicate(ss_req->bps, frame->method, sp->il_offset)) {
                                                        bp = set_breakpoint (frame->method, sp->il_offset, ss_req->req, NULL);
                                                        ss_req->bps = g_slist_append (ss_req->bps, bp);
                                                }