| /* 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" |
| #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; |
| 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) |
| { |
| 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 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; |
| } |
| |
| 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; |
| } |