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.
Using information form
CONTEXTyou 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
CONTEXTstruct. You are interested in program counter member. SinceCONTEXTis platform-dependent, you have to figure it out for yourself. You do it already when you initialize, for exampleSTACKFRAME64.AddrPC.Offset = CONTEXT.Ripfor x64 Windows. Now we start stack walk and useSTACKFRAME64.AddrPC.Offset, filled byStaclkWalk64as our starting point.You need to translate it to Relative Virtual Address (RVA) using allocation base address:
RVA = STACKFRAME64.AddrPC.Offset - AllocationBase. You can getAllocationBaseusingVirtualQuery.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
.mapfile content:where
00002f40is closest smaller offset to00002F8Dand so on. Last three addresses refer to CRT/OS functions that callmain(_tmainCRTstartupetc) – we should ignore them…So, we can see that we are able to recover stack trace with help of
.mapfile. 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) ) .