チラシの裏は意外と白くない

最近忘れっぽくなったので調べたことをチラシの裏に書きます

データフォルトと格闘した記録その1

背景・前提

LSIの検証プログラムで解決困難なエラーが発生したため、ある程度一般的な内容と思われるので記録と解析のモチベーションにするため記事にしてみた。

元々プログラムは動作していて、そこからある変更をしたらCPUが例外を吐いて止まるようになってしまった。

CPUはARM、言語はC。マルチコア環境だが、プログラムそのものはシングルコア用に設計している。

 

 

エラー出力とその内容

例外発生時のメッセージは以下。このエラーメッセージは例外発生時にSDKのユーザコードから出力されている。

ESR_EL3 = 0x0000000096000004
FAR_EL3 = 0x0400040004000400

 

まずは、このエラーが何を言っているのかを調べてみる。

 

ESR_EL3: Error Syndrome Register

下位32bitは0x9600_0004なので上記のドキュメントによると、

bits name value discription
[31:26] Exception Class 6'b10_0101 Data Abort taken without a change in Exception level. Used for MMU faults generated by data accesses, alignment faults other than those caused by Stack Pointer misalignment, and synchronous External aborts, including synchronous parity or ECC errors. Not used for debug related exceptions.
[25] Instruction Length 1'b1 32bit
[24:0] ISS 25'b0_0000_0000_0000_0000_0000_0100 ...

 [24:0]ISSエンコーディングは[31:26]Exception Classによって変わるらしく、今回のException ClassはData Abortなので、以下のエンコーディングの様だ。 ちなみにISSはInstruction Specific Syndromeの略称らしい。

 

bits name value discription
[24] Instruction syndrome valid 1'b1 [23:14] is invalid
[23:14] ... 10'b00_0000_0000 reserved
[13] VNCR 1'b0 reserved?
[12:11] Synchronous Error Type 2'b0 reserved?
[10] FAR not Valid 1'b0 reserved
[9] External Abort 1'b0 not external
[8] Chache Maintenance 1'b0 Neither Cache Maintenance Instruction nor Address Translation Instruction
[7] S1PTW 1'b0 ...
[6] Write not Read 1'b0 Read?
[5:0] Data Fault Status Code 6'b00_0100 Translation fault, level 0

 今回、[24]ISVが0なので、[23:14]はReserved扱い。[13]VNCRはARMv8.4-NVを使っていない限り、[12:11]SETはRAS Extentionを使っていない限り、どちらもReserved扱いになるようだ。多分、今回の環境でも無視してOK…だと思う。[10]FnVは[5:0]DFSCが6'b01_0000の時に意味を持つので、今回はReserved。[6]WnRが0'b0なのでReadアクセスが問題。DFSC[5:0]の”Translation fault, level 0”に着目しようと思う。

 

ESR_EL3の詳細については、以下のページを参考にした。

 

developer.arm.com

 

FAR_EL3: Fault Address Register

ESR_EL3[31:26]ECの内容で意味合いが変わるが、今回のEC=6'b10_0101 Data Abortの場合、例外発生要因となったアクセス命令がアクセスしようとした仮想アドレスを指すとのこと。今回、仮想アドレスの0x0400_0400_0400_0400番地にアクセスしようとして例外が引き起こされている…ということになるのだろうか?そもそもこの値はアドレスというより普通にデータに見える。

 

FAR_EL3の詳細は、以下のページから。

 

developer.arm.com

 

結局のところ

仮想アドレス0x0400_0400_0400_0400にReadアクセスしようとしたが、MMUで変換エラーが引き起こされているよ、ってことになるのだろう。

 

問題の命令の特定

運よく、良い解析ツールを使える環境にあるので、各種トレースから問題の関数と命令の特定を行った。

関数トレース結果

vprint的な関数からcurr_el_spx_sync_defaultを経て、エラー出力関数に飛んでいる。また、例外発生前にも何度かvprint的な関数はコールされているので、この関数が常に悪さしているわけではない。 

 

レジスタトレース結果

一回目のトライアルからvprint的な関数からcurr_el_spx_sync_defaultに飛ぶ瞬間のPCが0x2afcであることがわかった。0x2afcのディスアセンブリは、

2afc: 39400361 ldrb w1, [x27]

ここで汎用レジスタ名がw1になっているが、これはx1に32bitアクセスをするよという意味らしい。まだ64bitアーキテクチャに慣れない。で、命令の意味はx27番地のデータを8bit持ってきて、32bit拡張してからx1にロードせよ。

vprintf的な関数を無事に抜けるときと例外で落ちるときで関連するコアレジスタをみると、以下の差分があった。

register value(NG case) value(OK case)
PC 0x2afc 0x2afc
w1(x1) 0xffff_ffd0 0xffff_ffd0
x27 0x0400_0400_0400_0400 0x9_d110

たしかに、これではx27が指す0x0400_0400_0400_0400番地にReadに行ってしまう。

 

では、x27にこの値を書いてる不届きな命令は何かというと、その直前だった。

2af8: f940001b ldr x27, [x0]

この命令が実行されるとx27に0x0400_0400_0400_0400がロードされて、次のサイクルで例外で落ちる、という動きの様だ。

 

この命令を実行時の状況は、以下の内容であった。

register value(NG case) value(OK case)
PC 0x2af8 0x2af8
x0 0x821_c268 0x821_c728
x27 0xa12b1 0x9_d059

 

結局のところ、

  1. 0x2fa8の命令でx27レジスタに0x821_c268番地のデータ(0x0400_0400_0400_0400)がloadされて、
  2. 0x2facの命令でx1レジスタに0x0400_0400_0400_0400番地のデータをloadしようとアクセスに行って落ちる

ということになる。

少しプログラムを変更したりしたところ、どうも直接データアボートを引き起こす命令が0x2afcであることは同じなのだが、呼び出し元が変わるケースがある。タイミングに依存した何かがあるらしい。別のトライアルではx0=0x821_c478だったりした。いずれにせよ、この0x821_c268とか0x821_c478あたりが何なのかを確認する必要がある。まあ、スタック破壊なんだろうなと予想。

 

つづく。