Testing N64 emulation...

Greetings everyone !

Last week I ordered some new SDCard to test my new Tinkerboard, and use it on my new phone and… I got a mail from Amazon that the package went back to Amazon for no provided reasons…

So I ordered some new ones this week-end, hoping I get my STUFF THIS TIME, AMAZON !

Meanwhile, I put back the ugly reboot patch for Tinkerboard systems in my build scripts and, since no new kernel have been released this week, I thought about testing the new KMS interface of libSDL2 and give mupen64 another try !

Week-end tinkering

So, getting, compiling, installing and testing the new SDL2 libraries was very easy ! I’m amazed how fluid that went ! The tests worked perfectly and the KMS interface was used all along, without additional post-installation configuration. No Wayland and no X11 were used during the tests. Just pure OpenGL ES through KMS.
Nice !

The compilation went basically like this :

wget "https://www.libsdl.org/release/SDL2-2.0.7.tar.gz"
./configure --enable-video-opengles2 --disable-video-opengl --enable-video-kmsdrm --disable-video-mir --disable-mir-shared
make
make install
cd test
./configure
make
./testgles2

At that point I had a speedy turning colored cube on the screen, with some logs on the SSH session stating that the Mali drivers were being used.

Then I went to test mupen64plus, a Nintendo 64 emulator, and… The biggest issue with emulators projects is that they tend to be broken into little pieces and I never know which one I should use…

So, in order to get something working, I compiled the following projects :

With the following constants exported :

export USE_GLES=1
export NEON=1
export VFP_HARD=1
export VC=0
export NO_ASM=0

Each project must be compiled by going into the generated folder (mupen64plus-bla-bla-bla) and typing :

cd projects/unix
make all
make install

So, I then went to download some N64 ROM for testing purposes. I tried Mario 64 and… The beginning was kind of smooth but the demo with Bowser started to lag terribly when Bowser started to breath flames…

I saw some fragment shader linkage error in the logs, but I don’t know if these were relevant. I tried to fix the issue but it seems that the people who wrote the OpenGL ES shaders in the glide64mk2 renderer are trying to use gl_FragDepth in the fragment shader, which is not defined nor present in OpenGL ES 2.0.
The alternative gl_FragCoord.z being read only, I really don’t know how to fix that…

Still, removing this line and recompiling the renderer didn’t fix the performances issues so I thought about trying some basic Mali driver optimizations.
You can set up the Mali GPU to go full speed constantly by doing the following :

cd /sys/devices/platform/*.gpu/misc/mali0/device/devfreq/devfreq0
echo `cat available_frequencies | cut -f1 -d" "` > min_freq
echo 20 > polling_interval

Launching mupen64plus again with these optimizations gave a WAY smoother gameplay. There were still some serious slowdowns during the fire breathing scene, but not as much as before.

However, these optimizations generate some serious heat on the MiQi, so a good cooling device might be required.

Still, I can say that “it works”. It still requires a ton of optimizations and rework but… it works !

So here it is, with a little tinkering, you could play various emulators with decent speed on your RK3288 devices, using mainline kernels !

EDIT : My mupen64plus-core patch has been applied upstream and is therefore not needed anymore.

Assemble a native ARMv8 library, and call Android Java methods from its procedures invoked by an Android App, using the JNI conventions.

Purpose

This document demonstrates how to :

  • write functions (procedures), using the ARMv8 64-bit Gnu AS syntax, that will use the JNI library to call back Java functions from the Android app that called them
  • assemble these procedures into a native library, using the GNU Gold linker
  • write the code of the Android app that will call these native functions, and define the Java methods that will be called by the native functions called.

What will happen is that :

  • The Android app will be executed on an ARMv8 Android Phone/Tablet/Emulator
  • The Android app will call a native function in the native libraries we assembled.
  • The native function will build an UTF-16 Java String object, using the content defined inside the native library assembly script.
  • The native function will call back an Android app function, using the JNI library, and pass the UTF-16 String.
  • The Android app will then display the provided String on the screen.
  • The Android app function will then return.
  • The native function will also return.
  • And then the Android app will be left with the message on the screen, as long as it is running.

Conventions

The terms procedure and functions have the same meaning in this document. They are considered to be parameterized series of executable instructions. The term symbols refer to names stored in the libraries. If you create a library with a function named my_function or my_procedure, the library will contain the symbol my_function or my_procedure.

We assume that Android executes “Java” code, even if it’s not technically the case, since the same JNI conventions and the libraries are used.

Assumptions

This document assumes that :

  • You know how to create and install a basic Android application, either manually (gradlew) or with tools like Android Studio.
  • You know how to invoke a shell, or a command prompt, in a specific directory and execute commands in this shell or command prompt.
  • You have basic Java knowledge.

Requirements

In order to assemble the library and install the Android Application, you will need :

  • An Android SDK (Required by the Android application)
  • A cross-assembling GNU Sourceware AS that can interpret ARMv8 assembly and output ARMv8 machine code. (Required to generate the objects files from the assembly listings)
  • A cross-linking Gnu GOLD able to generate ARMv8 shared libraries. (Required to generate the native shared library)
  • An ARMv8 64-bits Android phone, tablet or emulator. (Required to run and test the produced application)

All the code written in this document is also on the following Git repository : https://github.com/Miouyouyou/ARMv8a-Call-Java-method-from-Assembly

Reminders

Calling Conventions

Native code calling conventions : ARMv8-A Application Procedure Call Standard

Now, in order make the logic of the following code clearer, I’ll restate some parts of the ARMv8-A 64 bits Application Procedure Call Standard (AAPCS), which is a convention that is followed by every compiler and manual assembler, to produce procedures in order that can cooperate smoothly. This document can be read here : http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf

x0..x7 means all the registers between x0 and x7, that is x0, x1, x2, x3, x4, x5, x6 and x7.

When following the AAPCS :

  • Procedures (aka functions) arguments are first passed in x0..x7. This means that you can pass up to 8 arguments through registers.
  • Procedures can return up to 8 values through the same registers, x0..x7. This means that these registers can and will often be overwritten by the procedures you call, in order to return values.
  • Procedures can use the registers x9..x15 to store temporary values, without second thought. This means that these registers can and will also be overwritten by the procedures you call.
  • Procedures must backup the registers between x19..x28 before overwriting their content. This means that these registers should be kept intact by the procedures you call.

So, if we need to save an argument for later use, x19..x28 is our best bet.

  • The content of x9..x15 will require a push/pop in the stack on every sub-procedure call, or else we might loose their contents.
  • We could also directly push/pop x0..x7 on every sub-procedure call, when we need to save/load their content again.
  • However x19..x28 only require one push/pop operation in an entire procedure, since every sub-procedure will save their contents by convention.

Java to Native code convention : Java Native Interface

Now, when trying to call native function from Java, in order to find the address of the native function to execute, the “JVM” will search in the loaded libraries, a symbol formed like this :
Java_Package_name_with_dots_replaced_by_underscores_Class_name_Native_function_name

So, for example, here :

  • our Package name is adventurers.decyphering.secrets.decyphapp
  • our Class name is DecypherActivity
  • our native function name is decypherArcaneSecrets

So the symbol looked up will be Java_adventurers_decyphering_secrets_decyphapp_DecypherActivity_decypherArcaneSecrets

  • The first two arguments that a native function will always receive are :
    • x0 ← The address of an address of a _JNIEnv structure, a.k.a. **JNINativeInterface or *JNIEnv in C.
      This *JNIEnv is passed as argument to almost every function provided through the JNINativeInterface structure, so we need to keep it until we’re done with the JNI functions.
    • (x1) ← A reference to the object instance calling this native method. We will use it to get a Method object, representing the method defined in the Android app to display the Java String built.

Native to Java method call convention : Java Native Interface

When looking for methods, using the native JNI library helpers, you will need to provide the signature of the name of the method, and its signature in the form of a descriptor.

The descriptor states :

  • The list of the method arguments types within parentheses.
  • The return thype.

The types are described using Java internal codes provided below, and also provided in the Java Native Interface documentation :

Java Type Internal Code
void V
boolean Z
byte B
char C
short S
int I
long J
float F
double D
Array [component_type_code
Class Lfully/qualified/name;

So for example, the Descriptor of our Java method void revealTheSecret(String) needs to be written like this (Ljava/lang/String;)V.

Note that the fully qualified name of the Java String object is java.lang.String .
The ; MUST be present, when describing classes
The [ IS NOT MATCHED with a closing bracket.
Generics are ignored. Meaning that Hashtable<String,String> is equivalent to Hashtable
Example : String[] a(Hashtable<String,Object>[]) descriptor is ([Ljava/util/Hashtable;)Ljava/lang/String;

Once you understand these parts, the logic of the following assembly code should be easier to understand.

The example

This example is heavily commented, in order to help people new to ARM Assembly, or the Assembly “language” in general.

So the point of the library assembled in this tutorial is to provide a procedure, that will generate a Java String object containing a specific text defined in the assembly listing. The text itself in encoded in UTF-16. The text contained in this Java String will then be passed to a Java function defined in the Android Application which will display the provided String on the screen, using appropriate Android SDK Widgets.

So, in simple terms, we need to :

  • Generate a Java String using our secret data.
  • Get the method void revealTheSecret(String) from our calling object.
  • Call it with the String we generated.

To do this, we will use the following functions available from _JNIEnv :

The Assembly listing

decypherArcane.s

.data
java_method_name:
	.asciz "revealTheSecret"
java_method_signature:
	.asciz "(Ljava/lang/String;)V"

// Our UTF16-LE encoded secret message
secret:
	.hword 55357, 56892, 85, 110, 32, 99, 104, 97, 116, 10
	.hword 55357, 56377, 12495, 12512, 12473, 12479, 12540, 10
	.hword 55357, 56360, 27193, 29066, 10
	.hword 55357, 56445, 65, 110, 32, 97, 108, 105, 101, 110, 10
secret_len = (. - secret) / 2

.text
.align 2
.globl Java_adventurers_decyphering_secrets_decyphapp_DecypherActivity_decypherArcaneSecrets
.type Java_adventurers_decyphering_secrets_decyphapp_DecypherActivity_decypherArcaneSecrets, %function
Java_adventurers_decyphering_secrets_decyphapp_DecypherActivity_decypherArcaneSecrets:
	sub sp, sp, 48 // Prepare to push x19, x20, x21, x22 and lr (x30)
	               // 5 registers of 8 bytes each -> 40 bytes
	               // Unless you like to deal with corner cases, you'll
	               // have to keep the stack aligned on 16 bytes.
	               // 40 % 16 != 0 but 48 % 16 == 0, so we use 48 bytes.
	stp x19, x20, [sp]
	stp x21, x22, [sp, 16]
	stp x23, x30, [sp, 32]

	// Passed parameters - x0 : *_JNIEnv, x1 : thisObject

	mov x19, x0   // x19 <- Backup of *JNIEnv as we'll use it very often
	mov x20, x1   // x20 <- Backup of thisObject as we'll invoke methods on it
	ldr x21, [x0] // x21 <- Backup of *_JNINativeInterface, located at *_JNIEnv,
	              //      since we'll also use it a lot

	/* Preparing to call NewString(*_JNIEnv : x0, 
	                     *string_characters : x1, 
	                          string_length : x2).
	   *_JNIEnv is still in x0.
	*/

	adr x1, secret       // x1 <- *secret : The UTF16-LE characters composing 
	                     //                 the java.lang.String we'll pass to
	                     //                 the Java method called afterwards.
	mov x2, #secret_len  // x2 <- secret_len : The length of that java.lang.String
	ldr x3, [x21, #1304] // x3 <- *JNINativeInterface->NewString function. 
	                     // +1304 is NewString's offset in the JNINativeInterface
	                     // structure.
	blr x3               // secret_java_string : x0 <- NewString(*_JNIEnv : x0, 
	                     //                                       *secret : x1,
	                     //                                    secret_len : x2)

	mov x22, x0          // x22 <- secret_java_string
	                     // Keep the returned string for later use

	/* Calling showText(java.lang.String) through the JNI
	
	   First : We need the class of thisObject. We could pass it directly
	   to the procedure but, for learning purposes, we'll use JNI methods
	   to get it.
	*/

	// Preparing to call GetObjectClass(*_JNIEnv : x0, thisObject : x1)
	mov x0, x19         // x0 <- *_JNIEnv (previously saved in x19)
	mov x1, x20         // x1 <- thisObject (previously saved in x20)
	ldr x2, [x21, #248] // x2 <- Get *JNINativeInterface->GetObjectClass (*JNINativeInterface+248)
	blr x2              // jclass : x0 <- GetObjectClass(*JNIEnv : x0, 
	                    //                            thisObject : x1)
	/* Second : We need the JNI ID of the method we want to call
	   Preparing for GetMethodId(*JNIEnv : x0, 
	                              jclass : x1, 
	                         method_name : x2, 
	                    method_signature : x3)
	*/

	mov x1, x0  // x1 <- jclass returned by GetObjectClass
	mov x0, x19 // x0 <- *JNIEnv, previously backed up in x19
	adr x2, java_method_name      // x2 <- &java_method_name : The method name
	adr x3, java_method_signature // x3 <- &java_method_signature : The method signature
	ldr x4, [x21, #264]           // Get *JNINativeInterface->GetMethodId (+264)

	blr x4     // revealTheSecretID : x0 <- GetMethodId(*_JNIEnv : x0, 
	           //                                         jclass : x1, 
	           //                                   &method_name : x2, 
	           //                              &method_signature : x3)

	// Finally : Call the method. Since it's a method returning void, 
	// we'll use CallVoidMethod.
	// Preparing to call CallVoidMethod(*_JNIEnv : x0, 
	//                                thisObject : x1,
	//                         revealTheSecretID : x2,
	//                             secret_string : x3)

	mov x2, x0          // x2 <- revealTheSecretID
	mov x1, x20         // x1 <- thisObject (previously saved in x20)
	mov x0, x19         // x0 <- *_JNIEnv (previously saved in x19)
	mov x3, x22         // x3 <- secret_java_string (previously saved in x22)
	ldr x4, [x21, #488] // x4 <- *_JNINativeInterface->CallVoidMethod (+488).
	blr x4 // CallVoidMethod(*_JNIEnv : x0, 
	       //              thisObject : x1,
	       //       revealTheSecretID : x2,
	       //              the_string : x3)
	       // => Java : revealTheSecret(the_string)

	ldp x19, x20, [sp]
	ldp x21, x22, [sp, 16]
	ldp x23, x30, [sp, 32]
	add sp, sp, 48
	ret

Then assemble and link this example library :

export PREFIX="aarch64-linux-gnu-"
$PREFIX-as -o decypherArcane.o decypherArcane.S
$PREFIX-ld.gold -shared --dynamic-linker=/system/bin/linker --hash-style=sysv -o libarcane.so decypherArcane.o

Calling this from Android

Now that our library is assembled, we’ll just need to create a class that will define :

  • The Java function that display a provided String on the screen, using the Android SDK Widgets.
    This will be the function that will be called by our native procedure, so we need to keep the same name in both the Java code and the native procedure.
  • The name of the native procedure that will be called by the Android App.
  • Load our library containing this native procedure in memory using loadLibrary.
  • Call the native procedure from our App.

So, to do that we’ll generate a project with :

  • the right package name : adventurers.decyphering.secrets.decyphapp
  • an activity named DecypherActivity using this package name defining :
    • a Java method public void revealTheSecret(String text) that will display the provided String, using a TextView here.
    • a native void decypherArcaneSecrets() function.

DecypherActivity.java

package adventurers.decyphering.secrets.decyphapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class DecypherActivity extends AppCompatActivity {

	static { System.loadLibrary("arcane"); }
	native void decypherArcaneSecrets();

	TextView mContentView;

	public void revealTheSecret(String text) {
		mContentView.setText(text);
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_decypher);

		mContentView = (TextView) findViewById(R.id.fullscreen_content);
		decypherArcaneSecrets();
	}
}

Here’s the XML of the interface used by this Activity

activity_decypher.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="#0099cc"
   tools:context="adventurers.decyphering.secrets.decyphapp.DecypherActivity"
  >

  <!-- The primary full-screen view. This can be replaced with whatever view
  is needed to present your content, e.g. VideoView, SurfaceView,
  TextureView, etc. -->
  <TextView
   android:id="@+id/fullscreen_content"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:gravity="center"
   android:keepScreenOn="true"
   android:textColor="#33b5e5"
   android:textSize="50sp"
   android:textStyle="bold"
   />

</FrameLayout>

Installation

Create a directory named jniLibs in $YourProjectRootFolder/app/src/main if it doesn’t exist

Then create a directory named arm64-v8 in it. Once done, the following path should exist :

$YourProjectRootFolder/app/src/main/jniLibs/armv8-64

Copy the previously assembled libarcane.so in that folder.

Then install the app on your phone, using the standard installation procedure. That is, either :

  • ./gradlew installDebug from the root folder of your Android App
  • The Run button of Android Studio

Then run it on your select terminal (phone, tablet, emulator) and you should see something like this :

Screenshot of the running app on a Huawei Honor 9

Special notes

Getting the Descriptor of a Java method

Even with the whole format provided, finding the Descriptor of a method can be difficult at first. Fortunately, there are two ways to handle this issue.

Using javap

One method is to create a simple Java project and define a Java function using the same signature (same return type, same name, same parameters types). Just provide a minimalist useless implementation, compile your project and use javap on the class containing your method, like this :

javap -s your/package/name/Class

For example, if your class :

  • was compiled from a file named Filename.java
  • contained a function declared like this : void a(int a, long b, String c, HashMap[] d, boolean e)

This should output something like this :

Compiled from "Filename.java"

    ...

    void a(int, long, java.lang.String, java.util.HashMap[], boolean);

        descriptor: (IJLjava/lang/String;[Ljava/util/HashMap;Z)V

    ...

Using Java reflect methods

Now, generating sample Java projects (or test files), just to copy-paste a function, compile it and analyse it through javap can be cumbersome. So another way is to simply use Java.lang.reflect methods, from your application, to get the signatures of every declared function in a Class and rebuild the appropriate Descriptor of these methods manually.

Here’s a sample code that help you do that

package your.package.name;

import java.lang.reflect.Method;
import java.util.HashMap;

import android.util.Log;

public class MethodsHelpers {

  static public HashMap<Class, String> primitive_types_codes;
  static public String LOG_TAG = "MY_APP";

  static {
    primitive_types_codes = new HashMap<Class,String>();
    primitive_types_codes.put(void.class,    "V");
    primitive_types_codes.put(boolean.class, "Z");
    primitive_types_codes.put(byte.class,    "B");
    primitive_types_codes.put(short.class,   "S");
    primitive_types_codes.put(char.class,    "C");
    primitive_types_codes.put(int.class,     "I");
    primitive_types_codes.put(long.class,    "J");
    primitive_types_codes.put(float.class,   "F");
    primitive_types_codes.put(double.class,  "D");
  }

  public static String code_of(final Class class_object) {
    final StringBuilder class_name_builder = new StringBuilder(20);
    Class component_class = class_object;
    while (component_class.isArray()) {
      class_name_builder.append("[");
      component_class = component_class.getComponentType();
    }
    if (component_class.isPrimitive())
      class_name_builder.append(primitive_types_codes.get(component_class));
    else {
      class_name_builder.append("L");
      class_name_builder.append(
        component_class.getCanonicalName().replace(".", "/")
      );
      class_name_builder.append(";");
    }
    return class_name_builder.toString();
  }

  public static void print_methods_descriptors_of(Class analysed_class) {
    StringBuilder descriptor_builder = new StringBuilder(32);
    Method[] methods = analysed_class.getDeclaredMethods();
    for (Method meth : methods) {
      descriptor_builder.append("(");

      for (Class param_class : meth.getParameterTypes())
        descriptor_builder.append(code_of(param_class));

      descriptor_builder.append(")");

      descriptor_builder.append(code_of(meth.getReturnType()));

      Log.d(LOG_TAG,
            String.format("%s\n"+
                          "Name       : %s\n"+
                          "Descriptor : %s\n\n",
                          meth.toString(),
                          meth.getName(),
                          descriptor_builder.toString())
      );

      descriptor_builder.delete(0, descriptor_builder.length());
    }
  }

}

Just use it like this :

import static your_package_name.MethodHelpers.print_methods_descriptors_of;

...

print_methods_descriptors_of(AnalysedClass.class);

And then you should see something like on the output :

    D/MY_APP  (22564): void your.package.name.a(int,long,java.lang.String,java.util.HashMap[],boolean)

    D/MY_APP  (22564): Name       : a

    D/MY_APP  (22564): Descriptor : (IJLjava/lang/String;[Ljava/util/HashMap;Z)V