Stack trace

A stack trace is debugging output, normally sent to a log file or a debug window that shows the hierarchy of callers that called the current function. A stack trace is generated by analysing the stack to find each stack frame. The addresses of the functions called can be retrieved from each stack frame and the names of the functions displayed.

To implement a stack trace you have to know the structure of the stack frames, which is shown in the article Stack for X86 CDECL.

Walking the stack

Often a stack trace is written in assembly as it involves finding the current value of the EBP register. To write a stack trace routine in a higher-level language you will need to find EBP. This can be done by finding the address of an object in a known location on the stack. One thing we always know is in a fixed location on the stack is the first argument to the current function. Taking the address of this argument gives us the value of the EBP plus 8 bytes.

The following C++ code shows how (given the existence of a Trace function) this can be used to walk up the stack:

Note that the above code requires a NULL return address, and GDB backtracing requires a NULL %ebp, to know when to stop. Otherwise the traces will run off into garbage. To account for this, set up a NULL stack frame before you jump to your C entry point:

With this, stack tracers will see the NULL %ebp and/or return address as the end of the trace. You can use call in place of push/jmp, but your tracer will need to check for a NULL %ebp, rather than a NULL return address.

Assembly Implementation

This assembly implementation for x86 uses the same algorithm as above and similarly relies on a NULL return address to be placed near the top of the stack. Rather than print the contents of the stack, however, it builds an array of addresses which can then be resolved into symbol names.

Resolving Function Names

The next step in producing meaningful output from a stack trace is to find the names of the functions containing the addresses found during the stack walk.

When looking up the name of a function you have to find the biggest address smaller than the value you are looking for. This is because the return address saved by the call is the address of the jsr instruction, which will be offset within the function that is making the call.

To get the information you need to lookup function names you will need to either include debugging symbols in your kernel or load the map file created by your linker into the kernel’s memory space. The map file shows the addresses of each of your functions. While you could include the entire map file, it is often quite large and inefficiently stored. Not only this but often functions are not listed in the order that they appear in the object file and the format is not amenable to tracing through to find a specific function.

One possible solution is to pre-process your map file to produce a smaller, more useful format for it. You could do this in a way that allows either binary or linear searching for a particular address. See NobleTech’s Web site[1] for C# code showing a way of reading the map file produced by GNU ld and outputting a binary file that allows more efficient linear searching for symbols. A binary Win32 console application to do the pre-processing is also available for free from that site. C++ code that can be used in your kernel to look up function names in the pre-processed file format is also shown.

void Debug::TraceStackTrace(unsignedint MaxFrames){// Stack contains:// Second function argument// First function argument (MaxFrames)// Return address in calling function// EBP of calling function (pointed to by current EBP)unsignedint* ebp =&MaxFrames -2; Trace("Stack trace:\n");for(unsignedint frame =0; frame < MaxFrames;++frame){unsignedint eip = ebp[1];if(eip ==0)// No caller on stackbreak;// Unwind to previous stack frame ebp =reinterpret_cast<unsignedint*>(ebp[0]);unsignedint* arguments =&ebp[2]; Trace(" 0x{0:16} \n", eip);}}
mov $stack_end,%esp; Initialize %esp…xor%ebp,%ebp; Set %ebp to NULLpush%ebp; Push a NULL return address to the stackjmp kmain ; According to calling convention, kmain will save %ebp (=NULL) to the stack
; Walks backwards through the call stack and builds a list of return addresses.; Args:; * Array of 32-bit addresses.; * Maximum number of elements in array.; Return value: The number of addresses stored in the array.; Calling convention: cdecl[global walk_stack] walk_stack:; Create stack frame & save caller’s EDI and EBX.pushebpmovebp,espmov[ebp-4],edimov[ebp-8],ebx; Set up local registers.xoreax,eax; EAX = return value (number of stack frames found).movebx,[esp+0]; EBX = old EBP.movedi,[esp+8]; Destination array pointer in EDI.movecx,[esp+12]; Maximum array size in ECX..walk:; Walk backwards through EBP linked list, storing return addresses in EDI array.movedx,[ebx+4]; EDX = previous stack frame’s IP.testedx,edxjz.done ; Leave if it is 0.movebx,[ebx+0]; EBX = previous stack frame’s[edi],edx; Copy IP.addedi,4inceaxloop.walk .done:; Restore caller’s EDI and EBX, leave stack frame & return EAX.movedi,[ebp-4]movebx,[ebp-8]movesp,ebppopebpret

Класс StackTrace

stack trace:

0023:68DC5AEE xrGame.dll, CDialogHolder::IgnorePause()
0023:690C8D42 xrGame.dll, CDialogHolder::operator=()
0023:00054A7D XR_3DA.exe, CSheduler::ProcessStep()
0023:00054805 XR_3DA.exe, CSheduler::Update()
0023:69117B5B xrGame.dll, CDialogHolder::operator=()

Результаты (русский) 1:

Трассировка стека:0023:68DC5AEE xrGame.dll, CDialogHolder::IgnorePause()0023:690C8D42 xrGame.dll, CDialogHolder::operator=()0023:00054A7D XR_3DA.exe, CSheduler::ProcessStep()0023:00054805 XR_3DA.exe, CSheduler::Update()0023:69117B5B xrGame.dll, CDialogHolder::operator=()

переводится, пожалуйста, подождите..

Результаты (русский) 2:

трассировки стека: 0023: 68DC5AEE xrGame.dll, CDialogHolder :: IgnorePause () 0023: 690C8D42 xrGame.dll, CDialogHolder :: оператор = () 0023: 00054A7D XR_3DA.exe, CSheduler :: ProcessStep () 0023: 00054805 XR_3DA.exe, CSheduler :: Update () 0023: 69117B5B xrGame.dll, CDialogHolder :: оператор = ()

переводится, пожалуйста, подождите..

Результаты (русский) 3:

просмотр стека

0023:68dc5aee xrgame.dll, cdialogholder: ignorepause()
0023:690c8d42 xrgame.dll, cdialogholder: оператор = ()
0023:00054a7d xr_3da.exe, csheduler: processstep()
0023:00054805 xr_3da.exe, csheduler: update()
0023:69117b5b xrgame.dll, cdialogholder: оператор = ().

переводится, пожалуйста, подождите..

Класс Stack


public class Stack
extends Vector
implements Cloneable, Collection, List, Serializable

Stack — подкласс класса Vector, который реализует простой механизм типа «последний вошел — первый вышел» (LIFO). В дополнение к стандартным методам своего родительского класса, Stack предлагает наличие описанных ниже методов.


           Создаёт пустой стек.




           Служит для проверки стека на наличие элементов — он возвращает true, если стек пуст.



           Возвращает верхний элемент, не удаляя его из стека.



           Извлекает верхний элемент удаляя его из стека.

Stack Trace Explorer window


push(Object item)

           Помещает элемент в вершину стека.


search(Object o)

           Метод ищет заданный элемент в стеке, возвращая количество операций pop, которые требуются для того чтобы перевести искомый элемент в вершину стека. Если заданный элемент в стеке отсутствует, этот метод возвращает -1.

Пример использования:

Ниже приведен пример программы, которая создает стек, заносит в него несколько объектов типа Integer, а затем извлекает их.

import java.util.Stack; import java.util.EmptyStackException; class StackExample { static void showpush(Stack st, int a) { st.push(new Integer(a)); System.out.println(«push(» + a + «)»); System.out.println(«stack: » + st); } static void showpop(Stack st) { System.out.print(«pop -> «); Integer a = (Integer) st.pop(); System.out.println(a); System.out.println(«stack: » + st); } public static void main(String args[]) { Stack st = new Stack(); System.out.println(«stack: » + st); showpush(st, 42); showpush(st, 66); showpush(st, 99); showpop(st); showpop(st); showpop(st); try { showpop(st); } catch (EmptyStackException e) { System.out.println(«empty stack»); } } }

Ниже приведен результат, полученный при запуске этой программы. Обратите внимание на то, что обработчик исключений реагирует на попытку извлечь данные из пустого стека.

Благодаря этому мы можем аккуратно обрабатывать ошибки такого рода.

Элементы трассировки стека


1 Введение
2 Пример работы стека
3 Исключительная ситуация: ссылка имеет значение null
4 Исключительная ситуация генерируется во время инициализации экземпляра
5 Ссылки


Стандартная библиотека Java имеет механизм для отображения трассировки стека, использующий метод . Этот метод используется для создания дампа контекста программы для не перехваченных исключительных ситуаций. Информация о трассировке выводится в поток или в указанный или .

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

Пример работы стека

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

Что такое трассировка стека, и как я могу использовать ее для отладки ошибок моего приложения?

Рассмотрим пример, чтобы понять, как это работает:

Исключительная ситуация: ссылка имеет значение null

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

Сначала программа отображает типовую трассировку стека, которая выглядит примерно так:

Затем отображается пользовательскую трассировку стека — с названием исключительной ситуации и последовательностью элементов

Exception: java.lang.NullPointerException filename = line number = 5 class name = A method name = f is native method = false filename = line number = 61 class name = TraceDemo1 method name = main is native method = false

Обратите внимание, что имя файла, имя класса и имя метода первого ссылается на исключительную ситуацию. Исключительная ситуация происходит в строке 5 программы внутри метода.

Исключительная ситуация генерируется во время инициализации экземпляра

Вторая часть примера показывает, что происходит, когда исключительная ситуация генерируется во время инициализации экземпляра. В этом примере программа создает объект C. При создании экземпляра объекта C делается попытка получить длину строки . Однако, поскольку никогда явно не инициализировалась, ссылка на нее равна . Вот результат работы этой части программы:

Exception: java.lang.NullPointerException filename = line number = 15 class name = C method name = <init> is native method = false filename = line number = 83 class name = TraceDemo1 method name = main is native method = false

Точка входа в исключительную ситуацию расположена в строке 15 программы в классе C в методе с именем , являющимся специальным именем для методов инициализации экземпляров в виртуальной машине Java.

Вы можете использовать способность реализовывать форматированные отчеты об исключительных ситуациях и собственные форматы журналов. Примером пользовательского формата ведения журнала является формат, ограничивающий трассировку стека до разумной глубины. Например, если есть 500 фреймов стека, вы, возможно, захотите сохранить первые десять и последние десять. Информация является частью сериализованного представления экземпляров класса , поэтому она доступна для десериализованных объектов.


Дополнительная информация об элементах трассировки стека находится в описании класса на странице

Также обратитесь к разделу 10.12 «Потоки и исключительные ситуации» в учебнике «Язык программирования Java(tm), третье издание» Arnold, Gosling и Holmes

Теги: стек

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *