Взаимодействие Java и… Ассемблера?

в 9:13, , рубрики: java, ассемблер, метки: ,

В Java существует возможность использования программного кода, реализованного на других языках программирования, так называемый JNI. Можно написать динамически линкуемую библиотеку, затем загрузить ее в Java-коде и использовать функции оттуда, объявив их как native методы загрузившего ее класса. JNI создавался в первую очередь для того, чтобы выполнять машинно-зависимые действия (а также, возможно, улучшить производительность критических по скорости частей приложения) на C/C++, но никто не мешает нам написать библиотеку и на ассемблере.

Инструментарий

Для создания сего извращения применялись Windows, Oracle JDK, Yasm (без макросов) и линковщик из Microsoft Visual Studio 2010. При желании, заменить любой из этих компонентов на ваш любимый не составит большого труда, я старался не использовать всякие нестандартные фичи.

Использование JNI

Для начала, создадим на Java класс, в котором будет вызываться функция из будущей .dll'ки:

public class TestJNI {    
    native static int sum(int x, int y);    // импортируемая функция sum
    
    public static void main(String[] args) {
        System.loadLibrary("mydll");        // загружаем библиотеку mydll.dll
        System.out.println(sum(2, 3));      // вызываем функцию
    }
}

Name Mangling в Java

Теперь нужно узнать, какое имя функции рассчитывает найти в нашей библиотеке Java-машина. Воспользуемся для этого программой javah из JDK:

javac TestJNI.java
javah TestJNI

При этом будет сгенерирован C++ header:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJNI */

#ifndef _Included_TestJNI
#define _Included_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     TestJNI
 * Method:    sum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_TestJNI_sum
  (JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

Информация, содержащаяся здесь, нужна главным образом для дальнейшего написания библиотеки на C++, нас же интересует только сигнатура функции:

JNIEXPORT jint JNICALL Java_TestJNI_sum
  (JNIEnv *, jclass, jint, jint);

Мы видим, что в dll функция должна иметь имя JNICALL Java_TestJNI_sum и принимать 4 параметра. Для простейших функций первые два из них нам не понадобятся. Всякие странные типы данных вроде JNIEXPORT, как несложно догадаться, объявлены в файле jni.h, который входит в состав JDK.

Ассемблер

Напишем теперь библиотеку:

;mydll.asm
section .text

global Java_TestJNI_sum
    
Java_TestJNI_sum:
    mov eax, [esp + 12] ; игнорируем первые 2 параметра
    add eax, [esp + 16]
    ret 16
    
end
;mydll.def
LIBRARY mydll
EXPORTS Java_TestJNI_sum

Скомпилируем и слинкуем:
yasm -f win32 mydll.asm
link /SUBSYSTEM:windows /DLL /NOENTRY /DEF:mydll.def mydll.obj

На выходе получим .dll-файл, который и нужно передавать коду на Java.

Результат

Положим файлы mydll.dll и TestJNI.class в одну папку и посмотрим, что получилось:

>java TestJNI
5

Это победа, мы научились складывать два числа!

Если вдруг кому-то стало интересно, то ему пригодятся следующие ссылки:

Автор: sementry

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js