]>
Commit | Line | Data |
---|---|---|
7d8aa2ee MW |
1 | /* -*- linux-c -*- |
2 | * VMA tracking and lookup functions. | |
3 | * | |
8e1a57de | 4 | * Copyright (C) 2005-2019 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]; | |
6ec3b7c0 YZ |
41 | if (m->path |
42 | && m->path[0] == '/' | |
b3131710 | 43 | && m->num_sections == 1) |
18da5887 MW |
44 | { |
45 | unsigned long notes_addr; | |
46 | int all_ok = 1; | |
b3131710 DS |
47 | |
48 | /* Assume that if the path's basename starts with 'vdso' | |
49 | * and ends with '.so', it is the vdso. | |
50 | * | |
51 | * Note that this logic should match up with the logic in | |
52 | * the find_vdso() function in translate.cxx. */ | |
53 | const char *name = strrchr(m->path, '/'); | |
54 | if (name) | |
55 | { | |
56 | const char *ext; | |
57 | ||
58 | name++; | |
59 | ext = strrchr(name, '.'); | |
60 | if (!ext | |
61 | || strncmp("vdso", name, 4) != 0 | |
62 | || strcmp(".so", ext) != 0) | |
63 | continue; | |
64 | } | |
65 | ||
31d50137 | 66 | notes_addr = vdso_addr + m->build_id_offset; |
8ca8aa7d | 67 | dbug_task_vma(1,"notes_addr %s: 0x%lx + 0x%lx = 0x%lx (len: %x)\n", m->path, |
31d50137 | 68 | vdso_addr, m->build_id_offset, notes_addr, m->build_id_len); |
18da5887 MW |
69 | for (j = 0; j < m->build_id_len; j++) |
70 | { | |
71 | int rc; | |
72 | unsigned char b; | |
414b89b8 DS |
73 | |
74 | /* | |
f1410a8a DS |
75 | * Since we're only reading here, we can call |
76 | * __access_process_vm_noflush(), which only calls | |
77 | * things that are exported. | |
414b89b8 | 78 | */ |
c0456b6f DS |
79 | if (tsk == current) |
80 | { | |
81 | rc = copy_from_user(&b, (void*)(notes_addr + j), 1); | |
82 | } | |
83 | else | |
84 | { | |
f1410a8a DS |
85 | rc = (__access_process_vm_noflush(tsk, (notes_addr + j), |
86 | &b, 1, 0) != 1); | |
c0456b6f | 87 | } |
18da5887 MW |
88 | if (rc || b != m->build_id_bits[j]) |
89 | { | |
a2b0b5c8 | 90 | dbug_task_vma(1,"darn, not equal (rc=%d) at %d (0x%x != 0x%x)\n", |
18da5887 | 91 | rc, j, b, m->build_id_bits[j]); |
18da5887 MW |
92 | all_ok = 0; |
93 | break; | |
94 | } | |
95 | } | |
96 | if (all_ok) | |
97 | found = m; | |
98 | } | |
99 | } | |
100 | if (found != NULL) | |
101 | { | |
102 | stap_add_vma_map_info(tsk, vdso_addr, | |
d1b55487 | 103 | vdso_addr + found->sections[0].size, 0, |
e4d6479f | 104 | "vdso", found); |
a2b0b5c8 | 105 | dbug_task_vma(1,"found vdso: %s\n", found->path); |
18da5887 MW |
106 | } |
107 | } | |
108 | #endif /* STAPCONF_MM_CONTEXT_VDSO */ | |
109 | } | |
110 | ||
c3c8589d | 111 | #ifdef HAVE_TASK_FINDER |
18da5887 MW |
112 | /* exec callback, will try to match vdso for new process, |
113 | will drop all vma maps for a process that disappears. */ | |
7d8aa2ee MW |
114 | static int _stp_vma_exec_cb(struct stap_task_finder_target *tgt, |
115 | struct task_struct *tsk, | |
116 | int register_p, | |
117 | int process_p) | |
118 | { | |
a2b0b5c8 | 119 | dbug_task_vma(1, |
7d8aa2ee MW |
120 | "tsk %d:%d , register_p: %d, process_p: %d\n", |
121 | tsk->pid, tsk->tgid, register_p, process_p); | |
18da5887 MW |
122 | if (process_p) |
123 | { | |
124 | if (register_p) | |
125 | _stp_vma_match_vdso(tsk); | |
126 | else | |
127 | stap_drop_vma_maps(tsk); | |
128 | } | |
7d8aa2ee MW |
129 | |
130 | return 0; | |
131 | } | |
132 | ||
133 | /* mmap callback, will match new vma with _stp_module or register vma name. */ | |
134 | static int _stp_vma_mmap_cb(struct stap_task_finder_target *tgt, | |
135 | struct task_struct *tsk, | |
136 | char *path, struct dentry *dentry, | |
137 | unsigned long addr, | |
138 | unsigned long length, | |
139 | unsigned long offset, | |
140 | unsigned long vm_flags) | |
141 | { | |
142 | int i, res; | |
143 | struct _stp_module *module = NULL; | |
2eb7fdfd | 144 | const char *ori_path = NULL; |
295de711 DS |
145 | const char *name = ((dentry != NULL) ? (char *)dentry->d_name.name |
146 | : NULL); | |
35f71b69 FCE |
147 | |
148 | if (path == NULL || *path == '\0') /* unknown? */ | |
149 | path = (char *)name; /* we'll copy this soon, in ..._add_vma_... */ | |
7d8aa2ee | 150 | |
a2b0b5c8 | 151 | dbug_task_vma(1, |
7d8aa2ee MW |
152 | "mmap_cb: tsk %d:%d path %s, addr 0x%08lx, length 0x%08lx, offset 0x%lx, flags 0x%lx\n", |
153 | tsk->pid, tsk->tgid, path, addr, length, offset, vm_flags); | |
4ae4592f FCE |
154 | |
155 | // We used to be only interested in the first load of the whole module that | |
156 | // is executable. But with modern enough gcc/ld.so, executables are mapped | |
d1b55487 SA |
157 | // in more small pieces (r--p,r-xp,rw-p, instead of r-xp, rw-p). NB: |
158 | // the first section might not be executable, so there can be an offset. | |
4ae4592f FCE |
159 | // |
160 | // We register whether or not we know the module, | |
7d8aa2ee | 161 | // so we can later lookup the name given an address for this task. |
d1b55487 | 162 | if (path != NULL && |
2eb7fdfd JL |
163 | (stap_find_vma_map_info(tsk->group_leader, addr, NULL, NULL, NULL, &ori_path, NULL) != 0 || |
164 | strcmp(ori_path ?: "", path) != 0)) { | |
7d8aa2ee | 165 | for (i = 0; i < _stp_num_modules; i++) { |
641e6d92 FCE |
166 | // PR20433: papering over possibility of NULL pointers |
167 | if (strcmp(path ?: "", _stp_modules[i]->path ?: "") == 0) | |
7d8aa2ee | 168 | { |
76783a24 MW |
169 | unsigned long vm_start = 0; |
170 | unsigned long vm_end = 0; | |
a2b0b5c8 | 171 | dbug_task_vma(1, |
7d8aa2ee MW |
172 | "vm_cb: matched path %s to module (sec: %s)\n", |
173 | path, _stp_modules[i]->sections[0].name); | |
7d8aa2ee | 174 | module = _stp_modules[i]; |
dd0dee22 SA |
175 | /* Make sure we really don't know about this module |
176 | yet. If we do know, we might want to extend | |
177 | the coverage. */ | |
178 | res = stap_find_vma_map_info_user(tsk->group_leader, | |
179 | module, | |
180 | &vm_start, &vm_end, | |
181 | NULL); | |
4a3ad353 | 182 | if (res == -ESRCH) |
dd0dee22 | 183 | res = stap_add_vma_map_info(tsk->group_leader, |
4a3ad353 JL |
184 | addr, addr + length, |
185 | 0, path, module); | |
186 | else | |
187 | res = stap_add_vma_map_info(tsk->group_leader, | |
188 | addr, addr + length, | |
189 | addr - vm_start, path, module); | |
190 | ||
c2537ee6 MW |
191 | /* VMA entries are allocated dynamically, this is fine, |
192 | * since we are in a task_finder callback, which is in | |
193 | * user context. */ | |
ea7c27bb | 194 | if (res != 0 && res != -EEXIST) { |
c2537ee6 | 195 | _stp_error ("Couldn't register module '%s' for pid %d (%d)\n", _stp_modules[i]->path, tsk->group_leader->pid, res); |
a6af96f5 | 196 | } |
7d8aa2ee MW |
197 | return 0; |
198 | } | |
199 | } | |
200 | ||
201 | /* None of the tracked modules matched, register without, | |
202 | * to make sure we can lookup the name later. Ignore errors, | |
203 | * we will just report unknown when asked and tables were | |
204 | * full. Restrict to target process when given to preserve | |
205 | * vma_map entry slots. */ | |
206 | if (_stp_target == 0 | |
207 | || _stp_target == tsk->group_leader->pid) | |
208 | { | |
209 | res = stap_add_vma_map_info(tsk->group_leader, addr, | |
d1b55487 SA |
210 | addr + length, offset, path, |
211 | NULL); | |
a2b0b5c8 | 212 | dbug_task_vma(1, |
b100897f | 213 | "registered '%s' for %d (res:%d) [%lx-%lx]\n", |
35f71b69 | 214 | path, tsk->group_leader->pid, |
b100897f | 215 | res, addr, addr + length); |
7d8aa2ee MW |
216 | } |
217 | ||
b100897f MW |
218 | } else if (path != NULL) { |
219 | // Once registered, we may want to extend an earlier | |
220 | // registered region. A segment might be mapped with | |
221 | // different flags for different offsets. If so we want | |
222 | // to record the extended range so we can address more | |
223 | // precisely to module names and symbols. | |
224 | res = stap_extend_vma_map_info(tsk->group_leader, | |
225 | addr, addr + length); | |
a2b0b5c8 | 226 | dbug_task_vma(1, |
b100897f MW |
227 | "extended '%s' for %d (res:%d) [%lx-%lx]\n", |
228 | path, tsk->group_leader->pid, | |
229 | res, addr, addr + length); | |
7d8aa2ee MW |
230 | } |
231 | return 0; | |
232 | } | |
233 | ||
234 | /* munmap callback, removes vma map info. */ | |
235 | static int _stp_vma_munmap_cb(struct stap_task_finder_target *tgt, | |
236 | struct task_struct *tsk, | |
237 | unsigned long addr, | |
238 | unsigned long length) | |
239 | { | |
240 | /* Unconditionally remove vm map info, ignore if not present. */ | |
241 | stap_remove_vma_map_info(tsk->group_leader, addr); | |
242 | return 0; | |
243 | } | |
244 | ||
c3a9ae38 SM |
245 | #endif |
246 | ||
7d8aa2ee MW |
247 | /* Initializes the vma tracker. */ |
248 | static int _stp_vma_init(void) | |
249 | { | |
250 | int rc = 0; | |
a915ecd6 | 251 | #ifdef HAVE_TASK_FINDER |
7d8aa2ee MW |
252 | static struct stap_task_finder_target vmcb = { |
253 | // NB: no .pid, no .procname filters here. | |
254 | // This means that we get a system-wide mmap monitoring | |
255 | // widget while the script is running. (The | |
256 | // system-wideness may be restricted by stap -c or | |
257 | // -x.) But this seems to be necessary if we want to | |
258 | // to stack tracebacks through arbitrary shared libraries. | |
259 | // | |
260 | // XXX: There may be an optimization opportunity | |
261 | // for executables (for which the main task-finder | |
262 | // callback should be sufficient). | |
263 | .pid = 0, | |
264 | .procname = NULL, | |
7a23ba3a | 265 | .build_id_len = 0, |
1af100fc | 266 | .purpose = "vma tracking", |
7d8aa2ee MW |
267 | .callback = &_stp_vma_exec_cb, |
268 | .mmap_callback = &_stp_vma_mmap_cb, | |
269 | .munmap_callback = &_stp_vma_munmap_cb, | |
270 | .mprotect_callback = NULL | |
271 | }; | |
14fc9001 MW |
272 | rc = stap_initialize_vma_map (); |
273 | if (rc != 0) { | |
274 | _stp_error("Couldn't initialize vma map: %d\n", rc); | |
275 | return rc; | |
276 | } | |
a2b0b5c8 | 277 | dbug_task_vma(1, |
7d8aa2ee | 278 | "registering vmcb (_stap_target: %d)\n", _stp_target); |
7d8aa2ee | 279 | rc = stap_register_task_finder_target (& vmcb); |
81b2ee28 YZ |
280 | /* NB: we don't need to call stap_cleanup_task_finder_target() to |
281 | * free up any dynamically-allocated procname buffers since .procname | |
282 | * is always NULL (see above). */ | |
7d8aa2ee MW |
283 | if (rc != 0) |
284 | _stp_error("Couldn't register task finder target: %d\n", rc); | |
285 | #endif | |
286 | return rc; | |
287 | } | |
288 | ||
14fc9001 MW |
289 | /* Get rid of the vma tracker (memory). */ |
290 | static void _stp_vma_done(void) | |
291 | { | |
0064e701 | 292 | /* See runtime/linux/runtime.h for more details. See also PR26123 */ |
fb59e8c7 | 293 | #ifdef HAVE_TASK_FINDER |
14fc9001 MW |
294 | stap_destroy_vma_map(); |
295 | #endif | |
296 | } | |
297 | ||
7d8aa2ee | 298 | #endif /* _STP_VMA_C_ */ |