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
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

586
rtools.c
View File

@ -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 <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"
/************************************************************************
* 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 <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)
{
return cproc_register_commands(cp, &isearch_command, 1);
return cproc_register_commands(cp, rtools_commands,
ARRAY_LEN(rtools_commands));
}