1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * arch/arm/probes/kprobes/actions-common.c |
4 | * |
5 | * Copyright (C) 2011 Jon Medhurst <tixy@yxit.co.uk>. |
6 | * |
7 | * Some contents moved here from arch/arm/include/asm/kprobes-arm.c which is |
8 | * Copyright (C) 2006, 2007 Motorola Inc. |
9 | */ |
10 | |
11 | #include <linux/kernel.h> |
12 | #include <linux/kprobes.h> |
13 | #include <asm/opcodes.h> |
14 | |
15 | #include "core.h" |
16 | |
17 | |
18 | static void __kprobes simulate_ldm1stm1(probes_opcode_t insn, |
19 | struct arch_probes_insn *asi, |
20 | struct pt_regs *regs) |
21 | { |
22 | int rn = (insn >> 16) & 0xf; |
23 | int lbit = insn & (1 << 20); |
24 | int wbit = insn & (1 << 21); |
25 | int ubit = insn & (1 << 23); |
26 | int pbit = insn & (1 << 24); |
27 | long *addr = (long *)regs->uregs[rn]; |
28 | int reg_bit_vector; |
29 | int reg_count; |
30 | |
31 | reg_count = 0; |
32 | reg_bit_vector = insn & 0xffff; |
33 | while (reg_bit_vector) { |
34 | reg_bit_vector &= (reg_bit_vector - 1); |
35 | ++reg_count; |
36 | } |
37 | |
38 | if (!ubit) |
39 | addr -= reg_count; |
40 | addr += (!pbit == !ubit); |
41 | |
42 | reg_bit_vector = insn & 0xffff; |
43 | while (reg_bit_vector) { |
44 | int reg = __ffs(reg_bit_vector); |
45 | reg_bit_vector &= (reg_bit_vector - 1); |
46 | if (lbit) |
47 | regs->uregs[reg] = *addr++; |
48 | else |
49 | *addr++ = regs->uregs[reg]; |
50 | } |
51 | |
52 | if (wbit) { |
53 | if (!ubit) |
54 | addr -= reg_count; |
55 | addr -= (!pbit == !ubit); |
56 | regs->uregs[rn] = (long)addr; |
57 | } |
58 | } |
59 | |
60 | static void __kprobes simulate_stm1_pc(probes_opcode_t insn, |
61 | struct arch_probes_insn *asi, |
62 | struct pt_regs *regs) |
63 | { |
64 | unsigned long addr = regs->ARM_pc - 4; |
65 | |
66 | regs->ARM_pc = (long)addr + str_pc_offset; |
67 | simulate_ldm1stm1(insn, asi, regs); |
68 | regs->ARM_pc = (long)addr + 4; |
69 | } |
70 | |
71 | static void __kprobes simulate_ldm1_pc(probes_opcode_t insn, |
72 | struct arch_probes_insn *asi, |
73 | struct pt_regs *regs) |
74 | { |
75 | simulate_ldm1stm1(insn, asi, regs); |
76 | load_write_pc(pcv: regs->ARM_pc, regs); |
77 | } |
78 | |
79 | static void __kprobes |
80 | emulate_generic_r0_12_noflags(probes_opcode_t insn, |
81 | struct arch_probes_insn *asi, struct pt_regs *regs) |
82 | { |
83 | register void *rregs asm("r1" ) = regs; |
84 | register void *rfn asm("lr" ) = asi->insn_fn; |
85 | |
86 | __asm__ __volatile__ ( |
87 | ARM( "stmdb sp!, {%[regs], r11} \n\t" ) |
88 | THUMB( "stmdb sp!, {%[regs], r7} \n\t" ) |
89 | "ldmia %[regs], {r0-r12} \n\t" |
90 | #if __LINUX_ARM_ARCH__ >= 6 |
91 | "blx %[fn] \n\t" |
92 | #else |
93 | "str %[fn], [sp, #-4]! \n\t" |
94 | "adr lr, 1f \n\t" |
95 | "ldr pc, [sp], #4 \n\t" |
96 | "1: \n\t" |
97 | #endif |
98 | "ldr lr, [sp], #4 \n\t" /* lr = regs */ |
99 | "stmia lr, {r0-r12} \n\t" |
100 | ARM( "ldr r11, [sp], #4 \n\t" ) |
101 | THUMB( "ldr r7, [sp], #4 \n\t" ) |
102 | : [regs] "=r" (rregs), [fn] "=r" (rfn) |
103 | : "0" (rregs), "1" (rfn) |
104 | : "r0" , "r2" , "r3" , "r4" , "r5" , "r6" , ARM("r7" ) THUMB("r11" ), |
105 | "r8" , "r9" , "r10" , "r12" , "memory" , "cc" |
106 | ); |
107 | } |
108 | |
109 | static void __kprobes |
110 | emulate_generic_r2_14_noflags(probes_opcode_t insn, |
111 | struct arch_probes_insn *asi, struct pt_regs *regs) |
112 | { |
113 | emulate_generic_r0_12_noflags(insn, asi, |
114 | (struct pt_regs *)(regs->uregs+2)); |
115 | } |
116 | |
117 | static void __kprobes |
118 | emulate_ldm_r3_15(probes_opcode_t insn, |
119 | struct arch_probes_insn *asi, struct pt_regs *regs) |
120 | { |
121 | emulate_generic_r0_12_noflags(insn, asi, |
122 | (struct pt_regs *)(regs->uregs+3)); |
123 | load_write_pc(pcv: regs->ARM_pc, regs); |
124 | } |
125 | |
126 | enum probes_insn __kprobes |
127 | kprobe_decode_ldmstm(probes_opcode_t insn, struct arch_probes_insn *asi, |
128 | const struct decode_header *h) |
129 | { |
130 | probes_insn_handler_t *handler = 0; |
131 | unsigned reglist = insn & 0xffff; |
132 | int is_ldm = insn & 0x100000; |
133 | int rn = (insn >> 16) & 0xf; |
134 | |
135 | if (rn <= 12 && (reglist & 0xe000) == 0) { |
136 | /* Instruction only uses registers in the range R0..R12 */ |
137 | handler = emulate_generic_r0_12_noflags; |
138 | |
139 | } else if (rn >= 2 && (reglist & 0x8003) == 0) { |
140 | /* Instruction only uses registers in the range R2..R14 */ |
141 | rn -= 2; |
142 | reglist >>= 2; |
143 | handler = emulate_generic_r2_14_noflags; |
144 | |
145 | } else if (rn >= 3 && (reglist & 0x0007) == 0) { |
146 | /* Instruction only uses registers in the range R3..R15 */ |
147 | if (is_ldm && (reglist & 0x8000)) { |
148 | rn -= 3; |
149 | reglist >>= 3; |
150 | handler = emulate_ldm_r3_15; |
151 | } |
152 | } |
153 | |
154 | if (handler) { |
155 | /* We can emulate the instruction in (possibly) modified form */ |
156 | asi->insn[0] = __opcode_to_mem_arm((insn & 0xfff00000) | |
157 | (rn << 16) | reglist); |
158 | asi->insn_handler = handler; |
159 | return INSN_GOOD; |
160 | } |
161 | |
162 | /* Fallback to slower simulation... */ |
163 | if (reglist & 0x8000) |
164 | handler = is_ldm ? simulate_ldm1_pc : simulate_stm1_pc; |
165 | else |
166 | handler = simulate_ldm1stm1; |
167 | asi->insn_handler = handler; |
168 | return INSN_GOOD_NO_SLOT; |
169 | } |
170 | |
171 | |