読者です 読者をやめる 読者になる 読者になる

NetBSD の ld.elf_so を読む

http://partake.in/events/1bed8969-5bc9-4f02-bc19-2698cb5577a3

のため

xprintf.c

loader はいろんなものが初期化されてないとこでスタートするんで、 printf とか当然自前定義になるんで、あるだろうな、と思ったらやっぱりあった。まぁ glibc の loader も printf は自前だし。 write 使って改行とか別々に出力したりしてるけど、 atomic にやっておいた方がいいよなぁとか。バッファサイズは 256B ぽい。

void
xvprintf(const char *fmt, va_list ap)
{
	char buf[256];

	(void) write(2, buf, xvsnprintf(buf, sizeof(buf), fmt, ap));
}

atomic じゃなくて気になるのはこういうやつ

void
xwarn(const char *fmt, ...)
{
	int saved_errno = errno;
	va_list ap;

	va_start(ap, fmt);
	xvprintf(fmt, ap);
	va_end(ap);

	xprintf(": %s\n", xstrerror(saved_errno));
	errno = saved_errno;
}

まー現実的に問題になるのは dlopen とかの時だけだろうけど。

Makefile

で、その write の定義はどこかなーと探すも見つからない。 libc の一部だけリンクしてるのかと思ったのだけど、そういうことをやってるのは exit だけ。

.PATH.c: ${NETBSDSRCDIR}/lib/libc/stdlib
SRCS+=		exit.c

おっかしいなーと眺めてたら、なんのことはない libc.a を普通にリンクしてるぽかった。 BSD は libc と loader が結構切れてるって聞いたことあった気がしたんだけど、気のせいか。でもそれだとなんで exit.c 必要なんだろう…とか。

arch/i386/rtld_start.S

前半部分

.rtld_start:
	subl	$8,%esp			# make room of obj_main and exit proc
	pushl	%ebx			# save ps_strings

	call	1f
1:
	popl	%edx
	leal	_DYNAMIC-1b(%edx),%ecx	# &_DYNAMIC
	movl	%ecx,%ebx
	subl	_GLOBAL_OFFSET_TABLE_-1b(%edx),%ebx

	pushl	%ebx			# relocbase
	pushl	%ecx			# &_DYNAMIC
	call	_rtld_relocate_nonplt_self

PC 取ってきてそれのずれっぷりで自分がどれだけずれて配置されてるか調べてる。たぶん address randomization されてるから。 _rtld_relocate_nonplt_self は

void
_rtld_relocate_nonplt_self(Elf_Dyn *dynp, Elf_Addr relocbase)
{
	const Elf_Rel *rel = 0, *rellim;
	Elf_Addr relsz = 0;
	Elf_Addr *where;

	for (; dynp->d_tag != DT_NULL; dynp++) {
		switch (dynp->d_tag) {
		case DT_REL:
			rel = (const Elf_Rel *)(relocbase + dynp->d_un.d_ptr);
			break;
		case DT_RELSZ:
			relsz = dynp->d_un.d_val;
			break;
		}
	}
	if (rel == 0 || relsz == 0)
		return;
	rellim = (const Elf_Rel *)((const uint8_t *)rel + relsz);
	for (; rel < rellim; rel++) {
		where = (Elf_Addr *)(relocbase + rel->r_offset);
		*where += (Elf_Addr)relocbase;
	}
}

で、まぁ別にいいんだろうけど、 R_386_RELATIVE 決め打ちだよなコレ…コメントくらい書かないものなのかな。

後半戦

	leal	12(%esp),%eax		# &cleanup
	pushl	%ebx			# relocbase
	pushl	%eax			# sp
	call	_rtld			# _rtld(sp, relocbase)

	addl	$16,%esp		# pop args

	popl	%ebx			# %ebx = ps_strings
	popl	%edx			# %edx = cleanup
	popl	%ecx			# %ecx = obj_main
	jmp	*%eax

_rtld に .rtld_start に入った時の esp の値を渡して call 、返り値がメインプログラムの entry point なので、 jmp って流れ。

loader に入った時の stack ってのは、

finalizer?
???
argc
arg0
arg1
...
argN
NULL
env0
env1
...
envN
NULL
auxv0 key
auxv0 value
auxv1 key
auxv1 value
...
AT_NULL
NULL

みたいな感じで並んでいるはず。最初のふたつの要素は undefined らしい。 linux だと finalizer と envc とか入ってたような気がするけど、気のせいかも。

rtld.c

対応してる auxvec は

	/* Digest the auxiliary vector. */
	for (auxp = aux; auxp->a_type != AT_NULL; ++auxp) {
		switch (auxp->a_type) {
		case AT_BASE:
			pAUX_base = auxp;
			break;
		case AT_ENTRY:
			pAUX_entry = auxp;
			break;
		case AT_EXECFD:
			pAUX_execfd = auxp;
			break;
		case AT_PHDR:
			pAUX_phdr = auxp;
			break;
		case AT_PHENT:
			pAUX_phent = auxp;
			break;
		case AT_PHNUM:
			pAUX_phnum = auxp;
			break;
#ifdef AT_EUID
		case AT_EUID:
			pAUX_euid = auxp;
			break;
		case AT_RUID:
			pAUX_ruid = auxp;
			break;
		case AT_EGID:
			pAUX_egid = auxp;
			break;
		case AT_RGID:
			pAUX_rgid = auxp;
			break;
#endif
#ifdef AT_SUN_EXECNAME
		case AT_SUN_EXECNAME:
			execname = (const char *)(const void *)auxp->a_v;
			break;
#endif
		case AT_PAGESZ:
			pAUX_pagesz = auxp;
			break;
		}

など。 linux にくらべると少ない感じか。

dlopen とかもこのファイルに定義されてる。 dl_iterate_phdr とかって、 NetBSD にもあるんだな…

静的リンカを怒らせないための dlopen とかのダミー定義は lib/libc/dlfcn/dlfcn_elf.c にあるみたい。

あと .init 呼ぶのはローダの仕事か。 glibc はメインプログラムが呼ぶ感じで、その方がプログラム本体の方の libc の初期化が終わってて、いい気がするんだけど、どうなのかな。

lib/csu/arch/i386/crt0.S

EBX, ECX, EDX で loader はメインプログラムに情報を伝えるみたい。 glibc はスタックで、同じレイアウトを使いまわす感じだったと思うけど。

STRONG_ALIAS(_start,__start)
_ENTRY(__start)
	pushl	%ebx
	pushl	%ecx
	pushl	%edx
	call	___start

EBX に入ってる ps_strings ってのは NetBSD の拡張で、 kernel からメインプログラムまで伝わってく構造体ぽい。中身は

struct ps_strings {
	char	**ps_argvstr;	/* first of 0 or more argument strings */
	int	ps_nargvstr;	/* the number of argument strings */
	char	**ps_envstr;	/* first of 0 or more environment strings */
	int	ps_nenvstr;	/* the number of environment strings */
};
なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h