jart / cosmopolitan

build-once run-anywhere c library
ISC License
17.84k stars 610 forks source link

OpenGL support integration #303

Open jacereda opened 2 years ago

jacereda commented 2 years ago

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.

jacereda commented 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?

jacereda commented 2 years ago

Hmmm, I guess I could just readelf -p.interp $SHELL?

jacereda commented 2 years ago

Looks like binutils isn't installed everywhere. Any suggestion on how to figure out the dynamic linker path?

jacereda commented 2 years ago

I guess I can just open the /usr/bin/env ELF and look for the PT_INTERP field...

coderofsalvation commented 2 years ago

I'm watching this...really exciting efforts :heart:

jacereda commented 2 years ago

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?

jacereda commented 2 years ago

FreeBSD works.

jacereda commented 2 years ago

OpenBSD is hard. I'm hitting this check:

https://github.com/openbsd/src/blob/2207c4325726fdc5c4bcd0011af0fdf7d3dab137/sys/sys/syscall_mi.h#L88

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?

jart commented 2 years ago

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.

https://github.com/jart/cosmopolitan/blob/e5d1536256064224e7188f0425dabec50e223c45/libc/sysv/systemfive.S#L408-L421

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.

jacereda commented 2 years ago

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.

jart commented 2 years ago

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.

jacereda commented 2 years ago

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...

jart commented 2 years ago

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.

jacereda commented 2 years ago

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.

jacereda commented 2 years ago

I can try mapping the region where libc will be loaded prior to calling msyscall, could that do the trick?

jart commented 2 years ago

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.

jart commented 2 years ago

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.

jacereda commented 2 years ago

On OpenBSD libc is loaded at some random region at 0x2xxxxxxxx and on Linux it's 0x7fxxxxxxxxxx. Dunno about other platforms right now...

jacereda commented 2 years ago

Nah, forget that, that's the region where the interpreter itself is loaded...

jacereda commented 2 years ago

I guess I need some sleep now, but looks like the way to go is indeed loading libc ourselves.

jart commented 2 years ago

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.

jart commented 2 years ago

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.

jacereda commented 2 years ago

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?

jacereda commented 2 years ago

Reported at https://github.com/jart/cosmopolitan/issues/331

jacereda commented 2 years ago

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.

jacereda commented 2 years ago

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.

jacereda commented 2 years ago

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.

jacereda commented 2 years ago

errno constants would be a PITA though.

jacereda commented 2 years ago

Which brings me again to this question:

https://github.com/jart/cosmopolitan/issues/103#issuecomment-939269631