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

Add more dummy methods to the Android launcher #1000

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

makingthematrix
Copy link

Issue

This is a follow up to #971 and #972.

I made a minimal Android app with FXGL, the game engine which uses JavaFX. It turns out that GraalVM Native Image
adds many more unresolved references when compiling FXGL. I added the dummy methods for them to launcher.c.

With these changes my minimal app works but I wonder if this is a good approach. All the methods I added have
something to do with image handling. What if, when I start to write a more complex app, GraalVM Native Image will
start adding even more references?

Here's my app: https://github.com/makingthematrix/scalaonandroid/tree/main/HelloFXGL

Progress

This is a follow up to gluonhq#971 and gluonhq#972.

I made a minimal Android app with FXGL, the game engine which uses JavaFX. It turns out that GraalVM Native Image
adds many more unresolved references when compiling FXGL. I added the dummy methods for them to launcher.c.

With these changes my minimal app works  but I wonder if this is a good approach. All the methods I added have
something to do with image handling. What if, when I start to write a more complex app, GraalVM Native Image will
start adding even more references?

Here's my app: https://github.com/makingthematrix/scalaonandroid/tree/main/HelloFXGL
@makingthematrix
Copy link
Author

Could someone take a look and give me some feedback? :)

@johanvos
Copy link
Contributor

It's a complex issue, and your analysis is correct: what if there are more references? We will end up with dummy methods for the AWT libs in the launchers.
So the question we need to answer first is: why are there references to those methods? Based on your sample, we should be able to find that: running native-image with verbose creates a report with the usage-tree. If you add the true parameter in the of the plugin, that parameter will be passed to maven. Next, you can search in the usage-tree report why those references are called (that is, you'll find the trace towards the code that invokes them). That would be interesting to find out.

@makingthematrix
Copy link
Author

@johanvos
I generated reports for Android and, for comparison, also for a desktop Linux app, which works. Here are the zip files:

  1. Android
  2. Desktop

I'm looking through them right now. I don't really know what to search for, to be honest, but I see the call to the methods I put in the commit have one thing in common. They are all followed by a similar sequence of calls to methods in com.oracle.svm.jni.JNIGeneratedMethodSupport and each line ends with @bci=-6->-6 (on other lines @bci is just one positive integer).
These lines also appear in the desktop report but there they are sometimes followed by more calls with normal @bci number.

For example, this is the call tree for com.sun.imageio.plugins.jpeg.JPEGImageReader.initReaderIDs on Android:

1370611 ├── entry com.sun.imageio.plugins.jpeg.JPEGImageReader.<clinit>():void id=1487 
1370612 │   ├── directly calls com.sun.imageio.plugins.jpeg.JPEGImageReader$1.<init>():void id=3946 @bci=4 
1370613 │   ├── directly calls com.sun.imageio.plugins.jpeg.JPEGImageReader.initReaderIDs(java.lang.Class, java.lang.Class, java.lang.Class):void id=3947 @bci=19 
1370614 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.boxObjectInLocalHandle(java.lang.Object):com.oracle.svm.jni.nativeapi.JNIObjectHandle id-ref=2850 @bci=-6->-6 
1370615 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.boxObjectInLocalHandle(java.lang.Object):com.oracle.svm.jni.nativeapi.JNIObjectHandle id-ref=2850 @bci=-6->-6 
1370616 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.boxObjectInLocalHandle(java.lang.Object):com.oracle.svm.jni.nativeapi.JNIObjectHandle id-ref=2850 @bci=-6->-6 
1370617 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.boxObjectInLocalHandle(java.lang.Object):com.oracle.svm.jni.nativeapi.JNIObjectHandle id-ref=2850 @bci=-6->-6 
1370618 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.environment():com.oracle.svm.jni.nativeapi.JNIEnvironment id-ref=5432 @bci=-6->-6 
1370619 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.nativeCallEpilogue(int):void id-ref=5434 @bci=-6->-6 
1370620 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.nativeCallPrologue():int id-ref=5435 @bci=-6->-6 
1370621 │   │   └── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.rethrowPendingException():void id-ref=5436 @bci=-6->-6 

And this is the same thing for Desktop:

1690671 ├── entry com.sun.imageio.plugins.jpeg.JPEGImageReader.<clinit>():void id=1999 
1690672 │   ├── directly calls com.sun.imageio.plugins.jpeg.JPEGImageReader$1.<init>():void id=4600 @bci=4 
1690673 │   ├── directly calls com.sun.imageio.plugins.jpeg.JPEGImageReader.initReaderIDs(java.lang.Class, java.lang.Class, java.lang.Class):void id=4601 @bci=19 
1690674 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.boxObjectInLocalHandle(java.lang.Object):com.oracle.svm.jni.nativeapi.JNIObjectHandle id-ref=3368 @bci=-6->-6 
1690675 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.boxObjectInLocalHandle(java.lang.Object):com.oracle.svm.jni.nativeapi.JNIObjectHandle id-ref=3368 @bci=-6->-6 
1690676 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.boxObjectInLocalHandle(java.lang.Object):com.oracle.svm.jni.nativeapi.JNIObjectHandle id-ref=3368 @bci=-6->-6 
1690677 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.boxObjectInLocalHandle(java.lang.Object):com.oracle.svm.jni.nativeapi.JNIObjectHandle id-ref=3368 @bci=-6->-6 
1690678 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.environment():com.oracle.svm.jni.nativeapi.JNIEnvironment id=19736 @bci=-6->-6 
1690679 │   │   │   └── directly calls com.oracle.svm.jni.JNIThreadLocalEnvironment.getAddress():com.oracle.svm.jni.nativeapi.JNIEnvironment id-ref=9814 @bci=0 
1690680 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.nativeCallEpilogue(int):void id=19737 @bci=-6->-6 
1690681 │   │   │   └── directly calls com.oracle.svm.jni.JNIObjectHandles.popLocalFramesIncluding(int):void id=28523 @bci=1 
1690682 │   │   │       ├── directly calls com.oracle.svm.core.handles.ThreadLocalHandles.popFramesIncluding(int):void id-ref=30770 @bci=4 
1690683 │   │   │       └── directly calls com.oracle.svm.jni.JNIObjectHandles.getExistingLocals():com.oracle.svm.core.handles.ThreadLocalHandles id-ref=23262 @bci=0 
1690684 │   │   ├── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.nativeCallPrologue():int id=19738 @bci=-6->-6 
1690685 │   │   │   └── directly calls com.oracle.svm.jni.JNIObjectHandles.pushLocalFrame(int):int id-ref=9784 @bci=2 
1690686 │   │   └── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.rethrowPendingException():void id=19739 @bci=-6->-6 
1690687 │   │       └── directly calls com.oracle.svm.jni.JNIGeneratedMethodSupport.getAndClearPendingException():java.lang.Throwable id-ref=3369 @bci=0 

Does it make any sense?

By the way, if I revert to GraalVM 20.x and the old maven-client-plugin instead of gluonfx-maven-plugin, the app works on Android. Maybe it's really a GraalVM issue?

@johanvos
Copy link
Contributor

johanvos commented Nov 2, 2021

That last issue is very relevant. Once JNI_OnLoad_javajpeg is found (and required), the other functions are probably linked into the executable, so you don't have to add them to the android-specific implementations.
Hence, we need to provide an option to pull in JNI_OnLoad_javajpeg.

@johanvos
Copy link
Contributor

In general, when some native functions are required, they have to be provided. Dummy methods don't really provide the implementation, so they are not a solution (but an easy hack, admitted).
The problem here comes from the java.desktop module which has com.sun.imageio.plugins.jpeg.JPEGImageReader and which will pull in a bunch of native functions. There are actually 2 scenario's:

  1. JPEGImageReader is required, and in that case the native functions are required as well. Dummy implementations will fail.
  2. JPEGImageReader is not required. In that case, it shouldn't show up in the analysis.

From the treedump, it seems that JPEGImageReader is required, but if the dummy methods are sufficient, it is very unlikely that they are ever invoked. So JPEGImageReader should not be required. Can you find out why JPEGImageReader is in the tree?

The slightly more elegant approach (instead of adding those methods to the launcher) is to use a separate file with empty methods. That is something we can do in Substrate: check for files in a given path, and compile them as well.

@johanvos
Copy link
Contributor

Fixed in metadata

@johanvos johanvos closed this Jan 25, 2022
@johanvos johanvos reopened this Jan 25, 2022
@johanvos
Copy link
Contributor

accidentally closed this

@ennerf
Copy link
Contributor

ennerf commented Jun 10, 2022

I ran into this issue as well and was also missing several other methods. I haven't figured out where they are coming from yet, but replacing them with stubs worked and none of them were ever called. For anyone in a similar situation, the missing stubs can be generated with

nm target/gluonfx/aarch64-android/lib*.so | \
    grep "U Java_" | \
    awk '{print "void " $2 "() {\n    fprintf(stderr, \"We should never reach here ("$2")\\n\");\n}\n"}'

I didn't see any way to add custom sources to the build step, so I ended up modifying the jar's native/android/launcher.c file.

Full list of missing references

nm target/gluonfx/aarch64-android/lib*.so | grep "U Java_" | awk '{print $2}'

Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_clearNativeReadAbortFlag
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_disposeReader
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_initJPEGImageReader
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_initReaderIDs
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_resetLibraryState
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_resetReader
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_setOutColorSpace
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_setSource
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_disposeWriter
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_initJPEGImageWriter
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_initWriterIDs
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_resetWriter
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_setDest
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables
Java_java_awt_image_BufferedImage_initIDs
Java_java_awt_image_ColorModel_initIDs
Java_java_awt_image_IndexColorModel_initIDs
Java_java_awt_image_Raster_initIDs
Java_java_awt_image_SampleModel_initIDs
Java_java_awt_image_SinglePixelPackedSampleModel_initIDs
Java_sun_awt_FontDescriptor_initIDs
Java_sun_awt_PlatformFont_initIDs
Java_sun_awt_image_BufImgSurfaceData_initIDs
Java_sun_awt_image_BufImgSurfaceData_initRaster
Java_sun_awt_image_ByteComponentRaster_initIDs
Java_sun_awt_image_BytePackedRaster_initIDs
Java_sun_awt_image_IntegerComponentRaster_initIDs
Java_sun_awt_image_ShortComponentRaster_initIDs
Java_sun_font_StrikeCache_getGlyphCacheDescription
Java_sun_font_SunFontManager_initIDs
Java_sun_java2d_DefaultDisposerRecord_invokeNativeDispose
Java_sun_java2d_Disposer_initIDs
Java_sun_java2d_SurfaceData_initIDs
Java_sun_java2d_SurfaceData_isOpaqueGray
Java_sun_java2d_cmm_lcms_LCMS_colorConvert
Java_sun_java2d_cmm_lcms_LCMS_createNativeTransform
Java_sun_java2d_cmm_lcms_LCMS_getProfileDataNative
Java_sun_java2d_cmm_lcms_LCMS_getProfileID
Java_sun_java2d_cmm_lcms_LCMS_getTagNative
Java_sun_java2d_cmm_lcms_LCMS_initLCMS
Java_sun_java2d_cmm_lcms_LCMS_loadProfileNative
Java_sun_java2d_loops_Blit_Blit
Java_sun_java2d_loops_GraphicsPrimitiveMgr_initIDs
Java_sun_java2d_loops_GraphicsPrimitiveMgr_registerNativeLoops
Java_sun_java2d_loops_MaskBlit_MaskBlit
Java_sun_java2d_pipe_Region_initIDs
Java_sun_java2d_pipe_ShapeSpanIterator_initIDs
Java_sun_java2d_pipe_SpanClipRenderer_initIDs

The slightly more elegant approach (instead of adding those methods to the launcher) is to use a separate file with empty methods. That is something we can do in Substrate: check for files in a given path, and compile them as well.

+1

@jperedadnr
Copy link
Contributor

@ennerf After #1103, you can also create a c file with all the missing methods that you need, like:

missing_symbols.c:

#include <stdlib.h>

void JNI_OnLoad_javajpeg() {
    fprintf(stderr, "We should never reach here (JNI_OnLoad_javajpeg)\n");
}
... 

then you can compile for Android:

$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -c -target aarch64-linux-android -I. missing_symbols.c 

and finally you can use linkerArgs to include it to the link process:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>${gluonfx.maven.version}</version>
    <configuration>
       <linkerArgs>
          <arg>/path/to/missing_symbols.o</arg>
       </linkerArgs> 
...

@FlorianKirmaier
Copy link

For the record, I ran into the same issue as #377 . The problem was, that I had ScenicView in the classpath.
Maybe this hint helps someone else running into the same issue.

@ennerf
Copy link
Contributor

ennerf commented Jul 15, 2022

It looks like JAX-B pulls in a lot of dependencies as well oracle/graal#3225

@H-Lo
Copy link

H-Lo commented Jun 2, 2023

Same issue here! Using graalvm-svm-java17-linux-gluon-22.1.0.1-Final on Ubuntu 22 LTS

https://stackoverflow.com/questions/76393159/dlopen-failed-cannot-locate-symbol-jni-onload-javajpeg

@FDelporte
Copy link

FDelporte commented Dec 7, 2023

I have the same problem with a basic FXGL demo application:
https://github.com/FDelporte/JavaFXGameSnakeApp

In the Google Play Store reports, several identical error messages are shown:

java.lang.UnsatisfiedLinkError: dlopen failed: 
cannot locate symbol "JNI_OnLoad_javajpeg" referenced by
"/data/app/~~mfcP9UKk5v7YB8oaYdyKAQ==/be.webtechie.emojisnakegameapp-xf_z_-5-dleGGUxh5ExT9A==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libsubstrate.so"...

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

Successfully merging this pull request may close these issues.

7 participants