From e24b71c0e41772c06313c0584f6aacc1792d34bc Mon Sep 17 00:00:00 2001 From: Andrey Pangin Date: Sat, 12 Aug 2017 03:32:01 +0300 Subject: [PATCH 01/13] Preparation for macOS support --- Makefile | 4 +- profiler.sh | 5 +- src/allocTracer.cpp | 8 +-- src/arch.h | 6 ++ src/jattach.c | 26 ++++++- src/{perfEvent.h => perfEvents.h} | 52 ++------------ src/{perfEvent.cpp => perfEvents_linux.cpp} | 51 +++++++++++-- src/perfEvents_macos.cpp | 80 +++++++++++++++++++++ src/profiler.cpp | 4 +- src/profiler.h | 2 +- src/stackFrame_x64.cpp | 19 +++-- src/symbols.h | 60 ---------------- src/{symbols.cpp => symbols_linux.cpp} | 64 +++++++++++++++++ src/symbols_macos.cpp | 29 ++++++++ src/vmEntry.cpp | 2 +- 15 files changed, 282 insertions(+), 130 deletions(-) rename src/{perfEvent.h => perfEvents.h} (59%) rename src/{perfEvent.cpp => perfEvents_linux.cpp} (84%) create mode 100755 src/perfEvents_macos.cpp rename src/{symbols.cpp => symbols_linux.cpp} (81%) create mode 100755 src/symbols_macos.cpp diff --git a/Makefile b/Makefile index 3675d9b13..dea2f061c 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ JATTACH=jattach CC=gcc CFLAGS=-O2 CPP=g++ -CPPFLAGS=-O2 -INCLUDES=-I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux +CPPFLAGS=-O2 -D_XOPEN_SOURCE +INCLUDES=-I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux -I$(JAVA_HOME)/include/darwin .PHONY: test diff --git a/profiler.sh b/profiler.sh index 435b60e78..8bd1f86ba 100755 --- a/profiler.sh +++ b/profiler.sh @@ -35,8 +35,7 @@ show_agent_output() { OPTIND=1 SCRIPT_DIR=$(dirname $0) JATTACH=$SCRIPT_DIR/build/jattach -# realpath is not present on all distros, notably on the Travis CI image -PROFILER=$(readlink -f $SCRIPT_DIR/build/libasyncProfiler.so) +PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so ACTION="collect" MODE="cpu" DURATION="60" @@ -94,7 +93,7 @@ done # if no -f argument is given, use temporary file to transfer output to caller terminal if [[ $USE_TMP ]]; then - FILE=$(mktemp --tmpdir async-profiler.XXXXXXXX) + FILE=$(mktemp /tmp/async-profiler.XXXXXXXX) fi case $ACTION in diff --git a/src/allocTracer.cpp b/src/allocTracer.cpp index 394560ee9..30cb19d9d 100755 --- a/src/allocTracer.cpp +++ b/src/allocTracer.cpp @@ -25,9 +25,9 @@ #ifdef __LP64__ -#define SZ "m" +# define SZ "m" #else -#define SZ "j" +# define SZ "j" #endif Trap AllocTracer::_in_new_tlab("_ZN11AllocTracer33send_allocation_in_new_tlab_eventE11KlassHandle" SZ SZ); @@ -36,8 +36,8 @@ Trap AllocTracer::_outside_tlab("_ZN11AllocTracer34send_allocation_outside_tlab_ // Make the entry point writeable and insert breakpoint at the very first instruction void Trap::install() { - uintptr_t page_start = (uintptr_t)_entry & ~0xfffULL; - mprotect((void*)page_start, 4096, PROT_READ | PROT_WRITE | PROT_EXEC); + uintptr_t page_start = (uintptr_t)_entry & ~PAGE_MASK; + mprotect((void*)page_start, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC); _saved_insn = *_entry; *_entry = BREAKPOINT; diff --git a/src/arch.h b/src/arch.h index 8f9b96963..6cbf1ea0c 100755 --- a/src/arch.h +++ b/src/arch.h @@ -18,6 +18,12 @@ #define _ARCH_H +typedef unsigned long long u64; + +const unsigned long PAGE_SIZE = 4096; +const unsigned long PAGE_MASK = PAGE_SIZE - 1; + + #if defined(__x86_64__) || defined(__i386__) typedef unsigned char instruction_t; diff --git a/src/jattach.c b/src/jattach.c index bf4961abd..a493433ad 100644 --- a/src/jattach.c +++ b/src/jattach.c @@ -26,12 +26,34 @@ #include #include #include -#include +#define PATH_MAX 1024 -static const char* get_temp_directory() { +// See hotspot/src/os/bsd/vm/os_bsd.cpp +// This must be hard coded because it's the system's temporary +// directory not the java application's temp directory, ala java.io.tmpdir. +#ifdef __APPLE__ +// macosx has a secure per-user temporary directory + +char temp_path_storage[PATH_MAX]; + +const char* get_temp_directory() { + static char *temp_path = NULL; + if (temp_path == NULL) { + int pathSize = confstr(_CS_DARWIN_USER_TEMP_DIR, temp_path_storage, PATH_MAX); + if (pathSize == 0 || pathSize > PATH_MAX) { + strlcpy(temp_path_storage, "/tmp", sizeof (temp_path_storage)); + } + temp_path = temp_path_storage; + } + return temp_path; +} +#else // __APPLE__ + +const char* get_temp_directory() { return "/tmp"; } +#endif // __APPLE__ // Check if remote JVM has already opened socket for Dynamic Attach static int check_socket(int pid) { diff --git a/src/perfEvent.h b/src/perfEvents.h similarity index 59% rename from src/perfEvent.h rename to src/perfEvents.h index 1edeeef6c..172bc4e6a 100755 --- a/src/perfEvent.h +++ b/src/perfEvents.h @@ -14,49 +14,14 @@ * limitations under the License. */ -#ifndef _PERFEVENT_H -#define _PERFEVENT_H +#ifndef _PERFEVENTS_H +#define _PERFEVENTS_H #include #include -#include -#include "spinLock.h" -typedef unsigned long long u64; -typedef unsigned int u32; -typedef unsigned short u16; -const size_t PAGE_SIZE = 4096; - - -class RingBuffer { - private: - const char* _start; - u32 _offset; - - public: - RingBuffer(struct perf_event_mmap_page* page) { - _start = (const char*)page + PAGE_SIZE; - } - - struct perf_event_header* seek(u64 offset) { - _offset = (u32)(offset & 0xfff); - return (struct perf_event_header*)(_start + _offset); - } - - u64 next() { - _offset = (_offset + sizeof(u64)) & 0xfff; - return *(u64*)(_start + _offset); - } -}; - -class PerfEvent : public SpinLock { - private: - int _fd; - struct perf_event_mmap_page* _page; - - friend class PerfEvents; -}; +class PerfEvent; class PerfEvents { private: @@ -79,13 +44,8 @@ class PerfEvents { static void stop(); static int getCallChain(const void** callchain, int max_depth); - static void JNICALL ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { - createForThread(tid()); - } - - static void JNICALL ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { - destroyForThread(tid()); - } + static void JNICALL ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread); + static void JNICALL ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread); }; -#endif // _PERFEVENT_H +#endif // _PERFEVENTS_H diff --git a/src/perfEvent.cpp b/src/perfEvents_linux.cpp similarity index 84% rename from src/perfEvent.cpp rename to src/perfEvents_linux.cpp index 0a508e9c1..62e064893 100755 --- a/src/perfEvent.cpp +++ b/src/perfEvents_linux.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#ifdef __linux__ + #include #include #include @@ -22,9 +24,11 @@ #include #include #include +#include #include "arch.h" -#include "perfEvent.h" +#include "perfEvents.h" #include "profiler.h" +#include "spinLock.h" // Ancient fcntl.h does not define F_SETOWN_EX constants and structures @@ -39,7 +43,36 @@ struct f_owner_ex { #endif // F_SETOWN_EX -const int PROF_SIGNAL = SIGPROF; +class RingBuffer { + private: + const char* _start; + unsigned long _offset; + + public: + RingBuffer(struct perf_event_mmap_page* page) { + _start = (const char*)page + PAGE_SIZE; + } + + struct perf_event_header* seek(u64 offset) { + _offset = (unsigned long)offset & PAGE_MASK; + return (struct perf_event_header*)(_start + _offset); + } + + u64 next() { + _offset = (_offset + sizeof(u64)) & PAGE_MASK; + return *(u64*)(_start + _offset); + } +}; + + +class PerfEvent : public SpinLock { + private: + int _fd; + struct perf_event_mmap_page* _page; + + friend class PerfEvents; +}; + int PerfEvents::_max_events = 0; PerfEvent* PerfEvents::_events = NULL; @@ -99,7 +132,7 @@ void PerfEvents::createForThread(int tid) { ex.pid = tid; fcntl(fd, F_SETFL, O_ASYNC); - fcntl(fd, F_SETSIG, PROF_SIGNAL); + fcntl(fd, F_SETSIG, SIGPROF); fcntl(fd, F_SETOWN_EX, &ex); ioctl(fd, PERF_EVENT_IOC_RESET, 0); @@ -149,7 +182,7 @@ void PerfEvents::installSignalHandler() { sa.sa_sigaction = signalHandler; sa.sa_flags = SA_RESTART | SA_SIGINFO; - sigaction(PROF_SIGNAL, &sa, NULL); + sigaction(SIGPROF, &sa, NULL); } void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) { @@ -216,3 +249,13 @@ int PerfEvents::getCallChain(const void** callchain, int max_depth) { event->unlock(); return depth; } + +void JNICALL PerfEvents::ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { + createForThread(tid()); +} + +void JNICALL PerfEvents::ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { + destroyForThread(tid()); +} + +#endif // __linux__ diff --git a/src/perfEvents_macos.cpp b/src/perfEvents_macos.cpp new file mode 100755 index 000000000..5b35afd9c --- /dev/null +++ b/src/perfEvents_macos.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2017 Andrei Pangin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef __APPLE__ + +#include +#include "perfEvents.h" +#include "profiler.h" + + +int PerfEvents::_max_events; +PerfEvent* PerfEvents::_events; +int PerfEvents::_interval; + + +void PerfEvents::init() {} + +int PerfEvents::tid() { return 0; } +int PerfEvents::getMaxPid() { return 0; } + +void PerfEvents::createForThread(int tid) {} +void PerfEvents::createForAllThreads() {} +void PerfEvents::destroyForThread(int tid) {} +void PerfEvents::destroyForAllThreads() {} + + +void PerfEvents::installSignalHandler() { + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_handler = NULL; + sa.sa_sigaction = signalHandler; + sa.sa_flags = SA_RESTART | SA_SIGINFO; + + sigaction(SIGPROF, &sa, NULL); +} + +void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) { + Profiler::_instance.recordSample(ucontext, 1, NULL); +} + +bool PerfEvents::start(int interval) { + if (interval <= 0) return false; + _interval = interval; + + installSignalHandler(); + + int sec = interval / 1000000000; + int usec = (interval % 1000000000) / 1000; + struct itimerval tv = {{sec, usec}, {sec, usec}}; + setitimer(ITIMER_PROF, &tv, NULL); + + return true; +} + +void PerfEvents::stop() { + struct itimerval tv = {{0, 0}, {0, 0}}; + setitimer(ITIMER_PROF, &tv, NULL); +} + +int PerfEvents::getCallChain(const void** callchain, int max_depth) { + return 0; +} + +void JNICALL PerfEvents::ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {} +void JNICALL PerfEvents::ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {} + +#endif // __APPLE__ diff --git a/src/profiler.cpp b/src/profiler.cpp index 78f282f40..1dc4a65d6 100755 --- a/src/profiler.cpp +++ b/src/profiler.cpp @@ -23,7 +23,7 @@ #include #include #include "profiler.h" -#include "perfEvent.h" +#include "perfEvents.h" #include "allocTracer.h" #include "stackFrame.h" #include "symbols.h" @@ -567,6 +567,8 @@ void Profiler::runInternal(Arguments& args, std::ostream& out) { if (args._dump_traces > 0) dumpTraces(out, args._dump_traces); if (args._dump_flat > 0) dumpFlat(out, args._dump_flat); break; + default: + break; } } diff --git a/src/profiler.h b/src/profiler.h index 7992b8d89..f425121c0 100755 --- a/src/profiler.h +++ b/src/profiler.h @@ -20,6 +20,7 @@ #include #include #include +#include "arch.h" #include "arguments.h" #include "spinLock.h" #include "codeCache.h" @@ -33,7 +34,6 @@ const int MAX_NATIVE_FRAMES = 128; const int MAX_NATIVE_LIBS = 4096; const int CONCURRENCY_LEVEL = 16; -typedef unsigned long long u64; static inline int cmp64(u64 a, u64 b) { return a > b ? 1 : a == b ? 0 : -1; diff --git a/src/stackFrame_x64.cpp b/src/stackFrame_x64.cpp index 7b8412aff..e984b474c 100755 --- a/src/stackFrame_x64.cpp +++ b/src/stackFrame_x64.cpp @@ -19,28 +19,35 @@ #include "stackFrame.h" +#ifdef __APPLE__ +# define REG(l, m) _ucontext->uc_mcontext->__ss.m +#else +# define REG(l, m) _ucontext->uc_mcontext.gregs[l] +#endif + + uintptr_t& StackFrame::pc() { - return (uintptr_t&)_ucontext->uc_mcontext.gregs[REG_RIP]; + return (uintptr_t&)REG(REG_RIP, __rip); } uintptr_t& StackFrame::sp() { - return (uintptr_t&)_ucontext->uc_mcontext.gregs[REG_RSP]; + return (uintptr_t&)REG(REG_RSP, __rsp); } uintptr_t& StackFrame::fp() { - return (uintptr_t&)_ucontext->uc_mcontext.gregs[REG_RBP]; + return (uintptr_t&)REG(REG_RBP, __rbp); } uintptr_t StackFrame::arg0() { - return (uintptr_t)_ucontext->uc_mcontext.gregs[REG_RDI]; + return (uintptr_t)REG(REG_RDI, __rdi); } uintptr_t StackFrame::arg1() { - return (uintptr_t)_ucontext->uc_mcontext.gregs[REG_RSI]; + return (uintptr_t)REG(REG_RSI, __rsi); } uintptr_t StackFrame::arg2() { - return (uintptr_t)_ucontext->uc_mcontext.gregs[REG_RDX]; + return (uintptr_t)REG(REG_RDX, __rdx); } void StackFrame::ret() { diff --git a/src/symbols.h b/src/symbols.h index 4f08ec390..935a5bd24 100755 --- a/src/symbols.h +++ b/src/symbols.h @@ -17,69 +17,9 @@ #ifndef _SYMBOLS_H #define _SYMBOLS_H -#include #include "codeCache.h" -#ifdef __LP64__ -const unsigned char ELFCLASS_SUPPORTED = ELFCLASS64; -typedef Elf64_Ehdr ElfHeader; -typedef Elf64_Shdr ElfSection; -typedef Elf64_Nhdr ElfNote; -typedef Elf64_Sym ElfSymbol; -#else -const unsigned char ELFCLASS_SUPPORTED = ELFCLASS32; -typedef Elf32_Ehdr ElfHeader; -typedef Elf32_Shdr ElfSection; -typedef Elf32_Nhdr ElfNote; -typedef Elf32_Sym ElfSymbol; -#endif // __LP64__ - - -class ElfParser { - private: - NativeCodeCache* _cc; - const char* _base; - const char* _file_name; - ElfHeader* _header; - const char* _sections; - - ElfParser(NativeCodeCache* cc, const char* base, const void* addr, const char* file_name = NULL) { - _cc = cc; - _base = base; - _file_name = file_name; - _header = (ElfHeader*)addr; - _sections = (const char*)addr + _header->e_shoff; - } - - bool valid_header() { - unsigned char* ident = _header->e_ident; - return ident[0] == 0x7f && ident[1] == 'E' && ident[2] == 'L' && ident[3] == 'F' - && ident[4] == ELFCLASS_SUPPORTED && ident[5] == ELFDATA2LSB && ident[6] == EV_CURRENT - && _header->e_shstrndx != SHN_UNDEF; - } - - ElfSection* section(int index) { - return (ElfSection*)(_sections + index * _header->e_shentsize); - } - - const char* at(ElfSection* section) { - return (const char*)_header + section->sh_offset; - } - - ElfSection* findSection(uint32_t type, const char* name); - - void loadSymbols(bool use_debug); - bool loadSymbolsUsingBuildId(); - bool loadSymbolsUsingDebugLink(); - void loadSymbolTable(ElfSection* symtab); - - public: - static bool parseFile(NativeCodeCache* cc, const char* base, const char* file_name, bool use_debug); - static void parseMem(NativeCodeCache* cc, const char* base, const void* addr); -}; - - class Symbols { private: static void parseKernelSymbols(NativeCodeCache* cc); diff --git a/src/symbols.cpp b/src/symbols_linux.cpp similarity index 81% rename from src/symbols.cpp rename to src/symbols_linux.cpp index 8f462abbe..8a13ba6a5 100755 --- a/src/symbols.cpp +++ b/src/symbols_linux.cpp @@ -14,11 +14,14 @@ * limitations under the License. */ +#ifdef __linux__ + #include #include #include #include #include +#include #include #include #include @@ -78,6 +81,65 @@ class MemoryMapDesc { }; +#ifdef __LP64__ +const unsigned char ELFCLASS_SUPPORTED = ELFCLASS64; +typedef Elf64_Ehdr ElfHeader; +typedef Elf64_Shdr ElfSection; +typedef Elf64_Nhdr ElfNote; +typedef Elf64_Sym ElfSymbol; +#else +const unsigned char ELFCLASS_SUPPORTED = ELFCLASS32; +typedef Elf32_Ehdr ElfHeader; +typedef Elf32_Shdr ElfSection; +typedef Elf32_Nhdr ElfNote; +typedef Elf32_Sym ElfSymbol; +#endif // __LP64__ + + +class ElfParser { + private: + NativeCodeCache* _cc; + const char* _base; + const char* _file_name; + ElfHeader* _header; + const char* _sections; + + ElfParser(NativeCodeCache* cc, const char* base, const void* addr, const char* file_name = NULL) { + _cc = cc; + _base = base; + _file_name = file_name; + _header = (ElfHeader*)addr; + _sections = (const char*)addr + _header->e_shoff; + } + + bool valid_header() { + unsigned char* ident = _header->e_ident; + return ident[0] == 0x7f && ident[1] == 'E' && ident[2] == 'L' && ident[3] == 'F' + && ident[4] == ELFCLASS_SUPPORTED && ident[5] == ELFDATA2LSB && ident[6] == EV_CURRENT + && _header->e_shstrndx != SHN_UNDEF; + } + + ElfSection* section(int index) { + return (ElfSection*)(_sections + index * _header->e_shentsize); + } + + const char* at(ElfSection* section) { + return (const char*)_header + section->sh_offset; + } + + ElfSection* findSection(uint32_t type, const char* name); + + void loadSymbols(bool use_debug); + bool loadSymbolsUsingBuildId(); + bool loadSymbolsUsingDebugLink(); + void loadSymbolTable(ElfSection* symtab); + + public: + static bool parseFile(NativeCodeCache* cc, const char* base, const char* file_name, bool use_debug); + static void parseMem(NativeCodeCache* cc, const char* base, const void* addr); +}; + + ElfSection* ElfParser::findSection(uint32_t type, const char* name) { const char* strtab = at(section(_header->e_shstrndx)); @@ -271,3 +333,5 @@ int Symbols::parseMaps(NativeCodeCache** array, int size) { return count; } + +#endif // __linux__ diff --git a/src/symbols_macos.cpp b/src/symbols_macos.cpp new file mode 100755 index 000000000..df23f1766 --- /dev/null +++ b/src/symbols_macos.cpp @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Andrei Pangin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef __APPLE__ + +#include "symbols.h" + + +void Symbols::parseKernelSymbols(NativeCodeCache* cc) { +} + +int Symbols::parseMaps(NativeCodeCache** array, int size) { + return 0; +} + +#endif // __APPLE__ diff --git a/src/vmEntry.cpp b/src/vmEntry.cpp index 74402566d..56b638ba6 100755 --- a/src/vmEntry.cpp +++ b/src/vmEntry.cpp @@ -19,7 +19,7 @@ #include "vmEntry.h" #include "arguments.h" #include "profiler.h" -#include "perfEvent.h" +#include "perfEvents.h" #include "vmStructs.h" From 09e208d593344430de0bb7b4e72273cef93b29ff Mon Sep 17 00:00:00 2001 From: Andrey Pangin Date: Sun, 13 Aug 2017 06:18:05 +0300 Subject: [PATCH 02/13] Minor cleanup --- src/perfEvents.h | 10 +++++++--- src/perfEvents_linux.cpp | 33 +++++++++++++-------------------- src/perfEvents_macos.cpp | 6 +----- 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/perfEvents.h b/src/perfEvents.h index 172bc4e6a..7bad4a918 100755 --- a/src/perfEvents.h +++ b/src/perfEvents.h @@ -30,7 +30,6 @@ class PerfEvents { static int _interval; static int tid(); - static int getMaxPid(); static void createForThread(int tid); static void createForAllThreads(); static void destroyForThread(int tid); @@ -44,8 +43,13 @@ class PerfEvents { static void stop(); static int getCallChain(const void** callchain, int max_depth); - static void JNICALL ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread); - static void JNICALL ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread); + static void JNICALL ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { + createForThread(tid()); + } + + static void JNICALL ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { + destroyForThread(tid()); + } }; #endif // _PERFEVENTS_H diff --git a/src/perfEvents_linux.cpp b/src/perfEvents_linux.cpp index 62e064893..aab7b9740 100755 --- a/src/perfEvents_linux.cpp +++ b/src/perfEvents_linux.cpp @@ -74,13 +74,25 @@ class PerfEvent : public SpinLock { }; +static int getMaxPID() { + char buf[16] = "65536"; + int fd = open("/proc/sys/kernel/pid_max", O_RDONLY); + if (fd != -1) { + ssize_t r = read(fd, buf, sizeof(buf) - 1); + (void) r; + close(fd); + } + return atoi(buf); +} + + int PerfEvents::_max_events = 0; PerfEvent* PerfEvents::_events = NULL; int PerfEvents::_interval; void PerfEvents::init() { - _max_events = getMaxPid(); + _max_events = getMaxPID(); _events = (PerfEvent*)calloc(_max_events, sizeof(PerfEvent)); } @@ -88,17 +100,6 @@ int PerfEvents::tid() { return syscall(__NR_gettid); } -int PerfEvents::getMaxPid() { - char buf[16] = "65536"; - int fd = open("/proc/sys/kernel/pid_max", O_RDONLY); - if (fd != -1) { - ssize_t r = read(fd, buf, sizeof(buf) - 1); - (void) r; - close(fd); - } - return atoi(buf); -} - void PerfEvents::createForThread(int tid) { struct perf_event_attr attr = {0}; attr.type = PERF_TYPE_SOFTWARE; @@ -250,12 +251,4 @@ int PerfEvents::getCallChain(const void** callchain, int max_depth) { return depth; } -void JNICALL PerfEvents::ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { - createForThread(tid()); -} - -void JNICALL PerfEvents::ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { - destroyForThread(tid()); -} - #endif // __linux__ diff --git a/src/perfEvents_macos.cpp b/src/perfEvents_macos.cpp index 5b35afd9c..572f0c1ff 100755 --- a/src/perfEvents_macos.cpp +++ b/src/perfEvents_macos.cpp @@ -28,8 +28,7 @@ int PerfEvents::_interval; void PerfEvents::init() {} -int PerfEvents::tid() { return 0; } -int PerfEvents::getMaxPid() { return 0; } +int PerfEvents::tid() { return 0; } void PerfEvents::createForThread(int tid) {} void PerfEvents::createForAllThreads() {} @@ -74,7 +73,4 @@ int PerfEvents::getCallChain(const void** callchain, int max_depth) { return 0; } -void JNICALL PerfEvents::ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {} -void JNICALL PerfEvents::ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {} - #endif // __APPLE__ From a694f5b9968826fd1d6965dec3fa9e12138827f0 Mon Sep 17 00:00:00 2001 From: Vsevolod Date: Sat, 16 Sep 2017 00:25:01 +0200 Subject: [PATCH 03/13] Fix profiler.sh on linux --- profiler.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/profiler.sh b/profiler.sh index 8bd1f86ba..77ba7f4f3 100755 --- a/profiler.sh +++ b/profiler.sh @@ -35,7 +35,14 @@ show_agent_output() { OPTIND=1 SCRIPT_DIR=$(dirname $0) JATTACH=$SCRIPT_DIR/build/jattach -PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so +UNAME_S=$(uname -s) +if [ "$UNAME_S" == "Darwin" ]; then + # jattach will make realpath call for mac os x + PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so +else + PROFILER=$(readlink -f $SCRIPT_DIR/build/libasyncProfiler.so) +fi + ACTION="collect" MODE="cpu" DURATION="60" From 5764992d31244d695e2123565ce8ea34935235b6 Mon Sep 17 00:00:00 2001 From: Vsevolod Date: Sat, 16 Sep 2017 00:25:28 +0200 Subject: [PATCH 04/13] Make smoke-test runnable on mac os x, add allocation smoke test --- test/AllocatingTarget.java | 21 +++++++++++++++++++++ test/Target.java | 6 ++++-- test/alloc-smoke-test.sh | 28 ++++++++++++++++++++++++++++ test/smoke-test.sh | 2 +- 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 test/AllocatingTarget.java create mode 100755 test/alloc-smoke-test.sh diff --git a/test/AllocatingTarget.java b/test/AllocatingTarget.java new file mode 100644 index 000000000..ab9624e67 --- /dev/null +++ b/test/AllocatingTarget.java @@ -0,0 +1,21 @@ +import java.util.concurrent.ThreadLocalRandom; + + +public class AllocatingTarget { + + public static volatile Object sink; + + public static void main(String[] args) { + while (true) { + allocate(); + } + } + + private static void allocate() { + if (ThreadLocalRandom.current().nextBoolean()) { + sink = new int[128 * 1000]; + } else { + sink = new Integer[128 * 1000]; + } + } +} diff --git a/test/Target.java b/test/Target.java index d9a88a9ef..26a508896 100644 --- a/test/Target.java +++ b/test/Target.java @@ -16,8 +16,10 @@ private static void method2() { private static void method3() throws Exception { for (int i = 0; i < 1000; ++i) { - new Scanner(new File("/proc/cpuinfo")).useDelimiter("\\Z").next(); - } + for (String s : new File("/tmp").list()) { + value += s.hashCode(); + } + } } public static void main(String[] args) throws Exception { diff --git a/test/alloc-smoke-test.sh b/test/alloc-smoke-test.sh new file mode 100755 index 000000000..e52d048f3 --- /dev/null +++ b/test/alloc-smoke-test.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +set -e # exit on any failure +set -x # print all executed lines + +pushd $(dirname $0) + +javac AllocatingTarget.java +java AllocatingTarget & + +FILENAME=/tmp/java.trace +JAVAPID=$! + +sleep 1 # allow the Java runtime to initialize +../profiler.sh -f $FILENAME -o collapsed -d 5 -m heap $JAVAPID + +kill $JAVAPID + +function assert_string() { + if ! grep -q "$1" $FILENAME; then + exit 1 + fi +} + +assert_string "AllocatingTarget.main;\[Ljava/lang/Integer;" +assert_string "AllocatingTarget.allocate;\[I " + +popd diff --git a/test/smoke-test.sh b/test/smoke-test.sh index 364be1877..16af47a27 100755 --- a/test/smoke-test.sh +++ b/test/smoke-test.sh @@ -24,6 +24,6 @@ function assert_string() { assert_string "Target.main;Target.method1 " assert_string "Target.main;Target.method2 " -assert_string "Target.main;Target.method3;java/util/Scanner" +assert_string "Target.main;Target.method3;java/io/File" popd From deb07e813e5bcecd154950563ac3110d5d55d507 Mon Sep 17 00:00:00 2001 From: Vsevolod Date: Sat, 16 Sep 2017 00:26:35 +0200 Subject: [PATCH 05/13] Call realpath in jattach on mac os x --- src/jattach.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/jattach.c b/src/jattach.c index a493433ad..6aaf7864f 100644 --- a/src/jattach.c +++ b/src/jattach.c @@ -150,6 +150,17 @@ int main(int argc, char** argv) { return 1; } +#ifdef __APPLE__ + // On macosx full path is required, but there is no readlink -f analogue + char* path = argv[3]; + char real_path[PATH_MAX]; + if (!realpath(path, real_path)) { + perror("Cannot canonicalize agent path"); + return 1; + } + argv[3] = real_path; +#endif // __APPLE__ + // Make write() return EPIPE instead of silent process termination signal(SIGPIPE, SIG_IGN); From 3b08211234968347ef82cbf76d0094a45ad3a52d Mon Sep 17 00:00:00 2001 From: Vsevolod Date: Sat, 16 Sep 2017 00:34:28 +0200 Subject: [PATCH 06/13] Heap profiling support on mac os x: build vmStructs using parsed debug symbols instead of dlsym call (required symbols are private in mac os x builds), mach-o parser for libjvm symbols added --- src/profiler.cpp | 2 - src/profiler.h | 2 + src/symbols_macos.cpp | 115 +++++++++++++++++++++++++++++++++++++++++- src/vmEntry.cpp | 5 +- src/vmStructs.cpp | 28 ++++------ src/vmStructs.h | 6 +-- 6 files changed, 131 insertions(+), 27 deletions(-) diff --git a/src/profiler.cpp b/src/profiler.cpp index 1dc4a65d6..5adf7c451 100755 --- a/src/profiler.cpp +++ b/src/profiler.cpp @@ -395,8 +395,6 @@ bool Profiler::start(Mode mode, int interval, int frame_buffer_size) { _frame_buffer_index = 0; _frame_buffer_overflow = false; - resetSymbols(); - bool success; if (mode == MODE_CPU) { success = PerfEvents::start(interval); diff --git a/src/profiler.h b/src/profiler.h index f425121c0..383300a78 100755 --- a/src/profiler.h +++ b/src/profiler.h @@ -207,6 +207,8 @@ class Profiler { const void* address, jint length) { _instance.addRuntimeStub(address, length, name); } + + friend class VM; }; #endif // _PROFILER_H diff --git a/src/symbols_macos.cpp b/src/symbols_macos.cpp index df23f1766..2c736cd44 100755 --- a/src/symbols_macos.cpp +++ b/src/symbols_macos.cpp @@ -16,14 +16,127 @@ #ifdef __APPLE__ +#include +#include +#include #include "symbols.h" +#include +#include +#include +// Workaround for newer macosx versions: since 10.12.x _dyld_get_all_image_infos exists, but is not exported +extern "C" const struct dyld_all_image_infos* _dyld_get_all_image_infos(); + +class MachOParser { + private: + void* _lib_addr; + + static load_command* find_command(mach_header_64* header, uint32_t command) { + load_command* result = (load_command*)((uint64_t)header + sizeof(mach_header_64)); + + if (command == 0) { + return result; + } + + for (uint32_t i = 0; i < header->ncmds; i++) { + if (result->cmd == command) { + break; + } + + result = (load_command*)((uint64_t)result + result->cmdsize); + } + return result; + } + + public: + MachOParser(void* _lib_addr) : _lib_addr(_lib_addr) { + } + + uintptr_t find_symbol_offset(const char* symbol_name) { + load_command* command = find_command((mach_header_64*)_lib_addr, LC_SYMTAB); + symtab_command* symtab = (symtab_command*)command; + nlist_64* symbol_table = (nlist_64*)((char*)_lib_addr + symtab->symoff); + + const char* str_table = (char*)_lib_addr + symtab->stroff; + // Scan through whole symbol table + for (uint32_t sym_n = 0; sym_n < symtab->nsyms; ++sym_n) { + uint32_t str_offset = symbol_table[sym_n].n_un.n_strx; + const char* sym_name = &str_table[str_offset]; + uintptr_t offset = symbol_table[sym_n].n_value; + if (strcmp(sym_name, symbol_name) == 0 && offset != 0) { + return offset; + } + } + return 0; + } +}; + +void add_symbol(MachOParser &parser, uintptr_t lib_addr, const char* symbol_name, NativeCodeCache* cc) { + uintptr_t offset = parser.find_symbol_offset(symbol_name); + cc->add((void*)(lib_addr + offset), sizeof(uintptr_t), symbol_name + 1); +} void Symbols::parseKernelSymbols(NativeCodeCache* cc) { } int Symbols::parseMaps(NativeCodeCache** array, int size) { - return 0; + + const dyld_all_image_infos* dyld_all_image_infos = _dyld_get_all_image_infos(); + const char* lib_path = NULL; + uintptr_t loaded_lib_addr = 0; + for (int i = 0; i < dyld_all_image_infos->infoArrayCount; i++) { + const char* path = dyld_all_image_infos->infoArray[i].imageFilePath; + size_t length = strlen(path); + if (strcmp(path + length - 12, "libjvm.dylib") == 0) { + lib_path = path; + loaded_lib_addr = (uintptr_t)dyld_all_image_infos->infoArray[i].imageLoadAddress; + } + } + + if (lib_path == NULL) { + return 0; + } + + // Loaded lib has different symbol table representation, it's much easier to parse original one + int fd = open(lib_path, O_RDONLY); + if (fd == -1) { + return 0; + } + + size_t length = (size_t)lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + void* mapped_lib_addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapped_lib_addr == MAP_FAILED) { + return 0; + } + + uint32_t magic_number; + read(fd, &magic_number, sizeof(magic_number)); + if (magic_number != MH_MAGIC_64 && magic_number != MH_CIGAM_64) { + munmap(mapped_lib_addr, length); + return 0; + } + + MachOParser parser(mapped_lib_addr); + + NativeCodeCache* cc = new NativeCodeCache(lib_path); + array[0] = cc; + + + // There is a lot of private symbols in libjvm, register only required ones (until native stack walking is implemented) + add_symbol(parser, loaded_lib_addr, "_AsyncGetCallTrace", cc); + + add_symbol(parser, loaded_lib_addr, "_gHotSpotVMStructs", cc); + add_symbol(parser, loaded_lib_addr, "_gHotSpotVMStructEntryArrayStride", cc); + add_symbol(parser, loaded_lib_addr, "_gHotSpotVMStructEntryTypeNameOffset", cc); + add_symbol(parser, loaded_lib_addr, "_gHotSpotVMStructEntryFieldNameOffset", cc); + add_symbol(parser, loaded_lib_addr, "_gHotSpotVMStructEntryOffsetOffset", cc); + + add_symbol(parser, loaded_lib_addr, "__ZN11AllocTracer33send_allocation_in_new_tlab_eventE11KlassHandlemm", cc); + add_symbol(parser, loaded_lib_addr, "__ZN11AllocTracer34send_allocation_outside_tlab_eventE11KlassHandlem", cc); + munmap(mapped_lib_addr, length); + cc->sort(); + return 1; } #endif // __APPLE__ diff --git a/src/vmEntry.cpp b/src/vmEntry.cpp index 56b638ba6..bebe3b6bc 100755 --- a/src/vmEntry.cpp +++ b/src/vmEntry.cpp @@ -64,13 +64,14 @@ bool VM::init(JavaVM* vm) { _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL); PerfEvents::init(); - VMStructs::init(); - _asyncGetCallTrace = (AsyncGetCallTrace)dlsym(RTLD_DEFAULT, "AsyncGetCallTrace"); if (_asyncGetCallTrace == NULL) { std::cerr << "Could not find AsyncGetCallTrace function" << std::endl; return false; } + + Profiler::_instance.resetSymbols(); + VMStructs::init(Profiler::_instance.jvmLibrary()); return true; } diff --git a/src/vmStructs.cpp b/src/vmStructs.cpp index 8689d8acf..fc0d31af8 100755 --- a/src/vmStructs.cpp +++ b/src/vmStructs.cpp @@ -13,31 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#include -#include +#include +#include #include "vmStructs.h" +#include "codeCache.h" +#include int VMStructs::_klass_name_offset = -1; int VMStructs::_symbol_length_offset = -1; int VMStructs::_symbol_body_offset = -1; - -uintptr_t VMStructs::getGlobalVar(const char* name) { - void* addr = dlsym(RTLD_DEFAULT, name); - if (addr == NULL) { - return 0; - } - return *(uintptr_t*)addr; -} - -void VMStructs::init() { - uintptr_t entry = getGlobalVar("gHotSpotVMStructs"); - uintptr_t stride = getGlobalVar("gHotSpotVMStructEntryArrayStride"); - uintptr_t type_offset = getGlobalVar("gHotSpotVMStructEntryTypeNameOffset"); - uintptr_t field_offset = getGlobalVar("gHotSpotVMStructEntryFieldNameOffset"); - uintptr_t offset_offset = getGlobalVar("gHotSpotVMStructEntryOffsetOffset"); +void VMStructs::init(NativeCodeCache* libjvm) { + uintptr_t entry = *((uintptr_t*)libjvm->findSymbol("gHotSpotVMStructs")); + uintptr_t stride = *((uintptr_t*)libjvm->findSymbol("gHotSpotVMStructEntryArrayStride")); + uintptr_t type_offset = *((uintptr_t*)libjvm->findSymbol("gHotSpotVMStructEntryTypeNameOffset")); + uintptr_t field_offset = *((uintptr_t*)libjvm->findSymbol("gHotSpotVMStructEntryFieldNameOffset")); + uintptr_t offset_offset = *((uintptr_t*)libjvm->findSymbol("gHotSpotVMStructEntryOffsetOffset")); if (entry == 0 || stride == 0) { return; diff --git a/src/vmStructs.h b/src/vmStructs.h index 855ec921a..676022ae0 100755 --- a/src/vmStructs.h +++ b/src/vmStructs.h @@ -19,11 +19,9 @@ #include +class NativeCodeCache; class VMStructs { - private: - static uintptr_t getGlobalVar(const char* name); - protected: static int _klass_name_offset; static int _symbol_length_offset; @@ -34,7 +32,7 @@ class VMStructs { } public: - static void init(); + static void init(NativeCodeCache* libjvm); static bool available() { return _klass_name_offset >= 0 From be28f8ecae58f1b457c347c864f1d73e9e8e0a40 Mon Sep 17 00:00:00 2001 From: Vsevolod Date: Sat, 16 Sep 2017 23:54:43 +0200 Subject: [PATCH 07/13] Find absolute path on mac os x via perl one-liner instead of modifying jattach --- profiler.sh | 2 +- src/jattach.c | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/profiler.sh b/profiler.sh index 77ba7f4f3..5223d47f3 100755 --- a/profiler.sh +++ b/profiler.sh @@ -37,8 +37,8 @@ SCRIPT_DIR=$(dirname $0) JATTACH=$SCRIPT_DIR/build/jattach UNAME_S=$(uname -s) if [ "$UNAME_S" == "Darwin" ]; then - # jattach will make realpath call for mac os x PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so + PROFILER=$(perl -MCwd -e 'print Cwd::abs_path shift' $PROFILER) else PROFILER=$(readlink -f $SCRIPT_DIR/build/libasyncProfiler.so) fi diff --git a/src/jattach.c b/src/jattach.c index 6aaf7864f..a493433ad 100644 --- a/src/jattach.c +++ b/src/jattach.c @@ -150,17 +150,6 @@ int main(int argc, char** argv) { return 1; } -#ifdef __APPLE__ - // On macosx full path is required, but there is no readlink -f analogue - char* path = argv[3]; - char real_path[PATH_MAX]; - if (!realpath(path, real_path)) { - perror("Cannot canonicalize agent path"); - return 1; - } - argv[3] = real_path; -#endif // __APPLE__ - // Make write() return EPIPE instead of silent process termination signal(SIGPIPE, SIG_IGN); From 7fc9245416499fc78c9a25f93d1f3e939688669c Mon Sep 17 00:00:00 2001 From: Vsevolod Date: Sun, 17 Sep 2017 00:01:34 +0200 Subject: [PATCH 08/13] Properly close opened file --- src/symbols_macos.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/symbols_macos.cpp b/src/symbols_macos.cpp index 2c736cd44..865afcb99 100755 --- a/src/symbols_macos.cpp +++ b/src/symbols_macos.cpp @@ -105,15 +105,18 @@ int Symbols::parseMaps(NativeCodeCache** array, int size) { size_t length = (size_t)lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); - void* mapped_lib_addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0); - if (mapped_lib_addr == MAP_FAILED) { - return 0; - } uint32_t magic_number; read(fd, &magic_number, sizeof(magic_number)); if (magic_number != MH_MAGIC_64 && magic_number != MH_CIGAM_64) { - munmap(mapped_lib_addr, length); + close(fd); + return 0; + } + + void* mapped_lib_addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + + if (mapped_lib_addr == MAP_FAILED) { return 0; } @@ -122,7 +125,6 @@ int Symbols::parseMaps(NativeCodeCache** array, int size) { NativeCodeCache* cc = new NativeCodeCache(lib_path); array[0] = cc; - // There is a lot of private symbols in libjvm, register only required ones (until native stack walking is implemented) add_symbol(parser, loaded_lib_addr, "_AsyncGetCallTrace", cc); From 0aa8f9932fe17cf06ae973049a525798d8e02098 Mon Sep 17 00:00:00 2001 From: Vsevolod Date: Mon, 25 Sep 2017 19:17:30 +0300 Subject: [PATCH 09/13] Load all libjvm symbols at once --- src/symbols_macos.cpp | 101 +++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 60 deletions(-) diff --git a/src/symbols_macos.cpp b/src/symbols_macos.cpp index 865afcb99..703dfe24b 100755 --- a/src/symbols_macos.cpp +++ b/src/symbols_macos.cpp @@ -29,9 +29,12 @@ extern "C" const struct dyld_all_image_infos* _dyld_get_all_image_infos(); class MachOParser { private: - void* _lib_addr; + NativeCodeCache* _cc; + const uintptr_t _base; + const void* _header; - static load_command* find_command(mach_header_64* header, uint32_t command) { + load_command* findCommand(uint32_t command) { + mach_header_64* header = (mach_header_64*)_header; load_command* result = (load_command*)((uint64_t)header + sizeof(mach_header_64)); if (command == 0) { @@ -48,33 +51,49 @@ class MachOParser { return result; } - public: - MachOParser(void* _lib_addr) : _lib_addr(_lib_addr) { - } - uintptr_t find_symbol_offset(const char* symbol_name) { - load_command* command = find_command((mach_header_64*)_lib_addr, LC_SYMTAB); + void loadSymbols() { + load_command* command = findCommand(LC_SYMTAB); symtab_command* symtab = (symtab_command*)command; - nlist_64* symbol_table = (nlist_64*)((char*)_lib_addr + symtab->symoff); + nlist_64* symbol_table = (nlist_64*)((char*)_header + symtab->symoff); + const char* str_table = (char*)_header + symtab->stroff; - const char* str_table = (char*)_lib_addr + symtab->stroff; - // Scan through whole symbol table for (uint32_t sym_n = 0; sym_n < symtab->nsyms; ++sym_n) { - uint32_t str_offset = symbol_table[sym_n].n_un.n_strx; - const char* sym_name = &str_table[str_offset]; - uintptr_t offset = symbol_table[sym_n].n_value; - if (strcmp(sym_name, symbol_name) == 0 && offset != 0) { - return offset; + nlist_64 descr = symbol_table[sym_n]; + uint32_t str_offset = descr.n_un.n_strx; + uintptr_t offset = descr.n_value; + if (offset != 0) { + const char* symbol_name = &str_table[str_offset]; + if (symbol_name != NULL && (descr.n_type)) { + _cc->add((void*)(_base + offset), sizeof(uintptr_t), symbol_name + 1); + } } } - return 0; + + _cc->sort(); } -}; -void add_symbol(MachOParser &parser, uintptr_t lib_addr, const char* symbol_name, NativeCodeCache* cc) { - uintptr_t offset = parser.find_symbol_offset(symbol_name); - cc->add((void*)(lib_addr + offset), sizeof(uintptr_t), symbol_name + 1); -} + public: + MachOParser(NativeCodeCache* cc, uintptr_t base, const char* header) : _cc(cc), _base(base), _header(header) { + } + + static void parseFile(NativeCodeCache* cc, uintptr_t base, const char* file_name) { + int fd = open(file_name, O_RDONLY); + if (fd == -1) { + return; + } + + size_t length = (size_t)lseek(fd, 0, SEEK_END); + void* addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + + if (addr != NULL) { + MachOParser parser(cc, base, (char*)addr); + parser.loadSymbols(); + munmap(addr, length); + } + } +}; void Symbols::parseKernelSymbols(NativeCodeCache* cc) { } @@ -97,47 +116,9 @@ int Symbols::parseMaps(NativeCodeCache** array, int size) { return 0; } - // Loaded lib has different symbol table representation, it's much easier to parse original one - int fd = open(lib_path, O_RDONLY); - if (fd == -1) { - return 0; - } - - size_t length = (size_t)lseek(fd, 0, SEEK_END); - lseek(fd, 0, SEEK_SET); - - uint32_t magic_number; - read(fd, &magic_number, sizeof(magic_number)); - if (magic_number != MH_MAGIC_64 && magic_number != MH_CIGAM_64) { - close(fd); - return 0; - } - - void* mapped_lib_addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0); - close(fd); - - if (mapped_lib_addr == MAP_FAILED) { - return 0; - } - - MachOParser parser(mapped_lib_addr); - NativeCodeCache* cc = new NativeCodeCache(lib_path); + MachOParser::parseFile(cc, loaded_lib_addr, lib_path); array[0] = cc; - - // There is a lot of private symbols in libjvm, register only required ones (until native stack walking is implemented) - add_symbol(parser, loaded_lib_addr, "_AsyncGetCallTrace", cc); - - add_symbol(parser, loaded_lib_addr, "_gHotSpotVMStructs", cc); - add_symbol(parser, loaded_lib_addr, "_gHotSpotVMStructEntryArrayStride", cc); - add_symbol(parser, loaded_lib_addr, "_gHotSpotVMStructEntryTypeNameOffset", cc); - add_symbol(parser, loaded_lib_addr, "_gHotSpotVMStructEntryFieldNameOffset", cc); - add_symbol(parser, loaded_lib_addr, "_gHotSpotVMStructEntryOffsetOffset", cc); - - add_symbol(parser, loaded_lib_addr, "__ZN11AllocTracer33send_allocation_in_new_tlab_eventE11KlassHandlemm", cc); - add_symbol(parser, loaded_lib_addr, "__ZN11AllocTracer34send_allocation_outside_tlab_eventE11KlassHandlem", cc); - munmap(mapped_lib_addr, length); - cc->sort(); return 1; } From c51f2aced532416fa90de265fbca8c67856ddcf0 Mon Sep 17 00:00:00 2001 From: Vsevolod Date: Mon, 25 Sep 2017 19:17:44 +0300 Subject: [PATCH 10/13] Safely dereference symbols in VMStructs to avoid crashes when these symbols are missing --- src/vmStructs.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/vmStructs.cpp b/src/vmStructs.cpp index fc0d31af8..0436f5c28 100755 --- a/src/vmStructs.cpp +++ b/src/vmStructs.cpp @@ -24,12 +24,22 @@ int VMStructs::_klass_name_offset = -1; int VMStructs::_symbol_length_offset = -1; int VMStructs::_symbol_body_offset = -1; +uintptr_t dereferenceSymbol(NativeCodeCache* lib, const char* symbol_name) { + const void* symbol = lib->findSymbol(symbol_name); + if (symbol == NULL || symbol < 0) { + // Fallback to avoid jvm crash in case of missing symbols + return 0; + } + + return *((uintptr_t*)symbol); +} + void VMStructs::init(NativeCodeCache* libjvm) { - uintptr_t entry = *((uintptr_t*)libjvm->findSymbol("gHotSpotVMStructs")); - uintptr_t stride = *((uintptr_t*)libjvm->findSymbol("gHotSpotVMStructEntryArrayStride")); - uintptr_t type_offset = *((uintptr_t*)libjvm->findSymbol("gHotSpotVMStructEntryTypeNameOffset")); - uintptr_t field_offset = *((uintptr_t*)libjvm->findSymbol("gHotSpotVMStructEntryFieldNameOffset")); - uintptr_t offset_offset = *((uintptr_t*)libjvm->findSymbol("gHotSpotVMStructEntryOffsetOffset")); + uintptr_t entry = dereferenceSymbol(libjvm, "gHotSpotVMStructs"); + uintptr_t stride = dereferenceSymbol(libjvm, "gHotSpotVMStructEntryArrayStride"); + uintptr_t type_offset = dereferenceSymbol(libjvm, "gHotSpotVMStructEntryTypeNameOffset"); + uintptr_t field_offset = dereferenceSymbol(libjvm, "gHotSpotVMStructEntryFieldNameOffset"); + uintptr_t offset_offset = dereferenceSymbol(libjvm, "gHotSpotVMStructEntryOffsetOffset"); if (entry == 0 || stride == 0) { return; From cfdf7efbc41be1caf857a09bbbbb5c6953e09fb1 Mon Sep 17 00:00:00 2001 From: Vsevolod Date: Mon, 25 Sep 2017 19:18:11 +0300 Subject: [PATCH 11/13] Initialize vm structs in profiler instead of VM::init --- src/profiler.cpp | 2 ++ src/profiler.h | 2 -- src/vmEntry.cpp | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/profiler.cpp b/src/profiler.cpp index 5adf7c451..16f3c9835 100755 --- a/src/profiler.cpp +++ b/src/profiler.cpp @@ -395,6 +395,8 @@ bool Profiler::start(Mode mode, int interval, int frame_buffer_size) { _frame_buffer_index = 0; _frame_buffer_overflow = false; + resetSymbols(); + VMStructs::init(jvmLibrary()); bool success; if (mode == MODE_CPU) { success = PerfEvents::start(interval); diff --git a/src/profiler.h b/src/profiler.h index 383300a78..f425121c0 100755 --- a/src/profiler.h +++ b/src/profiler.h @@ -207,8 +207,6 @@ class Profiler { const void* address, jint length) { _instance.addRuntimeStub(address, length, name); } - - friend class VM; }; #endif // _PROFILER_H diff --git a/src/vmEntry.cpp b/src/vmEntry.cpp index bebe3b6bc..4c9f9bde9 100755 --- a/src/vmEntry.cpp +++ b/src/vmEntry.cpp @@ -70,8 +70,6 @@ bool VM::init(JavaVM* vm) { return false; } - Profiler::_instance.resetSymbols(); - VMStructs::init(Profiler::_instance.jvmLibrary()); return true; } From 5332c7d1a9795326f9ca61a2b51f612360d4e3b7 Mon Sep 17 00:00:00 2001 From: Vsevolod Date: Mon, 25 Sep 2017 19:19:45 +0300 Subject: [PATCH 12/13] Make alloc-smoke-test-stable: use wildcard to avoid spurious ThreadLocalRandom methods in traces when AGCT is inaccurate --- test/alloc-smoke-test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/alloc-smoke-test.sh b/test/alloc-smoke-test.sh index e52d048f3..9bd32b485 100755 --- a/test/alloc-smoke-test.sh +++ b/test/alloc-smoke-test.sh @@ -22,7 +22,7 @@ function assert_string() { fi } -assert_string "AllocatingTarget.main;\[Ljava/lang/Integer;" -assert_string "AllocatingTarget.allocate;\[I " +assert_string "AllocatingTarget.allocate;.*\[Ljava/lang/Integer" +assert_string "AllocatingTarget.allocate;.*\[I" popd From 9026ed3b6cecee0c26de08bd636b36ca2daeba26 Mon Sep 17 00:00:00 2001 From: Andrey Pangin Date: Sat, 7 Oct 2017 16:50:17 +0300 Subject: [PATCH 13/13] Fixed compiler warning and tests --- src/jattach.c | 2 +- test/Target.java | 6 +++--- test/alloc-smoke-test.sh | 41 ++++++++++++++++++++++++---------------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/jattach.c b/src/jattach.c index f86bd18c8..67fc2c93d 100755 --- a/src/jattach.c +++ b/src/jattach.c @@ -197,7 +197,7 @@ static int enter_mount_ns(int pid) { return 1; } - return setns(newns, CLONE_NEWNS) < 0 ? 0 : 1; + return setns(newns, 0) < 0 ? 0 : 1; #else return 1; #endif diff --git a/test/Target.java b/test/Target.java index 26a508896..0e3b6f517 100644 --- a/test/Target.java +++ b/test/Target.java @@ -16,10 +16,10 @@ private static void method2() { private static void method3() throws Exception { for (int i = 0; i < 1000; ++i) { - for (String s : new File("/tmp").list()) { - value += s.hashCode(); - } + for (String s : new File("/tmp").list()) { + value += s.hashCode(); } + } } public static void main(String[] args) throws Exception { diff --git a/test/alloc-smoke-test.sh b/test/alloc-smoke-test.sh index 9bd32b485..baaa6f122 100755 --- a/test/alloc-smoke-test.sh +++ b/test/alloc-smoke-test.sh @@ -3,26 +3,35 @@ set -e # exit on any failure set -x # print all executed lines -pushd $(dirname $0) +if [ -z "${JAVA_HOME}" ] +then + echo "JAVA_HOME is not set" +fi -javac AllocatingTarget.java -java AllocatingTarget & +( + cd $(dirname $0) -FILENAME=/tmp/java.trace -JAVAPID=$! + if [ "AllocatingTarget.class" -ot "AllocatingTarget.java" ] + then + ${JAVA_HOME}/bin/javac AllocatingTarget.java + fi -sleep 1 # allow the Java runtime to initialize -../profiler.sh -f $FILENAME -o collapsed -d 5 -m heap $JAVAPID + ${JAVA_HOME}/bin/java AllocatingTarget & -kill $JAVAPID + FILENAME=/tmp/java.trace + JAVAPID=$! -function assert_string() { - if ! grep -q "$1" $FILENAME; then - exit 1 - fi -} + sleep 1 # allow the Java runtime to initialize + ../profiler.sh -f $FILENAME -o collapsed -d 5 -m heap $JAVAPID + + kill $JAVAPID -assert_string "AllocatingTarget.allocate;.*\[Ljava/lang/Integer" -assert_string "AllocatingTarget.allocate;.*\[I" + function assert_string() { + if ! grep -q "$1" $FILENAME; then + exit 1 + fi + } -popd + assert_string "AllocatingTarget.allocate;.*\[Ljava/lang/Integer" + assert_string "AllocatingTarget.allocate;.*\[I" +)