diff --git a/.gitignore b/.gitignore index d396fe4..512c531 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ CMakeCache.txt cmake_install.cmake Makefile *~ +*.svg out diff --git a/CMakeLists.txt b/CMakeLists.txt index f5f7b28..8b0759a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ endif() INCLUDE_DIRECTORIES(${JAVA_INCLUDE_PATH}) INCLUDE_DIRECTORIES(${JAVA_INCLUDE_PATH2}) -add_library(perfmap SHARED src/c/perf-map-agent.c src/c/perf-map-file.c) +add_library(perfmap SHARED src/c/perf-map-agent.c src/c/perf-map-file.c src/c/disassembling.c src/c/symtab.c src/c/salibelf.c src/c/libproc_impl.c) find_package(Java REQUIRED) include(UseJava) diff --git a/bin/java-dump.sh b/bin/java-dump.sh new file mode 100755 index 0000000..6b86997 --- /dev/null +++ b/bin/java-dump.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e +set -x + +CUR_DIR=`pwd` +ATTACH_JAR=attach-main.jar +PERF_MAP_DIR=$(dirname $(readlink -f $0))/.. +ATTACH_JAR_PATH=$PERF_MAP_DIR/out/$ATTACH_JAR + +if [ -z "$JAVA_HOME" ]; then + JAVA_HOME=/usr/lib/jvm/default-java +fi + +[ -d "$JAVA_HOME" ] || JAVA_HOME=/etc/alternatives/java_sdk +[ -d "$JAVA_HOME" ] || (echo "JAVA_HOME directory at '$JAVA_HOME' does not exist." && false) + +echo $* >> output.log +(cd $PERF_MAP_DIR/out && sudo -u johannes java -cp $ATTACH_JAR_PATH:$JAVA_HOME/lib/tools.jar net.virtualvoid.perf.AttachOnce $*) diff --git a/bin/perf-java-top b/bin/perf-java-top index bd4d522..aa4463c 100755 --- a/bin/perf-java-top +++ b/bin/perf-java-top @@ -6,4 +6,4 @@ PERF_MAP_DIR=$(dirname $(readlink -f $0))/.. PID=$1 $PERF_MAP_DIR/bin/create-java-perf-map.sh $PID "$PERF_MAP_OPTIONS" -sudo perf top -p $* +sudo perf top --objdump $PERF_MAP_DIR/bin/java-dump.sh -p $* diff --git a/src/c/disassembling.c b/src/c/disassembling.c new file mode 100644 index 0000000..97c34ab --- /dev/null +++ b/src/c/disassembling.c @@ -0,0 +1,143 @@ +#define _GNU_SOURCE + +#include + +#include +#include +#include +#include +#include +#include + +#include "disassembling.h" +#include "symtab.h" + +load_library_f *load_library; +decode_nmethod_f *decode_nmethod; +checked_resolve_f *checked_resolve_jmethod_id; +decode_address_f *decode_address; + +void* fdStream_vt; + +typedef struct { + const char* typeName; // The type name containing the given field (example: "Klass") + const char* fieldName; // The field name within the type (example: "_name") + const char* typeString; // Quoted name of the type of this field (example: "Symbol*"; + // parsed in Java to ensure type correctness + int32_t isStatic; // Indicates whether following field is an offset or an address + uint64_t offset; // Offset of field within structure; only used for nonstatic fields + void* address; // Address of field; only used for static fields + // ("offset" can not be reused because of apparent SparcWorks compiler bug + // in generation of initializer data) +} VMStructEntry; + +typedef struct { + const char* typeName; // Type name (example: "methodOopDesc") + const char* superclassName; // Superclass name, or null if none (example: "oopDesc") + int32_t isOopType; // Does this type represent an oop typedef? (i.e., "methodOop" or + // "klassOop", but NOT "methodOopDesc") + int32_t isIntegerType; // Does this type represent an integer type (of arbitrary size)? + int32_t isUnsigned; // If so, is it unsigned? + uint64_t size; // Size, in bytes, of the type +} VMTypeEntry; + +VMStructEntry *code_entry; +long code_offset; + +void findCode(void *libjvm_handle) { + VMStructEntry *entry = *((VMStructEntry **)dlsym(libjvm_handle, "gHotSpotVMStructs")); + + while (entry->fieldName) { + printf("%s.%s\n", entry->typeName, entry->fieldName); + if (strcmp(entry->typeName, "methodOopDesc") == 0 && + strcmp(entry->fieldName, "_code") == 0) { + code_entry = entry; + code_offset = entry -> offset; + return; + } + entry++; + } +} + +void decode(jmethodID method) { + char *methodoop = checked_resolve_jmethod_id(method); + void **code_ptr = methodoop + code_offset; + printf("a: %lx b: %lx oop: %lx code: %lx\n", *((void**)method), checked_resolve_jmethod_id(method), methodoop, *code_ptr); + decode_nmethod(*code_ptr, NULL); +} + +struct link_map *map; +struct symtab *res; + +void *find_symbol(char *symbol) { + int size; + void *ptr = search_symbol(res, map->l_addr, symbol, &size); + printf("%s Result: %lx\n", symbol, ptr); + return ptr; +} + +void newFdStream(int fd, void *buffer) { + memset(buffer, 0, 0x70); + *((void**)(buffer + 0x00)) = fdStream_vt + 0x10; + *((void**)(buffer + 0x0c)) = 80; // width + *((int**) (buffer + 0x28)) = fd; +} + +void accessDisassembler() { + //dlopen("/tmp/hsdis-amd64.so", RTLD_LAZY); + void *jvm = dlopen("libjvm.so", RTLD_LAZY); + printf("Got jvm %lx\n", jvm); + + findCode(jvm); + + dlinfo(jvm, RTLD_DI_LINKMAP, &map); + printf("Full name: %s, base: %lx\n", map->l_name, map->l_addr); + + char *path = map->l_name; + FILE *file = fopen(path, "r"); + printf("Got file %lx fd %d\n", file, fileno(file)); + res = build_symtab(fileno(file), path); + printf("Got syms %lx\n", res); + + char *sym = "_ZN12Disassembler12load_libraryEv"; + int size; + void *ptr = search_symbol(res, map->l_addr, sym, &size); + printf("Result: %lx\n", ptr); + load_library = ptr; + + char *decodeInsns = "_ZN12Disassembler6decodeEP7nmethodP12outputStream"; + ptr = search_symbol(res, map->l_addr, decodeInsns, &size); + printf("decode Result: %lx\n", ptr); + decode_nmethod = ptr; + + char *decodeCodeBlobInsns = "_ZN12Disassembler6decodeEP8CodeBlobP12outputStream"; + ptr = search_symbol(res, map->l_addr, decodeCodeBlobInsns, &size); + printf("decodeCodeBlob Result: %lx\n", ptr); + decode_codeblob = ptr; + + char *checked_str = "_ZN10JNIHandles26checked_resolve_jmethod_idEP10_jmethodID"; + ptr = search_symbol(res, map->l_addr, checked_str, &size); + printf("checked resolve Result: %lx\n", ptr); + checked_resolve_jmethod_id = ptr; + + char *decode_addr_str = "_ZN10decode_envC2EP8CodeBlobP12outputStream11CodeStrings"; + ptr = search_symbol(res, map->l_addr, decode_addr_str, &size); + printf("decode_addr Result: %lx\n", ptr); + decode_address = ptr; + + char *find_blob_str = "_ZN9CodeCache9find_blobEPv"; + ptr = search_symbol(res, map->l_addr, find_blob_str, &size); + printf("find_blob Result: %lx\n", ptr); + find_blob = ptr; + + fdStream_vt = find_symbol("_ZTV8fdStream"); + + _ps = find_symbol("_ps"); + + load_library(); +} + +// + +//uintptr_t search_symbol(struct symtab* symtab, uintptr_t base, +// const char *sym_name, int *sym_size); diff --git a/src/c/disassembling.h b/src/c/disassembling.h new file mode 100644 index 0000000..182b66e --- /dev/null +++ b/src/c/disassembling.h @@ -0,0 +1,28 @@ +void accessDisassembler(); + +typedef int(load_library_f()); +load_library_f *load_library; + +typedef void(decode_nmethod_f(void *,void*)); +decode_nmethod_f *decode_nmethod; + +typedef void(decode_codeblob_f(void *,void*)); +decode_codeblob_f *decode_codeblob; + +typedef void*(checked_resolve_f(jmethod)); +checked_resolve_f *checked_resolve_jmethod_id; + +//static void decode(address begin, address end, outputStream* st = NULL, CodeStrings c = CodeStrings()); +typedef void(decode_address_f(void *, void*, void*, void*)); +decode_address_f *decode_address; + +long code_offset; + +typedef void *(find_blob_f(void*)); +find_blob_f *find_blob; + +void newFdStream(int fd, void *buffer); + +typedef void (_ps_f()); +_ps_f *_ps; + diff --git a/src/c/elfmacros.h b/src/c/elfmacros.h new file mode 100644 index 0000000..15bf640 --- /dev/null +++ b/src/c/elfmacros.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef _ELFMACROS_H_ +#define _ELFMACROS_H_ + +#if defined(_LP64) +#define ELF_EHDR Elf64_Ehdr +#define ELF_SHDR Elf64_Shdr +#define ELF_PHDR Elf64_Phdr +#define ELF_SYM Elf64_Sym +#define ELF_NHDR Elf64_Nhdr +#define ELF_DYN Elf64_Dyn +#define ELF_ADDR Elf64_Addr + +#define ELF_ST_TYPE ELF64_ST_TYPE + +#else + +#define ELF_EHDR Elf32_Ehdr +#define ELF_SHDR Elf32_Shdr +#define ELF_PHDR Elf32_Phdr +#define ELF_SYM Elf32_Sym +#define ELF_NHDR Elf32_Nhdr +#define ELF_DYN Elf32_Dyn +#define ELF_ADDR Elf32_Addr + +#define ELF_ST_TYPE ELF32_ST_TYPE + +#endif + + +#endif /* _ELFMACROS_H_ */ diff --git a/src/c/libproc.h b/src/c/libproc.h new file mode 100644 index 0000000..5edd657 --- /dev/null +++ b/src/c/libproc.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef _LIBPROC_H_ +#define _LIBPROC_H_ + +#include +#include +#include +#include "proc_service.h" + +#if defined(arm) || defined(ppc) +#include "libproc_md.h" +#endif + +#if defined(sparc) || defined(sparcv9) +/* + If _LP64 is defined ptrace.h should be taken from /usr/include/asm-sparc64 + otherwise it should be from /usr/include/asm-sparc + These two files define pt_regs structure differently +*/ +#ifdef _LP64 +struct pt_regs { + unsigned long u_regs[16]; /* globals and ins */ + unsigned long tstate; + unsigned long tpc; + unsigned long tnpc; + unsigned int y; + + /* We encode a magic number, PT_REGS_MAGIC, along + * with the %tt (trap type) register value at trap + * entry time. The magic number allows us to identify + * accurately a trap stack frame in the stack + * unwinder, and the %tt value allows us to test + * things like "in a system call" etc. for an arbitray + * process. + * + * The PT_REGS_MAGIC is chosen such that it can be + * loaded completely using just a sethi instruction. + */ + unsigned int magic; +}; +#else +struct pt_regs { + unsigned long psr; + unsigned long pc; + unsigned long npc; + unsigned long y; + unsigned long u_regs[16]; /* globals and ins */ +}; +#endif + +#endif //sparc or sparcv9 + +/************************************************************************************ + +0. This is very minimal subset of Solaris libproc just enough for current application. +Please note that the bulk of the functionality is from proc_service interface. This +adds Pgrab__ and some missing stuff. We hide the difference b/w live process and core +file by this interface. + +1. pthread_id unique in both NPTL & LinuxThreads. We store this in +OSThread::_pthread_id in JVM code. + +2. All threads see the same pid when they call getpid() under NPTL. +Threads receive different pid under LinuxThreads. We used to save the result of +::getpid() call in OSThread::_thread_id. This way uniqueness of OSThread::_thread_id +was lost under NPTL. Now, we store the result of ::gettid() call in +OSThread::_thread_id. Because gettid returns actual pid of thread (lwp id), this is +unique again. We therefore use OSThread::_thread_id as unique identifier. + +3. There is a unique LWP id under both thread libraries. libthread_db maps pthread_id +to its underlying lwp_id under both the thread libraries. thread_info.lwp_id stores +lwp_id of the thread. The lwp id is nothing but the actual pid of clone'd processes. But +unfortunately libthread_db does not work very well for core dumps. So, we get pthread_id +only for processes. For core dumps, we don't use libthread_db at all (like gdb). + +4. ptrace operates on this LWP id under both the thread libraries. When we say 'pid' for +ptrace call, we refer to lwp_id of the thread. + +5. for core file, we parse ELF files and read data from them. For processes we use +combination of ptrace and /proc calls. + +*************************************************************************************/ + +#ifdef ia64 +struct user_regs_struct { +/* copied from user.h which doesn't define this in a struct */ + +#define IA64_REG_COUNT (EF_SIZE/8+32) /* integer and fp regs */ +unsigned long regs[IA64_REG_COUNT]; /* integer and fp regs */ +}; +#endif + +#if defined(sparc) || defined(sparcv9) || defined(ppc64) +#define user_regs_struct pt_regs +#endif + +// This C bool type must be int for compatibility with Linux calls and +// it would be a mistake to equivalence it to C++ bool on many platforms + +typedef int bool; +#define true 1 +#define false 0 + +struct ps_prochandle; + +// attach to a process +struct ps_prochandle* Pgrab(pid_t pid); + +// attach to a core dump +struct ps_prochandle* Pgrab_core(const char* execfile, const char* corefile); + +// release a process or core +void Prelease(struct ps_prochandle* ph); + +// functions not directly available in Solaris libproc + +// initialize libproc (call this only once per app) +// pass true to make library verbose +bool init_libproc(bool verbose); + +// get number of threads +int get_num_threads(struct ps_prochandle* ph); + +// get lwp_id of n'th thread +lwpid_t get_lwp_id(struct ps_prochandle* ph, int index); + +// get regs for a given lwp +bool get_lwp_regs(struct ps_prochandle* ph, lwpid_t lid, struct user_regs_struct* regs); + +// get number of shared objects +int get_num_libs(struct ps_prochandle* ph); + +// get name of n'th lib +const char* get_lib_name(struct ps_prochandle* ph, int index); + +// get base of lib +uintptr_t get_lib_base(struct ps_prochandle* ph, int index); + +// returns true if given library is found in lib list +bool find_lib(struct ps_prochandle* ph, const char *lib_name); + +// symbol lookup +uintptr_t lookup_symbol(struct ps_prochandle* ph, const char* object_name, + const char* sym_name); + +// address->nearest symbol lookup. return NULL for no symbol +const char* symbol_for_pc(struct ps_prochandle* ph, uintptr_t addr, uintptr_t* poffset); + +struct ps_prochandle* get_proc_handle(JNIEnv* env, jobject this_obj); + +void throw_new_debugger_exception(JNIEnv* env, const char* errMsg); + +#endif //__LIBPROC_H_ diff --git a/src/c/libproc_impl.c b/src/c/libproc_impl.c new file mode 100644 index 0000000..2ea0d0f --- /dev/null +++ b/src/c/libproc_impl.c @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#include +#include +#include +#include +#include +#include +#include "libproc_impl.h" + +static const char* alt_root = NULL; +static int alt_root_len = -1; + +#define SA_ALTROOT "SA_ALTROOT" + +static void init_alt_root() { + if (alt_root_len == -1) { + alt_root = getenv(SA_ALTROOT); + if (alt_root) { + alt_root_len = strlen(alt_root); + } else { + alt_root_len = 0; + } + } +} + +int pathmap_open(const char* name) { + int fd; + char alt_path[PATH_MAX + 1]; + + init_alt_root(); + + if (alt_root_len > 0) { + strcpy(alt_path, alt_root); + strcat(alt_path, name); + fd = open(alt_path, O_RDONLY); + if (fd >= 0) { + print_debug("path %s substituted for %s\n", alt_path, name); + return fd; + } + + if (strrchr(name, '/')) { + strcpy(alt_path, alt_root); + strcat(alt_path, strrchr(name, '/')); + fd = open(alt_path, O_RDONLY); + if (fd >= 0) { + print_debug("path %s substituted for %s\n", alt_path, name); + return fd; + } + } + } else { + fd = open(name, O_RDONLY); + if (fd >= 0) { + return fd; + } + } + + return -1; +} + +static bool _libsaproc_debug; + +void print_debug(const char* format,...) { + if (_libsaproc_debug) { + va_list alist; + + va_start(alist, format); + fputs("libsaproc DEBUG: ", stderr); + vfprintf(stderr, format, alist); + va_end(alist); + } +} + +void print_error(const char* format,...) { + va_list alist; + va_start(alist, format); + fputs("ERROR: ", stderr); + vfprintf(stderr, format, alist); + va_end(alist); +} + +bool is_debug() { + return _libsaproc_debug; +} + +// initialize libproc +bool init_libproc(bool debug) { + // init debug mode + _libsaproc_debug = debug; + + // initialize the thread_db library + if (td_init() != TD_OK) { + print_debug("libthread_db's td_init failed\n"); + return false; + } + + return true; +} + +static void destroy_lib_info(struct ps_prochandle* ph) { + lib_info* lib = ph->libs; + while (lib) { + lib_info *next = lib->next; + if (lib->symtab) { + destroy_symtab(lib->symtab); + } + free(lib); + lib = next; + } +} + +static void destroy_thread_info(struct ps_prochandle* ph) { + thread_info* thr = ph->threads; + while (thr) { + thread_info *next = thr->next; + free(thr); + thr = next; + } +} + +// ps_prochandle cleanup + +// ps_prochandle cleanup +void Prelease(struct ps_prochandle* ph) { + // do the "derived class" clean-up first + ph->ops->release(ph); + destroy_lib_info(ph); + destroy_thread_info(ph); + free(ph); +} + +lib_info* add_lib_info(struct ps_prochandle* ph, const char* libname, uintptr_t base) { + return add_lib_info_fd(ph, libname, -1, base); +} + +lib_info* add_lib_info_fd(struct ps_prochandle* ph, const char* libname, int fd, uintptr_t base) { + lib_info* newlib; + + if ( (newlib = (lib_info*) calloc(1, sizeof(struct lib_info))) == NULL) { + print_debug("can't allocate memory for lib_info\n"); + return NULL; + } + + strncpy(newlib->name, libname, sizeof(newlib->name)); + newlib->base = base; + + if (fd == -1) { + if ( (newlib->fd = pathmap_open(newlib->name)) < 0) { + print_debug("can't open shared object %s\n", newlib->name); + free(newlib); + return NULL; + } + } else { + newlib->fd = fd; + } + + // check whether we have got an ELF file. /proc//map + // gives out all file mappings and not just shared objects + if (is_elf_file(newlib->fd) == false) { + close(newlib->fd); + free(newlib); + return NULL; + } + + newlib->symtab = build_symtab(newlib->fd, libname); + if (newlib->symtab == NULL) { + print_debug("symbol table build failed for %s\n", newlib->name); + } + + // even if symbol table building fails, we add the lib_info. + // This is because we may need to read from the ELF file for core file + // address read functionality. lookup_symbol checks for NULL symtab. + if (ph->libs) { + ph->lib_tail->next = newlib; + ph->lib_tail = newlib; + } else { + ph->libs = ph->lib_tail = newlib; + } + ph->num_libs++; + + return newlib; +} + +// lookup for a specific symbol +uintptr_t lookup_symbol(struct ps_prochandle* ph, const char* object_name, + const char* sym_name) { + // ignore object_name. search in all libraries + // FIXME: what should we do with object_name?? The library names are obtained + // by parsing /proc//maps, which may not be the same as object_name. + // What we need is a utility to map object_name to real file name, something + // dlopen() does by looking at LD_LIBRARY_PATH and /etc/ld.so.cache. For + // now, we just ignore object_name and do a global search for the symbol. + + lib_info* lib = ph->libs; + while (lib) { + if (lib->symtab) { + uintptr_t res = search_symbol(lib->symtab, lib->base, sym_name, NULL); + if (res) return res; + } + lib = lib->next; + } + + print_debug("lookup failed for symbol '%s' in obj '%s'\n", + sym_name, object_name); + return (uintptr_t) NULL; +} + + +const char* symbol_for_pc(struct ps_prochandle* ph, uintptr_t addr, uintptr_t* poffset) { + const char* res = NULL; + lib_info* lib = ph->libs; + while (lib) { + if (lib->symtab && addr >= lib->base) { + res = nearest_symbol(lib->symtab, addr - lib->base, poffset); + if (res) return res; + } + lib = lib->next; + } + return NULL; +} + +// add a thread to ps_prochandle +thread_info* add_thread_info(struct ps_prochandle* ph, pthread_t pthread_id, lwpid_t lwp_id) { + thread_info* newthr; + if ( (newthr = (thread_info*) calloc(1, sizeof(thread_info))) == NULL) { + print_debug("can't allocate memory for thread_info\n"); + return NULL; + } + + // initialize thread info + newthr->pthread_id = pthread_id; + newthr->lwp_id = lwp_id; + + // add new thread to the list + newthr->next = ph->threads; + ph->threads = newthr; + ph->num_threads++; + return newthr; +} + + +// struct used for client data from thread_db callback +struct thread_db_client_data { + struct ps_prochandle* ph; + thread_info_callback callback; +}; + +// callback function for libthread_db +static int thread_db_callback(const td_thrhandle_t *th_p, void *data) { + struct thread_db_client_data* ptr = (struct thread_db_client_data*) data; + td_thrinfo_t ti; + td_err_e err; + + memset(&ti, 0, sizeof(ti)); + err = td_thr_get_info(th_p, &ti); + if (err != TD_OK) { + print_debug("libthread_db : td_thr_get_info failed, can't get thread info\n"); + return err; + } + + print_debug("thread_db : pthread %d (lwp %d)\n", ti.ti_tid, ti.ti_lid); + + if (ptr->callback(ptr->ph, ti.ti_tid, ti.ti_lid) != true) + return TD_ERR; + + return TD_OK; +} + +// read thread_info using libthread_db +bool read_thread_info(struct ps_prochandle* ph, thread_info_callback cb) { + struct thread_db_client_data mydata; + td_thragent_t* thread_agent = NULL; + if (td_ta_new(ph, &thread_agent) != TD_OK) { + print_debug("can't create libthread_db agent\n"); + return false; + } + + mydata.ph = ph; + mydata.callback = cb; + + // we use libthread_db iterator to iterate thru list of threads. + if (td_ta_thr_iter(thread_agent, thread_db_callback, &mydata, + TD_THR_ANY_STATE, TD_THR_LOWEST_PRIORITY, + TD_SIGNO_MASK, TD_THR_ANY_USER_FLAGS) != TD_OK) { + td_ta_delete(thread_agent); + return false; + } + + // delete thread agent + td_ta_delete(thread_agent); + return true; +} + + +// get number of threads +int get_num_threads(struct ps_prochandle* ph) { + return ph->num_threads; +} + +// get lwp_id of n'th thread +lwpid_t get_lwp_id(struct ps_prochandle* ph, int index) { + int count = 0; + thread_info* thr = ph->threads; + while (thr) { + if (count == index) { + return thr->lwp_id; + } + count++; + thr = thr->next; + } + return -1; +} + +// get regs for a given lwp +bool get_lwp_regs(struct ps_prochandle* ph, lwpid_t lwp_id, struct user_regs_struct* regs) { + return ph->ops->get_lwp_regs(ph, lwp_id, regs); +} + +// get number of shared objects +int get_num_libs(struct ps_prochandle* ph) { + return ph->num_libs; +} + +// get name of n'th solib +const char* get_lib_name(struct ps_prochandle* ph, int index) { + int count = 0; + lib_info* lib = ph->libs; + while (lib) { + if (count == index) { + return lib->name; + } + count++; + lib = lib->next; + } + return NULL; +} + +// get base address of a lib +uintptr_t get_lib_base(struct ps_prochandle* ph, int index) { + int count = 0; + lib_info* lib = ph->libs; + while (lib) { + if (count == index) { + return lib->base; + } + count++; + lib = lib->next; + } + return (uintptr_t)NULL; +} + +bool find_lib(struct ps_prochandle* ph, const char *lib_name) { + lib_info *p = ph->libs; + while (p) { + if (strcmp(p->name, lib_name) == 0) { + return true; + } + p = p->next; + } + return false; +} + +//-------------------------------------------------------------------------- +// proc service functions + +// get process id +pid_t ps_getpid(struct ps_prochandle *ph) { + return ph->pid; +} + +// ps_pglobal_lookup() looks up the symbol sym_name in the symbol table +// of the load object object_name in the target process identified by ph. +// It returns the symbol's value as an address in the target process in +// *sym_addr. + +ps_err_e ps_pglobal_lookup(struct ps_prochandle *ph, const char *object_name, + const char *sym_name, psaddr_t *sym_addr) { + *sym_addr = (psaddr_t) lookup_symbol(ph, object_name, sym_name); + return (*sym_addr ? PS_OK : PS_NOSYM); +} + +// read "size" bytes info "buf" from address "addr" +ps_err_e ps_pdread(struct ps_prochandle *ph, psaddr_t addr, + void *buf, size_t size) { + return ph->ops->p_pread(ph, (uintptr_t) addr, buf, size)? PS_OK: PS_ERR; +} + +// write "size" bytes of data to debuggee at address "addr" +ps_err_e ps_pdwrite(struct ps_prochandle *ph, psaddr_t addr, + const void *buf, size_t size) { + return ph->ops->p_pwrite(ph, (uintptr_t)addr, buf, size)? PS_OK: PS_ERR; +} + +// ------------------------------------------------------------------------ +// Functions below this point are not yet implemented. They are here only +// to make the linker happy. + +ps_err_e ps_lsetfpregs(struct ps_prochandle *ph, lwpid_t lid, const prfpregset_t *fpregs) { + print_debug("ps_lsetfpregs not implemented\n"); + return PS_OK; +} + +ps_err_e ps_lsetregs(struct ps_prochandle *ph, lwpid_t lid, const prgregset_t gregset) { + print_debug("ps_lsetregs not implemented\n"); + return PS_OK; +} + +ps_err_e ps_lgetfpregs(struct ps_prochandle *ph, lwpid_t lid, prfpregset_t *fpregs) { + print_debug("ps_lgetfpregs not implemented\n"); + return PS_OK; +} + +ps_err_e ps_lgetregs(struct ps_prochandle *ph, lwpid_t lid, prgregset_t gregset) { + print_debug("ps_lgetfpregs not implemented\n"); + return PS_OK; +} + +// new libthread_db of NPTL seem to require this symbol +ps_err_e ps_get_thread_area() { + print_debug("ps_get_thread_area not implemented\n"); + return PS_OK; +} diff --git a/src/c/libproc_impl.h b/src/c/libproc_impl.h new file mode 100644 index 0000000..bc907a9 --- /dev/null +++ b/src/c/libproc_impl.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef _LIBPROC_IMPL_H_ +#define _LIBPROC_IMPL_H_ + +#include +#include +#include "libproc.h" +#include "symtab.h" + +// data structures in this file mimic those of Solaris 8.0 - libproc's Pcontrol.h + +#define BUF_SIZE (PATH_MAX + NAME_MAX + 1) + +// list of shared objects +typedef struct lib_info { + char name[BUF_SIZE]; + uintptr_t base; + struct symtab* symtab; + int fd; // file descriptor for lib + struct lib_info* next; +} lib_info; + +// list of threads +typedef struct thread_info { + lwpid_t lwp_id; + pthread_t pthread_id; // not used cores, always -1 + struct user_regs_struct regs; // not for process, core uses for caching regset + struct thread_info* next; +} thread_info; + +// list of virtual memory maps +typedef struct map_info { + int fd; // file descriptor + off_t offset; // file offset of this mapping + uintptr_t vaddr; // starting virtual address + size_t memsz; // size of the mapping + struct map_info* next; +} map_info; + +// vtable for ps_prochandle +typedef struct ps_prochandle_ops { + // "derived class" clean-up + void (*release)(struct ps_prochandle* ph); + // read from debuggee + bool (*p_pread)(struct ps_prochandle *ph, + uintptr_t addr, char *buf, size_t size); + // write into debuggee + bool (*p_pwrite)(struct ps_prochandle *ph, + uintptr_t addr, const char *buf , size_t size); + // get integer regset of a thread + bool (*get_lwp_regs)(struct ps_prochandle* ph, lwpid_t lwp_id, struct user_regs_struct* regs); +} ps_prochandle_ops; + +// the ps_prochandle + +struct core_data { + int core_fd; // file descriptor of core file + int exec_fd; // file descriptor of exec file + int interp_fd; // file descriptor of interpreter (ld-linux.so.2) + // part of the class sharing workaround + int classes_jsa_fd; // file descriptor of class share archive + uintptr_t dynamic_addr; // address of dynamic section of a.out + uintptr_t ld_base_addr; // base address of ld.so + size_t num_maps; // number of maps. + map_info* maps; // maps in a linked list + // part of the class sharing workaround + map_info* class_share_maps;// class share maps in a linked list + map_info** map_array; // sorted (by vaddr) array of map_info pointers +}; + +struct ps_prochandle { + ps_prochandle_ops* ops; // vtable ptr + pid_t pid; + int num_libs; + lib_info* libs; // head of lib list + lib_info* lib_tail; // tail of lib list - to append at the end + int num_threads; + thread_info* threads; // head of thread list + struct core_data* core; // data only used for core dumps, NULL for process +}; + +int pathmap_open(const char* name); + +void print_debug(const char* format,...); +void print_error(const char* format,...); +bool is_debug(); + +typedef bool (*thread_info_callback)(struct ps_prochandle* ph, pthread_t pid, lwpid_t lwpid); + +// reads thread info using libthread_db and calls above callback for each thread +bool read_thread_info(struct ps_prochandle* ph, thread_info_callback cb); + +// adds a new shared object to lib list, returns NULL on failure +lib_info* add_lib_info(struct ps_prochandle* ph, const char* libname, uintptr_t base); + +// adds a new shared object to lib list, supply open lib file descriptor as well +lib_info* add_lib_info_fd(struct ps_prochandle* ph, const char* libname, int fd, + uintptr_t base); + +// adds a new thread to threads list, returns NULL on failure +thread_info* add_thread_info(struct ps_prochandle* ph, pthread_t pthread_id, lwpid_t lwp_id); + +// a test for ELF signature without using libelf +bool is_elf_file(int fd); + +#endif //_LIBPROC_IMPL_H_ diff --git a/src/c/perf-map-agent.c b/src/c/perf-map-agent.c index 9138210..a6b16c7 100644 --- a/src/c/perf-map-agent.c +++ b/src/c/perf-map-agent.c @@ -30,6 +30,7 @@ #include #include "perf-map-file.h" +#include "disassembling.h" #define STRING_BUFFER_SIZE 2000 @@ -260,8 +261,30 @@ jvmtiError set_callbacks(jvmtiEnv *jvmti) { return (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks)); } +void decode_now(void *blob) { + FILE *out = fopen("/tmp/test.output", "w"); + char *stream = malloc(0x100); + newFdStream(fileno(out), stream); + decode_codeblob(blob, stream); + free(stream); + fclose(out); +} + JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options, void *reserved) { + char *disasmOption = strstr(options, "disasm="); + if (disasmOption != NULL) { + accessDisassembler(); + void *code_addr; + sscanf(disasmOption + strlen("disasm="), "0x%lx", &code_addr); + void *blob = find_blob(code_addr); + printf("disasm: %lx, blob: %lx\n", code_addr, blob); + + if (blob) decode_now(blob); + + return 1; + } + open_map_file(); unfold_simple = strstr(options, "unfoldsimple") != NULL; diff --git a/src/c/proc_service.h b/src/c/proc_service.h new file mode 100644 index 0000000..802e5b0 --- /dev/null +++ b/src/c/proc_service.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef _PROC_SERVICE_H_ +#define _PROC_SERVICE_H_ + +#include +#include + +// Linux does not have the proc service library, though it does provide the +// thread_db library which can be used to manipulate threads without having +// to know the details of LinuxThreads or NPTL + +// copied from Solaris "proc_service.h" +typedef enum { + PS_OK, /* generic "call succeeded" */ + PS_ERR, /* generic error */ + PS_BADPID, /* bad process handle */ + PS_BADLID, /* bad lwp identifier */ + PS_BADADDR, /* bad address */ + PS_NOSYM, /* p_lookup() could not find given symbol */ + PS_NOFREGS /* FPU register set not available for given lwp */ +} ps_err_e; + +// ps_getpid() is only defined on Linux to return a thread's process ID +pid_t ps_getpid(struct ps_prochandle *ph); + +// ps_pglobal_lookup() looks up the symbol sym_name in the symbol table +// of the load object object_name in the target process identified by ph. +// It returns the symbol's value as an address in the target process in +// *sym_addr. + +ps_err_e ps_pglobal_lookup(struct ps_prochandle *ph, const char *object_name, + const char *sym_name, psaddr_t *sym_addr); + +// read "size" bytes of data from debuggee at address "addr" +ps_err_e ps_pdread(struct ps_prochandle *ph, psaddr_t addr, + void *buf, size_t size); + +// write "size" bytes of data to debuggee at address "addr" +ps_err_e ps_pdwrite(struct ps_prochandle *ph, psaddr_t addr, + const void *buf, size_t size); + +ps_err_e ps_lsetfpregs(struct ps_prochandle *ph, lwpid_t lid, const prfpregset_t *fpregs); + +ps_err_e ps_lsetregs(struct ps_prochandle *ph, lwpid_t lid, const prgregset_t gregset); + +ps_err_e ps_lgetfpregs(struct ps_prochandle *ph, lwpid_t lid, prfpregset_t *fpregs); + +ps_err_e ps_lgetregs(struct ps_prochandle *ph, lwpid_t lid, prgregset_t gregset); + +// new libthread_db of NPTL seem to require this symbol +ps_err_e ps_get_thread_area(); + +#endif /* _PROC_SERVICE_H_ */ diff --git a/src/c/salibelf.c b/src/c/salibelf.c new file mode 100644 index 0000000..9634da8 --- /dev/null +++ b/src/c/salibelf.c @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "salibelf.h" +#include +#include + +extern void print_debug(const char*,...); + +// ELF file parsing helpers. Note that we do *not* use libelf here. +int read_elf_header(int fd, ELF_EHDR* ehdr) { + if (pread(fd, ehdr, sizeof (ELF_EHDR), 0) != sizeof (ELF_EHDR) || + memcmp(&ehdr->e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0 || + ehdr->e_version != EV_CURRENT) { + return 0; + } + return 1; +} + +bool is_elf_file(int fd) { + ELF_EHDR ehdr; + return read_elf_header(fd, &ehdr); +} + +// read program header table of an ELF file +ELF_PHDR* read_program_header_table(int fd, ELF_EHDR* hdr) { + ELF_PHDR* phbuf = 0; + // allocate memory for program header table + size_t nbytes = hdr->e_phnum * hdr->e_phentsize; + + if ((phbuf = (ELF_PHDR*) malloc(nbytes)) == NULL) { + print_debug("can't allocate memory for reading program header table\n"); + return NULL; + } + + if (pread(fd, phbuf, nbytes, hdr->e_phoff) != nbytes) { + print_debug("ELF file is truncated! can't read program header table\n"); + free(phbuf); + return NULL; + } + + return phbuf; +} + +// read section header table of an ELF file +ELF_SHDR* read_section_header_table(int fd, ELF_EHDR* hdr) { + ELF_SHDR* shbuf = 0; + // allocate memory for section header table + size_t nbytes = hdr->e_shnum * hdr->e_shentsize; + + if ((shbuf = (ELF_SHDR*) malloc(nbytes)) == NULL) { + print_debug("can't allocate memory for reading section header table\n"); + return NULL; + } + + if (pread(fd, shbuf, nbytes, hdr->e_shoff) != nbytes) { + print_debug("ELF file is truncated! can't read section header table\n"); + free(shbuf); + return NULL; + } + + return shbuf; +} + +// read a particular section's data +void* read_section_data(int fd, ELF_EHDR* ehdr, ELF_SHDR* shdr) { + void *buf = NULL; + if (shdr->sh_type == SHT_NOBITS || shdr->sh_size == 0) { + return buf; + } + if ((buf = calloc(shdr->sh_size, 1)) == NULL) { + print_debug("can't allocate memory for reading section data\n"); + return NULL; + } + if (pread(fd, buf, shdr->sh_size, shdr->sh_offset) != shdr->sh_size) { + free(buf); + print_debug("section data read failed\n"); + return NULL; + } + return buf; +} + +uintptr_t find_base_address(int fd, ELF_EHDR* ehdr) { + uintptr_t baseaddr = (uintptr_t)-1; + int cnt; + ELF_PHDR *phbuf, *phdr; + + // read program header table + if ((phbuf = read_program_header_table(fd, ehdr)) == NULL) { + goto quit; + } + + // the base address of a shared object is the lowest vaddr of + // its loadable segments (PT_LOAD) + for (phdr = phbuf, cnt = 0; cnt < ehdr->e_phnum; cnt++, phdr++) { + if (phdr->p_type == PT_LOAD && phdr->p_vaddr < baseaddr) { + baseaddr = phdr->p_vaddr; + } + } + +quit: + if (phbuf) free(phbuf); + return baseaddr; +} diff --git a/src/c/salibelf.h b/src/c/salibelf.h new file mode 100644 index 0000000..957694b --- /dev/null +++ b/src/c/salibelf.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef _SALIBELF_H_ +#define _SALIBELF_H_ + +#include +#include "elfmacros.h" +#include "libproc_impl.h" + +// read ELF file header. +int read_elf_header(int fd, ELF_EHDR* ehdr); + +// is given file descriptor corresponds to an ELF file? +bool is_elf_file(int fd); + +// read program header table of an ELF file. caller has to +// free the result pointer after use. NULL on failure. +ELF_PHDR* read_program_header_table(int fd, ELF_EHDR* hdr); + +// read section header table of an ELF file. caller has to +// free the result pointer after use. NULL on failure. +ELF_SHDR* read_section_header_table(int fd, ELF_EHDR* hdr); + +// read a particular section's data. caller has to free the +// result pointer after use. NULL on failure. +void* read_section_data(int fd, ELF_EHDR* ehdr, ELF_SHDR* shdr); + +// find the base address at which the library wants to load itself +uintptr_t find_base_address(int fd, ELF_EHDR* ehdr); +#endif /* _SALIBELF_H_ */ diff --git a/src/c/symtab.c b/src/c/symtab.c new file mode 100644 index 0000000..7a45445 --- /dev/null +++ b/src/c/symtab.c @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include "symtab.h" +#include "salibelf.h" + + +// ---------------------------------------------------- +// functions for symbol lookups +// ---------------------------------------------------- + +struct elf_section { + ELF_SHDR *c_shdr; + void *c_data; +}; + +struct elf_symbol { + char *name; + uintptr_t offset; + uintptr_t size; +}; + +typedef struct symtab { + char *strs; + size_t num_symbols; + struct elf_symbol *symbols; + struct hsearch_data *hash_table; +} symtab_t; + + +// Directory that contains global debuginfo files. In theory it +// should be possible to change this, but in a Java environment there +// is no obvious place to put a user interface to do it. Maybe this +// could be set with an environment variable. +static const char debug_file_directory[] = "/usr/lib/debug"; + +/* The CRC used in gnu_debuglink, retrieved from + http://sourceware.org/gdb/current/onlinedocs/gdb/Separate-Debug-Files.html#Separate-Debug-Files. */ +unsigned int gnu_debuglink_crc32 (unsigned int crc, + unsigned char *buf, size_t len) +{ + static const unsigned int crc32_table[256] = + { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, + 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, + 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, + 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, + 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, + 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, + 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, + 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, + 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, + 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, + 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, + 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, + 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, + 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, + 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, + 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, + 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, + 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, + 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, + 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, + 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, + 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, + 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, + 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, + 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, + 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, + 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, + 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, + 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, + 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, + 0x2d02ef8d + }; + unsigned char *end; + + crc = ~crc & 0xffffffff; + for (end = buf + len; buf < end; ++buf) + crc = crc32_table[(crc ^ *buf) & 0xff] ^ (crc >> 8); + return ~crc & 0xffffffff; +} + +/* Open a debuginfo file and check its CRC. If it exists and the CRC + matches return its fd. */ +static int +open_debug_file (const char *pathname, unsigned int crc) +{ + unsigned int file_crc = 0; + unsigned char buffer[8 * 1024]; + + int fd = pathmap_open(pathname); + + if (fd < 0) + return -1; + + lseek(fd, 0, SEEK_SET); + + for (;;) { + int len = read(fd, buffer, sizeof buffer); + if (len <= 0) + break; + file_crc = gnu_debuglink_crc32(file_crc, buffer, len); + } + + if (crc == file_crc) + return fd; + else { + close(fd); + return -1; + } +} + +/* Find an ELF section. */ +static struct elf_section *find_section_by_name(char *name, + int fd, + ELF_EHDR *ehdr, + ELF_SHDR *shbuf, + struct elf_section *scn_cache) +{ + ELF_SHDR* cursct = NULL; + char *strtab; + int cnt; + + if (scn_cache[ehdr->e_shstrndx].c_data == NULL) { + if ((scn_cache[ehdr->e_shstrndx].c_data + = read_section_data(fd, ehdr, cursct)) == NULL) { + return NULL; + } + } + + strtab = scn_cache[ehdr->e_shstrndx].c_data; + + for (cursct = shbuf, cnt = 0; + cnt < ehdr->e_shnum; + cnt++, cursct++) { + if (strcmp(cursct->sh_name + strtab, name) == 0) { + scn_cache[cnt].c_data = read_section_data(fd, ehdr, cursct); + return &scn_cache[cnt]; + } + } + + return NULL; +} + +/* Look for a ".gnu_debuglink" section. If one exists, try to open a + suitable debuginfo file. */ +static int open_file_from_debug_link(const char *name, + int fd, + ELF_EHDR *ehdr, + ELF_SHDR *shbuf, + struct elf_section *scn_cache) +{ + int debug_fd; + struct elf_section *debug_link = find_section_by_name(".gnu_debuglink", fd, ehdr, + shbuf, scn_cache); + if (debug_link == NULL) + return -1; + char *debug_filename = debug_link->c_data; + int offset = (strlen(debug_filename) + 4) >> 2; + static unsigned int crc; + crc = ((unsigned int*)debug_link->c_data)[offset]; + char *debug_pathname = malloc(strlen(debug_filename) + + strlen(name) + + strlen(".debug/") + + strlen(debug_file_directory) + + 2); + strcpy(debug_pathname, name); + char *last_slash = strrchr(debug_pathname, '/'); + if (last_slash == NULL) + return -1; + + /* Look in the same directory as the object. */ + strcpy(last_slash+1, debug_filename); + + debug_fd = open_debug_file(debug_pathname, crc); + if (debug_fd >= 0) { + free(debug_pathname); + return debug_fd; + } + + /* Look in a subdirectory named ".debug". */ + strcpy(last_slash+1, ".debug/"); + strcat(last_slash, debug_filename); + + debug_fd = open_debug_file(debug_pathname, crc); + if (debug_fd >= 0) { + free(debug_pathname); + return debug_fd; + } + + /* Look in /usr/lib/debug + the full pathname. */ + strcpy(debug_pathname, debug_file_directory); + strcat(debug_pathname, name); + last_slash = strrchr(debug_pathname, '/'); + strcpy(last_slash+1, debug_filename); + + debug_fd = open_debug_file(debug_pathname, crc); + if (debug_fd >= 0) { + free(debug_pathname); + return debug_fd; + } + + free(debug_pathname); + return -1; +} + +static struct symtab* build_symtab_internal(int fd, const char *filename, bool try_debuginfo); + +/* Look for a ".gnu_debuglink" section. If one exists, try to open a + suitable debuginfo file and read a symbol table from it. */ +static struct symtab *build_symtab_from_debug_link(const char *name, + int fd, + ELF_EHDR *ehdr, + ELF_SHDR *shbuf, + struct elf_section *scn_cache) +{ + fd = open_file_from_debug_link(name, fd, ehdr, shbuf, scn_cache); + + if (fd >= 0) { + struct symtab *symtab = build_symtab_internal(fd, NULL, /* try_debuginfo */ false); + close(fd); + return symtab; + } + + return NULL; +} + +// Given a build_id, find the associated debuginfo file +static char * +build_id_to_debug_filename (size_t size, unsigned char *data) +{ + char *filename, *s; + + filename = malloc(strlen (debug_file_directory) + (sizeof "/.build-id/" - 1) + 1 + + 2 * size + (sizeof ".debug" - 1) + 1); + s = filename + sprintf (filename, "%s/.build-id/", debug_file_directory); + if (size > 0) + { + size--; + s += sprintf (s, "%02x", *data++); + } + if (size > 0) + *s++ = '/'; + while (size-- > 0) + s += sprintf (s, "%02x", *data++); + strcpy (s, ".debug"); + + return filename; +} + +// Read a build ID note. Try to open any associated debuginfo file +// and return its symtab +static struct symtab* build_symtab_from_build_id(Elf64_Nhdr *note) +{ + int fd; + struct symtab *symtab = NULL; + + unsigned char *bytes + = (unsigned char*)(note+1) + note->n_namesz; + unsigned char *filename + = (build_id_to_debug_filename (note->n_descsz, bytes)); + + fd = pathmap_open(filename); + if (fd >= 0) { + symtab = build_symtab_internal(fd, NULL, /* try_debuginfo */ false); + close(fd); + } + free(filename); + + return symtab; +} + +// read symbol table from given fd. If try_debuginfo) is true, also +// try to open an associated debuginfo file +static struct symtab* build_symtab_internal(int fd, const char *filename, bool try_debuginfo) { + ELF_EHDR ehdr; + char *names = NULL; + struct symtab* symtab = NULL; + + // Reading of elf header + struct elf_section *scn_cache = NULL; + int cnt = 0; + ELF_SHDR* shbuf = NULL; + ELF_SHDR* cursct = NULL; + ELF_PHDR* phbuf = NULL; + ELF_PHDR* phdr = NULL; + int sym_section = SHT_DYNSYM; + + uintptr_t baseaddr = (uintptr_t)-1; + + lseek(fd, (off_t)0L, SEEK_SET); + if (! read_elf_header(fd, &ehdr)) { + // not an elf + return NULL; + } + + // read ELF header + if ((shbuf = read_section_header_table(fd, &ehdr)) == NULL) { + goto quit; + } + + baseaddr = find_base_address(fd, &ehdr); + + scn_cache = (struct elf_section *) + calloc(ehdr.e_shnum * sizeof(struct elf_section), 1); + if (scn_cache == NULL) { + goto quit; + } + + for (cursct = shbuf, cnt = 0; cnt < ehdr.e_shnum; cnt++) { + scn_cache[cnt].c_shdr = cursct; + if (cursct->sh_type == SHT_SYMTAB || cursct->sh_type == SHT_STRTAB + || cursct->sh_type == SHT_NOTE || cursct->sh_type == SHT_DYNSYM) { + if ( (scn_cache[cnt].c_data = read_section_data(fd, &ehdr, cursct)) == NULL) { + goto quit; + } + } + if (cursct->sh_type == SHT_SYMTAB) { + // Full symbol table available so use that + sym_section = cursct->sh_type; + } + cursct++; + } + + for (cnt = 1; cnt < ehdr.e_shnum; cnt++) { + ELF_SHDR *shdr = scn_cache[cnt].c_shdr; + + if (shdr->sh_type == sym_section) { + ELF_SYM *syms; + int j, n, rslt; + size_t size; + + // FIXME: there could be multiple data buffers associated with the + // same ELF section. Here we can handle only one buffer. See man page + // for elf_getdata on Solaris. + + // guarantee(symtab == NULL, "multiple symtab"); + symtab = (struct symtab*)calloc(1, sizeof(struct symtab)); + if (symtab == NULL) { + goto quit; + } + // the symbol table + syms = (ELF_SYM *)scn_cache[cnt].c_data; + + // number of symbols + n = shdr->sh_size / shdr->sh_entsize; + + // create hash table, we use hcreate_r, hsearch_r and hdestroy_r to + // manipulate the hash table. + symtab->hash_table = (struct hsearch_data*) calloc(1, sizeof(struct hsearch_data)); + rslt = hcreate_r(n, symtab->hash_table); + // guarantee(rslt, "unexpected failure: hcreate_r"); + + // shdr->sh_link points to the section that contains the actual strings + // for symbol names. the st_name field in ELF_SYM is just the + // string table index. we make a copy of the string table so the + // strings will not be destroyed by elf_end. + size = scn_cache[shdr->sh_link].c_shdr->sh_size; + symtab->strs = (char *)malloc(size); + memcpy(symtab->strs, scn_cache[shdr->sh_link].c_data, size); + + // allocate memory for storing symbol offset and size; + symtab->num_symbols = n; + symtab->symbols = (struct elf_symbol *)calloc(n , sizeof(struct elf_symbol)); + + // copy symbols info our symtab and enter them info the hash table + for (j = 0; j < n; j++, syms++) { + ENTRY item, *ret; + char *sym_name = symtab->strs + syms->st_name; + + // skip non-object and non-function symbols + int st_type = ELF_ST_TYPE(syms->st_info); + if ( st_type != STT_FUNC && st_type != STT_OBJECT) + continue; + // skip empty strings and undefined symbols + if (*sym_name == '\0' || syms->st_shndx == SHN_UNDEF) continue; + + symtab->symbols[j].name = sym_name; + symtab->symbols[j].offset = syms->st_value - baseaddr; + symtab->symbols[j].size = syms->st_size; + + item.key = sym_name; + item.data = (void *)&(symtab->symbols[j]); + + hsearch_r(item, ENTER, &ret, symtab->hash_table); + } + } + } + + // Look for a separate debuginfo file. + if (try_debuginfo) { + + // We prefer a debug symtab to an object's own symtab, so look in + // the debuginfo file. We stash a copy of the old symtab in case + // there is no debuginfo. + struct symtab* prev_symtab = symtab; + symtab = NULL; + +#ifdef NT_GNU_BUILD_ID + // First we look for a Build ID + for (cursct = shbuf, cnt = 0; + symtab == NULL && cnt < ehdr.e_shnum; + cnt++) { + if (cursct->sh_type == SHT_NOTE) { + Elf64_Nhdr *note = (Elf64_Nhdr *)scn_cache[cnt].c_data; + if (note->n_type == NT_GNU_BUILD_ID) { + symtab = build_symtab_from_build_id(note); + } + } + cursct++; + } +#endif + + // Then, if that doesn't work, the debug link + if (symtab == NULL) { + symtab = build_symtab_from_debug_link(filename, fd, &ehdr, shbuf, + scn_cache); + } + + // If we still haven't found a symtab, use the object's own symtab. + if (symtab != NULL) { + if (prev_symtab != NULL) + destroy_symtab(prev_symtab); + } else { + symtab = prev_symtab; + } + } + +quit: + if (shbuf) free(shbuf); + if (phbuf) free(phbuf); + if (scn_cache) { + for (cnt = 0; cnt < ehdr.e_shnum; cnt++) { + if (scn_cache[cnt].c_data != NULL) { + free(scn_cache[cnt].c_data); + } + } + free(scn_cache); + } + return symtab; +} + +struct symtab* build_symtab(int fd, const char *filename) { + return build_symtab_internal(fd, filename, /* try_debuginfo */ true); +} + + +void destroy_symtab(struct symtab* symtab) { + if (!symtab) return; + if (symtab->strs) free(symtab->strs); + if (symtab->symbols) free(symtab->symbols); + if (symtab->hash_table) { + hdestroy_r(symtab->hash_table); + free(symtab->hash_table); + } + free(symtab); +} + +uintptr_t search_symbol(struct symtab* symtab, uintptr_t base, + const char *sym_name, int *sym_size) { + ENTRY item; + ENTRY* ret = NULL; + + // library does not have symbol table + if (!symtab || !symtab->hash_table) + return (uintptr_t)NULL; + + item.key = (char*) strdup(sym_name); + hsearch_r(item, FIND, &ret, symtab->hash_table); + if (ret) { + struct elf_symbol * sym = (struct elf_symbol *)(ret->data); + uintptr_t rslt = (uintptr_t) ((char*)base + sym->offset); + if (sym_size) *sym_size = sym->size; + free(item.key); + return rslt; + } + +quit: + free(item.key); + return (uintptr_t) NULL; +} + +const char* nearest_symbol(struct symtab* symtab, uintptr_t offset, + uintptr_t* poffset) { + int n = 0; + if (!symtab) return NULL; + for (; n < symtab->num_symbols; n++) { + struct elf_symbol* sym = &(symtab->symbols[n]); + if (sym->name != NULL && + offset >= sym->offset && offset < sym->offset + sym->size) { + if (poffset) *poffset = (offset - sym->offset); + return sym->name; + } + } + return NULL; +} diff --git a/src/c/symtab.h b/src/c/symtab.h new file mode 100644 index 0000000..b2b9a3b --- /dev/null +++ b/src/c/symtab.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef _SYMTAB_H_ +#define _SYMTAB_H_ + +#include + +// interface to manage ELF symbol tables + +struct symtab; + +// build symbol table for a given ELF file descriptor +struct symtab* build_symtab(int fd, const char *filename); + +// destroy the symbol table +void destroy_symtab(struct symtab* symtab); + +// search for symbol in the given symbol table. Adds offset +// to the base uintptr_t supplied. Returns NULL if not found. +uintptr_t search_symbol(struct symtab* symtab, uintptr_t base, + const char *sym_name, int *sym_size); + +// look for nearest symbol for a given offset (not address - base +// subtraction done by caller +const char* nearest_symbol(struct symtab* symtab, uintptr_t offset, + uintptr_t* poffset); + +#endif /*_SYMTAB_H_*/ diff --git a/src/java/AttachOnce.java b/src/java/AttachOnce.java index 3d38349..7556403 100644 --- a/src/java/AttachOnce.java +++ b/src/java/AttachOnce.java @@ -21,6 +21,9 @@ package net.virtualvoid.perf; import java.io.File; +import java.io.FileInputStream; + +import java.text.MessageFormat; import com.sun.tools.attach.VirtualMachine; import java.lang.management.ManagementFactory; @@ -29,7 +32,29 @@ public class AttachOnce { public static void main(String[] args) throws Exception { String pid = args[0]; String options = ""; - if (args.length > 1) options = args[1]; + if (args.length == 8) { // objdump + assert(args[0].startsWith("--start-address=")); + String addr = args[0].substring("--start-address=".length()); + //long addrl = Long.parseLong(addr, 16); // sanity check + options = "disasm="+addr; + + MessageFormat format = new MessageFormat("/tmp/perf-{0}.map"); + pid = (String) format.parse(args[7])[0]; + System.err.println("Got address "+addr+" and pid "+pid); + loadAgent(pid, options); + + byte[] buffer = new byte[10000]; + FileInputStream fis = new FileInputStream("/tmp/test.output"); + int read = fis.read(buffer); + while (read > 0) { + System.out.write(buffer, 0, read); + read = fis.read(buffer); + } + + return; + } else if (args.length > 1) { + options = args[1]; + } loadAgent(pid, options); }