pax_global_header00006660000000000000000000000064142374212440014515gustar00rootroot0000000000000052 comment=1521dfe063284551b955958fd7deb3426fd75d84 libtree-3.1.1/000077500000000000000000000000001423742124400131455ustar00rootroot00000000000000libtree-3.1.1/.clang-format000066400000000000000000000000471423742124400155210ustar00rootroot00000000000000--- BasedOnStyle: LLVM IndentWidth: 4 libtree-3.1.1/.github/000077500000000000000000000000001423742124400145055ustar00rootroot00000000000000libtree-3.1.1/.github/workflows/000077500000000000000000000000001423742124400165425ustar00rootroot00000000000000libtree-3.1.1/.github/workflows/build.yml000066400000000000000000000003361423742124400203660ustar00rootroot00000000000000name: build on: push jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - name: build run: make - name: test run: make check libtree-3.1.1/.gitignore000066400000000000000000000000461423742124400151350ustar00rootroot00000000000000*.o *.so exe* libtree *.swp Make.user libtree-3.1.1/CHANGELOG.md000066400000000000000000000056421423742124400147650ustar00rootroot00000000000000# v3.1.1 - Build system portability fixes - Fix `make check` exit code # v3.1.0 - Add a `--max-depth` flag to limit recursion depth. For example, `libtree --max-depth 1 ` will show the resolved paths of direct dependencies only. # v3.0.2 - Improve `make check`, `make clean` and CI - Preserve original timestamps when installing files - Add rpath order test # v3.0.1 - Fix man pages directory in `make install` - Skip dynamic linker on aarch64 and powerpc # v3.0.0 - Rewritten in C99 with 0 external dependencies. - Significantly faster & smaller (~50KB statically compiled with musl libc, or even smaller than the source file with diet libc). - Cross-compiled binaries now available thanks to [binarybuilder.org](https://binarybuilder.org/) - Improved search path printing when libraries cannot be located. - Improved rpath search: shows `[rpath of ...]` when lib is located by parent of parent ... of parent's rpath. - `fd` inspired highlight of filename when printing paths. - Caches files by inode instead of soname, which is useful in the sense that this allows you to find broken libraries that only work because of a particular search order of the tree. (Consider an executable A and libraries B, C and D, where A depends on B and C, and B and C depend on D: ``` B / \ A D \ / C ``` It may happen that D *can* be located through B's rpath, but not through C's. Then, depending on whether A - B - D is traversed first, or A - C - D, ld.so will complain about missing libraries or not. `libtree` on the other hand will always tell you that D can't be located through C. - More verbosity levels `-v`, `-vv`, `-vvv` instead of `-a` and `-v` flags. - Skip fewer libraries by default (only libc / libstdc++ type of libs). - `PLATFORM` rpath interpolation now uses `uname`, this is not always the same as `AT_PLATFORM`, but unlikely to be different, and in fact the feature is rarely used. - Support for `NODEFLIB` flag, which is a dynamic array entry flag that signals to the dynamic linker that it should not search default system paths including those specified in `ld.so.conf`. - Better FreeBSD support (`OSREL`, `OSNAME` interpolation in rpaths and `/etc/ld-elf.so.conf` config file support) - Support for relative includes in `ld.so.conf` config files. Breaking changes: - The bundling feature was dropped in `3.0.0`, but is still supported in `2.x`. It may return in a future `3.x` release, but my impression is that there are excellent tools like Exodus which do a better job at bundling (in particular: they ship a copy of the dynamic linker.) - The `--skip` and `--platform` flags were removed. # v2.0.0 - No changes to the libtree API - Provide static executables for ease of use on distros with an old glibc or musl. - Dropped the dependency on `cppglob`, use posix `glob` instead. - No more vendored dependencies, rely on `find_package` to find `cxxopts`, `elfio`, and `termcolor`. libtree-3.1.1/LICENSE000066400000000000000000000020601423742124400141500ustar00rootroot00000000000000MIT License Copyright (c) 2020 Harmen Stoppels Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. libtree-3.1.1/Makefile000066400000000000000000000013661423742124400146130ustar00rootroot00000000000000-include Make.user CFLAGS ?= -O2 LIBTREE_CFLAGS = -std=c99 -Wall -Wextra -Wshadow -pedantic LIBTREE_DEFINES = -D_FILE_OFFSET_BITS=64 PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin SHAREDIR ?= $(PREFIX)/share .PHONY: all check install clean all: libtree .c.o: $(CC) $(CFLAGS) $(LIBTREE_CFLAGS) $(LIBTREE_DEFINES) -c $< libtree-objs = libtree.o libtree: $(libtree-objs) $(CC) $(LDFLAGS) -o $@ $(libtree-objs) install: all mkdir -p $(DESTDIR)$(BINDIR) cp -p libtree $(DESTDIR)$(BINDIR) mkdir -p $(DESTDIR)$(SHAREDIR)/man/man1 cp -p doc/libtree.1 $(DESTDIR)$(SHAREDIR)/man/man1 check:: libtree clean:: rm -f *.o libtree clean check:: find tests -mindepth 1 -maxdepth 1 -type d | while read -r dir; do \ $(MAKE) -C "$$dir" $@ || exit 1 ;\ done libtree-3.1.1/README.md000066400000000000000000000044321423742124400144270ustar00rootroot00000000000000# libtree A tool that: - :deciduous_tree: turns `ldd` into a tree - :point_up: explains how shared libraries are found or why they cannot be located ![Screenshot of libtree](doc/screenshot.png) ## Output By default, certain standard dependencies are not shown. For more verbose output use - `libtree -v` Show libraries skipped by default - `libtree -vv` Show dependencies of libraries skipped by default - `libtree -vvv` Show dependencies of already encountered libraries Use the `--path` or `-p` flags to show paths rather than sonames: - `libtree -p $(which tar)` ## Install - [Prebuilt binaries for **v3.0.3**](https://github.com/haampie/libtree/releases/tag/v3.0.3) | arch | sha256sum | |---------|-----------| | [aarch64 (linux)](https://github.com/haampie/libtree/releases/download/v3.0.3/libtree_aarch64) | `6f5945b6fc2c7f82564f045054dbd04362567397f6ed6c816f161afd41067c21` | | [armv6l (linux)](https://github.com/haampie/libtree/releases/download/v3.0.3/libtree_armv6l) | `aed70facf4987f6e320bd88fbd0d5be2b7453e485ac3eb8e365011f588761bfd` | | [armv7l (linux)](https://github.com/haampie/libtree/releases/download/v3.0.3/libtree_armv7l) | `932284ce9897365f0623f4c802407aade63a8c6fd5cba523d20d77d683768fae` | | [i686 (linux)](https://github.com/haampie/libtree/releases/download/v3.0.3/libtree_i686) | `9e451ff5bd5dd229b65c48354d7136a54816da527308189ed045cea40a552c82` | | [x86_64 (linux)](https://github.com/haampie/libtree/releases/download/v3.0.3/libtree_x86_64) | `22ec893cc34892f88f25e42ba898314a480c7ab8456dcad2bdc1809e0e9d68b0` | - Fedora / RHEL / CentOS ```console $ dnf install epel-release # For RHEL and derivatives enable EPEL first $ dnf install libtree-ldd ``` - Ubuntu 22.04+ ```console apt-get install libtree ``` - [Older release **v2.0.0**](https://github.com/haampie/libtree/releases/tag/v2.0.0) ## Building from sources `libtree` requires a C compiler that understands c99 ``` git clone https://github.com/haampie/libtree.git cd libtree make # recommended: LDFLAGS=-static ```
Or use the following unsafe quick install instructions ``` curl -Lfs https://raw.githubusercontent.com/haampie/libtree/master/libtree.c | ${CC:-cc} -o libtree -x c - -std=c99 -D_FILE_OFFSET_BITS=64 ```
libtree-3.1.1/doc/000077500000000000000000000000001423742124400137125ustar00rootroot00000000000000libtree-3.1.1/doc/libtree.1000066400000000000000000000022231423742124400154210ustar00rootroot00000000000000.\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH libtree 1 "2020-04-13" Linux "User Manuals" .SH NAME libtree \- print shared object dependencies as a tree .SH SYNOPSIS .B libtree [ .I option ]... [--] [ .I file ]... .SH DESCRIPTION .B libtree prints the shared libraries required by each program or shared library on the command line as a tree. By default certain common system libraries are hidden to prune the tree. .SH OPTIONS .IP "-h, --help" Print usage .IP "--version" Print version info .IP "-p, --path" Show the path of libraries instead of their .B soname .IP "-v" Show libraries skipped by default .IP "-vv" Show dependencies of libraries skipped by default .IP "-vvv" Show dependencies of already encountered libraries .IP "--ldconf arg" Path to custom .I ld.so.conf or .I ld-elf.so.conf file .IP "--max-depth n" Limit library traversal to a depth of at most .I n \[char46] The value cannot be larger than 32. .IP "--" All arguments after '--' are interpreted as paths, not flags. .SH ENVIRONMENT .B LD_LIBRARY_PATH can be used to provide additional search paths. .SH AUTHOR Harmen Stoppels .SH "SEE ALSO" .BR ldd (1) libtree-3.1.1/doc/screenshot.png000066400000000000000000001732741423742124400166130ustar00rootroot00000000000000PNG  IHDRtsBIT|dtEXtSoftwaregnome-screenshot> IDATxgx} ` JlBR"NY%Xn8Qb[I8w۷ؾc;nqXQeYP"ERb; D lߝy^,@ D%Zp윳7gϜ!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!2 DmB!n`&J؝(B! +Vr!B1\qcv$ !BT "UDžܗcB!4@}! j%O!B\R z t߈Pjk}0JXB!t/5|B!#:}mTH@{oW!B\Z62Po}@ 3}rB!a}@#f@q_Ἧeu!BqqKu@\>zW׈1{/\߃} !B[!M0N{1jF"e艚z*K(B!x 4|([Ka}(CT.؅U-S!BqqF2/j}<eו^z_PBv/W__B!`s㭑̓=c˞#]Hh޷w_29 …B!.z{}l;T7` f6gg>i6p P(iB!+x<~ x%8Qܓ5{1jJϷbز]Ne]fY¶B!2]׉bz8X,^ccb#c ݽ={:a 75cW7X,k^^OfX,F,C:B!b`( & DSSSc,;P__x<@2|GA uvA0 W9Z+Cx]B!EJuhD"N6l^2GtHƂ=twpx@!B~q\.# # 6G:x!kZ>DnB!gE"Ngf[ ;`HNVsf̎}M+xNWUu`o!B1t]GQlppv8`i e t̞ piXD"_Qe00LHY B d\,UUEQI~wOW}bkӝc'z=3cE1 ihLn7pO,#*B!d\,!(= F{x #GW++x_`Dzz:b1_!i|.NvKS | '[xp_}TTh¾.3of"rPӆviB!D8Y).y׸+׎'a^6m᠇&S_W[FH B!.d\,4cSiG$p B!cg}ECQzg50WWt]6B!Btʟ}{>֭P~AYvp!BlaGfcL*51| {~lTǂw>QB@.B!.Q<SVB5A)U(uOl ZG2i*=FH7ؼB!NpS*[`Jpi8:G`#~ߋYF!.mEY0C/ 1&5kpSTT@uu57o歷"s ťj(At#ĻQ3җnBqi/?볏5T__>}nı %,iqŤxxGhjjԩSvZVZwƹRV_O=c/aXճzM*xox~~~[)b`&Gy-[/ءC8t֭[ַƽGK=3-cKTTcdt+d5'81z*ӻ\/| l۶tttt_) .˗&!ĠyQ3w|)vTPn͚5455{z1cW_֝O۹s瀏/]tLڱ:$L=^'yyӧqXN2h,GDT$lz1Xo|SB3 ‡m?G=|%z=<[7֏sOwNwIZ?cZ~{&D=b _䷧Cyd+>~#o?KK֭ edEMQ=2&Rk?8-?~)=y juDO+fdY!%4 N8\mʴ4e;:Xy\fQP=´F-2bRڼy3k׮4]M6QƸ~to#g(&sQQl>.݁[!&ܻ`еN:c:(+uTQF/'+9t̍oNOZf ʈfժU[2seeeﱦ#| s]KC׺y髒`0̙3Sjp|9-B|.~mۖs\v|~OL+a_69Cûl}GGGgUUw@׭:փ@Htݴ޵sdAQB?v|x7E\"6sv}^;O4yL!BLp۶mjB'}N;;9 -'\!BLQ:/1 dEMBB!sV>YYbp-e1]=r$jbo̦9HB!"öm;2&\!B1&!\!B16aCx<%!&gR*m,bMn0/l"z1 !8Y)D0!O4x^\.&iLtttzǼn!8Y)D0!Cxk{{;---$GB!d\,b"!DUEAQq[!m|.Nv 1&lEB!z,B ybB!3 B!B1 B!B1 B!Bq=13gB!Bq ᶴ^!B\B͍݄3d8B!cLBB!cLBB!cLBp(V]VM0moYōE!>JʻiDzetǹeBqqt!ܸ~~ϱd*'O~Ywfg55xd2y&WJi?+n.u˙=ԠhgX93>g1u o3(O~\q!"2yB9e 򗋱[\"wDï#?œJ*X3q8O)d +Wsu$P)gUW?7{ҠYj–>9U!}/-ɟN)S 4 -APx#=Oǻ|&Dm5lg%*>n:>{ar,F&o~"vtB!##9̙vet0՜<:U)m`A؎_"WJW$sNKuɲ_d3@ޏyׇc7uW^6L7. =?7'3Vźs)syOy⍓ttT &RF$vWz#u[ %|$*=|'il8Ʀ? ˆgJ} rI7-{t1P]_?T6˻2co6ӯ;M0u5_.˱k>#J!'zwW<܃=X^5_{xAKB!&G;zg]ϝ}}⑩j7zVŲU*_}.{*п=D/~l,8? VkN"=\}9nd7W<_&m*>AWԆR^|jY⊅?#mq)7P5HS Zcf,oZ W?}ywy^Ř{^W*B?pd! .*3TsX(ȡ9=2x"fڣjɟՕ!Sw}<&v|tξ,Z ̓B!$ >< ;_XU{-lGh4hk?ȶٷz9b,'i DH 47PW_f=ؾ?~#J%'~#Nb]xx6K{2TOqԼ_C +7𽿴w3Hhnma:'[¹NԁXXS<4XW*'p"|qPEeE\UbK 耞GSw̕V:;~ϖg^5UXPV!b2!l}c?ӗ,c ױqm#9ʏbn hd(FŎϝ_1P 2IIz`AifϛCS`א~[,Oah^۪hWvxnujln`빺5i>NL?|BnHDR!BL\#+\s*2jqGXy \[ eB ^k}:4?p0UM.^v䨧ٸIS|9+n)Mx{'\CuҵwpT=ߐFwvԦtN^|uǃ|&ŗ0AMkS*TGqWq;{m_MW?l=z_^aȁ~>zS_ږclidSŻG8j޴͖>m:\nrRBq!\`J+bی{<=ߐ$8SR n/*N{Omo=g'Шz)^(4l"c2[w$B-[Ɨ^Q R{׏ϳxoF r'ٲ&SgȿUkWm&9!2)Hy?kn[uwNgߓ9U!*O5/<}!aiÙ5{ӲjH@̩-z:Q@ne^Gc9ƄK+qiqT< !B8"B!.B!>,&38;[ !B!!9%pCrw14t CnƱO#VzoA@~UЈX/־O!?L1`'4g)7+d{`= U/x~!azf1Nq5&PrqVF7>ƃ+hyGz=OȩE&B!#aBp=wgE0=q2Oʥx]@nR{NlIBjȏ)XRoOweT7S=\ܠyqg_1LhFC$ ~UB! “@%::pT|J;Mɣu:Nݶy3]?C zjOۉS5Hit8rZy@M!B0qBceva a͝1-0i؁9 gG=}O#XBimrNJB=J1ZAU Qúfy% !B sb;NG>z8N7\ƅa2`&065D- #l8&=]t0]g]AkzV?̛Mˬ ΰJzezm#p m+J9AS>\{{@Vk*u?y3!B\&H2VB$z^uLG7Shˮfz\ َw91̍fPԉB!Eo)= , `Z7Sܵ޽<PH xҝ-?G!kn쬪.ut|@gzֵԻ=C6!?it*&D!B4Bb \m$@iB!]`Lx'' Ӟ7#B!. B!"!\!B1&!|Ԙ;c"Ni"?9=bS,4kr°B1D`LĒq'Vo&QAuޜ*kY=>9wo5nki:#0l@EsX^eѾb<-GqtE{ĠStA4t;pd&z'8ؚyzNL= z+5ōiSVT=W73ìims{xU U\sxIcR#(8ttPA7** f6+* &ۏ⻨Zz _3~*%qVkN*Ck%ҥhql9k0Nb JP|=L}q/N:^MB"V0H۳}K5x)\ |ƉUcƶEbmc͵Lm(Wܴ `O,ƳO, z7 B1n$殺pϧꞫZa;6z0TmLEmA1zcSY[h(C47D?2u_辌aQId~BJP:+ov^:zk;D=%ޘƋFI]3h`@7fA@w|=]B8d" vφBq!!|2ۍ!+jc:.'x'{qsP]=miĭf45׈.[Q=J*. =IyTPY|n{nXJ wwZ(>$pٓ.mzW{NN'(itb+v4:!9GmF ͧHKtVhf89i. ^xwpre ,4඲.YB{IQL27m&ͧS[Vⷂɔ3eJ۬th-/AsnrY!ySɬ-_^cNsL*mM PH1YviVȶjh1#Lyo 5Eլtn/jNFn_œ0n#DBN-m ZW9Wⶲ 986BGJ%m GYJdUrj}eTBqQ> Y0+X 8w5ZP;\"N c[3pV5~ZDyT\ZvZѳIL3t6hך1鯢ӈzr-yr8p;%6vbҋi[CEG vjX5;O>kWx#ko$hS0';p.Ѓa` U&BG:t.vP;X\D2hc2RyVuK>ˆtO.a#UDZ YoґKD-8q8}L|g}:S))8*383dU'y18WaSNeΗ\mʇ^M?7U֦B0V_w(N.E#GI,߾N'Qϯ֛NeS@sڋْ]ϗ9LnMǂf B!3!CbT;RJ} xyr47Pqc3 jɬpo}Fοʫ̟C8kxgLDo%6RX^qPkP{Lxwb;H9fS7Iȃ^i)A?IzN|j*bm{9?L3: f0guIԒЊYxK7xz;R|8{@۾(:5hD'[>n4֣;zI h^];Ku{%_dE&eJkjf'S vZ=ѬXcV|C6g8C[?N>qCZPh`n!C&%U9ai]lLUO i f(j $ >wَZA♙R ̣qaZ> x)/UT Њ%سrf/($aD#iQT7e 4LUX:j<(hNqT{tLzv PMFP-XSBD4+|:znnN|njxiN'Xco¤re8PuJz)G`;+}}`(mMŸ*3 @mi!B<)CZDYšNu4qUMLS@3QSˋoOQSfQND^>J 3sJD^ե@iY;%B 9eB7ai=94y/NTK۰wH#׮Tv+N|+$ۉшb?x8O}E{vj2I(= kX6hmOclkŠg/\FŽ1ō>[zQYSFCQ w-pƱ`=KiA/@ ݞNmGڑ8=q:9چc{dWRn*XvQUXQy큶EM1:0 2]ߙ܏TB!:ZmdOY:?t Uq s hؽx; qi~2V/v3&nġH,l 0&}dg(T[U\wq9}h*;+l0p|,~Ꮉj€NoqV GzN>j3s^%7Qv9?@Y"r;6hMWy(mGx,ȭZF\&>VS6CK!ACѨX&O. ]7#`ZֻӲs=+x_\\'fDͧqmKɿ910xhNc7>OAEx)&e.)N"ԬZeb;Krzu]A B 3A5A4#sO9Op+9=υbvPvU)\ pw]#*=-Y՚r&!|BS9]$ҲI(,zda0dW82o8Lri33, FksB 1Z=O `OLht n u%w;B;6T!B1&!\!B1&!a&_olᒦYxiGʔB!2>%fĉgITPwJdjgc(kνE 74Ñ/'"fcGƻ1c(97-{)*B ޱ׃EM؝*)t^&uΠ ik3b`b ;D̞pޕ9B~8ol7>$N!oIeU'/ufah~|/9Uc Qtk.#(8C=۱]=miĭf45׈.[ H_Mӈ,h ]nJ̺cSIoNuks%Kh/!j3B<[Cͤj0_D _=;:ўboY j,&S=K֙dTښ,hfSeלfUYl3k䙗9*FsߜEYmbpIZmHɽeb=A3SV$fPhwb^$UR;X(+]@޺JZNm] RhB!$_R`"nWAp jS7b۩B bkL̢cjW Cv p4\@ɖ0-"n1@.%05cK8g~}Q):@i(N,^0: g[=3+O0͌لcL粛haG cE$É'nN,>Xz1m+r^?xvϣƲqqq8}L|g}:S))8*383dU'y18WaSNeΗ\a Α6ԎV[ i 0X}ݡ8B%\~F:D>[o:EӗOk/gKv=_Z[3y5 % !5SER>2x&D TXFtLpϡ̅8Ik;@OK- ̘Ipw-m{dAs2N{\֏ث[Z*9W?iЍ4Jtޭ<`a&ze]R /8U M{(ΘpAcEArF#z< ZC$C'>c5K\1`6tI`=+ #Oqr8fd=RtҖt8qQRzh㦹ajwЀ#6uQofHjCkiVSw^ΡL>ԥu8_&jPż)qQY X$PvXilLßBLVS0jWz 0b49DUQF=e> n"@ ?v  u/y{a4Cئ` (Ħ.Y8j#=C:L'pbVS'I+?-&x WGr0t%6N[!7EՌ .WWv^W6x9YɮcY쬶ڰֳCO4+5 ߐ/`NOS/zBK qL-bHʙ Tڏ7?!fJם\*V~5-^FLV'`g;ZbgeԫY9Q<t]<d ٽ{8#L⴦qv_7y*wJX2[n~Z!2#҇sد֥JgBq&,dTĺ/ *(j2C?aF%ZZJHXV:lO<}gm3QFˈI/oAм|*gB"*aN71#ƳJ>کtnՋi]u7'>}7s<^X(ٛ_rL15'ǘ%t:@%6mA`9u]!Ŕ!IM"¬v4qUMLS@3QSˋoOQSfQND^>E 3sJD^ե@iY;%BL4>Y4P'nmػq{{@Vk*u?GvT'wDhD`](Dg]Ks ݞuѸ: 5ZGz)Fͨs2"qp†ơZ|+l53ȀOU;Yip?oq(1cifݔX$4*M]G_zÛxpI6$ j5ʖ<wvhUuGG4TTs4N%_Wu !BkųC^NgDQ̠XKmD]HGޝq݇v qE())r|RSvR~PU*yJ^JU<*rȖX+lh($@; 0LO}  IDATPߧjjBNcO9j R8k=j-2@ٹSTuR3AaYped1l9 P[gс2~88z!Įm@;%acϟh1R x!zPqu4g݌גgS(RR6xR/ingO٦:2r%w (-gv?꺍Zk e_k(sR(7o^O6?f?doj =gr\)FK9$ƥ#{j=ԂF,DtظaR'Z7Q5սlc7J}Oa~|nCPl! *B!&IB!w$6ru-V XJѤ/B!-&WX˛S>{N+dW`p}Z4CZIxV _w;%eOdo3zPG׮NTcO{XY/egX>H ˟wIY0/=>FG Ͱ {e|R@']~,?(qJb Oi/qkl4msd_-G<@'7u??{FyW^M&q~4P򃓫r~gN/Z߿yGWbHo1 <&ROmX˯PsߦBO{:|a#=|Gh}nc>F ^Rl!Z~ lTILLM sQ|i6N> vB!nI7vaCuE& zhKlgB Q(,>! ߡc^_wlD7l \Mi=5:HS+gbmd ZlT^bẅ́|tB `I~vǩGh D،bM~&ry Hok|tcź[c~>;W ׍&ٴ+gZysWnmMQqj 񈏣ǚc/YQ㼲"?/`[laַ9B!&I + ZA#c#JF'cul(Qܡ81żӥ&6v2܅NK܃1Yۡxmpr 1j}4^__`3Wӳ.'q:oOY9zc&5KFYvLZZd9b-GOe(|':*SGICC<Ͼ3`n^4, dRomi{bQYPiTZгvR'G;Jfou4 3kd&p:xnS/YfƛMy#Iu5+qfm;yS!Y p߂Z28u8<7nrٹ*N K9v shyURkqv<9?еLzˈ{8#t ccVLT1I `D&[ !7H>,Ю0ZI|\ jn{ep/q*+uuUU (nb1LYIڣjyzF͜ˏFSJN 8ȔV+,r\/=1t ӑN"6 œdOyźI3UFB$ &Skb]Cik#|):] SlExh%2>jay<;`=?}K10]Vc&GW!7lsbOnjӓ\u16K£ki! fKM6`3&^ zͷź缴m/p_`he`XȗQ:-x%'VAXVݬj&VeT 5csƺ Lwy,dhJ3l_({5;|wEk!sj%Y ngLOBKkYB!G2~ȌՌ=2):DGpMޚqE $+{E&R *uo{ClL9 sKm $xW1=t yyV݂Qoџ>̲"t+[^ yD)8]#_ j463+ rܟV>g,rO2tUGtWRɗS],F"6ԕzʗYJ|H84鐍-I98VM}[`?|r}tI IhޛGPω0w Uq?=i~{HZE]swoi7pdYxQoj)-8Z)oyp(yodGqy!4~S`"UJ eZ~毟}2Bkڟ䇫N]Ư& ܞNb no V ;&xCő|& Bo I_-~_,J;/YޥzԠв䖷:9`<Klo$Q=}7q~~sUA,Lf5korBn_7їof&u׼qfx([uzg Ah{8oA-x:d\қV7Sc\ajs'paȉօ94Tż*8;lSMZ&| e=Bz1+&*񘃤BTA0"-B|H+Qg]aȹOf_N?Mį ֲ4cxԐz%.w\}n:B ]%؍7^,f4I8+I{\o\T-OQU9{(Wz*8YɉRjE%C'A;{:iUq!x,uCY:=X71vsQHBQ"I7\KkّqF{ y-xV]DcKYEŁTgU>"ٚ(0G+_Qs C|av \Y1dsڵ٬n5i*B!4YB׋&?َMOr9ԙh,ɕJ $CS\nZnN, . ̈a!_F ^M\[юZLLccXL0I@%l)H㘺SA/b-`-#a)|*}yl6,V{jxJFRB?d5C,q73Fge&'5B,Bc !bn2~ȌՌ=2):DGpMTg"Ռ=")v?=َ!yR߅A׹67>Fܩ@ iWXORHs]V8&V޹=5Ǣk+u"QQ bfF^xpR!_ s猅BIƚ∮JU*?rʱHĆr]Y2SO{_ϕ>%)'̪ oc{l:OS1Xq;ɂ6_U{3#h9!vU8t_`z=\ ^>:gݶ>~'o/I\񼺋5XB!J/G=-^j [0µ"]3J#\KGVMCUB P{m'@V-E(;wR V(I5'?%KP K3JV28{z6Ց ԐSt, s_PEi>3Qm$ZKίE)8U]WF;BxvP9C}<O6\ʉ'i4.SYϟ/4b/'3 ;<Z鴯eTEV~9Y[uײQܶՖVWz݋P+Ü}WBƂGx@q@qT-H3)W_ixNNy&MQ+B!8 1V1O~0 @B;.wTἫB!$B\0pl ?>v!(y?6Bq]^!B!0I…B!$ mlZZA-+:ܗ k7I_)B!J&|\t}B wZ!jк>J[uLĝn҂hU si+KoPr~ Uͪ)֯;RÇRdR8Kc3,^>xТ #K !3If1gbZ|oz6|c=8}gBз:ז+9᫔9A`(yK#υP҂+r~gN/i|(0<3ґ#by LJXX˯PsBq3ϠaHzdZ9IʱOw \mc;1-BMD̛pFW(4uVOqjfJ vB!D$>,7-YT_d론'p^9M+&?:2uuEA)#GD aP h s' _G\ \#I55I9;χ?l$ajrN YA*~yk7n'PbΞrd 2TLY0 yr>*Cx,iϮ8VMQ)6=϶$U#o!}MϕRsX7s4`bg98`[`j_dqVdY sL+}*tV>ͳ)=:NM!qX{ŻAjW^gl+-ݟ=̿aB!7$`%+Vt=2rR63h$YzlUݶFJ:?Q@d+אXc8 n2DW4O`kIX$\؂a,7 /)$B!ݓIv[޿Xv_1KAe =[[-o'urw BKW'ҏLjMI:5X-5+I_<^}63x}e:R}>GPS<$M&tDqbԋ?o֬a AjyD_I>T\s_4nmUgmxClm9-&8dqqpyKoZtOsUϝ!'sZPx"sM5ko/$pG ,%:Ǭc RqLB!n=/V|O06ȫ]aȹ ()Ij l==8%q*ry-41WjH=;BYW7N@!_UUsoX`i4pV(b D /J<ϔbYjyzF͜ˏFSJN 8ȔV+,r\/=1t ӑN"6 œdOyźI3UFBEvO&,U(&`Rԅ%9QL앛$FŸJik#|):] Ζ_,Dr"wlt 3F%J G+_Qs C|av \Y1ds5- IDATެn5i*B!m!F׋ &?َMOxr9ԙh,IJ $CS&j8b'E9urYo hSCޥjl}Z/020nE@/d U:6[T˖R8K#ȷmtHM}ƷB4qecf92S{Wo62\}Ƞb 5dڗd)1 >[,3? -gIK!w&3+f T3LLto5f"Ռ=")v?=َ!yR߅A׹6B w0О&i%%R |b`ԅYB9Bbe+ًaV޹=5Ǣk+u"Q bfF^xpR!_ sUM=ja|ˎ?h۰죅 u(eV781|9_ğ+ |:d#oKR7NUl2huzc|m?+t]vm2)f2/Gц+-s" Cpzb|tϺm}dO^1VQmyukޱBqH>#DQrO~DLp֌R~'U8PU1^[ U hΝ ~+,vc撨Q_KRG/+p?IͩQPb 1^ԋmZCLc/J qݏn#Zr~-:HǩꚘ,MQ-L^추A(Yʡ>rt_.D4tAP c㆝Kh SkDtW[B*T+윬:k~:!*&\eXʿZ73[xK9lTBb !YhLy."kmXK϶ggsseU5fxsPp)Ze?֪4KZGg~AFm_m o5pUf!0gߕmBbcD__?#^zĀDu R^8ZX`0}J-g2~7[ cK0|dQ֓۽(W_ixN?KͣkWS'f*1~=,AmAW5X>H ˟JIY0/=>FG Ͱ {e|R@'f~,Q(B$ Jb Oi/qkl4ކg}i7pdYxRoj)-Q82)oyp(yodGqy!4~S`"UJ eZ~毟=9B$|u Ào }/=Fz#3Fݵ{J#k 71P_IS%2137-P4ھr[E񥩳8}S7VȤdեݰçB!$ 7aCuE& ztlq_Fi 1OR}~tJy^$m*J&uG.f D9-gFab@?k7n'PbΞrd lMm&`.I"KG7=NU%kڟ䇫N]Ư& ܞNe\!X{2 nK΂?ޥzԠв䖷:9qLê[oj콊bYMJש4op)7q~~sUA,LԬYxr |;=O7|d5#U^q8lM7v#+b{a[PK&MX0;We:܉\rb)?Ǯua U1Jj-Ύ'8T2]!{XDxAaB* ɖB!ĭ'J fhWp$l>r. 2ohQ*05d (6&f 1R厫xPՍSPWU+Ƌ ^F3 g%i+v)*O4srw/?JO';+91 SjXHDs|qp5hgOG:<.dOn> jay<;`=?}K12lNƛխ2MPe[!-dz1T'7Q O.:M%)RɵVd{[;KM)%63gj s%E . 諅|5x7YrqlD;j11a1j&VeT 5czsƺ L}ƷB4ՏQ\`Xὃ+7wWdPJ Y2 K -tӟ|.K!w&3+f T3LLto5y>)fH)趫ԽvTϓ2]. rΝ/Ñ5ɹ> 굜*?@!WDZ%r( W'/8>e-D*$V1j%᝻SXcq,F X'>Q1Hc3#/B8/`isB!$cMNLWeqDJp{%j*|9ba$bC]9ʮ|թͧ=Nd_W/JےÍo۷ 6Zç),_ G׸d_įf齙 p~ { C}*|/0|X/n[?ٓ}UT[x^w,!B18|ܓ"Q%S(*\+5>Bɵī|d$1T(p ז{dZds*>^j>:>D2PO<7?҉VB>5db* lZ^l ΞMud5K<T}QZό~u ֒+hAʾ8NUQ接P(o l~R~d73K9TS[.n˥qFґ=5ȞUjA#r":clܰspaju񛨚N^z XPH]tj嗝]G:x-C'Ceā+ [T]yfKr)Mv\H,!B1 T<kZ6ag[䳳ݲoڃ51Sm_m o5p׽r{9wez!X0O@1 Qzo(e(N2>%떳 +m[ɔ;Ա): 2D!$⁡U  ~g\mx]lr6w;B!ĽJpP8ynC!b!B!0I…B!$ mlZZA}/ qGJѤ/ήB!-&|\t}B wl!jк>J[uLĝk+.7] OL8џ_n俪Y5A|Gj_Ly1:ylP/㓐Z?>wdYE!+=gbZ|o-z6|c=w.Cb㌷7uJcTNs!Tǿej.fYy0#+ t|;V(7+0#*$|u Ào }/=Fz#3FI`%; c8" 4Zq=jr oTIL̼1aZ-h}𷘋KSgqTovI[KaWR!v$P‡%+"Lx=+=~۵cUn'Q]nSQ2q;ҍՄB <ӎhcϟhF)#ԓDj @(C ?ķ%Hƭ&ø.Ъej*ɺ%:49cVIt"гXT0eһd3vz-6 */G?1¹p37D{(g@lR*%ͣҪ)Y`35Ŧ'֚a`-"jxnULX#l L\MK^7ʚ > dvi]Ŷ2Ǻy5EGǩ)#>kbxKދ|[ Ծxmų3[L!$ /Y ZA#c#(۷T3 Jf[(V7yMG+ej@Ǣ`񐩪'if{4ԑhE6PG(w)̳?N˽(`,=1^p{u)1PX<18yش.tp^ClnK`M7Wļ"2P. p ījȪ]X0Û;eVkI͒Qv-.V>YιjQS6 _N~Je:lRijo% WwF0j8) +#6#bP9_Im vv!~Sp{kj1C|*S6IBUdݖv/fR=jPhBVrI,a&t%7x-J>NicXUrwrR W"I>5A_Rɦ ̑)%^?+f*0N➒d*zZѭ #s3&p:xTm`_Y&'Aq}vzn&jR]Gp}%Ul餠X LS!%Mp;--T*bkNϑl10m-%S˃^z {j,w2 \. 9c׺0W%gao ]_Bbqg|䁮}^q̊J< P@!wdK!{2 _d`vuJ#R05d ( 8>Rw:KHei9㨮!V:nt+0uCL^< ^z̭M1WjH=;BYW7N@!_U/^EX{q$= BEMg~òN[O.&^zb8#IVEl2'R7tu;fx: VBqO'\K RW& vF6!>/h\O|2Q ,EcoZLxD±on~}Zڈ._J%D׬~,?w3[SiTS`A %T 2[`xh%2>jay<;`=?}K12lNխ2MPe[!}@6뙍3M~rsU =@N|xRp3c` `q34#8 :/-@t \Z/w/RuZ~&K.NhG-&&ֱyU5p+J~2R*1u9b)Q:La[;I+A췣T X g|y,dhJk"~=`O \߿pU"R5P֐? 5C,q73Fge&'5B,Bc !# MfW fH!>k2qE $+{E&R *uo{ClL9 smT"i qyu["Ѝn~ugl G$~pF3Yp\y{< HER%YH6MJ5:>LUT$'UKnAUɭ$lɲbDҲe9bD"yht=܇n)8_UW7|z skX'kY$DL;YljY_!mbw#/މ6{ѤbՓW$I$%S#ؗH+ c W&ZaJ%&~JpEΡ1tobN_&y+ʰv3Lo&^QB$) K>; %%g'pL1"n+Fwo"Բd0f&g4piEKD 0Y-p]>o9tC `Ew~Jŧ(%}eCL5W)A{XY<ߥVɖ2NPr# ӫ9TO^q՜uDv51PL8~. 8y ͝k"Ӷ> A"T3蚝[}nnk"ŦxA_;%I$sosF..nkKXH-Slp)J9'v{ܞ%UɎXWO4h)'ߧrrXߋ{Ynſ~0y5^lwdVIeUf@ʱԏ#M_S0;?[/J$._ۜM4?OBN]%[_78֨ IDATK qMr2epk3lS8L ŰfYq E.suR{ 3 JZN$$fY Sא*'qbjMaűd{藽0XIƥ D1ZwݚY~E\å(^b_zzR%}4s[1 K)#?Z)g[0U%`"T +OKvE))Fl}tq*&fV#2]l[Ju/0un(l/2q(Y5ƞ͓tTkN:t?qZ2N=|9AWǥ ~o^G%ʋ_gO;|$It$.P4 [`)4>>2ڵ5uT W@bߘK=4 Ȗ4VIڶs3@ VOLn"+h\p{ 뗝]i\#IpHz1G"SQfFqOs_KPj"= )\czY9;ɨox;Wa@j 2RG߂u'kl;U)/f'>h33T&= N[ïBkxzo|^5yCc,S8#IqIU]lqZ6{W;omH OU6a $IB&xoH+uPQ$ޤrhzdZH|4woX$IH5&X{p`UodAq޺'߿DQs2X1!͇ΡbCYV.֞B%dxXeL6◨y,X ~>ҭm$N)dx~YU݋%$Pw卓iwbɕ%|Slo=%(jtH98/)=scyBZb &pnȅV! UPLj5Sp 6dJ&|2^S&ٰJ hIܩa HDMŖ$Iw:1?uxL''':H5~02Xh.p\$ܬȠ&f-]L{ ӳ".M Vxy.D-8ݨ"foE\{ >ϒd jߡۆ^c_&T%F ᛺-RVUd#\-㣮rN^vWX:81:t=`o{Hod7j Z7hVd:$IK$^+Ѕ`؃}}IL9zb bW]| q&Z&V_Cx*Svm*n0RrBLwQu`VgY_ ̢MRv2bML@@毇ϟx1S|#ߩB*WfE`XO.h, 9-ItW)t=T muL2@bij")\)1E`+7{B> MLY`U4@!f HL\ߞ-45V{G mj*w}dI<xMD;ҩ x,CkںTr]$Y+# [a3.3 i g㴸(`⭃zF*ƺ"벑.umUc5 Ų;Qpoߞ3TP\zr*8$ItדIœ"xǾDGZI`K 2 Sz(6V8#( `$p{u2STt!P@s,ol~j2%50Jx[I$sO(%I$I$&I$I$I$d2 n;&"5n)6=4S ʜp)I$I.hm W1z] O1} ?E)'v]x} )=K랗ۢL^y?wapM?#Ѳ[x KF$زe *~3 mXGx ڃYT)j}ߝ0ݟ- %InToai~ן`xd}-YJ̉A\מnrGKxmzɶ:NP#>>Ny̲Fe5- gwFRu/Ne.vd1ůz3m33&~)@ WO39%In'2 noI.X2apHT_6} %X(i^y-2Wi)6f@4*-70{'T F>ﭒ z $Iҭ%pi]7ees1b1\>%J΍us#J%KPϨ6@+afZjI;jd_2%,kajU=NLM )l#Cݡ8lO~+ɸ4ƒ0Hn /ɵP{ VfP:ҷȆՔ u_Z-JM!4@0]d5–`hS412^g- 6_a03Q‡9x^djQj='J tpt3/յd,{zsJKD;۽'OJ+@s'ّ-=?v,6I$馑I `Cw 3,%~fGq_F[a5 H 3@xh33T&= N[ïBkxzo|^5yCc,S8#IqIU]lqZ6{W;omH p6a $IB&wW_yԻR>e"xqIz7im#(>vzb&!մbamUdAJzNzh$~Gqɖ4:f f_XX{ :'ϩ4,z-p0ay|.AVcKZqk2f,MM:~r&!⠏<̍ k}6O\!Zi'784TAA3(O=5+jeFLZa шS@:-%I42 U'fZ4d#;V)1fA] j}Fbr^{nedPfS3.=$Vߥ B*% 6;Qpoߞ3TP\zr*8$ItדI<<%be>JX VGoĹzⱲxK-5dJA1*GxAWsǟ㺝9"ʅkb-q2|ظ€Gֹܹ/:m3$nN5ٹ*Gy9=3=Dxh,oc=꜇gWcc㛁^}jl$#wlKMA:*ۂR>߾̛zImX?#DXuH 7h[c2вɑpIV0G3rgt(NC.$I$\?߼W-dަFx;l tH$I$\Bi>[ W!^$I>R$I$In2K$I$IM&pINu=r;HjWLI$Ipfⶆ{?ՅR>ƨzr;a .<>iSZZnÿ~0ye`MKGe/һAKU$زe *~3 XGx ڃYT)j}ߝxݟm %InJ~ K-D+$kUdN DZ/M0VG jd)Yȳ1Uÿf4)V)lΕ=F8C!V~-`&cF7^/(iyn$I"p鎦noI.X2apS&Exk m(DqNC&RoQ5-X̵:XBeoi1ŸfS5JTF PuJ$I7L¥uSV>w >3>/ucO\>[V+afZjI;jd_B+!zBkjH{Xzm&?P.L!xJ2. 4 e?L b楸Vj/ QJg UٰDKvE))Fl}tq*&fV#2]la<>Lf&Jx=/ۋL\-JVg$U).nOܹp_oNPq负cxב4H|ŗ~$;rӎ&I$Kw§ KIƠgQ%F]XdKm$mO~;2ZD$C0H`u)gvb[w1F$q$#]ŜQH `Hi_U{uD+H+h7uUm]ǎ7FĢj8jǢ |ɁZ錨( ~+mvSE!ۅw?R 0OvMc^KhlR\@,aRU1[(!FC."[9R1_<5Cmf*SMe.ItI7ޕ:)+TMOһLkFTldAq޺'߿DQ\eZe~y o^ Qtq}rlMsiUt݋+ra -0\ǖB׀eHY.b{NuL.=7-EUץCA}IxbCmǟ0sC.NvoqhfRQz4ku%SW2-.&rO2iφU:GlX(D#NN A"dlZ.$I$\B2Pp~2n{E1փ/p\$GiiLh0Y@wiP#s}_%=tA6#ŌZ8O5]%L,_RVUd#\-㣮rN^vWX:81:t=`o{Hod7j ZO 9u69-ItKBq% f5WhaVPN )SǙwhG(Fs޲fʶ]d>/K#4YLM_fbwp9/U+f?Gẙ![-h, 9-ItǐItw\B)% 65_2 i2#_;އiq/P1[[;kTu)D>U~ IDATe#MK]/L%E۪4ƤkFe= ġybcI$Iw.9E;qGLJ&yngixy%O?LBۇyC<(^"۞!aɔ1,WX'e͌72싄lO:sshgduS{ԕ^MiEeK fC8gڵ#-EUe>6ˍBb%{)p?Zƾ(d>LsξDctڎ16Φupׅr(.82d'kS /UbJXɵVG=AEw~+FwDk[K҃2]@FeDP ׅ>FSz{I7Ԑ V:Zt T|:]x s]jGl!S %Q= "=Ce< ]^IT.m_]?m1P ȴkM<ε}Eiv uu_tέV> : vgEcA|3cI>dN8"^L,I$V B19vYͿV<4_> TT +/Vd{hNPSIJ׿xhXn; zrmA)o_qMyl$IwXx?"@,:}j3}dm2ϲɑpIZI&FQR/' O7^x{0G3rgt(NC.$Id.I+ɖ>&S^B΁ ||"BԲ~J/|;[I{L%i%0Jx[I{Q$I$In2K$I$IM&p#[Q˫nuC(5_FyĒ$IT,9'\*mÎc!{zִͨ$؍l6겫O/{:OJ}hr|aC0&hF at#q#$Ing2 c\8?!\(\pa/Eh.,gR'* 49FM]T>ZYX>̑1k<+cJM`ˆ /Sq@BSLa0.ʉy)G.a!=ZV92Yx]+E <E(YLgͻ}̱rkj^bX& 7&p=jÚ]> bŇJס5oMHbEs-t˨;4=)؏^jn`-[EɳXh̟' 8PʟD؝}czv^p!m.$5u]2Zvj0z>XYf?Ze*X)pnua]t~ʵ(_Ea+*8$I$bg49ςt꜇gWcc㛁>~oŇ[{ؿֱ?T!ecvS,nG`^K$,X?#DXuHFa柭k WUKw8QzšfZ)Hf!e†ܹf0//*h4M ;%Ia2 p.NJ_DL2 [rCܓD=ںQfZ_tLI$N%pü3_y%Cխ$InrBI$I$Id.I$I$I7Lv25MD֯#٫Fp3k7rbC<ՠ5$ItsosF..nkKﺍ-bpKjDrBOn'!8ޅ'~:3=>j Lz^hķ<^"WUcYQ<_nLs'ّ-=?vܨHI$IZLMXA3%;"6g*?mNY'()i'RIĶbՍHNIG{ %F]XdKm$mOeutIqJ÷bTm]ǎ7FĢj8jǢ |ɁZ錨( ~+mvSE!ۅw?R 0OvMc^KhlR\@,aRU1[(!FC."[9R1_<5Cmf*7Me.I$R2 /|Cξ"5ÊPzM*M'LGx  RHml(CSw͜py<'kcp X&G'.Lܐ ݛCj l]ԕLjeFLZa шS@:-%IۃL Rmʀ:~/̥2>*e'|JYUCΫSOL'݃ǩQ+FFx@ ]PJ:@SG`#ݒ$ImM&2E\ITčX&$B&34Bu5[V޸oS1/}WGDZM Kt?n+=\4{!hk_aX-NR2+Ok-7e"%I;L♭G MčIwgW((B:lPe ~#vG)_V fCWV Cؑ*2`bFT:)lfg_$dK|9o>d(c8nzLi;J88)Ñ^f<ȁ Ȑ=N)\8WsG?;)cҺiuIqä:N0ʥV<|K3j9!v99?8>b (a~~7oM*(L_=~K$IP2 SS#ؗH+ c W&ZaJ%&~JpEΡ1tobN_&yHѱ>aL6J 4 9A7/']Ad'CÙcH& }%)\jٻ(;]j%$!$$s` ėmv&Ԥ*;9̦j]ڙ:UMդlLxc{pl&l %uKw?[ -,$.WUWi~`YE ?qㅉ%D]ps5h/<3-cb`A)Bх.桍yHBuHa6~e=|o[84:elppV89:CTFslXQHܴiW:R.3\-j!汍!|6&7pEb&m !/t8>~Rxlp½xV*8W^~馺f돸]䗭)O_\ExSP#{KBQt( (b[' uÂ: &_tHI1s Dzr3q4 N].B1'$ 1,e$NW0|.Dys6\!w B V^˿X!wU!B9&!\!B9&!a'[DtٜAA.`DƗQm4+AU:BIMMN_3.wQ|52˶лKZNd&Fc$6񑳤qeC+[Ш:C a[?/u,wEkIK ܙ^|7HZU^¿{O7$Ps a7#>>x/|M~P!orJ|oi-|%ċk-ku]9W+KQwi6j!׸ /'oqzQ@aYOt)ũXͳ;c},~FLWg,Yxxe%V@ OzB!norjQ Դ[܃6}:;+vMܻHc JkT'mM H kQ)jlv>?RÑ+o)i@u[.[S!/o,cb =,c>/usŮ_qƿ8G1^bnL rV匮YHc%Y ^>C ZhZ5DZj8,~Q~f 1'l3 bMqߺ(DXn[u{x9AY6R,YuR熱>;P{3 &% B]̉՞¯2tq^u};_pd"~1lǻc)oY$N^:qݟJZ8㡸(*yW qeNbGQ ]1Nnp~$nJMʘO)ڋ'gclBJV1ኇt hM|_VL-̪ 7 Kޣ$f-LÍ$]NcFub_M]|= -whO&^i'gz}K)Ήmy"l]r pυVEWAQԖ[Gp6w$|1 _L2b^uaۻY@ XIiEG!s˨Bܪ$_l囆Ak˅>F3q[صr& V=j 0 V|9δ]y@G'.П 4P'XsHUNʫzjiZW99~}]en/y'Žղ8 ,9/N:z<ٖRAś`:LںBr0:MFB$TbQfjTufA2p{F/$^_XFٮTiwiDtu1ewr>hfqǿZ:N9kZۖi*B!nXϭBR_GL<,m,iBJ $8[(^r  fUqohV IDATKG?CZɕzPC.M|^h[u?CZuGTrT0DmSAA8{35("_;ȓfFoWoy |kIy=CK]?jaSFkLxI@SӼ5fږBq[Ezw dKh>j؇{LNe,/Pgxznx&[ W5bխ%4+'LHe fKZmhb1B|i3S?Wˉl}QG[4U+x~D5tfx" \4/NLj[(dd.Ϗ m.G^/'l;[ Q._f4˅biTco=A)\~glZ뛰=,t ;H% JTt>ޛ }9֜e^ˡԶWkKV=͏L)?)՞WgnK!IUa{}e>2j{(Zs=>Xy!zg_Gu$XF8qGc[KX#I+|{("Z_N܃Ma-wgߖ=Gjs'4.2 5Ud k,q*&3c|uZdK,clm)u}e/!X %K ]COf˅r}m<˓zjUqhtظtprbuӱi?̯k-tkLO] ص]SgFj[9'Cl[5cClM:ioHLB!nc3T&?[ -nVV߻ TT6/~C;L~:.{݋P#{KB;Ut( (b[' uÝ: &_uH7buegtiP\#!!\RϏ@d+A "nK:gk5B!Ib)?~^Z^~!SȧB!B1 B!B1 7lMeKHxd&1 4+A.B1o&| }Vlh4ߌEݿ~R,B ,Swʆj9ulǓqZ,OQRr'^tδArڭd}U#y&~{``a%Nj4eߝ|aoB!O>mbZjK/_";Zrv Z{qŹ|k\Å·'=\?f0E(fW_TrYGqFx>N?#߫3,l= ~)YS!ABjQ Դ[܃6}b^盏^Ƚ+4`(Ŀ.MxoZDXhşf#5B:e# ne+| !EBuc?Mgyѵ8S},m匮YHc%Y ^>C ZhA>­=*j"Q*[>+=Hu 9C5PcCT8_ZaYȅ1K_MوB^z]?ès[BKtPc;N.6|XK[HK-%ڏ!όу05g8a[krJdЁEF%YGT8 Hhla#5˒Uyj(un*w>jR YZoL٣ͼ;kNٜҫ(F?.G5 O/~揖gG׿B1$_?lhnKI.@`Tme`%5?R/U]\ľPa\!تd-;Yxh&]WCk` 4d5>eM~,P{{3{ o#gF(HG.qDMr% D6TXqMe<|G M4w7&U Z34?jvŜZPY**C:2#I]}P[Z>FeKna&RA90}n2[ISP o zI4a!)zu5r>RJO \WQ3oF.'ٱwQ:Sqfc]Q_M]|= -whO&^i'gz}K)Ήmy"l]r pυVEWAQԖ[Gp6w$|1 _L2b^uaۻY@ XIiEG!s-B:n>[a`nBNO֭@,`L|g tt*dU x\};
GYDs9$zC$φ>J͉i^QUT):g#֞sFrR^wUK*v~7uGuU:'*p{9;IW;Y`qpypɶ5 E( =y(HdNN*͔,95l2-wSۖ\  `8MC-.^H ]];ij~tOiP_yC/3QB"RrG$goczHT30sei`5h/|a0'B;uN緙1ds9紮-UB&dhZ>)r#E2MQ65RDGJ=STy_ƂSc`/ck8_AVӃo0։v|fkC&~;O3E6OI0&=dg8з48~}lCCקGTrT0Ή_y`*h:(6ҫghkH5v'=ޮ*~o)Z=֒4Jz4 2OזГJ 3mK!]Ht҃`%CO?XDQ>7=(dgK*h Լ^&iɖ1>U~j9O0Hbưjre>6ϏNP޽A"hta's84|>_{>Ndй,LKu\9x25=DQb19J}d\IOFMEU=/: Qu,# 8BExH"PC:XEVѰBOxa":3NX$ҲTcžn CPmy AJ.}({0NCT=u/{=%8=Gjs''Fɇ;QW%\MD2KA*:&όr:bK߲l,tb^S:e<4ivz\ȡ.),ϓB!bIB!bIc4]Gf@Irm[YqS!HMMN_3.wQ|2˶лKҢZNd&Fc$d޼;G~ߗx=BQ57LL /$wݪIVW=OII-nX G||"> +`ew'_(2eB܉ΛmbZjK/_-";Zrv Z{q sMAl#|OR4%-rC3}uT0z}D+9@MDN^7z|:2ND/ƮkY;(L8>S.׽87y68#X x}SK~cz7^YɯPSFBܩ$t?w?iL?m4m">Hiǰ)- m-kE! ;PC![Uؚd_{Fr-/p@6HxS)Ʈ|J?RÑ+*)S6ҀHBےe%+KXXnb?˘ϋqg o 9%w0 h5KJ2fWQ1P FY eHV81 J.I~֓GylrO P E-!3 US-u@[pF2_PrF׬a Zh/e!-%\CSgFõH[ fwQ:٥qn] )?^k5FM%2LOa[u{x9AY?P,YuR熱>;P{3 &% Bv2؍՟LcC.qDMr% D6TXQ-*VPO̙'"vq"4FԤj [kfOGNv}Q =K_e(NvLE&bR{WO=َwR+@K'/n8_O%-XiP\D[AU]\8YZVmk'⣨w^ފ~|v~8?K% !"K7dP%+t[TMLvq+xǓ2$V,%uj?.u5)YGvQP!x_ͻObՆf3fR}Լsw2zVGL TQOYUr2}61y ̀kjGVW?KBWL8LJRYR&x\hV:*R Uk^='{ Ls)9-O^ο}N`۪*(cF/O/x&m1/Ǻ䀎,_qԆJ,$ᴢ9 ŖB!'!n2M.([.4d $nl`y_5IOK"P wWYi**is͓ـnK ٮ2s輓tajYJO 'lKPc ZĉMDtQm]3`8 uN t !1 w2URaC2>BP0vuO~#IRm]XPQ3D j4a%o܍#8*0p{F/$^_XFٮTiwiDty3R)TSAy18`s)Ma`kF_1šaf?Ojw8o3+csi]o[ v !NopG1T _ ,m,L]BP65RxGrbT:2*bO(A\Y啸heL0V~@_2ߪŅfI-}z7wy`5I~]L?l&S`|f,AFy*pcjXb%WA 轫ѶjC a5+U4&P-ZDR8'~ ..m(B ԁQn*h:(6L'{ՇikHap_p=:^ə60lvUUΒ[KQ'Fy}4ꀫpdazqm = Xj؇{3Kt$M77Y'c EȖH r%芁tGY?O}- ),agQ,v4_.jzD%tE/ D ;9D[*7n#DGNJkkwf񪵜{v!Ijb:7F 41DS!̩ȥQr'u$EcX5 2``Ze 72 D R/i<_(T%зg4Ĺke`Ďtmw2Q].D]_OJ{ J@h7o&lcxeK4?AIuUe0/2Ar9vCmc[;֖ッ%z=S~R*=?qݖB1$*0=>x25=DQb19J}d\IOFMEU=/: Qu,# 8BEԨÚ\W:2X4cK /k&QQBY쨡'ViY@*PJZѱ a-r ŶKUa|NPqkJ\aӸ4ԐVU4ǩ8^όQNUk 7W-Q8~3\R=pK RPrt?3GxQ.dwY7m+ ɘ#}BǾ6^]|p];5ufS}2ĶU<1٤tH̤-!bVe½B~T,ǖ 7+`+ ]o*L/~C*B/|Ooߤ2$Kֿ䧯qA")}% !\?!?EnQ ^x$?=^{x%G qP ²l3btiP Bq.KIDATRϏ@d+A 0ʝownJ`5B1$K~^'b)?~^Z^~!wiB!sLBB!sLB c'[DtY- "B@j//ʙ ѻ]" e[]]cNoM:I\m#gIҚhkFВz2>'?1S׽C~*ߗxv:F5?w NZY$W3 Cs +ሏ?^ħa,l }SBqD0mbZjKhlzzz5aX^tF-3wFF 5j_kaG5g1ӥcv6n(3 pw1u^dݯx㕕Z5_>%kW !HB?J]?_As3p=hA,';]ktoRTW+&ImRd%t#}7M`a5sT'mM´XAroq5?EGj8ߓB:e# ne+a !: Გ%~W,YT7eE~N=N{}} #%> !;c_B/2́BUO_s2W0l/տzx3]7w9Ɏ})VIG'X*q"x,@ i:]T[W1SPPHBQp{\wP!DiD H_| ޡ쥬Vg 7S Uf-ERu,i EKf^?yxh/]M26SAA8{35_;ȓfFoWoy |kIy=CK]uA}CQ2zjؗE =(dgK*h Լ^&iɖ1>ե}`bhuz~ 6͊ Sr3hkRB'(mfg^ Pȕz1HĤm3HcMf2"8ǏI-' FIlVBj+KF{dTG+cq_ub!8X.d]6hao #dחR>;9{RZ5G67a+{Xv J*|7Ar9vCmc[;֖ッ%z=S~R*=?qݖB!IBt0=>x25=DQbQ9J}d\IOFMEU=/: Qu,# 8BEȢ~N1dsi$..xfk:>3Fɇ;QW%\MD2KA*:&VDkaZuI}H"PC:XEVѰBO,s]jsn!S@QzwTtߨ2.dwY7mB!bIB!bIB!br!ܺkjv!Bq;uu>ޅa/6zydBg!BqEB?W?,Xha0!\!BZnVti''Ip_B!B\[&܌3:amOS}#B!p({& y?7/RҸB!!oo84V#_k˔B!3r˄pQ_0X<}B!3pk4_| &޲ N B![cv?Ji6ݵk5:?;R(B!n%FWx3,GQ(n||$ åB!(Š<:D9 hsΕ}ͦ ʄ7˄{p?f-;'.qYBL {pˑ1T.~0yd|bB!=LW 21O.gvKӘωlNQxa|<[ۻa Ky YHpqΙIJS׼c-{!~)$5!'RXgtׯ :b9%(94sKV\Zs}nQ%{1G,#-q]x๬ExoYJ.=͙٠ef{,G)~9 NܦL"3r%[,D8O{l̬<?b9 kG|o&;D|pAp^u& 9-Uaz&[#^znj:c%>pv .kyl 7eMZck{\;û p2ߠYFxa^RGwR/8zDW zw5`[:G=yy}ET|-8kfC~>zD{~=sDyyGpN(%cp>ޛk%[Xֱ>/6ȭީo4-=}"g6LEغ-!>ݽ ;dGw.|9c-&ڳ;5gۺo]/>gvsT.!jt r(g.0[-A-Ֆ><ӽw}7{ w݃QBQ!v33^gIENDB`libtree-3.1.1/libtree.c000066400000000000000000001566661423742124400147630ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #define VERSION "3.1.1" #define ET_EXEC 2 #define ET_DYN 3 #define PT_NULL 0 #define PT_LOAD 1 #define PT_DYNAMIC 2 #define DT_NULL 0 #define DT_NEEDED 1 #define DT_STRTAB 5 #define DT_SONAME 14 #define DT_RPATH 15 #define DT_RUNPATH 29 #define BITS32 1 #define BITS64 2 #define ERR_INVALID_MAGIC 11 #define ERR_INVALID_CLASS 12 #define ERR_INVALID_DATA 13 #define ERR_INVALID_HEADER 14 #define ERR_INVALID_BITS 15 #define ERR_INVALID_ENDIANNESS 16 #define ERR_NO_EXEC_OR_DYN 17 #define ERR_INVALID_PHOFF 18 #define ERR_INVALID_PROG_HEADER 19 #define ERR_CANT_STAT 20 #define ERR_INVALID_DYNAMIC_SECTION 21 #define ERR_INVALID_DYNAMIC_ARRAY_ENTRY 22 #define ERR_NO_STRTAB 23 #define ERR_INVALID_SONAME 24 #define ERR_INVALID_RPATH 25 #define ERR_INVALID_RUNPATH 26 #define ERR_INVALID_NEEDED 27 #define ERR_DEPENDENCY_NOT_FOUND 28 #define ERR_NO_PT_LOAD 29 #define ERR_VADDRS_NOT_ORDERED 30 #define ERR_COULD_NOT_OPEN_FILE 31 #define ERR_INCOMPATIBLE_ISA 32 #define DT_FLAGS_1 0x6ffffffb #define DT_1_NODEFLIB 0x800 #define MAX_OFFSET_T 0xFFFFFFFFFFFFFFFF #define REGULAR_RED "\033[0;31m" #define BOLD_RED "\033[1;31m" #define CLEAR "\033[0m" #define BOLD_YELLOW "\033[33m" #define BOLD_CYAN "\033[1;36m" #define REGULAR_CYAN "\033[0;36m" #define REGULAR_MAGENTA "\033[0;35m" #define REGULAR_BLUE "\033[0;34m" #define BRIGHT_BLACK "\033[0;90m" #define REGULAR "\033[0m" // don't judge me. #define LIGHT_HORIZONTAL "\xe2\x94\x80" #define LIGHT_QUADRUPLE_DASH_VERTICAL "\xe2\x94\x8a" #define LIGHT_UP_AND_RIGHT "\xe2\x94\x94" #define LIGHT_VERTICAL "\xe2\x94\x82" #define LIGHT_VERTICAL_AND_RIGHT "\xe2\x94\x9c" #define JUST_INDENT " " #define LIGHT_VERTICAL_WITH_INDENT LIGHT_VERTICAL " " #define SMALL_VEC_SIZE 16 #define MAX_RECURSION_DEPTH 32 #define MAX_PATH_LENGTH 4096 // Libraries we do not show by default -- this reduces the verbosity quite a // bit. char const *exclude_list[] = {"ld-linux-aarch64.so", "ld-linux-armhf.so", "ld-linux-x86-64.so", "ld-linux.so", "ld64.so", "libc.musl-aarch64.so", "libc.musl-armhf.so", "libc.musl-i386.so", "libc.musl-x86_64.so", "libc.so", "libdl.so", "libgcc_s.so", "libm.so", "libstdc++.so"}; struct header_64_t { uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint64_t e_entry; uint64_t e_phoff; uint64_t e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; }; struct header_32_t { uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint32_t e_entry; uint32_t e_phoff; uint32_t e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; }; struct prog_64_t { uint32_t p_type; uint32_t p_flags; uint64_t p_offset; uint64_t p_vaddr; uint64_t p_paddr; uint64_t p_filesz; uint64_t p_memsz; uint64_t p_align; }; struct prog_32_t { uint32_t p_type; uint32_t p_offset; uint32_t p_vaddr; uint32_t p_paddr; uint32_t p_filesz; uint32_t p_memsz; uint32_t p_flags; uint32_t p_align; }; struct dyn_64_t { int64_t d_tag; uint64_t d_val; }; struct dyn_32_t { int32_t d_tag; uint32_t d_val; }; struct compat_t { char any; // 1 iff we don't look for libs matching a certain architecture uint8_t class; // 32 or 64 bits? uint16_t machine; // instruction set }; typedef enum { INPUT, DIRECT, RPATH, LD_LIBRARY_PATH, RUNPATH, LD_SO_CONF, DEFAULT } how_t; struct found_t { how_t how; // only set when found by in the rpath NOT of the direct parent. so, when // it is found in a "special" way only rpaths allow, which is worth // informing the user about. size_t depth; }; // large buffer in which to copy rpaths, needed libraries and sonames. struct string_table_t { char *arr; size_t n; size_t capacity; }; struct visited_file_t { dev_t st_dev; ino_t st_ino; }; struct visited_file_array_t { struct visited_file_t *arr; size_t n; size_t capacity; }; struct libtree_state_t { int verbosity; int path; int color; char *ld_conf_file; unsigned long max_depth; struct string_table_t string_table; struct visited_file_array_t visited; // rpath substitutions values (note: OSNAME/OSREL are FreeBSD specific, LIB // is glibc/Linux specific -- we substitute all so we can support // cross-compiled binaries). char *PLATFORM; char *LIB; char *OSNAME; char *OSREL; // rpath stack: if lib_a needs lib_b needs lib_c and all have rpaths // then first lib_c's rpaths are considered, then lib_b's, then lib_a's. // so this data structure keeps a list of offsets into the string buffer // where rpaths start, like [lib_a_rpath_offset, lib_b_rpath_offset, // lib_c_rpath_offset]... size_t rpath_offsets[MAX_RECURSION_DEPTH]; size_t ld_library_path_offset; size_t default_paths_offset; size_t ld_so_conf_offset; // This is so we know we have to print a | or white space // in the tree char found_all_needed[MAX_RECURSION_DEPTH]; }; // Keep track of the files we've see /** * small_vec_u64 is an array that lives on the stack until it grows to the heap */ struct small_vec_u64_t { uint64_t buf[SMALL_VEC_SIZE]; uint64_t *p; size_t n; size_t capacity; }; static inline void utoa(char *str, size_t v) { char *p = str; do { *p++ = '0' + (v % 10); v /= 10; } while (v > 0); size_t len = p - str; for (size_t i = 0; i < len / 2; i++) { char tmp = str[i]; str[i] = str[len - i - 1]; str[len - i - 1] = tmp; } str[len] = '\0'; } static inline void small_vec_u64_init(struct small_vec_u64_t *v) { memset(v, 0, sizeof(*v)); v->p = v->buf; } static void small_vec_u64_append(struct small_vec_u64_t *v, uint64_t val) { // The hopefully likely path if (v->n < SMALL_VEC_SIZE) { v->p[v->n++] = val; return; } // The slow fallback on the heap if (v->n == SMALL_VEC_SIZE) { v->capacity = 2 * SMALL_VEC_SIZE; v->p = malloc(v->capacity * sizeof(uint64_t)); if (v->p == NULL) exit(1); memcpy(v->p, v->buf, SMALL_VEC_SIZE * sizeof(uint64_t)); } else if (v->n == v->capacity) { v->capacity *= 2; uint64_t *p = realloc(v->p, v->capacity * sizeof(uint64_t)); if (p == NULL) exit(1); v->p = p; } v->p[v->n++] = val; } static void small_vec_u64_free(struct small_vec_u64_t *v) { if (v->n <= SMALL_VEC_SIZE) return; free(v->p); v->p = NULL; } /** * end of small_vec_u64_t */ static inline int host_is_little_endian() { int test = 1; char *bytes = (char *)&test; return bytes[0] == 1; } static int is_ascending_order(uint64_t *v, size_t n) { for (size_t j = 1; j < n; ++j) if (v[j - 1] >= v[j]) return 0; return 1; } static void string_table_maybe_grow(struct string_table_t *t, size_t n) { // The likely case of not having to resize if (t->n + n <= t->capacity) return; // Otherwise give twice the amount of required space. t->capacity = 2 * (t->n + n); char *arr = realloc(t->arr, t->capacity * sizeof(char)); if (arr == NULL) { exit(1); } t->arr = arr; } static void string_table_store(struct string_table_t *t, char const *str) { size_t n = strlen(str) + 1; string_table_maybe_grow(t, n); memcpy(t->arr + t->n, str, n); t->n += n; } static void string_table_copy_from_file(struct string_table_t *t, FILE *fptr) { int c; // TODO: this could be a bit more efficient... while ((c = getc(fptr)) != '\0' && c != EOF) { string_table_maybe_grow(t, 1); t->arr[t->n++] = c; } string_table_maybe_grow(t, 1); t->arr[t->n++] = '\0'; } static int is_in_exclude_list(char *soname) { // Get to the end. char *start = soname; char *end = strrchr(start, '\0'); // Empty needed string, is that even possible? if (start == end) return 0; --end; // Strip "1234567890." from the right. while (end != start && ((*end >= '0' && *end <= '9') || *end == '.')) { --end; } // Check if we should skip this one. for (size_t j = 0; j < sizeof(exclude_list) / sizeof(char *); ++j) { size_t len = strlen(exclude_list[j]); if (strncmp(start, exclude_list[j], len) != 0) continue; return 1; } return 0; } static void tree_preamble(struct libtree_state_t *s, size_t depth) { if (depth == 0) return; for (size_t i = 0; i < depth - 1; ++i) fputs(s->found_all_needed[i] ? JUST_INDENT : LIGHT_VERTICAL_WITH_INDENT, stdout); fputs(s->found_all_needed[depth - 1] ? LIGHT_UP_AND_RIGHT LIGHT_HORIZONTAL LIGHT_HORIZONTAL " " : LIGHT_VERTICAL_AND_RIGHT LIGHT_HORIZONTAL LIGHT_HORIZONTAL " ", stdout); } static int recurse(char *current_file, size_t depth, struct libtree_state_t *state, struct compat_t compat, struct found_t reason); static void apply_exclude_list(size_t *needed_not_found, struct small_vec_u64_t *needed_buf_offsets, struct libtree_state_t *s) { for (size_t i = 0; i < *needed_not_found;) { // If in exclude list, swap to the back. if (is_in_exclude_list(s->string_table.arr + needed_buf_offsets->p[i])) { size_t tmp = needed_buf_offsets->p[i]; needed_buf_offsets->p[i] = needed_buf_offsets->p[*needed_not_found - 1]; needed_buf_offsets->p[--*needed_not_found] = tmp; continue; } else { ++i; } } } static int check_absolute_paths(size_t *needed_not_found, struct small_vec_u64_t *needed_buf_offsets, size_t depth, struct libtree_state_t *s, struct compat_t compat) { int exit_code = 0; // First go over absolute paths in needed libs. for (size_t i = 0; i < *needed_not_found;) { struct string_table_t const *st = &s->string_table; // Skip dt_needed that have do not contain / if (strchr(st->arr + needed_buf_offsets->p[i], '/') == NULL) { ++i; continue; } // Copy the path over. char path[MAX_PATH_LENGTH]; size_t len = strlen(st->arr + needed_buf_offsets->p[i]); // Unlikely to happen but good to guard against if (len >= MAX_PATH_LENGTH) continue; // Include \0 memcpy(path, st->arr + needed_buf_offsets->p[i], len + 1); s->found_all_needed[depth] = *needed_not_found <= 1; char *err = NULL; // If it is not an absolute path, we bail, cause it then starts to // depend on the current working directory, which is rather // nonsensical. This is allowed by glibc though. if (path[0] != '/') { err = " is not absolute"; exit_code = ERR_DEPENDENCY_NOT_FOUND; } else { int code = recurse(path, depth + 1, s, compat, (struct found_t){.how = DIRECT}); if (code == ERR_DEPENDENCY_NOT_FOUND) exit_code = ERR_DEPENDENCY_NOT_FOUND; // Check if there was an issue with the direct dep and ignore errors // of transient deps. if (code != 0 && code != ERR_DEPENDENCY_NOT_FOUND) { err = " not found"; } } if (err) { tree_preamble(s, depth + 1); if (s->color) fputs(BOLD_RED, stdout); fputs(path, stdout); fputs(" is not absolute", stdout); fputs(s->color ? CLEAR "\n" : "\n", stdout); } // Handled this library, so swap to the back. size_t tmp = needed_buf_offsets->p[i]; needed_buf_offsets->p[i] = needed_buf_offsets->p[*needed_not_found - 1]; needed_buf_offsets->p[--*needed_not_found] = tmp; } return exit_code; } static int check_search_paths(struct found_t reason, size_t offset, size_t *needed_not_found, struct small_vec_u64_t *needed_buf_offsets, size_t depth, struct libtree_state_t *s, struct compat_t compat) { int exit_code = 0; char path[MAX_PATH_LENGTH]; char *path_end = path + MAX_PATH_LENGTH; struct string_table_t const *st = &s->string_table; while (st->arr[offset] != '\0') { // First remove trailing colons while (st->arr[offset] == ':' && st->arr[offset] != '\0') ++offset; // Check if it was only colons if (st->arr[offset] == '\0') return exit_code; // Copy the search path until the first \0 or : char *dest = path; while (st->arr[offset] != '\0' && st->arr[offset] != ':' && dest != path_end) *dest++ = st->arr[offset++]; // Path too long... Can't handle. if (dest + 1 >= path_end) continue; // Add a separator if necessary if (*(dest - 1) != '/') *dest++ = '/'; // Keep track of the end of the current search path. char *search_path_end = dest; // Try to open it -- if we've found anything, swap it with the back. for (size_t i = 0; i < *needed_not_found;) { size_t soname_len = strlen(st->arr + needed_buf_offsets->p[i]); // Path too long, can't handle. if (search_path_end + soname_len + 1 >= path_end) continue; // Otherwise append. memcpy(search_path_end, st->arr + needed_buf_offsets->p[i], soname_len + 1); s->found_all_needed[depth] = *needed_not_found <= 1; // And try to locate the lib. int code = recurse(path, depth + 1, s, compat, reason); if (code == ERR_DEPENDENCY_NOT_FOUND) exit_code = ERR_DEPENDENCY_NOT_FOUND; if (code == 0 || code == ERR_DEPENDENCY_NOT_FOUND) { // Found at least the direct dependency, so swap out the current // soname to the back and reduce the number of to be found by // one. size_t tmp = needed_buf_offsets->p[i]; needed_buf_offsets->p[i] = needed_buf_offsets->p[*needed_not_found - 1]; needed_buf_offsets->p[--(*needed_not_found)] = tmp; } else { ++i; } } } return exit_code; } static int interpolate_variables(struct libtree_state_t *s, size_t src, char const *ORIGIN) { // We do not write to dst if there is no variables to interpolate. size_t prev_src = src; size_t curr_src = src; struct string_table_t *st = &s->string_table; while (1) { // Find the next potential variable. char *dollar = strchr(st->arr + curr_src, '$'); if (dollar == NULL) break; curr_src = dollar - st->arr; size_t bytes_to_dollar = curr_src - prev_src; // Go past the dollar. ++curr_src; // Remember if we have to look for matching curly braces. int curly = 0; if (st->arr[curr_src] == '{') { curly = 1; ++curr_src; } // String to interpolate. char const *var_val = NULL; if (strncmp(&st->arr[curr_src], "ORIGIN", 6) == 0) { var_val = ORIGIN; curr_src += 6; } else if (strncmp(&st->arr[curr_src], "LIB", 3) == 0) { var_val = s->LIB; curr_src += 3; } else if (strncmp(&st->arr[curr_src], "PLATFORM", 8) == 0) { var_val = s->PLATFORM; curr_src += 8; } else if (strncmp(&st->arr[curr_src], "OSNAME", 6) == 0) { var_val = s->OSNAME; curr_src += 6; } else if (strncmp(&st->arr[curr_src], "OSREL", 5) == 0) { var_val = s->OSREL; curr_src += 5; } else { continue; } // Require matching {...}. if (curly) { if (st->arr[curr_src] != '}') { continue; } ++curr_src; } size_t var_len = strlen(var_val); // Make sure we have enough space to write to. string_table_maybe_grow(st, bytes_to_dollar + var_len); // First copy over the string until the variable. memcpy(&st->arr[s->string_table.n], &st->arr[prev_src], bytes_to_dollar); s->string_table.n += bytes_to_dollar; // Then move prev_src until after the variable. prev_src = curr_src; // Then copy the variable value (without null). memcpy(&st->arr[s->string_table.n], var_val, var_len); s->string_table.n += var_len; } // Did we copy anything? That implies a variable was interpolated. // Copy the remainder, including the \0. if (prev_src != src) { size_t n = strlen(st->arr + prev_src) + 1; string_table_maybe_grow(st, n); // note: we're copying from within the string table, so we // should not store st->arr + prev_src. memcpy(st->arr + st->n, st->arr + prev_src, n); st->n += n; return 1; } return 0; } static void print_colon_delimited_paths(char const *start, char const *indent) { while (1) { // Don't print empty string if (*start == '\0') break; // Find the next delimiter after start char *next = strchr(start, ':'); // Don't print empty strings if (start == next) { ++start; continue; } fputs(indent, stdout); fputs(JUST_INDENT, stdout); // Print up to but not including : or \0, followed by a newline. if (next == NULL) { puts(start); } else { fwrite(start, 1, next - start, stdout); putchar('\n'); } // We done yet? if (next == NULL) break; // Otherwise put the : back in place and continue. start = next + 1; } } static void print_line(size_t depth, char *name, char *color_bold, char *color_regular, int highlight, struct found_t reason, struct libtree_state_t *s) { tree_preamble(s, depth); // Color the filename different than the path name, if we have a path. char *slash = NULL; if (s->color && highlight && (slash = strrchr(name, '/')) != NULL) { fputs(color_regular, stdout); fwrite(name, 1, slash + 1 - name, stdout); fputs(color_bold, stdout); fputs(slash + 1, stdout); } else { if (s->color) fputs(color_bold, stdout); fputs(name, stdout); } if (s->color && highlight) fputs(CLEAR " " BOLD_YELLOW, stdout); else putchar(' '); switch (reason.how) { case RPATH: if (reason.depth + 1 >= depth) { fputs("[rpath]", stdout); } else { char num[8]; utoa(num, reason.depth + 1); fputs("[rpath of ", stdout); fputs(num, stdout); putchar(']'); } break; case LD_LIBRARY_PATH: fputs("[LD_LIBRARY_PATH]", stdout); break; case RUNPATH: fputs("[runpath]", stdout); break; case LD_SO_CONF: putchar('['); char *conf_name = strrchr(s->ld_conf_file, '/'); conf_name = conf_name == NULL ? s->ld_conf_file : conf_name + 1; fputs(conf_name, stdout); putchar(']'); break; case DIRECT: fputs("[direct]", stdout); break; case DEFAULT: fputs("[default path]", stdout); break; default: break; } if (s->color) fputs(CLEAR "\n", stdout); else putchar('\n'); } static void print_error(size_t depth, size_t needed_not_found, struct small_vec_u64_t *needed_buf_offsets, char *runpath, struct libtree_state_t *s, int no_def_lib) { for (size_t i = 0; i < needed_not_found; ++i) { s->found_all_needed[depth] = i + 1 >= needed_not_found; tree_preamble(s, depth + 1); if (s->color) fputs(BOLD_RED, stdout); fputs(s->string_table.arr + needed_buf_offsets->p[i], stdout); fputs(" not found\n", stdout); if (s->color) fputs(CLEAR, stdout); } // If anything was not found, we print the search paths in order they // are considered. char *box_vertical = s->color ? JUST_INDENT REGULAR_RED LIGHT_QUADRUPLE_DASH_VERTICAL CLEAR : JUST_INDENT LIGHT_QUADRUPLE_DASH_VERTICAL; char *indent = malloc(sizeof(LIGHT_VERTICAL_WITH_INDENT) * depth + strlen(box_vertical) + 1); char *p = indent; for (size_t i = 0; i < depth; ++i) { if (s->found_all_needed[i]) { int len = sizeof(JUST_INDENT) - 1; memcpy(p, JUST_INDENT, len); p += len; } else { int len = sizeof(LIGHT_VERTICAL_WITH_INDENT) - 1; memcpy(p, LIGHT_VERTICAL_WITH_INDENT, len); p += len; } } // dotted | in red strcpy(p, box_vertical); fputs(indent, stdout); if (s->color) fputs(BRIGHT_BLACK, stdout); fputs(" Paths considered in this order:\n", stdout); if (s->color) fputs(CLEAR, stdout); // Consider rpaths only when runpath is empty fputs(indent, stdout); if (runpath != NULL) { if (s->color) fputs(BRIGHT_BLACK, stdout); fputs(" 1. rpath is skipped because runpath was set\n", stdout); if (s->color) fputs(CLEAR, stdout); } else { if (s->color) fputs(BRIGHT_BLACK, stdout); fputs(" 1. rpath:\n", stdout); if (s->color) fputs(CLEAR, stdout); for (int j = depth; j >= 0; --j) { if (s->rpath_offsets[j] != SIZE_MAX) { char num[8]; utoa(num, j + 1); fputs(indent, stdout); if (s->color) fputs(BRIGHT_BLACK, stdout); fputs(" depth ", stdout); fputs(num, stdout); if (s->color) fputs(CLEAR, stdout); putchar('\n'); print_colon_delimited_paths( s->string_table.arr + s->rpath_offsets[j], indent); } } } // Environment variables fputs(indent, stdout); if (s->color) fputs(BRIGHT_BLACK, stdout); fputs(s->ld_library_path_offset == SIZE_MAX ? " 2. LD_LIBRARY_PATH was not set\n" : " 2. LD_LIBRARY_PATH:\n", stdout); if (s->color) fputs(CLEAR, stdout); if (s->ld_library_path_offset != SIZE_MAX) print_colon_delimited_paths( s->string_table.arr + s->ld_library_path_offset, indent); // runpath fputs(indent, stdout); if (s->color) fputs(BRIGHT_BLACK, stdout); fputs(runpath == NULL ? " 3. runpath was not set\n" : " 3. runpath:\n", stdout); if (s->color) fputs(CLEAR, stdout); if (runpath != NULL) print_colon_delimited_paths(runpath, indent); fputs(indent, stdout); if (s->color) fputs(BRIGHT_BLACK, stdout); fputs(no_def_lib ? " 4. ld config files not considered due to NODEFLIB flag\n" : " 4. ld config files:\n", stdout); if (s->color) fputs(CLEAR, stdout); print_colon_delimited_paths(s->string_table.arr + s->ld_so_conf_offset, indent); fputs(indent, stdout); if (s->color) fputs(BRIGHT_BLACK, stdout); fputs(no_def_lib ? " 5. Standard paths not considered due to NODEFLIB flag\n" : " 5. Standard paths:\n", stdout); if (s->color) fputs(CLEAR, stdout); print_colon_delimited_paths(s->string_table.arr + s->default_paths_offset, indent); free(indent); } static int visited_files_contains(struct visited_file_array_t *files, struct stat *needle) { for (size_t i = 0; i < files->n; ++i) { struct visited_file_t *f = &files->arr[i]; if (f->st_dev == needle->st_dev && f->st_ino == needle->st_ino) return 1; } return 0; } static void visited_files_append(struct visited_file_array_t *files, struct stat *new) { if (files->n == files->capacity) { files->capacity *= 2; files->arr = realloc(files->arr, files->capacity * sizeof(struct visited_file_t)); if (files->arr == NULL) exit(1); } files->arr[files->n].st_dev = new->st_dev; files->arr[files->n].st_ino = new->st_ino; ++files->n; } static int recurse(char *current_file, size_t depth, struct libtree_state_t *s, struct compat_t compat, struct found_t reason) { FILE *fptr = fopen(current_file, "rb"); if (fptr == NULL) return ERR_COULD_NOT_OPEN_FILE; // When we're done recursing, we should give back the memory we've claimed. size_t old_buf_size = s->string_table.n; // Parse the header char e_ident[16]; if (fread(&e_ident, 16, 1, fptr) != 1) { fclose(fptr); return ERR_INVALID_MAGIC; } // Find magic elfs if (e_ident[0] != 0x7f || e_ident[1] != 'E' || e_ident[2] != 'L' || e_ident[3] != 'F') { fclose(fptr); return ERR_INVALID_MAGIC; } // Do at least *some* header validation if (e_ident[4] != BITS32 && e_ident[4] != BITS64) { fclose(fptr); return ERR_INVALID_CLASS; } if (e_ident[5] != '\x01' && e_ident[5] != '\x02') { fclose(fptr); return ERR_INVALID_DATA; } struct compat_t curr_type = {.any = 0, .class = e_ident[4]}; int is_little_endian = e_ident[5] == '\x01'; // Make sure that we have matching bits with parent if (!compat.any && compat.class != curr_type.class) { fclose(fptr); return ERR_INVALID_BITS; } // Make sure that the elf file has a the host's endianness // Byte swapping is on the TODO list if (is_little_endian ^ host_is_little_endian()) { fclose(fptr); return ERR_INVALID_ENDIANNESS; } // And get the type union { struct header_64_t h64; struct header_32_t h32; } header; // Read the (rest of the) elf header if (curr_type.class == BITS64) { if (fread(&header.h64, sizeof(struct header_64_t), 1, fptr) != 1) { fclose(fptr); return ERR_INVALID_HEADER; } if (header.h64.e_type != ET_EXEC && header.h64.e_type != ET_DYN) { fclose(fptr); return ERR_NO_EXEC_OR_DYN; } curr_type.machine = header.h64.e_machine; if (!compat.any && compat.machine != curr_type.machine) { fclose(fptr); return ERR_INCOMPATIBLE_ISA; } if (fseek(fptr, header.h64.e_phoff, SEEK_SET) != 0) { fclose(fptr); return ERR_INVALID_PHOFF; } } else { if (fread(&header.h32, sizeof(struct header_32_t), 1, fptr) != 1) { fclose(fptr); return ERR_INVALID_HEADER; } if (header.h32.e_type != ET_EXEC && header.h32.e_type != ET_DYN) { fclose(fptr); return ERR_NO_EXEC_OR_DYN; } curr_type.machine = header.h32.e_machine; if (!compat.any && compat.machine != curr_type.machine) { fclose(fptr); return ERR_INCOMPATIBLE_ISA; } if (fseek(fptr, header.h32.e_phoff, SEEK_SET) != 0) { fclose(fptr); return ERR_INVALID_PHOFF; } } // Make sure it's an executable or library union { struct prog_64_t p64; struct prog_32_t p32; } prog; // map vaddr to file offset (we don't mmap the file, but directly seek in // the file which means that we have to translate vaddr to file offset) struct small_vec_u64_t pt_load_offset; struct small_vec_u64_t pt_load_vaddr; small_vec_u64_init(&pt_load_offset); small_vec_u64_init(&pt_load_vaddr); // Read the program header. uint64_t p_offset = MAX_OFFSET_T; if (curr_type.class == BITS64) { for (uint64_t i = 0; i < header.h64.e_phnum; ++i) { if (fread(&prog.p64, sizeof(struct prog_64_t), 1, fptr) != 1) { fclose(fptr); small_vec_u64_free(&pt_load_offset); small_vec_u64_free(&pt_load_vaddr); return ERR_INVALID_PROG_HEADER; } if (prog.p64.p_type == PT_LOAD) { small_vec_u64_append(&pt_load_offset, prog.p64.p_offset); small_vec_u64_append(&pt_load_vaddr, prog.p64.p_vaddr); } else if (prog.p64.p_type == PT_DYNAMIC) { p_offset = prog.p64.p_offset; } } } else { for (uint32_t i = 0; i < header.h32.e_phnum; ++i) { if (fread(&prog.p32, sizeof(struct prog_32_t), 1, fptr) != 1) { fclose(fptr); small_vec_u64_free(&pt_load_offset); small_vec_u64_free(&pt_load_vaddr); return ERR_INVALID_PROG_HEADER; } if (prog.p32.p_type == PT_LOAD) { small_vec_u64_append(&pt_load_offset, prog.p32.p_offset); small_vec_u64_append(&pt_load_vaddr, prog.p32.p_vaddr); } else if (prog.p32.p_type == PT_DYNAMIC) { p_offset = prog.p32.p_offset; } } } // At this point we're going to store the file as "success" struct stat finfo; if (stat(current_file, &finfo) != 0) { fclose(fptr); small_vec_u64_free(&pt_load_offset); small_vec_u64_free(&pt_load_vaddr); return ERR_CANT_STAT; } int seen_before = visited_files_contains(&s->visited, &finfo); if (!seen_before) visited_files_append(&s->visited, &finfo); // No dynamic section? if (p_offset == MAX_OFFSET_T) { print_line(depth, current_file, BOLD_CYAN, REGULAR_CYAN, 1, reason, s); fclose(fptr); small_vec_u64_free(&pt_load_offset); small_vec_u64_free(&pt_load_vaddr); return 0; } // I guess you always have to load at least a string // table, so if there are not PT_LOAD sections, then // it is an error. if (pt_load_offset.n == 0) { fclose(fptr); small_vec_u64_free(&pt_load_offset); small_vec_u64_free(&pt_load_vaddr); return ERR_NO_PT_LOAD; } // Go to the dynamic section if (fseek(fptr, p_offset, SEEK_SET) != 0) { fclose(fptr); small_vec_u64_free(&pt_load_offset); small_vec_u64_free(&pt_load_vaddr); return ERR_INVALID_DYNAMIC_SECTION; } // Shared libraries can disable searching in // "default" search paths, aka ld.so.conf and // /usr/lib etc. At least glibc respects this. int no_def_lib = 0; uint64_t strtab = MAX_OFFSET_T; uint64_t rpath = MAX_OFFSET_T; uint64_t runpath = MAX_OFFSET_T; uint64_t soname = MAX_OFFSET_T; // Offsets in strtab struct small_vec_u64_t needed; small_vec_u64_init(&needed); for (int cont = 1; cont;) { uint64_t d_tag; uint64_t d_val; if (curr_type.class == BITS64) { struct dyn_64_t dyn; if (fread(&dyn, sizeof(struct dyn_64_t), 1, fptr) != 1) { fclose(fptr); small_vec_u64_free(&pt_load_offset); small_vec_u64_free(&pt_load_vaddr); small_vec_u64_free(&needed); return ERR_INVALID_DYNAMIC_ARRAY_ENTRY; } d_tag = dyn.d_tag; d_val = dyn.d_val; } else { struct dyn_32_t dyn; if (fread(&dyn, sizeof(struct dyn_32_t), 1, fptr) != 1) { fclose(fptr); small_vec_u64_free(&pt_load_offset); small_vec_u64_free(&pt_load_vaddr); small_vec_u64_free(&needed); return ERR_INVALID_DYNAMIC_ARRAY_ENTRY; } d_tag = dyn.d_tag; d_val = dyn.d_val; } // Store strtab / rpath / runpath / needed / soname info. switch (d_tag) { case DT_NULL: cont = 0; break; case DT_STRTAB: strtab = d_val; break; case DT_RPATH: rpath = d_val; break; case DT_RUNPATH: runpath = d_val; break; case DT_NEEDED: small_vec_u64_append(&needed, d_val); break; case DT_SONAME: soname = d_val; break; case DT_FLAGS_1: no_def_lib |= (DT_1_NODEFLIB & d_val) == DT_1_NODEFLIB; break; } } if (strtab == MAX_OFFSET_T) { fclose(fptr); small_vec_u64_free(&pt_load_offset); small_vec_u64_free(&pt_load_vaddr); small_vec_u64_free(&needed); return ERR_NO_STRTAB; } // Let's verify just to be sure that the offsets are // ordered. if (!is_ascending_order(pt_load_vaddr.p, pt_load_vaddr.n)) { fclose(fptr); small_vec_u64_free(&pt_load_vaddr); small_vec_u64_free(&pt_load_offset); small_vec_u64_free(&needed); return ERR_VADDRS_NOT_ORDERED; } // Find the file offset corresponding to the strtab virtual address size_t vaddr_idx = 0; while (vaddr_idx + 1 != pt_load_vaddr.n && strtab >= pt_load_vaddr.p[vaddr_idx + 1]) { ++vaddr_idx; } uint64_t strtab_offset = pt_load_offset.p[vaddr_idx] + strtab - pt_load_vaddr.p[vaddr_idx]; small_vec_u64_free(&pt_load_vaddr); small_vec_u64_free(&pt_load_offset); // From this point on we actually copy strings from the ELF file into our // own string buffer. // Copy the current soname size_t soname_buf_offset = s->string_table.n; if (soname != MAX_OFFSET_T) { if (fseek(fptr, strtab_offset + soname, SEEK_SET) != 0) { s->string_table.n = old_buf_size; fclose(fptr); small_vec_u64_free(&needed); return ERR_INVALID_SONAME; } string_table_copy_from_file(&s->string_table, fptr); } int in_exclude_list = soname != MAX_OFFSET_T && is_in_exclude_list(s->string_table.arr + soname_buf_offset); // No need to recurse deeper when we aren't in very verbose mode. int should_recurse = depth < s->max_depth && ((!seen_before && !in_exclude_list) || (!seen_before && in_exclude_list && s->verbosity >= 2) || s->verbosity >= 3); // Just print the library and return if (!should_recurse) { char *print_name = soname == MAX_OFFSET_T || s->path ? current_file : (s->string_table.arr + soname_buf_offset); char *bold_color = in_exclude_list ? REGULAR_MAGENTA : seen_before ? REGULAR_BLUE : BOLD_CYAN; char *regular_color = in_exclude_list ? REGULAR_MAGENTA : seen_before ? REGULAR_BLUE : REGULAR_CYAN; int highlight = !seen_before && !in_exclude_list; print_line(depth, print_name, bold_color, regular_color, highlight, reason, s); s->string_table.n = old_buf_size; fclose(fptr); small_vec_u64_free(&needed); return 0; } // Store the ORIGIN string. char origin[MAX_PATH_LENGTH]; char *last_slash = strrchr(current_file, '/'); if (last_slash != NULL) { // Exclude the last slash size_t bytes = last_slash - current_file; memcpy(origin, current_file, bytes); origin[bytes] = '\0'; } else { // this only happens when the input is relative (e.g. in current dir) memcpy(origin, "./", 3); } // Copy DT_PRATH if (rpath == MAX_OFFSET_T) { s->rpath_offsets[depth] = SIZE_MAX; } else { s->rpath_offsets[depth] = s->string_table.n; if (fseek(fptr, strtab_offset + rpath, SEEK_SET) != 0) { s->string_table.n = old_buf_size; fclose(fptr); small_vec_u64_free(&needed); return ERR_INVALID_RPATH; } string_table_copy_from_file(&s->string_table, fptr); // We store the interpolated string right after the literal copy. size_t curr_buf_size = s->string_table.n; if (interpolate_variables(s, s->rpath_offsets[depth], origin)) s->rpath_offsets[depth] = curr_buf_size; } // Copy DT_RUNPATH size_t runpath_buf_offset = s->string_table.n; if (runpath != MAX_OFFSET_T) { if (fseek(fptr, strtab_offset + runpath, SEEK_SET) != 0) { s->string_table.n = old_buf_size; fclose(fptr); small_vec_u64_free(&needed); return ERR_INVALID_RUNPATH; } string_table_copy_from_file(&s->string_table, fptr); // We store the interpolated string right after the literal copy. size_t curr_buf_size = s->string_table.n; if (interpolate_variables(s, runpath_buf_offset, origin)) runpath_buf_offset = curr_buf_size; } // Copy needed libraries. struct small_vec_u64_t needed_buf_offsets; small_vec_u64_init(&needed_buf_offsets); for (size_t i = 0; i < needed.n; ++i) { small_vec_u64_append(&needed_buf_offsets, s->string_table.n); if (fseek(fptr, strtab_offset + needed.p[i], SEEK_SET) != 0) { s->string_table.n = old_buf_size; fclose(fptr); small_vec_u64_free(&needed_buf_offsets); small_vec_u64_free(&needed); return ERR_INVALID_NEEDED; } string_table_copy_from_file(&s->string_table, fptr); } fclose(fptr); char *print_name = soname == MAX_OFFSET_T || s->path ? current_file : (s->string_table.arr + soname_buf_offset); char *bold_color = in_exclude_list ? REGULAR_MAGENTA : seen_before ? REGULAR_BLUE : BOLD_CYAN; char *regular_color = in_exclude_list ? REGULAR_MAGENTA : seen_before ? REGULAR_BLUE : REGULAR_CYAN; int highlight = !seen_before && !in_exclude_list; print_line(depth, print_name, bold_color, regular_color, highlight, reason, s); // Finally start searching. int exit_code = 0; size_t needed_not_found = needed_buf_offsets.n; // Skip common libraries if not verbose if (needed_not_found && s->verbosity == 0) apply_exclude_list(&needed_not_found, &needed_buf_offsets, s); if (needed_not_found) exit_code |= check_absolute_paths( &needed_not_found, &needed_buf_offsets, depth, s, curr_type); // Consider rpaths only when runpath is empty if (runpath == MAX_OFFSET_T) { // We have a stack of rpaths, try them all, starting with one set at // this lib, then the parents. for (int j = depth; j >= 0 && needed_not_found; --j) { if (s->rpath_offsets[j] == SIZE_MAX) continue; exit_code |= check_search_paths( (struct found_t){.how = RPATH, .depth = j}, s->rpath_offsets[j], &needed_not_found, &needed_buf_offsets, depth, s, curr_type); } } // Then try LD_LIBRARY_PATH, if we have it. if (needed_not_found && s->ld_library_path_offset != SIZE_MAX) { exit_code |= check_search_paths( (struct found_t){.how = LD_LIBRARY_PATH}, s->ld_library_path_offset, &needed_not_found, &needed_buf_offsets, depth, s, curr_type); } // Then consider runpaths if (needed_not_found && runpath != MAX_OFFSET_T) { exit_code |= check_search_paths( (struct found_t){.how = RUNPATH}, runpath_buf_offset, &needed_not_found, &needed_buf_offsets, depth, s, curr_type); } // Check ld.so.conf paths if (needed_not_found && !no_def_lib) { exit_code |= check_search_paths( (struct found_t){.how = LD_SO_CONF}, s->ld_so_conf_offset, &needed_not_found, &needed_buf_offsets, depth, s, curr_type); } // Then consider standard paths if (needed_not_found && !no_def_lib) { exit_code |= check_search_paths( (struct found_t){.how = DEFAULT}, s->default_paths_offset, &needed_not_found, &needed_buf_offsets, depth, s, curr_type); } // Finally summarize those that could not be found. if (needed_not_found) { print_error(depth, needed_not_found, &needed_buf_offsets, runpath == MAX_OFFSET_T ? NULL : s->string_table.arr + runpath_buf_offset, s, no_def_lib); s->string_table.n = old_buf_size; small_vec_u64_free(&needed_buf_offsets); small_vec_u64_free(&needed); return ERR_DEPENDENCY_NOT_FOUND; } // Free memory in our string table s->string_table.n = old_buf_size; small_vec_u64_free(&needed_buf_offsets); small_vec_u64_free(&needed); return exit_code; } static int parse_ld_config_file(struct string_table_t *st, char *path); static int ld_conf_globbing(struct string_table_t *st, char *pattern) { glob_t result; memset(&result, 0, sizeof(result)); int status = glob(pattern, 0, NULL, &result); // Handle errors (no result is not an error...) switch (status) { case GLOB_NOSPACE: case GLOB_ABORTED: globfree(&result); return 1; case GLOB_NOMATCH: globfree(&result); return 0; } // Otherwise parse the files we've found! int code = 0; for (size_t i = 0; i < result.gl_pathc; ++i) code |= parse_ld_config_file(st, result.gl_pathv[i]); globfree(&result); return code; } static int parse_ld_config_file(struct string_table_t *st, char *path) { FILE *fptr = fopen(path, "r"); if (fptr == NULL) return 1; int c = 0; char line[MAX_PATH_LENGTH]; char tmp[MAX_PATH_LENGTH]; while (c != EOF) { size_t line_len = 0; while ((c = getc(fptr)) != '\n' && c != EOF) { if (line_len < MAX_PATH_LENGTH - 1) { line[line_len++] = c; } } line[line_len] = '\0'; char *begin = line; char *end = line + line_len; // Remove leading whitespace for (; isspace(*begin); ++begin) { } // Remove trailing comments char *comment = strchr(begin, '#'); if (comment != NULL) *comment = '\0'; // Remove trailing whitespace while (end != begin) if (!isspace(*--end)) break; // Skip empty lines if (begin == end) continue; // Put back the end of the string end[1] = '\0'; // 'include ': glob whatever follows. if (strncmp(begin, "include", 7) == 0 && isspace(begin[7])) { begin += 8; // Remove more whitespace. while (isspace(*begin)) ++begin; // Prepend current dir when include dir is relative. if (*begin != '/') { char *wd = strrchr(path, '/'); wd = wd == NULL ? strrchr(path, '\0') : wd; // bytes until / size_t wd_len = wd - path; size_t include_len = end - begin + 1; // just skip then. if (wd_len + 1 + include_len >= MAX_PATH_LENGTH) continue; memcpy(tmp, path, wd_len); tmp[wd_len] = '/'; memcpy(tmp + wd_len + 1, begin, include_len); tmp[wd_len + 1 + include_len] = '\0'; begin = tmp; } ld_conf_globbing(st, begin); } else { // Copy over and replace trailing \0 with :. string_table_store(st, begin); st->arr[st->n - 1] = ':'; } } fclose(fptr); return 0; } static void parse_ld_so_conf(struct libtree_state_t *s) { struct string_table_t *st = &s->string_table; s->ld_so_conf_offset = st->n; // Linux / glibc parse_ld_config_file(st, s->ld_conf_file); // Replace the last semicolon with a '\0' // if we have a nonzero number of paths. if (st->n > s->ld_so_conf_offset) { st->arr[st->n - 1] = '\0'; } else { string_table_store(st, ""); } } static void parse_ld_library_path(struct libtree_state_t *s) { s->ld_library_path_offset = SIZE_MAX; char *val = getenv("LD_LIBRARY_PATH"); // not set, so nothing to do. if (val == NULL) return; s->ld_library_path_offset = s->string_table.n; // otherwise, we just copy it over and replace ; with : string_table_store(&s->string_table, val); // replace ; with : char *search = s->string_table.arr + s->ld_library_path_offset; while ((search = strchr(search, ';')) != NULL) *search++ = ':'; } static void set_default_paths(struct libtree_state_t *s) { s->default_paths_offset = s->string_table.n; // TODO: how to retrieve this list properly at runtime? string_table_store(&s->string_table, "/lib:/lib64:/usr/lib:/usr/lib64"); } static void libtree_state_init(struct libtree_state_t *s) { s->string_table.n = 0; s->string_table.capacity = 1024; s->string_table.arr = malloc(s->string_table.capacity * sizeof(char)); s->visited.n = 0; s->visited.capacity = 256; s->visited.arr = malloc(s->visited.capacity * sizeof(struct visited_file_t)); } static void libtree_state_free(struct libtree_state_t *s) { free(s->string_table.arr); free(s->visited.arr); } static int print_tree(int pathc, char **pathv, struct libtree_state_t *s) { // First collect standard paths libtree_state_init(s); parse_ld_so_conf(s); parse_ld_library_path(s); set_default_paths(s); int exit_code = 0; for (int i = 0; i < pathc; ++i) { int code = recurse(pathv[i], 0, s, (struct compat_t){.any = 1}, (struct found_t){.how = INPUT}); fflush(stdout); if (code != 0) { exit_code = code; fputs("Error [", stderr); fputs(pathv[i], stderr); fputs("]: ", stderr); } char *msg = NULL; switch (code) { case ERR_INVALID_MAGIC: msg = "Invalid ELF magic bytes\n"; break; case ERR_INVALID_CLASS: msg = "Invalid ELF class\n"; break; case ERR_INVALID_DATA: msg = "Invalid ELF data\n"; break; case ERR_INVALID_HEADER: msg = "Invalid ELF header\n"; break; case ERR_INVALID_BITS: msg = "Invalid bits\n"; break; case ERR_INVALID_ENDIANNESS: msg = "Invalid endianness\n"; break; case ERR_NO_EXEC_OR_DYN: msg = "Not an ET_EXEC or ET_DYN ELF file\n"; break; case ERR_INVALID_PHOFF: msg = "Invalid ELF program header offset\n"; break; case ERR_INVALID_PROG_HEADER: msg = "Invalid ELF program header\n"; break; case ERR_CANT_STAT: msg = "Can't stat file\n"; break; case ERR_INVALID_DYNAMIC_SECTION: msg = "Invalid ELF dynamic section\n"; break; case ERR_INVALID_DYNAMIC_ARRAY_ENTRY: msg = "Invalid ELF dynamic array entry\n"; break; case ERR_NO_STRTAB: msg = "No ELF string table found\n"; break; case ERR_INVALID_SONAME: msg = "Can't read DT_SONAME\n"; break; case ERR_INVALID_RPATH: msg = "Can't read DT_RPATH\n"; break; case ERR_INVALID_RUNPATH: msg = "Can't read DT_RUNPATH\n"; break; case ERR_INVALID_NEEDED: msg = "Can't read DT_NEEDED\n"; break; case ERR_DEPENDENCY_NOT_FOUND: msg = "Not all dependencies were found\n"; break; case ERR_NO_PT_LOAD: msg = "No PT_LOAD found in ELF file\n"; break; case ERR_VADDRS_NOT_ORDERED: msg = "Virtual addresses are not ordered\n"; break; case ERR_COULD_NOT_OPEN_FILE: msg = "Could not open file\n"; break; case ERR_INCOMPATIBLE_ISA: msg = "Incompatible ISA\n"; break; } if (msg != NULL) fputs(msg, stderr); fflush(stderr); } libtree_state_free(s); return exit_code; } int main(int argc, char **argv) { // Enable or disable colors (no-color.com) struct libtree_state_t s; s.color = getenv("NO_COLOR") == NULL && isatty(STDOUT_FILENO); s.verbosity = 0; s.path = 0; s.max_depth = MAX_RECURSION_DEPTH; // We want to end up with an array of file names // in argv[1] up to argv[positional-1]. int positional = 1; struct utsname uname_val; if (uname(&uname_val) != 0) return 1; // Technically this should be AT_PLATFORM, but // (a) the feature is rarely used // (b) it's almost always the same s.PLATFORM = uname_val.machine; s.OSNAME = uname_val.sysname; s.OSREL = uname_val.release; s.ld_conf_file = "/etc/ld.so.conf"; if (strcmp(uname_val.sysname, "FreeBSD") == 0) s.ld_conf_file = "/etc/ld-elf.so.conf"; // TODO: how to find this value at runtime? s.LIB = "lib"; int opt_help = 0; int opt_version = 0; // After `--` we treat everything as filenames, not flags. int opt_raw = 0; for (int i = 1; i < argc; ++i) { char *arg = argv[i]; // Positional args don't start with - or are `-` literal. if (opt_raw || *arg != '-' || arg[1] == '\0') { argv[positional++] = arg; continue; } // Now we're in flag land! ++arg; // Long flags if (*arg == '-') { ++arg; // Literal '--' if (*arg == '\0') { opt_raw = 1; continue; } if (strcmp(arg, "version") == 0) { opt_version = 1; } else if (strcmp(arg, "path") == 0) { s.path = 1; } else if (strcmp(arg, "verbose") == 0) { ++s.verbosity; } else if (strcmp(arg, "help") == 0) { opt_help = 1; } else if (strcmp(arg, "ldconf") == 0) { // Require a value if (i + 1 == argc) { fputs("Expected value after `--ldconf`\n", stderr); return 1; } s.ld_conf_file = argv[++i]; } else if (strcmp(arg, "max-depth") == 0) { // Require a value if (i + 1 == argc) { fputs("Expected value after `--max-depth`\n", stderr); return 1; } // Limit it by MAX_RECURSION_DEPTH. char *ptr; s.max_depth = strtoul(argv[++i], &ptr, 10); if (s.max_depth > MAX_RECURSION_DEPTH) s.max_depth = MAX_RECURSION_DEPTH; } else { fputs("Unrecognized flag `--", stderr); fputs(arg, stderr); fputs("`\n", stderr); return 1; } continue; } // Short flags for (; *arg != '\0'; ++arg) { switch (*arg) { case 'h': opt_help = 1; break; case 'p': s.path = 1; break; case 'v': ++s.verbosity; break; default: fputs("Unrecognized flag `-", stderr); fputs(arg, stderr); fputs("`\n", stderr); return 1; } } } ++argv; --positional; // Print a help message on -h, --help or no positional args. if (opt_help || (!opt_version && positional == 0)) { // clang-format off fputs("Show the dynamic dependency tree of ELF files\n" "Usage: libtree [OPTION]... [--] FILE [FILES]...\n" "\n" " -h, --help Print help info\n" " --version Print version info\n" "\n" "File names starting with '-', for example '-.so', can be specified as follows:\n" " libtree -- -.so\n" "\n" "Locating libs options:\n" " -p, --path Show the path of libraries instead of the soname\n" " -v Show libraries skipped by default*\n" " -vv Show dependencies of libraries skipped by default*\n" " -vvv Show dependencies of already encountered libraries\n" " --ldconf Config file for extra search paths [", stdout); fputs(s.ld_conf_file, stdout); fputs("]\n" " --max-depth Limit library traversal to at most n levels of depth\n" "\n" "* For brevity, the following libraries are not shown by default:\n" " ", stdout); // clang-format on // Print a comma separated list of skipped libraries, // with some new lines every now and then to make it readable. size_t num_excluded = sizeof(exclude_list) / sizeof(char *); size_t cursor_x = 3; for (size_t j = 0; j < num_excluded; ++j) { cursor_x += strlen(exclude_list[j]); if (cursor_x > 60) { cursor_x = 3; fputs("\n ", stdout); } fputs(exclude_list[j], stdout); if (j + 1 != num_excluded) fputs(", ", stdout); } // rpath substitution values: fputs(".\n\nThe following rpath/runpath substitutions are used:\n", stdout); fputs(" PLATFORM ", stdout); fputs(s.PLATFORM, stdout); fputs("\n LIB ", stdout); fputs(s.LIB, stdout); fputs("\n OSNAME ", stdout); fputs(s.OSNAME, stdout); fputs("\n OSREL ", stdout); fputs(s.OSREL, stdout); putchar('\n'); // Return an error status code if no positional args were passed. return !opt_help; } if (opt_version) { puts(VERSION); return 0; } return print_tree(positional, argv, &s); } libtree-3.1.1/tests/000077500000000000000000000000001423742124400143075ustar00rootroot00000000000000libtree-3.1.1/tests/01_origin/000077500000000000000000000000001423742124400160765ustar00rootroot00000000000000libtree-3.1.1/tests/01_origin/Makefile000066400000000000000000000012611423742124400175360ustar00rootroot00000000000000# Basic test of $ORIGIN interpolation in rpath & runpath. .PHONY: clean LD_LIBRARY_PATH= all: check liba.so: echo 'int f(){return 1;}' | $(CC) -shared -Wl,-soname,$@ -o $@ -nostdlib -x c - exe_rpath: liba.so echo 'int _start(){return f();}' | $(CC) -o $@ -Wl,--no-as-needed -Wl,--disable-new-dtags '-Wl,-rpath,$$ORIGIN' -Wno-implicit-function-declaration -nostdlib liba.so -x c - exe_runpath: liba.so echo 'int _start(){return f();}' | $(CC) -o $@ -Wl,--no-as-needed -Wl,--enable-new-dtags '-Wl,-rpath,$$ORIGIN' -Wno-implicit-function-declaration -nostdlib liba.so -x c - check: exe_rpath exe_runpath ../../libtree exe_rpath ../../libtree exe_runpath clean: rm -f *.so exe* libtree-3.1.1/tests/02_rpath_of_parents_parent/000077500000000000000000000000001423742124400215175ustar00rootroot00000000000000libtree-3.1.1/tests/02_rpath_of_parents_parent/Makefile000066400000000000000000000016231423742124400231610ustar00rootroot00000000000000# This creates exe <- liba.so <- libb.so # where liba.so cannot directly locate libb.so, but it *can* through exe's rpaths. LD_LIBRARY_PATH= .PHONY: clean all: check libb.so: echo 'int g(){return 1;}' | $(CC) -shared -Wl,-soname,$@ -o $@ -nostdlib -x c - liba.so: libb.so echo 'int f(){return g();}' | $(CC) -shared -Wl,--no-as-needed -Wl,-soname,$@ -o $@ -Wno-implicit-function-declaration libb.so -nostdlib -x c - exe: liba.so echo 'int _start(){return f();}' | $(CC) -o $@ -Wl,--no-as-needed -Wl,--disable-new-dtags '-Wl,-rpath,$$ORIGIN' '-Wl,-rpath-link,$(CURDIR)' -Wno-implicit-function-declaration -nostdlib -L. -la -x c - check: exe liba.so ! ../../libtree liba.so # should not find libb.so LD_LIBRARY_PATH=$(CURDIR) ../../libtree liba.so # should find libb.so through LD_LIBRARY_PATH ../../libtree exe # should find libb.so through exe's rpath clean: rm -f *.so exe* CURDIR ?= $(.CURDIR) libtree-3.1.1/tests/03_direct_and_absolute_rpath/000077500000000000000000000000001423742124400220015ustar00rootroot00000000000000libtree-3.1.1/tests/03_direct_and_absolute_rpath/Makefile000066400000000000000000000022631423742124400234440ustar00rootroot00000000000000LD_LIBRARY_PATH= .PHONY: clean all: check some_dir/lib_f.so: mkdir some_dir echo 'int f(){return 42;}' | $(CC) -shared -Wl,-soname,$(@F) -Wl,--no-as-needed -o $@ -x c - lib_g.so: some_dir/lib_f.so echo 'extern int f(); int g(){return f();}' | $(CC) -shared -Wl,-soname,$@ -Wl,--no-as-needed "-Wl,-rpath,$(CURDIR)/some_dir" -L./some_dir -l_f -o $@ -x c - lib_without_soname.so: echo 'int i(){return 1;}' | $(CC) -shared -Wl,--no-as-needed -o $@ -x c - exe_a: some_dir/lib_f.so lib_g.so lib_without_soname.so echo 'extern int i(); extern int f(); extern int g(); int main(){return f() + g() + i();}' | $(CC) -Wl,--no-as-needed "-Wl,-rpath,$(CURDIR)/" -L. -L./some_dir -l_f -l_g $(CURDIR)/lib_without_soname.so -o $@ -x c - exe_b: some_dir/lib_f.so lib_g.so lib_without_soname.so echo 'extern int i(); extern int f(); extern int g(); int main(){return f() + g() + i();}' | $(CC) -Wl,--no-as-needed "-Wl,-rpath,$(CURDIR)/" "-Wl,-rpath,$(CURDIR)/some_dir" -L. -L./some_dir -l_f -l_g $(CURDIR)/lib_without_soname.so -o $@ -x c - check: exe_a exe_b ! ../../libtree exe_a # cannot find lib_f.so ../../libtree exe_b # should find lib_f.so clean: rm -rf *.so some_dir exe* CURDIR ?= $(.CURDIR) libtree-3.1.1/tests/04_rpath_over_env_over_runpath/000077500000000000000000000000001423742124400224275ustar00rootroot00000000000000libtree-3.1.1/tests/04_rpath_over_env_over_runpath/Makefile000066400000000000000000000025341423742124400240730ustar00rootroot00000000000000LD_LIBRARY_PATH= # test whether RUNPATH < LD_LIBRARY_PATH < RPATH # the exe's both need libb.so, but there are two of those: # ./dir/libb.so needs liba.so # ./libb.so needs nothing. .PHONY: clean all: check dir: mkdir $@ dir/liba.so: dir echo 'int a(){return 42;}' | $(CC) -shared -Wl,-soname,$(@F) -Wl,--no-as-needed -o $@ -nostdlib -x c - dir/libb.so: dir/liba.so echo 'int b(){return a();}' | $(CC) -shared -Wl,-soname,$(@F) -Wl,--no-as-needed -Wl,--disable-new-dtags '-Wl,-rpath,$$ORIGIN' -Wno-implicit-function-declaration -o $@ -nostdlib dir/liba.so -x c - libb.so: echo 'int b(){return 10;}' | $(CC) -shared -Wl,-soname,$(@F) -Wl,--no-as-needed -o $@ -Wno-implicit-function-declaration -nostdlib -x c - exe_rpath: libb.so echo 'int _start(){return b();}' | $(CC) -Wl,--no-as-needed -Wl,--disable-new-dtags "-Wl,-rpath,$(CURDIR)" libb.so -o $@ -Wno-implicit-function-declaration -nostdlib -x c - exe_runpath: libb.so echo 'int _start(){return b();}' | $(CC) -Wl,--no-as-needed -Wl,--enable-new-dtags "-Wl,-rpath,$(CURDIR)" libb.so -o $@ -Wno-implicit-function-declaration -nostdlib -x c - check: exe_rpath exe_runpath dir/libb.so ../../libtree exe_rpath LD_LIBRARY_PATH="$(CURDIR)/dir" ../../libtree exe_rpath ../../libtree exe_runpath LD_LIBRARY_PATH="$(CURDIR)/dir" ../../libtree exe_runpath clean: rm -rf *.so dir exe* CURDIR ?= $(.CURDIR) libtree-3.1.1/tests/05_32_bits/000077500000000000000000000000001423742124400160605ustar00rootroot00000000000000libtree-3.1.1/tests/05_32_bits/Makefile000066400000000000000000000015271423742124400175250ustar00rootroot00000000000000# Test 32 bits exectuables and libraries # Make sure the 64 bits versions are not picked up. LD_LIBRARY_PATH= .PHONY: clean all: check lib64/libx.so: mkdir -p $(@D) echo 'int a(){return 42;}' | $(CC) -shared -Wl,-soname,$(@F) -m64 -o $@ -nostdlib -x c - lib32/libx.so: mkdir -p $(@D) echo 'int a(){return 42;}' | $(CC) -shared -Wl,-soname,$(@F) -m32 -o $@ -nostdlib -x c - exe64: lib64/libx.so echo 'extern int a(); int _start(){return a();}' | $(CC) -m64 "-Wl,-rpath,$(CURDIR)/lib32" "-Wl,-rpath,$(CURDIR)/lib64" -o $@ -nostdlib -x c - -Llib64 -lx exe32: lib32/libx.so echo 'extern int a(); int _start(){return a();}' | $(CC) -m32 "-Wl,-rpath,$(CURDIR)/lib64" "-Wl,-rpath,$(CURDIR)/lib32" -o $@ -nostdlib -x c - -Llib32 -lx check: exe32 exe64 ../../libtree exe32 ../../libtree exe64 clean: rm -rf lib32 lib64 exe* CURDIR ?= $(.CURDIR) libtree-3.1.1/tests/06_symbol_versions/000077500000000000000000000000001423742124400200515ustar00rootroot00000000000000libtree-3.1.1/tests/06_symbol_versions/Makefile000066400000000000000000000021101423742124400215030ustar00rootroot00000000000000# Creates two versions of a library with the same (so)name, # and same symbol xyz, but versioned VER_V1 and VER_V2. # As far as I understand, ld.so does not continue to look # for another library in the search path which provides the # correct symbol versions (e.g. link to VER_V2, put the VER_V1 # version first in the search ath, and glibc fixes this lib # and simply errors). LD_LIBRARY_PATH= .PHONY: clean all: check v1/libx.so: v1.c v1.map v2/libx.so: v2.c v2.map v1/libx.so v2/libx.so: mkdir -p $(@D) $(CC) -shared -Wl,-soname,$(@F) -o $@ -Wl,--version-script,$(@D).map $(@D).c # this one is fine, link to v1, use v2 which provides the v1 symbol. exe_v1: main.c v1/libx.so v2/libx.so $(CC) -o $@ main.c v1/libx.so "-Wl,-rpath,$(CURDIR)/v2" "-Wl,-rpath,$(CURDIR)/v1" # this one is not fine, link to v2, use v1 which does not provide the v2 symbol exe_v2: main.c v1/libx.so v2/libx.so $(CC) -o $@ main.c v2/libx.so "-Wl,-rpath,$(CURDIR)/v1" "-Wl,-rpath,$(CURDIR)/v2" check: exe_v1 exe_v2 ../../libtree exe_v1 ../../libtree exe_v2 clean: rm -rf v1 v2 exe* CURDIR ?= $(.CURDIR) libtree-3.1.1/tests/06_symbol_versions/main.c000066400000000000000000000000601423742124400211350ustar00rootroot00000000000000extern int xyz(); int main() { return xyz(); } libtree-3.1.1/tests/06_symbol_versions/v1.c000066400000000000000000000000301423742124400205340ustar00rootroot00000000000000int xyz() { return 3; } libtree-3.1.1/tests/06_symbol_versions/v1.map000066400000000000000000000000521423742124400210730ustar00rootroot00000000000000VER_1 { global: xyz; local: *; }; libtree-3.1.1/tests/06_symbol_versions/v2.c000066400000000000000000000002071423742124400205430ustar00rootroot00000000000000__asm__(".symver xyz_old,xyz@VER_1"); __asm__(".symver xyz_new,xyz@@VER_2"); int xyz_old() { return 3; } int xyz_new() { return 4; } libtree-3.1.1/tests/06_symbol_versions/v2.map000066400000000000000000000001251423742124400210750ustar00rootroot00000000000000VER_1 { global: xyz; local: *; }; VER_2 { global: xyz; local: *; }; libtree-3.1.1/tests/07_origin_is_relative_to_symlink_location_not_realpath/000077500000000000000000000000001423742124400273725ustar00rootroot00000000000000libtree-3.1.1/tests/07_origin_is_relative_to_symlink_location_not_realpath/Makefile000066400000000000000000000017031423742124400310330ustar00rootroot00000000000000# ORIGIN should be resolved relative to the directory the lib was found in, # even if what we've found is a symlink. So this test creates exe <- a/libf <- # a/libg and a symlink at b/libf to a/libf, where a/libf has ${ORIGIN} rpath. LD_LIBRARY_PATH= .PHONY: clean all: check a/libf.so: mkdir -p $(@D) echo 'int f(){return 3;};' | $(CC) -shared -Wl,-soname,$(@F) -o $@ -x c - -nostdlib a/libg.so: a/libf.so mkdir -p $(@D) echo 'extern int f(); int g(){return f();};' | $(CC) -shared -Wl,-soname,$(@F) '-Wl,-rpath,$${ORIGIN}' -o $@ -x c - -La -lf -nostdlib b/libg.so: a/libg.so mkdir -p $(@D) ln -vs ../a/libg.so $@ exe: a/libg.so b/libg.so echo 'extern int g(); int _start(){return g();};' | $(CC) -Wl,-soname,$(@F) '-Wl,-rpath,$${ORIGIN}/b' -o $@ -x c - -La -lg -nostdlib check: exe ! ../../libtree exe # should not find libf.so LD_LIBRARY_PATH=$(CURDIR)/a ../../libtree exe # should find libf.so clean: rm -rf a b exe* CURDIR ?= $(.CURDIR) libtree-3.1.1/tests/08_nodeflib/000077500000000000000000000000001423742124400164005ustar00rootroot00000000000000libtree-3.1.1/tests/08_nodeflib/Makefile000066400000000000000000000026701423742124400200450ustar00rootroot00000000000000# When passing -z nodefaultlib, the runtime linker should not consider # ld.so.conf nor standard paths (/usr/lib, /lib, ...). This is a little bit # hard to test but in general it means that if you use the system compiler with # this flag, libc itself would not be located. # # There's two tests here: # 1. exe_a (itself nodefaultlib) depends on a lib with and a lib without -z # nodefaultlib # 2. exe_b (itself nodefaultlib) depends on a lib without nodefaultlib. # # The question is whether or not libc can be located as a lib in the default # paths. (The expected behavior is not well documented...) .PHONY: clean check LD_LIBRARY_PATH= all: check lib_nodefaultlib.so: echo 'int f(){return 1;}' | $(CC) -z nodefaultlib -Wl,--no-as-needed -shared -Wl,-soname,$@ -o $@ -x c - lib_defaultlib.so: echo 'int g(){return 1;}' | $(CC) -Wl,--no-as-needed -shared -Wl,-soname,$@ -o $@ -x c - exe_a: lib_nodefaultlib.so lib_defaultlib.so echo 'extern int f(); extern int g(); int main(){return f() + g();}' | $(CC) -z nodefaultlib -o $@ -Wl,--enable-new-dtags '-Wl,-rpath,$$ORIGIN' -x c - -L. -l_nodefaultlib -l_defaultlib exe_b: lib_defaultlib.so echo 'extern int g(); int main(){return g();}' | $(CC) -z nodefaultlib -o $@ -Wl,--disable-new-dtags '-Wl,-rpath,$$ORIGIN' -x c - -L. -l_defaultlib check: exe_a exe_b ! ../../libtree -vvv exe_a # should likely not find libc ! ../../libtree -vvv exe_b # should likely not find libc clean: rm -f *.so exe* libtree-3.1.1/tests/10_rpath_order/000077500000000000000000000000001423742124400171205ustar00rootroot00000000000000libtree-3.1.1/tests/10_rpath_order/Makefile000066400000000000000000000017321423742124400205630ustar00rootroot00000000000000# This creates exe <- liba.so <- libb.so There are two libraries: # {first,second}/libb.so and exe has an rpath to first, whereas liba.so has an # rpath to second LD_LIBRARY_PATH= .PHONY: clean all: check first/libb.so: @mkdir -p $(@D) echo 'int g(){return 1;}' | $(CC) -shared -Wl,-soname,$(@F) -o $@ -x c - second/libb.so: @mkdir -p $(@D) echo 'int g(){return 2;}' | $(CC) -shared -Wl,-soname,$(@F) -o $@ -x c - liba.so: second/libb.so echo 'extern int g(); int f(){return g();}' | $(CC) -shared -Wl,--disable-new-dtags -Wl,-soname,$@ -o $@ '-Wl,-rpath,$$ORIGIN/second' -x c - -Lsecond -lb exe: first/libb.so liba.so echo 'extern int f(); int main(){return f();}' | $(CC) -o $@ -Wl,--disable-new-dtags '-Wl,-rpath,$$ORIGIN,-rpath,$$ORIGIN/first' -x c - -L. -la check: exe liba.so ../../libtree -p liba.so # should find second/libb.so, not first/libb.so ../../libtree -p exe # should find second/libb.so, not first/libb.so clean: rm -rf -- *.so exe* first second