899 lines
22 KiB
C
899 lines
22 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1991 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
stktrace.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This module implements routines to snapshot a set of stack back traces
|
|||
|
in a data base. Useful for heap allocators to track allocation requests
|
|||
|
cheaply.
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Steve Wood (stevewo) 29-Jan-1992
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
17-May-1999 (silviuc) : added RtlWalkFrameChain that replaces the
|
|||
|
unsafe RtlCaptureStackBackTrace.
|
|||
|
|
|||
|
29-Jul-2000 (silviuc) : added RtlCaptureStackContext.
|
|||
|
|
|||
|
6-Nov-2000 (silviuc): IA64 runtime stack traces.
|
|||
|
|
|||
|
18-Feb-2001 (silviuc) : moved all x86 specific code into i386 directory.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
#include <ntos.h>
|
|||
|
#include <ntrtl.h>
|
|||
|
#include "ntrtlp.h"
|
|||
|
#include <nturtl.h>
|
|||
|
#include <zwapi.h>
|
|||
|
#include <stktrace.h>
|
|||
|
#include <heap.h>
|
|||
|
#include <heappriv.h>
|
|||
|
|
|||
|
//
|
|||
|
// Forward declarations.
|
|||
|
//
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
RtlpCaptureStackLimits (
|
|||
|
ULONG_PTR HintAddress,
|
|||
|
PULONG_PTR StartStack,
|
|||
|
PULONG_PTR EndStack);
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
RtlpStkIsPointerInDllRange (
|
|||
|
ULONG_PTR Value
|
|||
|
);
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
RtlpStkIsPointerInNtdllRange (
|
|||
|
ULONG_PTR Value
|
|||
|
);
|
|||
|
|
|||
|
VOID
|
|||
|
RtlpCaptureContext (
|
|||
|
OUT PCONTEXT ContextRecord
|
|||
|
);
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
NtdllOkayToLockRoutine(
|
|||
|
IN PVOID Lock
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
/////////////////////////////////////////////////////////////////////
|
|||
|
//////////////////////////////////////// Runtime stack trace database
|
|||
|
/////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
//
|
|||
|
// The following section implements a trace database used to store
|
|||
|
// stack traces captured with RtlCaptureStackBackTrace(). The database
|
|||
|
// is implemented as a hash table and does not allow deletions. It is
|
|||
|
// sensitive to "garbage" in the sense that spurios garbage (partially
|
|||
|
// correct stacks) will hash in different buckets and will tend to fill
|
|||
|
// the whole table. This is a problem only on x86 if "fuzzy" stack traces
|
|||
|
// are used. The typical function used to log the trace is
|
|||
|
// RtlLogStackBackTrace. One of the worst limitations of this package
|
|||
|
// is that traces are refered using a ushort index which means we cannot
|
|||
|
// ever store more than 65535 traces (remember we never delete traces).
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Global per process stack trace database.
|
|||
|
//
|
|||
|
|
|||
|
PSTACK_TRACE_DATABASE RtlpStackTraceDataBase;
|
|||
|
|
|||
|
PRTL_STACK_TRACE_ENTRY
|
|||
|
RtlpExtendStackTraceDataBase(
|
|||
|
IN PRTL_STACK_TRACE_ENTRY InitialValue,
|
|||
|
IN SIZE_T Size
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
RtlInitStackTraceDataBaseEx(
|
|||
|
IN PVOID CommitBase,
|
|||
|
IN SIZE_T CommitSize,
|
|||
|
IN SIZE_T ReserveSize,
|
|||
|
IN PRTL_INITIALIZE_LOCK_ROUTINE InitializeLockRoutine,
|
|||
|
IN PRTL_ACQUIRE_LOCK_ROUTINE AcquireLockRoutine,
|
|||
|
IN PRTL_RELEASE_LOCK_ROUTINE ReleaseLockRoutine,
|
|||
|
IN PRTL_OKAY_TO_LOCK_ROUTINE OkayToLockRoutine
|
|||
|
);
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
RtlInitStackTraceDataBaseEx(
|
|||
|
IN PVOID CommitBase,
|
|||
|
IN SIZE_T CommitSize,
|
|||
|
IN SIZE_T ReserveSize,
|
|||
|
IN PRTL_INITIALIZE_LOCK_ROUTINE InitializeLockRoutine,
|
|||
|
IN PRTL_ACQUIRE_LOCK_ROUTINE AcquireLockRoutine,
|
|||
|
IN PRTL_RELEASE_LOCK_ROUTINE ReleaseLockRoutine,
|
|||
|
IN PRTL_OKAY_TO_LOCK_ROUTINE OkayToLockRoutine
|
|||
|
)
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
PSTACK_TRACE_DATABASE DataBase;
|
|||
|
extern BOOLEAN RtlpFuzzyStackTracesEnabled;
|
|||
|
|
|||
|
//
|
|||
|
// On x86 where runtime stack tracing algorithms are unreliable
|
|||
|
// if we have a big enough trace database then we can enable fuzzy
|
|||
|
// stack traces that do not hash very well and have the potential
|
|||
|
// to fill out the trace database.
|
|||
|
//
|
|||
|
|
|||
|
#if defined(_X86_) && !defined(NTOS_KERNEL_RUNTIME)
|
|||
|
if (ReserveSize >= 16 * RTL_MEG) {
|
|||
|
RtlpFuzzyStackTracesEnabled = TRUE;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
DataBase = (PSTACK_TRACE_DATABASE)CommitBase;
|
|||
|
if (CommitSize == 0) {
|
|||
|
CommitSize = PAGE_SIZE;
|
|||
|
Status = ZwAllocateVirtualMemory( NtCurrentProcess(),
|
|||
|
(PVOID *)&CommitBase,
|
|||
|
0,
|
|||
|
&CommitSize,
|
|||
|
MEM_COMMIT,
|
|||
|
PAGE_READWRITE
|
|||
|
);
|
|||
|
if (!NT_SUCCESS( Status )) {
|
|||
|
KdPrint(( "RTL: Unable to commit space to extend stack trace data base - Status = %lx\n",
|
|||
|
Status
|
|||
|
));
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
DataBase->PreCommitted = FALSE;
|
|||
|
}
|
|||
|
else
|
|||
|
if (CommitSize == ReserveSize) {
|
|||
|
RtlZeroMemory( DataBase, sizeof( *DataBase ) );
|
|||
|
DataBase->PreCommitted = TRUE;
|
|||
|
}
|
|||
|
else {
|
|||
|
return STATUS_INVALID_PARAMETER;
|
|||
|
}
|
|||
|
|
|||
|
DataBase->CommitBase = CommitBase;
|
|||
|
DataBase->NumberOfBuckets = 137;
|
|||
|
DataBase->NextFreeLowerMemory = (PCHAR)
|
|||
|
(&DataBase->Buckets[ DataBase->NumberOfBuckets ]);
|
|||
|
DataBase->NextFreeUpperMemory = (PCHAR)CommitBase + ReserveSize;
|
|||
|
|
|||
|
if(!DataBase->PreCommitted) {
|
|||
|
DataBase->CurrentLowerCommitLimit = (PCHAR)CommitBase + CommitSize;
|
|||
|
DataBase->CurrentUpperCommitLimit = (PCHAR)CommitBase + ReserveSize;
|
|||
|
}
|
|||
|
else {
|
|||
|
RtlZeroMemory( &DataBase->Buckets[ 0 ],
|
|||
|
DataBase->NumberOfBuckets * sizeof( DataBase->Buckets[ 0 ] )
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
DataBase->EntryIndexArray = (PRTL_STACK_TRACE_ENTRY *)DataBase->NextFreeUpperMemory;
|
|||
|
|
|||
|
DataBase->AcquireLockRoutine = AcquireLockRoutine;
|
|||
|
DataBase->ReleaseLockRoutine = ReleaseLockRoutine;
|
|||
|
DataBase->OkayToLockRoutine = OkayToLockRoutine;
|
|||
|
|
|||
|
Status = (InitializeLockRoutine)( &DataBase->Lock.CriticalSection );
|
|||
|
if (!NT_SUCCESS( Status )) {
|
|||
|
KdPrint(( "RTL: Unable to initialize stack trace data base CriticalSection, Status = %lx\n",
|
|||
|
Status
|
|||
|
));
|
|||
|
return( Status );
|
|||
|
}
|
|||
|
|
|||
|
RtlpStackTraceDataBase = DataBase;
|
|||
|
return( STATUS_SUCCESS );
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
RtlInitializeStackTraceDataBase(
|
|||
|
IN PVOID CommitBase,
|
|||
|
IN SIZE_T CommitSize,
|
|||
|
IN SIZE_T ReserveSize
|
|||
|
)
|
|||
|
{
|
|||
|
#ifdef NTOS_KERNEL_RUNTIME
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
ExOkayToLockRoutine(
|
|||
|
IN PVOID Lock
|
|||
|
);
|
|||
|
|
|||
|
return RtlInitStackTraceDataBaseEx(
|
|||
|
CommitBase,
|
|||
|
CommitSize,
|
|||
|
ReserveSize,
|
|||
|
ExInitializeResourceLite,
|
|||
|
(PRTL_RELEASE_LOCK_ROUTINE)ExAcquireResourceExclusiveLite,
|
|||
|
(PRTL_RELEASE_LOCK_ROUTINE)ExReleaseResourceLite,
|
|||
|
ExOkayToLockRoutine
|
|||
|
);
|
|||
|
#else // #ifdef NTOS_KERNEL_RUNTIME
|
|||
|
|
|||
|
return RtlInitStackTraceDataBaseEx(
|
|||
|
CommitBase,
|
|||
|
CommitSize,
|
|||
|
ReserveSize,
|
|||
|
RtlInitializeCriticalSection,
|
|||
|
RtlEnterCriticalSection,
|
|||
|
RtlLeaveCriticalSection,
|
|||
|
NtdllOkayToLockRoutine
|
|||
|
);
|
|||
|
#endif // #ifdef NTOS_KERNEL_RUNTIME
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
PSTACK_TRACE_DATABASE
|
|||
|
RtlpAcquireStackTraceDataBase( VOID )
|
|||
|
{
|
|||
|
if (RtlpStackTraceDataBase != NULL) {
|
|||
|
if (RtlpStackTraceDataBase->DumpInProgress ||
|
|||
|
!(RtlpStackTraceDataBase->OkayToLockRoutine)( &RtlpStackTraceDataBase->Lock.CriticalSection )
|
|||
|
) {
|
|||
|
return( NULL );
|
|||
|
}
|
|||
|
|
|||
|
(RtlpStackTraceDataBase->AcquireLockRoutine)( &RtlpStackTraceDataBase->Lock.CriticalSection );
|
|||
|
}
|
|||
|
|
|||
|
return( RtlpStackTraceDataBase );
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
RtlpReleaseStackTraceDataBase( VOID )
|
|||
|
{
|
|||
|
(RtlpStackTraceDataBase->ReleaseLockRoutine)( &RtlpStackTraceDataBase->Lock.CriticalSection );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
PRTL_STACK_TRACE_ENTRY
|
|||
|
RtlpExtendStackTraceDataBase(
|
|||
|
IN PRTL_STACK_TRACE_ENTRY InitialValue,
|
|||
|
IN SIZE_T Size
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine extends the stack trace database in order to accomodate
|
|||
|
the new stack trace that has to be saved.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
InitialValue - stack trace to be saved.
|
|||
|
|
|||
|
Size - size of the stack trace in bytes. Note that this is not the
|
|||
|
depth of the trace but rather `Depth * sizeof(PVOID)'.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
The address of the just saved stack trace or null in case we have hit
|
|||
|
the maximum size of the database or we get commit errors.
|
|||
|
|
|||
|
Environment:
|
|||
|
|
|||
|
User mode.
|
|||
|
|
|||
|
Note. In order to make all this code work in kernel mode we have to
|
|||
|
rewrite this function that relies on VirtualAlloc.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
PRTL_STACK_TRACE_ENTRY p, *pp;
|
|||
|
SIZE_T CommitSize;
|
|||
|
PSTACK_TRACE_DATABASE DataBase;
|
|||
|
|
|||
|
DataBase = RtlpStackTraceDataBase;
|
|||
|
|
|||
|
//
|
|||
|
// We will try to find space for one stack trace entry in the
|
|||
|
// upper part of the database.
|
|||
|
//
|
|||
|
|
|||
|
pp = (PRTL_STACK_TRACE_ENTRY *)DataBase->NextFreeUpperMemory;
|
|||
|
|
|||
|
if ((! DataBase->PreCommitted) &&
|
|||
|
((PCHAR)(pp - 1) < (PCHAR)DataBase->CurrentUpperCommitLimit)) {
|
|||
|
|
|||
|
//
|
|||
|
// No more committed space in the upper part of the database.
|
|||
|
// We need to extend it downwards.
|
|||
|
//
|
|||
|
|
|||
|
DataBase->CurrentUpperCommitLimit =
|
|||
|
(PVOID)((PCHAR)DataBase->CurrentUpperCommitLimit - PAGE_SIZE);
|
|||
|
|
|||
|
if (DataBase->CurrentUpperCommitLimit < DataBase->CurrentLowerCommitLimit) {
|
|||
|
|
|||
|
//
|
|||
|
// No more space at all. We have got over the lower part of the db.
|
|||
|
// We failed therefore increase back the UpperCommitLimit pointer.
|
|||
|
//
|
|||
|
|
|||
|
DataBase->CurrentUpperCommitLimit =
|
|||
|
(PVOID)((PCHAR)DataBase->CurrentUpperCommitLimit + PAGE_SIZE);
|
|||
|
|
|||
|
return( NULL );
|
|||
|
}
|
|||
|
|
|||
|
CommitSize = PAGE_SIZE;
|
|||
|
Status = ZwAllocateVirtualMemory(
|
|||
|
NtCurrentProcess(),
|
|||
|
(PVOID *)&DataBase->CurrentUpperCommitLimit,
|
|||
|
0,
|
|||
|
&CommitSize,
|
|||
|
MEM_COMMIT,
|
|||
|
PAGE_READWRITE
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS( Status )) {
|
|||
|
|
|||
|
//
|
|||
|
// We tried to increase the upper part of the db by one page.
|
|||
|
// We failed therefore increase back the UpperCommitLimit pointer
|
|||
|
//
|
|||
|
|
|||
|
DataBase->CurrentUpperCommitLimit =
|
|||
|
(PVOID)((PCHAR)DataBase->CurrentUpperCommitLimit + PAGE_SIZE);
|
|||
|
|
|||
|
KdPrint(( "RTL: Unable to commit space to extend stack trace data base - Status = %lx\n",
|
|||
|
Status
|
|||
|
));
|
|||
|
return( NULL );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// We managed to make sure we have usable space in the upper part
|
|||
|
// therefore we take out one stack trace entry address.
|
|||
|
//
|
|||
|
|
|||
|
DataBase->NextFreeUpperMemory -= sizeof( *pp );
|
|||
|
|
|||
|
//
|
|||
|
// Now we will try to find space in the lower part of the database for
|
|||
|
// for the eactual stack trace.
|
|||
|
//
|
|||
|
|
|||
|
p = (PRTL_STACK_TRACE_ENTRY)DataBase->NextFreeLowerMemory;
|
|||
|
|
|||
|
if ((! DataBase->PreCommitted) &&
|
|||
|
(((PCHAR)p + Size) > (PCHAR)DataBase->CurrentLowerCommitLimit)) {
|
|||
|
|
|||
|
//
|
|||
|
// We need to extend the lower part.
|
|||
|
//
|
|||
|
|
|||
|
if (DataBase->CurrentLowerCommitLimit >= DataBase->CurrentUpperCommitLimit) {
|
|||
|
|
|||
|
//
|
|||
|
// We have hit the maximum size of the database.
|
|||
|
//
|
|||
|
|
|||
|
return( NULL );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Extend the lower part of the database by one page.
|
|||
|
//
|
|||
|
|
|||
|
CommitSize = Size;
|
|||
|
Status = ZwAllocateVirtualMemory(
|
|||
|
NtCurrentProcess(),
|
|||
|
(PVOID *)&DataBase->CurrentLowerCommitLimit,
|
|||
|
0,
|
|||
|
&CommitSize,
|
|||
|
MEM_COMMIT,
|
|||
|
PAGE_READWRITE
|
|||
|
);
|
|||
|
|
|||
|
if (! NT_SUCCESS( Status )) {
|
|||
|
KdPrint(( "RTL: Unable to commit space to extend stack trace data base - Status = %lx\n",
|
|||
|
Status
|
|||
|
));
|
|||
|
return( NULL );
|
|||
|
}
|
|||
|
|
|||
|
DataBase->CurrentLowerCommitLimit =
|
|||
|
(PCHAR)DataBase->CurrentLowerCommitLimit + CommitSize;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Take out the space for the stack trace.
|
|||
|
//
|
|||
|
|
|||
|
DataBase->NextFreeLowerMemory += Size;
|
|||
|
|
|||
|
//
|
|||
|
// Deal with a precommitted database case. If the lower and upper
|
|||
|
// pointers have crossed each other then rollback and return failure.
|
|||
|
//
|
|||
|
|
|||
|
if (DataBase->PreCommitted &&
|
|||
|
DataBase->NextFreeLowerMemory >= DataBase->NextFreeUpperMemory) {
|
|||
|
|
|||
|
DataBase->NextFreeUpperMemory += sizeof( *pp );
|
|||
|
DataBase->NextFreeLowerMemory -= Size;
|
|||
|
return( NULL );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Save the stack trace in the database
|
|||
|
//
|
|||
|
|
|||
|
RtlMoveMemory( p, InitialValue, Size );
|
|||
|
p->HashChain = NULL;
|
|||
|
p->TraceCount = 0;
|
|||
|
p->Index = (USHORT)(++DataBase->NumberOfEntriesAdded);
|
|||
|
|
|||
|
//
|
|||
|
// Save the address of the new stack trace entry in the
|
|||
|
// upper part of the databse.
|
|||
|
//
|
|||
|
|
|||
|
*--pp = p;
|
|||
|
|
|||
|
//
|
|||
|
// Return address of the saved stack trace entry.
|
|||
|
//
|
|||
|
|
|||
|
return( p );
|
|||
|
}
|
|||
|
|
|||
|
USHORT
|
|||
|
RtlLogStackBackTrace(
|
|||
|
VOID
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine will capture the current stacktrace (skipping the
|
|||
|
present function) and will save it in the global (per process)
|
|||
|
stack trace database. It should be noted that we do not save
|
|||
|
duplicate traces.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Index of the stack trace saved. The index can be used by tools
|
|||
|
to access quickly the trace data. This is the reason at the end of
|
|||
|
the database we save downwards a list of pointers to trace entries.
|
|||
|
This index can be used to find this pointer in constant time.
|
|||
|
|
|||
|
A zero index will be returned for error conditions (e.g. stack
|
|||
|
trace database not initialized).
|
|||
|
|
|||
|
Environment:
|
|||
|
|
|||
|
User mode.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PSTACK_TRACE_DATABASE DataBase;
|
|||
|
RTL_STACK_TRACE_ENTRY StackTrace;
|
|||
|
PRTL_STACK_TRACE_ENTRY p, *pp;
|
|||
|
ULONG Hash, RequestedSize, DepthSize;
|
|||
|
|
|||
|
if (RtlpStackTraceDataBase == NULL) {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
Hash = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Capture stack trace. The try/except was useful
|
|||
|
// in the old days when the function did not validate
|
|||
|
// the stack frame chain. We keep it just ot be defensive.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
StackTrace.Depth = RtlCaptureStackBackTrace(
|
|||
|
1,
|
|||
|
MAX_STACK_DEPTH,
|
|||
|
StackTrace.BackTrace,
|
|||
|
&Hash
|
|||
|
);
|
|||
|
}
|
|||
|
except(EXCEPTION_EXECUTE_HANDLER) {
|
|||
|
StackTrace.Depth = 0;
|
|||
|
}
|
|||
|
|
|||
|
if (StackTrace.Depth == 0) {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Lock the global per-process stack trace database.
|
|||
|
//
|
|||
|
|
|||
|
DataBase = RtlpAcquireStackTraceDataBase();
|
|||
|
|
|||
|
if (DataBase == NULL) {
|
|||
|
return( 0 );
|
|||
|
}
|
|||
|
|
|||
|
DataBase->NumberOfEntriesLookedUp++;
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
//
|
|||
|
// We will try to find out if the trace has been saved in the past.
|
|||
|
// We find the right hash chain and then traverse it.
|
|||
|
//
|
|||
|
|
|||
|
DepthSize = StackTrace.Depth * sizeof( StackTrace.BackTrace[ 0 ] );
|
|||
|
pp = &DataBase->Buckets[ Hash % DataBase->NumberOfBuckets ];
|
|||
|
|
|||
|
while (p = *pp) {
|
|||
|
if (p->Depth == StackTrace.Depth &&
|
|||
|
RtlCompareMemory( &p->BackTrace[ 0 ],
|
|||
|
&StackTrace.BackTrace[ 0 ],
|
|||
|
DepthSize
|
|||
|
) == DepthSize
|
|||
|
) {
|
|||
|
break;
|
|||
|
}
|
|||
|
else {
|
|||
|
pp = &p->HashChain;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (p == NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// We did not find the stack trace. We will extend the database
|
|||
|
// and save the new trace.
|
|||
|
//
|
|||
|
|
|||
|
RequestedSize = FIELD_OFFSET( RTL_STACK_TRACE_ENTRY, BackTrace ) +
|
|||
|
DepthSize;
|
|||
|
|
|||
|
p = RtlpExtendStackTraceDataBase( &StackTrace, RequestedSize );
|
|||
|
|
|||
|
//
|
|||
|
// If we managed to stack the trace we need to link it as the last
|
|||
|
// element in the proper hash chain.
|
|||
|
//
|
|||
|
|
|||
|
if (p != NULL) {
|
|||
|
*pp = p;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
except(EXCEPTION_EXECUTE_HANDLER) {
|
|||
|
|
|||
|
//
|
|||
|
// We should never get here if the algorithm is correct.
|
|||
|
//
|
|||
|
|
|||
|
p = NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Release global trace db.
|
|||
|
//
|
|||
|
|
|||
|
RtlpReleaseStackTraceDataBase();
|
|||
|
|
|||
|
if (p != NULL) {
|
|||
|
p->TraceCount++;
|
|||
|
return( p->Index );
|
|||
|
}
|
|||
|
else {
|
|||
|
return( 0 );
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
PVOID
|
|||
|
RtlpGetStackTraceAddress (
|
|||
|
USHORT Index
|
|||
|
)
|
|||
|
{
|
|||
|
if (RtlpStackTraceDataBase == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
if (! (Index > 0 && Index <= RtlpStackTraceDataBase->NumberOfEntriesAdded)) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
return (PVOID)(RtlpStackTraceDataBase->EntryIndexArray[-Index]);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#if defined(NTOS_KERNEL_RUNTIME)
|
|||
|
|
|||
|
USHORT
|
|||
|
RtlLogUmodeStackBackTrace(
|
|||
|
VOID
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine will capture the user mode stacktrace and will save
|
|||
|
it in the global (per system) stack trace database.
|
|||
|
It should be noted that we do not save duplicate traces.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Index of the stack trace saved. The index can be used by tools
|
|||
|
to access quickly the trace data. This is the reason at the end of
|
|||
|
the database we save downwards a list of pointers to trace entries.
|
|||
|
This index can be used to find this pointer in constant time.
|
|||
|
|
|||
|
A zero index will be returned for error conditions (e.g. stack
|
|||
|
trace database not initialized).
|
|||
|
|
|||
|
Environment:
|
|||
|
|
|||
|
User mode.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PSTACK_TRACE_DATABASE DataBase;
|
|||
|
RTL_STACK_TRACE_ENTRY StackTrace;
|
|||
|
PRTL_STACK_TRACE_ENTRY p, *pp;
|
|||
|
ULONG Hash, RequestedSize, DepthSize;
|
|||
|
ULONG Index;
|
|||
|
|
|||
|
if (RtlpStackTraceDataBase == NULL) {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
Hash = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Avoid weird situations.
|
|||
|
//
|
|||
|
|
|||
|
if (KeGetCurrentIrql() > PASSIVE_LEVEL) {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Capture user mode stack trace and hash value.
|
|||
|
//
|
|||
|
|
|||
|
StackTrace.Depth = (USHORT) RtlWalkFrameChain(StackTrace.BackTrace,
|
|||
|
MAX_STACK_DEPTH,
|
|||
|
1);
|
|||
|
if (StackTrace.Depth == 0) {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
for (Index = 0; Index < StackTrace.Depth; Index += 1) {
|
|||
|
Hash += PtrToUlong (StackTrace.BackTrace[Index]);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Lock the global per-process stack trace database.
|
|||
|
//
|
|||
|
|
|||
|
DataBase = RtlpAcquireStackTraceDataBase();
|
|||
|
|
|||
|
if (DataBase == NULL) {
|
|||
|
return( 0 );
|
|||
|
}
|
|||
|
|
|||
|
DataBase->NumberOfEntriesLookedUp++;
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
//
|
|||
|
// We will try to find out if the trace has been saved in the past.
|
|||
|
// We find the right hash chain and then traverse it.
|
|||
|
//
|
|||
|
|
|||
|
DepthSize = StackTrace.Depth * sizeof( StackTrace.BackTrace[ 0 ] );
|
|||
|
pp = &DataBase->Buckets[ Hash % DataBase->NumberOfBuckets ];
|
|||
|
|
|||
|
while (p = *pp) {
|
|||
|
if (p->Depth == StackTrace.Depth &&
|
|||
|
RtlCompareMemory( &p->BackTrace[ 0 ],
|
|||
|
&StackTrace.BackTrace[ 0 ],
|
|||
|
DepthSize
|
|||
|
) == DepthSize
|
|||
|
) {
|
|||
|
break;
|
|||
|
}
|
|||
|
else {
|
|||
|
pp = &p->HashChain;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (p == NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// We did not find the stack trace. We will extend the database
|
|||
|
// and save the new trace.
|
|||
|
//
|
|||
|
|
|||
|
RequestedSize = FIELD_OFFSET( RTL_STACK_TRACE_ENTRY, BackTrace ) +
|
|||
|
DepthSize;
|
|||
|
|
|||
|
p = RtlpExtendStackTraceDataBase( &StackTrace, RequestedSize );
|
|||
|
|
|||
|
//
|
|||
|
// If we managed to stack the trace we need to link it as the last
|
|||
|
// element in the proper hash chain.
|
|||
|
//
|
|||
|
|
|||
|
if (p != NULL) {
|
|||
|
*pp = p;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
except(EXCEPTION_EXECUTE_HANDLER) {
|
|||
|
|
|||
|
//
|
|||
|
// We should never get here if the algorithm is correct.
|
|||
|
//
|
|||
|
|
|||
|
p = NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Release global trace db.
|
|||
|
//
|
|||
|
|
|||
|
RtlpReleaseStackTraceDataBase();
|
|||
|
|
|||
|
if (p != NULL) {
|
|||
|
p->TraceCount++;
|
|||
|
return( p->Index );
|
|||
|
}
|
|||
|
else {
|
|||
|
return( 0 );
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
#endif // #if defined(NTOS_KERNEL_RUNTIME)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
RtlpCaptureStackLimits (
|
|||
|
ULONG_PTR HintAddress,
|
|||
|
PULONG_PTR StartStack,
|
|||
|
PULONG_PTR EndStack)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine figures out what are the stack limits for the current thread.
|
|||
|
This is used in other routines that need to grovel the stack for various
|
|||
|
information (e.g. potential return addresses).
|
|||
|
|
|||
|
The function is especially tricky in K-mode where the information kept in
|
|||
|
the thread structure about stack limits is not always valid because the
|
|||
|
thread might execute a DPC routine and in this case we use a different stack
|
|||
|
with different limits.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
HintAddress - Address of a local variable or parameter of the caller of the
|
|||
|
function that should be the start of the stack.
|
|||
|
|
|||
|
StartStack - start address of the stack (lower value).
|
|||
|
|
|||
|
EndStack - end address of the stack (upper value).
|
|||
|
|
|||
|
Return value:
|
|||
|
|
|||
|
False if some weird condition is discovered, like an End lower than a Start.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#ifdef NTOS_KERNEL_RUNTIME
|
|||
|
|
|||
|
//
|
|||
|
// Avoid weird conditions. Doing this in an ISR is never a good idea.
|
|||
|
//
|
|||
|
|
|||
|
if (KeGetCurrentIrql() > DISPATCH_LEVEL) {
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
*StartStack = (ULONG_PTR)(KeGetCurrentThread()->StackLimit);
|
|||
|
*EndStack = (ULONG_PTR)(KeGetCurrentThread()->StackBase);
|
|||
|
|
|||
|
if (*StartStack <= HintAddress && HintAddress <= *EndStack) {
|
|||
|
|
|||
|
*StartStack = HintAddress;
|
|||
|
}
|
|||
|
else {
|
|||
|
|
|||
|
#if defined(_WIN64)
|
|||
|
|
|||
|
//
|
|||
|
// On Win64 we do not know yet where DPCs are executed.
|
|||
|
//
|
|||
|
|
|||
|
return FALSE;
|
|||
|
#else
|
|||
|
*EndStack = (ULONG_PTR)(KeGetPcr()->Prcb->DpcStack);
|
|||
|
#endif
|
|||
|
*StartStack = *EndStack - KERNEL_STACK_SIZE;
|
|||
|
|
|||
|
//
|
|||
|
// Check if this is within the DPC stack for the current
|
|||
|
// processor.
|
|||
|
//
|
|||
|
|
|||
|
if (*EndStack && *StartStack <= HintAddress && HintAddress <= *EndStack) {
|
|||
|
|
|||
|
*StartStack = HintAddress;
|
|||
|
}
|
|||
|
else {
|
|||
|
|
|||
|
//
|
|||
|
// This is not current thread's stack and is not the DPC stack
|
|||
|
// of the current processor. Basically we have no idea on what
|
|||
|
// stack we are running. We need to investigate this. On free
|
|||
|
// builds we try to make the best out of it by using only one
|
|||
|
// page for stack limits.
|
|||
|
//
|
|||
|
// SilviuC: I disabled the code below because it seems under certain
|
|||
|
// conditions drivers do indeed switch execution to a different stack.
|
|||
|
// This function will need to be improved to deal with this too.
|
|||
|
//
|
|||
|
#if 0
|
|||
|
DbgPrint ("RtlpCaptureStackLimits: mysterious stack (prcb @ %p) \n",
|
|||
|
KeGetPcr()->Prcb);
|
|||
|
|
|||
|
DbgBreakPoint ();
|
|||
|
#endif
|
|||
|
|
|||
|
*StartStack = HintAddress;
|
|||
|
|
|||
|
*EndStack = (*StartStack + PAGE_SIZE) & ~((ULONG_PTR)PAGE_SIZE - 1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#else
|
|||
|
|
|||
|
*StartStack = HintAddress;
|
|||
|
|
|||
|
*EndStack = (ULONG_PTR)(NtCurrentTeb()->NtTib.StackBase);
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|