├── .github └── workflows │ └── c-cpp.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── arch.c ├── arch.h ├── arch ├── aarch64.S ├── arch_aarch64.c ├── arch_arm.c ├── arch_i386.c ├── arch_riscv.c ├── arch_x86_64.c ├── arm.S ├── i386.S ├── riscv.S └── x86_64.S ├── doc ├── man1 │ ├── preloader.1 │ └── preloader_cli.1 └── preloader.odg ├── ipc.c ├── ipc.h ├── load.c ├── load.h ├── log.c ├── log.h ├── preloader ├── preloader.c ├── preloader.h ├── preloader_cli.c ├── reaper.c ├── reaper.h ├── tests ├── test.c └── test.sh ├── util.c ├── util.h └── utils ├── finder.c ├── getlibs.sh ├── khash.h └── ltime.c /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | amd64_build: 11 | name: AMD64 Build 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Build & Tests 18 | run: make tests 19 | - name: Confirm arch 20 | run: file libpreloader.so preloader_cli tests/test 21 | 22 | arm64_build: 23 | name: ARM64 Build 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v3 29 | - name: Install deps 30 | run: sudo apt-get install -y gcc-aarch64-linux-gnu qemu-user-static binfmt-support 31 | - name: Build & Tests 32 | run: CC=aarch64-linux-gnu-gcc QEMU_LD_PREFIX=/usr/aarch64-linux-gnu make tests 33 | - name: Confirm arch 34 | run: file libpreloader.so preloader_cli tests/test 35 | 36 | i386_build: 37 | name: i386 Build 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v3 43 | - name: Install deps 44 | run: sudo apt-get install -y gcc-i686-linux-gnu 45 | - name: Build & Tests 46 | run: CC=i686-linux-gnu-gcc make tests 47 | - name: Confirm arch 48 | run: file libpreloader.so preloader_cli tests/test 49 | 50 | arm32_build: 51 | name: ARM32 Build 52 | runs-on: ubuntu-latest 53 | 54 | steps: 55 | - name: Checkout 56 | uses: actions/checkout@v3 57 | - name: Install deps 58 | run: sudo apt-get install -y gcc-arm-linux-gnueabi qemu-user-static binfmt-support 59 | - name: Build & Tests 60 | run: CC=arm-linux-gnueabi-gcc QEMU_LD_PREFIX=/usr/arm-linux-gnueabi make tests 61 | - name: Confirm arch 62 | run: file libpreloader.so preloader_cli tests/test 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2022 Davidson Francis 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 | 23 | # Default rules. 24 | *.o 25 | *.d 26 | .cache 27 | libpreloader.so 28 | preloader_cli 29 | tests/test 30 | utils/finder 31 | utils/ltest 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Davidson Francis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2022 Davidson Francis 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 | 23 | # Paths 24 | INCLUDE = -I. 25 | TESTS = $(CURDIR)/tests 26 | UTILS = $(CURDIR)/utils 27 | PREFIX ?= /usr/local 28 | BINDIR = $(PREFIX)/bin 29 | MANPAGES = $(CURDIR)/doc/man1 30 | LIBDIR = $(PREFIX)/lib 31 | MANDIR = $(PREFIX)/man 32 | 33 | # If TMPDIR exists, use it instead of /tmp 34 | ifneq ($(TMPDIR),) 35 | CFLAGS += -DPID_PATH=\"$(TMPDIR)\" 36 | endif 37 | 38 | # Flags 39 | CC ?= gcc 40 | CFLAGS += -O2 -g -Wall -Wextra 41 | PREFLAGS = -fPIC $(INCLUDE) -fvisibility=hidden $(CFLAGS) 42 | LDFLAGS = -shared 43 | LDLIBS = -ldl -pthread 44 | 45 | # 46 | # Guess target architecture: 47 | # I know... this is ugly, but there isn't an exactly 48 | # beautiful way to do it via Make/GNU Make. At least 49 | # this approach works automatically with cross-compiler 50 | # and caches the arch for future invocations. 51 | # 52 | # One downside is that it doesn't work in TCC, so it's 53 | # not exactly portable. 54 | # 55 | # Optionally, the user can configure the target 56 | # architecture beforehand, to support TCC and other 57 | # environments (which may not have sed, cut...): 58 | # $ ARCH=x86_64 make CC=tcc 59 | # 60 | ARCH ?= $(shell cat .cache 2>/dev/null || \ 61 | echo | $(CC) -dM -E - | \ 62 | grep -P "__i386__|__x86_64__|__arm__|__aarch64__|__riscv " | \ 63 | cut -d' ' -f2 | sed 's/__//g' | tee .cache) 64 | 65 | OBJ = preloader.o ipc.o util.o log.o load.o reaper.o arch.o 66 | OBJ += arch/arch_$(ARCH).o arch/$(ARCH).o 67 | DEP = $(OBJ:.o=.d) 68 | 69 | # Phone targets 70 | .PHONY: tests finder ltime install uninstall clean 71 | 72 | # Pretty print 73 | Q := @ 74 | ifeq ($(V), 1) 75 | Q := 76 | endif 77 | 78 | # Rules 79 | all: libpreloader.so preloader_cli 80 | 81 | # C Files 82 | %.o: %.c Makefile 83 | @echo " CC $@" 84 | $(Q)$(CC) $< -MMD -MP $(PREFLAGS) -c -o $@ 85 | 86 | # ASM Files 87 | %.o: %.S Makefile 88 | @echo " CC $@" 89 | $(Q)$(CC) $< $(PREFLAGS) -c -o $@ 90 | 91 | # Preloader 92 | libpreloader.so: $(OBJ) 93 | @echo " LD $@" 94 | $(Q)$(CC) $^ $(PREFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ 95 | 96 | # Client program 97 | preloader_cli.o: preloader_cli.c 98 | @echo " CC $@" 99 | $(Q)$(CC) $^ -c -D "PRG_NAME=\"$(basename $@)\"" $(CFLAGS) 100 | preloader_cli: preloader_cli.o 101 | @echo " LD $@" 102 | $(Q)$(CC) $^ -o $@ 103 | 104 | # Tests 105 | tests: libpreloader.so preloader_cli $(TESTS)/test 106 | @bash "$(TESTS)/test.sh" 107 | $(TESTS)/test.o: $(TESTS)/test.c 108 | @echo " CC $@" 109 | $(Q)$(CC) $^ -c -o $@ $(CFLAGS) 110 | $(TESTS)/test: $(TESTS)/test.o 111 | @echo " LD $@" 112 | $(Q)$(CC) $^ -o $@ 113 | 114 | # Finder 115 | finder: $(UTILS)/finder 116 | $(UTILS)/finder.o: $(UTILS)/finder.c 117 | @echo " CC $@" 118 | $(Q)$(CC) $^ -c -o $@ $(CFLAGS) 119 | $(UTILS)/finder: $(UTILS)/finder.o 120 | @echo " LD $@" 121 | $(Q)$(CC) $^ -o $@ -lelf 122 | 123 | # LTime 124 | ltime: $(UTILS)/ltime libpreloader.so preloader_cli 125 | $(UTILS)/ltime.o: $(UTILS)/ltime.c 126 | @echo " CC $@" 127 | $(Q)$(CC) $^ -c -o $@ $(CFLAGS) 128 | $(UTILS)/ltime: $(UTILS)/ltime.o 129 | @echo " LD $@" 130 | $(Q)$(CC) $^ -o $@ -lelf 131 | 132 | # Install 133 | install: libpreloader.so preloader_cli 134 | @echo " INSTALL $^" 135 | $(Q)install -d $(DESTDIR)$(LIBDIR) 136 | $(Q)install -m 755 $(CURDIR)/libpreloader.so $(DESTDIR)$(LIBDIR) 137 | $(Q)install -d $(DESTDIR)$(BINDIR) 138 | $(Q)install -m 755 preloader $(DESTDIR)$(BINDIR) 139 | $(Q)install -m 755 preloader_cli $(DESTDIR)$(BINDIR) 140 | $(Q)install -d $(DESTDIR)$(MANDIR)/man1 141 | $(Q)install -m 644 $(MANPAGES)/*.1 $(DESTDIR)$(MANDIR)/man1 142 | 143 | # Uninstall 144 | uninstall: 145 | $(RM) $(DESTDIR)$(LIBDIR)/libpreloader.so 146 | $(RM) $(DESTDIR)$(BINDIR)/preloader 147 | $(RM) $(DESTDIR)$(BINDIR)/preloader_cli 148 | $(RM) $(DESTDIR)$(MANDIR)/man1/preloader.1 149 | $(RM) $(DESTDIR)$(MANDIR)/man1/preloader_cli.1 150 | 151 | # Clean 152 | clean: 153 | $(RM) .cache 154 | $(RM) $(OBJ) 155 | $(RM) $(DEP) 156 | $(RM) preloader_cli.o 157 | $(RM) $(TESTS)/test.o 158 | $(RM) $(UTILS)/finder.o 159 | $(RM) $(UTILS)/ltime.o 160 | $(RM) $(CURDIR)/libpreloader.so 161 | $(RM) $(CURDIR)/preloader_cli 162 | $(RM) $(TESTS)/test 163 | $(RM) $(UTILS)/finder 164 | $(RM) $(UTILS)/ltime 165 | 166 | -include $(DEP) 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # preloader 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-blueviolet.svg)](https://opensource.org/licenses/MIT) 3 | [![CI](https://github.com/Theldus/preloader/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/Theldus/preloader/actions/workflows/c-cpp.yml) 4 | 5 | Preloader 'pre-loads' dynamically linked executables to speed up their load 6 | times 7 | 8 | ## Why? 9 | 10 | While dynamically linked executables have several advantages over static binaries, 11 | excessive use of dynamic libraries might lead to longer program load times, 12 | whether due to extensive symbol lookup, number of relocations, binary load, and 13 | etc. 14 | 15 | Usually this is not noticeable, but it becomes visible in short-lived processes, 16 | especially on 'older' hardware (e.g., Pentium Dual Core) and embedded devices 17 | like the Raspberry Pi. In this scenario, the load time can consume a significant 18 | amount of the execution time, or even more than the program's runtime. 19 | 20 | ## How it works? 21 | 22 | TL;DR: Preloader functions by injecting a library into the binary to be preloaded. 23 | Following injection, a server is created that listens for fork() requests, 24 | preventing libraries from being loaded again. 25 | 26 |
Longer explanation 27 | 28 | Preloader is divided into two parts: client and server. The image below 29 | depicts the server's operation (in a simplified manner), but an explanation of 30 | both follows below: 31 | 32 | ![preloader](https://user-images.githubusercontent.com/8294550/192086677-2261405a-4cfd-4ec7-a58c-fbe75f486cb6.png) 33 | 34 | Preloader works by injecting a dynamic library (libpreloader.so) to be preloaded 35 | while executing the desired binary. When injected (the library), the library's 36 | 'constructor' modifies the entry point of 'foo' so that when 'foo' is executed, 37 | the preloader's main routine (`preloader_main`) is executed instead of '`_start`'. 38 | 39 | Once '`preloader_main`' is executed, the dynamic loader has already loaded all 40 | dynamic libraries from that binary, as it was supposed to be for `_start/main` 41 | to be running. 42 | 43 | Following that, preloader launches a server that listens for connections from 44 | '_preloader_cli_.' The parameters used in the process execution are sent to each 45 | established connection. After receiving the parameters, a fork is performed, 46 | and the new child process configures the final steps (such as changing the 47 | argv) to begin. As a final step, the initial entry point is restored, and the 48 | child process resumes normal execution. 49 | 50 |
51 | 52 | ## Usage 53 | 54 | Preloader consists of two main components: `preloader` and `preloader_cli`. The 55 | first (preloader) is in charge of starting a server and preloading the program. 56 | The second (preloader_cli) is the client that connects to the server and requests 57 | that a new process is launched with the parameters provided. 58 | 59 | The preloader_cli is meant to work transparently to the user, as if it were the 60 | process itself, by forwarding (and receiving) standard input and outputs, signals, 61 | return code, and such to the original process. The idea is that it acts as a 62 | 'drop-in replacement' for the actual command, behaving similarly. 63 | 64 | Below are some examples of use cases and how to apply the preloader features to 65 | them: 66 | 67 | ### Basic usage 68 | 69 | The most basic example is to run a server, and in another terminal, run the 70 | client: 71 | ```bash 72 | # Terminal 1: start the server 73 | $ preloader /usr/bin/foo 74 | # or 75 | $ preloader foo # (if 'foo' is in the PATH) 76 | 77 | # Terminal 2: launch a preloaded process with the parameters: a, b and c: 78 | $ preloader_cli foo a b c 79 | ``` 80 | 81 | ### Daemon mode `-d,--daemonize`: 82 |
Click to expand 83 | 84 | The recommended way to run the preloader is however in daemon mode. In daemon 85 | mode, preloader runs as a standalone process, just like any server normally 86 | does: 87 | ```bash 88 | $ preloader -d foo 89 | $ preloader_cli foo a b c 90 | 91 | # If want to stop the daemon: 92 | $ preloader -s # (or --stop) 93 | ``` 94 |
95 | 96 | ### Bind mode `-b,--bind-now`: 97 |
Click to expand 98 | 99 | Preloader also supports immediate binding of the process to be executed 100 | (LD_BIND_NOW/RTLD_NOW). As a result, all symbols are resolved at startup rather 101 | than later (lazy binding). 102 | 103 | Generally immediate binding increases the program load time, but for the 104 | preloader it is very beneficial since this loading occurs only once: 105 | 106 | ```bash 107 | # Launches a daemon with bind-now of foo: 108 | $ preloader -b -d foo 109 | $ preloader_cli foo param1 param2... 110 | $ preloader -s 111 | ``` 112 | 113 | This option is not enabled by default, but its use is highly recommended. 114 |
115 | 116 | ### Preload dlopen'ed libs with `-f,--load-libs` / `getlibs.sh`: 117 |
Click to expand 118 | 119 | Sometimes there is a need to preload libraries loaded at runtime (i.e., via 120 | dlopen()). This can happen in different scenarios, such as loading plugins and 121 | libraries for a program or some programming language like Python. For this, 122 | preloader allows preloading these libraries by providing a txt file containing 123 | one library path per line. 124 | 125 | To make this task easier, preloader includes the `utils/getlibs.sh` tool, which 126 | is a bash script that executes the command that the user wants to preload and 127 | generates a txt file containing all of the libraries loaded by that program 128 | until it terminates. 129 | 130 | The general procedure is as follows: 131 | ```bash 132 | # 133 | # Launch the process normally by using getlibs.sh and extract all 134 | # libraries used into the foolibs.txt file 135 | # 136 | $ ./utils/getlibs.sh -o foolibs.txt myfoo param1 param2 ... paramN 137 | 138 | # Launch preloader and preload the foolibs.txt file 139 | $ preloader -f foolibs.txt myfoo 140 | 141 | # Launch client normally 142 | $ preloader_cli myfoo param1 param2 ... paramN 143 | ``` 144 |
145 | 146 | ### Preloading multiple processes 147 |
Click to expand 148 | 149 | Preloader also allows multiple instances to run simultaneously, as long as they 150 | each use a different 'port'¹ number. To specify a different port, use the 151 | `-p,--port` flag. 152 | 153 | If you want to preload foo and bar at the same time, you could do: 154 | ```bash 155 | $ preloader -d -p 4040 foo 156 | $ preloader -d -p 4041 bar 157 | 158 | # Later 159 | $ preloader_cli -p 4040 foo arg1 argN 160 | $ preloader_cli -p 4041 bar a b c 161 | 162 | # Stopping them 163 | $ preloader -s -p 4040 164 | $ preloader -s -p 4041 165 | ``` 166 | 167 | ¹Note: Please note that 'port' does not imply a TCP/UDP port. Preloader uses Unix 168 | Domain Socket for IPC and the port number only serves to compose the socket file 169 | name and distinguish between multiple instances. 170 |
171 | 172 | ### Transparent preloading 173 |
Click to expand 174 | 175 | The '`preloader_cli`' tool works similarly to BusyBox: the argv[0] of preloader_cli 176 | determines the argv[0] of the new process. If preloader_cli's first argument is 177 | not its own name (as in a symbolic link or if the file is renamed), preloader_cli 178 | will use this as the program's first argument. 179 | 180 | If you want to preload `clang` completely transparently to some program, you 181 | could do the following: 182 | 183 | ```bash 184 | cd /home/user/preloader 185 | ln -s preloader_cli clang 186 | export PATH=$PWD:$PATH 187 | ``` 188 | 189 | In this case, the symlink takes precedence in the PATH and is invoked instead 190 | of the original program. 191 |
192 | 193 | ### Tools: `ltime` and `finder`: 194 |
Click to expand 195 | 196 | Preloader also includes tools for evaluating potential programs that can benefit 197 | from preloading, such as `finder` and `ltime`. 198 | 199 | #### Finder 200 | Finder recursively analyzes one or more paths and displays the total number of 201 | relocations (of the executable and all dependent libraries) and the total number 202 | of dynamic libraries required. 203 | 204 | Obtaining the number of relocations *and* libraries gives an idea of how big an 205 | executable can be. After all, an executable with two libraries and 500k relocs 206 | can be as large as one with one hundred libraries and 500k relocs. 207 | 208 | Here are the top-5 programs with the most relocations on my system (Slackware 209 | 14.2-current, 15-ish): 210 | 211 | ```text 212 | /usr/lib64/qt5/libexec/QtWebEngineProcess 196 615522 213 | /usr/bin/lldb-vscode 99 480372 214 | /usr/bin/lldb 99 480166 215 | /usr/bin/clang-11 95 403394 216 | /usr/bin/c-index-test 93 399004 217 | ``` 218 | and the top-5 ordered by library amount: 219 | ```text 220 | /usr/lib64/qt5/libexec/QtWebEngineProcess 196 615522 221 | /usr/bin/SDLvncviewer 190 171352 222 | /usr/bin/ffprobe 187 198403 223 | /usr/bin/ffplay 187 198472 224 | /usr/bin/ffmpeg 187 198544 225 | ``` 226 | 227 | #### Ltime 228 | 229 | Ltime can also take multiple paths and parameters and analyzes them recursively, 230 | just like the finder. However, the purpose of ltime is to analyze program load 231 | time with and without preloader. 232 | 233 | The loading time considered by ltime is the time it takes a program to reach its 234 | entry point (usually `_start`) since, at that point, all libraries have already 235 | been loaded by the dynamic loader. Please note that the load times do not reflect 236 | libraries opened later at runtime. This is the case for many programs, such as 237 | firefox. 238 | 239 | Below are the top-5 load times with and without preloader (i5 7300HQ): 240 | ```text 241 | "/usr/bin/mplayer", 52.136383 ms, 1.689277 ms 242 | "/usr/bin/ffprobe", 49.085384 ms, 1.490358 ms 243 | "/usr/lib64/qt5/libexec/QtWebEngineProcess", 45.864020 ms, 1.565409 ms 244 | "/usr/bin/SDLvncviewer", 41.423378 ms, 1.487050 ms 245 | "/usr/bin/ffmpeg", 37.896008 ms, 1.410382 ms 246 | ``` 247 | 248 | In [this](https://gist.github.com/Theldus/dc08a7183be4d6852d49e8b322bedfd9) 249 | link you can find the runtimes with and without preloader of all 250 | 4645 executables on my system (Slackware 14.2-current + i5 7300HQ). 251 | 252 | A detailed description about these tools can be found in their respective 253 | source code: [ltime.c](utils/ltime.c), [finder.c](utils/finder.c) 254 |
255 | 256 | --- 257 | 258 | More information about the features can be found with `-h, --help` and in the 259 | [preloader.1](doc/man1/preloader.1) and [preloader_cli.1](doc/man1/preloader_cli.1) 260 | manpages. 261 | 262 | ## Benchmarks 263 | 264 | Two programs (`clang` and `ffmpeg`) were tested in four execution environments 265 | to estimate the preloader's performance. The number of shared libraries, total 266 | number of relocations (program + libraries), and load times with and without 267 | preloader were obtained for each execution environment: 268 | 269 | **Environment 1**: Intel Core i5 7300HQ (AMD64) + Slackware 14.2-current 270 | (15-ish), kernel v5.4.186 271 | 272 | | Program | No. of shared libs | No. of relocations | AVG load time (without preloader)| AVG load time (with preloader)| 273 | |:--------------:|:------------------:|:------------------:|:--------------------------------:|:-----------------------------:| 274 | | clang v11.0.0 | 95 | 403394 | 21.4 ms (± 0.21 ms) | 1.29 ms (± 0.03 ms) | 275 | | ffprobe v4.3.1 | 187 | 198403 | 38.86 ms (± 0.72 ms) | 1.76 ms (± 0.05 ms) | 276 | 277 | 278 | **Environment 2**: Intel Pentium T3200 (AMD64) + Slackware 15, kernel v5.15.19 279 | 280 | | Program | No. of shared libs | No. of relocations | AVG load time (without preloader)| AVG load time (with preloader)| 281 | |:--------------:|:------------------:|:------------------:|:--------------------------------:|:-----------------------------:| 282 | | clang v13.0.0 | 95 | 524043 | 70.9 ms (± 0.21 ms) | 3.94 ms (± 0.01 ms) | 283 | | ffprobe v4.4.1 | 204 | 258641 | 153.36 ms (± 2.97 ms) | 5.63 ms (± 0.04 ms) | 284 | 285 | **Environment 3**: Snapdragon 636 (AArch64) + Android 10 (with Termux), kernel 286 | v4.4.192-perf+ 287 | 288 | | Program | No. of shared libs | No. of relocations | AVG load time (without preloader)| AVG load time (with preloader)| 289 | |:--------------:|:------------------:|:------------------:|:--------------------------------:|:-----------------------------:| 290 | | clang v14.0.6 | 12 | 516887 | 122.43 ms (± 8.49 ms) | 30 ms (± 7.45 ms) | 291 | | ffprobe v5.0.1 | 57 | 88878 | 90.39 ms (± 7.73 ms) | 28.92 ms (± 7.96 ms) | 292 | 293 | **Environment 4**: Raspberry Pi 1B rev 2 (armv6) + Raspbian 10, kernel v5.4.51+ 294 | 295 | | Program | No. of shared libs | No. of relocations | AVG load time (without preloader)| AVG load time (with preloader)| 296 | |:--------------:|:------------------:|:------------------:|:--------------------------------:|:-----------------------------:| 297 | | clang v7.0.1 | 15 | 191459 | 219.9 ms (± 0.92 ms) | 27.87 ms (± 0.18 ms) | 298 | | ffprobe v4.1.9 | 187 | 341899 | 2698.41 ms (± 22 ms) | 47.12 ms (± 0.63 ms) | 299 | 300 | The amount of shared libs and relocations were determined using the 'finder' 301 | tool, and the load times were determined using the 'ltime' tool; more info 302 | on these tools can be found in the 'Usage' section. 303 | 304 | The following are the reasons for using `clang` and `ffprobe`: 305 | - Have a great amount of dependencies and relocations. 306 | - Due to the lack of a GUI, time measurement is simple. 307 | - Are short-lived, and load time can contribute a significant portion of total 308 | runtime. 309 | - Are 'scriptable', meaning they can be called dozens or hundreds of times. 310 | 311 | However, there are many other programs that can benefit from the preloader. 312 | Also, because clang is part of the LLVM project, it implies that many other 313 | LLVM-based tools can benefit from it as well. The same goes for ffprobe and 314 | any other program that makes use of the 315 | FFmpeg libraries. 316 | 317 | The table below shows various real-world scenarios in the four environments 318 | described earlier: 319 | 320 | | Description | Env 1 (w & w/o preloader) | Env 2 (w & w/o preloader) | Env 3 (w & w/o preloader) | Env 4 (w & w/o preloader) | 321 | |----------------------------------------------------------|:------------------------------------------:|:----------------------------------------:|:-----------------------------------------:|:-----------------------------------------:| 322 | | clang --version | 4.6 ms (± 0.4 ms) / 25.1 ms (± 0.9 ms) | 13.1 ms (± 0.5 ms) / 75.5 ms (± 0.4 ms) | 60.9 ms (± 3.1 ms) / 152.1 ms (± 5.9 ms) | 118.6 ms (± 0.4 ms) / 314.ms (± 2.9 ms) | 323 | | ffprobe -version | 3.8 ms (± 0.7 ms) / 40.4 ms (± 1.1 ms) | 14.3 ms (± 0.7 ms) / 157.2 ms (± 2.7 ms) | 29.6 ms (± 2.8 ms) / 99.1 ms (± 0.8 ms) | 116 ms (± 0.6 ms) / 2831 ms (± 11 ms) | 324 | | 1ffprobe somefile.mp4 | 15.2 ms (± 0.7 ms) / 51.3 ms (± 0.7 ms) | 41.8 ms (± 0.2 ms) / 183.3 ms (± 1.7 ms) | 92.7 ms (± 4.7 ms) / 149.9 ms (± 5.8 ms) | 705.7 ms (± 1.2 ms) / 3405 ms (± 16 ms) | 325 | | 2clang -c empty_main.c | 8.3 ms (± 0.5 ms) / 30.9 ms (± 0.9 ms) | 23.1 ms (± 0.6 ms) / 89.4 ms (± 0.3 ms) | 94.5 ms (± 6.9 ms) / 166.1 ms (± 7.4 ms) | 503.3 ms (± 0.6 ms) / 703.0 ms (± 5.1 ms) | 326 | | 3clang -c 1kloc.c | 25.8 ms (± 0.5 ms) / 49.9 ms (± 0.8 ms) | 88.3 ms (± 0.4 ms) / 163.6 ms (± 0.4 ms) | 184.5 ms (± 7.4 ms) / 239.0 ms (± 3.1 ms) | 1366 ms (± 6 ms) / 1574 ms (± 4 ms) | 327 | | 4Git #30cc8d w/ clang & -O0 (~494 obj files) | 30.730 s (± 0.262 s) / 34.693 s (± 0.066s) | 4m38s (± 0.14s) / 5m10s (± 0.42s) | -- untested --5 | -- untested --5 | 328 | | 4Qemu v4.4.1 w/ clang & -O0 (~6590 obj files) | 6m54s (± 0.16s) / 7m45s (± 0.23s) | -- untested --5 | -- untested --5 | -- untested --5 | 329 | 330 | ### Notes 331 | 332 | 1. 'somefile.mp4' is literally any file. I've noticed that 'ffprobe \' 333 | has roughly the same runtime for any file. 334 | 2. 'empty_main.c' is equivalent to: `int main(void){}`. 335 | 3. '1kloc.c' is equivalent to 336 | [this](https://gist.github.com/Theldus/09ed2205aa5ba15cdf4571b71cd1c8fc#file-test_aqua-c) 337 | file. 338 | 4. Builds that aren't optimized for Git and Qemu (using all cores available in 339 | each environment). This is because the builds here are focused on rapid 340 | debug-edit-build cycles, and thus, optimized builds (which are slower) are 341 | not the priority. However, there is a time gain for builds with -O2 and -O3, 342 | though it is not as significant as with -O0. 343 | 5. Scenarios marked 'untested' were not tested due to the long runtime. 344 | 6. It should be noted that the builds for Git and Qemu the time was measured for 345 | the 'make' command, and thus, the total build time, not the cumulative total 346 | of clang invocations (think that make can invoke other scripts and programs as 347 | well). 348 | 7. Each test had at least one warmup run and three consecutive runs. The times 349 | were obtained via [hyperfine](https://github.com/sharkdp/hyperfine). 350 | 351 | ## Related works 352 | 353 | Optimizing load times for dynamically linked executables is not new, and 354 | many works have been done on the subject, with 355 | [prelink](https://people.redhat.com/jakub/prelink.pdf) 356 | being the most prominent of them. Prelink achieves this by changing the ELF 357 | structure of the executable (and all shared libs it depends on) and setting a 358 | non-overlapping base address for them (that will serve as a hint to the dynamic 359 | linker later). Of course, this is a *very* simple explanation. Prelink, in fact, 360 | does complex things inside the ELF binary (while being fully reversible) to 361 | achieve prelinking. 362 | 363 | Unfortunately, prelink is no longer maintained (2004-2013) and no longer works 364 | on recent Linux distributions. Even on older Linux distros, I haven't managed 365 | to get it working properly, so I can't compare (performance-wise) with preloader. 366 | 367 | In [Dynamic-prelink](http://worldcomp-proceedings.com/proc/p2014/ESA2955.pdf), 368 | the authors propose a new approach in which libraries are not modified while 369 | still benefiting from ASLR. The relocation information is saved in an external 370 | cache file, and a new dynamic linker capable of prelinking (through its cache 371 | file) is implemented. The results demonstrated that it was faster than 372 | traditional ASLR (no prelink) but slower than Prelink. Unfortunately, no 373 | performance comparisons are possible because the paper does not provide the 374 | source code. 375 | 376 | The authors of 377 | [Performance Characterization of Prelinking and Preloading for Embedded Systems](http://www.cecs.uci.edu/~papers/esweek07/emsoft/p213.pdf) 378 | propose a preloader-like approach: `fork+dlopen()`. The executable that needs to 379 | be preloaded is rebuilt as a shared object so it can be loaded via dlopen(), and 380 | its functions (such as 'main') invoked later. Unfortunately, no source code is 381 | available for possible comparisons. 382 | 383 | Preloader differs from them in: being much simpler and not requiring any changes 384 | to the system or files. In addition, it achieves a great performance reduction 385 | since the forks are from the target process itself, so all relocations 386 | (in theory) are already resolved. 387 | 388 | ## Limitations 389 | 390 | As the preloader is quite 'low-level', there are a number of limitations on the 391 | environment it supports: 392 | 393 | - Operating System: Linux-only 394 | - Architectures supported: ARM32 (armv6), Aarch64 (armv8), i386, x86-64 and RISC-V (RV64I+C) 395 | - Libraries supported: GNU libc, Bionic, and uClibc-ng (do not work on Musl[^muslnote]) 396 | - System tools: Bash, grep, cut, any version 397 | - GNU Make 398 | 399 | [^muslnote]: Unlike GNU libc, Bionic, and uClibc, Musl's dynamic loader loads libraries 400 | after the '_start' of the program, rather than before. This results in the preloader's 401 | main loop not being invoked. While it is possible to fix this by rearranging the main 402 | loop, it would require significant code changes, which are currently not feasible. 403 | 404 | ## Should I use it? 405 | 406 | It depends. A dynamic executable (with or without a preloader) will always be 407 | slower than a static one, so the reasons to use it or not depend solely on the 408 | reasons to use a static binary or not, which may include: 409 | 410 | - **Availability**: There may not exist static packages for your desired program 411 | (and build times can be very time consuming!!) 412 | - **Internet issues**: Downloading a static package might take a long time. 413 | Preloader is lightweight and should download quickly even on slower connections. 414 | - **Disk space**: There may not be enough disk space to download a static version 415 | of a program. 416 | - **Limitations**: Recent Androids only support dynamically linked binaries. 417 | 418 | ## Building/Installing 419 | 420 | Preloader only requires a C99-compatible compiler and a Linux environment: 421 | 422 | ```bash 423 | $ git clone https://github.com/Theldus/preloader.git 424 | $ cd preloader/ 425 | $ make 426 | 427 | # Optionally, if you want to install 428 | $ make install # (PREFIX and DESTDIR allowed here) 429 | 430 | # Building ltime and finder (requires libelf): 431 | $ make finder 432 | $ make ltime 433 | ``` 434 | 435 | ## Contributing 436 | 437 | Preloader is always open to the community and willing to accept contributions, 438 | whether with issues, documentation, testing, new features, bugfixes, typos, and 439 | etc. Welcome aboard. 440 | 441 | ## License and Authors 442 | 443 | Preloader is licensed under MIT License. Written by Davidson Francis and 444 | (hopefully) other 445 | [contributors](https://github.com/Theldus/preloader/graphs/contributors). 446 | -------------------------------------------------------------------------------- /arch.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifndef __UCLIBC__ 31 | #include 32 | #else 33 | #define AT_ENTRY 9 34 | #endif 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | #include "arch.h" 41 | #include "log.h" 42 | #include "util.h" 43 | 44 | #define READ_SIZE 512 45 | 46 | /* Environment variables pointer. */ 47 | extern char **environ; 48 | 49 | /* Private auxiliary vector. */ 50 | static struct auxv_t { 51 | unsigned long type; 52 | unsigned long value; 53 | } auxv[64]; 54 | static int auxv_init; 55 | 56 | /** 57 | * @brief For a given address @p p and minimal given 58 | * size @p min_size, makes the region Read/Write/ 59 | * Executable. 60 | * 61 | * This is needed in order to inject our own code 62 | * into the main entry point.. 63 | * 64 | * @param p Target address. 65 | * @param min_size Minimal size. 66 | * 67 | * @return Returns 0 if success, -1 if error. 68 | * 69 | */ 70 | static int make_rwx(uintptr_t p, size_t min_size) 71 | { 72 | uintptr_t aligned_addr; 73 | size_t target_size; 74 | long page_size; 75 | 76 | page_size = sysconf(_SC_PAGESIZE); 77 | aligned_addr = p & ~(page_size - 1); 78 | target_size = (min_size + (page_size - 1)) & ~(page_size - 1); 79 | 80 | if (mprotect((void*)aligned_addr, target_size, 81 | PROT_READ|PROT_WRITE|PROT_EXEC) < 0) 82 | { 83 | perror("make_rwx:"); 84 | return (-1); 85 | } 86 | return (0); 87 | } 88 | 89 | /** 90 | * @brief Create a local copy of the auxiliary-vector. 91 | * 92 | * The old approach (reading from /proc/self/auxv) is not 93 | * always feasible: qemu-user shares the same /proc/self/auxv 94 | * pointer with the one obtained from main(), which implies 95 | * that auxv (even via /proc) can be changed when via qemu-user. 96 | * 97 | * To work around this, it's best to back up the auxv before 98 | * changing it, and then use the local copy instead of the one 99 | * provided by libc. 100 | */ 101 | static void init_local_auxv(void) 102 | { 103 | char buff[READ_SIZE]; 104 | ssize_t rem_bytes; 105 | ssize_t r; 106 | char *a; 107 | int fd; 108 | 109 | rem_bytes = (ssize_t)sizeof(auxv); 110 | 111 | fd = open("/proc/self/auxv", O_RDONLY); 112 | if (fd < 0) 113 | die("No /proc/self/auxv available, giving up...\n"); 114 | 115 | a = (char*)&auxv; 116 | 117 | while ((r = read(fd, buff, READ_SIZE)) > 0) 118 | { 119 | if (r > rem_bytes) 120 | die("Unable to fit entire system auxv!\n"); 121 | 122 | memcpy(a, buff, r); 123 | a += r; 124 | rem_bytes -= r; 125 | } 126 | 127 | auxv_init = 1; 128 | close(fd); 129 | } 130 | 131 | /** 132 | * @brief Retrieve a value from the auxiliary vector 133 | * 134 | * @param type Type to be retrieved. 135 | * 136 | * @return On success, returns the value corresponding 137 | * to @p type. Otherwise, returns 0. 138 | * 139 | * @note This function was made to replace the original 140 | * getauxval(): as with 'environ', auxv is also shifted 141 | * to the left and the reference that libc makes of it 142 | * no longer makes sense. 143 | * 144 | * Since there is no way to 'fix' the auxv pointer (and 145 | * this is quite library dependent), this function 'fixes' 146 | * getauxval() by replacing the original with another of 147 | * the same signature, which reads data directly from 148 | * /proc/self/auxv, which should always work. 149 | */ 150 | __attribute__((visibility("default"))) 151 | unsigned long getauxval(unsigned long type) 152 | { 153 | struct auxv_t *a; 154 | 155 | if (!auxv_init) 156 | init_local_auxv(); 157 | 158 | for (a = &auxv[0]; a->type; a++) 159 | if (a->type == type) 160 | return (a->value); 161 | 162 | return (0); 163 | } 164 | 165 | /** 166 | * @brief Replace the old argv (1..200) for the one supplied 167 | * in the preloader_cli in the new-forked process. 168 | * 169 | * @param argc Amount of arguments. 170 | * @param cwd_argv Argument list. 171 | * @param sp Stack pointer pointing to the first argv element. 172 | */ 173 | void arch_change_argv(int argc, char *cwd_argv, uintptr_t *sp) 174 | { 175 | char *p; /* Character pointer. */ 176 | int count; /* Current argv pos. */ 177 | char *argv; /* Argv pointer. */ 178 | uintptr_t *dest; 179 | uintptr_t *src; 180 | uintptr_t *len; 181 | 182 | /* Skip CWD. */ 183 | for (p = cwd_argv; *p != '\0'; p++); 184 | p++; /* skip CWD-nul char. */ 185 | 186 | /* Set argv nicely. */ 187 | for (count = 0, argv = p; count < argc; p++) 188 | { 189 | if (*p == '\0') 190 | { 191 | sp[count++] = (uintptr_t)argv; 192 | argv = p + 1; 193 | } 194 | } 195 | 196 | dest = &sp[count]; 197 | 198 | /* Advance pointer until find a NUL, this is our source. */ 199 | for (src = dest; *src; src++); 200 | 201 | /* Advance until find two NULs, this is our length. */ 202 | for (len = src + 1; *len; len++); 203 | for (len = len + 1; *len; len++); 204 | 205 | /* 206 | * Once the argv, envp and auxv were shifted to the left, 207 | * the reference glibc made to 'envp' no longer makes 208 | * sense and needs to be updated. This isn't exactly 209 | * pretty, but it's the only way I've found to keep the 210 | * argument list (when getting to _start) as expected 211 | * _and_ not messing up glibc. 212 | * 213 | * Although I make references to glibc here, this 214 | * should (or should) work in other libs such as Bionic. 215 | */ 216 | environ = (char **)&dest[1]; 217 | 218 | /* Move envp and auxv into the parameter list. */ 219 | while (dest <= len) 220 | *dest++ = *src++; 221 | } 222 | 223 | /** 224 | * @brief Given @p old_argc and @p new_argc, validates if the 225 | * @p new_argc is lesser than @p old_argc, i.e: if the new 226 | * argument list fits in our old argument list. 227 | * 228 | * @param old_argc Old argument count (e.g: 1..200). 229 | * @param new_argc New argument count received from 230 | * preloader_cli. 231 | */ 232 | void arch_validate_argc(int old_argc, int new_argc) 233 | { 234 | if (old_argc < new_argc) 235 | die("PRELOADed lib has argc (%d) less than the required (%d) argc!\n" 236 | "Please launch with a greater argc!\n", old_argc, new_argc); 237 | } 238 | 239 | /** 240 | * @brief Initialize arch-related things: retrieves 241 | * the program entry-point, make it RWX and patch-it 242 | * with a call to arch_pre_daemon_main(). 243 | */ 244 | void arch_setup(void) 245 | { 246 | COMPILE_TIME_ASSERT(sizeof(void*) == sizeof(uintptr_t)); 247 | 248 | uintptr_t arch_addr_start; 249 | 250 | /* Get _start/entry point address. */ 251 | if (!(arch_addr_start = getauxval(AT_ENTRY))) 252 | die("Unable to get AT_ENTRY, aborting...\n"); 253 | 254 | log_info("AT_ENTRY: %" PRIxPTR"\n", arch_addr_start); 255 | 256 | /* Make _start RWX. */ 257 | if (make_rwx(arch_addr_start, 512) < 0) 258 | die("Unable to set entry point as RWX!...\n"); 259 | 260 | /* Patch _start/entry point. */ 261 | arch_patch_start(arch_addr_start); 262 | } 263 | -------------------------------------------------------------------------------- /arch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef ARCH_H 26 | #define ARCH_H 27 | 28 | #include 29 | extern void arch_setup(void); 30 | extern void arch_pre_daemon_main(void); 31 | extern int arch_patch_start(uintptr_t start); 32 | 33 | #endif /* ARCH_H */ 34 | -------------------------------------------------------------------------------- /arch/aarch64.S: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | .section .bss 26 | .type new_argc, %object 27 | .size new_argc, 8 28 | new_argc: .zero 8 29 | 30 | /* 31 | * This is the preloader's main entrypoint 32 | * 33 | * Register usage: 34 | * x19: atexit_ptr/x0 backup 35 | * x20: cwd_argv 36 | * x21: tmp backup of new_argc value 37 | * x22: new_argc address 38 | * everything else: tmp values/args 39 | */ 40 | .align 8 41 | .section .text 42 | .globl arch_pre_daemon_main 43 | .type arch_pre_daemon_main, %function 44 | arch_pre_daemon_main: 45 | stp fp, lr, [sp, -16]! 46 | mov fp, sp 47 | 48 | /* Backup atexit pointer. */ 49 | mov x19, x0 50 | 51 | /* Call our daemon main 52 | * Return: returns cwd_argv in x0. */ 53 | adrp x0, :got:new_argc 54 | ldr x0, [x0, #:got_lo12:new_argc] 55 | mov x22, x0 /* Backup new_argc addr. */ 56 | bl daemon_main 57 | mov x20, x0 /* Backup our cwd_argv. */ 58 | 59 | /* Grab our old argc and compare to the new one 60 | * Our stack: 61 | * 24(sp) -> argv 62 | * 16(sp) -> argc 63 | * 8(sp) -> ret_addr/lr 64 | * 0(sp) -> fp */ 65 | ldr x0, [sp, #16] 66 | ldr x1, [x22] 67 | mov x21, x1 /* tmp backup of our new_argc. */ 68 | bl arch_validate_argc 69 | 70 | /* Change argc. */ 71 | str x21, [sp, #16] 72 | 73 | /* Change argv. */ 74 | mov x0, x21 /* new_argc */ 75 | mov x1, x20 /* cwd_argv */ 76 | add x2, sp, #24 /* sp[IDX_ARGV] */ 77 | bl arch_change_argv 78 | 79 | /* Restore original entry point content 80 | * Return: returns the size of the patch in x0. */ 81 | bl arch_restore_start 82 | 83 | /* Fix return address. */ 84 | mov sp, fp 85 | ldp fp, lr, [sp], 16 86 | sub lr, lr, x0 /* decrease patch size. */ 87 | 88 | /* Restore proper x0/atexit value and return. */ 89 | mov x0, x19 90 | ret 91 | -------------------------------------------------------------------------------- /arch/arch_aarch64.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include "arch.h" 29 | #include "log.h" 30 | #include "util.h" 31 | 32 | #ifndef __aarch64__ 33 | #error "This file should be built for aarch64-only targets!" 34 | #endif 35 | 36 | /* Entry point address. */ 37 | static uintptr_t arch_addr_start; 38 | 39 | /* 40 | * Patch instructions that will be injected on 41 | * _start, in order to call arch_pre_daemon_main(). 42 | * 43 | * @note: Instructions big-endian encoded. 44 | * x0/w0 should be preserved. 45 | */ 46 | static uint8_t patch[] = { 47 | /* ldr x1, 8 */ 48 | 0x41, 0x00, 0x00, 0x58, 49 | /* blr x1 */ 50 | 0x20, 0x00, 0x3f, 0xd6, 51 | /* target address to be loaded */ 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | }; 54 | 55 | /* Backup of original instructions at the beginning 56 | * of start. */ 57 | static uint8_t bck_start[sizeof patch] = {0}; 58 | 59 | /** 60 | * @brief Restore original instructions saved in bck_start. 61 | * 62 | * @return Returns the amount needed to be decremented 63 | * in order to fix the return address. 64 | */ 65 | size_t arch_restore_start(void) { 66 | memcpy((char*)arch_addr_start, bck_start, sizeof bck_start); 67 | return (sizeof bck_start) - 8; /* disregard target addr size. */ 68 | } 69 | 70 | /** 71 | * @brief Patch _start in order to call arch_pre_daemon_main(). 72 | * 73 | * @return Always 0. 74 | */ 75 | int arch_patch_start(uintptr_t start) 76 | { 77 | COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(uint64_t)); 78 | 79 | int i; 80 | 81 | union 82 | { 83 | int64_t addr; 84 | uint8_t val[8]; 85 | } u_addr; 86 | 87 | arch_addr_start = start; 88 | 89 | /* Backup the bytes that we're overwritten. */ 90 | memcpy(bck_start, (void*)start, sizeof patch); 91 | 92 | /* Calculate rel-offset. */ 93 | u_addr.addr = (uint64_t)arch_pre_daemon_main; 94 | 95 | /* Patch: constant to be loaded. */ 96 | for (i = 0; i < 8; i++) 97 | patch[8 + i] = u_addr.val[i]; 98 | 99 | memcpy((void*)start, patch, sizeof patch); 100 | return (0); 101 | } 102 | -------------------------------------------------------------------------------- /arch/arch_arm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include "arch.h" 29 | #include "log.h" 30 | #include "util.h" 31 | 32 | #ifndef __arm__ 33 | #error "This file should be built for arm-only targets!" 34 | #endif 35 | 36 | /* Entry point address. */ 37 | static uintptr_t arch_addr_start; 38 | 39 | /* 40 | * Patch instructions that will be injected on 41 | * _start, in order to call arch_pre_daemon_main(). 42 | * 43 | * @note: Instructions big-endian encoded. 44 | * R0/A1 should be preserved. 45 | */ 46 | static uint8_t patch[] = { 47 | /* ldr r1, [pc] */ 48 | 0x00, 0x10, 0x9f, 0xe5, 49 | /* blx r1 */ 50 | 0x31, 0xff, 0x2f, 0xe1, 51 | /* target address to be loaded */ 52 | 0x00, 0x00, 0x00, 0x00 53 | }; 54 | 55 | /* Backup of original instructions at the beginning 56 | * of start. */ 57 | static uint8_t bck_start[sizeof patch] = {0}; 58 | 59 | /** 60 | * @brief Restore original instructions saved in bck_start. 61 | * 62 | * @return Returns the amount needed to be decremented 63 | * in order to fix the return address. 64 | */ 65 | size_t arch_restore_start(void) { 66 | memcpy((char*)arch_addr_start, bck_start, sizeof bck_start); 67 | return (sizeof bck_start) - 4; /* disregard target addr size. */ 68 | } 69 | 70 | /** 71 | * @brief Patch _start in order to call arch_pre_daemon_main(). 72 | * 73 | * @return Always 0. 74 | */ 75 | int arch_patch_start(uintptr_t start) 76 | { 77 | COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(uint32_t)); 78 | 79 | int i; 80 | 81 | union 82 | { 83 | int32_t addr; 84 | uint8_t val[4]; 85 | } u_addr; 86 | 87 | arch_addr_start = start; 88 | 89 | /* Backup the bytes that we're overwritten. */ 90 | memcpy(bck_start, (void*)start, sizeof patch); 91 | 92 | /* Calculate rel-offset. */ 93 | u_addr.addr = (uint32_t)arch_pre_daemon_main; 94 | 95 | /* Patch: constant to be loaded. */ 96 | for (i = 0; i < 4; i++) 97 | patch[8 + i] = u_addr.val[i]; 98 | 99 | memcpy((void*)start, patch, sizeof patch); 100 | return (0); 101 | } 102 | -------------------------------------------------------------------------------- /arch/arch_i386.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include "arch.h" 29 | #include "log.h" 30 | #include "util.h" 31 | 32 | #ifndef __i386__ 33 | #error "This file should be built for i386-only targets!" 34 | #endif 35 | 36 | /* Entry point address. */ 37 | static uintptr_t arch_addr_start; 38 | 39 | /* 40 | * Patch instructions that will be injected on 41 | * _start, in order to call arch_pre_daemon_main(). 42 | * 43 | * @note: EDX should be preserved. 44 | */ 45 | static uint8_t patch[] = { 46 | /* mov $imm32, %eax. */ 47 | 0xb8, 48 | 0x00, 0x00, 0x00, 0x00, 49 | /* call *%eax. */ 50 | 0xff, 0xd0 51 | }; 52 | 53 | /* Backup of original instructions at the beginning 54 | * of start. */ 55 | static uint8_t bck_start[sizeof patch] = {0}; 56 | 57 | /** 58 | * @brief Restore original instructions saved in bck_start. 59 | * 60 | * @return Returns the amount needed to be decremented 61 | * in order to fix the return address. 62 | */ 63 | size_t arch_restore_start(void) { 64 | memcpy((char*)arch_addr_start, bck_start, sizeof bck_start); 65 | return (sizeof bck_start); 66 | } 67 | 68 | /** 69 | * @brief Patch _start in order to call arch_pre_daemon_main(). 70 | * 71 | * @return Always 0. 72 | */ 73 | int arch_patch_start(uintptr_t start) 74 | { 75 | COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(uint32_t)); 76 | 77 | int i; 78 | 79 | union 80 | { 81 | int32_t addr; 82 | uint8_t val[4]; 83 | } u_addr; 84 | 85 | arch_addr_start = start; 86 | 87 | /* Backup the bytes that we're overwritten. */ 88 | memcpy(bck_start, (void*)start, sizeof patch); 89 | 90 | /* Calculate rel-offset. */ 91 | u_addr.addr = (uint32_t)arch_pre_daemon_main; 92 | 93 | /* Patch: movabs $imm64, %rax. */ 94 | for (i = 0; i < 4; i++) 95 | patch[1 + i] = u_addr.val[i]; 96 | 97 | memcpy((void*)start, patch, sizeof patch); 98 | return (0); 99 | } 100 | -------------------------------------------------------------------------------- /arch/arch_riscv.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include "arch.h" 29 | #include "log.h" 30 | #include "util.h" 31 | 32 | 33 | /* Entry point address. */ 34 | static uintptr_t arch_addr_start; 35 | 36 | /* 37 | * Patch instructions that will be injected on 38 | * _start, in order to call arch_pre_daemon_main(). 39 | * 40 | * @note: Instructions big-endian encoded. 41 | * x0/w0 should be preserved. 42 | */ 43 | 44 | static uint8_t patch[] = { 45 | /* auipc a1, 0 */ 46 | 0x97,0x05,0x00,0x00, 47 | /* ld a1, 8(a1) */ 48 | 0x8c,0x65, 49 | /* jalr a1 */ 50 | 0x82,0x95, 51 | /* target address to be loaded */ 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | }; 54 | 55 | 56 | /* Backup of original instructions at the beginning 57 | * of start. */ 58 | static uint8_t bck_start[sizeof patch] = {0}; 59 | 60 | /** 61 | * @brief Restore original instructions saved in bck_start. 62 | * 63 | * @return Returns the amount needed to be decremented 64 | * in order to fix the return address. 65 | */ 66 | size_t arch_restore_start(void) { 67 | memcpy((char*)arch_addr_start, bck_start, sizeof bck_start); 68 | return (sizeof bck_start) - 8; /* disregard target addr size. */ 69 | } 70 | 71 | /** 72 | * @brief Patch _start in order to call arch_pre_daemon_main(). 73 | * 74 | * @return Always 0. 75 | */ 76 | int arch_patch_start(uintptr_t start) 77 | { 78 | COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(uint64_t)); 79 | 80 | int i; 81 | 82 | union 83 | { 84 | int64_t addr; 85 | uint8_t val[8]; 86 | } u_addr; 87 | 88 | arch_addr_start = start; 89 | 90 | /* Backup the bytes that we're overwritten. */ 91 | memcpy(bck_start, (void*)start, sizeof patch); 92 | 93 | /* Calculate rel-offset. */ 94 | u_addr.addr = (uint64_t)arch_pre_daemon_main; 95 | 96 | /* Patch: constant to be loaded. */ 97 | for (i = 0; i < 8; i++) 98 | patch[8 + i] = u_addr.val[i]; 99 | 100 | memcpy((void*)start, patch, sizeof patch); 101 | return (0); 102 | } 103 | -------------------------------------------------------------------------------- /arch/arch_x86_64.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include "arch.h" 29 | #include "log.h" 30 | #include "util.h" 31 | 32 | #ifndef __x86_64__ 33 | #error "This file should be built for x86_64-only targets!" 34 | #endif 35 | 36 | /* Entry point address. */ 37 | static uintptr_t arch_addr_start; 38 | 39 | /* 40 | * Patch instructions that will be injected on 41 | * _start, in order to call arch_pre_daemon_main(). 42 | * 43 | * @note: RDX should be preserved. 44 | */ 45 | static uint8_t patch[] = { 46 | /* movabs $imm64, %rax. */ 47 | 0x48, 0xb8, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | /* callq *%rax. */ 50 | 0xff, 0xd0 51 | }; 52 | 53 | /* Backup of original instructions at the beginning 54 | * of start. */ 55 | static uint8_t bck_start[sizeof patch] = {0}; 56 | 57 | /** 58 | * @brief Restore original instructions saved in bck_start. 59 | * 60 | * @return Returns the amount needed to be decremented 61 | * in order to fix the return address. 62 | */ 63 | size_t arch_restore_start(void) { 64 | memcpy((char*)arch_addr_start, bck_start, sizeof bck_start); 65 | return (sizeof bck_start); 66 | } 67 | 68 | /** 69 | * @brief Patch _start in order to call arch_pre_daemon_main(). 70 | * 71 | * @return Always 0. 72 | */ 73 | int arch_patch_start(uintptr_t start) 74 | { 75 | COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(uint64_t)); 76 | 77 | int i; 78 | 79 | union 80 | { 81 | int64_t addr; 82 | uint8_t val[8]; 83 | } u_addr; 84 | 85 | arch_addr_start = start; 86 | 87 | /* Backup the bytes that we're overwritten. */ 88 | memcpy(bck_start, (void*)start, sizeof patch); 89 | 90 | /* Calculate rel-offset. */ 91 | u_addr.addr = (uint64_t)arch_pre_daemon_main; 92 | 93 | /* Patch: movabs $imm64, %rax. */ 94 | for (i = 0; i < 8; i++) 95 | patch[2 + i] = u_addr.val[i]; 96 | 97 | memcpy((void*)start, patch, sizeof patch); 98 | return (0); 99 | } 100 | -------------------------------------------------------------------------------- /arch/arm.S: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | /* 26 | * This is the preloader's main entrypoint 27 | * 28 | * Register usage: 29 | * r4: atexit_ptr/a1/r0 backup 30 | * r5: cwd_argv 31 | * r6: tmp backup of new_argc value 32 | * everything else: tmp values/args 33 | */ 34 | .align 8 35 | .section .text 36 | .globl arch_pre_daemon_main 37 | .type arch_pre_daemon_main, %function 38 | arch_pre_daemon_main: 39 | push {fp, lr} 40 | add fp, sp, #0 41 | sub sp, sp, #8 42 | 43 | @ Backup atexit pointer 44 | mov r4, a1 45 | 46 | @ Call our daemon main 47 | @ Return: returns cwd_argv in r0/a1 48 | mov a1, sp @ sp[0] == old_argc 49 | bl daemon_main 50 | mov r5, a1 @ Backup our cwd_argv 51 | 52 | @ Grab our old argc and compare to the new one 53 | @ Our stack: 54 | @ 20(sp) -> argv 55 | @ 16(sp) -> argc 56 | @ 12(sp) -> ret_addr/lr 57 | @ 8(sp) -> fp 58 | @ 4(sp) -> nothing 59 | @ 0(sp) -> new_argc 60 | ldr a1, [sp, #16] 61 | ldr a2, [sp, #0] 62 | mov r6, a2 @ tmp backup of our new_argc 63 | bl arch_validate_argc 64 | 65 | @ Change argc 66 | str r6, [sp, #16] 67 | 68 | @ Change argv 69 | mov a1, r6 @ new_argc 70 | mov a2, r5 @ cwd_argv 71 | add a3, sp, #20 @ sp[IDX_ARGV] 72 | bl arch_change_argv 73 | 74 | @ Restore original entry point content 75 | @ Return: returns the size of the patch in r0/a1 76 | bl arch_restore_start 77 | 78 | @ Fix return address 79 | add sp, fp, #0 80 | pop {fp, lr} 81 | sub lr, lr, a1 @ decrease patch size 82 | 83 | @ Restore proper a1/r0/atexit value and return 84 | mov a1, r4 85 | bx lr 86 | -------------------------------------------------------------------------------- /arch/i386.S: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | /* Stack layout. */ 26 | .equ argv_pos, 28 27 | .equ argc_pos, 24 28 | .equ ret_addr_pos, 20 29 | .equ ebp_pos, 16 30 | 31 | /* Arguments. */ 32 | .equ new_argc_pos, 12 33 | .equ third_arg, 8 34 | .equ second_arg, 4 35 | .equ first_arg, 0 36 | 37 | /* Quick macro to load from a stack and store in the stack. */ 38 | .macro stack2stack src dest 39 | movl \src(%esp), %eax 40 | movl %eax, \dest(%esp) 41 | .endm 42 | 43 | /* Quick macro to load an address from stack and store in the stack. */ 44 | .macro lea2stack src dest 45 | lea \src(%esp), %eax 46 | mov %eax, \dest(%esp) 47 | .endm 48 | 49 | /* 50 | * This is the preloader's main entrypoint 51 | * 52 | * Register usage: 53 | * esi: atexit_ptr/%edx backup 54 | * edi: cwd_argv 55 | * everything else: tmp values/args 56 | */ 57 | .section .text 58 | .globl arch_pre_daemon_main 59 | .type arch_pre_daemon_main, @function 60 | arch_pre_daemon_main: 61 | push %ebp 62 | mov %esp, %ebp 63 | sub $16, %esp 64 | 65 | # Backup atexit pointer 66 | mov %edx, %esi 67 | 68 | # Call our daemon main 69 | # Return: returns cwd_argv in %eax 70 | lea2stack new_argc_pos first_arg 71 | call daemon_main 72 | mov %eax, %edi # Backup our cwd_argv 73 | 74 | # Grab our old argc and compare to the new one 75 | # Our stack: 76 | # 28(%esp) -> argv 77 | # 24(%esp) -> argc 78 | # 20(%esp) -> ret_addr 79 | # 16(%esp) -> rbp 80 | # 12(%esp) -> new_argc 81 | # 8(%esp) -> third func arg 82 | # 4(%esp) -> second func arg 83 | # 0(%esp) -> first func arg 84 | 85 | stack2stack argc_pos first_arg 86 | stack2stack new_argc_pos second_arg 87 | call arch_validate_argc 88 | 89 | # Change argc 90 | stack2stack new_argc_pos argc_pos # eax = new_argc_pos 91 | 92 | # Change argv 93 | mov %eax, first_arg(%esp) # new_argc_pos 94 | mov %edi, second_arg(%esp) # cwd_argv 95 | lea2stack argv_pos third_arg # sp[IDX_ARGV] 96 | call arch_change_argv 97 | 98 | # Restore original entry point content 99 | # Return: returns the size of the patch in %rax 100 | call arch_restore_start 101 | 102 | # Fix return address 103 | sub %eax, ret_addr_pos(%esp) 104 | 105 | # Restore %edx and return 106 | mov %esi, %edx 107 | leave 108 | ret 109 | -------------------------------------------------------------------------------- /arch/riscv.S: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | .section .bss 26 | .type new_argc, %object 27 | .size new_argc, 8 28 | new_argc: .zero 8 29 | 30 | /* 31 | * This is the preloader's main entrypoint 32 | * 33 | * Register usage: 34 | * s3: atexit_ptr/x0 backup 35 | * s4: cwd_argv 36 | * s5: tmp backup of new_argc value 37 | * s6: new_argc address 38 | * everything else: tmp values/args 39 | */ 40 | .align 8 41 | .section .text 42 | .globl hellomiaou 43 | .globl arch_pre_daemon_main 44 | .type arch_pre_daemon_main, %function 45 | arch_pre_daemon_main: 46 | addi sp, sp, -16 47 | sd ra, 8(sp) 48 | sd fp, 0(sp) 49 | mv fp, sp 50 | 51 | /* Backup atexit pointer. */ 52 | mv s3, a0 53 | 54 | /* Call our daemon main 55 | * Return: returns cwd_argv in x0. */ 56 | la a0, new_argc 57 | mv s6, a0 /* Backup new_argc addr. */ 58 | call daemon_main 59 | mv s4, a0 /* Backup our cwd_argv. */ 60 | 61 | /* Grab our old argc and compare to the new one 62 | * Our stack: 63 | * 24(sp) -> argv 64 | * 16(sp) -> argc 65 | * 8(sp) -> ret_addr/lr 66 | * 0(sp) -> fp */ 67 | ld a0, 16(sp) 68 | ld a1, 0(s6) 69 | mv s5, a1 /* tmp backup of our new_argc. */ 70 | call arch_validate_argc 71 | 72 | /* Change argc. */ 73 | sd s5, 16(sp) 74 | 75 | /* Change argv. */ 76 | mv a0, s5 /* new_argc */ 77 | mv a1, s4 /* cwd_argv */ 78 | addi a2, sp, 24 /* sp[IDX_ARGV] */ 79 | call arch_change_argv 80 | 81 | /* Restore original entry point content 82 | * Return: returns the size of the patch in x0. */ 83 | call arch_restore_start 84 | 85 | /* Fix return address. */ 86 | mv sp, fp 87 | ld fp, 0(sp) 88 | ld ra, 8(sp) 89 | addi sp, sp, 16 90 | sub ra, ra, a0 /* decrease patch size. */ 91 | 92 | /* Restore proper x0/atexit value and return. */ 93 | mv a0, s3 94 | ret 95 | -------------------------------------------------------------------------------- /arch/x86_64.S: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | .section .bss 26 | .lcomm new_argc, 8 27 | 28 | /* 29 | * This is the preloader's main entrypoint 30 | * 31 | * After the entrypoint/_start is modified, this function is executed in place 32 | * of the original code. 33 | * 34 | * This function is responsible for backing up/restore the pointer to be 35 | * registered in atexit(BA_OS), as well as invoking the 'daemon_main'. 36 | * 37 | * After the child process is forked and returns from daemon_main, this 38 | * function changes the arguments to the new argc/argv (via arch_change_argv), 39 | * retrieves the old content of _start/entrypoint and restarts execution as 40 | * if nothing had happened. 41 | * 42 | * Note: 43 | * This function was chosen to be written in assembly because of the expected 44 | * layout of the stack and registers at the beginning of _start: it was 'too 45 | * dangerous' to trust the C compiler to write code that was not optimized in 46 | * unexpected ways, plus small snippets of ASM would still be needed, even in 47 | * C. So choosing to write in ASM was the best and safest option, and it 48 | * allows us to optimize the rest of the code knowing that the 49 | * architecture-dependent part works as expected every time. 50 | * 51 | * Despite this, the general behavior should be similar on other architectures 52 | * as well. You can use this code as a starting point to write for other 53 | * architectures if you want to port the preloader to them. 54 | * 55 | * Register usage: 56 | * r12: atexit_ptr/%rdx backup 57 | * r13: cwd_argv 58 | * rax, rdi, rsi, rdx: tmp values/args 59 | */ 60 | .section .text 61 | .globl arch_pre_daemon_main 62 | .type arch_pre_daemon_main, @function 63 | arch_pre_daemon_main: 64 | push %rbp 65 | mov %rsp, %rbp 66 | 67 | # Backup atexit pointer 68 | mov %rdx, %r12 69 | 70 | # Call our daemon main 71 | # Return: returns cwd_argv in %rax 72 | lea new_argc(%rip), %rdi 73 | call daemon_main 74 | mov %rax, %r13 # Backup our cwd_argv 75 | 76 | # Grab our old argc and compare to the new one 77 | # Our stack: 78 | # 24(%rsp) -> argv 79 | # 16(%rsp) -> argc 80 | # 8(%rsp) -> ret_addr 81 | # 0(%rsp) -> rbp 82 | movl 16(%rsp), %edi 83 | movl new_argc(%rip), %esi 84 | call arch_validate_argc 85 | 86 | # Change argc 87 | mov new_argc(%rip), %rdi # First arg of change_argv too 88 | mov %rdi, 16(%rsp) 89 | 90 | # Change argv 91 | mov %r13, %rsi # cwd_argv 92 | lea 24(%rsp), %rdx # sp[IDX_ARGV] 93 | call arch_change_argv 94 | 95 | # Restore original entry point content 96 | # Return: returns the size of the patch in %rax 97 | call arch_restore_start 98 | 99 | # Fix return address 100 | sub %rax, 8(%rsp) 101 | 102 | # Restore %rdx and return 103 | mov %r12, %rdx 104 | leaveq 105 | retq 106 | -------------------------------------------------------------------------------- /doc/man1/preloader.1: -------------------------------------------------------------------------------- 1 | .\" MIT License 2 | .\" 3 | .\" Copyright (c) 2022 Davidson Francis 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 | .\" 23 | .TH "preloader" "1" "" "" "preloader man page" 24 | .SH NAME 25 | preloader \- preloads a program into memory to decrease load times 26 | .SH SYNOPSIS 27 | \fBpreloader\fR [\fIoptions\fR]... \fIprogram_name\fR 28 | .SH DESCRIPTION 29 | Although dynamically built executables has several advantages over static ones, 30 | overuse of dynamic libraries and/or large libraries can result in longer program 31 | load times. 32 | .PP 33 | While this is usually not noticeable, it becomes visible in short-lived 34 | processes, especially on older hardware and embedded devices like the Raspberry 35 | Pi. In this scenario, the load time can consume a significant amount of the 36 | execution time, or even more than the program's runtime. 37 | .PP 38 | Preloader attempts to address this issue by 'pre-loading' a program into 39 | memory in order to reduce the program's loading time. The preloaded process 40 | runs a server that listens for 41 | .BR preloader_cli (1) 42 | requests. Each request results in a new process being forked (with the 43 | libraries already loaded) and executed with the arguments provided by 44 | \fBpreloader_cli\fR(1). 45 | .SH OPTIONS 46 | .TP 47 | \fB\-p, \-\-port \fIport\fR 48 | Specifies the \fIport\fR to be listening (default: 3636). Note: Please note 49 | that \fIport\fR is just an abstraction. Preloader uses Unix Domain Sockets 50 | for IPC, and the port number only serves to compose the socket file name and 51 | distinguish between multiple instances. 52 | .TP 53 | \fB\-b, \-\-bind\-now 54 | Ask the dynamic linker to resolve all references immediately. This can shorten 55 | load times, so it might be useful to use it 56 | .TP 57 | \fB\-d, \-\-daemonize 58 | Run preloader as a daemon process, without blocking the terminal. Please note 59 | that while in daemon mode, logs are only visible if they are explicitly saved 60 | to file with the \fB-o\fR option. 61 | .TP 62 | \fB\-f, \-\-load\-libs \fItext_file\fR 63 | Loads a \fItext_file\fR containing a list of libraries (one per line). This is 64 | particularly useful when the library list is only known at runtime. The 65 | \fButils/getlibs.sh\fR helper script can run a program and return the complete 66 | list of libraries loaded at runtime. 67 | .TP 68 | \fB\-s, \-\-stop 69 | Stops the \fBpreloader\fR server for the default port, or for a specific port if 70 | \fB-p\fR is used. 71 | .TP 72 | \fB\-h, \-\-help 73 | Print (on the standard error) a brief usage of \fBpreloader\fR. 74 | .PP 75 | .SS Logging options 76 | .TP 77 | \fB\-o, \-\-log\-file \fIfile\fR 78 | Save log to \fIfile\fR (default is stderr). 79 | .TP 80 | \fB\-l, \-\-log\-level \fIinfo|err|crit|all\fR 81 | Specifies the log level (default: \fIinfo\fR) 82 | .br 83 | \fIinfo\fR: Only show information messages. 84 | .br 85 | \fIerr\fR: Only show error messages. 86 | .br 87 | \fIcrit\fR: Only show critical messages. 88 | .br 89 | \fIall\fR: All of the above. 90 | .br 91 | (Critical messages are always displayed) 92 | .SH NOTES 93 | .SS Preloader requirements 94 | Preloader requires the following environment to run: 95 | .IP - 3 96 | Operating Sytem: Linux only. 97 | .IP - 98 | Architectures supported: ARM32, ARM64, i386 and x86-64. 99 | .IP - 100 | Libraries supported: GNU libc, Bionic, and uClibc-ng (do not work on Musl). 101 | .IP - 102 | System tools: Bash, grep, cut, any version 103 | .IP - 104 | GNU Make 105 | .RE 106 | .SH BUGS 107 | .PP 108 | No known bugs. 109 | .SH EXAMPLES 110 | Preload a process \fIfoo\fR in daemon mode and then, execute \fIfoo\fR 111 | through the client with arguments: a, b, and c. After that, stops the daemon. 112 | .PP 113 | .nf 114 | .RS 115 | $ preloader -d foo 116 | $ preloader_cli foo a b c 117 | $ preloader -s 118 | .RE 119 | .fi 120 | .SH AUTHOR 121 | .PP 122 | Written by Davidson Francis (davidsondfgl@gmail.com), see 123 | \fIcontributors\fR page in github.com/Theldus/preloader for more details. 124 | .SH SEE ALSO 125 | .BR preloader_cli (1) 126 | -------------------------------------------------------------------------------- /doc/man1/preloader_cli.1: -------------------------------------------------------------------------------- 1 | .\" MIT License 2 | .\" 3 | .\" Copyright (c) 2022 Davidson Francis 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 | .\" 23 | .TH "preloader_cli" "1" "" "" "preloader man page" 24 | .SH NAME 25 | preloader_cli \- launches a preloaded program 26 | .SH SYNOPSIS 27 | \fBpreloader_cli\fR [\fB-p\fR \fIPORT\fR] 28 | .SH DESCRIPTION 29 | \fBpreloader_cli\fR connects to the 30 | .BR preloader (1) 31 | server and asks the server to run the preloaded process with the parameters 32 | given in the \fBpreloader_cli\fR. 33 | .PP 34 | A port can be specified optionally, in case the server runs on a port other 35 | than the default one (3636). 36 | .SH OPTIONS 37 | .TP 38 | \fB\-p\fR \fIPORT\fR 39 | Specifies a port other than the default port to connect to the server 40 | .SH BUGS 41 | .PP 42 | No known bugs. 43 | .SH EXAMPLES 44 | Preload a process \fIfoo\fR in daemon mode and then, execute \fIfoo\fR 45 | through the client with arguments: a, b, and c. After that, stops the daemon. 46 | .PP 47 | .nf 48 | .RS 49 | $ preloader -d foo 50 | $ preloader_cli foo a b c 51 | $ preloader -s 52 | .RE 53 | .fi 54 | .SH AUTHOR 55 | .PP 56 | Written by Davidson Francis (davidsondfgl@gmail.com), see 57 | \fIcontributors\fR page in github.com/Theldus/preloader for more details. 58 | .SH SEE ALSO 59 | .BR preloader (1) 60 | -------------------------------------------------------------------------------- /doc/preloader.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/preloader/cb000e0cd7f8a2303cf2167d60c295cac03b3a27/doc/preloader.odg -------------------------------------------------------------------------------- /ipc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "ipc.h" 36 | #include "log.h" 37 | #include "preloader.h" 38 | 39 | static int sv_fd; 40 | 41 | #define TIMEOUT_MS 128 42 | 43 | /** 44 | * @brief Given a 32-bit message, decodes the content 45 | * as a int32_t number. 46 | * 47 | * @param msg Content to be decoded. 48 | * 49 | * @return Returns message as uint32_t. 50 | */ 51 | static inline int32_t msg_to_int32(uint8_t *msg) 52 | { 53 | int32_t msg_int; 54 | /* Decodes as big-endian. */ 55 | msg_int = (msg[3] << 0) | (msg[2] << 8) | (msg[1] << 16) | 56 | (msg[0] << 24); 57 | return (msg_int); 58 | } 59 | 60 | /** 61 | * @brief Given a 32-bit message, encodes the content 62 | * to be sent. 63 | * 64 | * @param msg Message to be encoded. 65 | * @param msg Target buffer. 66 | */ 67 | static inline void int32_to_msg(int32_t msg, uint8_t *msg_buff) 68 | { 69 | /* Encodes as big-endian. */ 70 | msg_buff[0] = (msg >> 24); 71 | msg_buff[1] = (msg >> 16); 72 | msg_buff[2] = (msg >> 8); 73 | msg_buff[3] = (msg >> 0); 74 | } 75 | 76 | /** 77 | * @brief Check for error on a given pollfd. 78 | * 79 | * @param p Pollfd to be checked. 80 | * 81 | * @return Returns 0 if success, 1 otherwise. 82 | */ 83 | static inline int event_error(struct pollfd *p) 84 | { 85 | int ev = p->events; 86 | if ((ev & POLLHUP) || 87 | (ev & POLLERR) || 88 | (ev & POLLNVAL)) 89 | return (1); 90 | return (0); 91 | } 92 | 93 | /** 94 | * @brief Given a file descriptor @p fd and a timeout 95 | * @p timeout_ms, waits up to @p timeout_ms for a new 96 | * message on @p fd. 97 | * 98 | * @param fd Target fd to wait for an accept. 99 | * @param timeout_ms Timeout (in milliseconds) to wait. 100 | * 101 | * @return On success, returns 0, otherwise, -1. 102 | */ 103 | static int recv_timeout(int fd, int timeout_ms) 104 | { 105 | struct pollfd pfd; 106 | 107 | pfd.fd = fd; 108 | pfd.events = POLLIN; 109 | 110 | if (poll(&pfd, 1, timeout_ms) <= 0 || event_error(&pfd)) 111 | return (-1); 112 | 113 | return (0); 114 | } 115 | 116 | /* ================================================================== 117 | * Public IPC routines 118 | * ==================================================================*/ 119 | 120 | /** 121 | * @brief Initiates the server and puts it to listening 122 | * to the configured port/id. 123 | * 124 | * @return Always 0. 125 | */ 126 | int ipc_init(struct args *args) 127 | { 128 | struct sockaddr_un server; 129 | 130 | /* Validate path. */ 131 | if (strlen(args->pid_path) + sizeof "/preloader_65535.sock" > 132 | sizeof(server.sun_path)) 133 | { 134 | die("Socket path exceeds maximum allowed! (%zu)\n", 135 | sizeof(server.sun_path)); 136 | } 137 | 138 | sv_fd = socket(AF_UNIX, SOCK_STREAM, 0); 139 | if (sv_fd < 0) 140 | die("Cant start IPC!\n"); 141 | 142 | /* Prepare the sockaddr_un structure. */ 143 | memset((void*)&server, 0, sizeof(server)); 144 | server.sun_family = AF_UNIX; 145 | snprintf(server.sun_path, sizeof server.sun_path - 1, 146 | "%s/preloader_%d.sock", args->pid_path, args->port); 147 | 148 | /* Remove sock file if already exists. */ 149 | unlink(server.sun_path); 150 | 151 | /* Bind. */ 152 | if (bind(sv_fd, (struct sockaddr *)&server, sizeof(server)) < 0) 153 | die("Bind failed\n"); 154 | 155 | /* Listen. */ 156 | if (listen(sv_fd, SV_MAX_CLIENTS) < 0) 157 | die("Unable to listen at path (%s)\n", server.sun_path); 158 | 159 | return (0); 160 | } 161 | 162 | /** 163 | * @brief Releases all IPC resources. 164 | */ 165 | void ipc_finish(void) 166 | { 167 | close(sv_fd); 168 | } 169 | 170 | /** 171 | * @brief Wait for a new connection (no timeout). 172 | * 173 | * @return Returns the client file descriptor. 174 | */ 175 | int ipc_wait_conn(void) 176 | { 177 | int cli_fd; 178 | 179 | cli_fd = accept(sv_fd, NULL, NULL); 180 | if (cli_fd < 0) 181 | die("Failed while accepting connections, aborting...\n"); 182 | 183 | return (cli_fd); 184 | } 185 | 186 | /* Amount of bytes before the actual cwd_argv. */ 187 | #define ARGC_AMNT 8 188 | 189 | /** 190 | * @brief Receives the file descriptors (stdout, stdin and 191 | * stderr), the current work directory, and the command-line 192 | * arguments given to preloader_cli. 193 | * 194 | * @param conn_fd Client connection. 195 | * @param out Client stdout fd. 196 | * @param err Client stderr fd. 197 | * @param in Client stdin fd. 198 | * @param argc_p Argument count pointer. 199 | * 200 | * @return Returns the current work directory and the 201 | * argument list. 202 | */ 203 | char* ipc_recv_msg( 204 | int conn_fd, int *out, int *err, int *in, int *argc_p) 205 | { 206 | char buff[CMSG_SPACE(3 * sizeof(int))]; 207 | struct cmsghdr *cmsghdr; 208 | struct msghdr msghdr; 209 | char buff_data[128]; 210 | uint32_t rem_bytes; 211 | char *cwd_argv, *p; 212 | struct iovec iov; 213 | int fds[3]; 214 | ssize_t nr; 215 | 216 | /* Fill message header and our I/O vec. */ 217 | memset(&msghdr, 0, sizeof(msghdr)); 218 | msghdr.msg_iov = &iov; 219 | msghdr.msg_iovlen = 1; 220 | iov.iov_base = buff_data; 221 | iov.iov_len = sizeof(buff_data); 222 | 223 | /* Set 'msghdr' fields that describe ancillary data */ 224 | msghdr.msg_control = buff; 225 | msghdr.msg_controllen = sizeof(buff); 226 | 227 | /* Wait up to TIMEOUT_MS to receive something. */ 228 | if (recv_timeout(conn_fd, TIMEOUT_MS) < 0) 229 | return (NULL); 230 | 231 | /* Receive real & ancillary data. */ 232 | nr = recvmsg(conn_fd, &msghdr, 0); 233 | 234 | /* 235 | * We should receive at least 8 bytes: 236 | * 4 bytes: argc 237 | * 4 bytes: amnt of bytes remaining 238 | * 239 | * Yes... I'm assuming I'll always be able to receive 240 | * at least 8 bytes... recvmsg doesnt guarantee this, 241 | * thik of better solution.... 242 | */ 243 | if (nr < ARGC_AMNT) 244 | return (NULL); 245 | 246 | /* Save our argc + amnt. */ 247 | *argc_p = msg_to_int32((uint8_t*)buff_data); 248 | rem_bytes = msg_to_int32((uint8_t*)buff_data + 4); 249 | 250 | /* Check if the fds were received. */ 251 | cmsghdr = CMSG_FIRSTHDR(&msghdr); 252 | 253 | if (cmsghdr == NULL || 254 | cmsghdr->cmsg_len != CMSG_LEN(sizeof(int) * 3) || 255 | cmsghdr->cmsg_level != SOL_SOCKET || 256 | cmsghdr->cmsg_type != SCM_RIGHTS) 257 | { 258 | return (NULL); 259 | } 260 | 261 | /* Copy the fds into the proper place. */ 262 | memcpy(&fds, CMSG_DATA(cmsghdr), sizeof(int) * 3); 263 | *out = fds[0]; 264 | *err = fds[1]; 265 | *in = fds[2]; 266 | 267 | /* Read CWD and argv. */ 268 | cwd_argv = malloc(rem_bytes - 8); 269 | if (!cwd_argv) 270 | log_crit("Cant allocate memory (%d bytes)!\n", rem_bytes); 271 | 272 | rem_bytes -= nr; 273 | 274 | if (nr) 275 | memcpy(cwd_argv, buff_data + ARGC_AMNT, nr - ARGC_AMNT); 276 | p = cwd_argv + (nr - ARGC_AMNT); 277 | 278 | /* Fill cwd_argv. */ 279 | while (rem_bytes) 280 | { 281 | nr = recv(conn_fd, p, rem_bytes, 0); 282 | if (nr <= 0) 283 | goto out0; 284 | 285 | rem_bytes -= nr; 286 | p += nr; 287 | } 288 | 289 | return (cwd_argv); 290 | out0: 291 | free(cwd_argv); 292 | return (NULL); 293 | } 294 | 295 | /** 296 | * @brief For a given int32_t @p value, send to the 297 | * socket pointed by @p fd. 298 | * 299 | * @return Returns 1 if success, 0 otherwise. 300 | */ 301 | int ipc_send_int32(int32_t value, int fd) 302 | { 303 | uint8_t buff[4]; 304 | int32_to_msg(value, buff); 305 | return (send(fd, buff, sizeof buff, 0) == sizeof buff); 306 | } 307 | 308 | /** 309 | * Closes an arbitrary amount of file descriptors 310 | * specified in @p num. 311 | */ 312 | void ipc_close(int num, ...) 313 | { 314 | int i; 315 | va_list ap; 316 | 317 | va_start(ap, num); 318 | for (i = 0; i < num; i++) 319 | close(va_arg(ap, int)); 320 | va_end(ap); 321 | } 322 | -------------------------------------------------------------------------------- /ipc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef IPC_H 26 | #define IPC_H 27 | 28 | #include 29 | 30 | #define SV_DEFAULT_PORT 3636 31 | #define SV_MAX_CLIENTS 16 32 | 33 | struct args; 34 | 35 | extern int ipc_init(struct args *args); 36 | extern void ipc_finish(void); 37 | extern int ipc_wait_conn(void); 38 | extern char* ipc_recv_msg(int conn_fd, int *out, int *err, 39 | int *in, int *argc_p); 40 | extern int ipc_send_int32(int32_t value, int fd); 41 | extern void ipc_close(int num, ...); 42 | 43 | #endif /* IPC_H */ 44 | -------------------------------------------------------------------------------- /load.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #define _POSIX_C_SOURCE 200809L 26 | #include 27 | #include 28 | #include 29 | 30 | #include "log.h" 31 | 32 | /** 33 | * @brief For a given file @p file, read a dynamic 34 | * library for each line specified on it. 35 | * 36 | * @param file File to be read, containing shared lib 37 | * file paths, one per line. 38 | * 39 | * @return Always 0. 40 | */ 41 | int load_file(const char *file) 42 | { 43 | ssize_t lbytes; 44 | size_t rbytes; 45 | char *line; 46 | FILE *f; 47 | 48 | f = fopen(file, "r"); 49 | if (!f) 50 | die("Unable to read file: %s\n", file); 51 | 52 | line = NULL; 53 | lbytes = 0; 54 | rbytes = 0; 55 | while ((lbytes = getline(&line, &rbytes, f)) != -1) 56 | { 57 | line[lbytes - 1] = '\0'; 58 | 59 | /* 60 | * Yes... I am purposely ignoring the return of 'dlopen': 61 | * if it failed, there is nothing that can be done, if it 62 | * succeeded, there is no need to save the handler either, 63 | * since it is not known how long the library should stay 64 | * available in memory. 65 | * 66 | * Therefore, everything is expected to die along with the 67 | * process. 68 | */ 69 | if (!dlopen(line, RTLD_NOW)) 70 | log_info("Unable to dlopen lib: %s\nr: %s\n\n", line, 71 | dlerror()); 72 | } 73 | free(line); 74 | fclose(f); 75 | return (0); 76 | } 77 | -------------------------------------------------------------------------------- /load.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef LOAD_H 26 | #define LOAD_H 27 | 28 | extern int load_file(const char *file); 29 | 30 | #endif /* LOAD_H */ 31 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "log.h" 31 | #include "preloader.h" 32 | 33 | static const char *log_string[] = {"info", "err", "crit"}; 34 | static const char dev_null[] = "/dev/null"; 35 | static struct args *args; 36 | 37 | /** 38 | * @brief Initialize the log system for a given log-level log file 39 | * and log file provided in the programa arguments @p a. 40 | * 41 | * @param a Program arguments, containing the log info required 42 | * to initialize the log-level. 43 | * 44 | * @return Returns 0 if success, -1 otherwise. 45 | */ 46 | int log_init(struct args *a) 47 | { 48 | if (!a || a->log_lvl < LOG_LVL_INFO || a->log_lvl > LOG_LVL_ALL) 49 | return (-1); 50 | 51 | args = a; 52 | 53 | if (args->log_fd >= 0) 54 | return (0); 55 | 56 | if (!args->log_file) 57 | args->log_file = (char*)dev_null; 58 | 59 | args->log_fd = open(args->log_file, O_WRONLY|O_APPEND|O_CREAT, 60 | 0644); 61 | 62 | if (args->log_fd < 0) 63 | { 64 | args->log_fd = STDERR_FILENO; 65 | return (-1); 66 | } 67 | return (0); 68 | } 69 | 70 | /** 71 | * @brief Close all resources used during the logging. 72 | */ 73 | void log_close(void) 74 | { 75 | if (args && args->log_file) 76 | { 77 | close(args->log_fd); 78 | if (args->log_file != dev_null) 79 | free(args->log_file); 80 | } 81 | } 82 | 83 | /** 84 | * @brief Logs a formatted message provided in @p fmt, for a 85 | * given log level @p l, into the already specified output. 86 | * 87 | * @param l Log level. 88 | * @param fmt Formatted string to be logged into the screen, 89 | * or file. 90 | * @param ap va_list containing the arguments. 91 | */ 92 | static void log_msg(int l, const char *fmt, va_list ap) 93 | { 94 | int log_fd = STDERR_FILENO; 95 | if (l < LOG_LVL_INFO || l > LOG_LVL_ALL) 96 | return; 97 | if (args) 98 | log_fd = args->log_fd; 99 | dprintf(log_fd, "[%s] ", log_string[l]); 100 | vdprintf(log_fd, fmt, ap); 101 | } 102 | 103 | /** 104 | * @brief Logs a formatted message @p fmt as an info-message 105 | * into the already defined output. 106 | * 107 | * @param fmt Formatted message. 108 | */ 109 | void log_info(const char *fmt, ...) 110 | { 111 | va_list ap; 112 | if (args && args->log_lvl != LOG_LVL_INFO && args->log_lvl != LOG_LVL_ALL) 113 | return; 114 | va_start(ap, fmt); 115 | log_msg(LOG_LVL_INFO, fmt, ap); 116 | va_end(ap); 117 | } 118 | 119 | /** 120 | * @brief Logs a formatted message @p fmt as an error-message 121 | * into the already defined output. 122 | * 123 | * @param fmt Formatted message. 124 | */ 125 | void log_err(const char *fmt, ...) 126 | { 127 | va_list ap; 128 | if (args && args->log_lvl != LOG_LVL_ERR && args->log_lvl != LOG_LVL_ALL) 129 | return; 130 | va_start(ap, fmt); 131 | log_msg(LOG_LVL_ERR, fmt, ap); 132 | va_end(ap); 133 | } 134 | 135 | /** 136 | * @brief Logs a formatted message @p fmt as a critical-message 137 | * into the already defined output. 138 | * 139 | * @param fmt Formatted message. 140 | * 141 | * @note Critical messages cannot be omitted. 142 | */ 143 | void log_crit(const char *fmt, ...) 144 | { 145 | /* Critical log-level messages are always showed. */ 146 | va_list ap; 147 | va_start(ap, fmt); 148 | log_msg(LOG_LVL_CRIT, fmt, ap); 149 | va_end(ap); 150 | } 151 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef LOG_H 26 | #define LOG_H 27 | 28 | struct args; 29 | #include 30 | 31 | #define LOG_LVL_INFO 0 32 | #define LOG_LVL_ERR 1 33 | #define LOG_LVL_CRIT 2 34 | #define LOG_LVL_ALL 4 35 | 36 | extern int log_init(struct args *a); 37 | extern void log_close(void); 38 | extern void log_info(const char *fmt, ...); 39 | extern void log_err(const char *fmt, ...); 40 | extern void log_crit(const char *fmt, ...); 41 | 42 | #define die(...) \ 43 | do { \ 44 | log_crit(__VA_ARGS__); \ 45 | _exit(1); \ 46 | } while (0) 47 | 48 | #endif /* LOG_H */ 49 | -------------------------------------------------------------------------------- /preloader: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # MIT License 4 | # 5 | # Copyright (c) 2022 Davidson Francis 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | LIBNAME="libpreloader.so" 26 | SCRIPT_NAME="$0" 27 | 28 | export PRELOADER_PORT=3636 29 | 30 | # Paths 31 | CURDIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 32 | TMPDIR=${TMPDIR:="/tmp"} 33 | PRELOADER="$CURDIR/$LIBNAME" 34 | 35 | start() { 36 | # lib should exist 37 | if [ ! -f "$PRELOADER" ]; then 38 | # Build if we're inside the source tree 39 | if [ -f "$CURDIR/preloader.c" ]; then 40 | make -C "$CURDIR/" 41 | # Otherwise, we need to launch the installed version, 42 | # if any. 43 | else 44 | if [ ! -f "$CURDIR/../lib/$LIBNAME" ]; then 45 | echo "Unable to execute preloader, please check if the" >&2 46 | echo "library is properly built and/or installed and" >&2 47 | echo "try again!" >&2 48 | exit 1 49 | else 50 | PRELOADER="$(readlink -f "$CURDIR"/../lib/$LIBNAME)" 51 | fi 52 | fi 53 | fi 54 | 55 | pid_file="$TMPDIR/preloader_$PRELOADER_PORT.pid" 56 | 57 | # If there is a pid file 58 | if [ -r "$pid_file" ]; then 59 | 60 | # and the process still running 61 | pid="$(cat "$pid_file")" 62 | if kill -s 0 "$pid" &> /dev/null; then 63 | echo "Error: There is a daemon running with pid $pid" >&2 64 | echo "please kill it first or run in another port (-p)" >&2 65 | exit 1 66 | fi 67 | 68 | fi 69 | 70 | # 71 | # Obs: The argument list is fixed and defined here. The reason 72 | # for this is simplicity. It is much simpler to define a large 73 | # enough list of arguments and change it later than to allocate 74 | # a new list later. 75 | # 76 | # Allocating a new list also entails allocating envp and auxv, 77 | # which makes things unnecessarily more complicated. I prefer 78 | # to set a maximum argument size and edit the argument list 79 | # later. 80 | # 81 | # If for some reason the preloader_cli uses more than 200 82 | # arguments, an error will be thrown in stderr indicating the 83 | # exceeded amount of arguments and the daemon will stop. 84 | # 85 | LD_PRELOAD="$PRELOADER" "$1" {1..200} 86 | } 87 | 88 | stop() { 89 | 90 | pid_file="$TMPDIR/preloader_$PRELOADER_PORT.pid" 91 | 92 | if [ ! -r "$pid_file" ]; then 93 | echo "PID file not found for port $PRELOADER_PORT, please check" >&2 94 | echo "the parameters again. You can check which instances are" >&2 95 | echo "running by running:" >&2 96 | echo " $ sudo netstat -ntlp" >&2 97 | echo "and them you can kill the daemon accordingly." >&2 98 | exit 1 99 | fi 100 | 101 | pid="$(cat "$pid_file")" 102 | 103 | kill "$pid" &> /dev/null 104 | 105 | if [ "$?" -eq 1 ]; then 106 | echo "Unable to kill daemon with PID $pid, maybe the daemon is" >&2 107 | echo "already dead?" >&2 108 | exit 1 109 | fi 110 | 111 | rm "$pid_file" 112 | exit 0 113 | } 114 | 115 | check_for_null() { 116 | if [ -z "$1" ]; then 117 | echo "Parameter ($1) should not be empty!" 118 | usage 119 | fi 120 | } 121 | 122 | check_for_integer() { 123 | case $1 in 124 | ''|*[!0-9]*) 125 | echo "Parameter ($1) is not a number!" 126 | usage 127 | ;; 128 | esac 129 | } 130 | 131 | usage() { 132 | cat <&2 133 | Usage: $SCRIPT_NAME [options] 134 | 135 | Examples: 136 | $SCRIPT_NAME clang 137 | $SCRIPT_NAME -p 5050 --bind clang 138 | etc 139 | 140 | Options: 141 | -p,--port 142 | Specifies the port to be listening (default: 3636). 143 | Note: Please note that 'port' is just an abstraction. 144 | Preloader uses Unix Domain Socket for IPC and the port 145 | number only serves to compose the socket file name. 146 | 147 | -b,--bind-now 148 | Performs immediate binding, i.e: uses LD_BIND_NOW. 149 | 150 | -d,--daemonize 151 | Daemonizes the server (disabled by default). 152 | (Please note that logs are only saved if a file 153 | is specified with -o, otherwise, they are discarded). 154 | 155 | -f,--load-libs 156 | Preloads a set of libraries (one per line) defined in 157 | a text file. This is especially useful if the program 158 | dynamically loads *many* libraries. 159 | 160 | -s,--stop 161 | Stop daemon for a default port, or for a given port if 162 | -p is specified. 163 | 164 | Logging: 165 | -o,--log-file 166 | Save log to (default is stderr). 167 | 168 | -l,--log-level 169 | Specifies the log level (default: info): 170 | (Critical messages are always displayed) 171 | 172 | info: Only show information messages, that might be 173 | useful or not. 174 | err: Only show error messages. 175 | crit: Only show critical messages. 176 | all: All of the above. 177 | 178 | -h,--help 179 | This help 180 | EOF 181 | exit 1 182 | } 183 | 184 | ARGV=() 185 | 186 | while [[ $# -gt 0 ]]; do 187 | case $1 in 188 | -p|--port) 189 | check_for_null "$2" 190 | check_for_integer "$2" 191 | export PRELOADER_PORT="$2" 192 | shift 193 | shift 194 | ;; 195 | -b|--bind-now) 196 | export LD_BIND_NOW="1" 197 | shift 198 | ;; 199 | -d|--daemonize) 200 | export PRELOADER_DAEMONIZE="1" 201 | shift 202 | ;; 203 | -f|--load-libs) 204 | check_for_null "$2" 205 | export PRELOADER_LOAD_FILE="$2" 206 | shift 207 | shift 208 | ;; 209 | -s|--stop) 210 | STOP_DAEMON=1 211 | shift 212 | ;; 213 | -o|--log-file) 214 | check_for_null "$2" 215 | export PRELOADER_LOG_FILE="$2" 216 | shift 217 | shift 218 | ;; 219 | -l|--log-level) 220 | check_for_null "$2" 221 | export PRELOADER_LOG_LVL="$2" 222 | shift 223 | shift 224 | ;; 225 | -h|--help) 226 | usage 227 | ;; 228 | -*|--*) 229 | echo "Unknown option: $1" >&2 230 | usage 231 | ;; 232 | *) 233 | ARGV+=("$1") 234 | shift 235 | ;; 236 | esac 237 | done 238 | 239 | # Check if we should stop 240 | if [ -n "$STOP_DAEMON" ]; then 241 | stop 242 | fi 243 | 244 | # Validate program name 245 | if [ -z "${ARGV[0]}" ]; then 246 | echo "At least is required!" >&2 247 | usage 248 | else 249 | if ! command -v "${ARGV[0]}" &> /dev/null; then 250 | echo "Program (${ARGV[0]}) not found!" >&2 251 | usage 252 | fi 253 | fi 254 | 255 | # Run 256 | start "${ARGV[0]}" 257 | -------------------------------------------------------------------------------- /preloader.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "arch.h" 33 | #include "ipc.h" 34 | #include "load.h" 35 | #include "log.h" 36 | #include "preloader.h" 37 | #include "reaper.h" 38 | #include "util.h" 39 | 40 | 41 | #ifndef PID_PATH 42 | #define PID_PATH "/tmp" 43 | #endif 44 | 45 | /** 46 | * Preloader arguments. 47 | */ 48 | struct args args = { 49 | .port = SV_DEFAULT_PORT, 50 | .pid_path = PID_PATH, 51 | .log_lvl = LOG_LVL_INFO, 52 | .log_file = NULL, 53 | .log_fd = STDERR_FILENO, 54 | .load_file = NULL, 55 | }; 56 | 57 | /** 58 | * @brief Setup most of the things that should be done before 59 | * our child/real process execute. 60 | * 61 | * @param conn_fd Main connection socket fd. 62 | * @param stdout_fd Stdout socket fd. 63 | * @param stderr_fd Stderr socket fd. 64 | * @param stdin_fd Stdin socket fd. 65 | * @param cwd_argv Current work dir + argument list. 66 | * 67 | * @return Returns the new argv the child process should have. 68 | */ 69 | static char* setup_child(int conn_fd, int stdout_fd, int stderr_fd, 70 | int stdin_fd, char *cwd_argv) 71 | { 72 | setenv("LD_BIND_NOW", "", 1); 73 | 74 | /* Close server listening socket on client. */ 75 | ipc_finish(); 76 | 77 | /* Close log file, because we do not need to 78 | * inherit it. */ 79 | log_close(); 80 | 81 | /* Deallocates reaper data structures. */ 82 | reaper_finish(); 83 | 84 | /* Redirect std* to the preloader_cli fds. */ 85 | dup2(stdin_fd, STDIN_FILENO); 86 | dup2(stdout_fd, STDOUT_FILENO); 87 | dup2(stderr_fd, STDERR_FILENO); 88 | ipc_close(4, stdin_fd, stdout_fd, stderr_fd, conn_fd); 89 | 90 | /* Set the current directory. */ 91 | if (chdir(cwd_argv) < 0) 92 | die("Unable to chdir to: %s, aborting...\n", cwd_argv); 93 | 94 | /* Restore default signal handler. */ 95 | signal(SIGTERM, SIG_DFL); 96 | return (cwd_argv); 97 | } 98 | 99 | /** 100 | * @brief *This* is where occurs the main loop. 101 | * 102 | * daemon_main() is responsible to accept connections, receive 103 | * its arguments, fork a new process (to be normally executed) 104 | * and the proceeds to handle a next connection. 105 | * 106 | * @param argc Argument count pointer, this is the new child 107 | argc. 108 | * 109 | * @return Returns the new argv the child process should have. 110 | * 111 | * @note This routine is called from arch_pre_daemon_main(), 112 | * where the new argc/argv are actually changed, when this 113 | * function returns. 114 | */ 115 | char* daemon_main(int *argc) 116 | { 117 | char *cwd_argv = NULL; 118 | int stdout_fd; 119 | int stderr_fd; 120 | int stdin_fd; 121 | int conn_fd; 122 | pid_t pid; 123 | 124 | ipc_init(&args); 125 | reaper_init(); 126 | 127 | while (1) 128 | { 129 | conn_fd = ipc_wait_conn(); 130 | cwd_argv = ipc_recv_msg(conn_fd, &stdout_fd, &stderr_fd, 131 | &stdin_fd, argc); 132 | 133 | if (!cwd_argv) 134 | { 135 | log_info("Client took too long to respond, aborting!!!\n"); 136 | ipc_close(1, conn_fd); 137 | goto again; 138 | } 139 | 140 | /* If child. */ 141 | if ((pid = fork()) == 0) 142 | return setup_child(conn_fd, stdout_fd, stderr_fd, 143 | stdin_fd, cwd_argv); 144 | else 145 | reaper_add_child(pid, conn_fd); 146 | 147 | /* Send child PID. */ 148 | ipc_send_int32((int32_t)pid, conn_fd); 149 | 150 | again: 151 | /* Keep conn_fd as our reaper will close the connection. */ 152 | ipc_close(3, stdin_fd, stdout_fd, stderr_fd); 153 | free(cwd_argv); 154 | } 155 | 156 | _exit(0); 157 | } 158 | 159 | /** 160 | * @brief Parse the preloader arguments through env vars 161 | */ 162 | static void parse_args(void) 163 | { 164 | char *env; 165 | 166 | /* Check port. */ 167 | if ((env = getenv("PRELOADER_PORT")) != NULL) 168 | { 169 | if (str2int(&args.port, env) < 0 || 170 | (args.port < 0 || args.port > 65535)) 171 | { 172 | die("Invalid port (%s)\n", env); 173 | } 174 | } 175 | 176 | /* Check for log level. */ 177 | if ((env = getenv("PRELOADER_LOG_LVL")) != NULL) 178 | { 179 | if (!strcmp(env, "info")) 180 | args.log_lvl = LOG_LVL_INFO; 181 | else if (!strcmp(env, "err")) 182 | args.log_lvl = LOG_LVL_ERR; 183 | else if (!strcmp(env, "crit")) 184 | args.log_lvl = LOG_LVL_CRIT; 185 | else if (!strcmp(env, "all")) 186 | args.log_lvl = LOG_LVL_ALL; 187 | else 188 | die("Unrecognized log_lvl (%s), supported ones are: \n" 189 | " info, err, crit and all!\n", env); 190 | } 191 | 192 | /* Check log file. */ 193 | if ((env = getenv("PRELOADER_LOG_FILE")) != NULL) 194 | { 195 | args.log_file = strdup(env); 196 | args.log_fd = -1; 197 | } 198 | 199 | /* Check daemon. */ 200 | if (getenv("PRELOADER_DAEMONIZE")) 201 | { 202 | args.daemonize = 1; 203 | args.log_fd = -1; 204 | } 205 | 206 | /* Check if should load a given file too. */ 207 | if ((env = getenv("PRELOADER_LOAD_FILE")) != NULL) 208 | args.load_file = strdup(env); 209 | } 210 | 211 | /** 212 | * @brief Preloader's signal handler 213 | */ 214 | static void sig_handler(int sig) 215 | { 216 | ((void)sig); 217 | /* 218 | * It sounds stupid (and maybe it is) to handle SIGTERM 219 | * just once to reset the signal immediately... but it 220 | * was the way I found to kill this process and the 221 | * dummy process peacefully. 222 | */ 223 | signal(SIGTERM, SIG_DFL); 224 | kill(0, SIGTERM); 225 | } 226 | 227 | /** 228 | * @brief Creates a 'daemon' process. 229 | */ 230 | static void daemonize(void) 231 | { 232 | /* Fork and let the parent dies. */ 233 | if (fork() != 0) 234 | exit(0); 235 | 236 | /* Create a new session. */ 237 | setsid(); 238 | 239 | /* 240 | * We can't close our fd's here because our children need 241 | * to inherit them to redirect I/O to the socket. 242 | */ 243 | } 244 | 245 | /** 246 | * @brief Preloader library entrypoint 247 | */ 248 | void __attribute__ ((constructor)) my_init(void) 249 | { 250 | parse_args(); 251 | 252 | /* Check if we're already running, if so, do nothing. */ 253 | if (!read_and_check_pid(args.pid_path, args.port)) 254 | return; 255 | 256 | /* Initialize logs. */ 257 | if (log_init(&args) < 0) 258 | die("Unable to initialize logging, plese check your parameters " 259 | "and try again!\n"); 260 | 261 | /* Daemon. */ 262 | if (args.daemonize) 263 | daemonize(); 264 | 265 | /* Spawns a dummy process so our reaper always have some 266 | * child to wait for. */ 267 | if (!fork()) 268 | pause(); 269 | 270 | /* PID file. */ 271 | if (create_pid(args.pid_path, args.port) < 0) 272 | die("Unable to create pid file, aborting...\n"); 273 | 274 | log_info("Initializing...\n"); 275 | 276 | /* Setup signals. */ 277 | signal(SIGTERM, sig_handler); 278 | 279 | /* Read a load file, if specified. */ 280 | if (args.load_file) 281 | load_file(args.load_file); 282 | 283 | /* Setup arch-dependent things. */ 284 | arch_setup(); 285 | } 286 | 287 | void __attribute__ ((destructor)) my_fini(void){} 288 | -------------------------------------------------------------------------------- /preloader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef PRELOADER_H 26 | #define PRELOADER_H 27 | 28 | /* Our global arguments struct. */ 29 | struct args 30 | { 31 | int port; 32 | int daemonize; 33 | char *pid_path; 34 | /* Log stuff. */ 35 | int log_lvl; 36 | char *log_file; 37 | int log_fd; 38 | /* Load file. */ 39 | char *load_file; 40 | }; 41 | 42 | #endif /* PRELOADER_H */ 43 | -------------------------------------------------------------------------------- /preloader_cli.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #ifndef PRG_NAME 41 | #define PRG_NAME "preloader_cli" 42 | #endif 43 | 44 | #ifndef PID_PATH 45 | #define PID_PATH "/tmp" 46 | #endif 47 | 48 | #define die(...) \ 49 | do { \ 50 | fprintf(stderr, __VA_ARGS__); \ 51 | exit(EXIT_FAILURE); \ 52 | } while (0) 53 | 54 | #define SV_DEFAULT_PORT 3636 55 | 56 | /* Process PID. */ 57 | static pid_t process_pid; 58 | 59 | /** 60 | * @brief Given a 32-bit message, encodes the content 61 | * to be sent. 62 | * 63 | * @param msg 64 | * @param msg Target buffer. 65 | */ 66 | static inline void int32_to_msg(int32_t msg, uint8_t *msg_buff) 67 | { 68 | /* Encodes as big-endian. */ 69 | msg_buff[0] = (msg >> 24); 70 | msg_buff[1] = (msg >> 16); 71 | msg_buff[2] = (msg >> 8); 72 | msg_buff[3] = (msg >> 0); 73 | } 74 | 75 | /** 76 | * @brief Given a 32-bit message, decodes the content 77 | * as a int32_t number. 78 | * 79 | * @param msg Content to be decoded. 80 | * 81 | * @return Returns message as uint32_t. 82 | */ 83 | static inline int32_t msg_to_int32(uint8_t *msg) 84 | { 85 | int32_t msg_int; 86 | /* Decodes as big-endian. */ 87 | msg_int = (msg[3] << 0) | (msg[2] << 8) | (msg[1] << 16) | 88 | (msg[0] << 24); 89 | return (msg_int); 90 | } 91 | 92 | /** 93 | * @brief Prepare the initial data that should be sent to the 94 | * server and returns them as a char buffer. 95 | * 96 | * @param size Amount of data to be sent. 97 | * @param argc Argument count. 98 | * @param argv Argument list. 99 | * 100 | * @return If success, returns the data to be sent (as an char 101 | * array), otherwise, NULL. 102 | */ 103 | static char* prepare_data(size_t *size, int argc, char **argv) 104 | { 105 | int i; 106 | uint32_t amnt; 107 | char *p, *buff; 108 | char cwd[4096] = {0}; 109 | 110 | /* Get current working directory. */ 111 | if (!getcwd(cwd, sizeof(cwd))) 112 | return (NULL); 113 | 114 | /* Get the amounr of data to be sent. */ 115 | amnt = (uint32_t)strlen(cwd); 116 | for (i = 0; i < argc; i++) 117 | amnt += (uint32_t)strlen(argv[i]); 118 | amnt += argc + 1; /* + number of 'NUL'. */ 119 | amnt += 8; /* argc + amt_bytes. */ 120 | 121 | /* Allocate and create buffer to be sent. */ 122 | buff = calloc(amnt + 1, sizeof(char)); 123 | if (!buff) 124 | return (NULL); 125 | 126 | int32_to_msg(argc, (uint8_t*)buff); 127 | int32_to_msg(amnt, (uint8_t*)buff + 4); 128 | 129 | p = buff + 8; /* skip argc + amnt. */ 130 | 131 | strcpy(p, cwd); 132 | p += strlen(p) + 1; 133 | for (i = 0; i < argc; i++) 134 | { 135 | strcpy(p, argv[i]); 136 | p += strlen(p) + 1; 137 | } 138 | 139 | *size = amnt; 140 | return (buff); 141 | } 142 | 143 | /** 144 | * Safe string-to-int routine that takes into account: 145 | * - Overflow and Underflow 146 | * - No undefined behavior 147 | * 148 | * Taken from https://stackoverflow.com/a/12923949/3594716 149 | * and slightly adapted: no error classification, because 150 | * I don't need to know, error is error. 151 | * 152 | * @param out Pointer to integer. 153 | * @param s String to be converted. 154 | * 155 | * @return Returns 0 if success and a negative number otherwise. 156 | */ 157 | static int str2int(int *out, const char *s) 158 | { 159 | char *end; 160 | if (s[0] == '\0' || isspace(s[0])) 161 | return (-1); 162 | errno = 0; 163 | 164 | long l = strtol(s, &end, 10); 165 | 166 | /* Both checks are needed because INT_MAX == LONG_MAX is possible. */ 167 | if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) 168 | return (-1); 169 | if (l < INT_MIN || (errno == ERANGE && l == LONG_MIN)) 170 | return (-1); 171 | if (*end != '\0') 172 | return (-1); 173 | 174 | *out = l; 175 | return (0); 176 | } 177 | 178 | /** 179 | * @brief Program usage. 180 | * 181 | * @param prgname Program name. 182 | */ 183 | static void usage(const char *prgname) 184 | { 185 | fprintf(stderr, 186 | "Usage:\n" 187 | " %s [-p ] \n" 188 | "or\n" 189 | " %s \n", prgname, prgname 190 | ); 191 | exit(EXIT_FAILURE); 192 | } 193 | 194 | /** 195 | * @brief Parse command-line arguments. 196 | * 197 | * @param new_argc New argument counter. 198 | * @param argv Old argument list. 199 | * @param port Port to be connected. 200 | * 201 | * @return Returns the new argument list. 202 | */ 203 | static char **parse_args(int *new_argc, char **argv, int *port) 204 | { 205 | char **new_argv = argv; 206 | char *prog_base, *tmp; 207 | int argc = *new_argc; 208 | 209 | /* at least . */ 210 | if (argc < 2) 211 | usage(argv[0]); 212 | 213 | *port = SV_DEFAULT_PORT; 214 | 215 | /* Get true program name. */ 216 | prog_base = strdup(argv[0]); 217 | if (!prog_base) 218 | die("Unable to allocate memory!\n"); 219 | tmp = basename(argv[0]); 220 | 221 | /* 222 | * If calling client 'normally': 223 | * ./client ... , min 3 224 | * client ... , min 3 225 | * /path/client ... , min 3 226 | * ./client -p ... , min 4 227 | * client -p ... , min 4 228 | * /path/client -p ... , min 4 229 | */ 230 | if (!strcmp(tmp, PRG_NAME)) 231 | { 232 | free(prog_base); 233 | if (!strcmp(argv[1], "-p")) 234 | { 235 | if (argc < 4) 236 | usage(argv[0]); 237 | 238 | /* Validate port number. */ 239 | if (str2int(port, argv[2]) < 0 || (*port < 0 || *port > 65535)) 240 | { 241 | fprintf(stderr, "Invalid port number: (%s), " 242 | "should be in: 0-65535\n", argv[2]); 243 | usage(argv[0]); 244 | } 245 | 246 | argc -= 3; 247 | new_argv += 3; 248 | } 249 | 250 | /* Ok, no port specified, check the arg count. */ 251 | else if (argc < 2) 252 | usage(argv[0]); 253 | else 254 | { 255 | argc -= 1; 256 | new_argv += 1; 257 | } 258 | } 259 | else 260 | free(prog_base); 261 | 262 | /* 263 | * If called by a symlink or if this client is renamed, 264 | * like: 265 | * ... 266 | * do not touch argv and argv. */ 267 | 268 | *new_argc = argc; 269 | return (new_argv); 270 | } 271 | 272 | /** 273 | * @brief Connect to a given Unix Domain Socket ID and 274 | * saves the socket into @p sock. 275 | * 276 | * @param port Socket ID to be connect. 277 | * @param sock Returned socket pointer. 278 | * 279 | * @return Returns 0 if success, -1 otherwise. 280 | */ 281 | static int do_connect(uint16_t port, int *sock) 282 | { 283 | struct sockaddr_un sock_addr; 284 | 285 | /* Validate path. */ 286 | if (sizeof PID_PATH + sizeof "/preloader_65535.sock" > 287 | sizeof(sock_addr.sun_path)) 288 | { 289 | die("Socket path exceeds maximum allowed! (%zu)\n", 290 | sizeof(sock_addr.sun_path)); 291 | } 292 | 293 | /* Create socket. */ 294 | *sock = socket(AF_UNIX, SOCK_STREAM, 0); 295 | if (*sock < 0) 296 | die("Unable to create a socket!\n"); 297 | 298 | memset((void*)&sock_addr, 0, sizeof(sock_addr)); 299 | sock_addr.sun_family = AF_UNIX; 300 | snprintf(sock_addr.sun_path, sizeof sock_addr.sun_path - 1, 301 | "%s/preloader_%d.sock", PID_PATH, port); 302 | 303 | return connect(*sock, (struct sockaddr *)&sock_addr, 304 | sizeof(sock_addr)); 305 | } 306 | 307 | /** 308 | * @brief Send to @p sock all the data in the buffer @p buffer_data 309 | * and also the file descriptors the client process have too. 310 | * 311 | * @param sock Connection to send the data + fds. 312 | * @param buff_data Data to be sent. 313 | * @param buff_data_len Buffer length. 314 | * 315 | * @return Returns a positive number if success, otherwise, returns 316 | * a number lesser than or equal 0. 317 | */ 318 | static ssize_t send_fds(int sock, char *buff_data, size_t buff_data_len) 319 | { 320 | struct cmsghdr *cmsghdr; 321 | struct msghdr msghdr; 322 | struct iovec iov; 323 | int fds[3] = {STDOUT_FILENO, STDERR_FILENO, STDIN_FILENO}; 324 | 325 | char buff[CMSG_SPACE(3 * sizeof(int))]; 326 | 327 | /* Fill message header and our I/O vec. */ 328 | memset(&msghdr, 0, sizeof(msghdr)); 329 | msghdr.msg_iov = &iov; 330 | msghdr.msg_iovlen = 1; 331 | iov.iov_base = buff_data; 332 | iov.iov_len = buff_data_len; 333 | 334 | /* Set 'msghdr' fields that describe ancillary data */ 335 | msghdr.msg_control = buff; 336 | msghdr.msg_controllen = sizeof(buff); 337 | 338 | /* Set up ancillary data describing file descriptor to send */ 339 | cmsghdr = CMSG_FIRSTHDR(&msghdr); 340 | memset(cmsghdr, 0, sizeof(*cmsghdr)); 341 | cmsghdr->cmsg_level = SOL_SOCKET; 342 | cmsghdr->cmsg_type = SCM_RIGHTS; 343 | cmsghdr->cmsg_len = CMSG_LEN(sizeof(int) * 3); 344 | 345 | /* Copy fds. */ 346 | memcpy(CMSG_DATA(cmsghdr), &fds, sizeof(int) * 3); 347 | 348 | /* Send real data plus ancillary data */ 349 | return sendmsg(sock, &msghdr, 0); 350 | } 351 | 352 | /** 353 | * @brief Client signal handler. 354 | * 355 | * Since the client needs to behave transparently, as if 356 | * it were the original process, all signals that the 357 | * client receives are forwarded to the original process. 358 | * 359 | * @param sig Signal received. 360 | */ 361 | static void sig_handler(int sig) 362 | { 363 | if (process_pid) 364 | kill(process_pid, sig); 365 | } 366 | 367 | /* Main routine. */ 368 | int main(int argc, char **argv) 369 | { 370 | uint8_t ret_buff[4]; /* Generic buffer to I/O. */ 371 | char *send_buff; /* Data to be sent to the server. */ 372 | char **new_argv; /* New argument list. */ 373 | int new_argc; /* New argument count. */ 374 | size_t amnt; /* Amount of bytes to be sent. */ 375 | int32_t ret; /* Generic int32_t to I/O. */ 376 | int port; /* Main server/control port. */ 377 | 378 | int sock; /* Control socket fd. */ 379 | ret = 42; 380 | 381 | signal(SIGINT, sig_handler); 382 | signal(SIGTERM, sig_handler); 383 | 384 | /* Parse and validate arguments. */ 385 | new_argc = argc; 386 | new_argv = parse_args(&new_argc, argv, &port); 387 | 388 | /* Prepare data to be sent. */ 389 | if (!(send_buff = prepare_data(&amnt, new_argc, new_argv))) 390 | die("Unable to prepare data to be sent!\n"); 391 | 392 | /* Connect to server port. */ 393 | if (do_connect(port, &sock) < 0) 394 | die("Unable to connect on sv port %d!\n", port); 395 | 396 | /* 397 | * Send fds (stdout, stderr and stdin) + 398 | * data (argc, amt_bytes, cwd and argv) 399 | */ 400 | if (send_fds(sock, send_buff, amnt) != (ssize_t)amnt) 401 | die("Unable to send the file descriptors!...\n"); 402 | 403 | /* Wait for process PID. */ 404 | if ((amnt = recv(sock, ret_buff, 4, 0)) != 4) 405 | goto out; 406 | 407 | ret = msg_to_int32(ret_buff); 408 | process_pid = ret; 409 | 410 | /* 411 | * Our fds were already sent to the preloader and they're 412 | * used directly by the original process. No need to poll 413 | * or anything else here =). 414 | */ 415 | 416 | /* Wait for return value. */ 417 | if ((amnt = recv(sock, ret_buff, 4, 0)) == 4) 418 | ret = msg_to_int32(ret_buff); 419 | 420 | out: 421 | close(sock); 422 | free(send_buff); 423 | return ((int)ret); 424 | } 425 | -------------------------------------------------------------------------------- /reaper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "ipc.h" 31 | #include "log.h" 32 | 33 | /* 34 | * What is the reaper? 35 | * When a child process dies, it sends a SIGCHLD to the parent 36 | * process, signaling that it has died. 37 | * 38 | * If the parent does nothing, the child becomes a zombie waiting 39 | * for an ack from the parent. By accumulating multiple children 40 | * with no response from the parent, a zombie horde forms and eats 41 | * all your system resources, it's not cool. 42 | * 43 | * The parent process basically has 3 alternatives: 44 | * - Ignore children's SIGCHLD, so the child process can rest 45 | * in peace without waiting for the parent. 46 | * 47 | * - Define a signal handler to asynchronously handle when a 48 | * child dies. 49 | * 50 | * - Stop execution and wait for the children to die (via wait()). 51 | * 52 | * The first option is not viable: the preloader *needs* to know 53 | * when a child is terminated. The second option is too much work: 54 | * the child can interrupt the parent at any time, which would fail 55 | * to execute certain syscalls and etc. The third option is what 56 | * the reaper uses: a dedicated thread (the 'reaper') waits for 57 | * the children to die, and when it does, some action is taken. 58 | * 59 | * This way, the main thread can continue running peacefully without 60 | * worrying about the children dying. 61 | */ 62 | 63 | /* Children list mutex. */ 64 | static pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER; 65 | 66 | /* Children list. */ 67 | static struct child_list 68 | { 69 | size_t size; 70 | size_t last_empty; 71 | struct children 72 | { 73 | int fd; 74 | pid_t pid; 75 | } *c; 76 | } cl; 77 | 78 | /** 79 | * @brief Given a pid @p pid, gets the position 80 | * the child process occupies in the list. 81 | * 82 | * @param pid Child pid. 83 | * 84 | * @return If success, returns a number greater 85 | * than or equal 0. Otherwise, returns -1. 86 | */ 87 | static off_t get_child_pos(pid_t pid) 88 | { 89 | off_t i; 90 | 91 | pthread_mutex_lock(&list_mutex); 92 | for (i = 0; i < (off_t)cl.size; i++) 93 | if (cl.c[i].pid == pid) 94 | break; 95 | 96 | if (i == (off_t)cl.size) 97 | i = -1; 98 | pthread_mutex_unlock(&list_mutex); 99 | 100 | return (i); 101 | } 102 | 103 | /** 104 | * @brief Indefinitely waits for child processes to exit 105 | * and/or die. 106 | * 107 | * When a child process die, the reaper should get its 108 | * exit code and then send to the appropriate client 109 | * process. After that, the socket from the client 110 | * can safely be closed, as well the resources allocated 111 | * for the child. 112 | * 113 | * @param p Unused. 114 | * @return Always NULL (unused too). 115 | */ 116 | static void* wait_children(void *p) 117 | { 118 | ((void)p); 119 | 120 | #define MAX_ATTEMPTS 3 121 | #define PAUSE_MS 20 122 | 123 | int attempts; 124 | int wstatus; 125 | off_t cpos; 126 | pid_t pid; 127 | int ret; 128 | 129 | attempts = 0; 130 | 131 | while (1) 132 | { 133 | pid = wait(&wstatus); 134 | 135 | /* 136 | * There may be a slight race condition where the child 137 | * process dies before the parent process even adds it 138 | * to the list. To work around this scenario, the code 139 | * below tries MAX_ATTEMPTS times to get the child of 140 | * the list, with a pause of PAUSE_MS milliseconds 141 | * between each attempt. 142 | * 143 | * If it still can't get it, the daemon is aborted. 144 | */ 145 | again: 146 | cpos = get_child_pos(pid); 147 | if (cpos < 0) 148 | { 149 | attempts++; 150 | 151 | log_crit("Unable to find child (pid: %d), attempt: %d/%d\n", 152 | pid, attempts, MAX_ATTEMPTS); 153 | 154 | if (attempts < MAX_ATTEMPTS) 155 | { 156 | usleep(PAUSE_MS * 1000); 157 | goto again; 158 | } 159 | else 160 | die("Attempts exceeded for pid: %d, aborting!\n", pid); 161 | } 162 | else 163 | attempts = 0; 164 | 165 | /* Get return code. */ 166 | if (WIFEXITED(wstatus)) 167 | ret = WEXITSTATUS(wstatus); 168 | else if (WIFSIGNALED(wstatus)) 169 | ret = WTERMSIG(wstatus) + 128; /* I'm just mimicking bash here. */ 170 | else 171 | ret = 1; 172 | 173 | /* Send return code. */ 174 | if (!ipc_send_int32(ret, cl.c[cpos].fd)) 175 | log_crit("Unable to send return value to (pid: %d / fd: %d), " 176 | "maybe disconnected?\n", pid, cl.c[cpos].fd); 177 | 178 | ipc_close(1, cl.c[cpos].fd); 179 | 180 | /* Set empty position. */ 181 | pthread_mutex_lock(&list_mutex); 182 | cl.c[cpos].fd = -1; 183 | cl.last_empty = cpos; 184 | pthread_mutex_unlock(&list_mutex); 185 | } 186 | 187 | return (NULL); 188 | } 189 | 190 | /** 191 | * @brief As the children list is dynamically allocated, 192 | * this function increases the list capacity. 193 | * 194 | * @note This routine assumes that the list is safely 195 | * protected against race conditions before it is called, 196 | * as it does not use any kind of locks here. 197 | */ 198 | static void increase_buffer(void) 199 | { 200 | size_t i; 201 | struct children *c; 202 | 203 | c = realloc(cl.c, sizeof(*cl.c) * (cl.size * 2)); 204 | if (!c) 205 | die("Unable to increase buffer! (from %zu bytes to %zu bytes)\n", 206 | cl.size, cl.size * 2); 207 | 208 | /* Clear all new positions. */ 209 | for (i = 0; i < cl.size; i++) 210 | c->fd = -1; 211 | 212 | cl.last_empty = cl.size; 213 | cl.c = c; 214 | cl.size *= 2; 215 | } 216 | 217 | /** 218 | * @brief Adds a new pid/fd pair into the children list. 219 | * 220 | * @param pid PID pair to be added in the list. 221 | * @param fd FD pair to be added. 222 | */ 223 | void reaper_add_child(pid_t pid, int fd) 224 | { 225 | size_t i; 226 | size_t pos; 227 | 228 | pthread_mutex_lock(&list_mutex); 229 | pos = cl.last_empty; 230 | 231 | /* If last_empty is an invalid position or if already occupied. */ 232 | if (pos >= cl.size || cl.c[pos].fd != -1) 233 | { 234 | /* Look for empty position. */ 235 | for (i = 0; i < cl.size; i++) 236 | if (cl.c[i].fd == -1) 237 | break; 238 | 239 | /* If not found, increase buffer. */ 240 | if (i == cl.size) 241 | { 242 | increase_buffer(); 243 | pos = cl.last_empty; 244 | } 245 | else 246 | pos = i; 247 | } 248 | 249 | /* Add child. */ 250 | cl.c[pos].pid = pid; 251 | cl.c[pos].fd = fd; 252 | cl.last_empty = pos + 1; /* just an educated guess. */ 253 | pthread_mutex_unlock(&list_mutex); 254 | } 255 | 256 | /** 257 | * @brief Initialize all resources related to the reaper: 258 | * - Allocate children list. 259 | * - Start reaper thread. 260 | */ 261 | void reaper_init(void) 262 | { 263 | size_t i; 264 | pthread_t t; 265 | 266 | cl.size = 16; 267 | cl.last_empty = 0; 268 | cl.c = calloc(16, sizeof(*cl.c)); 269 | if (!cl.c) 270 | die("Cant allocate children list!\n"); 271 | 272 | /* Clear all new positions. */ 273 | for (i = 0; i < cl.size; i++) 274 | cl.c[i].fd = -1; 275 | 276 | /* Start our reaper. */ 277 | if (pthread_create(&t, NULL, wait_children, NULL)) 278 | die("Unable to create reaper thread..."); 279 | 280 | pthread_detach(t); 281 | } 282 | 283 | /** 284 | * @brief Deallocate all resources related to the reaper: 285 | * - Children list. 286 | */ 287 | void reaper_finish(void) 288 | { 289 | free(cl.c); 290 | } 291 | -------------------------------------------------------------------------------- /reaper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef REAPER_H 26 | #define REAPER_H 27 | 28 | /* External functions. */ 29 | extern void reaper_init(void); 30 | extern void reaper_finish(void); 31 | extern void reaper_add_child(pid_t pid, int fd); 32 | 33 | #endif /* REAPER_H */ 34 | -------------------------------------------------------------------------------- /tests/test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #ifndef __UCLIBC__ 29 | #include 30 | #endif 31 | 32 | static char expected_char = 'a'; 33 | 34 | int main(int argc, char **argv) 35 | { 36 | int i; 37 | char **p; 38 | char buff[128]; 39 | 40 | printf("hello stdout\n"); 41 | fprintf(stderr, "hello stderr\n"); 42 | 43 | /* Read from stdin until EOF. */ 44 | while (fgets(buff, sizeof buff, stdin)) 45 | puts(buff); 46 | 47 | /* Confirm EOF and test stdout after stdin is EOF'ed. */ 48 | printf("feof(stdin): %d\n", feof(stdin)); 49 | fprintf(stderr, "testing stderr again!\n"); 50 | 51 | /* Arguments. */ 52 | printf("argc: %d\n", argc); 53 | 54 | printf("argv[0] = %s\n", argv[0]); 55 | 56 | for (i = 1; i < argc; i++) 57 | { 58 | /* Validate arguments: shoud always be: 'a', 'b', 'c'... */ 59 | if (argv[i][0] == expected_char) 60 | { 61 | printf("argv[%d] = %s\n", i, argv[i]); 62 | expected_char++; 63 | if (expected_char > 'z') 64 | expected_char = 'a'; 65 | } 66 | else 67 | return (21); 68 | } 69 | 70 | /* 'Non-typical' way to print arguments. */ 71 | printf("\"non-typical\":\n"); 72 | p = argv; 73 | expected_char = 'a'; 74 | 75 | printf("argv: %s\n", *p++); 76 | for (; *p; p++) 77 | { 78 | if (p[0][0] == expected_char) 79 | { 80 | printf("argv: %s\n", *p); 81 | expected_char++; 82 | if (expected_char > 'z') 83 | expected_char = 'a'; 84 | } 85 | else 86 | return (22); 87 | } 88 | 89 | /* Get some env var to make sure they're no 'corrupted'. */ 90 | printf("PWD: (%s)\n", getenv("PWD")); 91 | 92 | /* Get some aux val to make sure they're accessible too. */ 93 | #ifndef __UCLIBC__ 94 | printf("AT_PAGESZ: %lu\n", getauxval(AT_PAGESZ)); 95 | #endif 96 | 97 | /* Return some number. */ 98 | return (42); 99 | } 100 | -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # MIT License 4 | # 5 | # Copyright (c) 2022 Davidson Francis 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | # Fancy colors =) 26 | RED="\033[0;31m" 27 | GREEN="\033[0;32m" 28 | NC="\033[0m" 29 | 30 | # Paths 31 | CURDIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 32 | PROG=$(readlink -f "$CURDIR/../preloader") 33 | CLI=$(readlink -f "$CURDIR/../preloader_cli") 34 | TEST="$CURDIR/test" 35 | 36 | announce() { 37 | printf "Test %s: " "$1" 38 | } 39 | 40 | not_pass() { 41 | $PROG -s 42 | printf "[%bNOT PASSED%b]\nReason: $2\n" "$RED" "$NC" >&2 43 | exit 1 44 | } 45 | 46 | pass() { 47 | $PROG -s 48 | printf "[%bPASSED%b]\n" "$GREEN" "$NC" 49 | rm -rf "$CURDIR/.out_normal.txt" 50 | rm -rf "$CURDIR/.out_cli.txt" 51 | } 52 | 53 | # Sanity checks 54 | if [ ! -f "$PROG" ]; then 55 | echo "$PROG not found, failed!" 56 | exit 1 57 | fi 58 | 59 | if [ ! -f "$TEST" ]; then 60 | echo "Test program not found, failed!" 61 | exit 1 62 | fi 63 | 64 | test1() { 65 | local flags="$1" 66 | local test_name="$2" 67 | 68 | announce "$2" 69 | 70 | # First: run test normally 71 | echo "some input to test stdin" | $TEST a b c d \ 72 | &> "$CURDIR/.out_normal.txt" 73 | out_n="$?" 74 | 75 | # Second: 76 | # 1) Launch preloader in daemon mode 77 | $PROG "$TEST" -d "$flags" 78 | sleep 2s # Wait for daemon start 79 | 80 | # 2) Run preloader client 81 | echo "some input to test stdin" | $CLI "$TEST" a b c d \ 82 | &> "$CURDIR/.out_cli.txt" 83 | out_c="$?" 84 | 85 | ## Compare return code and outputs 86 | if [ "$out_n" -ne "$out_c" ]; then 87 | not_pass "$test_name" \ 88 | "Return code differ from expected!, expected: $out_n, got: $out_c" 89 | fi 90 | 91 | # 92 | # Compare both outputs: 93 | # Since stdout might be 'reordered' with stderr, we need 94 | # to sort both outputs. 95 | # 96 | if ! cmp -s <(sort "$CURDIR/.out_cli.txt") \ 97 | <(sort "$CURDIR/.out_normal.txt"); then 98 | not_pass "$test_name" "Output differ from expected" 99 | fi 100 | 101 | pass "$test_name" 102 | } 103 | 104 | test2() { 105 | local flags="$1" 106 | local test_name="$2" 107 | local arr=({a..z}) 108 | local MAX=200 109 | 110 | announce "$2" 111 | 112 | # 1) Launch preloader in daemon mode 113 | $PROG "$TEST" -d "$flags" 114 | sleep 2s # Wait for daemon start 115 | 116 | # Loop through each amount of args 117 | for i in $(seq 1 $MAX) 118 | do 119 | # Build argument list 120 | # First run (i = 1): a 121 | # Second run (i = 2): a b 122 | # Third run (i = 3): a b c 123 | # n-th run (i = n): a b c .. z a b c ... 124 | args=() 125 | for j in $(seq 0 $((i-1))) 126 | do 127 | idx=$((j%26)) 128 | args+=(${arr[idx]}) 129 | done 130 | 131 | # Run 132 | echo "input" | $CLI "$TEST" "${args[@]}" &> /dev/null 133 | out_c="$?" 134 | 135 | if [ "$out_c" -ne 42 ]; then 136 | not_pass "$test_name" "Failed with $i arguments!" 137 | fi 138 | done 139 | 140 | pass "$test_name" 141 | } 142 | 143 | test1 "" "#1: normal run " 144 | test1 "-b" "#2: run w/ bind" 145 | test2 "" "#3: range test (this may take a while)" 146 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "log.h" 36 | #include "util.h" 37 | 38 | /** 39 | * @brief Given a @p pid_path and @p port, return the string 40 | * containing the full path for the pid file. 41 | * 42 | * @param pid_path PID path folder 43 | * @param port Main/control port. 44 | * 45 | * @return If success, return the path for the pid file, 46 | * otherwise, returns NULL. 47 | */ 48 | static char* get_pid_file_path(const char *pid_path, int port) 49 | { 50 | static char path[4096]; 51 | if (strlen(pid_path) + sizeof "/preloader_65535.pid" > sizeof path) 52 | return (NULL); 53 | 54 | snprintf(path, sizeof path - 1, "%s/preloader_%d.pid", pid_path, port); 55 | return (path); 56 | } 57 | 58 | /** 59 | * @brief Read the current PID file (if any) and check 60 | * if there is a process running for the read pid. 61 | * 62 | * @param pid_path PID path folder. 63 | * @param port Main/control port. 64 | * 65 | * @return Returns 0 if and only if there is a process 66 | * already running for the saved pid_file. In this case, 67 | * the library *should not* proceed with the execution. 68 | * 69 | * Otherwise, returns -1, meaning that the pid file 70 | * do not exist and/or is invalid and should be ignored, 71 | * the execution can proceed as usual. 72 | */ 73 | int read_and_check_pid(const char *pid_path, int port) 74 | { 75 | int i; 76 | int fd; 77 | int ret; 78 | ssize_t r; 79 | size_t pid; 80 | char *pid_file; 81 | char buff[16] = {0}; 82 | 83 | ret = -1; 84 | 85 | pid_file = get_pid_file_path(pid_path, port); 86 | if (!pid_file) 87 | die("pid_path is too big!\n"); 88 | 89 | fd = open(pid_file, O_RDONLY); 90 | if (fd < 0) 91 | return (ret); 92 | 93 | r = read(fd, buff, sizeof buff); 94 | if (r < 0) 95 | goto err0; 96 | 97 | pid = 0; 98 | for (i = 0; i < r; i++) 99 | { 100 | if (buff[i] < '0' || buff[i] > '9') 101 | goto err0; 102 | else 103 | { 104 | pid *= 10; 105 | pid += (buff[i] - '0'); 106 | } 107 | } 108 | 109 | /* Now we have a (possibly valid) pid, check if 110 | * the process is still running. */ 111 | if (kill(pid, 0) < 0) 112 | goto err0; /* Process not running. */ 113 | 114 | close(fd); 115 | return (0); 116 | 117 | /* In case of failure, erase the file. */ 118 | err0: 119 | close(fd); 120 | unlink(pid_file); 121 | return (ret); 122 | } 123 | 124 | /** 125 | * @brief Create a pid file for a given @p pid_path and @p port. 126 | * 127 | * @param pid_path PID path folder. 128 | * @param port Main/control port. 129 | * 130 | * @return Returns 0 if success, -1 otherwise. 131 | */ 132 | int create_pid(const char *pid_path, int port) 133 | { 134 | int fd; 135 | char *pid_file; 136 | 137 | pid_file = get_pid_file_path(pid_path, port); 138 | if (!pid_file) 139 | die("pid_path is too big!\n"); 140 | 141 | fd = creat(pid_file, 0644); 142 | if (fd < 0) 143 | return (-1); 144 | 145 | dprintf(fd, "%d", (int)getpid()); 146 | close(fd); 147 | return (0); 148 | } 149 | 150 | /** 151 | * Safe string-to-int routine that takes into account: 152 | * - Overflow and Underflow 153 | * - No undefined behavior 154 | * 155 | * Taken from https://stackoverflow.com/a/12923949/3594716 156 | * and slightly adapted: no error classification, because 157 | * I don't need to know, error is error. 158 | * 159 | * @param out Pointer to integer. 160 | * @param s String to be converted. 161 | * 162 | * @return Returns 0 if success and a negative number otherwise. 163 | */ 164 | int str2int(int *out, const char *s) 165 | { 166 | char *end; 167 | if (s[0] == '\0' || isspace(s[0])) 168 | return (-1); 169 | errno = 0; 170 | 171 | long l = strtol(s, &end, 10); 172 | 173 | /* Both checks are needed because INT_MAX == LONG_MAX is possible. */ 174 | if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) 175 | return (-1); 176 | if (l < INT_MIN || (errno == ERANGE && l == LONG_MIN)) 177 | return (-1); 178 | if (*end != '\0') 179 | return (-1); 180 | 181 | *out = l; 182 | return (0); 183 | } 184 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef UTIL_H 26 | #define UTIL_H 27 | 28 | #define COMPILE_TIME_ASSERT(expr) \ 29 | switch(0){case 0:case expr:;} 30 | 31 | extern int read_and_check_pid(const char *pid_file, int port); 32 | extern int create_pid(const char *pid_file, int port); 33 | extern int str2int(int *out, const char *s); 34 | 35 | #endif /* UTIL_H */ 36 | -------------------------------------------------------------------------------- /utils/finder.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #define _XOPEN_SOURCE 500 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "khash.h" 37 | 38 | /* 39 | * This is Finder. 40 | * Finder is a ldd-like tool made specifically for preloader[0]: it searches 41 | * for the dependencies of one or more files (recursively, if folder) and 42 | * counts how many libraries and relocations each binary has. 43 | * 44 | * It is also possible to display the list of dependencies if DUMP_LIBS = 1. 45 | * 46 | * Think of it as a 'bloat detector': with the amount of dependencies on 47 | * each executable and relocations, you can get an idea of how big a 48 | * program is for the system and how long it can take to load. 49 | * 50 | * Usage: 51 | * ./finder 52 | * 53 | * Output: 54 | * "foo",4,1532 55 | * "bar",190,233467 56 | * 57 | * where: 58 | * first column: ELF name. 59 | * second column: amount of shared libs/dependencies. 60 | * third column: amount of relocations. 61 | * 62 | * Real-world scenario: Find the top-5 ELF files with the most amount of 63 | * dynamic libraries. 64 | * 65 | # 1) Get everything first: 66 | * $ ./finder / /home | tee allfiles.txt 67 | * 68 | * 2) Sort by second column in descending order: 69 | * $ sort -t',' -rg -k2 allfiles.txt | head -n5 | column -ts',' 70 | * "/usr/bin/mplayer" 219 218822 71 | * "/usr/lib64/qt5/libexec/QtWebEngineProcess" 196 615522 72 | * "/usr/bin/SDLvncviewer" 190 171352 73 | * "/usr/bin/ffprobe" 187 198403 74 | * "/usr/bin/ffplay" 187 198472 75 | * 76 | * If sort by relocation amount: 77 | * $ sort -t',' -rg -k3 allfiles.txt | head -n5 | column -ts',' 78 | * "/usr/bin/wireshark" 74 1480265 79 | * "/usr/bin/tshark" 45 1362767 80 | * "/usr/bin/rawshark" 45 1362226 81 | * "/usr/bin/sharkd" 42 1360746 82 | * "/opt/google/chrome/chrome" 92 820895 83 | * 84 | * Notes: 85 | * 1) The search do not cross mount-points, so each desired mount need to be 86 | * specified one by one. If your /home belongs to the root partition (/), there 87 | * is no need to do that, but only: ./finder /. 88 | * 89 | * 2) Finder deps: libelf. 90 | * 91 | * Links: 92 | * [0]: https://github.com/Theldus/preloader 93 | */ 94 | 95 | #define DUMP_LIBS 0 96 | #define PRINT_HEADER 0 97 | #define VERBOSE 0 98 | 99 | /* Hashmap for our libs. */ 100 | KHASH_SET_INIT_STR(lib) 101 | 102 | /* Fancy macros. */ 103 | #if VERBOSE == 1 104 | #define errxit(...) \ 105 | do { \ 106 | fprintf(stderr, __VA_ARGS__); \ 107 | exit(EXIT_FAILURE); \ 108 | } while (0) 109 | 110 | #define errto(lbl, ...) \ 111 | do { \ 112 | fprintf(stderr, __VA_ARGS__); \ 113 | goto lbl; \ 114 | } while (0) 115 | #else 116 | #define errxit(...) exit(EXIT_FAILURE) 117 | #define errto(lbl, ...) goto lbl 118 | #endif 119 | 120 | /* ELF magic. */ 121 | static const unsigned char elf_magic[4] = {0x7f,0x45,0x4c,0x46}; 122 | 123 | /* Some possible search path for the libraries. */ 124 | static const char *search_path[] = { 125 | /* Slackware search path. */ 126 | "/usr/lib64", "/lib64", "/usr/local/lib64", 127 | "/usr/x86_64-slackware-linux/lib64", 128 | /* Ubuntu search path. */ 129 | "/usr/local/lib/i386-linux-gnu", 130 | "/lib/i386-linux-gnu", 131 | "/usr/local/lib/i686-linux-gnu", 132 | "/lib/i686-linux-gnu", 133 | "/usr/lib/i686-linux-gnu", 134 | "/usr/local/lib", 135 | "/usr/local/lib/x86_64-linux-gnu", 136 | "/lib/x86_64-linux-gnu", 137 | "/usr/lib/x86_64-linux-gnu", 138 | /* Raspberry-Pi (ARM32) search path. */ 139 | "/opt/vc/lib", 140 | "/usr/local/lib/arm-linux-gnueabihf", 141 | "/lib/arm-linux-gnueabihf", 142 | "/usr/lib/arm-linux-gnueabihf", 143 | "/usr/lib/arm-linux-gnueabihf/libfakeroot", 144 | "/usr/local/lib", 145 | /* Raspberry-Pi (ARM64, not tested) search path. */ 146 | "/opt/vc/lib", 147 | "/usr/local/lib/aarch64-linux-gnu", 148 | "/lib/aarch64-linux-gnu", 149 | "/usr/lib/aarch64-linux-gnu", 150 | "/usr/lib/aarch64-linux-gnu/libfakeroot", 151 | "/usr/local/lib", 152 | /* Android (ARM32 and ARM64) search path + Termux. */ 153 | "/system/lib", 154 | "/system/lib64", 155 | "/data/data/com.termux/files/usr/lib" 156 | }; 157 | 158 | /* Some data about the ELF file. */ 159 | struct open_elf 160 | { 161 | Elf *e; 162 | int fd; 163 | Elf_Data *strtab_data; 164 | }; 165 | 166 | static int dump_dyn_libs(struct open_elf *elf, Elf_Scn *scn, GElf_Shdr *shdr, 167 | khash_t(lib) *seen_list, uint64_t *rel_amnt); 168 | static int dump_elf(int fd, const char *file, khash_t(lib) *seen_list, 169 | uint64_t *rel_amnt); 170 | 171 | /** 172 | * @brief Given a library name, tries to found the appropriate 173 | * path for the library. 174 | * 175 | * E.g: foo.so -> /usr/lib/foo.so 176 | * 177 | * @param lib Library name. 178 | * 179 | * @return If found, returns a pointer containing the full 180 | * library path. If not found, returns NULL. 181 | */ 182 | static char* get_lib_path(const char *lib) 183 | { 184 | static char path[PATH_MAX] = {0}; 185 | struct stat st; 186 | size_t i; 187 | 188 | for (i = 0; i < sizeof search_path / sizeof search_path[0]; i++) 189 | { 190 | snprintf(path, PATH_MAX, "%s/%s", search_path[i], lib); 191 | if (!stat(path, &st)) 192 | return (path); 193 | } 194 | 195 | return (NULL); 196 | } 197 | 198 | /** 199 | * @brief Given a FD or file, open the ELF file and initialize 200 | * its data structure. 201 | * 202 | * @param fd File descriptor of an already opened file, if any. 203 | * @param file Path of the file to be opened, if any. 204 | * @param elf Data structure where we should save some 205 | * data about the ELF file. 206 | * 207 | * @return Returns 0 if success, -1 otherwise. 208 | */ 209 | static int open_elf(int fd, const char *file, struct open_elf *elf) 210 | { 211 | Elf_Kind ek; 212 | 213 | if (!elf) 214 | return (-1); 215 | 216 | if (fd > 0) 217 | { 218 | lseek(fd, 0, SEEK_SET); 219 | elf->fd = fd; 220 | } 221 | else 222 | if ((elf->fd = open(file, O_RDONLY, 0)) < 0) 223 | errto(out1, "Unable to open %s!\n", file); 224 | 225 | if ((elf->e = elf_begin(elf->fd, ELF_C_READ, NULL)) == NULL) 226 | errto(out2, "elf_begin() failed!\n"); 227 | 228 | ek = elf_kind(elf->e); 229 | if (ek != ELF_K_ELF) 230 | errto(out2, "File \"%d\" (fd: %d) is not an ELF file!\n", file, fd); 231 | 232 | return (0); 233 | out2: 234 | close(elf->fd); 235 | out1: 236 | elf_end(elf->e); 237 | return (-1); 238 | } 239 | 240 | /** 241 | * @brief Given an opened ELF file, closes all the 242 | * resources. 243 | * 244 | * @param elf Opened ELF file. 245 | */ 246 | static void close_elf(struct open_elf *elf) 247 | { 248 | if (!elf) 249 | return; 250 | if (elf->e) 251 | elf_end(elf->e); 252 | if (elf->fd > 0) 253 | close(elf->fd); 254 | } 255 | 256 | /** 257 | * @brief Give an ELF file, find the next section of type 258 | * @p sh_type and return it. 259 | * 260 | * @param elf Opened ELF file. 261 | * @param scn ELF section to be resumed from (NULL if 262 | * from the beginning). 263 | * @param shdr Elf section header. 264 | * @param sh_type Section type. 265 | * 266 | * @return Returns a pointer to the found section, NULL 267 | * if not found. 268 | */ 269 | static Elf_Scn *find_section(struct open_elf *elf, Elf_Scn *scn, 270 | GElf_Shdr *shdr, GElf_Word sh_type) 271 | { 272 | while ((scn = elf_nextscn(elf->e, scn)) != NULL) 273 | { 274 | if (gelf_getshdr(scn, shdr) != shdr) 275 | continue; 276 | 277 | if (shdr->sh_type == sh_type) 278 | return (scn); 279 | } 280 | return (NULL); 281 | } 282 | 283 | /** 284 | * @brief Given an ELF file, get the amount of relocations present 285 | * in that file, i.e: the amount of entries for all sections of 286 | * type SHT_RELA and SHT_REL. 287 | * 288 | * @param elf Opened ELF file. 289 | * 290 | * @return Returns the amount of relocations found, 0 if none. 291 | */ 292 | static uint64_t get_relocs_amnt(struct open_elf *elf) 293 | { 294 | Elf_Scn *scn; 295 | GElf_Shdr shdr; 296 | uint64_t size; 297 | 298 | scn = NULL; 299 | size = 0; 300 | while ((scn = elf_nextscn(elf->e, scn)) != NULL) 301 | { 302 | if (gelf_getshdr(scn, &shdr) != &shdr) 303 | continue; 304 | 305 | if (shdr.sh_type != SHT_RELA && shdr.sh_type != SHT_REL) 306 | continue; 307 | 308 | size += (shdr.sh_size / shdr.sh_entsize); 309 | } 310 | return (size); 311 | } 312 | 313 | /** 314 | * @Brief Given a opened ELF file, find a load its strtab content. 315 | * 316 | * @param elf Opened ELF file. 317 | * 318 | * @return Returns 1 if success, 0 otherwise. 319 | */ 320 | static int load_strtab(struct open_elf *elf) 321 | { 322 | Elf_Scn *scn; 323 | GElf_Shdr shdr; 324 | 325 | scn = NULL; 326 | if ((scn = find_section(elf, scn, &shdr, SHT_STRTAB)) == NULL) 327 | errto(out0, "Unable to find strtab section!\n"); 328 | 329 | elf->strtab_data = elf_getdata(scn, NULL); 330 | if (!elf->strtab_data) 331 | errto(out0, "Unable to find strtab data!\n"); 332 | 333 | return (1); 334 | out0: 335 | return (0); 336 | } 337 | 338 | /** 339 | * @brief Given a library name, try to find the proper 340 | * path and then add to the seen_list table. 341 | * 342 | * @param lib_needed Library name. 343 | * @param seen_list Hashtable representing the list of already seen 344 | libraries at the moment. 345 | * @param path_lib Pointer where the complete library path will 346 | * be saved. 347 | * 348 | * @return Returns -1 if error, 0 if already present, 1 if added 349 | * with success. 350 | */ 351 | static int add_lib_to_seen(const char *lib_needed, 352 | khash_t(lib) *seen_list, char **path_lib) 353 | { 354 | char *path; 355 | khint_t k; 356 | int ret; 357 | 358 | /* Try to add in our hash table. */ 359 | path = get_lib_path(lib_needed); 360 | if (!path) 361 | return (-1); /* path not found. */ 362 | 363 | k = kh_put(lib, seen_list, path, &ret); 364 | if (ret < 0) 365 | errxit("Unable to put lib in seen list, aborting!\n"); 366 | 367 | /* Already present, skip this one. */ 368 | else if (ret == 0) 369 | return (0); 370 | 371 | /* Not present, add the lib. */ 372 | path = strdup(path); 373 | if (!path) 374 | errxit("Unable to dup string!\n"); 375 | 376 | kh_key(seen_list, k) = path; 377 | *path_lib = path; 378 | return (1); 379 | } 380 | 381 | /** 382 | * @brief For a given opened ELF file, dump its content 383 | * recursively. 384 | * 385 | * @param elf Opened ELF file. 386 | * @param scn Elf dynamic section to be dumped. 387 | * @param shdr Section header for the given section. 388 | * @param seen_list Hashtable representing the list of already seen 389 | libraries at the moment. 390 | * @param rel_amnt Total relocation count. 391 | * 392 | * @return Returns -1 if error, 0 if success. 393 | */ 394 | static int dump_dyn_libs(struct open_elf *elf, Elf_Scn *scn, GElf_Shdr *shdr, 395 | khash_t(lib) *seen_list, uint64_t *rel_amnt) 396 | { 397 | GElf_Dyn dyn_entry; 398 | Elf_Data *dyn_data; 399 | char *lib_name; /* library name: libc.so. */ 400 | char *path_lib; /* full library path: /usr/lib64/libc.so. */ 401 | int count; 402 | int i; 403 | 404 | dyn_data = elf_getdata(scn, NULL); 405 | if (!dyn_data) 406 | errto(out0, "Unable to retrieve data from dynamic section!\n"); 407 | 408 | count = shdr->sh_size / shdr->sh_entsize; 409 | 410 | /* Close our ELF fd. */ 411 | close(elf->fd); elf->fd = -1; 412 | 413 | for (i = 0; i < count; i++) 414 | { 415 | if (!gelf_getdyn(dyn_data, i, &dyn_entry)) 416 | continue; 417 | 418 | if (dyn_entry.d_tag != DT_NEEDED) 419 | continue; 420 | 421 | lib_name = (char *)elf->strtab_data->d_buf + 422 | dyn_entry.d_un.d_val; 423 | 424 | /* 425 | * Try to add lib to seem list: 426 | * If: 0: already exists 427 | * If: < 0: unable to get the current path, skip too 428 | * If: > 0: not exists, added with success. 429 | */ 430 | if (add_lib_to_seen(lib_name, seen_list, &path_lib) <= 0) 431 | continue; 432 | 433 | dump_elf(-1, path_lib, seen_list, rel_amnt); 434 | } 435 | return (0); 436 | out0: 437 | return (-1); 438 | } 439 | 440 | /** 441 | * @brief Given a file (fd or path), dump its content: relocation 442 | * amount and recursive list of libraries. 443 | * 444 | * @param fd Opened fd, if any. 445 | * @param file File path, if any. 446 | * @param seen_list Hashtable representing the list of already seen 447 | libraries at the moment. 448 | * @param rel_amnt Total relocation count. 449 | * 450 | * @return Returns -1 if error and 0 if success. 451 | */ 452 | static int dump_elf(int fd, const char *file, khash_t(lib) *seen_list, 453 | uint64_t *rel_amnt) 454 | { 455 | struct open_elf op_elf = {0}; 456 | GElf_Shdr shdr; 457 | Elf_Scn *scn; 458 | int ret; 459 | 460 | ret = -1; 461 | 462 | if (open_elf(fd, file, &op_elf) < 0) 463 | return (-1); 464 | 465 | if (!load_strtab(&op_elf)) 466 | goto out; 467 | 468 | /* Increment total rel amount. */ 469 | *rel_amnt += get_relocs_amnt(&op_elf); 470 | 471 | /* Dump dyn-libs. */ 472 | scn = NULL; 473 | while ((scn = find_section(&op_elf, scn, &shdr, SHT_DYNAMIC))) 474 | dump_dyn_libs(&op_elf, scn, &shdr, seen_list, rel_amnt); 475 | 476 | ret = 0; 477 | out: 478 | close_elf(&op_elf); 479 | return (ret); 480 | } 481 | 482 | /** 483 | * @brief For a given file path, check if the given 484 | * file is an ELF file or not. 485 | * 486 | * @param path File to be checked. 487 | * 488 | * @return Returns a negative number if not an ELF, 489 | * and the file descriptor of the opened ELF file 490 | * otherwise. 491 | * 492 | * @note If @p path is an ELF file, the opened fd is not 493 | * closed here. 494 | */ 495 | static int is_elf(const char *path) 496 | { 497 | unsigned char buff[17] = {0}; /* including part of the ELF header. */ 498 | int ret; 499 | int fd; 500 | 501 | ret = -1; 502 | 503 | fd = open(path, O_RDONLY); 504 | if (fd < 0) 505 | return (ret); 506 | 507 | if (read(fd, buff, sizeof buff) != sizeof buff) 508 | goto out; 509 | 510 | /* Check signature. */ 511 | if (memcmp(buff, elf_magic, sizeof elf_magic)) 512 | goto out; 513 | 514 | /* 515 | * Check header. 516 | * 2 == executable, 3 == shared object. 517 | */ 518 | if (buff[16] != 2 && buff[16] != 3) 519 | goto out; 520 | 521 | return (fd); 522 | out: 523 | close(fd); 524 | return (ret); 525 | } 526 | 527 | /** 528 | * @brief For a given path, check if it is an ELF file 529 | * and then dump is content. 530 | * 531 | * @param path Potential ELF file to be analyzed. 532 | */ 533 | static void handle_possible_elf(const char *path) 534 | { 535 | int fd; 536 | char *l; 537 | khint_t k; 538 | uint64_t rel_amnt; 539 | khash_t(lib) *seen_list; 540 | 541 | rel_amnt = 0; 542 | 543 | /* Skip if not ELF file. */ 544 | if ((fd = is_elf(path)) < 0) 545 | return; 546 | 547 | /* Initialize our hash map. */ 548 | seen_list = kh_init(lib); 549 | if (!seen_list) 550 | errxit("Unable to create the seen table!\n"); 551 | 552 | /* Open ELF file. */ 553 | if (dump_elf(fd, path, seen_list, &rel_amnt) < 0) 554 | return; 555 | 556 | printf("\"%s\",%d,%d\n", 557 | path, (int)kh_size(seen_list), (int)rel_amnt); 558 | 559 | #if DUMP_LIBS == 1 560 | printf("\nlibs:\n"); 561 | #endif 562 | 563 | /* Dump our seen list. */ 564 | for (k = 0; k < kh_end(seen_list); k++) 565 | { 566 | if (!kh_exist(seen_list, k)) 567 | continue; 568 | 569 | l = (char*)kh_key(seen_list, k); 570 | #if DUMP_LIBS == 1 571 | puts(l); 572 | #endif 573 | free(l); 574 | } 575 | 576 | #if DUMP_LIBS == 1 577 | printf("\n"); 578 | #endif 579 | 580 | kh_destroy(lib, seen_list); 581 | } 582 | 583 | /** 584 | * @brief Roughly check if a given path is a library 585 | * or not. 586 | * 587 | * @param path Path to be checked. 588 | * 589 | * @return Returns 1 if a possible library, 0 otherwise. 590 | */ 591 | static int is_lib(const char *path) 592 | { 593 | char *p2 = strdup(path); 594 | char *base; 595 | size_t len; 596 | int ret; 597 | 598 | ret = 0; 599 | base = basename(p2); 600 | len = strlen(base); 601 | 602 | /* Must be at least lib[letter].so, or 7 characters. */ 603 | if (len < 7) 604 | goto out; 605 | 606 | if (!strncmp(base, "lib", 3)) 607 | if (strstr(base, ".so")) 608 | ret = 1; 609 | out: 610 | free(p2); 611 | return (ret); 612 | } 613 | 614 | /** 615 | * @brief nftw() handler, called each time a new file 616 | * is discovered. 617 | * 618 | * @param filepath File path to be analyzed. 619 | * @param info File path stat structure. 620 | * @param typeflag File path type. 621 | * @param pathinfo File path additional infos. 622 | * 623 | * @return Returns 0 if success, -1 otherwise. 624 | */ 625 | static int do_check(const char *path, const struct stat *info, 626 | const int typeflag, struct FTW *pathinfo) 627 | { 628 | ((void)pathinfo); 629 | 630 | /* Ignore everything that is not a file or is empty. */ 631 | if (typeflag != FTW_F || !info->st_size) 632 | return (0); 633 | 634 | /* Skip non executables. */ 635 | if (!(info->st_mode & S_IXUSR)) 636 | return (0); 637 | 638 | /* Skip libs. */ 639 | if (is_lib(path)) 640 | return (0); 641 | 642 | /* Check if ELF and handle it. */ 643 | handle_possible_elf(path); 644 | return (0); 645 | } 646 | 647 | /* Main. */ 648 | int main(int argc, char **argv) 649 | { 650 | struct stat path_stat; 651 | char *path; 652 | int res; 653 | int i; 654 | 655 | if (argc < 2) 656 | errxit("Usage: %s ", argv[0]); 657 | 658 | if (elf_version(EV_CURRENT) == EV_NONE) 659 | errxit("Unable to initialize libelf\n"); 660 | 661 | #if PRINT_HEADER == 1 662 | /* Print header, even if we not found anything. */ 663 | printf("binary_file sh_libs_amnt total_reloc_amnt\n"); 664 | #endif 665 | 666 | for (i = 1; i < argc; i++) 667 | { 668 | path = argv[i]; 669 | 670 | if (stat(path, &path_stat) != 0) 671 | errxit("Unable to stat path: %s\n", path); 672 | 673 | if (S_ISREG(path_stat.st_mode)) 674 | handle_possible_elf(path); 675 | else if (S_ISDIR(path_stat.st_mode)) 676 | { 677 | res = nftw(path, do_check, 10, FTW_PHYS|FTW_MOUNT); 678 | if (res) 679 | errxit("NFTW error!\n"); 680 | } 681 | else 682 | errxit("Parameter (%s) is not a regular file nor directory!\n"); 683 | } 684 | 685 | return (0); 686 | } 687 | -------------------------------------------------------------------------------- /utils/getlibs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # MIT License 4 | # 5 | # Copyright (c) 2022 Davidson Francis 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | # 26 | # Grab all the libraries opened via 'dlopen' and save to a given file 27 | # 28 | 29 | SCRIPT_NAME="$0" 30 | 31 | getlibs() { 32 | OUTPUT_FILE="$1" 33 | shift # '$@' should contain only the program+args 34 | 35 | TMPFOLDER="../.dump_$(date +%s%N)" 36 | mkdir "$TMPFOLDER" 37 | 38 | BIN="$(basename "$1")" 39 | 40 | strace -ff -o "$TMPFOLDER/dump_$BIN" "$@" 41 | 42 | # 43 | # This regex is an approximation of what a dlopen 44 | # might be: since dlopen is not a system call, we 45 | # can only try to 'guess' when a dlopen happens by 46 | # other syscalls. 47 | # 48 | # In *my* environments, it looks like this: 49 | # openat(AT_FDCWD, "/folder/foo.so", O_RDONLY|O_CLOEXEC) = positive_number 50 | # or this: 51 | # open("/folder/foo.so", O_RDONLY|O_CLOEXEC) = positive_number 52 | # 53 | grep -Pr \ 54 | "open(at)?\((AT_FDCWD, )?\".+\.so(\.[0-9]+)*\", O_RDONLY\|O_CLOEXEC\) = [^-]" "$TMPFOLDER" \ 55 | | cut -d'"' -f2 \ 56 | | sort -u > "$OUTPUT_FILE" 57 | 58 | rm -rf "$TMPFOLDER" 59 | } 60 | 61 | usage() { 62 | printf "Usage: %s -o output.txt " "$SCRIPT_NAME" 63 | printf "\n" 64 | exit 1 65 | } 66 | 67 | if [ "$#" -lt 3 ]; then 68 | echo "You should pass at least 3 parameters!" 69 | usage 70 | fi 71 | 72 | # Validate if '-o' was passed 73 | if [ "$1" != "-o" ]; then 74 | echo "Parameter ($1) is not output file (-o)!" 75 | usage 76 | fi 77 | 78 | getlibs "${@:2}" 79 | -------------------------------------------------------------------------------- /utils/khash.h: -------------------------------------------------------------------------------- 1 | /* The MIT License 2 | 3 | Copyright (c) 2008, 2009, 2011 by Attractive Chaos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | /* 27 | An example: 28 | 29 | #include "khash.h" 30 | KHASH_MAP_INIT_INT(32, char) 31 | int main() { 32 | int ret, is_missing; 33 | khiter_t k; 34 | khash_t(32) *h = kh_init(32); 35 | k = kh_put(32, h, 5, &ret); 36 | kh_value(h, k) = 10; 37 | k = kh_get(32, h, 10); 38 | is_missing = (k == kh_end(h)); 39 | k = kh_get(32, h, 5); 40 | kh_del(32, h, k); 41 | for (k = kh_begin(h); k != kh_end(h); ++k) 42 | if (kh_exist(h, k)) kh_value(h, k) = 1; 43 | kh_destroy(32, h); 44 | return 0; 45 | } 46 | */ 47 | 48 | /* 49 | 2013-05-02 (0.2.8): 50 | 51 | * Use quadratic probing. When the capacity is power of 2, stepping function 52 | i*(i+1)/2 guarantees to traverse each bucket. It is better than double 53 | hashing on cache performance and is more robust than linear probing. 54 | 55 | In theory, double hashing should be more robust than quadratic probing. 56 | However, my implementation is probably not for large hash tables, because 57 | the second hash function is closely tied to the first hash function, 58 | which reduce the effectiveness of double hashing. 59 | 60 | Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php 61 | 62 | 2011-12-29 (0.2.7): 63 | 64 | * Minor code clean up; no actual effect. 65 | 66 | 2011-09-16 (0.2.6): 67 | 68 | * The capacity is a power of 2. This seems to dramatically improve the 69 | speed for simple keys. Thank Zilong Tan for the suggestion. Reference: 70 | 71 | - http://code.google.com/p/ulib/ 72 | - http://nothings.org/computer/judy/ 73 | 74 | * Allow to optionally use linear probing which usually has better 75 | performance for random input. Double hashing is still the default as it 76 | is more robust to certain non-random input. 77 | 78 | * Added Wang's integer hash function (not used by default). This hash 79 | function is more robust to certain non-random input. 80 | 81 | 2011-02-14 (0.2.5): 82 | 83 | * Allow to declare global functions. 84 | 85 | 2009-09-26 (0.2.4): 86 | 87 | * Improve portability 88 | 89 | 2008-09-19 (0.2.3): 90 | 91 | * Corrected the example 92 | * Improved interfaces 93 | 94 | 2008-09-11 (0.2.2): 95 | 96 | * Improved speed a little in kh_put() 97 | 98 | 2008-09-10 (0.2.1): 99 | 100 | * Added kh_clear() 101 | * Fixed a compiling error 102 | 103 | 2008-09-02 (0.2.0): 104 | 105 | * Changed to token concatenation which increases flexibility. 106 | 107 | 2008-08-31 (0.1.2): 108 | 109 | * Fixed a bug in kh_get(), which has not been tested previously. 110 | 111 | 2008-08-31 (0.1.1): 112 | 113 | * Added destructor 114 | */ 115 | 116 | 117 | #ifndef __AC_KHASH_H 118 | #define __AC_KHASH_H 119 | 120 | /*! 121 | @header 122 | 123 | Generic hash table library. 124 | */ 125 | 126 | #define AC_VERSION_KHASH_H "0.2.8" 127 | 128 | #include 129 | #include 130 | #include 131 | 132 | /* compiler specific configuration */ 133 | 134 | #if UINT_MAX == 0xffffffffu 135 | typedef unsigned int khint32_t; 136 | #elif ULONG_MAX == 0xffffffffu 137 | typedef unsigned long khint32_t; 138 | #endif 139 | 140 | #if ULONG_MAX == ULLONG_MAX 141 | typedef unsigned long khint64_t; 142 | #else 143 | typedef unsigned long long khint64_t; 144 | #endif 145 | 146 | #ifndef kh_inline 147 | #ifdef _MSC_VER 148 | #define kh_inline __inline 149 | #else 150 | #define kh_inline inline 151 | #endif 152 | #endif /* kh_inline */ 153 | 154 | #ifndef klib_unused 155 | #if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) 156 | #define klib_unused __attribute__ ((__unused__)) 157 | #else 158 | #define klib_unused 159 | #endif 160 | #endif /* klib_unused */ 161 | 162 | typedef khint32_t khint_t; 163 | typedef khint_t khiter_t; 164 | 165 | #define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) 166 | #define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) 167 | #define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) 168 | #define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) 169 | #define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) 170 | #define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) 171 | #define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) 172 | 173 | #define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) 174 | 175 | #ifndef kroundup32 176 | #define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) 177 | #endif 178 | 179 | #ifndef kcalloc 180 | #define kcalloc(N,Z) calloc(N,Z) 181 | #endif 182 | #ifndef kmalloc 183 | #define kmalloc(Z) malloc(Z) 184 | #endif 185 | #ifndef krealloc 186 | #define krealloc(P,Z) realloc(P,Z) 187 | #endif 188 | #ifndef kfree 189 | #define kfree(P) free(P) 190 | #endif 191 | 192 | static const double __ac_HASH_UPPER = 0.77; 193 | 194 | #define __KHASH_TYPE(name, khkey_t, khval_t) \ 195 | typedef struct kh_##name##_s { \ 196 | khint_t n_buckets, size, n_occupied, upper_bound; \ 197 | khint32_t *flags; \ 198 | khkey_t *keys; \ 199 | khval_t *vals; \ 200 | } kh_##name##_t; 201 | 202 | #define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ 203 | extern kh_##name##_t *kh_init_##name(void); \ 204 | extern void kh_destroy_##name(kh_##name##_t *h); \ 205 | extern void kh_clear_##name(kh_##name##_t *h); \ 206 | extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ 207 | extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ 208 | extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ 209 | extern void kh_del_##name(kh_##name##_t *h, khint_t x); 210 | 211 | #define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ 212 | SCOPE kh_##name##_t *kh_init_##name(void) { \ 213 | return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ 214 | } \ 215 | SCOPE void kh_destroy_##name(kh_##name##_t *h) \ 216 | { \ 217 | if (h) { \ 218 | kfree((void *)h->keys); kfree(h->flags); \ 219 | kfree((void *)h->vals); \ 220 | kfree(h); \ 221 | } \ 222 | } \ 223 | SCOPE void kh_clear_##name(kh_##name##_t *h) \ 224 | { \ 225 | if (h && h->flags) { \ 226 | memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ 227 | h->size = h->n_occupied = 0; \ 228 | } \ 229 | } \ 230 | SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ 231 | { \ 232 | if (h->n_buckets) { \ 233 | khint_t k, i, last, mask, step = 0; \ 234 | mask = h->n_buckets - 1; \ 235 | k = __hash_func(key); i = k & mask; \ 236 | last = i; \ 237 | while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ 238 | i = (i + (++step)) & mask; \ 239 | if (i == last) return h->n_buckets; \ 240 | } \ 241 | return __ac_iseither(h->flags, i)? h->n_buckets : i; \ 242 | } else return 0; \ 243 | } \ 244 | SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ 245 | { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ 246 | khint32_t *new_flags = 0; \ 247 | khint_t j = 1; \ 248 | { \ 249 | kroundup32(new_n_buckets); \ 250 | if (new_n_buckets < 4) new_n_buckets = 4; \ 251 | if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ 252 | else { /* hash table size to be changed (shrink or expand); rehash */ \ 253 | new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ 254 | if (!new_flags) return -1; \ 255 | memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ 256 | if (h->n_buckets < new_n_buckets) { /* expand */ \ 257 | khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ 258 | if (!new_keys) { kfree(new_flags); return -1; } \ 259 | h->keys = new_keys; \ 260 | if (kh_is_map) { \ 261 | khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ 262 | if (!new_vals) { kfree(new_flags); return -1; } \ 263 | h->vals = new_vals; \ 264 | } \ 265 | } /* otherwise shrink */ \ 266 | } \ 267 | } \ 268 | if (j) { /* rehashing is needed */ \ 269 | for (j = 0; j != h->n_buckets; ++j) { \ 270 | if (__ac_iseither(h->flags, j) == 0) { \ 271 | khkey_t key = h->keys[j]; \ 272 | khval_t val; \ 273 | khint_t new_mask; \ 274 | new_mask = new_n_buckets - 1; \ 275 | if (kh_is_map) val = h->vals[j]; \ 276 | __ac_set_isdel_true(h->flags, j); \ 277 | while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ 278 | khint_t k, i, step = 0; \ 279 | k = __hash_func(key); \ 280 | i = k & new_mask; \ 281 | while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ 282 | __ac_set_isempty_false(new_flags, i); \ 283 | if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ 284 | { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ 285 | if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ 286 | __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ 287 | } else { /* write the element and jump out of the loop */ \ 288 | h->keys[i] = key; \ 289 | if (kh_is_map) h->vals[i] = val; \ 290 | break; \ 291 | } \ 292 | } \ 293 | } \ 294 | } \ 295 | if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ 296 | h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ 297 | if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ 298 | } \ 299 | kfree(h->flags); /* free the working space */ \ 300 | h->flags = new_flags; \ 301 | h->n_buckets = new_n_buckets; \ 302 | h->n_occupied = h->size; \ 303 | h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ 304 | } \ 305 | return 0; \ 306 | } \ 307 | SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ 308 | { \ 309 | khint_t x; \ 310 | if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ 311 | if (h->n_buckets > (h->size<<1)) { \ 312 | if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ 313 | *ret = -1; return h->n_buckets; \ 314 | } \ 315 | } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ 316 | *ret = -1; return h->n_buckets; \ 317 | } \ 318 | } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ 319 | { \ 320 | khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ 321 | x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ 322 | if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ 323 | else { \ 324 | last = i; \ 325 | while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ 326 | if (__ac_isdel(h->flags, i)) site = i; \ 327 | i = (i + (++step)) & mask; \ 328 | if (i == last) { x = site; break; } \ 329 | } \ 330 | if (x == h->n_buckets) { \ 331 | if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ 332 | else x = i; \ 333 | } \ 334 | } \ 335 | } \ 336 | if (__ac_isempty(h->flags, x)) { /* not present at all */ \ 337 | h->keys[x] = key; \ 338 | __ac_set_isboth_false(h->flags, x); \ 339 | ++h->size; ++h->n_occupied; \ 340 | *ret = 1; \ 341 | } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ 342 | h->keys[x] = key; \ 343 | __ac_set_isboth_false(h->flags, x); \ 344 | ++h->size; \ 345 | *ret = 2; \ 346 | } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ 347 | return x; \ 348 | } \ 349 | SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ 350 | { \ 351 | if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ 352 | __ac_set_isdel_true(h->flags, x); \ 353 | --h->size; \ 354 | } \ 355 | } 356 | 357 | #define KHASH_DECLARE(name, khkey_t, khval_t) \ 358 | __KHASH_TYPE(name, khkey_t, khval_t) \ 359 | __KHASH_PROTOTYPES(name, khkey_t, khval_t) 360 | 361 | #define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ 362 | __KHASH_TYPE(name, khkey_t, khval_t) \ 363 | __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) 364 | 365 | #define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ 366 | KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) 367 | 368 | /* --- BEGIN OF HASH FUNCTIONS --- */ 369 | 370 | /*! @function 371 | @abstract Integer hash function 372 | @param key The integer [khint32_t] 373 | @return The hash value [khint_t] 374 | */ 375 | #define kh_int_hash_func(key) (khint32_t)(key) 376 | /*! @function 377 | @abstract Integer comparison function 378 | */ 379 | #define kh_int_hash_equal(a, b) ((a) == (b)) 380 | /*! @function 381 | @abstract 64-bit integer hash function 382 | @param key The integer [khint64_t] 383 | @return The hash value [khint_t] 384 | */ 385 | #define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) 386 | /*! @function 387 | @abstract 64-bit integer comparison function 388 | */ 389 | #define kh_int64_hash_equal(a, b) ((a) == (b)) 390 | /*! @function 391 | @abstract const char* hash function 392 | @param s Pointer to a null terminated string 393 | @return The hash value 394 | */ 395 | static kh_inline khint_t __ac_X31_hash_string(const char *s) 396 | { 397 | khint_t h = (khint_t)*s; 398 | if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; 399 | return h; 400 | } 401 | /*! @function 402 | @abstract Another interface to const char* hash function 403 | @param key Pointer to a null terminated string [const char*] 404 | @return The hash value [khint_t] 405 | */ 406 | #define kh_str_hash_func(key) __ac_X31_hash_string(key) 407 | /*! @function 408 | @abstract Const char* comparison function 409 | */ 410 | #define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) 411 | 412 | static kh_inline khint_t __ac_Wang_hash(khint_t key) 413 | { 414 | key += ~(key << 15); 415 | key ^= (key >> 10); 416 | key += (key << 3); 417 | key ^= (key >> 6); 418 | key += ~(key << 11); 419 | key ^= (key >> 16); 420 | return key; 421 | } 422 | #define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key) 423 | 424 | /* --- END OF HASH FUNCTIONS --- */ 425 | 426 | /* Other convenient macros... */ 427 | 428 | /*! 429 | @abstract Type of the hash table. 430 | @param name Name of the hash table [symbol] 431 | */ 432 | #define khash_t(name) kh_##name##_t 433 | 434 | /*! @function 435 | @abstract Initiate a hash table. 436 | @param name Name of the hash table [symbol] 437 | @return Pointer to the hash table [khash_t(name)*] 438 | */ 439 | #define kh_init(name) kh_init_##name() 440 | 441 | /*! @function 442 | @abstract Destroy a hash table. 443 | @param name Name of the hash table [symbol] 444 | @param h Pointer to the hash table [khash_t(name)*] 445 | */ 446 | #define kh_destroy(name, h) kh_destroy_##name(h) 447 | 448 | /*! @function 449 | @abstract Reset a hash table without deallocating memory. 450 | @param name Name of the hash table [symbol] 451 | @param h Pointer to the hash table [khash_t(name)*] 452 | */ 453 | #define kh_clear(name, h) kh_clear_##name(h) 454 | 455 | /*! @function 456 | @abstract Resize a hash table. 457 | @param name Name of the hash table [symbol] 458 | @param h Pointer to the hash table [khash_t(name)*] 459 | @param s New size [khint_t] 460 | */ 461 | #define kh_resize(name, h, s) kh_resize_##name(h, s) 462 | 463 | /*! @function 464 | @abstract Insert a key to the hash table. 465 | @param name Name of the hash table [symbol] 466 | @param h Pointer to the hash table [khash_t(name)*] 467 | @param k Key [type of keys] 468 | @param r Extra return code: -1 if the operation failed; 469 | 0 if the key is present in the hash table; 470 | 1 if the bucket is empty (never used); 2 if the element in 471 | the bucket has been deleted [int*] 472 | @return Iterator to the inserted element [khint_t] 473 | */ 474 | #define kh_put(name, h, k, r) kh_put_##name(h, k, r) 475 | 476 | /*! @function 477 | @abstract Retrieve a key from the hash table. 478 | @param name Name of the hash table [symbol] 479 | @param h Pointer to the hash table [khash_t(name)*] 480 | @param k Key [type of keys] 481 | @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] 482 | */ 483 | #define kh_get(name, h, k) kh_get_##name(h, k) 484 | 485 | /*! @function 486 | @abstract Remove a key from the hash table. 487 | @param name Name of the hash table [symbol] 488 | @param h Pointer to the hash table [khash_t(name)*] 489 | @param k Iterator to the element to be deleted [khint_t] 490 | */ 491 | #define kh_del(name, h, k) kh_del_##name(h, k) 492 | 493 | /*! @function 494 | @abstract Test whether a bucket contains data. 495 | @param h Pointer to the hash table [khash_t(name)*] 496 | @param x Iterator to the bucket [khint_t] 497 | @return 1 if containing data; 0 otherwise [int] 498 | */ 499 | #define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) 500 | 501 | /*! @function 502 | @abstract Get key given an iterator 503 | @param h Pointer to the hash table [khash_t(name)*] 504 | @param x Iterator to the bucket [khint_t] 505 | @return Key [type of keys] 506 | */ 507 | #define kh_key(h, x) ((h)->keys[x]) 508 | 509 | /*! @function 510 | @abstract Get value given an iterator 511 | @param h Pointer to the hash table [khash_t(name)*] 512 | @param x Iterator to the bucket [khint_t] 513 | @return Value [type of values] 514 | @discussion For hash sets, calling this results in segfault. 515 | */ 516 | #define kh_val(h, x) ((h)->vals[x]) 517 | 518 | /*! @function 519 | @abstract Alias of kh_val() 520 | */ 521 | #define kh_value(h, x) ((h)->vals[x]) 522 | 523 | /*! @function 524 | @abstract Get the start iterator 525 | @param h Pointer to the hash table [khash_t(name)*] 526 | @return The start iterator [khint_t] 527 | */ 528 | #define kh_begin(h) (khint_t)(0) 529 | 530 | /*! @function 531 | @abstract Get the end iterator 532 | @param h Pointer to the hash table [khash_t(name)*] 533 | @return The end iterator [khint_t] 534 | */ 535 | #define kh_end(h) ((h)->n_buckets) 536 | 537 | /*! @function 538 | @abstract Get the number of elements in the hash table 539 | @param h Pointer to the hash table [khash_t(name)*] 540 | @return Number of elements in the hash table [khint_t] 541 | */ 542 | #define kh_size(h) ((h)->size) 543 | 544 | /*! @function 545 | @abstract Get the number of buckets in the hash table 546 | @param h Pointer to the hash table [khash_t(name)*] 547 | @return Number of buckets in the hash table [khint_t] 548 | */ 549 | #define kh_n_buckets(h) ((h)->n_buckets) 550 | 551 | /*! @function 552 | @abstract Iterate over the entries in the hash table 553 | @param h Pointer to the hash table [khash_t(name)*] 554 | @param kvar Variable to which key will be assigned 555 | @param vvar Variable to which value will be assigned 556 | @param code Block of code to execute 557 | */ 558 | #define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ 559 | for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ 560 | if (!kh_exist(h,__i)) continue; \ 561 | (kvar) = kh_key(h,__i); \ 562 | (vvar) = kh_val(h,__i); \ 563 | code; \ 564 | } } 565 | 566 | /*! @function 567 | @abstract Iterate over the values in the hash table 568 | @param h Pointer to the hash table [khash_t(name)*] 569 | @param vvar Variable to which value will be assigned 570 | @param code Block of code to execute 571 | */ 572 | #define kh_foreach_value(h, vvar, code) { khint_t __i; \ 573 | for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ 574 | if (!kh_exist(h,__i)) continue; \ 575 | (vvar) = kh_val(h,__i); \ 576 | code; \ 577 | } } 578 | 579 | /* More convenient interfaces */ 580 | 581 | /*! @function 582 | @abstract Instantiate a hash set containing integer keys 583 | @param name Name of the hash table [symbol] 584 | */ 585 | #define KHASH_SET_INIT_INT(name) \ 586 | KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) 587 | 588 | /*! @function 589 | @abstract Instantiate a hash map containing integer keys 590 | @param name Name of the hash table [symbol] 591 | @param khval_t Type of values [type] 592 | */ 593 | #define KHASH_MAP_INIT_INT(name, khval_t) \ 594 | KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) 595 | 596 | /*! @function 597 | @abstract Instantiate a hash set containing 64-bit integer keys 598 | @param name Name of the hash table [symbol] 599 | */ 600 | #define KHASH_SET_INIT_INT64(name) \ 601 | KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) 602 | 603 | /*! @function 604 | @abstract Instantiate a hash map containing 64-bit integer keys 605 | @param name Name of the hash table [symbol] 606 | @param khval_t Type of values [type] 607 | */ 608 | #define KHASH_MAP_INIT_INT64(name, khval_t) \ 609 | KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) 610 | 611 | typedef const char *kh_cstr_t; 612 | /*! @function 613 | @abstract Instantiate a hash map containing const char* keys 614 | @param name Name of the hash table [symbol] 615 | */ 616 | #define KHASH_SET_INIT_STR(name) \ 617 | KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) 618 | 619 | /*! @function 620 | @abstract Instantiate a hash map containing const char* keys 621 | @param name Name of the hash table [symbol] 622 | @param khval_t Type of values [type] 623 | */ 624 | #define KHASH_MAP_INIT_STR(name, khval_t) \ 625 | KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) 626 | 627 | #endif /* __AC_KHASH_H */ 628 | -------------------------------------------------------------------------------- /utils/ltime.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #define _POSIX_C_SOURCE 200112L 26 | #define _XOPEN_SOURCE 500 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | /* 47 | * This is LTime. 48 | * 49 | * LTime (Load Time) is another preloader auxiliary tool that aims to 50 | * obtain the times of a given program with and without the preloader. 51 | * LTime supports multiple files at once, as well as multiple folders, 52 | * for recursive analysis. 53 | * 54 | * The main idea is that ltime can be used to investigate potential 55 | * programs that can be accelerated with preloader. LTime is fast 56 | * enough to run system wide (~5k ELF executables) in about 30 minutes. 57 | * 58 | * Usage: 59 | * ./ltime .... 60 | * Like: 61 | * ./ltime clang ffprobe 62 | * ./ltime /usr/bin/clang 63 | * ./ltime /usr/bin /bin /home bin1 bin2 /path/bin1 64 | * 65 | * Output: 66 | * $ ./ltime foo 67 | * "foo", 21.916714 ms, 1.291591 ms 68 | * 69 | * First column: file analyzed 70 | * Second column: time without preloader (normal run) 71 | * Third column: time with preloader 72 | * 73 | * Real-world scenario: Find the top-5 ELF files with the longer 74 | * load times without preloader: 75 | * 76 | * 1) Get everything first: 77 | * $ ./ltime / | tee allfiles.txt 78 | * 79 | * 2) Sort by second column in descending order 80 | * $ sort -t',' -rg -k2 allfiles.txt | head -n5 | column -ts',' 81 | * "/usr/bin/mplayer" 52.136383 ms 1.689277 ms 82 | * "/usr/bin/ffprobe" 49.085384 ms 1.490358 ms 83 | * "/usr/lib64/qt5/libexec/QtWebEngineProcess" 45.864020 ms 1.565409 ms 84 | * "/usr/bin/SDLvncviewer" 41.423378 ms 1.487050 ms 85 | * "/usr/bin/ffmpeg" 37.896008 ms 1.410382 ms 86 | * 87 | * Notes: 88 | * 1) The search do not cross mount-points, so each desired mount need to be 89 | * specified one by one. If your /home belongs to the root partition (/), there 90 | * is no need to do that, but only: ./finder /. 91 | * 92 | * 2) Finder deps: libelf. 93 | * 94 | * 3) Please note that the times are relative to the load time of the 95 | * dynamic libraries, not the (complete) execution of the program. 96 | * 97 | * Preloader only becomes useful if the load time consumes a good 98 | * portion of the total execution time, examples like: 99 | * 100 | * $ time foo # doing some useful computation 101 | * real 0m0.051s 102 | * user 0m0.040s 103 | * sys 0m0.011s 104 | * 105 | * $ ./ltime foo 106 | * "foo", 38.517846 ms, 1.702319 ms 107 | * 108 | * It can be seen that 51ms was spent for a useful computation, where 109 | * 38ms is spent on load time... great opportunity for preloading. 110 | * 111 | * Implementation notes: 112 | * How it works is very simple: for each file to be analyzed, ltime 113 | * temporarily copies the binary to /tmp (or TMPDIR), and adds three 114 | * instructions to the program's entry point that basically just 115 | * terminate the program's execution. 116 | * 117 | * (Since the dynamic loader has already loaded all libraries at 118 | * this point, it is safe to terminate the program as soon as it 119 | * reaches the entrypoint. This is *not true* for Musl, and 120 | * libraries are loaded _after_ the entrypoint, so Musl is not 121 | * supported). 122 | * 123 | * Once this is done, the times are obtained with the execution 124 | * with and without the preloader. 125 | * 126 | * Links: 127 | * [0]: https://github.com/Theldus/preloader 128 | */ 129 | 130 | #define VERBOSE 1 131 | 132 | /* Fancy macros. */ 133 | #if VERBOSE == 1 134 | #define errxit(...) \ 135 | do { \ 136 | fprintf(stderr, __VA_ARGS__); \ 137 | exit(EXIT_FAILURE); \ 138 | } while (0) 139 | 140 | #define errto(lbl, ...) \ 141 | do { \ 142 | fprintf(stderr, __VA_ARGS__); \ 143 | goto lbl; \ 144 | } while (0) 145 | #else 146 | #define errxit(...) exit(EXIT_FAILURE) 147 | #define errto(lbl, ...) goto lbl 148 | #endif 149 | 150 | /* ELF magic. */ 151 | static const unsigned char elf_magic[4] = {0x7f,0x45,0x4c,0x46}; 152 | 153 | /* Patches. */ 154 | 155 | /* mov $60, %rax # NR_exit. 156 | * mov $0, %rdi 157 | * syscall */ 158 | static const unsigned char patch_amd64[] = { 159 | 0x48,0xc7,0xc0,0x3c,0x00,0x00,0x00,0x48, 160 | 0xc7,0xc7,0x00,0x00,0x00,0x00,0x0f,0x05 161 | }; 162 | 163 | /* mov $0x1, %eax # NR_exit. 164 | * xor %ebx, %ebx 165 | * int $0x80 */ 166 | static const unsigned char patch_i386[] = { 167 | 0xb8,0x01,0x00,0x00,0x00,0x31,0xdb,0xcd,0x80 168 | }; 169 | 170 | /* mov x8, #93 # NR_exit. 171 | * mov x0, #0 172 | * svc 0 */ 173 | static const unsigned char patch_aarch64[] = { 174 | 0x00,0x00,0x80,0xd2,0xa8,0x0b,0x80,0xd2, 175 | 0x01,0x00,0x00,0xd4 176 | }; 177 | 178 | /* mov r7, #1 # NR_exit. 179 | * mov r0, #0 180 | * swi 0 */ 181 | static const unsigned char patch_arm[] = { 182 | 0x01,0x70,0xa0,0xe3,0x00,0x00,0xa0,0xe3, 183 | 0x00,0x00,0x00,0xef 184 | }; 185 | 186 | /* Some data about the ELF file. */ 187 | static Elf *elf; 188 | static int nruns; 189 | static int fd_elf; 190 | static regex_t regex; 191 | static char target_file[PATH_MAX]; 192 | 193 | /** 194 | * @brief Given a file, open the ELF file and initialize 195 | * its data structure. 196 | * 197 | * @param file Path of the file to be opened, if any. 198 | * 199 | * @return Returns 0 if success, -1 otherwise. 200 | */ 201 | static int open_elf(const char *file) 202 | { 203 | Elf_Kind ek; 204 | 205 | if ((fd_elf = open(file, O_RDWR, 0)) < 0) 206 | errto(out1, "Unable to open %s!\n", file); 207 | 208 | if ((elf = elf_begin(fd_elf, ELF_C_READ, NULL)) == NULL) 209 | errto(out2, "elf_begin() failed!\n"); 210 | 211 | ek = elf_kind(elf); 212 | if (ek != ELF_K_ELF) 213 | errto(out2, "File \"%s\" is not an ELF file!\n", file); 214 | 215 | return (0); 216 | out2: 217 | close(fd_elf); 218 | out1: 219 | elf_end(elf); 220 | return (-1); 221 | } 222 | 223 | /** 224 | * @brief Given an opened ELF file, closes all the 225 | * resources. 226 | * 227 | * @param elf Opened ELF file. 228 | */ 229 | static void close_elf(void) 230 | { 231 | if (elf) 232 | { 233 | elf_end(elf); 234 | elf = NULL; 235 | } 236 | if (fd_elf > 0) 237 | { 238 | close(fd_elf); 239 | fd_elf = -1; 240 | } 241 | } 242 | 243 | /** 244 | * @brief For a given @p file, get its entry point, machine type, 245 | * and check if it is dynamic or not. 246 | * 247 | * @param file File to be checked. 248 | * @param machine Returned machine type. 249 | * @param is_dyn Returns 1 if dynamic, 0 if not. 250 | * 251 | * @return Returns the file offset if success, -1 if error. 252 | */ 253 | static off_t get_entry_offset(char *file, int *machine, int *is_dyn) 254 | { 255 | GElf_Ehdr ehdr; 256 | GElf_Addr entry; 257 | GElf_Phdr phdr; 258 | size_t i, num; 259 | off_t foff; 260 | 261 | foff = -1; 262 | 263 | if (open_elf(file) < 0) 264 | goto out0; 265 | 266 | gelf_getehdr(elf, &ehdr); 267 | 268 | /* Get entry point. */ 269 | entry = ehdr.e_entry; 270 | *machine = ehdr.e_machine; 271 | 272 | /* Iterate over the program headers to find the section 273 | * containing the address of the entry point. */ 274 | if (elf_getphdrnum(elf, &num) < 0) 275 | goto out0; 276 | 277 | /* Get entry offset. */ 278 | for (i = 0; i < num; i++) 279 | { 280 | gelf_getphdr(elf, i, &phdr); 281 | if (phdr.p_type != PT_LOAD) 282 | continue; 283 | 284 | if (entry >= phdr.p_vaddr && 285 | entry < phdr.p_vaddr + phdr.p_memsz) 286 | { 287 | foff = entry - phdr.p_vaddr + phdr.p_offset; 288 | break; 289 | } 290 | } 291 | 292 | /* 293 | * Check if its dynamic 294 | * 295 | * Note: Ideally we should also check for static-pie 296 | * executables, but I'm too lazy to do that... 297 | * homework for who are reading this =). 298 | */ 299 | for (*is_dyn = 0, i = 0; i < num; i++) 300 | { 301 | gelf_getphdr(elf, i, &phdr); 302 | if (phdr.p_type == PT_DYNAMIC) 303 | { 304 | *is_dyn = 1; 305 | break; 306 | } 307 | } 308 | 309 | /* Return error if binary is not dynamic. */ 310 | if (!*is_dyn) 311 | return (-1); 312 | 313 | out0: 314 | return (foff); 315 | } 316 | 317 | /** 318 | * @brief Patch the current file (target_file) accordingly 319 | * to the architecture supported. 320 | * 321 | * @param off File offset to apply patch. 322 | * @param machine Machine type. 323 | * 324 | * @return Returns 0 if success, -1 otherwise. 325 | */ 326 | static int patch_file(off_t off, int machine) 327 | { 328 | const unsigned char *patch; 329 | size_t sp; 330 | 331 | switch (machine) 332 | { 333 | case EM_X86_64: 334 | patch = patch_amd64; 335 | sp = sizeof(patch_amd64); 336 | break; 337 | case EM_386: 338 | patch = patch_i386; 339 | sp = sizeof(patch_i386); 340 | break; 341 | case EM_AARCH64: 342 | patch = patch_aarch64; 343 | sp = sizeof(patch_aarch64); 344 | break; 345 | case EM_ARM: 346 | patch = patch_arm; 347 | sp = sizeof(patch_arm); 348 | break; 349 | default: 350 | fprintf(stderr, "Error, architecture not identified!\n"); 351 | return (-1); 352 | } 353 | 354 | if (lseek(fd_elf, off, SEEK_SET) < 0) 355 | return (-1); 356 | 357 | if (write(fd_elf, patch, sp) != (ssize_t)sp) 358 | return (-1); 359 | 360 | return (0); 361 | } 362 | 363 | /** 364 | * @brief Given a file, copy it to /tmp (or TMPDIR, 365 | * if it exists). 366 | * 367 | * @param file File to be copied. 368 | * 369 | * @return Returns 0 if success, -1 otherwise. 370 | */ 371 | static ssize_t copy_to_tmp(const char *file) 372 | { 373 | char *t, *base, *tmp; 374 | struct stat st = {0}; 375 | int fdi, fdo; 376 | ssize_t ret; 377 | 378 | ret = -1; 379 | 380 | if ((t = getenv("TMPDIR")) == NULL) 381 | t = "/tmp"; 382 | 383 | if ((fdi = open(file, O_RDONLY)) < 0) 384 | return (-1); 385 | 386 | tmp = strdup(file); 387 | base = basename(tmp); 388 | snprintf(target_file, sizeof target_file - 1, "%s/%s", t, base); 389 | free(tmp); 390 | 391 | if ((fdo = creat(target_file, 0755)) < 0) 392 | goto out0; 393 | 394 | fstat(fdi, &st); 395 | ret = sendfile(fdo, fdi, NULL, st.st_size); 396 | 397 | if (ret != st.st_size) 398 | goto out0; 399 | 400 | ret = 0; 401 | out0: 402 | close(fdo); 403 | close(fdi); 404 | return (ret); 405 | } 406 | 407 | /** 408 | * @brief Get the file path and mode for the specified 409 | * file @p file, depending whether the file exists in 410 | * the informed path or in the PATH. 411 | * 412 | * @param file File to get the retrieved path. 413 | * @param mode File mode retrieved. 414 | * 415 | * @return If successful, returns the file path; 416 | * otherwise, returns NULL. 417 | */ 418 | static char *get_file_path(char *file, mode_t *mode) 419 | { 420 | char *t, *path, *cpath, tfile[PATH_MAX - 1] = {0}; 421 | struct stat st; 422 | char *tokptr; 423 | 424 | if (!stat(file, &st)) 425 | { 426 | *mode = st.st_mode; 427 | return (file); 428 | } 429 | 430 | /* Check against PATH if the file is there. */ 431 | path = t = strdup(getenv("PATH")); 432 | tokptr = NULL; 433 | 434 | for (cpath = strtok_r(path, ":", &tokptr); cpath != NULL; 435 | cpath = strtok_r(NULL, ":", &tokptr)) 436 | { 437 | snprintf(tfile, PATH_MAX - 1, "%s/%s", cpath, file); 438 | 439 | if (!stat(tfile, &st)) 440 | { 441 | *mode = st.st_mode; 442 | strncpy(target_file, tfile, sizeof target_file - 1); 443 | free(t); 444 | return (target_file); 445 | } 446 | } 447 | 448 | free(t); 449 | return (NULL); 450 | } 451 | 452 | /** 453 | * @brief For a given file path, check if the given 454 | * file is an ELF file or not. 455 | * 456 | * @param path File to be checked. 457 | * 458 | * @return Returns 0 if ELF, -1 otherwise. 459 | */ 460 | static int is_elf(const char *path) 461 | { 462 | unsigned char buff[4] = {0}; 463 | int ret; 464 | int fd; 465 | 466 | ret = -1; 467 | 468 | fd = open(path, O_RDONLY); 469 | if (fd < 0) 470 | return (ret); 471 | 472 | if (read(fd, buff, sizeof buff) != sizeof buff) 473 | goto out; 474 | 475 | /* Check signature. */ 476 | if (memcmp(buff, elf_magic, sizeof elf_magic)) 477 | goto out; 478 | 479 | ret = 0; 480 | out: 481 | close(fd); 482 | return (ret); 483 | } 484 | 485 | /** 486 | * @brief Starts a preloader daemon for the contents 487 | * of the variable 'target_file'. 488 | * 489 | * @return Returns 0 if success, -1 otherwise. 490 | */ 491 | static int start_daemon(void) 492 | { 493 | struct stat st; 494 | int wstatus; 495 | char *path; 496 | pid_t pid; 497 | 498 | path = realpath("../libpreloader.so", NULL); 499 | if (!path || stat(path, &st) < 0) 500 | { 501 | free(path); 502 | path = realpath("libpreloader.so", NULL); 503 | if (!path || stat(path, &st) < 0) 504 | { 505 | free(path); 506 | path = "/usr/local/lib/libpreloader.so"; 507 | if (stat(path, &st) < 0) 508 | return (-1); 509 | } 510 | } 511 | 512 | if ((pid = fork()) == 0) 513 | { 514 | putenv("LD_BIND_NOW=1"); 515 | putenv("PRELOADER_DAEMONIZE=1"); 516 | setenv("LD_PRELOAD", path, 1); 517 | execlp(target_file, target_file, NULL); 518 | exit(1); 519 | } 520 | free(path); 521 | 522 | waitpid(pid, &wstatus, 0); 523 | 524 | if (WIFEXITED(wstatus)) 525 | { 526 | if (WEXITSTATUS(wstatus)) 527 | return (-1); 528 | } 529 | else 530 | return (-1); 531 | 532 | return (0); 533 | } 534 | 535 | /** 536 | * @brief Stops a running preloader daemon. 537 | * 538 | * @return Returns 0 if successfully stopped, -1 otherwise. 539 | */ 540 | static int stop_daemon(void) 541 | { 542 | char *tmp, pid_file[PATH_MAX]; 543 | char buff[16] = {0}; 544 | pid_t pid; 545 | ssize_t r; 546 | int ret; 547 | int fd; 548 | int i; 549 | 550 | ret = -1; 551 | 552 | if (!(tmp = getenv("TMPDIR"))) 553 | tmp = "/tmp"; 554 | 555 | if (strlen(tmp) + sizeof "/preloader_3636.pid" > sizeof pid_file) 556 | errto(out0, "pid_file exceeds path capacity!\n"); 557 | 558 | snprintf(pid_file, sizeof pid_file - 1, "%s/preloader_3636.pid", tmp); 559 | 560 | if ((fd = open(pid_file, O_RDONLY)) < 0) 561 | errto(out0, "PID file (%s) not found!\n", pid_file); 562 | 563 | if ((r = read(fd, buff, sizeof buff)) < 0) 564 | errto(out1, "Unable to read complete file!\n"); 565 | 566 | for (pid = 0, i = 0; i < r; i++) 567 | { 568 | if (buff[i] < '0' || buff[i] > '9') 569 | errto(out1, "Malformed pid file!\n"); 570 | else 571 | { 572 | pid *= 10; 573 | pid += (buff[i] - '0'); 574 | } 575 | } 576 | 577 | /* Try to kill the process. */ 578 | if (kill(pid, SIGTERM) < 0) 579 | errto(out1, "Unable to kill daemon, maybe its not running?\n"); 580 | 581 | ret = 0; 582 | out1: 583 | close(fd); 584 | unlink(pid_file); 585 | out0: 586 | return (ret); 587 | } 588 | 589 | /** 590 | * @brief Starts a new process a measure its execution time. 591 | * 592 | * @param preload Should run prealoder_cli? 593 | * 594 | * @return If success, returns the elapsed time 595 | * (in milliseconds), otherwise, returns -1. 596 | */ 597 | static double spawn_child_ms(int preload) 598 | { 599 | struct timespec ts1, ts2; 600 | char *proc, *arg1; 601 | struct stat st; 602 | int wstatus; 603 | pid_t pid; 604 | double ms; 605 | 606 | proc = target_file; 607 | arg1 = target_file; 608 | if (preload) 609 | { 610 | arg1 = target_file; 611 | proc = "../preloader_cli"; 612 | if (stat(proc, &st) < 0) 613 | { 614 | proc = "./preloader_cli"; 615 | if (stat(proc, &st) < 0) 616 | { 617 | proc = "/usr/local/bin/preloader_cli"; 618 | if (stat(proc, &st) < 0) 619 | return (-1); 620 | } 621 | } 622 | } 623 | 624 | clock_gettime(CLOCK_MONOTONIC, &ts1); 625 | if ((pid = fork()) == 0) 626 | { 627 | execlp(proc, proc, arg1, NULL); 628 | exit(1); 629 | } 630 | waitpid(pid, &wstatus, 0); 631 | clock_gettime(CLOCK_MONOTONIC, &ts2); 632 | 633 | ms = ((ts2.tv_sec - ts1.tv_sec)*1000) + 634 | ((double)(ts2.tv_nsec - ts1.tv_nsec)/1000000); 635 | 636 | /* If abnormal exit (!= 0) or got signaled. 637 | * return a negative time, to indicate failure. */ 638 | if (WIFEXITED(wstatus)) 639 | { 640 | if (WEXITSTATUS(wstatus)) 641 | ms = -1; 642 | } 643 | else 644 | ms = -1; 645 | 646 | return (ms); 647 | } 648 | 649 | /** 650 | * @brief Benchmark a given file @p tfile both normally 651 | * and with preloader. 652 | * 653 | * The measured time is sent to stdout. 654 | * 655 | * @param orig_file Original parameter as-is. 656 | * @param tfile Processed file (considering PATH). 657 | * 658 | * @return Returns 0 if success, -1 otherwise. 659 | */ 660 | static int handle_file(const char *orig_file, const char *tfile) 661 | { 662 | double ms_normal, ms_pre, ms_tmp; 663 | int machine; 664 | off_t foff; 665 | int is_dyn; 666 | int ret; 667 | int i; 668 | 669 | ret = -1; 670 | 671 | /* Check if ELF. */ 672 | if (is_elf(tfile) < 0) 673 | errto(out0, "%s its not an ELF file!\n", tfile); 674 | 675 | /* Copy our target file to TMP. */ 676 | if (copy_to_tmp(tfile) < 0) 677 | errto(out0, "Unable to copy file: %s...\n", tfile); 678 | 679 | /* Get file entry offset and machine type. */ 680 | if ((foff = get_entry_offset(target_file, &machine, &is_dyn)) < 0) 681 | errto(out1, "Unable to get file offset or binary is static!\n"); 682 | 683 | /* Patch file for the appropriate architecture. */ 684 | if (patch_file(foff, machine) < 0) 685 | errto(out1, "Unable to patch file!\n"); 686 | close_elf(); 687 | 688 | /* =============== TIMMINGS START =============== */ 689 | if (start_daemon() < 0) 690 | errto(out1, "Unable to start preloader daemon!\n"); 691 | usleep(250*1000); /* Wait for daemon start. */ 692 | 693 | /* Cache file first. */ 694 | spawn_child_ms(0); 695 | 696 | /* -- Normal run -- */ 697 | for (ms_normal = 0, i = 0; i < nruns; i++) 698 | { 699 | if ((ms_tmp = spawn_child_ms(0)) < 0) 700 | errto(out2, "Error while during normal run, idx: %d\n", i); 701 | 702 | ms_normal += ms_tmp; 703 | } 704 | ms_normal /= nruns; 705 | 706 | /* -- Preloader run -- */ 707 | for (ms_pre = 0, i = 0; i < nruns; i++) 708 | { 709 | if ((ms_tmp = spawn_child_ms(1)) < 0) 710 | errto(out2, "Error while during preloader run, idx: %d\n", i); 711 | 712 | ms_pre += ms_tmp; 713 | } 714 | ms_pre /= nruns; 715 | 716 | #if VERBOSE == 1 717 | printf("file: \"%s\", w/o: %f ms, w/ preloader: %f ms\n", 718 | orig_file, ms_normal, ms_pre); 719 | #else 720 | printf("\"%s\", %f ms, %f ms\n", orig_file, ms_normal, ms_pre); 721 | #endif 722 | 723 | ret = 0; 724 | out2: 725 | stop_daemon(); 726 | out1: 727 | unlink(target_file); 728 | close_elf(); 729 | out0: 730 | return (ret); 731 | } 732 | 733 | /** 734 | * @brief nftw() handler, called each time a new file 735 | * is discovered. 736 | * 737 | * @param filepath File path to be analyzed. 738 | * @param info File path stat structure. 739 | * @param typeflag File path type. 740 | * @param pathinfo File path additional infos. 741 | * 742 | * @return Returns 0 if success, -1 otherwise. 743 | */ 744 | static int do_check(const char *path, const struct stat *info, 745 | const int typeflag, struct FTW *pathinfo) 746 | { 747 | ((void)pathinfo); 748 | 749 | /* Ignore everything that is not a file or is empty. */ 750 | if (typeflag != FTW_F || !info->st_size) 751 | return (0); 752 | 753 | /* Skip non executables. */ 754 | if (!(info->st_mode & S_IXUSR)) 755 | return (0); 756 | 757 | /* Skip libs. */ 758 | if (!regexec(®ex, path, 0, NULL, 0)) 759 | return (0); 760 | 761 | /* Check if ELF and handle it. */ 762 | handle_file(path, path); 763 | return (0); 764 | } 765 | 766 | /* Usage. */ 767 | static void usage(const char *prg) 768 | { 769 | fprintf(stderr, 770 | "Usage: %s [-r ] [...]\n" 771 | "Options: \n" 772 | " -r How many times to run to get the average\n", 773 | prg); 774 | exit(EXIT_FAILURE); 775 | } 776 | 777 | /** 778 | * Safe string-to-int routine that takes into account: 779 | * - Overflow and Underflow 780 | * - No undefined behavior 781 | * 782 | * Taken from https://stackoverflow.com/a/12923949/3594716 783 | * and slightly adapted: no error classification, because 784 | * I don't need to know, error is error. 785 | * 786 | * @param out Pointer to integer. 787 | * @param s String to be converted. 788 | * 789 | * @return Returns 0 if success and a negative number otherwise. 790 | */ 791 | static int str2int(int *out, const char *s) 792 | { 793 | char *end; 794 | if (s[0] == '\0' || isspace(s[0])) 795 | return (-1); 796 | errno = 0; 797 | 798 | long l = strtol(s, &end, 10); 799 | 800 | /* Both checks are needed because INT_MAX == LONG_MAX is possible. */ 801 | if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) 802 | return (-1); 803 | if (l < INT_MIN || (errno == ERANGE && l == LONG_MIN)) 804 | return (-1); 805 | if (*end != '\0') 806 | return (-1); 807 | 808 | *out = l; 809 | return (0); 810 | } 811 | 812 | /** 813 | * Handle command-line arguments accordingly 814 | * with their position. 815 | */ 816 | static int handle_args(int argc, char **argv) 817 | { 818 | int idx_files; 819 | 820 | idx_files = 1; 821 | nruns = 1; 822 | 823 | /* Valid num args. */ 824 | if (argc < 2) 825 | errto(err0, "At least is required!\n"); 826 | 827 | /* Get number of runs. */ 828 | if (!strcmp(argv[1], "-r")) 829 | { 830 | if (argc < 4) 831 | usage(argv[0]); 832 | 833 | if (str2int(&nruns, argv[2]) < 0) 834 | errto(err0, "Parameter '%s' is not a valid number!\n", 835 | argv[2]); 836 | 837 | if (!nruns) 838 | errto(err0, "Parameter '%s' must be greater than 0!\n", 839 | argv[2]); 840 | 841 | idx_files = 3; 842 | } 843 | 844 | return (idx_files); 845 | err0: 846 | usage(argv[0]); 847 | return (0); 848 | } 849 | 850 | /* Main. */ 851 | int main(int argc, char **argv) 852 | { 853 | int i; 854 | int res; 855 | mode_t mode; 856 | int idx_files; 857 | char *file_path; 858 | 859 | /* Init libelf. */ 860 | if (elf_version(EV_CURRENT) == EV_NONE) 861 | errxit("Unable to initialize libelf\n"); 862 | 863 | /* Init our regex to skip libs. */ 864 | if (regcomp(®ex, ".+\\.so(\\.[0-9]+)*$", REG_EXTENDED) != 0) 865 | errto(out0, "Failed to to compile regex!\n"); 866 | 867 | idx_files = handle_args(argc, argv); 868 | 869 | for (i = idx_files; i < argc; i++) 870 | { 871 | file_path = get_file_path(argv[i], &mode); 872 | if (!file_path) 873 | errto(out0, "ELF file (%s) not found!\n", argv[i]); 874 | 875 | if (S_ISREG(mode)) 876 | handle_file(argv[i], file_path); 877 | else if (S_ISDIR(mode)) 878 | { 879 | res = nftw(file_path, do_check, 10, FTW_PHYS|FTW_MOUNT); 880 | if (res) 881 | errto(out0, "NFTW error, path: %s\n", file_path); 882 | } 883 | else 884 | errto(out0, "Parametr (%s) is not a regular file!\n", file_path); 885 | } 886 | out0: 887 | regfree(®ex); 888 | return (0); 889 | } 890 | --------------------------------------------------------------------------------