Stack Trace: Difference between revisions

122 bytes removed ,  29 days ago
m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(7 intermediate revisions by 6 users not shown)
Line 6:
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 findingusing thea addresssmall ofassembly anfunction objector ininline aassembly, knownor locationby onusing __builtin_frame_address(0) if you use the stackGCC compiler.
On some platform (e.g. x86), the compiler does not necessary save the EBP on the stack. For example, for gcc, use the -fno-omit-frame-pointer to make sure that the EBP is saved. Note that omission of the frame pointer merely causes functions to be missed from the backtrace.
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:
 
<sourcesyntaxhighlight lang="cpp">
/* Assume, as is often the case, that EBP is the first thing pushed. If not, we are in trouble. */
struct stackframe {
struct stackframe* ebp;
uint32_t eip;
};
void Debug::TraceStackTrace(unsigned int MaxFrames)
{
struct stackframe *stk;
// Stack contains:
asm ("movl %%ebp,%0" : "=r"(stk) ::);
// Second function argument
// First function argument (MaxFrames)
// Return address in calling function
// EBP of calling function (pointed to by current EBP)
unsigned int * ebp = &MaxFrames - 2;
Trace("Stack trace:\n");
for(unsigned int frame = 0; stk && frame < MaxFrames; ++frame)
{
unsigned int eip = ebp[1];
if(eip == 0)
// No caller on stack
break;
// Unwind to previous stack frame
ebpTrace(" = reinterpret_cast<unsigned0x{0:16} int * \n", stk->(ebp[0]eip);
unsigned int * argumentsstk = &stk->ebp[2];
Trace(" 0x{0:16} \n", eip);
}
}
</syntaxhighlight>
</source>
 
Note that the above code requires a NULL return address, and GDB backtracing requiresrequire 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:
 
<sourcesyntaxhighlight lang="asm">
mov $stack_end, %esp ; Initialize %esp
...
xor %ebp, %ebp ; Set %ebp to NULL
push %ebpcall kmain ; PushAccording ato NULLcalling returnconvention, addresskmain will save %ebp (=NULL) to the stack
</syntaxhighlight>
jmp kmain ; According to calling convention, kmain will save %ebp (=NULL) to the stack
</source>
 
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 returnbase addresspointer 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.
 
<sourcesyntaxhighlight lang="asm">
; Walks backwards through the call stack and builds a list of return addresses.
; Args:
Line 63 ⟶ 58:
push ebp
mov ebp, esp
sub esp, break; 8
mov [ebp - 4], edi
mov [ebp - 8], ebx
; Set up local registers.
xor eax, eax ; EAX = return value (number of stack frames found).
mov ebx, [esp + 08] ; EBX = old EBP.
mov edi, [esp + 816] ; Destination array pointer in EDI.
mov ecx, [esp + 1220] ; Maximum array size in ECX.
.walk:
; Walk backwards through EBP linked list, storing return addresses in EDI array.
test edxebx, edxebx
jz .done
mov edx, [ebx + 4] ; EDX = previous stack frame's IP.
test edx, edx
jz .done ; Leave if it is 0.
mov ebx, [ebx + 0] ; EBX = previous stack frame's BP.
mov [edi], edx ; Copy IP.
Line 84 ⟶ 80:
mov edi, [ebp - 4]
mov ebx, [ebp - 8]
leave
mov esp, ebp
pop ebp
ret
</syntaxhighlight>
</source>
 
=== Resolving Function Names ===
Line 98 ⟶ 93:
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[http://www.nobletech.co.uk/Products/PenPot/Design/Kernel/Debug/FnNameLookup.aspx] 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.
 
[[Category:Debugging]]
[[Category:X86]]