Как известно, на любом языке можно писать, как на Java, а первая любовь джависта — это написание Garbage Collectors и JIT Compilers. С этим связано множество восхитительных вопросов, например: каким образом можно из управляемого кода напрямую работать с машинным кодом и ассемблером?


Кроме того, в этой статье будет небольшой пример на C#. В какой-то момент стало понятно, что нельзя всегда изучать одну Java. Рантаймы динамических языков используют общую теорию и на практике работают в рамках похожих проблем. Самый простой способ продвинуть свою работу — посмотреть, как там у соседей, и скопировать себе что-нибудь хорошее.


Теперь про ассемблер и машинный код. Зачем это нужно — вопрос открытый. Например, вы наслушались о Meltdown и хотите написать для него красивое API :-) Ну, и не надо забывать, что Oracle — не боги, поддержку того же AVX-512 добавили только в Девятке, прямое управление аппаратной транзакционной памятью не ложится на язык, часть стандартных методов можно реализовать лучше, чем это сделали в SDK и т.п. — у нас всегда есть с чем покопаться!


В проблеме есть два уровня:


  • нельзя напрямую заинлайнить машинный код или x86-ассемблер в Java-коде (вставить конструкцию __asm())
  • нельзя напрямую выполнить их (впрочем, при отсутствии синтаксиса, до этой проблемы добраться сложновато)

Но можно проэксплуатировать какие-то хаки, о чём и будет далее.


Наивный вариант: а возьмём и запустим!


Как известно, можно запускать нативный код с помощью JNI.
Значит, мы в C++ можем спокойно динамически погенерить машкод и потом дёрнуть его.


Для этого надо создать сегмент памяти, доступный на запись и исполнение одновременно.


Вот пример для Windows с VirtualAllocEx (в posix есть mprotect(2), но мне лень).
Попробуйте понять, что он делает. Если не будет PAGE_EXECUTE_READWRITE, то этот код тут же прибьёт Data Execution Prevention в винде.


#include <stdio.h>
#include <windows.h>

typedef unsigned char byte;

int arg1;
int arg2;
int res1;

typedef void (*pfunc)(void);

union funcptr {
  pfunc x;
  byte* y;
};

int main( void ) {

  byte* buf = (byte*)VirtualAllocEx( GetCurrentProcess(), 0, 1<<16, MEM_COMMIT, PAGE_EXECUTE_READWRITE );

  if( buf==0 ) return 0;

  byte* p = buf;

  *p++ = 0x50; // push eax
  *p++ = 0x52; // push edx

  *p++ = 0xA1; // mov eax, [arg2]
  (int*&)p[0] = &arg2; p+=sizeof(int*);

  *p++ = 0x92; // xchg edx,eax

  *p++ = 0xA1; // mov eax, [arg1]
  (int*&)p[0] = &arg1; p+=sizeof(int*);

  *p++ = 0xF7; *p++ = 0xEA; // imul edx

  *p++ = 0xA3; // mov [res1],eax
  (int*&)p[0] = &res1; p+=sizeof(int*);

  *p++ = 0x5A; // pop edx
  *p++ = 0x58; // pop eax
  *p++ = 0xC3; // ret

  funcptr func;
  func.y = buf;

  arg1 = 123; arg2 = 321; res1 = 0;

  func.x(); // call generated code

  printf( "arg1=%i arg2=%i arg1*arg2=%i func(arg1,arg2)=%i\n", arg1,arg2,arg1*arg2,res1 );

}

Конечно, делать это вручную — не самая здравая идея, и нужно притащить какой-то AsmJit. Потом из Java-кода пробрасываем конкретные данные, чем нам заполнять буфер, и ух — покатились!


Проблема здесь в том, что от инлайна мы ждём несколько более жирной функциональности. Хочется иметь доступ ко всему контексту вызова, плюс дергать разные системные штуки из SDK. Сделать это самостоятельное, наверное, можно — но долго и болезненно. К счастью, всё уже украдено до нас.


Java Native Interface


Использовать JNI всё равно можно, но по-другому.


Допустим, у нас есть такой класс:


public class MyJNIClass {
    public native void printVersion();
}

Идея в том, чтобы назвать символ в соответствии с конвенцией имён в JNI, и дальше оно само всё сделает. В нашем случае это будет выглядеть примерно как Java_MyJNIClass_printVersion.


Символ должен быть виден из других юнитов трансляции, и это можно сделать в NASM с помощью директивы global или в FASM с помощью public.


Сам асм нужно писать с пониманием конвенций вызова в используемой архитектуре (аргументы могут быть в регистрах, на стеке, в других структурах памяти и т.п.). Первый аргумент, который прилетит в функцию, будет указателем на JNIEnv, а оно, в свою очередь, — указатель на таблицу функций JNI.


Например, NASM под x86_64 будет выглядеть так:


global Java_MyJNIClass_printVersion

section .text

Java_MyJNIClass_printVersion:
    mov rax, [rdi]
    call [rax + 8*4]  ; pointer size in x86_64 * index of GetVersion
    ...  

Откуда взялся магический индекс GetVersion? Очень просто: они перечислены в документации.


Вот как выглядит описание GetVersion:


GetVersion
jint GetVersion(JNIEnv *env);

Returns the version of the native method interface.

LINKAGE:
Index 4 in the JNIEnv interface function table.

PARAMETERS:
env: the JNI interface pointer.

RETURNS:
Returns the major version number in the higher 16 bits and the minor version number in the lower 16 bits.

In JDK/JRE 1.1, GetVersion() returns 0x00010001.
In JDK/JRE 1.2, GetVersion() returns 0x00010002.
In JDK/JRE 1.4, GetVersion() returns 0x00010004.
In JDK/JRE 1.6, GetVersion() returns 0x00010006.

Как видим, таблица функций — это просто какой-то массив указателей. Нужно не забыть помножить эти индексы на размер указателя в целевой архитектуре, конечно.


Второй аргумент — ссылка на тот класс или объект, который позвал функцию. Все следующие аргументы — параметры метода, объявленного как native в Java-коде.


Дальше нужно из ассемблера собрать объектник: nasm -f elf64 -o GetVersion.o GetVersion.asm
Из объектника — библиотеку: gcc -shared -z noexecstack -o libGetVersion.so GetVersion.o
И под конец собрать сам файл: javac MyJNIClass.java


Можно выполнять куда более сложные операции. Вот пример сложения элементов массива.


И кажется, что всё бы хорошо, но хочется нескольких вещей.


Во-первых, если мы кодим в Java, то хотелось бы иметь для создания ассемблера красивый синтаксис со статическими проверками (что бы это ни значило в данном случае) и т.п. Я блондинко и хочу регистры выбирать в автодополнении IDE, а не бояться опечататься в одной букве. Ну хотя бы, пусть это будет Java API.


Во-вторых, собирать библиотеку руками рядом с кодом — это не очень хорошая идея. Ну и собирать файлики по одиночке — полное дно. Нужна инфраструктура, которая позволит не заботиться о таких вещах. Например, пусть asm-код будет инлайном, или плагин в Maven, или распространение как часть модифицированного JDK.


В-третьих, непонятно, стоит ли выбирать абстракцией именно ассемблер, ибо разных представлений — множество.


Библиотеки


Сразу же написал чувакам из Oracle, которые занимаются машинным кодом на Java, и получил ответ: нормальных красивых библиотек они не знают.


Но всё равно надо сходить в Google, мы же не лентяи. Используем кейворд "java call x86 assembly library" и медитируем на результат.


Результаты показывают, что с точки зрения библиотек — действительно всё плохо. Гуглится несколько недопиленных вещей, включая The Machine Level Java.


И там даже есть красивое API (насколько оно может быть красивым при использовании конструкций из Java):


public class SimpleNativeDemo extends X86InlineAssembly // X86InlineAssembly is a successor of InlineAssembly
{
    static // static initializer
    {
        InlineAssemblyHelper.initializeNativeCode_deleteExistingLibraryAndNoExceptions(new SimpleNativeDemo(System.out));
    }

    // constructor, which defines x86 architecture as a native method's target
    public SimpleNativeDemo(OutputStream debugStream)
    { super(Architectures.X86.architecture, false, debugStream); }

    // native method declaration
    public static native long multiply(int x, int y);

    // native method implementation
    @Override
    public void writeNativeCode()
    {
        parameterIn(r.EAX,IP.In0.ordinal());
        parameterIn(r.EBX,IP.In1.ordinal());
        mul.x32(r.EBX);
    }
}

Оно даёт нам и синтаксис (без модификации парсера Java), и способ исполнения — это хорошо.


Чуть ли не главная проблема здесь в том, что внутри находится весьма сложный код, который нужно поддерживать. Он является частью jEmbryOS — проектом по созданию операционной системы целиком на Java. И по внешним признакам, проект не очень жив: он всё ещё на Sourceforge (и его нет на GitHub и других популярных современных хостингах), пустой форум с последним сообщением за 2014 год. Последним гвоздём в гроб было то, что в релизе 2015 года на Sourceforge нет файла лицензии — использовать код без лицензии нельзя (применяются дефолтные правила копирайта, делающие такой код read-only).


Ладно, тут не вышло. Но в будущем можно написать самостоятельно, благо что идея ясна.


Интринсики


По этому поводу есть целый доклад вот здесь:



Если на конференции вообще стоит ходить, то вот как раз на такие доклады. Фолькер жжот.
Кстати, он будет на следующем JBreak в Новосибе, с очередным отжигом про сlass data sharing.


Вкратце, в OpenJDK у нас есть магический файл src/share/vm/classfile/vmSymbols.hpp. Просто откройте его в браузере по ссылке и всё поймёте. Мы можем поймать конкретные методы и подменить их ассемблером. Ну, пересобрать OpenJDK с этими изменениями, конечно.


Имхо, если совсем упороться, можно сделать так: написать препроцессор для .java-классов, который будет ловить вызовы конструкций типа __asm("ret"), потом генерить из них патч для интринсиков и автоматически пересобирать OpenJDK.


Почему мне это решение кажется не очень приятным? Во-первых, изменение интринсиков приводит к пересборке значительной части OpenJDK. Значит, придётся очень часто пить чай, курить, напиваться с горя и убивать время другими способами, пока горячий как печка ноутбук переколбашивает C++.


Во-вторых, интринсики работают не совсем так же, как нативные методы. Если в JNI мы работаем в нормальном режиме и JVM всегда может откатиться до сейфпоинта, то в случае с интринсиками это не работает. Нужно будет попотеть, чтобы чего-нибудь фатально не сломать.


Ну и в-третьих, есть подозрение, что нормальному человеку связываться с этой частью OpenJDK будет не очень приятно. Большинство кода там состоит из серьёзного колдунства, в котором можно увязнуть.


А что там у .NET?


Некоторым шоком оказалось то, что у дотнетчиков есть совершенно другой подход. Они могут вообще не оборачивать ассемблер, но запускать нативный код прямо из C#!


Идею подал пример, написанный ещё в далёком 2005 году. К сожалению, код по ссылке не работает, потому что его тут же прибьёт DEP. Пришлось его немного доработать, притащив мусор из kernel32.dll: через pinvoke достать VirtualAllocEx и нужные ему флаги — AllocationType и MemoryProtection. Это совершенно тот же самый трюк, который мы использовали в примере для C++.


Для простоты примера, пусть будет метод, который возвращает ответ на самый главный вопрос Жизни, Вселенной и Всего Остального:


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress,
        uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

    [Flags]
    public enum AllocationType
    {
        Commit = 0x1000,
        Reserve = 0x2000,
        Decommit = 0x4000,
        Release = 0x8000,
        Reset = 0x80000,
        Physical = 0x400000,
        TopDown = 0x100000,
        WriteWatch = 0x200000,
        LargePages = 0x20000000
    }

    [Flags]
    public enum MemoryProtection
    {
        Execute = 0x10,
        ExecuteRead = 0x20,
        ExecuteReadWrite = 0x40,
        ExecuteWriteCopy = 0x80,
        NoAccess = 0x01,
        ReadOnly = 0x02,
        ReadWrite = 0x04,
        WriteCopy = 0x08,
        GuardModifierflag = 0x100,
        NoCacheModifierflag = 0x200,
        WriteCombineModifierflag = 0x400
    }

    private delegate int IntReturner();

    private static void Main()

    {
        List<byte> bodyBuilder = new List<byte>();
        bodyBuilder.Add(0xb8);
        bodyBuilder.AddRange(BitConverter.GetBytes(42));
        bodyBuilder.Add(0xc3);
        byte[] body = bodyBuilder.ToArray();

        IntPtr buf = VirtualAllocEx(Process.GetCurrentProcess().Handle, (IntPtr) 0, Convert.ToUInt32(body.Length), AllocationType.Commit, MemoryProtection.ExecuteReadWrite);
        Marshal.Copy(body, 0, buf, body.Length);

        IntReturner ptr = (IntReturner) Marshal.GetDelegateForFunctionPointer(buf, typeof(IntReturner));
        Console.WriteLine(ptr());
        Console.ReadKey();        
    }
}

Если бы нам вдруг понадобился более умный пример с параметрами, Marshal умеет выделять unmanaged-память с помощью AllocHGlobal, чистить FreeHGlobal и ещё пачку методов на ту же тему.


Имея такие суперспособности, можно творить реальную дичь, например, заменять методы в классах. Перед публикацией этой статьи я просмотрел большое количество проектов на GitHub, но, к сожалению, ни один из этих хаков не обходился без C++, unsafe и, самое печальное, — очень объемного кода. Так что здесь всего этого писать не буду и вынесу в отдельную статью по хакам в .NET.


JVM Compiler Interface


Повернув мозги в правильном направлении, становится понятно, что в Java можно решить вопрос аналогичным образом. Дело в том, что в Java 9 реализован JEP 243: Java-Level JVM Compiler Interface.


Разработчики этой фичи понимали, что JIT-компилятор — это серьезный кусок говнокода софт, который хорошо бы разрабатывать отдельно, с использованием всех возможных фичей джавовой экосистемы, типа хороших бесплатных IDE. Далеко не все эти фичи можно использовать внутри OpenJDK — обычно ты открываешь его код в IDE, там всё красное и десять раз подчёркнуто как ошибка. Какое-то оправдание монолитной архитектуре есть в подсистемах, которым нужен прямой доступ к различным внутренним механизмам (например, это нужно интерпретатору байткода или сборщику мусора) — но компилятору это неважно.


Отсюда возникла идея выделить компилятор в отдельную сущность. Подключение должно производиться удобно, в виде плагина, включающегося из командной строки. Так родился JVMCI.


Если совсем коротко, то у нас есть простейший интерфейс:


interface JVMCICompiler {
    byte[] compileMethod(byte[] bytecode);
}

На вход приходит Java-байткод, на выход — нативный код. Очень походит на то, что было выше в C#. У нас это в чём-то даже лучше, ведь подобное использование не является грязным сайд-эффектом, а самым что ни на есть основным паттерном использования.


В реальности, просто байткода недостаточно. Это скорей некий CompilationRequest с дополнительными полями:


interface JVMCICompiler {
  void compileMethod(CompilationRequest request);
}

interface CompilationRequest {
    JavaMethod getMethod();
}

interface JavaMethod {
    byte[] getCode();
    int getMaxLocals();
    int getMaxStackSize();
    ProfilingInfo getProfilingInfo();
    ...
}

Долго ли, коротко ли, вы откомпилировали байткод, и его можно спокойно установить с помощью HotSpot.installCode(...);


В том числе, такой подход может решить изначальную проблему с интринсиками — необходиость пересобирать OpenJDK.


Проблемная часть здесь в том, что написать свою реализацию JVMCI — это не очень быстрая и простая задача. Документация по этой фиче почти отсутствует. Единственная всеобъемлющая документация — это код OpenJDK на C++, читать который ну очень не хочется.


Но и тут всё украдено за нас.


Graal и Truffle


В тёмных страшных подвалах Oracle Labs ведётся разработка нескольких крутых инструментов, которые в ближайшем будущем изменят картину для всех, кто расширяет OpenJDK. Эти проекты объединены под общим именем Graal и лежат вот в этом репозитории на GitHub.


В том числе:


  • Graal — оптимизирующий компилятор, написанный на Java и интегрирующийся с HotSpot JVM
  • Truffle — фреймворк для создания языков и тулов, использующих Graal как свой основной компилятор
  • Substrate VM — фреймворк, позволяющий делать ahead-of-time (AOT) компиляцию джавовых приложений и превращать их в исполняемые файлы

Интересно, что Graal и Truffle предоставляют свою реализацию JVMCI. И эта поддержка уже есть в OpenJDK 9 — достаточно подключить нужные флаги. Подключение этих флагов, конечно, не спасёт нас от пересборки самого Graal, но зато показывает, насколько серьёзно за дело взялись разработчики. Показывает, что всё это уже достаточно протестировано и созрело, чтобы превратиться в официальную фичу.


Очень хорошо о том, как работает Graal, рассказал Chris Seaton. Кстати, эта статья была написана по мотивам его выступления на Joker 2017.


Теперь к вопросу о том, насколько всё это хорошо работает и применимо на практике. На том же Joker выступал Christian Thalinger и рассказал о том, что значительная часть Twitter уже переведена на Graal. Использование его в качестве компилятора оказалось не только практичным, но и повысило производительность существующего кода на более чем 10%.


Кроме того, у нас есть JEP 317: Experimental Java-Based JIT Compiler, который с большой вероятностью войдёт в состав Java 10.


В этом разделе я хотел написать небольшой победоносный пример, который покажет, как можно использовать Graal в наших целях. К сожалению, пример всё ещё пишется, и похоже — будет писаться долго. Это тема для отдельной статьи.


Что здесь пропущено


Незаслуженно не были рассмотрены следующие вещи: VMStructs, Java Native Runtime (JNR-x86asm в данном случае), Project Panama в целом. В апреле после доклада apangin нужно будет написать спин-офф и раскрыть эти темы.


Заключение


В этой статье обзорно были показаны способы запуска нативного кода напрямую из Java.


Это только первая статья в серии. Каковы будут следующие шаги?


Вначале нужно провести интервью с Christian Thalinger, который как никто другой разбирается в Graal. Интервью будет опубликовано на Хабре в ближайшее время.


Кстати, он же приезжает на JBreak 2018 в Новосибирск с новым докладом "Graal: how to use the new JVM JIT compiler in real life" — на этот доклад стоит сходить.


В следующих статьях на эту тему нужно углубиться в архитектуру и организацию Graal и Truffle и показать, как мы можем делать простые изменения и добиваться быстрого эффекта.


Кроме того, можно попробовать связать современные материалы со старыми, но не утратившими полезности работами, повлиявшими на дизайн Graal. Например, одну такую статью (контекстно-зависимый инлайнинг трейсов) я уже публиковал на Хабре. Большое количество материала накопилось по связанным разработкам: например, текущий разработчик Graal, Doug Simon, раньше занимался Maxine VM, про которую есть внушительное количество публикаций.

Комментарии (28)


  1. blanabrother
    29.01.2018 17:46
    +1

    Эх, было в юности такое увлечение.


    1. olegchir Автор
      29.01.2018 17:50

      Ох ничего себе. Надо прочитать, прежде чем забуряться в хаки на .NET :) Меня почему-то очень сильно захватила идея забуриться в код и что-нибудь там динамически поперезаписывать. Даже не знаю, зачем это может быть нужно, но звучит захватывающе.


      1. blanabrother
        29.01.2018 18:14

        Да, очень интересно. Но большого смысла не имеет. Много сложной работы, шансы ошибиться и закрашить приложение, процессорозависимый нативный код из платформонезависимого C#/Java и т.д…


    1. boblenin
      30.01.2018 00:07
      +1

      Ну вы упоротый целеустремленный!


    1. Aquahawk
      30.01.2018 00:20

      забавно выглядит хаб .net по постом

      Программирование сопроцессора на C#? Да!
      .NET


  1. lany
    29.01.2018 18:39
    +1

    Странно, что здесь не упоминается ни JNR, ни тем более JNR-x86asm, а ведь это неплохое решение, которое уже работает. А ещё стоит покопать в проект Panama, Володю Иванова потыкать. Они там тоже ассемблер прямо в джаве пишут, причём почти без накладных расходов на переход туда и обратно.


    1. olegchir Автор
      29.01.2018 18:45

      Спасибо что напомнил, как осилю JNR — напишу продолжение. Хотя в общем, сейчас мозг занят Граалем, и в первую очередь будет про него.


    1. Hixon10
      29.01.2018 22:39
      +1

      Эх, люблю я этих парней.
      Panama, Amber, Valhalla — тысяча и одно очевидное название эксперементальных проектов. Смотришь такой на названия, и всё сразу понятно становится.


      1. olegchir Автор
        29.01.2018 22:59

        Тут на Хабре есть люди для каждого из проектов. lany вроде бы рассказывает об Amber. Если @iwan0www будет заглядывать в комментарии, он вытащит за Panama. Я буду топить за Graal. Ад пуст, все демоны собрались здесь. Как там ютуберы говорят, «ставь лайки и подписывайся» :-)


        1. Hixon10
          29.01.2018 23:00
          +1

          Ага, Тагир читал отличный доклад на Джокере-17, там тоже чувствовалось общее непонимание именованиями :)


  1. Kant8
    29.01.2018 21:52

    Тут грааль, там рослин, ну вот не могут же друг без друга.
    И кто на этот раз первый придумал? :)


    1. Prototik
      29.01.2018 23:24
      +1

      В первом коммите Graal можно найти упоминание даты — 2010. Именно тогда состоялось переименование в graal. Однако, разработка была начата ещё раньше, т.к. имел место быть экспорт hg -> git.
      С Roslyn сложнее, в исходниках дат не нашёл, но первый релиз был в конце 2011.

      Выводы делайте сами, но я таки склоняюсь, что graal был первее.


      1. kafeman
        29.01.2018 23:37
        +1

        имел место быть экспорт hg -> git
        А что мешало им перенести в git даты коммитов?


        1. Prototik
          30.01.2018 00:22

          Мешать то может и не мешало, но экспорт был одним большим новым коммитом.


  1. apangin
    30.01.2018 01:38

    В предстоящем докладе на JPoint расскажу, как вызывать ассемблерные вставки из Java без JNI оверхеда. В обычной OpenJDK 8.


    1. olegchir Автор
      30.01.2018 10:47

      Ощущение, когда облажался дважды: я же мог посмотреть и какой у тебя доклад на JPoint, и вообще прошарить эту тему… Очень ждём твоего доклада! Всего месяц остался, месяц можно и подождать


    1. olegchir Автор
      30.01.2018 10:53

      в смысле, два месяца, конечно. JPoint будет 6-7 апреля


      1. 23derevo
        30.01.2018 20:35

        ну ты мастер нативной рекламы :)


  1. AlexeiZavjalov
    30.01.2018 08:34
    +1

    Как-то мы имплементировали инлайнер нативных функций в Android ART — вот это было действительно безумие. Но прогресс был и ощутимый.


    1. olegchir Автор
      30.01.2018 10:46

      А можно подробней, как это работало и какое имело юзабилити? Может быть, отдельной статьёй, если не NDA?


      1. AlexeiZavjalov
        30.01.2018 19:12
        +1

        Дизассемблировалось тело функции и проводился анализ, что там делается. Мы заимплементили несколько юзкейсов, в основном геттеры и сеттеры, но до продукта не довели, так что в опенсорсе этого нет.

        Вот тут можно посмотреть некоторые другие оптимизации:
        github.com/android-art-intel/Nougat/tree/master/art-extension/compiler/optimizing/extensions

        А здесь github.com/android-art-intel/Fuzzer — мощный тул от нашего QA, который на раз валит любой java компилятор, путем генерации рандомного кода :)


        1. olegchir Автор
          30.01.2018 19:25

          Круто!


          Кстати. Наверное, я становлюсь как дедушка Столлман ("не Linux, а GNU/Linux!"), но… На Android нет Java, совсем нет. Есть нечто с джаваподобным синтаксисом. Можете спросить у Гугла, они обязаны подтвердить :-)


          1. AlexeiZavjalov
            30.01.2018 22:18

            Android SDK написан на Java и никто этого не скрывает en.wikipedia.org/wiki/Android_software_development. Суть спора с Ораклом была в копировании кода, насколько я помню, ибо сам язык запатентовать и залецензировать нельзя, как в процессе выяснилось.

            Сам компилятор, конечно, работает с байткодом и ему не важно — из Java он получен или из C++.


            1. olegchir Автор
              30.01.2018 22:27

              Проблема в том, что сам dalvik/art — очень плохи по сравнению с HotSpot и Graal :( Помню как на Google IO лид дальвика рассказывал, что типа вы с ООП не перебарщивайте, а то классов наплодите и приложуха будет тормозить Т_Т


              И проходят ли они все соответствия на гордое звание Java?


              Вот сейчас вышла Java 9, а скоро будет 10, 11, 12, и как Google собираются поддерживать совместимость? Непонятно. Будут люди продолжать жить в каменном веке.


              Короче, на основании всего этого проще говорить, что на Андроиде — не Java. Как сделают по-нормальному, тогда и поговорим :)


              Жду, когда на Android выкатят полноценный OpenJDK. Они же вроде обещали. Вот тогда можно будет разгуляться!


              1. AlexeiZavjalov
                30.01.2018 23:58

                Не очень люблю холиварить на эту тему (и не могу, по понятным причинам :). Мне довелось работать и над JDK и над Dalvik и ART. Я думаю, что ART гораздо лучше подходит для мобильных устройств — он легче, гибче и покрывает большую часть необходимых оптимизаций. Для мобильных устройств не так важен 1% производительности, а вот минимальная скорость компиляции и минимальный размер кода — критичены.


  1. marshinov
    30.01.2018 19:09

    .NET реализация сможет это делать только на win-платформе с запуском от администратора?


    1. olegchir Автор
      30.01.2018 19:22

      Код хака из статьи администратора не требует. Это обычная схема, так все JIT работают.


      Штуки типа GrayStorm и GrayFrost тоже не требуют админа, но работают поверх DLL-инжектора, и это уже платформоспецифично.


      Про кроссплатформенность в смысле операционной системы — без тестирования я бы не загадывал. Попробуйте! Говорят, сейчас Ubuntu можно прямо из винды запустить. Расскажите о результатах здесь в комментариях!


      Аппаратно код, конечно, не кроссплатформенный. Я запускал это на x86-64 (i7 6700k, skylake). На ARM будет другой машкод. Нужно рисовать матрицу совместимости, и поддерживать все программно-аппаратные платформы по отдельности.


    1. blanabrother
      30.01.2018 23:13

      только на win-платформ

      Если на той платформе, что Вы имеете ввиду, есть возможность обеспечить доступ к куску памяти на выполнение, помимо записи и чтения, то можно и на ней. В статье пример с виндовым VirtualAlloc(Ex), в который можно передать MemoryProtection.Execute*. Если будете работать под .NET Core в Linux/macOS, то выделить кусок памяти можете через Marshal, а потом запротектить этот кусок самописной на С простой либой, которая просто вызывает mprotect, ну а вызвать этот метод либы можно стандартным для .NET DllImport.