| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * 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. |
| */ |
| |
| #ifndef ART_COMPILER_DEBUG_ELF_DEBUG_FRAME_WRITER_H_ |
| #define ART_COMPILER_DEBUG_ELF_DEBUG_FRAME_WRITER_H_ |
| |
| #include <vector> |
| |
| #include "arch/instruction_set.h" |
| #include "base/macros.h" |
| #include "debug/method_debug_info.h" |
| #include "dwarf/debug_frame_opcode_writer.h" |
| #include "dwarf/dwarf_constants.h" |
| #include "dwarf/headers.h" |
| #include "elf/elf_builder.h" |
| |
| namespace art HIDDEN { |
| namespace debug { |
| |
| static constexpr bool kWriteDebugFrameHdr = false; |
| |
| // Binary search table is not useful if the number of entries is small. |
| // In particular, this avoids it for the in-memory JIT mini-debug-info. |
| static constexpr size_t kMinDebugFrameHdrEntries = 100; |
| |
| static void WriteCIE(InstructionSet isa, /*inout*/ std::vector<uint8_t>* buffer) { |
| using Reg = dwarf::Reg; |
| // Scratch registers should be marked as undefined. This tells the |
| // debugger that its value in the previous frame is not recoverable. |
| bool is64bit = Is64BitInstructionSet(isa); |
| switch (isa) { |
| case InstructionSet::kArm: |
| case InstructionSet::kThumb2: { |
| dwarf::DebugFrameOpCodeWriter<> opcodes; |
| opcodes.DefCFA(Reg::ArmCore(13), 0); // R13(SP). |
| // core registers. |
| for (int reg = 0; reg < 13; reg++) { |
| if (reg < 4 || reg == 12) { |
| opcodes.Undefined(Reg::ArmCore(reg)); |
| } else { |
| opcodes.SameValue(Reg::ArmCore(reg)); |
| } |
| } |
| // fp registers. |
| for (int reg = 0; reg < 32; reg++) { |
| if (reg < 16) { |
| opcodes.Undefined(Reg::ArmFp(reg)); |
| } else { |
| opcodes.SameValue(Reg::ArmFp(reg)); |
| } |
| } |
| auto return_reg = Reg::ArmCore(14); // R14(LR). |
| WriteCIE(is64bit, return_reg, opcodes, buffer); |
| return; |
| } |
| case InstructionSet::kArm64: { |
| dwarf::DebugFrameOpCodeWriter<> opcodes; |
| opcodes.DefCFA(Reg::Arm64Core(31), 0); // R31(SP). |
| // core registers. |
| for (int reg = 0; reg < 30; reg++) { |
| if (reg < 8 || reg == 16 || reg == 17) { |
| opcodes.Undefined(Reg::Arm64Core(reg)); |
| } else { |
| opcodes.SameValue(Reg::Arm64Core(reg)); |
| } |
| } |
| // fp registers. |
| for (int reg = 0; reg < 32; reg++) { |
| if (reg < 8 || reg >= 16) { |
| opcodes.Undefined(Reg::Arm64Fp(reg)); |
| } else { |
| opcodes.SameValue(Reg::Arm64Fp(reg)); |
| } |
| } |
| auto return_reg = Reg::Arm64Core(30); // R30(LR). |
| WriteCIE(is64bit, return_reg, opcodes, buffer); |
| return; |
| } |
| case InstructionSet::kRiscv64: { |
| dwarf::DebugFrameOpCodeWriter<> opcodes; |
| opcodes.DefCFA(Reg::Riscv64Core(2), 0); // X2(SP). |
| // core registers. |
| for (int reg = 3; reg < 32; reg++) { // Skip X0 (Zero), X1 (RA) and X2 (SP). |
| if ((reg >= 5 && reg < 8) || (reg >= 10 && reg < 18) || reg >= 28) { |
| opcodes.Undefined(Reg::Riscv64Core(reg)); |
| } else { |
| opcodes.SameValue(Reg::Riscv64Core(reg)); |
| } |
| } |
| // fp registers. |
| for (int reg = 0; reg < 32; reg++) { |
| if (reg < 8 || (reg >=10 && reg < 18) || reg >= 28) { |
| opcodes.Undefined(Reg::Riscv64Fp(reg)); |
| } else { |
| opcodes.SameValue(Reg::Riscv64Fp(reg)); |
| } |
| } |
| auto return_reg = Reg::Riscv64Core(1); // X1(RA). |
| WriteCIE(is64bit, return_reg, opcodes, buffer); |
| return; |
| } |
| case InstructionSet::kX86: { |
| // FIXME: Add fp registers once libunwind adds support for them. Bug: 20491296 |
| constexpr bool generate_opcodes_for_x86_fp = false; |
| dwarf::DebugFrameOpCodeWriter<> opcodes; |
| opcodes.DefCFA(Reg::X86Core(4), 4); // R4(ESP). |
| opcodes.Offset(Reg::X86Core(8), -4); // R8(EIP). |
| // core registers. |
| for (int reg = 0; reg < 8; reg++) { |
| if (reg <= 3) { |
| opcodes.Undefined(Reg::X86Core(reg)); |
| } else if (reg == 4) { |
| // Stack pointer. |
| } else { |
| opcodes.SameValue(Reg::X86Core(reg)); |
| } |
| } |
| // fp registers. |
| if (generate_opcodes_for_x86_fp) { |
| for (int reg = 0; reg < 8; reg++) { |
| opcodes.Undefined(Reg::X86Fp(reg)); |
| } |
| } |
| auto return_reg = Reg::X86Core(8); // R8(EIP). |
| WriteCIE(is64bit, return_reg, opcodes, buffer); |
| return; |
| } |
| case InstructionSet::kX86_64: { |
| dwarf::DebugFrameOpCodeWriter<> opcodes; |
| opcodes.DefCFA(Reg::X86_64Core(4), 8); // R4(RSP). |
| opcodes.Offset(Reg::X86_64Core(16), -8); // R16(RIP). |
| // core registers. |
| for (int reg = 0; reg < 16; reg++) { |
| if (reg == 4) { |
| // Stack pointer. |
| } else if (reg < 12 && reg != 3 && reg != 5) { // except EBX and EBP. |
| opcodes.Undefined(Reg::X86_64Core(reg)); |
| } else { |
| opcodes.SameValue(Reg::X86_64Core(reg)); |
| } |
| } |
| // fp registers. |
| for (int reg = 0; reg < 16; reg++) { |
| if (reg < 12) { |
| opcodes.Undefined(Reg::X86_64Fp(reg)); |
| } else { |
| opcodes.SameValue(Reg::X86_64Fp(reg)); |
| } |
| } |
| auto return_reg = Reg::X86_64Core(16); // R16(RIP). |
| WriteCIE(is64bit, return_reg, opcodes, buffer); |
| return; |
| } |
| case InstructionSet::kNone: |
| break; |
| } |
| LOG(FATAL) << "Cannot write CIE frame for ISA " << isa; |
| UNREACHABLE(); |
| } |
| |
| template<typename ElfTypes> |
| void WriteCFISection(ElfBuilder<ElfTypes>* builder, |
| const ArrayRef<const MethodDebugInfo>& method_infos) { |
| // The methods can be written in any order. |
| // Let's therefore sort them in the lexicographical order of the opcodes. |
| // This has no effect on its own. However, if the final .debug_frame section is |
| // compressed it reduces the size since similar opcodes sequences are grouped. |
| std::vector<const MethodDebugInfo*> sorted_method_infos; |
| sorted_method_infos.reserve(method_infos.size()); |
| for (size_t i = 0; i < method_infos.size(); i++) { |
| if (!method_infos[i].cfi.empty() && !method_infos[i].deduped) { |
| sorted_method_infos.push_back(&method_infos[i]); |
| } |
| } |
| if (sorted_method_infos.empty()) { |
| return; |
| } |
| std::stable_sort( |
| sorted_method_infos.begin(), |
| sorted_method_infos.end(), |
| [](const MethodDebugInfo* lhs, const MethodDebugInfo* rhs) { |
| ArrayRef<const uint8_t> l = lhs->cfi; |
| ArrayRef<const uint8_t> r = rhs->cfi; |
| return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); |
| }); |
| |
| std::vector<uint32_t> binary_search_table; |
| bool binary_search_table_is_valid = kWriteDebugFrameHdr; |
| if (binary_search_table_is_valid) { |
| binary_search_table.reserve(2 * sorted_method_infos.size()); |
| } |
| |
| // Write .debug_frame section. |
| auto* cfi_section = builder->GetDebugFrame(); |
| { |
| cfi_section->Start(); |
| const bool is64bit = Is64BitInstructionSet(builder->GetIsa()); |
| std::vector<uint8_t> buffer; // Small temporary buffer. |
| WriteCIE(builder->GetIsa(), &buffer); |
| cfi_section->WriteFully(buffer.data(), buffer.size()); |
| buffer.clear(); |
| for (const MethodDebugInfo* mi : sorted_method_infos) { |
| DCHECK(!mi->deduped); |
| DCHECK(!mi->cfi.empty()); |
| uint64_t code_address = mi->code_address + |
| (mi->is_code_address_text_relative ? builder->GetText()->GetAddress() : 0); |
| if (kWriteDebugFrameHdr) { |
| // Defensively check that the code address really fits. |
| DCHECK_LE(code_address, std::numeric_limits<uint32_t>::max()); |
| binary_search_table_is_valid &= code_address <= std::numeric_limits<uint32_t>::max(); |
| binary_search_table.push_back(static_cast<uint32_t>(code_address)); |
| binary_search_table.push_back(cfi_section->GetPosition()); |
| } |
| dwarf::WriteFDE(is64bit, |
| /* cie_pointer= */ 0, |
| code_address, |
| mi->code_size, |
| mi->cfi, |
| &buffer); |
| cfi_section->WriteFully(buffer.data(), buffer.size()); |
| buffer.clear(); |
| } |
| cfi_section->End(); |
| } |
| |
| if (binary_search_table_is_valid && method_infos.size() >= kMinDebugFrameHdrEntries) { |
| std::sort(binary_search_table.begin(), binary_search_table.end()); |
| |
| // Custom Android section. It is very similar to the official .eh_frame_hdr format. |
| std::vector<uint8_t> header_buffer; |
| dwarf::Writer<> header(&header_buffer); |
| header.PushUint8(1); // Version. |
| header.PushUint8(dwarf::DW_EH_PE_omit); // Encoding of .eh_frame pointer - none. |
| header.PushUint8(dwarf::DW_EH_PE_udata4); // Encoding of binary search table size. |
| header.PushUint8(dwarf::DW_EH_PE_udata4); // Encoding of binary search table data. |
| header.PushUint32(dchecked_integral_cast<uint32_t>(binary_search_table.size()/2)); |
| |
| auto* header_section = builder->GetDebugFrameHdr(); |
| header_section->Start(); |
| header_section->WriteFully(header_buffer.data(), header_buffer.size()); |
| header_section->WriteFully(binary_search_table.data(), |
| binary_search_table.size() * sizeof(binary_search_table[0])); |
| header_section->End(); |
| } |
| } |
| |
| } // namespace debug |
| } // namespace art |
| |
| #endif // ART_COMPILER_DEBUG_ELF_DEBUG_FRAME_WRITER_H_ |
| |