| /* Copyright (C) 2021-2023 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 <stdio.h> | 
 | #include <string.h> | 
 | #include <sys/param.h> | 
 |  | 
 | #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" | 
 |  | 
 | 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; | 
 | static const int MAX_INSTR_SIZE     = 8; | 
 |  | 
 | 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) | 
 | { | 
 |   (*info->fprintf_func) (info->stream, "0x%llx", (unsigned 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 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 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; | 
 | } | 
 |  | 
 | #if 0 | 
 | int | 
 | Disasm::get_instr_size (uint64_t vaddr, void *hndl) | 
 | { | 
 |   DisContext *ctx = (DisContext *) hndl; | 
 |   if (ctx == NULL || vaddr < ctx->first_pc || vaddr >= ctx->last_pc) | 
 |     return -1; | 
 |   ctx->pc = vaddr; | 
 |   size_t sz = ctx->is_Intel ? sizeof (ctx->codeptr) : 4; | 
 |   if (sz > ctx->last_pc - vaddr) | 
 |     sz = (size_t) (ctx->last_pc - vaddr); | 
 |   if (ctx->elf->get_data (ctx->f_offset + (vaddr - ctx->first_pc), | 
 | 			  sz, ctx->codeptr) == NULL) | 
 |     return -1; | 
 |  | 
 |   char buf[MAX_DISASM_STR]; | 
 |   *buf = 0; | 
 |   uint64_t inst_vaddr = vaddr; | 
 | #if MEZ_NEED_TO_FIX | 
 |   size_t instrs_cnt = 0; | 
 |   disasm_err_code_t status = disasm (handle, &inst_vaddr, ctx->last_pc, 1, | 
 | 				     ctx, buf, sizeof (buf), &instrs_cnt); | 
 |   if (instrs_cnt != 1 || status != disasm_err_ok) | 
 |     return -1; | 
 | #endif | 
 |   return (int) (inst_vaddr - vaddr); | 
 | } | 
 | #endif | 
 |  | 
 | 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_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); | 
 | #if MEZ_NEED_TO_FIX | 
 |   // Write instruction | 
 |   if (ctx.is_Intel)  // longest instruction length for Intel is 7 | 
 |     sb.appendf (NTXT ("%-7s %s"), parts_array[1], parts_array[2]); | 
 |   else  // longest instruction length for SPARC is 11 | 
 |     sb.appendf (NTXT ("%-11s %s"), parts_array[1], parts_array[2]); | 
 |   if (strcmp (parts_array[1], NTXT ("call")) == 0) | 
 |     { | 
 |       if (strncmp (parts_array[2], NTXT ("0x"), 2) == 0) | 
 | 	sb.append (GTXT ("\t! (Unable to determine target symbol)")); | 
 |     } | 
 | #endif | 
 |   return sb.toString (); | 
 | } | 
 |  | 
 | #if MEZ_NEED_TO_FIX | 
 | void * | 
 | Disasm::get_inst_ptr (disasm_handle_t, uint64_t vaddr, void *pass_through) | 
 | { | 
 |   // Actually it fetches only one instruction at a time for sparc, | 
 |   // and one byte at a time for intel. | 
 |   DisContext *ctx = (DisContext*) pass_through; | 
 |   size_t sz = ctx->is_Intel ? 1 : 4; | 
 |   if (vaddr + sz > ctx->last_pc) | 
 |     { | 
 |       ctx->codeptr[0] = -1; | 
 |       return ctx->codeptr; | 
 |     } | 
 |   if (ctx->elf->get_data (ctx->f_offset + (vaddr - ctx->first_pc), sz, ctx->codeptr) == NULL) | 
 |     { | 
 |       ctx->codeptr[0] = -1; | 
 |       return ctx->codeptr; | 
 |     } | 
 |   if (ctx->elf->need_swap_endian && !ctx->is_Intel) | 
 |     ctx->codeptr[0] = ctx->elf->decode (ctx->codeptr[0]); | 
 |   return ctx->codeptr; | 
 | } | 
 |  | 
 | // get a symbol name for an address | 
 | disasm_err_code_t | 
 | Disasm::get_sym_name (disasm_handle_t,          // an open disassembler handle | 
 | 		      uint64_t target_address,  // the target virtual address | 
 | 		      uint64_t inst_address,  // virtual address of instruction | 
 | 					      // being disassembled | 
 | 		      int use_relocation, // flag to use relocation information | 
 | 		      char *buffer,             // places the symbol here | 
 | 		      size_t buffer_size,       // limit on symbol length | 
 | 		      int *,                    // sys/elf_{SPARC.386}.h | 
 | 		      uint64_t *offset,       // from the symbol to the address | 
 | 		      void *pass_through)       // disassembler context | 
 | { | 
 |   char buf[MAXPATHLEN]; | 
 |   if (!use_relocation) | 
 |     return disasm_err_symbol; | 
 |  | 
 |   DisContext *ctxp = (DisContext*) pass_through; | 
 |   char *name = NULL; | 
 |   if (ctxp->stabs) | 
 |     { | 
 |       uint64_t addr = ctxp->f_offset + (inst_address - ctxp->first_pc); | 
 |       name = ctxp->stabs->sym_name (target_address, addr, use_relocation); | 
 |     } | 
 |   if (name == NULL) | 
 |     return disasm_err_symbol; | 
 |  | 
 |   char *s = NULL; | 
 |   if (*name == '_') | 
 |     s = cplus_demangle (name, DMGL_PARAMS); | 
 |   if (s) | 
 |     { | 
 |       snprintf (buffer, buffer_size, NTXT ("%s"), s); | 
 |       free (s); | 
 |     } | 
 |   else | 
 |     snprintf (buffer, buffer_size, NTXT ("%s"), name); | 
 |  | 
 |   *offset = 0; | 
 |   return disasm_err_ok; | 
 | } | 
 | #endif |