# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2018 Jason King # Copyright 2024 Oxide Computer Company # PROG= dis OBJS= dis_target.o dis_main.o dis_util.o dis_list.o $(HEXDUMP_OBJS) include ../Makefile.cmd include $(SRC)/common/hexdump/Makefile.com LDLIBS += -ldisasm -luutil -lelf -ldemangle-sys CERRWARN += $(CNOWARN_UNINIT) .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) install: all $(ROOTPROG) $(ROOTCCSBINLINK) clean: $(RM) $(OBJS) $(PROG) include $(SRC)/common/hexdump/Makefile.targ include ../Makefile.targ include ../Makefile.ctf /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include "dis_target.h" #include "dis_list.h" #include "dis_util.h" /* * List support functions. * * Support routines for managing lists of sections and functions. We first * process the command line arguments into lists of strings. For each target, * we resolve these strings against the set of available sections and/or * functions to arrive at the set of objects to disassemble. * * We export two types of lists, namelists and resolvelists. The first is used * to record names given as command line options. The latter is used to * maintain the data objects specific to a given target. */ typedef struct unresolved_name { const char *un_name; /* name of function or object */ int un_value; /* user-supplied data */ int un_mark; /* internal counter */ uu_list_node_t un_node; /* uulist node */ } unresolved_name_t; typedef struct resolved_name { void *rn_data; /* section or function data */ int rn_value; /* user-supplied data */ uu_list_node_t rn_node; /* uulist node */ } resolved_name_t; static uu_list_pool_t *unresolved_pool; static uu_list_pool_t *resolved_pool; static int current_mark = 0; static void initialize_pools(void) { unresolved_pool = uu_list_pool_create( "unresolved_pool", sizeof (unresolved_name_t), offsetof(unresolved_name_t, un_node), NULL, 0); resolved_pool = uu_list_pool_create( "resolved_pool", sizeof (resolved_name_t), offsetof(resolved_name_t, rn_node), NULL, 0); if (unresolved_pool == NULL || resolved_pool == NULL) die("out of memory"); } /* * Returns an empty list of unresolved names. */ dis_namelist_t * dis_namelist_create(void) { uu_list_t *listp; /* * If this is the first request to create a list, initialize the list * pools. */ if (unresolved_pool == NULL) initialize_pools(); if ((listp = uu_list_create(unresolved_pool, NULL, 0)) == NULL) die("out of memory"); return (listp); } /* * Adds the given name to the unresolved list. 'value' is an arbitrary value * which is preserved for this entry, even when resolved against a target. This * allows the caller to associate similar behavior (such as the difference * between -d, -D, and -s) without having to create multiple lists. */ void dis_namelist_add(dis_namelist_t *list, const char *name, int value) { unresolved_name_t *node; node = safe_malloc(sizeof (unresolved_name_t)); node->un_name = name; node->un_value = value; node->un_mark = 0; (void) uu_list_insert_before(list, NULL, node); } /* * Internal callback structure used */ typedef struct cb_data { int cb_mark; uu_list_t *cb_source; uu_list_t *cb_resolved; } cb_data_t; /* * For each section, walk the list of unresolved names and resolve those that * correspond to real functions. We mark functions as we see them, and re-walk * the list a second time to warn about functions we didn't find. * * This is an O(n * m) algorithm, but we typically search for only a single * function. */ /* ARGSUSED */ static void walk_sections(dis_tgt_t *tgt, dis_scn_t *scn, void *data) { cb_data_t *cb = data; unresolved_name_t *unp; uu_list_walk_t *walk; if ((walk = uu_list_walk_start(cb->cb_source, UU_DEFAULT)) == NULL) die("out of memory"); while ((unp = uu_list_walk_next(walk)) != NULL) { if (strcmp(unp->un_name, dis_section_name(scn)) == 0) { resolved_name_t *resolved; /* * Mark the current node as seen */ unp->un_mark = cb->cb_mark; /* * Add the data to the resolved list */ resolved = safe_malloc(sizeof (resolved_name_t)); resolved->rn_data = dis_section_copy(scn); resolved->rn_value = unp->un_value; (void) uu_list_insert_before(cb->cb_resolved, NULL, resolved); } } uu_list_walk_end(walk); } /* * Take a list of unresolved names and create a resolved list of sections. We * rely on walk_sections() to do the dirty work. After resolving the sections, * we check for any unmarked names and warn the user about missing sections. */ dis_scnlist_t * dis_namelist_resolve_sections(dis_namelist_t *namelist, dis_tgt_t *tgt) { uu_list_t *listp; cb_data_t cb; unresolved_name_t *unp; uu_list_walk_t *walk; /* * Walk all sections in the target, calling walk_sections() for each * one. */ if ((listp = uu_list_create(resolved_pool, NULL, UU_DEFAULT)) == NULL) die("out of memory"); cb.cb_mark = ++current_mark; cb.cb_source = namelist; cb.cb_resolved = listp; dis_tgt_section_iter(tgt, walk_sections, &cb); /* * Walk all elements of the unresolved list, and report any that we * didn't mark in the process. */ if ((walk = uu_list_walk_start(namelist, UU_DEFAULT)) == NULL) die("out of memory"); while ((unp = uu_list_walk_next(walk)) != NULL) { if (unp->un_mark != current_mark) warn("failed to find section '%s' in '%s'", unp->un_name, dis_tgt_name(tgt)); } uu_list_walk_end(walk); return (listp); } /* * Similar to walk_sections(), but for functions. */ /* ARGSUSED */ static void walk_functions(dis_tgt_t *tgt, dis_func_t *func, void *data) { cb_data_t *cb = data; unresolved_name_t *unp; uu_list_walk_t *walk; if ((walk = uu_list_walk_start(cb->cb_source, UU_DEFAULT)) == NULL) die("out of memory"); while ((unp = uu_list_walk_next(walk)) != NULL) { if (strcmp(unp->un_name, dis_function_name(func)) == 0) { resolved_name_t *resolved; unp->un_mark = cb->cb_mark; resolved = safe_malloc(sizeof (resolved_name_t)); resolved->rn_data = dis_function_copy(func); resolved->rn_value = unp->un_value; (void) uu_list_insert_before(cb->cb_resolved, NULL, resolved); } } uu_list_walk_end(walk); } /* * Take a list of unresolved names and create a resolved list of functions. We * rely on walk_functions() to do the dirty work. After resolving the * functions, * we check for any unmarked names and warn the user about missing * functions. */ dis_funclist_t * dis_namelist_resolve_functions(dis_namelist_t *namelist, dis_tgt_t *tgt) { uu_list_t *listp; uu_list_walk_t *walk; unresolved_name_t *unp; cb_data_t cb; if ((listp = uu_list_create(resolved_pool, NULL, UU_DEFAULT)) == NULL) die("out of memory"); cb.cb_mark = ++current_mark; cb.cb_source = namelist; cb.cb_resolved = listp; dis_tgt_function_iter(tgt, walk_functions, &cb); /* * Walk unresolved list and report any missing functions. */ if ((walk = uu_list_walk_start(namelist, UU_DEFAULT)) == NULL) die("out of memory"); while ((unp = uu_list_walk_next(walk)) != NULL) { if (unp->un_mark != current_mark) warn("failed to find function '%s' in '%s'", unp->un_name, dis_tgt_name(tgt)); } uu_list_walk_end(walk); return (listp); } /* * Returns true if the given list is empty. */ int dis_namelist_empty(dis_namelist_t *list) { return (uu_list_numnodes(list) == 0); } static void free_list(uu_list_t *list) { uu_list_walk_t *walk; void *data; if ((walk = uu_list_walk_start(list, UU_WALK_ROBUST)) == NULL) die("out of memory"); while ((data = uu_list_walk_next(walk)) != NULL) { uu_list_remove(list, data); free(data); } uu_list_walk_end(walk); uu_list_destroy(list); } /* * Destroy a list of sections. First, walk the list and free the associated * section data. Pass the list onto to free_list() to clean up the rest of the * list. */ void dis_scnlist_destroy(dis_scnlist_t *list) { uu_list_walk_t *walk; resolved_name_t *data; if ((walk = uu_list_walk_start(list, UU_DEFAULT)) == NULL) die("out of memory"); while ((data = uu_list_walk_next(walk)) != NULL) dis_section_free(data->rn_data); uu_list_walk_end(walk); free_list(list); } /* * Destroy a list of functions. First, walk the list and free the associated * function data. Pass the list onto to free_list() to clean up the rest of the * list. */ void dis_funclist_destroy(dis_funclist_t *list) { uu_list_walk_t *walk; resolved_name_t *data; if ((walk = uu_list_walk_start(list, UU_DEFAULT)) == NULL) die("out of memory"); while ((data = uu_list_walk_next(walk)) != NULL) dis_function_free(data->rn_data); uu_list_walk_end(walk); free_list(list); } /* * Destroy a lis tof unresolved names. */ void dis_namelist_destroy(dis_namelist_t *list) { free_list(list); } /* * Iterate over a resolved list of sections. */ void dis_scnlist_iter(uu_list_t *list, void (*func)(dis_scn_t *, int, void *), void *arg) { uu_list_walk_t *walk; resolved_name_t *data; if ((walk = uu_list_walk_start(list, UU_DEFAULT)) == NULL) die("out of memory"); while ((data = uu_list_walk_next(walk)) != NULL) func(data->rn_data, data->rn_value, arg); uu_list_walk_end(walk); } /* * Iterate over a resolved list of functions. */ void dis_funclist_iter(uu_list_t *list, void (*func)(dis_func_t *, int, void *), void *arg) { uu_list_walk_t *walk; resolved_name_t *data; if ((walk = uu_list_walk_start(list, UU_DEFAULT)) == NULL) die("out of memory"); while ((data = uu_list_walk_next(walk)) != NULL) func(data->rn_data, data->rn_value, arg); uu_list_walk_end(walk); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _DIS_LIST_H #define _DIS_LIST_H #include /* #include "dis_target.h" */ #ifdef __cplusplus extern "C" { #endif typedef uu_list_t dis_namelist_t; typedef uu_list_t dis_scnlist_t; typedef uu_list_t dis_funclist_t; dis_namelist_t *dis_namelist_create(void); void dis_namelist_add(dis_namelist_t *, const char *, int); dis_funclist_t *dis_namelist_resolve_functions(dis_namelist_t *, dis_tgt_t *); dis_scnlist_t *dis_namelist_resolve_sections(dis_namelist_t *, dis_tgt_t *); void dis_scnlist_iter(dis_scnlist_t *, void (*)(dis_scn_t *, int, void *), void *); void dis_funclist_iter(dis_funclist_t *, void (*)(dis_func_t *, int, void *), void *); int dis_namelist_empty(dis_namelist_t *); void dis_scnlist_destroy(dis_scnlist_t *); void dis_funclist_destroy(dis_funclist_t *); void dis_namelist_destroy(dis_namelist_t *); #ifdef __cplusplus } #endif #endif /* _DIS_LIST_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2011 Jason King. All rights reserved. * Copyright 2012 Joshua M. Clulow * Copyright 2015 Josef 'Jeff' Sipek * Copyright 2018, Joyent, Inc. * Copyright 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include "dis_target.h" #include "dis_util.h" #include "dis_list.h" int g_demangle; /* Demangle C++ names */ int g_quiet; /* Quiet mode */ int g_numeric; /* Numeric mode */ int g_flags; /* libdisasm language flags */ int g_doall; /* true if no functions or sections were given */ dis_namelist_t *g_funclist; /* list of functions to disassemble, if any */ dis_namelist_t *g_seclist; /* list of sections to disassemble, if any */ /* * Section options for -d, -D, and -s */ #define DIS_DATA_RELATIVE 1 #define DIS_DATA_ABSOLUTE 2 #define DIS_TEXT 3 /* * libdisasm callback data. Keeps track of current data (function or section) * and offset within that data. */ typedef struct dis_buffer { dis_tgt_t *db_tgt; /* current dis target */ void *db_data; /* function or section data */ uint64_t db_addr; /* address of function start */ size_t db_size; /* size of data */ uint64_t db_nextaddr; /* next address to be read */ } dis_buffer_t; #define MINSYMWIDTH 22 /* Minimum width of symbol portion of line */ /* * Given a symbol+offset as returned by dis_tgt_lookup(), print an appropriately * formatted symbol, based on the offset and current setttings. */ void getsymname(uint64_t addr, const char *symbol, off_t offset, char *buf, size_t buflen) { if (symbol == NULL || g_numeric) { if (g_flags & DIS_OCTAL) (void) snprintf(buf, buflen, "0%llo", addr); else (void) snprintf(buf, buflen, "0x%llx", addr); } else { if (g_demangle) symbol = dis_demangle(symbol); if (offset == 0) (void) snprintf(buf, buflen, "%s", symbol); else if (g_flags & DIS_OCTAL) (void) snprintf(buf, buflen, "%s+0%o", symbol, offset); else (void) snprintf(buf, buflen, "%s+0x%x", symbol, offset); } } /* * Determine if we are on an architecture with fixed-size instructions, * and if so, what size they are. */ static int insn_size(dis_handle_t *dhp) { int min = dis_min_instrlen(dhp); int max = dis_max_instrlen(dhp); if (min == max) return (min); return (0); } /* * The main disassembly routine. Given a fixed-sized buffer and starting * address, disassemble the data using the supplied target and libdisasm handle. */ void dis_data(dis_tgt_t *tgt, dis_handle_t *dhp, uint64_t addr, void *data, size_t datalen) { dis_buffer_t db = { 0 }; char buf[BUFSIZE]; char symbuf[BUFSIZE]; const char *symbol; const char *last_symbol; off_t symoffset; int i; int bytesperline; size_t symsize; int isfunc; size_t symwidth = 0; int ret; int insz = insn_size(dhp); db.db_tgt = tgt; db.db_data = data; db.db_addr = addr; db.db_size = datalen; dis_set_data(dhp, &db); if ((bytesperline = dis_max_instrlen(dhp)) > 6) bytesperline = 6; symbol = NULL; while (addr < db.db_addr + db.db_size) { ret = dis_disassemble(dhp, addr, buf, BUFSIZE); if (ret != 0 && insz > 0) { /* * Since we know instructions are fixed size, we * always know the address of the next instruction */ (void) snprintf(buf, sizeof (buf), "*** invalid opcode ***"); db.db_nextaddr = addr + insz; } else if (ret != 0) { off_t next; (void) snprintf(buf, sizeof (buf), "*** invalid opcode ***"); /* * On architectures with variable sized instructions * we have no way to figure out where the next * instruction starts if we encounter an invalid * instruction. Instead we print the rest of the * instruction stream as hex until we reach the * next valid symbol in the section. */ if ((next = dis_tgt_next_symbol(tgt, addr)) == 0) { db.db_nextaddr = db.db_addr + db.db_size; } else { if (next > db.db_size) db.db_nextaddr = db.db_addr + db.db_size; else db.db_nextaddr = addr + next; } } /* * Print out the line as: * * address: bytes text * * If there are more than 6 bytes in any given instruction, * spread the bytes across two lines. We try to get symbolic * information for the address, but if that fails we print out * the numeric address instead. * * We try to keep the address portion of the text aligned at * MINSYMWIDTH characters. If we are disassembling a function * with a long name, this can be annoying. So we pick a width * based on the maximum width that the current symbol can be. * This at least produces text aligned within each function. */ last_symbol = symbol; symbol = dis_tgt_lookup(tgt, addr, &symoffset, 1, &symsize, &isfunc); if (symbol == NULL) { symbol = dis_find_section(tgt, addr, &symoffset); symsize = symoffset; } if (symbol != last_symbol) getsymname(addr, symbol, symsize, symbuf, sizeof (symbuf)); symwidth = MAX(symwidth, strlen(symbuf)); getsymname(addr, symbol, symoffset, symbuf, sizeof (symbuf)); /* * If we've crossed a new function boundary, print out the * function name on a blank line. */ if (!g_quiet && symoffset == 0 && symbol != NULL && isfunc) (void) printf("%s()\n", symbol); (void) printf(" %s:%*s ", symbuf, symwidth - strlen(symbuf), ""); /* print bytes */ for (i = 0; i < MIN(bytesperline, (db.db_nextaddr - addr)); i++) { int byte = *((uchar_t *)data + (addr - db.db_addr) + i); if (g_flags & DIS_OCTAL) (void) printf("%03o ", byte); else (void) printf("%02x ", byte); } /* trailing spaces for missing bytes */ for (; i < bytesperline; i++) { if (g_flags & DIS_OCTAL) (void) printf(" "); else (void) printf(" "); } /* contents of disassembly */ (void) printf(" %s", buf); /* excess bytes that spill over onto subsequent lines */ for (; i < db.db_nextaddr - addr; i++) { int byte = *((uchar_t *)data + (addr - db.db_addr) + i); if (i % bytesperline == 0) (void) printf("\n %*s ", symwidth, ""); if (g_flags & DIS_OCTAL) (void) printf("%03o ", byte); else (void) printf("%02x ", byte); } (void) printf("\n"); addr = db.db_nextaddr; } } /* * libdisasm wrapper around symbol lookup. Invoke the target-specific lookup * function, and convert the result using getsymname(). */ int do_lookup(void *data, uint64_t addr, char *buf, size_t buflen, uint64_t *start, size_t *symlen) { dis_buffer_t *db = data; const char *symbol; off_t offset; size_t size; /* * If NULL symbol is returned, getsymname takes care of * printing appropriate address in buf instead of symbol. */ symbol = dis_tgt_lookup(db->db_tgt, addr, &offset, 0, &size, NULL); if (buf != NULL) getsymname(addr, symbol, offset, buf, buflen); if (start != NULL) *start = addr - offset; if (symlen != NULL) *symlen = size; if (symbol == NULL) return (-1); return (0); } /* * libdisasm wrapper around target reading. libdisasm will always read data * in order, so update our current offset within the buffer appropriately. * We only support reading from within the current object; libdisasm should * never ask us to do otherwise. */ int do_read(void *data, uint64_t addr, void *buf, size_t len) { dis_buffer_t *db = data; size_t offset; if (addr < db->db_addr || addr >= db->db_addr + db->db_size) return (-1); offset = addr - db->db_addr; len = MIN(len, db->db_size - offset); (void) memcpy(buf, (char *)db->db_data + offset, len); db->db_nextaddr = addr + len; return (len); } /* * Routine to dump raw data in a human-readable format. Used by the -d and -D * options. */ void dump_data(uint64_t addr, void *data, size_t datalen) { hexdump_t h; hexdump_init(&h); /* Print out data in two-byte chunks. */ hexdump_set_grouping(&h, 2); hexdump_set_addr(&h, addr); /* * Determine if the address given to us fits in 32-bit range, in which * case use a 4-byte width. */ if (((addr + datalen) & 0xffffffff00000000ULL) == 0ULL) hexdump_set_addrwidth(&h, 8); else hexdump_set_addrwidth(&h, 16); (void) hexdump_fileh(&h, data, datalen, HDF_DEFAULT | HDF_ALIGN, stdout); hexdump_fini(&h); } /* * Disassemble a section implicitly specified as part of a file. This function * is called for all sections when no other flags are specified. We ignore any * data sections, and print out only those sections containing text. */ void dis_text_section(dis_tgt_t *tgt, dis_scn_t *scn, void *data) { dis_handle_t *dhp = data; /* ignore data sections */ if (!dis_section_istext(scn)) return; if (!g_quiet) (void) printf("\nsection %s\n", dis_section_name(scn)); dis_data(tgt, dhp, dis_section_addr(scn), dis_section_data(scn), dis_section_size(scn)); } /* * Structure passed to dis_named_{section,function} which keeps track of both * the target and the libdisasm handle. */ typedef struct callback_arg { dis_tgt_t *ca_tgt; dis_handle_t *ca_handle; } callback_arg_t; /* * Disassemble a section explicitly named with -s, -d, or -D. The 'type' * argument contains the type of argument given. Pass the data onto the * appropriate helper routine. */ void dis_named_section(dis_scn_t *scn, int type, void *data) { callback_arg_t *ca = data; if (!g_quiet) (void) printf("\nsection %s\n", dis_section_name(scn)); switch (type) { case DIS_DATA_RELATIVE: dump_data(0, dis_section_data(scn), dis_section_size(scn)); break; case DIS_DATA_ABSOLUTE: dump_data(dis_section_addr(scn), dis_section_data(scn), dis_section_size(scn)); break; case DIS_TEXT: dis_data(ca->ca_tgt, ca->ca_handle, dis_section_addr(scn), dis_section_data(scn), dis_section_size(scn)); break; } } /* * Disassemble a function explicitly specified with '-F'. The 'type' argument * is unused. */ /* ARGSUSED */ void dis_named_function(dis_func_t *func, int type, void *data) { callback_arg_t *ca = data; dis_data(ca->ca_tgt, ca->ca_handle, dis_function_addr(func), dis_function_data(func), dis_function_size(func)); } /* * Disassemble a complete file. First, we determine the type of the file based * on the ELF machine type, and instantiate a version of the disassembler * appropriate for the file. We then resolve any named sections or functions * against the file, and iterate over the results (or all sections if no flags * were specified). */ void dis_file(const char *filename) { dis_tgt_t *tgt, *current; dis_scnlist_t *sections; dis_funclist_t *functions; dis_handle_t *dhp; GElf_Ehdr ehdr; /* * First, initialize the target */ if ((tgt = dis_tgt_create(filename)) == NULL) return; if (!g_quiet) (void) printf("disassembly for %s\n\n", filename); /* * A given file may contain multiple targets (if it is an archive, for * example). We iterate over all possible targets if this is the case. */ for (current = tgt; current != NULL; current = dis_tgt_next(current)) { dis_tgt_ehdr(current, &ehdr); /* * Eventually, this should probably live within libdisasm, and * we should be able to disassemble targets from different * architectures. For now, we only support objects as the * native machine type. */ switch (ehdr.e_machine) { case EM_SPARC: if (ehdr.e_ident[EI_CLASS] != ELFCLASS32 || ehdr.e_ident[EI_DATA] != ELFDATA2MSB) { warn("invalid E_IDENT field for SPARC object"); return; } g_flags |= DIS_SPARC_V8; break; case EM_SPARC32PLUS: { uint64_t flags = ehdr.e_flags & EF_SPARC_32PLUS_MASK; if (ehdr.e_ident[EI_CLASS] != ELFCLASS32 || ehdr.e_ident[EI_DATA] != ELFDATA2MSB) { warn("invalid E_IDENT field for SPARC object"); return; } if (flags != 0 && (flags & (EF_SPARC_32PLUS | EF_SPARC_SUN_US1 | EF_SPARC_SUN_US3)) != EF_SPARC_32PLUS) g_flags |= DIS_SPARC_V9 | DIS_SPARC_V9_SGI; else g_flags |= DIS_SPARC_V9; break; } case EM_SPARCV9: if (ehdr.e_ident[EI_CLASS] != ELFCLASS64 || ehdr.e_ident[EI_DATA] != ELFDATA2MSB) { warn("invalid E_IDENT field for SPARC object"); return; } g_flags |= DIS_SPARC_V9 | DIS_SPARC_V9_SGI; break; case EM_386: g_flags |= DIS_X86_SIZE32; break; case EM_AMD64: g_flags |= DIS_X86_SIZE64; break; case EM_S370: g_flags |= DIS_S370; if (ehdr.e_ident[EI_CLASS] != ELFCLASS32 || ehdr.e_ident[EI_DATA] != ELFDATA2MSB) { warn("invalid E_IDENT field for S370 object"); return; } break; case EM_S390: /* * Both 390 and z/Architecture use EM_S390, the only * differences is the class: ELFCLASS32 for plain * old s390 and ELFCLASS64 for z/Architecture (aka. * s390x). */ if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) { g_flags |= DIS_S390_31; } else if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { g_flags |= DIS_S390_64; } else { warn("invalid E_IDENT field for S390 object"); return; } if (ehdr.e_ident[EI_DATA] != ELFDATA2MSB) { warn("invalid E_IDENT field for S390 object"); return; } break; case EM_RISCV: /* * RISC-V is defined to be litle endian. The current ISA * makes it clear that the 64-bit instructions can * co-exist with the 32-bit ones and therefore we don't * need a separate elf class at this time. */ if (ehdr.e_ident[EI_DATA] != ELFDATA2LSB) { warn("invalid EI_DATA field for RISC-V object"); return; } if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) { g_flags |= DIS_RISCV_32; } else if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { g_flags |= DIS_RISCV_64; } else { warn("invalid EI_CLASS field for RISC-V " "object"); return; } break; default: die("%s: unsupported ELF machine 0x%x", filename, ehdr.e_machine); } /* * If ET_REL (.o), printing immediate symbols is likely to * result in garbage, as symbol lookups on unrelocated * immediates find false and useless matches. */ if (ehdr.e_type == ET_REL) g_flags |= DIS_NOIMMSYM; if (!g_quiet && dis_tgt_member(current) != NULL) (void) printf("\narchive member %s\n", dis_tgt_member(current)); /* * Instantiate a libdisasm handle based on the file type. */ if ((dhp = dis_handle_create(g_flags, current, do_lookup, do_read)) == NULL) die("%s: failed to initialize disassembler: %s", filename, dis_strerror(dis_errno())); if (g_doall) { /* * With no arguments, iterate over all sections and * disassemble only those that contain text. */ dis_tgt_section_iter(current, dis_text_section, dhp); } else { callback_arg_t ca; ca.ca_tgt = current; ca.ca_handle = dhp; /* * If sections or functions were explicitly specified, * resolve those names against the object, and iterate * over just the resulting data. */ sections = dis_namelist_resolve_sections(g_seclist, current); functions = dis_namelist_resolve_functions(g_funclist, current); dis_scnlist_iter(sections, dis_named_section, &ca); dis_funclist_iter(functions, dis_named_function, &ca); dis_scnlist_destroy(sections); dis_funclist_destroy(functions); } dis_handle_destroy(dhp); } dis_tgt_destroy(tgt); } void usage(void) { (void) fprintf(stderr, "usage: dis [-CVoqn] [-d sec] \n"); (void) fprintf(stderr, "\t[-D sec] [-F function] [-t sec] file ..\n"); exit(2); } typedef struct lib_node { char *path; struct lib_node *next; } lib_node_t; int main(int argc, char **argv) { int optchar; int i; lib_node_t *libs = NULL; g_funclist = dis_namelist_create(); g_seclist = dis_namelist_create(); while ((optchar = getopt(argc, argv, "Cd:D:F:l:Lot:Vqn")) != -1) { switch (optchar) { case 'C': g_demangle = 1; break; case 'd': dis_namelist_add(g_seclist, optarg, DIS_DATA_RELATIVE); break; case 'D': dis_namelist_add(g_seclist, optarg, DIS_DATA_ABSOLUTE); break; case 'F': dis_namelist_add(g_funclist, optarg, 0); break; case 'l': { /* * The '-l foo' option historically would attempt to * disassemble '$LIBDIR/libfoo.a'. The $LIBDIR * environment variable has never been supported or * documented for our linker. However, until this * option is formally EOLed, we have to support it. */ char *dir; lib_node_t *node; size_t len; if ((dir = getenv("LIBDIR")) == NULL || dir[0] == '\0') dir = "/usr/lib"; node = safe_malloc(sizeof (lib_node_t)); len = strlen(optarg) + strlen(dir) + sizeof ("/lib.a"); node->path = safe_malloc(len); (void) snprintf(node->path, len, "%s/lib%s.a", dir, optarg); node->next = libs; libs = node; break; } case 'L': /* * The '-L' option historically would attempt to read * the .debug section of the target to determine source * line information in order to annotate the output. * No compiler has emitted these sections in many years, * and the option has never done what it purported to * do. We silently consume the option for * compatibility. */ break; case 'n': g_numeric = 1; break; case 'o': g_flags |= DIS_OCTAL; break; case 'q': g_quiet = 1; break; case 't': dis_namelist_add(g_seclist, optarg, DIS_TEXT); break; case 'V': (void) printf("Solaris disassembler version 1.0\n"); return (0); default: usage(); break; } } argc -= optind; argv += optind; if (argc == 0 && libs == NULL) { warn("no objects specified"); usage(); } if (dis_namelist_empty(g_funclist) && dis_namelist_empty(g_seclist)) g_doall = 1; /* * See comment for 'l' option, above. */ while (libs != NULL) { lib_node_t *node = libs->next; dis_file(libs->path); free(libs->path); free(libs); libs = node; } for (i = 0; i < argc; i++) dis_file(argv[i]); dis_namelist_destroy(g_funclist); dis_namelist_destroy(g_seclist); return (g_error); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. * * Copyright 2011 Jason King. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "dis_target.h" #include "dis_util.h" /* * Standard ELF disassembler target. * * We only support disassembly of ELF files, though this target interface could * be extended in the future. Each basic type (target, func, section) contains * enough information to uniquely identify the location within the file. The * interfaces use libelf(3LIB) to do the actual processing of the file. */ /* * Symbol table entry type. We maintain our own symbol table sorted by address, * with the symbol name already resolved against the ELF symbol table. */ typedef struct sym_entry { GElf_Sym se_sym; /* value of symbol */ char *se_name; /* name of symbol */ int se_shndx; /* section where symbol is located */ } sym_entry_t; /* * Create a map of the virtual address ranges of every section. This will * allow us to create dummpy mappings for unassigned addresses. Otherwise * multiple sections with unassigned addresses will appear to overlap and * mess up symbol resolution (which uses the virtual address). */ typedef struct dis_shnmap { const char *dm_name; /* name of section */ uint64_t dm_start; /* virtual address of section */ size_t dm_length; /* address length */ boolean_t dm_mapped; /* did we assign the mapping */ } dis_shnmap_t; /* * Target data structure. This structure keeps track of the ELF file * information, a few bits of pre-processed section index information, and * sorted versions of the symbol table. We also keep track of the last symbol * looked up, as the majority of lookups remain within the same symbol. */ struct dis_tgt { Elf *dt_elf; /* libelf handle */ Elf *dt_elf_root; /* main libelf handle (for archives) */ const char *dt_filename; /* name of file */ int dt_fd; /* underlying file descriptor */ size_t dt_shstrndx; /* section index of .shstrtab */ size_t dt_symidx; /* section index of symbol table */ sym_entry_t *dt_symcache; /* last symbol looked up */ sym_entry_t *dt_symtab; /* sorted symbol table */ int dt_symcount; /* # of symbol table entries */ struct dis_tgt *dt_next; /* next target (for archives) */ Elf_Arhdr *dt_arhdr; /* archive header (for archives) */ dis_shnmap_t *dt_shnmap; /* section address map */ size_t dt_shncount; /* # of sections in target */ }; /* * Function data structure. We resolve the symbol and lookup the associated ELF * data when building this structure. The offset is calculated based on the * section's starting address. */ struct dis_func { sym_entry_t *df_sym; /* symbol table reference */ Elf_Data *df_data; /* associated ELF data */ size_t df_offset; /* offset within data */ }; /* * Section data structure. We store the entire section header so that we can * determine some properties (such as whether or not it contains text) after * building the structure. */ struct dis_scn { GElf_Shdr ds_shdr; const char *ds_name; Elf_Data *ds_data; }; /* Lifted from Psymtab.c, omitting STT_TLS */ #define DATA_TYPES \ ((1 << STT_OBJECT) | (1 << STT_FUNC) | (1 << STT_COMMON)) #define IS_DATA_TYPE(tp) (((1 << (tp)) & DATA_TYPES) != 0) /* * Save the virtual address range for this section and select the * best section to use as the symbol table. We prefer SHT_SYMTAB * over SHT_DYNSYM. */ /* ARGSUSED */ static void tgt_scn_init(dis_tgt_t *tgt, dis_scn_t *scn, void *data) { int *index = data; *index += 1; tgt->dt_shnmap[*index].dm_name = scn->ds_name; tgt->dt_shnmap[*index].dm_start = scn->ds_shdr.sh_addr; tgt->dt_shnmap[*index].dm_length = scn->ds_shdr.sh_size; tgt->dt_shnmap[*index].dm_mapped = B_FALSE; /* * Prefer SHT_SYMTAB over SHT_DYNSYM */ if (scn->ds_shdr.sh_type == SHT_DYNSYM && tgt->dt_symidx == 0) tgt->dt_symidx = *index; else if (scn->ds_shdr.sh_type == SHT_SYMTAB) tgt->dt_symidx = *index; } static int sym_compare(const void *a, const void *b) { const sym_entry_t *syma = a; const sym_entry_t *symb = b; const char *aname = syma->se_name; const char *bname = symb->se_name; size_t alen; size_t blen; if (syma->se_sym.st_value < symb->se_sym.st_value) return (-1); if (syma->se_sym.st_value > symb->se_sym.st_value) return (1); /* * Prefer functions over non-functions */ if (GELF_ST_TYPE(syma->se_sym.st_info) != GELF_ST_TYPE(symb->se_sym.st_info)) { if (GELF_ST_TYPE(syma->se_sym.st_info) == STT_FUNC) return (-1); if (GELF_ST_TYPE(symb->se_sym.st_info) == STT_FUNC) return (1); } /* * For symbols with the same address and type, we sort them according to * a hierarchy: * * 1. weak symbols (common name) * 2. global symbols (external name) * 3. local symbols */ if (GELF_ST_BIND(syma->se_sym.st_info) != GELF_ST_BIND(symb->se_sym.st_info)) { if (GELF_ST_BIND(syma->se_sym.st_info) == STB_WEAK) return (-1); if (GELF_ST_BIND(symb->se_sym.st_info) == STB_WEAK) return (1); if (GELF_ST_BIND(syma->se_sym.st_info) == STB_GLOBAL) return (-1); if (GELF_ST_BIND(symb->se_sym.st_info) == STB_GLOBAL) return (1); } /* * As a last resort, if we have multiple symbols of the same type at the * same address, prefer the version with the fewest leading underscores. */ if (aname == NULL) return (-1); if (bname == NULL) return (1); while (*aname == '_' && *bname == '_') { aname++; bname++; } if (*bname == '_') return (-1); if (*aname == '_') return (1); /* * Prefer the symbol with the smaller size. */ if (syma->se_sym.st_size < symb->se_sym.st_size) return (-1); if (syma->se_sym.st_size > symb->se_sym.st_size) return (1); /* * We really do have two identical symbols, choose the one with the * shortest name if we can, heuristically taking it to be the most * representative. */ alen = strlen(syma->se_name); blen = strlen(symb->se_name); if (alen < blen) return (-1); else if (alen > blen) return (1); /* * If all else fails, compare the names, so that we give a stable * sort */ return (strcmp(syma->se_name, symb->se_name)); } /* * Construct an optimized symbol table sorted by starting address. */ static void construct_symtab(dis_tgt_t *tgt) { Elf_Scn *scn; GElf_Shdr shdr; Elf_Data *symdata; int i; GElf_Word *symshndx = NULL; int symshndx_size; sym_entry_t *sym; sym_entry_t *p_symtab = NULL; int nsym = 0; /* count of symbols we're not interested in */ /* * Find the symshndx section, if any */ for (scn = elf_nextscn(tgt->dt_elf, NULL); scn != NULL; scn = elf_nextscn(tgt->dt_elf, scn)) { if (gelf_getshdr(scn, &shdr) == NULL) break; if (shdr.sh_type == SHT_SYMTAB_SHNDX && shdr.sh_link == tgt->dt_symidx) { Elf_Data *data; if ((data = elf_getdata(scn, NULL)) != NULL) { symshndx = (GElf_Word *)data->d_buf; symshndx_size = data->d_size / sizeof (GElf_Word); break; } } } if ((scn = elf_getscn(tgt->dt_elf, tgt->dt_symidx)) == NULL) die("%s: failed to get section information", tgt->dt_filename); if (gelf_getshdr(scn, &shdr) == NULL) die("%s: failed to get section header", tgt->dt_filename); if (shdr.sh_entsize == 0) die("%s: symbol table has zero size", tgt->dt_filename); if ((symdata = elf_getdata(scn, NULL)) == NULL) die("%s: failed to get symbol table", tgt->dt_filename); tgt->dt_symcount = symdata->d_size / gelf_fsize(tgt->dt_elf, ELF_T_SYM, 1, EV_CURRENT); p_symtab = safe_malloc(tgt->dt_symcount * sizeof (sym_entry_t)); for (i = 0, sym = p_symtab; i < tgt->dt_symcount; i++) { if (gelf_getsym(symdata, i, &(sym->se_sym)) == NULL) { warn("%s: gelf_getsym returned NULL for %d", tgt->dt_filename, i); nsym++; continue; } /* * We're only interested in data symbols. */ if (!IS_DATA_TYPE(GELF_ST_TYPE(sym->se_sym.st_info))) { nsym++; continue; } if (sym->se_sym.st_shndx == SHN_XINDEX && symshndx != NULL) { if (i > symshndx_size) { warn("%s: bad SHNX_XINDEX %d", tgt->dt_filename, i); sym->se_shndx = -1; } else { sym->se_shndx = symshndx[i]; } } else { sym->se_shndx = sym->se_sym.st_shndx; } /* Deal with symbols with special section indicies */ if (sym->se_shndx == SHN_ABS) { /* * If st_value == 0, references to these * symbols in code are modified in situ * thus we will never attempt to look * them up. */ if (sym->se_sym.st_value == 0) { /* * References to these symbols in code * are modified in situ by the runtime * linker and no code on disk will ever * attempt to look them up. */ nsym++; continue; } else { /* * If st_value != 0, (such as examining * something in /system/object/.../object) * the values should resolve to a value * within an existing section (such as * .data). This also means it never needs * to have st_value mapped. */ sym++; continue; } } /* * Ignore the symbol if it has some other special * section index */ if (sym->se_shndx == SHN_UNDEF || sym->se_shndx >= SHN_LORESERVE) { nsym++; continue; } if ((sym->se_name = elf_strptr(tgt->dt_elf, shdr.sh_link, (size_t)sym->se_sym.st_name)) == NULL) { warn("%s: failed to lookup symbol %d name", tgt->dt_filename, i); nsym++; continue; } /* * If we had to map this section, its symbol value * also needs to be mapped. */ if (tgt->dt_shnmap[sym->se_shndx].dm_mapped) sym->se_sym.st_value += tgt->dt_shnmap[sym->se_shndx].dm_start; sym++; } tgt->dt_symcount -= nsym; tgt->dt_symtab = realloc(p_symtab, tgt->dt_symcount * sizeof (sym_entry_t)); qsort(tgt->dt_symtab, tgt->dt_symcount, sizeof (sym_entry_t), sym_compare); } /* * Assign virtual address ranges for sections that need it */ static void create_addrmap(dis_tgt_t *tgt) { uint64_t addr; int i; if (tgt->dt_shnmap == NULL) return; /* find the greatest used address */ for (addr = 0, i = 1; i < tgt->dt_shncount; i++) if (tgt->dt_shnmap[i].dm_start > addr) addr = tgt->dt_shnmap[i].dm_start + tgt->dt_shnmap[i].dm_length; addr = P2ROUNDUP(addr, 0x1000); /* * Assign section a starting address beyond the largest mapped section * if no address was given. */ for (i = 1; i < tgt->dt_shncount; i++) { if (tgt->dt_shnmap[i].dm_start != 0) continue; tgt->dt_shnmap[i].dm_start = addr; tgt->dt_shnmap[i].dm_mapped = B_TRUE; addr = P2ROUNDUP(addr + tgt->dt_shnmap[i].dm_length, 0x1000); } } /* * Create a target backed by an ELF file. */ dis_tgt_t * dis_tgt_create(const char *file) { dis_tgt_t *tgt, *current; int idx; Elf *elf; GElf_Ehdr ehdr; Elf_Arhdr *arhdr = NULL; int cmd; if (elf_version(EV_CURRENT) == EV_NONE) die("libelf out of date"); tgt = safe_malloc(sizeof (dis_tgt_t)); if ((tgt->dt_fd = open(file, O_RDONLY)) < 0) { warn("%s: failed opening file, reason: %s", file, strerror(errno)); free(tgt); return (NULL); } if ((tgt->dt_elf_root = elf_begin(tgt->dt_fd, ELF_C_READ, NULL)) == NULL) { warn("%s: invalid or corrupt ELF file", file); dis_tgt_destroy(tgt); return (NULL); } current = tgt; cmd = ELF_C_READ; while ((elf = elf_begin(tgt->dt_fd, cmd, tgt->dt_elf_root)) != NULL) { size_t shnum = 0; if (elf_kind(tgt->dt_elf_root) == ELF_K_AR && (arhdr = elf_getarhdr(elf)) == NULL) { warn("%s: malformed archive", file); dis_tgt_destroy(tgt); return (NULL); } /* * Make sure that this Elf file is sane */ if (gelf_getehdr(elf, &ehdr) == NULL) { if (arhdr != NULL) { /* * For archives, we drive on in the face of bad * members. The "/" and "//" members are * special, and should be silently ignored. */ if (strcmp(arhdr->ar_name, "/") != 0 && strcmp(arhdr->ar_name, "//") != 0) warn("%s[%s]: invalid file type", file, arhdr->ar_name); cmd = elf_next(elf); (void) elf_end(elf); continue; } warn("%s: invalid file type", file); dis_tgt_destroy(tgt); return (NULL); } /* * If we're seeing a new Elf object, then we have an * archive. In this case, we create a new target, and chain it * off the master target. We can later iterate over these * targets using dis_tgt_next(). */ if (current->dt_elf != NULL) { dis_tgt_t *next = safe_malloc(sizeof (dis_tgt_t)); next->dt_elf_root = tgt->dt_elf_root; next->dt_fd = -1; current->dt_next = next; current = next; } current->dt_elf = elf; current->dt_arhdr = arhdr; if (elf_getshdrstrndx(elf, ¤t->dt_shstrndx) == -1) { warn("%s: failed to get section string table for " "file", file); dis_tgt_destroy(tgt); return (NULL); } if (elf_getshdrnum(elf, &shnum) == -1) { warn("%s: failed to get number of sections in file", file); dis_tgt_destroy(tgt); return (NULL); } current->dt_shnmap = safe_malloc(sizeof (dis_shnmap_t) * shnum); current->dt_shncount = shnum; idx = 0; dis_tgt_section_iter(current, tgt_scn_init, &idx); current->dt_filename = file; create_addrmap(current); if (current->dt_symidx != 0) construct_symtab(current); cmd = elf_next(elf); } /* * Final sanity check. If we had an archive with no members, then bail * out with a nice message. */ if (tgt->dt_elf == NULL) { warn("%s: empty archive\n", file); dis_tgt_destroy(tgt); return (NULL); } return (tgt); } /* * Return the filename associated with the target. */ const char * dis_tgt_name(dis_tgt_t *tgt) { return (tgt->dt_filename); } /* * Return the archive member name, if any. */ const char * dis_tgt_member(dis_tgt_t *tgt) { if (tgt->dt_arhdr) return (tgt->dt_arhdr->ar_name); else return (NULL); } /* * Return the Elf_Ehdr associated with this target. Needed to determine which * disassembler to use. */ void dis_tgt_ehdr(dis_tgt_t *tgt, GElf_Ehdr *ehdr) { (void) gelf_getehdr(tgt->dt_elf, ehdr); } /* * Return the next target in the list, if this is an archive. */ dis_tgt_t * dis_tgt_next(dis_tgt_t *tgt) { return (tgt->dt_next); } /* * Destroy a target and free up any associated memory. */ void dis_tgt_destroy(dis_tgt_t *tgt) { dis_tgt_t *current, *next; current = tgt->dt_next; while (current != NULL) { next = current->dt_next; if (current->dt_elf) (void) elf_end(current->dt_elf); if (current->dt_symtab) free(current->dt_symtab); free(current); current = next; } if (tgt->dt_elf) (void) elf_end(tgt->dt_elf); if (tgt->dt_elf_root) (void) elf_end(tgt->dt_elf_root); if (tgt->dt_symtab) free(tgt->dt_symtab); free(tgt); } /* * Given an address, return the section it is in and set the offset within * the section. */ const char * dis_find_section(dis_tgt_t *tgt, uint64_t addr, off_t *offset) { int i; for (i = 1; i < tgt->dt_shncount; i++) { if ((addr >= tgt->dt_shnmap[i].dm_start) && (addr < tgt->dt_shnmap[i].dm_start + tgt->dt_shnmap[i].dm_length)) { *offset = addr - tgt->dt_shnmap[i].dm_start; return (tgt->dt_shnmap[i].dm_name); } } *offset = 0; return (NULL); } /* * Given an address, returns the name of the corresponding symbol, as well as * the offset within that symbol. If no matching symbol is found, then NULL is * returned. * * If 'cache_result' is specified, then we keep track of the resulting symbol. * This cached result is consulted first on subsequent lookups in order to avoid * unecessary lookups. This flag should be used for resolving the current PC, * as the majority of addresses stay within the current function. */ const char * dis_tgt_lookup(dis_tgt_t *tgt, uint64_t addr, off_t *offset, int cache_result, size_t *size, int *isfunc) { int lo, hi, mid; sym_entry_t *sym, *osym, *match; int found; *offset = 0; *size = 0; if (isfunc != NULL) *isfunc = 0; if (tgt->dt_symcache != NULL && addr >= tgt->dt_symcache->se_sym.st_value && addr < tgt->dt_symcache->se_sym.st_value + tgt->dt_symcache->se_sym.st_size) { sym = tgt->dt_symcache; *offset = addr - sym->se_sym.st_value; *size = sym->se_sym.st_size; if (isfunc != NULL) *isfunc = (GELF_ST_TYPE(sym->se_sym.st_info) == STT_FUNC); return (sym->se_name); } lo = 0; hi = (tgt->dt_symcount - 1); found = 0; match = osym = NULL; while (lo <= hi) { mid = (lo + hi) / 2; sym = &tgt->dt_symtab[mid]; if (addr >= sym->se_sym.st_value && addr < sym->se_sym.st_value + sym->se_sym.st_size && (!found || sym->se_sym.st_value > osym->se_sym.st_value)) { osym = sym; found = 1; } else if (addr == sym->se_sym.st_value) { /* * Particularly for .plt objects, it's possible to have * a zero sized object. We want to return this, but we * want it to be a last resort. */ match = sym; } if (addr < sym->se_sym.st_value) hi = mid - 1; else lo = mid + 1; } if (!found) { if (match) osym = match; else return (NULL); } /* * Walk backwards to find the best match. */ do { sym = osym; if (osym == tgt->dt_symtab) break; osym = osym - 1; } while ((sym->se_sym.st_value == osym->se_sym.st_value) && (addr >= osym->se_sym.st_value) && (addr < osym->se_sym.st_value + osym->se_sym.st_size)); if (cache_result) tgt->dt_symcache = sym; *offset = addr - sym->se_sym.st_value; *size = sym->se_sym.st_size; if (isfunc) *isfunc = (GELF_ST_TYPE(sym->se_sym.st_info) == STT_FUNC); return (sym->se_name); } /* * Given an address, return the starting offset of the next symbol in the file. * Only needed on variable length instruction architectures. */ off_t dis_tgt_next_symbol(dis_tgt_t *tgt, uint64_t addr) { sym_entry_t *sym; sym = (tgt->dt_symcache != NULL) ? tgt->dt_symcache : tgt->dt_symtab; while (sym != (tgt->dt_symtab + tgt->dt_symcount)) { if (sym->se_sym.st_value >= addr) return (sym->se_sym.st_value - addr); sym++; } return (0); } /* * Iterate over all sections in the target, executing the given callback for * each. */ void dis_tgt_section_iter(dis_tgt_t *tgt, section_iter_f func, void *data) { dis_scn_t sdata; Elf_Scn *scn; int idx; for (scn = elf_nextscn(tgt->dt_elf, NULL), idx = 1; scn != NULL; scn = elf_nextscn(tgt->dt_elf, scn), idx++) { if (gelf_getshdr(scn, &sdata.ds_shdr) == NULL) { warn("%s: failed to get section %d header", tgt->dt_filename, idx); continue; } if ((sdata.ds_name = elf_strptr(tgt->dt_elf, tgt->dt_shstrndx, sdata.ds_shdr.sh_name)) == NULL) { warn("%s: failed to get section %d name", tgt->dt_filename, idx); continue; } if ((sdata.ds_data = elf_getdata(scn, NULL)) == NULL) { warn("%s: failed to get data for section '%s'", tgt->dt_filename, sdata.ds_name); continue; } /* * dis_tgt_section_iter is also used before the section map * is initialized, so only check when we need to. If the * section map is uninitialized, it will return 0 and have * no net effect. */ if (sdata.ds_shdr.sh_addr == 0) sdata.ds_shdr.sh_addr = tgt->dt_shnmap[idx].dm_start; func(tgt, &sdata, data); } } /* * Return 1 if the given section contains text, 0 otherwise. */ int dis_section_istext(dis_scn_t *scn) { return ((scn->ds_shdr.sh_type == SHT_PROGBITS) && (scn->ds_shdr.sh_flags == (SHF_ALLOC | SHF_EXECINSTR))); } /* * Return a pointer to the section data. */ void * dis_section_data(dis_scn_t *scn) { return (scn->ds_data->d_buf); } /* * Return the size of the section data. */ size_t dis_section_size(dis_scn_t *scn) { return (scn->ds_data->d_size); } /* * Return the address for the given section. */ uint64_t dis_section_addr(dis_scn_t *scn) { return (scn->ds_shdr.sh_addr); } /* * Return the name of the current section. */ const char * dis_section_name(dis_scn_t *scn) { return (scn->ds_name); } /* * Create an allocated copy of the given section */ dis_scn_t * dis_section_copy(dis_scn_t *scn) { dis_scn_t *new; new = safe_malloc(sizeof (dis_scn_t)); (void) memcpy(new, scn, sizeof (dis_scn_t)); return (new); } /* * Free section memory */ void dis_section_free(dis_scn_t *scn) { free(scn); } /* * Iterate over all functions in the target, executing the given callback for * each one. */ void dis_tgt_function_iter(dis_tgt_t *tgt, function_iter_f func, void *data) { int i; sym_entry_t *sym; dis_func_t df; Elf_Scn *scn; GElf_Shdr shdr; for (i = 0, sym = tgt->dt_symtab; i < tgt->dt_symcount; i++, sym++) { /* ignore non-functions */ if ((GELF_ST_TYPE(sym->se_sym.st_info) != STT_FUNC) || (sym->se_name == NULL) || (sym->se_sym.st_size == 0) || (sym->se_shndx >= SHN_LORESERVE)) continue; /* get the ELF data associated with this function */ if ((scn = elf_getscn(tgt->dt_elf, sym->se_shndx)) == NULL || gelf_getshdr(scn, &shdr) == NULL || (df.df_data = elf_getdata(scn, NULL)) == NULL || df.df_data->d_size == 0) { warn("%s: failed to read section %d", tgt->dt_filename, sym->se_shndx); continue; } if (tgt->dt_shnmap[sym->se_shndx].dm_mapped) shdr.sh_addr = tgt->dt_shnmap[sym->se_shndx].dm_start; /* * Verify that the address lies within the section that we think * it does. */ if (sym->se_sym.st_value < shdr.sh_addr || (sym->se_sym.st_value + sym->se_sym.st_size) > (shdr.sh_addr + shdr.sh_size)) { warn("%s: bad section %d for address %p", tgt->dt_filename, sym->se_sym.st_shndx, sym->se_sym.st_value); continue; } df.df_sym = sym; df.df_offset = sym->se_sym.st_value - shdr.sh_addr; func(tgt, &df, data); } } /* * Return the data associated with a given function. */ void * dis_function_data(dis_func_t *func) { return ((char *)func->df_data->d_buf + func->df_offset); } /* * Return the size of a function. */ size_t dis_function_size(dis_func_t *func) { return (func->df_sym->se_sym.st_size); } /* * Return the address of a function. */ uint64_t dis_function_addr(dis_func_t *func) { return (func->df_sym->se_sym.st_value); } /* * Return the name of the function */ const char * dis_function_name(dis_func_t *func) { return (func->df_sym->se_name); } /* * Return a copy of a function. */ dis_func_t * dis_function_copy(dis_func_t *func) { dis_func_t *new; new = safe_malloc(sizeof (dis_func_t)); (void) memcpy(new, func, sizeof (dis_func_t)); return (new); } /* * Free function memory */ void dis_function_free(dis_func_t *func) { free(func); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2011 Jason King. All rights reserved. */ #ifndef _DIS_TARGET_H #define _DIS_TARGET_H #ifdef __cplusplus extern "C" { #endif #include #include /* * Basic types */ typedef struct dis_tgt dis_tgt_t; typedef struct dis_func dis_func_t; typedef struct dis_scn dis_scn_t; /* * Target management */ dis_tgt_t *dis_tgt_create(const char *); void dis_tgt_destroy(dis_tgt_t *); const char *dis_tgt_lookup(dis_tgt_t *, uint64_t, off_t *, int, size_t *, int *); const char *dis_find_section(dis_tgt_t *, uint64_t, off_t *); const char *dis_tgt_name(dis_tgt_t *); const char *dis_tgt_member(dis_tgt_t *); void dis_tgt_ehdr(dis_tgt_t *, GElf_Ehdr *); off_t dis_tgt_next_symbol(dis_tgt_t *, uint64_t); dis_tgt_t *dis_tgt_next(dis_tgt_t *); /* * Section management */ typedef void (*section_iter_f)(dis_tgt_t *, dis_scn_t *, void *); void dis_tgt_section_iter(dis_tgt_t *, section_iter_f, void *); int dis_section_istext(dis_scn_t *); void *dis_section_data(dis_scn_t *); size_t dis_section_size(dis_scn_t *); uint64_t dis_section_addr(dis_scn_t *); const char *dis_section_name(dis_scn_t *); dis_scn_t *dis_section_copy(dis_scn_t *); void dis_section_free(dis_scn_t *); /* * Function management */ typedef void (*function_iter_f)(dis_tgt_t *, dis_func_t *, void *); void dis_tgt_function_iter(dis_tgt_t *, function_iter_f, void *); dis_func_t *dis_tgt_function_lookup(dis_tgt_t *, const char *); void *dis_function_data(dis_func_t *); size_t dis_function_size(dis_func_t *); uint64_t dis_function_addr(dis_func_t *); const char *dis_function_name(dis_func_t *); dis_func_t *dis_function_copy(dis_func_t *); void dis_function_free(dis_func_t *); #ifdef __cplusplus } #endif #endif /* _DIS_TARGET_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Jason King. * Copyright 2018, Joyent, Inc. */ #include #include #include #include #include #include "dis_util.h" int g_error; /* global process exit status, set when warn() is called */ /* * Fatal error. Print out the error with a leading "dis: ", and then exit the * program. */ void die(const char *fmt, ...) { va_list ap; (void) fprintf(stderr, "dis: fatal: "); va_start(ap, fmt); (void) vfprintf(stderr, fmt, ap); va_end(ap); (void) fprintf(stderr, "\n"); exit(1); } /* * Non-fatal error. Print out the error with a leading "dis: ", set the global * error flag, and return. */ void warn(const char *fmt, ...) { va_list ap; (void) fprintf(stderr, "dis: warning: "); va_start(ap, fmt); (void) vfprintf(stderr, fmt, ap); va_end(ap); (void) fprintf(stderr, "\n"); g_error = 1; } /* * Convenience wrapper around malloc() to cleanly exit if any allocation fails. */ void * safe_malloc(size_t size) { void *ret; if ((ret = calloc(1, size)) == NULL) die("Out of memory"); return (ret); } /* * Since the -C flag explicitly says C++, for now at least, force language to * C++ */ const char * dis_demangle(const char *name) { static char *demangled_name = NULL; /* * Since demangled_name is static, it may be preserved across * invocations. As such, make sure any memory that might be present * from previous invocations is freed. */ free(demangled_name); demangled_name = sysdemangle(name, SYSDEM_LANG_AUTO, NULL); return ((demangled_name != NULL) ? demangled_name : name); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _DIS_UTIL_H #define _DIS_UTIL_H #include #ifdef __cplusplus extern "C" { #endif void die(const char *, ...); void warn(const char *, ...); void *safe_malloc(size_t); const char *dis_demangle(const char *); extern int g_error; #define BUFSIZE 1024 #ifdef __cplusplus } #endif #endif /* _DIS_UTIL_H */