libc 解析 take.3 (可変引数関数)-CELLプロセッサ
"PPE Serviced SPE C Library Functions" ...
"PPE Serviced SPE C Library Functions" の 3 回目、可変引数関数の場合 について解説します。
可変引数関数の場合
以下は fprintf() を例に説明します。まずは、生成されたソースコード (fprintf.S) です。
#include "spe_c99_ops.h" #include "spe_posix1_ops.h" #define OPCLASS SPE_C99_CLASS #define NAME fprintf #define PARMS 2 #define OPCODE SPE_C99_VFPRINTF #define RETVAL 1 #include "spu_syscall_va.S"
非可変引数関数の場合は spu_syscall.S をインクルードしていましたが、可 変引数関数の場合は spu_syscall_va.S をインクルードしています。
注意すべき点としては、非可変引数関数と同様に命令列をスタック上にセー ブして、このスタック上の命令が実行されること以外にも、
可変引数関数の場合、引数がどれだけあるかわからないので、引数の書いて ある可能性のあるレジスタ (つまり全ての揮発性レジスタ) を全てスタックにセー ブする必要があります。
レジスタをセーブする命令もスタック上で実行されますが、メモリ使用量を 削減するため?に命令自体の書き換えを行っています。
これらのレジスタは va_list 形式 (パラメータと呼び出し関数のスタックへ のアドレス) で管理。
というところでしょうか。では実際に逆アセ結果を見てみます。まず前半の レジスタをセーブする部分です。
00000000: 0: 40 fd 70 02 il $2,-1312 // save_regs_1: の命令列へのスタック相対値をロード 4: 24 ff c0 cf stqd $79,-16($1) // 内部使用するレジスタの値を先にセーブ 8: 24 ff 80 ce stqd $78,-32($1) // c: 24 ff 40 cd stqd $77,-48($1) // 10: 24 ff 00 cc stqd $76,-64($1) // 14: 33 80 0f cf lqr $79,90 // save_regs_1: の命令列を $79 にロード 18: 18 00 80 cc a $76,$1,$2 // save_regs_1: の命令列をおくアドレスをロード 1c: 33 80 10 ce lqr $78,a0 // save_regs_2: の命令列を $78 にロード 20: 42 00 09 02 ila $2,12 // 命令列 (save_regs_*) のループ回数をロード 24: 33 80 11 cd lqr $77,b0 // save_regs_3: の命令列を $77 にロード 28: 24 00 26 4f stqd $79,0($76) // 命令列 (save_regs_1) をスタックにセーブ 2c: 24 00 a6 4d stqd $77,32($76) // 命令列 (save_regs_3) をスタックにセーブ 30: 1c f0 00 cd ai $77,$1,-64 // レジスタをセーブするアドレスをロード 34: 00 40 00 00 sync 38: 35 20 26 4f bisl $79,$76 // スタック上の命令列にジャンプ 3c: 1c 04 26 cd ai $77,$77,16 ... 00000090 : 90: 24 00 66 4e stqd $78,16($76) // 命令列 (save_regs_2) をスタックにセーブ 94: 00 40 00 00 sync 98: 1c ff c1 02 ai $2,$2,-1 // ループカウンタをデクリメント 9c: 1c ff 27 4e ai $78,$78,-4 // 命令列 (save_regs_2) のレジスタ指定値を -4 000000a0 : a0: 24 ff e6 cb stqd $75,-16($77) // 4 つのレジスタをセーブ a4: 24 ff a6 ca stqd $74,-32($77) // レジスタ番号はループ毎に -4 される a8: 24 ff 66 c9 stqd $73,-48($77) // 1 回目: 75, 74, 73, 72 ac: 24 ff 26 c8 stqd $72,-64($77) // 2 回目: 71, 70, 69, 68 000000b0 : b0: 1c f0 26 cd ai $77,$77,-64 // レジスタをセーブするアドレスを設定 (-16*4) b4: 21 7f fb 82 brnz $2,90 // スタック上の save_regs_1 にジャンプ b8: 35 00 27 80 bi $79 // 3c: にジャンプ bc: 00 00 00 00 stop
ということで 38: のジャンプ実行時のスタックが以下だとすると、
| Stack | high memory
| Parms |
| |
|------------|
| Link Reg |
|------------|
| Back Chain | <---- input SP
|------------|
| Reg 79 |
|------------|
| Reg 78 |
|------------|
| Reg 77 |
|------------|
| Reg 76 |
|------------|
| |
|------------|
// ... //
|------------|
| |
|============|
|save_regs_3 |
|------------|
| |
|------------|
|save_regs_1 | <---- $76
`------------'
low memory
39: 時点のスタックがこうなります。
| Stack | high memory
| Parms |
| |
|------------|
| Link Reg |
|------------|
| Back Chain | <---- input SP
|------------|
| Reg 79 |
|------------|
| Reg 78 |
|------------|
| Reg 77 |
|------------|
| Reg 76 |
|------------|
| Reg 75 |
|------------|
// ... //
|------------|
| Reg 4 |
|------------|
| |
|------------|
| |
|------------|
| |
|============|
|save_regs_3 |
|------------|
|save_regs_2 |
|------------|
|save_regs_1 | <---- $76
`------------'
low memory
次に後半の引数セーブと stop を実行する部分です。
3c: 1c 04 26 cd ai $77,$77,16 // 2 つ以上引数がある場合のアドレス調整
// この場合は Reg 4 分インクリメント
40: 24 ff e6 81 stqd $1,-16($77) // 現在の sp (call_stack) をセーブ
44: 24 ff a6 cd stqd $77,-32($77) // パラメータへのアドレス (next_arg) をセーブ
48: 24 ff 66 84 stqd $4,-48($77) // 引数 2 (format) をセーブ
4c: 24 ff 26 83 stqd $3,-64($77) // 引数 1 (stream) をセーブ
50: 1c f0 26 cd ai $77,$77,-64 // 引数 1 のアドレスをロード
54: 41 11 80 03 ilhu $3,8960 // OPCODE(0x23) をロード
58: 43 ff ff 84 ila $4,3ffff // マスク値を生成
5c: 33 80 0c cf lqr $79,c0 // call_ppe_1: の命令列をロード
60: 3e c1 00 85 cwd $5,4($1) // マスク値を生成
64: 80 73 41 84 selb $3,$3,$77,$4 // [24:31] OPCODE, [0:17] 引数のアドレス
68: b9 f3 c1 85 shufb $79,$3,$79,$5 // $3 を命令列 2 word 目に挿入
6c: 24 00 26 4f stqd $79,0($76) // 命令列をスタックにセーブ
70: 00 40 00 00 sync
74: 35 20 26 02 bisl $2,$76 // スタック上の 命令列にジャンプ
78: 34 ff 00 83 lqd $3,-64($1) // 返り値を $3 にロード
7c: 3f e3 01 82 shlqbyi $2,$3,12
80: 23 80 00 02 stqr $2,0 // errno をセーブ
84: 35 00 00 00 bi $0 // 呼び出し関数に復帰
...
000000c0 :
c0: 00 00 21 00 stop // stop OPCLASS
c4: 00 00 00 00 stop // ここに OPCODE + 引数のアドレス が入る
c8: 35 00 01 00 bi $2 // 78: にジャンプ
cc: 00 20 00 00 lnop
よって、スタック上の stop OPCLASS 命令を発行して SPE が停止した時のス タックはこのようになります。
| Stack | high memory
| Parms |
| |
|------------|
| Link Reg |
|------------|
| Back Chain |<-----. <---- input SP
|------------| |
| Reg 79 | |
|------------| |
| Reg 78 | |
|------------| |
// ... // |
|------------| |
| Reg 6 | |
|------------| |
| Reg 5 |<--. |
|------------| | |
| call_stack |------'
|------------| |
| next_arg |---'
|------------|
| format (r4)|
|------------|
| stream (r3)| <---- start of parameters
|============|
| |
|------------|
| |
|------------|
| call_ppe_1 | <---- $76
`------------'
low memory


