Aus Lachen habe ich mich einmal entschlossen , die Reversibilität des Prozesses zu beweisen und zu lernen, wie man JavaScript (oder besser Asm.js) aus Maschinencode generiert. QEMU wurde für das Experiment ausgewählt, einige Zeit später wurde ein Artikel über Habr geschrieben. In den Kommentaren wurde mir geraten, das Projekt auf WebAssembly neu zu erstellen, und ich selbst hatte keine Lust, das fast fertige Projekt selbst zu verlassen ... Die Arbeit ging weiter, aber sehr langsam, und jetzt erschien in diesem Artikel ein Kommentar zum Thema "Wie endete es?". Auf meine ausführliche Antwort hörte ich "Es zieht an einem Artikel." Nun, wenn es zieht, wird es einen Artikel geben. Vielleicht wird jemand nützlich sein. Daraus lernt der Leser einige Fakten über die QEMU-Codegenerierung des Backends des Geräts sowie das Schreiben eines Just-in-Time-Compilers für eine Webanwendung.
Die Aufgaben
Da ich bereits gelernt habe, wie man QEMU auf JavaScript "portiert", wurde diesmal beschlossen, dies mit Bedacht zu tun und alte Fehler nicht zu wiederholen.
Fehleranzahl: Verzweigung von Punktfreigabe
Mein erster Fehler war, meine Version von der Upstream-Version 2.4.1 zu verzweigen. Dann schien es mir eine gute Idee zu sein: Wenn es eine Punktfreigabe gibt, ist sie wahrscheinlich stabiler als eine einfache 2.4- und noch mehr master
Verzweigung. Und da ich vorhatte, eine ganze Menge meiner Bugs hinzuzufügen, brauchte ich überhaupt keine Fremden. Also ist es wahrscheinlich passiert. Aber hier ist das Pech: QEMU steht nicht still und irgendwann haben sie sogar die Optimierung des generierten Prozentcodes um 10 angekündigt. „Ja, jetzt friere ich ein“, dachte ich und brach ab . Hier müssen wir einen Exkurs machen: Aufgrund der Single-Threaded-Natur von QEMU.js und der Tatsache, dass die ursprüngliche QEMU nicht das Fehlen von Multithreading impliziert (das heißt, es ist entscheidend, dass mehrere nicht verwandte Codepfade gleichzeitig betrieben werden können und nicht nur alle Kernel "stecken"), die Hauptfunktionen von Threads musste sich für die Möglichkeit eines Anrufs von außen "herausstellen". Dies führte zu einigen natürlichen Verschmelzungsproblemen. Die Tatsache, dass einige der Änderungen aus dem Hauptzweig, mit denen ich versucht habe, meinen Code zusammenzuführen, auch in der Punktfreigabe (und daher in meinem Zweig) ausgewählt wurden, würde wahrscheinlich auch keinen Komfort hinzufügen.
Generell habe ich beschlossen, dass der Prototyp trotzdem Sinn macht rausschmeißen Zerlegen Sie die Teile und erstellen Sie eine neue Version von Grund auf neu, basierend auf etwas Frischerem und jetzt vom master
.
Fehler Nummer zwei: TLP-Methodik
Tatsächlich ist dies im Allgemeinen kein Fehler - es ist nur ein Merkmal der Erstellung eines Projekts unter den Bedingungen eines völligen Missverständnisses von „Wo und wie soll man sich bewegen?“ Und im Allgemeinen „Werden wir dorthin gelangen?“. Unter diesen Umständen war die Programmierung eine vertretbare Option, aber ich wollte sie natürlich nicht unnötig wiederholen. Diesmal wollte ich es mit Bedacht tun: Atomic Commits, absichtliche Codeänderungen (und nicht "Zufallszeichen aneinanderreihen, bis es kompiliert wird (mit Warnungen)", wie Linus Torvalds einmal über jemanden sagte, wenn Sie Wikitatnik glauben) usw.
Fehler Nummer drei: Ich weiß nicht, dass die Furt ins Wasser klettern soll
Ich habe dies noch nicht vollständig beseitigt, aber jetzt habe ich beschlossen, nicht den Weg des absolut geringsten Widerstands zu beschreiten und dies "auf erwachsene Weise" zu tun, nämlich mein TCG-Backend von Grund auf neu zu schreiben, damit ich später nicht sage: "Ja, das ist es." Natürlich langsam, aber ich kann nicht alles kontrollieren - TCI ist so geschrieben ... " Außerdem schien dies zunächst eine offensichtliche Lösung zu sein, da ich Binärcode generierte . Wie das Sprichwort sagt: "Ich habe Gent gesammelt, aber nicht diesen": Der Code ist natürlich binär, aber das Steuerelement kann nicht einfach so auf ihn übertragen werden - er muss zur Kompilierung explizit in den Browser verschoben werden, was zu einem bestimmten Objekt aus der JS-Welt führt müssen noch irgendwo sparen. Jedoch auf normal Für RISC-Architekturen ist meines Wissens eine typische Situation die Notwendigkeit, den Anweisungscache für den neu generierten Code explizit zu leeren. Wenn dies nicht das ist, was wir brauchen, ist es zumindest eng. Außerdem habe ich bei meinem letzten Versuch erfahren, dass das Steuerelement nicht in die Mitte des Übersetzungsblocks übertragen zu werden scheint. Daher benötigen wir den Bytecode, der aus einem Offset interpretiert wird, nicht wirklich und können einfach durch Funktion auf TB generieren.
Kam und trat
Obwohl ich bereits im Juli damit begonnen habe, den Code neu zu schreiben, kroch das magische Pendell unbemerkt: Normalerweise werden Briefe von GitHub als Benachrichtigungen über Antworten auf Issues- und Pull-Anfragen angezeigt , und dann sagt der Binaryen plötzlich als Qemu-Backend im Kontext : „Hier ist es - er hat so etwas getan, vielleicht sagt er etwas. “ Es ging darum, mithilfe der Emscripten-bezogenen Binaryen- Bibliothek eine WASM-JIT zu erstellen. Nun, ich sagte, dass Sie dort eine Apache 2.0-Lizenz haben und QEMU als Ganzes unter GPLv2 vertrieben wird und sie nicht sehr kompatibel sind. Es stellte sich plötzlich heraus, dass die Lizenz irgendwie korrigiert werden konnte (ich weiß nicht: vielleicht ändern, vielleicht doppelte Lizenzierung, vielleicht etwas anderes ...). Das hat mich natürlich glücklich gemacht, denn ich hatte mir zu diesem Zeitpunkt bereits mehrmals das Binärformat WebAssembly angesehen und war irgendwie traurig und für mich unverständlich. Hier gab es eine Bibliothek, die die Basisblöcke mit dem Übergangsgraphen verschlingt, den Bytecode ausgibt und ihn bei Bedarf sogar im Interpreter startet.
Dann gab es auch einen Brief auf der QEMU-Mailingliste, aber dies ist wahrscheinlicher für die Frage: "Wer braucht ihn überhaupt?" Und es war plötzlich notwendig. Zumindest können Sie solche Anwendungsfälle zusammenkratzen, wenn sie mehr oder weniger intelligent funktionieren:
- Starten von Unterricht ohne Installation
- Virtualisierung unter iOS, wo Gerüchten zufolge die einzige Anwendung, die das Recht hat, Code im laufenden Betrieb zu generieren, die JS-Engine ist (stimmt das?)
- Mini-OS-Demonstration - Single-Disk, eingebaute, alle Arten von Firmware, etc ...
Funktionen der Browser-Laufzeit
Wie gesagt, QEMU ist an Multithreading gebunden, aber nicht im Browser. Nun, das heißt, wie nicht ... Zuerst war es überhaupt nicht da, dann erschienen WebWorkers - so wie ich es verstehe, ist dies Multithreading basierend auf der Nachrichtenübermittlung ohne gegenseitig variable Variablen . Dies führt natürlich zu erheblichen Problemen beim Portieren von vorhandenem Code basierend auf einem Shared-Memory-Modell. Dann wurde es unter dem Druck der Öffentlichkeit unter dem Namen SharedArrayBuffers
. Sie führten es nach und nach ein, feierten seinen Start in verschiedenen Browsern, feierten dann das neue Jahr und dann Meltdown ... Danach kamen sie zu dem Schluss, dass unhöflich, keine unhöfliche Zeitmessung, aber mit Hilfe des gemeinsamen Speichers und eines Flusses, der den Zähler erhöht, immer noch ziemlich genau ist . Also haben sie Multithreading mit gemeinsamem Speicher deaktiviert. Es scheint, dass sie es später wieder eingeschaltet haben, aber wie aus dem ersten Experiment hervorgeht, gibt es ein Leben ohne es, und wenn ja, werden wir versuchen, es zu tun, ohne uns auf Multithreading zu verlassen.
Das zweite Merkmal ist die Unmöglichkeit von Manipulationen auf niedriger Ebene mit dem Stapel: Sie können nicht einfach den aktuellen Kontext speichern, speichern und zu einem neuen mit einem neuen Stapel wechseln. Der Aufrufstapel wird von der virtuellen JS-Maschine verwaltet. Es scheint, was ist das Problem, da wir uns immer noch entschlossen haben, die früheren Flows vollständig manuell zu verwalten? Tatsache ist, dass die Blockeingabe / -ausgabe in QEMU über Coroutinen implementiert wird, und hier wären Stapelmanipulationen auf niedriger Ebene für uns nützlich. Glücklicherweise enthält Emscipten bereits einen Mechanismus für asynchrone Operationen, sogar zwei: Asyncify und Emterpreter . Das erste funktioniert durch ein deutliches Aufblähen des generierten JavaScript-Codes und wird nicht mehr unterstützt. Der zweite ist der aktuelle "richtige Weg" und arbeitet durch die Erzeugung von Bytecode für seinen eigenen Interpreter. Es funktioniert natürlich langsam, aber es bläst den Code nicht auf. Die Coroutine-Unterstützung für diesen Mechanismus musste zwar selbst zugeordnet werden (es gab bereits Coroutinen, die unter Asyncify geschrieben wurden, und es gab eine Implementierung ungefähr derselben API für Emterpreter, Sie mussten sie nur verbinden).
Im Moment habe ich es noch nicht geschafft, den Code in WASM zu kompilieren und mit Emterpreter zu interpretieren, sodass Blockgeräte noch nicht funktionieren (siehe nächste Serie, wie sie sagen ...). Das heißt, am Ende solltest du so eine lustige Schicht bekommen:
- interpretierter Block I / O. Was haben Sie wirklich von einem emulierten NVMe mit nativer Leistung erwartet? :) :)
- statisch kompilierter QEMU-Hauptcode (Übersetzer, andere emulierte Geräte usw.)
- WASM dynamisch kompilierter Gastcode
Funktionen von QEMU-Quellen
Wie Sie wahrscheinlich bereits vermutet haben, sind der Emulationscode für Gastarchitekturen und der Code zum Generieren von Hostcomputeranweisungen aus QEMU getrennt. In der Tat gibt es noch ein wenig kniffliger:
- Es gibt Gastarchitekturen
- Es gibt Beschleuniger , nämlich KVM für die Hardwarevirtualisierung unter Linux (für kompatible Gast- und Hostsysteme), TCG für die JIT-Codegenerierung überall. Ab QEMU 2.9 wurde die Unterstützung für den HAXM-Hardwarevirtualisierungsstandard unter Windows angezeigt ( Details ).
- Wenn TCG verwendet wird und keine Hardwarevirtualisierung, wird die Codegenerierung für jede Hostarchitektur sowie für einen universellen Interpreter separat unterstützt
- ... und alles - emulierte Peripheriegeräte, Benutzeroberfläche, Migration, Wiedergabe von Datensätzen usw.
Wussten Sie übrigens: QEMU kann nicht nur den gesamten Computer emulieren, sondern auch den Prozessor für einen separaten Benutzerprozess im Host-Kernel, der beispielsweise vom AFL-Fuzzer zur Instrumentierung von Binärdateien verwendet wird. Vielleicht möchte jemand diesen QEMU-Betriebsmodus auf JS portieren? ;)
Wie die meisten langjährigen kostenlosen Programme wird QEMU durch einen Aufruf zum configure
und make
. Angenommen, Sie möchten etwas hinzufügen: ein TCG-Backend, eine Thread-Implementierung, etwas anderes. Beeilen Sie sich nicht, sich über die Aussicht auf Kommunikation mit Autoconf zu freuen / entsetzt zu sein (unterstreichen Sie sie gegebenenfalls) - tatsächlich scheint die configure
bei QEMU selbst geschrieben zu sein und es gibt nichts, woraus Sie etwas generieren können.
Webassembly
Was ist das für eine Sache - WebAssembly (auch bekannt als WASM)? Dies ist ein Ersatz für Asm.js, der jetzt nicht mehr vorgibt, gültiger JavaScript-Code zu sein. Im Gegenteil, es ist rein binär und optimiert, und selbst das Schreiben einer Ganzzahl ist nicht sehr einfach: Es wird aus Gründen der Kompaktheit im LEB128- Format gespeichert.
Möglicherweise haben Sie von dem Relooping-Algorithmus für Asm.js gehört, bei dem die Anweisungen zur Steuerung des Ausführungsflusses auf hoher Ebene (dh wenn-dann-sonst-Schleifen usw.) wiederhergestellt werden, unter denen JS-Engines vom LLVM-IR auf niedriger Ebene abgestimmt werden. näher an dem vom Prozessor ausgeführten Maschinencode. Natürlich ist die Zwischendarstellung von QEMU näher an der zweiten. Es scheint, dass hier, Bytecode, das Ende der Qual ist ... Und dann die Blöcke, wenn-dann-sonst und Schleifen! ..
Und dies ist ein weiterer Grund, warum Binaryen nützlich ist: Es kann natürlich Blöcke auf hoher Ebene akzeptieren, die nahe an dem liegen, was in WASM gespeichert wird. Es kann aber auch Code aus dem Diagramm der Basisblöcke und Übergänge zwischen diesen erzeugen. Nun, ich habe bereits gesagt, dass es das WebAssembly-Speicherformat hinter der praktischen C / C ++ - API verbirgt.
TCG (Tiny Code Generator)
TCG war ursprünglich ein Backend für den C-Compiler. Dann konnte es anscheinend die Konkurrenz mit GCC nicht aushalten, fand aber am Ende seinen Platz in QEMU als Code-Generierungsmechanismus für die Host-Plattform. Es gibt auch ein TCG-Backend, das einen abstrakten Bytecode generiert, der sofort vom Interpreter ausgeführt wird, aber ich habe mich entschlossen, diesmal zu gehen. Die Tatsache, dass QEMU bereits die Möglichkeit hat, den Übergang zur generierten TB über die Funktion tcg_qemu_tb_exec
, war für mich sehr hilfreich.
Um ein neues TCG-Backend zu QEMU hinzuzufügen, müssen Sie ein Unterverzeichnis tcg/< >
(in diesem Fall tcg/binaryen
) tcg/binaryen
befinden sich zwei Dateien: tcg-target.h
und tcg-target.inc.c
und alles registrieren Dies ist configure
. Sie können dort andere Dateien ablegen, aber wie Sie anhand der Namen dieser beiden Dateien erraten können, werden beide irgendwo enthalten sein: eine als reguläre Header-Datei (sie wird in tcg/tcg.h
, und diese befindet sich bereits in anderen Dateien in tcg
Verzeichnissen). accel
und nicht nur), das andere nur als Code-Snippet in tcg/tcg.c
, aber es hat Zugriff auf seine statischen Funktionen.
Nachdem ich beschlossen hatte, zu viel Zeit mit dem detaillierten Ablauf und der Funktionsweise zu verbringen, kopierte ich einfach die "Skelette" dieser beiden Dateien aus einer anderen Backend-Implementierung und gab dies im Lizenzheader ehrlich an.
Die tcg-target.h
enthält hauptsächlich Einstellungen in Form von #define
s:
- Wie viele Register und wie breit sind auf der Zielarchitektur (wir haben - so viel wir wollen, es gibt so viele - die Frage ist mehr als das, was der Browser in einem effizienteren Code auf einer "vollständig Ziel" -Architektur generiert ...)
- Ausrichtung der Host-Anweisungen: Auf x86 und in TCI werden die Anweisungen überhaupt nicht ausgerichtet, aber ich werde überhaupt keine Anweisungen in den Codepuffer einfügen, sondern Zeiger auf die Strukturen der Binaryen-Bibliothek, also sage ich: 4 Bytes
- Welche optionalen Anweisungen kann das Backend generieren? Schalten Sie alles ein, was wir in Binaryen finden, und lassen Sie den Beschleuniger den Rest in einfachere aufteilen
- Welche ungefähre Größe des TLB-Cache vom Backend angefordert wird. Tatsache ist, dass in QEMU alles ernst ist: Obwohl es Hilfsfunktionen gibt, die unter Berücksichtigung der Gast-MMU laden / speichern (und wo jetzt ohne?), Speichern sie ihren Übersetzungscache in Form einer Struktur, deren Verarbeitung bequem einzubetten ist direkt zu den Übersetzungsblöcken. Die Frage ist, welcher Offset in dieser Struktur von einer kleinen und schnellen Folge von Befehlen am effizientesten gehandhabt wird
- Hier können Sie den Zweck von einem oder zwei reservierten Registern verdrehen, den TB-Aufruf über eine Funktion aktivieren und optional einige kleine
inline
Funktionen wie flush_icache_range
(dies ist jedoch nicht unser Fall).
Die tcg-target.inc.c
ist natürlich normalerweise viel größer und enthält mehrere erforderliche Funktionen:
- Initialisierung, die unter anderem Einschränkungen angibt, mit welcher Anweisung welche Operanden arbeiten können. Unverschämt von mir aus einem anderen Backend kopiert
- Funktion, die eine Anweisung des internen Bytecodes akzeptiert
- Hier können Sie
tcg/tcg.c
, und auch hier können Sie statische Funktionen aus tcg/tcg.c
Für mich selbst habe ich die folgende Strategie gewählt: In den ersten Wörtern des nächsten Übersetzungsblocks habe ich vier Zeiger aufgeschrieben: die 0xFFFFFFFF
(ein bestimmter Wert in der Nähe von 0xFFFFFFFF
, der den aktuellen Status von TB bestimmt), den Kontext, das generierte Modul und die magische Zahl für das Debuggen. Zuerst wurde das Label auf 0xFFFFFFFF - n
, wobei n
eine kleine positive Zahl ist, und jedes Mal durch den Interpreter um 1 erhöht. Als es 0xFFFFFFFE
erreichte, wurde das Modul 0xFFFFFFFE
und in der Funktionstabelle gespeichert, die in einen kleinen „Launcher“ importiert wurde Die Ausführung verließ tcg_qemu_tb_exec
und das Modul wurde aus dem QEMU-Speicher gelöscht.
Um die Klassiker zu paraphrasieren: "Crutch, wie viel Proger hat sich in diesem Sound für das Herz verflochten ...". Die Erinnerung leckte jedoch irgendwo. Und es war eine von QEMU verwaltete Erinnerung! Ich hatte einen Code, der beim Aufschreiben der nächsten Anweisung (also eines Zeigers) den löschte, zu dem sich der Link an dieser Stelle zuvor befand, aber das half nicht. Im einfachsten Fall weist QEMU beim Start Speicher zu und schreibt dort den generierten Code. Wenn der Puffer endet, wird der Code verworfen und der nächste beginnt an seiner Stelle geschrieben zu werden.
Nachdem ich den Code studiert hatte, stellte ich fest, dass die Krücke mit der magischen Zahl es uns erlaubte, nicht auf die Zerstörung des Haufens zu fallen und beim ersten Durchgang etwas Falsches auf dem nicht initialisierten Puffer freizugeben. Aber wer überschreibt den Puffer, der meine Funktion später umgeht? Wie die Emscripten-Entwickler geraten hatten, habe ich nach einem Problem den resultierenden Code zurück in die native Anwendung portiert und Mozilla Record-Replay darauf gesetzt ... Im Allgemeinen wurde mir eine einfache Sache klar: Jedem Block wird ein struct TranslationBlock
mit seiner Beschreibung zugewiesen. Ratet mal, wo ... Das stimmt, direkt vor dem Block, direkt im Puffer. Nachdem ich dies erkannt hatte, beschloss ich, es mit Krücken (zumindest einigen) zu verknüpfen, warf einfach die magische Zahl heraus und übertrug die verbleibenden Wörter in die struct TranslationBlock
wurde eine einfach verknüpfte Liste erstellt, die Sie beim Zurücksetzen des Übersetzungscaches schnell durchlaufen und Speicher freigeben können.
Einige Krücken blieben erhalten: Zum Beispiel markierte Zeiger im BinaryenExpressionRef
- einige von ihnen sind einfach BinaryenExpressionRef
, BinaryenExpressionRef
sie betrachten Ausdrücke, die linear in die generierte Basiseinheit eingefügt werden müssen, Teil - die Übergangsbedingung zwischen den WBs, Teil - wohin. Nun, es gibt bereits vorbereitete Blöcke für Relooper, die entsprechend den Bedingungen angeschlossen werden müssen. Um zwischen ihnen zu unterscheiden, wird die Annahme verwendet, dass sie alle mindestens vier Bytes ausgerichtet sind, sodass Sie die unteren beiden Bits des Etiketts sicher verwenden können. Sie müssen nur daran denken, es bei Bedarf zu entfernen. Übrigens werden solche Bezeichnungen bereits in QEMU verwendet, um den Grund für das Verlassen des TCG-Zyklus anzugeben.
Verwenden von Binaryen
Module in WebAssembly enthalten Funktionen, von denen jede einen Körper enthält, der einen Ausdruck darstellt. Ausdrücke sind unäre und binäre Operationen, Blöcke, die aus Listen anderer Ausdrücke, Kontrollfluss usw. bestehen. Wie ich bereits sagte, ist der Kontrollfluss hier genau als übergeordnete Verzweigungen, Schleifen, Funktionsaufrufe usw. organisiert. Argumente an Funktionen werden nicht auf dem Stapel übergeben, sondern explizit wie in JS. Es gibt globale Variablen, aber ich habe sie nicht verwendet, daher werde ich nicht darüber sprechen.
Funktionen haben auch lokale Variablen, die von Grund auf neu nummeriert sind, vom Typ: int32 / int64 / float / double. Die ersten n lokalen Variablen sind die Argumente, die an die Funktion übergeben werden. Bitte beachten Sie, dass hier zwar nicht alles in Bezug auf den Kontrollfluss ganz niedrig ist, aber ganzzahlige Zahlen immer noch nicht das Vorzeichen / vorzeichenlose Vorzeichen tragen: Wie sich die Zahl verhält, hängt vom Operationscode ab.
Im Allgemeinen bietet Binaryen eine einfache C-API : Sie erstellen ein Modul, in dem Sie Ausdrücke erstellen - unär, binär, Blöcke aus anderen Ausdrücken, Kontrollfluss usw. Anschließend erstellen Sie eine Funktion, deren Körper Sie einen Ausdruck angeben müssen. Wenn Sie wie ich ein Übergangsdiagramm auf niedriger Ebene haben, hilft Ihnen die Relooper-Komponente. Soweit ich weiß, ist es möglich, den Ausführungsfluss im Block auf hoher Ebene zu steuern, solange er nicht über die Grenzen des Blocks hinausgeht. Das heißt, es ist möglich, einen internen Zweig für schnelle und langsame Pfade innerhalb des integrierten TLB-Cache-Verarbeitungscodes zu erstellen, aber es gibt keine Interferenzen . Wenn Sie relooper freigeben, werden seine Blöcke freigegeben, wenn Sie ein Modul freigeben, verschwinden Ausdrücke, Funktionen usw., die in seiner Arena zugewiesen sind.
Wenn Sie den Code jedoch unterwegs interpretieren möchten, ohne die Interpreterinstanz unnötig zu erstellen und zu löschen, ist es möglicherweise sinnvoll, diese Logik in eine C ++ - Datei zu übertragen und von dort aus die gesamte C ++ - API-Bibliothek direkt zu steuern und dabei die fertigen Wrapper zu umgehen.
Um den Code zu generieren, benötigen Sie also
… — , , — .
--, :
static char buf[1 << 20]; BinaryenModuleOptimize(MODULE); BinaryenSetMemory(MODULE, 0, -1, NULL, NULL, NULL, NULL, NULL, 0, 0); int sz = BinaryenModuleWrite(MODULE, buf, sizeof(buf)); BinaryenModuleDispose(MODULE); EM_ASM({ var module = new WebAssembly.Module(new Uint8Array(wasmMemory.buffer, $0, $1)); var fptr = $2; var instance = new WebAssembly.Instance(module, { 'env': { 'memory': wasmMemory,
- QEMU JS , ( ), . , translation block, , struct TranslationBlock
.
, ( ) Firefox. Chrome - , - WebAssembly, ...
. , , - . , . , WebAssembly , JS, , , .
: 32- , Binaryen, - - 2 32- . , Binaryen . ?
-, « , 32- Linux?» . , : 1 2 Gb.
- ( ). , — . « : , ...».
… Valgrind-, , , , , Valgrind :)
, - , ...