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 */ };