How to retrieve values of TTBR registers on ARM64 from my WinDbg extension?

I'm trying to retrieve the values of the TTBR registers on Aarch64 Windows system from my WinDbg extension. These registers:

TTBR0_EL1
TTBR1_EL1
TTBR0_EL2
TTBR0_EL3

They should contain the (physical) base addresses for the translation tables (MMU) for different exception levels.

I can get it from running the following assembly instruction:

mrs    x0,   ttbr0_el1     ; read TTBR0_EL1 into X0 register

But I need to do this from my WinDbg extension (assuming that I can have a crash dump target to work with.)

I am guessing that I need to call IDebugDataSpaces::ReadMsr, right. But what is the MSR address for those?

How is that going to work on a dump file?

1 Like

The same way as you can retrieve other registers from a dump.

Someone needs to save them for them to be in the dump. Windows doesn't save all MSRs, but it looks like TTBR0_EL1 and TTBR1_EL1 are saved in the KPRCB as part of the crash dump process:

1: kd> vertarget
Windows 10 Kernel Version 22621 MP (8 procs) Free ARM 64-bit (AArch64)
Product: WinNt, suite: TerminalServer SingleUserTS
Edition build lab: 22621.1.arm64fre.ni_release.220506-1250
Machine Name:
Kernel base = 0xfffff802`4e800000 PsLoadedModuleList = 0xfffff802`4f4daed0
Debug session time: Thu May 16 17:44:22.223 2024 (UTC - 4:00)
System Uptime: 0 days 0:00:45.159

1: kd> ??@$prcb->ProcessorState.ArchState
struct _KARM64_ARCH_STATE
...
   +0x028 Ttbr0_El1        : 0x00400000`80d45000
   +0x030 Ttbr1_El1        : 0x00400000`80d45800
...

I suspect this isn't generically exposed via ReadMsr (which seems to not work right on ARM even with a live target)

Yeah, thanks. I found it too. I guess in that case TTBR0_EL2 and TTBR0_EL3 are not accessible because the OS kernel runs at EL1, correct?

Correct.

Also, I think I figured out the how WinDbg's rdmsr command is supposed to work on ARM64. It's very strange and I'm going to try and file a bug for clarification. Also, note that this only works for live targets and not crash dumps.

Basically, when you do a rdmsr/wrmsr command the target machine dynamically builds a function to execute the instruction (c.f. KdpEncodeMsrAccess). From the ARM Architecture Reference manual Chapter D22 (AArch64 System Register Encoding), the target register for the MSR/MRS commands are encoded in bits op0, op1, CRn, CRm, and op2:

And then there's a giant table for all the registers:

These are the bits that you're specifying as the register value in the rdmsr/wrmsr commands. The way you encode them is non-intuitive though because you don't specify the Rt bits. I created this macro that I think gets you the right argument for rdmsr/wrmsr:

#define ARM64_WINDBG_SYSREG(op0, op1, crn, crm, op2) \
        ( ((op0) << 16) | \
          ((op1) << 12) | \
          ((crn) << 8) | \
          ((crm) << 4) | \
          ((op2) << 0) )

    DWORD ttbr0_el1 = ARM64_WINDBG_SYSREG(3, 0, 2, 0, 0);
== 0x00030200

0: kd> rdmsr 0x00030200
msr[30200] = 00400000`80d45000
0: kd> ??@$prcb->ProcessorState.ArchState.Ttbr0_El1
unsigned int64 0x00400000`80d45000

    DWORD sctlr_el1 = ARM64_WINDBG_SYSREG(3, 0, 1, 0, 0);
== 0x00030100
0: kd> rdmsr 0x00030100
msr[30100] = 00001000`30d0595d
0: kd> ??@$prcb->ProcessorState.ArchState.Sctlr_El1
unsigned int64 0x00001000`30d0595d

    DWORD mair_el1  = ARM64_WINDBG_SYSREG(3, 0, 10, 2, 0);
== 0x00030a20
0: kd> rdmsr 0x00030a20
msr[30a20] = 444400ff`444400ff
0: kd> ??@$prcb->ProcessorState.ArchState.Mair_El1
unsigned int64 0x444400ff`444400ff

Happy for correction if anyone else wants to grovel through KdpEncodeMsrAccess and update the macro...In the meantime I'll file a doc bug and see where that gets us.

I need to look how they do it with Ghidra. In the meantime, what documentation bug are you referring to?

The command that we need is:

mrs    x0,   #<msr_number>

I'm kinda curious now how they manage to execute it on the live target though.

The WinDbg documentation for rdmsr/wrmsr makes no mention of how these are supposed to work on ARM64, that's the doc bug I'm referring to...

Right. And the register number is encoded into the instruction itself...See the ARM documentation I referred to above.

The target system dynamically builds a subroutine to call. The value you provide to rdmsr/wrmsr becomes the bits that provide the register encoding.

Scott, unless you know someone at MSFT who is responsible for the documentation, it's a wasted effort. A lot of MSDN is woefully inadequate.

I'll check how they build a subroutine. Thanks for pointing it out. I'm really curious how they do it, because it's not just writing an mrs instruction, followed by ret, they also need to allocate a page of memory, and make it executable. And then also run that subroutine on the target. I didn't know it was possible from a WinDbg extension.

It's all much simpler than that...It all happens on the target side based on the command coming from the debugger. See KdpPrepareMsrAccessFunc for where all the magic happens.

Oh, I'm assuming via an IOCTL.

Nah, there's no IOCTLs going on when you're live debugging...It's all controlled by the KD protocol between the host and target.

Sorry, I meant IOCTL when you're trying to retrieve it from an extension. And you're probably referring to the internal kernel (debugging) module.

No, from an extension you would call IDebugDataSpaces::ReadMsr as you noted previously (which is all the rdmsr command is doing). The question really comes down to "what do you pass as the Msr argument" and that's what my macro above should get you.

@Scott_Noone_OSR, just wanted to say that I just checked, and your ARM64_WINDBG_SYSREG macro seems to be correct. Thanks for working it out.

1 Like