Java-C-Assembly Matryoshka

Written by krossovochkin | Published 2019/06/02
Tech Story Tags: java | c | c-programming | assembly | matryoshka | latest-tech-stories | assembly-matryoshka | software-programming

TLDR Java-C-Assembly Matryoshka is possible to “connect” java to C (via Java-Native-Interface or JNI) to speed up some critical pieces of code. In real world there is no advantage of such delegation as it won’t speed up anything. The example we’ll look at will be: given a command line program (written in Java) We execute the program with providing 2 integers as arguments (with error handling on the client side) The main business logic is the “sum” method, which we consider a critical piece of the program.via the TL;DR App

Disclaimers:
I’ll use Windows and more particularly Visual C++ with its Inline Assembler. If you use MacOs or Linux you will have significant differences comparing to what is described in the article.
Everything below is shown mostly for demonstration purposes

Introduction

Java is mature self-sufficient language, though as we all know it is possible to “connect” java to C (via Java-Native-Interface or JNI) to speed up some critical pieces of code.
Also for C/C++ it is possible to delegate some even more critical pieces of code directly to Assembly.
In this article I want to show you how this Java-C-Assembly Matryoshka can look like. But note that example will be pretty simple so in real world there is no advantage of such delegation as it won’t speed up anything.
The example we’ll look at will be:
  • given a command line program (written in Java)
  • we execute the program with providing 2 integers as arguments (with error handling on the client side)
  • main business logic is the “sum” method, which we consider a critical piece of the program we’d like to “speed up” using C and Assembly

Java

Setup
First of all we need to download JDK.
I have pretty old version installed, but feel free to install newer version.
After installation verify that everything works:
>java -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) Client VM (build 25.121-b13, mixed mode, sharing)
Code
Here is our program: main function, parsing command line arguments and our target method “sum” (with implementation in java):
public class Test {
 
    public static void main(String[] args) {
  
        if (args.length != 2) {
            System.out.println("Error: wrong params count");
            return;
        }
  
        int a;
        try {
            a = Integer.parseInt(args[0]);
        } catch (Throwable throwable) {
            System.out.println("First param is not a number");
            return;
        }
  
        int b;
        try {
            b = Integer.parseInt(args[1]);
        } catch (Throwable throwable) {
            System.out.println("Second param is not a number");
            return;
        }
  
        Test test = new Test();
        System.out.println(test.sum(a, b));
    }
 
    public static int sum(int a, int b) {
        return a + b;
    }
}
Compile
In order to run the program we first need to compile it with java compiler. It will generate 
Test.class
 binary file which we’ll later on execute.
> javac Test.java
Execute
To execute program call java and provide arguments. See that our program works correctly and prints sum of numbers.
> java Test 3 4
7

C/JNI

Setup
Install Chocolatey and using it install Visual C++ build tools:
choco install visualcpp-build-tools
We’ll need these tools to compile C files into library dll file. Specifically for compilation we’ll need 
cl
 command, so check that it works:
>cl
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27031.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

Code

Java can communicate with C via JNI. In order to setup that communication we need to update our Java program:
public class Test {
 
    public static void main(String[] args) {
  
        if (args.length != 2) {
            System.out.println("Error: wrong params count");
            return;
        }
  
        int a;
        try {
            a = Integer.parseInt(args[0]);
        } catch (Throwable throwable) {
            System.out.println("First param is not a number");
            return;
        }
  
        int b;
        try {
            b = Integer.parseInt(args[1]);
        } catch (Throwable throwable) {
            System.out.println("Second param is not a number");
            return;
        }
        System.loadLibrary("Test");
        Test test = new Test();
        System.out.println(test.sum(a, b));
    }
 
    public native int sum(int a, int b);
}
First, instead of static method with implementation we provide so called native method. It doesn’t have any implementation because we expect it to be provided via JNI. Second, we need to load our C library — and we do that with 
System.loadLibrary
 method.
After we updated our program we need to generate Test.h header file:
> javah Test
Generated header file will contain all the setup for our C program. We had one native method in our Java program and here we have method declaration for our method generated in header file:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Test */
#ifndef _Included_Test
#define _Included_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Test
 * Method:    sum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_Test_sum
  (JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
We need to have an implementation of our function, so create Test.c file and implement method:
#include "Test.h"
JNIEXPORT jint JNICALL Java_Test_sum
  (JNIEnv *env, jobject obj, jint a, jint b) {
   return a + b;
  }
void main() {}

Compile

Compile our C library into Test.dll:
> cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD Test.c -FeTest.dll

Execute

Recompile our Java program as shown above and execute to see that it still works:
> java Test 3 4
7

Assembly

Code

Previously we had C implementation which looked basically as on java: 
a + b
. When we work with Assembly we work on a lower level so such small operations require quite more code.
Let’s update our C program to use Assembly — for this we add
 __asm block
— which is Inline Assembler for Visual C++.
Inside that block we write instructions. You see that we put our variable a into register 
eax
, put our variable 
b
 into register 
ebx
. Then we make a sum from contents of registers and store it in the 
eax
 register (this is what add command does).
Lastly we store value of the 
eax
 register in our result field:
#include "Test.h"
#include <stdio.h>
JNIEXPORT jint JNICALL Java_Test_sum
  (JNIEnv *env, jobject obj, jint a, jint b) {
    int result;
    __asm {
        mov eax, a
        mov ebx, b
        add eax, ebx
        mov result, eax
    }
    return result;
}
void main() {}
Recompile C library (no need to recompile Java program) and execute Java program again:
> java Test 3 4
7
So, it works.
Hope you’ve enjoyed and maybe learned something today. If not then I hope at least it was funny.
Happy coding!
References

Written by krossovochkin | .
Published by HackerNoon on 2019/06/02