├── .clang-format ├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── doc ├── libtree.1 └── screenshot.png ├── libtree.c └── tests ├── 01_origin └── Makefile ├── 02_rpath_of_parents_parent └── Makefile ├── 03_direct_and_absolute_rpath └── Makefile ├── 04_rpath_over_env_over_runpath └── Makefile ├── 05_32_bits └── Makefile ├── 06_symbol_versions ├── Makefile ├── main.c ├── v1.c ├── v1.map ├── v2.c └── v2.map ├── 07_origin_is_relative_to_symlink_location_not_realpath └── Makefile ├── 08_nodeflib └── Makefile └── 10_rpath_order └── Makefile /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 12 | - run: make 13 | - run: make check 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | exe* 4 | libtree 5 | *.swp 6 | Make.user 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v3.1.1 2 | - Build system portability fixes 3 | - Fix make check exit code 4 | 5 | # v3.1.0 6 | - Add a `--max-depth` flag to limit recursion depth. For example, 7 | `libtree --max-depth 1 ` will show the resolved direct dependencies only. 8 | 9 | # v3.0.4 10 | - Build system portability fixes 11 | - Fix `make check` exit code 12 | 13 | # v3.0.3 14 | - `libtree -vvvv...` is now treated as `libtree -vvv`. 15 | 16 | # v3.0.2 17 | - Improve `make check`, `make clean` and CI 18 | - Preserve original timestamps when installing files 19 | - Add rpath order test 20 | 21 | # v3.0.1 22 | - Fix man pages directory in `make install` 23 | - Skip dynamic linker on aarch64 and powerpc 24 | 25 | # v3.0.0 26 | - Rewritten in C99 with 0 external dependencies. 27 | - Significantly faster & smaller (~50KB statically compiled with musl libc, or 28 | even smaller than the source file with diet libc). 29 | - Cross-compiled binaries now available thanks to 30 | [binarybuilder.org](https://binarybuilder.org/) 31 | - Improved search path printing when libraries cannot be located. 32 | - Improved rpath search: shows `[rpath of ...]` when lib is located by parent 33 | of parent ... of parent's rpath. 34 | - `fd` inspired highlight of filename when printing paths. 35 | - Caches files by inode instead of soname, which is useful in the sense that 36 | this allows you to find broken libraries that only work because of a 37 | particular search order of the tree. (Consider an executable A and libraries 38 | B, C and D, where A depends on B and C, and B and C depend on D: 39 | 40 | ``` 41 | B 42 | / \ 43 | A D 44 | \ / 45 | C 46 | ``` 47 | 48 | It may happen that D *can* be located through B's rpath, but not through C's. 49 | Then, depending on whether A - B - D is traversed first, or A - C - D, ld.so 50 | will complain about missing libraries or not. `libtree` on the other hand 51 | will always tell you that D can't be located through C. 52 | - More verbosity levels `-v`, `-vv`, `-vvv` instead of `-a` and `-v` flags. 53 | - Skip fewer libraries by default (only libc / libstdc++ type of libs). 54 | - `PLATFORM` rpath interpolation now uses `uname`, this is not always the same 55 | as `AT_PLATFORM`, but unlikely to be different, and in fact the feature is 56 | rarely used. 57 | - Support for `NODEFLIB` flag, which is a dynamic array entry flag that signals 58 | to the dynamic linker that it should not search default system paths 59 | including those specified in `ld.so.conf`. 60 | - Better FreeBSD support (`OSREL`, `OSNAME` interpolation in rpaths and 61 | `/etc/ld-elf.so.conf` config file support) 62 | - Support for relative includes in `ld.so.conf` config files. 63 | 64 | Breaking changes: 65 | - The bundling feature was dropped in `3.0.0`, but is still supported in `2.x`. 66 | It may return in a future `3.x` release, but my impression is that there are 67 | excellent tools like Exodus which do a better job at bundling (in particular: 68 | they ship a copy of the dynamic linker.) 69 | - The `--skip` and `--platform` flags were removed. 70 | 71 | # v2.0.0 72 | 73 | - No changes to the libtree API 74 | - Provide static executables for ease of use on distros with an old glibc or 75 | musl. 76 | - Dropped the dependency on `cppglob`, use posix `glob` instead. 77 | - No more vendored dependencies, rely on `find_package` to find `cxxopts`, 78 | `elfio`, and `termcolor`. 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Harmen Stoppels 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS ?= -O2 2 | LIBTREE_CFLAGS = -std=c99 -Wall -Wextra -Wshadow -pedantic 3 | LIBTREE_DEFINES = -D_FILE_OFFSET_BITS=64 4 | 5 | # uppercase variables for backwards compatibility only: 6 | PREFIX = /usr/local 7 | 8 | prefix = $(PREFIX) 9 | exec_prefix = $(prefix) 10 | bindir = $(exec_prefix)/bin 11 | datarootdir = $(prefix)/share 12 | mandir = $(datarootdir)/man 13 | 14 | .PHONY: all check install clean 15 | 16 | all: libtree 17 | 18 | .c.o: 19 | $(CC) $(CFLAGS) $(LIBTREE_CFLAGS) $(LIBTREE_DEFINES) -c $< 20 | 21 | libtree-objs = libtree.o 22 | libtree: $(libtree-objs) 23 | $(CC) $(LDFLAGS) -o $@ $(libtree-objs) 24 | 25 | install: all 26 | mkdir -p $(DESTDIR)$(bindir) 27 | cp -p libtree $(DESTDIR)$(bindir) 28 | mkdir -p $(DESTDIR)$(mandir)/man1 29 | cp -p doc/libtree.1 $(DESTDIR)$(mandir)/man1 30 | 31 | check:: libtree 32 | 33 | clean:: 34 | rm -f *.o libtree 35 | 36 | clean check:: 37 | find tests -mindepth 1 -maxdepth 1 -type d | while read -r dir; do \ 38 | $(MAKE) -C "$$dir" $@ || exit 1 ;\ 39 | done 40 | 41 | -include Make.user 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libtree 2 | 3 | A tool that: 4 | - :deciduous_tree: turns `ldd` into a tree 5 | - :point_up: explains how shared libraries are found or why they cannot be located 6 | 7 | ![Screenshot of libtree](doc/screenshot.png) 8 | 9 | 10 | ## Output 11 | 12 | By default, certain standard dependencies are not shown. For more verbose output use 13 | 14 | - `libtree -v` Show libraries skipped by default 15 | - `libtree -vv` Show dependencies of libraries skipped by default 16 | - `libtree -vvv` Show dependencies of already encountered libraries 17 | 18 | Use the `--path` or `-p` flags to show paths rather than sonames: 19 | 20 | - `libtree -p $(which tar)` 21 | 22 | Use `--max-depth` to limit the recursion depth. 23 | 24 | 25 | ## Install 26 | 27 | - [Prebuilt binaries for **v3.1.1**](https://github.com/haampie/libtree/releases/tag/v3.1.1) 28 | | arch | sha256sum | 29 | |---------|-----------| 30 | | [aarch64 (linux)](https://github.com/haampie/libtree/releases/download/v3.1.1/libtree_aarch64) | `c5d4fbcd4e3fb46f02c028532f60fcf1c92f7c6aad5b07a991c67550c2554862` | 31 | | [armv6l (linux)](https://github.com/haampie/libtree/releases/download/v3.1.1/libtree_armv6l) | `16f5a7503a095bd88ebc5e21ec4ba8337c5d9712cac355bf89399c9e6beef661` | 32 | | [armv7l (linux)](https://github.com/haampie/libtree/releases/download/v3.1.1/libtree_armv7l) | `17f493621e7cc651e2bddef207c1554a64a114e1c907dbe5b79ff0e97180b29e` | 33 | | [i686 (linux)](https://github.com/haampie/libtree/releases/download/v3.1.1/libtree_i686) | `230a163c20f4a88a983d8647a9aa793317be6556e2c6a79e8a6295389e651ef5` | 34 | | [x86_64 (linux)](https://github.com/haampie/libtree/releases/download/v3.1.1/libtree_x86_64) | `49218482f89648972ea4ef38cf986e85268efd1ce8f27fe14b23124bca009e6f` | 35 | - Fedora / RHEL / CentOS 36 | ```console 37 | $ dnf install epel-release # For RHEL and derivatives enable EPEL first 38 | $ dnf install libtree-ldd 39 | ``` 40 | - Ubuntu 22.04+ 41 | ```console 42 | apt-get install libtree 43 | ``` 44 | 45 | - [GNU Guix](https://guix.gnu.org/) 46 | ```console 47 | guix install libtree 48 | ``` 49 | 50 | - [Older release **v2.0.0**](https://github.com/haampie/libtree/releases/tag/v2.0.0) 51 | 52 | 53 | ## Building from sources 54 | 55 | `libtree` requires a C compiler that understands c99 56 | 57 | ``` 58 | git clone https://github.com/haampie/libtree.git 59 | cd libtree 60 | make # recommended: LDFLAGS=-static 61 | ``` 62 | 63 |
64 | Or use the following unsafe quick install instructions 65 | 66 | ``` 67 | curl -Lfs https://raw.githubusercontent.com/haampie/libtree/master/libtree.c | ${CC:-cc} -o libtree -x c - -std=c99 -D_FILE_OFFSET_BITS=64 68 | ``` 69 |
70 | -------------------------------------------------------------------------------- /doc/libtree.1: -------------------------------------------------------------------------------- 1 | .\" Process this file with 2 | .\" groff -man -Tascii foo.1 3 | .\" 4 | .TH libtree 1 "2020-04-13" Linux "User Manuals" 5 | .SH NAME 6 | libtree \- print shared object dependencies as a tree 7 | .SH SYNOPSIS 8 | .B libtree 9 | [ 10 | .I option 11 | ]... [--] [ 12 | .I file 13 | ]... 14 | .SH DESCRIPTION 15 | .B libtree 16 | 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. 17 | .SH OPTIONS 18 | .IP "-h, --help" 19 | Print usage 20 | .IP "--version" 21 | Print version info 22 | .IP "-p, --path" 23 | Show the path of libraries instead of their 24 | .B soname 25 | .IP "-v" 26 | Show libraries skipped by default 27 | .IP "-vv" 28 | Show dependencies of libraries skipped by default 29 | .IP "-vvv" 30 | Show dependencies of already encountered libraries 31 | .IP "--ldconf arg" 32 | Path to custom 33 | .I ld.so.conf 34 | or 35 | .I ld-elf.so.conf 36 | file 37 | .IP "--max-depth n" 38 | Limit library traversal to a depth of at most 39 | .IR n . 40 | The value cannot be larger than 32. 41 | .IP "--" 42 | All arguments after '--' are interpreted as paths, not flags. 43 | .SH ENVIRONMENT 44 | .B LD_LIBRARY_PATH 45 | can be used to provide additional search paths. 46 | .SH AUTHOR 47 | Harmen Stoppels 48 | .SH "SEE ALSO" 49 | .BR ldd (1) 50 | 51 | -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haampie/libtree/95d8992968a18ae957fef723f103b416d0843398/doc/screenshot.png -------------------------------------------------------------------------------- /libtree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define VERSION "3.2.0-dev" 14 | 15 | #define ET_EXEC 2 16 | #define ET_DYN 3 17 | 18 | #define PT_NULL 0 19 | #define PT_LOAD 1 20 | #define PT_DYNAMIC 2 21 | 22 | #define DT_NULL 0 23 | #define DT_NEEDED 1 24 | #define DT_STRTAB 5 25 | #define DT_SONAME 14 26 | #define DT_RPATH 15 27 | #define DT_RUNPATH 29 28 | 29 | #define BITS32 1 30 | #define BITS64 2 31 | 32 | #define ERR_INVALID_MAGIC 11 33 | #define ERR_INVALID_CLASS 12 34 | #define ERR_INVALID_DATA 13 35 | #define ERR_INVALID_HEADER 14 36 | #define ERR_INVALID_BITS 15 37 | #define ERR_INVALID_ENDIANNESS 16 38 | #define ERR_NO_EXEC_OR_DYN 17 39 | #define ERR_INVALID_PHOFF 18 40 | #define ERR_INVALID_PROG_HEADER 19 41 | #define ERR_CANT_STAT 20 42 | #define ERR_INVALID_DYNAMIC_SECTION 21 43 | #define ERR_INVALID_DYNAMIC_ARRAY_ENTRY 22 44 | #define ERR_NO_STRTAB 23 45 | #define ERR_INVALID_SONAME 24 46 | #define ERR_INVALID_RPATH 25 47 | #define ERR_INVALID_RUNPATH 26 48 | #define ERR_INVALID_NEEDED 27 49 | #define ERR_DEPENDENCY_NOT_FOUND 28 50 | #define ERR_NO_PT_LOAD 29 51 | #define ERR_VADDRS_NOT_ORDERED 30 52 | #define ERR_COULD_NOT_OPEN_FILE 31 53 | #define ERR_INCOMPATIBLE_ISA 32 54 | 55 | #define DT_FLAGS_1 0x6ffffffb 56 | #define DT_1_NODEFLIB 0x800 57 | 58 | #define MAX_OFFSET_T 0xFFFFFFFFFFFFFFFF 59 | 60 | #define REGULAR_RED "\033[0;31m" 61 | #define BOLD_RED "\033[1;31m" 62 | #define CLEAR "\033[0m" 63 | #define BOLD_YELLOW "\033[33m" 64 | #define BOLD_CYAN "\033[1;36m" 65 | #define REGULAR_CYAN "\033[0;36m" 66 | #define REGULAR_MAGENTA "\033[0;35m" 67 | #define REGULAR_BLUE "\033[0;34m" 68 | #define BRIGHT_BLACK "\033[0;90m" 69 | #define REGULAR "\033[0m" 70 | 71 | // don't judge me. 72 | #define LIGHT_HORIZONTAL "\xe2\x94\x80" 73 | #define LIGHT_QUADRUPLE_DASH_VERTICAL "\xe2\x94\x8a" 74 | #define LIGHT_UP_AND_RIGHT "\xe2\x94\x94" 75 | #define LIGHT_VERTICAL "\xe2\x94\x82" 76 | #define LIGHT_VERTICAL_AND_RIGHT "\xe2\x94\x9c" 77 | 78 | #define JUST_INDENT " " 79 | #define LIGHT_VERTICAL_WITH_INDENT LIGHT_VERTICAL " " 80 | 81 | #define SMALL_VEC_SIZE 16 82 | #define MAX_RECURSION_DEPTH 32 83 | #define MAX_PATH_LENGTH 4096 84 | 85 | // Libraries we do not show by default -- this reduces the verbosity quite a 86 | // bit. 87 | char const *exclude_list[] = {"ld-linux-aarch64.so", 88 | "ld-linux-armhf.so", 89 | "ld-linux-x86-64.so", 90 | "ld-linux.so", 91 | "ld64.so", 92 | "libc.musl-aarch64.so", 93 | "libc.musl-armhf.so", 94 | "libc.musl-i386.so", 95 | "libc.musl-x86_64.so", 96 | "libc.so", 97 | "libdl.so", 98 | "libgcc_s.so", 99 | "libm.so", 100 | "libstdc++.so"}; 101 | 102 | struct header_64_t { 103 | uint16_t e_type; 104 | uint16_t e_machine; 105 | uint32_t e_version; 106 | uint64_t e_entry; 107 | uint64_t e_phoff; 108 | uint64_t e_shoff; 109 | uint32_t e_flags; 110 | uint16_t e_ehsize; 111 | uint16_t e_phentsize; 112 | uint16_t e_phnum; 113 | uint16_t e_shentsize; 114 | uint16_t e_shnum; 115 | uint16_t e_shstrndx; 116 | }; 117 | 118 | struct header_32_t { 119 | uint16_t e_type; 120 | uint16_t e_machine; 121 | uint32_t e_version; 122 | uint32_t e_entry; 123 | uint32_t e_phoff; 124 | uint32_t e_shoff; 125 | uint32_t e_flags; 126 | uint16_t e_ehsize; 127 | uint16_t e_phentsize; 128 | uint16_t e_phnum; 129 | uint16_t e_shentsize; 130 | uint16_t e_shnum; 131 | uint16_t e_shstrndx; 132 | }; 133 | 134 | struct prog_64_t { 135 | uint32_t p_type; 136 | uint32_t p_flags; 137 | uint64_t p_offset; 138 | uint64_t p_vaddr; 139 | uint64_t p_paddr; 140 | uint64_t p_filesz; 141 | uint64_t p_memsz; 142 | uint64_t p_align; 143 | }; 144 | 145 | struct prog_32_t { 146 | uint32_t p_type; 147 | uint32_t p_offset; 148 | uint32_t p_vaddr; 149 | uint32_t p_paddr; 150 | uint32_t p_filesz; 151 | uint32_t p_memsz; 152 | uint32_t p_flags; 153 | uint32_t p_align; 154 | }; 155 | 156 | struct dyn_64_t { 157 | int64_t d_tag; 158 | uint64_t d_val; 159 | }; 160 | 161 | struct dyn_32_t { 162 | int32_t d_tag; 163 | uint32_t d_val; 164 | }; 165 | 166 | struct compat_t { 167 | char any; // 1 iff we don't look for libs matching a certain architecture 168 | uint8_t class; // 32 or 64 bits? 169 | uint16_t machine; // instruction set 170 | }; 171 | 172 | typedef enum { 173 | INPUT, 174 | DIRECT, 175 | RPATH, 176 | LD_LIBRARY_PATH, 177 | RUNPATH, 178 | LD_SO_CONF, 179 | DEFAULT 180 | } how_t; 181 | 182 | struct found_t { 183 | how_t how; 184 | // only set when found by in the rpath NOT of the direct parent. so, when 185 | // it is found in a "special" way only rpaths allow, which is worth 186 | // informing the user about. 187 | size_t depth; 188 | }; 189 | 190 | // large buffer in which to copy rpaths, needed libraries and sonames. 191 | struct string_table_t { 192 | char *arr; 193 | size_t n; 194 | size_t capacity; 195 | }; 196 | 197 | struct visited_file_t { 198 | dev_t st_dev; 199 | ino_t st_ino; 200 | }; 201 | 202 | struct visited_file_array_t { 203 | struct visited_file_t *arr; 204 | size_t n; 205 | size_t capacity; 206 | }; 207 | 208 | struct libtree_state_t { 209 | int verbosity; 210 | int path; 211 | int color; 212 | char *ld_conf_file; 213 | unsigned long max_depth; 214 | 215 | struct string_table_t string_table; 216 | struct visited_file_array_t visited; 217 | 218 | // rpath substitutions values (note: OSNAME/OSREL are FreeBSD specific, LIB 219 | // is glibc/Linux specific -- we substitute all so we can support 220 | // cross-compiled binaries). 221 | char *PLATFORM; 222 | char *LIB; 223 | char *OSNAME; 224 | char *OSREL; 225 | 226 | // rpath stack: if lib_a needs lib_b needs lib_c and all have rpaths 227 | // then first lib_c's rpaths are considered, then lib_b's, then lib_a's. 228 | // so this data structure keeps a list of offsets into the string buffer 229 | // where rpaths start, like [lib_a_rpath_offset, lib_b_rpath_offset, 230 | // lib_c_rpath_offset]... 231 | size_t rpath_offsets[MAX_RECURSION_DEPTH]; 232 | size_t ld_library_path_offset; 233 | size_t default_paths_offset; 234 | size_t ld_so_conf_offset; 235 | 236 | // This is so we know we have to print a | or white space 237 | // in the tree 238 | char found_all_needed[MAX_RECURSION_DEPTH]; 239 | }; 240 | 241 | // Keep track of the files we've see 242 | 243 | /** 244 | * small_vec_u64 is an array that lives on the stack until it grows to the heap 245 | */ 246 | struct small_vec_u64_t { 247 | uint64_t buf[SMALL_VEC_SIZE]; 248 | uint64_t *p; 249 | size_t n; 250 | size_t capacity; 251 | }; 252 | 253 | static inline void utoa(char *str, size_t v) { 254 | char *p = str; 255 | do { 256 | *p++ = '0' + (v % 10); 257 | v /= 10; 258 | } while (v > 0); 259 | size_t len = p - str; 260 | for (size_t i = 0; i < len / 2; i++) { 261 | char tmp = str[i]; 262 | str[i] = str[len - i - 1]; 263 | str[len - i - 1] = tmp; 264 | } 265 | str[len] = '\0'; 266 | } 267 | 268 | static inline void small_vec_u64_init(struct small_vec_u64_t *v) { 269 | memset(v, 0, sizeof(*v)); 270 | v->p = v->buf; 271 | } 272 | 273 | static void small_vec_u64_append(struct small_vec_u64_t *v, uint64_t val) { 274 | // The hopefully likely path 275 | if (v->n < SMALL_VEC_SIZE) { 276 | v->p[v->n++] = val; 277 | return; 278 | } 279 | 280 | // The slow fallback on the heap 281 | if (v->n == SMALL_VEC_SIZE) { 282 | v->capacity = 2 * SMALL_VEC_SIZE; 283 | v->p = malloc(v->capacity * sizeof(uint64_t)); 284 | if (v->p == NULL) 285 | exit(1); 286 | memcpy(v->p, v->buf, SMALL_VEC_SIZE * sizeof(uint64_t)); 287 | } else if (v->n == v->capacity) { 288 | v->capacity *= 2; 289 | uint64_t *p = realloc(v->p, v->capacity * sizeof(uint64_t)); 290 | if (p == NULL) 291 | exit(1); 292 | v->p = p; 293 | } 294 | 295 | v->p[v->n++] = val; 296 | } 297 | 298 | static void small_vec_u64_free(struct small_vec_u64_t *v) { 299 | if (v->n <= SMALL_VEC_SIZE) 300 | return; 301 | free(v->p); 302 | v->p = NULL; 303 | } 304 | 305 | /** 306 | * end of small_vec_u64_t 307 | */ 308 | 309 | static inline int host_is_little_endian() { 310 | int test = 1; 311 | char *bytes = (char *)&test; 312 | return bytes[0] == 1; 313 | } 314 | 315 | static int is_ascending_order(uint64_t *v, size_t n) { 316 | for (size_t j = 1; j < n; ++j) 317 | if (v[j - 1] >= v[j]) 318 | return 0; 319 | 320 | return 1; 321 | } 322 | 323 | static void string_table_maybe_grow(struct string_table_t *t, size_t n) { 324 | // The likely case of not having to resize 325 | if (t->n + n <= t->capacity) 326 | return; 327 | 328 | // Otherwise give twice the amount of required space. 329 | t->capacity = 2 * (t->n + n); 330 | char *arr = realloc(t->arr, t->capacity * sizeof(char)); 331 | if (arr == NULL) { 332 | exit(1); 333 | } 334 | t->arr = arr; 335 | } 336 | 337 | static void string_table_store(struct string_table_t *t, char const *str) { 338 | size_t n = strlen(str) + 1; 339 | string_table_maybe_grow(t, n); 340 | memcpy(t->arr + t->n, str, n); 341 | t->n += n; 342 | } 343 | 344 | static void string_table_copy_from_file(struct string_table_t *t, FILE *fptr) { 345 | int c; 346 | // TODO: this could be a bit more efficient... 347 | while ((c = getc(fptr)) != '\0' && c != EOF) { 348 | string_table_maybe_grow(t, 1); 349 | t->arr[t->n++] = c; 350 | } 351 | string_table_maybe_grow(t, 1); 352 | t->arr[t->n++] = '\0'; 353 | } 354 | 355 | static int is_in_exclude_list(char *soname) { 356 | // Get to the end. 357 | char *start = soname; 358 | char *end = strrchr(start, '\0'); 359 | 360 | // Empty needed string, is that even possible? 361 | if (start == end) 362 | return 0; 363 | 364 | --end; 365 | 366 | // Strip "1234567890." from the right. 367 | while (end != start && ((*end >= '0' && *end <= '9') || *end == '.')) { 368 | --end; 369 | } 370 | 371 | // Check if we should skip this one. 372 | for (size_t j = 0; j < sizeof(exclude_list) / sizeof(char *); ++j) { 373 | size_t len = strlen(exclude_list[j]); 374 | if (strncmp(start, exclude_list[j], len) != 0) 375 | continue; 376 | return 1; 377 | } 378 | 379 | return 0; 380 | } 381 | 382 | static void tree_preamble(struct libtree_state_t *s, size_t depth) { 383 | if (depth == 0) 384 | return; 385 | 386 | for (size_t i = 0; i < depth - 1; ++i) 387 | fputs(s->found_all_needed[i] ? JUST_INDENT : LIGHT_VERTICAL_WITH_INDENT, 388 | stdout); 389 | 390 | fputs(s->found_all_needed[depth - 1] 391 | ? LIGHT_UP_AND_RIGHT LIGHT_HORIZONTAL LIGHT_HORIZONTAL " " 392 | : LIGHT_VERTICAL_AND_RIGHT LIGHT_HORIZONTAL LIGHT_HORIZONTAL " ", 393 | stdout); 394 | } 395 | 396 | static int recurse(char *current_file, size_t depth, 397 | struct libtree_state_t *state, struct compat_t compat, 398 | struct found_t reason); 399 | 400 | static void apply_exclude_list(size_t *needed_not_found, 401 | struct small_vec_u64_t *needed_buf_offsets, 402 | struct libtree_state_t *s) { 403 | for (size_t i = 0; i < *needed_not_found;) { 404 | // If in exclude list, swap to the back. 405 | if (is_in_exclude_list(s->string_table.arr + 406 | needed_buf_offsets->p[i])) { 407 | size_t tmp = needed_buf_offsets->p[i]; 408 | needed_buf_offsets->p[i] = 409 | needed_buf_offsets->p[*needed_not_found - 1]; 410 | needed_buf_offsets->p[--*needed_not_found] = tmp; 411 | continue; 412 | } else { 413 | ++i; 414 | } 415 | } 416 | } 417 | 418 | static int check_absolute_paths(size_t *needed_not_found, 419 | struct small_vec_u64_t *needed_buf_offsets, 420 | size_t depth, struct libtree_state_t *s, 421 | struct compat_t compat) { 422 | int exit_code = 0; 423 | // First go over absolute paths in needed libs. 424 | for (size_t i = 0; i < *needed_not_found;) { 425 | struct string_table_t const *st = &s->string_table; 426 | 427 | // Skip dt_needed that have do not contain / 428 | if (strchr(st->arr + needed_buf_offsets->p[i], '/') == NULL) { 429 | ++i; 430 | continue; 431 | } 432 | 433 | // Copy the path over. 434 | char path[MAX_PATH_LENGTH]; 435 | size_t len = strlen(st->arr + needed_buf_offsets->p[i]); 436 | 437 | // Unlikely to happen but good to guard against 438 | if (len >= MAX_PATH_LENGTH) 439 | continue; 440 | 441 | // Include \0 442 | memcpy(path, st->arr + needed_buf_offsets->p[i], len + 1); 443 | 444 | s->found_all_needed[depth] = *needed_not_found <= 1; 445 | char *err = NULL; 446 | 447 | // If it is not an absolute path, we bail, cause it then starts to 448 | // depend on the current working directory, which is rather 449 | // nonsensical. This is allowed by glibc though. 450 | if (path[0] != '/') { 451 | err = " is not absolute"; 452 | exit_code = ERR_DEPENDENCY_NOT_FOUND; 453 | } else { 454 | int code = recurse(path, depth + 1, s, compat, 455 | (struct found_t){.how = DIRECT}); 456 | if (code == ERR_DEPENDENCY_NOT_FOUND) 457 | exit_code = ERR_DEPENDENCY_NOT_FOUND; 458 | 459 | // Check if there was an issue with the direct dep and ignore errors 460 | // of transient deps. 461 | if (code != 0 && code != ERR_DEPENDENCY_NOT_FOUND) { 462 | err = " not found"; 463 | } 464 | } 465 | 466 | if (err) { 467 | tree_preamble(s, depth + 1); 468 | if (s->color) 469 | fputs(BOLD_RED, stdout); 470 | fputs(path, stdout); 471 | fputs(" is not absolute", stdout); 472 | fputs(s->color ? CLEAR "\n" : "\n", stdout); 473 | } 474 | 475 | // Handled this library, so swap to the back. 476 | size_t tmp = needed_buf_offsets->p[i]; 477 | needed_buf_offsets->p[i] = needed_buf_offsets->p[*needed_not_found - 1]; 478 | needed_buf_offsets->p[--*needed_not_found] = tmp; 479 | } 480 | 481 | return exit_code; 482 | } 483 | 484 | static int check_search_paths(struct found_t reason, size_t offset, 485 | size_t *needed_not_found, 486 | struct small_vec_u64_t *needed_buf_offsets, 487 | size_t depth, struct libtree_state_t *s, 488 | struct compat_t compat) { 489 | int exit_code = 0; 490 | char path[MAX_PATH_LENGTH]; 491 | char *path_end = path + MAX_PATH_LENGTH; 492 | 493 | struct string_table_t const *st = &s->string_table; 494 | 495 | while (st->arr[offset] != '\0') { 496 | // First remove trailing colons 497 | while (st->arr[offset] == ':' && st->arr[offset] != '\0') 498 | ++offset; 499 | 500 | // Check if it was only colons 501 | if (st->arr[offset] == '\0') 502 | return exit_code; 503 | 504 | // Copy the search path until the first \0 or : 505 | char *dest = path; 506 | while (st->arr[offset] != '\0' && st->arr[offset] != ':' && 507 | dest != path_end) 508 | *dest++ = st->arr[offset++]; 509 | 510 | // Path too long... Can't handle. 511 | if (dest + 1 >= path_end) 512 | continue; 513 | 514 | // Add a separator if necessary 515 | if (*(dest - 1) != '/') 516 | *dest++ = '/'; 517 | 518 | // Keep track of the end of the current search path. 519 | char *search_path_end = dest; 520 | 521 | // Try to open it -- if we've found anything, swap it with the back. 522 | for (size_t i = 0; i < *needed_not_found;) { 523 | size_t soname_len = strlen(st->arr + needed_buf_offsets->p[i]); 524 | 525 | // Path too long, can't handle. 526 | if (search_path_end + soname_len + 1 >= path_end) 527 | continue; 528 | 529 | // Otherwise append. 530 | memcpy(search_path_end, st->arr + needed_buf_offsets->p[i], 531 | soname_len + 1); 532 | s->found_all_needed[depth] = *needed_not_found <= 1; 533 | 534 | // And try to locate the lib. 535 | int code = recurse(path, depth + 1, s, compat, reason); 536 | if (code == ERR_DEPENDENCY_NOT_FOUND) 537 | exit_code = ERR_DEPENDENCY_NOT_FOUND; 538 | if (code == 0 || code == ERR_DEPENDENCY_NOT_FOUND) { 539 | // Found at least the direct dependency, so swap out the current 540 | // soname to the back and reduce the number of to be found by 541 | // one. 542 | size_t tmp = needed_buf_offsets->p[i]; 543 | needed_buf_offsets->p[i] = 544 | needed_buf_offsets->p[*needed_not_found - 1]; 545 | needed_buf_offsets->p[--(*needed_not_found)] = tmp; 546 | } else { 547 | ++i; 548 | } 549 | } 550 | } 551 | 552 | return exit_code; 553 | } 554 | 555 | static int interpolate_variables(struct libtree_state_t *s, size_t src, 556 | char const *ORIGIN) { 557 | // We do not write to dst if there is no variables to interpolate. 558 | size_t prev_src = src; 559 | size_t curr_src = src; 560 | 561 | struct string_table_t *st = &s->string_table; 562 | 563 | while (1) { 564 | // Find the next potential variable. 565 | char *dollar = strchr(st->arr + curr_src, '$'); 566 | if (dollar == NULL) 567 | break; 568 | curr_src = dollar - st->arr; 569 | 570 | size_t bytes_to_dollar = curr_src - prev_src; 571 | 572 | // Go past the dollar. 573 | ++curr_src; 574 | 575 | // Remember if we have to look for matching curly braces. 576 | int curly = 0; 577 | if (st->arr[curr_src] == '{') { 578 | curly = 1; 579 | ++curr_src; 580 | } 581 | 582 | // String to interpolate. 583 | char const *var_val = NULL; 584 | if (strncmp(&st->arr[curr_src], "ORIGIN", 6) == 0) { 585 | var_val = ORIGIN; 586 | curr_src += 6; 587 | } else if (strncmp(&st->arr[curr_src], "LIB", 3) == 0) { 588 | var_val = s->LIB; 589 | curr_src += 3; 590 | } else if (strncmp(&st->arr[curr_src], "PLATFORM", 8) == 0) { 591 | var_val = s->PLATFORM; 592 | curr_src += 8; 593 | } else if (strncmp(&st->arr[curr_src], "OSNAME", 6) == 0) { 594 | var_val = s->OSNAME; 595 | curr_src += 6; 596 | } else if (strncmp(&st->arr[curr_src], "OSREL", 5) == 0) { 597 | var_val = s->OSREL; 598 | curr_src += 5; 599 | } else { 600 | continue; 601 | } 602 | 603 | // Require matching {...}. 604 | if (curly) { 605 | if (st->arr[curr_src] != '}') { 606 | continue; 607 | } 608 | ++curr_src; 609 | } 610 | 611 | size_t var_len = strlen(var_val); 612 | 613 | // Make sure we have enough space to write to. 614 | string_table_maybe_grow(st, bytes_to_dollar + var_len); 615 | 616 | // First copy over the string until the variable. 617 | memcpy(&st->arr[s->string_table.n], &st->arr[prev_src], 618 | bytes_to_dollar); 619 | s->string_table.n += bytes_to_dollar; 620 | 621 | // Then move prev_src until after the variable. 622 | prev_src = curr_src; 623 | 624 | // Then copy the variable value (without null). 625 | memcpy(&st->arr[s->string_table.n], var_val, var_len); 626 | s->string_table.n += var_len; 627 | } 628 | 629 | // Did we copy anything? That implies a variable was interpolated. 630 | // Copy the remainder, including the \0. 631 | if (prev_src != src) { 632 | size_t n = strlen(st->arr + prev_src) + 1; 633 | string_table_maybe_grow(st, n); 634 | // note: we're copying from within the string table, so we 635 | // should not store st->arr + prev_src. 636 | memcpy(st->arr + st->n, st->arr + prev_src, n); 637 | st->n += n; 638 | return 1; 639 | } 640 | 641 | return 0; 642 | } 643 | 644 | static void print_colon_delimited_paths(char const *start, char const *indent) { 645 | while (1) { 646 | // Don't print empty string 647 | if (*start == '\0') 648 | break; 649 | 650 | // Find the next delimiter after start 651 | char *next = strchr(start, ':'); 652 | 653 | // Don't print empty strings 654 | if (start == next) { 655 | ++start; 656 | continue; 657 | } 658 | 659 | fputs(indent, stdout); 660 | fputs(JUST_INDENT, stdout); 661 | 662 | // Print up to but not including : or \0, followed by a newline. 663 | if (next == NULL) { 664 | puts(start); 665 | } else { 666 | fwrite(start, 1, next - start, stdout); 667 | putchar('\n'); 668 | } 669 | 670 | // We done yet? 671 | if (next == NULL) 672 | break; 673 | 674 | // Otherwise put the : back in place and continue. 675 | start = next + 1; 676 | } 677 | } 678 | 679 | static void print_line(size_t depth, char *name, char *color_bold, 680 | char *color_regular, int highlight, 681 | struct found_t reason, struct libtree_state_t *s) { 682 | tree_preamble(s, depth); 683 | // Color the filename different than the path name, if we have a path. 684 | char *slash = NULL; 685 | if (s->color && highlight && (slash = strrchr(name, '/')) != NULL) { 686 | fputs(color_regular, stdout); 687 | fwrite(name, 1, slash + 1 - name, stdout); 688 | fputs(color_bold, stdout); 689 | fputs(slash + 1, stdout); 690 | } else { 691 | if (s->color) 692 | fputs(color_bold, stdout); 693 | 694 | fputs(name, stdout); 695 | } 696 | if (s->color && highlight) 697 | fputs(CLEAR " " BOLD_YELLOW, stdout); 698 | else 699 | putchar(' '); 700 | switch (reason.how) { 701 | case RPATH: 702 | if (reason.depth + 1 >= depth) { 703 | fputs("[rpath]", stdout); 704 | } else { 705 | char num[8]; 706 | utoa(num, reason.depth + 1); 707 | fputs("[rpath of ", stdout); 708 | fputs(num, stdout); 709 | putchar(']'); 710 | } 711 | break; 712 | case LD_LIBRARY_PATH: 713 | fputs("[LD_LIBRARY_PATH]", stdout); 714 | break; 715 | case RUNPATH: 716 | fputs("[runpath]", stdout); 717 | break; 718 | case LD_SO_CONF: 719 | putchar('['); 720 | char *conf_name = strrchr(s->ld_conf_file, '/'); 721 | conf_name = conf_name == NULL ? s->ld_conf_file : conf_name + 1; 722 | fputs(conf_name, stdout); 723 | putchar(']'); 724 | break; 725 | case DIRECT: 726 | fputs("[direct]", stdout); 727 | break; 728 | case DEFAULT: 729 | fputs("[default path]", stdout); 730 | break; 731 | default: 732 | break; 733 | } 734 | if (s->color) 735 | fputs(CLEAR "\n", stdout); 736 | else 737 | putchar('\n'); 738 | } 739 | 740 | static void print_error(size_t depth, size_t needed_not_found, 741 | struct small_vec_u64_t *needed_buf_offsets, 742 | char *runpath, struct libtree_state_t *s, 743 | int no_def_lib) { 744 | for (size_t i = 0; i < needed_not_found; ++i) { 745 | s->found_all_needed[depth] = i + 1 >= needed_not_found; 746 | tree_preamble(s, depth + 1); 747 | if (s->color) 748 | fputs(BOLD_RED, stdout); 749 | fputs(s->string_table.arr + needed_buf_offsets->p[i], stdout); 750 | fputs(" not found\n", stdout); 751 | if (s->color) 752 | fputs(CLEAR, stdout); 753 | } 754 | 755 | // If anything was not found, we print the search paths in order they 756 | // are considered. 757 | char *box_vertical = 758 | s->color ? JUST_INDENT REGULAR_RED LIGHT_QUADRUPLE_DASH_VERTICAL CLEAR 759 | : JUST_INDENT LIGHT_QUADRUPLE_DASH_VERTICAL; 760 | char *indent = malloc(sizeof(LIGHT_VERTICAL_WITH_INDENT) * depth + 761 | strlen(box_vertical) + 1); 762 | char *p = indent; 763 | for (size_t i = 0; i < depth; ++i) { 764 | if (s->found_all_needed[i]) { 765 | int len = sizeof(JUST_INDENT) - 1; 766 | memcpy(p, JUST_INDENT, len); 767 | p += len; 768 | } else { 769 | int len = sizeof(LIGHT_VERTICAL_WITH_INDENT) - 1; 770 | memcpy(p, LIGHT_VERTICAL_WITH_INDENT, len); 771 | p += len; 772 | } 773 | } 774 | // dotted | in red 775 | strcpy(p, box_vertical); 776 | 777 | fputs(indent, stdout); 778 | if (s->color) 779 | fputs(BRIGHT_BLACK, stdout); 780 | fputs(" Paths considered in this order:\n", stdout); 781 | if (s->color) 782 | fputs(CLEAR, stdout); 783 | 784 | // Consider rpaths only when runpath is empty 785 | fputs(indent, stdout); 786 | if (runpath != NULL) { 787 | if (s->color) 788 | fputs(BRIGHT_BLACK, stdout); 789 | fputs(" 1. rpath is skipped because runpath was set\n", stdout); 790 | if (s->color) 791 | fputs(CLEAR, stdout); 792 | } else { 793 | if (s->color) 794 | fputs(BRIGHT_BLACK, stdout); 795 | fputs(" 1. rpath:\n", stdout); 796 | if (s->color) 797 | fputs(CLEAR, stdout); 798 | for (int j = depth; j >= 0; --j) { 799 | if (s->rpath_offsets[j] != SIZE_MAX) { 800 | char num[8]; 801 | utoa(num, j + 1); 802 | fputs(indent, stdout); 803 | if (s->color) 804 | fputs(BRIGHT_BLACK, stdout); 805 | fputs(" depth ", stdout); 806 | fputs(num, stdout); 807 | if (s->color) 808 | fputs(CLEAR, stdout); 809 | putchar('\n'); 810 | print_colon_delimited_paths( 811 | s->string_table.arr + s->rpath_offsets[j], indent); 812 | } 813 | } 814 | } 815 | 816 | // Environment variables 817 | fputs(indent, stdout); 818 | if (s->color) 819 | fputs(BRIGHT_BLACK, stdout); 820 | fputs(s->ld_library_path_offset == SIZE_MAX 821 | ? " 2. LD_LIBRARY_PATH was not set\n" 822 | : " 2. LD_LIBRARY_PATH:\n", 823 | stdout); 824 | if (s->color) 825 | fputs(CLEAR, stdout); 826 | if (s->ld_library_path_offset != SIZE_MAX) 827 | print_colon_delimited_paths( 828 | s->string_table.arr + s->ld_library_path_offset, indent); 829 | 830 | // runpath 831 | fputs(indent, stdout); 832 | if (s->color) 833 | fputs(BRIGHT_BLACK, stdout); 834 | fputs(runpath == NULL ? " 3. runpath was not set\n" : " 3. runpath:\n", 835 | stdout); 836 | if (s->color) 837 | fputs(CLEAR, stdout); 838 | if (runpath != NULL) 839 | print_colon_delimited_paths(runpath, indent); 840 | 841 | fputs(indent, stdout); 842 | if (s->color) 843 | fputs(BRIGHT_BLACK, stdout); 844 | fputs(no_def_lib 845 | ? " 4. ld config files not considered due to NODEFLIB flag\n" 846 | : " 4. ld config files:\n", 847 | stdout); 848 | if (s->color) 849 | fputs(CLEAR, stdout); 850 | print_colon_delimited_paths(s->string_table.arr + s->ld_so_conf_offset, 851 | indent); 852 | 853 | fputs(indent, stdout); 854 | if (s->color) 855 | fputs(BRIGHT_BLACK, stdout); 856 | fputs(no_def_lib 857 | ? " 5. Standard paths not considered due to NODEFLIB flag\n" 858 | : " 5. Standard paths:\n", 859 | stdout); 860 | if (s->color) 861 | fputs(CLEAR, stdout); 862 | print_colon_delimited_paths(s->string_table.arr + s->default_paths_offset, 863 | indent); 864 | 865 | free(indent); 866 | } 867 | 868 | static int visited_files_contains(struct visited_file_array_t *files, 869 | struct stat *needle) { 870 | for (size_t i = 0; i < files->n; ++i) { 871 | struct visited_file_t *f = &files->arr[i]; 872 | if (f->st_dev == needle->st_dev && f->st_ino == needle->st_ino) 873 | return 1; 874 | } 875 | return 0; 876 | } 877 | 878 | static void visited_files_append(struct visited_file_array_t *files, 879 | struct stat *new) { 880 | if (files->n == files->capacity) { 881 | files->capacity *= 2; 882 | files->arr = realloc(files->arr, 883 | files->capacity * sizeof(struct visited_file_t)); 884 | if (files->arr == NULL) 885 | exit(1); 886 | } 887 | files->arr[files->n].st_dev = new->st_dev; 888 | files->arr[files->n].st_ino = new->st_ino; 889 | ++files->n; 890 | } 891 | 892 | static int recurse(char *current_file, size_t depth, struct libtree_state_t *s, 893 | struct compat_t compat, struct found_t reason) { 894 | FILE *fptr = fopen(current_file, "rb"); 895 | 896 | if (fptr == NULL) 897 | return ERR_COULD_NOT_OPEN_FILE; 898 | 899 | // When we're done recursing, we should give back the memory we've claimed. 900 | size_t old_buf_size = s->string_table.n; 901 | 902 | // Parse the header 903 | char e_ident[16]; 904 | if (fread(&e_ident, 16, 1, fptr) != 1) { 905 | fclose(fptr); 906 | return ERR_INVALID_MAGIC; 907 | } 908 | 909 | // Find magic elfs 910 | if (e_ident[0] != 0x7f || e_ident[1] != 'E' || e_ident[2] != 'L' || 911 | e_ident[3] != 'F') { 912 | fclose(fptr); 913 | return ERR_INVALID_MAGIC; 914 | } 915 | 916 | // Do at least *some* header validation 917 | if (e_ident[4] != BITS32 && e_ident[4] != BITS64) { 918 | fclose(fptr); 919 | return ERR_INVALID_CLASS; 920 | } 921 | 922 | if (e_ident[5] != '\x01' && e_ident[5] != '\x02') { 923 | fclose(fptr); 924 | return ERR_INVALID_DATA; 925 | } 926 | 927 | struct compat_t curr_type = {.any = 0, .class = e_ident[4]}; 928 | int is_little_endian = e_ident[5] == '\x01'; 929 | 930 | // Make sure that we have matching bits with parent 931 | if (!compat.any && compat.class != curr_type.class) { 932 | fclose(fptr); 933 | return ERR_INVALID_BITS; 934 | } 935 | 936 | // Make sure that the elf file has a the host's endianness 937 | // Byte swapping is on the TODO list 938 | if (is_little_endian ^ host_is_little_endian()) { 939 | fclose(fptr); 940 | return ERR_INVALID_ENDIANNESS; 941 | } 942 | 943 | // And get the type 944 | union { 945 | struct header_64_t h64; 946 | struct header_32_t h32; 947 | } header; 948 | 949 | // Read the (rest of the) elf header 950 | if (curr_type.class == BITS64) { 951 | if (fread(&header.h64, sizeof(struct header_64_t), 1, fptr) != 1) { 952 | fclose(fptr); 953 | return ERR_INVALID_HEADER; 954 | } 955 | if (header.h64.e_type != ET_EXEC && header.h64.e_type != ET_DYN) { 956 | fclose(fptr); 957 | return ERR_NO_EXEC_OR_DYN; 958 | } 959 | curr_type.machine = header.h64.e_machine; 960 | if (!compat.any && compat.machine != curr_type.machine) { 961 | fclose(fptr); 962 | return ERR_INCOMPATIBLE_ISA; 963 | } 964 | if (fseek(fptr, header.h64.e_phoff, SEEK_SET) != 0) { 965 | fclose(fptr); 966 | return ERR_INVALID_PHOFF; 967 | } 968 | } else { 969 | if (fread(&header.h32, sizeof(struct header_32_t), 1, fptr) != 1) { 970 | fclose(fptr); 971 | return ERR_INVALID_HEADER; 972 | } 973 | if (header.h32.e_type != ET_EXEC && header.h32.e_type != ET_DYN) { 974 | fclose(fptr); 975 | return ERR_NO_EXEC_OR_DYN; 976 | } 977 | curr_type.machine = header.h32.e_machine; 978 | if (!compat.any && compat.machine != curr_type.machine) { 979 | fclose(fptr); 980 | return ERR_INCOMPATIBLE_ISA; 981 | } 982 | if (fseek(fptr, header.h32.e_phoff, SEEK_SET) != 0) { 983 | fclose(fptr); 984 | return ERR_INVALID_PHOFF; 985 | } 986 | } 987 | 988 | // Make sure it's an executable or library 989 | union { 990 | struct prog_64_t p64; 991 | struct prog_32_t p32; 992 | } prog; 993 | 994 | // map vaddr to file offset (we don't mmap the file, but directly seek in 995 | // the file which means that we have to translate vaddr to file offset) 996 | struct small_vec_u64_t pt_load_offset; 997 | struct small_vec_u64_t pt_load_vaddr; 998 | 999 | small_vec_u64_init(&pt_load_offset); 1000 | small_vec_u64_init(&pt_load_vaddr); 1001 | 1002 | // Read the program header. 1003 | uint64_t p_offset = MAX_OFFSET_T; 1004 | if (curr_type.class == BITS64) { 1005 | for (uint64_t i = 0; i < header.h64.e_phnum; ++i) { 1006 | if (fread(&prog.p64, sizeof(struct prog_64_t), 1, fptr) != 1) { 1007 | fclose(fptr); 1008 | small_vec_u64_free(&pt_load_offset); 1009 | small_vec_u64_free(&pt_load_vaddr); 1010 | return ERR_INVALID_PROG_HEADER; 1011 | } 1012 | 1013 | if (prog.p64.p_type == PT_LOAD) { 1014 | small_vec_u64_append(&pt_load_offset, prog.p64.p_offset); 1015 | small_vec_u64_append(&pt_load_vaddr, prog.p64.p_vaddr); 1016 | } else if (prog.p64.p_type == PT_DYNAMIC) { 1017 | p_offset = prog.p64.p_offset; 1018 | } 1019 | } 1020 | } else { 1021 | for (uint32_t i = 0; i < header.h32.e_phnum; ++i) { 1022 | if (fread(&prog.p32, sizeof(struct prog_32_t), 1, fptr) != 1) { 1023 | fclose(fptr); 1024 | small_vec_u64_free(&pt_load_offset); 1025 | small_vec_u64_free(&pt_load_vaddr); 1026 | return ERR_INVALID_PROG_HEADER; 1027 | } 1028 | 1029 | if (prog.p32.p_type == PT_LOAD) { 1030 | small_vec_u64_append(&pt_load_offset, prog.p32.p_offset); 1031 | small_vec_u64_append(&pt_load_vaddr, prog.p32.p_vaddr); 1032 | } else if (prog.p32.p_type == PT_DYNAMIC) { 1033 | p_offset = prog.p32.p_offset; 1034 | } 1035 | } 1036 | } 1037 | 1038 | // At this point we're going to store the file as "success" 1039 | struct stat finfo; 1040 | if (stat(current_file, &finfo) != 0) { 1041 | fclose(fptr); 1042 | small_vec_u64_free(&pt_load_offset); 1043 | small_vec_u64_free(&pt_load_vaddr); 1044 | return ERR_CANT_STAT; 1045 | } 1046 | 1047 | int seen_before = visited_files_contains(&s->visited, &finfo); 1048 | 1049 | if (!seen_before) 1050 | visited_files_append(&s->visited, &finfo); 1051 | 1052 | // No dynamic section? 1053 | if (p_offset == MAX_OFFSET_T) { 1054 | print_line(depth, current_file, BOLD_CYAN, REGULAR_CYAN, 1, reason, s); 1055 | fclose(fptr); 1056 | small_vec_u64_free(&pt_load_offset); 1057 | small_vec_u64_free(&pt_load_vaddr); 1058 | return 0; 1059 | } 1060 | 1061 | // I guess you always have to load at least a string 1062 | // table, so if there are not PT_LOAD sections, then 1063 | // it is an error. 1064 | if (pt_load_offset.n == 0) { 1065 | fclose(fptr); 1066 | small_vec_u64_free(&pt_load_offset); 1067 | small_vec_u64_free(&pt_load_vaddr); 1068 | return ERR_NO_PT_LOAD; 1069 | } 1070 | 1071 | // Go to the dynamic section 1072 | if (fseek(fptr, p_offset, SEEK_SET) != 0) { 1073 | fclose(fptr); 1074 | small_vec_u64_free(&pt_load_offset); 1075 | small_vec_u64_free(&pt_load_vaddr); 1076 | return ERR_INVALID_DYNAMIC_SECTION; 1077 | } 1078 | 1079 | // Shared libraries can disable searching in 1080 | // "default" search paths, aka ld.so.conf and 1081 | // /usr/lib etc. At least glibc respects this. 1082 | int no_def_lib = 0; 1083 | 1084 | uint64_t strtab = MAX_OFFSET_T; 1085 | uint64_t rpath = MAX_OFFSET_T; 1086 | uint64_t runpath = MAX_OFFSET_T; 1087 | uint64_t soname = MAX_OFFSET_T; 1088 | 1089 | // Offsets in strtab 1090 | struct small_vec_u64_t needed; 1091 | small_vec_u64_init(&needed); 1092 | 1093 | for (int cont = 1; cont;) { 1094 | uint64_t d_tag; 1095 | uint64_t d_val; 1096 | 1097 | if (curr_type.class == BITS64) { 1098 | struct dyn_64_t dyn; 1099 | if (fread(&dyn, sizeof(struct dyn_64_t), 1, fptr) != 1) { 1100 | fclose(fptr); 1101 | small_vec_u64_free(&pt_load_offset); 1102 | small_vec_u64_free(&pt_load_vaddr); 1103 | small_vec_u64_free(&needed); 1104 | return ERR_INVALID_DYNAMIC_ARRAY_ENTRY; 1105 | } 1106 | d_tag = dyn.d_tag; 1107 | d_val = dyn.d_val; 1108 | 1109 | } else { 1110 | struct dyn_32_t dyn; 1111 | if (fread(&dyn, sizeof(struct dyn_32_t), 1, fptr) != 1) { 1112 | fclose(fptr); 1113 | small_vec_u64_free(&pt_load_offset); 1114 | small_vec_u64_free(&pt_load_vaddr); 1115 | small_vec_u64_free(&needed); 1116 | return ERR_INVALID_DYNAMIC_ARRAY_ENTRY; 1117 | } 1118 | d_tag = dyn.d_tag; 1119 | d_val = dyn.d_val; 1120 | } 1121 | 1122 | // Store strtab / rpath / runpath / needed / soname info. 1123 | switch (d_tag) { 1124 | case DT_NULL: 1125 | cont = 0; 1126 | break; 1127 | case DT_STRTAB: 1128 | strtab = d_val; 1129 | break; 1130 | case DT_RPATH: 1131 | rpath = d_val; 1132 | break; 1133 | case DT_RUNPATH: 1134 | runpath = d_val; 1135 | break; 1136 | case DT_NEEDED: 1137 | small_vec_u64_append(&needed, d_val); 1138 | break; 1139 | case DT_SONAME: 1140 | soname = d_val; 1141 | break; 1142 | case DT_FLAGS_1: 1143 | no_def_lib |= (DT_1_NODEFLIB & d_val) == DT_1_NODEFLIB; 1144 | break; 1145 | } 1146 | } 1147 | 1148 | if (strtab == MAX_OFFSET_T) { 1149 | fclose(fptr); 1150 | small_vec_u64_free(&pt_load_offset); 1151 | small_vec_u64_free(&pt_load_vaddr); 1152 | small_vec_u64_free(&needed); 1153 | return ERR_NO_STRTAB; 1154 | } 1155 | 1156 | // Let's verify just to be sure that the offsets are 1157 | // ordered. 1158 | if (!is_ascending_order(pt_load_vaddr.p, pt_load_vaddr.n)) { 1159 | fclose(fptr); 1160 | small_vec_u64_free(&pt_load_vaddr); 1161 | small_vec_u64_free(&pt_load_offset); 1162 | small_vec_u64_free(&needed); 1163 | return ERR_VADDRS_NOT_ORDERED; 1164 | } 1165 | 1166 | // Find the file offset corresponding to the strtab virtual address 1167 | size_t vaddr_idx = 0; 1168 | while (vaddr_idx + 1 != pt_load_vaddr.n && 1169 | strtab >= pt_load_vaddr.p[vaddr_idx + 1]) { 1170 | ++vaddr_idx; 1171 | } 1172 | 1173 | uint64_t strtab_offset = 1174 | pt_load_offset.p[vaddr_idx] + strtab - pt_load_vaddr.p[vaddr_idx]; 1175 | 1176 | small_vec_u64_free(&pt_load_vaddr); 1177 | small_vec_u64_free(&pt_load_offset); 1178 | 1179 | // From this point on we actually copy strings from the ELF file into our 1180 | // own string buffer. 1181 | 1182 | // Copy the current soname 1183 | size_t soname_buf_offset = s->string_table.n; 1184 | if (soname != MAX_OFFSET_T) { 1185 | if (fseek(fptr, strtab_offset + soname, SEEK_SET) != 0) { 1186 | s->string_table.n = old_buf_size; 1187 | fclose(fptr); 1188 | small_vec_u64_free(&needed); 1189 | return ERR_INVALID_SONAME; 1190 | } 1191 | string_table_copy_from_file(&s->string_table, fptr); 1192 | } 1193 | 1194 | int in_exclude_list = 1195 | soname != MAX_OFFSET_T && 1196 | is_in_exclude_list(s->string_table.arr + soname_buf_offset); 1197 | 1198 | // No need to recurse deeper when we aren't in very verbose mode. 1199 | int should_recurse = 1200 | depth < s->max_depth && 1201 | ((!seen_before && !in_exclude_list) || 1202 | (!seen_before && in_exclude_list && s->verbosity >= 2) || 1203 | s->verbosity >= 3); 1204 | 1205 | // Just print the library and return 1206 | if (!should_recurse) { 1207 | char *print_name = soname == MAX_OFFSET_T || s->path 1208 | ? current_file 1209 | : (s->string_table.arr + soname_buf_offset); 1210 | 1211 | char *bold_color = in_exclude_list 1212 | ? REGULAR_MAGENTA 1213 | : seen_before ? REGULAR_BLUE : BOLD_CYAN; 1214 | char *regular_color = in_exclude_list 1215 | ? REGULAR_MAGENTA 1216 | : seen_before ? REGULAR_BLUE : REGULAR_CYAN; 1217 | 1218 | int highlight = !seen_before && !in_exclude_list; 1219 | print_line(depth, print_name, bold_color, regular_color, highlight, 1220 | reason, s); 1221 | 1222 | s->string_table.n = old_buf_size; 1223 | fclose(fptr); 1224 | small_vec_u64_free(&needed); 1225 | return 0; 1226 | } 1227 | 1228 | // Store the ORIGIN string. 1229 | char origin[MAX_PATH_LENGTH]; 1230 | char *last_slash = strrchr(current_file, '/'); 1231 | if (last_slash != NULL) { 1232 | // Exclude the last slash 1233 | size_t bytes = last_slash - current_file; 1234 | memcpy(origin, current_file, bytes); 1235 | origin[bytes] = '\0'; 1236 | } else { 1237 | // this only happens when the input is relative (e.g. in current dir) 1238 | memcpy(origin, "./", 3); 1239 | } 1240 | 1241 | // Copy DT_PRATH 1242 | if (rpath == MAX_OFFSET_T) { 1243 | s->rpath_offsets[depth] = SIZE_MAX; 1244 | } else { 1245 | s->rpath_offsets[depth] = s->string_table.n; 1246 | if (fseek(fptr, strtab_offset + rpath, SEEK_SET) != 0) { 1247 | s->string_table.n = old_buf_size; 1248 | fclose(fptr); 1249 | small_vec_u64_free(&needed); 1250 | return ERR_INVALID_RPATH; 1251 | } 1252 | 1253 | string_table_copy_from_file(&s->string_table, fptr); 1254 | 1255 | // We store the interpolated string right after the literal copy. 1256 | size_t curr_buf_size = s->string_table.n; 1257 | if (interpolate_variables(s, s->rpath_offsets[depth], origin)) 1258 | s->rpath_offsets[depth] = curr_buf_size; 1259 | } 1260 | 1261 | // Copy DT_RUNPATH 1262 | size_t runpath_buf_offset = s->string_table.n; 1263 | if (runpath != MAX_OFFSET_T) { 1264 | if (fseek(fptr, strtab_offset + runpath, SEEK_SET) != 0) { 1265 | s->string_table.n = old_buf_size; 1266 | fclose(fptr); 1267 | small_vec_u64_free(&needed); 1268 | return ERR_INVALID_RUNPATH; 1269 | } 1270 | 1271 | string_table_copy_from_file(&s->string_table, fptr); 1272 | 1273 | // We store the interpolated string right after the literal copy. 1274 | size_t curr_buf_size = s->string_table.n; 1275 | if (interpolate_variables(s, runpath_buf_offset, origin)) 1276 | runpath_buf_offset = curr_buf_size; 1277 | } 1278 | 1279 | // Copy needed libraries. 1280 | struct small_vec_u64_t needed_buf_offsets; 1281 | small_vec_u64_init(&needed_buf_offsets); 1282 | 1283 | for (size_t i = 0; i < needed.n; ++i) { 1284 | small_vec_u64_append(&needed_buf_offsets, s->string_table.n); 1285 | if (fseek(fptr, strtab_offset + needed.p[i], SEEK_SET) != 0) { 1286 | s->string_table.n = old_buf_size; 1287 | fclose(fptr); 1288 | small_vec_u64_free(&needed_buf_offsets); 1289 | small_vec_u64_free(&needed); 1290 | return ERR_INVALID_NEEDED; 1291 | } 1292 | string_table_copy_from_file(&s->string_table, fptr); 1293 | } 1294 | 1295 | fclose(fptr); 1296 | 1297 | char *print_name = soname == MAX_OFFSET_T || s->path 1298 | ? current_file 1299 | : (s->string_table.arr + soname_buf_offset); 1300 | 1301 | char *bold_color = in_exclude_list ? REGULAR_MAGENTA 1302 | : seen_before ? REGULAR_BLUE : BOLD_CYAN; 1303 | char *regular_color = in_exclude_list 1304 | ? REGULAR_MAGENTA 1305 | : seen_before ? REGULAR_BLUE : REGULAR_CYAN; 1306 | 1307 | int highlight = !seen_before && !in_exclude_list; 1308 | print_line(depth, print_name, bold_color, regular_color, highlight, reason, 1309 | s); 1310 | 1311 | // Finally start searching. 1312 | 1313 | int exit_code = 0; 1314 | 1315 | size_t needed_not_found = needed_buf_offsets.n; 1316 | 1317 | // Skip common libraries if not verbose 1318 | if (needed_not_found && s->verbosity == 0) 1319 | apply_exclude_list(&needed_not_found, &needed_buf_offsets, s); 1320 | 1321 | if (needed_not_found) 1322 | exit_code |= check_absolute_paths( 1323 | &needed_not_found, &needed_buf_offsets, depth, s, curr_type); 1324 | 1325 | // Consider rpaths only when runpath is empty 1326 | if (runpath == MAX_OFFSET_T) { 1327 | // We have a stack of rpaths, try them all, starting with one set at 1328 | // this lib, then the parents. 1329 | for (int j = depth; j >= 0 && needed_not_found; --j) { 1330 | if (s->rpath_offsets[j] == SIZE_MAX) 1331 | continue; 1332 | 1333 | exit_code |= check_search_paths( 1334 | (struct found_t){.how = RPATH, .depth = j}, s->rpath_offsets[j], 1335 | &needed_not_found, &needed_buf_offsets, depth, s, curr_type); 1336 | } 1337 | } 1338 | 1339 | // Then try LD_LIBRARY_PATH, if we have it. 1340 | if (needed_not_found && s->ld_library_path_offset != SIZE_MAX) { 1341 | exit_code |= check_search_paths( 1342 | (struct found_t){.how = LD_LIBRARY_PATH}, s->ld_library_path_offset, 1343 | &needed_not_found, &needed_buf_offsets, depth, s, curr_type); 1344 | } 1345 | 1346 | // Then consider runpaths 1347 | if (needed_not_found && runpath != MAX_OFFSET_T) { 1348 | exit_code |= check_search_paths( 1349 | (struct found_t){.how = RUNPATH}, runpath_buf_offset, 1350 | &needed_not_found, &needed_buf_offsets, depth, s, curr_type); 1351 | } 1352 | 1353 | // Check ld.so.conf paths 1354 | if (needed_not_found && !no_def_lib) { 1355 | exit_code |= check_search_paths( 1356 | (struct found_t){.how = LD_SO_CONF}, s->ld_so_conf_offset, 1357 | &needed_not_found, &needed_buf_offsets, depth, s, curr_type); 1358 | } 1359 | 1360 | // Then consider standard paths 1361 | if (needed_not_found && !no_def_lib) { 1362 | exit_code |= check_search_paths( 1363 | (struct found_t){.how = DEFAULT}, s->default_paths_offset, 1364 | &needed_not_found, &needed_buf_offsets, depth, s, curr_type); 1365 | } 1366 | 1367 | // Finally summarize those that could not be found. 1368 | if (needed_not_found) { 1369 | print_error(depth, needed_not_found, &needed_buf_offsets, 1370 | runpath == MAX_OFFSET_T 1371 | ? NULL 1372 | : s->string_table.arr + runpath_buf_offset, 1373 | s, no_def_lib); 1374 | s->string_table.n = old_buf_size; 1375 | small_vec_u64_free(&needed_buf_offsets); 1376 | small_vec_u64_free(&needed); 1377 | return ERR_DEPENDENCY_NOT_FOUND; 1378 | } 1379 | 1380 | // Free memory in our string table 1381 | s->string_table.n = old_buf_size; 1382 | small_vec_u64_free(&needed_buf_offsets); 1383 | small_vec_u64_free(&needed); 1384 | return exit_code; 1385 | } 1386 | 1387 | static int parse_ld_config_file(struct string_table_t *st, char *path); 1388 | 1389 | static int ld_conf_globbing(struct string_table_t *st, char *pattern) { 1390 | glob_t result; 1391 | memset(&result, 0, sizeof(result)); 1392 | int status = glob(pattern, 0, NULL, &result); 1393 | 1394 | // Handle errors (no result is not an error...) 1395 | switch (status) { 1396 | case GLOB_NOSPACE: 1397 | case GLOB_ABORTED: 1398 | globfree(&result); 1399 | return 1; 1400 | case GLOB_NOMATCH: 1401 | globfree(&result); 1402 | return 0; 1403 | } 1404 | 1405 | // Otherwise parse the files we've found! 1406 | int code = 0; 1407 | for (size_t i = 0; i < result.gl_pathc; ++i) 1408 | code |= parse_ld_config_file(st, result.gl_pathv[i]); 1409 | 1410 | globfree(&result); 1411 | return code; 1412 | } 1413 | 1414 | static int parse_ld_config_file(struct string_table_t *st, char *path) { 1415 | FILE *fptr = fopen(path, "r"); 1416 | 1417 | if (fptr == NULL) 1418 | return 1; 1419 | 1420 | int c = 0; 1421 | char line[MAX_PATH_LENGTH]; 1422 | char tmp[MAX_PATH_LENGTH]; 1423 | 1424 | while (c != EOF) { 1425 | size_t line_len = 0; 1426 | while ((c = getc(fptr)) != '\n' && c != EOF) { 1427 | if (line_len < MAX_PATH_LENGTH - 1) { 1428 | line[line_len++] = c; 1429 | } 1430 | } 1431 | 1432 | line[line_len] = '\0'; 1433 | 1434 | char *begin = line; 1435 | char *end = line + line_len; 1436 | // Remove leading whitespace 1437 | for (; isspace(*begin); ++begin) { 1438 | } 1439 | 1440 | // Remove trailing comments 1441 | char *comment = strchr(begin, '#'); 1442 | if (comment != NULL) 1443 | *comment = '\0'; 1444 | 1445 | // Remove trailing whitespace 1446 | while (end != begin) 1447 | if (!isspace(*--end)) 1448 | break; 1449 | 1450 | // Skip empty lines 1451 | if (begin == end) 1452 | continue; 1453 | 1454 | // Put back the end of the string 1455 | end[1] = '\0'; 1456 | 1457 | // 'include ': glob whatever follows. 1458 | if (strncmp(begin, "include", 7) == 0 && isspace(begin[7])) { 1459 | begin += 8; 1460 | // Remove more whitespace. 1461 | while (isspace(*begin)) 1462 | ++begin; 1463 | 1464 | // Prepend current dir when include dir is relative. 1465 | if (*begin != '/') { 1466 | char *wd = strrchr(path, '/'); 1467 | wd = wd == NULL ? strrchr(path, '\0') : wd; 1468 | 1469 | // bytes until / 1470 | size_t wd_len = wd - path; 1471 | size_t include_len = end - begin + 1; 1472 | 1473 | // just skip then. 1474 | if (wd_len + 1 + include_len >= MAX_PATH_LENGTH) 1475 | continue; 1476 | 1477 | memcpy(tmp, path, wd_len); 1478 | tmp[wd_len] = '/'; 1479 | memcpy(tmp + wd_len + 1, begin, include_len); 1480 | tmp[wd_len + 1 + include_len] = '\0'; 1481 | begin = tmp; 1482 | } 1483 | 1484 | ld_conf_globbing(st, begin); 1485 | } else { 1486 | // Copy over and replace trailing \0 with :. 1487 | string_table_store(st, begin); 1488 | st->arr[st->n - 1] = ':'; 1489 | } 1490 | } 1491 | 1492 | fclose(fptr); 1493 | 1494 | return 0; 1495 | } 1496 | 1497 | static void parse_ld_so_conf(struct libtree_state_t *s) { 1498 | struct string_table_t *st = &s->string_table; 1499 | s->ld_so_conf_offset = st->n; 1500 | 1501 | // Linux / glibc 1502 | parse_ld_config_file(st, s->ld_conf_file); 1503 | 1504 | // Replace the last semicolon with a '\0' 1505 | // if we have a nonzero number of paths. 1506 | if (st->n > s->ld_so_conf_offset) { 1507 | st->arr[st->n - 1] = '\0'; 1508 | } else { 1509 | string_table_store(st, ""); 1510 | } 1511 | } 1512 | 1513 | static void parse_ld_library_path(struct libtree_state_t *s) { 1514 | s->ld_library_path_offset = SIZE_MAX; 1515 | char *val = getenv("LD_LIBRARY_PATH"); 1516 | 1517 | // not set, so nothing to do. 1518 | if (val == NULL) 1519 | return; 1520 | 1521 | s->ld_library_path_offset = s->string_table.n; 1522 | 1523 | // otherwise, we just copy it over and replace ; with : 1524 | string_table_store(&s->string_table, val); 1525 | 1526 | // replace ; with : 1527 | char *search = s->string_table.arr + s->ld_library_path_offset; 1528 | while ((search = strchr(search, ';')) != NULL) 1529 | *search++ = ':'; 1530 | } 1531 | 1532 | static void set_default_paths(struct libtree_state_t *s) { 1533 | s->default_paths_offset = s->string_table.n; 1534 | // TODO: how to retrieve this list properly at runtime? 1535 | string_table_store(&s->string_table, "/lib:/lib64:/usr/lib:/usr/lib64"); 1536 | } 1537 | 1538 | static void libtree_state_init(struct libtree_state_t *s) { 1539 | s->string_table.n = 0; 1540 | s->string_table.capacity = 1024; 1541 | s->string_table.arr = malloc(s->string_table.capacity * sizeof(char)); 1542 | s->visited.n = 0; 1543 | s->visited.capacity = 256; 1544 | s->visited.arr = 1545 | malloc(s->visited.capacity * sizeof(struct visited_file_t)); 1546 | } 1547 | 1548 | static void libtree_state_free(struct libtree_state_t *s) { 1549 | free(s->string_table.arr); 1550 | free(s->visited.arr); 1551 | } 1552 | 1553 | static int print_tree(int pathc, char **pathv, struct libtree_state_t *s) { 1554 | // First collect standard paths 1555 | libtree_state_init(s); 1556 | 1557 | parse_ld_so_conf(s); 1558 | parse_ld_library_path(s); 1559 | set_default_paths(s); 1560 | 1561 | int exit_code = 0; 1562 | 1563 | for (int i = 0; i < pathc; ++i) { 1564 | int code = recurse(pathv[i], 0, s, (struct compat_t){.any = 1}, 1565 | (struct found_t){.how = INPUT}); 1566 | fflush(stdout); 1567 | if (code != 0) { 1568 | exit_code = code; 1569 | fputs("Error [", stderr); 1570 | fputs(pathv[i], stderr); 1571 | fputs("]: ", stderr); 1572 | } 1573 | char *msg = NULL; 1574 | switch (code) { 1575 | case ERR_INVALID_MAGIC: 1576 | msg = "Invalid ELF magic bytes\n"; 1577 | break; 1578 | case ERR_INVALID_CLASS: 1579 | msg = "Invalid ELF class\n"; 1580 | break; 1581 | case ERR_INVALID_DATA: 1582 | msg = "Invalid ELF data\n"; 1583 | break; 1584 | case ERR_INVALID_HEADER: 1585 | msg = "Invalid ELF header\n"; 1586 | break; 1587 | case ERR_INVALID_BITS: 1588 | msg = "Invalid bits\n"; 1589 | break; 1590 | case ERR_INVALID_ENDIANNESS: 1591 | msg = "Invalid endianness\n"; 1592 | break; 1593 | case ERR_NO_EXEC_OR_DYN: 1594 | msg = "Not an ET_EXEC or ET_DYN ELF file\n"; 1595 | break; 1596 | case ERR_INVALID_PHOFF: 1597 | msg = "Invalid ELF program header offset\n"; 1598 | break; 1599 | case ERR_INVALID_PROG_HEADER: 1600 | msg = "Invalid ELF program header\n"; 1601 | break; 1602 | case ERR_CANT_STAT: 1603 | msg = "Can't stat file\n"; 1604 | break; 1605 | case ERR_INVALID_DYNAMIC_SECTION: 1606 | msg = "Invalid ELF dynamic section\n"; 1607 | break; 1608 | case ERR_INVALID_DYNAMIC_ARRAY_ENTRY: 1609 | msg = "Invalid ELF dynamic array entry\n"; 1610 | break; 1611 | case ERR_NO_STRTAB: 1612 | msg = "No ELF string table found\n"; 1613 | break; 1614 | case ERR_INVALID_SONAME: 1615 | msg = "Can't read DT_SONAME\n"; 1616 | break; 1617 | case ERR_INVALID_RPATH: 1618 | msg = "Can't read DT_RPATH\n"; 1619 | break; 1620 | case ERR_INVALID_RUNPATH: 1621 | msg = "Can't read DT_RUNPATH\n"; 1622 | break; 1623 | case ERR_INVALID_NEEDED: 1624 | msg = "Can't read DT_NEEDED\n"; 1625 | break; 1626 | case ERR_DEPENDENCY_NOT_FOUND: 1627 | msg = "Not all dependencies were found\n"; 1628 | break; 1629 | case ERR_NO_PT_LOAD: 1630 | msg = "No PT_LOAD found in ELF file\n"; 1631 | break; 1632 | case ERR_VADDRS_NOT_ORDERED: 1633 | msg = "Virtual addresses are not ordered\n"; 1634 | break; 1635 | case ERR_COULD_NOT_OPEN_FILE: 1636 | msg = "Could not open file\n"; 1637 | break; 1638 | case ERR_INCOMPATIBLE_ISA: 1639 | msg = "Incompatible ISA\n"; 1640 | break; 1641 | } 1642 | 1643 | if (msg != NULL) 1644 | fputs(msg, stderr); 1645 | 1646 | fflush(stderr); 1647 | } 1648 | 1649 | libtree_state_free(s); 1650 | return exit_code; 1651 | } 1652 | 1653 | int main(int argc, char **argv) { 1654 | // Enable or disable colors (no-color.com) 1655 | struct libtree_state_t s; 1656 | s.color = getenv("NO_COLOR") == NULL && isatty(STDOUT_FILENO); 1657 | s.verbosity = 0; 1658 | s.path = 0; 1659 | s.max_depth = MAX_RECURSION_DEPTH; 1660 | 1661 | // We want to end up with an array of file names 1662 | // in argv[1] up to argv[positional-1]. 1663 | int positional = 1; 1664 | 1665 | struct utsname uname_val; 1666 | if (uname(&uname_val) != 0) 1667 | return 1; 1668 | 1669 | // Technically this should be AT_PLATFORM, but 1670 | // (a) the feature is rarely used 1671 | // (b) it's almost always the same 1672 | s.PLATFORM = uname_val.machine; 1673 | s.OSNAME = uname_val.sysname; 1674 | s.OSREL = uname_val.release; 1675 | s.ld_conf_file = "/etc/ld.so.conf"; 1676 | 1677 | if (strcmp(uname_val.sysname, "FreeBSD") == 0) 1678 | s.ld_conf_file = "/etc/ld-elf.so.conf"; 1679 | 1680 | // TODO: how to find this value at runtime? 1681 | s.LIB = "lib"; 1682 | 1683 | int opt_help = 0; 1684 | int opt_version = 0; 1685 | 1686 | // After `--` we treat everything as filenames, not flags. 1687 | int opt_raw = 0; 1688 | 1689 | for (int i = 1; i < argc; ++i) { 1690 | char *arg = argv[i]; 1691 | 1692 | // Positional args don't start with - or are `-` literal. 1693 | if (opt_raw || *arg != '-' || arg[1] == '\0') { 1694 | argv[positional++] = arg; 1695 | continue; 1696 | } 1697 | 1698 | // Now we're in flag land! 1699 | ++arg; 1700 | 1701 | // Long flags 1702 | if (*arg == '-') { 1703 | ++arg; 1704 | 1705 | // Literal '--' 1706 | if (*arg == '\0') { 1707 | opt_raw = 1; 1708 | continue; 1709 | } 1710 | 1711 | if (strcmp(arg, "version") == 0) { 1712 | opt_version = 1; 1713 | } else if (strcmp(arg, "path") == 0) { 1714 | s.path = 1; 1715 | } else if (strcmp(arg, "verbose") == 0) { 1716 | ++s.verbosity; 1717 | } else if (strcmp(arg, "help") == 0) { 1718 | opt_help = 1; 1719 | } else if (strcmp(arg, "ldconf") == 0) { 1720 | // Require a value 1721 | if (i + 1 == argc) { 1722 | fputs("Expected value after `--ldconf`\n", stderr); 1723 | return 1; 1724 | } 1725 | s.ld_conf_file = argv[++i]; 1726 | } else if (strcmp(arg, "max-depth") == 0) { 1727 | // Require a value 1728 | if (i + 1 == argc) { 1729 | fputs("Expected value after `--max-depth`\n", stderr); 1730 | return 1; 1731 | } 1732 | // Limit it by MAX_RECURSION_DEPTH. 1733 | char *ptr; 1734 | s.max_depth = strtoul(argv[++i], &ptr, 10); 1735 | if (s.max_depth > MAX_RECURSION_DEPTH) 1736 | s.max_depth = MAX_RECURSION_DEPTH; 1737 | } else { 1738 | fputs("Unrecognized flag `--", stderr); 1739 | fputs(arg, stderr); 1740 | fputs("`\n", stderr); 1741 | return 1; 1742 | } 1743 | 1744 | continue; 1745 | } 1746 | 1747 | // Short flags 1748 | for (; *arg != '\0'; ++arg) { 1749 | switch (*arg) { 1750 | case 'h': 1751 | opt_help = 1; 1752 | break; 1753 | case 'p': 1754 | s.path = 1; 1755 | break; 1756 | case 'v': 1757 | ++s.verbosity; 1758 | break; 1759 | default: 1760 | fputs("Unrecognized flag `-", stderr); 1761 | fputs(arg, stderr); 1762 | fputs("`\n", stderr); 1763 | return 1; 1764 | } 1765 | } 1766 | } 1767 | 1768 | ++argv; 1769 | --positional; 1770 | 1771 | // Print a help message on -h, --help or no positional args. 1772 | if (opt_help || (!opt_version && positional == 0)) { 1773 | // clang-format off 1774 | fputs("Show the dynamic dependency tree of ELF files\n" 1775 | "Usage: libtree [OPTION]... [--] FILE [FILES]...\n" 1776 | "\n" 1777 | " -h, --help Print help info\n" 1778 | " --version Print version info\n" 1779 | "\n" 1780 | "File names starting with '-', for example '-.so', can be specified as follows:\n" 1781 | " libtree -- -.so\n" 1782 | "\n" 1783 | "Locating libs options:\n" 1784 | " -p, --path Show the path of libraries instead of the soname\n" 1785 | " -v Show libraries skipped by default*\n" 1786 | " -vv Show dependencies of libraries skipped by default*\n" 1787 | " -vvv Show dependencies of already encountered libraries\n" 1788 | " --ldconf Config file for extra search paths [", stdout); 1789 | fputs(s.ld_conf_file, stdout); 1790 | fputs("]\n" 1791 | " --max-depth Limit library traversal to at most n levels of depth\n" 1792 | "\n" 1793 | "* For brevity, the following libraries are not shown by default:\n" 1794 | " ", 1795 | stdout); 1796 | // clang-format on 1797 | 1798 | // Print a comma separated list of skipped libraries, 1799 | // with some new lines every now and then to make it readable. 1800 | size_t num_excluded = sizeof(exclude_list) / sizeof(char *); 1801 | 1802 | size_t cursor_x = 3; 1803 | for (size_t j = 0; j < num_excluded; ++j) { 1804 | cursor_x += strlen(exclude_list[j]); 1805 | if (cursor_x > 60) { 1806 | cursor_x = 3; 1807 | fputs("\n ", stdout); 1808 | } 1809 | fputs(exclude_list[j], stdout); 1810 | if (j + 1 != num_excluded) 1811 | fputs(", ", stdout); 1812 | } 1813 | 1814 | // rpath substitution values: 1815 | fputs(".\n\nThe following rpath/runpath substitutions are used:\n", 1816 | stdout); 1817 | fputs(" PLATFORM ", stdout); 1818 | fputs(s.PLATFORM, stdout); 1819 | fputs("\n LIB ", stdout); 1820 | fputs(s.LIB, stdout); 1821 | fputs("\n OSNAME ", stdout); 1822 | fputs(s.OSNAME, stdout); 1823 | fputs("\n OSREL ", stdout); 1824 | fputs(s.OSREL, stdout); 1825 | putchar('\n'); 1826 | 1827 | // Return an error status code if no positional args were passed. 1828 | return !opt_help; 1829 | } 1830 | 1831 | if (opt_version) { 1832 | puts(VERSION); 1833 | return 0; 1834 | } 1835 | 1836 | return print_tree(positional, argv, &s); 1837 | } 1838 | -------------------------------------------------------------------------------- /tests/01_origin/Makefile: -------------------------------------------------------------------------------- 1 | # Basic test of $ORIGIN interpolation in rpath & runpath. 2 | 3 | .PHONY: clean 4 | 5 | LD_LIBRARY_PATH= 6 | 7 | all: check 8 | 9 | liba.so: 10 | echo 'int f(void){return 1;}' | $(CC) -shared -Wl,-soname,$@ -o $@ -nostdlib -x c - 11 | 12 | exe_rpath: liba.so 13 | echo 'int f(void); int _start(void){return f();}' | $(CC) -o $@ -Wl,--no-as-needed -Wl,--disable-new-dtags '-Wl,-rpath,$$ORIGIN' -Wno-implicit-function-declaration -nostdlib liba.so -x c - 14 | 15 | exe_runpath: liba.so 16 | echo 'int f(void); int _start(void){return f();}' | $(CC) -o $@ -Wl,--no-as-needed -Wl,--enable-new-dtags '-Wl,-rpath,$$ORIGIN' -Wno-implicit-function-declaration -nostdlib liba.so -x c - 17 | 18 | check: exe_rpath exe_runpath 19 | ../../libtree exe_rpath 20 | ../../libtree exe_runpath 21 | 22 | clean: 23 | rm -f *.so exe* 24 | -------------------------------------------------------------------------------- /tests/02_rpath_of_parents_parent/Makefile: -------------------------------------------------------------------------------- 1 | # This creates exe <- liba.so <- libb.so 2 | # where liba.so cannot directly locate libb.so, but it *can* through exe's rpaths. 3 | 4 | LD_LIBRARY_PATH= 5 | 6 | .PHONY: clean 7 | 8 | all: check 9 | 10 | libb.so: 11 | echo 'int g(void){return 1;}' | $(CC) -shared -Wl,-soname,$@ -o $@ -nostdlib -x c - 12 | 13 | liba.so: libb.so 14 | echo 'int g(void); int f(void){return g();}' | $(CC) -shared -Wl,--no-as-needed -Wl,-soname,$@ -o $@ -Wno-implicit-function-declaration libb.so -nostdlib -x c - 15 | 16 | exe: liba.so 17 | echo 'int f(void); 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 - 18 | 19 | check: exe liba.so 20 | ! ../../libtree liba.so # should not find libb.so 21 | LD_LIBRARY_PATH=$(CURDIR) ../../libtree liba.so # should find libb.so through LD_LIBRARY_PATH 22 | ../../libtree exe # should find libb.so through exe's rpath 23 | 24 | clean: 25 | rm -f *.so exe* 26 | 27 | CURDIR ?= $(.CURDIR) 28 | -------------------------------------------------------------------------------- /tests/03_direct_and_absolute_rpath/Makefile: -------------------------------------------------------------------------------- 1 | LD_LIBRARY_PATH= 2 | 3 | .PHONY: clean 4 | 5 | all: check 6 | 7 | some_dir/lib_f.so: 8 | mkdir some_dir 9 | echo 'int f(){return 42;}' | $(CC) -shared -Wl,-soname,$(@F) -Wl,--no-as-needed -o $@ -x c - 10 | 11 | lib_g.so: some_dir/lib_f.so 12 | 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 - 13 | 14 | lib_without_soname.so: 15 | echo 'int i(){return 1;}' | $(CC) -shared -Wl,--no-as-needed -o $@ -x c - 16 | 17 | exe_a: some_dir/lib_f.so lib_g.so lib_without_soname.so 18 | 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 - 19 | 20 | exe_b: some_dir/lib_f.so lib_g.so lib_without_soname.so 21 | 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 - 22 | 23 | check: exe_a exe_b 24 | ! ../../libtree exe_a # cannot find lib_f.so 25 | ../../libtree exe_b # should find lib_f.so 26 | 27 | clean: 28 | rm -rf *.so some_dir exe* 29 | 30 | CURDIR ?= $(.CURDIR) 31 | -------------------------------------------------------------------------------- /tests/04_rpath_over_env_over_runpath/Makefile: -------------------------------------------------------------------------------- 1 | LD_LIBRARY_PATH= 2 | 3 | # test whether RUNPATH < LD_LIBRARY_PATH < RPATH 4 | # the exe's both need libb.so, but there are two of those: 5 | # ./dir/libb.so needs liba.so 6 | # ./libb.so needs nothing. 7 | 8 | .PHONY: clean 9 | 10 | all: check 11 | 12 | dir: 13 | mkdir $@ 14 | 15 | dir/liba.so: dir 16 | echo 'int a(void){return 42;}' | $(CC) -shared -Wl,-soname,$(@F) -Wl,--no-as-needed -o $@ -nostdlib -x c - 17 | 18 | dir/libb.so: dir/liba.so 19 | echo 'int a(void); int b(void){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 - 20 | 21 | libb.so: 22 | echo 'int b(){return 10;}' | $(CC) -shared -Wl,-soname,$(@F) -Wl,--no-as-needed -o $@ -Wno-implicit-function-declaration -nostdlib -x c - 23 | 24 | exe_rpath: libb.so 25 | echo 'int b(void); int _start(void){return b();}' | $(CC) -Wl,--no-as-needed -Wl,--disable-new-dtags "-Wl,-rpath,$(CURDIR)" libb.so -o $@ -Wno-implicit-function-declaration -nostdlib -x c - 26 | 27 | exe_runpath: libb.so 28 | echo 'int b(void); int _start(void){return b();}' | $(CC) -Wl,--no-as-needed -Wl,--enable-new-dtags "-Wl,-rpath,$(CURDIR)" libb.so -o $@ -Wno-implicit-function-declaration -nostdlib -x c - 29 | 30 | check: exe_rpath exe_runpath dir/libb.so 31 | ../../libtree exe_rpath 32 | LD_LIBRARY_PATH="$(CURDIR)/dir" ../../libtree exe_rpath 33 | ../../libtree exe_runpath 34 | LD_LIBRARY_PATH="$(CURDIR)/dir" ../../libtree exe_runpath 35 | 36 | clean: 37 | rm -rf *.so dir exe* 38 | 39 | CURDIR ?= $(.CURDIR) 40 | -------------------------------------------------------------------------------- /tests/05_32_bits/Makefile: -------------------------------------------------------------------------------- 1 | # Test 32 bits exectuables and libraries 2 | # Make sure the 64 bits versions are not picked up. 3 | 4 | LD_LIBRARY_PATH= 5 | 6 | .PHONY: clean 7 | 8 | all: check 9 | 10 | lib64/libx.so: 11 | mkdir -p $(@D) 12 | echo 'int a(){return 42;}' | $(CC) -shared -Wl,-soname,$(@F) -m64 -o $@ -nostdlib -x c - 13 | 14 | lib32/libx.so: 15 | mkdir -p $(@D) 16 | echo 'int a(){return 42;}' | $(CC) -shared -Wl,-soname,$(@F) -m32 -o $@ -nostdlib -x c - 17 | 18 | exe64: lib64/libx.so 19 | echo 'extern int a(); int _start(){return a();}' | $(CC) -m64 "-Wl,-rpath,$(CURDIR)/lib32" "-Wl,-rpath,$(CURDIR)/lib64" -o $@ -nostdlib -x c - -Llib64 -lx 20 | 21 | exe32: lib32/libx.so 22 | echo 'extern int a(); int _start(){return a();}' | $(CC) -m32 "-Wl,-rpath,$(CURDIR)/lib64" "-Wl,-rpath,$(CURDIR)/lib32" -o $@ -nostdlib -x c - -Llib32 -lx 23 | 24 | check: exe32 exe64 25 | ../../libtree exe32 26 | ../../libtree exe64 27 | 28 | clean: 29 | rm -rf lib32 lib64 exe* 30 | 31 | CURDIR ?= $(.CURDIR) 32 | -------------------------------------------------------------------------------- /tests/06_symbol_versions/Makefile: -------------------------------------------------------------------------------- 1 | # Creates two versions of a library with the same (so)name, 2 | # and same symbol xyz, but versioned VER_V1 and VER_V2. 3 | # As far as I understand, ld.so does not continue to look 4 | # for another library in the search path which provides the 5 | # correct symbol versions (e.g. link to VER_V2, put the VER_V1 6 | # version first in the search ath, and glibc fixes this lib 7 | # and simply errors). 8 | 9 | LD_LIBRARY_PATH= 10 | 11 | .PHONY: clean 12 | 13 | all: check 14 | 15 | v1/libx.so: v1.c v1.map 16 | v2/libx.so: v2.c v2.map 17 | v1/libx.so v2/libx.so: 18 | mkdir -p $(@D) 19 | $(CC) -shared -Wl,-soname,$(@F) -o $@ -Wl,--version-script,$(@D).map $(@D).c 20 | 21 | # this one is fine, link to v1, use v2 which provides the v1 symbol. 22 | exe_v1: main.c v1/libx.so v2/libx.so 23 | $(CC) -o $@ main.c v1/libx.so "-Wl,-rpath,$(CURDIR)/v2" "-Wl,-rpath,$(CURDIR)/v1" 24 | 25 | # this one is not fine, link to v2, use v1 which does not provide the v2 symbol 26 | exe_v2: main.c v1/libx.so v2/libx.so 27 | $(CC) -o $@ main.c v2/libx.so "-Wl,-rpath,$(CURDIR)/v1" "-Wl,-rpath,$(CURDIR)/v2" 28 | 29 | check: exe_v1 exe_v2 30 | ../../libtree exe_v1 31 | ../../libtree exe_v2 32 | 33 | clean: 34 | rm -rf v1 v2 exe* 35 | 36 | CURDIR ?= $(.CURDIR) 37 | -------------------------------------------------------------------------------- /tests/06_symbol_versions/main.c: -------------------------------------------------------------------------------- 1 | extern int xyz(); 2 | 3 | int main() { return xyz(); } 4 | -------------------------------------------------------------------------------- /tests/06_symbol_versions/v1.c: -------------------------------------------------------------------------------- 1 | int xyz() { return 3; } 2 | -------------------------------------------------------------------------------- /tests/06_symbol_versions/v1.map: -------------------------------------------------------------------------------- 1 | VER_1 { 2 | global: xyz; 3 | local: *; 4 | }; 5 | -------------------------------------------------------------------------------- /tests/06_symbol_versions/v2.c: -------------------------------------------------------------------------------- 1 | __asm__(".symver xyz_old,xyz@VER_1"); 2 | __asm__(".symver xyz_new,xyz@@VER_2"); 3 | 4 | int xyz_old() { return 3; } 5 | int xyz_new() { return 4; } 6 | 7 | -------------------------------------------------------------------------------- /tests/06_symbol_versions/v2.map: -------------------------------------------------------------------------------- 1 | VER_1 { 2 | global: xyz; 3 | local: *; 4 | }; 5 | 6 | VER_2 { 7 | global: xyz; 8 | local: *; 9 | }; 10 | -------------------------------------------------------------------------------- /tests/07_origin_is_relative_to_symlink_location_not_realpath/Makefile: -------------------------------------------------------------------------------- 1 | # ORIGIN should be resolved relative to the directory the lib was found in, 2 | # even if what we've found is a symlink. So this test creates exe <- a/libf <- 3 | # a/libg and a symlink at b/libf to a/libf, where a/libf has ${ORIGIN} rpath. 4 | 5 | LD_LIBRARY_PATH= 6 | 7 | .PHONY: clean 8 | 9 | all: check 10 | 11 | a/libf.so: 12 | mkdir -p $(@D) 13 | echo 'int f(){return 3;};' | $(CC) -shared -Wl,-soname,$(@F) -o $@ -x c - -nostdlib 14 | 15 | a/libg.so: a/libf.so 16 | mkdir -p $(@D) 17 | echo 'extern int f(); int g(){return f();};' | $(CC) -shared -Wl,-soname,$(@F) '-Wl,-rpath,$${ORIGIN}' -o $@ -x c - -La -lf -nostdlib 18 | 19 | b/libg.so: a/libg.so 20 | mkdir -p $(@D) 21 | ln -vs ../a/libg.so $@ 22 | 23 | exe: a/libg.so b/libg.so 24 | echo 'extern int g(); int _start(){return g();};' | $(CC) -Wl,-soname,$(@F) '-Wl,-rpath,$${ORIGIN}/b' -o $@ -x c - -La -lg -nostdlib 25 | 26 | check: exe 27 | ! ../../libtree exe # should not find libf.so 28 | LD_LIBRARY_PATH=$(CURDIR)/a ../../libtree exe # should find libf.so 29 | 30 | clean: 31 | rm -rf a b exe* 32 | 33 | CURDIR ?= $(.CURDIR) 34 | -------------------------------------------------------------------------------- /tests/08_nodeflib/Makefile: -------------------------------------------------------------------------------- 1 | # When passing -z nodefaultlib, the runtime linker should not consider 2 | # ld.so.conf nor standard paths (/usr/lib, /lib, ...). This is a little bit 3 | # hard to test but in general it means that if you use the system compiler with 4 | # this flag, libc itself would not be located. 5 | # 6 | # There's two tests here: 7 | # 1. exe_a (itself nodefaultlib) depends on a lib with and a lib without -z 8 | # nodefaultlib 9 | # 2. exe_b (itself nodefaultlib) depends on a lib without nodefaultlib. 10 | # 11 | # The question is whether or not libc can be located as a lib in the default 12 | # paths. (The expected behavior is not well documented...) 13 | 14 | .PHONY: clean check 15 | 16 | LD_LIBRARY_PATH= 17 | 18 | all: check 19 | 20 | lib_nodefaultlib.so: 21 | echo 'int f(){return 1;}' | $(CC) -z nodefaultlib -Wl,--no-as-needed -shared -Wl,-soname,$@ -o $@ -x c - 22 | 23 | lib_defaultlib.so: 24 | echo 'int g(){return 1;}' | $(CC) -Wl,--no-as-needed -shared -Wl,-soname,$@ -o $@ -x c - 25 | 26 | exe_a: lib_nodefaultlib.so lib_defaultlib.so 27 | 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 28 | 29 | exe_b: lib_defaultlib.so 30 | echo 'extern int g(); int main(){return g();}' | $(CC) -z nodefaultlib -o $@ -Wl,--disable-new-dtags '-Wl,-rpath,$$ORIGIN' -x c - -L. -l_defaultlib 31 | 32 | check: exe_a exe_b 33 | ! ../../libtree -vvv exe_a # should likely not find libc 34 | ! ../../libtree -vvv exe_b # should likely not find libc 35 | 36 | clean: 37 | rm -f *.so exe* 38 | -------------------------------------------------------------------------------- /tests/10_rpath_order/Makefile: -------------------------------------------------------------------------------- 1 | # This creates exe <- liba.so <- libb.so There are two libraries: 2 | # {first,second}/libb.so and exe has an rpath to first, whereas liba.so has an 3 | # rpath to second 4 | 5 | LD_LIBRARY_PATH= 6 | 7 | .PHONY: clean 8 | 9 | all: check 10 | 11 | first/libb.so: 12 | @mkdir -p $(@D) 13 | echo 'int g(){return 1;}' | $(CC) -shared -Wl,-soname,$(@F) -o $@ -x c - 14 | 15 | second/libb.so: 16 | @mkdir -p $(@D) 17 | echo 'int g(){return 2;}' | $(CC) -shared -Wl,-soname,$(@F) -o $@ -x c - 18 | 19 | liba.so: second/libb.so 20 | 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 21 | 22 | exe: first/libb.so liba.so 23 | echo 'extern int f(); int main(){return f();}' | $(CC) -o $@ -Wl,--disable-new-dtags '-Wl,-rpath,$$ORIGIN,-rpath,$$ORIGIN/first' -x c - -L. -la 24 | 25 | check: exe liba.so 26 | ../../libtree -p liba.so # should find second/libb.so, not first/libb.so 27 | ../../libtree -p exe # should find second/libb.so, not first/libb.so 28 | 29 | clean: 30 | rm -rf -- *.so exe* first second 31 | --------------------------------------------------------------------------------