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));
}