I am working on a class which I would like to use to log the current Call Stack on computers with Windows Vista/7. (Very similar to “Walking the callstack” http://www.codeproject.com/Articles/11132/Walking-the-callstack).
First I used RtlCaptureContext to get the current context record then I used StackWalk64 to get the individual stack frames. Now, I realized that the Program counter in STACKFRAME64.AddrPC actually changes for a specific code line whenever I close my program and start it again. For some reason I thought that the PC-Address for a specific code line would stay the same as long as I don’t change the source code and recompile it again.
I need the PC-Address to use SymFromAddr and SymGetLineFromAddr64 to get information about the called function, code file and line number. Unfortunately that only works as long as the Program-Debug-Database (PDB-File) is around, but I am not allowed to provide that to the client.
My plan was to record the PC-Addresses of the call stack (whenever it is needed) and then send it from the client to me. So that I could use my PDB-Files to find out which functions were called but that of course only works if the PC-Addresses are unique identifiers. Since they change every time I start the program, I cannot use that approach.
Do you know a better way to read the call stack or to overcome the problem with the changing program counter?
I think one possible solution could be to always get the PC-Address of a known location and use that as a reference to determine only the offset between different PC-Addresses. That appears to work, but I am not sure if that is a valid method and will always work.
Thank you very much for your help! I will publish the final (encapsulated) solution in codeproject.com and IF YOU LIKE I will say that you helped me.
The stack itself is not enough, you need the loaded modules map so that then you can associate any address (random, true) with the module and locate the PDB symbol. But you're really reinventing the wheel, because there are at least two well supported out-of-the-box solutions to solve this problem:
the Windows specific DbgHlp minidump API:
MiniDumpWriteDump
. You app should not call this directly, but instead you should ship with a tiny .exe that all it does it take sa dump of a process (process ID given as argument) and your app, when encounters an error condition, should launch this .exe and then waitr for its completion. The reason is that the 'dumper' process will freeze the dumped process during the dump, so the process being dumped cannot be the same process taking the dump. This scheme is common with all apps that implement WER. Not to mention that the resulted dump is a true .mdmp that you can load in WinDbg (or in VisualStudio if that's your fancy).the cross platform open-source solution: Breakpad. Used by Chrome, Firefox, Picassa and other well known apps.
So, primarily, don't reinvent the wheel. As a side note, there are also services that do value-add to error reporting, like aggregation, notifications, tracking and automated client responses, like the aforementioned WER offered by Microsoft (your code must be digitally signed to qualify), airbreak.io, exceptioneer.com, bugcollect.com (this one is create by yours truly) and other, but afaik. only the WER works with native Windows apps.
Using information form
CONTEXT
you can find function section and offset in PE image. For example, you can use this info to get function name from .map file generated by linker.Get
CONTEXT
struct. You are interested in program counter member. SinceCONTEXT
is platform-dependent, you have to figure it out for yourself. You do it already when you initialize, for exampleSTACKFRAME64.AddrPC.Offset = CONTEXT.Rip
for x64 Windows. Now we start stack walk and useSTACKFRAME64.AddrPC.Offset
, filled byStaclkWalk64
as our starting point.You need to translate it to Relative Virtual Address (RVA) using allocation base address:
RVA = STACKFRAME64.AddrPC.Offset - AllocationBase
. You can getAllocationBase
usingVirtualQuery
.Once you have that, you need to find into which Section this RVA falls and subtract section start address from it to get SectionOffset:
SectionOffset = RVA - SectionBase = STACKFRAME64.AddrPC.Offset - AllocationBase - SectionBase
. In order to do that you need to access PE image header structure (IMAGE_DOS_HEADER, IMAGE_NT_HEADER, IMAGE_SECTION_HEADER) to get number of sections in PE and their start/end addresses. It's pretty straightforward.That's it. Now you have section number and offset in PE image. Function offset is the highest offset smaller than SectionOffset in .map file.
I can post code later, if you like.
EDIT: Code to print
function address
(we assume x64 generic CPU):Notice, our call stack is (inside out)
GenerateReport()->Run()->main()
. Program output (on my machine, path is absolute):Now, call stack in terms of addresses is (inside out)
00002F8D->000031EB->00003253->00007947->0001552D->0002B521
. Comparing first three offsets to.map
file content:where
00002f40
is closest smaller offset to00002F8D
and so on. Last three addresses refer to CRT/OS functions that callmain
(_tmainCRTstartup
etc) - we should ignore them...So, we can see that we are able to recover stack trace with help of
.map
file. In order to generate stack trace for thrown exception, all you have to do is to placeGenerateReport()
code into exception constructor (in fact, thisGenerateReport()
was taken from my custom exception class constructor code (some part of it it) ) .I would suggest looking at your Visual Studio project's settings: Linker->Advanced->Randomized Base Address for all your programs and dependent dlls (that you can rebuilt) and try again. That is the only one thing that comes to mind.
Hope that helps.
You need send the program's running memory mapping which tells your the base address library/program loaded from client to you.
Then you can calculate the symbol with the base address.