[FIX] Change safe_arm and safe_thumb type
[kernel/swap-modules.git] / kprobe / dbi_insn_slots.c
1 /*
2  *  Kernel Probes (KProbes)
3  *  kernel/kprobes.c
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  *
19  * Copyright (C) IBM Corporation, 2002, 2004
20  */
21
22 /*
23  *  Dynamic Binary Instrumentation Module based on KProbes
24  *  modules/kprobe/dbi_insn_slots.c
25  *
26  * This program is free software; you can redistribute it and/or modify
27  * it under the terms of the GNU General Public License as published by
28  * the Free Software Foundation; either version 2 of the License, or
29  * (at your option) any later version.
30  *
31  * This program is distributed in the hope that it will be useful,
32  * but WITHOUT ANY WARRANTY; without even the implied warranty of
33  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
34  * GNU General Public License for more details.
35  *
36  * You should have received a copy of the GNU General Public License
37  * along with this program; if not, write to the Free Software
38  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
39  *
40  * Copyright (C) Samsung Electronics, 2006-2012
41  *
42  * 2008-2009    Alexey Gerenkov <a.gerenkov@samsung.com> User-Space
43  *              Probes initial implementation; Support x86/ARM/MIPS for both user and kernel spaces.
44  * 2010         Ekaterina Gorelkina <e.gorelkina@samsung.com>: redesign module for separating core and arch parts
45  * 2012         Vyacheslav Cherkashin <v.cherkashin@samsung.com> new memory allocator for slots
46  */
47
48 #include "dbi_insn_slots.h"
49 #include "dbi_uprobes.h"
50 #include "dbi_kdebug.h"
51
52 #include <linux/hash.h>
53 #include <linux/mman.h>
54 #include <linux/hugetlb.h>
55
56 #include <linux/slab.h>
57 #include <linux/spinlock.h>
58
59 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
60 extern unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
61                         unsigned long len, unsigned long prot,
62                         unsigned long flags, unsigned long pgoff, unsigned long *populate);
63 #else /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) */
64 extern unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
65                         unsigned long len, unsigned long prot,
66                         unsigned long flags, unsigned long pgoff);
67 #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) */
68
69 struct hlist_head kprobe_insn_pages;
70 struct hlist_head uprobe_insn_pages;
71
72 struct chunk {
73         unsigned long *data;
74         unsigned long first_available;
75         unsigned long count_available;
76
77         spinlock_t    lock;
78         unsigned long size;
79         unsigned long *index;
80 };
81
82 struct kprobe_insn_page
83 {
84         struct hlist_node hlist;
85
86         struct chunk chunk;
87         struct task_struct *task;
88 };
89
90 static void chunk_init(struct chunk *chunk, void *data, size_t size, size_t size_block)
91 {
92         unsigned long i;
93         unsigned long *p;
94
95         spin_lock_init(&chunk->lock);
96         chunk->data = (unsigned long *)data;
97         chunk->first_available = 0;
98         chunk->count_available = size / size_block;
99         chunk->size = chunk->count_available;
100
101         chunk->index = kmalloc(sizeof(*chunk->index)*chunk->count_available, GFP_ATOMIC);
102
103         p = chunk->index;
104         for (i = 0; i != chunk->count_available; ++p) {
105                 *p = ++i;
106         }
107 }
108
109 static void chunk_uninit(struct chunk *chunk)
110 {
111         kfree(chunk->index);
112 }
113
114 static void* chunk_allocate(struct chunk *chunk, size_t size_block)
115 {
116         unsigned long *ret;
117
118         if (!chunk->count_available) {
119                 return NULL;
120         }
121
122         spin_lock(&chunk->lock);
123         ret = chunk->data + chunk->first_available*size_block;
124         chunk->first_available = chunk->index[chunk->first_available];
125         --chunk->count_available;
126         spin_unlock(&chunk->lock);
127
128         return ret;
129 }
130
131 static void chunk_deallocate(struct chunk *chunk, void *p, size_t size_block)
132 {
133         unsigned long idx = ((unsigned long *)p - chunk->data)/size_block;
134
135         spin_lock(&chunk->lock);
136         chunk->index[idx] = chunk->first_available;
137         chunk->first_available = idx;
138         ++chunk->count_available;
139         spin_unlock(&chunk->lock);
140 }
141
142 static inline int chunk_check_ptr(struct chunk *chunk, void *p, size_t size)
143 {
144         if (( chunk->data                             <= (unsigned long *)p) &&
145             ((chunk->data + size/sizeof(chunk->data))  > (unsigned long *)p)) {
146                 return 1;
147         }
148
149         return 0;
150 }
151
152 static inline int chunk_free(struct chunk *chunk)
153 {
154         return (chunk->count_available == chunk->size);
155 }
156
157 static unsigned long alloc_user_pages(struct task_struct *task, unsigned long len, unsigned long prot, unsigned long flags, int atomic)
158 {
159         unsigned long ret = 0;
160         struct task_struct *otask = current;
161         struct mm_struct *mm;
162
163         mm = atomic ? task->active_mm : get_task_mm (task);
164         if (mm) {
165                 if (!atomic) {
166                         if (!down_write_trylock(&mm->mmap_sem)) {
167                                 rcu_read_lock();
168
169                                 up_read(&mm->mmap_sem);
170                                 down_write(&mm->mmap_sem);
171
172                                 rcu_read_unlock();
173                         }
174                 }
175                 // FIXME: its seems to be bad decision to replace 'current' pointer temporarily
176                 current_thread_info()->task = task;
177 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
178                 ret = do_mmap_pgoff(NULL, 0, len, prot, flags, 0, 0);
179 #else /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) */
180                 ret = do_mmap_pgoff(NULL, 0, len, prot, flags, 0);
181 #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) */
182
183                 current_thread_info()->task = otask;
184                 if (!atomic) {
185                         downgrade_write (&mm->mmap_sem);
186                         mmput(mm);
187                 }
188         } else {
189                 printk("proc %d has no mm", task->tgid);
190         }
191
192         return ret;
193 }
194
195 static void *page_new(struct task_struct *task, int atomic)
196 {
197         if (task) {
198                 return (void *)alloc_user_pages(task, PAGE_SIZE,
199                                 PROT_EXEC|PROT_READ|PROT_WRITE,
200                                 MAP_ANONYMOUS|MAP_PRIVATE/*MAP_SHARED*/, atomic);
201         } else {
202                 return kmalloc(PAGE_SIZE, GFP_ATOMIC);
203         }
204 }
205
206 static void page_free(void *data, struct task_struct *task)
207 {
208         if (task) {
209                 //E. G.: This code provides kernel dump because of rescheduling while atomic.
210                 //As workaround, this code was commented. In this case we will have memory leaks
211                 //for instrumented process, but instrumentation process should functionate correctly.
212                 //Planned that good solution for this problem will be done during redesigning KProbe
213                 //for improving supportability and performance.
214 #if 0
215                 mm = get_task_mm (task);
216                 if (mm) {
217                         down_write (&mm->mmap_sem);
218                         do_munmap(mm, (unsigned long)(data), PAGE_SIZE);
219                         up_write (&mm->mmap_sem);
220                         mmput(mm);
221                 }
222 #endif
223                 // FIXME: implement the removal of memory for task
224         } else {
225                 kfree(data);
226         }
227 }
228
229 static inline size_t slot_size(struct task_struct *task)
230 {
231         if (task) {
232                 return UPROBES_TRAMP_LEN;
233         } else {
234                 return KPROBES_TRAMP_LEN;
235         }
236 }
237
238 static struct kprobe_insn_page *kip_new(struct task_struct *task, int atomic)
239 {
240         void *data;
241         struct kprobe_insn_page *kip;
242
243         kip = kmalloc(sizeof(*kip), GFP_ATOMIC);
244         if (kip == NULL) {
245                 return NULL;
246         }
247
248         data = page_new(task, atomic);
249         if(data == NULL) {
250                 kfree(kip);
251                 return NULL;
252         }
253
254         chunk_init(&kip->chunk, data, PAGE_SIZE/sizeof(unsigned long), slot_size(task));
255         kip->task = task;
256
257         return kip;
258 }
259
260 static void kip_free(struct kprobe_insn_page * kip)
261 {
262         chunk_uninit(&kip->chunk);
263         page_free(kip->chunk.data, kip->task);
264         kfree(kip);
265 }
266
267 /**
268  * get_us_insn_slot() - Find a slot on an executable page for an instruction.
269  * We allocate an executable page if there's no room on existing ones.
270  */
271 kprobe_opcode_t *get_insn_slot(struct task_struct *task, int atomic)
272 {
273         kprobe_opcode_t * free_slot;
274         struct kprobe_insn_page *kip;
275         struct hlist_node *pos;
276         struct hlist_head *page_list = task ? &uprobe_insn_pages : &kprobe_insn_pages;
277
278         swap_hlist_for_each_entry_rcu(kip, pos, page_list, hlist) {
279                 if (!task || (kip->task->tgid == task->tgid)) {
280                         free_slot = chunk_allocate(&kip->chunk, slot_size(task));
281                         if (free_slot == NULL) {
282                                 break;
283                         }
284
285                         return free_slot;
286                 }
287         }
288
289         kip = kip_new(task, atomic);
290         if(kip == NULL)
291                 return NULL;
292
293         INIT_HLIST_NODE (&kip->hlist);
294         hlist_add_head_rcu(&kip->hlist, page_list);
295
296         return chunk_allocate(&kip->chunk, slot_size(task));
297 }
298
299 void free_insn_slot(struct hlist_head *page_list, struct task_struct *task, kprobe_opcode_t *slot)
300 {
301         struct kprobe_insn_page *kip;
302         struct hlist_node *pos;
303
304         swap_hlist_for_each_entry_rcu(kip, pos, page_list, hlist) {
305                 if (!(!task || (kip->task->tgid == task->tgid)))
306                         continue;
307
308                 if (!chunk_check_ptr(&kip->chunk, slot, PAGE_SIZE))
309                         continue;
310
311                 chunk_deallocate(&kip->chunk, slot, slot_size(task));
312
313                 if (chunk_free(&kip->chunk)) {
314                         hlist_del_rcu(&kip->hlist);
315                         kip_free(kip);
316                 }
317
318                 return;
319         }
320
321         panic("free_insn_slot: slot=%p is not data base\n", slot);
322 }
323
324 #ifdef CONFIG_ARM
325 static struct kprobe *get_kprobe_by_insn_slot_arm(kprobe_opcode_t *addr, pid_t tgid)
326 {
327         struct hlist_head *head;
328         struct hlist_node *node;
329         struct kprobe *p, *retVal = NULL;
330
331         //TODO: test - two processes invokes instrumented function
332         head = &uprobe_insn_slot_table[hash_ptr (addr, KPROBE_HASH_BITS)];
333         swap_hlist_for_each_entry_rcu (p, node, head, is_hlist_arm) {
334                 if (p->ainsn.insn == addr && tgid == p->tgid) {
335                         retVal = p;
336                         break;
337                 }
338         }
339
340         DBPRINTF ("get_kprobe: probe %p", retVal);
341         return retVal;
342 }
343
344 static struct kprobe *get_kprobe_by_insn_slot_thumb(kprobe_opcode_t *addr, pid_t tgid)
345 {
346         struct hlist_head *head;
347         struct hlist_node *node;
348         struct kprobe *p, *retVal = NULL;
349
350         //TODO: test - two processes invokes instrumented function
351         head = &uprobe_insn_slot_table[hash_ptr (addr, KPROBE_HASH_BITS)];
352         swap_hlist_for_each_entry_rcu (p, node, head, is_hlist_thumb) {
353                 if (p->ainsn.insn == addr && tgid == p->tgid) {
354                         retVal = p;
355                         break;
356                 }
357         }
358
359         DBPRINTF ("get_kprobe: probe %p", retVal);
360         return retVal;
361 }
362
363 struct kprobe *get_kprobe_by_insn_slot(kprobe_opcode_t *addr, pid_t tgid, struct pt_regs *regs)
364 {
365         struct kprobe *p = NULL;
366
367         if (!thumb_mode(regs)) {
368                 p = get_kprobe_by_insn_slot_arm(addr - UPROBES_TRAMP_RET_BREAK_IDX, tgid);
369         } else {
370                 p = get_kprobe_by_insn_slot_thumb((kprobe_opcode_t *)((unsigned long)addr - 0x1a), tgid);
371         }
372
373         return p;
374 }
375 #else /* CONFIG_ARM */
376 struct kprobe *get_kprobe_by_insn_slot (void *addr, int tgid, struct task_struct *ctask)
377 {
378         struct hlist_head *head;
379         struct hlist_node *node;
380         struct kprobe *p, *retVal = NULL;
381
382         //TODO: test - two processes invokes instrumented function
383         head = &uprobe_insn_slot_table[hash_ptr (addr, KPROBE_HASH_BITS)];
384         hlist_for_each_entry_rcu (p, node, head, is_hlist) {
385                 if (p->ainsn.insn == addr && tgid == p->tgid) {
386                         retVal = p;
387                         break;
388                 }
389         }
390
391         DBPRINTF ("get_kprobe: probe %p", retVal);
392         return retVal;
393 }
394 #endif /* CONFIG_ARM */
395