Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linux: can you trust uname() to find out which version to load from a bundle? #115

Open
attilammagyar opened this issue Jul 2, 2023 · 3 comments

Comments

@attilammagyar
Copy link

attilammagyar commented Jul 2, 2023

Reproduction

  1. I have compiled my VST 3 synth plugin for 32 bit Linux, and installed it as an i686-only bundle like this:

    ~/.vst3/
    ~/.vst3/js80p.vst3
    ~/.vst3/js80p.vst3/Contents
    ~/.vst3/js80p.vst3/Contents/Resources
    ~/.vst3/js80p.vst3/Contents/i686-linux
    ~/.vst3/js80p.vst3/Contents/i686-linux/js80p.so
    
  2. I ran a VST plugin scan in the Linux i686 version of Reaper 6.80, but on an x86_64 Ubuntu 20.04 system.

Expected Result

The plugin is found and loaded by the host.

Actual Result

  1. The plugin is indicated by Reaper as having failed to scan.
  2. If instead of the VST 3 bundle, the plugin is supplied in the deprecated, single js80p.vst3 file format, then the plugin can be successfully loaded by the 32 bit version of Reaper on a 64 bit Linux system.

Sure, but where exactly is this an SDK issue rather than a bug in Reaper?

As can be seen from the output of strace -f -s 128 --abbrev=none ./REAPER/reaper below, the problem occurs when Reaper calls uname(), conforming to what the specs say (see VST3::Hosting::getCurrentMachineName()): uname() will report the current kernel's architecture, but that's not necessarily the same as the architecture of the calling application, and the two are not necessarily compatible with each other in both directions.

[pid 12854] uname({sysname="Linux", ..., machine="x86_64", ...}) = 0
...
[pid 12854] write(1, "\n!REAPER-MSG:: js80p.vst3\n", 26) = 26
[pid 12854] stat64("/home/.../.config/REAPER/reaper-vstplugins.ini", {..., st_mode=S_IFREG|0664, ...}) = 0
[pid 12854] openat(AT_FDCWD, "/home/.../.config/REAPER/.reaper-vstplugins.ini.new", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 4
[pid 12854] flock(4, LOCK_EX)           = 0
[pid 12854] fstat64(4, {..., st_mode=S_IFREG|0664, ...}) = 0
[pid 12854] write(4, "[vstcache]\njs80p.vst3=002AA98EEBA9D901\nreacast.vst.so=...", 1565) = 1565
[pid 12854] flock(4, LOCK_UN)           = 0
[pid 12854] close(4)                    = 0
[pid 12854] rename("/home/.../.config/REAPER/.reaper-vstplugins.ini.new", "/home/.../.config/REAPER/reaper-vstplugins.ini") = 0
[pid 12854] stat64("/home/.../.config/REAPER/reaper-vstplugins.ini", {..., st_mode=S_IFREG|0664, ...}) = 0
[pid 12854] openat(AT_FDCWD, "/home/.../.config/REAPER/reaper-vstbridge32.ini", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 12854] openat(AT_FDCWD, "/home/.../.vst3/js80p.vst3/Contents/unknown-linux/js80p.so", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
[pid 12854] stat64("/home/.../.vst3/js80p.vst3/Contents/unknown-linux/js80p.so", 0xffb335d0) = -1 ENOENT (No such file or directory)
[pid 12854] openat(AT_FDCWD, "/home/.../.vst3/js80p.vst3", O_RDONLY|O_LARGEFILE|O_CLOEXEC <unfinished ...>
[pid 12854] <... openat resumed>)       = 4
[pid 12854] read(4,  <unfinished ...>
[pid 12854] <... read resumed>0xffb32fb0, 512) = -1 EISDIR (Is a directory)
[pid 12854] close(4 <unfinished ...>
[pid 12854] <... close resumed>)        = 0
[pid 12854] stat64("/home/.../.vst3/js80p.vst3", {..., st_mode=S_IFDIR|0775, ...}) = 0
[pid 12854] stat64("/home/.../.config/REAPER/reaper-vstbridge32.ini", 0xffb33140) = -1 ENOENT (No such file or directory)
[pid 12854] readlink("/proc/self/exe", "/home/.../reaper680/REAPER/reaper", 3072) = 39
[pid 12854] stat64("/home/.../reaper680/REAPER/Plugins/reaper_host_i686", {..., st_mode=S_IFREG|0755, ...}) = 0
[pid 12854] stat64("/home/.../.vst3/js80p.vst3", {..., st_mode=S_IFDIR|0775, ...}) = 0
[pid 12854] openat(AT_FDCWD, "/home/.../.vst3/js80p.vst3/Contents", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY <unfinished ...>
[pid 12854] <... openat resumed>)       = 4
[pid 12854] fstat64(4,  <unfinished ...>
[pid 12854] <... fstat64 resumed>{..., st_mode=S_IFDIR|0775, ...}) = 0
[pid 12854] getdents64(4, [{..., d_type=DT_DIR, d_name="."}, {..., d_type=DT_DIR, d_name="Resources"}, {..., d_type=DT_DIR, d_name="i686-linux"}, {..., d_type=DT_DIR, d_name=".."}], 32768) = 112
[pid 12854] getdents64(4, [], 32768)    = 0
[pid 12854] close(4)                    = 0
[pid 12854] getpid()                    = 12854
[pid 12854] exit_group(0)               = ?
[pid 12854] +++ exited with 0 +++

Note that Reaper won't even try looking for x86_64-linux/js80p.so, probably because it detects that it won't be able to load a plugin with that architecture, so it falls back to unknown-linux. (This fallback is probably specific to Reaper, since the SDK doesn't seem to contain code like this.)

Long story short: an i686 host that is using uname() to figure out which plugin architecture to load from a bundle, will never find i686-linux when running on a system with an x86_64 kernel.

Solution?

I think public.sdk/source/vst/hosting/module_linux.cpp could use a compile-time assembled fallback list of compatible platform names, similarly to public.sdk/source/vst/hosting/module_win32.cpp. However, since there are a lot of possible xxx-linux combinations, I'm not sure how could such an approach be both backward-compatible with existing hosts and plugins, and future-proof and robust at the same time. A change like this could potentially introduce a lot of bugs with existing plugins and hosts.

Maybe it would be more safe to just mention this 64 bit kernel vs. 32 bit host application corner case in the documentation as a known issue, and keep the current uname() based logic unchanged?

@ygrabit
Copy link
Contributor

ygrabit commented Jul 6, 2023

we have such multiple locations check for Windows with Arm64EC and x64... where a Arm64EC host could load Arm64EC and x64 plugin binaries..
#if SMTG_CPU_ARM_64EC
if (instance == nullptr)
instance = loadAsPackage (inPath, architectureArm64XString);
if (instance == nullptr)
instance = loadAsPackage (inPath, architectureX64String);
#endif
maybe you could propose a way to do it for Linux?

@attilammagyar
Copy link
Author

Unfortunately my knowledge about the internals of Linux are enough to tell that there's a problem here, but insufficient to confidently propose a solution that is both future-proof and backward compatible.

At the end of the day, the information returned by uname() comes from the init_uts_ns structure from the Linux kernel in init/version-timestamp.c. The UTS_MACHINE string macro here comes from a makefile variable with the same name which gets written into include/generated/compile.h when you run make (see scripts/mkcompile_h).

The problem is that this makefile variable can have a lot of different values (someone on StackOverflow dug up a few), and I have no idea which is compatible with which, if any. And to make things worse, future versions of the kernel may drop some of these values or introduce new ones.

My best bet would be an algorithm like this, but this would only solve the 64 bit vs. 32 bit case:

  1. Let A be the architecture that uname() returned.
  2. Try to load the plugin from the A + "-linux" directory.
  3. If step 1 failed and A != "i686" then try i686-linux.
  4. If we still haven't successfully loaded anything, and A != "i386" then try i386-linux.

I'm not sure if similar problems can occur between 64 bit architectures, and if yes, how to resolve them. :-(

attilammagyar added a commit to attilammagyar/vst3_public_sdk that referenced this issue Jul 9, 2023
When a 32 bit host application is running with a 64 bit Linux kernel,
then getCurrentMachineName will return "x86_64", but dlopen will fail
with "wrong ELF class: ELFCLASS64". When this happens, our best bet
might be to try loading the plugin from "i686-linux" and "i386-linux".

See: steinbergmedia/vst3sdk#115
@attilammagyar
Copy link
Author

I made a PR from the proposed solution: steinbergmedia/vst3_public_sdk#57

I compiled the validator application for 32 bits, and sucessfully validated my plugin bundle with it on a 64 bit Linux.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants