Implemented "cgraph" command.

This commit is contained in:
Daniel Beer 2010-05-18 16:35:02 +12:00
parent d9e42457f5
commit daad76338b
2 changed files with 579 additions and 19 deletions

View File

@ -106,6 +106,18 @@ question.
See the section marked \fBADDRESS EXPRESSIONS\fR for more information on See the section marked \fBADDRESS EXPRESSIONS\fR for more information on
the syntax of expressions. 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]" .IP "\fBdis\fR \fIaddress\fR [\fIlength\fR]"
Dissassemble a section of memory. Both arguments may be address Dissassemble a section of memory. Both arguments may be address
expressions. If no length is specified, a section of the default expressions. If no length is specified, a section of the default

586
rtools.c
View File

@ -29,6 +29,11 @@
#include "stab.h" #include "stab.h"
#include "expr.h" #include "expr.h"
#include "cproc_util.h" #include "cproc_util.h"
#include "vector.h"
/************************************************************************
* Instruction search ("isearch")
*/
#define ISEARCH_OPCODE 0x0001 #define ISEARCH_OPCODE 0x0001
#define ISEARCH_BW 0x0002 #define ISEARCH_BW 0x0002
@ -423,27 +428,570 @@ static int cmd_isearch(cproc_t cp, char **arg)
return do_isearch(cp, addr, len, &q); return do_isearch(cp, addr, len, &q);
} }
static const struct cproc_command isearch_command = { /************************************************************************
.name = "isearch", * Call graph ("cgraph")
.func = cmd_isearch, */
.help =
"isearch <address> <length> [options ...]\n" struct cg_edge {
" Search for an instruction matching certain search terms. These\n" int is_tail_call;
" terms may be any of the following:\n"
" opcode <opcode>\n" uint16_t src;
" byte|word\n" uint16_t dst;
" jump|single|double|noarg\n" };
" src <value>\n"
" dst <value>\n" static int cmp_branch_by_dst(const void *a, const void *b)
" srcreg <register>\n" {
" dstreg <register>\n" const struct cg_edge *br_a = (const struct cg_edge *)a;
" srcmode R|I|S|&|@|+|#\n" const struct cg_edge *br_b = (const struct cg_edge *)b;
" dstmode R|I|S|&|@|+|#\n"
" For single-operand instructions, the operand is considered the\n" if (br_a->dst < br_b->dst)
" destination operand.\n" 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 <address> <length> [options ...]\n"
" Search for an instruction matching certain search terms. These\n"
" terms may be any of the following:\n"
" opcode <opcode>\n"
" byte|word\n"
" jump|single|double|noarg\n"
" src <value>\n"
" dst <value>\n"
" srcreg <register>\n"
" dstreg <register>\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 <address> <length> [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) 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));
} }