代码之家  ›  专栏  ›  技术社区  ›  mickp

为什么调试陷阱执行的次数比预期的多?

  •  4
  • mickp  · 技术社区  · 7 年前

    以下代码触发调试陷阱3次。不过,我只期待两次处决——一次是为了 foo ,第二个 echo hello :

    foo() {
        echo hello
    }
    
    set -T
    trap 'echo oops' DEBUG
    foo
    

    输出:

    oops
    oops
    oops
    hello
    

    预期输出:

    oops
    oops
    hello
    

    Bash 测试版本:

    GNU bash, version 4.3.30(1)-release (x86_64-unknown-linux-gnu)
    GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)
    GNU bash, version 5.0.0(1)-alpha (x86_64-pc-linux-gnu)
    

    我很肯定我只是误解了手册,我遗漏了一些非常简单/明显的东西。

    2 回复  |  直到 7 年前
        1
  •  2
  •   that other guy    7 年前

    tl;dr:bash在调用函数以允许在函数本身而不是其第一个命令上中断时,会额外运行一次调试陷阱。这是源代码的 execute_function 用于调用bash函数:

      /* Run the debug trap here so we can trap at the start of a function's
         execution rather than the execution of the body's first command. */
    

    震源潜水时间

    Here is the code 执行调试陷阱的:

    int run_debug_trap () {
    (...)
        trap_exit_value = _run_trap_internal (DEBUG_TRAP, "debug trap");
    

    我在那条线上放了一个断点, trap.c:1081 从今天开始,我们查看了回溯:

    初审

    Breakpoint 1, run_debug_trap () at trap.c:1081
    1081          trap_exit_value = _run_trap_internal (DEBUG_TRAP, "debug trap");
    (gdb) where
    #0  run_debug_trap () at trap.c:1081
    #1  0x000055555559fd3d in execute_simple_command (simple_command=0x5555558aacc8, pipe_in=-1, pipe_out=-1, async=0, fds_to_close=0x555555899148) at execute_cmd.c:4056
    #2  0x0000555555599fd7 in execute_command_internal (command=0x5555558aae08, asynchronous=0, pipe_in=-1, pipe_out=-1, fds_to_close=0x555555899148) at execute_cmd.c:807
    #3  0x00005555555995c1 in execute_command (command=0x5555558aae08) at execute_cmd.c:405
    #4  0x0000555555583c9e in reader_loop () at eval.c:180
    #5  0x0000555555581794 in main (argc=2, argv=0x7fffffffe4d8, env=0x7fffffffe4f0) at shell.c:792
    (gdb) up
    #1  0x000055555559fd3d in execute_simple_command (simple_command=0x5555558aacc8, pipe_in=-1, pipe_out=-1, async=0, fds_to_close=0x555555899148) at execute_cmd.c:4056
    4056      result = run_debug_trap ();
    
    (gdb) print *simple_command->words->word
    $3 = {word = 0x5555558a5268 "foo", flags = 0}
    

    换句话说,这是一个简单的命令 foo . 到现在为止,一直都还不错。

    二审

    Breakpoint 1, run_debug_trap () at trap.c:1081
    1081          trap_exit_value = _run_trap_internal (DEBUG_TRAP, "debug trap");
    (gdb) where
    #0  run_debug_trap () at trap.c:1081
    #1  0x00005555555a170a in execute_function (var=0x5555558ab648, words=0x5555558aac28, flags=0, fds_to_close=0x555555899148, async=0, subshell=0) at execute_cmd.c:4787
    #2  0x00005555555a1c68 in execute_builtin_or_function (words=0x5555558aac28, builtin=0x0, var=0x5555558ab648, redirects=0x0, fds_to_close=0x555555899148, flags=0) at execute_cmd.c:5030
    #3  0x00005555555a0660 in execute_simple_command (simple_command=0x5555558aacc8, pipe_in=-1, pipe_out=-1, async=0, fds_to_close=0x555555899148) at execute_cmd.c:4330
    #4  0x0000555555599fd7 in execute_command_internal (command=0x5555558aae08, asynchronous=0, pipe_in=-1, pipe_out=-1, fds_to_close=0x555555899148) at execute_cmd.c:807
    #5  0x00005555555995c1 in execute_command (command=0x5555558aae08) at execute_cmd.c:405
    #6  0x0000555555583c9e in reader_loop () at eval.c:180
    #7  0x0000555555581794 in main (argc=2, argv=0x7fffffffe4d8, env=0x7fffffffe4f0) at shell.c:792
    

    这更有趣。我们仍在执行简单的命令 从以前开始,但它又触发了!为什么呢?让我们看一下呼叫地点, execute_cmd.c:4787 :

      /* Run the debug trap here so we can trap at the start of a function's
         execution rather than the execution of the body's first command. */
      showing_function_line = 1;
      save_current = currently_executing_command;
      result = run_debug_trap ();
    

    换句话说,bash似乎有意额外运行一次调试陷阱,目的是破坏一个函数,而不是它的第一个命令。

    第三例

    不出所料,函数的 echo 命令,所以我不包括它。

        2
  •  2
  •   Inian    7 年前

    我想你明白了 DEBUG trap允许运行在其定义中定义的命令( trap 'action' signal )在所有其他shell命令之前 set -T 同时对所有子shell和函数启用陷阱。所以一旦你设置了 trap 你有三次处决。

    1. 调用函数 foo ,在此之前 陷阱 被激发,导致打印字符串的第一个实例。
    2. 然后你有一个 echo oops 命令内部 陷阱 定义位置 echo 只是 另一个 命令,所以在运行它之前 陷阱 被解雇了。
    3. 而实际 回声 命令在 陷阱 生成第三个实例
    4. hello 完成后最后一个 陷阱 执行完成。

    从调试模式运行时查看序列

    $ bash -x script.sh
    + set -T
    + trap 'echo oops' DEBUG
    ++ echo oops                # <--- triggered by call to 'foo'
    oops
    + foo
    ++ echo oops                # <--- triggered by call to 'echo oops' inside trap definition
    oops
    ++ echo oops                # <--- result of the actual command to be run
    oops
    + echo hello                # <--- result of the function call 'foo'
    hello
    

    添加更多的解释来解释序列,想象一下 调试 陷阱被点燃 之前 任何shell函数/命令都将运行。我们将其分解为以下内容。了解这是两条指令/命令以及这些命令的两个结果操作

    1. 打电话给 是第一条指令,因此触发陷阱并执行操作 回声OOP 现在就跑。记住这里 是触发和 回声OOP 行动 我们第一次看到它。
    2. 现在第二条指令是 回声OOP 哪一个 触发器 另一个动作,所以因为 陷阱 , the 回声OOP 被激发,然后实际的指令被完成,这也恰好是 回声OOP