Implemented "cgraph" command.
This commit is contained in:
parent
d9e42457f5
commit
daad76338b
12
mspdebug.man
12
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
|
||||
|
|
586
rtools.c
586
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 <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));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue