]>
Commit | Line | Data |
---|---|---|
7d8aa2ee MW |
1 | /* -*- linux-c -*- |
2 | * VMA tracking and lookup functions. | |
3 | * | |
ef36f781 | 4 | * Copyright (C) 2005-2014 Red Hat Inc. |
7d8aa2ee MW |
5 | * Copyright (C) 2006 Intel Corporation. |
6 | * | |
7 | * This file is part of systemtap, and is free software. You can | |
8 | * redistribute it and/or modify it under the terms of the GNU General | |
9 | * Public License (GPL); either version 2, or (at your option) any | |
10 | * later version. | |
11 | */ | |
12 | ||
13 | #ifndef _STP_VMA_C_ | |
14 | #define _STP_VMA_C_ | |
15 | ||
16 | #include "sym.h" | |
2466bf70 | 17 | #include "stp_string.c" |
7d8aa2ee MW |
18 | #include "task_finder_vma.c" |
19 | ||
18da5887 MW |
20 | #include <asm/uaccess.h> |
21 | ||
22 | static void _stp_vma_match_vdso(struct task_struct *tsk) | |
23 | { | |
24 | /* vdso is arch specific */ | |
3b62d74b | 25 | #if defined(STAPCONF_MM_CONTEXT_VDSO) || defined(STAPCONF_MM_CONTEXT_VDSO_BASE) |
18da5887 MW |
26 | int i, j; |
27 | if (tsk->mm) | |
28 | { | |
29 | struct _stp_module *found = NULL; | |
3b62d74b MW |
30 | |
31 | #ifdef STAPCONF_MM_CONTEXT_VDSO | |
18da5887 | 32 | unsigned long vdso_addr = (unsigned long) tsk->mm->context.vdso; |
3b62d74b MW |
33 | #else |
34 | unsigned long vdso_addr = tsk->mm->context.vdso_base; | |
35 | #endif | |
36 | ||
a2b0b5c8 | 37 | dbug_task_vma(1,"tsk: %d vdso: 0x%lx\n", tsk->pid, vdso_addr); |
3b62d74b | 38 | |
18da5887 MW |
39 | for (i = 0; i < _stp_num_modules && found == NULL; i++) { |
40 | struct _stp_module *m = _stp_modules[i]; | |
41 | if (m->path[0] == '/' | |
42 | && m->num_sections == 1 | |
43 | && strncmp(m->name, "vdso", 4) == 0) | |
44 | { | |
45 | unsigned long notes_addr; | |
46 | int all_ok = 1; | |
31d50137 | 47 | notes_addr = vdso_addr + m->build_id_offset; |
8ca8aa7d | 48 | dbug_task_vma(1,"notes_addr %s: 0x%lx + 0x%lx = 0x%lx (len: %x)\n", m->path, |
31d50137 | 49 | vdso_addr, m->build_id_offset, notes_addr, m->build_id_len); |
18da5887 MW |
50 | for (j = 0; j < m->build_id_len; j++) |
51 | { | |
52 | int rc; | |
53 | unsigned char b; | |
414b89b8 DS |
54 | |
55 | /* | |
56 | * Why check CONFIG_UTRACE here? If we're using real | |
57 | * in-kernel utrace, we can always just call | |
58 | * get_user() (since tsk == current). | |
59 | * | |
f1410a8a DS |
60 | * Since we're only reading here, we can call |
61 | * __access_process_vm_noflush(), which only calls | |
62 | * things that are exported. | |
414b89b8 DS |
63 | */ |
64 | #ifdef CONFIG_UTRACE | |
65 | rc = copy_from_user(&b, (void*)(notes_addr + j), 1); | |
66 | #else | |
c0456b6f DS |
67 | if (tsk == current) |
68 | { | |
69 | rc = copy_from_user(&b, (void*)(notes_addr + j), 1); | |
70 | } | |
71 | else | |
72 | { | |
f1410a8a DS |
73 | rc = (__access_process_vm_noflush(tsk, (notes_addr + j), |
74 | &b, 1, 0) != 1); | |
c0456b6f | 75 | } |
414b89b8 | 76 | #endif |
18da5887 MW |
77 | if (rc || b != m->build_id_bits[j]) |
78 | { | |
a2b0b5c8 | 79 | dbug_task_vma(1,"darn, not equal (rc=%d) at %d (0x%x != 0x%x)\n", |
18da5887 | 80 | rc, j, b, m->build_id_bits[j]); |
18da5887 MW |
81 | all_ok = 0; |
82 | break; | |
83 | } | |
84 | } | |
85 | if (all_ok) | |
86 | found = m; | |
87 | } | |
88 | } | |
89 | if (found != NULL) | |
90 | { | |
91 | stap_add_vma_map_info(tsk, vdso_addr, | |
92 | vdso_addr + found->sections[0].size, | |
e4d6479f | 93 | "vdso", found); |
a2b0b5c8 | 94 | dbug_task_vma(1,"found vdso: %s\n", found->path); |
18da5887 MW |
95 | } |
96 | } | |
97 | #endif /* STAPCONF_MM_CONTEXT_VDSO */ | |
98 | } | |
99 | ||
c3c8589d | 100 | #ifdef HAVE_TASK_FINDER |
18da5887 MW |
101 | /* exec callback, will try to match vdso for new process, |
102 | will drop all vma maps for a process that disappears. */ | |
7d8aa2ee MW |
103 | static int _stp_vma_exec_cb(struct stap_task_finder_target *tgt, |
104 | struct task_struct *tsk, | |
105 | int register_p, | |
106 | int process_p) | |
107 | { | |
a2b0b5c8 | 108 | dbug_task_vma(1, |
7d8aa2ee MW |
109 | "tsk %d:%d , register_p: %d, process_p: %d\n", |
110 | tsk->pid, tsk->tgid, register_p, process_p); | |
18da5887 MW |
111 | if (process_p) |
112 | { | |
113 | if (register_p) | |
114 | _stp_vma_match_vdso(tsk); | |
115 | else | |
116 | stap_drop_vma_maps(tsk); | |
117 | } | |
7d8aa2ee MW |
118 | |
119 | return 0; | |
120 | } | |
121 | ||
122 | /* mmap callback, will match new vma with _stp_module or register vma name. */ | |
123 | static int _stp_vma_mmap_cb(struct stap_task_finder_target *tgt, | |
124 | struct task_struct *tsk, | |
125 | char *path, struct dentry *dentry, | |
126 | unsigned long addr, | |
127 | unsigned long length, | |
128 | unsigned long offset, | |
129 | unsigned long vm_flags) | |
130 | { | |
131 | int i, res; | |
132 | struct _stp_module *module = NULL; | |
295de711 DS |
133 | const char *name = ((dentry != NULL) ? (char *)dentry->d_name.name |
134 | : NULL); | |
35f71b69 FCE |
135 | |
136 | if (path == NULL || *path == '\0') /* unknown? */ | |
137 | path = (char *)name; /* we'll copy this soon, in ..._add_vma_... */ | |
7d8aa2ee | 138 | |
a2b0b5c8 | 139 | dbug_task_vma(1, |
7d8aa2ee MW |
140 | "mmap_cb: tsk %d:%d path %s, addr 0x%08lx, length 0x%08lx, offset 0x%lx, flags 0x%lx\n", |
141 | tsk->pid, tsk->tgid, path, addr, length, offset, vm_flags); | |
7d8aa2ee MW |
142 | // We are only interested in the first load of the whole module that |
143 | // is executable. We register whether or not we know the module, | |
144 | // so we can later lookup the name given an address for this task. | |
bde34231 MW |
145 | if (path != NULL && offset == 0 && (vm_flags & VM_EXEC) |
146 | && stap_find_vma_map_info(tsk, addr, NULL, NULL, NULL, NULL) != 0) { | |
7d8aa2ee MW |
147 | for (i = 0; i < _stp_num_modules; i++) { |
148 | if (strcmp(path, _stp_modules[i]->path) == 0) | |
149 | { | |
76783a24 MW |
150 | unsigned long vm_start = 0; |
151 | unsigned long vm_end = 0; | |
a2b0b5c8 | 152 | dbug_task_vma(1, |
7d8aa2ee MW |
153 | "vm_cb: matched path %s to module (sec: %s)\n", |
154 | path, _stp_modules[i]->sections[0].name); | |
7d8aa2ee | 155 | module = _stp_modules[i]; |
76783a24 MW |
156 | /* Make sure we really don't know about this module |
157 | yet. If we do know, we might want to extend | |
158 | the coverage. */ | |
159 | res = stap_find_vma_map_info_user(tsk->group_leader, | |
160 | module, | |
161 | &vm_start, &vm_end, | |
162 | NULL); | |
163 | if (res == -ESRCH) | |
164 | res = stap_add_vma_map_info(tsk->group_leader, | |
165 | addr, addr + length, | |
166 | path, module); | |
167 | else if (res == 0 && vm_end + 1 == addr) | |
168 | res = stap_extend_vma_map_info(tsk->group_leader, | |
169 | vm_start, | |
170 | addr + length); | |
c2537ee6 MW |
171 | /* VMA entries are allocated dynamically, this is fine, |
172 | * since we are in a task_finder callback, which is in | |
173 | * user context. */ | |
a6af96f5 | 174 | if (res != 0) { |
c2537ee6 | 175 | _stp_error ("Couldn't register module '%s' for pid %d (%d)\n", _stp_modules[i]->path, tsk->group_leader->pid, res); |
a6af96f5 | 176 | } |
7d8aa2ee MW |
177 | return 0; |
178 | } | |
179 | } | |
180 | ||
181 | /* None of the tracked modules matched, register without, | |
182 | * to make sure we can lookup the name later. Ignore errors, | |
183 | * we will just report unknown when asked and tables were | |
184 | * full. Restrict to target process when given to preserve | |
185 | * vma_map entry slots. */ | |
186 | if (_stp_target == 0 | |
187 | || _stp_target == tsk->group_leader->pid) | |
188 | { | |
189 | res = stap_add_vma_map_info(tsk->group_leader, addr, | |
35f71b69 | 190 | addr + length, path, NULL); |
a2b0b5c8 | 191 | dbug_task_vma(1, |
b100897f | 192 | "registered '%s' for %d (res:%d) [%lx-%lx]\n", |
35f71b69 | 193 | path, tsk->group_leader->pid, |
b100897f | 194 | res, addr, addr + length); |
7d8aa2ee MW |
195 | } |
196 | ||
b100897f MW |
197 | } else if (path != NULL) { |
198 | // Once registered, we may want to extend an earlier | |
199 | // registered region. A segment might be mapped with | |
200 | // different flags for different offsets. If so we want | |
201 | // to record the extended range so we can address more | |
202 | // precisely to module names and symbols. | |
203 | res = stap_extend_vma_map_info(tsk->group_leader, | |
204 | addr, addr + length); | |
a2b0b5c8 | 205 | dbug_task_vma(1, |
b100897f MW |
206 | "extended '%s' for %d (res:%d) [%lx-%lx]\n", |
207 | path, tsk->group_leader->pid, | |
208 | res, addr, addr + length); | |
7d8aa2ee MW |
209 | } |
210 | return 0; | |
211 | } | |
212 | ||
213 | /* munmap callback, removes vma map info. */ | |
214 | static int _stp_vma_munmap_cb(struct stap_task_finder_target *tgt, | |
215 | struct task_struct *tsk, | |
216 | unsigned long addr, | |
217 | unsigned long length) | |
218 | { | |
219 | /* Unconditionally remove vm map info, ignore if not present. */ | |
220 | stap_remove_vma_map_info(tsk->group_leader, addr); | |
221 | return 0; | |
222 | } | |
223 | ||
c3a9ae38 SM |
224 | #endif |
225 | ||
7d8aa2ee MW |
226 | /* Initializes the vma tracker. */ |
227 | static int _stp_vma_init(void) | |
228 | { | |
229 | int rc = 0; | |
a915ecd6 | 230 | #ifdef HAVE_TASK_FINDER |
7d8aa2ee MW |
231 | static struct stap_task_finder_target vmcb = { |
232 | // NB: no .pid, no .procname filters here. | |
233 | // This means that we get a system-wide mmap monitoring | |
234 | // widget while the script is running. (The | |
235 | // system-wideness may be restricted by stap -c or | |
236 | // -x.) But this seems to be necessary if we want to | |
237 | // to stack tracebacks through arbitrary shared libraries. | |
238 | // | |
239 | // XXX: There may be an optimization opportunity | |
240 | // for executables (for which the main task-finder | |
241 | // callback should be sufficient). | |
242 | .pid = 0, | |
243 | .procname = NULL, | |
1af100fc | 244 | .purpose = "vma tracking", |
7d8aa2ee MW |
245 | .callback = &_stp_vma_exec_cb, |
246 | .mmap_callback = &_stp_vma_mmap_cb, | |
247 | .munmap_callback = &_stp_vma_munmap_cb, | |
248 | .mprotect_callback = NULL | |
249 | }; | |
14fc9001 MW |
250 | rc = stap_initialize_vma_map (); |
251 | if (rc != 0) { | |
252 | _stp_error("Couldn't initialize vma map: %d\n", rc); | |
253 | return rc; | |
254 | } | |
a2b0b5c8 | 255 | dbug_task_vma(1, |
7d8aa2ee | 256 | "registering vmcb (_stap_target: %d)\n", _stp_target); |
7d8aa2ee MW |
257 | rc = stap_register_task_finder_target (& vmcb); |
258 | if (rc != 0) | |
259 | _stp_error("Couldn't register task finder target: %d\n", rc); | |
260 | #endif | |
261 | return rc; | |
262 | } | |
263 | ||
14fc9001 MW |
264 | /* Get rid of the vma tracker (memory). */ |
265 | static void _stp_vma_done(void) | |
266 | { | |
267 | #if defined(CONFIG_UTRACE) | |
268 | stap_destroy_vma_map(); | |
269 | #endif | |
270 | } | |
271 | ||
7d8aa2ee | 272 | #endif /* _STP_VMA_C_ */ |