summaryrefslogtreecommitdiff
path: root/loader/loader.c
diff options
context:
space:
mode:
authorA. Wilcox <awilcox@wilcox-tech.com>2018-02-01 22:05:36 +0000
committerA. Wilcox <awilcox@wilcox-tech.com>2018-02-01 22:05:36 +0000
commit88815225fdabe7fa862f1c07abfcc971fdf5b04e (patch)
tree7aaae7c04fcf06aea85505fc028f476a131b9c50 /loader/loader.c
parentc545f7393f0a10c182c2c085a36d7099b9c07a7f (diff)
parent78722ef403ffadcb78917e93e1859d43b9bf9df5 (diff)
downloadgcompat-88815225fdabe7fa862f1c07abfcc971fdf5b04e.tar.gz
gcompat-88815225fdabe7fa862f1c07abfcc971fdf5b04e.tar.bz2
gcompat-88815225fdabe7fa862f1c07abfcc971fdf5b04e.tar.xz
gcompat-88815225fdabe7fa862f1c07abfcc971fdf5b04e.zip
Merge branch 'patch-1' into 'master'
Clean up everything and add lots of new functions I hope everything here is okay. I fixed some bugs in existing functions (mostly wrong prototypes or off-by-one errors) and formatted everything to a consistent style. If you'd like me to adjust the style, that's no problem. It wasn't very consistent to start with (within the code, and compared to the documentation). *I added specific notes you may want to comment on to some of the commit messages.* Major features: * Pass correct `argv[0]` in loader * Intercept `readlink("/proc/self/exe")` to allow re-exec * Add almost all reasonable-to-implement functions in LSB 5.0.0 core generic libc. Remaining functions are: - Impossible-to-implement: sigreturn - Not useful: reentrant random (`*rand48_r`, etc.), argz, envz, pmap, rpc (clnt_*, svc*, xdr*), bindresvport - Got tired of it: checked wchar * Add additional functions used by android and its NDK tools (clang, cmake, lldb, ninja, etc.). At this point, I am able to run Android Studio with the bundled prebuilt JDK, and compile, install, and run an android application (including one with native libraries) on a real device, with only a few minor issues: * must export `LD_LIBRARY_PATH=/opt/android-studio/jre/jre/lib/amd64/server` because musl and glibc interpret the variable differently with regards to `dlopen`. This is something that has to be patched in musl builds of openjdk, so it's not a gcompat issue. * ld.bfd fails to parse the argument `--sysroot=/path`, but can parse `--sysroot /path`. So there's some difference with `getopt_long_only` (or getopt in general). May be a bug, may be just an API difference. May be it can be patched up. * LLDB fails to connect to the android phone for native debugging -- I haven't tried it on glibc yet, so it may not be a gcompat issue at all (may be a phone or the app issue). I'd be happy to send some documentation later. See merge request !1
Diffstat (limited to 'loader/loader.c')
-rw-r--r--loader/loader.c98
1 files changed, 78 insertions, 20 deletions
diff --git a/loader/loader.c b/loader/loader.c
index f2942a4..53a2c38 100644
--- a/loader/loader.c
+++ b/loader/loader.c
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2017 William Pitcock <nenolod@dereferenced.org>
+ * Copyright (c) 2018 Samuel Holland <samuel@sholland.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -10,42 +11,99 @@
* from the use of this software.
*/
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <limits.h>
+#include <limits.h> /* PATH_MAX */
+#include <stddef.h> /* NULL */
+#include <stdio.h> /* fputs, fwrite, stderr */
+#include <stdlib.h> /* calloc, EXIT_FAILURE, getenv */
+#include <string.h> /* strcpy, strlcpy */
+#include <unistd.h> /* execve, ssize_t */
#ifndef PATH_MAX
-#define PATH_MAX 16384
+#define PATH_MAX 16384
+#endif
+
+#ifndef LIBGCOMPAT
+#error LIBGCOMPAT must be defined
#endif
#ifndef LINKER
#error LINKER must be defined
#endif
-#ifndef LIBGCOMPAT
-#error LIBGCOMPAT must be defined
+#ifndef LOADER
+#error LOADER must be defined
#endif
-int main(int argc, char *argv[], char *envp[]) {
- size_t i = 0;
+/*
+ * Given the argv { "foo", "bar", "baz" }, and the environment variable
+ * "LD_PRELOAD=/another/preload.so", the new argv will be {
+ * "ld-linux-$ARCH.so.N",
+ * "--argv0",
+ * "foo",
+ * "--preload",
+ * "/path/to/libgcompat.so /another/preload.so",
+ * "/path/to/foo",
+ * "bar",
+ * "baz"
+ * }.
+ *
+ * NOTE: The new argv needs 6 more entries than the original argc: five for
+ * arguments added at the beginning, and one for the NULL sentinel at the end
+ * not included in argc. Because calloc() is used, the sentinel is not
+ * explicitly set on its own, but *it is still there*!
+ *
+ * NOTE: The name given in argv[0] to musl *must* match glibc's ld.so filename.
+ * Many glibc-linked programs have an explicit dependency on its ld.so, but the
+ * file placed at that path (this loader) is not a real shared library. If musl
+ * tries to load this file as a shared library, it will fail. Thankfully, since
+ * musl treats argv[0] as "its" name when run directly, it considers a library
+ * by that name to already be loaded, and ignores the problematic dependency.
+ *
+ * NOTE: LD_PRELOAD entries are processed in order, and the environment variable
+ * is ignored if the parameter is given. In case the glibc-linked program needs
+ * another preloaded library, it should be appended to the argument so it gets
+ * loaded after libgcompat. Leave the environment variable as is, so if the
+ * program runs other glibc-linked programs, the same transformation gets
+ * applied there as well.
+ *
+ * NOTE: In order for the program to be able to re-exec itself it needs the
+ * fully-resolved path from /proc/self/exe ("target" below). This path is given
+ * to musl on the command line in new_argv[5], but it is not directly available
+ * to the program. An intercepted readlink("/proc/self/exe") must read it from
+ * /proc/self/cmdline. Thus, if its position in new_argv changes, that function
+ * must also be updated to match.
+ */
+int main(int argc, char *argv[], char *envp[])
+{
+ char **new_argv = calloc(argc + 6, sizeof(char *));
+ char preload[PATH_MAX];
char target[PATH_MAX];
- char **new_argv = calloc(sizeof(char *), argc + 5);
+ ssize_t i, j, len;
- memset(target, 0, sizeof(target));
- if (readlink("/proc/self/exe", target, sizeof(target)) < 0) {
+ strcpy(preload, LIBGCOMPAT " ");
+ if (getenv("LD_PRELOAD") != NULL) {
+ len = strlcat(preload, getenv("LD_PRELOAD"), sizeof(preload));
+ if ((size_t) len >= sizeof(preload)) {
+ fputs("too many preloaded libraries", stderr);
+ return EXIT_FAILURE;
+ }
+ }
+
+ len = readlink("/proc/self/exe", target, sizeof(target));
+ if (len < 0 || len == sizeof(target)) {
perror("readlink");
return EXIT_FAILURE;
}
+ target[len] = '\0';
- new_argv[0] = argv[0];
- new_argv[1] = "--preload";
- new_argv[2] = LIBGCOMPAT;
- new_argv[3] = target;
-
- for (i = 1; i < argc; i++) {
- new_argv[3 + i] = argv[i];
+ new_argv[0] = LOADER;
+ new_argv[1] = "--argv0";
+ new_argv[2] = argv[0];
+ new_argv[3] = "--preload";
+ new_argv[4] = preload;
+ new_argv[5] = target;
+ for (i = 6, j = 1; j < argc; ++i, ++j) {
+ new_argv[i] = argv[j];
}
execve(LINKER, new_argv, envp);