[IMPROVE] implement bidirectionality sspt tree
[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_kdebug.h"
50
51 #include <linux/hash.h>
52 #include <linux/mman.h>
53 #include <linux/hugetlb.h>
54
55 #include <linux/slab.h>
56 #include <linux/spinlock.h>
57 #include <linux/module.h>
58
59
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);
63
64 struct chunk {
65         unsigned long *data;
66         unsigned long first_available;
67         unsigned long count_available;
68
69         spinlock_t    lock;
70         unsigned long size;
71         unsigned long *index;
72 };
73
74 struct kprobe_insn_page
75 {
76         struct hlist_node hlist;
77
78         struct chunk chunk;
79         struct task_struct *task;
80 };
81
82 static void chunk_init(struct chunk *chunk, void *data, size_t size, size_t size_block)
83 {
84         unsigned long i;
85         unsigned long *p;
86
87         spin_lock_init(&chunk->lock);
88         chunk->data = (unsigned long *)data;
89         chunk->first_available = 0;
90         chunk->count_available = size / size_block;
91         chunk->size = chunk->count_available;
92
93         chunk->index = kmalloc(sizeof(*chunk->index)*chunk->count_available, GFP_ATOMIC);
94
95         p = chunk->index;
96         for (i = 0; i != chunk->count_available; ++p) {
97                 *p = ++i;
98         }
99 }
100
101 static void chunk_uninit(struct chunk *chunk)
102 {
103         kfree(chunk->index);
104 }
105
106 static void* chunk_allocate(struct chunk *chunk, size_t size_block)
107 {
108         unsigned long *ret;
109
110         if (!chunk->count_available) {
111                 return NULL;
112         }
113
114         spin_lock(&chunk->lock);
115         ret = chunk->data + chunk->first_available*size_block;
116         chunk->first_available = chunk->index[chunk->first_available];
117         --chunk->count_available;
118         spin_unlock(&chunk->lock);
119
120         return ret;
121 }
122
123 static void chunk_deallocate(struct chunk *chunk, void *p, size_t size_block)
124 {
125         unsigned long idx = ((unsigned long *)p - chunk->data)/size_block;
126
127         spin_lock(&chunk->lock);
128         chunk->index[idx] = chunk->first_available;
129         chunk->first_available = idx;
130         ++chunk->count_available;
131         spin_unlock(&chunk->lock);
132 }
133
134 static inline int chunk_check_ptr(struct chunk *chunk, void *p, size_t size)
135 {
136         if (( chunk->data                             <= (unsigned long *)p) &&
137             ((chunk->data + size/sizeof(chunk->data))  > (unsigned long *)p)) {
138                 return 1;
139         }
140
141         return 0;
142 }
143
144 static inline int chunk_free(struct chunk *chunk)
145 {
146         return (chunk->count_available == chunk->size);
147 }
148
149 static unsigned long alloc_user_pages(struct task_struct *task, unsigned long len, unsigned long prot, unsigned long flags, int atomic)
150 {
151         unsigned long ret = 0;
152         struct task_struct *otask = current;
153         struct mm_struct *mm;
154
155         mm = atomic ? task->active_mm : get_task_mm (task);
156         if (mm) {
157                 if (!atomic) {
158                         if (!down_write_trylock(&mm->mmap_sem)) {
159                                 rcu_read_lock();
160
161                                 up_read(&mm->mmap_sem);
162                                 down_write(&mm->mmap_sem);
163
164                                 rcu_read_unlock();
165                         }
166                 }
167                 // FIXME: its seems to be bad decision to replace 'current' pointer temporarily
168                 current_thread_info()->task = task;
169                 ret = do_mmap_pgoff(NULL, 0, len, prot, flags, 0);
170                 current_thread_info()->task = otask;
171                 if (!atomic) {
172                         downgrade_write (&mm->mmap_sem);
173                         mmput(mm);
174                 }
175         } else {
176                 printk("proc %d has no mm", task->tgid);
177         }
178
179         return ret;
180 }
181
182 static void *page_new(struct task_struct *task, int atomic)
183 {
184         if (task) {
185                 return (void *)alloc_user_pages(task, PAGE_SIZE,
186                                 PROT_EXEC|PROT_READ|PROT_WRITE,
187                                 MAP_ANONYMOUS|MAP_PRIVATE/*MAP_SHARED*/, atomic);
188         } else {
189                 return kmalloc(PAGE_SIZE, GFP_ATOMIC);
190         }
191 }
192
193 static void page_free(void *data, struct task_struct *task)
194 {
195         if (task) {
196                 //E. G.: This code provides kernel dump because of rescheduling while atomic.
197                 //As workaround, this code was commented. In this case we will have memory leaks
198                 //for instrumented process, but instrumentation process should functionate correctly.
199                 //Planned that good solution for this problem will be done during redesigning KProbe
200                 //for improving supportability and performance.
201 #if 0
202                 mm = get_task_mm (task);
203                 if (mm) {
204                         down_write (&mm->mmap_sem);
205                         do_munmap(mm, (unsigned long)(data), PAGE_SIZE);
206                         up_write (&mm->mmap_sem);
207                         mmput(mm);
208                 }
209 #endif
210                 // FIXME: implement the removal of memory for task
211         } else {
212                 kfree(data);
213         }
214 }
215
216 static inline size_t slot_size(struct task_struct *task)
217 {
218         if (task) {
219                 return UPROBES_TRAMP_LEN;
220         } else {
221                 return KPROBES_TRAMP_LEN;
222         }
223 }
224
225 static struct kprobe_insn_page *kip_new(struct task_struct *task, int atomic)
226 {
227         void *data;
228         struct kprobe_insn_page *kip;
229
230         kip = kmalloc(sizeof(*kip), GFP_ATOMIC);
231         if (kip == NULL) {
232                 return NULL;
233         }
234
235         data = page_new(task, atomic);
236         if(data == NULL) {
237                 kfree(kip);
238                 return NULL;
239         }
240
241         chunk_init(&kip->chunk, data, PAGE_SIZE/sizeof(unsigned long), slot_size(task));
242         kip->task = task;
243
244         return kip;
245 }
246
247 static void kip_free(struct kprobe_insn_page * kip)
248 {
249         chunk_uninit(&kip->chunk);
250         page_free(kip->chunk.data, kip->task);
251         kfree(kip);
252 }
253
254 /**
255  * get_us_insn_slot() - Find a slot on an executable page for an instruction.
256  * We allocate an executable page if there's no room on existing ones.
257  */
258 kprobe_opcode_t *get_insn_slot(struct task_struct *task, struct hlist_head *page_list, int atomic)
259 {
260         kprobe_opcode_t * free_slot;
261         struct kprobe_insn_page *kip;
262         struct hlist_node *pos;
263
264         hlist_for_each_entry_rcu(kip, pos, page_list, hlist) {
265                 if (!task || (kip->task->tgid == task->tgid)) {
266                         free_slot = chunk_allocate(&kip->chunk, slot_size(task));
267                         if (free_slot == NULL) {
268                                 break;
269                         }
270
271                         return free_slot;
272                 }
273         }
274
275         kip = kip_new(task, atomic);
276         if(kip == NULL)
277                 return NULL;
278
279         INIT_HLIST_NODE (&kip->hlist);
280         hlist_add_head_rcu(&kip->hlist, page_list);
281
282         return chunk_allocate(&kip->chunk, slot_size(task));
283 }
284 EXPORT_SYMBOL_GPL(get_insn_slot);
285
286 void free_insn_slot(struct hlist_head *page_list, struct task_struct *task, kprobe_opcode_t *slot)
287 {
288         struct kprobe_insn_page *kip;
289         struct hlist_node *pos;
290
291         hlist_for_each_entry_rcu(kip, pos, page_list, hlist) {
292                 if (!(!task || (kip->task->tgid == task->tgid)))
293                         continue;
294
295                 if (!chunk_check_ptr(&kip->chunk, slot, PAGE_SIZE))
296                         continue;
297
298                 chunk_deallocate(&kip->chunk, slot, slot_size(task));
299
300                 if (chunk_free(&kip->chunk)) {
301                         hlist_del_rcu(&kip->hlist);
302                         kip_free(kip);
303                 }
304
305                 return;
306         }
307
308         panic("free_insn_slot: slot=%p is not data base\n", slot);
309 }
310 EXPORT_SYMBOL_GPL(free_insn_slot);