love2d / love-android

Android build setup for LÖVE.
https://love2d.org
Other
207 stars 72 forks source link

cannot load C library from either `/lib/[abi]/lib?.so` or `/lib/[abi]/?.so` #238

Closed ImpleLee closed 2 years ago

ImpleLee commented 2 years ago

Steps to reproduce

  1. Download 11.4 Android apk from releases of the official repository.
  2. Use apktool to unzip the apk into a directory example
    apktool decode -o example love-11.4-android.apk
  3. Put a native library into example/lib/[abi]/ (for simplicity, I just use the native library we are using in our game). This library is loadable if we use the old trick, i.e. copy CCloader.so into the save directory and then require it. In each directory are two .so file, libcold_clear.so and libCCloader.so. libCCloader.so is what we want to require, and libcold_clear.so is its dependency. We copy libCCloader.so into CCloader.so, so we can load it if either name works.
    wget https://github.com/26F-Studio/cold_clear_ai_love2d_wrapper/releases/download/11.4/Android.zip
    unzip Android.zip
    for arch in arm64-v8a armeabi-v7a
    do  cp $arch/* example/lib/$arch/
    cp example/lib/$arch/libCCloader.so example/lib/$arch/CCloader.so
    done
  4. Create a directory called assets in example, and put a file main.lua in it with the following content. It requires the native library there, and create an error handler to copy possible error message to clip board.
    
    function love.errorhandler(msg)
    love.system.setClipboardText(msg)
    end

require 'CCloader'

5. Pack the apk and sign it with a keystore (here I use `~/.keystore`).
```bash
apktool build -o example.apk example
zipalign -p 4 example.apk example-aligned.apk
mv example-aligned.apk example.apk
apksigner sign --ks ~/.keystore example.apk
  1. Install the apk on an Android phone, and open it.

An one-step bash script to reproduce

#!bash
set -eux

# step 1
wget https://github.com/love2d/love/releases/download/11.4/love-11.4-android.apk
# step 2
apktool decode -o example love-11.4-android.apk

# step 3
wget https://github.com/26F-Studio/cold_clear_ai_love2d_wrapper/releases/download/11.4/Android.zip
unzip Android.zip
for arch in arm64-v8a armeabi-v7a
do  cp $arch/* example/lib/$arch/
    cp example/lib/$arch/libCCloader.so example/lib/$arch/CCloader.so
done

# step 4
mkdir -p example/assets/
cat >example/assets/main.lua <<EOF
function love.errorhandler(msg)
    love.system.setClipboardText(msg)
end

require 'CCloader'
EOF

# step 5
apktool build -o example.apk example
zipalign -p 4 example.apk example-aligned.apk
mv example-aligned.apk example.apk
apksigner sign --ks ~/.keystore example.apk

Expected Behavior

It exits immediately, and nothing remains in the clip board.

Actual Behavior

It exits immediately, but there is some message in the clip board.

main.lua:5: module 'CCloader' not found:
    no field package.preload['CCloader']
    no 'CCloader' in LOVE game directories.
    no file 'CCloader' in LOVE paths.
    no file './CCloader.lua'
    no file '/usr/local/share/luajit-2.1.0-beta3/CCloader.lua'
    no file '/usr/local/share/lua/5.1/CCloader.lua'
    no file '/usr/local/share/lua/5.1/CCloader/init.lua'
    no file './CCloader.so'
    no file '/usr/local/lib/lua/5.1/CCloader.so'
    no file '/usr/local/lib/lua/5.1/loadall.so'

System Environment Info

I am working on an Ubuntu 20.04.5 LTS. The phone I use to test is a Samsung Galaxy S20 5G, running Android 12, OneUI 4.1.

ImpleLee commented 2 years ago

I basically did what I was told in this issue

Yes. That commit allows you to put your C modules alongside the APK in the "lib" dir, alongside the liblove.so.

Originally posted by @MikuAuahDark in https://github.com/love2d/love-android/issues/227#issuecomment-1087815709

MikuAuahDark commented 2 years ago

Can you inspect the resulting APK and confirm that CCloader.so is indeed exist inside?

ImpleLee commented 2 years ago

Can you inspect the resulting APK and confirm that CCloader.so is indeed exist inside?

Yes, it can be verified by apktool decode -o test example.apk, and you will see CCloader.so is in test/lib/[abi]/, right next to liblove.so.

MikuAuahDark commented 2 years ago

I wonder if it's case sensitivity issue but I doubt it. Can you send the .so or the .apk? Maybe through my E-mail if you're uncomfortable.

ImpleLee commented 2 years ago

I wonder if it's case sensitivity issue but I doubt it. Can you send the .so or the .apk? Maybe through my E-mail if you're uncomfortable.

Yes. The .so is available at https://github.com/26F-Studio/cold_clear_ai_love2d_wrapper/releases/download/11.4/Android.zip .

ImpleLee commented 2 years ago

example.apk is available at https://drive.google.com/file/d/1H2KaqW5ZuoxXNMyR9c1WXKs0nxkW2Xo6.

MikuAuahDark commented 2 years ago

Apparently CCloader.so depends on libcold_clear.so. Transitive dependencies is not handled automatically by Android, thus you have to load libcold_clear.so first through dlopen (and you have to load dlopen and dlclose symbol yourself through FFI). ffi.load can't be used because it checks for the file existence while dlopen in Android is modified so you can just pass dlopen("libcold_clear.so", 0) and it just works.

The transitive dependencies can be verified using objdump -p CCloader.so | grep "NEEDED"

ImpleLee commented 2 years ago

Apparently CCloader.so depends on libcold_clear.so. Transitive dependencies is not handled automatically by Android, thus you have to load libcold_clear.so first through dlopen (and you have to load dlopen and dlclose symbol yourself through FFI). ffi.new can't be used because it checks for the file existence while dlopen in Android is modified so you can just pass dlopen("libcold_clear.so", 0) and it just works.

The transitive dependencies can be verified using objdump -p CCloader.so | grep "NEEDED"

It does depend on libcold_clear.so, but in the old days, the trick of copying only CCloader.so into the save directory and then requiring it works. Why does that work but the new way of requiring does not work?

MikuAuahDark commented 2 years ago

As for that, I have no idea it actually works like that. I can't say much about it.

ImpleLee commented 2 years ago

Well, we will try to merge the two files and see if it can be loaded. I will close this issue if that works. Thanks for your help!

ImpleLee commented 2 years ago

It is still not loadable after merging. Here's the link to the .so files.

ImpleLee commented 2 years ago

Here is the link to example(1).apk.

MikuAuahDark commented 2 years ago

I see it tries to load libstdc++.so. We don't support this in Android. Please link with libc++_shared.so instead.

ImpleLee commented 2 years ago

Now it depends on neither libstdc++.so nor libc++_shared.so, but still cannot be loaded. The .so files are available here, and the apk is available here.

MikuAuahDark commented 2 years ago

Alright one last try, remove libCCloader.so but keep CCloader.so in the APK. Looks like Android is confused when both libx.so and x.so is present and only extract the ones with lib prefix, which I can confirm in Android 11 AVD.

ImpleLee commented 2 years ago

Still unloadable. The apk is available here.

ImpleLee commented 2 years ago

I am still optimistic about debugging :)

MikuAuahDark commented 2 years ago

This is real headache, as if CCloader.so is not seen by Android at all. I confirm the native libraries that are extracted in the lib folder doesn't contain CCloader.so yet it's there.

Also weirdly, if I decompile it through APKTool 2.6.1 and rebuilding it back without any modifications, then performing zipalign (which is useless since the libraries are compressed anyway) and signing it, the APK works, extracts CCloader.so as needed, and the game runs.

Here's my resulting re-compiled re-signed example (3).apk, remove the .zip

I don't think this is LOVE issue, but something to do with Android quirks.

As additional detail, when I try to sideload your APK through AVD, it uses streamed install whilst my APK uses incremental install.

ImpleLee commented 2 years ago

Well, I thought it was related to the version of apktool, but using apktool 2.6.1 does not make it work on my phone. I am now really confused. And the apk you give me does NOT work on my phone either. Android, what is wrong with you???

ImpleLee commented 2 years ago

Maybe you can try our game which is compiled from source (I mean the APK part) and see if that works... But I am waiting for our CI to build it for me though :(

MikuAuahDark commented 2 years ago

This time, I retested my APK with my Samsung Galaxy A52s running Android 12 and it installs successfully and works. I'd like to know the output when you sideload my APK into your phone through ADB, since ADB gives more information.

As a side note, I also tried to sideload your whole game but still the CCloader.so is not loaded. What about using the Android 11.4 way of packaging it with prebuilt trick? i.e. put <arch>/CCloader.so and this script as Android.mk in love-android/love/src/jni/lua-modules/ccloader:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := CCloader
LOCAL_MODULE_FILENAME := CCloader

LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/CCloader.so
include $(PREBUILT_SHARED_LIBRARY)
ImpleLee commented 2 years ago

It is still not loadable even though I rebuild everything from source during APK compilation.

The apk is here. You can compare that branch with current main branch (i.e. tag 11.4) to see what I changed; basically I only added CCloader and apk signing. As a reference, you can also find a compile-everything-from-source version of our game here, in which CCloader still cannot be loaded.

Both tests are performed on the same phone that I tested previous APKs. I am not familiar with ADB etc, so I don't have information from ADB. Maybe I can test with ADB sometime later.

ImpleLee commented 2 years ago

I tried to use ADB to get more information about the package, but I cannot figure out how to use ADB, neither did I find good tutorial to use it. Could you please give some detailed steps on how to use ADB to collect information about why the package cannot be loaded?

ravener commented 2 years ago

By the way, since your code is copying the error into clipboard, are you clearing the clipboard afterwards to notice the success? Just saying because it might've succeeded the next run but your clipboard could still contain the previous error run.

ImpleLee commented 2 years ago

By the way, since your code is copying the error into clipboard, are you clearing the clipboard afterwards to notice the success? Just saying because it might've succeeded the next run but your clipboard could still contain the previous error run.

Yes, basically I run the app, realize I need to clear my clipboard, clear it and run the app again.

To be honest, most of the time I run the app just twice or three times to check the library, because it is hard to believe that the same package can sometimes load and sometimes not. From a game developer's perspective, I would expect it to be loaded every time.

ImpleLee commented 2 years ago

ping @MikuAuahDark

MikuAuahDark commented 2 years ago

I'm aware of the current status. I currently don't have time to investigate.

ImpleLee commented 2 years ago

:-( Okay, I will wait

MikuAuahDark commented 2 years ago

After further investigation, I don't think there's much I can do beside blaming it to Android itself. I've tried various combination but my resulting app always loads the shared object properly, even with clean copy of the repository.

I think your best bet is to compile your library statically, thus embedding everything to liblove.so and modifying LOVE source code to register your library to package.preload using luax_preload. It may be not ideal depending on the program license but if Android doesn't want to load library that you compiled, then there's nothing I can do either as the CI and my locally compiled app loads shared objects fine.