# 以SIGSEGV为例详解信号处理(与栈回溯)

1. 信号处理例程

  1 #include <string.h>
2 #include <signal.h>
3 #include <stdio.h>
4 #include <unistd.h>
6 #define POPCNT(data)                            do {        \
7         data = (data & 0x55555555) + ((data >> 1) & 0x55555555);    \
8         data = (data & 0x33333333) + ((data >> 2) & 0x33333333);    \
9         data = (data & 0x0F0F0F0F) + ((data >> 4) & 0x0F0F0F0F);    \
10         data = (data & 0x00FF00FF) + ((data >> 8) & 0x00FF00FF);    \
11         data = (data & 0x0000FFFF) + ((data >> 16) & 0x0000FFFF);    \
12     } while (0);
13 /**
14  * we only calculate sp decrease which is static confirm in compile time
15  * that is sub immediate & push instruction(and return when we find push)
16  *
17 **/
18 void backtrace_stack(unsigned int **pppc, unsigned int **ppsp)
19 {
20     unsigned int *ppc_last = *pppc;
21     unsigned int *psp = *ppsp;
22     unsigned int decrease = 0;
23     int i;
24     enum
25     {
26         INS_SUB_IMM = 0,
27         INS_STM1,
28         INS_STR_LR,
29         INS_STR_FP,
30         INS_BUTT
31     };
32     //see ARM reference manual for more detail
33     struct ins_map
34     {
36         unsigned int ins;
37     };
38     struct ins_map map[INS_BUTT] =
39     {
40         {0xFFEFF000, 0xE24DD000},
41         {0xFFFF4000, 0xE92D4000},
42         {0xFFFFFFFF, 0xE52DE004},
43         {0xFFFFFFFF, 0xE52DB004},
44     };
45 again:
46     ppc_last--;
47     for (i = 0; i < INS_BUTT; i++)
48     {
49         if (map[i].ins == (*ppc_last &map[i].mask))
50         {
51             break;
52         }
53     }
54     switch (i)
55     {
56     case INS_SUB_IMM:
57         //sub sp, sp, imm
58         decrease = (*ppc_last & 0xFF) << ((32 - 2 * (*ppc_last & 0xF00)) % 32);
59         psp += decrease / sizeof(unsigned int);
60         break;
61     case INS_STM1:
62         //push lr, ...
63         decrease = *ppc_last & 0xFFFF;
64         POPCNT(decrease);
65         psp += decrease;
66         *pppc = *(psp - 1);
67         *ppsp = psp;
68         return;
69     case INS_STR_LR:
70         //push lr
71         psp += 1;
72         *pppc = *(psp - 1);
73         *ppsp = psp;
74         return;
75     case INS_STR_FP:
76         //push fp
77         psp += 1;
78         *ppsp = psp;
79         return;
80     default:
81         break;
82     }
83     goto again;
84 }
85 /**
86  * process stack when catch a sigsegv:
87  * ------------   stack top
88  * | ......
89  * | fault addr   sp position when memory fault happen
90  * | sigframe     kernel use to resotre context DO NOT MODIFY(same to data)
91  * | siginfo      glibc push this struct into stack(same to siginfo)
92  * | current sp   sp position when enter signal handle
93  *
94 **/
95 void sighandle(int sig, siginfo_t *siginfo, void *data)
96 {
97     //data point to sigframe which is not seen to user
98     //search struct ucontext in kernel for more detail
99     unsigned int *psp = ((unsigned int *)data) + 21;
100     unsigned int *plr = ((unsigned int *)data) + 22;
101     unsigned int *ppc = ((unsigned int *)data) + 23;
102     unsigned int pc_val[5] = {0};
103     unsigned int sp_val[5] = {0};
104     char **ppstr;
105     int i;
106
108     pc_val[0] = *ppc;
109     sp_val[0] = *psp;
110     for (i = 1; i < 4; i++)
111     {
112         pc_val[i] = pc_val[i - 1];
113         sp_val[i] = sp_val[i - 1];
114         backtrace_stack((unsigned int **)(&pc_val[i]), (unsigned int **)(&sp_val[i]));
115         /**
116          * for subroutine use push {fp} instruction, we can't get it's caller pc
117          * so we use last lr as pc and hope program won't push {fp} twice
118          *
119         **/
120         if (pc_val[i] == pc_val[i - 1])
121         {
122             pc_val[i] = *plr;
123         }
124         pc_val[i] -= 4;
125     }
126     ppstr = backtrace_symbols((void **)pc_val, 5);
127     for (i = 0; i < 5; i++)
128     {
129         printf("%u: pc[0x%08x] sp[0x%08x] %s\n", i, pc_val[i], sp_val[i], ppstr[i]);
130     }
131     exit(1);
132 }
133 void fault_func3()
134 {
135     int *p = NULL;
136     *p = 1;
137 }
138 void fault_func2()
139 {
140     int a = 0x5678;
141     fault_func3();
142     return;
143 }
144 void fault_func1(void *pvoid)
145 {
146     int a = 0x1234;
147     fault_func2();
148     return;
149 }
150 int main(int argc, char *argv[])
151 {
152     struct sigaction sigact;
153     int *p = NULL;
154     memset(&sigact, 0, sizeof(struct sigaction));
155     sigact.sa_sigaction = sighandle;
156     sigact.sa_flags = SA_SIGINFO | SA_RESTART;
157     sigaction(SIGSEGV, &sigact, NULL);
158     getc(stdin);
161     while (1)
162     {
163         ;
164     }
165     return 0;
166 } 

2. 内核信号量数据结构与系统调用

sighand_struct(每个信号处理句柄, 保护信号的自旋锁)
signal_struct(信号量结构, 大部分参数都在该结构中)
sigpending(挂起队列, 用于索引挂起的信号)

......

struct signal_struct *signal;
//信号处理句柄, 包括每个信号的action, 锁与等待队列
struct sighand_struct *sighand;

sigset_t blocked, real_blocked;
struct sigpending pending;

......
};

struct sighand_struct {
atomic_t count;
//保存信号处理句柄的数组
struct k_sigaction action[_NSIG];
spinlock_t siglock;
};

/**
* signal_struct自身没有锁
* 因为一个共享的signal_struct往往对饮一个共享的sighand_struct
* 即使用sighand_struct的锁是signal_struct的超集
*
**/
struct signal_struct {
......

struct sigpending shared_pending;

......
};

//描述挂起信号的结构体
//成员list为进程所有挂起信号的双线链表的头
//成员signal为进程挂起信号量的位图, 挂起的信号对应的位置位
struct sigpending {
//sigqueue链表头
//当前挂起的信号量位图
sigset_t signal;
};

//描述一个挂起信号的结构体
struct sigqueue {
//sigqueue链表节点
int flags;
//该挂起信号的信息
siginfo_t info;
struct user_struct *user;
};

//描述信号相关信息的结构体
typedef struct siginfo {
int si_signo;
int si_errno;
int si_code;

......
} __ARCH_SI_ATTRIBUTES siginfo_t;

 1 /**
2  * 定义见kernel/signal.c
3  * 获取或修改拦截的信号
4  * @how: 为SIG_BLOCK / SIG_UNBLOCK / SIG_SETMASK的一种
5  * @nset: 如果非空为增加或移除的信号
6  * @oset: 如果非空为之前的信号
8  *       调用set_current_blocked中会先剔除SIGKILL与SIGSTOP, 用户传递这两个值是无效的
10  *
11 **/
13     old_sigset_t __user *, nset, \
14     old_sigset_t __user *, oset);
15 /**
16  * 定义见kernel/signal.c
17  * 获取或修改拦截信号的action
18  * @sig: 为拦截的信号
19  * @act: 如果非空为信号sig的action
20  * @oact: 如果非空为返回之前信号sig的action
21  * note: 如果传入未定义信号或SIGKILL与SIGSTOP会直接返回EINVAL
23  *       然后检测所拦截的信号是否挂起, 如果有挂起则将其从队列中删除
24  *
25 **/
26 SYSCALL_DEFINE3(sigaction, int, sig, \
27     const struct old_sigaction __user *, act, \
28     struct old_sigaction __user *, oact);
29 /**
30  * 定义见kernel/signal.c
31  * 以下两接口为发送信号的接口, 实际调用send_signal
32  * send_signal()调用__send_signal
33  *
34 **/
35 int do_send_sig_info(int sig, struct siginfo *info, \
36     struct task_struct *p, bool group);
37 int __group_send_sig_info(int sig, \
38     struct siginfo *info, struct task_struct *p); 

  1 /**
2  * 定义见kernel/signal.c
3  * 实际发送信号的函数, 本接口未加锁, 需外部保证锁
4  *
5 **/
6 static int __send_signal(int sig, struct siginfo *info, \
7     struct task_struct *t, int group, int from_ancestor_ns)
8 {
9     //检测是否已锁, 此处使用sighand的锁是因为sighand_struct与signal_struct往往一一对应
10     assert_spin_locked(&t->sighand->siglock);
11     //调用prepare_signal判断信号是否需要发送及做其它准备情况
12     //主要是处理SIGSTOP/SIGCONT, 对于SIGCONT立即发生, 对于SIGSTOP则不是立刻停止
13     //1. 对于即将退出的进程, 除SIGKILL外都不发送信号
14     //2. 如果是停止信号, 需先将进程挂起的SIGCONT移出挂起队列
15     //3. 如果是SIGCONT信号, 需先将所有停止信号都移出挂起队列同时清除线程标记位
16     //4. 判断信号是否需要忽略, 阻塞的信号不忽略, 忽略处理句柄为空与内核认为需要忽略信号
17     if (!prepare_signal(sig, t, from_ancestor_ns || (info == SEND_SIG_FORCED)))
18         goto ret;
19     pending = group   &t->signal->shared_pending : &t->pending;
20     //对于已挂起信号不再处理, 确保每种信号在队列中仅存在一个
21     if (legacy_queue(pending, sig))
22         goto ret;
23     //对于内核内部信号如SIGSTOP或SIGKILL走捷径
24     if (info == SEND_SIG_FORCED)
25         goto out_set;
26     //实时信号必须通过sigqueue或其它实时机制入队列
27     //但考虑到内存不足时kill不允许失败所以保证至少一个信号可以传递
28     if (sig < SIGRTMIN)
29         override_rlimit = (is_si_special(info) || info->si_code >= 0);
30     else
31         override_rlimit = 0;
32     q = __sigqueue_alloc(sig, t, \
33         GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE, override_rlimit);
34     if (q) {
36         switch ((unsigned long) info) {
37         case (unsigned long) SEND_SIG_NOINFO:
38             q->info.si_signo = sig;
39             q->info.si_errno = 0;
40             q->info.si_code = SI_USER;
42             q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
43             break;
44         case (unsigned long) SEND_SIG_PRIV:
45             q->info.si_signo = sig;
46             q->info.si_errno = 0;
47             q->info.si_code = SI_KERNEL;
48             q->info.si_pid = 0;
49             q->info.si_uid = 0;
50             break;
51         default:
52             copy_siginfo(&q->info, info);
53             if (from_ancestor_ns)
54                 q->info.si_pid = 0;
55             break;
56         }
57         userns_fixup_signal_uid(&q->info, t);
58     } else if (!is_si_special(info)) {
59         if (sig >= SIGRTMIN && info->si_code != SI_USER) {
60             //信号队列溢出, 放弃
61             result = TRACE_SIGNAL_OVERFLOW_FAIL;
62             ret = -EAGAIN;
63             goto ret;
64         } else {
65             //继续传递信号, 但info信息丢失
66             result = TRACE_SIGNAL_LOSE_INFO;
67         }
68     }
69 out_set:
70     signalfd_notify(t, sig);
71     //挂起队列位图对应位置位
73     complete_signal(sig, t, group);
74 ret:
75     //跟踪信号生成, 该接口直接搜索不存在
76     //在include/trace/events/signal.h中宏定义
77     //其中TRACE_EVENT定义见include/linux/tracepoint.h
78     trace_signal_generate(sig, info, t, group, result);
79     return ret;
80 }
81 static void complete_signal(int sig, struct task_struct *p, int group)
82 {
83     //寻找可唤醒的线程
87     if (wants_signal(sig, p))
88         t = p;
89     else if (!group || thread_group_empty(p))
90         /*
91         * There is just one thread and it does not need to be woken.
92         * It will dequeue unblocked signals before it runs again.
93         */
94         //仅一个线程无需唤醒, 自动在运行前去除未阻塞信号
95         return;
96     else {
97         t = signal->curr_target;
98         while (!wants_signal(sig, t)) {
100             if (t == signal->curr_target)
101                 //遍历所有线程, 没有线程需要唤醒
102                 return;
103         }
104         signal->curr_target = t;
105     }
106     //寻找可杀死的线程
107     if (sig_fatal(p, sig) &&
108         !(signal->flags & (SIGNAL_UNKILLABLE | SIGNAL_GROUP_EXIT)) &&
109         !sigismember(&t->real_blocked, sig) &&
110         (sig == SIGKILL || !t->ptrace)) {
111             //唤醒整个线程组
112             if (!sig_kernel_coredump(sig)) {
113             signal->flags = SIGNAL_GROUP_EXIT;
114             signal->group_exit_code = sig;
115             signal->group_stop_count = 0;
116             t = p;
117             do {
120                 signal_wake_up(t, 1);
122             return;
123         }
124     }
125     //唤醒线程去队列中获取信号
126     signal_wake_up(t, sig == SIGKILL);
127 } 

3. 信号处理流程

arm一共有7种异常处理模式, reset, und, swi, pabt, dabt, irq, fiq(reference manual A2-13).

 1 .macro vector_stub, name, mode, correction=0
2     .align 5
3     vector_\name:
4     .if \correction
5     sub lr, lr, #\correction
6     .endif
7     @
8     @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
9     @ (parent CPSR)
10     @
11     stmia sp, {r0, lr}  @ save r0, lr
12     mrs lr, spsr
13     str lr, [sp, #8]    @ save spsr
14     @
15     @ Prepare for SVC32 mode.  IRQs remain disabled.
16     @
17     mrs r0, cpsr
18     eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
19     msr spsr_cxsf, r0
20     @
21     @ the branch table must immediately follow this code
22     @
23     and lr, lr, #0x0f
25     THUMB(ldr lr, [r0, lr, lsl #2])
26     mov r0, sp
27     ARM( ldr lr, [pc, lr, lsl #2])
28     movs pc, lr         @ branch to handler in SVC mode
29 ENDPROC(vector_\name) 

1 vector_stub dabt, ABT_MODE, 8
2 .long __dabt_usr       @  0  (USR_26 / USR_32)
3 .long __dabt_invalid   @  1  (FIQ_26 / FIQ_32)
4 .long __dabt_invalid   @  2  (IRQ_26 / IRQ_32)
5 .long __dabt_svc       @  3  (SVC_26 / SVC_32) 

1 __dabt_usr:
2     usr_entry
3     kuser_cmpxchg_check
4     mov r2, sp
5     dabt_helper
6     b ret_from_exception
7     UNWIND(.fnend)
8 ENDPROC(__dabt_usr) 

 1 .macro usr_entry
2     UNWIND(.fnstart)
3     UNWIND(.cantunwind)             @ don't unwind the user space
4     sub sp, sp, #S_FRAME_SIZE
5     ARM( stmib sp, {r1 - r12})
6     THUMB( stmia sp, {r0 - r12})
7     ldmia r0, {r3 - r5}
8     add r0, sp, #S_PC               @ here for interlock avoidance
9     mov r6, #-1
10     str r3, [sp]                    @ save the "real" r0 copied
11                                     @ from the exception stack
12     @
13     @ We are now ready to fill in the remaining blanks on the stack:
14     @
15     @  r4 - lr_<exception>, already fixed up for correct return/restart
16     @  r5 - spsr_<exception>
17     @  r6 - orig_r0 (see pt_regs definition in ptrace.h)
18     @
19     @ Also, separately save sp_usr and lr_usr
20     @
21     stmia r0, {r4 - r6}
22     ARM( stmdb r0, {sp, lr}^)
23     THUMB( store_user_sp_lr r0, r1, S_SP - S_PC)
24     @
25     @ Enable the alignment trap while in kernel mode
26     @
27     alignment_trap r0
28     @
29     @ Clear FP to mark the first stack frame
30     @
31     zero_fp
32 #ifdef CONFIG_IRQSOFF_TRACER
33     bl trace_hardirqs_off
34 #endif
35     ct_user_exit save = 0
36 .endm 

r0需作为栈地址参数传入异常处理函数, 其原始值被修改, 所以通过栈传入.

 1 .macro dabt_helper
2     @
3     @ Call the processor-specific abort handler:
4     @
5     @  r2 - pt_regs
6     @  r4 - aborted context pc
7     @  r5 - aborted context psr
8     @
9     @ The abort handler must return the aborted address in r0, and
10     @ the fault status register in r1.  r9 must be preserved.
11     @
12 #ifdef MULTI_DABORT
13     ldr ip, .LCprocfns
14     mov lr, pc
15     ldr pc, [ip, #PROCESSOR_DABT_FUNC]
16 #else
17     bl CPU_DABORT_HANDLER
18 #endif
19 .endm
20 #ifdef MULTI_DABORT
21 .LCprocfns:
22     .word processor
23 #endif 

handler返回时abort地址保存在r0中, 错误状态寄存器(fsr)保存在r1中, r9保留.

.LCprocfns段存放的是全局变量processor, 其定义在arch/arm/include/asm/proc-fns.h.
PROCESSOR_DABT_FUNC定义见arch/arm/kernel/asm-offsets.c, 即指向processor._data_abort.
.

 1 ENTRY(lookup_processor_type)
2     stmfd sp!, {r4 - r6, r9, lr}
3     mov r9, r0
4     bl __lookup_processor_type
5     mov r0, r5
6     ldmfd sp!, {r4 - r6, r9, pc}
7 ENDPROC(lookup_processor_type)
8 __lookup_processor_type:
10     ldmia r3, {r4 - r6}
11     sub r3, r3, r4             @ get offset between virt&phys
14 1:  ldmia r5, {r3, r4}         @ value, mask
15     and r4, r4, r9             @ mask wanted bits
16     teq r3, r4
17     beq 2f
18     add r5, r5, #PROC_INFO_SZ  @ sizeof(proc_info_list)
19     cmp r5, r6
20     blo 1b
21     mov r5, #0                 @ unknown processor
22 2:  mov pc, lr
23 ENDPROC(__lookup_processor_type) 

__lookup_processor_type的注释解释了代码意图: 从CP15读取处理器id并从链接时建立的数组中查找.

lookup_processor_type首先将cpuid保存在r9, 然后获取程序装载地址的偏移.
__lookup_processor_type_data是数据段对象, 其包含两个数据__proc_info_begin与__proc_info_end.

r3是编译时的程序地址, r4是运行时的实际地址.
r3与r4相减即无MMU时程序加载地址相对程序文件地址的偏移.
r5与r6分别为__lookup_processor_type_data数据段的起始地址与结束地址.

 1 .macro __v7_proc initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions
2     ALT_SMP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
3         PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
4     ALT_UP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
5         PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
6     .long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
7         PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
8     W(b) \initfunc
9     .long cpu_arch_nam
10     .long cpu_elf_name
11     .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
12         HWCAP_EDSP | HWCAP_TLS | \hwcaps
13     .long cpu_v7_name
14     .long \proc_fns
15     .long v7wbi_tlb_fns
16     .long v6_user_fns
17     .long v7_cache_fns
18 .endm 

__v7_proc_info的processor成员是v7_processor_functions, 再来看看该成员.

 1 .macro define_processor_functions name:req, dabort:req, pabort:req, nommu=0, suspend=0
2     .type \name\()_processor_functions, #object
3     .align 2
4 ENTRY(\name\()_processor_functions)
5     .word \dabort
6     .word \pabort
7     .word cpu_\name\()_proc_init
8     .word cpu_\name\()_proc_fin
9     .word cpu_\name\()_reset
10     .word cpu_\name\()_do_idle
11     .word cpu_\name\()_dcache_clean_area
12     .word cpu_\name\()_switch_mm
13     .if \nommu
14     .word 0
15     .else
16     .word cpu_\name\()_set_pte_ext
17     .endif
18     .if \suspend
19     .word cpu_\name\()_suspend_size
20 #ifdef CONFIG_PM_SLEEP
21     .word cpu_\name\()_do_suspend
22     .word cpu_\name\()_do_resume
23 #else
24     .word 0
25     .word 0
26 #endif
27     .else
28     .word 0
29     .word 0
30     .word 0
31     .endif
32     .size \name\()_processor_functions, . - \name\()_processor_functions
33 .endm
34 define_processor_functions v7, dabort=v7_early_abort, pabort=v7_pabort, suspend=1 

 1 ENTRY(v7_early_abort)
2     /*
3      * The effect of data aborts on on the exclusive access monitor are
4      * UNPREDICTABLE. Do a CLREX to clear the state
5      */
6     clrex
7     mrc p15, 0, r1, c5, c0, 0         @ get FSR
8     mrc p15, 0, r0, c6, c0, 0         @ get FAR
9     /*
10      * V6 code adjusts the returned DFSR.
11      * New designs should not need to patch up faults.
12      */
13 #if defined(CONFIG_VERIFY_PERMISSION_FAULT)
14     /*
15      * Detect erroneous permission failures and fix
16      */
17     ldr r3, =0x40d               @ On permission fault
18     and r3, r1, r3
19     cmp r3, #0x0d
20     bne do_DataAbort
21     mcr p15, 0, r0, c7, c8, 0    @ Retranslate FAR
22     isb
23     mrc p15, 0, ip, c7, c4, 0    @ Read the PAR
24     and r3, ip, #0x7b            @ On translation fault
25     cmp r3, #0x0b
26     bne do_DataAbort
27     bic r1, r1, #0xf             @ Fix up FSR FS[5:0]
28     and ip, ip, #0x7e
29     orr r1, r1, ip, LSR #1
30 #endif
31     b do_DataAbort
32 ENDPROC(v7_early_abort)

v7_early_abort很简单, 先对FSR与FAR的处理(reference manual B3-18), 然后调用do_DataAbort.

 1 asmlinkage void __exception
2 do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
3 {
4     const struct fsr_info *inf = fsr_info + fsr_fs(fsr);
5     struct siginfo info;
6     if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))
7         return;
8     printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n",
10     info.si_signo = inf->sig;
11     info.si_errno = 0;
12     info.si_code  = inf->code;
14     arm_notify_die("", regs, &info, fsr, 0);
15 }
16 struct fsr_info {
17     int (*fn)(unsigned long addr, unsigned int fsr, struct pt_regs *regs);
18     int sig;
19     int code;
20     const char *name;
21 };
22 /* FSR definition */
23 #ifdef CONFIG_ARM_LPAE
24 #include "fsr-3level.c"
25 #else
26 #include "fsr-2level.c"
27 #endif 

do_DataAbort也很简单, 调用fsr_info数组某个元素的回调, 返回后根据结果向进程发送信号.

.

 1 ENTRY(ret_from_exception)
2     UNWIND(.fnstart)
3     UNWIND(.cantunwind)
5     mov why, #0
6     b ret_to_user
7     UNWIND(.fnend)
8 ENDPROC(__pabt_usr)
9 ENDPROC(ret_from_exception)
10 ENTRY(ret_to_user)
11 ret_slow_syscall:
12     disable_irq                   @ disable interrupts
13 ENTRY(ret_to_user_from_irq)
14     ldr r1, [tsk, #TI_FLAGS]
16     bne work_pending
17     no_work_pending:
18     asm_trace_hardirqs_on
19     /* perform architecture specific actions before user return */
20     arch_ret_to_user r1, lr
21     ct_user_enter save = 0
22     restore_user_regs fast = 0, offset = 0
23 ENDPROC(ret_to_user_from_irq)
24 ENDPROC(ret_to_user) 

1 work_pending:
2     mov r0, sp    @ 'regs'
3     mov r2, why    @ 'syscall'
4     bl do_work_pending
5     cmp r0, #0
6     beq no_work_pending
7     movlt scno, #(__NR_restart_syscall - __NR_SYSCALL_BASE)
8     ldmia sp, {r0 - r6}   @ have to reload r0 - r6
9     b local_restart   @ ... and off we go 

do_work_pending(defined in arch/arm/kernel/signal.c)的作用是判断是否需要调度或信号处理:

 1 asmlinkage int do_work_pending(struct pt_regs *regs, \
2     unsigned int thread_flags, int syscall);
3 {
4     do {
5         /**
7          * 同样regs值为态sp, syscall值为why
9          *
10         **/
11         if (likely(thread_flags & _TIF_NEED_RESCHED)) {
12             schedule();
13         } else {
14             /**
15              * 如果CPSR模式位不在用户态, 即之前程序就工作在内核态
16              * 被高优先级的任务抢占(比如系统调用时被中断打断)
17              * 那么此时直接返回继续之前任务
18              *
19             **/
20             if (unlikely(!user_mode(regs)))
21                 return 0;
22             local_irq_enable();
23             /**
24              * 判断是否有信号挂起
25              * 该标记位在signal_wake_up_state与recalc_sigpending_tsk设置
26              *
27             **/
28             if (thread_flags & _TIF_SIGPENDING) {
29                 //do_signal(defined in arch/arm/kernel/signal.c)定义见下
30                 int restart = do_signal(regs, syscall);
31                 if (unlikely(restart)) {
32                     //处理失败直接返回, 不调用回调
33                     return restart;
34                 }
35                 syscall = 0;
36             } else {
38                 tracehook_notify_resume(regs);
39             }
40         }
41         local_irq_disable();
44     return 0;
45 } 

do_signal作用是处理挂起信号, 保存内核寄存器状态, 为内核执行用户态回调做准备.

 1 static int do_signal(struct pt_regs *regs, int syscall)
2 {
3     ......
4     /**
5      * 实际调用get_signal_to_deliver(defined in kernel/signal.c)
8      * 还有很多判断, 先忽略
9      *
10     **/
11     if (get_signal(&ksig)) {
12         /**
13          * 在执行信号回调句柄前准备工作, 在用户态栈保存内核数据
14          * handle_signal实际调用setup_frame或setup_rt_frame(如果为rt信号)
15          * 以setup_frame为例:
16          * 1. 首先调用get_sigframe获取用户态栈地址, 对齐并确认可写
17          *    注意sigframe结构体的排布, 在用户态获取lr时会用到该结构
18          * 2. 设置uc.uc_flags为0x5a3c3c5a
19          * 3. 调用setup_sigframe填充sigframe结构
20          * 4. 调用setup_return设置回调接口返回(设置pt_regs)
21          *    注意此时pt_regs仍在栈上:
22          *    pt_regs->pc设置为信号回调句柄
23          *    pt_regs->r0设置为signo
24          *    pt_regs->lr被修改为retcode
25          *    pt_regs->sp被修改为frame(frame是结构体起始地址, 与栈方向相反, 所以是栈底!)
26          * 在栈帧建立后调用signal_setup_done恢复阻塞的信号
27          *
28         **/
29         handle_signal(&ksig, regs);
30     }
31     ......
32 } 

 1 .macro restore_user_regs, fast = 0, offset = 0
2     clrex                                  @ clear the exclusive monitor
3     mov r2, sp
4     load_user_sp_lr r2, r3, \offset + S_SP @ calling sp, lr
5     ldr r1, [sp, #\offset + S_PSR]         @ get calling cpsr
6     ldr lr, [sp, #\offset + S_PC]          @ get pc
7     add sp, sp, #\offset + S_SP
8     msr spsr_cxsf, r1                      @ save in spsr_svc
9     .if \fast
10     ldmdb sp, {r1 - r12}                   @ get calling r1 - r12
11     .else
12     ldmdb sp, {r0 - r12}                   @ get calling r0 - r12
13     .endif
14     add sp, sp, #S_FRAME_SIZE - S_SP
15     movs pc, lr                            @ return & move spsr_svc into cpsr
16 .endm
17 .macro load_user_sp_lr, rd, rtemp, offset = 0
18     mrs \rtemp, cpsr
19     eor \rtemp, \rtemp, #(SVC_MODE ^ SYSTEM_MODE)
20     msr cpsr_c, \rtemp                     @ switch to the SYS mode
21     ldr sp, [\rd, #\offset]                @ load sp_usr
22     ldr lr, [\rd, #\offset + 4]            @ load lr_usr
23     eor \rtemp, \rtemp, #(SVC_MODE ^ SYSTEM_MODE)
24     msr cpsr_c, \rtemp                     @ switch back to the SVC mode
25 .endm 

clrex用于清除本地cpu独占访问某块内存区域的标记.
S_SP定义见arch/arm/kernel/asm-offsets.c, 是ARM_sp在pt_regs的偏移.

4. 用户进程回溯堆栈

...

sigframe
siginfo