/* * 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 0x0d: {// DW_CFA_def_cfa_register _cfa_reg = static_cast(read_leb(false)); break; } case 0x0a: // DW_CFA_remenber_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; }