QEMU.js: sada ozbiljno i s WASM-om

Jednom davno odlučio sam se za zabavu dokazati reverzibilnost procesa i naučite kako generirati JavaScript (točnije, Asm.js) iz strojnog koda. Za eksperiment je odabran QEMU, a nešto kasnije napisan je članak na Habru. U komentarima mi je savjetovano da preradim projekt u WebAssemblyju, pa čak i sam odustanem skoro gotovo Nekako nisam želio projekt... Radovi su išli, ali jako sporo, a sad, nedavno je izašao taj članak komentar na temu “Pa kako je sve završilo?” Kao odgovor na moj detaljan odgovor, čuo sam "Ovo zvuči kao članak." Pa ako može bit će članak. Možda će nekome biti od koristi. Iz nje će čitatelj naučiti neke činjenice o dizajnu QEMU pozadina za generiranje koda, kao i kako napisati Just-in-Time kompajler za web aplikaciju.

zadaci

Budući da sam već naučio kako "nekako" prenijeti QEMU na JavaScript, ovaj put je odlučeno da to učinim mudro i ne ponavljam stare greške.

Pogreška broj jedan: ogranak od oslobađanja točke

Moja prva pogreška bila je račvanje moje verzije iz uzvodne verzije 2.4.1. Onda mi se to učinilo dobrom idejom: ako point release postoji, onda je vjerojatno stabilniji od jednostavnog 2.4, a još više od grane master. A budući da sam planirao dodati priličnu količinu svojih grešaka, uopće mi nisu trebali tuđi. Tako je valjda i ispalo. Ali evo u čemu je stvar: QEMU ne miruje, čak su u nekom trenutku najavili optimizaciju generiranog koda za 10 posto.“Da, sad ću se smrznuti“, pomislio sam i slomio se. Ovdje trebamo napraviti digresiju: ​​zbog jednonitne prirode QEMU.js i činjenice da izvorni QEMU ne podrazumijeva odsutnost višenitnosti (to jest, sposobnost istovremenog rada s nekoliko nepovezanih staza koda, i ne samo "koristi sve jezgre") kritično je za to, glavne funkcije niti koje sam morao "isključiti" da mogu pozvati izvana. To je stvorilo neke prirodne probleme tijekom spajanja. Međutim, činjenica da su neke od promjena iz grane master, s kojima sam pokušao spojiti svoj kod, također su odabrani u izdanju točke (i prema tome u mojoj grani) također vjerojatno ne bi predstavljali dodatnu pogodnost.

Općenito, odlučio sam da još uvijek ima smisla izbaciti prototip, rastaviti ga na dijelove i napraviti novu verziju od nule na temelju nečeg svježijeg i sada od master.

Pogreška broj dva: TLP metodologija

U biti, to nije pogreška, općenito, to je samo značajka stvaranja projekta u uvjetima potpunog nerazumijevanja i "gdje i kako se kretati?" I općenito "hoćemo li stići?" U ovim uvjetima nespretno programiranje bila opravdana opcija, ali je, naravno, nisam htio nepotrebno ponavljati. Ovaj put sam to želio učiniti mudro: atomska predaja, svjesne promjene koda (a ne "nizanje nasumičnih znakova dok se ne kompajlira (s upozorenjima)", kao što je Linus Torvalds jednom rekao o nekome, prema Wikicitatu), itd.

Pogreška broj tri: ući u vodu ne poznajući gaz

Još uvijek se nisam u potpunosti riješio toga, ali sada sam odlučio da uopće neću ići putem manjeg otpora i da ću to učiniti “kao odrasla osoba”, naime, napisati svoj TCG backend od nule, kako ne bi da bih kasnije morao reći: "Da, ovo je naravno, polako, ali ne mogu sve kontrolirati - tako se piše TCI..." Štoviše, ovo se u početku činilo kao očito rješenje, jer Generiram binarni kod. Kako kažu, “Gent je okupioу, ali ne taj”: kod je, naravno, binarni, ali se kontrola ne može jednostavno prenijeti na njega - mora se eksplicitno gurnuti u preglednik za kompilaciju, što rezultira određenim objektom iz JS svijeta, koji još treba biti negdje spremljen. Međutim, na normalnim RISC arhitekturama, koliko ja razumijem, tipična situacija je potreba za eksplicitnim resetiranjem predmemorije instrukcija za regenerirani kod - ako to nije ono što nam treba, onda je, u svakom slučaju, blizu. Osim toga, iz mog posljednjeg pokušaja naučio sam da se kontrola ne čini prebačenom u sredinu bloka prijevoda, tako da zapravo ne trebamo bajt kod interpretiran iz bilo kojeg pomaka i možemo ga jednostavno generirati iz funkcije na TB .

Došli su i šutnuli

Iako sam počeo prepisivati ​​kod još u srpnju, nezapaženo se pojavio čarobni udarac: obično pisma s GitHuba stižu kao obavijesti o odgovorima na Issues i Pull zahtjeve, ali ovdje, iznenada spomenuti u niti Binaryen kao qemu backend u kontekstu, "Učinio je tako nešto, možda će nešto reći." Razgovarali smo o korištenju srodne knjižnice Emscriptena Binaryen za stvaranje WASM JIT. Pa, rekao sam da tamo imate Apache 2.0 licencu, a QEMU kao cjelina se distribuira pod GPLv2, i nisu baš kompatibilni. Odjednom se pokazalo da licenca može biti popraviti nekako (Ne znam: možda promijeniti, možda dvojno licenciranje, možda nešto drugo...). To me je, naravno, obradovalo, jer sam do tada već dobro pogledao binarni format WebAssembly, i bilo mi je nekako tužno i neshvatljivo. Postojala je i knjižnica koja bi proždirala osnovne blokove s prijelaznim grafom, proizvodila bajt kod, pa čak i pokretala u samom interpreteru, ako je potrebno.

Zatim je bilo još pismo na QEMU listi za slanje e-pošte, ali ovo je više o pitanju, "Kome to uopće treba?" I to je iznenada, pokazalo se da je potrebno. U najmanju ruku, možete skupiti takve mogućnosti korištenja ako radi manje ili više brzo:

  • pokretanje nečeg edukativnog bez ikakve instalacije
  • virtualizacija na iOS-u, gdje je, prema glasinama, jedina aplikacija koja ima pravo na generiranje koda u hodu JS engine (je li to istina?)
  • demonstracija mini-OS-a - single-floppy, ugrađeni, sve vrste firmware-a, itd...

Značajke vremena izvođenja preglednika

Kao što sam već rekao, QEMU je vezan za multithreading, ali ga preglednik nema. Pa, to jest, ne ... U početku uopće nije postojao, onda su se pojavili WebWorkers - koliko sam shvatio, ovo je multithreading temeljen na prijenosu poruka bez zajedničkih varijabli. Naravno, ovo stvara značajne probleme prilikom prijenosa postojećeg koda temeljenog na modelu zajedničke memorije. Tada je pod pritiskom javnosti i to provedeno pod imenom SharedArrayBuffers. Uvodio se postupno, slavili su njegovo lansiranje u različitim preglednicima, zatim su slavili Novu godinu, pa Meltdown... Nakon čega su došli do zaključka da je grubo ili grubo mjerenje vremena, ali uz pomoć zajedničke memorije i nit koja povećava brojač, sve je isto ispast će prilično točno. Stoga smo onemogućili višenitnost sa dijeljenom memorijom. Čini se da su ga kasnije ponovno uključili, ali, kao što je postalo jasno iz prvog eksperimenta, ima života i bez njega, a ako je tako, pokušat ćemo to učiniti bez oslanjanja na multithreading.

Druga značajka je nemogućnost niskorazinskih manipulacija sa stogom: ne možete jednostavno uzeti, spremiti trenutni kontekst i prebaciti se na novi s novim stogom. Stogom poziva upravlja JS virtualni stroj. Čini se, u čemu je problem, budući da smo ipak odlučili upravljati bivšim tokovima potpuno ručno? Činjenica je da je blok I/O u QEMU-u implementiran kroz korutine, i tu bi dobro došla manipulacija stekom niske razine. Srećom, Emscipten već sadrži mehanizam za asinkrone operacije, čak dva: Asincificirati и Emterpreter. Prvi radi kroz značajno povećanje generiranog JavaScript koda i više nije podržan. Drugi je trenutni "ispravan način" i radi kroz generiranje bajtkoda za izvorni tumač. Radi, naravno, sporo, ali ne opterećuje kod. Istina, podrška za korutine za ovaj mehanizam morala se pridonijeti neovisno (već su postojale korutine napisane za Asyncify i postojala je implementacija približno istog API-ja za Emterpreter, samo ih je trebalo povezati).

U ovom trenutku još nisam uspio razdvojiti kod u jedan kompajliran u WASM-u i interpretiran pomoću Emterpretera, tako da blok uređaji još ne rade (pogledajte u sljedećoj seriji, kako kažu...). To jest, na kraju biste trebali dobiti nešto poput ove smiješne slojevite stvari:

  • interpretirani blok I/O. Pa, jeste li stvarno očekivali emulirani NVMe s izvornim performansama? 🙂
  • statički kompilirani glavni QEMU kod (prevoditelj, drugi emulirani uređaji, itd.)
  • dinamički kompilirani gostujući kod u WASM

Značajke QEMU izvora

Kao što ste vjerojatno već pogodili, kod za emulaciju gostujućih arhitektura i kod za generiranje instrukcija glavnog stroja odvojeni su u QEMU. Zapravo, još je malo složenije:

  • postoje gostujuće arhitekture
  • tu je akceleratorima, naime, KVM za virtualizaciju hardvera na Linuxu (za gostujuće i host sustave koji su međusobno kompatibilni), TCG za generiranje JIT koda bilo gdje. Počevši od QEMU 2.9, pojavila se podrška za HAXM hardverski virtualizacijski standard na Windowsima (pojedinosti)
  • ako se koristi TCG, a ne hardverska virtualizacija, tada ima zasebnu podršku za generiranje koda za svaku arhitekturu glavnog računala, kao i za univerzalni tumač
  • ... i oko svega toga - emulirane periferije, korisničko sučelje, migracija, snimanje-reprodukcija, itd.

Usput, jeste li znali: QEMU može emulirati ne samo cijelo računalo, već i procesor za odvojeni korisnički proces u glavnom kernelu, koji koristi, na primjer, AFL fuzzer za binarnu instrumentaciju. Možda bi netko želio ovaj način rada QEMU-a prenijeti na JS? 😉

Kao i većina dugotrajnog besplatnog softvera, QEMU je izgrađen putem poziva configure и make. Recimo da odlučite dodati nešto: TCG backend, implementaciju niti, nešto drugo. Nemojte žuriti da budete sretni/užasnuti (podcrtajte ako je potrebno) zbog mogućnosti komunikacije s Autoconfom - zapravo, configure QEMU je očito sam napisao i nije generiran ni iz čega.

WebAssembly

Dakle, što je ta stvar koja se zove WebAssembly (aka WASM)? Ovo je zamjena za Asm.js, više se ne pretvara da je važeći JavaScript kod. Naprotiv, on je čisto binarni i optimiziran, pa čak ni jednostavno pisanje cijelog broja u njega nije baš jednostavno: radi kompaktnosti, pohranjuje se u formatu LEB128.

Možda ste čuli za algoritam ponovnog petljanja za Asm.js - ovo je obnavljanje instrukcija kontrole toka "visoke razine" (to jest, if-then-else, petlje, itd.), za koje su dizajnirani JS motori, od LLVM IR niske razine, bliži strojnom kodu koji izvršava procesor. Naravno, srednji prikaz QEMU je bliži drugom. Reklo bi se, evo ga, bajtkod, kraj mukama... A onda idu blokovi, if-then-else i petlje!..

I ovo je još jedan razlog zašto je Binaryen koristan: može prirodno prihvatiti blokove visoke razine bliske onima koji bi bili pohranjeni u WASM. Ali također može proizvesti kod iz grafa osnovnih blokova i prijelaza između njih. Pa, već sam rekao da skriva WebAssembly format pohrane iza praktičnog C/C++ API-ja.

TCG (Tiny Code Generator)

TCG bio izvorno backend za prevodilac C. Tada, očito, nije mogao izdržati konkurenciju s GCC-om, ali je na kraju našao svoje mjesto u QEMU-u kao mehanizam za generiranje koda za host platformu. Postoji i TCG pozadina koja generira neki apstraktni bajt kod, koji prevoditelj odmah izvršava, ali odlučio sam da ga ovaj put izbjegnem. Međutim, činjenica da je u QEMU već moguće omogućiti prijelaz na generirani TB kroz funkciju tcg_qemu_tb_exec, pokazalo se vrlo korisnim za mene.

Za dodavanje nove TCG pozadine u QEMU, morate stvoriti poddirektorij tcg/<имя архитектуры> (u ovom slučaju, tcg/binaryen), a sadrži dvije datoteke: tcg-target.h и tcg-target.inc.c и propisati sve je o configure. Tamo možete staviti druge datoteke, ali, kao što možete pogoditi iz naziva ove dvije, obje će biti uključene negdje: jedna kao obična datoteka zaglavlja (uključena je u tcg/tcg.h, a taj se već nalazi u drugim datotekama u direktorijima tcg, accel i ne samo), drugi - samo kao isječak koda u tcg/tcg.c, ali ima pristup svojim statičkim funkcijama.

Odlučivši da ću potrošiti previše vremena na detaljna istraživanja o tome kako funkcionira, jednostavno sam kopirao "kosture" ove dvije datoteke iz druge pozadinske implementacije, iskreno naznačivši to u zaglavlju licence.

datoteka tcg-target.h sadrži uglavnom postavke u obrascu #define-ov:

  • koliko registara i koje širine ima na ciljnoj arhitekturi (imamo ih koliko god želimo, koliko god želimo - pitanje je više o tome što će preglednik generirati u učinkovitiji kod na "potpuno ciljanoj" arhitekturi ...)
  • usklađivanje instrukcija glavnog računala: na x86, pa čak i u TCI-ju, instrukcije uopće nisu usklađene, ali u međuspremnik koda uopće neću staviti instrukcije, već pokazivače na strukture biblioteka Binaryen, pa ću reći: 4 bajtova
  • koje opcijske upute backend može generirati - uključujemo sve što nađemo u Binaryenu, neka akcelerator sam razloži ostalo na jednostavnije
  • Koja je približna veličina TLB predmemorije koju zahtijeva pozadina. Činjenica je da je u QEMU sve ozbiljno: iako postoje pomoćne funkcije koje izvode učitavanje/spremanje uzimajući u obzir gostujući MMU (gdje bismo sada bili bez njega?), one spremaju svoju predmemoriju prijevoda u obliku strukture, čiju je obradu prikladno ugraditi izravno u blokove emitiranja. Pitanje je koji se pomak u ovoj strukturi najučinkovitije obrađuje malim i brzim nizom naredbi?
  • ovdje možete podesiti svrhu jednog ili dva rezervirana registra, omogućiti pozivanje TB kroz funkciju i po izboru opisati nekoliko malih inline-funkcije poput flush_icache_range (ali ovo nije naš slučaj)

datoteka tcg-target.inc.c, naravno, obično je mnogo veće veličine i sadrži nekoliko obveznih funkcija:

  • inicijalizacija, uključujući ograničenja na to koje instrukcije mogu raditi na kojim operandima. Očigledno sam kopirao iz druge pozadine
  • funkcija koja uzima jednu internu instrukciju bajt koda
  • Ovdje možete staviti i pomoćne funkcije, a možete koristiti i statičke funkcije iz tcg/tcg.c

Za sebe sam odabrao sljedeću strategiju: u prvim riječima sljedećeg bloka prijevoda zapisao sam četiri pokazivača: početnu oznaku (određenu vrijednost u blizini 0xFFFFFFFF, koji je odredio trenutno stanje TB-a), kontekst, generirani modul i magični broj za otklanjanje pogrešaka. Isprva je oznaka postavljena u 0xFFFFFFFF - nGdje n - mali pozitivan broj, a svaki put kada je izvršen preko interpretera povećavao se za 1. Kada je dosegao 0xFFFFFFFE, došlo je do kompilacije, modul je spremljen u tablicu funkcija, uvezen u mali "pokretač", u koji je izvršenje otišlo iz tcg_qemu_tb_exec, a modul je uklonjen iz QEMU memorije.

Da parafraziramo klasike, “Crutch, koliko je toga utkano u ovaj zvuk za progersko srce...”. Međutim, sjećanje je negdje curilo. Štoviše, memorijom je upravljao QEMU! Imao sam kod koji je prilikom pisanja sljedeće instrukcije (dobro, pokazivača) izbrisao onu čiji je link ranije bio na ovom mjestu, ali to nije pomoglo. Zapravo, u najjednostavnijem slučaju, QEMU dodjeljuje memoriju pri pokretanju i tamo zapisuje generirani kod. Kada međuspremnik ponestane, kod se izbacuje i na njegovo mjesto počinje se pisati sljedeći.

Nakon proučavanja koda, shvatio sam da mi je trik s magičnim brojem omogućio da ne pogriješim pri uništavanju gomile oslobađanjem nečeg pogrešnog na neinicijaliziranom međuspremniku pri prvom prolazu. Ali tko prepisuje međuspremnik da bi kasnije zaobišao moju funkciju? Kao što programeri Emscriptena savjetuju, kad sam naišao na problem, portirao sam dobiveni kod natrag u nativnu aplikaciju, postavio Mozilla Record-Replay na njega... Općenito, na kraju sam shvatio jednostavnu stvar: za svaki blok, a struct TranslationBlock sa svojim opisom. Pogodite gdje... Tako je, neposredno prije bloka točno u međuspremniku. Shvativši to, odlučio sam prestati koristiti štake (barem neke) i jednostavno izbacio magični broj, a preostale riječi prebacio na struct TranslationBlock, stvarajući pojedinačno povezani popis koji se može brzo pregledati kada se poništi predmemorija prijevoda i oslobodi memoriju.

Neke štake ostaju: na primjer, označeni pokazivači u međuspremniku koda - neki od njih su jednostavno BinaryenExpressionRef, odnosno gledaju izraze koje treba linearno staviti u generirani osnovni blok, dio je uvjet za prijelaz između BB-ova, dio je kamo ići. Pa već postoje pripremljeni blokovi za Relooper koje je potrebno spojiti prema uvjetima. Kako bismo ih razlikovali, koristi se pretpostavka da su svi poravnati s najmanje četiri bajta, tako da možete sigurno koristiti najmanje značajna dva bita za oznaku, samo se trebate sjetiti ukloniti ih ako je potrebno. Usput, takve se oznake već koriste u QEMU za označavanje razloga za izlazak iz TCG petlje.

Korištenje Binaryena

Moduli u WebAssemblyju sadrže funkcije od kojih svaka sadrži tijelo, koje je izraz. Izrazi su unarne i binarne operacije, blokovi koji se sastoje od popisa drugih izraza, kontrolni tijek itd. Kao što sam već rekao, tok upravljanja ovdje je organiziran upravo kao grane visoke razine, petlje, pozivi funkcija itd. Argumenti funkcijama se ne prosljeđuju na stogu, već eksplicitno, baš kao u JS-u. Postoje i globalne varijable, ali ih nisam koristio, pa vam neću govoriti o njima.

Funkcije također imaju lokalne varijable, numerirane od nule, tipa: int32 / int64 / float / double. U ovom slučaju, prvih n lokalnih varijabli su argumenti proslijeđeni funkciji. Imajte na umu da, iako sve ovdje nije sasvim niske razine u smislu tijeka kontrole, cijeli brojevi još uvijek ne nose atribut "predpisan/nepredpisan": kako se broj ponaša ovisi o kodu operacije.

Općenito govoreći, Binaryen pruža jednostavan C-API: kreirate modul, u njemu kreirati izraze - unarne, binarne, blokove iz drugih izraza, kontrolirati protok itd. Zatim kreirate funkciju s izrazom kao tijelom. Ako vi, poput mene, imate graf prijelaza niske razine, komponenta relooper će vam pomoći. Koliko ja razumijem, moguće je koristiti visoku razinu kontrole tijeka izvršenja u bloku, sve dok ne ide izvan granica bloka - to jest, moguće je napraviti interni brzi put / spor staza se grana unutar ugrađenog koda za obradu predmemorije TLB-a, ali da ne ometa "vanjski" tok kontrole. Kada oslobodite relooper, oslobađaju se njegovi blokovi; kada oslobodite modul, izrazi, funkcije itd. koji su mu dodijeljeni nestaju arena.

Međutim, ako želite interpretirati kod u hodu bez nepotrebnog stvaranja i brisanja instance tumača, možda bi imalo smisla staviti ovu logiku u C++ datoteku i odatle izravno upravljati cijelim C++ API-jem biblioteke, zaobilazeći gotove izrađeni omoti.

Dakle, za generiranje koda koji vam je potreban

// настроить глобальные параметры (можно поменять потом)
BinaryenSetAPITracing(0);

BinaryenSetOptimizeLevel(3);
BinaryenSetShrinkLevel(2);

// создать модуль
BinaryenModuleRef MODULE = BinaryenModuleCreate();

// описать типы функций (как создаваемых, так и вызываемых)
helper_type  BinaryenAddFunctionType(MODULE, "helper-func", BinaryenTypeInt32(), int32_helper_args, ARRAY_SIZE(int32_helper_args));
// (int23_helper_args приоб^Wсоздаются отдельно)

// сконструировать супер-мега выражение
// ... ну тут уж вы как-нибудь сами :)

// потом создать функцию
BinaryenAddFunction(MODULE, "tb_fun", tb_func_type, func_locals, FUNC_LOCALS_COUNT, expr);
BinaryenAddFunctionExport(MODULE, "tb_fun", "tb_fun");
...
BinaryenSetMemory(MODULE, (1 << 15) - 1, -1, NULL, NULL, NULL, NULL, NULL, 0, 0);
BinaryenAddMemoryImport(MODULE, NULL, "env", "memory", 0);
BinaryenAddTableImport(MODULE, NULL, "env", "tb_funcs");

// запросить валидацию и оптимизацию при желании
assert (BinaryenModuleValidate(MODULE));
BinaryenModuleOptimize(MODULE);

... ako sam nešto zaboravio, oprostite, ovo je samo prikaz razmjera, a detalji su u dokumentaciji.

I sad počinje crack-fex-pex, otprilike ovako:

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,
          // ...
      }
  );
  // и вот уже у вас есть instance!
}, buf, sz);

Kako bi se na neki način povezali svjetovi QEMU i JS te ujedno brzo pristupilo kompajliranim funkcijama, kreiran je niz (tablica funkcija za uvoz u launcher) i tamo su smještene generirane funkcije. Za brzi izračun indeksa, u početku je korišten indeks bloka prijevoda nulte riječi, ali onda je indeks izračunat pomoću ove formule počeo jednostavno stati u polje u struct TranslationBlock.

Usput, demo (trenutno s mutnom licencom) dobro radi samo u Firefoxu. Chromeovi programeri bili su nekako nije spreman na činjenicu da bi netko želio stvoriti više od tisuću instanci WebAssembly modula, pa su jednostavno dodijelili gigabajt virtualnog adresnog prostora za svaki...

To je sve za sada. Možda će biti još koji članak ako nekoga bude zanimalo. Naime, ostaje najmanje samo učiniti da blok uređaji rade. Također bi moglo imati smisla kompilaciju WebAssembly modula učiniti asinkronom, kao što je uobičajeno u JS svijetu, budući da još uvijek postoji tumač koji sve to može učiniti dok izvorni modul ne bude spreman.

Na kraju jedna zagonetka: kompajlirali ste binarnu datoteku na 32-bitnoj arhitekturi, ali se kod kroz memorijske operacije penje iz Binaryena, negdje na stogu ili negdje drugdje u gornja 2 GB 32-bitnog adresnog prostora. Problem je u tome što s Binaryen-ove točke gledišta ovo pristupa prevelikoj rezultirajućoj adresi. Kako to zaobići?

Na način administratora

Nisam ovo testirao, ali prva mi je pomisao bila "Što ako sam instalirao 32-bitni Linux?" Tada će gornji dio adresnog prostora biti zauzet kernelom. Pitanje je samo koliko će biti zauzeto: 1 ili 2 Gb.

Na programerski način (opcija za praktičare)

Napuhajmo balon na vrhu adresnog prostora. Ni sam ne razumijem zašto radi - tamo već mora postojati hrpa. Ali “mi smo praktičari: sve nam ide, ali nitko ne zna zašto...”

// 2gbubble.c
// Usage: LD_PRELOAD=2gbubble.so <program>

#include <sys/mman.h>
#include <assert.h>

void __attribute__((constructor)) constr(void)
{
  assert(MAP_FAILED != mmap(1u >> 31, (1u >> 31) - (1u >> 20), PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
}

... istina je da nije kompatibilan s Valgrindom, ali, srećom, sam Valgrind vrlo učinkovito gura sve odatle :)

Možda će netko bolje objasniti kako funkcionira ovaj moj kod...

Izvor: www.habr.com

Dodajte komentar