libcのスタートアップを読む

__libc_start_main とか、そのへんをたどってみよう

glibc

  • glibc != Linux
    • v2.27
    • とりあえず $ rg __libc_start_main
  • 構成
    • sysdeps/ アーキテクチャ依存部分
    • csu/ C StartUp
  • libc-start.c: LIBC_START_MAIN (=__libc_start_main)
    • mainに渡る引数のenvpはauxvecのことだった
    • __libc_init_secure()
    • __libc_init_first()
      • シンプルにmain()を呼ぶ
    • startupではなくてcleanupというものもあるみたい
  • init-first.c: __libc_init_first 共有ライブラリでは_init
    • .ctors/.dtorsセクションがなければ自分で呼び出している
  • elf-init.c: __libc_csu_fini
    • __fini_array_start配列内の関数をそれぞれ実行していく
  • elf-init.c: __libc_csu_init
    • __(pre)init_array_start配列内の関数を逐次実行していく
      • 配列の中身はリンカが決める
      • $ ld -verboseでリンカスクリプトを見てみると、以下のようにセクションが定義されている
      • code:ld
        • .preinit_array :
        • {
          • PROVIDE_HIDDEN (__preinit_array_start = .);
          • KEEP (*(.preinit_array))
          • PROVIDE_HIDDEN (__preinit_array_end = .);
        • }

バイナリを逆アセンブルしてみる

  • いつもは.textと.pltぐらいしか読んでないなあ
  • とりあえず適当にHello, World!してobjdump

  • __libc_csu_init
  • __libc_csu_fini
  • _fini→(i386, x86_64)/crti.S
    • スタックを拡張しておわり? (sub esp)
  • frame_dummy
  • __do_global_dtors_aux
  • register_tm_clones
  • deregister_tm_clones
    • Linuxにもglibcにも見つからない、調べてみるとgccと__TMC_END__というのが関係しているみたい (__TMC_END__は$ nmで確認すると一番最後にある)
  • _start →i386/start.S
    • アセンブラで直書きされている
    • edxに何か値が入っているみたい
    • GOTとかPLTも出てくる
    • PICなら__libc_csu_initなどのアドレスを相対的に指定して, そうでなければ普通に指定してスタックに積む
      • call 1f→’‘f’‘orwardにある’‘1’’: labelを呼び出す https://stackoverflow.com/a/5530331
    • __libc_start_mainを呼び出し
  • _init→(i386, x86_64)/crti.S
    • LOAD_PIC_REGでGOTの設定をしている
    • PREINIT_FUNCTION = gmon_start を呼んでいる

Makefileを読む

  • crt0 → “traditional” なバイナリに使う
  • crt1, crti(‘‘i’‘nitializer), crtn(fi’‘n’‘alizer←わからん)→ELFバイナリに使う
  • elf-init.cに_init, _finiのシンボルはリンカが作ってくれると書かれている
    • 実装はinit-first.cにある
  • crtbegin, crtendについての話はないが、glibc/Makeconfig内に $(CC) .. –print-file-name=crtbegin.o などとあるのでそこで生成されているはず

枝葉末節

  • sysdeps/cpu-features.c: init_cpu_features()
    • CPUにどのような機能があるか保存して、haswellやxeon_phiといった系統も見ている
    • CPUの機能(=CPUID命令)の0番を呼び出すとebx, ecx, edxに”GenuineIntel”, “AuthenticAMD”といった文字列が格納される
  • sysdeps/unix/sysv/linux/i386/init-first.c: i386依存の初期化コード
    • vDSO周りの設定
  • PIC: PIEのこと
  • rtld: 動的リンカ=「shared objectを読み込んで編集する機能」

NetBSDのlibc

  • src/lib/libc – ここにスタートアップの話はない – かと思いきゃ__libc_initの実装がある — threadとかの初期化をしている
  • src/lib/csuもある、C Startupとlibcが分離されているな… – arch/以下にはcrt.S, common/以下にはC言語のコードが → glibcではこれを分離してない – arch/以下 — crtbegin.S: .ctorsにリンカが配置する —- .init/.fini→call __do_global_ctors_aux/__do_global_dtors_aux —- crti.SではなくこっちでGOTを設定をしているように見える — crtend.S: .dtorsにリンカが配置する — からっぽの.dtors, .eh_frame, .jcr — crti.S: initialization —- glibcのcsu/にあるのと同じ —- _init: push ebpしてespをそこに動かしてるだけ, gmonがない — crtn.S: finalization —- からっぽ — crt0.S: セットアップ —- _ _startが定義してあって、_ _ _startを呼ぶだけ – common/以下 — crt0-common.c→_ _ _start(アンダーバーが1個多い) —- ps_strings, ps_envstr, ps_argvstrなるものを見ている —- 動的リンクであればmagic, versionをチェックする —- atexit —- _libc_init, _(pre)init, mainを呼んでいる — Makefile.inc —- %%デフォルトのリンカスクリプト($ ld -verbose, NetBSDのそれがどこにあるかはわからないので憶測だけど)から参照されているのはcrtbegin.o, crtend.o, crtbegin.o, crtend*.oだけ%% —- %%それらの.oを作るのにcrt0.S, crti.S, crtn.S, crt0-common.cは使われていないので必要ない(作りたければmake realallとすれば出てくる)%% —- %%glibcでcrt0とかがtraditionalなモノに使われると書いていたのはそういうことみたい%% —- %%いやでもcrt0-common.cからしかmainは呼び出されていないということは使われてるはずだ…?????%%

https://stackoverflow.com/a/15919493 より:

libgloss(newlib)

  • gdbでちょっと見たアレ
  • スタートアップを持っているのはlibglossのほうらしいが、newlib/libc/sys/(arm, linux…)にもcrt0.Sが入っている。読んでみるとlibglossに入っているものとほぼ同じだったりコンストラクタが呼び出されずにFIXMEが貼ってあったりするので深入りしない

i386/

何やらCygmonというものの上で動かすことになっているらしいが…

  • cygmon-gmon.c
  • cygmon-gmon.h
  • cygmon.ld – 独自のリンカスクリプト – esp = $__stack = 0x500000
  • cygmon-syscall.h – SYS___get_program_arguments など独自のsyscallを定義、なんのため?(デバッグモニタのにおいがしなくもない – インラインアセンブラ: =aがeaxへの出力, =がついていないのは入力(引数)で、0とか1ってのはスタックに積むという意味かな
  • cygmon-crt0.S – 単純, __startのみ – .bssを0で初期化 – signalの設定→くわしく — __install_signal_handlerの実装が見つからない, SPARCのは見つかったが… — _sigtramp – .dtorsをatexitに – .ctorsの実行 – argcをスタックに積んでmain呼び出し — __get_program_argumentsも実装が見つからない – exit呼び出し
  • cygmon-salib.c – __do_global_dtorsと_ctorsでループの構造が若干違う、していることは同じ
  • Makefile.in

  • newlib/libc/signal/signal.c
    • __sigtramp = signal trampoline
      • signalが呼ばれたときにハンドラを呼び後処理もする部分
      • ハンドラがあるかどうかチェックして呼び出す、1回呼び出したらハンドラの登録は解除する(SIG_DFL)

スタートアップを書く

  • たとえばx86向けのを書くとしたらどういう感じになるんだろう…???
  • ___startが呼ばれるのでそこからGOTなどをゴニョゴニョして最終的にmainを呼べばよい
  • SHARED, non SHARED, dynamically linked, statically linkedあたりがわかれば作れそう

  • NetBSDとglibcのいいとこ取り(?) ```c #include #include #include

extern void (__init_array_start[])(int, char **, char **); extern void (__init_array_end[])(int, char *, char **); extern void (__fini_array_start[])(); extern void (*__fini_array_end[])();

void run_dtors() { int i; size_t size = __fini_array_end - __fini_array_start; for (i = 0; i < size; i++) { (*__fini_array_start[i])(); } }

void __start(int argc, char **argv) { printf(“starting up…\n”);

char **envp = &argv[argc+1];

size_t size = __init_array_end - __init_array_start; int i; for (i = 0; i < size; i++) { (*__init_array_start[i])(argc, argv, envp); }

atexit(run_dtors); exit(main(argc, argv, envp)); }


```S
        .text
        .align 4
 
         .globl _start
 _start:
   pop %rdi
   mov %rsp, %rsi
   
   call __start
	$ clang -nostartfiles first.c crt.c start.S -o first 
	$ ./first

Backlinks