Pointerhibák ellen védő új processzort dolgozott ki az ARM
2022-01-26T08:03:05+01:00
2022-06-20T09:11:38+02:00
2022-07-20T03:46:33+02:00
  • Minden egyes memória(dupla)szóhoz (fizikai/virtuális) lesz egy ilyen pointerjelző bit.
    Mutasd a teljes hozzászólást!
  • Mondjuk van nekem egy függvényem, aminek a paramétere egy láncolt lista, ami pointerekkel (vagyis kápókkal) van megvalósítva. A feladat a lista bejárása, aminek a része a kápók felolvasása a memóriából. Amit ugye nem szabad. Egyelőre itt el is akadtam.
    Mutasd a teljes hozzászólást!
  • No, kicsit olvasgattam, annyit már érteni vélek, hogy  pointerek helyett "kápó"-kat használunk; egy kápó egy memória-tartományt ír le, hozzáférési jogbitekkel kiegészítve (rwx).

    A kápó kétszer akkora, mint egy sima pointer, plusz egy bit, ami láthatatlan és hozzáférhetetlen. (Ezt kicsit nehezen értem, de még dolgozom rajta.)

    A memóriában tárolt kápókat is kápókkal védetten érjük el, nyilván, hogy ne lehessen invalid értéket tenni bele. Amikor memóriából felolvassuk a kápót egy regiszterbe, akkor igazolnunk kell (a részleteket nem tudom) hogy az egy olyan tartományt ír le, ami benne van az általunk már elérhető tartományok valamelyikében.

    A malloc is egy kápót ad vissza, értelemszerűen, a free viszont nem teszi rögtön újrafelhasználhatóvá a memóriát, hanem "feketelistára" teszi, addig, míg egy algoritmus be nem járta a program összes kápóját, és nem ellenőrizte, hogy egyik sem vonatkozik a felszabadított területre.
    Mutasd a teljes hozzászólást!
  • A stack a memóriában van. A memóriába pointert írni speciális utasítással lehet, ami egyben rárakja a rejtett flaget is. Nem értem miért lenne ez speciális eset.
    Mutasd a teljes hozzászólást!
  • Ha mondjuk a hívott szubrutinnak (qsort, pl.) a stacken adok át pointereket, akkor az talán valamiféle rejtett alternatív csodastacken át történik... valahogy az az érzésem, hogy minden kérdésre van egy olyan válasz, ami két újabb kérdést generál, míg végül eljutunk oda, hogy száz embernek lesz ebből tudományos fokozata, elköltünk $500,000,000-at, forradalmasítjuk a CPU-tervezést, de igazából az okozott problémák halmaza bazinagy, a megoldottaké meg üres.
    Mutasd a teljes hozzászólást!
  • Még sok ilyet kellene olvassak, hogy értsem, mi lenne a módja annak, hogy a 129-bites pointert ne lehessen memóriában tárolni, vagy ha lehet, akkor hogyan lehet megoldani, hogy a legfelső bitet ne lehessen módosítani, még akkor sem, ha memóriában van.

    Csak a negyedik oldalig kell eljutni, és leírják:

    A 1-bit out-of-band tag, differentiating unstructured data from capability
    • Tags held in-line in registers and caches, “somewhere unseen” in memory
    • Storing data anywhere within a 128-bit granule of memory clears the associated tag
    • Loads, stores, jumps, etc. using a clear tag ==> CPU exception

    Szóval van a memóriának is egy "láthatatlan" része (a regisztereken és a cache-en túl, bár azoknál ezelőtt is volt hasonló), amibe nem irkálhatsz szabadon, és ami megjelöli az adott címet cabability-nek. Az már a hardverre van bízva, hogy ezt hogy oldja meg hatékonyan, de gondolom nem ússza meg a felhasználó, hogy a dupla méretű pointerek mellett még ezt a rejtett memóriát is ki kelljen valahogy fizetnie.

    (Sokkal tovább nem olvastam, annyira nem érdekel hogy egy 50 oldalas prezit végigolvassak miatta.)
    Mutasd a teljes hozzászólást!
  • Még sok ilyet kellene olvassak, hogy értsem, mi lenne a módja annak, hogy a 129-bites pointert ne lehessen memóriában tárolni, vagy ha lehet, akkor hogyan lehet megoldani, hogy a legfelső bitet ne lehessen módosítani, még akkor sem, ha memóriában van. Vagy mondjuk ki kell mondani, hogy pointer nem tárolható memóriában, ha elfogynak a regiszterek, akkor megáll a program.
    Vagy memóriában csak 128 bitet tárolunk, és egy spéci LoadPointer utasítás mesterséges intelligenciával alakítja 129-bites regisztertartalommá.
    Tényleg nem értem az egészet, így vaktában azt mondanám, hogy aki biztonságos pointereket akar, az használhat Java-t például.
    Mutasd a teljes hozzászólást!
  • Valami olyasmit mondott az első előadó, hogy ha nyersen írod a regisztert, akkor a legfelső bit törlődik, és onnan kezdve a capability megszűnik capability lenni, hogy ne tudj magadnak tetszőleges jogosultságokat adni. Az is lehet, hogy a cap bit csak akkor nem törlődik, ha az új értékkel a meglevő jogosultságokat meghagyod vagy szűkíted (ez utóbbit lehet). A kezdeti capability előállításához meg lehet, hogy lesz egy privilegizált utasítás, amit kernelszinten a memóriamanager használ, hogy előállítson egy többszörösen laphatárokra igazított capabilityt (szóval granularitás szevasz), de ez már csak az én tippem.
    Mutasd a teljes hozzászólást!
  • Mindig a nyers pointer méretével azonos méretű a 'ráadás'.
    Mutasd a teljes hozzászólást!
  • Addig talán oké, hogy véletlen túlcímzések ellen véd, de rosszakaratú programozó ellen nyilván nem, hacsak nem mondják azt, hogy a további 64 bit előállításának módja szabadalommal védett, azt userland programban nem szabad megcsinálni.
    uintptr_t ip= 0xc1cababaUL; CalcUpper64bit(&ip, 0UL, ~0UL); /* range: from, to */ char *p= (char *)ip;
    Mutasd a teljes hozzászólást!
  • Megnéztem a lényeges részt, az eddigi 64 bites regiszterek bővülnek ki 129(!) bitre, a 64 bites pointer fogalmát pedig váltja a 128 bites capability. Ennek a felső 64 bitjébe sűritik be a terület deszkriptorát (határok, jogosultságok), a 129. bit pedig megmondja, hogy a többi 128 capability-e vagy sem. Ez már eleve kérdéseket vet fel a granularitással kapcsolatban is, gondolom a bázis/méret csak 64K vagy még nagyobb alignnal lesz megadható, ami sokkal inkább arra viszi a dolgot, hogy egy processzen belül a moduloknak legyen saját privát szférájuk, és egymást ne tudják tönkretenni, csak saját magukat. Persze ha ptr-t, akarom mondani capability-t cserélnek, akkor már ez sem igaz.

    Nekem nem tetszik, ez a kerék újrafeltalálása ronda formában, gyakorlati haszon nélkül.
    Mutasd a teljes hozzászólást!
  • Psszt... gyere csak közelebb!

    Azokkal a pointerekkel mi a helyzet, amik már most is 16 bájtosak, nem csak 8 bájt a méretük?
    Mutasd a teljes hozzászólást!
  • 64 bites nettó pointer mellett további 64 bit metaadat lesz, szóval 128 bites intptr_t kell hozzá.
    https://www.google.com/url?sa=t&source=web&rct=j&url=https://www.cl...
    Mutasd a teljes hozzászólást!
  • Én sem hallgattam még végig a videót, csak az alapelvre reagáltam: lokális, adott célra allokált memóriadarabkák, amelyeken van bounds-cheking, azaz nem lehet kicímezni belőlük. A gyakorlatban persze ennek az lesz a vége, hogy "nagy" chunkokkal dolgoznak majd a fejlesztők, belső szuballokációval, azaz mégiscsak elronthat olyan adatokat egy rosszul célzott írás, amelyeket nem kellene...

    x86-on egyébként egy szegmens létrehozása nem volt költségesebb, mint általában véve más művelet, amelyek kernel-szinten vannak megvalósítva. 16 bites windózon mindennapi dolog volt az ezekkel végzett művelet, ld. a régi LocalAlloc és GlobalAlloc közti különbségeket. TLB-flush nem kell létrehozás után, mert az a virtuális memória laptábláinak a címe, nincs közvetlen köze a szegmensekhez (a szegmensekből képzett cím még mindig virtuális), és 16 bites CPU-n nem is volt ilyen. Arra akkor kell flush, amikor két processz között van kontext-váltás, hiszen minden processznek saját mappingje van.

    Ez az egész pont amiatt halt ki a gyakorlatból, mert senkinek nem volt kedve lokális memóriadarabkákkal szórakozni, sokkal egyszerűbb csakis egyszerű "near" pointerekkel mindent elérni, ami aztán a 32 bites címzéssel meg is adatott.

    Ami a granularitást illeti: ahogy írtam, a fejlesztőn is múlik. Túl sok kis lokális memóriadarabkát létrehozni és hw-esen menedzselni sokkal költégesebb, mint a másik véglet, az "egyetlen nagy", szuballokált csonkon belül dolgozni, de az meg pont a lényegét veszi el az egésznek. A kettő között vhol lenne az optimális, de sokan úgyis mindig a kényelmet választják.
    Én amúgy is többre tartom a vmiféle sanity-checkerrel leellenőrzött kódot, mint a futás során állandóan fellépő többletköltséget. Windózon ott van pl. az Application Verifier, az lapszinten tudja kimutatni a túlcímzéseket (lapméretre kerekített, és mindkét irányból védett csonkok). Az említett dolgok miatt persze nem tud mindent kimutatni, de én elég sokszor nagy hasznát vettem.
    Mutasd a teljes hozzászólást!
  • Nagyon sok múlik azon, hogyan valósítják meg az egészet a gyakorlatban. (Biztos elmondják a videóban, de ezért nem vagyok hajlandó másfél órát végighallgatni.)

    x86-on a szegmenseket tipikusan csak kernel privilégiummal lehetett létrehozni illetve módosítani, illetve kellett utána egy drága TLB flush. Valószínűleg ez is közrejátszott abban, hogy nem nagyon ment senki tovább a kód/adat/verem szentháromságnál. Ha ez a technológia lehetővé teszi a szegmensek olcsó és egyszerű karbantartását, akkor sokkal többre viheti.

    (Persze az is jó kérdés, milyen granularitásra gondoltak a fejlesztők. Nem mindegy, hogy a cikkben említett rekeszek mondjuk az "abcd könyvtár által foglalt memória" címkét viselik, vagy "a 15738. malloc hívás által foglalt memória" címkét.)
    Mutasd a teljes hozzászólást!
  • Az ARM feltalálta a szegmentált memóriamodellt? Ahol a pointer mellé egy szegmens-érték is tartozik, amely egy deszkriptorra mutat, amelyben a terület kezdőcíme, mérete, jogosultságok meg miegyebek vannak eltárolva?
    Ez x86-vonalon a 16 bites világban jelent meg (near/far pointerek) még a villany feltalálása előtt, de a 32 bittel megjelent a virtuális memória is, ezért a gyakorlatban egyik oprendszer sem alkalmazta, jött helyette a lapozható flat címtér.
    Maga a processzor támogatja a szegmentálást bekapcsolt lapozással is, de csak 32 biten, ellenben semmi nem használja, ahogy írtam. Nos, néhány speciális esetet kivéve: 64 bites windózon pl. egy 32 bites kódszegmensen belül futnak az x86-os processzek, és egy 16+32 bites far pointerrel vált át az nt.dll 64 bites kódszegmensre, mielőtt belehívna a kernelbe (merthogy az 64 bites).

    64 bites üzemmódban hw-esen el is dobták a bounds támogatást: bár a szegmensek fizikailag még mindig léteznek, de ha azok nem 0 bázisra és max méretre, azaz a teljes címtartományra vannak beállítva, akkor jön az exception.

    Érdekes ez egyébként: az x86 architektúrát (jogosan) az a kritika éri, hogy túl bloated, rengeteg felesleges dolog van már benne. Erre az ARM "feltalálja" a bloated kerék egy részét, és újdonságként jelenti be. Az Intel meg lehet, hogy a Meteor Lake-kel vagy az utána következő architektúrával kidob egy csomót.
    Mutasd a teljes hozzászólást!
  • Mármint az volt a koncepció, hogy a gyakorlatban használni kellene ezeket a 16+32=48 bites pointereket. Értelemszerűen ha OS, programnyelv és könyvtárak nélkül 'baremetal' fejlesztesz, akkor természetesen használhatsz ilyen pointereket.
    Mutasd a teljes hozzászólást!
  • Amire én gondolok az nem koncepció volt. Intel és AMD processzorok a mai napig tartalmazzák a segment-eket. Ha 32 bites védett módban használod őket, akkor a mai napig tudod használni a segment-ek által nyújt védelmet. Csak a programozon múlik, hogy használja is őket vagy sem. A címzésnél a cím 2 részből áll, egy segment register és egy 32 bites offset. Itt nincs 48 bites pointer.
    Mutasd a teljes hozzászólást!
  • 32-biten volt ilyen koncepció, 48-bites pointerekkel járt volna, a programozók nem voltak tüzesek érte.
    Mutasd a teljes hozzászólást!
  • x86-os vonalon "segment"-nek nevezik és már nagyon régóta létezik, csak sajnos a 64 bites módban ez ki lett lapítva és így már olyan, mintha nem is lenne. Pedig az ötlet jó. A megvalósítási hiba az, hogy nagyon kevés a segment register és nem optimális az erőforrás igénye, ha sok segment kell, mert a segment regiszterek folyamatos átírása erőforrás igényes. A lapozással együtt használva könnyen bonyolulttá válhat. Ahelyett, hogy több általános célú és segment regisztereket adtak volna a processorhoz, helyette lett a 64 bit csökkentettet a bonyolultsággal aminek sebezhetőség növekedése az ára.
    Mutasd a teljes hozzászólást!
  • Annyit segítek, hogy modern C-ben van egy intptr_t típus, ez egy olyan integer, ami képes tárolni egy tetszőleges adat-pointert. Tehát akkor az intptr_t típust is 24-bájtosra kell növelni?
    Mutasd a teljes hozzászólást!
  • Esetleg minden pointer 8 byte helyett 3*8 byte lesz?


    Én 4 byte-os pointer plusz lapozás mellett csinálnám mondjuk még 4 byte hozzáadásával aminek szerkezete:

    - 4 byte kezdőcím
    - 2 byte (index)
    - 2 byte (hossz)

    Ami ebbe nem fér bele, arra lehet csinálni pazarlóbb címzési módot, vagy több ilyen pointert használsz és csőváz. De ez csak egy trivi megoldás. Ha CISC-ebb irányba mész, akkor lehetne 6 byte-os pointer is, de még normálisabb talán, ha csak valami regiszterekkel kombinált címzési módod van és mondjuk risc az egész mégis. Tehát mint a mov eax,[ebx+ecx] @@ edx jellegű módon ha x86 lenne... szóval bázisregiszter + eltolás és mellé tudod írni a @@ mögé, hogy melyik regben van a hossz. Ezt persze asm-et kézzel írva szenvedős programozni, de nyelvből fordítva valszeg kivédi a hibáid ez is.

    De nagyon sok ilyesmi - és ennél talán jobb - variációt tudok most elképzelni, csak leírtam párat.

    Na jó... még egy alternatíva: csak indirekt címzést engedsz meg és a "pointer" által mutatott kezdőcímen mindig ott van a hossz:

    Tehát:

    samov eax, [0a000h+edi]

    és akkor a 0a000h címen (vagy ha ott regiszterből számolt, akkor annak címén) nem az adat van, hanem az adat hossz és az offsetet ezzel csekkolod össze... Ez is egy módszer és megint csak mondtam "valamit".

    Ez annyiból jobb megoldás, hogy így a pointer mérete nem nő egyáltalán - pont annyi bites, mint egyébként - hanem így ugyebár a "tömb" mérete lesz kötelezően 4/8 byte-al nagyobb... Mellesleg emiatt valszeg az architektúrában ki kell kötnöd, hogy alignolva legyenek a címek minden tömb/pointer elérésnél, de ez van, akkor is spórolósabb így. Macerásabb viszont ezzel cache-elést csinálni, pipeline-olást meg hasonlókat, mert ha nincs a cache-ben az adat és be kell húzni a ramból az szivattyúnak (lassúnak) hangzik.

    ui.: Mondhatjátok, hogy "dehát az utóbbi és az előbbi "megoldás" is egyaránt ugyanannyi byte".... de nem az... nem mindegy hogy tömbbönként pazarolsz 4-8 byte-ot, vagy pointerenként. De cserébe vannak architekturális macerák vele.
    Mutasd a teljes hozzászólást!
  • Nem tudom hogyan képzelném el. :)

    De tény, hogy az összes memória szó csak egy kis töredéke pointer, szóval elvileg lehet valami megoldás, ha nem is túl hatékonynak látszik. Bár hardveresen valamivel jobb a helyzet.
    Mondjuk nem muszáj 3-szoros tárterület, hisz nem logikus, hogy a teljes megcímezhető terület  egy blokkban használjuk. Valami lapokban gondolkodás elképzelhető, ha kis kompromisszumot bevállalunk. Ahol a jelenlegi pointer meghatározza a lapot és már csak néhány bit kell hogy a lapméretet jelölje. Bonyolult, és jó sw esetén nem szükséges azért, pl. egy bytekód végrehajtású rendszerben (C# vagy java példának) a bytekód végrehajtó motor egész jól tud vigyázni a címtartományokra. Igaz az SW-es megoldás és lassabb.
    A HW-es meg több memóriát igényel. 
    Szóval semmi sincs ingyen.
    Mutasd a teljes hozzászólást!
  • (Itt valami duplázódott, simán 8.88178419700125e-16)
    Mutasd a teljes hozzászólást!
  • De mégis hogyan képzelnéd ezt el CPU szinten? Minden regiszter mellett lesz két árnyékregiszter (tól és ig címek)? És ha mondjuk felolvasunk a memóriából egy pointert, akkor hogyan állítsuk be ezeket az árnyékregisztereket? Esetleg minden pointer 8 byte helyett 3*8 byte lesz?
    Mutasd a teljes hozzászólást!
  • No, akkor egy gonddal kevesebb. Azért a próba kedvéért:

    PRINT (0.8-0.1)*10-7
    például egy másik platformon:

    $ perl -e 'print ((0.8-0.1)*10-7);' 8.88178419700125e-168.88178419700125e-16
    Mutasd a teljes hozzászólást!
  • 0.8 - 0.1 != 0.7 

    Megoldották, csak olyan nyelvet kell használni ami alkalmas az elvárásaid teljesítésére és ismeri a BCD számokat (1950 óta sok nyelv) vagy a Decimal (c#) típust.

    C# Decimal - high-precision calculation in C# with Decimal

    double x = 0.1 + 0.1 + 0.1; double y = 0.3; Console.WriteLine(x); Console.WriteLine(y); Console.WriteLine(x == y); decimal u = 0.1m + 0.1m + 0.1m; decimal v = 0.3m; Console.WriteLine(u); Console.WriteLine(v); Console.WriteLine(u == v);

    This example shows that values 0.1 can be represented exactly in decimal type.
    $ dotnet run

    0.30000000000000004 0.3 False 0.3 0.3 True
    Mutasd a teljes hozzászólást!
  • Most az egyenlőséget tudja, vagy a kiiratási kerekítést?

    Kicsit mélyebb ill. háttér tudás jót tesz ebben a szakmában.
    (Ha már sokat beszélünk az informatikai gyorstalpalókról és hogy az egyetem milyen felesleges)
    Mutasd a teljes hozzászólást!
  • 10 PRINT 0.8-0.1 RUN .7 READY.
    c64 tudja
    Mutasd a teljes hozzászólást!
  • Ha a pointer mellé tényleg kerül és "tól-ig" adminisztráció, akkor az nagy dobás lehet.

    Mondjuk egy C# vagy Java esetén nem is kell se a forráson, se a bytekódon változtatni, a bytekód motor képes olyan proci tárgykódot fordítani ami képes ezt kihasználni, hiszen a méret adatok adottak lehetnek. 
    Egy C/C++ (stb) nyelvnél újrafordítást és sok esetben attribútumokat tudok elképzelni, hogy kihasználja. Bár a std libekbe bekerülhet a kihasználáshoz szükséges infó kezelése.
    A Basic/JS féle nyelveknél nem is látom hogy a nagy szabadság (nekem szabadosság) mellett milyen előnyt tud hozni.
    Mutasd a teljes hozzászólást!
abcd