1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2012 Rabin Vincent <rabin at rab.in> |
4 | */ |
5 | |
6 | #include <linux/kernel.h> |
7 | #include <linux/stddef.h> |
8 | #include <linux/errno.h> |
9 | #include <linux/highmem.h> |
10 | #include <linux/sched.h> |
11 | #include <linux/uprobes.h> |
12 | #include <linux/notifier.h> |
13 | |
14 | #include <asm/opcodes.h> |
15 | #include <asm/traps.h> |
16 | |
17 | #include "../decode.h" |
18 | #include "../decode-arm.h" |
19 | #include "core.h" |
20 | |
21 | #define UPROBE_TRAP_NR UINT_MAX |
22 | |
23 | bool is_swbp_insn(uprobe_opcode_t *insn) |
24 | { |
25 | return (__mem_to_opcode_arm(*insn) & 0x0fffffff) == |
26 | (UPROBE_SWBP_ARM_INSN & 0x0fffffff); |
27 | } |
28 | |
29 | int set_swbp(struct arch_uprobe *auprobe, struct mm_struct *mm, |
30 | unsigned long vaddr) |
31 | { |
32 | return uprobe_write_opcode(auprobe, mm, vaddr, |
33 | __opcode_to_mem_arm(auprobe->bpinsn)); |
34 | } |
35 | |
36 | bool arch_uprobe_ignore(struct arch_uprobe *auprobe, struct pt_regs *regs) |
37 | { |
38 | if (!auprobe->asi.insn_check_cc(regs->ARM_cpsr)) { |
39 | regs->ARM_pc += 4; |
40 | return true; |
41 | } |
42 | |
43 | return false; |
44 | } |
45 | |
46 | bool arch_uprobe_skip_sstep(struct arch_uprobe *auprobe, struct pt_regs *regs) |
47 | { |
48 | probes_opcode_t opcode; |
49 | |
50 | if (!auprobe->simulate) |
51 | return false; |
52 | |
53 | opcode = __mem_to_opcode_arm(*(unsigned int *) auprobe->insn); |
54 | |
55 | auprobe->asi.insn_singlestep(opcode, &auprobe->asi, regs); |
56 | |
57 | return true; |
58 | } |
59 | |
60 | unsigned long |
61 | arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr, |
62 | struct pt_regs *regs) |
63 | { |
64 | unsigned long orig_ret_vaddr; |
65 | |
66 | orig_ret_vaddr = regs->ARM_lr; |
67 | /* Replace the return addr with trampoline addr */ |
68 | regs->ARM_lr = trampoline_vaddr; |
69 | return orig_ret_vaddr; |
70 | } |
71 | |
72 | int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm, |
73 | unsigned long addr) |
74 | { |
75 | unsigned int insn; |
76 | unsigned int bpinsn; |
77 | enum probes_insn ret; |
78 | |
79 | /* Thumb not yet support */ |
80 | if (addr & 0x3) |
81 | return -EINVAL; |
82 | |
83 | insn = __mem_to_opcode_arm(*(unsigned int *)auprobe->insn); |
84 | auprobe->ixol[0] = __opcode_to_mem_arm(insn); |
85 | auprobe->ixol[1] = __opcode_to_mem_arm(UPROBE_SS_ARM_INSN); |
86 | |
87 | ret = arm_probes_decode_insn(insn, &auprobe->asi, false, |
88 | uprobes_probes_actions, NULL); |
89 | switch (ret) { |
90 | case INSN_REJECTED: |
91 | return -EINVAL; |
92 | |
93 | case INSN_GOOD_NO_SLOT: |
94 | auprobe->simulate = true; |
95 | break; |
96 | |
97 | case INSN_GOOD: |
98 | default: |
99 | break; |
100 | } |
101 | |
102 | bpinsn = UPROBE_SWBP_ARM_INSN & 0x0fffffff; |
103 | if (insn >= 0xe0000000) |
104 | bpinsn |= 0xe0000000; /* Unconditional instruction */ |
105 | else |
106 | bpinsn |= insn & 0xf0000000; /* Copy condition from insn */ |
107 | |
108 | auprobe->bpinsn = bpinsn; |
109 | |
110 | return 0; |
111 | } |
112 | |
113 | void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr, |
114 | void *src, unsigned long len) |
115 | { |
116 | void *xol_page_kaddr = kmap_atomic(page); |
117 | void *dst = xol_page_kaddr + (vaddr & ~PAGE_MASK); |
118 | |
119 | preempt_disable(); |
120 | |
121 | /* Initialize the slot */ |
122 | memcpy(dst, src, len); |
123 | |
124 | /* flush caches (dcache/icache) */ |
125 | flush_uprobe_xol_access(page, vaddr, dst, len); |
126 | |
127 | preempt_enable(); |
128 | |
129 | kunmap_atomic(xol_page_kaddr); |
130 | } |
131 | |
132 | |
133 | int arch_uprobe_pre_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) |
134 | { |
135 | struct uprobe_task *utask = current->utask; |
136 | |
137 | if (auprobe->prehandler) |
138 | auprobe->prehandler(auprobe, &utask->autask, regs); |
139 | |
140 | utask->autask.saved_trap_no = current->thread.trap_no; |
141 | current->thread.trap_no = UPROBE_TRAP_NR; |
142 | regs->ARM_pc = utask->xol_vaddr; |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | int arch_uprobe_post_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) |
148 | { |
149 | struct uprobe_task *utask = current->utask; |
150 | |
151 | WARN_ON_ONCE(current->thread.trap_no != UPROBE_TRAP_NR); |
152 | |
153 | current->thread.trap_no = utask->autask.saved_trap_no; |
154 | regs->ARM_pc = utask->vaddr + 4; |
155 | |
156 | if (auprobe->posthandler) |
157 | auprobe->posthandler(auprobe, &utask->autask, regs); |
158 | |
159 | return 0; |
160 | } |
161 | |
162 | bool arch_uprobe_xol_was_trapped(struct task_struct *t) |
163 | { |
164 | if (t->thread.trap_no != UPROBE_TRAP_NR) |
165 | return true; |
166 | |
167 | return false; |
168 | } |
169 | |
170 | void arch_uprobe_abort_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) |
171 | { |
172 | struct uprobe_task *utask = current->utask; |
173 | |
174 | current->thread.trap_no = utask->autask.saved_trap_no; |
175 | instruction_pointer_set(regs, val: utask->vaddr); |
176 | } |
177 | |
178 | int arch_uprobe_exception_notify(struct notifier_block *self, |
179 | unsigned long val, void *data) |
180 | { |
181 | return NOTIFY_DONE; |
182 | } |
183 | |
184 | static int uprobe_trap_handler(struct pt_regs *regs, unsigned int instr) |
185 | { |
186 | unsigned long flags; |
187 | |
188 | local_irq_save(flags); |
189 | instr &= 0x0fffffff; |
190 | if (instr == (UPROBE_SWBP_ARM_INSN & 0x0fffffff)) |
191 | uprobe_pre_sstep_notifier(regs); |
192 | else if (instr == (UPROBE_SS_ARM_INSN & 0x0fffffff)) |
193 | uprobe_post_sstep_notifier(regs); |
194 | local_irq_restore(flags); |
195 | |
196 | return 0; |
197 | } |
198 | |
199 | unsigned long uprobe_get_swbp_addr(struct pt_regs *regs) |
200 | { |
201 | return instruction_pointer(regs); |
202 | } |
203 | |
204 | static struct undef_hook uprobes_arm_break_hook = { |
205 | .instr_mask = 0x0fffffff, |
206 | .instr_val = (UPROBE_SWBP_ARM_INSN & 0x0fffffff), |
207 | .cpsr_mask = (PSR_T_BIT | MODE_MASK), |
208 | .cpsr_val = USR_MODE, |
209 | .fn = uprobe_trap_handler, |
210 | }; |
211 | |
212 | static struct undef_hook uprobes_arm_ss_hook = { |
213 | .instr_mask = 0x0fffffff, |
214 | .instr_val = (UPROBE_SS_ARM_INSN & 0x0fffffff), |
215 | .cpsr_mask = (PSR_T_BIT | MODE_MASK), |
216 | .cpsr_val = USR_MODE, |
217 | .fn = uprobe_trap_handler, |
218 | }; |
219 | |
220 | static int arch_uprobes_init(void) |
221 | { |
222 | register_undef_hook(&uprobes_arm_break_hook); |
223 | register_undef_hook(&uprobes_arm_ss_hook); |
224 | |
225 | return 0; |
226 | } |
227 | device_initcall(arch_uprobes_init); |
228 | |