From 7b2737fb99f33ed96117e2eca8675df4a5b2b0b3 Mon Sep 17 00:00:00 2001 From: Rot127 <45763064+Rot127@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:19:40 +0000 Subject: [PATCH] Port `ae` commands to RzShell (#4732) * Port ae command to RzShell * Port aepc to RzShell. * Port aek commands to RzShell * Fix help of ported ae commands * Port aeb to RzShell * Port aex command to RzShell * Remove ae* command. It was marked as not working and had no direct advantage. With the transition to RzIL it becomes obsolete. * Port aef commands to RzShell * Port ae?? command to aeH * Fix off by one error * Port ael commands to RzShell. * Port aea command to RzShell. * Highlight ESIL is used for emulation in ae commands. * Make ae parameter mandatory * Remove a* It executed not existing commands * Remove legacy ae command handler. * Fix ae tests by putting expressions in strings. They contain special/illegal argument characters for the RzShell. * Fix parameter order of aea command. For the most common case people just pass a number. Hence, it must be the first argument. * Run yamllint --- librz/core/cmd/cmd.c | 1 - librz/core/cmd/cmd_analysis.c | 515 ++++++++++--------------- librz/core/cmd_descs/cmd_analysis.yaml | 139 +++++++ librz/core/cmd_descs/cmd_descs.c | 261 ++++++++++++- librz/core/cmd_descs/cmd_descs.h | 26 +- librz/core/cmd_descs/cmd_descs.yaml | 1 - test/db/cmd/cmd_aea | 4 +- test/db/esil/avr | 2 - test/db/esil/cmp | 72 +++- test/db/esil/esil | 44 ++- test/db/esil/flag_tests | 26 +- test/db/esil/intreg | 4 +- test/db/esil/math | 62 ++- 13 files changed, 760 insertions(+), 397 deletions(-) diff --git a/librz/core/cmd/cmd.c b/librz/core/cmd/cmd.c index e815e649d59..473732c3eaa 100644 --- a/librz/core/cmd/cmd.c +++ b/librz/core/cmd/cmd.c @@ -5568,7 +5568,6 @@ RZ_API void rz_core_cmd_init(RzCore *core) { RzCmdCb cb; } cmds[] = { { "/", "search kw, pattern aes", rz_cmd_search }, - { "a", "analysis", rz_cmd_analysis }, { "k", "perform sdb query", rz_cmd_kuery }, { "p", "print current block", rz_cmd_print }, { "V", "enter visual mode", rz_cmd_visual }, diff --git a/librz/core/cmd/cmd_analysis.c b/librz/core/cmd/cmd_analysis.c index 233cb63b45c..051b117ab0d 100644 --- a/librz/core/cmd/cmd_analysis.c +++ b/librz/core/cmd/cmd_analysis.c @@ -2,8 +2,12 @@ // SPDX-FileCopyrightText: 2009-2021 maijin // SPDX-License-Identifier: LGPL-3.0-only +#include #include +#include +#include #include +#include #include "../core_private.h" @@ -40,45 +44,6 @@ static const char *help_msg_a[] = { NULL }; -static const char *help_msg_ae[] = { - "Usage:", "ae[idesr?] [arg]", "ESIL code emulation", - "ae", " [expr]", "evaluate ESIL expression", - "ae?", "", "show this help", - "ae??", "", "show ESIL help", - "ae[aA]", "[f] [count]", "analyse esil accesses (regs, mem..)", - "aeC", "[arg0 arg1..] @ addr", "appcall in esil", - "aec", "[?]", "continue until ^C", - "aecb", "", "continue back until breakpoint", - "aecs", "", "continue until syscall", - "aecc", "", "continue until call", - "aecu", " [addr]", "continue until address", - "aecue", " [esil]", "continue until esil expression match", - "aef", " [addr]", "emulate function", - "aefa", " [addr]", "emulate function to find out args in given or current offset", - "aei", "", "initialize ESIL VM state (aei- to deinitialize)", - "aeim", " [addr] [size] [name]", "initialize ESIL VM stack (aeim- remove)", - "aeip", "", "initialize ESIL program counter to curseek", - "aek", " [query]", "perform sdb query on ESIL.info", - "aek-", "", "resets the ESIL.info sdb instance", - "aeli", "", "list loaded ESIL interrupts", - "aeli", " [file]", "load ESIL interrupts from shared object", - "aelir", " [interrupt number]", "remove ESIL interrupt and free it if needed", - "aepc", " [addr]", "change esil PC to this address", - "aes", "", "perform emulated debugger step", - "aesp", " [X] [N]", "evaluate N instr from offset X", - "aesb", "", "step back", - "aeso", " ", "step over", - "aesou", " [addr]", "step over until given address", - "aess", " ", "step skip (in case of CALL, just skip, instead of step into)", - "aesu", " [addr]", "step until given address", - "aesue", " [esil]", "step until esil expression match", - "aesuo", " [optype]", "step until given opcode type", - "aets", "[?]", "ESIL Trace session", - "aex", " [hex]", "evaluate opcode expression", - "aez", "[?]", "RzIL Emulation", - NULL -}; - static const char *help_detail_ae[] = { "Examples:", "ESIL", " examples and documentation", "=", "", "assign updating internal flags", @@ -144,30 +109,6 @@ static const char *help_detail_ae[] = { NULL }; -static const char *help_msg_aea[] = { - "Examples:", "aea", " show regs and memory accesses used in a range", - "aea", " [ops]", "Show regs/memory accesses used in N instructions ", - "aea*", " [ops]", "Create mem.* flags for memory accesses", - "aeab", "", "Show regs used in current basic block", - "aeaf", "", "Show regs used in current function", - "aear", " [ops]", "Show regs read in N instructions", - "aeaw", " [ops]", "Show regs written in N instructions", - "aean", " [ops]", "Show regs not written in N instructions", - "aeaj", " [ops]", "Show aea output in JSON format", - "aeA", " [len]", "Show regs used in N bytes (subcommands are the same)", - "Legend:", "", "", - "I", "", "input registers (read before being set)", - "A", "", "all regs accessed", - "R", "", "register values read", - "W", "", "registers written", - "N", "", "read but never written", - "V", "", "values", - "@R", "", "memreads", - "@W", "", "memwrites", - "NOTE:", "", "mem{reads,writes} with PIC only fetch the offset", - NULL -}; - /** * \brief Helper to get function in \p offset * @@ -1345,8 +1286,7 @@ static bool _aeli_iter(void *user, const ut64 key, const void *value) { return true; } -static void rz_analysis_aefa(RzCore *core, const char *arg) { - ut64 to = rz_num_math(core->num, arg); +static void rz_analysis_aefa(RzCore *core, ut64 to) { ut64 at, from = core->offset; RzAnalysisFunction *fcn = rz_analysis_get_fcn_in(core->analysis, to, -1); if (!from || from == UT64_MAX) { @@ -1441,232 +1381,6 @@ static void __analysis_esil_function(RzCore *core, ut64 addr) { rz_analysis_esil_free(core->analysis->esil); } -static void cmd_analysis_esil(RzCore *core, const char *input) { - RzAnalysisEsil *esil = core->analysis->esil; - int stacksize = rz_config_get_i(core->config, "esil.stack.depth"); - int iotrap = rz_config_get_i(core->config, "esil.iotrap"); - int romem = rz_config_get_i(core->config, "esil.romem"); - int stats = rz_config_get_i(core->config, "esil.stats"); - int noNULL = rz_config_get_i(core->config, "esil.noNULL"); - unsigned int addrsize = rz_config_get_i(core->config, "esil.addr.size"); - - switch (input[0]) { - case 'p': - switch (input[1]) { - case 'c': // "aepc" - if (input[2] == ' ' || input[2] == '=') { - // seek to this address - ut64 pc_val = rz_num_math(core->num, rz_str_trim_head_ro(input + 3)); - rz_core_analysis_set_reg(core, "PC", pc_val); - } else { - RZ_LOG_ERROR("core: Missing argument\n"); - } - break; - default: - rz_core_cmd_help(core, help_msg_ae); - break; - } - break; - case '*': // "ae*" - // XXX: this is wip, not working atm - if (core->analysis->esil) { - rz_cons_printf("trap: %d\n", core->analysis->esil->trap); - rz_cons_printf("trap-code: %d\n", core->analysis->esil->trap_code); - } else { - RZ_LOG_ERROR("core: esil vm not initialized. run `aei`\n"); - } - break; - case ' ': // "ae " - // rz_analysis_esil_eval (core->analysis, input+1); - if (!esil && !(core->analysis->esil = esil = rz_analysis_esil_new(stacksize, iotrap, addrsize))) { - return; - } - rz_analysis_esil_setup(esil, core->analysis, romem, stats, noNULL); // setup io - rz_analysis_esil_set_pc(esil, core->offset); - rz_analysis_esil_parse(esil, input + 1); - rz_core_esil_dumpstack(esil); - rz_analysis_esil_stack_free(esil); - break; - case 'k': // "aek" - switch (input[1]) { - case '\0': // "aek" - input = "123*"; - /* fall through */ - case ' ': // "aek " - if (esil && esil->stats) { - char *out = sdb_querys(esil->stats, NULL, 0, input + 2); - if (out) { - rz_cons_println(out); - free(out); - } - } else { - RZ_LOG_ERROR("core: esil.stats is empty. Run 'aei'\n"); - } - break; - case '-': // "aek-" - if (esil) { - sdb_reset(esil->stats); - } - break; - } - break; - case 'l': // ael commands - switch (input[1]) { - case 'i': // aeli interrupts - switch (input[2]) { - case ' ': // "aeli" with arguments - if (!rz_analysis_esil_load_interrupts_from_lib(esil, input + 3)) { - RZ_LOG_ERROR("core: Failed to load interrupts from '%s'.\n", input + 3); - } - break; - case 0: // "aeli" with no args - if (esil && esil->interrupts) { - ht_up_foreach(esil->interrupts, _aeli_iter, NULL); - } - break; - case 'r': // "aelir" - if (esil && esil->interrupts) { - ht_up_delete(esil->interrupts, rz_num_math(core->num, input + 3)); - } - break; - } - } - break; - case 'b': // "aeb" - rz_core_analysis_esil_emulate_bb(core); - break; - case 'f': // "aef" - if (input[1] == 'a') { // "aefa" - rz_analysis_aefa(core, rz_str_trim_head_ro(input + 2)); - } else { // This should be aefb -> because its emulating all the bbs - __analysis_esil_function(core, core->offset); - } - break; - case 'A': // "aeA" - if (input[1] == '?') { - rz_core_cmd_help(core, help_msg_aea); - } else if (input[1] == 'r') { - cmd_aea(core, 1 + (1 << 1), core->offset, rz_num_math(core->num, input + 2)); - } else if (input[1] == 'w') { - cmd_aea(core, 1 + (1 << 2), core->offset, rz_num_math(core->num, input + 2)); - } else if (input[1] == 'n') { - cmd_aea(core, 1 + (1 << 3), core->offset, rz_num_math(core->num, input + 2)); - } else if (input[1] == 'j') { - cmd_aea(core, 1 + (1 << 4), core->offset, rz_num_math(core->num, input + 2)); - } else if (input[1] == '*') { - cmd_aea(core, 1 + (1 << 5), core->offset, rz_num_math(core->num, input + 2)); - } else if (input[1] == 'f') { - RzAnalysisFunction *fcn = rz_analysis_get_fcn_in(core->analysis, core->offset, -1); - if (fcn) { - cmd_aea(core, 1, rz_analysis_function_min_addr(fcn), rz_analysis_function_linear_size(fcn)); - } - } else { - cmd_aea(core, 1, core->offset, (int)rz_num_math(core->num, input + 2)); - } - break; - case 'a': // "aea" - { - RzReg *reg = core->analysis->reg; - ut64 pc = rz_reg_getv(reg, "PC"); - RzAnalysisOp *op = rz_core_analysis_op(core, pc, 0); - if (!op) { - break; - } - ut64 newPC = core->offset + op->size; - rz_reg_setv(reg, "PC", newPC); - if (input[1] == '?') { - rz_core_cmd_help(core, help_msg_aea); - } else if (input[1] == 'r') { - cmd_aea(core, 1 << 1, core->offset, rz_num_math(core->num, input + 2)); - } else if (input[1] == 'w') { - cmd_aea(core, 1 << 2, core->offset, rz_num_math(core->num, input + 2)); - } else if (input[1] == 'n') { - cmd_aea(core, 1 << 3, core->offset, rz_num_math(core->num, input + 2)); - } else if (input[1] == 'j') { - cmd_aea(core, 1 << 4, core->offset, rz_num_math(core->num, input + 2)); - } else if (input[1] == '*') { - cmd_aea(core, 1 << 5, core->offset, rz_num_math(core->num, input + 2)); - } else if (input[1] == 'b') { // "aeab" - bool json = input[2] == 'j'; - int a = json ? 3 : 2; - ut64 addr = (input[a] == ' ') ? rz_num_math(core->num, input + a) : core->offset; - RzList *l = rz_analysis_get_blocks_in(core->analysis, addr); - RzAnalysisBlock *b; - RzListIter *iter; - rz_list_foreach (l, iter, b) { - int mode = json ? (1 << 4) : 1; - cmd_aea(core, mode, b->addr, b->size); - break; - } - } else if (input[1] == 'f') { - RzAnalysisFunction *fcn = rz_analysis_get_fcn_in(core->analysis, core->offset, -1); - // "aeafj" - if (fcn) { - switch (input[2]) { - case 'j': // "aeafj" - cmd_aea(core, 1 << 4, rz_analysis_function_min_addr(fcn), rz_analysis_function_linear_size(fcn)); - break; - default: - cmd_aea(core, 1, rz_analysis_function_min_addr(fcn), rz_analysis_function_linear_size(fcn)); - break; - } - break; - } - } else if (input[1] == 'b') { // "aeab" - RzAnalysisBlock *bb = rz_analysis_find_most_relevant_block_in(core->analysis, core->offset); - if (bb) { - switch (input[2]) { - case 'j': // "aeabj" - cmd_aea(core, 1 << 4, bb->addr, bb->size); - break; - default: - cmd_aea(core, 1, bb->addr, bb->size); - break; - } - } - } else { - const char *arg = input[1] ? input + 2 : ""; - ut64 len = rz_num_math(core->num, arg); - cmd_aea(core, 0, core->offset, len); - } - rz_reg_setv(reg, "PC", pc); - } break; - case 'x': { // "aex" - char *hex; - int ret, bufsz; - - input = rz_str_trim_head_ro(input + 1); - hex = rz_str_dup(input); - if (!hex) { - break; - } - - RzAnalysisOp aop = RZ_EMPTY; - bufsz = rz_hex_str2bin(hex, (ut8 *)hex); - rz_analysis_op_init(&aop); - ret = rz_analysis_op(core->analysis, &aop, core->offset, - (const ut8 *)hex, bufsz, RZ_ANALYSIS_OP_MASK_ESIL); - if (ret > 0) { - const char *str = RZ_STRBUF_SAFEGET(&aop.esil); - char *str2 = rz_str_newf(" %s", str); - cmd_analysis_esil(core, str2); - free(str2); - } - rz_analysis_op_fini(&aop); - break; - } - case '?': // "ae?" - if (input[1] == '?') { - rz_core_cmd_help(core, help_detail_ae); - break; - } - /* fallthrough */ - default: - rz_core_cmd_help(core, help_msg_ae); - break; - } -} - static bool print_cmd_analysis_after_traps_print(RZ_NONNULL RzCore *core, ut64 n_bytes) { int bufi = 0, minop = 1; // 4 ut8 *buf = NULL; @@ -2067,29 +1781,6 @@ RZ_API void rz_core_cmd_show_analysis_help(RZ_NONNULL RzCore *core) { rz_core_cmd_help(core, help_msg_a); } -RZ_IPI int rz_cmd_analysis(void *data, const char *input) { - RzCore *core = (RzCore *)data; - ut32 tbs = core->blocksize; - switch (input[0]) { - case 'e': cmd_analysis_esil(core, input + 1); break; // "ae" - case '*': // "a*" - rz_core_cmd0_rzshell(core, "afl*"); - rz_core_cmd0_rzshell(core, "ah*"); - rz_core_cmd0_rzshell(core, "ax*"); - break; - default: - rz_core_cmd_help(core, help_msg_a); - break; - } - if (tbs != core->blocksize) { - rz_core_block_size(core, tbs); - } - if (rz_cons_is_breaked()) { - rz_cons_clear_line(1); - } - return 0; -} - RZ_IPI RzCmdStatus rz_analysis_function_blocks_list_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) { RzAnalysisFunction *fcn = analysis_get_function_in(core->analysis, core->offset); if (!fcn) { @@ -6563,3 +6254,199 @@ RZ_IPI RzCmdStatus rz_analysis_data_trampoline_handler(RzCore *core, int argc, c print_trampolines(core, minimum, maximum, bits / 8); return RZ_CMD_STATUS_OK; } + +static RzCmdStatus emulate_esil_expr(RzCore *core, const char *expr) { + int stacksize = rz_config_get_i(core->config, "esil.stack.depth"); + int iotrap = rz_config_get_i(core->config, "esil.iotrap"); + int romem = rz_config_get_i(core->config, "esil.romem"); + int stats = rz_config_get_i(core->config, "esil.stats"); + int noNULL = rz_config_get_i(core->config, "esil.noNULL"); + unsigned int addrsize = rz_config_get_i(core->config, "esil.addr.size"); + + RzAnalysisEsil *esil = core->analysis->esil; + + if (!esil && !(core->analysis->esil = esil = rz_analysis_esil_new(stacksize, iotrap, addrsize))) { + RZ_LOG_ERROR("Failed to init ESIL VM.\n"); + return RZ_CMD_STATUS_ERROR; + } + rz_analysis_esil_setup(esil, core->analysis, romem, stats, noNULL); // setup io + rz_analysis_esil_set_pc(esil, core->offset); + rz_analysis_esil_parse(esil, expr); + rz_core_esil_dumpstack(esil); + rz_analysis_esil_stack_free(esil); + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_eval_expr_handler(RzCore *core, int argc, const char **argv) { + return emulate_esil_expr(core, argv[1]); +} + +RZ_IPI RzCmdStatus rz_analyze_esil_set_pc_handler(RzCore *core, int argc, const char **argv) { + ut64 pc_val = rz_num_math(core->num, argv[1]); + rz_core_analysis_set_reg(core, "PC", pc_val); + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_sdb_query_handler(RzCore *core, int argc, const char **argv) { + RzAnalysisEsil *esil = core->analysis->esil; + if (!esil || !esil->stats) { + RZ_LOG_ERROR("core: esil.stats is empty. Run 'aei'\n"); + return RZ_CMD_STATUS_ERROR; + } + char *out = sdb_querys(esil->stats, NULL, 0, argv[1]); + if (out) { + rz_cons_println(out); + free(out); + } + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_sdb_reset_handler(RzCore *core, int argc, const char **argv) { + RzAnalysisEsil *esil = core->analysis->esil; + if (esil) { + sdb_reset(esil->stats); + } + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_emulate_block_handler(RzCore *core, int argc, const char **argv) { + rz_core_analysis_esil_emulate_bb(core); + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_eval_opcode_expr_handler(RzCore *core, int argc, const char **argv) { + const char *hex = argv[1]; + + RzAnalysisOp aop = RZ_EMPTY; + rz_analysis_op_init(&aop); + + int bufsz = rz_hex_str2bin(hex, (ut8 *)hex); + int ret = rz_analysis_op(core->analysis, &aop, core->offset, + (const ut8 *)hex, bufsz, RZ_ANALYSIS_OP_MASK_ESIL); + RzCmdStatus status = RZ_CMD_STATUS_ERROR; + if (ret > 0) { + const char *str = RZ_STRBUF_SAFEGET(&aop.esil); + status = emulate_esil_expr(core, str); + } else { + RZ_LOG_ERROR("Failed to decode bytes.\n"); + } + rz_analysis_op_fini(&aop); + return status; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_emu_fcn_handler(RzCore *core, int argc, const char **argv) { + ut64 addr = argc == 1 ? core->offset : rz_num_math(core->num, argv[1]); + __analysis_esil_function(core, addr); + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_emu_fcn_find_args_handler(RzCore *core, int argc, const char **argv) { + ut64 addr = argc == 1 ? core->offset : rz_num_math(core->num, argv[1]); + rz_analysis_aefa(core, addr); + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_expr_help_handler(RzCore *core, int argc, const char **argv) { + rz_core_cmd_help(core, help_detail_ae); + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_int_list_load_handler(RzCore *core, int argc, const char **argv) { + RzAnalysisEsil *esil = core->analysis->esil; + if (!esil) { + RZ_LOG_ERROR("ESIL VM is not initialized. Did you run 'aei'?\n"); + return RZ_CMD_STATUS_ERROR; + } + + if (argc == 1) { + // List interrupts + if (esil->interrupts) { + ht_up_foreach(esil->interrupts, _aeli_iter, NULL); + } + return RZ_CMD_STATUS_OK; + } + + // Load interrupts + if (!rz_analysis_esil_load_interrupts_from_lib(esil, argv[1])) { + RZ_LOG_ERROR("Failed to load interrupts from '%s'.\n", argv[1]); + } + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_int_remove_handler(RzCore *core, int argc, const char **argv) { + RzAnalysisEsil *esil = core->analysis->esil; + if (esil && esil->interrupts) { + ht_up_delete(esil->interrupts, rz_num_math(core->num, argv[1])); + } + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_analyze_esil_insn_access_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) { + int aea_mode = ((mode == RZ_OUTPUT_MODE_JSON) ? (1 << 4) : 0); + int len = rz_num_math(core->num, argv[1]); + bool is_aeA = argc == 4; + if (is_aeA) { + aea_mode |= 1; + } + + // Don't ask me why this has to be done. I am just the janitor. + RzReg *reg = core->analysis->reg; + ut64 pc = rz_reg_getv(reg, "PC"); + if (!is_aeA) { + RzAnalysisOp *op = rz_core_analysis_op(core, pc, 0); + if (!op) { + RZ_LOG_ERROR("Failed to decode at current pc.\n"); + return RZ_CMD_STATUS_ERROR; + } + ut64 newPC = core->offset + op->size; + rz_reg_setv(reg, "PC", newPC); + } + + const char cmd_type = argv[2][0]; + switch (cmd_type) { + default: + RZ_LOG_ERROR("Unhandled option: '%c'\n", cmd_type); + return RZ_CMD_STATUS_ERROR; + case 'r': + aea_mode |= (1 << 1); + break; + case 'w': + aea_mode |= (1 << 2); + break; + case 'n': + aea_mode |= (1 << 3); + break; + case '*': + aea_mode |= (1 << 5); + break; + case 'b': { + ut64 addr = len != 0 ? len : core->offset; + aea_mode |= (1 << 1); + RzAnalysisBlock *b; + RzListIter *iter; + RzList *l = rz_analysis_get_blocks_in(core->analysis, addr); + rz_list_foreach (l, iter, b) { + cmd_aea(core, aea_mode, b->addr, b->size); + break; + } + return RZ_CMD_STATUS_OK; + } + case 'f': { + aea_mode |= (1 << 1); + RzAnalysisFunction *fcn = rz_analysis_get_fcn_in(core->analysis, core->offset, -1); + if (!fcn) { + RZ_LOG_ERROR("Failed to decode function at 0x%" PFMT64x "\n", core->offset) + return RZ_CMD_STATUS_ERROR; + } + cmd_aea(core, aea_mode, rz_analysis_function_min_addr(fcn), rz_analysis_function_linear_size(fcn)); + return RZ_CMD_STATUS_OK; + } + case 'd': + break; + } + cmd_aea(core, aea_mode, core->offset, len); + if (!is_aeA) { + rz_reg_setv(reg, "PC", pc); + } + return RZ_CMD_STATUS_OK; +} diff --git a/librz/core/cmd_descs/cmd_analysis.yaml b/librz/core/cmd_descs/cmd_analysis.yaml index 20df567d2b6..e50236d47a8 100644 --- a/librz/core/cmd_descs/cmd_analysis.yaml +++ b/librz/core/cmd_descs/cmd_analysis.yaml @@ -2402,3 +2402,142 @@ commands: modes: - RZ_OUTPUT_MODE_STANDARD - RZ_OUTPUT_MODE_JSON + - name: ae + summary: ESIL analysis commands + subcommands: + - name: ae + summary: Analyze all flags starting with sym. and entry + cname: analyze_esil_eval_expr + args: + - name: expr + type: RZ_CMD_ARG_TYPE_STRING + - name: aeH + summary: Show ESIL help. + cname: analyze_esil_expr_help + args: [] + - name: aeb + summary: Emulate current block with ESIL. + cname: analyze_esil_emulate_block + args: [] + - name: aepc + summary: Set ESIL PC to given address. + cname: analyze_esil_set_pc + args: + - name: addr + type: RZ_CMD_ARG_TYPE_NUM + - name: aek + summary: SDB queries on ESIL info (emulation statistics). + subcommands: + - name: aek + summary: Perform sdb query on ESIL info. + cname: analyze_esil_sdb_query + args: + - name: query + type: RZ_CMD_ARG_TYPE_STRING + default_value: "123*" + - name: aek- + summary: Resets the ESIL info sdb instance. + cname: analyze_esil_sdb_reset + args: [] + - name: aex + summary: Emulate the instruction encoded in the given bytes with ESIL. + cname: analyze_esil_eval_opcode_expr + args: + - name: bytes + type: RZ_CMD_ARG_TYPE_STRING + - name: aef + summary: Emulate functions with ESIL. + subcommands: + - name: aef + summary: Emulate the function at given or current offset with ESIL. + cname: analyze_esil_emu_fcn + args: + - name: addr + type: RZ_CMD_ARG_TYPE_NUM + optional: true + - name: aefa + summary: Emulate function at given or current offset to find arguments with ESIL. + cname: analyze_esil_emu_fcn_find_args + args: + - name: addr + type: RZ_CMD_ARG_TYPE_NUM + optional: true + - name: ael + summary: ESIL interrupt commands. + subcommands: + - name: aeli + summary: List ESIL interrupts or load them from the given shared object. + cname: analyze_esil_int_list_load + args: + - name: file + type: RZ_CMD_ARG_TYPE_STRING + optional: true + - name: aelir + summary: Remove ESIL interrupt and free it if needed. + cname: analyze_esil_int_remove + args: + - name: interrupt number + type: RZ_CMD_ARG_TYPE_NUM + - name: aea + summary: ESIL emulation to retrieve arguments. + subcommands: + - name: aea + summary: Show register and memory access of the next [len] instructions or bytes. + cname: analyze_esil_insn_access + args: + - name: len + type: RZ_CMD_ARG_TYPE_NUM + default_value: 0 + - name: type + type: RZ_CMD_ARG_TYPE_CHOICES + choices: ['d', '*', 'r', 'w', 'n', 'b', 'f'] + default_value: 'd' + - name: A + type: RZ_CMD_ARG_TYPE_OPTION + optional: true + modes: + - RZ_OUTPUT_MODE_STANDARD + - RZ_OUTPUT_MODE_JSON + details: + - name: "Flag" + entries: + - text: "A" + comment: "Interpret the [len] parameter as number of bytes. Not as number of instructions." + - name: "Options" + entries: + - text: "*" + comment: "Create mem.* flags for memory accesses." + - text: "r" + comment: "Show regs read in N instructions." + - text: "w" + comment: "Show regs written in N instructions." + - text: "n" + comment: "Show regs not written in N instructions." + - text: "b" + comment: > + Show regs used in current basic block. The [len] parameter, + if not 0, is interpreted as address. + - text: "f" + comment: "Show regs used in current function." + - text: "d" + comment: "Show memory and register access." + - name: Legend + entries: + - text: "I" + comment: "input registers (read before being set)" + - text: "A" + comment: "all regs accessed" + - text: "R" + comment: "register values read" + - text: "W" + comment: "registers written" + - text: "N" + comment: "read but never written" + - text: "V" + comment: "values" + - text: "@R" + comment: "memreads" + - text: "@W" + comment: "memwrites" + - text: "NOTE:" + comment: "mem{reads,writes} with PIC only fetch the offset" diff --git a/librz/core/cmd_descs/cmd_descs.c b/librz/core/cmd_descs/cmd_descs.c index b4b94233925..854f1485650 100644 --- a/librz/core/cmd_descs/cmd_descs.c +++ b/librz/core/cmd_descs/cmd_descs.c @@ -46,6 +46,7 @@ static const RzCmdDescDetail analysis_hint_set_val_details[2]; static const RzCmdDescDetail analysis_hint_set_optype_details[2]; static const RzCmdDescDetail analysis_hint_set_immbase_details[3]; static const RzCmdDescDetail analysis_hint_set_offset_details[2]; +static const RzCmdDescDetail analyze_esil_insn_access_details[4]; static const RzCmdDescDetail cmd_cmp_unified_details[2]; static const RzCmdDescDetail cw_details[2]; static const RzCmdDescDetail cmd_debug_list_bp_details[2]; @@ -316,6 +317,15 @@ static const RzCmdDescArg analysis_syscall_dump_assembly_args[2]; static const RzCmdDescArg analysis_syscall_dump_c_args[2]; static const RzCmdDescArg analysis_syscall_name_args[2]; static const RzCmdDescArg analysis_syscall_number_args[2]; +static const RzCmdDescArg analyze_esil_eval_expr_args[2]; +static const RzCmdDescArg analyze_esil_set_pc_args[2]; +static const RzCmdDescArg analyze_esil_sdb_query_args[2]; +static const RzCmdDescArg analyze_esil_eval_opcode_expr_args[2]; +static const RzCmdDescArg analyze_esil_emu_fcn_args[2]; +static const RzCmdDescArg analyze_esil_emu_fcn_find_args_args[2]; +static const RzCmdDescArg analyze_esil_int_list_load_args[2]; +static const RzCmdDescArg analyze_esil_int_remove_args[2]; +static const RzCmdDescArg analyze_esil_insn_access_args[4]; static const RzCmdDescArg block_args[2]; static const RzCmdDescArg block_decrease_args[2]; static const RzCmdDescArg block_increase_args[2]; @@ -6440,6 +6450,220 @@ static const RzCmdDescHelp list_plugins_help = { .args = list_plugins_args, }; +static const RzCmdDescHelp ae_help = { + .summary = "ESIL analysis commands", +}; +static const RzCmdDescArg analyze_esil_eval_expr_args[] = { + { + .name = "expr", + .type = RZ_CMD_ARG_TYPE_STRING, + .flags = RZ_CMD_ARG_FLAG_LAST, + + }, + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_eval_expr_help = { + .summary = "Analyze all flags starting with sym. and entry", + .args = analyze_esil_eval_expr_args, +}; + +static const RzCmdDescArg analyze_esil_expr_help_args[] = { + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_expr_help_help = { + .summary = "Show ESIL help.", + .args = analyze_esil_expr_help_args, +}; + +static const RzCmdDescArg analyze_esil_emulate_block_args[] = { + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_emulate_block_help = { + .summary = "Emulate current block with ESIL.", + .args = analyze_esil_emulate_block_args, +}; + +static const RzCmdDescArg analyze_esil_set_pc_args[] = { + { + .name = "addr", + .type = RZ_CMD_ARG_TYPE_NUM, + + }, + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_set_pc_help = { + .summary = "Set ESIL PC to given address.", + .args = analyze_esil_set_pc_args, +}; + +static const RzCmdDescHelp aek_help = { + .summary = "SDB queries on ESIL info (emulation statistics).", +}; +static const RzCmdDescArg analyze_esil_sdb_query_args[] = { + { + .name = "query", + .type = RZ_CMD_ARG_TYPE_STRING, + .flags = RZ_CMD_ARG_FLAG_LAST, + .default_value = "123*", + + }, + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_sdb_query_help = { + .summary = "Perform sdb query on ESIL info.", + .args = analyze_esil_sdb_query_args, +}; + +static const RzCmdDescArg analyze_esil_sdb_reset_args[] = { + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_sdb_reset_help = { + .summary = "Resets the ESIL info sdb instance.", + .args = analyze_esil_sdb_reset_args, +}; + +static const RzCmdDescArg analyze_esil_eval_opcode_expr_args[] = { + { + .name = "bytes", + .type = RZ_CMD_ARG_TYPE_STRING, + .flags = RZ_CMD_ARG_FLAG_LAST, + + }, + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_eval_opcode_expr_help = { + .summary = "Emulate the instruction encoded in the given bytes with ESIL.", + .args = analyze_esil_eval_opcode_expr_args, +}; + +static const RzCmdDescHelp aef_help = { + .summary = "Emulate functions with ESIL.", +}; +static const RzCmdDescArg analyze_esil_emu_fcn_args[] = { + { + .name = "addr", + .type = RZ_CMD_ARG_TYPE_NUM, + .optional = true, + + }, + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_emu_fcn_help = { + .summary = "Emulate the function at given or current offset with ESIL.", + .args = analyze_esil_emu_fcn_args, +}; + +static const RzCmdDescArg analyze_esil_emu_fcn_find_args_args[] = { + { + .name = "addr", + .type = RZ_CMD_ARG_TYPE_NUM, + .optional = true, + + }, + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_emu_fcn_find_args_help = { + .summary = "Emulate function at given or current offset to find arguments with ESIL.", + .args = analyze_esil_emu_fcn_find_args_args, +}; + +static const RzCmdDescHelp ael_help = { + .summary = "ESIL interrupt commands.", +}; +static const RzCmdDescArg analyze_esil_int_list_load_args[] = { + { + .name = "file", + .type = RZ_CMD_ARG_TYPE_STRING, + .flags = RZ_CMD_ARG_FLAG_LAST, + .optional = true, + + }, + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_int_list_load_help = { + .summary = "List ESIL interrupts or load them from the given shared object.", + .args = analyze_esil_int_list_load_args, +}; + +static const RzCmdDescArg analyze_esil_int_remove_args[] = { + { + .name = "interrupt number", + .type = RZ_CMD_ARG_TYPE_NUM, + + }, + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_int_remove_help = { + .summary = "Remove ESIL interrupt and free it if needed.", + .args = analyze_esil_int_remove_args, +}; + +static const RzCmdDescHelp aea_help = { + .summary = "ESIL emulation to retrieve arguments.", +}; +static const RzCmdDescDetailEntry analyze_esil_insn_access_Flag_detail_entries[] = { + { .text = "A", .arg_str = NULL, .comment = "Interpret the [len] parameter as number of bytes. Not as number of instructions." }, + { 0 }, +}; + +static const RzCmdDescDetailEntry analyze_esil_insn_access_Options_detail_entries[] = { + { .text = "*", .arg_str = NULL, .comment = "Create mem.* flags for memory accesses." }, + { .text = "r", .arg_str = NULL, .comment = "Show regs read in N instructions." }, + { .text = "w", .arg_str = NULL, .comment = "Show regs written in N instructions." }, + { .text = "n", .arg_str = NULL, .comment = "Show regs not written in N instructions." }, + { .text = "b", .arg_str = NULL, .comment = "Show regs used in current basic block. The [len] parameter, if not 0, is interpreted as address." }, + { .text = "f", .arg_str = NULL, .comment = "Show regs used in current function." }, + { .text = "d", .arg_str = NULL, .comment = "Show memory and register access." }, + { 0 }, +}; + +static const RzCmdDescDetailEntry analyze_esil_insn_access_Legend_detail_entries[] = { + { .text = "I", .arg_str = NULL, .comment = "input registers (read before being set)" }, + { .text = "A", .arg_str = NULL, .comment = "all regs accessed" }, + { .text = "R", .arg_str = NULL, .comment = "register values read" }, + { .text = "W", .arg_str = NULL, .comment = "registers written" }, + { .text = "N", .arg_str = NULL, .comment = "read but never written" }, + { .text = "V", .arg_str = NULL, .comment = "values" }, + { .text = "@R", .arg_str = NULL, .comment = "memreads" }, + { .text = "@W", .arg_str = NULL, .comment = "memwrites" }, + { .text = "NOTE:", .arg_str = NULL, .comment = "mem{reads,writes} with PIC only fetch the offset" }, + { 0 }, +}; +static const RzCmdDescDetail analyze_esil_insn_access_details[] = { + { .name = "Flag", .entries = analyze_esil_insn_access_Flag_detail_entries }, + { .name = "Options", .entries = analyze_esil_insn_access_Options_detail_entries }, + { .name = "Legend", .entries = analyze_esil_insn_access_Legend_detail_entries }, + { 0 }, +}; +static const char *analyze_esil_insn_access_type_choices[] = { "d", "*", "r", "w", "n", "b", "f", NULL }; +static const RzCmdDescArg analyze_esil_insn_access_args[] = { + { + .name = "len", + .type = RZ_CMD_ARG_TYPE_NUM, + .default_value = "0", + + }, + { + .name = "type", + .type = RZ_CMD_ARG_TYPE_CHOICES, + .default_value = "d", + .choices.choices = analyze_esil_insn_access_type_choices, + + }, + { + .name = "A", + .type = RZ_CMD_ARG_TYPE_OPTION, + .optional = true, + + }, + { 0 }, +}; +static const RzCmdDescHelp analyze_esil_insn_access_help = { + .summary = "Show register and memory access of the next [len] instructions or bytes.", + .details = analyze_esil_insn_access_details, + .args = analyze_esil_insn_access_args, +}; + static const RzCmdDescHelp b_help = { .summary = "Display or change the block size", }; @@ -19431,7 +19655,7 @@ RZ_IPI void rzshell_cmddescs_init(RzCore *core) { RzCmdDesc *push_escaped_cd = rz_cmd_desc_argv_new(core->rcmd, root_cd, "<", rz_push_escaped_handler, &push_escaped_help); rz_warn_if_fail(push_escaped_cd); - RzCmdDesc *cmd_analysis_cd = rz_cmd_desc_oldinput_new(core->rcmd, root_cd, "a", rz_cmd_analysis, &cmd_analysis_help); + RzCmdDesc *cmd_analysis_cd = rz_cmd_desc_group_new(core->rcmd, root_cd, "a", NULL, NULL, &cmd_analysis_help); rz_warn_if_fail(cmd_analysis_cd); RzCmdDesc *aa_cd = rz_cmd_desc_group_new(core->rcmd, cmd_analysis_cd, "aa", rz_analyze_simple_handler, &analyze_simple_help, &aa_help); rz_warn_if_fail(aa_cd); @@ -20287,6 +20511,41 @@ RZ_IPI void rzshell_cmddescs_init(RzCore *core) { RzCmdDesc *list_plugins_cd = rz_cmd_desc_argv_state_new(core->rcmd, cmd_analysis_cd, "aL", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_JSON, rz_list_plugins_handler, &list_plugins_help); rz_warn_if_fail(list_plugins_cd); + RzCmdDesc *ae_cd = rz_cmd_desc_group_new(core->rcmd, cmd_analysis_cd, "ae", rz_analyze_esil_eval_expr_handler, &analyze_esil_eval_expr_help, &ae_help); + rz_warn_if_fail(ae_cd); + RzCmdDesc *analyze_esil_expr_help_cd = rz_cmd_desc_argv_new(core->rcmd, ae_cd, "aeH", rz_analyze_esil_expr_help_handler, &analyze_esil_expr_help_help); + rz_warn_if_fail(analyze_esil_expr_help_cd); + + RzCmdDesc *analyze_esil_emulate_block_cd = rz_cmd_desc_argv_new(core->rcmd, ae_cd, "aeb", rz_analyze_esil_emulate_block_handler, &analyze_esil_emulate_block_help); + rz_warn_if_fail(analyze_esil_emulate_block_cd); + + RzCmdDesc *analyze_esil_set_pc_cd = rz_cmd_desc_argv_new(core->rcmd, ae_cd, "aepc", rz_analyze_esil_set_pc_handler, &analyze_esil_set_pc_help); + rz_warn_if_fail(analyze_esil_set_pc_cd); + + RzCmdDesc *aek_cd = rz_cmd_desc_group_new(core->rcmd, ae_cd, "aek", rz_analyze_esil_sdb_query_handler, &analyze_esil_sdb_query_help, &aek_help); + rz_warn_if_fail(aek_cd); + RzCmdDesc *analyze_esil_sdb_reset_cd = rz_cmd_desc_argv_new(core->rcmd, aek_cd, "aek-", rz_analyze_esil_sdb_reset_handler, &analyze_esil_sdb_reset_help); + rz_warn_if_fail(analyze_esil_sdb_reset_cd); + + RzCmdDesc *analyze_esil_eval_opcode_expr_cd = rz_cmd_desc_argv_new(core->rcmd, ae_cd, "aex", rz_analyze_esil_eval_opcode_expr_handler, &analyze_esil_eval_opcode_expr_help); + rz_warn_if_fail(analyze_esil_eval_opcode_expr_cd); + + RzCmdDesc *aef_cd = rz_cmd_desc_group_new(core->rcmd, ae_cd, "aef", rz_analyze_esil_emu_fcn_handler, &analyze_esil_emu_fcn_help, &aef_help); + rz_warn_if_fail(aef_cd); + RzCmdDesc *analyze_esil_emu_fcn_find_args_cd = rz_cmd_desc_argv_new(core->rcmd, aef_cd, "aefa", rz_analyze_esil_emu_fcn_find_args_handler, &analyze_esil_emu_fcn_find_args_help); + rz_warn_if_fail(analyze_esil_emu_fcn_find_args_cd); + + RzCmdDesc *ael_cd = rz_cmd_desc_group_new(core->rcmd, ae_cd, "ael", NULL, NULL, &ael_help); + rz_warn_if_fail(ael_cd); + RzCmdDesc *analyze_esil_int_list_load_cd = rz_cmd_desc_argv_new(core->rcmd, ael_cd, "aeli", rz_analyze_esil_int_list_load_handler, &analyze_esil_int_list_load_help); + rz_warn_if_fail(analyze_esil_int_list_load_cd); + + RzCmdDesc *analyze_esil_int_remove_cd = rz_cmd_desc_argv_new(core->rcmd, ael_cd, "aelir", rz_analyze_esil_int_remove_handler, &analyze_esil_int_remove_help); + rz_warn_if_fail(analyze_esil_int_remove_cd); + + RzCmdDesc *aea_cd = rz_cmd_desc_group_modes_new(core->rcmd, ae_cd, "aea", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_JSON, rz_analyze_esil_insn_access_handler, &analyze_esil_insn_access_help, &aea_help); + rz_warn_if_fail(aea_cd); + RzCmdDesc *b_cd = rz_cmd_desc_group_state_new(core->rcmd, root_cd, "b", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_JSON | RZ_OUTPUT_MODE_RIZIN, rz_block_handler, &block_help, &b_help); rz_warn_if_fail(b_cd); RzCmdDesc *block_decrease_cd = rz_cmd_desc_argv_new(core->rcmd, b_cd, "b-", rz_block_decrease_handler, &block_decrease_help); diff --git a/librz/core/cmd_descs/cmd_descs.h b/librz/core/cmd_descs/cmd_descs.h index 57adcd46733..2e1860ddb2d 100644 --- a/librz/core/cmd_descs/cmd_descs.h +++ b/librz/core/cmd_descs/cmd_descs.h @@ -777,8 +777,30 @@ RZ_IPI RzCmdStatus rz_analysis_syscall_name_handler(RzCore *core, int argc, cons RZ_IPI RzCmdStatus rz_analysis_syscall_number_handler(RzCore *core, int argc, const char **argv); // "aL" RZ_IPI RzCmdStatus rz_list_plugins_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state); -// "a" -RZ_IPI int rz_cmd_analysis(void *data, const char *input); +// "ae" +RZ_IPI RzCmdStatus rz_analyze_esil_eval_expr_handler(RzCore *core, int argc, const char **argv); +// "aeH" +RZ_IPI RzCmdStatus rz_analyze_esil_expr_help_handler(RzCore *core, int argc, const char **argv); +// "aeb" +RZ_IPI RzCmdStatus rz_analyze_esil_emulate_block_handler(RzCore *core, int argc, const char **argv); +// "aepc" +RZ_IPI RzCmdStatus rz_analyze_esil_set_pc_handler(RzCore *core, int argc, const char **argv); +// "aek" +RZ_IPI RzCmdStatus rz_analyze_esil_sdb_query_handler(RzCore *core, int argc, const char **argv); +// "aek-" +RZ_IPI RzCmdStatus rz_analyze_esil_sdb_reset_handler(RzCore *core, int argc, const char **argv); +// "aex" +RZ_IPI RzCmdStatus rz_analyze_esil_eval_opcode_expr_handler(RzCore *core, int argc, const char **argv); +// "aef" +RZ_IPI RzCmdStatus rz_analyze_esil_emu_fcn_handler(RzCore *core, int argc, const char **argv); +// "aefa" +RZ_IPI RzCmdStatus rz_analyze_esil_emu_fcn_find_args_handler(RzCore *core, int argc, const char **argv); +// "aeli" +RZ_IPI RzCmdStatus rz_analyze_esil_int_list_load_handler(RzCore *core, int argc, const char **argv); +// "aelir" +RZ_IPI RzCmdStatus rz_analyze_esil_int_remove_handler(RzCore *core, int argc, const char **argv); +// "aea" +RZ_IPI RzCmdStatus rz_analyze_esil_insn_access_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode); // "b" RZ_IPI RzCmdStatus rz_block_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state); // "b-" diff --git a/librz/core/cmd_descs/cmd_descs.yaml b/librz/core/cmd_descs/cmd_descs.yaml index ed7f59fa986..b7483676748 100644 --- a/librz/core/cmd_descs/cmd_descs.yaml +++ b/librz/core/cmd_descs/cmd_descs.yaml @@ -184,7 +184,6 @@ commands: - name: a cname: cmd_analysis summary: Analysis commands - type: RZ_CMD_DESC_TYPE_OLDINPUT subcommands: cmd_analysis - name: b summary: Display or change the block size diff --git a/test/db/cmd/cmd_aea b/test/db/cmd/cmd_aea index ef6b5134bc0..1b437affa51 100644 --- a/test/db/cmd/cmd_aea +++ b/test/db/cmd/cmd_aea @@ -67,10 +67,10 @@ EXPECT=< FILE== -CMDS="ae 1,2,>" +CMDS=<" +EOF EXPECT=< FILE== -CMDS="ae 2,1,>" +CMDS=<" +EOF EXPECT=<=" +CMDS=<=" +EOF EXPECT=<= FILE== -CMDS="ae 2,1,>=" +CMDS=<=" +EOF EXPECT=<=" +CMDS=<=" +EOF EXPECT=< FILE== -CMDS="ae -1,1,>" +CMDS=<" +EOF EXPECT=<= FILE== -CMDS="ae -1,1,>=" +CMDS=<=" +EOF EXPECT=<=" +CMDS=<=" +EOF EXPECT=<>" -"ae 8,1,>>" +ae "1,8,>>" +ae "8,1,>>" EOF EXPECT=<>>,eax,=" +ae "0x1,eax,>>>,eax,=" ar eax EOF EXPECT=<,31,$o,of,:=,31,$s,sf,:=,31,$c,cf,:=,$z,zf,:=,sf,of,^,zf,|,!,==,$z,NUM" +ae "2,eax,=,1,ebx,=,ebx,eax,>,31,$o,of,:=,31,$s,sf,:=,31,$c,cf,:=,$z,zf,:=,sf,of,^,zf,|,!,==,$z,NUM" EOF EXPECT=<,31,$o,of,:=,31,$s,sf,:=,31,$c,cf,:=,$z,zf,:=,cf,==,$z,NUM" +ae "-1,eax,=,1,ebx,=,ebx,eax,>,31,$o,of,:=,31,$s,sf,:=,31,$c,cf,:=,$z,zf,:=,cf,==,$z,NUM" EOF EXPECT=<