From b7b5ac144e2d3c3e44ef767c37cd478b2de2df8f Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Fri, 4 Oct 2024 18:14:21 +0200 Subject: [PATCH] drop dlopen machinery and custom package loader (#519) Unnecessary, now that we bundle native libraries & Lua modules into one big library. --- assets/android.lua | 48 ----------- assets/dl.lua | 176 -------------------------------------- assets/elf.lua | 139 ------------------------------ assets/elf_h.lua | 108 ----------------------- ffi-cdecl/elf_decl.c | 66 -------------- utils/print_dt_needed.lua | 18 ---- 6 files changed, 555 deletions(-) delete mode 100644 assets/dl.lua delete mode 100644 assets/elf.lua delete mode 100644 assets/elf_h.lua delete mode 100644 ffi-cdecl/elf_decl.c delete mode 100644 utils/print_dt_needed.lua diff --git a/assets/android.lua b/assets/android.lua index dcfa0ddad..0f631a3ba 100644 --- a/assets/android.lua +++ b/assets/android.lua @@ -1574,35 +1574,6 @@ function android.asset_loader(modulename) return errmsg end ---[[-- -This loader function just loads dependency libraries for the C module. - -@string modulename -@treturn bool success or failure ---]] -function android.deplib_loader(modulename) - local function readable(filename) - local f = io.open(filename, "r") - if f == nil then return false end - f:close() - return true - end - - local modulepath = string.gsub(modulename, "%.", "/") - for path in string.gmatch(package.cpath, "([^;]+)") do - local module = string.gsub(path, "%?", modulepath) - -- try to load dependencies of this module with our dlopen implementation - if readable(module) then - android.DEBUG("try to load module "..module) - local ok, err = pcall(android.dl.dlopen, module) - if ok then return end - if err then - android.LOGE("error: " .. err) - end - end - end -end - function android.get_application_directory() end @@ -2764,28 +2735,9 @@ local function run(android_app_state) -- set up a sensible package.path package.path = "?.lua;"..android.dir.."/?.lua;" - -- set absolute cpath - package.cpath = "?.so;"..android.dir.."/?.so;" -- register the asset loader table.insert(package.loaders, 2, android.asset_loader) - -- load the dlopen() implementation - android.dl = require("dl") - android.dl.system_libdir = ffi.abi("64bit") and "/system/lib64" or "/system/lib" - android.dl.library_path = android.nativeLibraryDir..":".. - android.dir..":"..android.dir.."/libs:".. - string.gsub("@:@/lib?.so", "@", android.dl.system_libdir) - - -- register the dependency lib loader - table.insert(package.loaders, 3, android.deplib_loader) - - -- ffi.load wrapper - local ffi_load = ffi.load - ffi.load = function(library, ...) -- luacheck: ignore 212 - android.DEBUG("ffi.load "..library) - return android.dl.dlopen(library, ffi_load) - end - local installed = android.extractAssets() if not installed then error("error extracting assets") diff --git a/assets/dl.lua b/assets/dl.lua deleted file mode 100644 index f60dd77a8..000000000 --- a/assets/dl.lua +++ /dev/null @@ -1,176 +0,0 @@ ---[[ -A LuaJIT FFI based version of dlopen() which loads dependencies -first (for implementations of dlopen() lacking that feature, like -on Android before API 23, -c.f., https://android.googlesource.com/platform/bionic/+/refs/heads/master/android-changes-for-ndk-developers.md) - -This is heavily inspired by the lo_dlopen() implementation from -LibreOffice (see -https://cgit.freedesktop.org/libreoffice/core/tree/sal/android/lo-bootstrap.c?id=963c98a65e4eddf179e170ff0bb30e4bfafc6b16) -and as such: - - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. ---]] - --- Disable the JIT in this module, to avoid weird and mysterious issues with dlopen (here and in ffi.load), --- as well as the nested loops in dl.dlopen mysteriously breaking early. -jit.off(true, true) - -local ffi = require("ffi") -local A = require("android") -local Elf = require("elf") -local log = "dlopen" - -local C = ffi.C - --- There's a bit of heinous hackery going on on 32-bit ABIs with RTLD_NOW & RTLD_GLOBAL... --- c.f., https://android.googlesource.com/platform/bionic/+/refs/heads/master/libc/include/dlfcn.h -if ffi.abi("64bit") then - ffi.cdef[[ - void *dlopen(const char *filename, int flag); - char *dlerror(void); - const static int RTLD_LOCAL = 0; - const static int RTLD_LAZY = 0x00001; - const static int RTLD_NOW = 0x00002; - const static int RTLD_NOLOAD = 0x00004; - const static int RTLD_GLOBAL = 0x00100; - const static int RTLD_NODELETE = 0x01000; - ]] -else - ffi.cdef[[ - void *dlopen(const char *filename, int flag); - char *dlerror(void); - const static int RTLD_LOCAL = 0; - const static int RTLD_LAZY = 0x00001; - const static int RTLD_NOW = 0x00000; - const static int RTLD_NOLOAD = 0x00004; - const static int RTLD_GLOBAL = 0x00002; - const static int RTLD_NODELETE = 0x01000; - ]] -end - -local dl = { - -- set this to search in certain directories - library_path = '/lib/?;/usr/lib/?;/usr/local/lib/?', - -- set this to the directory of system libraries - -- (to be ignored when loading dependencies). - system_libdir = nil, -} - -local function sys_dlopen(library, global, padding) - A.LOG(C.ANDROID_LOG_VERBOSE, string.format( - "%"..padding.."ssys_dlopen - loading library %s (in %s namespace)", - "", library, global and "global" or "local"), log) - - local p = C.dlopen(library, bit.bor(C.RTLD_NOW, global and C.RTLD_GLOBAL or C.RTLD_LOCAL)) - if p == nil then - local err_msg = C.dlerror() - if err_msg ~= nil then - local err = "error dlopen'ing "..library..": "..ffi.string(err_msg) - A.LOG(C.ANDROID_LOG_ERROR, err, log) - error(err) - end - else - C.dlerror() - end - return p -end - ---[[ -load_func will be used to load the library (but not its dependencies!) -if not given, the system's dlopen() will be used - -if the library name is an absolute path (starting with "/"), then -the library_path will not be used ---]] -function dl.dlopen(library, load_func, depth) - load_func = load_func or sys_dlopen - depth = depth or 0 - local padding = depth * 4 - - for pspec in string.gmatch( - library:sub(1, 1) == "/" and "/" or dl.library_path, - "([^;:]+)") do - - local lname - if library:sub(1, 1) == "/" and pspec == "/" then - lname = library - else - local matches - lname, matches = string.gsub(pspec, "%?", library) - if matches == 0 then - -- if pathspec does not contain a '?', - -- we append the library name to the pathspec - lname = lname .. '/' .. library - end - end - - local ok, lib = pcall(Elf.open, lname) - if not ok and lname:find("%.so%.%d[.%d]*$") then - lname = lname:gsub("%.so%.%d[.%d]*$", "%.so") - ok, lib = pcall(Elf.open, lname) - end - if ok then - local level = depth == 0 and C.ANDROID_LOG_INFO or C.ANDROID_LOG_VERBOSE - A.LOG(level, string.format("%"..padding.."sdl.dlopen - %s => %s", "", library, lname), log) - -- Skip loading system libraries (unless we explicitly asked for one, in which case, that's on the caller ;)). - -- c.f., https://github.com/koreader/android-luajit-launcher/pull/69 - -- Note that, technically, for Android >= 6.0, the list of safe system libraries is: - -- libandroid, libc, libcamera2ndk, libdl, libGLES, libjnigraphics, - -- liblog, libm, libmediandk, libOpenMAXAL, libOpenSLES, libstdc++, - -- libvulkan, and libz - -- c.f., https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#private-api-enforced-for-api-level-24 - -- Our current code should *never* hit any private system libs, so, this is basically overkill ;). - if depth > 0 and (pspec == dl.system_libdir or library == "libdl.so") then - -- depth > 0 to allow explicitly loading a system lib - -- (because this might have genuine use cases, as some early API levels do not put DT_NEEDED libraries into the global namespace) - -- pspec to reject system libs - -- secondary check on libdl, because apparently there are old ROMs out there where it isn't in /sytem/lib ?! - A.LOG(C.ANDROID_LOG_VERBOSE, string.format("%"..padding.."sdl.dlopen - skipping %s (system lib)", "", lname), log) - -- We won't load it, so, we don't even need to look at its deps. - lib:close() - return nil - end - - depth = depth + 1 - padding = depth * 4 - -- we found a library, now load its requirements - -- we do _not_ pass the load_func to the cascaded - -- calls, so those will always use sys_dlopen() - local lib_needs = lib:dlneeds() - lib:close() - for i, needed in ipairs(lib_needs) do - A.LOG(C.ANDROID_LOG_VERBOSE, string.format("%"..padding.."sdl.dlopen - needed => %s (%d of %d) <= %s", "", needed, i, #lib_needs, lname), log) - dl.dlopen(needed, sys_dlopen, depth) - end - depth = depth - 1 - padding = depth * 4 - if load_func == sys_dlopen then - return sys_dlopen(lname, false, padding) - else - A.LOG(C.ANDROID_LOG_VERBOSE, string.format("%"..padding.."sdl.dlopen - load_func -> %s", "", lname), log) - return load_func(lname) - end - else - -- The first io.open assert will return a table, so that we can preserve the errno, - -- allowing us to somewhat cleanly skip logging ENOENT errors, - -- because 99.99% of those will happen in the course of the searchpath lookups... - if type(lib) == "table" then - -- NOTE: #define ENOENT 2 @ /usr/include/asm-generic/errno-base.h ;). - if lib.num and lib.num ~= 2 then - A.LOG(C.ANDROID_LOG_WARN, string.format("Failed to open %s", lib.str), log) - end - else - A.LOG(C.ANDROID_LOG_WARN, string.format("Failed to parse ELF binary %s: %s", lname, lib), log) - end - end - end - - local err = "could not find library " .. library - A.LOG(C.ANDROID_LOG_WARN, err, log) - error(err) -end - -return dl diff --git a/assets/elf.lua b/assets/elf.lua deleted file mode 100644 index 237a61a66..000000000 --- a/assets/elf.lua +++ /dev/null @@ -1,139 +0,0 @@ ---[[ -Tools for handling ELF files - -not much implemented for now - just enough to parse 32bit -ELF files for their needed libraries. - -This is heavily inspired by the lo_dlopen() implementation from -LibreOffice (see -http://cgit.freedesktop.org/libreoffice/core/tree/sal/android/lo-bootstrap.c) -and as such: - - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. ---]] - -local ffi = require("ffi") - -local C = ffi.C - --- Pull in the necessary cdefs -require("elf_h") - -local Elf = {__index={}} - --- open an ELF file -function Elf.open(filename) - local e = {} - e.filename = filename - -- Slightly more roundabout than e.file = assert(io.open(filename, "r")) in order to preserve the errno... - local err = {} - e.file, err.str, err.num = io.open(filename, "r") - assert(e.file, err) - -- should also raise error if 'filename' is a directory - assert(e.file:read(0)) - setmetatable(e, Elf) - -- Check the Elf class (head of the Ehdr, which is at the head of the file) - local e_ident = e:read_at(0, "set", "unsigned char[?]", C.EI_NIDENT) - -- Check the ELF magic, first - assert(e_ident[C.EI_MAG0] == C.ELFMAG0 and - e_ident[C.EI_MAG1] == C.ELFMAG1 and - e_ident[C.EI_MAG2] == C.ELFMAG2 and - e_ident[C.EI_MAG3] == C.ELFMAG3, - "not a valid ELF binary") - -- Then the class - e.class = e_ident[C.EI_CLASS] - assert(e.class == C.ELFCLASS32 or e.class == C.ELFCLASS64, "invalid ELF class") - -- Set the ctypes we'll use given the Elf class - if e.class == C.ELFCLASS64 then - e.Elf_Ehdr = ffi.typeof("Elf64_Ehdr") - e.Elf_Shdr = ffi.typeof("Elf64_Shdr") - e.Elf_Dyn = ffi.typeof("Elf64_Dyn") - else - e.Elf_Ehdr = ffi.typeof("Elf32_Ehdr") - e.Elf_Shdr = ffi.typeof("Elf32_Shdr") - e.Elf_Dyn = ffi.typeof("Elf32_Dyn") - end - return e -end - --- close file when object is garbage collected -function Elf:__gc() - if self.file ~= nil then - self.file:close() - end -end - -function Elf.__index:close() - if self.file ~= nil then - self.file:close() - self.file = nil - end -end - --- convenience method that seeks and reads and also casts to an FFI ctype -function Elf.__index:read_at(pos, whence, ctype, size) - -- We'll get a cdata instead of a plain Lua number with Elf64, coerce that back in a way seek handles - pos = tonumber(pos) - local t - if size then - t = ffi.new(ctype, size) - -- Same idea as for pos above, io.read doesn't like a cdata size ;) - size = tonumber(size) - else - t = ffi.new(ctype) - end - self.file:seek(whence, pos) - local s = assert(self.file:read(size or ffi.sizeof(t))) - assert(#s == size or ffi.sizeof(t), "short read") - ffi.copy(t, s, #s) - return t -end - --- read the list of libraries that are needed by the ELF file -function Elf.__index:dlneeds() - -- ELF header: - local hdr = self:read_at(0, "set", self.Elf_Ehdr) - - -- Fetch string tables - local shdr_pos = tonumber(hdr.e_shoff + hdr.e_shstrndx * ffi.sizeof(self.Elf_Shdr)) - local shdr = self:read_at(shdr_pos, "set", self.Elf_Shdr) - local shstrtab = self:read_at(shdr.sh_offset, "set", "char[?]", shdr.sh_size) - - -- read .dynstr string table which contains the actual library names - local dynstr - self.file:seek("set", tonumber(hdr.e_shoff)) - for i = 0, hdr.e_shnum - 1 do - shdr = self:read_at(0, "cur", self.Elf_Shdr) - if shdr.sh_type == C.SHT_STRTAB - and ffi.string(shstrtab + shdr.sh_name) == ".dynstr" then - dynstr = self:read_at(shdr.sh_offset, "set", "char[?]", shdr.sh_size) - break - end - end - assert(dynstr, "no .dynstr section") - - local needs = {} - -- walk through the table of needed libraries - self.file:seek("set", tonumber(hdr.e_shoff)) - for i = 0, hdr.e_shnum - 1 do - shdr = self:read_at(0, "cur", self.Elf_Shdr) - if shdr.sh_type == C.SHT_DYNAMIC then - local offs = 0 - self.file:seek("set", tonumber(shdr.sh_offset)) - while offs < shdr.sh_size do - local dyn = self:read_at(0, "cur", self.Elf_Dyn) - offs = offs + ffi.sizeof(dyn) - if dyn.d_tag == C.DT_NEEDED then - table.insert(needs, ffi.string(dynstr + dyn.d_un.d_val)) - end - end - break - end - end - - return needs -end - -return Elf diff --git a/assets/elf_h.lua b/assets/elf_h.lua deleted file mode 100644 index 99e96d5b4..000000000 --- a/assets/elf_h.lua +++ /dev/null @@ -1,108 +0,0 @@ -local ffi = require("ffi") - -ffi.cdef[[ -static const int EI_NIDENT = 16; -static const int EI_MAG0 = 0; -static const int ELFMAG0 = 127; -static const int EI_MAG1 = 1; -static const int ELFMAG1 = 69; -static const int EI_MAG2 = 2; -static const int ELFMAG2 = 76; -static const int EI_MAG3 = 3; -static const int ELFMAG3 = 70; -static const int EI_CLASS = 4; -static const int ELFCLASSNONE = 0; -static const int ELFCLASS32 = 1; -static const int ELFCLASS64 = 2; -static const int SHT_STRTAB = 3; -static const int SHT_DYNAMIC = 6; -static const int DT_NEEDED = 1; -typedef uint16_t Elf32_Half; -typedef uint32_t Elf32_Word; -typedef int32_t Elf32_Sword; -typedef uint64_t Elf32_Xword; -typedef int64_t Elf32_Sxword; -typedef uint32_t Elf32_Addr; -typedef uint32_t Elf32_Off; -typedef uint16_t Elf32_Section; -typedef Elf32_Half Elf32_Versym; -typedef struct { - unsigned char e_ident[16]; - Elf32_Half e_type; - Elf32_Half e_machine; - Elf32_Word e_version; - Elf32_Addr e_entry; - Elf32_Off e_phoff; - Elf32_Off e_shoff; - Elf32_Word e_flags; - Elf32_Half e_ehsize; - Elf32_Half e_phentsize; - Elf32_Half e_phnum; - Elf32_Half e_shentsize; - Elf32_Half e_shnum; - Elf32_Half e_shstrndx; -} Elf32_Ehdr; -typedef struct { - Elf32_Word sh_name; - Elf32_Word sh_type; - Elf32_Word sh_flags; - Elf32_Addr sh_addr; - Elf32_Off sh_offset; - Elf32_Word sh_size; - Elf32_Word sh_link; - Elf32_Word sh_info; - Elf32_Word sh_addralign; - Elf32_Word sh_entsize; -} Elf32_Shdr; -typedef struct { - Elf32_Sword d_tag; - union { - Elf32_Word d_val; - Elf32_Addr d_ptr; - } d_un; -} Elf32_Dyn; -typedef uint16_t Elf64_Half; -typedef uint32_t Elf64_Word; -typedef int32_t Elf64_Sword; -typedef uint64_t Elf64_Xword; -typedef int64_t Elf64_Sxword; -typedef uint64_t Elf64_Addr; -typedef uint64_t Elf64_Off; -typedef uint16_t Elf64_Section; -typedef Elf64_Half Elf64_Versym; -typedef struct { - unsigned char e_ident[16]; - Elf64_Half e_type; - Elf64_Half e_machine; - Elf64_Word e_version; - Elf64_Addr e_entry; - Elf64_Off e_phoff; - Elf64_Off e_shoff; - Elf64_Word e_flags; - Elf64_Half e_ehsize; - Elf64_Half e_phentsize; - Elf64_Half e_phnum; - Elf64_Half e_shentsize; - Elf64_Half e_shnum; - Elf64_Half e_shstrndx; -} Elf64_Ehdr; -typedef struct { - Elf64_Word sh_name; - Elf64_Word sh_type; - Elf64_Xword sh_flags; - Elf64_Addr sh_addr; - Elf64_Off sh_offset; - Elf64_Xword sh_size; - Elf64_Word sh_link; - Elf64_Word sh_info; - Elf64_Xword sh_addralign; - Elf64_Xword sh_entsize; -} Elf64_Shdr; -typedef struct { - Elf64_Sxword d_tag; - union { - Elf64_Xword d_val; - Elf64_Addr d_ptr; - } d_un; -} Elf64_Dyn; -]] diff --git a/ffi-cdecl/elf_decl.c b/ffi-cdecl/elf_decl.c deleted file mode 100644 index 52d2e40e5..000000000 --- a/ffi-cdecl/elf_decl.c +++ /dev/null @@ -1,66 +0,0 @@ -#include - -#include "ffi-cdecl.h" - -// Constants -cdecl_const(EI_NIDENT) - -cdecl_const(EI_MAG0) -cdecl_const(ELFMAG0) -cdecl_const(EI_MAG1) -cdecl_const(ELFMAG1) -cdecl_const(EI_MAG2) -cdecl_const(ELFMAG2) -cdecl_const(EI_MAG3) -cdecl_const(ELFMAG3) - -cdecl_const(EI_CLASS) -cdecl_const(ELFCLASSNONE) -cdecl_const(ELFCLASS32) -cdecl_const(ELFCLASS64) - - -cdecl_const(SHT_STRTAB) -cdecl_const(SHT_DYNAMIC) - -cdecl_const(DT_NEEDED) - -// ELFCLASS32 -cdecl_c99_type(Elf32_Half, uint16_t) -cdecl_c99_type(Elf32_Word, uint32_t) -cdecl_c99_type(Elf32_Sword, int32_t) - -cdecl_c99_type(Elf32_Xword, uint64_t) -cdecl_c99_type(Elf32_Sxword, int64_t) - -cdecl_c99_type(Elf32_Addr, uint32_t) - -cdecl_c99_type(Elf32_Off, uint32_t) - -cdecl_c99_type(Elf32_Section, uint16_t) - -cdecl_c99_type(Elf32_Versym, Elf32_Half) - -cdecl_type(Elf32_Ehdr) -cdecl_type(Elf32_Shdr) -cdecl_type(Elf32_Dyn) - -// ELFCLASS64 -cdecl_c99_type(Elf64_Half, uint16_t) -cdecl_c99_type(Elf64_Word, uint32_t) -cdecl_c99_type(Elf64_Sword, int32_t) - -cdecl_c99_type(Elf64_Xword, uint64_t) -cdecl_c99_type(Elf64_Sxword, int64_t) - -cdecl_c99_type(Elf64_Addr, uint64_t) - -cdecl_c99_type(Elf64_Off, uint64_t) - -cdecl_c99_type(Elf64_Section, uint16_t) - -cdecl_c99_type(Elf64_Versym, Elf64_Half) - -cdecl_type(Elf64_Ehdr) -cdecl_type(Elf64_Shdr) -cdecl_type(Elf64_Dyn) diff --git a/utils/print_dt_needed.lua b/utils/print_dt_needed.lua deleted file mode 100644 index 1e175544f..000000000 --- a/utils/print_dt_needed.lua +++ /dev/null @@ -1,18 +0,0 @@ --- Simple wrapper around elf.lua to test its behavior. - --- On the off-chance it's as jittery as on Android... -jit.off(true, true) - -local ffi = require("ffi") - -package.path = "?.lua;" .. "utils/?.lua;" .. "assets/?.lua;" .. "../assets/?.lua;" .. package.path -local Elf = require("elf") - -lname = arg[1] - -local lib = Elf.open(lname) -local lib_needs = lib:dlneeds() -lib:close() -for i, needed in ipairs(lib_needs) do - print(string.format("needed => %s (%d of %d) <= %s", needed, i, #lib_needs, lname)) -end