/* The task_finder_callback we use for ET_DYN targets. This just forces an unmap of everything as the process exits. (PR11151) */ static int stap_uprobe_process_munmap (struct stap_task_finder_target *tgt, struct task_struct *tsk, int register_p, int process_p) { const struct stap_uprobe_tf *stf = container_of(tgt, struct stap_uprobe_tf, finder); if (! process_p) return 0; /* ignore threads */ dbug_task_vma (1, "%cproc pid %d stf %p %p path %s\n", register_p?'+':'-', tsk->tgid, tgt, stf, stf->pathname); /* Covering 0->TASK_SIZE means "unmap everything" */ if (!register_p) return stap_uprobe_change_minus (tsk, 0, TASK_SIZE, stf); return 0; }
/* The task_finder_mmap_callback */ static int stap_uprobe_mmap_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, char *path, struct dentry *dentry, unsigned long addr, unsigned long length, unsigned long offset, unsigned long vm_flags) { int rc = 0; const struct stap_uprobe_tf *stf = container_of(tgt, struct stap_uprobe_tf, finder); /* 1 - shared libraries' executable segments load from offset 0 * - ld.so convention offset != 0 is now allowed * so stap_uprobe_change_plus can set a semaphore, * i.e. a static extern, in a shared object * 2 - the shared library we're interested in * 3 - mapping should be executable or writable (for semaphore in .so) * NB: or both, on kernels that lack noexec mapping */ if (path == NULL || strcmp (path, stf->pathname)) return 0; /* Check non-writable, executable sections for probes. */ if ((vm_flags & VM_EXEC) && !(vm_flags & VM_WRITE)) { dbug_task_vma (1, "+mmap X pid %d path %s addr %p length %u offset %p stf %p %p path %s\n", tsk->tgid, path, (void *) addr, (unsigned)length, (void*) offset, tgt, stf, stf->pathname); rc = stap_uprobe_change_plus (tsk, addr, length, stf, offset, vm_flags); } /* Check writable sections for semaphores. * NB: They may have also been executable for the check above, if we're * running a kernel that lacks noexec mappings. So long as there's * no error (rc == 0), we need to look for semaphores too. */ if ((rc == 0) && (vm_flags & VM_WRITE)) { dbug_task_vma (1, "+mmap W pid %d path %s addr %p length %u offset %p stf %p %p path %s\n", tsk->tgid, path, (void *) addr, (unsigned)length, (void*) offset, tgt, stf, stf->pathname); rc = stap_uprobe_change_semaphore_plus (tsk, addr, length, stf); } return rc; }
/* The task_finder_callback we use for ET_EXEC targets. We used to perform uprobe insertion/removal here, but not any more. (PR10524) */ static int stap_uprobe_process_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, int register_p, int process_p) { const struct stap_uprobe_tf *stf = container_of(tgt, struct stap_uprobe_tf, finder); if (! process_p) return 0; /* ignore threads */ dbug_task_vma(1, "%cproc pid %d stf %p %p path %s\n", register_p?'+':'-', tsk->tgid, tgt, stf, stf->pathname); /* ET_EXEC events are like shlib events, but with 0 relocation bases */ if (register_p) { int rc = stap_uprobe_change_plus (tsk, 0, TASK_SIZE, stf, 0, 0); stap_uprobe_change_semaphore_plus (tsk, 0, TASK_SIZE, stf); return rc; } else return stap_uprobe_change_minus (tsk, 0, TASK_SIZE, stf); }
/* exec callback, will try to match vdso for new process, will drop all vma maps for a process that disappears. */ static int _stp_vma_exec_cb(struct stap_task_finder_target *tgt, struct task_struct *tsk, int register_p, int process_p) { dbug_task_vma(1, "tsk %d:%d , register_p: %d, process_p: %d\n", tsk->pid, tsk->tgid, register_p, process_p); if (process_p) { if (register_p) _stp_vma_match_vdso(tsk); else stap_drop_vma_maps(tsk); } return 0; }
/* Initializes the vma tracker. */ static int _stp_vma_init(void) { int rc = 0; #ifdef HAVE_TASK_FINDER static struct stap_task_finder_target vmcb = { // NB: no .pid, no .procname filters here. // This means that we get a system-wide mmap monitoring // widget while the script is running. (The // system-wideness may be restricted by stap -c or // -x.) But this seems to be necessary if we want to // to stack tracebacks through arbitrary shared libraries. // // XXX: There may be an optimization opportunity // for executables (for which the main task-finder // callback should be sufficient). .pid = 0, .procname = NULL, .purpose = "vma tracking", .callback = &_stp_vma_exec_cb, .mmap_callback = &_stp_vma_mmap_cb, .munmap_callback = &_stp_vma_munmap_cb, .mprotect_callback = NULL }; rc = stap_initialize_vma_map (); if (rc != 0) { _stp_error("Couldn't initialize vma map: %d\n", rc); return rc; } dbug_task_vma(1, "registering vmcb (_stap_target: %d)\n", _stp_target); rc = stap_register_task_finder_target (& vmcb); if (rc != 0) _stp_error("Couldn't register task finder target: %d\n", rc); #endif return rc; } /* Get rid of the vma tracker (memory). */ static void _stp_vma_done(void) { #if defined(CONFIG_UTRACE) stap_destroy_vma_map(); #endif }
/* The task_finder_munmap_callback */ static int stap_uprobe_munmap_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, unsigned long addr, unsigned long length) { const struct stap_uprobe_tf *stf = container_of(tgt, struct stap_uprobe_tf, finder); dbug_task_vma (1, "-mmap pid %d addr %p length %lu stf %p %p path %s\n", tsk->tgid, (void *) addr, length, tgt, stf, stf->pathname); return stap_uprobe_change_minus (tsk, addr, length, stf); }
static void _stp_vma_match_vdso(struct task_struct *tsk) { /* vdso is arch specific */ #if defined(STAPCONF_MM_CONTEXT_VDSO) || defined(STAPCONF_MM_CONTEXT_VDSO_BASE) int i, j; if (tsk->mm) { struct _stp_module *found = NULL; #ifdef STAPCONF_MM_CONTEXT_VDSO unsigned long vdso_addr = (unsigned long) tsk->mm->context.vdso; #else unsigned long vdso_addr = tsk->mm->context.vdso_base; #endif dbug_task_vma(1,"tsk: %d vdso: 0x%lx\n", tsk->pid, vdso_addr); for (i = 0; i < _stp_num_modules && found == NULL; i++) { struct _stp_module *m = _stp_modules[i]; if (m->path[0] == '/' && m->num_sections == 1 && strncmp(m->name, "vdso", 4) == 0) { unsigned long notes_addr; int all_ok = 1; notes_addr = vdso_addr + m->build_id_offset; dbug_task_vma(1,"notes_addr %s: 0x%lx + 0x%lx = 0x%lx (len: %x)\n", m->path, vdso_addr, m->build_id_offset, notes_addr, m->build_id_len); for (j = 0; j < m->build_id_len; j++) { int rc; unsigned char b; /* * Why check CONFIG_UTRACE here? If we're using real * in-kernel utrace, we can always just call * get_user() (since tsk == current). * * Since we're only reading here, we can call * __access_process_vm_noflush(), which only calls * things that are exported. */ #ifdef CONFIG_UTRACE rc = copy_from_user(&b, (void*)(notes_addr + j), 1); #else if (tsk == current) { rc = copy_from_user(&b, (void*)(notes_addr + j), 1); } else { rc = (__access_process_vm_noflush(tsk, (notes_addr + j), &b, 1, 0) != 1); } #endif if (rc || b != m->build_id_bits[j]) { dbug_task_vma(1,"darn, not equal (rc=%d) at %d (0x%x != 0x%x)\n", rc, j, b, m->build_id_bits[j]); all_ok = 0; break; } } if (all_ok) found = m; } } if (found != NULL) { stap_add_vma_map_info(tsk, vdso_addr, vdso_addr + found->sections[0].size, "vdso", found); dbug_task_vma(1,"found vdso: %s\n", found->path); } } #endif /* STAPCONF_MM_CONTEXT_VDSO */ }
/* mmap callback, will match new vma with _stp_module or register vma name. */ static int _stp_vma_mmap_cb(struct stap_task_finder_target *tgt, struct task_struct *tsk, char *path, struct dentry *dentry, unsigned long addr, unsigned long length, unsigned long offset, unsigned long vm_flags) { int i, res; struct _stp_module *module = NULL; const char *name = ((dentry != NULL) ? (char *)dentry->d_name.name : NULL); if (path == NULL || *path == '\0') /* unknown? */ path = (char *)name; /* we'll copy this soon, in ..._add_vma_... */ dbug_task_vma(1, "mmap_cb: tsk %d:%d path %s, addr 0x%08lx, length 0x%08lx, offset 0x%lx, flags 0x%lx\n", tsk->pid, tsk->tgid, path, addr, length, offset, vm_flags); // We are only interested in the first load of the whole module that // is executable. We register whether or not we know the module, // so we can later lookup the name given an address for this task. if (path != NULL && offset == 0 && (vm_flags & VM_EXEC) && stap_find_vma_map_info(tsk, addr, NULL, NULL, NULL, NULL) != 0) { for (i = 0; i < _stp_num_modules; i++) { if (strcmp(path, _stp_modules[i]->path) == 0) { unsigned long vm_start = 0; unsigned long vm_end = 0; dbug_task_vma(1, "vm_cb: matched path %s to module (sec: %s)\n", path, _stp_modules[i]->sections[0].name); module = _stp_modules[i]; /* Make sure we really don't know about this module yet. If we do know, we might want to extend the coverage. */ res = stap_find_vma_map_info_user(tsk->group_leader, module, &vm_start, &vm_end, NULL); if (res == -ESRCH) res = stap_add_vma_map_info(tsk->group_leader, addr, addr + length, path, module); else if (res == 0 && vm_end + 1 == addr) res = stap_extend_vma_map_info(tsk->group_leader, vm_start, addr + length); /* VMA entries are allocated dynamically, this is fine, * since we are in a task_finder callback, which is in * user context. */ if (res != 0) { _stp_error ("Couldn't register module '%s' for pid %d (%d)\n", _stp_modules[i]->path, tsk->group_leader->pid, res); } return 0; } } /* None of the tracked modules matched, register without, * to make sure we can lookup the name later. Ignore errors, * we will just report unknown when asked and tables were * full. Restrict to target process when given to preserve * vma_map entry slots. */ if (_stp_target == 0 || _stp_target == tsk->group_leader->pid) { res = stap_add_vma_map_info(tsk->group_leader, addr, addr + length, path, NULL); dbug_task_vma(1, "registered '%s' for %d (res:%d) [%lx-%lx]\n", path, tsk->group_leader->pid, res, addr, addr + length); } } else if (path != NULL) { // Once registered, we may want to extend an earlier // registered region. A segment might be mapped with // different flags for different offsets. If so we want // to record the extended range so we can address more // precisely to module names and symbols. res = stap_extend_vma_map_info(tsk->group_leader, addr, addr + length); dbug_task_vma(1, "extended '%s' for %d (res:%d) [%lx-%lx]\n", path, tsk->group_leader->pid, res, addr, addr + length); } return 0; }