/* Copyright (C) 2021-2024 Free Software Foundation, Inc. Contributed by Oracle. This file is part of GNU Binutils. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program 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 for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #include #include #include #include "disassemble.h" #include "dis-asm.h" #include "demangle.h" #include "dbe_types.h" #include "DbeSession.h" #include "Elf.h" #include "Disasm.h" #include "Stabs.h" #include "i18n.h" #include "util.h" #include "StringBuilder.h" #include "Function.h" struct DisContext { bool is_Intel; Stabs *stabs; uint64_t pc; // first_pc <= pc < last_pc uint64_t first_pc; uint64_t last_pc; uint64_t f_offset; // file offset for first_pc int codeptr[4]; // longest instruction length may not be > 16 Data_window *elf; }; static const int MAX_DISASM_STR = 2048; Disasm::Disasm (char *fname) { dwin = NULL; dis_str = NULL; need_swap_endian = false; my_stabs = Stabs::NewStabs (fname, fname); if (my_stabs == NULL) return; stabs = my_stabs; platform = stabs->get_platform (); disasm_open (); } Disasm::Disasm (Platform_t _platform, Stabs *_stabs) { dwin = NULL; dis_str = NULL; need_swap_endian = false; stabs = _stabs; platform = _platform; my_stabs = NULL; disasm_open (); } static int fprintf_func (void *arg, const char *fmt, ...) { char buf[512]; va_list vp; va_start (vp, fmt); int cnt = vsnprintf (buf, sizeof (buf), fmt, vp); va_end (vp); Disasm *dis = (Disasm *) arg; dis->dis_str->append (buf); return cnt; } static int fprintf_styled_func (void *arg, enum disassembler_style st ATTRIBUTE_UNUSED, const char *fmt, ...) { char buf[512]; va_list vp; va_start (vp, fmt); int cnt = vsnprintf (buf, sizeof (buf), fmt, vp); va_end (vp); Disasm *dis = (Disasm *) arg; dis->dis_str->append (buf); return cnt; } /* Get LENGTH bytes from info's buffer, at target address memaddr. Transfer them to myaddr. */ static int read_memory_func (bfd_vma memaddr, bfd_byte *myaddr, unsigned int length, disassemble_info *info) { unsigned int opb = info->octets_per_byte; size_t end_addr_offset = length / opb; size_t max_addr_offset = info->buffer_length / opb; size_t octets = (memaddr - info->buffer_vma) * opb; if (memaddr < info->buffer_vma || memaddr - info->buffer_vma > max_addr_offset || memaddr - info->buffer_vma + end_addr_offset > max_addr_offset || (info->stop_vma && (memaddr >= info->stop_vma || memaddr + end_addr_offset > info->stop_vma))) return -1; memcpy (myaddr, info->buffer + octets, length); return 0; } static void print_address_func (bfd_vma addr, disassemble_info *info) { bfd_signed_vma off; unsigned long long ta; Disasm *dis; switch (info->insn_type) { case dis_branch: case dis_condbranch: off = (bfd_signed_vma) addr; dis = (Disasm *) info->stream; ta = dis->inst_addr + off; (*info->fprintf_func) (info->stream, ".%c0x%llx [ 0x%llx ]", off > 0 ? '+' : '-', (long long) (off > 0 ? off : -off), ta); return; case dis_jsr: off = (bfd_signed_vma) addr; dis = (Disasm *) info->stream; ta = dis->inst_addr + off; const char *nm = NULL; Function *f = dis->map_PC_to_func (ta); if (f) { if (dis->inst_addr >= f->img_offset && dis->inst_addr < f->img_offset + f->size) { // Same function (*info->fprintf_func) (info->stream, ".%c0x%llx [ 0x%llx ]", off > 0 ? '+' : '-', (long long) (off > 0 ? off : -off), ta); return; } if (f->flags & FUNC_FLAG_PLT) nm = dis->get_funcname_in_plt(ta); if (nm == NULL) nm = f->get_name (); } if (nm) (*info->fprintf_func) (info->stream, "%s [ 0x%llx, .%c0x%llx]", nm, ta, off > 0 ? '+' : '-', (long long) (off > 0 ? off : -off)); else (*info->fprintf_func) (info->stream, ".%c0x%llx [ 0x%llx ] // Unable to determine target symbol", off > 0 ? '+' : '-', (long long) (off > 0 ? off : -off), ta); return; } (*info->fprintf_func) (info->stream, "0x%llx", (long long) addr); } static asymbol * symbol_at_address_func (bfd_vma addr ATTRIBUTE_UNUSED, disassemble_info *info ATTRIBUTE_UNUSED) { return NULL; } static bfd_boolean symbol_is_valid (asymbol * sym ATTRIBUTE_UNUSED, disassemble_info *info ATTRIBUTE_UNUSED) { return TRUE; } static void memory_error_func (int status, bfd_vma addr, disassemble_info *info) { info->fprintf_func (info->stream, "Address 0x%llx is out of bounds.\n", (unsigned long long) addr); } void Disasm::disasm_open () { hex_visible = 1; snprintf (addr_fmt, sizeof (addr_fmt), NTXT ("%s"), NTXT ("%8llx: ")); if (dis_str == NULL) dis_str = new StringBuilder; switch (platform) { case Aarch64: case Intel: case Amd64: need_swap_endian = (DbeSession::platform == Sparc); break; case RISCV: case Sparcv8plus: case Sparcv9: case Sparc: default: need_swap_endian = (DbeSession::platform != Sparc); break; } memset (&dis_info, 0, sizeof (dis_info)); dis_info.flavour = bfd_target_unknown_flavour; dis_info.endian = BFD_ENDIAN_UNKNOWN; dis_info.endian_code = dis_info.endian; dis_info.octets_per_byte = 1; dis_info.disassembler_needs_relocs = FALSE; dis_info.fprintf_func = fprintf_func; dis_info.fprintf_styled_func = fprintf_styled_func; dis_info.stream = this; dis_info.disassembler_options = NULL; dis_info.read_memory_func = read_memory_func; dis_info.memory_error_func = memory_error_func; dis_info.print_address_func = print_address_func; dis_info.symbol_at_address_func = symbol_at_address_func; dis_info.symbol_is_valid = symbol_is_valid; dis_info.display_endian = BFD_ENDIAN_UNKNOWN; dis_info.symtab = NULL; dis_info.symtab_size = 0; dis_info.buffer_vma = 0; switch (platform) { case Aarch64: dis_info.arch = bfd_arch_aarch64; dis_info.mach = bfd_mach_aarch64; break; case Intel: case Amd64: dis_info.arch = bfd_arch_i386; dis_info.mach = bfd_mach_x86_64; break; case RISCV: case Sparcv8plus: case Sparcv9: case Sparc: default: dis_info.arch = bfd_arch_unknown; dis_info.endian = BFD_ENDIAN_UNKNOWN; break; } dis_info.display_endian = dis_info.endian = BFD_ENDIAN_BIG; dis_info.display_endian = dis_info.endian = BFD_ENDIAN_LITTLE; dis_info.display_endian = dis_info.endian = BFD_ENDIAN_UNKNOWN; disassemble_init_for_target (&dis_info); } Disasm::~Disasm () { delete my_stabs; delete dwin; delete dis_str; } void Disasm::set_img_name (char *img_fname) { if (stabs == NULL && img_fname && dwin == NULL) { dwin = new Data_window (img_fname); if (dwin->not_opened ()) { delete dwin; dwin = NULL; return; } dwin->need_swap_endian = need_swap_endian; } } void Disasm::remove_disasm_hndl (void *hndl) { DisContext *ctx = (DisContext *) hndl; delete ctx; } void Disasm::set_addr_end (uint64_t end_address) { char buf[32]; int len = snprintf (buf, sizeof (buf), "%llx", (long long) end_address); snprintf (addr_fmt, sizeof (addr_fmt), "%%%dllx: ", len < 8 ? 8 : len); } char * Disasm::get_disasm (uint64_t inst_address, uint64_t end_address, uint64_t start_address, uint64_t f_offset, int64_t &inst_size) { inst_size = 0; if (inst_address >= end_address) return NULL; Data_window *dw = stabs ? stabs->openElf (false) : dwin; if (dw == NULL) return NULL; unsigned char buffer[MAX_DISASM_STR]; dis_info.buffer = buffer; dis_info.buffer_length = end_address - inst_address; if (dis_info.buffer_length > sizeof (buffer)) dis_info.buffer_length = sizeof (buffer); dw->get_data (f_offset + (inst_address - start_address), dis_info.buffer_length, dis_info.buffer); dis_str->setLength (0); bfd abfd; disassembler_ftype disassemble = disassembler (dis_info.arch, dis_info.endian, dis_info.mach, &abfd); if (disassemble == NULL) { printf ("ERROR: unsupported disassemble\n"); return NULL; } inst_addr = inst_address; inst_size = disassemble (0, &dis_info); if (inst_size <= 0) { inst_size = 0; return NULL; } StringBuilder sb; sb.appendf (addr_fmt, inst_address); // Write address // Write hex bytes of instruction if (hex_visible) { char bytes[64]; *bytes = '\0'; for (int i = 0; i < inst_size; i++) { unsigned int hex_value = buffer[i] & 0xff; snprintf (bytes + 3 * i, sizeof (bytes) - 3 * i, "%02x ", hex_value); } const char *fmt = "%s "; if (platform == Intel) fmt = "%-21s "; // 21 = 3 * 7 - maximum instruction length on Intel sb.appendf (fmt, bytes); } sb.append (dis_str); return sb.toString (); } Function * Disasm::map_PC_to_func (uint64_t pc) { uint64_t low_pc = 0; if (stabs) return stabs->map_PC_to_func (pc, low_pc, NULL); return NULL; } const char * Disasm::get_funcname_in_plt (uint64_t pc) { if (stabs) { Elf *elf = stabs->openElf (true); if (elf) return elf->get_funcname_in_plt (pc); } return NULL; }