nix-ldとLinuxの動的リンクについて勉強する
nix-ldとかいう魔法の存在を知って、動作原理を知っておきたいとなった
NixOSの環境は本来外部からもってきたバイナリが動作しないところ、nix-ldを使うと「いい感じ」に動的リンクをしてくれて動作するようになる。
わかってないこと: elfの構造、動的リンクの仕組み、nix内外のバイナリの違い、などなど
nix-ldを使うには(復習)
- nix-ldをインストールする (
/lib64/ld-linux-x86-64.so.2
が作成される) - 環境変数
NIX_LD_LIBRARY_PATH
(,NIX_LD
)を設定して実行する - nix-ldが動的リンクしてくれる
mkShellに纏めることで自動化してもよい lib.makeLibraryPath
ヘルパが便利
バイナリを見比べる
実験1
$ ./hello-nixos # NixOS内で作ったバイナリ
Hello, world!
$ ./hello-ubuntu # NixOS外からもってきたバイナリ
Hello, world!
$ mv /lib64/ld-linux-x86-64.so.2{,.bak} # rmしてもいいけど 戻せる自信ない
$ ./hello-nixos
Hello, world!
$ ./hello-ubuntu
zsh: no such file or directory: ./hello-ubuntu
$ strace ./hello-ubuntu
execve("./hello-ubuntu", ["./hello-ubuntu"], 0x7ffc7a22c570 /* 91 vars */) = -1 ENOENT (No such file or directory)
strace: exec: No such file or directory
+++ exited with 1 +++
execve
が仕事をするときにld.soを見ているらしい
バイナリを観察する
$ readelf --program-headers hello-ubuntu
Elf file type is DYN (Position-Independent Executable file)
Entry point 0x1060
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000002d8 0x00000000000002d8 R 0x8
INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000628 0x0000000000000628 R 0x1000
(略)
$ grep '/lib64/ld-linux-x86-64.so.2' hello-ubuntu
grep: hello-ubuntu: binary file matches
ELFにld.soのパスがハードコードされていることが確認できた。
一方
$ readelf --program-headers hello-nixos
Elf file type is EXEC (Executable file)
Entry point 0x401040
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000002d8 0x00000000000002d8 R 0x8
INTERP 0x0000000000000318 0x0000000000400318 0x0000000000400318
0x0000000000000053 0x0000000000000053 R 0x1
[Requesting program interpreter: /nix/store/wn7v2vhyyyi6clcyn0s9ixvl7d4d87ic-glibc-2.40-36/lib/ld-linux-x86-64.so.2]
nix-store内のglibcのld.soを直接参照してる。こちらもハードコードではある
実際にリンクしたいライブラリはどうなっているか?
% readelf --dynamic ./hello-nixos
Dynamic section at offset 0x2de8 contains 26 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000001d (RUNPATH) Library runpath: [/nix/store/wn7v2vhyyyi6clcyn0s9ixvl7d4d87ic-glibc-2.40-36/lib:/nix/store/ybjcla5bhj8g1y84998pn4a2drfxybkv-gcc-13.3.0-lib/lib]
(略)
% readelf --dynamic ./hello-ubuntu
Dynamic section at offset 0x2dc8 contains 27 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x1000
(略)
どちらもShared libraryは名前だけで書かれていて、ld.soが「パス」から探すことになっているようだ。
ただし、NixOSのバイナリではRUNPATHエントリが増えていて、ここにパスが設定されている
実験2
$ cp /mnt/lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2
$ ./hello-ubuntu
./hello-ubuntu: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory
$ LD_LIBRARY_PATH=/nix/store/wn7v2vhyyyi6clcyn0s9ixvl7d4d87ic-glibc-2.40-36/lib64/ ./hello-ubuntu
Hello, world!
ld.soは「普通の」ものでも動作した。
nix-ldの動作(予想)
READMEによると、NIX_LD_LIBRARY_PATH
を使うのは内部でLD_LIBRARY_PATH
を使うときの動作を汚染しないためらしい。つまり、動作としてはNIX_LD_LIBRARY_PATH
も合わせて読んでくれるほかはld.soと同じ?
参考: https://qiita.com/saikoro-steak/items/5d90ae34b61c16f6b3cf
原典にあたってみる
nix-ldのREADMEに書いてあった…
Also read this blog post
to get the explaination in full detail. A summary is below:Precompiled binaries that were not created for NixOS usually have a so-called
link-loader hardcoded into them. On Linux/x86_64 this is for example
/lib64/ld-linux-x86-64.so.2
. for glibc. NixOS, on the other hand, usually has
its dynamic linker in the glibc package in the Nix store and therefore cannot
run these binaries. Nix-ld provides a shim layer for these types of binaries. It
is installed in the same location where other Linux distributions install their
link loader, ie./lib64/ld-linux-x86-64.so.2
and then loads the actual link
loader as specified in the environment variableNIX_LD
. In addition, it also
accepts a colon-separated path from library lookup paths inNIX_LD_LIBRARY_PATH
.
This environment variable is rewritten toLD_LIBRARY_PATH
before
passing execution to the actual ld. This allows you to specify additional
libraries that the executable needs to run.
NIX_LD
に設定されたld.so(デフォルトはglibcのそれになってるみたい)を呼び出す、そのときにNIX_LD_LIBRARY_PATH
を合わせて渡してくれる
ということらしい
つまりこれも動く:
$ LD_LIBRARY_PATH=/nix/store/wn7v2vhyyyi6clcyn0s9ixvl7d4d87ic-glibc-2.40-36/lib64/ NIX_LD=/mnt/lib64/ld-linux-x86-64.so.2 ./hello-ubuntu
Hello, world!
NIX_LD= ./hello-ubuntu
がなぜか動くと思ったらfallbackが設定されてた…