QEMU.js: zdaj resno in z WASM

Nekoč sem se odločil za zabavo dokazati reverzibilnost procesa in se naučite generirati JavaScript (natančneje Asm.js) iz strojne kode. Za poskus je bil izbran QEMU, nekaj časa kasneje pa je bil napisan članek na Habru. V komentarjih so mi svetovali, naj projekt predelam v WebAssembly in celo sam zapustim skoraj končano Nekako si nisem želel projekta ... Delo je potekalo, vendar zelo počasi, in zdaj, pred kratkim se je pojavil ta članek komentar na temo "Kako se je torej vse končalo?" Kot odgovor na moj podroben odgovor sem slišal "To zveni kot članek." No, če lahko, bo članek. Mogoče bo komu koristilo. Iz nje bo bralec izvedel nekaj dejstev o zasnovi ozadij za generiranje kode QEMU, pa tudi o tem, kako napisati pravočasni prevajalnik za spletno aplikacijo.

naloge

Ker sem se že naučil "nekako" prenesti QEMU na JavaScript, sem se tokrat odločil, da to storim pametno in ne ponavljam starih napak.

Napaka številka ena: odcep od sprostitve točke

Moja prva napaka je bila, da sem razcepil svojo različico iz zgornje različice 2.4.1. Potem se mi je zdela dobra ideja: če obstaja točkovna sprostitev, potem je verjetno bolj stabilna kot preprosta 2.4, še bolj pa veja master. In ker sem nameraval dodati precej svojih hroščev, sploh nisem potreboval nobenih drugih. Verjetno se je tako obrnilo. Pa še to: QEMU ne miruje in na neki točki so napovedali celo optimizacijo generirane kode za 10 odstotkov.»ja, zdaj pa bom zmrznil,« sem pomislil in se zlomil. Tukaj moramo narediti digresijo: zaradi enonitne narave QEMU.js in dejstva, da prvotni QEMU ne pomeni odsotnosti večnitnosti (to je zmožnost hkratnega delovanja več nepovezanih kodnih poti in ne samo »uporabi vsa jedra«) ključnega pomena za to, glavne funkcije niti sem moral »izklopiti«, da sem lahko klical od zunaj. To je povzročilo nekaj naravnih težav med združitvijo. Vendar pa je dejstvo, da nekatere spremembe iz veje master, s katerimi sem poskušal združiti svojo kodo, prav tako izbrane v izdaji točke (in torej v moji veji) prav tako verjetno ne bi dodale priročnosti.

Na splošno sem se odločil, da je še vedno smiselno vreči prototip, ga razstaviti na dele in zgraditi novo različico iz nič, ki temelji na nečem novejšem in zdaj iz master.

Napaka številka dve: metodologija TLP

V bistvu to ni napaka, na splošno je le značilnost ustvarjanja projekta v pogojih popolnega nerazumevanja tako "kam in kako se premakniti?" In na splošno "ali bomo prišli tja?" V teh razmerah nerodno programiranje je bila upravičena možnost, a je seveda nisem hotel ponavljati po nepotrebnem. Tokrat sem želel to narediti pametno: atomske zaveze, zavestne spremembe kode (in ne »nizanje naključnih znakov skupaj, dokler se ne prevede (z opozorili)«, kot je Linus Torvalds nekoč rekel o nekom, glede na Wikicitat) itd.

Napaka številka tri: vstopiti v vodo, ne da bi vedeli, kje je prehod

Tega se še vedno nisem popolnoma znebil, zdaj pa sem se odločil, da sploh ne bom šel po poti najmanjšega odpora in da bom to počel »kot odrasel«, in sicer da bom svoj TCG backend napisal iz nič, da ne da bi moral kasneje reči: "Ja, to je seveda počasi, ampak ne morem vsega nadzorovati - tako se piše TCI ..." Poleg tega se je to sprva zdela samoumevna rešitev, saj Ustvarim binarno kodo. Kot pravijo, »Gent je zbralу, ampak ne tistega”: koda je seveda binarna, a nadzora nanjo ni mogoče kar tako prenesti - treba jo je eksplicitno potisniti v brskalnik za prevajanje, kar povzroči določen objekt iz sveta JS, ki mora še nekje shraniti. Vendar pa je na običajnih arhitekturah RISC, kolikor razumem, tipična situacija potreba po izrecni ponastavitvi predpomnilnika navodil za regenerirano kodo - če to ni tisto, kar potrebujemo, potem je v vsakem primeru blizu. Poleg tega sem iz svojega zadnjega poskusa izvedel, da se zdi, da nadzor ni prenesen na sredino prevajalskega bloka, tako da pravzaprav ne potrebujemo bajtne kode, interpretirane iz katerega koli odmika, in jo lahko preprosto ustvarimo iz funkcije na TB .

Prišli so in brcali

Čeprav sem kodo začel znova pisati že julija, se je neopazno prikradel čarobni udarec: običajno pisma iz GitHuba prispejo kot obvestila o odgovorih na težave in zahteve za vlečenje, tukaj pa nenadoma omeni v niti Binaryen kot zaledje qemu v kontekstu: "Naredil je nekaj takega, morda bo kaj rekel." Govorili smo o uporabi sorodne knjižnice Emscriptena Binaryen ustvariti WASM JIT. No, rekel sem, da imate tam licenco Apache 2.0, QEMU kot celota pa se distribuira pod GPLv2 in nista zelo združljiva. Nenadoma se je izkazalo, da je licenca lahko nekako popraviti (Ne vem: morda spremeniti, morda dvojno licenciranje, morda kaj drugega ...). To me je seveda razveselilo, saj sem takrat že dobro pogledal dvojiški format WebAssembly, in bilo mi je nekako žalostno in nerazumljivo. Obstajala je tudi knjižnica, ki bi požrla osnovne bloke s prehodnim grafom, proizvedla bajtno kodo in jo po potrebi celo zagnala v samem tolmaču.

Potem je bilo še več pismo na poštnem seznamu QEMU, vendar gre bolj za vprašanje, "Kdo ga sploh potrebuje?" In je nenadoma, izkazalo se je, da je to potrebno. Najmanj lahko zberete naslednje možnosti uporabe, če deluje bolj ali manj hitro:

  • zagon nečesa izobraževalnega brez kakršne koli namestitve
  • virtualizacija na iOS, kjer je po govoricah edina aplikacija, ki ima pravico do sprotnega generiranja kode motor JS (je to res?)
  • predstavitev mini-OS - enodisketni, vgrajeni, vse vrste firmware-a itd...

Funkcije izvajalnega okolja brskalnika

Kot sem že rekel, je QEMU vezan na večnitnost, brskalnik pa tega nima. No, to je, ne ... Sprva sploh ni obstajal, potem so se pojavili WebWorkers - kolikor razumem, je to večnitnost, ki temelji na posredovanju sporočil brez skupnih spremenljivk. Seveda to ustvarja velike težave pri prenosu obstoječe kode, ki temelji na modelu skupnega pomnilnika. Nato pod pritiskom javnosti tudi uveljavili pod imenom SharedArrayBuffers. Uvajali so ga postopoma, praznovali so njegov začetek v različnih brskalnikih, nato praznovali novo leto, nato pa Meltdown ... Nakar so prišli do zaključka, da je grobo ali grobo merjenje časa, vendar s pomočjo skupnega pomnilnika in nit, ki povečuje števec, je vse isto bo delovalo precej natančno. Zato smo onemogočili večnitnost s skupnim pomnilnikom. Zdi se, da so ga pozneje znova vklopili, a kot je postalo jasno iz prvega poskusa, obstaja življenje brez njega, in če je tako, bomo to poskušali storiti, ne da bi se zanašali na večnitnost.

Druga značilnost je nezmožnost nizkonivojskih manipulacij s skladom: ne morete preprosto vzeti, shraniti trenutnega konteksta in preklopiti na novega z novim skladom. Sklad klicev upravlja virtualni stroj JS. Zdi se, v čem je težava, saj smo se še vedno odločili, da bomo prejšnje tokove upravljali popolnoma ročno? Dejstvo je, da je blokovni V/I v QEMU implementiran s pomočjo korutin, in tukaj bi prišle prav manipulacije nizkonivojskega sklada. Na srečo Emscipten že vsebuje mehanizem za asinhrone operacije, celo dva: Asincificiraj и Emterpreter. Prvi deluje s precejšnjim napihnjenjem ustvarjene kode JavaScript in ni več podprt. Drugi je trenutni "pravilni način" in deluje prek generiranja bajtne kode za izvorni tolmač. Deluje seveda počasi, a kode ne napihne. Res je, da je bilo treba podporo za korutine za ta mehanizem prispevati neodvisno (za Asyncify so že bile napisane korutine in obstajala je implementacija približno enakega API-ja za Emterpreter, le povezati jih je bilo treba).

Trenutno mi še ni uspelo razdeliti kode v eno, ki bi bila prevedena v WASM in interpretirana z Emterpreterjem, zato blokovne naprave še ne delujejo (glej v naslednji seriji, kot pravijo ...). Se pravi, na koncu bi morali dobiti nekaj podobnega tej smešni večplastni stvari:

  • interpretirani blok I/O. No, ali ste res pričakovali emuliran NVMe z izvorno zmogljivostjo? 🙂
  • statično prevedena glavna koda QEMU (prevajalnik, druge emulirane naprave itd.)
  • dinamično prevedeno gostujočo kodo v WASM

Značilnosti virov QEMU

Kot ste verjetno že uganili, sta koda za posnemanje gostujočih arhitektur in koda za generiranje navodil gostiteljskega stroja v QEMU ločeni. Pravzaprav je še nekoliko težje:

  • obstajajo gostujoče arhitekture
  • obstaja pospeševalci, in sicer KVM za virtualizacijo strojne opreme v Linuxu (za medsebojno združljive sisteme za goste in gostitelje), TCG za generiranje kode JIT kjer koli. Od QEMU 2.9 se je pojavila podpora za standard virtualizacije strojne opreme HAXM v sistemu Windows (podrobnosti)
  • če se uporablja TCG in ne virtualizacija strojne opreme, ima ločeno podporo za ustvarjanje kode za vsako arhitekturo gostitelja, pa tudi za univerzalni tolmač
  • ... in okoli vsega tega - emulirana periferija, uporabniški vmesnik, migracija, snemanje-ponovno predvajanje itd.

Mimogrede, ali ste vedeli: QEMU lahko posnema ne le celoten računalnik, temveč tudi procesor za ločen uporabniški proces v gostiteljskem jedru, ki ga na primer uporablja AFL fuzzer za binarno instrumentacijo. Bi morda kdo želel ta način delovanja QEMU prenesti na JS? 😉

Kot večina dolgotrajne brezplačne programske opreme je tudi QEMU zgrajen prek klica configure и make. Recimo, da se odločite dodati nekaj: zaledje TCG, izvajanje niti, nekaj drugega. Ne hitite z veseljem/grozom (podčrtajte, kot je primerno) ob možnosti komuniciranja z Autoconfom - pravzaprav configure QEMU je očitno napisan sam in ni ustvarjen iz ničesar.

WebAssembly

Torej, kaj je ta stvar, imenovana WebAssembly (aka WASM)? To je zamenjava za Asm.js, ki se ne pretvarja več, da je veljavna koda JavaScript. Nasprotno, je povsem binaren in optimiziran, in celo preprosto pisanje celega števila vanj ni zelo preprosto: zaradi kompaktnosti je shranjeno v formatu LEB128.

Morda ste že slišali za algoritem ponovne zanke za Asm.js - to je obnovitev navodil za nadzor toka na "visoki ravni" (to je if-then-else, zanke itd.), za katere so zasnovani motorji JS, od nizkonivojski LLVM IR, bližje strojni kodi, ki jo izvaja procesor. Seveda je vmesna predstavitev QEMU bližje drugi. Zdi se, da je tukaj, bytecode, konec muk ... In potem so tu še bloki, if-then-else in zanke!..

In to je še en razlog, zakaj je Binaryen uporaben: lahko naravno sprejme bloke na visoki ravni, ki so blizu tistim, ki bi bili shranjeni v WASM. Lahko pa tudi ustvari kodo iz grafa osnovnih blokov in prehodov med njimi. No, rekel sem že, da skriva obliko shranjevanja WebAssembly za priročnim API-jem C/C++.

TCG (Tiny Code Generator)

TCG je bilo prvotno zaledje za prevajalnik C. Potem očitno ni zdržal konkurence z GCC, vendar je na koncu našel svoje mesto v QEMU kot mehanizem za ustvarjanje kode za gostiteljsko platformo. Obstaja tudi zaledje TCG, ki ustvari neko abstraktno bajtno kodo, ki jo tolmač takoj izvede, vendar sem se odločil, da se ji tokrat izognem. Vendar dejstvo, da je v QEMU že možno prek funkcije omogočiti prehod na generiran TB tcg_qemu_tb_exec, mi je prišel zelo prav.

Če želite v QEMU dodati novo zaledje TCG, morate ustvariti podimenik tcg/<имя архитектуры> (v tem primeru, tcg/binaryen), in vsebuje dve datoteki: tcg-target.h и tcg-target.inc.c и predpisati vse je o configure. Tja lahko postavite druge datoteke, vendar, kot lahko sklepate iz imen teh dveh, bosta obe nekje vključeni: ena kot običajna datoteka glave (vključena je v tcg/tcg.h, in ta je že v drugih datotekah v imenikih tcg, accel in ne samo), drugi - samo kot delček kode v tcg/tcg.c, vendar ima dostop do svojih statičnih funkcij.

Ker sem se odločil, da bom porabil preveč časa za podrobne preiskave o tem, kako deluje, sem preprosto kopiral "okostja" teh dveh datotek iz druge zaledne izvedbe in to pošteno navedel v glavi licence.

datoteka tcg-target.h vsebuje predvsem nastavitve v obrazcu #define-s:

  • koliko registrov in kakšne širine je na ciljni arhitekturi (imamo jih kolikor hočemo, kolikor hočemo - vprašanje je bolj v tem, kaj bo brskalnik na "popolnoma ciljni" arhitekturi generiral v bolj učinkovito kodo ...)
  • poravnava navodil gostitelja: na x86 in celo v TCI navodila sploh niso poravnana, vendar v medpomnilnik kode ne bom dal navodil, ampak kazalce na strukture knjižnice Binaryen, zato bom rekel: 4 bajtov
  • katera neobvezna navodila lahko generira zaledje - vključimo vse, kar najdemo v Binaryen, preostalo naj pospeševalnik sam razdeli na enostavnejša
  • Kakšna je približna velikost predpomnilnika TLB, ki ga zahteva zaledje. Dejstvo je, da je v QEMU vse resno: čeprav obstajajo pomožne funkcije, ki izvajajo nalaganje/shranjevanje ob upoštevanju gostujočega MMU (kje bi bili zdaj brez njega?), shranijo svoj predpomnilnik prevodov v obliki strukture, obdelavo, ki jo je priročno vdelati neposredno v oddajne bloke. Vprašanje je, kateri odmik v tej strukturi je najučinkoviteje obdelan z majhnim in hitrim zaporedjem ukazov?
  • tukaj lahko prilagodite namen enega ali dveh rezerviranih registrov, omogočite klic TB prek funkcije in po želji opišete nekaj majhnih inline- deluje kot flush_icache_range (vendar to ni naš primer)

datoteka tcg-target.inc.c, seveda, je običajno veliko večji in vsebuje več obveznih funkcij:

  • inicializacijo, vključno z omejitvami, katera navodila lahko delujejo na katere operande. Očitno sem kopiral iz drugega ozadja
  • funkcija, ki sprejme eno notranjo navodilo bajtne kode
  • Tukaj lahko postavite tudi pomožne funkcije in uporabite tudi statične funkcije iz tcg/tcg.c

Zase sem izbral naslednjo strategijo: v prve besede naslednjega prevodnega bloka sem zapisal štiri kazalce: začetno oznako (določeno vrednost v bližini 0xFFFFFFFF, ki je določil trenutno stanje TB), kontekst, ustvarjen modul in čarobno številko za odpravljanje napak. Sprva je bila oznaka postavljena v 0xFFFFFFFF - nČe n - majhno pozitivno število in vsakič, ko je bilo izvedeno prek tolmača, se je povečalo za 1. Ko je doseglo 0xFFFFFFFE, prevajanje je potekalo, modul je bil shranjen v funkcijsko tabelo, uvožen v majhen »zaganjalnik«, v katerega je šlo izvajanje iz tcg_qemu_tb_exec, in modul je bil odstranjen iz pomnilnika QEMU.

Če parafraziram klasike, “Crutch, koliko je prepletenega v tem zvoku za progersko srce ...”. Vendar je spomin nekam uhajal. Poleg tega je pomnilnik upravljal QEMU! Imel sem kodo, ki je ob pisanju naslednjega navodila (no, torej kazalca) izbrisala tisto, katere povezava je bila prej na tem mestu, vendar to ni pomagalo. Pravzaprav v najpreprostejšem primeru QEMU dodeli pomnilnik ob zagonu in tja zapiše ustvarjeno kodo. Ko se medpomnilnik izprazni, se koda vrže ven in na njeno mesto se začne pisati naslednja.

Ko sem preučil kodo, sem ugotovil, da mi je trik s čarobno številko omogočil, da ne spodletim pri uničenju kopice, tako da osvobodim nekaj napačnega v neinicializiranem medpomnilniku pri prvem prehodu. Kdo pa prepiše medpomnilnik, da bi kasneje zaobšel mojo funkcijo? Kot svetujejo razvijalci Emscriptena, ko sem naletel na težavo, sem prenesel nastalo kodo nazaj v izvorno aplikacijo, nastavil Mozilla Record-Replay na njej ... Na splošno sem na koncu spoznal preprosto stvar: za vsak blok, a struct TranslationBlock s svojim opisom. Ugani kje... Tako je, tik pred blokom v medpomnilniku. Ko sem se tega zavedal, sem se odločil, da opustim uporabo bergel (vsaj nekaterih), in čarobno številko preprosto vrgel ven, preostale besede pa prenesel na struct TranslationBlock, ustvarjanje enojno povezanega seznama, ki ga je mogoče hitro pregledati, ko je predpomnilnik prevodov ponastavljen, in sprostitev pomnilnika.

Nekatere bergle ostajajo: na primer označeni kazalci v medpomnilniku kode - nekateri so preprosto BinaryenExpressionRef, torej pogledajo izraze, ki jih je treba linearno vstaviti v generirani osnovni blok, del je pogoj za prehod med BB-ji, del je kam iti. No, za Relooper so že pripravljeni bloki, ki jih je treba povezati glede na pogoje. Za njihovo razlikovanje se uporablja predpostavka, da so vsi poravnani z vsaj štirimi bajti, tako da lahko varno uporabite najmanj pomembna dva bita za oznako, le ne pozabite ju odstraniti, če je potrebno. Mimogrede, takšne oznake se že uporabljajo v QEMU za označevanje razloga za izhod iz zanke TCG.

Uporaba Binaryen

Moduli v WebAssembly vsebujejo funkcije, od katerih vsaka vsebuje telo, ki je izraz. Izrazi so unarne in binarne operacije, bloki, sestavljeni iz seznamov drugih izrazov, krmilni tok itd. Kot sem že rekel, je krmilni tok tukaj organiziran natančno kot veje na visoki ravni, zanke, klici funkcij itd. Argumenti funkcij se ne posredujejo na skladu, ampak eksplicitno, tako kot v JS. Obstajajo tudi globalne spremenljivke, vendar jih nisem uporabil, zato vam o njih ne bom govoril.

Funkcije imajo tudi lokalne spremenljivke, oštevilčene od nič, tipa: int32 / int64 / float / double. V tem primeru so prvih n lokalnih spremenljivk argumenti, posredovani funkciji. Upoštevajte, da čeprav vse tukaj ni povsem na nizki ravni v smislu nadzornega toka, cela števila še vedno nimajo atributa »predznačeno/nepodpisano«: kako se število obnaša, je odvisno od kode operacije.

Na splošno ponuja Binaryen preprost C-API: ustvarite modul, v njem ustvarjanje izrazov - unarnih, binarnih, blokov iz drugih izrazov, nadzor toka itd. Nato ustvarite funkcijo z izrazom kot njenim telesom. Če imate tako kot jaz prehodni graf nizke ravni, vam bo pomagala komponenta relooper. Kolikor razumem, je mogoče uporabiti visokonivojski nadzor toka izvajanja v bloku, če le ta ne preseže meja bloka - to pomeni, da je mogoče narediti notranjo hitro pot / počasno razvejanje poti znotraj vgrajene kode za obdelavo predpomnilnika TLB, vendar ne za motenje "zunanjega" toka nadzora. Ko sprostite relooper, se sprostijo njegovi bloki; ko sprostite modul, izrazi, funkcije itd., ki so mu dodeljeni, izginejo arena.

Če pa želite sproti tolmačiti kodo brez nepotrebnega ustvarjanja in brisanja primerka tolmača, je morda smiselno to logiko vnesti v datoteko C++ in od tam neposredno upravljati celoten API C++ knjižnice, mimo pripravljenega izdelani ovitki.

Torej za ustvarjanje kode, ki jo potrebujete

// настроить глобальные параметры (можно поменять потом)
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);

... če sem kaj pozabil, se opravičujem, to je samo prikaz merila, podrobnosti pa so v dokumentaciji.

In zdaj se začne crack-fex-pex, nekako takole:

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 svetova QEMU in JS ter hkrati hitro dostopali do prevedenih funkcij, je bila ustvarjena matrika (tabela funkcij za uvoz v zaganjalnik) in vanj umeščene generirane funkcije. Za hiter izračun indeksa je bil sprva uporabljen indeks bloka prevajanja ničelne besede, nato pa se je indeks, izračunan s to formulo, začel preprosto prilegati polju v struct TranslationBlock.

Mimogrede, demo (trenutno z mračno licenco) dobro deluje le v Firefoxu. Razvijalci Chroma so bili nekako ni pripravljen na dejstvo, da bi nekdo želel ustvariti več kot tisoč primerkov modulov WebAssembly, zato so preprosto dodelili gigabajt virtualnega naslovnega prostora za vsakega ...

To je vse za zdaj. Mogoče bo še kakšen članek, če koga zanima. Namreč ostane vsaj samo omogočiti delovanje blokovnih naprav. Morda bi bilo tudi smiselno narediti prevajanje modulov WebAssembly asinhrono, kot je običajno v svetu JS, saj še vedno obstaja tolmač, ki lahko naredi vse to, dokler izvorni modul ni pripravljen.

Za konec še uganka: ste prevedli binarno datoteko na 32-bitni arhitekturi, vendar se koda prek pomnilniških operacij povzpne iz Binaryen, nekje na sklad ali nekje drugje v zgornjih 2 GB 32-bitnega naslovnega prostora. Težava je v tem, da z vidika Binaryen to dostopa do prevelikega naslova. Kako se temu izogniti?

Na admin način

Tega na koncu nisem preizkusil, vendar je bila moja prva misel "Kaj če bi namestil 32-bitni Linux?" Nato bo zgornji del naslovnega prostora zasedel jedro. Vprašanje je le, koliko bo zasedeno: 1 ali 2 Gb.

Na programerski način (možnost za praktike)

Napihnimo mehurček na vrhu naslovnega prostora. Sam ne razumem, zakaj deluje - tam že mora biti kup. Toda »smo praktiki: vse nam gre, a nihče ne ve, zakaj ...«

// 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));
}

... res je, da ni združljiv z Valgrindom, a na srečo sam Valgrind zelo učinkovito izriva vse od tam :)

Mogoče bo kdo bolje razložil, kako deluje ta moja koda...

Vir: www.habr.com

Dodaj komentar