Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Using RtlLookupFunctionEntry for profiling...

653 views
Skip to first unread message

Christian Kaiser

unread,
Oct 29, 2009, 9:48:40 AM10/29/09
to
Hi,

I wanted to implement a trivial profiling for my 64 bit application.
Reason is that sometimes the application "hangs" for several seconds
at a customer's site, and I would like to be able to get periodic
information about where the application's main thread currently is
(and get a small stack trace for information why the application is at
the current routine).

On profiling start, I do create a second thread which periodically
calls (here without error checks etc)

::SuspendThread(_hProfiledThread);
::GetThreadContext(_hProfiledThread,&CTX);
<and walk the stack using CTX, RtlLookupFunctionEntry and
RtlVirtualUnwind>
::ResumeThread(_hProfiledThread);

This works nicely until the profiled thread loads a library and is
suspended, because the profiler thread calls RtlLookupFunctionEntry(),
which tries to access the same synchronization object that the
LoadLibrary() call uses somewhere deep inside. For example:

Profiled thread (suspended):

ntdll.dll!RtlInsertInvertedFunctionTable() + 0x36f6e bytes (calling
RtlAcquireResourceExclusive)
ntdll.dll!LdrpMapDll() + 0x36e11 bytes
ntdll.dll!LdrpLoadDll() - 0x1a0ef bytes
ntdll.dll!LdrLoadDll() + 0x124 bytes
kernel32.dll!LoadLibraryExW() + 0x120 bytes
kernel32.dll!LoadLibraryA() + 0x86 bytes
...

Profiler thread (waiting for the FunctionTable synchronization
object):

ntdll.dll!NtWaitForSingleObject() + 0xa bytes
ntdll.dll!RtlAcquireResourceShared() - 0x1372c bytes
ntdll.dll!RtlLookupFunctionTable() + 0x4c413 bytes
ntdll.dll!RtlLookupFunctionEntry() + 0xab bytes
...

Is there a (more or less official) way to check whether that
synchonization object is "in use" in order to skip the profiling
"tick"?

Christian


rogero

unread,
Oct 30, 2009, 8:58:57 AM10/30/09
to
Christian Kaiser wrote:

> Is there a (more or less official) way to check whether that
> synchonization object is "in use" in order to skip the profiling
> "tick"?

Use windbg !locks command to get the name for the lock you're waiting
on.
It will likely be ntdll!LdrpLoaderLock which is a
_RTL_CRITICAL_SECTION.

Then change your profiler thread to either (a) check if the object is
locked
or (b) do a trylock on it

HTH,
Roger.

Christian Kaiser

unread,
Nov 4, 2009, 7:46:51 AM11/4/09
to
Thanks, but how do I get the address of LdrpLoaderLock? DEPENDS.EXE
does not show this export in "ntdll.dll".

Christian

"rogero" <roge...@gmail.com> wrote in message
news:7070e7cb-c74f-417b...@m16g2000yqc.googlegroups.com...

Christian Kaiser

unread,
Nov 5, 2009, 4:43:42 AM11/5/09
to
OK, found it. Heavily undocumented and subject to change without
notice etc..., but now I can detect whether the loader lock is active
or not.

For the record in the world wide web here it comes... ;-)


a) ASM file for WIN64 (to be compiled in VS using for example
"ml64.exe /Zi /c /nologo /Fo "$(OutDir)$(InputName).obj"
"$(InputPath)""):
PUBLIC __getPEB

.CODE

ALIGN 8

__getPEB PROC

mov rax, gs:[60h]

ret

__getPEB ENDP

END


b) declaration:

#if defined(WIN64)

extern "C"

{

void* __fastcall __getPEB();

}

#else

inline __declspec(naked) void* __getPEB()

{

__asm

{

mov eax, fs:[0x30]

ret

}

}

#endif

c) the final function:

bool clsLibInfo::IsLoaderLockActive()

{

#if defined(_M_AMD64)

RTL_CRITICAL_SECTION*
pLoaderLock(*(RTL_CRITICAL_SECTION**)(((BYTE*)__getPEB())+0x0110));

#else

#if defined(_M_IX86)

RTL_CRITICAL_SECTION*
pLoaderLock(*(RTL_CRITICAL_SECTION**)(((BYTE*)__getPEB())+0x00a0));

#else

#pragma error("unknown target")

#endif

#endif

if (!RtlTryEnterCriticalSection(pLoaderLock)) // from ntdll.dll

{

return(true);

}

RtlLeaveCriticalSection(pLoaderLock);

return(false);

}

OMG ;-)

Christian


"Christian Kaiser" <bc...@gmx.de> wrote in message
news:OiojjzUX...@TK2MSFTNGP04.phx.gbl...

Christian Kaiser

unread,
Nov 5, 2009, 4:53:48 AM11/5/09
to
Sorry, Outlook Express did kill formatting ;-(

I hope it's better now.

----

a) ASM file for WIN64 (to be compiled in VS using for example
"ml64.exe /Zi /c /nologo /Fo "$(OutDir)$(InputName).obj"
"$(InputPath)""):

PUBLIC __getPEB
.CODE
ALIGN 8
__getPEB PROC
mov rax, gs:[60h]
ret
__getPEB ENDP
END

b) declaration:

#if defined(_WIN64)


extern "C"
{
void* __fastcall __getPEB();
}
#else
inline __declspec(naked) void* __getPEB()
{
__asm
{
mov eax, fs:[0x30]
ret
}
}
#endif

c) the final function:

bool IsLoaderLockActive()
{
#if defined(_M_AMD64)
RTL_CRITICAL_SECTION*
pLoaderLock(*(RTL_CRITICAL_SECTION**)(((BYTE*)__getPEB())+0x0110));
#elif defined(_M_IX86)


RTL_CRITICAL_SECTION*
pLoaderLock(*(RTL_CRITICAL_SECTION**)(((BYTE*)__getPEB())+0x00a0));
#else
#pragma error("unknown target")
#endif

rogero

unread,
Nov 5, 2009, 7:03:14 AM11/5/09
to
Hello Christian,
your method ought to work -- I'd assumed you'd be loading the symbols
for ntdll and getting the address from those.
There may be reasons to prefer one way to the other but if you've got
a way that works I'd stick with it!
Roger.

Christian Kaiser

unread,
Nov 5, 2009, 7:54:24 AM11/5/09
to

Roger,

yes, the original code does load the NTDLL APIs dynamically, and (as
the lookup uses Windows API that uses the LoaderLock itself) they must
do that before calling the function ;-)

I'd prefer having direct "documented" access, but I did not find the
LdrpLoaderLock export in NTDLL, at least not using DEPENDS.EXE.
Getting the symbols would possibly mean to need DBGHELP et al on a
client's system, and the correct PDBs.

Christian

"rogero" <roge...@gmail.com> wrote in message

news:9baf50ca-da01-4463...@k17g2000yqb.googlegroups.com...

0 new messages