--- old/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxDebuggerLocal.cpp 2020-03-11 18:43:16.037061600 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/LinuxDebuggerLocal.cpp 2020-03-11 18:43:15.221833100 +0900 @@ -1,6 +1,6 @@ /* * Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, NTT DATA. + * Copyright (c) 2019, 2020, NTT DATA. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -616,3 +616,15 @@ return result; } + +/* + * Class: sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal + * Method: findLibPtrByAddress0 + * Signature: (J)J + */ +extern "C" +JNIEXPORT jlong JNICALL Java_sun_jvm_hotspot_debugger_linux_LinuxDebuggerLocal_findLibPtrByAddress0 + (JNIEnv *env, jobject this_obj, jlong pc) { + struct ps_prochandle* ph = get_proc_handle(env, this_obj); + return reinterpret_cast(find_lib_by_address(ph, pc)); +} --- old/src/jdk.hotspot.agent/linux/native/libsaproc/libproc.h 2020-03-11 18:43:18.787039500 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/libproc.h 2020-03-11 18:43:18.001413200 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2020, 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 @@ -54,6 +54,7 @@ #endif struct ps_prochandle; +struct lib_info; #ifdef __cplusplus extern "C" { @@ -99,6 +100,9 @@ // returns true if given library is found in lib list bool find_lib(struct ps_prochandle* ph, const char *lib_name); +// returns lib which contains pc +struct lib_info *find_lib_by_address(struct ps_prochandle* ph, uintptr_t pc); + // symbol lookup uintptr_t lookup_symbol(struct ps_prochandle* ph, const char* object_name, const char* sym_name); --- old/src/jdk.hotspot.agent/linux/native/libsaproc/libproc_impl.c 2020-03-11 18:43:21.761111200 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/libproc_impl.c 2020-03-11 18:43:20.968060600 +0900 @@ -29,6 +29,7 @@ #include #include "libproc_impl.h" #include "proc_service.h" +#include "salibelf.h" #define SA_ALTROOT "SA_ALTROOT" @@ -127,6 +128,7 @@ if (lib->symtab) { destroy_symtab(lib->symtab); } + free(lib->eh_frame.data); free(lib); lib = next; } @@ -157,6 +159,81 @@ return add_lib_info_fd(ph, libname, -1, base); } +static bool fill_instr_info(lib_info* lib) { + off_t current_pos; + ELF_EHDR ehdr; + ELF_PHDR* phbuf = NULL; + ELF_PHDR* ph = NULL; + int cnt; + long align = sysconf(_SC_PAGE_SIZE); + + current_pos = lseek(lib->fd, (off_t)0L, SEEK_CUR); + lseek(lib->fd, (off_t)0L, SEEK_SET); + read_elf_header(lib->fd, &ehdr); + if ((phbuf = read_program_header_table(lib->fd, &ehdr)) == NULL) { + lseek(lib->fd, current_pos, SEEK_SET); + return false; + } + + lib->exec_start = (uintptr_t)-1L; + lib->exec_end = (uintptr_t)-1L; + for (ph = phbuf, cnt = 0; cnt < ehdr.e_phnum; cnt++, ph++) { + if ((ph->p_type == PT_LOAD) && (ph->p_flags & PF_X)) { + print_debug("[%d] vaddr = 0x%lx, memsz = 0x%lx, filesz = 0x%lx\n", cnt, ph->p_vaddr, ph->p_memsz, ph->p_filesz); + if ((lib->exec_start == -1L) || (lib->exec_start > ph->p_vaddr)) { + lib->exec_start = ph->p_vaddr; + } + if ((lib->exec_end == (uintptr_t)-1L) || (lib->exec_end < (ph->p_vaddr + ph->p_memsz))) { + lib->exec_end = ph->p_vaddr + ph->p_memsz; + } + align = ph->p_align; + } + } + + free(phbuf); + lseek(lib->fd, current_pos, SEEK_SET); + + if ((lib->exec_start == -1L) || (lib->exec_end == -1L)) { + return false; + } else { + lib->exec_start = (lib->exec_start + lib->base) & ~(align - 1); + lib->exec_end = (lib->exec_end + lib->base + align) & ~(align - 1); + return true; + } + +} + +bool read_eh_frame(struct ps_prochandle* ph, lib_info* lib) { + off_t current_pos = -1; + ELF_EHDR ehdr; + ELF_SHDR* shbuf = NULL; + ELF_SHDR* sh = NULL; + char* strtab = NULL; + void* result = NULL; + int cnt; + + current_pos = lseek(lib->fd, (off_t)0L, SEEK_CUR); + lseek(lib->fd, (off_t)0L, SEEK_SET); + + read_elf_header(lib->fd, &ehdr); + shbuf = read_section_header_table(lib->fd, &ehdr); + strtab = read_section_data(lib->fd, &ehdr, &shbuf[ehdr.e_shstrndx]); + + for (cnt = 0, sh = shbuf; cnt < ehdr.e_shnum; cnt++, sh++) { + if (strcmp(".eh_frame", sh->sh_name + strtab) == 0) { + lib->eh_frame.library_base_addr = lib->base; + lib->eh_frame.v_addr = sh->sh_addr; + lib->eh_frame.data = read_section_data(lib->fd, &ehdr, sh); + break; + } + } + + free(strtab); + free(shbuf); + lseek(lib->fd, current_pos, SEEK_SET); + return lib->eh_frame.data != NULL; +} + lib_info* add_lib_info_fd(struct ps_prochandle* ph, const char* libname, int fd, uintptr_t base) { lib_info* newlib; @@ -197,6 +274,14 @@ print_debug("symbol table build failed for %s\n", newlib->name); } + if (fill_instr_info(newlib)) { + if (!read_eh_frame(ph, newlib)) { + print_debug("Could not find .eh_frame section in %s\n", newlib->name); + } + } else { + print_debug("Could not find executable section in %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. @@ -356,6 +441,17 @@ return false; } +struct lib_info *find_lib_by_address(struct ps_prochandle* ph, uintptr_t pc) { + lib_info *p = ph->libs; + while (p) { + if ((p->exec_start <= pc) && (pc < p->exec_end)) { + return p; + } + p = p->next; + } + return NULL; +} + //-------------------------------------------------------------------------- // proc service functions --- old/src/jdk.hotspot.agent/linux/native/libsaproc/libproc_impl.h 2020-03-11 18:43:24.596091100 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/libproc_impl.h 2020-03-11 18:43:23.795199200 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2020, 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 @@ -34,10 +34,20 @@ #define BUF_SIZE (PATH_MAX + NAME_MAX + 1) +// .eh_frame data +typedef struct eh_frame_info { + uintptr_t library_base_addr; + uintptr_t v_addr; + unsigned char* data; +} eh_frame_info; + // list of shared objects typedef struct lib_info { char name[BUF_SIZE]; uintptr_t base; + uintptr_t exec_start; + uintptr_t exec_end; + eh_frame_info eh_frame; struct symtab* symtab; int fd; // file descriptor for lib struct lib_info* next; @@ -101,6 +111,10 @@ struct core_data* core; // data only used for core dumps, NULL for process }; +#ifdef __cplusplus +extern "C" { +#endif + int pathmap_open(const char* name); void print_debug(const char* format,...); @@ -123,4 +137,8 @@ // a test for ELF signature without using libelf bool is_elf_file(int fd); +#ifdef __cplusplus +} +#endif + #endif //_LIBPROC_IMPL_H_ --- old/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.c 2020-03-11 18:43:27.261940300 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.c 2020-03-11 18:43:26.459012200 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2020, 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 @@ -124,3 +124,32 @@ if (phbuf) free(phbuf); return baseaddr; } + +struct elf_section *find_section_by_name(char *name, + int fd, + ELF_EHDR *ehdr, + struct elf_section *scn_cache) { + char *strtab; + int cnt; + int strtab_size; + + // Section cache have to already contain data for e_shstrndx section. + // If it's not true - elf file is broken, so just bail out + if (scn_cache[ehdr->e_shstrndx].c_data == NULL) { + return NULL; + } + + strtab = scn_cache[ehdr->e_shstrndx].c_data; + strtab_size = scn_cache[ehdr->e_shstrndx].c_shdr->sh_size; + + for (cnt = 0; cnt < ehdr->e_shnum; ++cnt) { + if (scn_cache[cnt].c_shdr->sh_name < strtab_size) { + if (strcmp(scn_cache[cnt].c_shdr->sh_name + strtab, name) == 0) { + scn_cache[cnt].c_data = read_section_data(fd, ehdr, scn_cache[cnt].c_shdr); + return &scn_cache[cnt]; + } + } + } + + return NULL; +} --- old/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.h 2020-03-11 18:43:30.176739100 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.h 2020-03-11 18:43:29.383320000 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2020, 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 @@ -29,6 +29,11 @@ #include "elfmacros.h" #include "libproc_impl.h" +struct elf_section { + ELF_SHDR *c_shdr; + void *c_data; +}; + // read ELF file header. int read_elf_header(int fd, ELF_EHDR* ehdr); @@ -49,4 +54,10 @@ // find the base address at which the library wants to load itself uintptr_t find_base_address(int fd, ELF_EHDR* ehdr); + +// Find an ELF section. +struct elf_section *find_section_by_name(char *name, + int fd, + ELF_EHDR *ehdr, + struct elf_section *scn_cache); #endif /* _SALIBELF_H_ */ --- old/src/jdk.hotspot.agent/linux/native/libsaproc/symtab.c 2020-03-11 18:43:33.091108500 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/symtab.c 2020-03-11 18:43:32.295428900 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2020, 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 @@ -35,11 +35,6 @@ // functions for symbol lookups // ---------------------------------------------------- -struct elf_section { - ELF_SHDR *c_shdr; - void *c_data; -}; - struct elf_symbol { char *name; uintptr_t offset; @@ -158,37 +153,6 @@ } } -/* Find an ELF section. */ -static struct elf_section *find_section_by_name(char *name, - int fd, - ELF_EHDR *ehdr, - struct elf_section *scn_cache) -{ - char *strtab; - int cnt; - int strtab_size; - - // Section cache have to already contain data for e_shstrndx section. - // If it's not true - elf file is broken, so just bail out - if (scn_cache[ehdr->e_shstrndx].c_data == NULL) { - return NULL; - } - - strtab = scn_cache[ehdr->e_shstrndx].c_data; - strtab_size = scn_cache[ehdr->e_shstrndx].c_shdr->sh_size; - - for (cnt = 0; cnt < ehdr->e_shnum; ++cnt) { - if (scn_cache[cnt].c_shdr->sh_name < strtab_size) { - if (strcmp(scn_cache[cnt].c_shdr->sh_name + strtab, name) == 0) { - scn_cache[cnt].c_data = read_section_data(fd, ehdr, scn_cache[cnt].c_shdr); - 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, --- old/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxCDebugger.java 2020-03-11 18:43:35.929441800 +0900 +++ new/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxCDebugger.java 2020-03-11 18:43:35.121745600 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, Red Hat Inc. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -90,11 +90,9 @@ return new LinuxX86CFrame(dbg, ebp, pc); } else if (cpu.equals("amd64")) { AMD64ThreadContext context = (AMD64ThreadContext) thread.getContext(); - Address rbp = context.getRegisterAsAddress(AMD64ThreadContext.RBP); - if (rbp == null) return null; Address pc = context.getRegisterAsAddress(AMD64ThreadContext.RIP); if (pc == null) return null; - return new LinuxAMD64CFrame(dbg, rbp, pc); + return LinuxAMD64CFrame.getTopFrame(dbg, pc, context); } else if (cpu.equals("sparc")) { SPARCThreadContext context = (SPARCThreadContext) thread.getContext(); Address sp = context.getRegisterAsAddress(SPARCThreadContext.R_SP); --- old/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebugger.java 2020-03-11 18:43:38.689295600 +0900 +++ new/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebugger.java 2020-03-11 18:43:37.883204300 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2020, 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 @@ -52,6 +52,7 @@ public long[] getThreadIntegerRegisterSet(int lwp_id) throws DebuggerException; public long getAddressValue(Address addr) throws DebuggerException; public Address newAddress(long value) throws DebuggerException; + public Address findLibPtrByAddress(Address pc); // For LinuxCDebugger public List getThreadList(); --- old/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebuggerLocal.java 2020-03-11 18:43:41.515212200 +0900 +++ new/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/LinuxDebuggerLocal.java 2020-03-11 18:43:40.695328200 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2020, 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 @@ -121,6 +121,15 @@ @Override public native String demangle(String sym); + public native long findLibPtrByAddress0(long pc); + + @Override + public Address findLibPtrByAddress(Address pc) { + long ptr = findLibPtrByAddress0(pc.asLongValue()); + return (ptr == 0L) ? null + : new LinuxAddress(this, ptr); + } + // Note on Linux threads are really processes. When target process is // attached by a serviceability agent thread, only that thread can do // ptrace operations on the target. This is because from kernel's point --- old/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/LinuxAMD64CFrame.java 2020-03-11 18:43:44.534775000 +0900 +++ new/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/LinuxAMD64CFrame.java 2020-03-11 18:43:43.705737200 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2020, 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 @@ -31,11 +31,36 @@ import sun.jvm.hotspot.debugger.cdbg.basic.*; final public class LinuxAMD64CFrame extends BasicCFrame { - public LinuxAMD64CFrame(LinuxDebugger dbg, Address rbp, Address rip) { + + public static LinuxAMD64CFrame getTopFrame(LinuxDebugger dbg, Address rip, ThreadContext context) { + Address libptr = dbg.findLibPtrByAddress(rip); + Address cfa = context.getRegisterAsAddress(AMD64ThreadContext.RBP); + DwarfParser dwarf = null; + + if (libptr != null) { // Native frame + try { + dwarf = new DwarfParser(libptr); + dwarf.processDwarf(rip); + cfa = ((dwarf.getCFARegister() == AMD64ThreadContext.RBP) && + !dwarf.isBPOffsetAvailable()) + ? context.getRegisterAsAddress(AMD64ThreadContext.RBP) + : context.getRegisterAsAddress(dwarf.getCFARegister()) + .addOffsetTo(dwarf.getCFAOffset()); + } catch (DebuggerException e) { + // Bail out to Java frame case + } + } + + return (cfa == null) ? null + : new LinuxAMD64CFrame(dbg, cfa, rip, dwarf); + } + + private LinuxAMD64CFrame(LinuxDebugger dbg, Address cfa, Address rip, DwarfParser dwarf) { super(dbg.getCDebugger()); - this.rbp = rbp; + this.cfa = cfa; this.rip = rip; this.dbg = dbg; + this.dwarf = dwarf; } // override base class impl to avoid ELF parsing @@ -49,36 +74,97 @@ } public Address localVariableBase() { - return rbp; + return cfa; } - public CFrame sender(ThreadProxy thread) { - AMD64ThreadContext context = (AMD64ThreadContext) thread.getContext(); - Address rsp = context.getRegisterAsAddress(AMD64ThreadContext.RSP); + private Address getNextPC(boolean useDwarf) { + try { + long offs = useDwarf ? dwarf.getReturnAddressOffsetFromCFA() + : ADDRESS_SIZE; + return cfa.getAddressAt(offs); + } catch (UnmappedAddressException | UnalignedAddressException e) { + return null; + } + } - if ( (rbp == null) || rbp.lessThan(rsp) ) { - return null; - } + private boolean isValidFrame(Address nextCFA, ThreadContext context) { + return (nextCFA != null) && + !nextCFA.lessThan(context.getRegisterAsAddress(AMD64ThreadContext.RSP)); + } - // Check alignment of rbp - if ( dbg.getAddressValue(rbp) % ADDRESS_SIZE != 0) { - return null; - } + private Address getNextCFA(DwarfParser nextDwarf, ThreadContext context) { + Address nextCFA; - Address nextRBP = rbp.getAddressAt( 0 * ADDRESS_SIZE); - if (nextRBP == null || nextRBP.lessThanOrEqual(rbp)) { - return null; - } - Address nextPC = rbp.getAddressAt( 1 * ADDRESS_SIZE); - if (nextPC == null) { - return null; - } - return new LinuxAMD64CFrame(dbg, nextRBP, nextPC); + if (nextDwarf == null) { // Next frame is Java + nextCFA = (dwarf == null) ? cfa.getAddressAt(0) // Current frame is Java (Use RBP) + : cfa.getAddressAt(dwarf.getBasePointerOffsetFromCFA()); // Current frame is Native + } else { // Next frame is Native + if (dwarf == null) { // Current frame is Java (Use RBP) + nextCFA = cfa.getAddressAt(0); + } else { // Current frame is Native + int nextCFAReg = nextDwarf.getCFARegister(); + if (!dwarf.isBPOffsetAvailable() && // Use RBP as CFA + (nextCFAReg == AMD64ThreadContext.RBP) && + (nextCFAReg != dwarf.getCFARegister())) { + nextCFA = context.getRegisterAsAddress(AMD64ThreadContext.RBP); + if (nextCFA == null) { + return null; + } + nextCFA = nextCFA.getAddressAt(0); + } else { + nextCFA = cfa.getAddressAt(dwarf.getBasePointerOffsetFromCFA()); + } + } + if (nextCFA != null) { + nextCFA = nextCFA.addOffsetTo(-nextDwarf.getBasePointerOffsetFromCFA()); + } + } + + return isValidFrame(nextCFA, context) ? nextCFA : null; + } + + private DwarfParser getNextDwarf(Address nextPC) { + DwarfParser nextDwarf = null; + + if ((dwarf != null) && dwarf.isIn(nextPC)) { + nextDwarf = dwarf; + } else { + Address libptr = dbg.findLibPtrByAddress(nextPC); + if (libptr != null) { + try { + nextDwarf = new DwarfParser(libptr); + } catch (DebuggerException e) { + // Bail out to Java frame + } + } + } + + if (nextDwarf != null) { + nextDwarf.processDwarf(nextPC); + } + + return nextDwarf; + } + + @Override + public CFrame sender(ThreadProxy thread) { + ThreadContext context = thread.getContext(); + + Address nextPC = getNextPC(dwarf != null); + if (nextPC == null) { + return null; + } + + DwarfParser nextDwarf = getNextDwarf(nextPC); + Address nextCFA = getNextCFA(nextDwarf, context); + return isValidFrame(nextCFA, context) ? new LinuxAMD64CFrame(dbg, nextCFA, nextPC, nextDwarf) + : null; } // package/class internals only private static final int ADDRESS_SIZE = 8; private Address rip; - private Address rbp; + private Address cfa; private LinuxDebugger dbg; + private DwarfParser dwarf; } --- /dev/null 2020-03-11 18:39:32.786759700 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/DwarfParser.cpp 2020-03-11 18:43:46.490642100 +0900 @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, NTT DATA. + * 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 "dwarf.hpp" +#include "libproc.h" + +#define CHECK_EXCEPTION if (env->ExceptionOccurred()) { return; } + +static jfieldID p_dwarf_context_ID = 0; +static jint sa_RAX = -1; +static jint sa_RDX = -1; +static jint sa_RCX = -1; +static jint sa_RBX = -1; +static jint sa_RSI = -1; +static jint sa_RDI = -1; +static jint sa_RBP = -1; +static jint sa_RSP = -1; +static jint sa_R8 = -1; +static jint sa_R9 = -1; +static jint sa_R10 = -1; +static jint sa_R11 = -1; +static jint sa_R12 = -1; +static jint sa_R13 = -1; +static jint sa_R14 = -1; +static jint sa_R15 = -1; + +static jlong get_dwarf_context(JNIEnv *env, jobject obj) { + return env->GetLongField(obj, p_dwarf_context_ID); +} + +#define SET_REG(env, reg, reg_cls) \ + jfieldID reg##_ID = env->GetStaticFieldID(reg_cls, #reg, "I"); \ + CHECK_EXCEPTION \ + sa_##reg = env->GetStaticIntField(reg_cls, reg##_ID); \ + CHECK_EXCEPTION + +/* + * Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser + * Method: init0 + * Signature: ()V + */ +extern "C" +JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_init0 + (JNIEnv *env, jclass this_cls) { + jclass cls = env->FindClass("sun/jvm/hotspot/debugger/linux/amd64/DwarfParser"); + CHECK_EXCEPTION + p_dwarf_context_ID = env->GetFieldID(cls, "p_dwarf_context", "J"); + CHECK_EXCEPTION + + jclass reg_cls = env->FindClass("sun/jvm/hotspot/debugger/amd64/AMD64ThreadContext"); + CHECK_EXCEPTION + SET_REG(env, RAX, reg_cls); + SET_REG(env, RDX, reg_cls); + SET_REG(env, RCX, reg_cls); + SET_REG(env, RBX, reg_cls); + SET_REG(env, RSI, reg_cls); + SET_REG(env, RDI, reg_cls); + SET_REG(env, RBP, reg_cls); + SET_REG(env, RSP, reg_cls); + SET_REG(env, R8, reg_cls); + SET_REG(env, R9, reg_cls); + SET_REG(env, R10, reg_cls); + SET_REG(env, R11, reg_cls); + SET_REG(env, R12, reg_cls); + SET_REG(env, R13, reg_cls); + SET_REG(env, R14, reg_cls); + SET_REG(env, R15, reg_cls); +} + +/* + * Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser + * Method: createDwarfContext + * Signature: (J)J + */ +extern "C" +JNIEXPORT jlong JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_createDwarfContext + (JNIEnv *env, jclass this_cls, jlong lib) { + jlong result = 0L; + + DwarfParser *parser = new DwarfParser(reinterpret_cast(lib)); + if (!parser->is_parseable()) { + jclass ex_cls = env->FindClass("sun/jvm/hotspot/debugger/DebuggerException"); + env->ThrowNew(ex_cls, "DWARF not found"); + return 0L; + } + + return reinterpret_cast(parser); +} + +/* + * Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser + * Method: destroyDwarfContext + * Signature: (J)V + */ +extern "C" +JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_destroyDwarfContext + (JNIEnv *env, jclass this_cls, jlong context) { + DwarfParser *parser = reinterpret_cast(context); + delete parser; +} + +/* + * Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser + * Method: isIn0 + * Signature: (J)Z + */ +extern "C" +JNIEXPORT jboolean JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_isIn0 + (JNIEnv *env, jobject this_obj, jlong pc) { + DwarfParser *parser = reinterpret_cast(get_dwarf_context(env, this_obj)); + return static_cast(parser->is_in(pc)); +} + +/* + * Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser + * Method: processDwarf0 + * Signature: (J)V + */ +extern "C" +JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_processDwarf0 + (JNIEnv *env, jobject this_obj, jlong pc) { + DwarfParser *parser = reinterpret_cast(get_dwarf_context(env, this_obj)); + if (!parser->process_dwarf(pc)) { + jclass ex_cls = env->FindClass("sun/jvm/hotspot/debugger/DebuggerException"); + env->ThrowNew(ex_cls, "Could not find PC in DWARF"); + return; + } +} + +/* + * Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser + * Method: getCFARegister + * Signature: ()I + */ +extern "C" +JNIEXPORT jint JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_getCFARegister + (JNIEnv *env, jobject this_obj) { + DwarfParser *parser = reinterpret_cast(get_dwarf_context(env, this_obj)); + switch (parser->get_cfa_register()) { + case RAX: return sa_RAX; + case RDX: return sa_RDX; + case RCX: return sa_RCX; + case RBX: return sa_RBX; + case RSI: return sa_RSI; + case RDI: return sa_RDI; + case RBP: return sa_RBP; + case RSP: return sa_RSP; + case R8: return sa_R8; + case R9: return sa_R9; + case R10: return sa_R10; + case R11: return sa_R11; + case R12: return sa_R12; + case R13: return sa_R13; + case R14: return sa_R14; + case R15: return sa_R15; + default: return -1; + } +} + +/* + * Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser + * Method: getCFAOffset + * Signature: ()I + */ +extern "C" +JNIEXPORT jint JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_getCFAOffset + (JNIEnv *env, jobject this_obj) { + DwarfParser *parser = reinterpret_cast(get_dwarf_context(env, this_obj)); + return parser->get_cfa_offset(); +} + +/* + * Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser + * Method: getReturnAddressOffsetFromCFA + * Signature: ()I + */ +extern "C" +JNIEXPORT jint JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_getReturnAddressOffsetFromCFA + (JNIEnv *env, jobject this_obj) { + DwarfParser *parser = reinterpret_cast(get_dwarf_context(env, this_obj)); + return parser->get_ra_cfa_offset(); +} + +/* + * Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser + * Method: getBasePointerOffsetFromCFA + * Signature: ()I + */ +extern "C" +JNIEXPORT jint JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_getBasePointerOffsetFromCFA + (JNIEnv *env, jobject this_obj) { + DwarfParser *parser = reinterpret_cast(get_dwarf_context(env, this_obj)); + return parser->get_bp_cfa_offset(); +} + +/* + * Class: sun_jvm_hotspot_debugger_linux_amd64_DwarfParser + * Method: isBPOffsetAvailable + * Signature: ()Z + */ +extern "C" +JNIEXPORT jboolean JNICALL Java_sun_jvm_hotspot_debugger_linux_amd64_DwarfParser_isBPOffsetAvailable + (JNIEnv *env, jobject this_obj) { + DwarfParser *parser = reinterpret_cast(get_dwarf_context(env, this_obj)); + return parser->is_bp_offset_available(); +} + --- /dev/null 2020-03-11 18:39:32.786759700 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.cpp 2020-03-11 18:43:48.623995700 +0900 @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, NTT DATA. + * 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 "dwarf.hpp" +#include "libproc_impl.h" + +/* from read_leb128() in dwarf.c in binutils */ +uintptr_t DwarfParser::read_leb(bool sign) { + uintptr_t result = 0L; + unsigned char b; + unsigned int shift = 0; + + while (true) { + b = *_buf++; + result |= static_cast(b & 0x7f) << shift; + shift += 7; + if ((b & 0x80) == 0) { + break; + } + } + + if (sign && (shift < (8 * sizeof(result))) && (b & 0x40)) { + result |= static_cast(-1L) << shift; + } + + return result; +} + +uint64_t DwarfParser::get_entry_length() { + uint64_t length = *(reinterpret_cast(_buf)); + _buf += 4; + if (length == 0xffffffff) { + length = *(reinterpret_cast(_buf)); + _buf += 8; + } + return length; +} + +bool DwarfParser::process_cie(unsigned char *start_of_entry, uint32_t id) { + unsigned char *orig_pos = _buf; + _buf = start_of_entry - id; + + uint64_t length = get_entry_length(); + if (length == 0L) { + return false; + } + unsigned char *end = _buf + length; + + _buf += 4; // Skip ID (This value of CIE would be always 0) + _buf++; // Skip version (assume to be "1") + + char *augmentation_string = reinterpret_cast(_buf); + bool has_ehdata = (strcmp("eh", augmentation_string) == 0); + bool fde_encoded = (strchr(augmentation_string, 'R') != NULL); + _buf += strlen(augmentation_string) + 1; // includes '\0' + if (has_ehdata) { + _buf += sizeof(void *); // Skip EH data + } + + _code_factor = read_leb(false); + _data_factor = static_cast(read_leb(true)); + _return_address_reg = static_cast(*_buf++); + + if (fde_encoded) { + uintptr_t augmentation_length = read_leb(false); + _encoding = *_buf++; + } + + // Clear state + _current_pc = 0L; + _cfa_reg = RSP; + _return_address_reg = RA; + _cfa_offset = 0; + _ra_cfa_offset = 0; + _bp_cfa_offset = 0; + _bp_offset_available = false; + + parse_dwarf_instructions(0L, static_cast(-1L), end); + + _buf = orig_pos; + return true; +} + +void DwarfParser::parse_dwarf_instructions(uintptr_t begin, uintptr_t pc, const unsigned char *end) { + uintptr_t operand1; + _current_pc = begin; + + /* for remember state */ + enum DWARF_Register rem_cfa_reg = MAX_VALUE; + int rem_cfa_offset = 0; + int rem_ra_cfa_offset = 0; + int rem_bp_cfa_offset = 0; + + while ((_buf < end) && (_current_pc < pc)) { + unsigned char op = *_buf++; + unsigned char opa = op & 0x3f; + if (op & 0xc0) { + op &= 0xc0; + } + + switch (op) { + case 0x0: // DW_CFA_nop + return; + case 0x01: // DW_CFA_set_loc + operand1 = get_decoded_value(); + if (_current_pc != 0L) { + _current_pc = operand1; + } + break; + case 0x0c: // DW_CFA_def_cfa + _cfa_reg = static_cast(read_leb(false)); + _cfa_offset = read_leb(false); + break; + case 0x80: {// DW_CFA_offset + operand1 = read_leb(false); + enum DWARF_Register reg = static_cast(opa); + if (reg == RBP) { + _bp_cfa_offset = operand1 * _data_factor; + _bp_offset_available = true; + } else if (reg == RA) { + _ra_cfa_offset = operand1 * _data_factor; + } + break; + } + case 0xe: // DW_CFA_def_cfa_offset + _cfa_offset = read_leb(false); + break; + case 0x40: // DW_CFA_advance_loc + if (_current_pc != 0L) { + _current_pc += opa * _code_factor; + } + break; + case 0x02: { // DW_CFA_advance_loc1 + unsigned char ofs = *_buf++; + if (_current_pc != 0L) { + _current_pc += ofs * _code_factor; + } + break; + } + case 0x03: { // DW_CFA_advance_loc2 + unsigned short ofs = *(reinterpret_cast(_buf)); + _buf += 2; + if (_current_pc != 0L) { + _current_pc += ofs * _code_factor; + } + break; + } + case 0x04: { // DW_CFA_advance_loc4 + unsigned int ofs = *(reinterpret_cast(_buf)); + _buf += 4; + if (_current_pc != 0L) { + _current_pc += ofs * _code_factor; + } + break; + } + case 0x0d: {// DW_CFA_def_cfa_register + _cfa_reg = static_cast(read_leb(false)); + break; + } + case 0x0a: // DW_CFA_remember_state + rem_cfa_reg = _cfa_reg; + rem_cfa_offset = _cfa_offset; + rem_ra_cfa_offset = _ra_cfa_offset; + rem_bp_cfa_offset = _bp_cfa_offset; + break; + case 0x0b: // DW_CFA_restore_state + _cfa_reg = rem_cfa_reg; + _cfa_offset = rem_cfa_offset; + _ra_cfa_offset = rem_ra_cfa_offset; + _bp_cfa_offset = rem_bp_cfa_offset; + break; + default: + print_debug("DWARF: Unknown opcode: 0x%x\n", op); + return; + } + } +} + +/* from dwarf.c in binutils */ +uint32_t DwarfParser::get_decoded_value() { + int size; + uintptr_t result; + + switch (_encoding & 0x7) { + case 0: // DW_EH_PE_absptr + size = sizeof(void *); + result = *(reinterpret_cast(_buf)); + break; + case 2: // DW_EH_PE_udata2 + size = 2; + result = *(reinterpret_cast(_buf)); + break; + case 3: // DW_EH_PE_udata4 + size = 4; + result = *(reinterpret_cast(_buf)); + break; + case 4: // DW_EH_PE_udata8 + size = 8; + result = *(reinterpret_cast(_buf)); + break; + default: + return 0; + } + + // On x86-64, we have to handle it as 32 bit value, and it is PC relative. + // https://gcc.gnu.org/ml/gcc-help/2010-09/msg00166.html +#if defined(_LP64) + if (size == 8) { + result += _lib->eh_frame.v_addr + static_cast(_buf - _lib->eh_frame.data); + size = 4; + } else +#endif + if ((_encoding & 0x70) == 0x10) { // 0x10 = DW_EH_PE_pcrel + result += _lib->eh_frame.v_addr + static_cast(_buf - _lib->eh_frame.data); + } else if (size == 2) { + result = static_cast(result) + _lib->eh_frame.v_addr + static_cast(_buf - _lib->eh_frame.data); + size = 4; + } + + _buf += size; + return static_cast(result); +} + +unsigned int DwarfParser::get_pc_range() { + int size; + uintptr_t result; + + switch (_encoding & 0x7) { + case 0: // DW_EH_PE_absptr + size = sizeof(void *); + result = *(reinterpret_cast(_buf)); + break; + case 2: // DW_EH_PE_udata2 + size = 2; + result = *(reinterpret_cast(_buf)); + break; + case 3: // DW_EH_PE_udata4 + size = 4; + result = *(reinterpret_cast(_buf)); + break; + case 4: // DW_EH_PE_udata8 + size = 8; + result = *(reinterpret_cast(_buf)); + break; + default: + return 0; + } + + // On x86-64, we have to handle it as 32 bit value, and it is PC relative. + // https://gcc.gnu.org/ml/gcc-help/2010-09/msg00166.html +#if defined(_LP64) + if ((size == 8) || (size == 2)) { + size = 4; + } +#endif + + _buf += size; + return static_cast(result); +} + +bool DwarfParser::process_dwarf(const uintptr_t pc) { + // https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/ehframechpt.html + _buf = _lib->eh_frame.data; + while (true) { + uint64_t length = get_entry_length(); + if (length == 0L) { + return false; + } + unsigned char *next_entry = _buf + length; + unsigned char *start_of_entry = _buf; + uint32_t id = *(reinterpret_cast(_buf)); + _buf += 4; + if (id != 0) { // FDE + uintptr_t pc_begin = get_decoded_value() + _lib->eh_frame.library_base_addr; + uintptr_t pc_end = pc_begin + get_pc_range(); + + if ((pc >= pc_begin) && (pc < pc_end)) { + // Process CIE + if (!process_cie(start_of_entry, id)) { + return false; + } + + // Skip Augumenation + uintptr_t augmentation_length = read_leb(false); + _buf += augmentation_length; // skip + + // Process FDE + parse_dwarf_instructions(pc_begin, pc, next_entry); + break; + } + } + + _buf = next_entry; + } + + return true; +} --- /dev/null 2020-03-11 18:39:32.786759700 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.hpp 2020-03-11 18:43:50.760083200 +0900 @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, NTT DATA. + * 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 _DWARF_HPP_ +#define _DWARF_HPP_ + +#include "libproc_impl.h" + +/* + * from System V Application Binary Interface + * AMD64 Architecture Processor Supplement + * Figure 3.38: DWARF Register Number Mapping + * https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf + */ +enum DWARF_Register { + RAX, + RDX, + RCX, + RBX, + RSI, + RDI, + RBP, + RSP, + R8, + R9, + R10, + R11, + R12, + R13, + R14, + R15, + RA, + MAX_VALUE +}; + +/* + * DwarfParser finds out CFA (Canonical Frame Address) from DWARF in ELF binary. + * Also Return Address (RA) and Base Pointer (BP) are calculated from CFA. + */ +class DwarfParser { + private: + const lib_info *_lib; + unsigned char *_buf; + unsigned char _encoding; + enum DWARF_Register _cfa_reg; + enum DWARF_Register _return_address_reg; + unsigned int _code_factor; + int _data_factor; + + uintptr_t _current_pc; + int _cfa_offset; + int _ra_cfa_offset; + int _bp_cfa_offset; + bool _bp_offset_available; + + uintptr_t read_leb(bool sign); + uint64_t get_entry_length(); + bool process_cie(unsigned char *start_of_entry, uint32_t id); + void parse_dwarf_instructions(uintptr_t begin, uintptr_t pc, const unsigned char *end); + uint32_t get_decoded_value(); + unsigned int get_pc_range(); + + public: + DwarfParser(lib_info *lib) : _lib(lib), + _buf(NULL), + _encoding(0), + _cfa_reg(RSP), + _return_address_reg(RA), + _code_factor(0), + _data_factor(0), + _current_pc(0L), + _cfa_offset(0), + _ra_cfa_offset(0), + _bp_cfa_offset(0), + _bp_offset_available(false) {}; + + ~DwarfParser() {} + bool process_dwarf(const uintptr_t pc); + enum DWARF_Register get_cfa_register() { return _cfa_reg; } + int get_cfa_offset() { return _cfa_offset; } + int get_ra_cfa_offset() { return _ra_cfa_offset; } + int get_bp_cfa_offset() { return _bp_cfa_offset; } + bool is_bp_offset_available() { return _bp_offset_available; } + + bool is_in(long pc) { + return (_lib->exec_start <= pc) && (pc < _lib->exec_end); + } + + bool is_parseable() { + return _lib->eh_frame.data != NULL; + } +}; + +#endif //_DWARF_HPP_ --- /dev/null 2020-03-11 18:39:32.786759700 +0900 +++ new/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/DwarfParser.java 2020-03-11 18:43:52.904564800 +0900 @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, NTT DATA. + * 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. + * + */ + +package sun.jvm.hotspot.debugger.linux.amd64; + +import java.lang.ref.Cleaner; +import sun.jvm.hotspot.debugger.Address; +import sun.jvm.hotspot.debugger.DebuggerException; + +public class DwarfParser { + private final long p_dwarf_context; // native dwarf context handle + + private static native void init0(); + private static native long createDwarfContext(long lib); + private static native void destroyDwarfContext(long context); + private native boolean isIn0(long pc); + + static { + init0(); + } + + public DwarfParser(Address lib) { + p_dwarf_context = createDwarfContext(lib.asLongValue()); + + if (p_dwarf_context == 0L) { + throw new DebuggerException("Could not create DWARF context"); + } + + Cleaner.create() + .register(this, () -> DwarfParser.destroyDwarfContext(p_dwarf_context)); + } + + public boolean isIn(Address pc) { + return isIn0(pc.asLongValue()); + } + + private native void processDwarf0(long pc); + + public void processDwarf(Address pc) { + processDwarf0(pc.asLongValue()); + } + + public native int getCFARegister(); + public native int getCFAOffset(); + public native int getReturnAddressOffsetFromCFA(); + public native int getBasePointerOffsetFromCFA(); + public native boolean isBPOffsetAvailable(); +}