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