Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 04 May 2013 09:08:19 -0600
From:      Ian Lepore <ian@FreeBSD.org>
To:        Andrew Turner <andrew@fubar.geek.nz>
Cc:        freebsd-arm <freebsd-arm@FreeBSD.org>
Subject:   Re: A fix for the clang + eabi + kdb backtrace endless loop
Message-ID:  <1367680099.1180.162.camel@revolution.hippie.lan>
In-Reply-To: <20130503115141.6c9cd15a@bender>
References:  <1367439452.1180.92.camel@revolution.hippie.lan> <20130503115141.6c9cd15a@bender>

next in thread | previous in thread | raw e-mail | index | archive | help

--=-ln0HrZPFdHK2Vh66Xe3w
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit

On Fri, 2013-05-03 at 11:51 +0100, Andrew Turner wrote:
> On Wed, 01 May 2013 14:17:32 -0600
> Ian Lepore <ian@FreeBSD.org> wrote:
> 
> > The attached patch fixes the problem where a kdb backtrace loops
> > endlessly on an eabi kernel.  
> > 
> > I'm no expert on this stuff, so while this fixes the problem for me,
> > I'm not sure it's right, especially the STOP_UNWINDING in
> > exception_exit (which handles a test case of breaking into the
> > debugger with the keyboard and typing 'bt').  I'm not going to commit
> > this until it's been reviewed by someone who actually knows what
> > they're doing. :)
> STOP_UNWINDING tells binutils to generate the required unwind op-code
> to tell the kernel's unwinder it needs to stop in this function. There
> is a bug with the unwinder where it should tell the user it is in the
> function, however it doesn't currently.
> 
> See below for more comments.

Thanks for the review and info.  I didn't realize FP was unused in EABI.
I guess keying off it just accidentally worked right in the few cases I
tested.  

I was also a bit worried about the recursion case with using the PC to
see if unwinding seemed to be stuck.  I studied the code some more and
discovered that there's a mask of which registers changed, used for
pretty-printing.  I think that's a safer way to determine whether
unwinding is stuck in a loop -- if no registers changed then nothing was
unwound.

I also restructured things a bit so that the decision to stop unwinding
isn't acted on until after the current frame is printed; that seems to
ensure that the frame for a STOP_UNWINDING function gets printed before
exiting the loop.

So, here's a somewhat more clueful patch; how's this look?

-- Ian


--=-ln0HrZPFdHK2Vh66Xe3w
Content-Disposition: inline; filename="eabi_unwind_fixes2.diff"
Content-Type: text/x-patch; name="eabi_unwind_fixes2.diff"; charset="us-ascii"
Content-Transfer-Encoding: 7bit

Index: sys/arm/arm/db_trace.c
===================================================================
--- sys/arm/arm/db_trace.c	(revision 250163)
+++ sys/arm/arm/db_trace.c	(working copy)
@@ -342,8 +342,11 @@ db_stack_trace_cmd(struct unwind_state *state)
 	c_db_sym_t sym;
 	u_int reg, i;
 	char *sep;
+	uint16_t upd_mask;
+	bool finished;
 
-	while (1) {
+	finished = false;
+	while (!finished) {
 		/* Reset the mask of updated registers */
 		state->update_mask = 0;
 
@@ -353,28 +356,20 @@ db_stack_trace_cmd(struct unwind_state *state)
 		/* Find the item to run */
 		index = db_find_index(state->start_pc);
 
-		if (index->insn == EXIDX_CANTUNWIND) {
-			db_printf("Unable to unwind\n");
-			break;
-		} else if (index->insn & (1 << 31)) {
-			/* The data is within the instruction */
-			state->insn = &index->insn;
-		} else {
-			/* We have a prel31 offset to the unwind table */
-			uint32_t prel31_tbl = db_expand_prel31(index->insn);
-
-			state->insn = (uint32_t *)((uintptr_t)&index->insn +
-			    prel31_tbl);
+		if (index->insn != EXIDX_CANTUNWIND) {
+			if (index->insn & (1 << 31)) {
+				/* The data is within the instruction */
+				state->insn = &index->insn;
+			} else {
+				/* A prel31 offset to the unwind table */
+				state->insn = (uint32_t *)
+				    ((uintptr_t)&index->insn + 
+				     db_expand_prel31(index->insn));
+			}
+			/* Run the unwind function */
+			finished = db_unwind_tab(state);
 		}
 
-		/* Run the unwind function */
-		if (db_unwind_tab(state) != 0)
-			break;
-
-		/* This is not a kernel address, stop processing */
-		if (state->registers[PC] < VM_MIN_KERNEL_ADDRESS)
-			break;
-
 		/* Print the frame details */
 		sym = db_search_symbol(state->start_pc, DB_STGY_ANY, &offset);
 		if (sym == C_DB_SYM_NULL) {
@@ -393,12 +388,11 @@ db_stack_trace_cmd(struct unwind_state *state)
 		    state->registers[SP], state->registers[FP]);
 
 		/* Don't print the registers we have already printed */
-		state->update_mask &= ~((1 << SP) | (1 << FP) | (1 << LR) |
-		    (1 << PC));
+		upd_mask = state->update_mask & 
+		    ~((1 << SP) | (1 << FP) | (1 << LR) | (1 << PC));
 		sep = "\n\t";
-		for (i = 0, reg = 0; state->update_mask != 0;
-		    state->update_mask >>= 1, reg++) {
-			if ((state->update_mask & 1) != 0) {
+		for (i = 0, reg = 0; upd_mask != 0; upd_mask >>= 1, reg++) {
+			if ((upd_mask & 1) != 0) {
 				db_printf("%s%sr%d = 0x%08x", sep,
 				    (reg < 10) ? " " : "", reg,
 				    state->registers[reg]);
@@ -412,6 +406,25 @@ db_stack_trace_cmd(struct unwind_state *state)
 			}
 		}
 		db_printf("\n");
+
+		/* Stop if directed to do so, or if we've unwound back to the
+		 * kernel entry point, or if the unwind function didn't change
+		 * anything (to avoid getting stuck in this loop forever).
+		 * If the latter happens, it's an indication that the unwind
+		 * information is incorrect somehow for the function named in
+		 * the last frame printed before you see the unwind failure
+		 * message (maybe it needs a STOP_UNWINDING).
+		 */
+		if (index->insn == EXIDX_CANTUNWIND) {
+			db_printf("Unable to unwind further\n");
+			finished = true;
+		} else if (state->registers[PC] < VM_MIN_KERNEL_ADDRESS) {
+			db_printf("Unable to unwind into user mode\n");
+			finished = true;
+		} else if (state->update_mask == 0) {
+			db_printf("Unwind failure (no registers changed)\n");
+			finished = true;
+		}
 	}
 }
 #endif
Index: sys/arm/arm/exception.S
===================================================================
--- sys/arm/arm/exception.S	(revision 250163)
+++ sys/arm/arm/exception.S	(working copy)
@@ -196,15 +196,19 @@ END(address_exception_entry)
  * Interrupts are disabled at suitable points to avoid ASTs
  * being posted between testing and exit to user mode.
  *
- * This function uses PULLFRAMEFROMSVCANDEXIT and
- * DO_AST
- * only be called if the exception handler used PUSHFRAMEINSVC
+ * This function uses PULLFRAMEFROMSVCANDEXIT and DO_AST and can
+ * only be called if the exception handler used PUSHFRAMEINSVC.
  *
+ * For EABI, don't try to unwind any further than this, because the unwind
+ * info generated here is a single INSN_FINISH instruction.  Nothing gets 
+ * unwound (no registers change) so the unwind would loop here forever.
  */
 
-exception_exit:
+ASENTRY_NP(exception_exit)
+	STOP_UNWINDING
 	DO_AST
 	PULLFRAMEFROMSVCANDEXIT
+END(exception_exit)
 
 /*
  * undefined_entry:
Index: sys/arm/arm/locore.S
===================================================================
--- sys/arm/arm/locore.S	(revision 250163)
+++ sys/arm/arm/locore.S	(working copy)
@@ -77,6 +77,8 @@ __FBSDID("$FreeBSD$");
  */
 ENTRY_NP(btext)
 ASENTRY_NP(_start)
+	STOP_UNWINDING		/* Can't unwind into the bootloader! */
+
 	mov	r9, r0		/* 0 or boot mode from boot2 */
 	mov	r8, r1		/* Save Machine type */
 	mov	ip, r2		/* Save meta data */
Index: sys/arm/arm/swtch.S
===================================================================
--- sys/arm/arm/swtch.S	(revision 250163)
+++ sys/arm/arm/swtch.S	(working copy)
@@ -540,6 +540,7 @@ ENTRY(savectx)
 END(savectx)
 
 ENTRY(fork_trampoline)
+	STOP_UNWINDING	/* Can't unwind beyond the thread enty point */
 	mov	r1, r5
 	mov	r2, sp
 	mov	r0, r4

--=-ln0HrZPFdHK2Vh66Xe3w--




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?1367680099.1180.162.camel>