관리-도구
편집 파일: vma.c
/* -*- linux-c -*- * VMA tracking and lookup functions. * * Copyright (C) 2005-2014 Red Hat Inc. * Copyright (C) 2006 Intel Corporation. * * This file is part of systemtap, and is free software. You can * redistribute it and/or modify it under the terms of the GNU General * Public License (GPL); either version 2, or (at your option) any * later version. */ #ifndef _STP_VMA_C_ #define _STP_VMA_C_ #include "sym.h" #include "stp_string.c" #include "task_finder_vma.c" #include <asm/uaccess.h> 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) { unsigned long notes_addr; int all_ok = 1; /* Assume that if the path's basename starts with 'vdso' * and ends with '.so', it is the vdso. * * Note that this logic should match up with the logic in * the find_vdso() function in translate.cxx. */ const char *name = strrchr(m->path, '/'); if (name) { const char *ext; name++; ext = strrchr(name, '.'); if (!ext || strncmp("vdso", name, 4) != 0 || strcmp(".so", ext) != 0) continue; } 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 */ } #ifdef HAVE_TASK_FINDER /* 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; } /* 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++) { // PR20433: papering over possibility of NULL pointers 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; } /* munmap callback, removes vma map info. */ static int _stp_vma_munmap_cb(struct stap_task_finder_target *tgt, struct task_struct *tsk, unsigned long addr, unsigned long length) { /* Unconditionally remove vm map info, ignore if not present. */ stap_remove_vma_map_info(tsk->group_leader, addr); return 0; } #endif /* 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 } #endif /* _STP_VMA_C_ */