ftraceについて(スタック書き換え)

前回の続き。
prepare_ftrace_return()では「parent」を求める。
parentはmcount呼び出し先関数の戻りアドレスが格納されているスタックポインタのことである。
これを取得するのがftrace_get_parent_addr()である。

unsigned long ftrace_get_parent_addr(unsigned long self_addr,
				     unsigned long parent,
				     unsigned long parent_addr,
				     unsigned long fp)
{
	unsigned long sp, ip, ra;
	unsigned int code;
	int faulted;

	/* in module or kernel? */
	if (self_addr & 0x40000000) {
		/* module: move to the instruction "lui v1, HI_16BIT_OF_MCOUNT" */
		ip = self_addr - 20;
	} else {
		/* kernel: move to the instruction "move ra, at" */
		ip = self_addr - 12;
	}

	/* search the text until finding the non-store instruction or "s{d,w}
	 * ra, offset(sp)" instruction */
	do {
		ip -= 4;

		/* get the code at "ip": code = *(unsigned int *)ip; */
		safe_load_code(code, ip, faulted);

		if (unlikely(faulted))
			return 0;

		/* If we hit the non-store instruction before finding where the
		 * ra is stored, then this is a leaf function and it does not
		 * store the ra on the stack. */
		if ((code & S_R_SP) != S_R_SP)
			return parent_addr;

	} while (((code & S_RA_SP) != S_RA_SP));

	sp = fp + (code & OFFSET_MASK);

	/* ra = *(unsigned long *)sp; */
	safe_load_stack(ra, sp, faulted);
	if (unlikely(faulted))
		return 0;

	if (ra == parent)
		return sp;
	return 0;
}

mipsではレジスタに格納されている値をみただけでは関数呼び出しによってどのくらいスタックポインタが移動したかがわからない。よって、戻りアドレスがスタックのどこにつまれたのかもレジスタ値だけではわからないことになる。

その代わりにmipsでは関数呼び出し直後付近で、ベタなストア命令を使って戻りアドレスをスタックに格納している。
よって、戻りアドレス格納先のアドレスを知るためには、関数の先頭付近の命令をトレースし該当ストア命令を検索し、この命令を解析して戻りアドレスを知ることになる。具体的には

s{d,w} ra,offset(sp)

を実行している箇所を見つけ、offsetにあたる値の取得を行う。
これによって、sp(スタックポインタ)+ offsetが戻りアドレスの格納先アドレスであることがわかる。

あとは、prepare_ftrace_return()に戻り、safe_store_stack()で「mcount呼び出し先関数の戻りアドレス」を書き換え、元の戻りアドレスなどをftrace_push_return_trace()で保存しておくことになる。