blob: a9ee66c189daafb52faaecfde873b9a762c1d6b1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013-2020 Xilinx, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* Xilinx - initial API and implementation
*******************************************************************************/
#include <tcf/config.h>
#if SERVICE_Disassembly
#include <stdio.h>
#include <assert.h>
#include <tcf/framework/json.h>
#include <tcf/framework/context.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/cache.h>
#include <tcf/framework/trace.h>
#include <tcf/services/runctrl.h>
#include <tcf/services/symbols.h>
#include <tcf/services/linenumbers.h>
#include <tcf/services/memorymap.h>
#include <tcf/services/disassembly.h>
#define MAX_INSTRUCTION_SIZE 8
#define DEFAULT_ALIGMENT 16
typedef struct {
const char * isa;
Disassembler * disassembler;
} DisassemblerInfo;
typedef struct {
DisassemblerInfo * disassemblers;
unsigned disassemblers_cnt;
unsigned disassemblers_max;
} ContextExtensionDS;
typedef struct {
char token[256];
char id[256];
ContextAddress addr;
ContextAddress size;
char * isa;
int simplified;
int pseudo_instr;
int opcode_value;
} DisassembleCmdArgs;
typedef struct {
char token[256];
char id[256];
} GetCapabilitiesCmdArgs;
static const char * DISASSEMBLY = "Disassembly";
static size_t context_extension_offset = 0;
#define EXT(ctx) (ctx ? ((ContextExtensionDS *)((char *)(ctx) + context_extension_offset)) : NULL)
static DisassemblerInfo * find_disassembler_info(Context * ctx, const char * isa) {
if (isa != NULL) {
unsigned i = 0;
ContextExtensionDS * ext = EXT(ctx);
while (i < ext->disassemblers_cnt) {
if (strcmp(ext->disassemblers[i].isa, isa) == 0)
return ext->disassemblers + i;
i++;
}
}
return NULL;
}
Disassembler * find_disassembler(Context * ctx, const char * isa) {
DisassemblerInfo * i = find_disassembler_info(ctx, isa);
return i ? i->disassembler : NULL;
}
void add_disassembler(Context * ctx, const char * isa, Disassembler disassembler) {
DisassemblerInfo * i = NULL;
ContextExtensionDS * ext = EXT(ctx);
assert(ctx == context_get_group(ctx, CONTEXT_GROUP_CPU));
if ((i = find_disassembler_info(ctx, isa)) == NULL) {
if (ext->disassemblers_cnt >= ext->disassemblers_max) {
ext->disassemblers_max += 8;
ext->disassemblers = (DisassemblerInfo *)loc_realloc(ext->disassemblers,
sizeof(DisassemblerInfo) * ext->disassemblers_max);
}
i = ext->disassemblers + ext->disassemblers_cnt++;
i->isa = loc_strdup(isa);
}
i->disassembler = disassembler;
}
static void command_get_capabilities_cache_client(void * x) {
int error = 0;
Context * ctx = NULL;
ContextExtensionDS * ext = NULL;
GetCapabilitiesCmdArgs * args = (GetCapabilitiesCmdArgs *)x;
Channel * c = cache_channel();
ctx = id2ctx(args->id);
if (ctx == NULL) error = ERR_INV_CONTEXT;
else if (ctx->exited) error = ERR_ALREADY_EXITED;
else ext = EXT(context_get_group(ctx, CONTEXT_GROUP_CPU));
cache_exit();
if (!is_channel_closed(c)) {
OutputStream * out = &c->out;
write_stringz(out, "R");
write_stringz(out, args->token);
write_errno(out, error);
write_stream(out, '[');
if (ext != NULL) {
unsigned i;
for (i = 0; i < ext->disassemblers_cnt; i++) {
if (i > 0) write_stream(out, ',');
write_stream(out, '{');
json_write_string(out, "ISA");
write_stream(out, ':');
json_write_string(out, ext->disassemblers[i].isa);
write_stream(out, '}');
}
}
write_stream(out, ']');
write_stream(out, 0);
write_stream(out, MARKER_EOM);
}
}
static void command_get_capabilities(char * token, Channel * c) {
GetCapabilitiesCmdArgs args;
json_read_string(&c->inp, args.id, sizeof(args.id));
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
strlcpy(args.token, token, sizeof(args.token));
cache_enter(command_get_capabilities_cache_client, c, &args, sizeof(args));
}
int get_disassembler_isa(Context * ctx, ContextAddress addr, ContextISA * isa) {
if (context_get_isa(ctx, addr, isa) < 0) {
memset(isa, 0, sizeof(ContextISA));
return -1;
}
#if ENABLE_MemoryMap
if (isa->size == 0) {
unsigned i, j;
MemoryMap * client_map = NULL;
MemoryMap * target_map = NULL;
Context * mem = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
if (memory_map_get(mem, &client_map, &target_map) == 0) {
isa->addr = addr;
isa->size = ~addr + 1;
for (j = 0; j < 2; j++) {
MemoryMap * map = j ? target_map : client_map;
for (i = 0; i < map->region_cnt; i++) {
MemoryRegion * r = map->regions + i;
ContextAddress x = r->addr;
ContextAddress y = r->addr + r->size;
if (x > addr && (addr + isa->size == 0 || x < addr + isa->size)) isa->size = x - addr;
if (y > addr && (addr + isa->size == 0 || y < addr + isa->size)) isa->size = y - addr;
}
}
}
}
#endif
return 0;
}
static int disassemble_block(Context * ctx, OutputStream * out, uint8_t * mem_buf,
ContextAddress buf_addr, ContextAddress buf_size,
ContextAddress mem_size, ContextISA * isa,
DisassembleCmdArgs * args) {
ContextAddress offs = 0;
Disassembler * disassembler = NULL;
Context * cpu = context_get_group(ctx, CONTEXT_GROUP_CPU);
DisassemblerParams params;
int disassembler_ok = 0;
memset(&params, 0, sizeof(DisassemblerParams));
params.ctx = ctx;
params.big_endian = ctx->big_endian;
params.pseudo_instr = args->pseudo_instr;
params.simplified = args->simplified;
if (args->isa) {
isa->isa = args->isa;
isa->addr = args->addr;
isa->size = args->size;
}
write_stream(out, '[');
while (offs < buf_size && offs < mem_size) {
ContextAddress addr = buf_addr + offs;
ContextAddress size = mem_size - offs;
DisassemblyResult * dr = NULL;
if (args->isa == NULL && (addr < isa->addr ||
(isa->addr + isa->size >= isa->addr && addr >= isa->addr + isa->size))) {
if (get_disassembler_isa(ctx, addr, isa) < 0) return -1;
disassembler_ok = 0;
}
if (!disassembler_ok) {
if (isa->isa != NULL) disassembler = find_disassembler(cpu, isa->isa);
else disassembler = find_disassembler(cpu, isa->def);
disassembler_ok = 1;
}
if (disassembler) dr = disassembler(mem_buf + (size_t)offs, addr, size, &params);
if (dr == NULL) {
static char buf[32];
static DisassemblyResult dd;
memset(&dd, 0, sizeof(dd));
if (isa->alignment >= 4 && (addr & 0x3) == 0 && offs <= mem_size + 4) {
unsigned i;
unsigned v = 0;
for (i = 0; i < 4; i++) v |= (unsigned)mem_buf[offs + i] << (i * 8);
snprintf(buf, sizeof(buf), ".word 0x%08x", v);
dd.size = 4;
}
else if (isa->alignment >= 2 && (addr & 0x1) == 0 && offs <= mem_size + 2) {
unsigned i;
uint16_t v = 0;
for (i = 0; i < 2; i++) v |= (uint16_t)mem_buf[offs + i] << (i * 8);
snprintf(buf, sizeof(buf), ".half 0x%04x", v);
dd.size = 2;
}
else {
snprintf(buf, sizeof(buf), ".byte 0x%02x", mem_buf[offs]);
dd.size = 1;
}
dd.text = buf;
dr = &dd;
}
assert(dr->size > 0);
if (offs > 0) write_stream(out, ',');
write_stream(out, '{');
json_write_string(out, "Address");
write_stream(out, ':');
json_write_uint64(out, addr);
write_stream(out, ',');
json_write_string(out, "Size");
write_stream(out, ':');
json_write_uint64(out, dr->size);
write_stream(out, ',');
json_write_string(out, "Instruction");
write_stream(out, ':');
write_stream(out, '[');
write_stream(out, '{');
json_write_string(out, "Type");
write_stream(out, ':');
json_write_string(out, "String");
write_stream(out, ',');
json_write_string(out, "Text");
write_stream(out, ':');
json_write_string(out, dr->text);
write_stream(out, '}');
write_stream(out, ']');
if (args->opcode_value) {
write_stream(out, ',');
json_write_string(out, "OpcodeValue");
write_stream(out, ':');
json_write_binary(out, mem_buf + (size_t)offs, (size_t)dr->size);
}
write_stream(out, '}');
offs += dr->size;
}
write_stream(out, ']');
loc_free(params.state);
return 0;
}
#if ENABLE_LineNumbers
static void address_to_line_cb(CodeArea * area, void * args) {
CodeArea ** p = (CodeArea **)args;
if (*p == NULL || (*p)->start_address < area->start_address) {
*p = (CodeArea *)tmp_alloc(sizeof(CodeArea));
**p = *area;
}
}
#endif
static void disassemble_cache_client(void * x) {
DisassembleCmdArgs * args = (DisassembleCmdArgs *)x;
int error = 0;
Context * ctx = NULL;
uint8_t * mem_buf = NULL;
ContextAddress buf_addr = 0;
ContextAddress buf_size = 0;
size_t mem_size = 0;
ByteArrayOutputStream buf;
OutputStream * buf_out = create_byte_array_output_stream(&buf);
Channel * c = cache_channel();
char * data = NULL;
size_t size = 0;
ContextISA isa;
memset(&isa, 0, sizeof(isa));
ctx = id2ctx(args->id);
if (ctx == NULL) error = ERR_INV_CONTEXT;
else if (ctx->exited) error = ERR_ALREADY_EXITED;
trace(LOG_DISASM, "%s: ctx %s address 0x%16" PRIx64 " size %" PRId64 " ISA %s",
DISASSEMBLY, ctx->id, (uint64_t)args->addr, (uint64_t)args->size,
args->isa == NULL ? "(null)" : args->isa);
if (!error) {
Context * mem = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
if ((mem->mem_access & MEM_ACCESS_RD_STOP) != 0) {
check_all_stopped(ctx);
}
if ((mem->mem_access & MEM_ACCESS_RD_RUNNING) == 0) {
if (!is_all_stopped(ctx)) error = set_errno(errno, "Cannot read memory if not stopped");
}
}
if (!error) {
ContextAddress sym_addr = 0;
ContextAddress sym_size = 0;
int sym_addr_ok = 0;
int sym_size_ok = 0;
#if ENABLE_Symbols
{
Symbol * sym = NULL;
if (find_symbol_by_addr(ctx, STACK_NO_FRAME, args->addr, &sym) == 0) {
if (get_symbol_address(sym, &sym_addr) == 0) sym_addr_ok = 1;
if (get_symbol_size(sym, &sym_size) == 0) sym_size_ok = 1;
}
if (sym_addr_ok && sym_addr <= args->addr && args->addr - sym_addr >= 0x1000) {
sym_addr_ok = 0;
sym_size_ok = 0;
}
}
#endif
#if ENABLE_LineNumbers
if (!sym_addr_ok || !sym_size_ok) {
CodeArea * area = NULL;
address_to_line(ctx, args->addr, args->addr + 1, address_to_line_cb, &area);
if (area != NULL) {
sym_addr = area->start_address;
sym_size = area->end_address - area->start_address;
sym_addr_ok = 1;
sym_size_ok = 1;
}
}
#endif
if (sym_addr_ok && sym_addr <= args->addr) {
if (get_disassembler_isa(ctx, sym_addr, &isa) < 0) {
error = errno;
}
else {
buf_addr = sym_addr;
buf_size = args->addr + args->size - buf_addr;
if (isa.max_instruction_size > 0) {
mem_size = (size_t)(buf_size + isa.max_instruction_size);
}
else {
mem_size = (size_t)(buf_size + MAX_INSTRUCTION_SIZE);
}
}
}
else {
/* Use default address alignment */
if (get_disassembler_isa(ctx, args->addr, &isa) < 0) {
error = errno;
}
else {
if (isa.alignment > 0) {
buf_addr = args->addr & ~(ContextAddress)(isa.alignment - 1);
}
else {
buf_addr = args->addr & ~(ContextAddress)(DEFAULT_ALIGMENT - 1);
}
buf_size = args->addr + args->size - buf_addr;
if (isa.max_instruction_size > 0) {
mem_size = (size_t)(buf_size + isa.max_instruction_size);
}
else {
mem_size = (size_t)(buf_size + MAX_INSTRUCTION_SIZE);
}
}
}
while (!error) {
if (sym_addr_ok && sym_size_ok &&
sym_addr <= buf_addr && sym_addr + sym_size > buf_addr &&
sym_addr + sym_size <= buf_addr + buf_size) {
buf_size = sym_addr + sym_size - buf_addr;
mem_size = (size_t)buf_size;
}
mem_buf = (uint8_t *)tmp_alloc(mem_size);
if (context_read_mem(ctx, buf_addr, mem_buf, mem_size) == 0) break;
error = errno;
#if ENABLE_ExtendedMemoryErrorReports
{
MemoryErrorInfo info;
if (context_get_mem_error_info(&info) == 0) {
ContextAddress addr_valid = buf_addr + info.size_valid;
ContextAddress addr_error = addr_valid + info.size_error;
if (addr_valid < buf_addr || args->addr < addr_valid) {
mem_size = info.size_valid;
error = 0;
break;
}
if (addr_error < buf_addr || args->addr < addr_error) break;
buf_addr = addr_error;
buf_size = args->addr + args->size - buf_addr;
if (get_disassembler_isa(ctx, buf_addr, &isa) < 0) {
error = errno;
break;
}
if (isa.max_instruction_size > 0) {
mem_size = (size_t)(buf_size + isa.max_instruction_size);
}
else {
mem_size = (size_t)(buf_size + MAX_INSTRUCTION_SIZE);
}
/* Continue after unreadable range */
error = 0;
}
}
#endif
}
}
if (!error && disassemble_block(
ctx, buf_out, mem_buf, buf_addr, buf_size,
mem_size, &isa, args) < 0) error = errno;
if (get_error_code(error) == ERR_CACHE_MISS) {
loc_free(buf.mem);
buf.mem = NULL;
buf.max = 0;
buf.pos = 0;
}
cache_exit();
get_byte_array_output_stream_data(&buf, &data, &size);
if (!is_channel_closed(c)) {
OutputStream * out = &c->out;
write_stringz(out, "R");
write_stringz(out, args->token);
write_errno(out, error);
if (size > 0) {
write_block_stream(out, data, size);
}
else {
write_string(out, "null");
}
write_stream(out, 0);
write_stream(out, MARKER_EOM);
}
loc_free(data);
run_ctrl_unlock();
}
static void read_disassembly_params(InputStream * inp, const char * name, void * x) {
DisassembleCmdArgs * args = (DisassembleCmdArgs *) x;
if (strcmp(name, "ISA") == 0) {
args->isa = json_read_alloc_string(inp);
}
else if (strcmp(name, "Simplified") == 0) {
args->simplified = json_read_boolean(inp);
}
else if (strcmp(name, "Pseudo") == 0) {
args->pseudo_instr = json_read_boolean(inp);
}
else if (strcmp(name, "OpcodeValue") == 0) {
args->opcode_value = json_read_boolean(inp);
}
else {
json_skip_object(inp);
}
}
static void command_disassemble(char * token, Channel * c) {
DisassembleCmdArgs args;
memset(&args, 0, sizeof(args));
json_read_string(&c->inp, args.id, sizeof(args.id));
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
args.addr = (ContextAddress)json_read_uint64(&c->inp);
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
args.size = (ContextAddress)json_read_uint64(&c->inp);
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
json_read_struct(&c->inp, read_disassembly_params, &args);
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
run_ctrl_lock();
strlcpy(args.token, token, sizeof(args.token));
cache_enter(disassemble_cache_client, c, &args, sizeof(DisassembleCmdArgs));
}
static void event_context_disposed(Context * ctx, void * args) {
unsigned i;
ContextExtensionDS * ext = EXT(ctx);
for (i = 0; i < ext->disassemblers_cnt; i++) {
loc_free(ext->disassemblers[i].isa);
}
loc_free(ext->disassemblers);
}
void ini_disassembly_service(Protocol * proto) {
static ContextEventListener listener = {
NULL,
NULL,
NULL,
NULL,
NULL,
event_context_disposed
};
if (context_extension_offset == 0) {
add_context_event_listener(&listener, NULL);
context_extension_offset = context_extension(sizeof(ContextExtensionDS));
}
add_command_handler(proto, DISASSEMBLY, "getCapabilities", command_get_capabilities);
add_command_handler(proto, DISASSEMBLY, "disassemble", command_disassemble);
}
#endif /* SERVICE_Disassembly */