Commit adf1423698f00d00b267f7dca8231340ce7d65ef

Authored by Jan Beulich
Committed by Andi Kleen
1 parent ab2e0b46cb

[PATCH] i386/x86-64: Work around gcc bug with noreturn functions in unwinder

Current gcc generates calls not jumps to noreturn functions. When that happens the
return address can point to the next function, which confuses the unwinder.

This patch works around it by marking asynchronous exception
frames in contrast normal call frames in the unwind information.  Then teach
the unwinder to decode this.

For normal call frames the unwinder now subtracts one from the address which avoids
this problem.  The standard libgcc unwinder uses the same trick.

It doesn't include adjustment of the printed address (i.e. for the original
example, it'd still be kernel_math_error+0 that gets displayed, but the
unwinder wouldn't get confused anymore.

This only works with binutils 2.6.17+ and some versions of H.J.Lu's 2.6.16
unfortunately because earlier binutils don't support .cfi_signal_frame

[AK: added automatic detection of the new binutils and wrote description]

Signed-off-by: Jan Beulich <jbeulich@novell.com>
Signed-off-by: Andi Kleen <ak@suse.de>

Showing 11 changed files with 73 additions and 9 deletions Side-by-side Diff

... ... @@ -50,6 +50,10 @@
50 50 cflags-y += $(call as-instr,.cfi_startproc\n.cfi_endproc,-DCONFIG_AS_CFI=1,)
51 51 AFLAGS += $(call as-instr,.cfi_startproc\n.cfi_endproc,-DCONFIG_AS_CFI=1,)
52 52  
  53 +# is .cfi_signal_frame supported too?
  54 +cflags-y += $(call as-instr,.cfi_startproc\n.cfi_endproc,-DCONFIG_AS_CFI=1,)
  55 +AFLAGS += $(call as-instr,.cfi_startproc\n.cfi_endproc,-DCONFIG_AS_CFI=1,)
  56 +
53 57 CFLAGS += $(cflags-y)
54 58  
55 59 # Default subarch .c files
arch/i386/kernel/entry.S
... ... @@ -183,18 +183,21 @@
183 183  
184 184 #define RING0_INT_FRAME \
185 185 CFI_STARTPROC simple;\
  186 + CFI_SIGNAL_FRAME;\
186 187 CFI_DEF_CFA esp, 3*4;\
187 188 /*CFI_OFFSET cs, -2*4;*/\
188 189 CFI_OFFSET eip, -3*4
189 190  
190 191 #define RING0_EC_FRAME \
191 192 CFI_STARTPROC simple;\
  193 + CFI_SIGNAL_FRAME;\
192 194 CFI_DEF_CFA esp, 4*4;\
193 195 /*CFI_OFFSET cs, -2*4;*/\
194 196 CFI_OFFSET eip, -3*4
195 197  
196 198 #define RING0_PTREGS_FRAME \
197 199 CFI_STARTPROC simple;\
  200 + CFI_SIGNAL_FRAME;\
198 201 CFI_DEF_CFA esp, OLDESP-EBX;\
199 202 /*CFI_OFFSET cs, CS-OLDESP;*/\
200 203 CFI_OFFSET eip, EIP-OLDESP;\
... ... @@ -275,6 +278,7 @@
275 278 # sysenter call handler stub
276 279 ENTRY(sysenter_entry)
277 280 CFI_STARTPROC simple
  281 + CFI_SIGNAL_FRAME
278 282 CFI_DEF_CFA esp, 0
279 283 CFI_REGISTER esp, ebp
280 284 movl TSS_sysenter_esp0(%esp),%esp
arch/x86_64/Makefile
... ... @@ -58,6 +58,10 @@
58 58 cflags-y += $(call as-instr,.cfi_startproc\n.cfi_endproc,-DCONFIG_AS_CFI=1,)
59 59 AFLAGS += $(call as-instr,.cfi_startproc\n.cfi_endproc,-DCONFIG_AS_CFI=1,)
60 60  
  61 +# is .cfi_signal_frame supported too?
  62 +cflags-y += $(call as-instr,.cfi_startproc\n.cfi_signal_frame\n.cfi_endproc,-DCONFIG_AS_CFI_SIGNAL_FRAME=1,)
  63 +AFLAGS += $(call as-instr,.cfi_startproc\n.cfi_signal_frame\n.cfi_endproc,-DCONFIG_AS_CFI_SIGNAL_FRAME=1,)
  64 +
61 65 cflags-$(CONFIG_CC_STACKPROTECTOR) += $(shell $(CONFIG_SHELL) $(srctree)/scripts/gcc-x86_64-has-stack-protector.sh $(CC) -fstack-protector )
62 66 cflags-$(CONFIG_CC_STACKPROTECTOR_ALL) += $(shell $(CONFIG_SHELL) $(srctree)/scripts/gcc-x86_64-has-stack-protector.sh $(CC) -fstack-protector-all )
63 67  
arch/x86_64/ia32/ia32entry.S
... ... @@ -71,6 +71,7 @@
71 71 */
72 72 ENTRY(ia32_sysenter_target)
73 73 CFI_STARTPROC32 simple
  74 + CFI_SIGNAL_FRAME
74 75 CFI_DEF_CFA rsp,0
75 76 CFI_REGISTER rsp,rbp
76 77 swapgs
... ... @@ -186,6 +187,7 @@
186 187 */
187 188 ENTRY(ia32_cstar_target)
188 189 CFI_STARTPROC32 simple
  190 + CFI_SIGNAL_FRAME
189 191 CFI_DEF_CFA rsp,PDA_STACKOFFSET
190 192 CFI_REGISTER rip,rcx
191 193 /*CFI_REGISTER rflags,r11*/
... ... @@ -293,6 +295,7 @@
293 295  
294 296 ENTRY(ia32_syscall)
295 297 CFI_STARTPROC simple
  298 + CFI_SIGNAL_FRAME
296 299 CFI_DEF_CFA rsp,SS+8-RIP
297 300 /*CFI_REL_OFFSET ss,SS-RIP*/
298 301 CFI_REL_OFFSET rsp,RSP-RIP
... ... @@ -370,6 +373,7 @@
370 373 popq %r11
371 374 CFI_ENDPROC
372 375 CFI_STARTPROC32 simple
  376 + CFI_SIGNAL_FRAME
373 377 CFI_DEF_CFA rsp,SS+8-ARGOFFSET
374 378 CFI_REL_OFFSET rax,RAX-ARGOFFSET
375 379 CFI_REL_OFFSET rcx,RCX-ARGOFFSET
arch/x86_64/kernel/entry.S
... ... @@ -123,6 +123,7 @@
123 123 .macro CFI_DEFAULT_STACK start=1
124 124 .if \start
125 125 CFI_STARTPROC simple
  126 + CFI_SIGNAL_FRAME
126 127 CFI_DEF_CFA rsp,SS+8
127 128 .else
128 129 CFI_DEF_CFA_OFFSET SS+8
... ... @@ -207,6 +208,7 @@
207 208  
208 209 ENTRY(system_call)
209 210 CFI_STARTPROC simple
  211 + CFI_SIGNAL_FRAME
210 212 CFI_DEF_CFA rsp,PDA_STACKOFFSET
211 213 CFI_REGISTER rip,rcx
212 214 /*CFI_REGISTER rflags,r11*/
... ... @@ -324,6 +326,7 @@
324 326 */
325 327 ENTRY(int_ret_from_sys_call)
326 328 CFI_STARTPROC simple
  329 + CFI_SIGNAL_FRAME
327 330 CFI_DEF_CFA rsp,SS+8-ARGOFFSET
328 331 /*CFI_REL_OFFSET ss,SS-ARGOFFSET*/
329 332 CFI_REL_OFFSET rsp,RSP-ARGOFFSET
... ... @@ -484,6 +487,7 @@
484 487 */
485 488 .macro _frame ref
486 489 CFI_STARTPROC simple
  490 + CFI_SIGNAL_FRAME
487 491 CFI_DEF_CFA rsp,SS+8-\ref
488 492 /*CFI_REL_OFFSET ss,SS-\ref*/
489 493 CFI_REL_OFFSET rsp,RSP-\ref
include/asm-i386/dwarf2.h
... ... @@ -28,8 +28,14 @@
28 28 #define CFI_RESTORE_STATE .cfi_restore_state
29 29 #define CFI_UNDEFINED .cfi_undefined
30 30  
  31 +#ifdef CONFIG_AS_CFI_SIGNAL_FRAME
  32 +#define CFI_SIGNAL_FRAME .cfi_signal_frame
31 33 #else
  34 +#define CFI_SIGNAL_FRAME
  35 +#endif
32 36  
  37 +#else
  38 +
33 39 /* Due to the structure of pre-exisiting code, don't use assembler line
34 40 comment character # to ignore the arguments. Instead, use a dummy macro. */
35 41 .macro ignore a=0, b=0, c=0, d=0
... ... @@ -48,6 +54,7 @@
48 54 #define CFI_REMEMBER_STATE ignore
49 55 #define CFI_RESTORE_STATE ignore
50 56 #define CFI_UNDEFINED ignore
  57 +#define CFI_SIGNAL_FRAME ignore
51 58  
52 59 #endif
53 60  
include/asm-i386/unwind.h
... ... @@ -18,6 +18,7 @@
18 18 {
19 19 struct pt_regs regs;
20 20 struct task_struct *task;
  21 + unsigned call_frame:1;
21 22 };
22 23  
23 24 #define UNW_PC(frame) (frame)->regs.eip
... ... @@ -43,6 +44,10 @@
43 44 PTREGS_INFO(esi), \
44 45 PTREGS_INFO(edi), \
45 46 PTREGS_INFO(eip)
  47 +
  48 +#define UNW_DEFAULT_RA(raItem, dataAlign) \
  49 + ((raItem).where == Memory && \
  50 + !((raItem).value * (dataAlign) + 4))
46 51  
47 52 static inline void arch_unw_init_frame_info(struct unwind_frame_info *info,
48 53 /*const*/ struct pt_regs *regs)
include/asm-x86_64/dwarf2.h
... ... @@ -28,6 +28,11 @@
28 28 #define CFI_REMEMBER_STATE .cfi_remember_state
29 29 #define CFI_RESTORE_STATE .cfi_restore_state
30 30 #define CFI_UNDEFINED .cfi_undefined
  31 +#ifdef CONFIG_AS_CFI_SIGNAL_FRAME
  32 +#define CFI_SIGNAL_FRAME .cfi_signal_frame
  33 +#else
  34 +#define CFI_SIGNAL_FRAME
  35 +#endif
31 36  
32 37 #else
33 38  
... ... @@ -45,6 +50,7 @@
45 50 #define CFI_REMEMBER_STATE #
46 51 #define CFI_RESTORE_STATE #
47 52 #define CFI_UNDEFINED #
  53 +#define CFI_SIGNAL_FRAME #
48 54  
49 55 #endif
50 56  
include/asm-x86_64/unwind.h
... ... @@ -18,6 +18,7 @@
18 18 {
19 19 struct pt_regs regs;
20 20 struct task_struct *task;
  21 + unsigned call_frame:1;
21 22 };
22 23  
23 24 #define UNW_PC(frame) (frame)->regs.rip
... ... @@ -56,6 +57,10 @@
56 57 PTREGS_INFO(r14), \
57 58 PTREGS_INFO(r15), \
58 59 PTREGS_INFO(rip)
  60 +
  61 +#define UNW_DEFAULT_RA(raItem, dataAlign) \
  62 + ((raItem).where == Memory && \
  63 + !((raItem).value * (dataAlign) + 8))
59 64  
60 65 static inline void arch_unw_init_frame_info(struct unwind_frame_info *info,
61 66 /*const*/ struct pt_regs *regs)
... ... @@ -603,6 +603,7 @@
603 603 #define FRAME_REG(r, t) (((t *)frame)[reg_info[r].offs])
604 604 const u32 *fde = NULL, *cie = NULL;
605 605 const u8 *ptr = NULL, *end = NULL;
  606 + unsigned long pc = UNW_PC(frame) - frame->call_frame;
606 607 unsigned long startLoc = 0, endLoc = 0, cfa;
607 608 unsigned i;
608 609 signed ptrType = -1;
... ... @@ -612,7 +613,7 @@
612 613  
613 614 if (UNW_PC(frame) == 0)
614 615 return -EINVAL;
615   - if ((table = find_table(UNW_PC(frame))) != NULL
  616 + if ((table = find_table(pc)) != NULL
616 617 && !(table->size & (sizeof(*fde) - 1))) {
617 618 unsigned long tableSize = table->size;
618 619  
... ... @@ -647,7 +648,7 @@
647 648 ptrType & DW_EH_PE_indirect
648 649 ? ptrType
649 650 : ptrType & (DW_EH_PE_FORM|DW_EH_PE_signed));
650   - if (UNW_PC(frame) >= startLoc && UNW_PC(frame) < endLoc)
  651 + if (pc >= startLoc && pc < endLoc)
651 652 break;
652 653 cie = NULL;
653 654 }
654 655  
655 656  
... ... @@ -657,16 +658,28 @@
657 658 state.cieEnd = ptr; /* keep here temporarily */
658 659 ptr = (const u8 *)(cie + 2);
659 660 end = (const u8 *)(cie + 1) + *cie;
  661 + frame->call_frame = 1;
660 662 if ((state.version = *ptr) != 1)
661 663 cie = NULL; /* unsupported version */
662 664 else if (*++ptr) {
663 665 /* check if augmentation size is first (and thus present) */
664 666 if (*ptr == 'z') {
665   - /* check for ignorable (or already handled)
666   - * nul-terminated augmentation string */
667   - while (++ptr < end && *ptr)
668   - if (strchr("LPR", *ptr) == NULL)
  667 + while (++ptr < end && *ptr) {
  668 + switch(*ptr) {
  669 + /* check for ignorable (or already handled)
  670 + * nul-terminated augmentation string */
  671 + case 'L':
  672 + case 'P':
  673 + case 'R':
  674 + continue;
  675 + case 'S':
  676 + frame->call_frame = 0;
  677 + continue;
  678 + default:
669 679 break;
  680 + }
  681 + break;
  682 + }
670 683 }
671 684 if (ptr >= end || *ptr)
672 685 cie = NULL;
... ... @@ -755,7 +768,7 @@
755 768 state.org = startLoc;
756 769 memcpy(&state.cfa, &badCFA, sizeof(state.cfa));
757 770 /* process instructions */
758   - if (!processCFI(ptr, end, UNW_PC(frame), ptrType, &state)
  771 + if (!processCFI(ptr, end, pc, ptrType, &state)
759 772 || state.loc > endLoc
760 773 || state.regs[retAddrReg].where == Nowhere
761 774 || state.cfa.reg >= ARRAY_SIZE(reg_info)
... ... @@ -763,6 +776,11 @@
763 776 || state.cfa.offs % sizeof(unsigned long))
764 777 return -EIO;
765 778 /* update frame */
  779 +#ifndef CONFIG_AS_CFI_SIGNAL_FRAME
  780 + if(frame->call_frame
  781 + && !UNW_DEFAULT_RA(state.regs[retAddrReg], state.dataAlign))
  782 + frame->call_frame = 0;
  783 +#endif
766 784 cfa = FRAME_REG(state.cfa.reg, unsigned long) + state.cfa.offs;
767 785 startLoc = min((unsigned long)UNW_SP(frame), cfa);
768 786 endLoc = max((unsigned long)UNW_SP(frame), cfa);
... ... @@ -866,6 +884,7 @@
866 884 /*const*/ struct pt_regs *regs)
867 885 {
868 886 info->task = tsk;
  887 + info->call_frame = 0;
869 888 arch_unw_init_frame_info(info, regs);
870 889  
871 890 return 0;
... ... @@ -879,6 +898,7 @@
879 898 struct task_struct *tsk)
880 899 {
881 900 info->task = tsk;
  901 + info->call_frame = 0;
882 902 arch_unw_init_blocked(info);
883 903  
884 904 return 0;
... ... @@ -894,6 +914,7 @@
894 914 void *arg)
895 915 {
896 916 info->task = current;
  917 + info->call_frame = 0;
897 918  
898 919 return arch_unwind_init_running(info, callback, arg);
899 920 }
scripts/Kbuild.include
... ... @@ -66,8 +66,8 @@
66 66 # as-instr
67 67 # Usage: cflags-y += $(call as-instr, instr, option1, option2)
68 68  
69   -as-instr = $(shell if echo -e "$(1)" | $(AS) -Z -o astest$$$$.out \
70   - 2>&1 >/dev/null ; then echo "$(2)"; else echo "$(3)"; fi; \
  69 +as-instr = $(shell if echo -e "$(1)" | $(AS) >/dev/null 2>&1 -W -Z -o astest$$$$.out ; \
  70 + then echo "$(2)"; else echo "$(3)"; fi; \
71 71 rm -f astest$$$$.out)
72 72  
73 73 # cc-option