Windows System Software -- Consulting, Training, Development -- Unique Expertise, Guaranteed Results

Before Posting...

Please check out the Community Guidelines in the Announcements and Administration Category.

More Info on Driver Writing and Debugging


The free OSR Learning Library has more than 50 articles on a wide variety of topics about writing and debugging device drivers and Minifilters. From introductory level to advanced. All the articles have been recently reviewed and updated, and are written using the clear and definitive style you've come to expect from OSR over the years.


Check out The OSR Learning Library at: https://www.osr.com/osr-learning-library/


How does Windbg recover function arguments in x64 crash dumps? Or can it?

brad_Hbrad_H Member Posts: 189

Whenever I use windbg on a x64 kernel dump, and go through the local variables for each of the callstack functions, I see a lot of junk values.

My question is, is this because the arguments to functions are lost in x64 crash dumps (since most of them are passed in registers) ?
If not, then why I see so many junk values in Local variables of x64 crash dumps, and how can Windbg recover the function arguments passed in register in call stack functions?

Comments

  • Dejan_MaksimovicDejan_Maksimovic Member - All Emails Posts: 636
    via Email
    Local as in dv /v output, Locals window or kv callstack "parameters"?
    The first two are the same and won't be junk.
    Kv output won't show "parameters", because even if they are passed on the
    stack (more than 4 params needed), the local stack value may get reused or
    the value changed. This is useless for any parameter viewing.

    You can inspect the entire stack frame for values manually and guess more
    than you can be sure.

    Dejan.
  • Scott_Noone_(OSR)Scott_Noone_(OSR) Administrator Posts: 3,679

    Long answer:

    In the Windows ABI the first four arguments are passed in RCX, RDX, R8, and R9. There is an area on the stack called the Home Space* that can be used to "home" the arguments onto the stack. This is always the first 32 bytes on the stack after the return address*. The remaining arguments to the function are then passed on the stack after the Home Space.

    In the debug build of your code the compiler always homes the arguments onto the stack. This allows the debugger to always show you the arguments even if the registers get reused. You can see this behavior very clearly if you look at KeBugcheckEx, which is always compiled without optimizations:

    3: kd> u nt!KeBugCheckEx
    nt!KeBugCheckEx:
    fffff800`02a7ef00 mov     qword ptr [rsp+8],rcx
    fffff800`02a7ef05 mov     qword ptr [rsp+10h],rdx
    fffff800`02a7ef0a mov     qword ptr [rsp+18h],r8
    fffff800`02a7ef0f mov     qword ptr [rsp+20h],r9
    fffff800`02a7ef14 pushfq
    
    

    However, in the optimized build the function is free to use that Home Space however it wants. Some functions ignore it, some functions store non-volatile registers, some even home random arguments for some reason...For example:

    3: kd> u nt!IoGetDmaAdapter
    nt!IoGetDmaAdapter:
    fffff800`02ee3610 mov     qword ptr [rsp+10h],rbx
    fffff800`02ee3615 mov     qword ptr [rsp+18h],rbp
    fffff800`02ee361a mov     qword ptr [rsp+20h],rsi
    fffff800`02ee361f push    rdi
    
    

    The debugger is dumb very trusting so it just always assumes the arguments are in the Home Space. So, sometimes you get junk and sometimes you get the right values. This is also why the parameters are always garbage if you break at the start of the function but magically fix themselves once you single step.

    -scott

    • This is also called the Shadow Space or Spill Space...I like Home Space

    • Note that there's always 32-bytes of Home Space, even if the function doesn't take any arguments...

    -scott
    OSR

  • brad_Hbrad_H Member Posts: 189
    edited June 2023

    @Scott_Noone_(OSR) said:
    Long answer:

    In the Windows ABI the first four arguments are passed in RCX, RDX, R8, and R9. There is an area on the stack called the Home Space* that can be used to "home" the arguments onto the stack. This is always the first 32 bytes on the stack after the return address*. The remaining arguments to the function are then passed on the stack after the Home Space.

    In the debug build of your code the compiler always homes the arguments onto the stack. This allows the debugger to always show you the arguments even if the registers get reused. You can see this behavior very clearly if you look at KeBugcheckEx, which is always compiled without optimizations:

    3: kd> u nt!KeBugCheckEx
    nt!KeBugCheckEx:
    fffff800`02a7ef00 mov     qword ptr [rsp+8],rcx
    fffff800`02a7ef05 mov     qword ptr [rsp+10h],rdx
    fffff800`02a7ef0a mov     qword ptr [rsp+18h],r8
    fffff800`02a7ef0f mov     qword ptr [rsp+20h],r9
    fffff800`02a7ef14 pushfq
    
    

    However, in the optimized build the function is free to use that Home Space however it wants. Some functions ignore it, some functions store non-volatile registers, some even home random arguments for some reason...For example:

    3: kd> u nt!IoGetDmaAdapter
    nt!IoGetDmaAdapter:
    fffff800`02ee3610 mov     qword ptr [rsp+10h],rbx
    fffff800`02ee3615 mov     qword ptr [rsp+18h],rbp
    fffff800`02ee361a mov     qword ptr [rsp+20h],rsi
    fffff800`02ee361f push    rdi
    
    

    The debugger is dumb very trusting so it just always assumes the arguments are in the Home Space. So, sometimes you get junk and sometimes you get the right values. This is also why the parameters are always garbage if you break at the start of the function but magically fix themselves once you single step.

    -scott

    • This is also called the Shadow Space or Spill Space...I like Home Space

    • Note that there's always 32-bytes of Home Space, even if the function doesn't take any arguments...

    Wow, even after so many years of reading assembly I never knew this shadow space exists! Thanks for the detailed answer.

    So basically in the release builds, the arguments to the functions are junks (since I assume the compiler will never pass arguments to the shadow stack in release builds when optimization is on), correct?

    Also another question: Sometimes when I view the local variables on a crash dump, some of the local functions are straight up missing. So are these optimized out? if so, then does this mean that If i see a local (not function argument) variable in the local variables view of windbg, then it should always be correct (since in this case I at least know that it was not optimized out) ? And if it's a junk value, then it means that it was overwritten with a junk value which means a stack overflow?

  • Dejan_MaksimovicDejan_Maksimovic Member - All Emails Posts: 636
    via Email
    Where are you looking, exactly?
  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,837

    I never knew this shadow space exists!

    This is all part of the Windows x64 calling convention. Linux does it a little bit differently.

    ... the arguments to the functions are junks ... the compiler will never pass arguments to the shadow stack in release builds ...

    The first 4 arguments go in rcx, rdx, r8, and r9. They are not stored on the stack, in either release or debug builds, even though space is allocated for them. In the debug build, the function that RECEIVES the parameters tucks them away on the stack to help the debugger.

    ... some of the local functions are straight up missing ... then it should always be correct ...

    I assume you mean local variables. In general, you should not make any "should always be" assumptions with the debugger. It does the best that it can, but sometimes the information it needs is simply not present.

    Tim Roberts, [email protected]
    Software Wizard Emeritus

  • brad_Hbrad_H Member Posts: 189
    edited June 2023

    @Dejan_Maksimovic said:
    Where are you looking, exactly?

    Well, almost all of the crash dumps that I analyze contain a lot of junk values in Local view.
    My main goal is to understand whether these junk values mean an overflow happened, or it's because of loss of information (because of optimization, argument passing, etc).

  • brad_Hbrad_H Member Posts: 189
    edited June 2023

    @Tim_Roberts said:
    I assume you mean local variables. In general, you should not make any "should always be" assumptions with the debugger. It does the best that it can, but sometimes the information it needs is simply not present.

    I apologize, yes I meant local variables, not local functions.

    So is there anyway that I can be 100% sure whether or not a local variable having a junk value is because of optimization/registry argument passing, or because of some overflow/corruption?

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,837

    100% sure? No. Since the spill region happens BEFORE the function call, if there is a stack overflow, it will usually have trashed the return address as well. It would be a very specific corruption that hops over the return address.

    Tim Roberts, [email protected]
    Software Wizard Emeritus

  • Dejan_MaksimovicDejan_Maksimovic Member - All Emails Posts: 636
    via Email
    Locals, or " dv /v" command will not show random values if PDBs are present.
    Both will show "variable is not available" for optimized away variables.

    Unless corruption happened or Mosmatched PDBs, the values are very likely
    to be correct here.
    It is the STACK output (kv command or Stack window) that are not likely to
    show any correct values for arguments.
  • brad_Hbrad_H Member Posts: 189

    @Dejan_Maksimovic said:
    Locals, or " dv /v" command will not show random values if PDBs are present.
    Both will show "variable is not available" for optimized away variables.

    Unless corruption happened or Mosmatched PDBs, the values are very likely
    to be correct here.
    It is the STACK output (kv command or Stack window) that are not likely to
    show any correct values for arguments.

    But I have seen random values for some of the locals in the kernel crash dumps many many times, Specially in x64 crash dumps, and almost always they had nothing to do with the crash dump so I always assumed these random values/NULLs that happen in local variables in x64 is because of the fact that function arguments are lost since they are passed in registers (assuming its a release build)

    Also if there is a pdb mismatch, then the symbols wont even load to begin with.

  • Dejan_MaksimovicDejan_Maksimovic Member - All Emails Posts: 636
    via Email
    I asked 3 times now where you are looking and you repeat a reply that
    doesn't answer :)
Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. Sign in or register to get started.

Upcoming OSR Seminars
OSR has suspended in-person seminars due to the Covid-19 outbreak. But, don't miss your training! Attend via the internet instead!
Kernel Debugging 13-17 May 2024 Live, Online
Developing Minifilters 1-5 Apr 2024 Live, Online
Internals & Software Drivers 11-15 Mar 2024 Live, Online
Writing WDF Drivers 20-24 May 2024 Live, Online