--- /dev/null 2020-03-11 09:16:56.700079500 +0900 +++ new/src/jdk.hotspot.agent/linux/native/libsaproc/dwarf.cpp 2020-03-11 11:02:05.828758000 +0900 @@ -0,0 +1,312 @@ +/* + * 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; +} + +void 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(); + 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; +} + +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 short 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); +} + +uintptr_t 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(); + 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 + process_cie(start_of_entry, id); + + // 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 0L; +}