QEMU.js: nå seriøs og med WASM

En gang i tiden bestemte jeg meg for moro skyld bevise reversibiliteten til prosessen og lær hvordan du genererer JavaScript (mer presist, Asm.js) fra maskinkode. QEMU ble valgt til eksperimentet, og en tid senere ble det skrevet en artikkel om Habr. I kommentarfeltet ble jeg rådet til å lage prosjektet på nytt i WebAssembly, og til og med slutte selv nesten ferdig Jeg ville på en eller annen måte ikke ha prosjektet ... Arbeidet pågikk, men veldig sakte, og nå, nylig, dukket det opp i den artikkelen комментарий om emnet "Så hvordan endte det hele?" Som svar på mitt detaljerte svar hørte jeg «Dette høres ut som en artikkel». Vel, hvis du kan, kommer det en artikkel. Kanskje noen finner det nyttig. Fra den vil leseren lære noen fakta om utformingen av QEMU-kodegenereringsbackends, samt hvordan man skriver en Just-in-Time-kompilator for en nettapplikasjon.

oppgaver

Siden jeg allerede hadde lært å "på en eller annen måte" porte QEMU til JavaScript, ble det denne gangen besluttet å gjøre det klokt og ikke gjenta gamle feil.

Feil nummer én: gren fra punktutgivelse

Min første feil var å dele versjonen min fra oppstrømsversjon 2.4.1. Da virket det som en god idé for meg: hvis punktutgivelse eksisterer, så er den sannsynligvis mer stabil enn enkel 2.4, og enda mer grenen master. Og siden jeg planla å legge til en god del av mine egne feil, trengte jeg ikke noen andre i det hele tatt. Det ble nok slik det ble. Men her er saken: QEMU står ikke stille, og på et tidspunkt annonserte de til og med optimalisering av den genererte koden med 10 prosent. «Ja, nå skal jeg fryse,» tenkte jeg og brøt sammen. Her må vi gjøre en digresjon: på grunn av den enkelt-trådede naturen til QEMU.js og det faktum at den opprinnelige QEMU ikke innebærer fravær av multi-threading (det vil si muligheten til å operere flere urelaterte kodebaner samtidig, og ikke bare "bruk alle kjerner") er avgjørende for det, hovedfunksjonene til tråder jeg måtte "slå det ut" for å kunne ringe utenfra. Dette skapte noen naturlige problemer under sammenslåingen. Men det faktum at noen av endringene fra grenen master, som jeg prøvde å slå sammen koden min med, ble også plukket kirsebær i poengutgivelsen (og derfor i grenen min) også ville sannsynligvis ikke ha lagt til.

Generelt bestemte jeg meg for at det fortsatt er fornuftig å kaste ut prototypen, demontere den for deler og bygge en ny versjon fra bunnen av basert på noe ferskere og nå fra master.

Feil nummer to: TLP-metodikk

I hovedsak er dette ikke en feil, generelt sett er det bare en funksjon for å lage et prosjekt under forhold med fullstendig misforståelse av både "hvor og hvordan vi skal flytte?" og generelt "vil vi komme dit?" I disse forholdene klønete programmering var et berettiget alternativ, men jeg ønsket naturligvis ikke å gjenta det unødvendig. Denne gangen ønsket jeg å gjøre det klokt: atomiske forpliktelser, bevisste kodeendringer (og ikke "strenge tilfeldige tegn sammen før det kompileres (med advarsler)", som Linus Torvalds en gang sa om noen, ifølge Wikiquote), osv.

Feil nummer tre: å komme i vannet uten å kjenne vadestedet

Jeg har fortsatt ikke blitt helt kvitt dette, men nå har jeg bestemt meg for å ikke følge den minste motstands vei i det hele tatt, og å gjøre det "som voksen", nemlig skrive TCG-backend fra bunnen av, for å ikke å måtte si senere, "Ja, dette går selvfølgelig sakte, men jeg kan ikke kontrollere alt - det er slik TCI er skrevet..." Dessuten virket dette i utgangspunktet som en åpenbar løsning, siden Jeg genererer binær kode. Som de sier: «Ghent samlet segу, men ikke den": koden er selvfølgelig binær, men kontrollen kan ikke bare overføres til den - den må eksplisitt skyves inn i nettleseren for kompilering, noe som resulterer i et bestemt objekt fra JS-verdenen, som fortsatt må lagres et sted. Men på vanlige RISC-arkitekturer, så vidt jeg forstår, er en typisk situasjon behovet for å eksplisitt tilbakestille instruksjonsbufferen for regenerert kode - hvis dette ikke er det vi trenger, så er det i alle fall nært. I tillegg, fra mitt siste forsøk, lærte jeg at kontrollen ikke ser ut til å bli overført til midten av oversettelsesblokken, så vi trenger egentlig ikke bytekode tolket fra noen offset, og vi kan ganske enkelt generere den fra funksjonen på TB .

De kom og sparket

Selv om jeg begynte å skrive om koden tilbake i juli, snek det seg et magisk kick ubemerket: vanligvis kommer brev fra GitHub som varsler om svar på problemer og pull-forespørsler, men her, plutselig nevne i tråden Binaryen som en qemu-backend i konteksten, "Han gjorde noe sånt, kanskje han vil si noe." Vi snakket om å bruke Emscriptens relaterte bibliotek Binaryen å lage WASM JIT. Vel, jeg sa at du har en Apache 2.0-lisens der, og QEMU som helhet er distribuert under GPLv2, og de er ikke særlig kompatible. Plutselig viste det seg at en lisens kan være det fikse det på en eller annen måte (Jeg vet ikke: kanskje endre det, kanskje dobbel lisensiering, kanskje noe annet...). Dette gjorde meg selvfølgelig glad, for på den tiden hadde jeg allerede sett nøye på binært format WebAssembly, og jeg var på en eller annen måte trist og uforståelig. Det var også et bibliotek som ville sluke de grunnleggende blokkene med overgangsgrafen, produsere bytekoden, og til og med kjøre den i selve tolken, om nødvendig.

Så var det mer et brev på QEMU-postlisten, men dette handler mer om spørsmålet "Hvem trenger det egentlig?" Og det er plutselig, viste det seg at det var nødvendig. Som et minimum kan du skrape sammen følgende bruksmuligheter, hvis det fungerer mer eller mindre raskt:

  • lanserer noe lærerikt uten installasjon i det hele tatt
  • virtualisering på iOS, der, ifølge rykter, er den eneste applikasjonen som har rett til kodegenerering en JS-motor (er dette sant?)
  • demonstrasjon av mini-OS - single-floppy, innebygd, alle typer firmware, etc...

Funksjoner for nettleserkjøring

Som jeg allerede sa, er QEMU knyttet til multithreading, men nettleseren har det ikke. Vel, det vil si, nei... Først eksisterte det ikke i det hele tatt, så dukket WebWorkers opp - så vidt jeg forstår er dette multithreading basert på meldingsoverføring uten delte variabler. Naturligvis skaper dette betydelige problemer ved portering av eksisterende kode basert på delt minnemodell. Så, under offentlig press, ble det også implementert under navnet SharedArrayBuffers. Det ble gradvis introdusert, de feiret lanseringen i forskjellige nettlesere, så feiret de nyttår, og deretter Meltdown... Deretter kom de til den konklusjonen at grov eller grov tidsmåling, men ved hjelp av delt minne og en tråden øker telleren, det er det samme det vil fungere ganske nøyaktig. Så vi deaktiverte multithreading med delt minne. Det ser ut til at de senere har slått det på igjen, men som det ble klart fra det første eksperimentet, er det liv uten det, og i så fall vil vi prøve å gjøre det uten å stole på multithreading.

Den andre funksjonen er umuligheten av manipulasjoner på lavt nivå med stabelen: du kan ikke bare ta, lagre gjeldende kontekst og bytte til en ny med en ny stabel. Anropsstakken administreres av den virtuelle JS-maskinen. Det ser ut til, hva er problemet, siden vi fortsatt bestemte oss for å administrere de tidligere strømmene helt manuelt? Faktum er at blokk-I/O i QEMU implementeres gjennom coroutines, og det er her stackmanipulasjoner på lavt nivå vil komme godt med. Heldigvis inneholder Emscipten allerede en mekanisme for asynkrone operasjoner, til og med to: Asynkroniser и Emperpreter. Den første fungerer gjennom betydelig oppblåsthet i den genererte JavaScript-koden og støttes ikke lenger. Den andre er den nåværende "riktige måten" og fungerer gjennom bytekodegenerering for den opprinnelige tolken. Det fungerer selvfølgelig sakte, men det blåser ikke koden. Riktignok måtte støtte for koroutiner for denne mekanismen bidra med uavhengig (det var allerede skrevet koroutiner for Asyncify og det var en implementering av omtrent samme API for Emterpreter, du trengte bare å koble dem til).

For øyeblikket har jeg ennå ikke klart å dele koden til en kompilert i WASM og tolket ved hjelp av Emterpreter, så blokkenheter fungerer ikke enda (se i neste serie, som de sier...). Det vil si, til slutt bør du få noe sånt som denne morsomme lagdelte tingen:

  • tolket blokk I/O. Vel, forventet du virkelig emulert NVMe med innebygd ytelse? 🙂
  • statisk kompilert hoved-QEMU-kode (oversetter, andre emulerte enheter, etc.)
  • dynamisk kompilert gjestekode i WASM

Funksjoner av QEMU-kilder

Som du sikkert allerede har gjettet, er koden for å emulere gjestearkitekturer og koden for å generere vertsmaskininstruksjoner atskilt i QEMU. Faktisk er det enda litt vanskeligere:

  • det er gjestearkitekturer
  • det er akseleratorer, nemlig KVM for maskinvarevirtualisering på Linux (for gjeste- og vertssystemer som er kompatible med hverandre), TCG for generering av JIT-kode hvor som helst. Fra og med QEMU 2.9 dukket det opp støtte for HAXM maskinvarevirtualiseringsstandard på Windows (detaljene)
  • hvis TCG brukes og ikke maskinvarevirtualisering, har den separat kodegenereringsstøtte for hver vertsarkitektur, så vel som for den universelle tolken
  • ... og rundt alt dette - emulert periferiutstyr, brukergrensesnitt, migrering, avspilling av opptak, etc.

Visste du forresten: QEMU kan emulere ikke bare hele datamaskinen, men også prosessoren for en egen brukerprosess i vertskjernen, som for eksempel brukes av AFL fuzzer for binær instrumentering. Kanskje noen vil overføre denne driftsmåten til QEMU til JS? 😉

Som mest langvarig gratis programvare, bygges QEMU gjennom samtalen configure и make. La oss si at du bestemmer deg for å legge til noe: en TCG-backend, trådimplementering, noe annet. Ikke skynd deg å være glad/forskrekket (understrek etter behov) ved muligheten til å kommunisere med Autoconf – faktisk, configure QEMU-er er tilsynelatende selvskrevne og er ikke generert fra noe.

WebAssembly

Så hva heter denne tingen WebAssembly (aka WASM)? Dette er en erstatning for Asm.js, og utgir seg ikke lenger for å være gyldig JavaScript-kode. Tvert imot, det er rent binært og optimalisert, og til og med bare å skrive et heltall inn i det er ikke veldig enkelt: for kompakthet er det lagret i formatet LEB128.

Du har kanskje hørt om relooping-algoritmen for Asm.js - dette er gjenopprettingen av "høyt nivå" flytkontrollinstruksjoner (det vil si hvis-da-ellers, loops, etc.), som JS-motorer er designet for, fra lavnivå LLVM IR, nærmere maskinkoden som kjøres av prosessoren. Naturligvis er mellomrepresentasjonen av QEMU nærmere den andre. Det ser ut til at her er det, bytecode, slutten på plagen... Og så er det blokker, hvis-så-ellers og løkker!..

Og dette er en annen grunn til at Binaryen er nyttig: den kan naturlig akseptere blokker på høyt nivå nær det som vil bli lagret i WASM. Men den kan også produsere kode fra en graf av grunnleggende blokker og overganger mellom dem. Vel, jeg har allerede sagt at det skjuler WebAssembly-lagringsformatet bak det praktiske C/C++ API.

TCG (Tiny Code Generator)

TCG var opprinnelig backend for C-kompilatoren. Da kunne den tilsynelatende ikke motstå konkurransen med GCC, men til slutt fant den sin plass i QEMU som en kodegenereringsmekanisme for vertsplattformen. Det er også en TCG-backend som genererer litt abstrakt bytekode, som umiddelbart blir utført av tolken, men jeg bestemte meg for å unngå å bruke den denne gangen. Imidlertid er det faktum at i QEMU allerede mulig å aktivere overgangen til generert TB gjennom funksjonen tcg_qemu_tb_exec, viste det seg å være veldig nyttig for meg.

For å legge til en ny TCG-backend til QEMU, må du opprette en underkatalog tcg/<имя архитектуры> (i dette tilfellet, tcg/binaryen), og den inneholder to filer: tcg-target.h и tcg-target.inc.c и ordinere alt handler om configure. Du kan legge inn andre filer der, men som du kan gjette ut fra navnene på disse to, vil de begge bli inkludert et sted: en som en vanlig overskriftsfil (den er inkludert i tcg/tcg.h, og den er allerede i andre filer i katalogene tcg, accel og ikke bare), den andre - bare som en kodebit i tcg/tcg.c, men den har tilgang til sine statiske funksjoner.

Da jeg bestemte meg for at jeg ville bruke for mye tid på detaljerte undersøkelser av hvordan det fungerer, kopierte jeg ganske enkelt "skjelettene" til disse to filene fra en annen backend-implementering, og indikerte dette ærlig i lisensoverskriften.

fil tcg-target.h inneholder hovedsakelig innstillinger i skjemaet #define-s:

  • hvor mange registre og hvilken bredde er det på målarkitekturen (vi har så mange vi vil, så mange vi vil - spørsmålet er mer om hva som vil bli generert til mer effektiv kode av nettleseren på "fullstendig mål"-arkitekturen ...)
  • justering av vertsinstruksjoner: på x86, og til og med i TCI, er instruksjoner ikke justert i det hele tatt, men jeg skal legge inn kodebufferen ikke instruksjoner i det hele tatt, men pekere til Binaryen-bibliotekstrukturer, så jeg vil si: 4 bytes
  • hvilke valgfrie instruksjoner backend kan generere - vi inkluderer alt vi finner i Binaryen, la akseleratoren dele resten opp i enklere selv
  • Hva er den omtrentlige størrelsen på TLB-cachen som er forespurt av backend. Faktum er at i QEMU er alt seriøst: selv om det er hjelpefunksjoner som utfører lasting/lagring med tanke på gjeste-MMU (hvor ville vi vært uten den nå?), lagrer de oversettelsesbufferen i form av en struktur, behandling som er praktisk å bygge direkte inn i kringkastingsblokker. Spørsmålet er, hvilken forskyvning i denne strukturen behandles mest effektivt av en liten og rask sekvens av kommandoer?
  • her kan du justere formålet med ett eller to reserverte registre, aktivere å ringe TB gjennom en funksjon og eventuelt beskrive et par små inline-funksjoner som flush_icache_range (men dette er ikke vårt tilfelle)

fil tcg-target.inc.c, selvfølgelig, er vanligvis mye større i størrelse og inneholder flere obligatoriske funksjoner:

  • initialisering, inkludert restriksjoner på hvilke instruksjoner som kan operere på hvilke operander. Åpenbart kopiert av meg fra en annen backend
  • funksjon som tar én intern bytekode-instruksjon
  • Du kan også legge inn hjelpefunksjoner her, og du kan også bruke statiske funksjoner fra tcg/tcg.c

For meg selv valgte jeg følgende strategi: i de første ordene i neste oversettelsesblokk skrev jeg ned fire pekere: et startmerke (en viss verdi i nærheten 0xFFFFFFFF, som bestemte den nåværende tilstanden til TB), kontekst, generert modul og magisk tall for feilsøking. Først ble merket plassert inn 0xFFFFFFFF - nDer n - et lite positivt tall, og hver gang det ble utført gjennom tolken økte det med 1. Når det nådde 0xFFFFFFFE, kompilering fant sted, modulen ble lagret i funksjonstabellen, importert til en liten «launcher», som utførelsen gikk fra tcg_qemu_tb_exec, og modulen ble fjernet fra QEMU-minnet.

For å parafrasere klassikerne, "Crutch, hvor mye er flettet sammen i denne lyden for progers hjerte ...". Imidlertid lekket minnet et sted. Dessuten var det minne administrert av QEMU! Jeg hadde en kode som, når jeg skrev den neste instruksjonen (vel, det vil si en peker), slettet den hvis lenke var på dette stedet tidligere, men dette hjalp ikke. Faktisk, i det enkleste tilfellet, tildeler QEMU minne ved oppstart og skriver den genererte koden der. Når bufferen går tom, blir koden kastet ut og den neste begynner å bli skrevet på sin plass.

Etter å ha studert koden, innså jeg at trikset med det magiske tallet tillot meg å ikke mislykkes på haugdestruksjon ved å frigjøre noe galt på en uinitialisert buffer ved første pass. Men hvem skriver om bufferen for å omgå funksjonen min senere? Som Emscripten-utviklerne anbefaler, da jeg fikk et problem, porterte jeg den resulterende koden tilbake til den opprinnelige applikasjonen, satte Mozilla Record-Replay på den... Generelt, til slutt innså jeg en enkel ting: for hver blokk, en struct TranslationBlock med beskrivelsen. Gjett hvor... Det stemmer, rett før blokken rett i bufferen. Da jeg innså dette bestemte jeg meg for å slutte å bruke krykker (i det minste noen), og kastet rett og slett ut det magiske tallet og overførte de resterende ordene til struct TranslationBlock, lage en enkeltlenket liste som raskt kan krysses når oversettelsesbufferen tilbakestilles, og frigjøre minne.

Noen krykker gjenstår: for eksempel markerte pekere i kodebufferen - noen av dem er ganske enkelt BinaryenExpressionRef, det vil si at de ser på uttrykkene som må settes lineært inn i den genererte grunnblokken, en del er betingelsen for overgang mellom BB-er, en del er hvor man skal gå. Vel, det er allerede klargjorte blokker for Relooper som må kobles i henhold til forholdene. For å skille dem, brukes antagelsen om at de alle er justert med minst fire byte, slik at du trygt kan bruke de minst signifikante to bitene for etiketten, du trenger bare å huske å fjerne den om nødvendig. Forresten, slike etiketter brukes allerede i QEMU for å indikere årsaken til å gå ut av TCG-sløyfen.

Bruker Binaryen

Moduler i WebAssembly inneholder funksjoner som hver inneholder en body, som er et uttrykk. Uttrykk er unære og binære operasjoner, blokker som består av lister over andre uttrykk, kontrollflyt osv. Som jeg allerede har sagt, er kontrollflyten her organisert nøyaktig som grener på høyt nivå, løkker, funksjonskall osv. Argumenter til funksjoner sendes ikke på stabelen, men eksplisitt, akkurat som i JS. Det er også globale variabler, men jeg har ikke brukt dem, så jeg vil ikke fortelle deg om dem.

Funksjoner har også lokale variabler, nummerert fra null, av typen: int32 / int64 / float / double. I dette tilfellet er de første n lokale variablene argumentene som sendes til funksjonen. Vær oppmerksom på at selv om alt her ikke er helt lavt nivå når det gjelder kontrollflyt, har heltall fortsatt ikke "signert/usignert"-attributtet: hvordan tallet oppfører seg avhenger av operasjonskoden.

Generelt sett gir Binaryen enkel C-API: du lager en modul, i han lage uttrykk - unære, binære, blokker fra andre uttrykk, kontrollflyt, etc. Så lager du en funksjon med et uttrykk som kropp. Hvis du, som meg, har en overgangsgraf på lavt nivå, vil relooper-komponenten hjelpe deg. Så vidt jeg forstår er det mulig å bruke høynivåkontroll av utførelsesflyten i en blokk, så lenge den ikke går utover blokkens grenser - det vil si at det er mulig å lage intern rask bane / sakte bane som forgrener seg inne i den innebygde TLB-bufferbehandlingskoden, men ikke for å forstyrre den "eksterne" kontrollflyten . Når du frigjør en relooper, frigjøres blokkene, når du frigjør en modul, forsvinner uttrykkene, funksjonene osv. som er allokert til den. arena.

Men hvis du ønsker å tolke kode i farten uten unødvendig oppretting og sletting av en tolkforekomst, kan det være fornuftig å legge denne logikken inn i en C++-fil, og derfra direkte administrere hele C++ API-en til biblioteket, utenom klar- laget omslag.

Så for å generere koden du trenger

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

... hvis jeg har glemt noe, beklager, dette er bare for å representere skalaen, og detaljene er i dokumentasjonen.

Og nå begynner crack-fex-pex, noe sånt som dette:

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

For på en eller annen måte å koble sammen verdenene til QEMU og JS og samtidig få tilgang til de kompilerte funksjonene raskt, ble det opprettet en array (en tabell med funksjoner for import til launcheren), og de genererte funksjonene ble plassert der. For raskt å beregne indeksen ble indeksen til nullordsoversettelsesblokken opprinnelig brukt som den, men så begynte indeksen som ble beregnet ved hjelp av denne formelen ganske enkelt å passe inn i feltet i struct TranslationBlock.

Forresten, demo (for øyeblikket med en skummel lisens) fungerer bare bra i Firefox. Chrome-utviklere var liksom ikke klar til det faktum at noen ville ønske å lage mer enn tusen forekomster av WebAssembly-moduler, så de tildelte ganske enkelt en gigabyte med virtuell adresseplass for hver ...

Det er alt for nå. Kanskje det kommer en annen artikkel hvis noen er interessert. Det gjenstår nemlig i det minste kun få blokkenheter til å fungere. Det kan også være fornuftig å gjøre kompileringen av WebAssembly-moduler asynkron, slik det er vanlig i JS-verdenen, siden det fortsatt er en tolk som kan gjøre alt dette til den opprinnelige modulen er klar.

Til slutt en gåte: du har kompilert en binær på en 32-bits arkitektur, men koden, gjennom minneoperasjoner, klatrer fra Binaryen, et sted på stabelen, eller et annet sted i de øvre 2 GB av 32-biters adresserom. Problemet er at fra Binaryens synspunkt er dette tilgang til en for stor resulterende adresse. Hvordan komme rundt dette?

På admins måte

Jeg endte ikke opp med å teste dette, men min første tanke var "Hva om jeg installerte 32-bit Linux?" Da vil den øvre delen av adresseområdet bli okkupert av kjernen. Spørsmålet er bare hvor mye som vil bli okkupert: 1 eller 2 Gb.

På en programmerers måte (alternativ for utøvere)

La oss blåse en boble øverst i adressefeltet. Selv skjønner jeg ikke hvorfor det fungerer - der allerede det må være en stabel. Men "vi er utøvere: alt fungerer for oss, men ingen vet hvorfor ..."

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

... det er sant at det ikke er kompatibelt med Valgrind, men heldigvis skyver Valgrind selv veldig effektivt alle ut derfra :)

Kanskje noen vil gi en bedre forklaring på hvordan denne koden min fungerer...

Kilde: www.habr.com

Legg til en kommentar