diff --git a/mspdebug.man b/mspdebug.man index e0af7ac..801b1df 100644 --- a/mspdebug.man +++ b/mspdebug.man @@ -106,6 +106,18 @@ question. See the section marked \fBADDRESS EXPRESSIONS\fR for more information on the syntax of expressions. +.IP "\fBcgraph\fR \fIaddress\fR \fIlength\fR [\fIaddress\fR]" +Construct the call graph of all functions contained or referenced in +the given range of memory. If a particular function is specified, then +details for that node of the graph are displayed. Otherwise, a summary +of all nodes is displayed. + +Information from the symbol table is used for hinting at the possible +locations of function starts. Any symbol which does not contain a "." +is considered a possible function start. + +Callers and callee names are shown prefixed by a "*" where the +transition is a tail-call type transition. .IP "\fBdis\fR \fIaddress\fR [\fIlength\fR]" Dissassemble a section of memory. Both arguments may be address expressions. If no length is specified, a section of the default diff --git a/rtools.c b/rtools.c index 43003b9..a0071fa 100644 --- a/rtools.c +++ b/rtools.c @@ -29,6 +29,11 @@ #include "stab.h" #include "expr.h" #include "cproc_util.h" +#include "vector.h" + +/************************************************************************ + * Instruction search ("isearch") + */ #define ISEARCH_OPCODE 0x0001 #define ISEARCH_BW 0x0002 @@ -423,27 +428,570 @@ static int cmd_isearch(cproc_t cp, char **arg) return do_isearch(cp, addr, len, &q); } -static const struct cproc_command isearch_command = { - .name = "isearch", - .func = cmd_isearch, - .help = - "isearch
[options ...]\n" - " Search for an instruction matching certain search terms. These\n" - " terms may be any of the following:\n" - " opcode \n" - " byte|word\n" - " jump|single|double|noarg\n" - " src \n" - " dst \n" - " srcreg \n" - " dstreg \n" - " srcmode R|I|S|&|@|+|#\n" - " dstmode R|I|S|&|@|+|#\n" - " For single-operand instructions, the operand is considered the\n" - " destination operand.\n" +/************************************************************************ + * Call graph ("cgraph") + */ + +struct cg_edge { + int is_tail_call; + + uint16_t src; + uint16_t dst; +}; + +static int cmp_branch_by_dst(const void *a, const void *b) +{ + const struct cg_edge *br_a = (const struct cg_edge *)a; + const struct cg_edge *br_b = (const struct cg_edge *)b; + + if (br_a->dst < br_b->dst) + return -1; + if (br_a->dst > br_b->dst) + return 1; + + if (br_a->src < br_b->src) + return -1; + if (br_a->src > br_b->src) + return 1; + + if (!br_a->is_tail_call && br_b->is_tail_call) + return -1; + if (br_a->is_tail_call && !br_b->is_tail_call) + return 1; + + return 0; +} + +static int cmp_branch_by_src(const void *a, const void *b) +{ + const struct cg_edge *br_a = (const struct cg_edge *)a; + const struct cg_edge *br_b = (const struct cg_edge *)b; + + if (br_a->src < br_b->src) + return -1; + if (br_a->src > br_b->src) + return 1; + + if (br_a->dst < br_b->dst) + return -1; + if (br_a->dst > br_b->dst) + return 1; + + if (!br_a->is_tail_call && br_b->is_tail_call) + return -1; + if (br_a->is_tail_call && !br_b->is_tail_call) + return 1; + + return 0; +} + +struct cg_node { + uint16_t offset; +}; + +static int cmp_node(const void *a, const void *b) +{ + const struct cg_node *na = (const struct cg_node *)a; + const struct cg_node *nb = (const struct cg_node *)b; + + if (na->offset < nb->offset) + return -1; + if (na->offset > nb->offset) + return 1; + + return 0; +} + +struct call_graph { + int offset; + int len; + + struct vector edge_to; + struct vector edge_from; + struct vector node_list; +}; + +#define CG_NODE(g, i) (VECTOR_PTR((g)->node_list, (i), struct cg_node)) +#define CG_EDGE_FROM(g, i) (VECTOR_PTR((g)->edge_from, (i), struct cg_edge)) +#define CG_EDGE_TO(g, i) (VECTOR_PTR((g)->edge_to, (i), struct cg_edge)) + +static void cgraph_destroy(struct call_graph *graph) +{ + vector_destroy(&graph->edge_to); + vector_destroy(&graph->edge_from); + vector_destroy(&graph->node_list); +} + +static int find_possible_edges(int offset, int len, uint8_t *memory, + struct call_graph *graph) +{ + int i; + + for (i = 0; i < len; i += 2) { + struct msp430_instruction insn; + + if (dis_decode(memory + i, offset + i, len - i, &insn) < 0) + continue; + + if (insn.dst_mode == MSP430_AMODE_IMMEDIATE && + (insn.op == MSP430_OP_CALL || insn.op == MSP430_OP_BR) && + !(insn.dst_addr & 1)) { + struct cg_edge br; + + br.src = offset + i; + br.dst = insn.dst_addr; + br.is_tail_call = insn.op != MSP430_OP_CALL; + + if (vector_push(&graph->edge_from, &br, 1) < 0) + return -1; + } + } + + return 0; +} + +static int add_nodes_from_edges(struct call_graph *graph) +{ + int i; + uint16_t last_addr; + int have_last_addr = 0; + + qsort(graph->edge_from.ptr, graph->edge_from.size, + graph->edge_from.elemsize, cmp_branch_by_dst); + + /* Look for unique destination addresses */ + for (i = 0; i < graph->edge_from.size; i++) { + const struct cg_edge *br = CG_EDGE_FROM(graph, i); + + if (!have_last_addr || + br->dst != last_addr) { + struct cg_node n; + + n.offset = br->dst; + + last_addr = br->dst; + have_last_addr = 1; + + if (vector_push(&graph->node_list, &n, 1) < 0) + return -1; + } + } + + return 0; +} + +static void relabel_sources(struct call_graph *graph) +{ + int i = 0; /* Node index */ + int j = 0; /* Edge index */ + + /* Identify the source nodes for each edge */ + qsort(graph->edge_from.ptr, graph->edge_from.size, + graph->edge_from.elemsize, cmp_branch_by_src); + + while (j < graph->edge_from.size) { + struct cg_edge *br = CG_EDGE_FROM(graph, j); + struct cg_node *n; + + /* Skip over nodes which are too early for this edge */ + while (i + 1 < graph->node_list.size && + CG_NODE(graph, i + 1)->offset <= br->src) + i++; + + n = CG_NODE(graph, i); + if (n->offset <= br->src) + br->src = n->offset; + j++; + } +} + +static void remove_duplicate_nodes(struct call_graph *graph) +{ + int i = 0; + int j = 0; + + qsort(graph->node_list.ptr, graph->node_list.size, + graph->node_list.elemsize, cmp_node); + + while (i < graph->node_list.size) { + struct cg_node *n = CG_NODE(graph, i); + struct cg_node *l = CG_NODE(graph, j - 1); + + if (!j || n->offset != l->offset) { + if (i != j) + memcpy(l + 1, n, sizeof(*l)); + j++; + } + + i++; + } + + graph->node_list.size = j; +} + +static void remove_duplicate_edges(struct call_graph *graph) +{ + int i = 0; /* Source index */ + int j = 0; /* Destination index */ + + qsort(graph->edge_from.ptr, graph->edge_from.size, + graph->edge_from.elemsize, cmp_branch_by_src); + + while (i < graph->edge_from.size) { + struct cg_edge *e = CG_EDGE_FROM(graph, i); + struct cg_edge *l = CG_EDGE_FROM(graph, j - 1); + + if (!j || + l->src != e->src || + l->dst != e->dst || + l->is_tail_call != e->is_tail_call) { + if (i != j) + memcpy(l + 1, e, sizeof(*l)); + j++; + } + + i++; + } + + graph->edge_from.size = j; +} + +static int build_inverse(struct call_graph *graph) +{ + graph->edge_to.size = 0; + + if (vector_push(&graph->edge_to, graph->edge_from.ptr, + graph->edge_from.size) < 0) + return -1; + + qsort(graph->edge_to.ptr, graph->edge_to.size, + graph->edge_to.elemsize, cmp_branch_by_dst); + + return 0; +} + +static int add_irq_edges(int offset, int len, uint8_t *memory, + struct call_graph *graph) +{ + int i; + + if (offset < 0xffe0) { + len -= (0xffe0 - offset); + memory += (0xffe0 - offset); + offset = 0xffe0; + } + + if (offset + len > 0x10000) + len = 0x10000 - offset; + + if (offset & 1) { + offset++; + memory++; + len--; + } + + for (i = 0; i < len; i += 2) { + struct cg_edge br; + + br.src = offset + i; + br.dst = ((uint16_t)memory[i]) | + (((uint16_t)memory[i + 1]) << 8); + br.is_tail_call = 0; + + if (vector_push(&graph->edge_from, &br, 1) < 0) + return -1; + } + + return 0; +} + +static int add_symbol_nodes(void *user_data, const char *name, + uint16_t offset) +{ + struct call_graph *graph = (struct call_graph *)user_data; + + while (*name) { + if (*name == '.') + return 0; + name++; + } + + if (offset > graph->offset && + offset <= graph->offset + graph->len) { + struct cg_node n; + + n.offset = offset; + return vector_push(&graph->node_list, &n, 1); + } + + return 0; +} + +static int cgraph_init(int offset, int len, uint8_t *memory, + struct call_graph *graph, stab_t stab) +{ + vector_init(&graph->edge_to, sizeof(struct cg_edge)); + vector_init(&graph->edge_from, sizeof(struct cg_edge)); + vector_init(&graph->node_list, sizeof(struct cg_node)); + + graph->offset = offset; + graph->len = len; + + if (find_possible_edges(offset, len, memory, graph) < 0) + goto fail; + if (add_irq_edges(offset, len, memory, graph) < 0) + goto fail; + + if (stab_enum(stab, add_symbol_nodes, graph) < 0) + goto fail; + if (add_nodes_from_edges(graph) < 0) + goto fail; + remove_duplicate_nodes(graph); + + relabel_sources(graph); + remove_duplicate_edges(graph); + + if (build_inverse(graph) < 0) + goto fail; + + return 0; + + fail: + cgraph_destroy(graph); + return -1; +} + +static void cgraph_summary(struct call_graph *graph, cproc_t cp) +{ + stab_t stab = cproc_stab(cp); + int i; + int j = 0; /* Edge from index */ + int k = 0; /* Edge to index */ + + for (i = 0; i < graph->node_list.size; i++) { + struct cg_node *n = CG_NODE(graph, i); + int from_count = 0; + int to_count = 0; + char name[64]; + uint16_t o; + + while (j < graph->edge_from.size && + CG_EDGE_FROM(graph, j)->src < n->offset) + j++; + + while (k < graph->edge_to.size && + CG_EDGE_TO(graph, k)->dst < n->offset) + k++; + + while (j < graph->edge_from.size && + CG_EDGE_FROM(graph, j)->src == n->offset) { + from_count++; + j++; + } + + while (k < graph->edge_to.size && + CG_EDGE_TO(graph, k)->dst == n->offset) { + to_count++; + k++; + } + + if (stab_nearest(stab, n->offset, name, sizeof(name), &o) || + o) + name[0] = 0; + + printf("0x%04x [%3d ==> %3d] %s\n", + n->offset, to_count, from_count, name); + } +} + +static void cgraph_func_info(struct call_graph *graph, cproc_t cp, + int addr) +{ + stab_t stab = cproc_stab(cp); + int i = 0; + int j = 0; + int k = 0; + char name[64]; + u_int16_t offset; + struct cg_node *n; + + while (i < graph->node_list.size && + CG_NODE(graph, i)->offset < addr) + i++; + if (i >= graph->node_list.size) { + printf("No information for address 0x%04x\n", addr); + return; + } + + n = CG_NODE(graph, i); + + while (j < graph->edge_from.size && + CG_EDGE_FROM(graph, j)->src < n->offset) + j++; + + while (k < graph->edge_to.size && + CG_EDGE_TO(graph, k)->dst < n->offset) + k++; + + if (stab_nearest(stab, n->offset, name, sizeof(name), &offset)) + printf("0x%04x:\n", n->offset); + else if (offset) + printf("0x%04x %s+0x%x:\n", n->offset, name, offset); + else + printf("0x%04x %s:\n", n->offset, name); + + if (j < graph->edge_from.size && + CG_EDGE_FROM(graph, j)->src == n->offset) { + printf(" Callees:\n"); + while (j < graph->edge_from.size) { + struct cg_edge *e = CG_EDGE_FROM(graph, j); + + if (e->src != n->offset) + break; + + if (stab_nearest(stab, e->dst, name, sizeof(name), + &offset) || + offset) + snprintf(name, sizeof(name), "0x%04x", e->dst); + printf(" %s%s\n", + e->is_tail_call ? "*" : "", name); + + j++; + } + printf("\n"); + } + + if (k < graph->edge_to.size && + CG_EDGE_TO(graph, k)->dst == n->offset) { + printf(" Callers:\n"); + while (k < graph->edge_to.size) { + struct cg_edge *e = CG_EDGE_TO(graph, k); + + if (e->dst != n->offset) + break; + + if (stab_nearest(stab, e->src, name, sizeof(name), + &offset) || + offset) + snprintf(name, sizeof(name), "0x%04x", e->src); + printf(" %s%s\n", + e->is_tail_call ? "*" : "", name); + + k++; + } + } +} + +static int cmd_cgraph(cproc_t cp, char **arg) +{ + stab_t stab = cproc_stab(cp); + device_t dev = cproc_device(cp); + char *offset_text, *len_text, *addr_text;; + int offset, len, addr; + uint8_t *memory; + struct call_graph graph; + + /* Figure out what the arguments are */ + offset_text = get_arg(arg); + len_text = get_arg(arg); + addr_text = get_arg(arg); + + if (!(offset_text && len_text)) { + fprintf(stderr, "cgraph: offset and length must be " + "specified\n"); + return -1; + } + + if (expr_eval(stab, offset_text, &offset) < 0) { + fprintf(stderr, "cgraph: invalid offset: %s\n", offset_text); + return -1; + } + offset &= ~1; + + if (expr_eval(stab, len_text, &len) < 0) { + fprintf(stderr, "cgraph: invalid length: %s\n", len_text); + return -1; + } + len &= ~1; + + if (addr_text && expr_eval(stab, addr_text, &addr) < 0) { + fprintf(stderr, "cgraph: invalid address: %s\n", addr_text); + return -1; + } + + if (offset < 0 || offset >= 0x10000 || + len <= 0 || (offset + len) > 0x10000) { + fprintf(stderr, "cgraph: invalid range\n"); + return -1; + } + + /* Grab the memory to be analysed */ + memory = malloc(len); + if (!memory) { + fprintf(stderr, "cgraph: couldn't allocate memory: %s\n", + strerror(errno)); + return -1; + } + + if (dev->readmem(dev, offset, memory, len) < 0) { + fprintf(stderr, "cgraph: couldn't fetch memory\n"); + free(memory); + return -1; + } + + /* Produce and display the call graph */ + if (cgraph_init(offset, len, memory, &graph, cproc_stab(cp)) < 0) { + fprintf(stderr, "cgraph: couldn't build call graph\n"); + free(memory); + return -1; + } + free(memory); + + if (addr_text) + cgraph_func_info(&graph, cp, addr); + else + cgraph_summary(&graph, cp); + + cgraph_destroy(&graph); + return 0; +} + +/************************************************************************ + * Setup and registration + */ + +static const struct cproc_command rtools_commands[] = { + { + .name = "isearch", + .func = cmd_isearch, + .help = +"isearch
[options ...]\n" +" Search for an instruction matching certain search terms. These\n" +" terms may be any of the following:\n" +" opcode \n" +" byte|word\n" +" jump|single|double|noarg\n" +" src \n" +" dst \n" +" srcreg \n" +" dstreg \n" +" srcmode R|I|S|&|@|+|#\n" +" dstmode R|I|S|&|@|+|#\n" +" For single-operand instructions, the operand is considered the\n" +" destination operand.\n" + }, + { + .name = "cgraph", + .func = cmd_cgraph, + .help = +"cgraph
[function]\n" +" Analyse the range given and produce a call graph. Displays a summary\n" +" of all functions if no function address is given.\n" + } }; int rtools_register(cproc_t cp) { - return cproc_register_commands(cp, &isearch_command, 1); + return cproc_register_commands(cp, rtools_commands, + ARRAY_LEN(rtools_commands)); }