-
Notifications
You must be signed in to change notification settings - Fork 0
JUEL UNIX Environment Layer
JUEL is a binary compatibility layer, and a set of libraries, and other components (e.g. the ELF loader, system call dispatcher, and liblinux
) that aims to enable support for running UNIX/Linux applications (primarily, the GNU toolchain), on Orion, without modification.
JUEL itself does not aim to provide direct source compatibility with existing applications, although liblinux
provides emulations of various Linux-specific functionality, but a concurrent goal is to implement POSIX support (and, API extensions for other platforms), in a pervasive manner, in other parts of Orion, where it aids portability.
A similar concept has been implemented in Solaris, FreeBSD, NetBSD, Windows 10, and LynxOS, previously.
The current status is that a subset of POSIX/UNIX API functions, and headers have been implemented, based on code from FreeBSD/Mach, and new glue code to interface with L4 APIs, so some third-party code will at least partially-compile.
This code surface was tested against:
- Sony's CLEFIA reference implementation (fully-compiles, but runtime is untested)
- V2Linux (itself an implementation of the VxWorks API surface, on Linux - much of which compiles, barring code depending on POSIX Threads/Semaphores)
- CacheLineSize, MojoELF, and SQLite will partially-compile, although linking fails, due to missing functionality
Currently, POSIX Threads/Semaphores, BSD sockets/STREAMS, and ELF loading are all undergoing implementation, so functionality depending on them will likely fail to compile, and run.
The name "JUEL" was initially contrived as an acronym of "Just Use Enough Linux" (referring to the primary ambition of being able to reuse various versions of the GNU toolchain, under Orion, without having to maintain full virtual machines, or Docker images of various different Linux distributions, and compiler versions).
In the spirit of UNIX reimplementations, such as XNU, and GNU, it could also be considered a recursive acronym for "JUEL UNIX Environment Layer", or "JUEL UNIX Emulation Layer".
Conveniently, in hindsight, it also happens to be an archaic spelling of "jewel" (a precious gem/artefact, or a well-designed object), making JUEL a valuable component of the Orion system.
On a high-level, when a file with the x
(Execute) permission is invoked, under Linux, a series of system calls are made, in order to request execution of the contents of a file at a specified path, configure library preload/cache, determine file access permissions/sizes/access times/types, and begin mapping it into RAM, at a specified location, before setting page protection bits, remapping certain regions, and then closing the file.
On a big-endian POWER8 machine, running Fedora 28, based on Linux 4.16.3, this looks like:
[root@fedora28 elf-loader]# strace uname
execve("/usr/bin/uname", ["uname"], 0x7fffe8ef1940 /* 21 vars */) = 0
brk(NULL) = 0x10019a70000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=31305, ...}) = 0
mmap(NULL, 31305, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fffb3640000
close(3) = 0
openat(AT_FDCWD, "/lib64/power8/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\2\1\3\0\0\0\0\0\0\0\0\0\3\0\25\0\0\0\1\0\0\0\0\0!5\270"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2482760, ...}) = 0
mmap(NULL, 2250632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fffb3410000
mprotect(0x7fffb3610000, 65536, PROT_NONE) = 0
mmap(0x7fffb3620000, 131072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x200000) = 0x7fffb3620000
close(3) = 0
mprotect(0x7fffb3620000, 65536, PROT_READ) = 0
mprotect(0x128020000, 65536, PROT_READ) = 0
mprotect(0x7fffb36a0000, 65536, PROT_READ) = 0
munmap(0x7fffb3640000, 31305) = 0
brk(NULL) = 0x10019a70000
brk(0x10019aa0000) = 0x10019aa0000
brk(NULL) = 0x10019aa0000
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=6398800, ...}) = 0
mmap(NULL, 6398800, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fffb2df0000
close(3) = 0
uname({sysname="Linux", nodename="fedora28.novalocal", ...}) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "Linux\n", 6Linux
) = 6
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
This process is performed recursively, for every shared object, and other dependency file, until the first useful system calls can be performed, in order to do more useful work, by various kernel subsystems that handle each system call (passed by the dispatcher), and for dynamic libraries, the dynamic linker will also be invoked.
Each dynamically-linked binary gets loaded to a randomised "I/O vector base", everytime that the dynamic linker invokes it:
[root@fedora28 elf-loader]# file /lib64/ld-2.27.so
/lib64/ld-2.27.so: ELF 64-bit MSB shared object, 64-bit PowerPC or cisco 7500, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=3438c42ff9378381af22ee2ad4fbc81e34c0b381, not stripped
[root@fedora28 elf-loader]# /lib64/ld64.so.1 --list /bin/uname
linux-vdso64.so.1 (0x00007fffadc70000)
libc.so.6 => /lib64/power8/libc.so.6 (0x00007fffada00000)
/lib64/ld64.so.1 (0x00007fffadc90000)
Since documentation this process is scarce, running strace -ff /lib64/ld64.so.1 --list /bin/uname
is extremely illuminating on how this process actually works:
[root@fedora28 elf-loader]# strace -ff /lib64/ld64.so.1 --list /bin/uname
execve("/lib64/ld64.so.1", ["/lib64/ld64.so.1", "--list", "/bin/uname"], 0x7ffff764dac8 /* 21 vars */) = 0
brk(NULL) = 0x7fffc41b0000
openat(AT_FDCWD, "/bin/uname", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\2\1\0\0\0\0\0\0\0\0\0\0\3\0\25\0\0\0\1\0\0\0\0\0\1\365@"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=74992, ...}) = 0
mmap(NULL, 132432, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fffacd10000
mmap(0x7fffacd20000, 131072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0x7fffacd20000
close(3) = 0
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=31305, ...}) = 0
mmap(NULL, 31305, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fffacd00000
close(3) = 0
openat(AT_FDCWD, "/lib64/power8/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\2\1\3\0\0\0\0\0\0\0\0\0\3\0\25\0\0\0\1\0\0\0\0\0!5\270"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2482760, ...}) = 0
mmap(NULL, 2250632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fffacad0000
mprotect(0x7fffaccd0000, 65536, PROT_NONE) = 0
mmap(0x7fffacce0000, 131072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x200000) = 0x7fffacce0000
close(3) = 0
writev(1, [{iov_base="\t", iov_len=1}, {iov_base="linux-vdso64.so.1", iov_len=17}, {iov_base=" (0x", iov_len=4}, {iov_base="00007fffacd40000", iov_len=16}, {iov_base=")\n", iov_len=2}], 5 linux-vdso64.so.1 (0x00007fffacd40000)
) = 40
writev(1, [{iov_base="\t", iov_len=1}, {iov_base="libc.so.6", iov_len=9}, {iov_base=" => ", iov_len=4}, {iov_base="/lib64/power8/libc.so.6", iov_len=23}, {iov_base=" (0x", iov_len=4}, {iov_base="00007fffacad0000", iov_len=16}, {iov_base=")\n", iov_len=2}], 7 libc.so.6 => /lib64/power8/libc.so.6 (0x00007fffacad0000)
) = 59
writev(1, [{iov_base="\t", iov_len=1}, {iov_base="/lib64/ld64.so.1", iov_len=16}, {iov_base=" (0x", iov_len=4}, {iov_base="00007fffacd60000", iov_len=16}, {iov_base=")\n", iov_len=2}], 5 /lib64/ld64.so.1 (0x00007fffacd60000)
) = 39
exit_group(0) = ?
+++ exited with 0 +++
In Linux, with the GNU userland, this process is handled by the kernel, in conjunction with a reconfigurable dynamic linker (encoded into SONAME
, in a header of the file), but in Orion, this process is split between various userspace components.
- The Program Library HOWTO, at The Linux Documentation Project has extensive information on the processes of creating, loading, and loading shared objects, under Linux
- The "How programs get run" article, at Linux Weekly News may also be interesting
- FreeBSD's AMD64 Linux
sysvec
implementation