# HG changeset patch # User Christian Brabandt # Date 1452955504 -3600 # Node ID 8fc60af6dbf57e19daa8d3f8cd6a1b62f22e7d4d # Parent 09a1aca8d98089a160427b5855f086b46744bbb6 commit https://github.com/vim/vim/commit/f1f60f859cdbb2638b3662ccf7b1d179865fe7dc Author: Bram Moolenaar Date: Sat Jan 16 15:40:53 2016 +0100 patch 7.4.1102 Problem: Debugger has no stack backtrace support. Solution: Add "backtrace", "frame", "up" and "down" commands. (Alberto Fanjul, closes https://github.com/vim/vim/issues/433) diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -1,4 +1,4 @@ -*repeat.txt* For Vim version 7.4. Last change: 2015 Apr 13 +*repeat.txt* For Vim version 7.4. Last change: 2016 Jan 16 VIM REFERENCE MANUAL by Bram Moolenaar @@ -483,16 +483,44 @@ Additionally, these commands can be used finish Finish the current script or user function and come back to debug mode for the command after the one that sourced or called it. + *>bt* + *>backtrace* + *>where* + backtrace Show the call stacktrace for current debugging session. + bt + where + *>frame* + frame N Goes to N bactrace level. + and - signs make movement + relative. E.g., ":frame +3" goes three frames up. + *>up* + up Goes one level up from call stacktrace. + *>down* + down Goes one level down from call stacktrace. About the additional commands in debug mode: - There is no command-line completion for them, you get the completion for the normal Ex commands only. -- You can shorten them, up to a single character: "c", "n", "s" and "f". +- You can shorten them, up to a single character, unless more then one command + starts with the same letter. "f" stands for "finish", use "fr" for "frame". - Hitting will repeat the previous one. When doing another command, this is reset (because it's not clear what you want to repeat). - When you want to use the Ex command with the same name, prepend a colon: ":cont", ":next", ":finish" (or shorter). +The backtrace shows the hierarchy of function calls, e.g.: + >bt ~ + 3 function One[3] ~ + 2 Two[3] ~ + ->1 Three[3] ~ + 0 Four ~ + line 1: let four = 4 ~ + +The "->" points to the current frame. Use "up", "down" and "frame N" to +select another frame. + +In the current frame you can evaluate the local function variables. There is +no way to see the command at the current line yet. + DEFINING BREAKPOINTS *:breaka* *:breakadd* diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -812,6 +812,7 @@ static char_u *get_tv_string_buf_chk __A static dictitem_T *find_var __ARGS((char_u *name, hashtab_T **htp, int no_autoload)); static dictitem_T *find_var_in_ht __ARGS((hashtab_T *ht, int htname, char_u *varname, int no_autoload)); static hashtab_T *find_var_ht __ARGS((char_u *name, char_u **varname)); +static funccall_T *get_funccal __ARGS((void)); static void vars_clear_ext __ARGS((hashtab_T *ht, int free_val)); static void delete_var __ARGS((hashtab_T *ht, hashitem_T *hi)); static void list_one_var __ARGS((dictitem_T *v, char_u *prefix, int *first)); @@ -21735,7 +21736,7 @@ find_var_ht(name, varname) if (current_funccal == NULL) return &globvarht; /* global variable */ - return ¤t_funccal->l_vars.dv_hashtab; /* l: variable */ + return &get_funccal()->l_vars.dv_hashtab; /* l: variable */ } *varname = name + 2; if (*name == 'g') /* global variable */ @@ -21756,9 +21757,9 @@ find_var_ht(name, varname) if (*name == 'v') /* v: variable */ return &vimvarht; if (*name == 'a' && current_funccal != NULL) /* function argument */ - return ¤t_funccal->l_avars.dv_hashtab; + return &get_funccal()->l_avars.dv_hashtab; if (*name == 'l' && current_funccal != NULL) /* local function variable */ - return ¤t_funccal->l_vars.dv_hashtab; + return &get_funccal()->l_vars.dv_hashtab; if (*name == 's' /* script variable */ && current_SID > 0 && current_SID <= ga_scripts.ga_len) return &SCRIPT_VARS(current_SID); @@ -21766,6 +21767,32 @@ find_var_ht(name, varname) } /* + * Get function call environment based on bactrace debug level + */ + static funccall_T * +get_funccal() +{ + int i; + funccall_T *funccal; + funccall_T *temp_funccal; + + funccal = current_funccal; + if (debug_backtrace_level > 0) + { + for (i = 0; i < debug_backtrace_level; i++) + { + temp_funccal = funccal->caller; + if (temp_funccal) + funccal = temp_funccal; + else + /* backtrace level overflow. reset to max */ + debug_backtrace_level = i; + } + } + return funccal; +} + +/* * Get the string value of a (global/local) variable. * Note: see get_tv_string() for how long the pointer remains valid. * Returns NULL when it doesn't exist. diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -68,6 +68,10 @@ typedef struct sn_prl_S #if defined(FEAT_EVAL) || defined(PROTO) static int debug_greedy = FALSE; /* batch mode debugging: don't save and restore typeahead. */ +static int get_maxbacktrace_level(void); +static void do_setdebugtracelevel(char_u *arg); +static void do_checkbacktracelevel(void); +static void do_showbacktrace(char_u *cmd); /* * do_debug(): Debug mode. @@ -101,6 +105,10 @@ do_debug(cmd) #define CMD_FINISH 4 #define CMD_QUIT 5 #define CMD_INTERRUPT 6 +#define CMD_BACKTRACE 7 +#define CMD_FRAME 8 +#define CMD_UP 9 +#define CMD_DOWN 10 #ifdef ALWAYS_USE_GUI /* Can't do this when there is no terminal for input/output. */ @@ -178,6 +186,7 @@ do_debug(cmd) # endif cmdline_row = msg_row; + msg_starthere(); if (cmdline != NULL) { /* If this is a debug command, set "last_cmd". @@ -197,8 +206,18 @@ do_debug(cmd) case 's': last_cmd = CMD_STEP; tail = "tep"; break; - case 'f': last_cmd = CMD_FINISH; - tail = "inish"; + case 'f': + last_cmd = 0; + if (p[1] == 'r') + { + last_cmd = CMD_FRAME; + tail = "rame"; + } + else + { + last_cmd = CMD_FINISH; + tail = "inish"; + } break; case 'q': last_cmd = CMD_QUIT; tail = "uit"; @@ -206,6 +225,21 @@ do_debug(cmd) case 'i': last_cmd = CMD_INTERRUPT; tail = "nterrupt"; break; + case 'b': last_cmd = CMD_BACKTRACE; + if (p[1] == 't') + tail = "t"; + else + tail = "acktrace"; + break; + case 'w': last_cmd = CMD_BACKTRACE; + tail = "here"; + break; + case 'u': last_cmd = CMD_UP; + tail = "p"; + break; + case 'd': last_cmd = CMD_DOWN; + tail = "own"; + break; default: last_cmd = 0; } if (last_cmd != 0) @@ -217,7 +251,7 @@ do_debug(cmd) ++p; ++tail; } - if (ASCII_ISALPHA(*p)) + if (ASCII_ISALPHA(*p) && last_cmd != CMD_FRAME) last_cmd = 0; } } @@ -250,7 +284,31 @@ do_debug(cmd) /* Do not repeat ">interrupt" cmd, continue stepping. */ last_cmd = CMD_STEP; break; + case CMD_BACKTRACE: + do_showbacktrace(cmd); + continue; + case CMD_FRAME: + if (*p == NUL) + { + do_showbacktrace(cmd); + } + else + { + p = skipwhite(p); + do_setdebugtracelevel(p); + } + continue; + case CMD_UP: + debug_backtrace_level++; + do_checkbacktracelevel(); + continue; + case CMD_DOWN: + debug_backtrace_level--; + do_checkbacktracelevel(); + continue; } + /* Going out reset backtrace_level */ + debug_backtrace_level = 0; break; } @@ -285,6 +343,92 @@ do_debug(cmd) debug_did_msg = TRUE; } + static int +get_maxbacktrace_level(void) +{ + char *p, *q; + int maxbacktrace = 1; + + maxbacktrace = 0; + if (sourcing_name != NULL) + { + p = (char *)sourcing_name; + while ((q = strstr(p, "..")) != NULL) + { + p = q + 2; + maxbacktrace++; + } + } + return maxbacktrace; +} + + static void +do_setdebugtracelevel(char_u *arg) +{ + int level; + + level = atoi((char *)arg); + if (*arg == '+' || level < 0) + debug_backtrace_level += level; + else + debug_backtrace_level = level; + + do_checkbacktracelevel(); +} + + static void +do_checkbacktracelevel(void) +{ + if (debug_backtrace_level < 0) + { + debug_backtrace_level = 0; + MSG(_("frame is zero")); + } + else + { + int max = get_maxbacktrace_level(); + + if (debug_backtrace_level > max) + { + debug_backtrace_level = max; + smsg((char_u *)_("frame at highest level: %d"), max); + } + } +} + + static void +do_showbacktrace(char_u *cmd) +{ + char *cur; + char *next; + int i = 0; + int max = get_maxbacktrace_level(); + + if (sourcing_name != NULL) + { + cur = (char *)sourcing_name; + while (!got_int) + { + next = strstr(cur, ".."); + if (next != NULL) + *next = NUL; + if (i == max - debug_backtrace_level) + smsg((char_u *)"->%d %s", max - i, cur); + else + smsg((char_u *)" %d %s", max - i, cur); + ++i; + if (next == NULL) + break; + *next = '.'; + cur = next + 2; + } + } + if (sourcing_lnum != 0) + smsg((char_u *)_("line %ld: %s"), (long)sourcing_lnum, cmd); + else + smsg((char_u *)_("cmd: %s"), cmd); +} + /* * ":debug". */ diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -231,6 +231,7 @@ EXTERN int ex_nesting_level INIT(= 0); / EXTERN int debug_break_level INIT(= -1); /* break below this level */ EXTERN int debug_did_msg INIT(= FALSE); /* did "debug mode" message */ EXTERN int debug_tick INIT(= 0); /* breakpoint change count */ +EXTERN int debug_backtrace_level INIT(= 0); /* breakpoint backtrace level */ # ifdef FEAT_PROFILE EXTERN int do_profiling INIT(= PROF_NONE); /* PROF_ values */ # endif diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -89,6 +89,7 @@ SCRIPTS_ALL = \ test105.out \ test106.out \ test107.out \ + test108.out \ test_argument_0count.out \ test_argument_count.out \ test_autocmd_option.out \ diff --git a/src/testdir/test108.in b/src/testdir/test108.in new file mode 100644 --- /dev/null +++ b/src/testdir/test108.in @@ -0,0 +1,87 @@ +Tests for backtrace debug commands. vim: set ft=vim : + +STARTTEST +:so small.vim +:function! Foo() +: let var1 = 1 +: let var2 = Bar(var1) + 9 +: return var2 +:endfunction +: +:function! Bar(var) +: let var1 = 2 + a:var +: let var2 = Bazz(var1) + 4 +: return var2 +:endfunction +: +:function! Bazz(var) +: let var1 = 3 + a:var +: let var3 = "another var" +: return var1 +:endfunction +:new +:debuggreedy +:redir => out +:debug echo Foo() +step +step +step +step +step +step +echo "- show backtrace:\n" +backtrace +echo "\nshow variables on different levels:\n" +echo var1 +up +back +echo var1 +u +bt +echo var1 +echo "\n- undefined vars:\n" +step +frame 2 +echo "undefined var3 on former level:" +echo var3 +fr 0 +echo "here var3 is defined with \"another var\":" +echo var3 +step +step +step +up +echo "\nundefined var2 on former level" +echo var2 +down +echo "here var2 is defined with 10:" +echo var2 +echo "\n- backtrace movements:\n" +b +echo "\nnext command cannot go down, we are on bottom\n" +down +up +echo "\nnext command cannot go up, we are on top\n" +up +b +echo "fil is not frame or finish, it is file" +fil +echo "\n- relative backtrace movement\n" +fr -1 +frame +fra +1 +fram +echo "\n- go beyond limits does not crash\n" +fr 100 +fra +frame -40 +fram +echo "\n- final result 19:" +cont +:0debuggreedy +:redir END +:$put =out +:w! test.out +:qa! +ENDTEST + diff --git a/src/testdir/test108.ok b/src/testdir/test108.ok new file mode 100644 --- /dev/null +++ b/src/testdir/test108.ok @@ -0,0 +1,84 @@ + + + +- show backtrace: + + 2 function Foo[2] + 1 Bar[2] +->0 Bazz +line 2: let var3 = "another var" + +show variables on different levels: + +6 + 2 function Foo[2] +->1 Bar[2] + 0 Bazz +line 2: let var3 = "another var" +3 +->2 function Foo[2] + 1 Bar[2] + 0 Bazz +line 2: let var3 = "another var" +1 + +- undefined vars: + +undefined var3 on former level: +Error detected while processing function Foo[2]..Bar[2]..Bazz: +line 3: +E121: Undefined variable: var3 +E15: Invalid expression: var3 +here var3 is defined with "another var": +another var + +undefined var2 on former level +Error detected while processing function Foo[2]..Bar: +line 3: +E121: Undefined variable: var2 +E15: Invalid expression: var2 +here var2 is defined with 10: +10 + +- backtrace movements: + + 1 function Foo[2] +->0 Bar +line 3: End of function + +next command cannot go down, we are on bottom + +frame is zero + +next command cannot go up, we are on top + +frame at highest level: 1 +->1 function Foo[2] + 0 Bar +line 3: End of function +fil is not frame or finish, it is file +"[No Name]" --No lines in buffer-- + +- relative backtrace movement + + 1 function Foo[2] +->0 Bar +line 3: End of function +->1 function Foo[2] + 0 Bar +line 3: End of function + +- go beyond limits does not crash + +frame at highest level: 1 +->1 function Foo[2] + 0 Bar +line 3: End of function +frame is zero + 1 function Foo[2] +->0 Bar +line 3: End of function + +- final result 19: +19 + diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -742,6 +742,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1102, +/**/ 1101, /**/ 1100,