QEMU.js: sasa ni mbaya na kwa WASM

Mara moja kwa wakati niliamua kwa furaha thibitisha urejeshaji wa mchakato na ujifunze jinsi ya kutengeneza JavaScript (kwa usahihi zaidi, Asm.js) kutoka kwa msimbo wa mashine. QEMU ilichaguliwa kwa ajili ya jaribio, na muda fulani baadaye makala iliandikwa kuhusu Habr. Katika maoni nilishauriwa kufanya upya mradi katika WebAssembly, na hata kujiondoa karibu kumaliza Kwa namna fulani sikutaka mradi ... Kazi ilikuwa ikiendelea, lakini polepole sana, na sasa, hivi karibuni katika makala hiyo ilionekana. ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ kwenye mada "Kwa hivyo yote yaliishaje?" Kujibu jibu langu la kina, nilisikia "Hii inaonekana kama nakala." Naam, ikiwa unaweza, kutakuwa na makala. Labda mtu atapata kuwa muhimu. Kutoka humo msomaji atajifunza ukweli fulani kuhusu uundaji wa viambajengo vya uundaji wa msimbo wa QEMU, na pia jinsi ya kuandika mkusanyaji wa Wakati wa Wakati kwa ajili ya programu tumizi ya wavuti.

kazi

Kwa kuwa nilikuwa tayari nimejifunza jinsi ya "kwa namna fulani" bandari QEMU kwa JavaScript, wakati huu iliamua kufanya hivyo kwa busara na si kurudia makosa ya zamani.

Hitilafu namba moja: tawi kutoka kwa kutolewa kwa pointi

Kosa langu la kwanza lilikuwa kugawa toleo langu kutoka kwa toleo la juu la mkondo 2.4.1. Kisha ilionekana kwangu kama wazo zuri: ikiwa kutolewa kwa uhakika kunapatikana, basi labda ni thabiti zaidi kuliko rahisi 2.4, na hata zaidi tawi. master. Na kwa kuwa nilipanga kuongeza kiasi cha haki cha mende zangu mwenyewe, sikuhitaji mtu mwingine yeyote. Labda ndivyo ilivyotokea. Lakini hapa ni jambo: QEMU haisimama, na wakati fulani hata walitangaza uboreshaji wa msimbo uliozalishwa kwa asilimia 10. "Ndio, sasa nitafungia," nilifikiri na kuvunja. Hapa tunahitaji kufanya mchepuo: kwa sababu ya asili ya nyuzi moja ya QEMU.js na ukweli kwamba QEMU ya asili haimaanishi kutokuwepo kwa nyuzi nyingi (hiyo ni, uwezo wa kufanya kazi kwa wakati mmoja njia kadhaa za msimbo zisizohusiana, na. sio tu "tumia kernels zote") ni muhimu kwa hilo, kazi kuu za nyuzi ilibidi "kuzizima" ili kuweza kupiga simu kutoka nje. Hii iliunda matatizo ya asili wakati wa kuunganisha. Hata hivyo, ukweli kwamba baadhi ya mabadiliko kutoka tawi master, ambayo nilijaribu kuunganisha nambari yangu, pia ilichukuliwa cherry katika kutolewa kwa uhakika (na kwa hivyo katika tawi langu) pia labda isingeongeza urahisi.

Kwa ujumla, niliamua kuwa bado inaeleweka kutupa mfano huo, kuitenganisha kwa sehemu na kuunda toleo jipya kutoka mwanzo kulingana na kitu kipya na sasa kutoka. master.

Kosa namba mbili: Mbinu ya TLP

Kwa asili, hii sio kosa, kwa ujumla, ni kipengele tu cha kuunda mradi katika hali ya kutokuelewana kamili kwa wote "wapi na jinsi ya kuhamia?" na kwa ujumla "tutafika huko?" Katika hali hizi programu mbaya ilikuwa chaguo la haki, lakini, kwa kawaida, sikutaka kurudia bila lazima. Wakati huu nilitaka kuifanya kwa busara: ahadi za atomiki, mabadiliko ya msimbo fahamu (na sio "kuunganisha herufi bila mpangilio hadi itakapoundwa (na maonyo)", kama Linus Torvalds alisema mara moja juu ya mtu, kulingana na Wikiquote), n.k.

Kosa namba tatu: kuingia ndani ya maji bila kujua kivuko

Bado sijaondoa kabisa hii, lakini sasa nimeamua kutofuata njia ya upinzani hata kidogo, na kuifanya "kama mtu mzima," yaani, andika maandishi yangu ya nyuma ya TCG kutoka mwanzo, ili kusema baadaye, "Ndio, hii ni kweli, polepole, lakini siwezi kudhibiti kila kitu - ndivyo TCI inavyoandikwa ..." Kwa kuongezea, hii hapo awali ilionekana kama suluhisho dhahiri, kwani Ninatoa nambari ya binary. Kama wanasema, "Ghent ilikusanyikaу, lakini sio hiyo": nambari hiyo, kwa kweli, ya binary, lakini udhibiti hauwezi kuhamishiwa kwake - lazima isomwe waziwazi kwenye kivinjari ili kukusanywa, na kusababisha kitu fulani kutoka kwa ulimwengu wa JS, ambacho bado kinahitaji kuokolewa mahali fulani. Walakini, kwenye usanifu wa kawaida wa RISC, kwa kadiri ninavyoelewa, hali ya kawaida ni hitaji la kuweka upya kashe ya maagizo kwa nambari iliyotengenezwa upya - ikiwa hii sio tunayohitaji, basi, kwa hali yoyote, iko karibu. Kwa kuongezea, kutoka kwa jaribio langu la mwisho, nilijifunza kuwa udhibiti hauonekani kuhamishwa hadi katikati ya kizuizi cha kutafsiri, kwa hivyo hatuitaji bytecode kufasiriwa kutoka kwa urekebishaji wowote, na tunaweza kuizalisha kwa urahisi kutoka kwa kazi kwenye TB. .

Walikuja na kupiga teke

Ingawa nilianza kuandika tena msimbo mnamo Julai, teke la uchawi lilipanda bila kutambuliwa: kawaida barua kutoka kwa GitHub hufika kama arifa kuhusu majibu ya Masuala na maombi ya Vuta, lakini hapa , ghafla kutaja katika thread Binaryen kama qemu backend katika muktadha, "Alifanya kitu kama hicho, labda atasema kitu." Tulikuwa tunazungumza juu ya kutumia maktaba inayohusiana na Emscripten Binaryen ili kuunda WASM JIT. Kweli, nilisema kuwa unayo leseni ya Apache 2.0 hapo, na QEMU kwa ujumla inasambazwa chini ya GPLv2, na haziendani sana. Ghafla ikawa kwamba leseni inaweza kuwa rekebisha kwa namna fulani (Sijui: labda ibadilishe, labda leseni mbili, labda kitu kingine ...). Hii, bila shaka, ilinifurahisha, kwa sababu wakati huo nilikuwa tayari nimeangalia kwa karibu umbizo la binary WebAssembly, na kwa namna fulani nilikuwa na huzuni na isiyoeleweka. Pia kulikuwa na maktaba ambayo ingemeza vizuizi vya msingi na grafu ya mpito, kutoa bytecode, na hata kuiendesha kwa mkalimani yenyewe, ikiwa ni lazima.

Kisha kulikuwa na zaidi barua kwenye orodha ya utumaji barua ya QEMU, lakini hii inahusu zaidi swali, "Ni nani anayeihitaji hata hivyo?" Na ndivyo ilivyo ghafla, ikawa ni lazima. Kwa uchache, unaweza kukwangua pamoja uwezekano ufuatao wa matumizi, ikiwa inafanya kazi zaidi au chini ya haraka:

  • kuzindua kitu cha kuelimisha bila usanikishaji wowote
  • uboreshaji kwenye iOS, ambapo, kulingana na uvumi, programu pekee ambayo ina haki ya kutengeneza nambari kwenye kuruka ni injini ya JS (hii ni kweli?)
  • maonyesho ya mini-OS - single-floppy, kujengwa ndani, kila aina ya firmware, nk...

Vipengele vya Muda wa Kivinjari

Kama nilivyokwisha sema, QEMU imefungwa kwa usomaji mwingi, lakini kivinjari hakina. Kweli, yaani, hapana ... Mwanzoni haikuwepo kabisa, basi WebWorkers walionekana - ninavyoelewa, hii ni multithreading kulingana na kupitisha ujumbe. bila vigezo vya pamoja. Kwa kawaida, hii inaleta matatizo makubwa wakati wa kuhamisha msimbo uliopo kulingana na mfano wa kumbukumbu iliyoshirikiwa. Kisha, chini ya shinikizo la umma, pia ilitekelezwa chini ya jina SharedArrayBuffers. Ilianzishwa hatua kwa hatua, waliadhimisha uzinduzi wake katika vivinjari tofauti, kisha wakasherehekea Mwaka Mpya, na kisha Meltdown ... Baada ya hapo walifikia hitimisho kwamba kipimo cha muda kilipungua au kikubwa, lakini kwa msaada wa kumbukumbu ya pamoja na a. thread incrementing counter, yote ni sawa itafanya kazi kwa usahihi kabisa. Kwa hivyo tulizima usomaji mwingi na kumbukumbu iliyoshirikiwa. Inaonekana kwamba baadaye waliigeuza tena, lakini, kama ilivyokuwa wazi kutoka kwa jaribio la kwanza, kuna maisha bila hiyo, na ikiwa ni hivyo, tutajaribu kuifanya bila kutegemea multithreading.

Kipengele cha pili ni kutowezekana kwa upotoshaji wa kiwango cha chini na mrundikano: huwezi kuchukua tu, kuhifadhi muktadha wa sasa na kubadili mpya na mrundikano mpya. Ratiba ya simu inadhibitiwa na mashine pepe ya JS. Inaonekana, shida ni nini, kwani bado tuliamua kusimamia mtiririko wa zamani kabisa kwa mikono? Ukweli ni kwamba block I/O katika QEMU inatekelezwa kupitia coroutines, na hapa ndipo upotoshaji wa kiwango cha chini ungefaa. Kwa bahati nzuri, Emscipten tayari ina utaratibu wa shughuli za asynchronous, hata mbili: Asyncify ΠΈ Emterpreter. Ya kwanza hufanya kazi kupitia msimbo wa JavaScript uliozalishwa na haitumiki tena. Ya pili ni "njia sahihi" ya sasa na inafanya kazi kupitia kizazi cha bytecode kwa mkalimani asilia. Inafanya kazi, bila shaka, polepole, lakini haina bloat kanuni. Ni kweli, msaada wa coroutines kwa utaratibu huu ulipaswa kuchangiwa kwa kujitegemea (tayari kulikuwa na coroutines zilizoandikwa kwa Asyncify na kulikuwa na utekelezaji wa takriban API sawa ya Emterpreter, ulihitaji tu kuziunganisha).

Kwa sasa, bado sijaweza kugawanya nambari hiyo kuwa moja iliyokusanywa katika WASM na kufasiriwa kwa kutumia Emterpreter, kwa hivyo vifaa vya kuzuia havifanyi kazi bado (tazama katika safu inayofuata, kama wanasema ...). Hiyo ni, mwishowe unapaswa kupata kitu kama hiki cha kuchekesha:

  • block I/O iliyotafsiriwa. Kweli, ulitarajia NVMe iliyoigwa na utendaji asilia? πŸ™‚
  • msimbo mkuu wa QEMU ulioundwa kwa tuli (mtafsiri, vifaa vingine vilivyoigwa, n.k.)
  • ilikusanya msimbo wa mgeni kwa nguvu katika WASM

Vipengele vya vyanzo vya QEMU

Kama ulivyokisia tayari, msimbo wa kuiga usanifu wa wageni na msimbo wa kutoa maagizo ya mashine ya mwenyeji hutenganishwa katika QEMU. Kwa kweli, ni ngumu zaidi:

  • kuna usanifu wa wageni
  • kuna vichapuzi, yaani, KVM ya uboreshaji wa maunzi kwenye Linux (kwa mifumo ya wageni na mwenyeji inayooana), TCG kwa utengenezaji wa msimbo wa JIT popote pale. Kuanzia na QEMU 2.9, usaidizi wa kiwango cha uboreshaji wa vifaa vya HAXM kwenye Windows ulionekana (maelezo)
  • ikiwa TCG inatumika na sio uboreshaji wa vifaa, basi ina usaidizi tofauti wa utengenezaji wa nambari kwa kila usanifu wa mwenyeji, na vile vile kwa mkalimani wa ulimwengu wote.
  • ... na karibu na haya yote - vifaa vya pembeni vilivyoigwa, kiolesura cha mtumiaji, uhamiaji, uchezaji wa rekodi, nk.

Kwa njia, ulijua: QEMU inaweza kuiga sio kompyuta nzima tu, bali pia kichakataji cha mchakato tofauti wa mtumiaji kwenye kernel ya mwenyeji, ambayo hutumiwa, kwa mfano, na fuzzer ya AFL kwa ala za binary. Labda mtu angependa kuweka hali hii ya uendeshaji wa QEMU hadi JS? πŸ˜‰

Kama programu nyingi za bure za muda mrefu, QEMU hujengwa kupitia simu configure ΠΈ make. Hebu sema unaamua kuongeza kitu: backend TCG, utekelezaji thread, kitu kingine. Usikimbilie kuwa na furaha/kutishwa (piga mstari inavyofaa) kwa matarajio ya kuwasiliana na Autoconf - kwa kweli, configure QEMU's inaonekana imeandikwa yenyewe na haijatolewa kutoka kwa chochote.

WebAssembly

Kwa hivyo ni nini kitu hiki kinachoitwa WebAssembly (aka WASM)? Hii ni badala ya Asm.js, haijifanyi tena kuwa msimbo halali wa JavaScript. Kinyume chake, ni ya binary na iliyoboreshwa, na hata kuandika nambari ndani yake sio rahisi sana: kwa kuunganishwa, imehifadhiwa katika muundo. 128.

Huenda umesikia kuhusu algoriti ya kurudisha nyuma kwa Asm.js - huu ni urejeshaji wa maagizo ya udhibiti wa mtiririko wa "kiwango cha juu" (yaani, ikiwa-basi-vingine, vitanzi, n.k.), ambayo injini za JS zimeundwa, kutoka. LLVM IR ya kiwango cha chini, karibu na msimbo wa mashine unaotekelezwa na kichakataji. Kwa kawaida, uwakilishi wa kati wa QEMU ni karibu na wa pili. Inaweza kuonekana kuwa hapa ndio, bytecode, mwisho wa mateso ... Na kisha kuna vizuizi, ikiwa-basi-vingine na vitanzi!..

Na hii ni sababu nyingine kwa nini Binaryen ni muhimu: inaweza kukubali vitalu vya kiwango cha juu karibu na kile ambacho kingehifadhiwa katika WASM. Lakini pia inaweza kutoa msimbo kutoka kwa grafu ya vizuizi vya msingi na mabadiliko kati yao. Kweli, tayari nimesema kwamba inaficha muundo wa uhifadhi wa WebAssembly nyuma ya C/C++ API inayofaa.

TCG (Jenereta ya Msimbo mdogo)

GTC ilikuwa awali backend kwa mkusanyaji C. Kisha, inaonekana, haikuweza kuhimili ushindani na GCC, lakini mwishowe ilipata nafasi yake katika QEMU kama utaratibu wa kuzalisha msimbo kwa jukwaa la mwenyeji. Kuna pia backend ya TCG ambayo hutoa bytecode ya kufikirika, ambayo inatekelezwa mara moja na mkalimani, lakini niliamua kuepuka kuitumia wakati huu. Walakini, ukweli kwamba katika QEMU tayari inawezekana kuwezesha mpito kwa TB inayozalishwa kupitia kazi. tcg_qemu_tb_exec, iligeuka kuwa muhimu sana kwangu.

Ili kuongeza nakala mpya ya nyuma ya TCG kwenye QEMU, unahitaji kuunda saraka ndogo tcg/<имя Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρ‹> (kwa kesi hii, tcg/binaryen), na ina faili mbili: tcg-target.h ΠΈ tcg-target.inc.c ΠΈ kujiandikisha ni yote kuhusu configure. Unaweza kuweka faili zingine hapo, lakini, kama unavyoweza kukisia kutoka kwa majina ya hizi mbili, zote mbili zitajumuishwa mahali pengine: moja kama faili ya kichwa cha kawaida (imejumuishwa ndani. tcg/tcg.h, na hiyo tayari iko kwenye faili zingine kwenye saraka tcg, accel na sio tu), nyingine - tu kama kijisehemu cha msimbo ndani tcg/tcg.c, lakini ina ufikiaji wa kazi zake tuli.

Kuamua kwamba ningetumia muda mwingi katika uchunguzi wa kina wa jinsi inavyofanya kazi, nilinakili tu "mifupa" ya faili hizi mbili kutoka kwa utekelezaji mwingine wa nyuma, nikionyesha hii kwa uaminifu kwenye kichwa cha leseni.

file tcg-target.h ina hasa mipangilio katika fomu #define-s:

  • ni rejista ngapi na upana gani kwenye usanifu unaolengwa (tunayo mengi tunayotaka, mengi tunayotaka - swali ni zaidi juu ya kile kitakachotolewa kwa nambari bora zaidi na kivinjari kwenye usanifu wa "lengo kabisa" ...)
  • upatanishaji wa maagizo ya mwenyeji: kwenye x86, na hata katika TCI, maagizo hayajaoanishwa hata kidogo, lakini nitaweka kwenye buffer ya nambari sio maagizo hata kidogo, lakini viashiria vya miundo ya maktaba ya Binaryen, kwa hivyo nitasema: 4 baiti
  • ni maagizo gani ya hiari ambayo backend inaweza kutoa - tunajumuisha kila kitu tunachopata kwenye Binaryen, acha kiongeza kasi kivunje mengine kuwa rahisi zaidi yenyewe.
  • Je, ni takriban ukubwa gani wa kache ya TLB iliyoombwa na mandhari ya nyuma. Ukweli ni kwamba katika QEMU kila kitu ni kikubwa: ingawa kuna kazi za wasaidizi ambazo hufanya mzigo / duka kwa kuzingatia MMU ya mgeni (tungekuwa wapi bila hiyo sasa?), wanahifadhi kashe yao ya tafsiri katika mfumo wa muundo, usindikaji ambao ni rahisi kupachika moja kwa moja kwenye vizuizi vya utangazaji. Swali ni ni kukabiliana gani katika muundo huu kunasindika kwa ufanisi zaidi na mlolongo mdogo na wa haraka wa amri?
  • hapa unaweza kurekebisha madhumuni ya rejista moja au mbili zilizohifadhiwa, kuwezesha kupiga simu kwa TB kupitia kitendaji na kwa hiari kuelezea michache ndogo. inline-kazi kama flush_icache_range (lakini hii sio kesi yetu)

file tcg-target.inc.c, kwa kweli, kawaida ni kubwa zaidi kwa saizi na ina kazi kadhaa za lazima:

  • uanzishaji, pamoja na vizuizi ambavyo maagizo yanaweza kufanya kazi ambayo waendeshaji. Imenakiliwa kwa ukali na mimi kutoka kwa sehemu nyingine ya nyuma
  • kazi ambayo inachukua maagizo ya ndani ya bytecode
  • Unaweza pia kuweka vitendaji vya usaidizi hapa, na pia unaweza kutumia vitendaji tuli kutoka tcg/tcg.c

Kwa nafsi yangu, nilichagua mkakati ufuatao: kwa maneno ya kwanza ya kizuizi kinachofuata cha tafsiri, niliandika vidokezo vinne: alama ya kuanza (thamani fulani katika eneo la karibu. 0xFFFFFFFF, ambayo iliamua hali ya sasa ya TB), muktadha, moduli iliyotengenezwa, na nambari ya uchawi kwa utatuzi. Hapo awali, alama iliwekwa 0xFFFFFFFF - nAmbapo n - nambari ndogo chanya, na kila wakati ilipotekelezwa kupitia mkalimani iliongezeka kwa 1. Ilipofikia 0xFFFFFFFE, mkusanyiko ulifanyika, moduli ilihifadhiwa kwenye jedwali la kazi, ikaingizwa kwenye "kizindua" kidogo, ambacho utekelezaji ulitoka. tcg_qemu_tb_exec, na moduli iliondolewa kwenye kumbukumbu ya QEMU.

Ili kufafanua classics, "Crutch, ni kiasi gani kinachounganishwa katika sauti hii kwa moyo wa proger ...". Walakini, kumbukumbu ilikuwa ikivuja mahali fulani. Zaidi ya hayo, ilikuwa kumbukumbu iliyosimamiwa na QEMU! Nilikuwa na nambari ambayo, wakati wa kuandika maagizo yaliyofuata (vizuri, yaani, pointer), ilifuta yule ambaye kiungo chake kilikuwa mahali hapa mapema, lakini hii haikusaidia. Kwa kweli, katika hali rahisi zaidi, QEMU hutenga kumbukumbu wakati wa kuanza na kuandika nambari inayotokana hapo. Wakati buffer inaisha, msimbo hutupwa nje na inayofuata huanza kuandikwa mahali pake.

Baada ya kusoma msimbo, niligundua kuwa ujanja wa nambari ya uchawi uliniruhusu kutofaulu kwa uharibifu wa lundo kwa kuachilia kitu kibaya kwenye bafa isiyojulikana kwenye pasi ya kwanza. Lakini ni nani anaandika upya bafa ili kukwepa utendakazi wangu baadaye? Kama watengenezaji wa Emscripten wanavyoshauri, nilipokutana na tatizo, nilirejesha msimbo uliotokana na programu asilia, nikaweka Rekodi ya Mozilla-Replay juu yake... Kwa ujumla, mwishowe niligundua jambo rahisi: kwa kila kizuizi, a struct TranslationBlock na maelezo yake. Nadhani ni wapi... Hiyo ni kweli, kabla tu ya kizuizi kwenye bafa. Kugundua hili, niliamua kuacha kutumia vigongo (angalau vingine), na nikatupa nambari ya uchawi, na kuhamisha maneno yaliyobaki kwa struct TranslationBlock, kuunda orodha iliyounganishwa moja ambayo inaweza kupitiwa kwa haraka akiba ya tafsiri inapowekwa upya, na uhifadhi kumbukumbu.

Baadhi ya magongo yanabaki: kwa mfano, viashiria vilivyowekwa alama kwenye bafa ya msimbo - baadhi yao ni rahisi BinaryenExpressionRef, yaani, wanaangalia misemo ambayo inahitaji kuwekwa kwa mstari kwenye kizuizi cha msingi kilichozalishwa, sehemu ni hali ya mpito kati ya BBs, sehemu ni wapi kwenda. Kweli, tayari kuna vizuizi vilivyotayarishwa vya Relooper ambavyo vinahitaji kuunganishwa kulingana na masharti. Ili kuzitofautisha, dhana hutumiwa kuwa zote zimeunganishwa na angalau ka nne, kwa hivyo unaweza kutumia kwa usalama bits mbili muhimu kwa lebo, unahitaji tu kukumbuka kuiondoa ikiwa ni lazima. Kwa njia, lebo kama hizo tayari zinatumika katika QEMU ili kuonyesha sababu ya kutoka kwa kitanzi cha TCG.

Kutumia Binaryen

Moduli katika WebAssembly zina vitendaji, ambavyo kila moja ina mwili, ambayo ni usemi. Misemo ni shughuli zisizo za kawaida na za binary, vizuizi vinavyojumuisha orodha za misemo mingine, mtiririko wa udhibiti, n.k. Kama nilivyosema tayari, mtiririko wa udhibiti hapa umepangwa kwa usahihi kama matawi ya kiwango cha juu, vitanzi, simu za kazi, n.k. Hoja za utendakazi hazipitishwa kwenye rafu, lakini kwa uwazi, kama vile JS. Pia kuna vigezo vya kimataifa, lakini sijazitumia, kwa hivyo sitakuambia juu yao.

Kazi pia zina vigezo vya ndani, vilivyohesabiwa kutoka sifuri, vya aina: int32 / int64 / kuelea / mara mbili. Katika kesi hii, vigezo vya kwanza vya n ndani ni hoja zinazopitishwa kwa kazi. Tafadhali kumbuka kuwa ingawa kila kitu hapa si cha kiwango cha chini kabisa kulingana na mtiririko wa udhibiti, nambari kamili bado hazina sifa ya "iliyotiwa saini au isiyotiwa saini": jinsi nambari inavyofanya kazi inategemea nambari ya operesheni.

Kwa ujumla, Binaryen hutoa C-API rahisi: unaunda moduli, ndani yake kuunda misemo - unary, binary, vitalu kutoka kwa maneno mengine, mtiririko wa udhibiti, nk. Kisha unaunda kazi na usemi kama mwili wake. Ikiwa wewe, kama mimi, una grafu ya kiwango cha chini cha mpito, sehemu ya relooper itakusaidia. Kwa kadiri ninavyoelewa, inawezekana kutumia udhibiti wa kiwango cha juu cha mtiririko wa utekelezaji kwenye kizuizi, mradi hauendi zaidi ya mipaka ya kizuizi - ambayo ni, inawezekana kutengeneza njia ya haraka ya ndani / polepole. kuunganisha njia ndani ya msimbo wa uchakataji wa akiba ya TLB uliojengewa ndani, lakini si kuingilia kati na mtiririko wa udhibiti wa "nje". Unapokomboa kifunga tena, vizuizi vyake huachiliwa; unapotoa moduli, misemo, vitendaji, n.k. vilivyotengwa kwa hiyo hupotea. uwanja.

Walakini, ikiwa unataka kutafsiri nambari kwenye nzi bila kuunda na kufuta mfano wa mkalimani, inaweza kuwa na maana kuweka mantiki hii kwenye faili ya C++, na kutoka hapo kudhibiti moja kwa moja API nzima ya C++ ya maktaba, kupita tayari- vifuniko vilivyotengenezwa.

Kwa hivyo ili kutoa nambari unayohitaji

// Π½Π°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ Π³Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ (ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠΌΠ΅Π½ΡΡ‚ΡŒ ΠΏΠΎΡ‚ΠΎΠΌ)
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);

... ikiwa nilisahau chochote, samahani, hii ni kuwakilisha tu kiwango, na maelezo yako kwenye hati.

Na sasa crack-fex-pex huanza, kitu kama hiki:

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

Ili kwa namna fulani kuunganisha ulimwengu wa QEMU na JS na wakati huo huo kufikia kazi zilizokusanywa haraka, safu iliundwa (meza ya kazi za kuagiza kwenye kizindua), na kazi zinazozalishwa ziliwekwa pale. Ili kuhesabu faharisi haraka, faharisi ya kizuizi cha tafsiri ya neno sifuri ilitumiwa hapo awali kama hiyo, lakini kisha faharisi iliyohesabiwa kwa kutumia fomula hii ilianza kutoshea tu kwenye uwanja. struct TranslationBlock.

Kwa njia, demo (kwa sasa na leseni mbaya) inafanya kazi vizuri tu katika Firefox. Watengenezaji wa Chrome walikuwa kwa namna fulani si tayari kwa ukweli kwamba mtu angetaka kuunda zaidi ya mifano elfu ya moduli za WebAssembly, kwa hivyo walitenga tu gigabyte ya nafasi ya anwani ya kawaida kwa kila ...

Ni hayo tu kwa sasa. Labda kutakuwa na nakala nyingine ikiwa mtu yeyote ana nia. Yaani, kuna bado angalau pekee fanya vifaa vya kuzuia kufanya kazi. Inaweza pia kuwa na maana kufanya mkusanyiko wa moduli za WebAssembly kuwa sawa, kama ilivyo kawaida katika ulimwengu wa JS, kwani bado kuna mkalimani anayeweza kufanya haya yote hadi moduli asili iko tayari.

Hatimaye kitendawili: umekusanya binary kwenye usanifu wa 32-bit, lakini msimbo, kupitia shughuli za kumbukumbu, hupanda kutoka Binaryen, mahali fulani kwenye stack, au mahali pengine kwenye GB 2 ya juu ya nafasi ya anwani ya 32-bit. Shida ni kwamba kutoka kwa maoni ya Binaryen hii ni kupata anwani kubwa sana ya matokeo. Jinsi ya kuzunguka hii?

Kwa njia ya admin

Sikuishia kujaribu hii, lakini wazo langu la kwanza lilikuwa "Nini ikiwa nitasakinisha Linux ya 32-bit?" Kisha sehemu ya juu ya nafasi ya anwani itachukuliwa na kernel. Swali pekee ni kiasi gani kitakachochukuliwa: 1 au 2 Gb.

Kwa njia ya programu (chaguo kwa watendaji)

Hebu tupige kiputo juu ya nafasi ya anwani. Mimi mwenyewe sielewi kwa nini inafanya kazi - huko tayari lazima kuwe na stack. Lakini "sisi ni watendaji: kila kitu kinatufanyia kazi, lakini hakuna anayejua kwa nini..."

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

... ni kweli kwamba haiendani na Valgrind, lakini, kwa bahati nzuri, Valgrind yenyewe inasukuma kila mtu kutoka hapo :)

Labda mtu atatoa maelezo bora ya jinsi nambari yangu hii inavyofanya kazi ...

Chanzo: mapenzi.com

Kuongeza maoni