X64 の TrapFrame は信用できない?

こんにちは、八木です。

今日はデバッガに表示されるちょっと気になるメッセージについて書きたいと思います。

 X64のメモリダンプをデバッガで開き、トラップフレームを表示させると、こんな注意書きが表示されます。

 9: kd> .trap 0xfffff880023ae150
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=fffff880023ae418 rbx=0000000000000000 rcx=fffffa807c446788
rdx=00000000c0000240 rsi=0000000000000000 rdi=0000000000000000
rip=0000000000000000 rsp=fffff880023ae2e8 rbp=0000000000000000
r8=0000000000000000 r9=0000000000000000 r10=fffffa80323d5b00
r11=0000000000000002 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei ng nz na po nc
00000000'00000000 ?? ???

 トラップフレームは、STOP エラーが発生したり割り込みが発生した場合など、その時点の CPU のレジスタの値をスタックメモリに保存したもので、メモリダンプの解析を行う際にはとても重要な情報ですが、メッセージが示すようにトラップフレームに保存されたレジスタ値の幾つかは現象発生時の値では無いのです(x64の場合)。

 

でも、いったいどのレジスタの値が正しくて、どの値が正しく無いのかについては、どこにも示されていません。 デバッガのヘルプファイルにも書かれていなかったと思います。

 

そこで、実際にやってみることにしました。

 

ライブのカーネルデバッグで x64 ターゲットに接続し、レジスタの値をデバッガで無理やり書き換えていきます

 

現在のレジスタの内容。

1: kd> r
rax=0000000000003001 rbx=fffff880009bf180 rcx=0000000000000001
rdx=0000000000000010 rsi=0000000000026160 rdi=0000000000001032
rip=fffff80001679f60 rsp=fffff880009d2ac8 rbp=0000000000000000
r8=fffffa80024db000 r9=000000000000000f r10=000000000168e325
r11=fffff880009d29b0 r12=fffff880009d2b00 r13=fffffa8002b92d00
r14=fffffa8002b32200 r15=0000000000000000
iopl=0 nv up ei pl nz na pe nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00000202
nt!DbgBreakPointWithStatus:
fffff800'01679f60 cc int 3

 

各レジスタに値を入れていきます。(後で判別しやすい値を選んで設定しておきます。)

1: kd> r rax=1111111111111111
1: kd> r rbx=2222222222222222
1: kd> r rcx=3333333333333333
1: kd> r rdx=4444444444444444
1: kd> r rsi=5555555555555555
1: kd> r rdi=6666666666666666
1: kd> r rip=7777777777777777
1: kd> r rsp=8888888888888888
1: kd> r rbp=9999999999999999
1: kd> r r8 =aaaaaaaaaaaaaaaa
1: kd> r r9 =bbbbbbbbbbbbbbbb
1: kd> r r10=cccccccccccccccc
1: kd> r r11=dddddddddddddddd
1: kd> r r12=eeeeeeeeeeeeeeee
1: kd> r r13=f0f0f0f0f0f0f0f0
1: kd> r r14=f1f1f1f1f1f1f1f1
1: kd> r r15=f2f2f2f2f2f2f2f2

 

書き換えた後のレジスタの内容:

1: kd> r
rax=1111111111111111 rbx=2222222222222222 rcx=3333333333333333
rdx=4444444444444444 rsi=5555555555555555 rdi=6666666666666666
rip=7777777777777777 rsp=8888888888888888 rbp=9999999999999999
r8=aaaaaaaaaaaaaaaa r9=bbbbbbbbbbbbbbbb r10=cccccccccccccccc
r11=dddddddddddddddd r12=eeeeeeeeeeeeeeee r13=f0f0f0f0f0f0f0f0
r14=f1f1f1f1f1f1f1f1 r15=f2f2f2f2f2f2f2f2
iopl=0 nv up ei pl nz na pe nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00000202
77777777'77777777 ?? ???

 

この状態から、デバッガをリリースすると、(命令ポインタ rip = 7777777777777777 が不正なアドレスなので) STOP エラーが発生します。

 

その後保存された memory.dmp をデバッガで開くと、下記の通りエラー発生前に設定したはずの値が幾つか無くなっています。(本来の値が残っているものを赤で示しています。)

 

0: kd> .trap 0xfffff880009d2900

NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=1111111111111111 rbx=0000000000000000 rcx=3333333333333333
rdx=4444444444444444 rsi=0000000000000000 rdi=0000000000000000
rip=fffff800016816ac rsp=fffff880009d2a98 rbp=9999999999999999
r8=aaaaaaaaaaaaaaaa r9=bbbbbbbbbbbbbbbb r10=cccccccccccccccc
r11=dddddddddddddddd r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up di ng nz na pe nc
nt!KiExceptionDispatch+0x22c:
fffff800'016816ac 48cf iretq

 

この結果から、

トラップフレームに保存されるのは、

rax, rcx, rdx, rbp, r8, r9, r10, r11

保存されないのは、

rbx, rsi, rdi, rip, r12, r13, r14, r15

ということのようです。

 

ここでお気づきの方もいらっしゃると思いますが、どうやらトラップフレームに保存されているレジスタの値は、関数呼び出し上の規約(Calling Conventions)の中で揮発的(Volatile)なレジスタとして定義されているもののようです。(rbp は nonvolatile ですが、ここでは特別のようです。)

 

実際にトラップフレームを作成するコードも見てみることにします。

 

デバッガから !idt と入力すると割り込みディスパッチテーブルが表示されます。

0: kd> !idt

Dumping IDT:

00: fffff800016b8c40 nt!KiDivideErrorFault
01: fffff800016b8d40 nt!KiDebugTrapOrFault
02: fffff800016b8f00 nt!KiNmiInterrupt Stack = 0xFFFFF80001465000

03: fffff800016b9280 nt!KiBreakpointTrap
04: fffff800016b9380 nt!KiOverflowTrap
05: fffff800016b9480 nt!KiBoundFault
06: fffff800016b9580 nt!KiInvalidOpcodeFault
07: fffff800016b97c0 nt!KiNpxNotAvailableFault
08: fffff800016b9880 nt!KiDoubleFaultAbort Stack = 0xFFFFF80001463000

09: fffff800016b9940 nt!KiNpxSegmentOverrunAbort
0a: fffff800016b9a00 nt!KiInvalidTssFault
0b: fffff800016b9ac0 nt!KiSegmentNotPresentFault
0c: fffff800016b9c00 nt!KiStackFault
0d: fffff800016b9d40 nt!KiGeneralProtectionFault
0e: fffff800016b9e80 nt!KiPageFault
10: fffff800016ba240 nt!KiFloatingErrorFault
11: fffff800016ba3c0 nt!KiAlignmentFault
12: fffff800016ba4c0 nt!KiMcheckAbort Stack = 0xFFFFF80001467000

 

ここで、ページフォールトをハンドルするルーチン nt!KiPageFault を見てみます。

0: kd> u nt!KiPageFault
nt!KiPageFault:
fffff800'016b9e80 55 push rbp
fffff800'016b9e81 4881ec58010000 sub rsp,158h
fffff800'016b9e88 488dac2480000000 lea rbp,[rsp+80h]
fffff800'016b9e90 c645ab01 mov byte ptr [rbp-55h],1
fffff800'016b9e94 488945b0        mov qword ptr [rbp-50h],rax
fffff800'016b9e98 48894db8        mov qword ptr [rbp-48h],rcx
fffff800'016b9e9c 488955c0 mov qword ptr [rbp-40h],rdx
fffff800'016b9ea0 4c8945c8        mov qword ptr [rbp-38h],r8
0: kd> u
nt!KiPageFault+0x24:
fffff800'016b9ea4 4c894dd0 mov qword ptr [rbp-30h],r9
fffff800'016b9ea8 4c8955d8 mov qword ptr [rbp-28h],r10
fffff800'016b9eac 4c895de0 mov qword ptr [rbp-20h],r11
fffff800'016b9eb0 f685f000000001 test byte ptr [rbp+0F0h],1
fffff800'016b9eb7 7474 je nt!KiPageFault+0xad (fffff800'016b9f2d)
fffff800'016b9eb9 0f01f8 swapgs
fffff800'016b9ebc 654c8b142588010000 mov r10,qword ptr gs:[188h]
fffff800'016b9ec5 6683bdf000000033 cmp word ptr [rbp+0F0h],33h

 

まさしく、揮発的(Volatile)なレジスタと rbp がスタックに保存されている様子が確認できました。

 

結論

x64 のトラップフレームで信用できるレジスタは

rax, rcx, rdx, rbp, r8, r9, r10, r11

です。