#include <ctype.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/hexdump.h>
#include <sys/sysmacros.h>
#include <sys/elf_SPARC.h>
#include <libdisasm.h>
#include "dis_target.h"
#include "dis_util.h"
#include "dis_list.h"
int g_demangle;
int g_quiet;
int g_numeric;
int g_flags;
int g_doall;
dis_namelist_t *g_funclist;
dis_namelist_t *g_seclist;
#define DIS_DATA_RELATIVE 1
#define DIS_DATA_ABSOLUTE 2
#define DIS_TEXT 3
typedef struct dis_buffer {
dis_tgt_t *db_tgt;
void *db_data;
uint64_t db_addr;
size_t db_size;
uint64_t db_nextaddr;
} dis_buffer_t;
#define MINSYMWIDTH 22
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);
}
}
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);
}
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) {
(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 ***");
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;
}
}
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 (!g_quiet && symoffset == 0 && symbol != NULL && isfunc)
(void) printf("%s()\n", symbol);
(void) printf(" %s:%*s ", symbuf,
symwidth - strlen(symbuf), "");
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);
}
for (; i < bytesperline; i++) {
if (g_flags & DIS_OCTAL)
(void) printf(" ");
else
(void) printf(" ");
}
(void) printf(" %s", buf);
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;
}
}
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;
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);
}
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);
}
void
dump_data(uint64_t addr, void *data, size_t datalen)
{
hexdump_t h;
hexdump_init(&h);
hexdump_set_grouping(&h, 2);
hexdump_set_addr(&h, addr);
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);
}
void
dis_text_section(dis_tgt_t *tgt, dis_scn_t *scn, void *data)
{
dis_handle_t *dhp = data;
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));
}
typedef struct callback_arg {
dis_tgt_t *ca_tgt;
dis_handle_t *ca_handle;
} callback_arg_t;
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;
}
}
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));
}
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;
if ((tgt = dis_tgt_create(filename)) == NULL)
return;
if (!g_quiet)
(void) printf("disassembly for %s\n\n", filename);
for (current = tgt; current != NULL; current = dis_tgt_next(current)) {
dis_tgt_ehdr(current, &ehdr);
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:
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:
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 (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));
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) {
dis_tgt_section_iter(current, dis_text_section, dhp);
} else {
callback_arg_t ca;
ca.ca_tgt = current;
ca.ca_handle = dhp;
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': {
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':
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;
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);
}