QEMU.js: nú alvarlegt og með WASM

Einu sinni ákvað ég mér til skemmtunar sanna að ferlinu sé afturkræft og lærðu hvernig á að búa til JavaScript (nánar tiltekið, Asm.js) úr vélkóða. QEMU varð fyrir tilrauninni og nokkru síðar var skrifuð grein á Habr. Í athugasemdunum var mér bent á að endurgera verkefnið í WebAssembly og jafnvel hætta sjálfur næstum búin Ég einhvern veginn vildi ekki verkefnið... Verkið var í gangi, en mjög hægt, og núna, nýlega birtist í greininni athugasemd um efnið "Svo hvernig endaði þetta allt?" Sem svar við ítarlegu svari mínu heyrði ég "Þetta hljómar eins og grein." Jæja, ef þú getur, þá verður grein. Kannski mun einhverjum finnast það gagnlegt. Af því mun lesandinn læra nokkrar staðreyndir um hönnun QEMU kóða kynslóðar bakenda, sem og hvernig á að skrifa Just-in-Time þýðanda fyrir vefforrit.

verkefni

Þar sem ég hafði þegar lært hvernig á að „einhvern veginn“ flytja QEMU yfir í JavaScript var ákveðið að gera það skynsamlega að þessu sinni og ekki endurtaka gömul mistök.

Villa númer eitt: grein frá punktaútgáfu

Fyrstu mistökin mín voru að flokka útgáfuna mína frá andstreymisútgáfu 2.4.1. Þá fannst mér það góð hugmynd: ef punktaútgáfa er til, þá er hún líklega stöðugri en einföld 2.4, og enn frekar greinin master. Og þar sem ég ætlaði að bæta við töluverðu magni af mínum eigin pöddum, þurfti ég alls ekki neins annars. Þannig varð það líklega. En hér er málið: QEMU stendur ekki kyrr, og á einhverjum tímapunkti tilkynntu þeir meira að segja hagræðingu á myndaða kóðanum um 10 prósent. „Já, nú ætla ég að frjósa,“ hugsaði ég og brotnaði niður. Hér þurfum við að gera útrás: vegna einsþræðis eðlis QEMU.js og þeirrar staðreyndar að upprunalega QEMU þýðir ekki að fjölþráður sé ekki til staðar (þ. ekki bara “nota alla kjarna”) er mikilvægt fyrir það, helstu aðgerðir þráða sem ég þurfti að “snúa út” til að geta hringt utan frá. Þetta skapaði nokkur náttúruleg vandamál við sameininguna. Hins vegar er sú staðreynd að sumar breytingarnar frá útibú master, sem ég reyndi að sameina kóðann minn við, voru einnig valin kirsuber í punktaútgáfunni (og þar af leiðandi í útibúinu mínu) hefði líklega ekki aukist þægindi.

Almennt séð ákvað ég að það væri enn skynsamlegt að henda frumgerðinni, taka hana í sundur fyrir hluta og byggja nýja útgáfu frá grunni byggða á einhverju ferskara og nú frá master.

Villa númer tvö: TLP aðferðafræði

Í meginatriðum eru þetta ekki mistök, almennt séð er þetta bara eiginleiki þess að búa til verkefni við aðstæður fullkomins misskilnings á bæði "hvert og hvernig á að flytja?" og almennt "máum við komast þangað?" Við þessar aðstæður klaufaleg forritun var réttlætanlegur kostur, en ég vildi náttúrulega ekki endurtaka hann að óþörfu. Í þetta skiptið vildi ég gera það skynsamlega: frumeindaskuldbindingar, meðvitaðar kóðabreytingar (og ekki „strengja saman handahófskennda stafi fyrr en það er sett saman (með viðvörunum)“, eins og Linus Torvalds sagði einu sinni um einhvern, samkvæmt Wikiquote), o.s.frv.

Villa númer þrjú: að komast í vatnið án þess að þekkja vaðið

Ég er samt ekki alveg búinn að losa mig við þetta, en núna hef ég ákveðið að fara alls ekki leið minnstu mótstöðunnar og gera það „sem fullorðinn“, nefnilega skrifa TCG bakendann minn frá grunni, svo ekki að þurfa að segja seinna: "Já, þetta er auðvitað hægt, en ég get ekki stjórnað öllu - svona er TCI skrifað..." Þar að auki virtist þetta í upphafi augljós lausn, þar sem Ég bý til tvöfaldur kóða. Eins og þeir segja: „Ghent safnaðist samanу, en ekki þessi“: kóðinn er auðvitað tvíundir, en ekki er hægt að flytja stjórn á hann einfaldlega - það verður að ýta honum sérstaklega inn í vafrann til að safna saman, sem leiðir til ákveðins hluts úr JS heiminum, sem þarf samt að vera vistuð einhvers staðar. Hins vegar, á venjulegum RISC arkitektúrum, eftir því sem ég skil, er dæmigerð staða þörf á að endurstilla leiðbeiningar skyndiminni fyrir endurgerðan kóða - ef þetta er ekki það sem við þurfum, þá er það í öllum tilvikum nálægt. Þar að auki, af síðustu tilraun minni, lærði ég að stjórn virðist ekki vera flutt í miðjan þýðingarblokk, svo við þurfum í raun ekki bækikóða sem er túlkaður út frá neinni offset, og við getum einfaldlega búið það til úr aðgerðinni á TB .

Þeir komu og spörkuðu

Þó að ég hafi byrjað að endurskrifa kóðann aftur í júlí, læddist töfraspark fram óséður: venjulega berast bréf frá GitHub sem tilkynningar um svör við vandamálum og Pull beiðnum, en hér, skyndilega nefna í þræði Binaryen sem qemu bakendi í samhenginu, "Hann gerði eitthvað svona, kannski segir hann eitthvað." Við vorum að tala um að nota tengd bókasafn Emscripten Binaryen til að búa til WASM JIT. Jæja, ég sagði að þú sért með Apache 2.0 leyfi þar og QEMU í heild sinni er dreift undir GPLv2 og þau eru ekki mjög samhæf. Skyndilega kom í ljós að leyfi getur verið laga það einhvern veginn (Ég veit það ekki: kannski breyta því, kannski tvöfalt leyfi, kannski eitthvað annað...). Þetta gladdi mig auðvitað, því þá var ég búinn að skoða vel tvöfalt snið WebAssembly, og ég var einhvern veginn leiður og óskiljanlegur. Það var líka bókasafn sem myndi éta grunnblokkina með umbreytingargrafinu, framleiða bækikóðann og jafnvel keyra hann í túlknum sjálfum, ef þörf krefur.

Svo var meira bréf á QEMU póstlistanum, en þetta snýst meira um spurninguna: "Hver þarf það eiginlega?" Og það er skyndilega, kom í ljós að það var nauðsynlegt. Að minnsta kosti er hægt að skafa saman eftirfarandi notkunarmöguleika, ef það virkar meira eða minna hratt:

  • setja af stað eitthvað fræðandi án nokkurrar uppsetningar
  • sýndarvæðing á iOS, þar sem, samkvæmt sögusögnum, er eina forritið sem hefur rétt til að búa til kóða á flugu JS vél (er þetta satt?)
  • sýning á mini-OS - einn disklingur, innbyggður, alls kyns vélbúnaðar osfrv...

Vafra Runtime Eiginleikar

Eins og ég sagði þegar, QEMU er bundið við multithreading, en vafrinn hefur það ekki. Jæja, það er, nei... Fyrst var það alls ekki til, svo birtust WebWorkers - eftir því sem ég skil, er þetta fjölþráður byggður á skilaboðum án sameiginlegra breyta. Auðvitað skapar þetta veruleg vandamál þegar núverandi kóða er flutt á grundvelli samnýttrar minnislíkans. Síðan, undir þrýstingi almennings, var það einnig innleitt undir nafninu SharedArrayBuffers. Það var smám saman kynnt, þeir fögnuðu kynningu þess í mismunandi vöfrum, síðan fögnuðu þeir nýju ári og svo Meltdown... Eftir það komust þeir að þeirri niðurstöðu að grófa eða grófa tímamælinguna, en með hjálp sameiginlegs minnis og a þráður sem hækkar teljarann, það er allt eins það mun ganga nokkuð nákvæmlega. Þannig að við slökktum á fjölþráðum með sameiginlegu minni. Það virðist sem þeir hafi síðar kveikt á því aftur, en eins og það kom í ljós frá fyrstu tilrauninni er líf án þess, og ef svo er reynum við að gera það án þess að treysta á fjölþráða.

Annar eiginleikinn er ómöguleikinn á lágu stigi meðhöndlunar með stafla: þú getur ekki einfaldlega tekið, vistað núverandi samhengi og skipt yfir í nýjan með nýjum stafla. Símtalsstaflanum er stjórnað af JS sýndarvélinni. Það virðist, hvað er vandamálið, þar sem við ákváðum samt að stjórna fyrrnefndu flæðinu alveg handvirkt? Staðreyndin er sú að blokk I/O í QEMU er útfærð í gegnum coroutines, og þetta er þar sem lág-stigi stafla meðferð myndi koma sér vel. Sem betur fer inniheldur Emscipten nú þegar kerfi fyrir ósamstilltar aðgerðir, jafnvel tvær: Ósamstilla и túlkur. Sá fyrsti vinnur í gegnum verulegan uppþembu í JavaScript kóðanum sem myndast og er ekki lengur studd. Annað er núverandi „rétta leiðin“ og vinnur í gegnum bækakóðagerð fyrir innfæddan túlk. Það virkar auðvitað hægt, en það eykur ekki kóðann. Að vísu þurfti að leggja fram stuðning við coroutines fyrir þetta kerfi sjálfstætt (það voru þegar skrifaðar coroutines fyrir Asyncify og það var útfærsla á um það bil sama API fyrir Emterpreter, þú þurftir bara að tengja þær).

Í augnablikinu hef ég ekki enn náð að skipta kóðanum í einn sem er settur saman í WASM og túlkað með Emterpreter, þannig að blokkartæki virka ekki ennþá (sjá í næstu seríu, eins og sagt er...). Það er að segja að á endanum ættirðu að fá eitthvað eins og þetta fyndna lagskipt:

  • túlkað blokk I/O. Jæja, bjóst þú virkilega við eftirlíkingu NVMe með innfæddum frammistöðu? 🙂
  • kyrrstöðusamsettur aðal QEMU kóða (þýðandi, önnur líkt tæki, osfrv.)
  • breytilega safnað saman gestakóða í WASM

Eiginleikar QEMU heimilda

Eins og þú hefur líklega þegar giskað á, er kóðinn til að líkja eftir gestaarkitektúr og kóðinn til að búa til leiðbeiningar um hýsingarvél aðskilin í QEMU. Reyndar er það jafnvel aðeins erfiðara:

  • það eru gestaarkitektúr
  • есть eldsneytisgjöf, nefnilega KVM fyrir sýndarvæðingu vélbúnaðar á Linux (fyrir gesta- og gestgjafakerfi sem eru samhæf hvert við annað), TCG fyrir JIT kóða kynslóð hvar sem er. Frá og með QEMU 2.9 birtist stuðningur við HAXM vélbúnaðar sýndarvæðingarstaðalinn á Windows (smáatriðin)
  • ef TCG er notað en ekki virtualization vélbúnaðar, þá hefur það sérstakan kóðaframleiðslustuðning fyrir hvern gestgjafaarkitektúr, sem og fyrir alhliða túlkinn
  • ... og í kringum allt þetta - líkt eftir jaðartæki, notendaviðmót, flutningur, endurspilun upptöku o.s.frv.

Við the vegur, vissir þú: QEMU getur líkt ekki aðeins eftir allri tölvunni, heldur einnig örgjörvanum fyrir sérstakt notendaferli í hýsilkjarnanum, sem er til dæmis notað af AFL fuzzer fyrir tvöfalda tækjabúnað. Kannski vill einhver flytja þennan aðgerðarmáta QEMU til JS? 😉

Eins og flestir langvarandi ókeypis hugbúnaður er QEMU smíðaður í gegnum símtalið configure и make. Segjum að þú ákveður að bæta einhverju við: TCG bakenda, þráðarútfærslu, eitthvað annað. Ekki flýta þér að vera ánægð/hrædd (undirstrikaðu eftir því sem við á) við möguleika á samskiptum við Autoconf - í raun, configure QEMU er greinilega sjálfskrifað og er ekki búið til úr neinu.

WebAssembly

Svo hvað er þetta sem heitir WebAssembly (aka WASM)? Þetta kemur í staðinn fyrir Asm.js, þykist ekki lengur vera gildur JavaScript kóða. Þvert á móti er það eingöngu tvíundarlegt og fínstillt, og jafnvel einfaldlega að skrifa heiltölu inn í það er ekki mjög einfalt: fyrir þéttleika er það geymt á sniðinu LEB128.

Þú gætir hafa heyrt um relooping reikniritið fyrir Asm.js - þetta er endurheimt "hástigs" flæðistýringarleiðbeininga (þ.e. ef-þá-annað, lykkjur osfrv.), sem JS vélar eru hannaðar fyrir, frá lágstigs LLVM IR, nær vélkóðanum sem framkvæmt er af örgjörvanum. Auðvitað er milliframsetning QEMU nær þeirri seinni. Svo virðist sem hér sé það, bætikóði, endir kvölarinnar... Og svo eru kubbar, ef-þá-annar og lykkjur!..

Og þetta er önnur ástæða fyrir því að Binaryen er gagnlegt: það getur náttúrulega tekið við blokkum á háu stigi nálægt því sem væri geymt í WASM. En það getur líka framleitt kóða úr línuriti yfir grunnblokkir og umskipti á milli þeirra. Jæja, ég hef þegar sagt að það felur WebAssembly geymslusniðið á bak við þægilega C/C++ API.

TCG (Tiny Code Generator)

GTC var upphaflega bakendi fyrir C þýðandann. Svo virðist hann ekki standast samkeppnina við GCC, en á endanum fann hann sinn stað í QEMU sem kóðaframleiðslukerfi fyrir hýsilpallinn. Það er líka til TCG bakendi sem býr til óhlutbundinn bætikóða, sem er strax keyrður af túlknum, en ég ákvað að forðast að nota hann í þetta skiptið. Hins vegar er sú staðreynd að í QEMU er nú þegar hægt að virkja umskipti yfir í myndaða berkla í gegnum aðgerðina tcg_qemu_tb_exec, það reyndist mér mjög gagnlegt.

Til að bæta nýjum TCG bakenda við QEMU þarftu að búa til undirskrá tcg/<имя архитектуры> (í þessu tilfelli, tcg/binaryen), og það inniheldur tvær skrár: tcg-target.h и tcg-target.inc.c и mæla fyrir þetta snýst allt um configure. Þú getur sett aðrar skrár þar, en eins og þú getur giskað á af nöfnum þessara tveggja, þá verða þær báðar með einhvers staðar: önnur sem venjuleg hausskrá (það er innifalið í tcg/tcg.h, og þessi er nú þegar í öðrum skrám í möppunum tcg, accel og ekki aðeins), hitt - aðeins sem kóðabút inn tcg/tcg.c, en það hefur aðgang að kyrrstæðum aðgerðum sínum.

Þegar ég ákvað að ég myndi eyða of miklum tíma í ítarlegar rannsóknir á því hvernig það virkar, afritaði ég einfaldlega „beinagrind“ þessara tveggja skráa úr annarri bakendaútfærslu og sýndi þetta heiðarlega í leyfishausnum.

skrá tcg-target.h inniheldur aðallega stillingar í formi #define-s:

  • hversu margar skrár og hvaða breidd eru á markarkitektúrnum (við höfum eins marga og við viljum, eins marga og við viljum - spurningin er meira um hvað verður búið til í skilvirkari kóða af vafranum á „fullkomlega miða“ arkitektúrnum ...)
  • röðun hýsingarleiðbeininga: á x86, og jafnvel í TCI, eru leiðbeiningar alls ekki samræmdar, en ég ætla að setja í kóðabuffið alls ekki leiðbeiningar, heldur vísa til Binaryen bókasafnsbygginga, svo ég segi: 4 bæti
  • hvaða valkvæða leiðbeiningar bakendinn getur búið til - við tökum allt sem við finnum í Binaryen, látum hraðalinn skipta restinni í einfaldari sjálfur
  • Hver er áætlaða stærð TLB skyndiminni sem bakendinn bað um. Staðreyndin er sú að í QEMU er allt alvarlegt: þó að það séu hjálparaðgerðir sem framkvæma hleðslu/geymslu að teknu tilliti til gesta-MMU (hvar værum við án þess núna?), vista þær þýðingarskyndiminni í formi mannvirkis, vinnsla sem er þægilegt að fella beint inn í útsendingarblokkir. Spurningin er, hvaða mótvægi í þessari uppbyggingu er skilvirkast unnin með lítilli og hröðri röð skipana?
  • hér geturðu lagfært tilgang einnar eða tveggja frátekinna skráa, virkjað að hringja í TB í gegnum aðgerð og mögulega lýst nokkrum litlum inline-virkni eins og flush_icache_range (en þetta er ekki okkar mál)

skrá tcg-target.inc.c, auðvitað, er venjulega mun stærri í stærð og inniheldur nokkrar skyldubundnar aðgerðir:

  • frumstilling, þar á meðal takmarkanir á því hvaða leiðbeiningar geta starfað á hvaða operanda. Hreinlega afritað af mér úr öðrum bakenda
  • fall sem tekur eina innri bækikóða leiðbeiningar
  • Þú getur líka sett hjálparaðgerðir hér, og þú getur líka notað truflanir aðgerðir frá tcg/tcg.c

Fyrir sjálfan mig valdi ég eftirfarandi stefnu: í fyrstu orðum næsta þýðingarblokk skrifaði ég niður fjórar ábendingar: upphafsmerki (ákveðið gildi í nágrenninu 0xFFFFFFFF, sem ákvarðaði núverandi ástand TB), samhengi, myndaða einingu og töfranúmer fyrir villuleit. Í fyrstu var merkið sett inn 0xFFFFFFFF - nhvar n - lítil jákvæð tala, og í hvert sinn sem hún var keyrð í gegnum túlk hækkaði hún um 1. Þegar hún náði 0xFFFFFFFE, samantekt fór fram, einingin var vistuð í aðgerðatöflunni, flutt inn í lítið „ræsiforrit“ sem keyrsla fór í frá tcg_qemu_tb_exec, og einingin var fjarlægð úr QEMU minni.

Til að umorða klassíkina, "Hækja, hversu mikið er samofið þessu hljóði fyrir hjarta progersins ...". Hins vegar var minnið að leka einhvers staðar. Þar að auki var það minni stjórnað af QEMU! Ég var með kóða sem þegar ég skrifaði næstu leiðbeiningar (tja, það er að segja bendill) eyddi þeim sem tengillinn var á þessum stað áðan, en þetta hjálpaði ekki. Reyndar, í einfaldasta tilfellinu, úthlutar QEMU minni við ræsingu og skrifar þar myndaða kóðann. Þegar biðminni klárast er kóðanum hent út og næsti byrjar að skrifast á sinn stað.

Eftir að hafa kynnt mér kóðann áttaði ég mig á því að bragðið með töfranúmerinu gerði mér kleift að mistakast ekki í hrúgaeyðingu með því að losa eitthvað rangt á óforstilltum biðminni í fyrstu ferð. En hver endurskrifar biðminni til að komast framhjá aðgerðinni minni síðar? Eins og Emscripten forritararnir ráðleggja, þegar ég lenti í vandræðum, flutti ég kóðann sem fékkst aftur í upprunalega forritið, setti Mozilla Record-Replay á það... Almennt séð áttaði ég mig á einföldum hlut: fyrir hverja blokk, a struct TranslationBlock með lýsingu þess. Giska á hvar... Það er rétt, rétt fyrir blokkina rétt í biðminni. Þegar ég áttaði mig á þessu ákvað ég að hætta að nota hækjur (að minnsta kosti sumar) og henti einfaldlega út töfratölunni og flutti orðin sem eftir voru yfir á struct TranslationBlock, búa til einn tengdan lista sem hægt er að fara fljótt yfir þegar þýðingarskyndiminni er endurstillt og losa um minni.

Sumar hækjur eru eftir: til dæmis merktir ábendingar í kóða biðminni - sumir þeirra eru einfaldlega BinaryenExpressionRef, það er að segja, þeir skoða tjáningar sem þarf að setja línulega inn í myndaða grunnblokkina, hluti er skilyrðið fyrir umskipti á milli BBs, hluti er hvert á að fara. Jæja, það eru þegar tilbúnar blokkir fyrir Relooper sem þarf að tengja í samræmi við aðstæður. Til að aðgreina þá er gengið út frá þeirri forsendu að þeir séu allir samræmdir með að minnsta kosti fjórum bætum, þannig að þú getur örugglega notað minnstu tvo bitana fyrir merkimiðann, þú þarft bara að muna að fjarlægja það ef þörf krefur. Við the vegur, slík merki eru nú þegar notuð í QEMU til að gefa til kynna ástæðu þess að fara út úr TCG lykkju.

Að nota Binaryen

Einingar í WebAssembly innihalda aðgerðir, sem hver um sig inniheldur meginmál, sem er tjáning. Tjáningar eru einar og tvöfaldar aðgerðir, blokkir sem samanstanda af listum yfir önnur tjáning, stjórnflæði o.s.frv. Eins og ég sagði þegar er stjórnflæði hér skipulagt nákvæmlega sem útibú á háu stigi, lykkjur, aðgerðarköll osfrv. Rök fyrir föll eru ekki send á stafla, heldur beinlínis, alveg eins og í JS. Það eru líka til alþjóðlegar breytur, en ég hef ekki notað þær, svo ég mun ekki segja þér frá þeim.

Aðgerðir hafa einnig staðbundnar breytur, tölusettar frá núlli, af gerðinni: int32 / int64 / flot / tvöfalt. Í þessu tilviki eru fyrstu n staðbundnu breyturnar rökin sem send eru til fallsins. Vinsamlegast athugaðu að þó að allt hér sé ekki alveg á lágu stigi hvað varðar stjórnflæði, þá bera heiltölur samt ekki „undirritað/óundirritað“ eigindina: hvernig talan hegðar sér fer eftir aðgerðarkóðanum.

Almennt séð veitir Binaryen einfalt C-API: þú býrð til einingu, í honum búa til tjáningar - einar, tvíundir, blokkir úr öðrum tjáningum, stjórna flæði o.s.frv. Síðan býrðu til fall með tjáningu sem líkama. Ef þú, eins og ég, ert með lágt umbreytingargraf, mun relooper íhluturinn hjálpa þér. Eftir því sem mér skilst er hægt að nota háþróaða stjórn á framkvæmdarflæðinu í blokk, svo framarlega sem það fer ekki út fyrir mörk blokkarinnar - það er, það er hægt að gera innri hraðan slóð / hægan slóð sem greinist inni í innbyggða TLB skyndiminni vinnslukóðanum, en ekki til að trufla „ytri“ stýriflæðið . Þegar þú losar relooper losnar kubbar hans, þegar þú losar einingu hverfa tjáningar, aðgerðir o.s.frv. vettvangur.

Hins vegar, ef þú vilt túlka kóða á flugi án þess að búa til óþarfa og eyða túlkatilviki, gæti verið skynsamlegt að setja þessa rökfræði inn í C++ skrá, og þaðan stjórna beint öllu C++ API bókasafnsins, framhjá tilbúnum- búið til umbúðir.

Svo til að búa til kóðann sem þú þarft

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

... ef ég gleymdi einhverju, því miður, þetta er bara til að tákna mælikvarðann og upplýsingarnar eru í skjölunum.

Og nú byrjar crack-fex-pex, eitthvað á þessa leið:

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

Til þess að tengja saman heima QEMU og JS á einhvern hátt og um leið fá fljótt aðgang að samanteknu föllunum var búið til fylki (tafla yfir virkni til að flytja inn í ræsiforritið) og föllin sem mynduð voru voru sett þar. Til að reikna vísitöluna fljótt út var vísitalan á núllorðsþýðingarreitnum upphaflega notaður sem hann, en síðan fór vísitalan sem reiknuð var með þessari formúlu einfaldlega að passa inn í reitinn í struct TranslationBlock.

Tilviljun, kynningu (eins og er með gruggugt leyfi) virkar bara fínt í Firefox. Chrome verktaki voru einhvern veginn ekki tilbúinn til þeirrar staðreyndar að einhver myndi vilja búa til meira en þúsund tilvik af WebAssembly einingum, svo þeir úthlutaðu einfaldlega gígabæta af sýndarvistfangarými fyrir hverja...

Það er allt í bili. Kannski kemur önnur grein ef einhver hefur áhuga. Það er nefnilega eftir amk aðeins láta blokkartæki virka. Það gæti líka verið skynsamlegt að gera samantekt WebAssembly eininga ósamstillta, eins og tíðkast í JS heiminum, þar sem enn er til túlkur sem getur gert þetta allt þar til innfædda einingin er tilbúin.

Að lokum gáta: þú hefur sett saman tvöfalda á 32-bita arkitektúr, en kóðinn, í gegnum minnisaðgerðir, klifrar upp úr Binaryen, einhvers staðar á staflanum eða einhvers staðar annars staðar í efri 2 GB af 32-bita vistfangarýminu. Vandamálið er að frá sjónarhóli Binaryen er þetta að fá aðgang að of stóru heimilisfangi. Hvernig á að komast í kringum þetta?

Að hætti stjórnanda

Ég endaði ekki á að prófa þetta, en fyrsta hugsun mín var "Hvað ef ég setti upp 32-bita Linux?" Þá verður efri hluti heimilisfangsrýmisins upptekinn af kjarnanum. Eina spurningin er hversu mikið verður upptekið: 1 eða 2 Gb.

Að hætti forritara (valkostur fyrir iðkendur)

Við skulum blása bólu efst á heimilisfangarýminu. Sjálfur skil ég ekki af hverju það virkar - þarna þegar það verður að vera stafli. En „við erum iðkendur: allt virkar fyrir okkur, en enginn veit hvers vegna...“

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

... það er satt að það er ekki samhæft við Valgrind, en sem betur fer ýtir Valgrind sjálft alla á mjög áhrifaríkan hátt út þaðan :)

Kannski getur einhver útskýrt betur hvernig þessi kóði minn virkar...

Heimild: www.habr.com

Bæta við athugasemd