Kaua aega tagasi otsustasin nalja pärast tõestada protsessi pöörduvust ja õppida genereerima JavaScripti (täpsemalt Asm.js) masinakoodist. Katseks valiti QEMU, mõni aeg hiljem kirjutati artikkel Habrisse. Kommentaarides soovitati mul projekt WebAssembly-le üle tuua, ja isiklikult ei olnud mul tahtmist peaaegu lõpetatud projekti kuidagi loobuda... Töö liikus edasi, kuid väga aeglaselt, ja nüüd ilmus sellele artiklile küsimus „Kuidas kõik lõppes?“. Minu põhjalikku vastust kuuldes öeldi: „See läheb artiklisse.” Kui see läheb, siis tuleb artikkel. Võib-olla on kellelegi kasu. Sellest saab lugeja teada mõningaid fakte QEMU koodigeneratsiooni backend'ide ülesehituse kohta, samuti seda, kuidas kirjutada Just-in-Time kompilaator veebirakendusele.
Ülesanded
Kuna „kuidas-kuidas” portimine QEMU-d JavaScriptile ma juba õppisin, siis seekord otsustasin teha asju korralikult ja mitte korda vanu vigu.
Viga number üks: forks point release'ilt
Minu esimene viga oli eralduda oma versiooniga upstream-versioonist 2.4.1. Siis tundus see hea ideena: kui point release eksisteerib, siis on see ilmselt stabiilsem kui lihtne 2.4, rääkimata juba harust master. Kuna ma kavatsesin lisada märkimisväärse hulga oma bug'e, ei olnud mul teiste omad tõesti vaja. Nii see võib-olla läkski. Aga siin on probleem: QEMU ei seisa paigal ja mingil hetkel kuulutati isegi välja, et genereeritud koodi optimeerimine on 10% võrra paranenud. „Aga, nüüd ma teen selle korda,” mõtlesin ma ja pidin pettuma. Siin on vaja teha kõrvalepõige: QEMU.js ühetaktilise olemuse ja arvestades, et originaalne QEMU ei eelda mitme lõime puudumist (st tema jaoks on oluline, et mitu ühendamata kooditee saaksid samaaegselt töötada, mitte lihtsalt „kasutada kõiki tuumasid”), pidin uue kaudu väljakutses kõige peamised lõimede funktsioonid „tagurpidi keerduma”. See tekitas loomulikult teatud probleeme liitumisel. Siiski asjaolu, et osa muudatusi harust, master, kust ma üritasin oma koodi liita, samuti need cherry picked punkti väljaandes (ja seega ka minu harusse) ka ei lisanud ilmselt mugavusi.
Ühesõnaga, otsustasin, et igal juhul on mõtet prototüüp tükkideks võtta ja uut versiooni nullist ehitada millegi uuemaga, ja nüüd juba master.
Viga number kaks: TLP-metoodika
Põhimõtteliselt ei ole see viga, vaid lihtsalt projektitegemise spetsiifika täieliku arusaamatuse tingimustes, kuidas „kuhu ja kuidas liikuda?” ja üldiselt „kas me jõuame sihile?”. Neis tingimustes töötamine ilma selget plaani omamata oli õigustatud valik, kuid loomulikult ei tahtnud seda vajaduseta korrata. Seekord tahtsin teha korralikult: aatomilised commit'id, teadlikud koodimuudatused (mitte „sattumine juhuslike tähemärkide kokku, kuni see kompassib (hoiatustega)”, nagu ütles Linus Torvalds kellegi kohta, kui uskuda Viki tsitaate) jne.
Viga number kolm: teadmata sügavustesse, astuda vette
Sellest ei ole ma siiani täielikult vabanenud, kuid nüüd olen otsustanud mitte minna täiesti väikseima vastupanu teed ja teha „täiskasvanulikult”, nimelt kirjutada oma TCG backend nullist, et hiljem ei ütleks, et „Jah, see on loomulikult aeglane, aga ma ei saa kõike kontrollida — TCI on nii kirjutatud…”. Lisaks tundus see algselt ilmne lahendus, kuna ma genereerin binaarkoodi. Nagu öeldakse, „Kogusin Genton, aga mitte mitte»: kood on muidugi binaarne, kuid juhtimist ei saa lihtsalt nii edasi anda — see tuleb selgelt brauserisse suruda kompileerimiseks, saades tulemuseks mingi objekt JS maailmast, mille tuleb veel kuhugi salvestada. Siiski, normaalses RISC-arkitektuuris, kui ma ei eksi, on tüüpiline olukord vajadus selgelt tühjendada juhiste vahemälu taas genereeritud koodi jaoks — kui see ei ole just see, mida me vajame, siis igal juhul on see lähedal. Lisaks, oma varasest katsest olen õppinud, et juhtimine pole edastatud tõlkevööndi keskele, seega ei ole meile bytecode, mida tõlgendada mis tahes positsioonilt, eriti vajalik, ja saame lihtsalt genereerida funktsiooni TB-l.
Tulid ja andsid tõu!
Kuigi hakkasin koodi ümber kirjutama juba juulis, tuli maagiline tõuge märkamatult: tavaliselt tuleb GitHubist kirju nagu teavitusi Issue'delt ja Pull requests'ilt, aga sel korral, ütleb ühtäkki mainimine teemas kontekstis, „Siin tegi ta midagi sarnast, võib-olla ütleb ta midagi”. Rääkisime Emscripteni-lähedase raamatukogu kasutamisest WASM JIT-i loomiseks. Ma ütlesin, et teil on seal Apache 2.0 litsents, aga QEMU jaotatakse tervikuna GPLv2 alusel ning need ei ole just väga ühilduvad. Ühel hetkel selgus, et litsentsi saab kuidagi kohandada (ei tea: võib-olla vahetada, võib-olla topeltlitsentseerida, võib-olla veel midagi…). See tegi mind loomulikult rõõmsaks, sest olin juba mitu korda vaadanud WebAssembly, ja see tundus mulle kuidagi kurb ja arusaamatu. Siin oli raamatukogu, mis nii baasplokid ülemineku graafikuga neelab, annab välja ka bytecode'i ning kui vajalik, käivitab selle isegi tõlgis.
Siis tuli veel QEMU meililistes, aga see on juba küsimus, "Aga kellele seda üldse vaja on?". Ja selgus, et ütleb ühtäkki, oli seda tõesti vaja. Vähemalt on võimalik saada selliseid kasutamisvõimalusi, kui see töötab enam-vähem kiiresti:
- midagi õppimisvõimekust käivitada ilma installimata
- virtualiseerimine iOS-is, kus kuulujuttude järgi on ainus rakendus, mis omab õigust koodigeneratsiooniks reaalajas — JS-mootor (kas see on tõsi?)
- mini-OSi demonstraatsioon — ühes diskette, sisseehitatud, erinevad püsiprogrammid jms...
Brauseri täitmisfunktsioonid
Nagu ma juba mainisin, on QEMU sõltuv mitmeid teemasid, kuid brauseris ei ole seda. No, teisisõnu, alguses ei olnud seda üldse, hiljem ilmusid WebWorkers — nii palju kui ma aru saan, on see mitme teemaline, mis põhineb sõnumite edastamisel ilma ühiselt muudetavate muutujate. Loomulikult tekitab see märkimisväärseid probleeme olemasoleva koodi portimise puhul, mis põhineb jagatud mälu mudelil. Seejärel, avalikkuse surve all, viidi see ellu ja nimetasid selle SharedArrayBuffers. Seda hakati järk-järgult kasutusele võtma, tähistati selle käivitamist erinevates brauserites, seejärel tähistati uut aastat ja siis Meltdown... Pärast seda jõuti järeldusele, et ei ole vahet, kas mõõta aega, ja jagatud mälu ning voog, mis suurendab loendurit, ikkagi . Nii et nad keelasid mitme teemalise jagatud mälu. Ilmselt lülitati see hiljem tagasi sisse, kuid nagu esimesest eksperimendist selgus, on ka elu olemas, ja kuna nii on, siis proovime teha ilma sõltumata mitme teema kasutamisest.
Teine omadus on madalate tasemete manipuleerimise puudumine virna osas: ei saa lihtsalt võtta, salvestada praegust konteksti ja vahetada uue virnaga. Kutsumise virnaga tegeleb JS virtual machine. Tundub, et mis probleem see ikka on, kui me otsustasime, et haldame olnud vooge täielikult käsitsi? Asi on selles, et plokk-io QEMU-s on rakendatud läbi koorutinide, ja just siin oleksid madalat taset virna manipuleerimised väga kasulikud. Õnneks sisaldab Emscripten juba mehhanismi asünkroonsete operatsioonide jaoks, isegi kahte: ja . Esimene töötab märkimisväärse JavaScript-koodi paisutamisega ja ei ole enam toetatud. Teine on praegune "õige viis" ja töötab oma interpreteerija jaoks baitkoodi genereerimise kaudu. Töötab, muidugi, aeglaselt, kuid ei paisuta koodi. Tõsi, selle mehhanismi koorutite toe jaoks pidime ise panustama (seal olid juba Asyncify alla kirjutatud koorutid ja teise sarnase API teostus Emterpreterile, tuli lihtsalt need ühendada).
Praegu pole ma veel jõudnud koodi jagada kompileeritavaks WASM ja Emterpreteriga interpreteeritavaks, seega ei tööta plokkseadmed veel (vaadake järgmistes osades, nagu öeldakse…). Lõppkokkuvõttes peaks tulema midagi sellist toredat kihilist:
- interpreteeritav plokk sisend-väljund. No ja mis, kas tõesti ootasite emuleeritud NVMe algset jõudlust? 🙂
- staatiliselt kompileeritud QEMU põhikood (tõlkija, ülejäänud emuleeritud seadmed jne)
- dünaamiliselt kompileeritav WASM külaliskood
QEMU lähtekoodide omadused
Kuidas te juba ilmselt arvasite, on külalisarhitektuuride emuleerimise kood ja hosti masinainstruktsioonide genereerimise kood QEMU-s eraldi. Tegelikult on see isegi natuke keerulisem:
- on külalisarhitektuurid
- has kiirendajad, nimelt KVM riistvaralist virtualiseerimist Linuxis (koos üksteisega ühilduvate külaliste ja hostisüsteemide jaoks), TCG JIT-koodigeneratsiooniks igal pool. Alates QEMU 2.9 on Windowsis lisandunud riistvara virtualiseerimise HAXM standardi tugi ()
- kui kasutatakse TCG-d, mitte riistvara virtualiseerimist, siis sellel on iga hostimisarhitektuuri jaoks eraldi toetus koodigeneraatorile, samuti universaalsele interpreteerijale
- … ja kogu selle ümber on emuleeritud perifeeria, kasutajaliides, migratsioon, record-replay jne.
Muide, kas teadsite: QEMU suudab emuleerida mitte ainult terve arvuti, vaid ka protsessori eraldi kasutajaprotsessile hosti tuumas, mida kasutab näiteks AFL fauzzer binaarfailide instrumenteerimiseks. Võib-olla tahaks keegi selle QEMU töörežiimi portida JS-ile? 😉
Nagu enamus pikaajalisi avatud lähtekoodiga programme, kompileeritakse QEMU kutse kaudu configure ja make. Oletame, et otsustasite midagi lisada: TCG tagaplaan, voogude rakendamine, midagi muud. Ärge kiirustage rõõmustama/ärevust tundma (vajalik joonida) Autoconfiga suhtlemise perspektiivi — tegelikult, configure näib, et QEMU-l on oma käsitsi kirjutatud ja ei genereerita sellest midagi.
WebAssembly
Mis asi on siis WebAssembly (tuntud ka kui WASM)? See on Asm.js asendaja, mis ei püüa enam olla kehtiv JavaScripti kood. Vastupidi, see on rangelt binaarne ja optimeeritud, ning isegi lihtsalt täisarvu kirjutamine sellesse pole kuigi lihtne: see salvestatakse kompaktsemalt formaadis. .
Võib-olla olete kuulnud relooping'i algoritmist Asm.js jaoks — see taastab „kõrgetasemelised“ juhtimisjuhised (nagu if-then-else, tsüklid jne), millega JavaScripti mootorsõidukid on kohandatud, madalamast LLVM IR-ist, mis on lähemal masinakoodile, mida protsessor täidab. Loomulikult on QEMU vahepealne esitlus lähemal teisele. Tundub, et siin ta on, baitkood, piinade lõpp… Ja siin on plokid, if-then-else ja tsüklid!
Ja see on veel üks põhjus, miks Binaryen on kasulik: see võib loomulikult võtta kõrgtasemel plokke, mis on lähedased sellele, mis salvestatakse WASM-i. Kuid see võib ka väljastada koodi põhiplokkide graafist ja nende vahelisest üleminekust. Ja nagu ma juba ütlesin, varjab ta WebAssembly salvestusformaadi mugava C/C++ API taga.
TCG (Tiny Code Generator)
TCG C kompilaatori jaoks back-end. Siis ilmselt ei suutnud ta GCC-ga konkureerida, kuid leidis lõpuks oma koha QEMU-s, toimides hostimisplatvormile mõeldud koodigeneratsioonimehhanismina. On olemas ka TCG back-end, mis genereerib mingi abstraktse bytecode'i, mida kohe interpreteerija käivitab, kuid otsustasin seekord sellest loobuda. Siiski, fakt, et QEMU-s on juba võimalik aktiveerida üleminek genereeritud TB-le funktsiooni tcg_qemu_tb_exec, oli mulle väga käepärane.
Uue TCG back-end'i lisamiseks QEMU-s tuleb luua alamkataloog tcg/<arhitektuuri nimi> (selles osas, tcg/binaryen), ja seal kaks faili: tcg-target.h ja tcg-target.inc.c ja kõik see asi configure. Siia võib panna ka teisi faile, kuid nagu nendel kahes nimekirjast järeldada võib, nad mõlemad kuskile kaasatakse: üks nagu tavaline päisefail (seda kaasatakse tcg/tcg.h, ja seejärel teised failid kataloogides tcg, accel ja mitte ainult), teine — ainult kui koodilõik tcg/tcg.c, kuid tal on juurdepääs selle staatilistele funktsioonidele.
Otsustasin, et ma kulutan liiga palju aega selle üksikasjalikule mõistmisele, kuidas see töötab, seega kopeerisin lihtsalt nende kahe faili «skeleton’id» teise tagasisuunalise teostuse seest, märkides selle ausalt litsentsipealkirjas.
File tcg-target.h peamiselt sisaldab seadistusi kujul #define-de:
- mitu registrit ja kui laiad need on sihtarchitectuuril (meil - nii palju kui tahame, küsimus on pigem selles, mis genereeritakse efektiivsemaks koodiks brauseri «täiesti sihtmärgi» arhitektuuril...)
- hosti käskude joondamine: x86-l ja TCI-s ei ole käskud üldse joondatud, ma plaanin panna koodipufferisse mitte käskusid, vaid osutusi Binaryen’i struktuuridele, seega ütlen: 4 baiti.
- milliseid valikulisi käske võib tagasisuunamine genereerida - aktiveerime kõik, mida leiame Binaryen’is, ülejäänu laseb kiirendi murda lihtsamateks käsudeks.
- milliseid TLB-mälu suurusi backend küsib. Asi on selles, et QEMU-s on kõik tõsine: kuigi on abifunktsioonid, mis teevad laadimise/salvestamise külalis-MMU arvestusega (kuidas me ilma selleta praegu hakkama saaks?), säilitavad nad oma translatsioonivahendi oma struktuuris, mille töötlemine on mugav otse translatsiooni plokkidesse integreerida. Küsimus on aga selles, milline nihke struktuur on kõige tõhusam väikese ja kiire käskude järjestusega.
- siin saab kohandada ühe või kahe reserveeritud registeri määramist, aktiveerida TB kutsumise funktsiooni ja võimalusel kirjeldada paar pisiasja.
inline-funktsioonide naguflush_icache_range(aga see ei ole meie juhtum)
File tcg-target.inc.c, loomulikult on see tavaliselt palju suurem ja sisaldab mitmeid kohustuslikke funktsioone:
- algatamine, mis näitab muu hulgas piirangud, millised käsklused võivad töötada koos milliste operandidega. See on julmalt kopeeritud minult teiselt backendilt.
- funktsioon, mis võtab vastu ühe sisemise bytecode käskluse
- siia saab samuti lisada abifunktsioone ning siin saab kasutada staatilisi funktsioone.
tcg/tcg.c
Olen valinud endale järgmise strateegia: iga uue edastamisploki esimestes sõnades kirjutasin alla neli näidikut: alguse sildid (mingi väärtus ümbruses 0xFFFFFFFF, mille põhjal määrati TB jooksva oleku), konteksti, genereeritud mooduli ja silmapaistva numbri tõrkeotsinguks. Esiteks määrati silt 0xFFFFFFFF - n, kus n — väike positiivne arv, ja iga teostuse korral tõusis see läbi tõlgendi 1 võrra. Kui see jõudis 0xFFFFFFFE, toimus kompileerimine, moodul salvestati funktsioonide tabelisse, mis imporditi väikesse "käivitajasse", kuhu suundus ka teostamine tcg_qemu_tb_exec, ja moodul eemaldati QEMU mälust.
Para fraseerides klassikat, "Kostil, kui palju on selles helis arendaja südame jaoks...". Kuid mälestus mujale voolas. Ja see oli mälestus, mida haldas QEMU! Mul oli kood, mis järgmise käsu (noh, suunanäitaja) kirjutamisel kustutas selle, millele viidati, aga see ei aidanud. Tegelikult eraldab QEMU lihtsaimal juhul käivitamisel mälu ja kirjutab sinna genereeritud koodi. Kui puhver lõpeb, visatakse kood välja ja järgmine hakkab sellele kohale kirjutama.
Koodi uurides mõistsin, et kostil koos magic number'i kasutamisega võimaldas vältida kuhja purunemist, vabastades midagi valele mitte-initsialiseeritud puhvri esimesel läbimisel. Aga kes kirjutab puhvri ümber minu funktsiooni mööda? Nagu Emscripteni arendajad soovitavad, loobusin probleemist ja portisin saadud koodi tagasi natiivseks rakenduseks, suunates sellele Mozilla Record-Replay... Kokkuvõttes mõistsin lihtsat asja: iga ploki jaoks eraldatakse struct TranslationBlock tema. Arvatka, kus… Õige, otse enne plokki puhvris. Seda mõistes otsustasin ma lõpetada toimetamise (vähemalt mõne), ja lihtsalt viskasin magic number ära, ning tõin ülejäänud sõnad struct TranslationBlock, luues ühe sidusa nimekirja, mille kaudu saab kiiresti liikuda, kui vahemälu tuleb tühjendada, ja vabastada mälu.
Mõned toimetamisnipid jäid alles: näiteks, märgitud näidikud koodipuhvris — osa neist on lihtsalt BinaryenExpressionRef, see tähendab, et need vaatavad väljendeid, mis tuleb lineaarselt paigutada genereeritavasse põhiplokki, osa — ülemineku tingimus BB vahel, osa — kuhu minna. Ja on juba valmistatud plokid Relooper'ile, mida tuleb ühendama tingimuste järgi. Nende eristamiseks kasutatakse oletust, et kõik need on joondatud vähemalt nelja baidi kaupa, nii et saab rahulikult kasutada madalamaid kahte bitti märgina, tuleb ainult meeles pidada, et seda kasutada kui vaja. Muide, selliseid märke kasutatakse juba QEMU's tsüklist väljumise põhjuse tähistamiseks.
Binaryeni kasutamine
WebAssembly moodulid sisaldavad funktsioone, millest igaühel on keha, mis koosneb väljendist. Väljendid on uni- ja binaaroperatsioonid, plokid, mis koosnevad teiste väljendite loenditest, juhtimisvoog jne. Nagu ma juba ütlesin, korraldatakse juhtimisvoog siin kõrgetasemeliste harude, tsüklite, funktsioonikutsumiste jne kujul. Argumente edastatakse funktsioonidele mitte virnas, vaid selgelt, nagu JavaScriptis. On ka globaalsed muutujad, kuid ma ei kasutanud neid, seega ei räägi ma neist.
Funktsioonidel on samuti nullist numereeritud lokaalsed muutujad, mille tüübid on: int32 / int64 / float / double. Samuti on esimesed n lokaalsed muutujad funktsioonile edastatud argumendid. Pange tähele, et kuigi siin ei ole kõik madalal tasemel juhtimisvoo osas, ei kanna täisarvud siiski endas tähist «märgiga/ilma märgita»: kuidas number käitub, sõltub operatsiooni koodist.
Üldiselt pakub Binaryen : loote mooduli, milles loote väljendeid — unaarseid, binaarseid, teisi väljendeid sisaldavaid blokke, juhtimisvooge jne. Siis loote funktsiooni, mille kehas tuleb märkida väljend. Kui teil, nagu ka minul, on madala taseme üleminekute graaf — siis aitab teid komponent relooper. Nii palju kui ma aru saan, saab kõrgetasemelist juhtimist blokis kasutada, kuni see ei ulatu bloki piiridest välja — see tähendab, et sisemise harunemise fast path / slow path tegemine sisseehitatud TLB vahemälu töötlemise koodis on võimalik, kuid „välise” juhtimisvooga sekkuda ei saa. Kui vabastate relooperi, vabastatakse selle blokkide mälu, ja kui vabastate mooduli, kaovad väljendid, funktsioonid jne, mis tema sisse on eraldatud. areen.
Siiski, kui soovite koodi jooksvalt tõlgendada ilma lisainstantseerimise ja kustutamiseta, võib olla mõistlik see loogika C++ faili viia ja sealt otse hallata kogu C++ API teeki, mööda minnes valmis pakenditest.
Seega, et koodi genereerida, on vajalik
// настроить глобальные параметры (можно поменять потом)
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);… kui midagi unustasin — vabandust, see on lihtsalt selleks, et mõista ulatust, ja detailid — need on dokumentatsioonis.
Ja nüüd algab kreks-feks-peks, umbes selline:
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,
// ...
}
);
// ja nüüd on teil instance!
}, buf, sz);Kuidas siduda QEMU maailma ja JS-i ning samal ajal kiiresti siseneda kompileeritud funktsioonidesse, loodi massiiv (funktsioonide importimise tabel käivitajasse), kuhu paigutati genereeritud funktsioonid. Kiire indeksite arvutamiseks kasutati algselt null-sõna tõlkeploki indeksi väärtust, kuid hiljem arvutatud indeks lihtsalt kirjutati välja struct TranslationBlock.
Muide, (hetkel ähmaste litsentsidega) töötab normaalselt ainult Firefoxis. Chrome'i arendajad olid üllatunud et keegi sooviks luua üle tuhande WebAssembly mooduli instantsi, seetõttu eraldati lihtsalt igaühele ükshaaval gigabait virtuaalsest aadressiruumist...
See on kõik seni. Võib-olla tuleb veel üks artikkel, kui see kellelegi huvi pakub. Nimelt on jäänud veel vähemalt ainult blokeerivate seadmete töölepanemine. Võib-olla on mõttekas ka teha WebAssembly moodulite kompileerimine asünkroonseks, nagu see on tavaline JS maailmas, kuna on juba olemas tõlgendaja, mis suudab seda kõike täita, kuni natiivne moodul on valmis.
Lõpetuseks mõistatus: te lõite binaarfaili 32-bitise arhitektuuri jaoks, kuid mäluoperaatsioonide kaudu siseneb kood Binaryenist kuhugi kuhugi kuhugi 32-bitise aadressiruumi ülemisse 2 GB. Probleem on selles, et Binaryeni vaatenurgast on see juurdepääs liiga suurele lõppaadressile. Kuidas seda vältida?
Administraatori stiilis
Ma ei testinud seda lõppkokkuvõttes, kuid esmane mõte oli: "Aga mis siis, kui installida 32-bitine Linux?" Siis hõivab ülalt aadressiruumi osa südamik. Küsimus on ainult selles, kui palju on hõivatud: 1 või 2 GB.
Programmeerija stiilis (praktiline variant)
Teeme mulli aadressiruumi ülemises osas. Ma ei mõista, miks see töötab — seal ju juba peab olema stek. Aga "meie praktikas: meil kõik töötab, kuid keegi ei tea, miks…".
// 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));
}… Valgrindiga ei ühildu, aga õnneks tõukab Valgrind seal kõik tõhusalt välja 🙂
Võib-olla keegi annab parema seletuse, kuidas see minu kood töötab…
Allikas: habr.com
