UST原理:如果gflags標志中包含了UST標志,堆管理器會為當前進程分配一塊內存,這個內存區域就是UST數據庫(user-mode stack trace database),並建立一個STACK_TRACE_DATABASE
數據結構來管理這個數據庫,下面是從WRK上找到的數據結構。
typedef struct _STACK_TRACE_DATABASE {
union {
RTL_CRITICAL_SECTION CriticalSection;
ERESOURCE Resource;
PVOID Lock; // real lock (the other two kept for compatibility)
};
PVOID Reserved[3]; // fields no longer used but kept for compatibility
BOOLEAN PreCommitted; //數據庫提交標志
BOOLEAN DumpInProgress; //轉儲標志
PVOID CommitBase; //數據庫基地址
PVOID CurrentLowerCommitLimit;
PVOID CurrentUpperCommitLimit;
PCHAR NextFreeLowerMemory; //下一個空閑位置的低地址
PCHAR NextFreeUpperMemory; //下一個空閑位置的高地址
ULONG NumberOfEntriesLookedUp;
ULONG NumberOfEntriesAdded;
PRTL_STACK_TRACE_ENTRY *EntryIndexArray; // Indexed by [-1 .. -NumberOfEntriesAdded]
ULONG NumberOfBuckets; //下面Buckets數組的元素數
PRTL_STACK_TRACE_ENTRY Buckets [1]; //每一項是都是說明數據庫信息用的
} STACK_TRACE_DATABASE, *PSTACK_TRACE_DATABASE;
這個是PRTL_STACK_TRACE_ENTRY
的數據結構
typedef struct _RTL_STACK_TRACE_ENTRY {
struct _RTL_STACK_TRACE_ENTRY * HashChain;
ULONG TraceCount;
USHORT Index;
USHORT Depth;
PVOID BackTrace [MAX_STACK_DEPTH];
} RTL_STACK_TRACE_ENTRY, *PRTL_STACK_TRACE_ENTRY;
繼續……
看看初始化UST數據庫的過程
NTSTATUS
RtlInitializeStackTraceDataBase(
IN PVOID CommitBase, //提交基址
IN SIZE_T CommitSize, //
IN SIZE_T ReserveSize //
)
{
NTSTATUS Status;
PSTACK_TRACE_DATABASE DataBase; //聲明局部變量的STACK_TRACE_DATABASE結構
DataBase = (PSTACK_TRACE_DATABASE)CommitBase;
if (CommitSize == 0) { //如果提交大小=0,進入默認值處理分支
//
// Initially commit enough pages to accommodate the increased
// number of hash chains (for improved performance we switched from ~100
// to ~1000 in the hope that the hash chains will decrease ten-fold in
// length).
//
//提交的大小 = 一個默認值大小=1567
CommitSize = ROUND_TO_PAGES (NUMBER_OF_BUCKETS * sizeof (DataBase->Buckets[ 0 ]));
//在預期的CommitBase地址分配一塊內存,返回地址也在CommitBase
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;
}
//默認流程處理完,准備提交標志置為false
DataBase->PreCommitted = FALSE;
}
else if (CommitSize == ReserveSize) {//如果提交大小 = 保留大小
//僅僅初始化數據庫結構大小的內存,不留buckets的內存
RtlZeroMemory (DataBase, sizeof( *DataBase ));
//准備提交標志置為T
DataBase->PreCommitted = TRUE;
}
else {//error
return STATUS_INVALID_PARAMETER;
}
/*
置一些標志位:
CommitBase
NumberOfBuckets
NextFreeLowerMemory下一個自由內存地址下線:所有桶位之后地址
NextFreeUpperMemory下一個自由內存地址上線:提交基址+保留大小
*/
DataBase->CommitBase = CommitBase;
DataBase->NumberOfBuckets = NUMBER_OF_BUCKETS;
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;
//
// Initialize the database lock.
//
DataBase->Lock = &RtlpStackTraceDataBaseLock;
Status = INITIALIZE_DATABASE_LOCK (DataBase->Lock);
if (! NT_SUCCESS(Status)) {
KdPrint(("RTL: Unable to initialize stack trace database lock (status %X)\n", Status));
return Status;
}
//這里把初始化好的UST數據庫賦值給全局變量
RtlpStackTraceDataBase = DataBase;
return STATUS_SUCCESS;
}
建立了數據庫之后,當堆塊分配函數再被調用的時候,堆管理器將當前棧回溯信息記錄到UST數據庫中。
堆塊分配函數調用RtlLogStackBackTrace
記錄
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.
此函數跳過本函數捕獲棧回溯並保存到每個進程的全局UST中
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.
棧回溯的索引號。這個索引號可以被工具用來快速訪問到trace數據。
A zero index will be returned for error conditions (e.g. stack
trace database not initialized).
返回0為錯誤。
Environment:
User mode.
--*/
{
return RtlpLogStackBackTraceEx (1);
}
RtlpLogStackBackTraceEx
函數
USHORT
RtlpLogStackBackTraceEx(
ULONG FramesToSkip
)
/*++
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:
FramesToSkip - no of frames that are not interesting and
should be skipped.
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.
--*/
{
RTL_STACK_TRACE_ENTRY Trace;
USHORT TraceIndex;
NTSTATUS Status;
ULONG Hash;
PSTACK_TRACE_DATABASE DataBase;
//
// Check the context in which we are running.
//
DataBase = RtlpStackTraceDataBase; // 全局變量的數據庫指針
if (DataBase == NULL) {
return 0;
}
if (! OKAY_TO_LOCK_DATABASE (DataBase->Lock)) {
return 0;
}
//
// Capture stack trace.
//
// 4個參數
//顯然&Trace, &Hash是輸出參數
if (RtlpCaptureStackTraceForLogging (&Trace, &Hash, FramesToSkip + 1, FALSE) == FALSE) {
return 0;
}
//
// Add the trace if it is not already there.
// Return trace index.
//
//添加trace,如果沒有在UST中,看起來像是一個查找函數
TraceIndex = RtlpLogCapturedStackTrace (&Trace, Hash);
return TraceIndex;
}
現在要分析的是這兩個函數
RtlpCaptureStackTraceForLogging
RtlpLogCapturedStackTrace
先從第一個RtlpCaptureStackTraceForLogging
搞起
LOGICAL
RtlpCaptureStackTraceForLogging (
PRTL_STACK_TRACE_ENTRY Trace,//[out] 棧回溯數組+數組深度
PULONG Hash, //PRTL_STACK_TRACE_ENTRY整個結構的hash
ULONG FramesToSkip,
LOGICAL UserModeStackFromKernelMode
)
{
//這個參數傳進來的是1,跳過此分支
if (UserModeStackFromKernelMode == FALSE) {
//
// 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 to be defensive.
//
try {
Trace->Depth = RtlCaptureStackBackTrace (FramesToSkip + 1,
MAX_STACK_DEPTH,
Trace->BackTrace,
Hash);
}
except(EXCEPTION_EXECUTE_HANDLER) {
Trace->Depth = 0;
}
if (Trace->Depth == 0) {
return FALSE;
}
else {
return TRUE;
}
}
else {
ULONG Index;
//
// Avoid weird situations.
//
if (KeAreAllApcsDisabled () == TRUE) {
return FALSE;
}
//
// Capture user mode stack trace and hash value.
//
//關鍵函數:RtlWalkFrameChain
//Trace->BackTrace參數是棧回溯的函數返回地址的數組
Trace->Depth = (USHORT) RtlWalkFrameChain(Trace->BackTrace,
MAX_STACK_DEPTH,
1);
if (Trace->Depth == 0) {
return FALSE;
}
else {
*Hash = 0;
// 計算hash
for (Index = 0; Index < Trace->Depth; Index += 1) {
*Hash += PtrToUlong (Trace->BackTrace[Index]);
}
return TRUE;
}
}
}
RtlWalkFrameChain
函數
ULONG
RtlWalkFrameChain (
OUT PVOID *Callers,
IN ULONG Count,
IN ULONG Flags
)
/*++
Routine Description:
This function tries to walk the EBP chain and fill out a vector of
return addresses. It is possible that the function cannot fill the
requested number of callers. In this case the function will just return
with a smaller stack trace. In kernel mode the function should not take
any exceptions (page faults) because it can be called at all sorts of
irql levels.
嘗試遍歷EBP鏈填充返回地址向量。可能函數不能填充請求數量的調用者。在這個例子中這個函數將會
返回一個小的棧回溯。在內核模式函數因為有不同的IRLQ請求等級所以不會異常。
The `Flags' parameter is used for future extensions. A zero value will be
compatible with new stack walking algorithms.
flags參數用於未來擴展。傳遞0兼容新的棧遍歷算法。
A value of 1 for `Flags' means we are running in K-mode and we want to get
the user mode stack trace.
Return value:
在棧上被識別了的返回地址的數量。可以小於被請求的數量。
The number of identified return addresses on the stack. This can be less
then the Count requested.
--*/
{
ULONG_PTR Fp, NewFp, ReturnAddress;
ULONG Index;
ULONG_PTR StackEnd, StackStart;
BOOLEAN Result;
BOOLEAN InvalidFpValue;
//
// Get the current EBP pointer which is supposed to
// be the start of the EBP chain.
//
// 得到當前棧楨上的ebp,作為ebp鏈的開始
_asm mov Fp, EBP;
StackStart = Fp; //start_ebp
InvalidFpValue = FALSE;
// 上層函數 flag=1,不進入此分支
if (Flags == 0) {
if (! RtlpCaptureStackLimits (Fp, &StackStart, &StackEnd)) {
return 0;
}
}
try {
//
// If we need to get the user mode stack trace from kernel mode
// figure out the proper limits.
//
// 上層函數 flag=1
if (Flags == 1) {
PKTHREAD Thread = KeGetCurrentThread ();
PTEB Teb;
PKTRAP_FRAME TrapFrame;
ULONG_PTR Esp;
// 看了PKTRAP_FRAME的結構,保存了所有的寄存器,感覺是異常環境現場
TrapFrame = Thread->TrapFrame;
Teb = Thread->Teb;
//
// If this is a system thread, it has no Teb and no kernel mode
// stack, so check for it so we don't dereference NULL.
//
//如果是系統線程沒有TEB也有沒有內核模式棧
// If there is no trap frame (probably an APC), or it's attached,
// or the irql is greater than dispatch, this code can't log a
// stack.
//
//如果是這幾種情況,是不能記錄棧的,我們關心正常流程
if (Teb == NULL ||
IS_SYSTEM_ADDRESS((PVOID)TrapFrame) == FALSE ||
(PVOID)TrapFrame <= Thread->StackLimit ||
(PVOID)TrapFrame >= Thread->StackBase ||
KeIsAttachedProcess() ||
(KeGetCurrentIrql() >= DISPATCH_LEVEL)) {
return 0;
}
// 我理解StackStart是棧的上線,StackEnd是棧的下線,棧是從下線(高)向上線(低)增長
StackStart = (ULONG_PTR)(Teb->NtTib.StackLimit);
StackEnd = (ULONG_PTR)(Teb->NtTib.StackBase);
Fp = (ULONG_PTR)(TrapFrame->Ebp);
if (StackEnd <= StackStart) {
return 0;
}
// 探測棧是否可讀
ProbeForRead (StackStart, StackEnd - StackStart, sizeof (UCHAR));
}
// 遍歷所有的棧,上層參數:Count是棧的最大深度
for (Index = 0; Index < Count; Index += 1) {
// 一些check
if (Fp >= StackEnd ||
( (Index == 0)?
(Fp < StackStart):
(Fp <= StackStart) ) ||
StackEnd - Fp < sizeof(ULONG_PTR) * 2) {
break;
}
// 回溯到上一層棧
NewFp = *((PULONG_PTR)(Fp + 0));
ReturnAddress = *((PULONG_PTR)(Fp + sizeof(ULONG_PTR)));
//
// Figure out if the new frame pointer is ok. This validation
// should avoid all exceptions in kernel mode because we always
// read within the current thread's stack and the stack is
// guaranteed to be in memory (no page faults). It is also guaranteed
// that we do not take random exceptions in user mode because we always
// keep the frame pointer within stack limits.
//
if (! (Fp < NewFp && NewFp < StackEnd)) {
InvalidFpValue = TRUE;
}
//
// Figure out if the return address is ok. If return address
// is a stack address or <64k then something is wrong. There is
// no reason to return garbage to the caller therefore we stop.
//
if (StackStart < ReturnAddress && ReturnAddress < StackEnd) {
break;
}
if (Flags == 0 && IS_SYSTEM_ADDRESS((PVOID)ReturnAddress) == FALSE) {
break;
}
//
// Store new fp and return address and move on.
// If the new FP value is bogus but the return address
// looks ok then we still save the address.
//
// 保存返回地址到數組
Callers[Index] = (PVOID)ReturnAddress;
if (InvalidFpValue) {
Index += 1;
break;
}
else {
Fp = NewFp;
}
}
}
except (RtlpWalkFrameChainExceptionFilter (_exception_code(), _exception_info())) {
Index = 0;
}
//
// Return the number of return addresses identified on the stack.
//
// 返回遍歷到的索引
return Index;
}
接着分析第二個函數RtlpLogCapturedStackTrace
:
USHORT
RtlpLogCapturedStackTrace(
PRTL_STACK_TRACE_ENTRY Trace,//PRTL_STACK_TRACE_ENTRY
ULONG hash
)
{
PSTACK_TRACE_DATABASE DataBase;
PRTL_STACK_TRACE_ENTRY p, *pp;
ULONG RequestedSize, DepthSize;
USHORT ReturnValue;
//RtlpStackTraceDataBase用戶模式或系統系統每個進程全局的棧回溯數據庫
DataBase = RtlpStackTraceDataBase;
//
// Update statistics counters. Since they are used only for reference and do not
// control decisions we increment them without protection even if this means we may
// have numbers slightly out of sync.
//
DataBase->NumberOfEntriesLookedUp += 1;
//
// Lock the global per-process stack trace database.
//
if (RtlpAcquireStackTraceDataBase() == NULL) {
//
// Fail the log operation if we cannot acquire the lock.
// This can happen only if there is a dump in progress or we are in
// an invalid context (process shutdown (Umode) or DPC routine (Kmode).
//
return 0;
}
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.
//
//遍歷hash鏈,嘗試找到是否這個trace已經被保存過。
DepthSize = Trace->Depth * sizeof (Trace->BackTrace[0]);
// 當前hash%數組大小 --> hash表,從hash表中比對數組大小,元素值
pp = &DataBase->Buckets[ Hash % DataBase->NumberOfBuckets ];
while (p = *pp) {
if (p->Depth == Trace->Depth) {
if (RtlCompareMemory( &p->BackTrace[ 0 ], &Trace->BackTrace[ 0 ], DepthSize) == DepthSize) {
break;
}
}
pp = &p->HashChain;
}
// 沒有查詢到了相同的棧回溯記錄,添加之
if (p == NULL) {
//
// If we get here we did not find a similar trace in the database. We need
// to add it.
//
// We got the `*pp' value (address of last chain element) while the
// database lock was acquired shared so we need to take into consideration
// the case where another thread managed to acquire database exclusively
// and add a new trace at the end of the chain. Therefore if `*pp' is no longer
// null we continue to traverse the chain until we get to the end.
//
p = NULL;
if (*pp != NULL) {
//
// Somebody added some traces at the end of the chain while we
// were trying to convert the lock from shared to exclusive.
//
while (p = *pp) {
if (p->Depth == Trace->Depth) {
if (RtlCompareMemory( &p->BackTrace[ 0 ], &Trace->BackTrace[ 0 ], DepthSize) == DepthSize) {
break;
}
}
pp = &p->HashChain;
}
}
if (p == NULL) {
//
// Nobody added the trace and now `*pp' really points to the end
// of the chain either because we traversed the rest of the chain
// or it was at the end anyway.
//
RequestedSize = FIELD_OFFSET (RTL_STACK_TRACE_ENTRY, BackTrace) + DepthSize;
// 添加trace到數據庫
p = RtlpExtendStackTraceDataBase (Trace, RequestedSize);
if (p != NULL) {
//
// We added the trace no chain it as the last element.
//
*pp = p;
}
}
else {
//
// Some other thread managed to add the same trace to the database
// while we were trying to acquire the lock exclusive. `p' has the
// address to the stack trace entry.
//
}
}
}
except(EXCEPTION_EXECUTE_HANDLER) {
//
// We should never get here if the algorithm is correct.
//
p = NULL;
}
//
// Release locks and return. At this stage we may return zero (failure)
// if we did not manage to extend the database with a new trace (e.g. due to
// out of memory conditions).
//
// 查詢到了相同的棧回溯記錄
if (p != NULL) {
p->TraceCount += 1; //TraceCount+1
ReturnValue = p->Index;
}
else {
ReturnValue = 0;
}
RtlpReleaseStackTraceDataBase();
return ReturnValue;
}