*/
#define _GNU_SOURCE /* memmem() */
+#include <fnmatch.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <objtool/disas.h>
#include <objtool/check.h>
#include <objtool/special.h>
+#include <objtool/trace.h>
#include <objtool/warn.h>
#include <objtool/checksum.h>
#include <objtool/util.h>
static struct cfi_state func_cfi;
static struct cfi_state force_undefined_cfi;
-static size_t sym_name_max_len;
+struct disas_context *objtool_disas_ctx;
+
+size_t sym_name_max_len;
struct instruction *find_insn(struct objtool_file *file,
struct section *sec, unsigned long offset)
return false;
/* ANNOTATE_IGNORE_ALTERNATIVE */
- if (insn->alt_group->ignore)
+ if (insn->alt_group->ignore) {
+ TRACE_INSN(insn, "alt group ignored");
return true;
+ }
/*
* For NOP patched with CLAC/STAC, only follow the latter to avoid
static int validate_branch(struct objtool_file *file, struct symbol *func,
struct instruction *insn, struct insn_state state);
+static int do_validate_branch(struct objtool_file *file, struct symbol *func,
+ struct instruction *insn, struct insn_state state);
static int validate_insn(struct objtool_file *file, struct symbol *func,
struct instruction *insn, struct insn_state *statep,
if (!insn->hint && !insn_cfi_match(insn, &statep->cfi))
return 1;
- if (insn->visited & visited)
+ if (insn->visited & visited) {
+ TRACE_INSN(insn, "already visited");
return 0;
+ }
} else {
nr_insns_visited++;
}
* It will be seen later via the
* straight-line path.
*/
- if (!prev_insn)
+ if (!prev_insn) {
+ TRACE_INSN(insn, "defer restore");
return 0;
+ }
WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo");
return 1;
return 1;
if (insn->alts) {
+ int i, num_alts;
+
+ num_alts = 0;
+ for (alt = insn->alts; alt; alt = alt->next)
+ num_alts++;
+
+ i = 1;
for (alt = insn->alts; alt; alt = alt->next) {
+ TRACE_INSN(insn, "alternative %d/%d", i, num_alts);
ret = validate_branch(file, func, alt->insn, *statep);
if (ret) {
BT_INSN(insn, "(alt)");
return ret;
}
+ i++;
}
+ TRACE_INSN(insn, "alternative DEFAULT");
}
if (skip_alt_group(insn))
switch (insn->type) {
case INSN_RETURN:
+ TRACE_INSN(insn, "return");
return validate_return(func, insn, statep);
case INSN_CALL:
case INSN_CALL_DYNAMIC:
+ if (insn->type == INSN_CALL)
+ TRACE_INSN(insn, "call");
+ else
+ TRACE_INSN(insn, "indirect call");
+
ret = validate_call(file, insn, statep);
if (ret)
return ret;
case INSN_JUMP_CONDITIONAL:
case INSN_JUMP_UNCONDITIONAL:
if (is_sibling_call(insn)) {
+ TRACE_INSN(insn, "sibling call");
ret = validate_sibling_call(file, insn, statep);
if (ret)
return ret;
} else if (insn->jump_dest) {
- ret = validate_branch(file, func,
- insn->jump_dest, *statep);
+ if (insn->type == INSN_JUMP_UNCONDITIONAL)
+ TRACE_INSN(insn, "unconditional jump");
+ else
+ TRACE_INSN(insn, "jump taken");
+
+ ret = validate_branch(file, func, insn->jump_dest, *statep);
if (ret) {
BT_INSN(insn, "(branch)");
return ret;
if (insn->type == INSN_JUMP_UNCONDITIONAL)
return 0;
+ TRACE_INSN(insn, "jump not taken");
break;
case INSN_JUMP_DYNAMIC:
case INSN_JUMP_DYNAMIC_CONDITIONAL:
+ TRACE_INSN(insn, "indirect jump");
if (is_sibling_call(insn)) {
ret = validate_sibling_call(file, insn, statep);
if (ret)
break;
case INSN_SYSCALL:
+ TRACE_INSN(insn, "syscall");
if (func && (!next_insn || !next_insn->hint)) {
WARN_INSN(insn, "unsupported instruction in callable function");
return 1;
break;
case INSN_SYSRET:
+ TRACE_INSN(insn, "sysret");
if (func && (!next_insn || !next_insn->hint)) {
WARN_INSN(insn, "unsupported instruction in callable function");
return 1;
return 0;
case INSN_STAC:
+ TRACE_INSN(insn, "stac");
if (!opts.uaccess)
break;
break;
case INSN_CLAC:
+ TRACE_INSN(insn, "clac");
if (!opts.uaccess)
break;
break;
case INSN_STD:
+ TRACE_INSN(insn, "std");
if (statep->df) {
WARN_INSN(insn, "recursive STD");
return 1;
break;
case INSN_CLD:
+ TRACE_INSN(insn, "cld");
if (!statep->df && func) {
WARN_INSN(insn, "redundant CLD");
return 1;
break;
}
- *dead_end = insn->dead_end;
+ if (insn->dead_end)
+ TRACE_INSN(insn, "dead end");
+ *dead_end = insn->dead_end;
return 0;
}
* each instruction and validate all the rules described in
* tools/objtool/Documentation/objtool.txt.
*/
-static int validate_branch(struct objtool_file *file, struct symbol *func,
- struct instruction *insn, struct insn_state state)
+static int do_validate_branch(struct objtool_file *file, struct symbol *func,
+ struct instruction *insn, struct insn_state state)
{
struct instruction *next_insn, *prev_insn = NULL;
bool dead_end;
if (func && func->ignore)
return 0;
- while (1) {
+ do {
+ insn->trace = 0;
next_insn = next_insn_to_validate(file, insn);
if (opts.checksum && func && insn->sec)
ret = validate_insn(file, func, insn, &state, prev_insn, next_insn,
&dead_end);
- if (dead_end)
- break;
- if (!next_insn) {
+ if (!insn->trace) {
+ if (ret)
+ TRACE_INSN(insn, "warning (%d)", ret);
+ else
+ TRACE_INSN(insn, NULL);
+ }
+
+ if (!dead_end && !next_insn) {
if (state.cfi.cfa.base == CFI_UNDEFINED)
return 0;
if (file->ignore_unreachables)
prev_insn = insn;
insn = next_insn;
- }
+
+ } while (!dead_end);
+
+ return ret;
+}
+
+static int validate_branch(struct objtool_file *file, struct symbol *func,
+ struct instruction *insn, struct insn_state state)
+{
+ int ret;
+
+ trace_depth_inc();
+ ret = do_validate_branch(file, func, insn, state);
+ trace_depth_dec();
return ret;
}
if (opts.checksum)
checksum_init(func);
+ if (opts.trace && !fnmatch(opts.trace, sym->name, 0)) {
+ trace_enable();
+ TRACE("%s: validation begin\n", sym->name);
+ }
+
ret = validate_branch(file, func, insn, *state);
if (ret)
BT_INSN(insn, "<=== (sym)");
+ TRACE("%s: validation %s\n\n", sym->name, ret ? "failed" : "end");
+ trace_disable();
+
if (opts.checksum)
checksum_finish(func);
free(chunk->addr);
}
-static struct disas_context *objtool_disas_ctx;
-
const char *objtool_disas_insn(struct instruction *insn)
{
struct disas_context *dctx = objtool_disas_ctx;
* disassembly context to disassemble instruction or function
* on warning or backtrace.
*/
- if (opts.verbose || opts.backtrace) {
+ if (opts.verbose || opts.backtrace || opts.trace) {
disas_ctx = disas_context_create(file);
+ if (!disas_ctx)
+ opts.trace = false;
objtool_disas_ctx = disas_ctx;
}
return dctx->result;
}
+#define DISAS_INSN_OFFSET_SPACE 10
+#define DISAS_INSN_SPACE 60
+
+/*
+ * Print a message in the instruction flow. If insn is not NULL then
+ * the instruction address is printed in addition of the message,
+ * otherwise only the message is printed. In all cases, the instruction
+ * itself is not printed.
+ */
+static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset,
+ int depth, const char *format, va_list ap)
+{
+ const char *addr_str;
+ int i, n;
+ int len;
+
+ len = sym_name_max_len + DISAS_INSN_OFFSET_SPACE;
+ if (depth < 0) {
+ len += depth;
+ depth = 0;
+ }
+
+ n = 0;
+
+ if (sec) {
+ addr_str = offstr(sec, offset);
+ n += fprintf(stream, "%6lx: %-*s ", offset, len, addr_str);
+ free((char *)addr_str);
+ } else {
+ len += DISAS_INSN_OFFSET_SPACE + 1;
+ n += fprintf(stream, "%-*s", len, "");
+ }
+
+ /* print vertical bars to show the code flow */
+ for (i = 0; i < depth; i++)
+ n += fprintf(stream, "| ");
+
+ if (format)
+ n += vfprintf(stream, format, ap);
+
+ return n;
+}
+
+/*
+ * Print a message in the instruction flow. If insn is not NULL then
+ * the instruction address is printed in addition of the message,
+ * otherwise only the message is printed. In all cases, the instruction
+ * itself is not printed.
+ */
+void disas_print_info(FILE *stream, struct instruction *insn, int depth,
+ const char *format, ...)
+{
+ struct section *sec;
+ unsigned long off;
+ va_list args;
+
+ if (insn) {
+ sec = insn->sec;
+ off = insn->offset;
+ } else {
+ sec = NULL;
+ off = 0;
+ }
+
+ va_start(args, format);
+ disas_vprint(stream, sec, off, depth, format, args);
+ va_end(args);
+}
+
+/*
+ * Print an instruction address (offset and function), the instruction itself
+ * and an optional message.
+ */
+void disas_print_insn(FILE *stream, struct disas_context *dctx,
+ struct instruction *insn, int depth,
+ const char *format, ...)
+{
+ char fake_nop_insn[32];
+ const char *insn_str;
+ bool fake_nop;
+ va_list args;
+ int len;
+
+ /*
+ * Alternative can insert a fake nop, sometimes with no
+ * associated section so nothing to disassemble.
+ */
+ fake_nop = (!insn->sec && insn->type == INSN_NOP);
+ if (fake_nop) {
+ snprintf(fake_nop_insn, 32, "<fake nop> (%d bytes)", insn->len);
+ insn_str = fake_nop_insn;
+ } else {
+ disas_insn(dctx, insn);
+ insn_str = disas_result(dctx);
+ }
+
+ /* print the instruction */
+ len = (depth + 1) * 2 < DISAS_INSN_SPACE ? DISAS_INSN_SPACE - (depth+1) * 2 : 1;
+ disas_print_info(stream, insn, depth, "%-*s", len, insn_str);
+
+ /* print message if any */
+ if (!format)
+ return;
+
+ if (strcmp(format, "\n") == 0) {
+ fprintf(stream, "\n");
+ return;
+ }
+
+ fprintf(stream, " - ");
+ va_start(args, format);
+ vfprintf(stream, format, args);
+ va_end(args);
+}
+
/*
* Disassemble a single instruction. Return the size of the instruction.
*/