nm
shows symbols defined in an executable. GHC includes library name in
symbols, so we can use this to inspect libraries that make it into the compiled
binary. GHC uses z-encoding so we pipe the output of nm
into an ad-hoc invocation of sed, viz.
$ nm -D $(which pandoc) \
| sed 's/\([^z]\)zi/\1./g ;s/\([^z]\)zm/\1-/g; s/\([^z]\)zd/\1$/g; s/ZC/:/g; s/zz/z/g'
...
000000000d50d930 D zlib-0.6.3.0-2d9559b6e6a6701fec620c96115752eff9b4a69c5037bf15e8d98c669f8aeecfCodec.Compression.ZZlib.StreamzeroDictionaryHash1closure
000000000d50d930 D zlib-0.6.3.0-2d9559b6e6a6701fec620c96115752eff9b4a69c5037bf15e8d98c669f8aeecfCodec.Compression.ZZlib.StreamzeroDictionaryHashclosure
000000000d50f198 D zlib-0.6.3.0-2d9559b6e6a6701fec620c96115752eff9b4a69c5037bf15e8d98c669f8aeecfCodec.Compression.ZZlib.StreamZZlibclosure
00000000097c3688 T zlib-0.6.3.0-2d9559b6e6a6701fec620c96115752eff9b4a69c5037bf15e8d98c669f8aeecfCodec.Compression.ZZlib.StreamZZlibconinfo
000000000d50c060 D zlib-0.6.3.0-2d9559b6e6a6701fec620c96115752eff9b4a69c5037bf15e8d98c669f8aeecfCodec.Compression.ZZlib.StreamzlibFormatclosure
To focus on only the library name, we use a regular to expression to capture and
then Jacinda's ~.
to deduplicate.
$ nm -D $(which pandoc) \
| sed 's/\([^z]\)zi/\1./g ;s/\([^z]\)zm/\1-/g; s/\([^z]\)zd/\1$/g; s/ZC/:/g; s/zz/z/g' \
| ja '~..?{`2 ~ /^(T|t)$/}{`3 ~* 1 /([A-Za-z][A-Za-z0-9\-]*-\d+(\.\d+)*)-[0-9a-f]{4}/}'
aeson-2.2.1.0
aeson-pretty-0.8.10
appar-0.1.8
array-0.5.6.0
asn1-encoding-0.9.6
...
Thus we have a list of libraries that provide functions that end up in the final executable.
This is different from library dependencies that get linked, and in fact we can compare with diff
(don't forget the | sort
):
diff \
<(readelf -p '.debug-ghc-link-info' $(which ja) | ja -R, '.?{|`0 ~* 1 /-lHS([A-Aa-z][A-Za-z0-9\-]*\d+(\.\d+)*)/}' | sort) \
<(nm $(which ja) | sed 's/\([^z]\)zi/\1./g ;s/\([^z]\)zm/\1-/g; s/\([^z]\)zd/\1$/g; s/ZC/:/g; s/zz/z/g' | ja '~..?{`2 ~ /^(T|t)$/}{`3 ~* 1 /([A-Za-z][A-Za-z0-9\-]*\-\d+(\.\d+)*)\-[0-9a-f]{4}/}' | sort)
<( ... )
is the syntax for process substitution provided by the shell.
This gives us:
< array-0.5.6.0
< base-4.19.1.0
< binary-0.8.9.1
8d4
< composition-prelude-3.0.0.2
14,19d9
< ghc-bignum-1.3
< ghc-boot-th-9.8.2
< ghc-prim-0.11.0
< jacinda-2.0.3.0
< microlens-0.4.13.1
< microlens-mtl-0.2.0.3
23d12
< pretty-1.1.3.6
30d18
< rts-1.0.2
32,33d19
< stm-2.5.2.1
< template-haskell-2.21.0.0
37d22
< transformers-compat-0.7.2
Notably, this shows that microlens
is linked at
compile-time but there are no functions from microlens
in the final
executable. This means that GHC's simplifier worked and optimized away all lenses.
Again, this is tortuous, but it shows some of the features of the Unix compiler toolchain and the shell's impressive capabilities when marshaling text input/output. What dignity it loses from being jury-rigged is made up by its flexibility—IDEs offer no such capability.