QEMU.js: sada ozbiljan i sa WASM-om

Jednom davno sam se odlučio za zabavu dokazati reverzibilnost procesa i naučite kako da generišete JavaScript (tačnije, Asm.js) iz mašinskog koda. Za eksperiment je odabran QEMU, a nešto kasnije napisan je članak na Habru. U komentarima mi je savjetovano da prepravim projekat u WebAssembly, pa čak i sam odustanem skoro gotovo Nekako nisam htela projekat... Posao je tekao, ali vrlo sporo, a sada se nedavno u tom članku pojavio komentar na temu “Pa kako se sve završilo?” Kao odgovor na moj detaljan odgovor, čuo sam "Ovo zvuči kao članak." Pa, ako možete, biće članak. Možda će nekome biti od koristi. Iz njega će čitalac naučiti neke činjenice o dizajnu pozadinskih delova za generisanje QEMU koda, kao i o tome kako napisati Just-in-Time kompajler za web aplikaciju.

zadaci

Pošto sam već naučio kako da „nekako” portujem QEMU u JavaScript, ovog puta je odlučeno da to uradim mudro i da ne ponavljam stare greške.

Greška broj jedan: grananje od tačke oslobađanja

Moja prva greška je bila što sam odvojio svoju verziju od prethodne verzije 2.4.1. Tada mi se učinilo dobrom idejom: ako postoji point release, onda je vjerovatno stabilniji od jednostavnog 2.4, a još više od grane master. A pošto sam planirao da dodam priličnu količinu sopstvenih grešaka, uopšte mi nije trebao niko drugi. Tako je vjerovatno ispalo. Ali evo u čemu je stvar: QEMU ne stoji mirno, a u nekom trenutku su čak najavili optimizaciju generiranog koda za 10 posto. „Da, sad ću se smrznuti“, pomislio sam i prekinuo. Ovdje moramo napraviti digresiju: ​​zbog jednonitne prirode QEMU.js i činjenice da originalni QEMU ne podrazumijeva odsustvo višenitnog rada (odnosno, mogućnost istovremenog rada s nekoliko nepovezanih puteva koda, i ne samo "koristite sve kernele") je kritična za njega, glavne funkcije niti koje sam morao "ispostaviti" da bih mogao pozvati izvana. Ovo je stvorilo neke prirodne probleme tokom spajanja. Međutim, činjenica da su neke promjene iz grane master, sa kojima sam pokušao spojiti svoj kod, također su bili odabrani u trenutku izdanja (a samim tim i u mojoj grani) također vjerovatno ne bi dodatno pogodili.

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.

Greška broj dva: TLP metodologija

U suštini, ovo nije greška, već generalno, to je samo karakteristika kreiranja projekta u uslovima potpunog nerazumevanja i „kuda i kako da se krećemo?“ i uopšte „hoćemo li stići tamo?“ U ovim uslovima nespretno programiranje bila opravdana opcija, ali, naravno, nisam želeo da je ponavljam bez potrebe. Ovaj put sam to htio učiniti mudro: atomsko urezivanje, svjesne promjene koda (a ne „nizanje nasumičnih znakova dok se ne kompajlira (sa upozorenjima)“, kako je jednom rekao Linus Torvalds o nekome, prema Wikicitatu) itd.

Greška broj tri: ulazak u vodu bez poznavanja forda

Ovoga se još nisam u potpunosti riješio, ali sada sam odlučio da uopće ne idem putem manjeg otpora, i da to uradim „kao odrasla osoba“, naime, napišem svoj TCG backend ispočetka, da ne da kasnije moram reći: „Da, ovo je naravno, polako, ali ne mogu sve kontrolisati - tako se piše TCI...“ Štaviše, ovo je u početku izgledalo kao očigledno rešenje, pošto Ja generišem binarni kod. Kako kažu, „Gent se okupioу, ali ne i taj”: kod je, naravno, binarni, ali kontrola se ne može jednostavno prenijeti na njega – mora se eksplicitno ugurati u pretraživač radi kompilacije, što rezultira određenim objektom iz JS svijeta, koji još uvijek treba biti sačuvan negde. Međutim, na normalnim RISC arhitekturama, koliko sam shvatio, tipična situacija je potreba da se eksplicitno resetuje keš instrukcija za regenerisani kod - ako ovo nije ono što nam treba, onda je, u svakom slučaju, blizu. Osim toga, iz mog posljednjeg pokušaja, naučio sam da se čini da se kontrola ne prenosi na sredinu bloka za prevođenje, tako da nam zapravo ne treba bajtkod interpretiran iz bilo kojeg pomaka, i možemo ga jednostavno generirati iz funkcije na TB-u. .

Došli su i šutirali

Iako sam počeo da prepisujem kod još u julu, magični udarac se prikrao neprimećeno: pisma sa GitHuba obično stižu kao obaveštenja o odgovorima na probleme i zahteve za izvlačenje, ali ovde, odjednom pomenuti u temi Binaryen kao qemu backend u kontekstu, "On je tako nešto uradio, možda će nešto i reći." Razgovarali smo o korištenju Emscripten povezane biblioteke Binaryen da kreirate WASM JIT. Pa, rekao sam da tamo imate Apache 2.0 licencu, a QEMU se kao cjelina distribuira pod GPLv2, i nisu baš kompatibilni. Odjednom se pokazalo da licenca može biti popravi to 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, a ja sam bio nekako tužan i neshvatljiv. Postojala je i biblioteka koja bi progutala osnovne blokove sa grafom prelaza, proizvela bajtkod, pa čak i pokrenula u samom interpretatoru, ako je potrebno.

Onda je bilo više pismo na QEMU mailing listi, ali ovo se više odnosi na pitanje „Kome ​​je to uopće potrebno?“ I jeste odjednom, ispostavilo se da je neophodno. U najmanju ruku, možete skupiti sljedeće 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 firmvera, itd...

Funkcije vremena rada pretraživača

Kao što sam već rekao, QEMU je vezan za multithreading, ali pretraživač to nema. Pa, to jest, ne... Isprva uopšte nije postojao, a onda se pojavio WebWorkers - koliko sam shvatio, ovo je višenitnost zasnovana na prenošenju poruka bez zajedničkih varijabli. Naravno, ovo stvara značajne probleme prilikom prenosa postojećeg koda zasnovanog na modelu dijeljene memorije. Tada je pod pritiskom javnosti i sprovedeno pod imenom SharedArrayBuffers. Postepeno je uvodio, slavili su njegovo lansiranje u različitim pretraživačima, zatim su slavili Novu godinu, pa Meltdown... Nakon čega su došli do zaključka da je mjerenje vremena grubo ili grubo, ali uz pomoć zajedničke memorije i nit povećava brojač, sve je isto to će raditi prilično precizno. Tako smo onemogućili višenitnost sa zajedničkom memorijom. Čini se da su ga kasnije ponovo uključili, ali, kao što je postalo jasno iz prvog eksperimenta, postoji život i bez toga, a ako jeste, pokušat ćemo to učiniti bez oslanjanja na multithreading.

Druga karakteristika je nemogućnost manipulacija niskog nivoa sa stekom: ne možete jednostavno uzeti, sačuvati trenutni kontekst i prebaciti se na novi sa novim stekom. Stekom poziva upravlja JS virtuelna mašina. Čini se, u čemu je problem, budući da smo ipak odlučili da bivšim tokovima upravljamo potpuno ručno? Činjenica je da se blok I/O u QEMU implementira kroz korutine, i tu bi manipulacije stekom niskog nivoa dobro došle. Na sreću, Emscipten već sadrži mehanizam za asinkrone operacije, čak dva: Asincificiraj и Emterpreter. Prvi radi kroz značajnu naduvanost u generiranom JavaScript kodu i više nije podržan. Drugi je trenutni "ispravan način" i radi kroz generisanje bajtkoda za izvorni interpreter. Radi, naravno, sporo, ali ne naduvava kod. Istina, podrška za korutine za ovaj mehanizam je morala biti neovisna (već su postojale korutine napisane za Asyncify i postojala je implementacija približno istog API-ja za Emterpreter, samo ih je trebalo povezati).

Trenutno još nisam uspio podijeliti kod na jedan kompajliran u WASM-u i interpretiran pomoću Emterpreter-a, tako da blok uređaji još ne rade (pogledajte u sljedećoj seriji, kako kažu...). Odnosno, na kraju biste trebali dobiti nešto poput ove smiješne slojevite stvari:

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

Karakteristike QEMU izvora

Kao što ste verovatno već pretpostavili, kod za emulaciju gostujuće arhitekture i kod za generisanje instrukcija host mašine su odvojeni u QEMU. U stvari, to je još malo teže:

  • postoje gostujuće arhitekture
  • je akceleratori, naime, KVM za virtuelizaciju hardvera na Linuxu (za gostujuće i host sisteme međusobno kompatibilne), TCG za generisanje JIT koda bilo gde. Počevši od QEMU 2.9, pojavila se podrška za standard virtuelizacije hardvera HAXM na Windows (detalji)
  • ako se koristi TCG a ne hardverska virtuelizacija, onda ima posebnu podršku za generisanje koda za svaku arhitekturu hosta, kao i za univerzalni interpreter
  • ... i oko svega toga - emulirane periferije, korisničko sučelje, migracija, repriza snimanja itd.

Usput, da li ste znali: QEMU može emulirati ne samo cijeli računar, već i procesor za poseban korisnički proces u jezgru hosta, koji koristi, na primjer, AFL fuzzer za binarnu instrumentaciju. Možda bi neko želio da prenese ovaj način rada QEMU-a na JS? 😉

Kao i većina dugotrajnog besplatnog softvera, QEMU je izgrađen putem poziva configure и make. Recimo da ste odlučili dodati nešto: TCG backend, implementaciju niti, nešto drugo. Nemojte žuriti da budete sretni/užasnuti (podvucite prema potrebi) zbog mogućnosti komuniciranja s Autoconf - u stvari, configure QEMU je očigledno sam pisan i nije generisan ni iz čega.

WebAssembly

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

Možda ste čuli za algoritam reloopinga za Asm.js - ovo je obnavljanje instrukcija kontrole toka na “visokom nivou” (tj. if-then-else, petlje, itd.), za koje su dizajnirani JS motori, od LLVM IR niskog nivoa, bliže mašinskom kodu koji izvršava procesor. Naravno, srednji prikaz QEMU je bliži drugom. Čini se da je tu, bajtkod, kraj mukama... A onda su blokovi, if-onda-else i petlje!..

I ovo je još jedan razlog zašto je Binaryen koristan: može prirodno prihvatiti blokove visokog nivoa blizu onoga što bi bilo pohranjeno u WASM-u. Ali također može proizvesti kod iz grafa osnovnih blokova i prijelaza između njih. Pa, već sam rekao da skriva WebAssembly format skladištenja iza praktičnog C/C++ API-ja.

TCG (Tiny Code Generator)

TCG je prvobitno backend za kompajler C. Tada, očigledno, nije mogao da izdrži konkurenciju sa GCC-om, ali je na kraju našao svoje mesto u QEMU kao mehanizam za generisanje koda za host platformu. Postoji i TCG backend koji generiše neki apstraktni bajt kod, koji se odmah izvršava od strane interpretatora, ali sam odlučio da ga ovaj put izbegnem. Međutim, činjenica da je u QEMU već moguće omogućiti prijelaz na generiranu TB kroz funkciju tcg_qemu_tb_exec, ispostavilo se da mi je veoma korisno.

Da biste dodali novi TCG backend u QEMU, morate kreirati poddirektorij tcg/<имя архитектуры> (u ovom slučaju, tcg/binaryen), a sadrži dva fajla: tcg-target.h и tcg-target.inc.c и propisati sve je o tome configure. Tamo možete staviti druge datoteke, ali, kao što možete pretpostaviti iz imena ova dva, oba će biti uključena negdje: jedan kao običan fajl zaglavlja (uključen 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 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 to funkcionira, jednostavno sam kopirao „skelete“ ova dva fajla iz druge pozadinske implementacije, iskreno navodeći to u zaglavlju licence.

fajl tcg-target.h sadrži uglavnom postavke u obrascu #define-s:

  • koliko registara i koja širina ima na ciljnoj arhitekturi (imamo koliko hoćemo, koliko hoćemo - više je pitanje šta će pretraživač generisati u efikasniji kod na "potpuno ciljanoj" arhitekturi ...)
  • poravnanje instrukcija hosta: na x86, pa čak i u TCI-u, instrukcije uopće nisu usklađene, ali ću staviti u međuspremnik koda uopće ne instrukcije, već pokazivače na strukture Binaryen biblioteke, pa ću reći: 4 bajtova
  • koje neobavezne instrukcije backend može generirati - uključujemo sve što pronađemo u Binaryenu, neka akcelerator sam razbije ostalo na jednostavnije
  • Koja je približna veličina TLB keš memorije koju traži backend. Činjenica je da je u QEMU sve ozbiljno: iako postoje pomoćne funkcije koje obavljaju učitavanje/skladištenje uzimajući u obzir gostujući MMU (gdje bismo sada bili bez njega?), oni spremaju svoju prijevodnu keš memoriju u obliku strukture, tj. čiju obradu je pogodno ugraditi direktno u blokove emitovanja. Pitanje je, koji ofset u ovoj strukturi se najefikasnije obrađuje malim i brzim nizom naredbi?
  • ovdje možete podesiti svrhu jednog ili dva rezervirana registra, omogućiti pozivanje TB kroz funkciju i opciono opisati nekoliko malih inline-funkcioniše kao flush_icache_range (ali ovo nije naš slučaj)

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

  • inicijalizacija, uključujući ograničenja o tome koje instrukcije mogu raditi na kojim operandima. Ja sam očigledno kopirao sa drugog backenda
  • funkcija koja uzima jednu internu instrukciju bajtkoda
  • 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 prijevodnog bloka zapisao sam četiri pokazivača: početnu oznaku (određena vrijednost u blizini 0xFFFFFFFF, koji je odredio trenutno stanje TB), kontekst, generirani modul i magični broj za otklanjanje grešaka. U početku je oznaka postavljena 0xFFFFFFFF - ngde n - mali pozitivan broj, i svaki put kada se izvršavao preko interpretatora povećavao se za 1. Kada je dostigao 0xFFFFFFFE, kompilacija je obavljena, modul je sačuvan u funkcijskoj tabeli, uvezen u mali „launcher“, u koji je išlo izvršenje tcg_qemu_tb_exec, a modul je uklonjen iz QEMU memorije.

Da parafraziramo klasike, "Štaka, koliko je u ovom zvuku isprepleteno za srce progera...". Međutim, sjećanje je negdje curilo. Štaviše, memorijom je upravljao QEMU! Imao sam kod koji je prilikom pisanja sljedeće instrukcije (pa, odnosno pokazivača) izbrisao onaj čiji je link ranije bio na ovom mjestu, ali to nije pomoglo. Zapravo, u najjednostavnijem slučaju, QEMU dodjeljuje memoriju pri pokretanju i tamo upisuje generirani kod. Kada se bafer potroši, kod se izbacuje i na njegovo mjesto počinje da se piše sljedeći.

Nakon što sam proučio kod, shvatio sam da mi je trik sa magičnim brojem omogućio da ne propadnem u uništavanju hrpe tako što sam oslobodio nešto pogrešno na neinicijaliziranom baferu pri prvom prolazu. Ali ko prepisuje bafer da bi zaobišao moju funkciju kasnije? Kao što savetuju Emscripten programeri, kada sam naišao na problem, preneo sam rezultujući kod nazad u matičnu aplikaciju, postavio Mozilla Record-Replay na nju... Generalno, na kraju sam shvatio jednostavnu stvar: za svaki blok, a struct TranslationBlock sa svojim opisom. Pogodi gdje... Tako je, neposredno prije bloka u baferu. Shvativši to, odlučio sam prestati koristiti štake (barem neke), i jednostavno sam izbacio magični broj, a preostale riječi prenio na struct TranslationBlock, kreirajući jednostruko povezanu listu kojom se može brzo preći kada se keš prijevoda resetuje i oslobađa memoriju.

Ostale su neke štake: na primjer, označeni pokazivači u međuspremniku koda - neki od njih su jednostavni BinaryenExpressionRef, odnosno gledaju izraze koje je potrebno linearno staviti u generirani osnovni blok, dio je uvjet za prijelaz između BB-ova, dio je kuda ići. Pa, već postoje pripremljeni blokovi za Relooper koje je potrebno povezati prema uslovima. Da bismo ih razlikovali, koristi se pretpostavka da su svi poravnati za najmanje četiri bajta, tako da možete sigurno koristiti najmanje značajna dva bita za oznaku, samo trebate zapamtiti da je uklonite ako je potrebno. Inače, takve oznake se već koriste u QEMU-u da naznače razlog izlaska iz TCG petlje.

Koristeći Binaryen

Moduli u WebAssembly-u sadrže funkcije, od kojih svaka sadrži tijelo, koje je izraz. Izrazi su unarne i binarne operacije, blokovi koji se sastoje od lista drugih izraza, kontrolni tok itd. Kao što sam već rekao, kontrolni tok ovdje je organiziran upravo kao grane visokog nivoa, petlje, pozivi funkcija itd. Argumenti funkcijama se ne prosljeđuju na stog, već eksplicitno, baš kao u JS-u. Postoje i globalne varijable, ali ih nisam koristio, pa vam neću pričati 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 ovdje sve nije na niskom nivou u smislu toka kontrole, cijeli brojevi još uvijek ne nose atribut “potpisano/nepotpisano”: kako se broj ponaša ovisi o kodu operacije.

Uopšteno govoreći, Binaryen pruža jednostavan C-API: kreirate modul, u njemu kreirajte izraze - unarne, binarne, blokove iz drugih izraza, kontrolni tok, itd. Zatim kreirate funkciju sa izrazom kao tijelom. Ako i vi, poput mene, imate graf tranzicije niskog nivoa, komponenta reloopera će vam pomoći. Koliko sam shvatio, moguće je koristiti kontrolu toka izvršavanja na visokom nivou u bloku, sve dok ne ide izvan granica bloka - odnosno moguće je napraviti internu brzu/sporu putanju grananje staze unutar ugrađenog koda za obradu keš memorije TLB-a, ali da ne ometa "eksterni" tok kontrole. Kada oslobodite relooper, njegovi blokovi se oslobađaju; kada oslobodite modul, izrazi, funkcije itd. koji su mu dodijeljeni nestaju arena.

Međutim, ako želite interpretirati kod u hodu bez nepotrebnog kreiranja i brisanja instance interpretatora, možda bi imalo smisla staviti ovu logiku u C++ datoteku i odatle direktno upravljati cijelim C++ API-jem biblioteke, zaobilazeći spreman- napravljene omote.

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, izvinite, ovo je samo da predstavim skalu, a detalji su u dokumentaciji.

A sada 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);

Da bi nekako povezali svjetove QEMU-a i JS-a i istovremeno brzo pristupili kompajliranim funkcijama, kreiran je niz (tabela funkcija za uvoz u pokretač), a generirane funkcije su smještene tamo. Za brzo izračunavanje indeksa, u početku je korišten indeks bloka prijevoda nula riječi, ali je onda indeks izračunat pomoću ove formule počeo jednostavno da se uklapa u polje u struct TranslationBlock.

Inače, demo (trenutno sa mutnom licencom) radi dobro samo u Firefoxu. Chrome programeri su bili nekako nije spreman na činjenicu da bi neko želeo da kreira više od hiljadu instanci WebAssembly modula, pa je jednostavno dodelio gigabajt virtuelnog adresnog prostora za svaki...

To je sve za sada. Možda će biti još neki članak ako nekoga zanima. Naime, ostaje barem samo učiniti da blok uređaji rade. Takođe bi moglo imati smisla napraviti asinhronu kompilaciju WebAssembly modula, kao što je uobičajeno u JS svijetu, budući da još uvijek postoji interpretator koji sve to može učiniti dok izvorni modul nije spreman.

Konačno jedna zagonetka: kompajlirali ste binarnu datoteku na 32-bitnoj arhitekturi, ali se kod, kroz memorijske operacije, penje od Binaryen-a, negdje na steku ili negdje drugdje u gornja 2 GB 32-bitnog adresnog prostora. Problem je u tome što sa Binaryenove tačke gledišta ovo pristupa prevelikoj rezultantnoj adresi. Kako ovo zaobići?

Na admin način

Nisam završio ovo testiranje, ali moja prva pomisao je bila “Šta ako instaliram 32-bitni Linux?” Tada će gornji dio adresnog prostora biti zauzet od strane kernela. Pitanje je samo koliko će biti zauzeto: 1 ili 2 Gb.

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

Hajde da pustimo balon na vrhu adresnog prostora. Ni sam ne razumijem zašto to funkcionira - eto već mora postojati stog. Ali "mi smo praktičari: sve nam radi, ali niko 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 sa Valgrindom, ali, srećom, sam Valgrind vrlo efikasno tjera sve odatle :)

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

izvor: www.habr.com

Dodajte komentar