38

I'm looking for a way to load generated object code directly from memory.

I understand that if I write it to a file, I can call dlopen to dynamically load its symbols and link them. However, this seems a bit of a roundabout way, considering that it starts off in memory, is written to disk, and then is reloaded in memory by dlopen. I'm wondering if there is some way to dynamically link object code that exists in memory. From what I can tell there might be a few different ways to do this:

  1. Trick dlopen into thinking that your memory location is a file, even though it never leaves memory.

  2. Find some other system call which does what I'm looking for (I don't think this exists).

  3. Find some dynamic linking library which can link code directly in memory. Obviously, this one is a bit hard to google for, as "dynamic linking library" turns up information on how to dynamically link libraries, not on libraries which perform the task of dynamically linking.

  4. Abstract some API from a linker and create a new library out its codebase. (obviously this is the least desirable option for me).

So which ones of these are possible? feasible? Could you point me to any of the things I hypothesized existed? Is there another way I haven't even thought of?

Jeremy Salwen
  • 8,061
  • 5
  • 50
  • 73

9 Answers9

14

I needed a solution to this because I have a scriptable system that has no filesystem (using blobs from a database) and needs to load binary plugins to support some scripts. This is the solution I came up with which works on FreeBSD but may not be portable.

void *dlblob(const void *blob, size_t len) {
    /* Create shared-memory file descriptor */
    int fd = shm_open(SHM_ANON, O_RDWR, 0);
    ftruncate(fd, len);
    /* MemMap file descriptor, and load data */
    void *mem = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd, 0);
    memcpy(mem, blob, len);
    munmap(mem, len);
    /* Open Dynamic Library from SHM file descriptor */
    void *so = fdlopen(fd,RTLD_LAZY);
    close(fd);
    return so;
}

Obviously the code lacks any kind of error checking etc, but this is the core functionality.

ETA: My initial assumption that fdlopen is POSIX was wrong, this appears to be a FreeBSD-ism.

Parakleta
  • 1,121
  • 10
  • 19
  • People seem to get away with plain dlopen [here](https://github.com/stephenrkell/libdlbind/blob/master/src/libdlbind.c#L218). – yugr Feb 25 '17 at 09:54
  • 2
    @yugr your suggestion is exactly the trivial case that the questioner already dismissed. – Parakleta Feb 26 '17 at 12:04
  • Not quite, with `/run/shm` the file is never written to disk. – yugr Feb 27 '17 at 08:34
  • 2
    @yugr `/run/shm` is not POSIX, it's a Linux-ism, and without it the function falls back to just writing out to `/tmp`. Regardless of whether the file makes it to disk (`/tmp` could be a ramdisk on some systems) you still have to interact with the filesystem, have permissions to create it, control whether other people can access it, ensure that you unlink it properly when you're done (or crash). Why don't you post an answer with your proposal and let people comment and vote on it? – Parakleta Feb 27 '17 at 21:54
  • Well, I don't think this minor addition really deserves a separate answer. Agree for Linux-ism but OP didn't explicitly mention that he needs a POSIX-compliant solution. As for filesystem - again, good point but I think the OP cared more about actual disk access ("written to disk, and then is reloaded in memory by dlopen"). – yugr Feb 27 '17 at 22:03
  • @yugr My reference to it being non-POSIX and a Linux-ism is that you've commented on an answer that uses a BSD-ism for the solution with a proposal that uses a Linux-ism, so apart from the `/tmp` fallback there is zero intersection between these solutions. Both solutions have merit on their respective platforms, but I don't see how they are related. Essentially it seems the case is on BSD use `fdlopen` with no filesystem access required, then on Linux use `/run/shm` or on POSIX use `/tmp`, both requiring filesystem access. – Parakleta Feb 27 '17 at 23:25
  • Technically, this solution still writes to a filesystem (`/dev/shm` to be more precise). However, this might be the closest thing possible. One thing that this answers suffers from, if the program that called `dlblob` exits, the file will still be in `/dev/shm`. – DrownedSuccess Sep 29 '22 at 20:54
  • @DrownedSuccess do you actually have any evidence to support your claims? `/dev` isn't really a filesystem in the true sense, it doesn't have any storage, it is just a mapping of devices into the filesystem realm to make them manipulatable with standard tools. Additionally, `shm_open` doesn't instantiate anything at that path, whether I use SHM_ANON (which how would that even work) or named objects. Further, your claim that the object hangs around for the duration of the program is not supported by the `shm_open` man page which says it will be collected when the last reference is removed. – Parakleta Sep 29 '22 at 23:50
  • @DrownedSuccess On reflection, I suppose your comment regarding `/dev` has some relevance in the general context of the preceding discussion as it does have a permissions/ownership structure, but the fact remains that the `shm_open` call uses a kernel mechanism to generate a the storage space, and does not operate through the `/dev` mount, so these permissions/ownership actually have no bearing on this behaviour. – Parakleta Sep 30 '22 at 00:00
  • @Parakleta I just tried it, and it does in fact make files in `/dev/shm`. I'm on Linux, which may explain the difference. – DrownedSuccess Sep 30 '22 at 00:50
  • @DrownedSuccess Yeah, this is a FreeBSD solution, and without `fdlopen` it's not going to work at all (so not really sure what you're attempting here). Out of interest, what is the file named when you create it with `SHM_ANON`? – Parakleta Sep 30 '22 at 04:38
  • @Parakleta Oh, no, Linux doesn't have `SHM_ANON`. However, I have since learned that Linux has `memfd_create`, which accomplishes the same thing. – DrownedSuccess Sep 30 '22 at 20:05
10

I don't see why you'd be considering dlopen, since that will require a lot more nonportable code to generate the right object format on disk (e.g. ELF) for loading. If you already know how to generate machine code for your architecture, just mmap memory with PROT_READ|PROT_WRITE|PROT_EXEC and put your code there, then assign the address to a function pointer and call it. Very simple.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 4
    This doesn't seem like a very nice way to do it if there's going to be more than a few people developing. Also, won't your injected code need to resolve its own function pointers, and be PIC, etc? It just seems like compiling a .so and then being able to `dlopen` it would be a lot nicer. – mrduclaw Feb 19 '11 at 22:49
  • I guess it depends on what kind of code you're generating. I was thinking JIT code for a virtual machine/dynrec for an emulator, where there would not be arbitrary calls and access to data within the calling program. – R.. GitHub STOP HELPING ICE Feb 19 '11 at 23:25
  • 1
    This is indeed a nice way to handle relatively simple self-contained code (also: at then end of the day, how often do you really want dynamically generated code to be able to make arbitrary calls?) – Stephen Canon Feb 20 '11 at 00:43
  • R.. I certainly considered this, but this would also require a linker, because the output of the compiler I'm working with is object code, not machine code. This is why I have suggestions 3 and 4 up there: If I did this I would need to find some sort of cross-platform library for dynamically linking in memory. But if that doesn't exist, then this isn't a solution at all. – Jeremy Salwen Feb 20 '11 at 02:01
  • @Stephen Canon, actually this is a pretty regular requirement in some lines of business and happens on Windows pretty frequently. It is, however, the type of thing you write once and just keep reusing. – mrduclaw Feb 23 '11 at 16:59
  • @JeremySalwen The Glasgow Haskell Compiler has a linker built-in to its run-time system for the exact purpose you are describing. I am not sure how reusable it is, but I do know that it is written in C, not Haskell, and works across multiple platforms. – Demi Oct 15 '15 at 20:41
9

There is no standard way to do it other than writing out the file and then loading it again with dlopen().

You may find some alternative method on your current specific platform. It will up to you to decide whether that is better than using the 'standard and (relatively) portable' approach.

Since generating the object code in the first place is rather platform specific, additional platform-specific techniques may not matter to you. But it is a judgement call - and in any case depends on there being a non-standard technique, which is relatively improbable.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    Does a pipe count as filedesktriptor, too? So cannot you like... pipe it into dlopen() ? – imacake Mar 31 '12 at 15:41
  • 1
    @imacake - it's a file descriptor, but not one you can seek or mmap. – Flexo Dec 15 '12 at 10:01
  • "There is no standard way to do it other than writing out the file and then loading it again" should be corrected to something like "You can write out the file and load it", see R.. answer. – Simon Apr 14 '14 at 09:56
  • 2
    @Simon: If the code to be loaded doesn't need to call any other functions (is completely self-contained), you can use `mmap()` directly and it will probably work. If the code to be loaded makes calls to other functions, you have to resolve the addresses of those symbols by some method or other. This is normally done by `dlopen()` for you. If you short-circuit `dlopen()`, then the onus is on you as the code creator to ensure that you've taken ASLR into account, for example, and have the correct function addresses at the correct locations in the code. – Jonathan Leffler Apr 14 '14 at 13:31
  • Thanks for the ASLR info, I didn't knew about that. But I guess you should still be able to link the libraries if you load them yourself ? – Simon Apr 17 '14 at 13:02
  • 2
    A small "gotcha" to be aware of: on Linux, I have found that if I want one program to write out a .so, dlopen it, dlsym from it, and then write out another .so, dlopen it, and dlsym from it, then the two .so filenames must differ. – Mike Spear Apr 20 '19 at 00:56
7

We implemented a way to do this at Google. Unfortunately upstream glibc has failed to comprehend the need so it was never accepted. The feature request with patches has stalled. It's known as dlopen_from_offset.

The dlopen_with_offset glibc code is available in the glibc google/grte* branches. But nobody should enjoy modifying their own glibc.

gps
  • 1,360
  • 12
  • 12
1

Loading the solib from memory has an inherent limitation. Namely, the DT_NEEDED deps of a solib cannot refer to the memory buffer. This means, among other things, that you can't easily load solib with deps from the memory buffer. I am afraid, unless the ELF specification is extended to allow DT_NEEDED to refer to other objects than the file names, there will be no standard API for loading the solib from memory buffer.

I think you need to use posix's shm_open(), then mmap the shared memory, generate your solib there, then use plain dlopen() via /dev/shm mount point. This way also the deps can be handled: they can refer to the regular files or to the /dev/shm objects that have your generated solibs.

stsp
  • 308
  • 1
  • 6
1

Here's how you can do it entirely in-memory on Linux (no writing to /tmp/xxx) using a memory fd with memfd_create:

user@system $ ./main < example-library.so
add(1, 2) = 3
// example-library.c
int add(int a, int b) { return a + b; }
#include <cstdio>
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <vector>

// Compile and then invoke as:
// $ ./main < my-shared-lib.so
int main() {
  // Read the shared library contents from stdin
  std::vector<char> library_contents;
  char buffer[1024];
  ssize_t bytes_read;
  while ((bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer))) > 0) {
    library_contents.insert(library_contents.end(), buffer,
                            buffer + bytes_read);
  }

  // Create a memory file descriptor using memfd_create
  int fd = memfd_create("shared_library", 0);
  if (fd == -1) {
    perror("memfd_create failed");
    return 1;
  }

  // Write the shared library contents to the file descriptor
  if (write(fd, library_contents.data(), library_contents.size()) !=
      static_cast<ssize_t>(library_contents.size())) {
    perror("write failed");
    return 1;
  }

  // Create a path to the file descriptor using /proc/self/fd
  // https://sourceware.org/bugzilla/show_bug.cgi?id=30100#c33
  char path[100]; // > 35 == strlen("/proc/self/fd/") + log10(pow(2, 64)) + 1
  snprintf(path, sizeof(path), "/proc/self/fd/%d", fd);

  // Use dlopen to dynamically load the shared library
  void *handle = dlopen(path, RTLD_NOW);
  if (handle == NULL) {
    fprintf(stderr, "dlopen failed: %s\n", dlerror());
    return 1;
  }

  // Use the shared library...
  // Get a pointer to the function "int add(int, int)"
  int (*add)(int, int) =
      reinterpret_cast<int (*)(int, int)>(dlsym(handle, "add"));

  if (add == NULL) {
    fprintf(stderr, "dlsym failed: %s\n", dlerror());
    return 1;
  }

  // Call the function "int add(int, int)"
  printf("add(1, 2) = %d\n", add(1, 2));

  // Cleanup
  dlclose(handle);
  close(fd);
  return 0;
}
Gavin Ray
  • 595
  • 1
  • 3
  • 10
1

You don't need to load the code generated in memory, since it is already in memory!

However, you can -in a non portable way- generate machine code in memory (provided it is in a memory segment mmap-ed with PROT_EXEC flag).

(in that case, no "linking" or relocation step is required, since you generate machine code with definitive absolute or relative addresses, in particular to call external functions)

Some libraries exist which do that: On GNU/Linux under x86 or x86-64, I know of GNU Lightning (which generates quickly machine code which runs slowly), DotGNU LibJIT (which generates medium quality code), and LLVM & GCCJIT (which is able to generate quite optimized code in memory, but takes time to emit it). And LuaJit has some similar facility too. Since 2015 GCC 5 has a gccjit library.

And of course, you can still generate C code in a file, fork a compiler to compile it into a shared object, and dlopen that shared object file. I'm doing that in GCC MELT , a domain specific language to extend GCC. It does work quite well in practice.

addenda

If performance of writing the generated C file is a concern (it should not be, since compiling a C file is much slower than writing it) consider using some tmpfs file system for that (perhaps in /tmp/ which is often a tmpfs filesystem on Linux)

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
0

I found a solution to this, creating a memory file using memfd_create and then opening from dlopen.

user16217248
  • 3,119
  • 19
  • 19
  • 37
StarDust
  • 846
  • 1
  • 13
  • 26
0

It should be noted that using shm_open+dlopen loads the dynamic library from shared memory, if/dev/shm has the permission of noexec, the dynamic library will fail to load.