Re: [rfc/rfa] Use ARM exception tables as GDB unwinder

Ulrich Weigand
2011-03-18T23:01:27Z

Daniel Jacobowitz wrote:
> On Wed, Mar 09, 2011 at 07:43:04PM +0100, Ulrich Weigand wrote:
> > I finally got around to continue working on this. The patch below
> > implements this approach. However, this uncovered one rather fundamental
> > problem: when implementing the vfork system call, the syscall stub
> > must *not* put anything on the stack or else things will break due to
> > the peculiar semantics of vfork:
> >
> > The system will return to execute solely the child at first, until such
> > time as the child calls exec; and in the meantime, the child will share
> > the parent's address space. Now if vfork pushed something on the stack,
> > the child will pop that value, and go on to overwrite that stack location
> > with other stuff. Once the parent later gets to run as well, it will
> > restore a corrupted value into r7 ...
> >
> > I'm not quite sure if there is a way around this ... Any suggestions
> > would be appreciated.
>
> Hmm, I wonder if that's why I did it this way before.
>
> Can we use a different technique to implement vfork than for other
> system calls? I don't see a reason they all need to be the same.

After some back and forth, I've been able to come up with a solution
that works well enough for all GDB tests to pass. It is not completely
perfect since unwinding (on the parent side) out of an interrupted vfork
call works only while the child has not yet clobbered the stack, but I
don't see how to do anything better ...

Patch is appended below. Does this look reasonable to you?

> > As Andreas notes, DOARGS is called with a 0 argument. However, I'm
> > wondering if there isn't a bug anyway:
> >
> > __##syscall_name##_nocancel: \
> > .cfi_sections .debug_frame; \
> > cfi_startproc; \
> > DO_CALL (syscall_name, args); \
> > PSEUDO_RET; \
> > cfi_endproc; \
> > .size __##syscall_name##_nocancel,.-__##syscall_name##_nocancel; \
> >
> > Shouldn't there be a
> > cmn r0, $4096;
> > in between the DO_CALL and the PSEUDO_RET?
>
> It does look that way.
>
> > Also, some of the code in the linuxthreads/ directories seems to be broken,
> > but I'm wondering why those are still there in the first place; linuxthreads
> > support has been removed from glibc a long time ago, hasn't it?
>
> Yes. Those files should just be removed.

OK, thanks. I'll prepare patches for those two issues.

Bye,
Ulrich

diff --git a/sysdeps/unix/sysv/linux/arm/eabi/nptl/sysdep-cancel.h b/sysdeps/unix/sysv/linux/arm/eabi/nptl/sysdep-cancel.h
index 458558b..f4a8af4 100644
--- a/sysdeps/unix/sysv/linux/arm/eabi/nptl/sysdep-cancel.h
+++ b/sysdeps/unix/sysv/linux/arm/eabi/nptl/sysdep-cancel.h
@@ -47,20 +47,20 @@
DOARGS_##args; \
bne .Lpseudo_cancel; \
cfi_remember_state; \
- DO_CALL (syscall_name, 0); \
+ ldr r7, =SYS_ify (syscall_name); \
+ swi 0x0; \
UNDOARGS_##args; \
cmn r0, $4096; \
PSEUDO_RET; \
cfi_restore_state; \
.Lpseudo_cancel: \
- .fnstart; \
+ .fnstart; /* matched by the .fnend in UNDOARGS below. */ \
DOCARGS_##args; /* save syscall args etc. around CENABLE. */ \
CENABLE; \
mov ip, r0; /* put mask in safe place. */ \
UNDOCARGS_##args; /* restore syscall args. */ \
ldr r7, =SYS_ify (syscall_name); \
swi 0x0; /* do the call. */ \
- .fnend; /* Past here we can't easily unwind. */ \
mov r7, r0; /* save syscall return value. */ \
mov r0, ip; /* get mask back. */ \
CDISABLE; \
@@ -69,34 +69,34 @@
UNDOARGS_##args; \
cmn r0, $4096

-/* DOARGS pushes four bytes on the stack for five arguments, eight bytes for
- six arguments, and nothing for fewer. In order to preserve doubleword
+/* DOARGS pushes eight bytes on the stack for five arguments, twelve bytes for
+ six arguments, and four bytes for fewer. In order to preserve doubleword
alignment, sometimes we must save an extra register. */

# define RESTART_UNWIND \
.fnend; \
.fnstart; \
- .save {r7, lr}
+ .save {r7}; \
+ .save {lr}

# define DOCARGS_0 \
- stmfd sp!, {r7, lr}; \
- cfi_adjust_cfa_offset (8); \
- cfi_rel_offset (r7, 0); \
- cfi_rel_offset (lr, 4); \
- .save {r7, lr}
+ .save {r7}; \
+ str lr, [sp, #-4]!; \
+ cfi_adjust_cfa_offset (4); \
+ cfi_rel_offset (lr, 0); \
+ .save {lr}
# define UNDOCARGS_0
# define RESTORE_LR_0 \
- ldmfd sp!, {r7, lr}; \
- cfi_adjust_cfa_offset (-8); \
- cfi_restore (r7); \
+ ldr lr, [sp], #4; \
+ cfi_adjust_cfa_offset (-4); \
cfi_restore (lr)

# define DOCARGS_1 \
- stmfd sp!, {r0, r1, r7, lr}; \
- cfi_adjust_cfa_offset (16); \
- cfi_rel_offset (r7, 8); \
- cfi_rel_offset (lr, 12); \
- .save {r7, lr}; \
+ .save {r7}; \
+ stmfd sp!, {r0, r1, lr}; \
+ cfi_adjust_cfa_offset (12); \
+ cfi_rel_offset (lr, 8); \
+ .save {lr}; \
.pad #8
# define UNDOCARGS_1 \
ldr r0, [sp], #8; \
@@ -106,11 +106,11 @@
RESTORE_LR_0

# define DOCARGS_2 \
- stmfd sp!, {r0, r1, r7, lr}; \
- cfi_adjust_cfa_offset (16); \
- cfi_rel_offset (r7, 8); \
- cfi_rel_offset (lr, 12); \
- .save {r7, lr}; \
+ .save {r7}; \
+ stmfd sp!, {r0, r1, lr}; \
+ cfi_adjust_cfa_offset (12); \
+ cfi_rel_offset (lr, 8); \
+ .save {lr}; \
.pad #8
# define UNDOCARGS_2 \
ldmfd sp!, {r0, r1}; \
@@ -120,11 +120,11 @@
RESTORE_LR_0

# define DOCARGS_3 \
- stmfd sp!, {r0, r1, r2, r3, r7, lr}; \
- cfi_adjust_cfa_offset (24); \
- cfi_rel_offset (r7, 16); \
- cfi_rel_offset (lr, 20); \
- .save {r7, lr}; \
+ .save {r7}; \
+ stmfd sp!, {r0, r1, r2, r3, lr}; \
+ cfi_adjust_cfa_offset (20); \
+ cfi_rel_offset (lr, 16); \
+ .save {lr}; \
.pad #16
# define UNDOCARGS_3 \
ldmfd sp!, {r0, r1, r2, r3}; \
@@ -134,11 +134,11 @@
RESTORE_LR_0

# define DOCARGS_4 \
- stmfd sp!, {r0, r1, r2, r3, r7, lr}; \
- cfi_adjust_cfa_offset (24); \
- cfi_rel_offset (r7, 16); \
- cfi_rel_offset (lr, 20); \
- .save {r7, lr}; \
+ .save {r7}; \
+ stmfd sp!, {r0, r1, r2, r3, lr}; \
+ cfi_adjust_cfa_offset (20); \
+ cfi_rel_offset (lr, 16); \
+ .save {lr}; \
.pad #16
# define UNDOCARGS_4 \
ldmfd sp!, {r0, r1, r2, r3}; \
@@ -149,43 +149,40 @@

/* r4 is only stmfd'ed for correct stack alignment. */
# define DOCARGS_5 \
- .save {r4}; \
- stmfd sp!, {r0, r1, r2, r3, r4, r7, lr}; \
- cfi_adjust_cfa_offset (28); \
- cfi_rel_offset (r7, 20); \
- cfi_rel_offset (lr, 24); \
- .save {r7, lr}; \
+ .save {r4, r7}; \
+ stmfd sp!, {r0, r1, r2, r3, r4, lr}; \
+ cfi_adjust_cfa_offset (24); \
+ cfi_rel_offset (lr, 20); \
+ .save {lr}; \
.pad #20
# define UNDOCARGS_5 \
ldmfd sp!, {r0, r1, r2, r3}; \
cfi_adjust_cfa_offset (-16); \
.fnend; \
.fnstart; \
- .save {r4}; \
- .save {r7, lr}; \
+ .save {r4, r7}; \
+ .save {lr}; \
.pad #4
# define RESTORE_LR_5 \
- ldmfd sp!, {r4, r7, lr}; \
- cfi_adjust_cfa_offset (-12); \
+ ldmfd sp!, {r4, lr}; \
+ cfi_adjust_cfa_offset (-8); \
/* r4 will be marked as restored later. */ \
- cfi_restore (r7); \
cfi_restore (lr)

# define DOCARGS_6 \
- .save {r4, r5}; \
- stmfd sp!, {r0, r1, r2, r3, r7, lr}; \
- cfi_adjust_cfa_offset (24); \
- cfi_rel_offset (r7, 16); \
- cfi_rel_offset (lr, 20); \
- .save {r7, lr}; \
+ .save {r4, r5, r7}; \
+ stmfd sp!, {r0, r1, r2, r3, lr}; \
+ cfi_adjust_cfa_offset (20); \
+ cfi_rel_offset (lr, 16); \
+ .save {lr}; \
.pad #16
# define UNDOCARGS_6 \
ldmfd sp!, {r0, r1, r2, r3}; \
cfi_adjust_cfa_offset (-16); \
.fnend; \
.fnstart; \
- .save {r4, r5}; \
- .save {r7, lr}
+ .save {r4, r5, r7}; \
+ .save {lr};
# define RESTORE_LR_6 \
RESTORE_LR_0

diff --git a/sysdeps/unix/sysv/linux/arm/eabi/sysdep.h b/sysdeps/unix/sysv/linux/arm/eabi/sysdep.h
index b7815ba..a80621e 100644
--- a/sysdeps/unix/sysv/linux/arm/eabi/sysdep.h
+++ b/sysdeps/unix/sysv/linux/arm/eabi/sysdep.h
@@ -106,12 +106,95 @@
#undef DO_CALL
#define DO_CALL(syscall_name, args) \
DOARGS_##args; \
- mov ip, r7; \
- cfi_register (r7, ip); \
ldr r7, =SYS_ify (syscall_name); \
swi 0x0; \
- mov r7, ip; \
- cfi_restore (r7); \
UNDOARGS_##args

+#undef DOARGS_0
+#define DOARGS_0 \
+ .fnstart; \
+ str r7, [sp, #-4]!; \
+ cfi_adjust_cfa_offset (4); \
+ cfi_rel_offset (r7, 0); \
+ .save { r7 }
+#undef DOARGS_1
+#define DOARGS_1 DOARGS_0
+#undef DOARGS_2
+#define DOARGS_2 DOARGS_0
+#undef DOARGS_3
+#define DOARGS_3 DOARGS_0
+#undef DOARGS_4
+#define DOARGS_4 DOARGS_0
+#undef DOARGS_5
+#define DOARGS_5 \
+ .fnstart; \
+ stmfd sp!, {r4, r7}; \
+ cfi_adjust_cfa_offset (8); \
+ cfi_rel_offset (r4, 0); \
+ cfi_rel_offset (r7, 4); \
+ .save { r4, r7 }; \
+ ldr r4, [sp, #8]
+#undef DOARGS_6
+#define DOARGS_6 \
+ .fnstart; \
+ mov ip, sp; \
+ stmfd sp!, {r4, r5, r7}; \
+ cfi_adjust_cfa_offset (12); \
+ cfi_rel_offset (r4, 0); \
+ cfi_rel_offset (r5, 4); \
+ cfi_rel_offset (r7, 8); \
+ .save { r4, r5, r7 }; \
+ ldmia ip, {r4, r5}
+#undef DOARGS_7
+#define DOARGS_7 \
+ .fnstart; \
+ mov ip, sp; \
+ stmfd sp!, {r4, r5, r6, r7}; \
+ cfi_adjust_cfa_offset (16); \
+ cfi_rel_offset (r4, 0); \
+ cfi_rel_offset (r5, 4); \
+ cfi_rel_offset (r6, 8); \
+ cfi_rel_offset (r7, 12); \
+ .save { r4, r5, r6, r7 }; \
+ ldmia ip, {r4, r5, r6}
+
+#undef UNDOARGS_0
+#define UNDOARGS_0 \
+ ldr r7, [sp], #4; \
+ cfi_adjust_cfa_offset (-4); \
+ cfi_restore (r7); \
+ .fnend
+#undef UNDOARGS_1
+#define UNDOARGS_1 UNDOARGS_0
+#undef UNDOARGS_2
+#define UNDOARGS_2 UNDOARGS_0
+#undef UNDOARGS_3
+#define UNDOARGS_3 UNDOARGS_0
+#undef UNDOARGS_4
+#define UNDOARGS_4 UNDOARGS_0
+#undef UNDOARGS_5
+#define UNDOARGS_5 \
+ ldmfd sp!, {r4, r7}; \
+ cfi_adjust_cfa_offset (-8); \
+ cfi_restore (r4); \
+ cfi_restore (r7); \
+ .fnend
+#undef UNDOARGS_6
+#define UNDOARGS_6 \
+ ldmfd sp!, {r4, r5, r7}; \
+ cfi_adjust_cfa_offset (-12); \
+ cfi_restore (r4); \
+ cfi_restore (r5); \
+ cfi_restore (r7); \
+ .fnend
+#undef UNDOARGS_7
+#define UNDOARGS_7 \
+ ldmfd sp!, {r4, r5, r6, r7}; \
+ cfi_adjust_cfa_offset (-16); \
+ cfi_restore (r4); \
+ cfi_restore (r5); \
+ cfi_restore (r6); \
+ cfi_restore (r7); \
+ .fnend
+
#endif /* _LINUX_ARM_EABI_SYSDEP_H */
diff --git a/sysdeps/unix/sysv/linux/arm/vfork.S b/sysdeps/unix/sysv/linux/arm/vfork.S
index a020658..e63690e 100644
--- a/sysdeps/unix/sysv/linux/arm/vfork.S
+++ b/sysdeps/unix/sysv/linux/arm/vfork.S
@@ -33,7 +33,28 @@ ENTRY (__vfork)
#ifdef SAVE_PID
SAVE_PID
#endif
- DO_CALL (vfork, 0)
+#ifdef __ARM_EABI__
+ /* The DO_CALL macro saves r7 on the stack, to enable generation
+ of ARM unwind info. Since the stack is initially shared between
+ parent and child of vfork, that saved value could be corrupted.
+ To avoid this problem, we save r7 into ip as well, and restore
+ from there. */
+ mov ip, r7
+ cfi_register (r7, ip)
+ .fnstart
+ str r7, [sp, #-4]!
+ cfi_adjust_cfa_offset (4)
+ .save { r7 }
+ ldr r7, =SYS_ify (vfork)
+ swi 0x0
+ .fnend
+ add sp, sp, #4
+ cfi_adjust_cfa_offset (-4)
+ mov r7, ip
+ cfi_restore (r7);
+#else
+ swi SYS_ify(vfork)
+#endif
#ifdef RESTORE_PID
RESTORE_PID
#endif