Трассировки стека - Stack trace

В вычислении , А трассировки стека (также называемый стек трассировку или стек отслеживающий ) представляет собой отчет из активных кадров стека в определенный момент времени во время выполнения программы . Когда программа запускается, память часто динамически выделяется в двух местах; стека и кучи . Память постоянно выделяется в стеке, но не в куче, что отражает их имена. Стек также относится к программной конструкции, поэтому для отличия этот стек называется стеком вызовов функций программы . Технически, как только блок памяти был выделен в стеке, его нелегко удалить, поскольку могут быть другие блоки памяти, которые были выделены до него. Каждый раз, когда функция вызывается в программе, блок памяти, называемый записью активации, выделяется поверх стека вызовов. Обычно запись активации хранит аргументы функции и локальные переменные. Что именно он содержит и как он расположен, определяется соглашением о вызовах .

Программисты обычно используют трассировку стека во время интерактивной и посмертной отладки . Конечные пользователи могут видеть трассировку стека, отображаемую как часть сообщения об ошибке , о котором пользователь может затем сообщить программисту.

Трассировка стека позволяет отслеживать последовательность вызываемых вложенных функций - до точки, где создается трассировка стека. В посмертном сценарии это распространяется на функцию, в которой произошел сбой (но не обязательно). Вызовы одного уровня не отображаются в трассировке стека.

Языковая поддержка

Многие языки программирования, включая Java и C # , имеют встроенную поддержку для получения текущей трассировки стека с помощью системных вызовов. C ++ не имеет встроенной поддержки для этого, но пользователи C ++ могут извлекать трассировки стека с помощью (например) библиотеки stacktrace . В JavaScript , исключение держать свойство , которое содержит стек от того места , где он был брошен. stack

Python

В качестве примера следующая программа Python содержит ошибку.

def a():
    i = 0
    j = b(i)
    return j

def b(z):
    k = 5
    if z == 0:
        c()
    return k + z

def c():
    error()

a()

При запуске программы в стандартном интерпретаторе Python появляется следующее сообщение об ошибке.

Traceback (most recent call last):
  File "tb.py", line 15, in <module>
    a()
  File "tb.py", line 3, in a
    j = b(i)
  File "tb.py", line 9, in b
    c()
  File "tb.py", line 13, in c
    error()
NameError: name 'error' is not defined

Трассировка стека показывает, где возникает ошибка, а именно в cфункции. Это также показывает, что cфункция была вызвана пользователем b, который был вызван пользователем a, который, в свою очередь, был вызван кодом в строке 15 (последней строке) программы. Записи активации для каждой из этих трех функций должны быть расположены в стеке таким образом, чтобы aфункция занимала нижнюю часть стека, а cфункция - верхнюю часть стека.

Джава

В Java трассировки стека можно сбрасывать вручную, Thread.dumpStack()используя следующие входные данные:

public class Main {
  public static void main(String args[]) {
    demo();
  }
  static void demo() {
    demo1();
  }
  static void demo1() {
    demo2();
  }
  static void demo2() {
    demo3();
  }
  static void demo3() {
    Thread.dumpStack();
  }
}

В исключении перечислены функции в порядке убывания, поэтому наиболее внутренний вызов идет первым.

java.lang.Exception: Stack trace
        at java.lang.Thread.dumpStack(Thread.java:1336)
        at Main.demo3(Main.java:15)
        at Main.demo2(Main.java:12)
        at Main.demo1(Main.java:9)
        at Main.demo(Main.java:6)
        at Main.main(Main.java:3)

C и C ++

И C, и C ++ не имеют встроенной поддержки для получения трассировки стека, но библиотеки, такие как glibc и boost, предоставляют эту функцию. В этих языках некоторые оптимизации компилятора могут мешать информации стека вызовов, которую можно восстановить во время выполнения. Например, встраивание может привести к отсутствию фреймов стека, оптимизация хвостовых вызовов может заменить один фрейм стека другим, а исключение указателя фрейма может помешать инструментам анализа стека вызовов правильно интерпретировать содержимое стека вызовов.

Например, backtrace()функция glibc возвращает вывод с программной функцией и адресом памяти.

./a.out() [0x40067f]
./a.out() [0x4006fe]
./a.out() [0x40070a]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7f7e60738f45]
./a.out() [0x400599]

Ржавчина

В Rust есть два типа ошибок. Функции, использующие макрос паники , «невозможно восстановить», и текущий поток станет отравленным из-за раскручивания стека. Функции, возвращающие a std::result::Result, «восстанавливаемы» и могут обрабатываться изящно. Однако исправимые ошибки не могут генерировать трассировку стека, поскольку они добавляются вручную, а не являются результатом ошибки времени выполнения.

По состоянию на июнь 2021 года в Rust появилась экспериментальная поддержка трассировки стека при неисправимых ошибках. Rust поддерживает печать в stderr при возникновении паники в потоке, но ее необходимо включить, задав RUST_BACKTRACE переменную окружения .

Когда эта функция включена, такая обратная трассировка выглядит примерно так, как показано ниже, с самым последним вызовом в первую очередь.

thread 'main' panicked at 'execute_to_panic', main.rs:3
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
   1: std::panicking::default_hook::{{closure}}
   2: std::panicking::default_hook
   3: std::panicking::rust_panic_with_hook
   4: std::panicking::begin_panic
   5: futures::task_impl::with
   6: futures::task_impl::park
...

Смотрите также

использованная литература