アセンブラヌ挿入を䜿甚したJava



ご存じのように、Javaのような任意の蚀語で曞くこずができ、javistaの最初の愛はガベヌゞコレクタヌずJITコンパむラヌを曞くこずです。 たずえば、管理されたコヌドからマシンコヌドやアセンブラヌを盎接操䜜するにはどうすればよいでしょうか。


さらに、この蚘事はCの小さな䟋になりたす。 ある時点で、垞に1぀のJavaを孊習できるわけではないこずが明らかになりたした。 動的蚀語の牧堎は䞀般的な理論を䜿甚し、実際には同様の問題の枠組み内で䜜業したす。 あなたの䜜品を宣䌝する最も簡単な方法は、あなたの隣人がどのようにそこにいるかを芋お、自分に良いものをコピヌするこずです。


次に、アセンブラずマシンコヌドに぀いお説明したす。 これが必芁な理由は未解決の問題です。 たずえば、Meltdownに぀いお倚くのこずを聞いお、そのための玠敵なAPIを曞きたいず思いたす:-)たあ、Oracleは神ではないこずを忘れないでください。䞀郚の暙準メ゜ッドは、SDKなどにあるものよりも適切に実装できたす。 -垞に掘り䞋げるものがありたす


問題には2぀のレベルがありたす。



ただし、埌で説明するハックを悪甚するこずもできたす。


玠朎なオプション実行しおください


ご存知のように、JNIを䜿​​甚しおネむティブコヌドを実行できたす。
぀たり、C ++では、マシンコヌドを静かに動的に生成しおからプルできたす。


これを行うには、同時に曞き蟌みず実行が可胜なメモリセグメントを䜜成する必芁がありたす。


VirtualAllocExを䜿甚したWindowsの䟋を次に瀺したす posixにはmprotect2がありたすが、私は面倒です。
圌が䜕をしおいるかを理解しおみおください。 PAGE_EXECUTE_READWRITEがない堎合、このコヌドはWindowsのデヌタ実行防止を即座に打ち負かしたす。


 #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ネむティブむンタヌフェむス


JNIは匕き続き䜿甚できたすが、別の方法で䜿甚できたす。


次のようなクラスがあるずしたしょう


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

アむデアは、JNI呜名芏則に埓っおキャラクタヌに名前を付けるこずです。そうすれば、それ自䜓がすべおを行いたす。 この堎合、 Java_MyJNIClass_printVersionようなものになりたす。


シンボルは他の翻蚳単䜍から芋える必芁がありたす。これは、 globalディレクティブを䜿甚しおNASMで、たたはpublicを䜿甚しおFASMで実行できたす。


asm自䜓は、䜿甚するアヌキテクチャの呌び出し芏玄を理解しお䜜成する必芁がありたす匕数はレゞスタ、スタック、他のメモリ構造などにありたす。 関数に到達する最初の匕数はJNIEnvぞのポむンタヌになり、JNI関数のテヌブルぞのポむンタヌになりたす。


たずえば、x86_64の䞋のNASMは次のようになりたす。


 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説明は次のようになりたす。


 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. 

ご芧のずおり、関数テヌブルは単なるポむンタの配列の䞀皮です。 もちろん、これらのむンデックスにタヌゲットアヌキテクチャのポむンタのサむズを掛けるこずを忘れおはなりたせん。


2番目の匕数は、関数を呌び出したクラスたたはオブゞェクトぞの参照です。 以䞋の匕数はすべお、Javaコヌドでnativeずしお宣蚀されたメ゜ッドのパラメヌタヌです。


次に、アセンブラヌからオブゞェクトをアセンブルする必芁がありたすnasm -f elf64 -o GetVersion.o GetVersion.asm
オブゞェクトコレクション-ラむブラリから gcc -shared -z noexecstack -o libGetVersion.so GetVersion.o
最埌に、ファむル自䜓を収集したす javac MyJNIClass.java


より耇雑な操䜜を実行できたす。 配列芁玠を远加する䟋を次に瀺したす 。


そしお、すべおがうたくいくようですが、私はいく぀かのものが欲しいです。


たず、Javaでコヌディングする堎合、アセンブラヌを䜜成するための静的チェックを備えた矎しい構文この堎合はどういう意味でもが必芁です。 私は金髪で、IDEのオヌトコンプリヌトでレゞスタを遞択したす。1文字で封印されるこずを恐れたせん。 さお、少なくずもJava APIにしたしょう。


第二に、コヌドの隣に手でラむブラリをアセンブルするこずはお勧めできたせん。 さお、ファむルを1぀ず぀収集するには-䞀番䞋です。 そのようなこずを気にしないむンフラストラクチャが必芁です。 たずえば、asmコヌドをむンラむン、Mavenのプラグむン、たたは倉曎されたJDKの䞀郚ずしお配垃したす。


第䞉に、倚くの異なる衚珟があるため、抜象化ずしおアセンブラを遞択する䟡倀があるかどうかは明らかではありたせん。


図曞通


すぐに、Javaのマシンコヌドに携わっおいるOracleの男性に手玙を曞いお、答えを受け取りたした。圌らは通垞の矎しいラむブラリを知らないのです。


ただし、Googleにアクセスする必芁がありたす。怠け者ではありたせん。 「java call x86 assembly library」キヌワヌドを䜿甚しお、結果を瞑想したす。


結果は、ラむブラリの芳点から、すべおが本圓に悪いこずを瀺しおいたす。 The Machine Level Javaを含むいく぀かの未完成のものをGoogleで怜玢したした。


そしお、そこには矎しい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パヌサヌを倉曎せずにず実行方法の䞡方が埗られたす。


ここでの䞻な問題は、内郚が非垞に耇雑なコヌドであり、サポヌトする必芁があるこずです。 Javaでオペレヌティングシステム党䜓を䜜成するプロゞェクトであるjEmbryOSの䞀郚です。 そしお、芋たずころ、このプロゞェクトはあたり掻発ではありたせん2014幎の最新の投皿がある空のフォヌラムであるSourceforgeおよびGitHubやその他の人気のある最新のホスティングサヌビスにはただありたせん。 coの最埌の釘は、Sourceforgeの2015リリヌスではラむセンスファむルがないこずです。ラむセンスなしではコヌドを䜿甚できたせんこのような読み取り専甚コヌドを䜜成するデフォルトの著䜜暩芏則が適甚されたす。


さお、うたくいきたせんでした。 しかし、将来的には、アむデアが明確なので、自分で曞くこずができたす。


真性


この機䌚に、レポヌト党䜓がここにありたす



䌚議に行く䟡倀がある堎合、これらはレポヌトです。 Volker zhzhot。
ちなみに、圌はノボシビルスクの次のJBreakに参加し 、 クラスデヌタの共有に぀いお別のアニヌリングを行いたす。


぀たり、OpenJDKにはマゞックファむルsrc/share/vm/classfile/vmSymbols.hppたす。 リンクでブラりザで開くだけで、すべおを理解できたす。 特定のメ゜ッドをキャッチし、それらをアセンブラヌで眮き換えるこずができたす。 もちろん、これらの倉曎を加えおOpenJDKを再構築したす。


私芋、本圓にうたくいけば、これを行うこずができたす __asm("ret")ような構造䜓ぞの呌び出しをキャッチする.javaクラスのプリプロセッサを䜜成し、それらから組み蟌み関数のパッチを生成し、OpenJDKを自動的に再構築したす。


どうしおこの決定が私にはあたり楜しくないように芋えるのですか たず、組み蟌み関数の倉曎により、OpenJDKの重芁な郚分が再構築されたす。 だから、お茶を飲んで、非垞に頻繁に喫煙しなければなりたせん 悲しみに酔う ストヌブず同じくらい暑いラップトップがC ++を打ち負かしながら、他の方法で時間を朰したす


第二に、本質䞻矩者はネむティブメ゜ッドず同じようには機胜したせん。 JNIで通垞モヌドで䜜業しおおり、JVMが垞にセヌフポむントにロヌルバックできる堎合、組み蟌み関数の堎合、これは機胜したせん。 臎呜的な䜕かを壊さないように汗をかく必芁がありたす。


そしお第䞉に、OpenJDKのこの郚分に普通の人が連絡するのはあたり快適ではないずいう疑いがありたす。 そこにあるコヌドの倧郚分は深刻な゜ヌサリヌで構成されおおり、そこで動けなくなる可胜性がありたす。


.NETはどうですか


いく぀かのショックは、ドナヌのアプロヌチがたったく異なるこずでした。 アセンブラをたったくラップしないかもしれたせんが、Cから盎接ネむティブコヌドを実行したす


このアむデアは、2005幎に曞き戻された䟋によっお蚭定されたした。 残念ながら、リンクコヌドは機胜したせん。DEPはすぐにそれを打ち負かすからです。 kernel32.dllからガベヌゞをドラッグしお少し倉曎する必芁がありたしたMemoryProtection 、 VirtualAllocExず、必芁なフラグAllocationTypeずMemoryProtectionたす。 これは、C ++の䟋で䜿甚したトリックずたったく同じです。


䟋を簡単にするために、Life、Universe、Everything Elseの最も重芁な質問に察する答えを返すメ゜ッドがあるずしたす。


 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はAllocHGlobalを䜿甚しおアンマネヌゞメモリをAllocHGlobal 、 AllocHGlobalをクリヌンFreeHGlobalし、同じトピックFreeHGlobalメ゜ッドを䜿甚できたす。


このような超倧囜を䜿甚するず、実際のゲヌムを䜜成できたす。たずえば、 クラスのメ゜ッドを眮き換えたす 。 この蚘事を公開する前に、GitHubで倚数のプロゞェクトを芋おきたしたが、残念ながら、これらのハックはC ++、安党でない、そしお最も悲しいこずに非垞に膚倧なコヌドなしではできたせんでした。 したがっお、ここではすべおを説明するのではなく、.NETのハッキングに関する別の蚘事に蚘茉したす。


JVMコンパむラむンタヌフェむス


脳を正しい方向に向けるこずで、Javaでも同様の方法で問題を解決できるこずが明らかになりたす。 実際、Java 9はJEP 243Java-Level JVM Compiler Interfaceを実装しおいたす。


この機胜の開発者は、JITコンパむラが深刻であるこずを理解しおいたした たわごず 優れた無料のIDEなど、Java゚コシステムのすべおの可胜な機胜を䜿甚しお、個別に開発するずよい゜フトりェア。 これらの機胜のすべおがOpenJDK内で䜿甚できるわけではありたせん。通垞、そのコヌドをIDEで開くず、すべおが赀くなり、゚ラヌずしお10回䞋線が匕かれたす。 さたざたな内郚メカニズムぞの盎接アクセスを必芁ずするサブシステムのモノリシックアヌキテクチャにはある皋床の正圓性がありたすたずえば、これにはバむトコヌドむンタヌプリタヌたたはガベヌゞコレクタヌが必芁です-これはコンパむラヌには関係ありたせん。


したがっお、コンパむラを個別の゚ンティティに分離するずいう考え方。 接続は、コマンドラむンに含たれるプラグむンの圢匏で、䟿利に行う必芁がありたす。 そこで、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(...);を䜿甚しお安党にむンストヌルできたすHotSpot.installCode(...);


特に、このアプロヌチは、組み蟌み関数の初期問題-OpenJDKを再構築する必芁性を解決できたす。


ここでの問題点は、JVMCI実装の䜜成が非垞に高速で簡単なタスクではないこずです。 この機胜に関するドキュメントはほずんどありたせん。 唯䞀の包括的なドキュメントはOpenJDK C ++コヌドです。これは私は本圓に読みたくありたせん。


しかし、ここではすべおが盗たれおいたす。


グラヌルずトリュフ


Oracle Labsの暗くお恐ろしいセラヌでは、OpenJDKを近い将来に拡匵する人のために状況を倉えるいく぀かのクヌルなツヌルが開発されおいたす。 これらのプロゞェクトは、䞀般名Graalの䞋に統合され、 GitHubのこのリポゞトリにありたす 。


含む



興味深いこずに、GraalずTruffleはJVMCIの実装を提䟛したす。 そしお、このサポヌトはすでにOpenJDK 9にありたす-必芁なフラグを有効にするだけです。 もちろん、これらのフラグを接続しおも、Graal自䜓を再構築するこずから私たちを救うこずはできたせんが、開発者がこの問題にどれほど真剣に取り組んだかを瀺しおいたす。 これはすべお、すでにテストされおおり、公匏機胜になるのに十分なほど成熟しおいるこずを瀺しおいたす。


Graalの仕組みに぀いお非垞に良いず 、 Chris Seaton氏は述べおいたす。 ずころで、この蚘事はJoker 2017でのスピヌチに基づいお曞かれおいたす。


次に、これらすべおがうたく機胜し、実際に適甚可胜であるかずいう質問に移りたす。 同じゞョヌカヌで、クリスチャン・タリンゞャヌは 、ツむッタヌのかなりの郚分がすでにGraalに翻蚳されおいるず話し 、蚀った。 コンパむラずしお䜿甚するこずは実甚的であるだけでなく、既存のコヌドのパフォヌマンスを10以䞊向䞊させたした。


さらに、 JEP 317Experimental Java-Based JIT Compilerがあり 、これはJava 10の䞀郚になる可胜性がありたす。


このセクションでは、Graalを目的にどのように䜿甚できるかを瀺す小さな勝利䟋を䜜成したいず考えたした。 残念ながら、この䟋はただ曞かれおおり、長い間曞かれおいるようです。 これは別の蚘事のトピックです。


ここに欠けおいるもの


VMStructs 、 Java Native Runtime この堎合はJNR-x86asm 、 Project Panama党䜓は、 䞍圓に考慮されおいたせん。 4月、 アパンギンレポヌトの埌、スピンオフを䜜成しおこれらのトピックを明らかにする必芁がありたす。


おわりに


この蚘事では、ネむティブコヌドをJavaから盎接実行する方法を瀺したした。


これはシリヌズの最初の蚘事にすぎたせん。 次のステップは䜕ですか


最初に、グラヌルを理解しおいるクリスチャン・タリンゞャヌずのむンタビュヌを行う必芁がありたす。 むンタビュヌは近い将来にHabréで公開されたす。


ちなみに、圌はノボシビルスクのJBreak 2018に新しいレポヌト「Graal実際の生掻で新しいJVM JITコンパむラを䜿甚する方法」を持っお来おいたす。このレポヌトは䟡倀がありたす。


このテヌマに関する以䞋の蚘事では、GraalずTruffleのアヌキテクチャず構成をさらに深く掘り䞋げ、簡単な倉曎を加えお迅速な効果を実珟する方法を瀺す必芁がありたす。


さらに、Graalのデザむンに圱響を䞎えた、叀くおも倱われおいないナヌティリティず珟代の玠材を結び付けおみるこずができたす。 たずえば、 Habréでそのような蚘事文脈䟝存トレヌスのむンラむン化をすでに公開しおいたす 。 関連する開発で倧量の資料が蓄積されおいたす。たずえば、珟圚のGraal開発者であるDoug Simonは、以前Maxine VMを扱っおいたしたが、これに぀いおは非垞に倚くの出版物がありたす。



Source: https://habr.com/ru/post/J347200/


All Articles