3224 lines
97 KiB
C
3224 lines
97 KiB
C
// SPDX-FileCopyrightText: 2021-2026 RizinOrg <info@rizin.re>
|
|
// SPDX-FileCopyrightText: 2021-2026 deroad <deroad@kumo.xn--q9jyb4c>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include <rz_core.h>
|
|
#include <rz_io.h>
|
|
#include <rz_bin.h>
|
|
#include <rz_diff.h>
|
|
#include <rz_util.h>
|
|
#include <rz_main.h>
|
|
|
|
#define RZ_DIFF_DISTANCE_MAX_SIZE (1024 * 1024) // 1 miB
|
|
|
|
#define SAFE_STR_DEF(x, y) (x ? x : y)
|
|
#define SAFE_STR(x) (x ? x : "")
|
|
#define IF_STRCMP_S(ret, x, y) \
|
|
do { \
|
|
if ((x) != (y) && (!(x) || !(y))) { \
|
|
return !(y) ? 1 : -1; \
|
|
} else if ((x) && (y) && (ret = strcmp((x), (y)))) { \
|
|
return ret; \
|
|
} \
|
|
} while (0)
|
|
|
|
typedef enum {
|
|
DIFF_MODE_STANDARD = 0,
|
|
DIFF_MODE_JSON,
|
|
DIFF_MODE_QUIET,
|
|
} DiffMode;
|
|
|
|
typedef enum {
|
|
DIFF_DISTANCE_UNKNOWN = 0,
|
|
DIFF_DISTANCE_MYERS,
|
|
DIFF_DISTANCE_LEVENSHTEIN,
|
|
DIFF_DISTANCE_LCS_ROLLING,
|
|
DIFF_DISTANCE_SSDEEP,
|
|
} DiffDistance;
|
|
|
|
typedef enum {
|
|
DIFF_TYPE_UNKNOWN = 0,
|
|
DIFF_TYPE_BYTES,
|
|
DIFF_TYPE_CLASSES,
|
|
DIFF_TYPE_COMMAND,
|
|
DIFF_TYPE_ENTRIES,
|
|
DIFF_TYPE_FIELDS,
|
|
DIFF_TYPE_FUNCTIONS,
|
|
DIFF_TYPE_IMPORTS,
|
|
DIFF_TYPE_LIBRARIES,
|
|
DIFF_TYPE_LINES,
|
|
DIFF_TYPE_SECTIONS,
|
|
DIFF_TYPE_STRINGS,
|
|
DIFF_TYPE_SYMBOLS,
|
|
DIFF_TYPE_PLOTDIFF,
|
|
} DiffType;
|
|
|
|
typedef enum {
|
|
DIFF_OPT_UNKNOWN = 0,
|
|
DIFF_OPT_ERROR,
|
|
DIFF_OPT_HELP,
|
|
DIFF_OPT_USAGE,
|
|
DIFF_OPT_VERSION,
|
|
DIFF_OPT_DISTANCE,
|
|
DIFF_OPT_UNIFIED,
|
|
DIFF_OPT_GRAPH,
|
|
DIFF_OPT_HEX_VISUAL,
|
|
} DiffOption;
|
|
|
|
typedef struct diff_screen_t {
|
|
int width;
|
|
int height;
|
|
} DiffScreen;
|
|
|
|
typedef struct diff_context_t {
|
|
DiffType type;
|
|
DiffMode mode;
|
|
DiffOption option;
|
|
DiffDistance distance;
|
|
ut32 arch_bits;
|
|
bool compare_addresses;
|
|
bool show_time;
|
|
bool colors;
|
|
bool analyze_all;
|
|
bool command_line;
|
|
bool verbose;
|
|
const char *architecture;
|
|
const char *input_a;
|
|
const char *input_b;
|
|
const char *file_a;
|
|
const char *file_b;
|
|
const char *theme_name;
|
|
DiffScreen screen;
|
|
RzList /*<char *>*/ *evars;
|
|
} DiffContext;
|
|
|
|
typedef struct diff_io_t {
|
|
const char *filename;
|
|
ut64 filesize;
|
|
RzIO *io;
|
|
} DiffIO;
|
|
|
|
typedef struct diff_file_t {
|
|
/* const */
|
|
RzBinFile *file;
|
|
RzBinPlugin *plugin;
|
|
/* to free */
|
|
RzBin *bin;
|
|
DiffIO *dio;
|
|
} DiffFile;
|
|
|
|
typedef struct diff_function_t {
|
|
char *name;
|
|
int bits;
|
|
ut64 address;
|
|
int n_instructions;
|
|
} DiffFunction;
|
|
|
|
typedef struct diff_colors_t {
|
|
const char *number;
|
|
const char *match;
|
|
const char *unmatch;
|
|
const char *legenda;
|
|
const char *reset;
|
|
} DiffColors;
|
|
|
|
typedef struct diff_hex_view_t {
|
|
char *line;
|
|
ut8 *buffer_a;
|
|
ut8 *buffer_b;
|
|
ut64 size_a;
|
|
ut64 size_b;
|
|
ut64 address_a;
|
|
ut64 address_b;
|
|
DiffIO *io_a;
|
|
DiffIO *io_b;
|
|
bool column_descr;
|
|
DiffColors colors;
|
|
RzConsCanvas *canvas;
|
|
DiffScreen screen;
|
|
} DiffHexView;
|
|
|
|
#define rz_diff_error(f, ...) \
|
|
RZ_LOG_ERROR("rz-diff: error, " f, ##__VA_ARGS__)
|
|
|
|
#define rz_diff_error_ret(fail, f, ...) \
|
|
RZ_LOG_ERROR("rz-diff: error, " f, ##__VA_ARGS__); \
|
|
return (fail)
|
|
|
|
#define rz_diff_error_opt(x, o, f, ...) \
|
|
(x)->option = o; \
|
|
RZ_LOG_ERROR("rz-diff: error, " f, ##__VA_ARGS__); \
|
|
return
|
|
|
|
#define rz_diff_ctx_set(x, k, v) (x)->k = (v)
|
|
|
|
#define rz_diff_set_def(x, d, v) \
|
|
do { \
|
|
if ((x) != (d)) { \
|
|
RZ_LOG_ERROR("rz-diff: error, invalid combination of arguments for '-%c' (expected " #d " but found something else)\n", c); \
|
|
return; \
|
|
} \
|
|
(x) = (v); \
|
|
} while (0)
|
|
|
|
#define rz_diff_ctx_set_def(x, k, d, v) \
|
|
do { \
|
|
if ((x)->k != (d)) { \
|
|
rz_diff_error_opt(x, DIFF_OPT_UNKNOWN, "invalid combination of arguments for '-%c' (expected " #d " but found something else)\n", c); \
|
|
} \
|
|
(x)->k = (v); \
|
|
} while (0)
|
|
|
|
#define rz_diff_ctx_set_val(x, k, d, v) \
|
|
do { \
|
|
if ((x)->k != (d)) { \
|
|
rz_diff_error_opt(x, DIFF_OPT_UNKNOWN, "invalid combination of arguments for '-%c' (expected " #d " but found something else)\n", c); \
|
|
} \
|
|
(x)->k = (v); \
|
|
} while (0)
|
|
|
|
#define rz_diff_ctx_set_unsigned(x, k, i) \
|
|
do { \
|
|
(x)->k = strtoull((i), NULL, 0); \
|
|
if ((x)->k < 1) { \
|
|
rz_diff_error_opt(x, DIFF_OPT_UNKNOWN, "argument must be > 0\n"); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define rz_diff_ctx_add_evar(x, o) \
|
|
do { \
|
|
char *copy = rz_str_dup(o); \
|
|
if (!copy || !rz_list_append((x)->evars, copy)) { \
|
|
free(copy); \
|
|
rz_diff_error_opt(x, DIFF_OPT_ERROR, "cannot add evar '%s' to list\n", o); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define rz_diff_ctx_set_dist(x, t) rz_diff_ctx_set_def(x, distance, DIFF_DISTANCE_UNKNOWN, t)
|
|
#define rz_diff_ctx_set_type(x, t) rz_diff_ctx_set_def(x, type, DIFF_TYPE_UNKNOWN, t)
|
|
#define rz_diff_ctx_set_mode(x, m) rz_diff_ctx_set_def(x, mode, DIFF_MODE_STANDARD, m)
|
|
#define rz_diff_ctx_set_opt(x, o) rz_diff_ctx_set_def(x, option, DIFF_OPT_UNKNOWN, o)
|
|
|
|
static void rz_diff_show_help(bool usage_only) {
|
|
printf("%s%s%s", Color_CYAN, "Usage: ", Color_RESET);
|
|
printf("rz-diff [options] <file0> <file1>\n");
|
|
if (usage_only) {
|
|
return;
|
|
}
|
|
const char *options[] = {
|
|
// clang-format off
|
|
"-a", "arch", "Specify architecture plugin to use (x86, arm, ..)",
|
|
"-b", "bits", "Specify register size for arch (16 (thumb), 32, 64, ..)",
|
|
"-d", "myers", "Compute edit distance using Eugene W. Myers' O(ND) algorithm (no substitution)",
|
|
"-d", "leven", "Compute edit distance using Levenshtein O(N^2) algorithm (with substitution)",
|
|
"-d", "lcs-roll", "Compute edit distance using Myers+FastCDC with chunk size " RZ_STR_DEF(RZ_DIFF_LCS_ROLL_DEFAULT_BLOCK_SIZE) " (-0 to change chunk size)",
|
|
"-d", "ssdeep", "Compute edit distance using Context triggered piecewise hashing comparison",
|
|
"-i", "", "Use command line arguments instead of files (only for -d)",
|
|
"-H", "", "Hexadecimal visual mode",
|
|
"-h", "", "Show this help",
|
|
"-j", "", "JSON output",
|
|
"-q", "", "Quiet output",
|
|
"-v", "", "Show version information",
|
|
"-V", "", "Be more verbose (stderr output)",
|
|
"-K", "theme", "Set a give color theme (see rizin 'eco' command)",
|
|
"-e", "k=v", "Set an evaluable config variable",
|
|
"-A", "", "Compare virtual and physical addresses",
|
|
"-B", "", "Run 'aaa' when loading the bin",
|
|
"-C", "", "Disable colors",
|
|
"-T", "", "Show timestamp information",
|
|
"-S", "WxH", "Set the width and height of the terminal for visual mode",
|
|
"-0", "cmd", "Input for file0 when option -t 'commands' is given.",
|
|
"", "", "The same value will be set for file1, if -1 is not set.",
|
|
"-1", "cmd", "Input for file1 when option -t 'commands' is given.",
|
|
"-t", "bytes", "Compare raw bytes in the files (only for small files)",
|
|
"-t", "lines", "Compare text files",
|
|
"-t", "functions", "Compare functions found in the files",
|
|
"", "", "optional -0 <fcn name|offset> to compare only one function",
|
|
"-t", "classes", "Compare classes found in the files",
|
|
"-t", "command", "Compare command output returned when executed in both files",
|
|
"", "", "requires -0 <cmd> and -1 <cmd> is optional",
|
|
"-t", "entries", "Compare entries found in the files",
|
|
"-t", "fields", "Compare fields found in the files",
|
|
"-t", "graphs", "Compare 2 functions and outputs in graphviz/dot format",
|
|
"", "", "requires -0 <fcn name|offset> and -1 <fcn name|offset> is optional",
|
|
"-t", "imports", "Compare imports found in the files",
|
|
"-t", "libraries", "Compare libraries found in the files",
|
|
"-t", "sections", "Compare sections found in the files",
|
|
"-t", "strings", "Compare strings found in the files",
|
|
"-t", "symbols", "Compare symbols found in the files",
|
|
// clang-format on
|
|
};
|
|
rz_print_colored_help(options, RZ_ARRAY_SIZE(options), false);
|
|
printf(
|
|
"Palette colors can be changed by adding the following lines inside the $HOME/.rizinrc file\n"
|
|
" ec diff.unknown blue | offset color\n"
|
|
" ec diff.match green | match color\n"
|
|
" ec diff.unmatch red | mismatch color\n"
|
|
"Environment variables\n"
|
|
" RZ_COLOR | enables/disables colors support\n");
|
|
}
|
|
|
|
static bool rz_diff_is_file(const char *file) {
|
|
if (IS_NULLSTR(file)) {
|
|
rz_diff_error_ret(false, "cannot open a file without a name.\n");
|
|
}
|
|
if (rz_file_is_directory(file)) {
|
|
rz_diff_error_ret(false, "cannot open directories (%s).\n", file);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool diff_env_get_bool(const char *key, bool def_value) {
|
|
bool value = def_value;
|
|
char *tmp = rz_sys_getenv(key);
|
|
if (RZ_STR_ISNOTEMPTY(tmp)) {
|
|
value = rz_num_get(NULL, tmp) != 0;
|
|
}
|
|
free(tmp);
|
|
return value;
|
|
}
|
|
|
|
static void rz_diff_parse_arguments(int argc, const char **argv, DiffContext *ctx) {
|
|
const char *type = NULL;
|
|
const char *algorithm = NULL;
|
|
const char *screen = NULL;
|
|
memset((void *)ctx, 0, sizeof(DiffContext));
|
|
ctx->colors = diff_env_get_bool("RZ_COLOR", true);
|
|
ctx->evars = rz_list_newf(free);
|
|
|
|
if (!ctx->evars) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_ERROR, "cannot allocate list for evars");
|
|
return;
|
|
}
|
|
|
|
RzGetopt opt;
|
|
int c;
|
|
rz_getopt_init(&opt, argc, argv, "hHjqvViABCTa:b:K:e:d:t:0:1:S:");
|
|
while ((c = rz_getopt_next(&opt)) != -1) {
|
|
switch (c) {
|
|
case '0': rz_diff_ctx_set_def(ctx, input_a, NULL, opt.arg); break;
|
|
case '1': rz_diff_ctx_set_def(ctx, input_b, NULL, opt.arg); break;
|
|
case 'A': rz_diff_ctx_set_def(ctx, compare_addresses, false, true); break;
|
|
case 'B': rz_diff_ctx_set_def(ctx, analyze_all, false, true); break;
|
|
case 'C': rz_diff_ctx_set_def(ctx, colors, ctx->colors, false); break;
|
|
case 'T': rz_diff_ctx_set_def(ctx, show_time, false, true); break;
|
|
case 'a': rz_diff_ctx_set_def(ctx, architecture, NULL, opt.arg); break;
|
|
case 'b': rz_diff_ctx_set_unsigned(ctx, arch_bits, opt.arg); break;
|
|
case 'd': rz_diff_set_def(algorithm, NULL, opt.arg); break;
|
|
case 'h': rz_diff_ctx_set_opt(ctx, DIFF_OPT_HELP); break;
|
|
case 'i': rz_diff_ctx_set_def(ctx, command_line, false, true); break;
|
|
case 'j': rz_diff_ctx_set_mode(ctx, DIFF_MODE_JSON); break;
|
|
case 'q': rz_diff_ctx_set_mode(ctx, DIFF_MODE_QUIET); break;
|
|
case 't': rz_diff_set_def(type, NULL, opt.arg); break;
|
|
case 'v': rz_diff_ctx_set_opt(ctx, DIFF_OPT_VERSION); break;
|
|
case 'V': rz_diff_ctx_set_def(ctx, verbose, false, true); break;
|
|
case 'S': rz_diff_set_def(screen, NULL, opt.arg); break;
|
|
case 'H': rz_diff_ctx_set_opt(ctx, DIFF_OPT_HEX_VISUAL); break;
|
|
case 'e': rz_diff_ctx_add_evar(ctx, opt.arg); break;
|
|
case 'K': rz_diff_set_def(ctx->theme_name, NULL, opt.arg); break;
|
|
default:
|
|
rz_diff_error_opt(ctx, DIFF_OPT_ERROR, "unknown flag '%c'\n", c);
|
|
}
|
|
}
|
|
|
|
if (ctx->option == DIFF_OPT_HELP ||
|
|
ctx->option == DIFF_OPT_VERSION) {
|
|
return;
|
|
}
|
|
|
|
if (opt.ind >= argc || (argc - opt.ind) != 2) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_USAGE, "expected 2 files but got %d.\n", (argc - opt.ind));
|
|
}
|
|
|
|
ctx->file_a = argv[opt.ind + 0];
|
|
ctx->file_b = argv[opt.ind + 1];
|
|
|
|
if (!rz_diff_is_file(ctx->file_a) ||
|
|
!rz_diff_is_file(ctx->file_b)) {
|
|
ctx->option = DIFF_OPT_USAGE;
|
|
return;
|
|
}
|
|
|
|
if (algorithm) {
|
|
if (type) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_ERROR, "option -t argument is not compatible with -d.\n");
|
|
} else if (ctx->show_time) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_ERROR, "option -T argument is not compatible with -d.\n");
|
|
}
|
|
|
|
rz_diff_ctx_set_opt(ctx, DIFF_OPT_DISTANCE);
|
|
if (!strcmp(algorithm, "myers")) {
|
|
rz_diff_ctx_set_dist(ctx, DIFF_DISTANCE_MYERS);
|
|
} else if (!strcmp(algorithm, "leven")) {
|
|
rz_diff_ctx_set_dist(ctx, DIFF_DISTANCE_LEVENSHTEIN);
|
|
} else if (!strcmp(algorithm, "lcs-roll")) {
|
|
rz_diff_ctx_set_dist(ctx, DIFF_DISTANCE_LCS_ROLLING);
|
|
} else if (!strcmp(algorithm, "ssdeep")) {
|
|
rz_diff_ctx_set_dist(ctx, DIFF_DISTANCE_SSDEEP);
|
|
} else {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_ERROR, "option -d argument '%s' is not a recognized algorithm.\n", algorithm);
|
|
}
|
|
} else if (type) {
|
|
rz_diff_ctx_set_opt(ctx, DIFF_OPT_UNIFIED);
|
|
|
|
if (ctx->command_line) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_ERROR, "option -i is not supported with -t flag.\n");
|
|
}
|
|
|
|
if (!strcmp(type, "bytes")) {
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_BYTES);
|
|
} else if (!strcmp(type, "lines")) {
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_LINES);
|
|
} else if (!strcmp(type, "functions")) {
|
|
if (ctx->input_b) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_ERROR, "option -t '%s' does not support -1.\n", type);
|
|
}
|
|
ctx->option = DIFF_OPT_GRAPH;
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_FUNCTIONS);
|
|
} else if (!strcmp(type, "classes")) {
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_CLASSES);
|
|
} else if (!strcmp(type, "command")) {
|
|
if (!ctx->input_a) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_ERROR, "option -t '%s' requires -0 <command>.\n", type);
|
|
}
|
|
if (!ctx->input_b) {
|
|
ctx->input_b = ctx->input_a;
|
|
}
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_COMMAND);
|
|
} else if (!strcmp(type, "entries")) {
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_ENTRIES);
|
|
} else if (!strcmp(type, "fields")) {
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_FIELDS);
|
|
} else if (!strcmp(type, "graphs")) {
|
|
if (!ctx->input_a) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_ERROR, "option -t '%s' requires -0 <fcn name|address>.\n", type);
|
|
} else if (ctx->input_a && !ctx->input_b) {
|
|
ctx->input_b = ctx->input_a;
|
|
}
|
|
ctx->option = DIFF_OPT_GRAPH;
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_PLOTDIFF);
|
|
} else if (!strcmp(type, "imports")) {
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_IMPORTS);
|
|
} else if (!strcmp(type, "libraries")) {
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_LIBRARIES);
|
|
} else if (!strcmp(type, "sections")) {
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_SECTIONS);
|
|
} else if (!strcmp(type, "strings")) {
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_STRINGS);
|
|
} else if (!strcmp(type, "symbols")) {
|
|
rz_diff_ctx_set_type(ctx, DIFF_TYPE_SYMBOLS);
|
|
} else {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_ERROR, "option -t argument '%s' is not a recognized type.\n", type);
|
|
}
|
|
} else if (screen) {
|
|
const char *hp = NULL;
|
|
if (!(hp = strchr(screen, 'x'))) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_USAGE, "invalid format for -S; example 120x20 where width=120 and height=20.\n");
|
|
}
|
|
ut64 height = strtoull(screen, NULL, 0);
|
|
ut64 width = strtoull(hp + 1, NULL, 0);
|
|
if (width < 1 || width > 0xFFFF || height < 1 || height > 0xFFFF) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_USAGE, "invalid format for -S; example 120x20 where width=120 and height=20.\n");
|
|
} else if (width < 20 || height < 120) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_USAGE, "Min width=120, Min height=20.\n");
|
|
}
|
|
ctx->screen.width = (int)width;
|
|
ctx->screen.height = (int)height;
|
|
} else if (ctx->option == DIFF_OPT_UNKNOWN) {
|
|
rz_diff_error_opt(ctx, DIFF_OPT_USAGE, "option -t or -d is required to be specified.\n");
|
|
}
|
|
}
|
|
|
|
static void rz_diff_get_colors(DiffColors *dcolors, RzConsContext *ctx, bool colors) {
|
|
dcolors->number = colors ? ctx->pal.diff_unknown : "";
|
|
dcolors->match = colors ? ctx->pal.diff_match : "";
|
|
dcolors->unmatch = colors ? ctx->pal.diff_unmatch : Color_INVERT;
|
|
dcolors->legenda = colors ? ctx->pal.comment : "";
|
|
dcolors->reset = Color_RESET;
|
|
}
|
|
|
|
static DiffIO *rz_diff_io_open(const char *file) {
|
|
RzIODesc *desc = NULL;
|
|
RzIO *io = NULL;
|
|
DiffIO *dio = NULL;
|
|
|
|
dio = RZ_NEW0(DiffIO);
|
|
if (!dio) {
|
|
rz_diff_error("cannot allocate diff io\n");
|
|
goto rz_diff_io_open_end;
|
|
}
|
|
|
|
io = rz_io_new();
|
|
if (!io) {
|
|
rz_diff_error("cannot allocate io\n");
|
|
goto rz_diff_io_open_end;
|
|
}
|
|
|
|
desc = rz_io_open_nomap(io, file, RZ_PERM_R, 0);
|
|
if (!desc) {
|
|
rz_diff_error("cannot open file '%s'\n", file);
|
|
goto rz_diff_io_open_end;
|
|
}
|
|
|
|
dio->filename = file;
|
|
dio->filesize = rz_io_desc_size(desc);
|
|
dio->io = io;
|
|
return dio;
|
|
|
|
rz_diff_io_open_end:
|
|
rz_io_desc_close(desc);
|
|
rz_io_free(io);
|
|
free(dio);
|
|
return NULL;
|
|
}
|
|
|
|
static void rz_diff_io_close(DiffIO *dio) {
|
|
if (!dio) {
|
|
return;
|
|
}
|
|
rz_io_desc_close(dio->io->desc);
|
|
rz_io_free(dio->io);
|
|
free(dio);
|
|
}
|
|
|
|
/* This is terrible because can eat a lot of memory */
|
|
static ut8 *rz_diff_slurp_file(const char *file, size_t *size) {
|
|
ut8 *buffer = NULL;
|
|
ssize_t read = 0;
|
|
DiffIO *dio = NULL;
|
|
|
|
dio = rz_diff_io_open(file);
|
|
if (!dio) {
|
|
goto rz_diff_slurp_file_end;
|
|
}
|
|
|
|
buffer = malloc(dio->filesize + 1);
|
|
if (!buffer) {
|
|
rz_diff_error("cannot allocate buffer\n");
|
|
goto rz_diff_slurp_file_end;
|
|
}
|
|
buffer[dio->filesize] = 0;
|
|
|
|
read = rz_io_pread_at(dio->io, 0, buffer, dio->filesize);
|
|
if (read != dio->filesize) {
|
|
free(buffer);
|
|
buffer = NULL;
|
|
rz_diff_error("cannot read buffer correctly\n");
|
|
goto rz_diff_slurp_file_end;
|
|
}
|
|
|
|
*size = dio->filesize;
|
|
|
|
rz_diff_slurp_file_end:
|
|
rz_diff_io_close(dio);
|
|
return buffer;
|
|
}
|
|
|
|
static ut32 check_distance_max_size(ut32 size, const char *algo, const char *name) {
|
|
const ut32 max_size = RZ_DIFF_DISTANCE_MAX_SIZE;
|
|
if (size <= max_size) {
|
|
return size;
|
|
}
|
|
|
|
RZ_LOG_WARN("diff: length of buffer %s exceeds the limit size for %s distance (size: %u > max: %u); buffer length has been changed.\n", name, algo, size, max_size);
|
|
return max_size;
|
|
}
|
|
|
|
static bool rz_diff_calculate_distance(DiffContext *ctx) {
|
|
size_t a_size = 0;
|
|
size_t b_size = 0;
|
|
ut8 *a_buffer = NULL;
|
|
ut8 *b_buffer = NULL;
|
|
ut32 distance = 0;
|
|
st32 chunk_size = 0;
|
|
double similarity = 0.0;
|
|
|
|
if (ctx->command_line) {
|
|
if (!(a_buffer = (ut8 *)rz_str_dup(ctx->file_a))) {
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
a_size = strlen((const char *)a_buffer);
|
|
if (!(b_buffer = (ut8 *)rz_str_dup(ctx->file_b))) {
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
b_size = strlen((const char *)b_buffer);
|
|
} else {
|
|
if (!(a_buffer = rz_diff_slurp_file(ctx->file_a, &a_size))) {
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
if (!(b_buffer = rz_diff_slurp_file(ctx->file_b, &b_size))) {
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
}
|
|
|
|
switch (ctx->distance) {
|
|
case DIFF_DISTANCE_MYERS:
|
|
a_size = check_distance_max_size(a_size, "myers", "file0");
|
|
b_size = check_distance_max_size(b_size, "myers", "file1");
|
|
if (!rz_diff_myers_distance(a_buffer, a_size, b_buffer, b_size, &distance, &similarity)) {
|
|
rz_diff_error("failed to calculate distance with myers algorithm\n");
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
break;
|
|
case DIFF_DISTANCE_LEVENSHTEIN:
|
|
a_size = check_distance_max_size(a_size, "levenshtein", "file0");
|
|
b_size = check_distance_max_size(b_size, "levenshtein", "file1");
|
|
if (!rz_diff_levenshtein_distance(a_buffer, a_size, b_buffer, b_size, &distance, &similarity)) {
|
|
rz_diff_error("failed to calculate distance with levenshtein algorithm\n");
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
break;
|
|
case DIFF_DISTANCE_LCS_ROLLING:
|
|
chunk_size = RZ_DIFF_LCS_ROLL_DEFAULT_BLOCK_SIZE;
|
|
if (ctx->input_a) {
|
|
chunk_size = atoi(ctx->input_a);
|
|
if (chunk_size < 1) {
|
|
rz_diff_error("invalid to chunk size (%d)\n", chunk_size);
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
}
|
|
if (!rz_diff_lcs_rolling_distance(a_buffer, a_size, b_buffer, b_size, chunk_size, &distance, &similarity)) {
|
|
rz_diff_error("failed to calculate distance with lcs rolling algorithm with chunk size %d\n", chunk_size);
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
break;
|
|
case DIFF_DISTANCE_SSDEEP:
|
|
if ((similarity = rz_hash_ssdeep_compare((const char *)a_buffer, (const char *)b_buffer)) < 0) {
|
|
rz_diff_error("failed to calculate distance with ssdeep compare algorithm\n");
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
break;
|
|
default:
|
|
rz_diff_error("unknown distance algorithm\n");
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
|
|
if (ctx->mode == DIFF_MODE_JSON) {
|
|
PJ *pj = pj_new();
|
|
if (!pj) {
|
|
rz_diff_error("failed to allocate json\n");
|
|
goto rz_diff_calculate_distance_bad;
|
|
}
|
|
pj_o(pj);
|
|
pj_kd(pj, "similarity", similarity);
|
|
if (ctx->distance != DIFF_DISTANCE_SSDEEP) {
|
|
pj_kn(pj, "distance", distance);
|
|
}
|
|
if (ctx->distance == DIFF_DISTANCE_LCS_ROLLING) {
|
|
pj_kn(pj, "chunk_size", chunk_size);
|
|
}
|
|
pj_end(pj);
|
|
printf("%s\n", pj_string(pj));
|
|
pj_free(pj);
|
|
} else if (ctx->mode == DIFF_MODE_QUIET) {
|
|
printf("%.3f\n", similarity);
|
|
if (ctx->distance != DIFF_DISTANCE_SSDEEP) {
|
|
printf("%d\n", distance);
|
|
}
|
|
} else {
|
|
// DIFF_MODE_STANDARD
|
|
printf("similarity: %.3f\n", similarity);
|
|
if (ctx->distance != DIFF_DISTANCE_SSDEEP) {
|
|
printf("distance: %u\n", distance);
|
|
}
|
|
if (ctx->distance == DIFF_DISTANCE_LCS_ROLLING) {
|
|
printf("chunksize: %d\n", chunk_size);
|
|
}
|
|
}
|
|
free(a_buffer);
|
|
free(b_buffer);
|
|
return true;
|
|
|
|
rz_diff_calculate_distance_bad:
|
|
free(a_buffer);
|
|
free(b_buffer);
|
|
return false;
|
|
}
|
|
|
|
static inline RzBinFile *core_get_file(RzCoreFile *cfile) {
|
|
return rz_pvector_at(&cfile->binfiles, 0);
|
|
}
|
|
|
|
static RzCoreFile *rz_diff_load_file_with_core(const char *filename, const char *architecture, ut32 arch_bits, RzList /*<char *>*/ *evars, bool colors, bool verbose) {
|
|
RzCore *core = NULL;
|
|
RzCoreFile *cfile = NULL;
|
|
RzBinFile *bfile = NULL;
|
|
RzListIter *it;
|
|
char *config;
|
|
|
|
if (verbose) {
|
|
fprintf(stderr, "rz-diff: loading file '%s'\n", filename);
|
|
}
|
|
|
|
core = rz_core_new();
|
|
if (!core) {
|
|
rz_diff_error("cannot allocate core\n");
|
|
goto rz_diff_load_file_with_core_fail;
|
|
}
|
|
rz_core_loadlibs(core, RZ_CORE_LOADLIBS_ALL);
|
|
|
|
rz_config_set_i(core->config, "scr.color", colors ? 1 : 0);
|
|
rz_config_set_b(core->config, "scr.interactive", false);
|
|
rz_config_set_b(core->config, "cfg.debug", false);
|
|
rz_config_set_b(core->config, "scr.prompt", false);
|
|
core->print->scr_prompt = false;
|
|
cfile = rz_core_file_open(core, filename, 0, 0);
|
|
if (!cfile) {
|
|
rz_diff_error("cannot open file '%s'\n", filename);
|
|
goto rz_diff_load_file_with_core_fail;
|
|
}
|
|
|
|
if (!rz_core_bin_load(core, NULL, UT64_MAX)) {
|
|
rz_diff_error("cannot load file '%s'\n", filename);
|
|
goto rz_diff_load_file_with_core_fail;
|
|
}
|
|
|
|
if (!rz_core_bin_update_arch_bits(core)) {
|
|
rz_diff_error("cannot set architecture with bits\n");
|
|
goto rz_diff_load_file_with_core_fail;
|
|
}
|
|
|
|
bfile = core_get_file(cfile);
|
|
if (!bfile) {
|
|
rz_diff_error("cannot get architecture with bits\n");
|
|
goto rz_diff_load_file_with_core_fail;
|
|
}
|
|
|
|
if (rz_pvector_empty(bfile->o->maps)) {
|
|
rz_config_set_i(core->config, "io.va", false);
|
|
}
|
|
|
|
if (architecture) {
|
|
rz_config_set(core->config, "asm.arch", architecture);
|
|
}
|
|
|
|
if (arch_bits) {
|
|
rz_config_set_i(core->config, "asm.bits", arch_bits);
|
|
}
|
|
|
|
rz_list_foreach (evars, it, config) {
|
|
rz_config_eval(core->config, config);
|
|
}
|
|
|
|
return cfile;
|
|
|
|
rz_diff_load_file_with_core_fail:
|
|
rz_core_free(core);
|
|
return NULL;
|
|
}
|
|
|
|
static bool rz_diff_file_open(DiffFile *dfile, const char *filename) {
|
|
memset((void *)dfile, 0, sizeof(DiffFile));
|
|
RzBinOptions opt = { 0 };
|
|
RzBinFile *file = NULL;
|
|
RzBin *bin = NULL;
|
|
DiffIO *dio = NULL;
|
|
|
|
dio = rz_diff_io_open(filename);
|
|
if (!dio) {
|
|
goto rz_diff_file_open_bad;
|
|
}
|
|
|
|
bin = rz_bin_new();
|
|
if (!bin) {
|
|
rz_diff_error("cannot allocate bin\n");
|
|
goto rz_diff_file_open_bad;
|
|
}
|
|
|
|
rz_io_bind(dio->io, &bin->iob);
|
|
|
|
// TODO: no RzConfig ???
|
|
rz_bin_options_init(&opt, dio->io->desc->fd, 0, 0, false);
|
|
opt.obj_opts.elf_load_sections = true;
|
|
opt.obj_opts.elf_checks_sections = true;
|
|
opt.obj_opts.elf_checks_segments = true;
|
|
opt.sz = rz_io_desc_size(dio->io->desc);
|
|
|
|
file = rz_bin_open_io(bin, &opt);
|
|
if (!file) {
|
|
rz_diff_error("cannot open bin file via io\n");
|
|
goto rz_diff_file_open_bad;
|
|
}
|
|
|
|
dfile->plugin = rz_bin_file_cur_plugin(file);
|
|
dfile->file = file;
|
|
dfile->bin = bin;
|
|
dfile->dio = dio;
|
|
return true;
|
|
|
|
rz_diff_file_open_bad:
|
|
rz_bin_free(bin);
|
|
rz_diff_io_close(dio);
|
|
return false;
|
|
}
|
|
|
|
static void rz_diff_file_close(DiffFile *file) {
|
|
// plugin and file are freed by rz_bin_free
|
|
rz_bin_free(file->bin);
|
|
rz_diff_io_close(file->dio);
|
|
}
|
|
|
|
#define rz_diff_file_get(df, n) ((df)->file->o->n)
|
|
|
|
/**************************************** rzpvector ***************************************/
|
|
|
|
static const void *rz_diff_pvector_elem_at(const RzPVector /*<void *>*/ *array, ut32 index) {
|
|
return rz_pvector_at(array, index);
|
|
}
|
|
|
|
/**************************************** imports ***************************************/
|
|
|
|
static ut32 import_hash(const RzBinImport *elem) {
|
|
ut32 hash = rz_diff_hash_data((const ut8 *)elem->name, strlen(elem->name));
|
|
hash ^= rz_diff_hash_data((const ut8 *)elem->bind, strlen(SAFE_STR(elem->bind)));
|
|
hash ^= rz_diff_hash_data((const ut8 *)elem->type, strlen(SAFE_STR(elem->type)));
|
|
return hash;
|
|
}
|
|
|
|
static void import_stringify(const RzBinImport *elem, RzStrBuf *sb) {
|
|
rz_strbuf_setf(sb, "%-7s %-7s %s\n", SAFE_STR_DEF(elem->bind, RZ_BIN_BIND_UNKNOWN_STR),
|
|
SAFE_STR_DEF(elem->type, RZ_BIN_TYPE_UNKNOWN_STR), elem->name);
|
|
}
|
|
|
|
static int import_compare(const RzBinImport *a, const RzBinImport *b) {
|
|
int ret;
|
|
IF_STRCMP_S(ret, a->name, b->name);
|
|
IF_STRCMP_S(ret, a->bind, b->bind);
|
|
IF_STRCMP_S(ret, a->type, b->type);
|
|
return 0;
|
|
}
|
|
|
|
static int import_compare_vec(const RzBinImport *a, const RzBinImport *b, void *user) {
|
|
return import_compare(a, b);
|
|
}
|
|
|
|
static RzDiff *rz_diff_imports_new(DiffFile *dfile_a, DiffFile *dfile_b) {
|
|
RzPVector *vec_a = NULL;
|
|
RzPVector *vec_b = NULL;
|
|
|
|
vec_a = rz_diff_file_get(dfile_a, imports);
|
|
if (!vec_a) {
|
|
rz_diff_error_ret(NULL, "cannot get imports from '%s'\n", dfile_a->dio->filename);
|
|
}
|
|
|
|
vec_b = rz_diff_file_get(dfile_b, imports);
|
|
if (!vec_b) {
|
|
rz_diff_error_ret(NULL, "cannot get imports from '%s'\n", dfile_b->dio->filename);
|
|
}
|
|
|
|
rz_pvector_sort(vec_a, (RzPVectorComparator)import_compare_vec, NULL);
|
|
rz_pvector_sort(vec_b, (RzPVectorComparator)import_compare_vec, NULL);
|
|
|
|
RzDiffMethods methods = {
|
|
.elem_at = (RzDiffMethodElemAt)rz_diff_pvector_elem_at,
|
|
.elem_hash = (RzDiffMethodElemHash)import_hash,
|
|
.compare = (RzDiffMethodCompare)import_compare,
|
|
.stringify = (RzDiffMethodStringify)import_stringify,
|
|
.ignore = NULL,
|
|
};
|
|
|
|
return rz_diff_generic_new(vec_a, rz_pvector_len(vec_a), vec_b, rz_pvector_len(vec_b), &methods);
|
|
}
|
|
|
|
/**************************************** symbols ***************************************/
|
|
|
|
static ut32 symbol_hash_addr(const RzBinSymbol *elem) {
|
|
ut32 hash = rz_diff_hash_data((const ut8 *)elem->name, elem->name ? strlen(elem->name) : 0);
|
|
hash ^= rz_diff_hash_data((const ut8 *)elem->dname, elem->dname ? strlen(elem->dname) : 0);
|
|
hash ^= rz_diff_hash_data((const ut8 *)elem->libname, elem->libname ? strlen(elem->libname) : 0);
|
|
hash ^= rz_diff_hash_data((const ut8 *)elem->classname, elem->classname ? strlen(elem->classname) : 0);
|
|
hash ^= (ut32)(elem->vaddr >> 32);
|
|
hash ^= (ut32)elem->vaddr;
|
|
hash ^= (ut32)(elem->paddr >> 32);
|
|
hash ^= (ut32)elem->paddr;
|
|
return hash;
|
|
}
|
|
|
|
static int symbol_compare_addr(const RzBinSymbol *a, const RzBinSymbol *b) {
|
|
st64 ret;
|
|
IF_STRCMP_S(ret, a->classname, b->classname);
|
|
IF_STRCMP_S(ret, a->libname, b->libname);
|
|
IF_STRCMP_S(ret, a->dname, b->dname);
|
|
IF_STRCMP_S(ret, a->name, b->name);
|
|
ret = ((st64)b->paddr) - ((st64)a->paddr);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
return ((st64)b->vaddr) - ((st64)a->vaddr);
|
|
}
|
|
|
|
static void symbol_stringify_addr(const RzBinSymbol *elem, RzStrBuf *sb) {
|
|
rz_strbuf_setf(sb, "virt: 0x%016" PFMT64x " phys: 0x%016" PFMT64x " %s %s %s\n", elem->vaddr, elem->paddr, elem->libname, elem->classname, elem->name);
|
|
}
|
|
|
|
static ut32 symbol_hash(const RzBinSymbol *elem) {
|
|
return rz_diff_hash_data((const ut8 *)elem->name, strlen(elem->name));
|
|
}
|
|
|
|
static int symbol_compare(const RzBinSymbol *a, const RzBinSymbol *b) {
|
|
int ret;
|
|
IF_STRCMP_S(ret, a->name, b->name);
|
|
return 0;
|
|
}
|
|
|
|
static int symbol_compare_vec(const RzBinSymbol *a, const RzBinSymbol *b, void *user) {
|
|
return symbol_compare(a, b);
|
|
}
|
|
|
|
static void symbol_stringify(const RzBinSymbol *elem, RzStrBuf *sb) {
|
|
rz_strbuf_setf(sb, "%s %s %s\n", elem->libname, elem->classname, elem->name);
|
|
}
|
|
|
|
static RzDiff *rz_diff_symbols_new(DiffFile *dfile_a, DiffFile *dfile_b, bool compare_addr) {
|
|
RzPVector *vec_a = NULL;
|
|
RzPVector *vec_b = NULL;
|
|
|
|
vec_a = rz_diff_file_get(dfile_a, symbols);
|
|
if (!vec_a) {
|
|
rz_diff_error_ret(NULL, "cannot get symbols from '%s'\n", dfile_a->dio->filename);
|
|
}
|
|
|
|
vec_b = rz_diff_file_get(dfile_b, symbols);
|
|
if (!vec_b) {
|
|
rz_diff_error_ret(NULL, "cannot get symbols from '%s'\n", dfile_b->dio->filename);
|
|
}
|
|
|
|
rz_pvector_sort(vec_a, (RzPVectorComparator)symbol_compare_vec, NULL);
|
|
rz_pvector_sort(vec_b, (RzPVectorComparator)symbol_compare_vec, NULL);
|
|
|
|
RzDiffMethods methods = {
|
|
.elem_at = (RzDiffMethodElemAt)rz_diff_pvector_elem_at,
|
|
.elem_hash = (RzDiffMethodElemHash)(compare_addr ? symbol_hash_addr : symbol_hash),
|
|
.compare = (RzDiffMethodCompare)(compare_addr ? symbol_compare_addr : symbol_compare),
|
|
.stringify = (RzDiffMethodStringify)(compare_addr ? symbol_stringify_addr : symbol_stringify),
|
|
.ignore = NULL,
|
|
};
|
|
|
|
return rz_diff_generic_new(vec_a, rz_pvector_len(vec_a), vec_b, rz_pvector_len(vec_b), &methods);
|
|
}
|
|
|
|
/**************************************** strings ***************************************/
|
|
|
|
static ut32 string_hash_addr(const RzBinString *elem) {
|
|
ut32 hash = rz_diff_hash_data((const ut8 *)elem->string, elem->size);
|
|
hash ^= (ut32)(elem->vaddr >> 32);
|
|
hash ^= (ut32)elem->vaddr;
|
|
hash ^= (ut32)(elem->paddr >> 32);
|
|
hash ^= (ut32)elem->paddr;
|
|
return hash;
|
|
}
|
|
|
|
static int string_compare_addr(const RzBinString *a, const RzBinString *b) {
|
|
st64 ret;
|
|
ret = ((st64)b->size) - ((st64)a->size);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
IF_STRCMP_S(ret, a->string, b->string);
|
|
ret = ((st64)b->paddr) - ((st64)a->paddr);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
return ((st64)b->vaddr) - ((st64)a->vaddr);
|
|
}
|
|
|
|
static void string_stringify_addr(const RzBinString *elem, RzStrBuf *sb) {
|
|
rz_strbuf_setf(sb, "virt: 0x%016" PFMT64x " phys: 0x%016" PFMT64x " %s\n", elem->vaddr, elem->paddr, elem->string);
|
|
}
|
|
|
|
static ut32 string_hash(const RzBinString *elem) {
|
|
return rz_diff_hash_data((const ut8 *)elem->string, elem->size);
|
|
}
|
|
|
|
static int string_compare(const RzBinString *a, const RzBinString *b) {
|
|
st64 ret;
|
|
ret = ((st64)b->size) - ((st64)a->size);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
IF_STRCMP_S(ret, a->string, b->string);
|
|
return 0;
|
|
}
|
|
|
|
static void string_stringify(const RzBinString *elem, RzStrBuf *sb) {
|
|
rz_strbuf_setf(sb, "%s\n", elem->string);
|
|
}
|
|
|
|
static int string_compare_vec(const RzBinString *a, const RzBinString *b, void *user) {
|
|
return string_compare(a, b);
|
|
}
|
|
|
|
static RzDiff *rz_diff_strings_new(DiffFile *dfile_a, DiffFile *dfile_b, bool compare_addr) {
|
|
RzPVector *vec_a = NULL;
|
|
RzPVector *vec_b = NULL;
|
|
|
|
vec_a = (RzPVector *)rz_bin_object_get_strings(dfile_a->file->o);
|
|
if (!vec_a) {
|
|
rz_diff_error_ret(NULL, "cannot get strings from '%s'\n", dfile_a->dio->filename);
|
|
}
|
|
|
|
vec_b = (RzPVector *)rz_bin_object_get_strings(dfile_b->file->o);
|
|
if (!vec_b) {
|
|
rz_diff_error_ret(NULL, "cannot get strings from '%s'\n", dfile_b->dio->filename);
|
|
}
|
|
|
|
rz_pvector_sort(vec_a, (RzPVectorComparator)string_compare_vec, NULL);
|
|
rz_pvector_sort(vec_b, (RzPVectorComparator)string_compare_vec, NULL);
|
|
|
|
RzDiffMethods methods = {
|
|
.elem_at = (RzDiffMethodElemAt)rz_diff_pvector_elem_at,
|
|
.elem_hash = (RzDiffMethodElemHash)(compare_addr ? string_hash_addr : string_hash),
|
|
.compare = (RzDiffMethodCompare)(compare_addr ? string_compare_addr : string_compare),
|
|
.stringify = (RzDiffMethodStringify)(compare_addr ? string_stringify_addr : string_stringify),
|
|
.ignore = NULL,
|
|
};
|
|
|
|
return rz_diff_generic_new(vec_a, rz_pvector_len(vec_a), vec_b, rz_pvector_len(vec_b), &methods);
|
|
}
|
|
|
|
/**************************************** classes ***************************************/
|
|
|
|
static ut32 class_hash_addr(const RzBinClass *elem) {
|
|
ut32 hash = rz_diff_hash_data((const ut8 *)elem->name, strlen(elem->name));
|
|
hash ^= rz_diff_hash_data((const ut8 *)elem->super, strlen(elem->super));
|
|
hash ^= (ut32)(elem->addr >> 32);
|
|
hash ^= (ut32)elem->addr;
|
|
return hash;
|
|
}
|
|
|
|
static int class_compare_addr(const RzBinClass *a, const RzBinClass *b) {
|
|
int ret;
|
|
IF_STRCMP_S(ret, a->super, b->super);
|
|
IF_STRCMP_S(ret, a->name, b->name);
|
|
return a->addr - b->addr;
|
|
}
|
|
|
|
static void class_stringify_addr(const RzBinClass *elem, RzStrBuf *sb) {
|
|
rz_strbuf_setf(sb, "0x%016" PFMT64x " %s %s\n", elem->addr, SAFE_STR(elem->super), elem->name);
|
|
}
|
|
|
|
static ut32 class_hash(const RzBinClass *elem) {
|
|
ut32 hash = rz_diff_hash_data((const ut8 *)elem->name, strlen(elem->name));
|
|
hash ^= rz_diff_hash_data((const ut8 *)elem->super, strlen(elem->super));
|
|
return hash;
|
|
}
|
|
|
|
static int class_compare(const RzBinClass *a, const RzBinClass *b) {
|
|
int ret;
|
|
IF_STRCMP_S(ret, a->super, b->super);
|
|
IF_STRCMP_S(ret, a->name, b->name);
|
|
return 0;
|
|
}
|
|
|
|
static int class_compare_vec(const RzBinClass *a, const RzBinClass *b, void *user) {
|
|
return class_compare(a, b);
|
|
}
|
|
|
|
static void class_stringify(const RzBinClass *elem, RzStrBuf *sb) {
|
|
rz_strbuf_setf(sb, "%s %s\n", SAFE_STR(elem->super), elem->name);
|
|
}
|
|
|
|
static RzDiff *rz_diff_classes_new(DiffFile *dfile_a, DiffFile *dfile_b, bool compare_addr) {
|
|
RzPVector *vec_a = NULL;
|
|
RzPVector *vec_b = NULL;
|
|
|
|
vec_a = rz_diff_file_get(dfile_a, classes);
|
|
if (!vec_a) {
|
|
rz_diff_error_ret(NULL, "cannot get classes from '%s'\n", dfile_a->dio->filename);
|
|
}
|
|
|
|
vec_b = rz_diff_file_get(dfile_b, classes);
|
|
if (!vec_b) {
|
|
rz_diff_error_ret(NULL, "cannot get classes from '%s'\n", dfile_b->dio->filename);
|
|
}
|
|
|
|
rz_pvector_sort(vec_a, (RzPVectorComparator)class_compare_vec, NULL);
|
|
rz_pvector_sort(vec_b, (RzPVectorComparator)class_compare_vec, NULL);
|
|
|
|
RzDiffMethods methods = {
|
|
.elem_at = (RzDiffMethodElemAt)rz_diff_pvector_elem_at,
|
|
.elem_hash = (RzDiffMethodElemHash)(compare_addr ? class_hash_addr : class_hash),
|
|
.compare = (RzDiffMethodCompare)(compare_addr ? class_compare_addr : class_compare),
|
|
.stringify = (RzDiffMethodStringify)(compare_addr ? class_stringify_addr : class_stringify),
|
|
.ignore = NULL,
|
|
};
|
|
|
|
return rz_diff_generic_new(vec_a, rz_pvector_len(vec_a), vec_b, rz_pvector_len(vec_b), &methods);
|
|
}
|
|
|
|
/**************************************** entries ***************************************/
|
|
|
|
static ut32 entry_hash(const RzBinAddr *elem) {
|
|
ut32 hash = rz_diff_hash_data((const ut8 *)"entry", strlen("entry"));
|
|
hash ^= (ut32)(elem->vaddr >> 32);
|
|
hash ^= (ut32)elem->vaddr;
|
|
hash ^= (ut32)(elem->paddr >> 32);
|
|
hash ^= (ut32)elem->paddr;
|
|
hash ^= (ut32)elem->type;
|
|
return hash;
|
|
}
|
|
|
|
static int entry_compare(const RzBinAddr *a, const RzBinAddr *b, void *user) {
|
|
st64 ret;
|
|
ret = ((st64)b->paddr) - ((st64)a->paddr);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
ret = ((st64)b->vaddr) - ((st64)a->vaddr);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
return b->type - a->type;
|
|
}
|
|
|
|
static void entry_stringify(const RzBinAddr *elem, RzStrBuf *sb) {
|
|
const char *name = NULL;
|
|
switch (elem->type) {
|
|
case RZ_BIN_ENTRY_TYPE_PROGRAM:
|
|
name = "program";
|
|
break;
|
|
case RZ_BIN_ENTRY_TYPE_MAIN:
|
|
name = "main";
|
|
break;
|
|
case RZ_BIN_ENTRY_TYPE_INIT:
|
|
name = "init";
|
|
break;
|
|
case RZ_BIN_ENTRY_TYPE_FINI:
|
|
name = "fini";
|
|
break;
|
|
case RZ_BIN_ENTRY_TYPE_TLS:
|
|
name = "tls";
|
|
break;
|
|
case RZ_BIN_ENTRY_TYPE_PREINIT:
|
|
name = "preinit";
|
|
break;
|
|
default:
|
|
name = "unknown";
|
|
break;
|
|
}
|
|
rz_strbuf_setf(sb, "virt: 0x%016" PFMT64x " phys: 0x%016" PFMT64x " entry %s\n", elem->vaddr, elem->paddr, name);
|
|
}
|
|
|
|
static RzDiff *rz_diff_entries_new(DiffFile *dfile_a, DiffFile *dfile_b) {
|
|
RzPVector *vec_a = NULL;
|
|
RzPVector *vec_b = NULL;
|
|
|
|
vec_a = rz_diff_file_get(dfile_a, entries);
|
|
if (!vec_a) {
|
|
rz_diff_error_ret(NULL, "cannot get entries from '%s'\n", dfile_a->dio->filename);
|
|
}
|
|
|
|
vec_b = rz_diff_file_get(dfile_b, entries);
|
|
if (!vec_b) {
|
|
rz_diff_error_ret(NULL, "cannot get entries from '%s'\n", dfile_b->dio->filename);
|
|
}
|
|
|
|
rz_pvector_sort(vec_a, (RzPVectorComparator)entry_compare, NULL);
|
|
rz_pvector_sort(vec_b, (RzPVectorComparator)entry_compare, NULL);
|
|
|
|
RzDiffMethods methods = {
|
|
.elem_at = (RzDiffMethodElemAt)rz_diff_pvector_elem_at,
|
|
.elem_hash = (RzDiffMethodElemHash)entry_hash,
|
|
.compare = (RzDiffMethodCompare)entry_compare,
|
|
.stringify = (RzDiffMethodStringify)entry_stringify,
|
|
.ignore = NULL,
|
|
};
|
|
|
|
return rz_diff_generic_new(vec_a, rz_pvector_len(vec_a), vec_b, rz_pvector_len(vec_b), &methods);
|
|
}
|
|
|
|
/**************************************** libraries ***************************************/
|
|
|
|
static ut32 libs_hash(const char *elem) {
|
|
return rz_diff_hash_data((const ut8 *)elem, strlen(elem));
|
|
}
|
|
|
|
static int libs_compare(const char *a, const char *b) {
|
|
int ret;
|
|
IF_STRCMP_S(ret, a, b);
|
|
return 0;
|
|
}
|
|
|
|
static int libs_compare_vec(const char *a, const char *b, void *user) {
|
|
return libs_compare(a, b);
|
|
}
|
|
|
|
static void libs_stringify(const char *elem, RzStrBuf *sb) {
|
|
rz_strbuf_setf(sb, "%s\n", SAFE_STR(elem));
|
|
}
|
|
|
|
static RzDiff *rz_diff_libraries_new(DiffFile *dfile_a, DiffFile *dfile_b) {
|
|
RzPVector *vec_a = NULL;
|
|
RzPVector *vec_b = NULL;
|
|
|
|
vec_a = rz_diff_file_get(dfile_a, libs);
|
|
if (!vec_a) {
|
|
rz_diff_error_ret(NULL, "cannot get libraries from '%s'\n", dfile_a->dio->filename);
|
|
}
|
|
|
|
vec_b = rz_diff_file_get(dfile_b, libs);
|
|
if (!vec_b) {
|
|
rz_diff_error_ret(NULL, "cannot get libraries from '%s'\n", dfile_b->dio->filename);
|
|
}
|
|
|
|
rz_pvector_sort(vec_a, (RzPVectorComparator)libs_compare_vec, NULL);
|
|
rz_pvector_sort(vec_b, (RzPVectorComparator)libs_compare_vec, NULL);
|
|
|
|
RzDiffMethods methods = {
|
|
.elem_at = (RzDiffMethodElemAt)rz_diff_pvector_elem_at,
|
|
.elem_hash = (RzDiffMethodElemHash)libs_hash,
|
|
.compare = (RzDiffMethodCompare)libs_compare,
|
|
.stringify = (RzDiffMethodStringify)libs_stringify,
|
|
.ignore = NULL,
|
|
};
|
|
|
|
return rz_diff_generic_new(vec_a, rz_pvector_len(vec_a), vec_b, rz_pvector_len(vec_b), &methods);
|
|
}
|
|
|
|
/**************************************** sections ***************************************/
|
|
|
|
static ut32 section_hash_addr(const RzBinSection *elem) {
|
|
ut32 hash = rz_diff_hash_data((const ut8 *)elem->name, strlen(elem->name));
|
|
hash ^= elem->perm;
|
|
hash ^= (ut32)(elem->align >> 32);
|
|
hash ^= (ut32)elem->align;
|
|
hash ^= (ut32)(elem->vsize >> 32);
|
|
hash ^= (ut32)elem->vsize;
|
|
hash ^= (ut32)(elem->size >> 32);
|
|
hash ^= (ut32)elem->size;
|
|
hash ^= (ut32)(elem->vaddr >> 32);
|
|
hash ^= (ut32)elem->vaddr;
|
|
hash ^= (ut32)(elem->paddr >> 32);
|
|
hash ^= (ut32)elem->paddr;
|
|
return hash;
|
|
}
|
|
|
|
static int section_compare_addr(const RzBinSection *a, const RzBinSection *b) {
|
|
st64 ret;
|
|
IF_STRCMP_S(ret, a->name, b->name);
|
|
ret = ((st64)b->size) - ((st64)a->size);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
ret = ((st64)b->vsize) - ((st64)a->vsize);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
ret = ((st64)b->paddr) - ((st64)a->paddr);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
ret = ((st64)b->vaddr) - ((st64)a->vaddr);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
ret = ((st64)b->perm) - ((st64)a->perm);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
return ((st64)b->align) - ((st64)a->align);
|
|
}
|
|
|
|
static void section_stringify_addr(const RzBinSection *elem, RzStrBuf *sb) {
|
|
char perm[5];
|
|
|
|
perm[0] = elem->perm & RZ_PERM_SHAR ? 's' : '-';
|
|
perm[1] = elem->perm & RZ_PERM_R ? 'r' : '-';
|
|
perm[2] = elem->perm & RZ_PERM_W ? 'w' : '-';
|
|
perm[3] = elem->perm & RZ_PERM_X ? 'x' : '-';
|
|
perm[4] = 0;
|
|
|
|
rz_strbuf_setf(sb, "virt: 0x%016" PFMT64x ":0x%04" PFMT64x " phys: 0x%016" PFMT64x ":0x%04" PFMT64x " align: 0x%08" PFMT64x " %s %s\n",
|
|
elem->vaddr, elem->vsize, elem->paddr, elem->size, elem->align, perm, elem->name);
|
|
}
|
|
|
|
static ut32 section_hash(const RzBinSection *elem) {
|
|
ut32 hash = rz_diff_hash_data((const ut8 *)elem->name, strlen(elem->name));
|
|
hash ^= elem->perm;
|
|
hash ^= (ut32)(elem->align >> 32);
|
|
hash ^= (ut32)elem->align;
|
|
return hash;
|
|
}
|
|
|
|
static int section_compare(const RzBinSection *a, const RzBinSection *b) {
|
|
st64 ret;
|
|
IF_STRCMP_S(ret, a->name, b->name);
|
|
ret = ((st64)b->perm) - ((st64)a->perm);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
return ((st64)b->align) - ((st64)a->align);
|
|
}
|
|
|
|
static void section_stringify(const RzBinSection *elem, RzStrBuf *sb) {
|
|
char perm[5];
|
|
|
|
perm[0] = elem->perm & RZ_PERM_SHAR ? 's' : '-';
|
|
perm[1] = elem->perm & RZ_PERM_R ? 'r' : '-';
|
|
perm[2] = elem->perm & RZ_PERM_W ? 'w' : '-';
|
|
perm[3] = elem->perm & RZ_PERM_X ? 'x' : '-';
|
|
perm[4] = 0;
|
|
|
|
rz_strbuf_setf(sb, "align: 0x%08" PFMT64x " %s %s\n", elem->align, perm, elem->name);
|
|
}
|
|
|
|
static RzDiff *rz_diff_sections_new(DiffFile *dfile_a, DiffFile *dfile_b, bool compare_addr) {
|
|
RzPVector *vec_a = NULL;
|
|
RzPVector *vec_b = NULL;
|
|
|
|
vec_a = rz_diff_file_get(dfile_a, sections);
|
|
if (!vec_a) {
|
|
rz_diff_error_ret(NULL, "cannot get sections from '%s'\n", dfile_a->dio->filename);
|
|
}
|
|
|
|
vec_b = rz_diff_file_get(dfile_b, sections);
|
|
if (!vec_b) {
|
|
rz_diff_error_ret(NULL, "cannot get sections from '%s'\n", dfile_b->dio->filename);
|
|
}
|
|
|
|
rz_pvector_sort(vec_a, (RzPVectorComparator)section_compare, NULL);
|
|
rz_pvector_sort(vec_b, (RzPVectorComparator)section_compare, NULL);
|
|
|
|
RzDiffMethods methods = {
|
|
.elem_at = (RzDiffMethodElemAt)rz_diff_pvector_elem_at,
|
|
.elem_hash = (RzDiffMethodElemHash)(compare_addr ? section_hash_addr : section_hash),
|
|
.compare = (RzDiffMethodCompare)(compare_addr ? section_compare_addr : section_compare),
|
|
.stringify = (RzDiffMethodStringify)(compare_addr ? section_stringify_addr : section_stringify),
|
|
.ignore = NULL,
|
|
};
|
|
|
|
return rz_diff_generic_new(vec_a, rz_pvector_len(vec_a), vec_b, rz_pvector_len(vec_b), &methods);
|
|
}
|
|
|
|
/**************************************** fields ***************************************/
|
|
|
|
static ut32 field_hash_addr(const RzBinField *elem) {
|
|
ut32 hash = rz_diff_hash_data((const ut8 *)elem->name, strlen(elem->name));
|
|
hash ^= rz_diff_hash_data((const ut8 *)elem->type, strlen(SAFE_STR(elem->type)));
|
|
hash ^= (ut32)(elem->paddr >> 32);
|
|
hash ^= (ut32)elem->paddr;
|
|
hash ^= (ut32)(elem->vaddr >> 32);
|
|
hash ^= (ut32)elem->vaddr;
|
|
return hash;
|
|
}
|
|
|
|
static int field_compare_addr(const RzBinField *a, const RzBinField *b) {
|
|
st64 ret;
|
|
IF_STRCMP_S(ret, a->name, b->name);
|
|
IF_STRCMP_S(ret, a->type, b->type);
|
|
ret = ((st64)b->size) - ((st64)a->size);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
ret = ((st64)b->paddr) - ((st64)a->paddr);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
return ((st64)b->vaddr) - ((st64)a->vaddr);
|
|
}
|
|
|
|
static void field_stringify_addr(const RzBinField *elem, RzStrBuf *sb) {
|
|
rz_strbuf_setf(sb, "virt: 0x%016" PFMT64x " phys: 0x%016" PFMT64x " %-8s %s\n",
|
|
elem->vaddr, elem->paddr, SAFE_STR(elem->type), elem->name);
|
|
}
|
|
|
|
static ut32 field_hash(const RzBinField *elem) {
|
|
ut32 hash = rz_diff_hash_data((const ut8 *)elem->name, strlen(elem->name));
|
|
hash ^= rz_diff_hash_data((const ut8 *)elem->type, strlen(SAFE_STR(elem->type)));
|
|
return hash;
|
|
}
|
|
|
|
static int field_compare(const RzBinField *a, const RzBinField *b) {
|
|
int ret;
|
|
IF_STRCMP_S(ret, a->name, b->name);
|
|
IF_STRCMP_S(ret, a->type, b->type);
|
|
return 0;
|
|
}
|
|
|
|
static int field_compare_addr_vec(const RzBinField *a, const RzBinField *b, void *user) {
|
|
return field_compare_addr(a, b);
|
|
}
|
|
|
|
static int field_compare_vec(const RzBinField *a, const RzBinField *b, void *user) {
|
|
return field_compare(a, b);
|
|
}
|
|
|
|
static void field_stringify(const RzBinField *elem, RzStrBuf *sb) {
|
|
rz_strbuf_setf(sb, "%s %s\n", SAFE_STR(elem->type), elem->name);
|
|
}
|
|
|
|
static RzDiff *rz_diff_fields_new(DiffFile *dfile_a, DiffFile *dfile_b, bool compare_addr) {
|
|
RzPVector *vec_a = NULL;
|
|
RzPVector *vec_b = NULL;
|
|
|
|
vec_a = rz_diff_file_get(dfile_a, fields);
|
|
if (!vec_a) {
|
|
rz_diff_error_ret(NULL, "cannot get fields from '%s'\n", dfile_a->dio->filename);
|
|
}
|
|
|
|
vec_b = rz_diff_file_get(dfile_b, fields);
|
|
if (!vec_b) {
|
|
rz_diff_error_ret(NULL, "cannot get fields from '%s'\n", dfile_b->dio->filename);
|
|
}
|
|
|
|
rz_pvector_sort(vec_a, (RzPVectorComparator)(compare_addr ? field_compare_addr_vec : field_compare_vec), NULL);
|
|
rz_pvector_sort(vec_b, (RzPVectorComparator)(compare_addr ? field_compare_addr_vec : field_compare_vec), NULL);
|
|
|
|
RzDiffMethods methods = {
|
|
.elem_at = (RzDiffMethodElemAt)rz_diff_pvector_elem_at,
|
|
.elem_hash = (RzDiffMethodElemHash)(compare_addr ? field_hash_addr : field_hash),
|
|
.compare = (RzDiffMethodCompare)(compare_addr ? field_compare_addr : field_compare),
|
|
.stringify = (RzDiffMethodStringify)(compare_addr ? field_stringify_addr : field_stringify),
|
|
.ignore = NULL,
|
|
};
|
|
|
|
return rz_diff_generic_new(vec_a, rz_pvector_len(vec_a), vec_b, rz_pvector_len(vec_b), &methods);
|
|
}
|
|
|
|
/**************************************** commands ***************************************/
|
|
|
|
static char *execute_command(const char *command, const char *filename, DiffContext *ctx) {
|
|
RzCoreFile *cfile = rz_diff_load_file_with_core(filename, ctx->architecture, ctx->arch_bits, ctx->evars, ctx->colors, ctx->verbose);
|
|
if (!cfile) {
|
|
return NULL;
|
|
}
|
|
|
|
if (ctx->analyze_all) {
|
|
if (ctx->verbose) {
|
|
fprintf(stderr, "rz-diff: analysing file '%s'\n", filename);
|
|
}
|
|
if (!rz_core_analysis_everything(cfile->core, false, NULL)) {
|
|
rz_diff_error("cannot analyze binary '%s'\n", filename);
|
|
}
|
|
}
|
|
|
|
char *output = rz_core_cmd_str(cfile->core, command);
|
|
rz_core_free(cfile->core);
|
|
return output;
|
|
}
|
|
|
|
static RzDiff *rz_diff_command_new(DiffContext *ctx) {
|
|
char *output_a = NULL;
|
|
char *output_b = NULL;
|
|
|
|
output_a = execute_command(ctx->input_a, ctx->file_a, ctx);
|
|
if (!output_a) {
|
|
rz_diff_error_ret(NULL, "cannot execute command '%s' on file '%s'\n", ctx->input_a, ctx->file_a);
|
|
}
|
|
|
|
output_b = execute_command(ctx->input_b, ctx->file_b, ctx);
|
|
if (!output_b) {
|
|
free(output_a);
|
|
rz_diff_error_ret(NULL, "cannot execute command '%s' on file '%s'\n", ctx->input_b, ctx->file_b);
|
|
}
|
|
|
|
RzDiff *diff = rz_diff_lines_new(output_a, output_b, NULL);
|
|
free(output_a);
|
|
free(output_b);
|
|
return diff;
|
|
}
|
|
|
|
/**************************************** unified ***************************************/
|
|
|
|
static bool rz_diff_unified_files(DiffContext *ctx) {
|
|
size_t a_size = 0;
|
|
size_t b_size = 0;
|
|
ut8 *a_buffer = NULL;
|
|
ut8 *b_buffer = NULL;
|
|
DiffFile dfile_a = { 0 };
|
|
DiffFile dfile_b = { 0 };
|
|
RzDiff *diff = NULL;
|
|
bool result = false;
|
|
|
|
if (ctx->type == DIFF_TYPE_BYTES ||
|
|
ctx->type == DIFF_TYPE_LINES) {
|
|
if (!(a_buffer = rz_diff_slurp_file(ctx->file_a, &a_size))) {
|
|
goto rz_diff_unified_files_bad;
|
|
}
|
|
|
|
if (!(b_buffer = rz_diff_slurp_file(ctx->file_b, &b_size))) {
|
|
goto rz_diff_unified_files_bad;
|
|
}
|
|
} else if (ctx->type != DIFF_TYPE_FUNCTIONS && ctx->type != DIFF_TYPE_COMMAND) {
|
|
if (!rz_diff_file_open(&dfile_a, ctx->file_a)) {
|
|
goto rz_diff_unified_files_bad;
|
|
}
|
|
if (!rz_diff_file_open(&dfile_b, ctx->file_b)) {
|
|
goto rz_diff_unified_files_bad;
|
|
}
|
|
}
|
|
|
|
switch (ctx->type) {
|
|
case DIFF_TYPE_BYTES:
|
|
diff = rz_diff_bytes_new(a_buffer, a_size, b_buffer, b_size);
|
|
break;
|
|
case DIFF_TYPE_CLASSES:
|
|
diff = rz_diff_classes_new(&dfile_a, &dfile_b, ctx->compare_addresses);
|
|
break;
|
|
case DIFF_TYPE_COMMAND:
|
|
diff = rz_diff_command_new(ctx);
|
|
break;
|
|
case DIFF_TYPE_ENTRIES:
|
|
diff = rz_diff_entries_new(&dfile_a, &dfile_b);
|
|
break;
|
|
case DIFF_TYPE_FIELDS:
|
|
diff = rz_diff_fields_new(&dfile_a, &dfile_b, ctx->compare_addresses);
|
|
break;
|
|
case DIFF_TYPE_IMPORTS:
|
|
diff = rz_diff_imports_new(&dfile_a, &dfile_b);
|
|
break;
|
|
case DIFF_TYPE_LIBRARIES:
|
|
diff = rz_diff_libraries_new(&dfile_a, &dfile_b);
|
|
break;
|
|
case DIFF_TYPE_LINES:
|
|
diff = rz_diff_lines_new((const char *)a_buffer, (const char *)b_buffer, NULL);
|
|
break;
|
|
case DIFF_TYPE_SECTIONS:
|
|
diff = rz_diff_sections_new(&dfile_a, &dfile_b, ctx->compare_addresses);
|
|
break;
|
|
case DIFF_TYPE_STRINGS:
|
|
diff = rz_diff_strings_new(&dfile_a, &dfile_b, ctx->compare_addresses);
|
|
break;
|
|
case DIFF_TYPE_SYMBOLS:
|
|
diff = rz_diff_symbols_new(&dfile_a, &dfile_b, ctx->compare_addresses);
|
|
break;
|
|
default:
|
|
rz_diff_error("unknown type\n");
|
|
goto rz_diff_unified_files_bad;
|
|
}
|
|
|
|
if (!diff) {
|
|
goto rz_diff_unified_files_bad;
|
|
}
|
|
|
|
if (ctx->mode == DIFF_MODE_JSON) {
|
|
PJ *pj = rz_diff_unified_json(diff, ctx->file_a, ctx->file_b, ctx->show_time);
|
|
if (!pj) {
|
|
goto rz_diff_unified_files_bad;
|
|
}
|
|
printf("%s\n", pj_string(pj));
|
|
pj_free(pj);
|
|
} else {
|
|
// DIFF_MODE_STANDARD & DIFF_MODE_QUIET
|
|
char *result = rz_diff_unified_text(diff, ctx->file_a, ctx->file_b, ctx->show_time, ctx->colors);
|
|
if (!result) {
|
|
goto rz_diff_unified_files_bad;
|
|
}
|
|
puts(result);
|
|
free(result);
|
|
}
|
|
|
|
result = true;
|
|
|
|
rz_diff_unified_files_bad:
|
|
rz_diff_free(diff);
|
|
rz_diff_file_close(&dfile_a);
|
|
rz_diff_file_close(&dfile_b);
|
|
free(a_buffer);
|
|
free(b_buffer);
|
|
return result;
|
|
}
|
|
|
|
/**************************************** graphs ***************************************/
|
|
|
|
static const char *get_config_or_default(RzCore *core, const char *key, const char *def_value) {
|
|
const char *value = rz_config_get(core->config, key);
|
|
if (RZ_STR_ISEMPTY(value)) {
|
|
return def_value;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static void graphviz_dot_header(RzCore *core_a) {
|
|
const char *font = get_config_or_default(core_a, "graph.font", "Courier");
|
|
const char *gv_edge = get_config_or_default(core_a, "graph.gv.edge", "arrowhead=\"normal\"");
|
|
const char *gv_node = get_config_or_default(core_a, "graph.gv.node", "fillcolor=gray style=filled shape=box");
|
|
const char *gv_spline = get_config_or_default(core_a, "graph.gv.spline", "splines=\"ortho\"");
|
|
rz_cons_printf("digraph code {\n"
|
|
"\tgraph [bgcolor=azure fontsize=8 fontname=\"%s\" %s];\n"
|
|
"\tnode [%s];\n"
|
|
"\tedge [%s];\n",
|
|
font, gv_spline, gv_node, gv_edge);
|
|
}
|
|
|
|
static void print_color_node(RzCore *core, RzAnalysisBlock *bbi) {
|
|
bool color_current = rz_config_get_b(core->config, "graph.gv.current");
|
|
bool current = rz_analysis_block_contains(bbi, core->offset);
|
|
if (current && color_current) {
|
|
rz_cons_printf("\t\"0x%08" PFMT64x "\" ", bbi->addr);
|
|
rz_cons_printf("\t[fillcolor=gray style=filled shape=box];\n");
|
|
}
|
|
}
|
|
|
|
static char *basic_block_opcodes(RzCore *core, RzAnalysisBlock *bbi) {
|
|
char *opcodes = NULL;
|
|
RzConfigHold *hc = NULL;
|
|
ut8 *block = NULL;
|
|
|
|
hc = rz_config_hold_new2(core->config, "scr.color", "scr.utf8", "asm.offset", "asm.lines", "asm.cmt.right", "asm.lines.fcn", "asm.bytes", "asm.comments", NULL);
|
|
if (!hc) {
|
|
return NULL;
|
|
}
|
|
rz_config_set_i(core->config, "scr.utf8", 0);
|
|
rz_config_set_i(core->config, "asm.offset", 0);
|
|
rz_config_set_i(core->config, "asm.lines", 0);
|
|
rz_config_set_i(core->config, "asm.cmt.right", 0);
|
|
rz_config_set_i(core->config, "asm.lines.fcn", 0);
|
|
rz_config_set_i(core->config, "asm.bytes", 0);
|
|
rz_config_set_i(core->config, "asm.comments", 0);
|
|
rz_config_set_i(core->config, "scr.color", COLOR_MODE_DISABLED);
|
|
|
|
rz_cons_push();
|
|
RzAnalysisBlock *b = rz_analysis_find_most_relevant_block_in(core->analysis, bbi->addr);
|
|
if (!b) {
|
|
RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", bbi->addr);
|
|
goto exit;
|
|
}
|
|
|
|
block = malloc(b->size + 1);
|
|
if (!block) {
|
|
RZ_LOG_ERROR("Cannot allocate buffer\n");
|
|
goto exit;
|
|
}
|
|
|
|
rz_io_read_at_mapped(core->io, b->addr, block, b->size);
|
|
RzCoreDisasmOptions disasm_options = {
|
|
.cbytes = 2,
|
|
};
|
|
rz_core_print_disasm(core, b->addr, block, b->size, 9999, NULL, &disasm_options);
|
|
rz_cons_filter();
|
|
const char *retstr = rz_str_get(rz_cons_get_buffer());
|
|
opcodes = rz_str_dup(retstr);
|
|
exit:
|
|
rz_cons_pop();
|
|
rz_cons_echo(NULL);
|
|
free(block);
|
|
rz_config_hold_restore(hc);
|
|
rz_config_hold_free(hc);
|
|
return opcodes;
|
|
}
|
|
|
|
static const char *pair_color(RzAnalysisMatchPair *pair) {
|
|
if (pair->similarity >= 1.0) {
|
|
return "lightgray";
|
|
} else if (pair->similarity >= RZ_ANALYSIS_SIMILARITY_THRESHOLD) {
|
|
return "yellow";
|
|
}
|
|
return "red";
|
|
}
|
|
|
|
static void graphviz_dot_nodes(RzCore *core_a, RzAnalysisFunction *fcn_a, RzCore *core_b, RzAnalysisFunction *fcn_b, RzAnalysisMatchResult *result) {
|
|
char addr_a[32], addr_b[32];
|
|
|
|
RzAnalysisMatchPair *pair = NULL;
|
|
RzAnalysisBlock *bbi = NULL;
|
|
RzListIter *iter = NULL;
|
|
const char *font = get_config_or_default(core_a, "graph.font", "Courier");
|
|
|
|
rz_strf(addr_a, "0x%08" PFMT64x, fcn_a->addr);
|
|
rz_strf(addr_b, "0x%08" PFMT64x, fcn_b->addr);
|
|
|
|
const char *norig = fcn_a->name ? fcn_a->name : addr_a;
|
|
const char *nmodi = fcn_b->name ? fcn_b->name : addr_b;
|
|
|
|
// we add all the matching basic block first
|
|
rz_list_foreach (result->matches, iter, pair) {
|
|
bbi = (RzAnalysisBlock *)pair->pair_a;
|
|
|
|
const char *fillcolor = pair_color(pair);
|
|
char *original = basic_block_opcodes(core_a, bbi);
|
|
if (!original) {
|
|
break;
|
|
}
|
|
|
|
if (pair->similarity >= RZ_ANALYSIS_SIMILARITY_THRESHOLD) {
|
|
// if they are similar then we diff the opcodes.
|
|
|
|
char *modified = basic_block_opcodes(core_b, (RzAnalysisBlock *)pair->pair_b);
|
|
if (modified && strcmp(original, modified) != 0) {
|
|
RzDiff *dff = rz_diff_lines_new(original, modified, NULL);
|
|
char *diffstr = rz_diff_unified_text(dff, norig, nmodi, false, false);
|
|
rz_diff_free(dff);
|
|
free(modified);
|
|
|
|
rz_str_replace_char(diffstr, '"', '\'');
|
|
diffstr = rz_str_replace(diffstr, "\n", "\\l", 1);
|
|
rz_cons_printf("\t\"0x%08" PFMT64x "\" [fillcolor=\"%s\","
|
|
"color=\"black\", fontname=\"%s\","
|
|
" label=\"%s\", URL=\"%s/0x%08" PFMT64x "\"]\n",
|
|
bbi->addr, fillcolor, font, diffstr, fcn_a->name,
|
|
bbi->addr);
|
|
free(diffstr);
|
|
free(original);
|
|
continue;
|
|
}
|
|
|
|
// sometimes the mismatch is on a call value
|
|
// but the output is actually the same due sym./imp.
|
|
// thus we ignore the similarity check and consider this
|
|
// as a perfect match.
|
|
free(modified);
|
|
}
|
|
|
|
rz_str_replace_char(original, '"', '\'');
|
|
original = rz_str_replace(original, "\n", "\\l", 1);
|
|
rz_cons_printf("\t\"0x%08" PFMT64x "\" [fillcolor=\"%s\","
|
|
"color=\"black\", fontname=\"%s\","
|
|
" label=\"%s\", URL=\"%s/0x%08" PFMT64x "\"]\n",
|
|
bbi->addr, fillcolor, font, original, fcn_a->name, bbi->addr);
|
|
free(original);
|
|
}
|
|
|
|
// we then add all the unmatched basic blocks
|
|
rz_list_foreach (result->unmatch_a, iter, bbi) {
|
|
char *opcodes = basic_block_opcodes(core_a, bbi);
|
|
if (!opcodes) {
|
|
break;
|
|
}
|
|
|
|
rz_str_replace_char(opcodes, '"', '\'');
|
|
opcodes = rz_str_replace(opcodes, "\n", "\\l", 1);
|
|
rz_cons_printf("\t\"0x%08" PFMT64x "\" [fillcolor=\"white\","
|
|
"color=\"black\", fontname=\"%s\","
|
|
" label=\"%s\", URL=\"%s/0x%08" PFMT64x "\"]\n",
|
|
bbi->addr, font, opcodes, fcn_a->name, bbi->addr);
|
|
free(opcodes);
|
|
}
|
|
}
|
|
|
|
#define PAL_JUMP "#0037da"
|
|
#define PAL_FAIL "#c50f1f"
|
|
#define PAL_TRUE "#13a10e"
|
|
static void graphviz_dot_edges(RzCore *core, RzAnalysisFunction *fcn) {
|
|
RzAnalysisBlock *bbi;
|
|
void **iter;
|
|
|
|
rz_pvector_foreach (fcn->bbs, iter) {
|
|
bbi = (RzAnalysisBlock *)*iter;
|
|
if (bbi->jump != UT64_MAX) {
|
|
rz_cons_printf("\t\"0x%08" PFMT64x "\" -> \"0x%08" PFMT64x "\" [color=\"%s\"];\n",
|
|
bbi->addr, bbi->jump,
|
|
bbi->fail != UT64_MAX ? PAL_TRUE : PAL_JUMP);
|
|
print_color_node(core, bbi);
|
|
}
|
|
if (bbi->fail != UT64_MAX) {
|
|
rz_cons_printf("\t\"0x%08" PFMT64x "\" -> \"0x%08" PFMT64x "\" [color=\"" PAL_FAIL "\"];\n",
|
|
bbi->addr, bbi->fail);
|
|
print_color_node(core, bbi);
|
|
}
|
|
if (bbi->switch_op) {
|
|
RzAnalysisCaseOp *caseop;
|
|
RzListIter *iter2;
|
|
|
|
if (bbi->fail != UT64_MAX) {
|
|
rz_cons_printf("\t\"0x%08" PFMT64x "\" -> \"0x%08" PFMT64x "\" [color=\"" PAL_FAIL "\"];\n",
|
|
bbi->addr, bbi->fail);
|
|
print_color_node(core, bbi);
|
|
}
|
|
rz_list_foreach (bbi->switch_op->cases, iter2, caseop) {
|
|
rz_cons_printf("\t\"0x%08" PFMT64x "\" -> \"0x%08" PFMT64x "\" [color2=\"" PAL_FAIL "\"];\n",
|
|
caseop->addr, caseop->jump);
|
|
print_color_node(core, bbi);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#undef PAL_JUMP
|
|
#undef PAL_FAIL
|
|
#undef PAL_TRUE
|
|
|
|
static void graphviz_dot_graph(RzCore *core_a, RzAnalysisFunction *fcn_a, RzCore *core_b, RzAnalysisFunction *fcn_b, RzAnalysisMatchResult *result) {
|
|
graphviz_dot_header(core_a);
|
|
if (fcn_a->bbs) {
|
|
graphviz_dot_nodes(core_a, fcn_a, core_b, fcn_b, result);
|
|
graphviz_dot_edges(core_a, fcn_a);
|
|
} else {
|
|
rz_cons_printf("\t\"0x%08" PFMT64x "\";\n", fcn_a->addr);
|
|
}
|
|
rz_cons_printf("}\n");
|
|
}
|
|
|
|
static void graph_basic_block_json(const char *name, RzAnalysisBlock *bbi, PJ *pj) {
|
|
pj_ko(pj, name); // "<name>": { -- object begin
|
|
pj_kn(pj, "address", bbi->addr);
|
|
if (bbi->jump != UT64_MAX) {
|
|
pj_kn(pj, "jump", bbi->jump);
|
|
}
|
|
if (bbi->fail != UT64_MAX) {
|
|
pj_kn(pj, "fail", bbi->fail);
|
|
}
|
|
if (!bbi->switch_op) {
|
|
pj_end(pj); // } -- object end
|
|
return;
|
|
}
|
|
|
|
RzAnalysisCaseOp *caseop;
|
|
RzListIter *iter;
|
|
pj_ka(pj, "switch"); // [ -- "switch" begin
|
|
rz_list_foreach (bbi->switch_op->cases, iter, caseop) {
|
|
pj_o(pj); // { -- caseop object begin
|
|
pj_kn(pj, "address", caseop->addr);
|
|
pj_kn(pj, "jump", caseop->jump);
|
|
pj_end(pj); // } -- caseop object end
|
|
}
|
|
pj_end(pj); // ] -- "switch" end
|
|
pj_end(pj); // } -- object end
|
|
}
|
|
|
|
static void diff_graph_result_as_json(RzCore *core_a, RzAnalysisFunction *fcn_a, RzCore *core_b, RzAnalysisFunction *fcn_b, RzAnalysisMatchResult *result, PJ *pj) {
|
|
RzAnalysisMatchPair *pair = NULL;
|
|
RzAnalysisBlock *bbi_a = NULL, *bbi_b = NULL;
|
|
RzListIter *iter = NULL;
|
|
char *opcodes_a = NULL, *opcodes_b = NULL;
|
|
char addr_a[32], addr_b[32];
|
|
|
|
rz_strf(addr_a, "0x%08" PFMT64x, fcn_a->addr);
|
|
rz_strf(addr_b, "0x%08" PFMT64x, fcn_b->addr);
|
|
|
|
const char *norig = fcn_a->name ? fcn_a->name : addr_a;
|
|
const char *nmodi = fcn_b->name ? fcn_b->name : addr_b;
|
|
|
|
pj_ka(pj, "result"); // "result": { -- array object begin
|
|
|
|
// we add all the matching basic block first
|
|
rz_list_foreach (result->matches, iter, pair) {
|
|
bbi_a = (RzAnalysisBlock *)pair->pair_a;
|
|
bbi_b = (RzAnalysisBlock *)pair->pair_b;
|
|
|
|
opcodes_a = basic_block_opcodes(core_a, bbi_a);
|
|
if (!opcodes_a) {
|
|
break;
|
|
}
|
|
|
|
pj_o(pj); // { -- array object begin
|
|
|
|
pj_ko(pj, "pair"); // "pair": {
|
|
graph_basic_block_json("source", bbi_a, pj);
|
|
graph_basic_block_json("match", bbi_b, pj);
|
|
pj_end(pj); // } -- "pair" end
|
|
|
|
pj_ko(pj, "similarity"); // "similarity": {
|
|
pj_ks(pj, "type", RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity));
|
|
pj_kd(pj, "score", pair->similarity);
|
|
pj_end(pj); // } -- "similarity" end
|
|
|
|
if (pair->similarity >= RZ_ANALYSIS_SIMILARITY_THRESHOLD) {
|
|
// if they are similar then we diff the opcodes.
|
|
|
|
opcodes_b = basic_block_opcodes(core_b, bbi_b);
|
|
if (opcodes_b && strcmp(opcodes_a, opcodes_b) != 0) {
|
|
RzDiff *dff = rz_diff_lines_new(opcodes_a, opcodes_b, NULL);
|
|
char *diffstr = rz_diff_unified_text(dff, norig, nmodi, false, false);
|
|
rz_diff_free(dff);
|
|
if (diffstr) {
|
|
free(opcodes_a);
|
|
opcodes_a = diffstr;
|
|
}
|
|
}
|
|
|
|
// sometimes the mismatch is on a call value
|
|
// but the output is actually the same due sym./imp.
|
|
// thus we ignore the similarity check and consider this
|
|
// as a perfect match.
|
|
free(opcodes_b);
|
|
}
|
|
|
|
pj_ks(pj, "opcodes", opcodes_a);
|
|
pj_end(pj); // } -- array object end
|
|
free(opcodes_a);
|
|
}
|
|
|
|
// we then add all the unmatched basic blocks
|
|
// first from the source function
|
|
rz_list_foreach (result->unmatch_a, iter, bbi_a) {
|
|
opcodes_a = basic_block_opcodes(core_a, bbi_a);
|
|
if (!opcodes_a) {
|
|
break;
|
|
}
|
|
|
|
pj_o(pj); // { -- array object begin
|
|
|
|
pj_ko(pj, "pair"); // "pair": {
|
|
graph_basic_block_json("source", bbi_a, pj);
|
|
pj_knull(pj, "match");
|
|
pj_end(pj); // } -- "pair" end
|
|
|
|
pj_ko(pj, "similarity"); // "similarity": {
|
|
pj_ks(pj, "type", RZ_ANALYSIS_SIMILARITY_UNLIKE_STR);
|
|
pj_kd(pj, "score", 0.0);
|
|
pj_end(pj); // } -- "similarity" end
|
|
|
|
pj_ks(pj, "opcodes", opcodes_a);
|
|
pj_end(pj); // } -- array object end
|
|
free(opcodes_a);
|
|
}
|
|
|
|
// then from the match function
|
|
rz_list_foreach (result->unmatch_b, iter, bbi_b) {
|
|
opcodes_b = basic_block_opcodes(core_b, bbi_b);
|
|
if (!opcodes_b) {
|
|
break;
|
|
}
|
|
|
|
pj_o(pj); // { -- array object begin
|
|
|
|
pj_ko(pj, "pair"); // "pair": {
|
|
pj_knull(pj, "source");
|
|
graph_basic_block_json("match", bbi_b, pj);
|
|
pj_end(pj); // } -- "pair" end
|
|
|
|
pj_ko(pj, "similarity"); // "similarity": {
|
|
pj_ks(pj, "type", RZ_ANALYSIS_SIMILARITY_UNLIKE_STR);
|
|
pj_kd(pj, "score", 0.0);
|
|
pj_end(pj); // } -- "similarity" end
|
|
|
|
pj_ks(pj, "opcodes", opcodes_b);
|
|
pj_end(pj); // } -- array object end
|
|
free(opcodes_b);
|
|
}
|
|
|
|
pj_end(pj); // } -- result object end
|
|
}
|
|
|
|
static void diff_graph_function_detail_as_json(const char *object_name, RzAnalysisFunction *fcn, PJ *pj) {
|
|
pj_ko(pj, object_name); // "<object_name>: {" -- object begin
|
|
|
|
char *fcn_name = rz_str_escape_utf8_for_json(fcn->name, -1);
|
|
pj_ks(pj, "name", rz_str_get_null(fcn_name));
|
|
free(fcn_name);
|
|
pj_kn(pj, "offset", fcn->addr);
|
|
pj_ki(pj, "ninstr", fcn->ninstr);
|
|
pj_kn(pj, "nargs", rz_analysis_arg_count(fcn));
|
|
pj_kn(pj, "nlocals", rz_analysis_var_local_count(fcn));
|
|
pj_kn(pj, "size", rz_analysis_function_linear_size(fcn));
|
|
pj_ki(pj, "stack", fcn->maxstack);
|
|
pj_ks(pj, "type", rz_analysis_fcntype_tostring(fcn->type));
|
|
|
|
pj_end(pj); // } -- <object_name> object end
|
|
}
|
|
|
|
static void diff_graph_as_json(RzCore *core_a, RzAnalysisFunction *fcn_a, RzCore *core_b, RzAnalysisFunction *fcn_b, RzAnalysisMatchResult *result, PJ *pj) {
|
|
pj_o(pj);
|
|
diff_graph_function_detail_as_json("source", fcn_a, pj);
|
|
diff_graph_function_detail_as_json("match", fcn_a, pj);
|
|
diff_graph_result_as_json(core_a, fcn_a, core_b, fcn_b, result, pj);
|
|
pj_end(pj);
|
|
}
|
|
|
|
static bool diff_progess_status(const size_t n_left, const size_t n_matches, void *user) {
|
|
rz_cons_clear_line(stderr);
|
|
fprintf(stderr, "rz-diff: to check %" PFMTSZu " | matches %" PFMTSZu "\r", n_left, n_matches);
|
|
return !rz_cons_is_breaked();
|
|
}
|
|
|
|
static bool diff_check_ctrl_c(const size_t n_left, const size_t n_matches, void *user) {
|
|
return !rz_cons_is_breaked();
|
|
}
|
|
|
|
static RzAnalysisFunction *find_best_matching_function(RzAnalysis *analysis_a, RzAnalysis *analysis_b, RzAnalysisFunction *find, bool verbose) {
|
|
RzAnalysisMatchPair *pair = NULL;
|
|
RzAnalysisFunction *match = NULL;
|
|
RzAnalysisMatchResult *result = NULL;
|
|
RzAnalysisMatchOpt opts = { 0 };
|
|
RzList *list_a = rz_list_new();
|
|
if (!list_a || !rz_list_append(list_a, find)) {
|
|
RZ_LOG_ERROR("rz-diff: cannot allocate and initialize RzList for function search\n");
|
|
goto fail;
|
|
}
|
|
|
|
opts.callback = verbose ? diff_progess_status : diff_check_ctrl_c;
|
|
opts.analysis_a = analysis_a;
|
|
opts.analysis_b = analysis_b;
|
|
|
|
RzList *fcns_b = rz_analysis_function_list(analysis_b);
|
|
result = rz_analysis_match_functions(list_a, fcns_b, &opts);
|
|
if (result && rz_list_length(result->matches) > 0) {
|
|
pair = (RzAnalysisMatchPair *)rz_list_first_val(result->matches);
|
|
match = (RzAnalysisFunction *)pair->pair_b;
|
|
}
|
|
|
|
fail:
|
|
rz_analysis_match_result_free(result);
|
|
rz_list_free(list_a);
|
|
return match;
|
|
}
|
|
|
|
static int compareBlocks(const RzAnalysisBlock *a, const RzAnalysisBlock *b, void *user) {
|
|
return (a && b && a->addr && b->addr ? (a->addr > b->addr) - (a->addr < b->addr) : 0);
|
|
}
|
|
|
|
static int comparePairBlocks(const RzAnalysisMatchPair *ma, const RzAnalysisMatchPair *mb, void *user) {
|
|
const RzAnalysisBlock *a = ma->pair_a;
|
|
const RzAnalysisBlock *b = mb->pair_a;
|
|
return compareBlocks(a, b, user);
|
|
}
|
|
|
|
/**
|
|
* \brief Generate a json or graphviz dot output of the graph and its data.
|
|
*
|
|
* Each node that doesn't match 100% with the other function will include
|
|
* a unified diff of the assembly of the same basic block.
|
|
* */
|
|
static void core_show_function_diff(RzCore *core_a, ut64 addr_a, RzCore *core_b, ut64 addr_b, DiffMode mode, bool verbose) {
|
|
rz_return_if_fail(core_a && core_b);
|
|
|
|
PJ *pj = NULL;
|
|
RzAnalysisFunction *fcn_b = NULL, *fcn_a = NULL;
|
|
RzAnalysisMatchResult *result = NULL;
|
|
RzAnalysisMatchOpt opts = { 0 };
|
|
|
|
// find function
|
|
fcn_a = rz_analysis_get_function_at(core_a->analysis, addr_a);
|
|
if (!fcn_a) {
|
|
RZ_LOG_ERROR("rz-diff: cannot get function at 0x%" PFMT64x "\n", addr_a);
|
|
return;
|
|
}
|
|
|
|
if (addr_b == UT64_MAX) {
|
|
// find matching function on core B
|
|
fcn_b = find_best_matching_function(core_a->analysis, core_b->analysis, fcn_a, verbose);
|
|
if (!fcn_b) {
|
|
RZ_LOG_ERROR("rz-diff: cannot find best matching function for function at 0x%" PFMT64x "\n", addr_a);
|
|
return;
|
|
}
|
|
} else {
|
|
fcn_b = rz_analysis_get_function_at(core_b->analysis, addr_b);
|
|
if (!fcn_b) {
|
|
RZ_LOG_ERROR("rz-diff: cannot get function at 0x%" PFMT64x "\n", addr_b);
|
|
return;
|
|
}
|
|
}
|
|
|
|
opts.callback = verbose ? diff_progess_status : diff_check_ctrl_c;
|
|
opts.analysis_a = core_a->analysis;
|
|
opts.analysis_b = core_b->analysis;
|
|
|
|
// calculate all the matches between the basic blocks of the 2 functions.
|
|
result = rz_analysis_match_basic_blocks(fcn_a, fcn_b, &opts);
|
|
if (!result) {
|
|
RZ_LOG_ERROR("rz-diff: cannot calculate matching basic blocks for function at 0x%" PFMT64x "\n", addr_a);
|
|
return;
|
|
}
|
|
|
|
rz_list_sort(result->matches, (RzListComparator)comparePairBlocks, NULL);
|
|
rz_list_sort(result->unmatch_a, (RzListComparator)compareBlocks, NULL);
|
|
rz_list_sort(result->unmatch_b, (RzListComparator)compareBlocks, NULL);
|
|
|
|
switch (mode) {
|
|
case DIFF_MODE_JSON:
|
|
pj = pj_new();
|
|
if (!pj) {
|
|
RZ_LOG_ERROR("rz-diff: cannot allocate json structure for function matching\n");
|
|
rz_analysis_match_result_free(result);
|
|
return;
|
|
}
|
|
|
|
diff_graph_as_json(core_a, fcn_a, core_b, fcn_b, result, pj);
|
|
rz_cons_printf("%s\n", pj_string(pj));
|
|
pj_free(pj);
|
|
break;
|
|
default:
|
|
graphviz_dot_graph(core_a, fcn_a, core_b, fcn_b, result);
|
|
break;
|
|
}
|
|
rz_cons_flush();
|
|
|
|
rz_analysis_match_result_free(result);
|
|
}
|
|
|
|
static void diff_function_as_json(const char *obj_name, RzAnalysisFunction *fcn, PJ *pj) {
|
|
if (!fcn) {
|
|
pj_knull(pj, obj_name);
|
|
return;
|
|
}
|
|
pj_ko(pj, obj_name); // "<obj_name>": { -- object end
|
|
pj_ks(pj, "name", fcn->name);
|
|
pj_kn(pj, "addr", fcn->addr);
|
|
pj_kn(pj, "size", rz_analysis_function_realsize(fcn));
|
|
pj_end(pj); // } -- <obj_name> object end
|
|
}
|
|
|
|
static void diff_similarity_as_json(RzAnalysisFunction *fcn_a, RzAnalysisFunction *fcn_b, double similarity, PJ *pj) {
|
|
pj_o(pj); // { -- match object begin
|
|
pj_kd(pj, "similarity", similarity);
|
|
pj_ks(pj, "type", RZ_ANALYSIS_SIMILARITY_TYPE_STR(similarity));
|
|
diff_function_as_json("original", fcn_a, pj);
|
|
diff_function_as_json("modified", fcn_b, pj);
|
|
pj_end(pj); // } -- match object end
|
|
}
|
|
|
|
static void diff_similarity_as_table(RzAnalysisFunction *fcn_a, RzAnalysisFunction *fcn_b, double similarity, bool no_name, RzTable *table) {
|
|
char tmp[128];
|
|
const char *type_s = NULL;
|
|
const char *type_n = NULL;
|
|
|
|
ut64 size_a = fcn_a ? rz_analysis_function_realsize(fcn_a) : 0;
|
|
ut64 size_b = fcn_b ? rz_analysis_function_realsize(fcn_b) : 0;
|
|
|
|
if (similarity > 0.0 && fcn_a && fcn_b) {
|
|
type_n = tmp;
|
|
if (similarity >= 1.0) {
|
|
rz_strf(tmp, "%.6f", similarity);
|
|
type_s = "COMPLETE";
|
|
} else if (similarity >= RZ_ANALYSIS_SIMILARITY_THRESHOLD) {
|
|
rz_strf(tmp, "%.6f", similarity);
|
|
type_s = "PARTIAL";
|
|
} else {
|
|
rz_strf(tmp, "%.4f", similarity);
|
|
type_s = "UNLIKE";
|
|
}
|
|
} else {
|
|
type_n = "0.000000";
|
|
type_s = "UNLIKE";
|
|
}
|
|
|
|
if (no_name) {
|
|
if (fcn_a && fcn_b) {
|
|
rz_table_add_rowf(table, "nXssXn", size_a, fcn_a->addr, type_s, type_n, fcn_b->addr, fcn_b->addr, size_b);
|
|
} else if (fcn_a) {
|
|
rz_table_add_rowf(table, "nXssXn", size_a, fcn_a->addr, type_s, type_n, UT64_MAX, size_b);
|
|
} else {
|
|
rz_table_add_rowf(table, "nXssXn", size_a, UT64_MAX, type_s, type_n, size_b, fcn_b->addr);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// with names
|
|
if (fcn_a && fcn_b) {
|
|
rz_table_add_rowf(table, "snXssXns", fcn_a->name, size_a, fcn_a->addr, type_s, type_n, fcn_b->addr, size_b, fcn_b->name);
|
|
} else if (fcn_a) {
|
|
rz_table_add_rowf(table, "snXssXns", fcn_a->name, size_a, fcn_a->addr, type_s, type_n, UT64_MAX, size_b, "");
|
|
} else {
|
|
rz_table_add_rowf(table, "snXssXns", "", size_a, UT64_MAX, type_s, type_n, fcn_b->addr, size_b, fcn_b->name);
|
|
}
|
|
}
|
|
|
|
static int comparePairFunctions(const RzAnalysisMatchPair *ma, const RzAnalysisMatchPair *mb, void *user) {
|
|
const RzAnalysisFunction *a = ma->pair_a;
|
|
const RzAnalysisFunction *b = mb->pair_a;
|
|
return (a && b && a->addr && b->addr ? (a->addr > b->addr) - (a->addr < b->addr) : 0);
|
|
}
|
|
|
|
static const char *diff_table_color_selector(const char *value, const char *column, const size_t column_n, const RzTableColumnType type, void *user) {
|
|
if (!column || !value) {
|
|
return NULL;
|
|
}
|
|
const RzConsContext *ctx = (const RzConsContext *)user;
|
|
if (RZ_STR_EQ(column, "type")) {
|
|
if (RZ_STR_EQ(value, "COMPLETE")) {
|
|
return ctx->pal.diff_match;
|
|
} else if (RZ_STR_EQ(value, "PARTIAL")) {
|
|
return ctx->pal.diff_unmatch;
|
|
}
|
|
return ctx->pal.diff_unknown;
|
|
}
|
|
if (RZ_STR_EQ(column, "similarity")) {
|
|
return ctx->pal.cjmp;
|
|
} else if (rz_str_startswith(column, "size")) {
|
|
return ctx->pal.num;
|
|
} else if (rz_str_startswith(column, "addr")) {
|
|
return ctx->pal.offset;
|
|
}
|
|
return ctx->pal.label;
|
|
}
|
|
|
|
/**
|
|
* \brief Performs function matching and shows the result in a table.
|
|
*
|
|
* Takes 2 cores and tries to match all the functions with eachother;
|
|
* Then the scores are shown in a table (when in quiet mode, the table
|
|
* is headerless)
|
|
* */
|
|
static void core_diff_show(RzCore *core_a, RzCore *core_b, const char *addr_a, DiffMode mode, bool verbose) {
|
|
rz_return_if_fail(core_a && core_b);
|
|
|
|
char *output = NULL;
|
|
RzList *fcns_a = NULL, *fcns_b = NULL;
|
|
PJ *pj = NULL;
|
|
RzTable *table = NULL;
|
|
RzAnalysisMatchResult *result = NULL;
|
|
RzAnalysisMatchPair *pair = NULL;
|
|
RzAnalysisMatchOpt opts = { 0 };
|
|
RzAnalysisFunction *fcn_a = NULL, *fcn_b = NULL;
|
|
RzListIter *iter = NULL;
|
|
bool color = false, no_name = false;
|
|
if (RZ_STR_ISEMPTY(addr_a)) {
|
|
fcns_a = rz_list_clone(rz_analysis_function_list(core_a->analysis));
|
|
} else {
|
|
fcns_a = rz_list_new();
|
|
if (!fcns_a) {
|
|
RZ_LOG_ERROR("rz-diff: failed to create new list.\n");
|
|
goto fail;
|
|
}
|
|
ut64 addr = rz_num_get(core_a->num, addr_a);
|
|
RzAnalysisFunction *fcn = rz_analysis_get_function_at(core_a->analysis, addr);
|
|
if (!fcn) {
|
|
RZ_LOG_ERROR("rz-diff: failed to find %s in file0.\n", addr_a);
|
|
goto fail;
|
|
}
|
|
rz_list_append(fcns_a, fcn);
|
|
}
|
|
if (rz_list_empty(fcns_a)) {
|
|
RZ_LOG_ERROR("rz-diff: No functions found in file0.\n");
|
|
goto fail;
|
|
}
|
|
|
|
fcns_b = rz_list_clone(rz_analysis_function_list(core_b->analysis));
|
|
if (rz_list_empty(fcns_b)) {
|
|
RZ_LOG_ERROR("rz-diff: No functions found in file1.\n");
|
|
goto fail;
|
|
}
|
|
|
|
opts.callback = verbose ? diff_progess_status : diff_check_ctrl_c;
|
|
opts.analysis_a = core_a->analysis;
|
|
opts.analysis_b = core_b->analysis;
|
|
|
|
// calculate all the matches between the functions of the 2 different core files.
|
|
result = rz_analysis_match_functions(fcns_a, fcns_b, &opts);
|
|
if (!result) {
|
|
RZ_LOG_ERROR("rz-diff: cannot perform matching functions search\n");
|
|
goto fail;
|
|
}
|
|
|
|
rz_list_sort(result->matches, (RzListComparator)comparePairFunctions, NULL);
|
|
rz_list_sort(result->unmatch_a, rz_analysis_get_column_sort(core_a->analysis), NULL);
|
|
rz_list_sort(result->unmatch_b, rz_analysis_get_column_sort(core_b->analysis), NULL);
|
|
|
|
if (mode == DIFF_MODE_JSON) {
|
|
pj = pj_new();
|
|
if (!pj) {
|
|
RZ_LOG_ERROR("rz-diff: cannot allocate json structure for function matching\n");
|
|
goto fail;
|
|
}
|
|
pj_a(pj); // [ -- list of pairs begin
|
|
} else {
|
|
color = rz_config_get_i(core_a->config, "scr.color") > 0 || rz_config_get_i(core_b->config, "scr.color") > 0;
|
|
no_name = rz_config_get_b(core_a->config, "diff.bare") || rz_config_get_b(core_b->config, "diff.bare");
|
|
|
|
table = rz_table_new();
|
|
if (!table) {
|
|
RZ_LOG_ERROR("rz-diff: cannot allocate table structure for function matching\n");
|
|
goto fail;
|
|
}
|
|
|
|
if (no_name) {
|
|
rz_table_set_columnsf(table, "nXssXn", "size0", "addr0", "type", "similarity", "addr1", "size1");
|
|
} else {
|
|
rz_table_set_columnsf(table, "snXssXns", "name0", "size0", "addr0", "type", "similarity", "addr1", "size1", "name1");
|
|
}
|
|
}
|
|
|
|
// first the matching functions.
|
|
rz_list_foreach (result->matches, iter, pair) {
|
|
fcn_a = (RzAnalysisFunction *)pair->pair_a;
|
|
fcn_b = (RzAnalysisFunction *)pair->pair_b;
|
|
if (fcn_a->type != RZ_ANALYSIS_FCN_TYPE_FCN && fcn_a->type != RZ_ANALYSIS_FCN_TYPE_SYM) {
|
|
continue;
|
|
}
|
|
if (mode == DIFF_MODE_JSON) {
|
|
diff_similarity_as_json(fcn_a, fcn_b, pair->similarity, pj);
|
|
} else {
|
|
diff_similarity_as_table(fcn_a, fcn_b, pair->similarity, no_name, table);
|
|
}
|
|
}
|
|
|
|
// then the unmatched functions from list A.
|
|
rz_list_foreach (result->unmatch_a, iter, fcn_a) {
|
|
if (fcn_a->type != RZ_ANALYSIS_FCN_TYPE_FCN && fcn_a->type != RZ_ANALYSIS_FCN_TYPE_SYM) {
|
|
continue;
|
|
}
|
|
if (mode == DIFF_MODE_JSON) {
|
|
diff_similarity_as_json(fcn_a, NULL, 0.0, pj);
|
|
} else {
|
|
diff_similarity_as_table(fcn_a, NULL, 0.0, no_name, table);
|
|
}
|
|
}
|
|
|
|
// then the unmatched functions from list B.
|
|
rz_list_foreach (result->unmatch_b, iter, fcn_b) {
|
|
if (fcn_b->type != RZ_ANALYSIS_FCN_TYPE_FCN && fcn_b->type != RZ_ANALYSIS_FCN_TYPE_SYM) {
|
|
continue;
|
|
}
|
|
if (mode == DIFF_MODE_JSON) {
|
|
diff_similarity_as_json(NULL, fcn_b, 0.0, pj);
|
|
} else {
|
|
diff_similarity_as_table(NULL, fcn_b, 0.0, no_name, table);
|
|
}
|
|
}
|
|
|
|
if (table) {
|
|
if (color) {
|
|
rz_table_set_color_selector(table, diff_table_color_selector, core_a->cons->context);
|
|
}
|
|
if (core_a->cons->use_utf8_curvy) {
|
|
rz_table_set_char_mode(table, RZ_TABLE_CHAR_MODE_UTF8_CURVY);
|
|
} else if (core_a->cons->use_utf8) {
|
|
rz_table_set_char_mode(table, RZ_TABLE_CHAR_MODE_UTF8);
|
|
}
|
|
}
|
|
|
|
switch (mode) {
|
|
case DIFF_MODE_JSON:
|
|
pj_end(pj); // ] -- list of pairs end
|
|
output = pj_drain(pj);
|
|
rz_cons_printf("%s\n", output);
|
|
pj = NULL;
|
|
break;
|
|
case DIFF_MODE_STANDARD:
|
|
output = rz_table_tofancystring(table);
|
|
rz_cons_printf("%s", output);
|
|
break;
|
|
default: // DIFF_MODE_QUIET
|
|
rz_table_align(table, 0, RZ_TABLE_ALIGN_RIGHT);
|
|
rz_table_show_header(table, false);
|
|
output = rz_table_tosimplestring(table);
|
|
rz_cons_printf("%s", output);
|
|
break;
|
|
}
|
|
|
|
rz_cons_flush();
|
|
|
|
fail:
|
|
free(output);
|
|
rz_table_free(table);
|
|
rz_analysis_match_result_free(result);
|
|
rz_list_free(fcns_a);
|
|
rz_list_free(fcns_b);
|
|
}
|
|
|
|
static bool convert_offset_from_input(RzCore *core, const char *input, ut64 *offset) {
|
|
if (rz_num_is_valid_input(NULL, input)) {
|
|
*offset = rz_num_get_input_value(NULL, input);
|
|
return true;
|
|
}
|
|
|
|
RzFlagItem *fi = rz_flag_get(core->flags, input);
|
|
if (fi) {
|
|
*offset = fi->offset;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool rz_diff_graphs_files(DiffContext *ctx) {
|
|
bool success = false;
|
|
RzCoreFile *a = NULL;
|
|
RzCoreFile *b = NULL;
|
|
|
|
a = rz_diff_load_file_with_core(ctx->file_a, ctx->architecture, ctx->arch_bits, ctx->evars, ctx->colors, ctx->verbose);
|
|
if (!a) {
|
|
goto rz_diff_graphs_files_bad;
|
|
}
|
|
|
|
b = rz_diff_load_file_with_core(ctx->file_b, ctx->architecture, ctx->arch_bits, ctx->evars, ctx->colors, ctx->verbose);
|
|
if (!b) {
|
|
goto rz_diff_graphs_files_bad;
|
|
}
|
|
|
|
if (ctx->type == DIFF_TYPE_PLOTDIFF) {
|
|
ut64 address_a = UT64_MAX;
|
|
ut64 address_b = UT64_MAX;
|
|
|
|
if (!convert_offset_from_input(a->core, ctx->input_a, &address_a)) {
|
|
rz_diff_error("cannot convert '%s' into an offset\n", ctx->input_a);
|
|
goto rz_diff_graphs_files_bad;
|
|
}
|
|
|
|
if (!convert_offset_from_input(b->core, ctx->input_b, &address_b)) {
|
|
rz_diff_error("cannot convert '%s' into an offset\n", ctx->input_b);
|
|
goto rz_diff_graphs_files_bad;
|
|
}
|
|
|
|
if (ctx->analyze_all) {
|
|
if (ctx->verbose) {
|
|
fprintf(stderr, "rz-diff: analysing file '%s'\n", ctx->file_a);
|
|
}
|
|
if (!rz_core_analysis_everything(a->core, false, NULL)) {
|
|
rz_diff_error("cannot analyze binary '%s'\n", ctx->file_a);
|
|
goto rz_diff_graphs_files_bad;
|
|
}
|
|
if (ctx->verbose) {
|
|
fprintf(stderr, "rz-diff: analysing file '%s'\n", ctx->file_b);
|
|
}
|
|
if (!rz_core_analysis_everything(b->core, false, NULL)) {
|
|
rz_diff_error("cannot analyze binary '%s'\n", ctx->file_b);
|
|
goto rz_diff_graphs_files_bad;
|
|
}
|
|
} else {
|
|
bool analyze_recursively = rz_config_get_i(a->core->config, "analysis.calls");
|
|
if (!rz_core_analysis_function_add(a->core, NULL, address_a, analyze_recursively)) {
|
|
rz_diff_error("cannot find function at '%s' in '%s' \n", ctx->input_a, ctx->file_a);
|
|
goto rz_diff_graphs_files_bad;
|
|
}
|
|
if (!rz_core_analysis_function_add(b->core, NULL, address_b, analyze_recursively)) {
|
|
rz_diff_error("cannot find function at '%s' in '%s' \n", ctx->input_b, ctx->file_b);
|
|
goto rz_diff_graphs_files_bad;
|
|
}
|
|
}
|
|
if (ctx->verbose) {
|
|
fprintf(stderr, "rz-diff: start diffing.\n");
|
|
}
|
|
core_show_function_diff(a->core, address_a, b->core, address_b, ctx->mode, ctx->verbose);
|
|
} else {
|
|
if (ctx->verbose) {
|
|
fprintf(stderr, "rz-diff: analysing file '%s'\n", ctx->file_a);
|
|
}
|
|
if (!rz_core_analysis_everything(a->core, false, NULL)) {
|
|
rz_diff_error("cannot analyze binary '%s'\n", ctx->file_a);
|
|
goto rz_diff_graphs_files_bad;
|
|
}
|
|
if (ctx->verbose) {
|
|
fprintf(stderr, "rz-diff: analysing file '%s'\n", ctx->file_b);
|
|
}
|
|
if (!rz_core_analysis_everything(b->core, false, NULL)) {
|
|
rz_diff_error("cannot analyze binary '%s'\n", ctx->file_b);
|
|
goto rz_diff_graphs_files_bad;
|
|
}
|
|
if (ctx->verbose) {
|
|
fprintf(stderr, "rz-diff: start diffing.\n");
|
|
}
|
|
if (ctx->colors) {
|
|
rz_core_theme_load(a->core, ctx->theme_name ? ctx->theme_name : "default");
|
|
}
|
|
core_diff_show(a->core, b->core, ctx->input_a, ctx->mode, ctx->verbose);
|
|
}
|
|
|
|
success = true;
|
|
|
|
rz_diff_graphs_files_bad:
|
|
rz_core_free(a ? a->core : NULL);
|
|
rz_core_free(b ? b->core : NULL);
|
|
return success;
|
|
}
|
|
|
|
/********************************************************************************/
|
|
|
|
typedef enum diff_hex_len_t {
|
|
DIFF_HEX_8 = 58,
|
|
DIFF_HEX_16 = 90,
|
|
DIFF_HEX_32 = 154,
|
|
} DiffHexLen;
|
|
|
|
static inline int diff_hexdump_partial(DiffHexView *hview, int hexlen, int lp, int lsize, const ut8 *bytes_a, const ut8 *bytes_b, ut64 address_a, ut64 address_b, ut64 size_a, ut64 size_b, ut64 pos, ssize_t read_a, ssize_t read_b, ssize_t skip_a, ssize_t skip_b) {
|
|
const char *number = hview->colors.number;
|
|
const char *match = hview->colors.match;
|
|
const char *unmatch = hview->colors.unmatch;
|
|
const char *reset = hview->colors.reset;
|
|
ssize_t i;
|
|
char *line = hview->line;
|
|
|
|
#define P(x) (IS_PRINTABLE(x) ? x : '.')
|
|
#define printline(fmt, ...) snprintf(line + lp, RZ_MAX(lsize - lp, 0), fmt, ##__VA_ARGS__)
|
|
// write to buffer fileA offset + hex bytes
|
|
lp += printline("%s0x%016" PFMT64x "%s | ", number, address_a + pos, reset);
|
|
for (i = 0; i < hexlen && i < read_a; ++i) {
|
|
if (pos + i >= size_a || pos + i < skip_a) {
|
|
// if the byte is outside the range [0 - fileA size) then do not write any hex
|
|
memset(line + lp, ' ', 3);
|
|
lp += 3;
|
|
} else if (i < read_b && pos + i >= skip_b) {
|
|
// if the byte is inside the range [0 - fileA size) check bytes_b for match/mismatch
|
|
const char *color = bytes_a[pos + i] == bytes_b[pos + i] ? match : unmatch;
|
|
lp += printline("%s%02x%s ", color, bytes_a[pos + i], reset);
|
|
} else {
|
|
// if the byte is inside the range [0 - fileA size) but address_b
|
|
// is outside [0 - fileB size) then is a mismatch
|
|
lp += printline("%s%02x%s ", unmatch, bytes_a[pos + i], reset);
|
|
}
|
|
}
|
|
if (i < hexlen) {
|
|
// fill any missing space to have fileA bytes aligned
|
|
memset(line + lp, ' ', (hexlen - i) * 3);
|
|
lp += (hexlen - i) * 3;
|
|
}
|
|
|
|
// print now printable chars of the printed hex values
|
|
lp += printline(" | ");
|
|
for (i = 0; i < hexlen && i < read_a; ++i) {
|
|
if (pos + i >= size_a || pos + i < skip_a) {
|
|
// if the byte is outside the range [0 - fileA size) then do not write any hex
|
|
line[lp] = ' ';
|
|
lp++;
|
|
} else if (i < read_b && pos + i >= skip_b) {
|
|
// if the byte is inside the range [0 - fileA size) check bytes_b for match/mismatch
|
|
const char *color = bytes_a[pos + i] == bytes_b[pos + i] ? match : unmatch;
|
|
lp += printline("%s%c%s", color, P(bytes_a[pos + i]), reset);
|
|
} else {
|
|
// if the byte is inside the range [0 - fileA size) but address_b
|
|
// is outside [0 - fileB size) then is a mismatch
|
|
lp += printline("%s%c%s", unmatch, P(bytes_a[pos + i]), reset);
|
|
}
|
|
}
|
|
if (i < hexlen) {
|
|
// fill any missing space to have fileA bytes aligned
|
|
memset(line + lp, ' ', (hexlen - i));
|
|
lp += (hexlen - i);
|
|
}
|
|
return lp;
|
|
#undef printline
|
|
#undef P
|
|
}
|
|
|
|
static inline void diff_hexdump_line(DiffHexView *hview, DiffHexLen hlen, ut64 pos, ssize_t read_a, ssize_t read_b, ssize_t skip_a, ssize_t skip_b) {
|
|
int width = hview->screen.width;
|
|
int height = hview->screen.height;
|
|
char *line = hview->line;
|
|
const ut8 *buffer_a = hview->buffer_a;
|
|
const ut8 *buffer_b = hview->buffer_b;
|
|
ut64 address_a = hview->address_a;
|
|
ut64 address_b = hview->address_b;
|
|
int lp = 0;
|
|
int lsize = width * height;
|
|
int hexlen = 0;
|
|
|
|
switch (hlen) {
|
|
case DIFF_HEX_16:
|
|
hexlen = 16;
|
|
break;
|
|
case DIFF_HEX_32:
|
|
hexlen = 32;
|
|
break;
|
|
default:
|
|
hexlen = 8;
|
|
break;
|
|
}
|
|
|
|
#define printline(fmt, ...) snprintf(line + lp, RZ_MAX(lsize - lp, 0), fmt, ##__VA_ARGS__)
|
|
lp = diff_hexdump_partial(hview, hexlen, 0, lsize, buffer_a, buffer_b, address_a, address_b, hview->size_a, hview->size_b, pos, read_a, read_b, skip_a, skip_b);
|
|
lp += printline(" | ");
|
|
lp = diff_hexdump_partial(hview, hexlen, lp, lsize, buffer_b, buffer_a, address_b, address_a, hview->size_b, hview->size_a, pos, read_b, read_a, skip_b, skip_a);
|
|
lp += printline(" |");
|
|
#undef printline
|
|
}
|
|
|
|
static inline int len_draw_hexdump(DiffHexView *hview) {
|
|
int width = hview->screen.width;
|
|
if (width >= (DIFF_HEX_32 * 2)) {
|
|
return DIFF_HEX_32;
|
|
} else if (width >= (DIFF_HEX_16 * 2)) {
|
|
return DIFF_HEX_16;
|
|
}
|
|
return DIFF_HEX_8;
|
|
}
|
|
|
|
static inline int seek_min_shift(DiffHexView *hview) {
|
|
int width = hview->screen.width;
|
|
if (width >= (DIFF_HEX_32 * 2)) {
|
|
return 5;
|
|
} else if (width >= (DIFF_HEX_16 * 2)) {
|
|
return 4;
|
|
}
|
|
return 3;
|
|
}
|
|
|
|
static inline int seek_min_value(DiffHexView *hview) {
|
|
int width = hview->screen.width;
|
|
if (width >= (DIFF_HEX_32 * 2)) {
|
|
return 32;
|
|
} else if (width >= (DIFF_HEX_16 * 2)) {
|
|
return 16;
|
|
}
|
|
return 8;
|
|
}
|
|
|
|
static inline int offset_len(DiffHexView *hview) {
|
|
ut64 filesize = RZ_MAX(hview->io_a->filesize, hview->io_b->filesize);
|
|
if (filesize > UT32_MAX) {
|
|
return 16;
|
|
} else if (filesize > UT16_MAX) {
|
|
return 8;
|
|
}
|
|
return 4;
|
|
}
|
|
|
|
static bool rz_diff_draw_tui(DiffHexView *hview, bool show_help) {
|
|
ssize_t read_a = 0, read_b = 0;
|
|
char *line = hview->line;
|
|
int shift = 8, offlen = 16, xpos = 0;
|
|
int width = hview->screen.width;
|
|
int height = hview->screen.height;
|
|
int lsize = width * height;
|
|
DiffHexLen hlen = 0;
|
|
DiffIO *io_a = hview->io_a;
|
|
DiffIO *io_b = hview->io_b;
|
|
ut64 filesize_a = hview->io_a->filesize;
|
|
ut64 filesize_b = hview->io_b->filesize;
|
|
ut64 max_rows = height - 2;
|
|
ut64 skip_a = 0;
|
|
ut64 skip_b = 0;
|
|
RzConsCanvas *canvas = hview->canvas;
|
|
const char *reset = hview->colors.reset;
|
|
const char *legenda = hview->colors.legenda;
|
|
const char *toolbar = NULL;
|
|
bool utf8 = rz_cons_singleton()->use_utf8;
|
|
const char *arrow_up = utf8 ? RUNE_ARROW_UP " " : "/\\";
|
|
const char *arrow_down = utf8 ? RUNE_ARROW_DOWN " " : "\\/";
|
|
const char *arrow_right = utf8 ? RUNE_ARROW_RIGHT " " : "> ";
|
|
const char *arrow_left = utf8 ? RUNE_ARROW_LEFT " " : "< ";
|
|
|
|
if (!line || !hview->buffer_a || !hview->buffer_b) {
|
|
return false;
|
|
}
|
|
|
|
offlen = offset_len(hview);
|
|
hlen = len_draw_hexdump(hview);
|
|
xpos = RZ_MAX((width / 2) - hlen, 0);
|
|
|
|
const char *p = NULL;
|
|
const char *file_a = io_a->filename;
|
|
const char *file_b = io_b->filename;
|
|
|
|
p = io_a->filename;
|
|
while ((p = strstr(file_a, RZ_SYS_DIR))) {
|
|
file_a = p + 1;
|
|
}
|
|
|
|
p = io_b->filename;
|
|
while ((p = strstr(file_b, RZ_SYS_DIR))) {
|
|
file_b = p + 1;
|
|
}
|
|
|
|
if (hview->column_descr) {
|
|
max_rows--;
|
|
}
|
|
|
|
if (hview->address_a > hview->size_a && (hview->address_a + hview->size_a) < hview->size_a) {
|
|
// underflow
|
|
ut64 size = hview->address_a + hview->size_a;
|
|
ut64 offset_p = hview->size_a - size;
|
|
read_a = rz_io_pread_at(io_a->io, 0, hview->buffer_a + offset_p, size);
|
|
if (read_a > 0) {
|
|
// include also excluded bytes from underflow
|
|
read_a += offset_p;
|
|
skip_a = offset_p;
|
|
}
|
|
} else {
|
|
read_a = rz_io_pread_at(io_a->io, hview->address_a, hview->buffer_a, hview->size_a);
|
|
}
|
|
|
|
if (hview->address_b > hview->size_b && (hview->address_b + hview->size_b) < hview->size_b) {
|
|
// underflow
|
|
ut64 size = hview->address_b + hview->size_b;
|
|
ut64 offset_p = hview->size_b - size;
|
|
read_b = rz_io_pread_at(io_b->io, 0, hview->buffer_b + offset_p, size);
|
|
if (read_b > 0) {
|
|
// include also excluded bytes from underflow
|
|
read_b += offset_p;
|
|
skip_b = offset_p;
|
|
}
|
|
} else {
|
|
read_b = rz_io_pread_at(io_b->io, hview->address_b, hview->buffer_b, hview->size_b);
|
|
}
|
|
|
|
rz_cons_goto_origin_reset();
|
|
rz_cons_clear();
|
|
rz_cons_canvas_clear(canvas);
|
|
shift = seek_min_shift(hview);
|
|
for (ut64 h = 0, pos = 0; h < max_rows; ++h) {
|
|
// draw hexadecimal values
|
|
pos = h << shift;
|
|
diff_hexdump_line(hview, hlen, pos, read_a - pos, read_b - pos, skip_a, skip_b);
|
|
rz_cons_canvas_gotoxy(canvas, xpos, h + (hview->column_descr ? 2 : 1));
|
|
rz_cons_canvas_write(canvas, line);
|
|
}
|
|
|
|
switch (len_draw_hexdump(hview)) {
|
|
case DIFF_HEX_32: toolbar = " 0 1 2 3 4 5 6 7 8 9 A B C D E F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"; break;
|
|
case DIFF_HEX_16: toolbar = " 0 1 2 3 4 5 6 7 8 9 A B C D E F"; break;
|
|
default: toolbar = " 0 1 2 3 4 5 6 7"; break;
|
|
}
|
|
|
|
rz_cons_canvas_box(canvas, 0, 0, width, height - 1, reset);
|
|
if (hview->column_descr) {
|
|
rz_cons_canvas_gotoxy(canvas, xpos + 21, 1);
|
|
rz_cons_canvas_write(canvas, toolbar);
|
|
rz_cons_canvas_gotoxy(canvas, xpos, 0);
|
|
} else {
|
|
rz_cons_canvas_gotoxy(canvas, xpos, 0);
|
|
}
|
|
snprintf(line, lsize, " [%*" PFMT64x "]( %.42s )", offlen, filesize_a, file_a);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
if (hview->column_descr) {
|
|
rz_cons_canvas_gotoxy(canvas, xpos + hlen + 22, 1);
|
|
rz_cons_canvas_write(canvas, toolbar);
|
|
rz_cons_canvas_gotoxy(canvas, xpos + hlen, 0);
|
|
} else {
|
|
rz_cons_canvas_gotoxy(canvas, xpos + hlen, 0);
|
|
}
|
|
snprintf(line, lsize, " [%*" PFMT64x "]( %.42s )", offlen, filesize_b, file_b);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
// clang-format off
|
|
toolbar = " "
|
|
"%s1 2%s -/+0x%x | "
|
|
"%sZ A%s file0 +/-1 | "
|
|
"%sC D%s file1 +/-1 | "
|
|
"%sG B%s end/begin | "
|
|
"%sN M%s next/prev | "
|
|
"%s%s%s%s +/-%u | "
|
|
"%s%s%s%s +/-1 | "
|
|
"%s:%s seek";
|
|
snprintf(line, lsize, toolbar
|
|
, legenda, reset, (1 << shift) * max_rows
|
|
, legenda, reset
|
|
, legenda, reset
|
|
, legenda, reset
|
|
, legenda, reset
|
|
, legenda, arrow_down, arrow_up, reset, 1 << shift
|
|
, legenda, arrow_left, arrow_right, reset
|
|
, legenda, reset);
|
|
// clang-format on
|
|
|
|
rz_cons_canvas_gotoxy(canvas, 0, height);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
if (show_help) {
|
|
rz_cons_canvas_fill(canvas, 4, 2, 56, 16, ' ');
|
|
rz_cons_canvas_box(canvas, 4, 2, 56, 16, legenda);
|
|
|
|
snprintf(line, lsize, "%sHelp page%s\n", legenda, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 3);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%s1 2%s increase/decrease the offsets by 0x%x\n", legenda, reset, (1 << shift) * (height - 2));
|
|
rz_cons_canvas_gotoxy(canvas, 6, 5);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%sZ A%s increase/decrease the offset of the file0 by 1\n", legenda, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 6);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%sC D%s increase/decrease the offset of the file1 by 1\n", legenda, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 7);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%sN M%s next/previous difference\n", legenda, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 8);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%sG B%s seek to end/begin\n", legenda, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 9);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%s9%s sets both offsets to a common value\n", legenda, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 10);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%s0%s shows/hides the column legenda\n", legenda, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 11);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%s%s %s%s increase/decrease both offsets by %u\n", legenda, arrow_down, arrow_up, reset, 1 << shift);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 12);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%s%s %s%s increase/decrease both offsets by 1\n", legenda, arrow_left, arrow_right, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 13);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%s:%s seek at offset (relative via +-)\n", legenda, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 14);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%s3%s file0 seek at offset (relative via +-)\n", legenda, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 15);
|
|
rz_cons_canvas_write(canvas, line);
|
|
|
|
snprintf(line, lsize, "%s4%s file1 seek at offset (relative via +-)\n", legenda, reset);
|
|
rz_cons_canvas_gotoxy(canvas, 6, 16);
|
|
rz_cons_canvas_write(canvas, line);
|
|
}
|
|
|
|
rz_cons_canvas_print(canvas);
|
|
rz_cons_flush();
|
|
|
|
// allow to refresh the terminal
|
|
// before printing again the ui
|
|
rz_sys_usleep(200);
|
|
return true;
|
|
}
|
|
|
|
static char *visual_prompt(DiffHexView *hview, const char *prompt) {
|
|
char buf[1024];
|
|
rz_cons_gotoxy(0, hview->screen.height);
|
|
rz_cons_clear_line(stdout);
|
|
rz_cons_printf("%s%s ", hview->colors.reset, prompt);
|
|
rz_line_set_prompt(rz_cons_singleton()->line, ":> ");
|
|
rz_cons_flush();
|
|
rz_cons_fgets(buf, sizeof(buf), 0, NULL);
|
|
if (*buf) {
|
|
return rz_str_dup(buf);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void prompt_offset_and_seek(DiffHexView *hview, ut64 minseek) {
|
|
char *value = visual_prompt(hview, " you can input an absolute offset or a relative offset by adding the prefix + or -\n offset");
|
|
if (value) {
|
|
const char *p = rz_str_trim_head_ro(value);
|
|
if (!IS_DIGIT(*p) && *p != '-' && *p != '+') {
|
|
free(value);
|
|
return;
|
|
}
|
|
st64 number = strtoll((*p == '+' || *p == '-') ? p + 1 : p, NULL, 0);
|
|
if (*p == '-') {
|
|
hview->address_a -= number;
|
|
hview->address_b -= number;
|
|
} else if (*p == '+') {
|
|
hview->address_a += number;
|
|
hview->address_b += number;
|
|
} else {
|
|
hview->address_a = number;
|
|
hview->address_b = number;
|
|
}
|
|
}
|
|
free(value);
|
|
}
|
|
|
|
static void prompt_offset_and_seek_file(DiffHexView *hview, ut64 minseek, bool is_file0) {
|
|
char *value = visual_prompt(hview, " you can input an absolute offset or a relative offset by adding the prefix + or -\n offset");
|
|
if (value) {
|
|
const char *p = rz_str_trim_head_ro(value);
|
|
if (!IS_DIGIT(*p) && *p != '-' && *p != '+') {
|
|
free(value);
|
|
return;
|
|
}
|
|
st64 number = strtoll((*p == '+' || *p == '-') ? p + 1 : p, NULL, 0);
|
|
if (*p == '-') {
|
|
if (is_file0) {
|
|
hview->address_a -= number;
|
|
} else {
|
|
hview->address_b -= number;
|
|
}
|
|
} else if (*p == '+') {
|
|
if (is_file0) {
|
|
hview->address_a += number;
|
|
} else {
|
|
hview->address_b += number;
|
|
}
|
|
} else {
|
|
if (is_file0) {
|
|
hview->address_a = number;
|
|
} else {
|
|
hview->address_b = number;
|
|
}
|
|
}
|
|
}
|
|
free(value);
|
|
}
|
|
|
|
static void find_next_diff(DiffHexView *hview, ut64 seek) {
|
|
if (!hview->buffer_a || !hview->buffer_b) {
|
|
return;
|
|
}
|
|
|
|
DiffIO *io_a = hview->io_a;
|
|
DiffIO *io_b = hview->io_b;
|
|
ssize_t read_a = 0, read_b = 0;
|
|
ssize_t minread = 0, minseek = 0;
|
|
ut64 address_a = hview->address_a + seek;
|
|
ut64 address_b = hview->address_b + seek;
|
|
ut64 minsize = RZ_MIN(hview->size_a, hview->size_b);
|
|
if (RZ_MIN(io_a->filesize, io_b->filesize) < seek) {
|
|
hview->address_a = 0;
|
|
hview->address_b = 0;
|
|
return;
|
|
}
|
|
do {
|
|
read_a = rz_io_pread_at(io_a->io, address_a, hview->buffer_a, minsize);
|
|
read_b = rz_io_pread_at(io_b->io, address_b, hview->buffer_b, minsize);
|
|
if (read_a < 1 || read_b < 1) {
|
|
break;
|
|
}
|
|
minread = RZ_MIN(read_a, read_b);
|
|
if (minread != minsize && !memcmp(hview->buffer_a, hview->buffer_b, minread)) {
|
|
address_a += RZ_MAX(minread - seek, 0);
|
|
address_b += RZ_MAX(minread - seek, 0);
|
|
break;
|
|
} else if (minread == minsize && !memcmp(hview->buffer_a, hview->buffer_b, minsize)) {
|
|
address_a += minsize;
|
|
address_b += minsize;
|
|
continue;
|
|
}
|
|
minread = RZ_MIN(minsize, minread);
|
|
minseek = RZ_MIN(seek, minread);
|
|
for (ssize_t i = 0; i < minread; i += minseek) {
|
|
if (memcmp(&hview->buffer_a[i], &hview->buffer_b[i], minseek)) {
|
|
hview->address_a = address_a;
|
|
hview->address_b = address_b;
|
|
return;
|
|
}
|
|
address_a += minseek;
|
|
address_b += minseek;
|
|
}
|
|
} while (1);
|
|
|
|
if (address_a >= io_a->filesize) {
|
|
address_a = io_a->filesize - seek;
|
|
}
|
|
|
|
if (address_b >= io_b->filesize) {
|
|
address_b = io_b->filesize - seek;
|
|
}
|
|
|
|
hview->address_a = address_a;
|
|
hview->address_b = address_b;
|
|
}
|
|
|
|
static void find_prev_diff(DiffHexView *hview, ut64 seek) {
|
|
if (!hview->buffer_a || !hview->buffer_b) {
|
|
return;
|
|
}
|
|
|
|
DiffIO *io_a = hview->io_a;
|
|
DiffIO *io_b = hview->io_b;
|
|
ssize_t read_a = 0, read_b = 0;
|
|
st64 address_a = hview->address_a;
|
|
st64 address_b = hview->address_b;
|
|
|
|
do {
|
|
address_a -= seek;
|
|
address_b -= seek;
|
|
if (address_a < 0) {
|
|
address_a = 0;
|
|
}
|
|
if (address_b < 0) {
|
|
address_b = 0;
|
|
}
|
|
read_a = rz_io_pread_at(io_a->io, address_a, hview->buffer_a, seek);
|
|
read_b = rz_io_pread_at(io_b->io, address_b, hview->buffer_b, seek);
|
|
if (read_a < 1 || read_b < 1) {
|
|
break;
|
|
}
|
|
if (memcmp(hview->buffer_a, hview->buffer_b, seek)) {
|
|
break;
|
|
}
|
|
if (address_a == 0 || address_b == 0) {
|
|
break;
|
|
}
|
|
} while (1);
|
|
hview->address_a = RZ_MAX(address_a, 0);
|
|
hview->address_b = RZ_MAX(address_b, 0);
|
|
}
|
|
|
|
static void rz_diff_resize_buffer(DiffHexView *hview) {
|
|
int height, width = rz_cons_get_size(&height);
|
|
|
|
ut64 size_a = ((st64)(width / 2) * (height - 2));
|
|
ut64 size_b = ((st64)(width / 2) * (height - 2));
|
|
st64 video_size = width;
|
|
video_size *= height;
|
|
|
|
// build the replacement view state first so resize is like all-or-nothing.
|
|
char *new_line = malloc(video_size);
|
|
ut8 *new_buf_a = malloc(size_a);
|
|
ut8 *new_buf_b = malloc(size_b);
|
|
if (!new_line || !new_buf_a || !new_buf_b) {
|
|
free(new_line);
|
|
free(new_buf_a);
|
|
free(new_buf_b);
|
|
return;
|
|
}
|
|
|
|
RzConsCanvas *new_canvas = rz_cons_canvas_new(width, height);
|
|
if (!new_canvas) {
|
|
free(new_line);
|
|
free(new_buf_a);
|
|
free(new_buf_b);
|
|
return;
|
|
}
|
|
|
|
new_canvas->color = true;
|
|
new_canvas->linemode = 1;
|
|
|
|
// swap only after every replacement alloc succeeded.
|
|
free(hview->line);
|
|
free(hview->buffer_a);
|
|
free(hview->buffer_b);
|
|
rz_cons_canvas_free(hview->canvas);
|
|
hview->line = new_line;
|
|
hview->buffer_a = new_buf_a;
|
|
hview->buffer_b = new_buf_b;
|
|
hview->size_a = size_a;
|
|
hview->size_b = size_b;
|
|
hview->screen.width = width;
|
|
hview->screen.height = height;
|
|
hview->canvas = new_canvas;
|
|
|
|
rz_diff_draw_tui(hview, false);
|
|
}
|
|
|
|
static bool rz_diff_hex_visual(DiffContext *ctx) {
|
|
RzCons *console = NULL;
|
|
DiffIO *io_a = NULL;
|
|
DiffIO *io_b = NULL;
|
|
RzConsCanvas *canvas = NULL;
|
|
DiffHexView hview;
|
|
bool draw_visual = true;
|
|
bool show_help = false;
|
|
int read, pressed;
|
|
int height = ctx->screen.width;
|
|
int width = ctx->screen.height;
|
|
ut64 size_a = 0;
|
|
ut64 size_b = 0;
|
|
|
|
hview.line = NULL;
|
|
hview.buffer_a = NULL;
|
|
hview.buffer_b = NULL;
|
|
|
|
RzCore *core = rz_core_new();
|
|
if (!core) {
|
|
rz_diff_error("cannot allocate core\n");
|
|
goto rz_diff_hex_visual_fail;
|
|
}
|
|
|
|
rz_core_parse_rizinrc(core);
|
|
|
|
console = rz_cons_singleton();
|
|
if (!console) {
|
|
rz_diff_error("cannot get console.\n");
|
|
goto rz_diff_hex_visual_fail;
|
|
}
|
|
|
|
rz_cons_set_interactive(false);
|
|
|
|
io_a = rz_diff_io_open(ctx->file_a);
|
|
if (!io_a) {
|
|
goto rz_diff_hex_visual_fail;
|
|
}
|
|
|
|
io_b = rz_diff_io_open(ctx->file_b);
|
|
if (!io_b) {
|
|
goto rz_diff_hex_visual_fail;
|
|
}
|
|
|
|
if (width < 1 && height < 1) {
|
|
width = rz_cons_get_size(&height);
|
|
if (width < 1 && height < 1) {
|
|
rz_diff_error("invalid screen size; use -S WxH to define the sizes.\n");
|
|
goto rz_diff_hex_visual_fail;
|
|
}
|
|
}
|
|
|
|
canvas = rz_cons_canvas_new(width, height);
|
|
if (!canvas) {
|
|
rz_diff_error("cannot allocate canvas. try to use -S WxH to define the sizes.\n");
|
|
goto rz_diff_hex_visual_fail;
|
|
}
|
|
|
|
size_a = ((width / 2) * (height - 2));
|
|
size_b = ((width / 2) * (height - 2));
|
|
|
|
canvas->color = true;
|
|
canvas->linemode = 1;
|
|
|
|
st64 video_size = width;
|
|
video_size *= height;
|
|
hview.line = malloc(video_size);
|
|
if (!hview.line) {
|
|
rz_diff_error("cannot allocate line buffer.\n");
|
|
goto rz_diff_hex_visual_fail;
|
|
}
|
|
hview.buffer_a = malloc(size_a);
|
|
if (!hview.buffer_a) {
|
|
rz_diff_error("cannot allocate buffer for %s.\n", io_a->filename);
|
|
goto rz_diff_hex_visual_fail;
|
|
}
|
|
hview.buffer_b = malloc(size_b);
|
|
if (!hview.buffer_b) {
|
|
rz_diff_error("cannot allocate buffer for %s.\n", io_b->filename);
|
|
goto rz_diff_hex_visual_fail;
|
|
}
|
|
|
|
hview.size_a = size_a;
|
|
hview.size_b = size_b;
|
|
hview.io_a = io_a;
|
|
hview.io_b = io_b;
|
|
hview.canvas = canvas;
|
|
hview.screen.width = width;
|
|
hview.screen.height = height;
|
|
hview.address_a = 0;
|
|
hview.address_b = 0;
|
|
hview.column_descr = true;
|
|
rz_diff_get_colors(&hview.colors, console->context, ctx->colors);
|
|
|
|
rz_cons_show_cursor(false);
|
|
rz_cons_enable_mouse(false);
|
|
|
|
console->event_data = &hview;
|
|
console->event_resize = (RzConsEvent)rz_diff_resize_buffer;
|
|
|
|
int seekmin = 0;
|
|
while (draw_visual && !rz_cons_is_breaked()) {
|
|
if (!rz_diff_draw_tui(&hview, show_help)) {
|
|
break;
|
|
}
|
|
seekmin = seek_min_value(&hview);
|
|
read = rz_cons_readchar();
|
|
pressed = rz_cons_arrow_to_hjkl(read);
|
|
|
|
if (show_help && (pressed == 'q' || pressed == 'Q')) {
|
|
// allow to close the help without closing the util
|
|
pressed = 0;
|
|
}
|
|
|
|
show_help = false;
|
|
switch (pressed) {
|
|
case '0':
|
|
hview.column_descr = !hview.column_descr;
|
|
break;
|
|
case '?':
|
|
show_help = true;
|
|
break;
|
|
case ':':
|
|
prompt_offset_and_seek(&hview, seekmin);
|
|
break;
|
|
case '3':
|
|
prompt_offset_and_seek_file(&hview, seekmin, true);
|
|
break;
|
|
case '4':
|
|
prompt_offset_and_seek_file(&hview, seekmin, false);
|
|
break;
|
|
case '9':
|
|
hview.address_a = hview.address_b = RZ_MIN(hview.address_a, hview.address_b);
|
|
break;
|
|
case 'G':
|
|
case 'g':
|
|
hview.address_a = io_a->filesize - seekmin;
|
|
hview.address_b = io_b->filesize - seekmin;
|
|
break;
|
|
case 'B':
|
|
case 'b':
|
|
hview.address_a = 0;
|
|
hview.address_b = 0;
|
|
break;
|
|
case 'A':
|
|
case 'a':
|
|
hview.address_a--;
|
|
break;
|
|
case 'N':
|
|
case 'n':
|
|
find_next_diff(&hview, seekmin);
|
|
break;
|
|
case 'M':
|
|
case 'm':
|
|
find_prev_diff(&hview, seekmin);
|
|
break;
|
|
case 'Z':
|
|
case 'z':
|
|
hview.address_a++;
|
|
break;
|
|
case 'D':
|
|
case 'd':
|
|
hview.address_b--;
|
|
break;
|
|
case 'C':
|
|
case 'c':
|
|
hview.address_b++;
|
|
break;
|
|
/* ARROWS */
|
|
case '1':
|
|
hview.address_a -= (seekmin * (height - 2));
|
|
hview.address_b -= (seekmin * (height - 2));
|
|
break;
|
|
case '2':
|
|
hview.address_a += (seekmin * (height - 2));
|
|
hview.address_b += (seekmin * (height - 2));
|
|
break;
|
|
case 'K':
|
|
case 'k':
|
|
hview.address_a -= seekmin;
|
|
hview.address_b -= seekmin;
|
|
break;
|
|
case 'J':
|
|
case 'j':
|
|
hview.address_a += seekmin;
|
|
hview.address_b += seekmin;
|
|
break;
|
|
case 'L':
|
|
case 'l':
|
|
hview.address_a--;
|
|
hview.address_b--;
|
|
break;
|
|
case 'H':
|
|
case 'h':
|
|
hview.address_a++;
|
|
hview.address_b++;
|
|
break;
|
|
case -1: // EOF
|
|
case 'Q':
|
|
case 'q':
|
|
draw_visual = false;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
canvas = hview.canvas;
|
|
console->event_data = NULL;
|
|
console->event_resize = NULL;
|
|
|
|
rz_cons_show_cursor(true);
|
|
rz_cons_goto_origin_reset();
|
|
rz_cons_clear();
|
|
rz_cons_print(Color_RESET_TERMINAL);
|
|
rz_cons_flush();
|
|
|
|
rz_diff_hex_visual_fail:
|
|
free(hview.line);
|
|
free(hview.buffer_a);
|
|
free(hview.buffer_b);
|
|
rz_cons_canvas_free(canvas);
|
|
rz_diff_io_close(io_a);
|
|
rz_diff_io_close(io_b);
|
|
rz_core_free(core);
|
|
rz_cons_free();
|
|
return true;
|
|
}
|
|
|
|
RZ_API int rz_main_rz_diff(int argc, const char **argv) {
|
|
bool success = false;
|
|
DiffContext ctx;
|
|
rz_diff_parse_arguments(argc, argv, &ctx);
|
|
|
|
switch (ctx.option) {
|
|
case DIFF_OPT_DISTANCE:
|
|
success = rz_diff_calculate_distance(&ctx);
|
|
break;
|
|
case DIFF_OPT_UNIFIED:
|
|
success = rz_diff_unified_files(&ctx);
|
|
break;
|
|
case DIFF_OPT_GRAPH:
|
|
success = rz_diff_graphs_files(&ctx);
|
|
break;
|
|
case DIFF_OPT_HEX_VISUAL:
|
|
success = rz_diff_hex_visual(&ctx);
|
|
break;
|
|
case DIFF_OPT_VERSION: {
|
|
RzPath *sys_path = rz_path_new();
|
|
if (!sys_path) {
|
|
rz_diff_show_help(false);
|
|
break;
|
|
}
|
|
rz_main_version_print(sys_path, "rz-diff");
|
|
rz_path_free(sys_path);
|
|
success = true;
|
|
break;
|
|
}
|
|
case DIFF_OPT_USAGE:
|
|
rz_diff_show_help(true);
|
|
break;
|
|
case DIFF_OPT_ERROR:
|
|
break;
|
|
case DIFF_OPT_HELP:
|
|
success = true;
|
|
// fallthrough
|
|
default:
|
|
rz_diff_show_help(false);
|
|
break;
|
|
}
|
|
|
|
rz_list_free(ctx.evars);
|
|
return success ? 0 : 1;
|
|
}
|