595 lines
14 KiB
C
595 lines
14 KiB
C
/* $Id$ */
|
|
/* Copyright (c) 2011 Pierre Pronchery <khorben@defora.org> */
|
|
/* This file is part of DeforaOS Devel asm */
|
|
/* 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, version 3 of the License.
|
|
*
|
|
* 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, see <http://www.gnu.org/licenses/>. */
|
|
|
|
|
|
|
|
#include <System.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include "Asm/as.h"
|
|
#include "Asm/format.h"
|
|
#include "arch.h"
|
|
#include "asm.h"
|
|
#include "../config.h"
|
|
|
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
|
|
|
|
|
/* disas */
|
|
/* private */
|
|
/* types */
|
|
typedef struct _DisasFormat
|
|
{
|
|
Plugin * plugin;
|
|
FormatPlugin * format;
|
|
} DisasFormat;
|
|
|
|
typedef struct _Disas
|
|
{
|
|
FormatPluginHelper helper;
|
|
char const * arch;
|
|
As * as;
|
|
|
|
DisasFormat * format;
|
|
size_t format_cnt;
|
|
} Disas;
|
|
|
|
|
|
/* prototypes */
|
|
static int _disas(char const * arch, char const * format,
|
|
char const * filename);
|
|
static int _disas_buffer(char const * arch, char const * format,
|
|
char const * buffer, size_t size);
|
|
static int _disas_string(char const * arch, char const * format,
|
|
char const * string);
|
|
static int _disas_list(void);
|
|
|
|
static int _disas_error(char const * message, int ret);
|
|
|
|
/* format plug-ins */
|
|
static DisasFormat const * _disas_format_open(Disas * disas,
|
|
char const * format);
|
|
static int _disas_format_open_all(Disas * disas);
|
|
static int _disas_format_init(DisasFormat const * format, char const * arch);
|
|
static int _disas_format_exit(DisasFormat const * format);
|
|
static void _disas_format_close(DisasFormat const * format);
|
|
static void _disas_format_close_all(Disas * disas);
|
|
|
|
/* callbacks */
|
|
static int _disas_format_callback(FormatPlugin * format, char const * section,
|
|
off_t offset, size_t size, off_t base);
|
|
|
|
/* helpers */
|
|
static int _hex2bin(int c);
|
|
static int _ishex(int c);
|
|
|
|
|
|
/* functions */
|
|
/* disas */
|
|
static int _disas_do_format(Disas * disas, char const * format);
|
|
static int _disas_do(Disas * disas);
|
|
static int _do_callback(Disas * disas, FormatPlugin * format);
|
|
static int _do_flat(Disas * disas, off_t offset, size_t size, off_t base);
|
|
static void _do_flat_print(Arch * arch, unsigned long address,
|
|
char const * buffer, size_t size, ArchInstruction * ai);
|
|
|
|
static int _disas(char const * arch, char const * format, char const * filename)
|
|
{
|
|
int ret = 1;
|
|
Disas disas;
|
|
size_t i;
|
|
|
|
memset(&disas.helper, 0, sizeof(disas.helper));
|
|
disas.arch = arch;
|
|
disas.as = NULL;
|
|
disas.format = NULL;
|
|
disas.format_cnt = 0;
|
|
if((disas.helper.fp = fopen(filename, "r")) == NULL)
|
|
return -_disas_error(filename, 1);
|
|
disas.helper.filename = filename;
|
|
disas.helper.priv = &disas;
|
|
if(format != NULL)
|
|
ret = _disas_do_format(&disas, format);
|
|
else
|
|
ret = _disas_do(&disas);
|
|
for(i = 0; i < disas.format_cnt; i++)
|
|
_disas_format_close(&disas.format[i]);
|
|
if(disas.as != NULL)
|
|
as_delete(disas.as);
|
|
fclose(disas.helper.fp);
|
|
if(ret != 0)
|
|
error_print("disas");
|
|
return ret;
|
|
}
|
|
|
|
static int _disas_do_format(Disas * disas, char const * format)
|
|
{
|
|
int ret;
|
|
DisasFormat const * df;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, format);
|
|
#endif
|
|
if((df = _disas_format_open(disas, format)) == NULL)
|
|
return -1;
|
|
if(df->format->disas == NULL)
|
|
ret = -error_set_code(1, "%s: %s", format,
|
|
"Does not support disassembly");
|
|
else if((ret = _disas_format_init(df, disas->arch)) == 0)
|
|
{
|
|
ret = _do_callback(disas, df->format);
|
|
_disas_format_exit(df);
|
|
}
|
|
_disas_format_close(df);
|
|
return ret;
|
|
}
|
|
|
|
static int _disas_do(Disas * disas)
|
|
{
|
|
int ret = 1;
|
|
size_t i;
|
|
size_t s = 0;
|
|
char * buf;
|
|
FormatPlugin * format;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s()\n", __func__);
|
|
#endif
|
|
_disas_format_open_all(disas);
|
|
for(i = 0; i < disas->format_cnt; i++)
|
|
if(disas->format[i].format->signature_len > s)
|
|
s = disas->format[i].format->signature_len;
|
|
if((buf = malloc(s)) == NULL)
|
|
{
|
|
free(buf);
|
|
return -_disas_error(disas->helper.filename, 1);
|
|
}
|
|
if(fread(buf, sizeof(*buf), s, disas->helper.fp) != s)
|
|
{
|
|
if(ferror(disas->helper.fp))
|
|
ret = -_disas_error(disas->helper.filename, 1);
|
|
else
|
|
ret = _disas_do_format(disas, "flat");
|
|
}
|
|
else
|
|
{
|
|
for(i = 0; i < disas->format_cnt; i++)
|
|
{
|
|
format = disas->format[i].format;
|
|
if(format->signature_len == 0)
|
|
continue;
|
|
else if(memcmp(format->signature, buf,
|
|
format->signature_len) == 0)
|
|
{
|
|
if((ret = _disas_format_init(&disas->format[i],
|
|
disas->arch))
|
|
!= 0)
|
|
break;
|
|
ret = _do_callback(disas, format);
|
|
_disas_format_exit(&disas->format[i]);
|
|
break;
|
|
}
|
|
}
|
|
if(i == disas->format_cnt)
|
|
/* FIXME look it up in the existing list instead */
|
|
ret = _disas_do_format(disas, "flat");
|
|
}
|
|
free(buf);
|
|
_disas_format_close_all(disas);
|
|
return ret;
|
|
}
|
|
|
|
static int _do_callback(Disas * disas, FormatPlugin * format)
|
|
{
|
|
int ret;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s()\n", __func__);
|
|
#endif
|
|
if(disas->arch == NULL)
|
|
{
|
|
if(format->detect == NULL)
|
|
return -error_set_code(1, "%s: %s", format->name,
|
|
"Unable to detect the architecture");
|
|
if((disas->arch = format->detect(format)) == NULL)
|
|
return -1;
|
|
}
|
|
if((disas->as = as_new(disas->arch, format->name)) == NULL)
|
|
return -error_print("disas");
|
|
printf("\n%s: %s-%s\n", disas->helper.filename, format->name,
|
|
as_get_arch_name(disas->as));
|
|
ret = format->disas(format, _disas_format_callback);
|
|
return ret;
|
|
}
|
|
|
|
static int _do_flat(Disas * disas, off_t offset, size_t size, off_t base)
|
|
{
|
|
int ret = 0;
|
|
Arch * arch = as_get_arch(disas->as);
|
|
size_t pos;
|
|
char buf[8];
|
|
size_t buf_cnt = 0;
|
|
size_t cnt;
|
|
ArchInstruction * ai;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s(%p, 0x%lx, 0x%lx) arch=\"%s\"\n", __func__,
|
|
(void *)disas, (unsigned long)offset, size,
|
|
as_get_arch_name(disas->as));
|
|
#endif
|
|
if(fseek(disas->helper.fp, offset, SEEK_SET) != 0)
|
|
return -_disas_error(disas->helper.filename, 1);
|
|
printf("\n%08lx:\n", (unsigned long)offset + base);
|
|
memset(buf, 0, sizeof(buf));
|
|
for(pos = 0; pos < size; pos += cnt)
|
|
{
|
|
if((cnt = sizeof(buf) - buf_cnt) > 0 && size - pos - 1 > 0)
|
|
{
|
|
if((cnt = fread(&buf[buf_cnt], 1, cnt,
|
|
disas->helper.fp)) == 0)
|
|
break;
|
|
buf_cnt += cnt;
|
|
}
|
|
cnt = buf_cnt;
|
|
if((ai = as_decode(disas->as, buf, &cnt)) != NULL)
|
|
_do_flat_print(arch, base + offset + pos, buf, cnt, ai);
|
|
else
|
|
cnt = 1; /* FIXME print missing instruction */
|
|
memmove(buf, &buf[cnt], buf_cnt - cnt);
|
|
buf_cnt -= cnt;
|
|
}
|
|
if(ferror(disas->helper.fp))
|
|
return -_disas_error(disas->helper.filename, 1);
|
|
else if(fseek(disas->helper.fp, offset + size, SEEK_SET) != 0)
|
|
return -_disas_error(disas->helper.filename, 1);
|
|
return ret;
|
|
}
|
|
|
|
static void _do_flat_print(Arch * arch, unsigned long address,
|
|
char const * buffer, size_t size, ArchInstruction * ai)
|
|
{
|
|
size_t pos = ai->size;
|
|
size_t i;
|
|
int col;
|
|
ArchOperands operands;
|
|
unsigned int reg;
|
|
ArchRegister * ar;
|
|
char const * sep = " ";
|
|
unsigned long u;
|
|
size_t j;
|
|
size_t s;
|
|
|
|
col = printf(" %5lx:", address);
|
|
for(i = 0; i < size; i++)
|
|
col += printf(" %02x", (unsigned char)buffer[i]);
|
|
for(; col < 31; col++)
|
|
putchar(' ');
|
|
printf(" %s", ai->name);
|
|
for(i = 0, operands = ai->operands; operands > 0; i++, operands >>= 8)
|
|
{
|
|
s = (i == 0) ? ai->op1size : ((i == 1) ? ai->op2size
|
|
: ai->op3size);
|
|
if((operands & _AO_OP) == _AO_REG && s == 0)
|
|
{
|
|
reg = (operands & 0xff) >> 2;
|
|
if((ar = arch_register_get_by_id(arch, reg))
|
|
!= NULL)
|
|
printf("%s%%%s", sep, ar->name);
|
|
else
|
|
printf("%s%d", sep, reg);
|
|
sep = ", ";
|
|
}
|
|
else if((operands & _AO_OP) == _AO_REG)
|
|
{
|
|
for(j = 0, u = 0; j < s; j++)
|
|
u = (u << 8) | (unsigned char)buffer[pos++];
|
|
/* XXX fix endian */
|
|
if((ar = arch_register_get_by_id(arch, u)) != NULL)
|
|
printf("%s%%%s", sep, ar->name);
|
|
else
|
|
printf("%s%lu", sep, u);
|
|
sep = ", ";
|
|
}
|
|
else if((operands & _AO_OP) == _AO_DREG)
|
|
{
|
|
reg = (operands & 0xff) >> 2;
|
|
if((ar = arch_register_get_by_id(arch, reg))
|
|
!= NULL)
|
|
printf("%s(%%%s)", sep, ar->name);
|
|
else
|
|
printf("%s(%d)", sep, reg);
|
|
sep = ", ";
|
|
}
|
|
else if((operands & _AO_OP) == _AO_IMM)
|
|
{
|
|
for(j = 0, u = 0; j < s; j++)
|
|
u = (u << 8) | (unsigned char)buffer[pos++];
|
|
/* XXX fix endian */
|
|
printf("%s$0x%lx", sep, u);
|
|
sep = ", ";
|
|
}
|
|
}
|
|
putchar('\n');
|
|
}
|
|
|
|
|
|
/* disas_buffer */
|
|
static int _disas_buffer(char const * arch, char const * format,
|
|
char const * buffer, size_t size)
|
|
{
|
|
As * as;
|
|
Arch * a;
|
|
size_t pos;
|
|
size_t cnt;
|
|
ArchInstruction * ai;
|
|
|
|
if((as = as_new(arch, format)) == NULL)
|
|
return -1;
|
|
if((a = as_get_arch(as)) == NULL)
|
|
{
|
|
as_delete(as);
|
|
return -1;
|
|
}
|
|
for(pos = 0; pos < size; pos += cnt)
|
|
{
|
|
cnt = size - pos;
|
|
if((ai = as_decode(as, &buffer[pos], &cnt)) != NULL)
|
|
_do_flat_print(a, pos, &buffer[pos], cnt, ai);
|
|
else
|
|
cnt = 1;
|
|
}
|
|
as_delete(as);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* disas_string */
|
|
static int _disas_string(char const * arch, char const * format,
|
|
char const * string)
|
|
{
|
|
int ret;
|
|
unsigned char * str = (unsigned char *)string;
|
|
size_t len = strlen(string);
|
|
char * s;
|
|
size_t i;
|
|
size_t j;
|
|
|
|
if((s = malloc(len + 1)) == NULL)
|
|
return -_disas_error("string", 1);
|
|
for(i = 0, j = 0; i < len; i++)
|
|
{
|
|
if(str[i] != '\\')
|
|
s[j++] = str[i];
|
|
else if(str[i + 1] != 'x') /* "\\" */
|
|
s[j++] = str[++i];
|
|
else if(i + 3 < len && _ishex(str[i + 2])
|
|
&& _ishex(str[i + 3])) /* "\xHH" */
|
|
{
|
|
s[j++] = (_hex2bin(str[i + 2]) << 4)
|
|
| _hex2bin(str[i + 3]);
|
|
i += 3;
|
|
}
|
|
}
|
|
s[j] = '\0'; /* not really necessary */
|
|
ret = _disas_buffer(arch, format, s, j);
|
|
free(s);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* disas_list */
|
|
static int _disas_list(void)
|
|
{
|
|
Disas disas;
|
|
size_t i;
|
|
char const * sep = "";
|
|
|
|
memset(&disas, 0, sizeof(disas));
|
|
as_plugin_list(ASPT_ARCH);
|
|
_disas_format_open_all(&disas);
|
|
fputs("\nAvailable format plug-ins:\n", stderr);
|
|
for(i = 0; i < disas.format_cnt; i++)
|
|
{
|
|
if(disas.format[i].format->disas == NULL)
|
|
continue;
|
|
fprintf(stderr, "%s%s", sep, disas.format[i].format->name);
|
|
sep = ", ";
|
|
}
|
|
fputc('\n', stderr);
|
|
_disas_format_close_all(&disas);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* disas_error */
|
|
static int _disas_error(char const * message, int ret)
|
|
{
|
|
fputs("disas: ", stderr);
|
|
perror(message);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* format plug-ins */
|
|
/* disas_format_open */
|
|
static DisasFormat const * _disas_format_open(Disas * disas,
|
|
char const * format)
|
|
{
|
|
DisasFormat * p;
|
|
|
|
if((p = realloc(disas->format, sizeof(*p) * (disas->format_cnt + 1)))
|
|
== NULL)
|
|
{
|
|
error_set_code(1, "%s", strerror(errno));
|
|
return NULL;
|
|
}
|
|
disas->format = p;
|
|
p = &disas->format[disas->format_cnt];
|
|
if((p->plugin = plugin_new(LIBDIR, PACKAGE, "format", format)) == NULL)
|
|
return NULL;
|
|
if((p->format = plugin_lookup(p->plugin, "format_plugin")) == NULL)
|
|
{
|
|
plugin_delete(p->plugin);
|
|
return NULL;
|
|
}
|
|
p->format->helper = &disas->helper;
|
|
return &disas->format[disas->format_cnt++];
|
|
}
|
|
|
|
|
|
/* disas_format_open_all */
|
|
static int _disas_format_open_all(Disas * disas)
|
|
{
|
|
char const path[] = LIBDIR "/" PACKAGE "/format";
|
|
DIR * dir;
|
|
struct dirent * de;
|
|
size_t len;
|
|
char const ext[] = ".so";
|
|
|
|
if((dir = opendir(path)) == NULL)
|
|
return -error_set_print("disas", 1, "%s: %s", path, strerror(
|
|
errno));
|
|
while((de = readdir(dir)) != NULL)
|
|
{
|
|
if((len = strlen(de->d_name)) < 4)
|
|
continue;
|
|
if(strcmp(&de->d_name[len - sizeof(ext) + 1], ext) != 0)
|
|
continue;
|
|
de->d_name[len - sizeof(ext) + 1] = '\0';
|
|
_disas_format_open(disas, de->d_name);
|
|
}
|
|
closedir(dir);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* disas_format_init */
|
|
static int _disas_format_init(DisasFormat const * format, char const * arch)
|
|
{
|
|
if(format->format->init == NULL)
|
|
return 0;
|
|
return format->format->init(format->format, arch);
|
|
}
|
|
|
|
|
|
/* disas_format_exit */
|
|
static int _disas_format_exit(DisasFormat const * format)
|
|
{
|
|
if(format->format->exit == NULL)
|
|
return 0;
|
|
return format->format->exit(format->format);
|
|
}
|
|
|
|
|
|
/* disas_format_close */
|
|
static void _disas_format_close(DisasFormat const * format)
|
|
{
|
|
plugin_delete(format->plugin);
|
|
}
|
|
|
|
|
|
/* disas_format_close_all */
|
|
static void _disas_format_close_all(Disas * disas)
|
|
{
|
|
size_t i;
|
|
|
|
for(i = 0; i < disas->format_cnt; i++)
|
|
_disas_format_close(&disas->format[i]);
|
|
disas->format_cnt = 0;
|
|
}
|
|
|
|
|
|
/* callbacks */
|
|
static int _disas_format_callback(FormatPlugin * format, char const * section,
|
|
off_t offset, size_t size, off_t base)
|
|
{
|
|
Disas * disas = format->helper->priv;
|
|
|
|
if(section != NULL)
|
|
printf("\nDisassembly of section %s:\n", section);
|
|
return _do_flat(disas, offset, size, base);
|
|
}
|
|
|
|
|
|
/* helpers */
|
|
/* hex2bin */
|
|
static int _hex2bin(int c)
|
|
{
|
|
if(c >= '0' && c <= '9')
|
|
return c - '0';
|
|
if(c >= 'a' && c <= 'f')
|
|
return c - 'a' + 10;
|
|
if(c >= 'A' && c <= 'F')
|
|
return c - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* ishex */
|
|
static int _ishex(int c)
|
|
{
|
|
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
|
|
|| (c >= 'A' || c <= 'F');
|
|
}
|
|
|
|
|
|
/* usage */
|
|
static int _usage(void)
|
|
{
|
|
fputs("Usage: disas [-a arch][-f format] filename\n"
|
|
" disas [-a arch][-f format] -s string\n"
|
|
" disas -l\n", stderr);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* main */
|
|
int main(int argc, char * argv[])
|
|
{
|
|
int o;
|
|
char const * arch = NULL;
|
|
char const * format = NULL;
|
|
char const * string = NULL;
|
|
|
|
while((o = getopt(argc, argv, "a:f:ls:")) != -1)
|
|
switch(o)
|
|
{
|
|
case 'a':
|
|
arch = optarg;
|
|
break;
|
|
case 'f':
|
|
format = optarg;
|
|
break;
|
|
case 'l':
|
|
return _disas_list();
|
|
case 's':
|
|
string = optarg;
|
|
break;
|
|
default:
|
|
return _usage();
|
|
}
|
|
if(optind == argc && string != NULL)
|
|
return _disas_string(arch, format, string);
|
|
else if(optind + 1 == argc && string == NULL)
|
|
return (_disas(arch, format, argv[optind])) == 0 ? 0 : 2;
|
|
else
|
|
return _usage();
|
|
}
|