Open jacereda opened 2 years ago
I've noticed a problem with my approach. I build a dynamically-linked executable for this program:
https://github.com/jacereda/cosmogfx/blob/main/helper.c
The main executable will load this helper executable and jump to its entry point. The helper just transfers control back to the main executable passing the libdl entry points.
The problem is the dynamic linker location is different across different Linux systems. I could try to detect some common locations, but on NixOS the situation is even worse, it wil just be different across different generations.
Any suggestion on how to handle that?
Hmmm, I guess I could just readelf -p.interp $SHELL
?
Looks like binutils
isn't installed everywhere. Any suggestion on how to figure out the dynamic linker path?
I guess I can just open the /usr/bin/env
ELF and look for the PT_INTERP field...
I'm watching this...really exciting efforts :heart:
I think NetBSD should work now, but I'm unable to test because I can't get acceleration working on qemu... Anyone willing to help testing it?
FreeBSD works.
OpenBSD is hard. I'm hitting this check:
Given that the cosmopolitan binary is static, manually loading the helper (dynamic executable) won't do the trick, as soon as ld.so
attempts a syscall it will be killed (because the syscall origin isn't in the static executable text section).
I guess this check will make it sooner or later into other BSDs and Linux, so this might be a showstopper in the future for those as well.
Any suggestion?
That's msyscall. I had a feeling earlier you'd run into this eventually: https://github.com/jart/cosmopolitan/issues/309#issuecomment-958334349 since it's been pain point for the execve_ape()
change I've been working on too. We'll be able to work around it. To get you unblocked as quickly as possible, here's what I recommend you try.
Try changing that to instead use the memory interval msyscall(0, 2**47)
which effectively authorizes the entire memory space to use the SYSCALL
instruction. We'll have to cross our fingers and hope the OpenBSD kernel isn't picky about letting us do it. Otherwise you could try shrinking the range to msyscall(2*1024*1024, 2**47 - 2*1024*1024)
. If that still doesn't work, then we might need to mmap a big empty block that's big enough for the executable image as well as OpenBSD libc, and then copy OpenBSD libc into that privileged block. If that ends up not working, possibly because the kernel mmap
and munmap
won't change something that overlaps with the msyscall interval, then what we'll do is roll up our sleeves and use Xed, since nothing can stop Xed.
Good idea. My preliminary tests still fail and my guess is that msyscall
will only affect already-mapped memory. I'll try the empty block thing next.
one simple way to do it might be allocating a big static block into the privileged section. That's the part of the binary where we currently put functions that have SYSCALL
instructions which is our current workaround to the msyscall challenge. One way to do that might be:
#include "libc/macros.h"
.privileged
.align 4096
openbsdlibc:
.zero 4 * 1024 * 1024
.globl openbsdlibc
Then say:
extern char openbsdlibc[];
Which will be r-x so you'll need to call mprotect twice to copy the dso into that array, which will have msyscall privileges.
There's a problem with that approach, I'm not loading libc, I'm just loading the interpreter and the interpreter will load libc. So that would just allow the interpreter to do syscalls, not libc itself...
What is the filename of the interpreter on OpenBSD? Is there any reason why we can't just load the DSOs ourselves? That's how Musl does it.
The interpreter is /usr/libexec/ld.so
I'm using the interpreter to avoid dealing with the relocations and all that, but that might be an option.
I can try mapping the region where libc will be loaded prior to calling msyscall
, could that do the trick?
Last time I looked at the Musl code I think they managed to do it using about 500 LOC in one file. It's not easy but it's certainly achievable. In terms of mapping the region, one thing you could try is mapping their libc right after _end
since both need to have access and the msyscall range needs to be contiguous.
Do you also know for certain what memory ranges DSOs will be loaded into by these interpreters on various platforms? We have to track memory intervals in order to support Windows. If these interpreters only map to a known range then we can ensure the two mmap() implementations never overlap.
On OpenBSD libc is loaded at some random region at 0x2xxxxxxxx and on Linux it's 0x7fxxxxxxxxxx. Dunno about other platforms right now...
Nah, forget that, that's the region where the interpreter itself is loaded...
I guess I need some sleep now, but looks like the way to go is indeed loading libc ourselves.
ELF comes up frequently in this project. Here's the APE loading code I wrote recently. It gets loaded to address 0x200000 which appears to overlap with OpenBSD's loader. Obviously we have the ability to relocate our code to different addresses as needed, but it's a good example of the potential for overlap. https://github.com/jart/cosmopolitan/blob/fdb543cbb32444b9cca5c2d39ce9c3866e801b67/ape/loader.c#L184
We have code for loading symbol tables here: https://github.com/jart/cosmopolitan/blob/fdb543cbb32444b9cca5c2d39ce9c3866e801b67/libc/runtime/opensymboltable.c#L108
Blinkenlights also has code for loading static executables. https://github.com/jart/cosmopolitan/blob/fdb543cbb32444b9cca5c2d39ce9c3866e801b67/tool/build/lib/loader.c#L40
I've only done it for the simple case of loading executables. If you can help us do it for DSOs too then that'll pay dividends elsewhere too. For example, it's been requested on more than a few occasions that Blinkenlights be able to run the dynamic executables that ship with Linux distros.
In terms of where to load DSOs, would 0x7e0400000000-0x7f0000000000 be possible? 1TB of DSOs ought to be enough for anyone, right?
I don't have citations to prove it, but I'm reasonably certain that 0x7f0000000000-0x800000000000 is what operating systems reserve for its vDSOs and auto-stack. It'd be great if you could confirm that.
I'm going to be using 0x7e0000000000-0x7e0400000000 for APE execve().
Beneath that is stack, heap, shadow, arena, image, and finally loader.
For example, it's been requested on more than a few occasions that Blinkenlights be able to run the dynamic executables that ship with Linux distros.
Silly question, ld.so is a static executable and you can use it in the command line as:
/path/to/ld.so my-dynamic-executable
Shouldn't that suffice to run dynamic executables via blinkenlights?
Looks like the answer is no, because I'm getting a crash at a brk
syscall, any idea why?
Reported at https://github.com/jart/cosmopolitan/issues/331
I've attempted to port the musl loader, but it didn't go very far. I can invoke stdio functions from musl, but attempting to load libX11 or libGL just fails (because those are linked to glibc and looks like musl isn't there yet). Attempting to load glibc as a previous step to loading libX11/libGL is also problematic and requires carnal knowledge about glibc that would result in a maintenance nightmare, so I'm afraid I'll abandon that route.
I was thinking about loading ld.so via LoadProgram
instead and using it to load libdl, but on OpenBSD it isn't a static executable.
Yet another option would be to compile the required bits from xorg statically with cosmopolitan. That sounds scary and bloated, but a program linking statically against xorg isn't that big (provided you use LTO or gc-sections). I don't have numbers, but I ported the X server + FreeNX to an embedded platform and IIRC the result was quite small.
errno
constants would be a PITA though.
Which brings me again to this question:
https://github.com/jart/cosmopolitan/issues/103#issuecomment-939269631
I've uploaded a repo with my work so far at https://github.com/jacereda/cosmogfx
I'll send a PR with my changes to this repo in a while.