Fara beint í aðalefni
OpenAI

30. júní 2026

Verkfræði

Faraldsfræði core dumpa: 18 ára villa löguð

Greining á þýðisstigi notuð til að villuleita snúin hrun í gagnainnviðum okkar.

Hleður inn...

Líkön og fulltrúar OpenAI reiða sig í síauknum mæli á skalanlega gagnainnviði til að leita að viðeigandi gögnum við ályktunartíma: þegar líkönin eru að hugsa um spurninguna þína. Sumar þessara þjónusta eru skrifaðar í C++, þar sem lágstigsstýring á kerfinu gerir okkur kleift að hámarka afköst og lágmarka minnisnotkun. Þessi hagkvæmni skiptir máli þegar við skölum, en skortur C++ á minnisöryggi þýðir að villur geta valdið hruni með því að skrifa á rangar eða ótilteknar minnisvistföng.

Fyrir nokkrum mánuðum urðum við vör við hrun inni í Rockset-þjónustunni, sérsniðnum hluta gagnainnviða ChatGPT sem er lykilatriði fyrir mörg gagnaviðbætur og leit í samtölum. Í hverju þessara hruna virtist venjulegt C++ fall ljúka keyrslu og snúa síðan aftur á falskt vistfang, sem varð til þess að kjarninn stöðvaði forritið þar sem skipanabendillinn benti ekki lengur á kóða. Stundum var hólfið fyrir skilavistfangið í staflarammanum NULL. Stundum virtist örgjörvaskráin fyrir staflabendilinn sjálf hafa skekkst um 8 bæti, eins og %rsp hefði einhvern veginn verið lækkað í miðri eðlilegri keyrslu. Í báðum tilvikum varð hrunið við skil.

Þetta eru ekki venjulegar bilunarleiðir í forritskóða. Villt skrif sem lendir aðeins á vistuðu skilavistfangi er mögulegt, en afar ólíklegt. Villa sem skekkir %rsp um 8 án þess að tengjast innfelldu assembly, setcontext eða longjmp (sem við notum ekkert af) er enn undarlegri, því þýddur kóði stillir þessa skrá aðeins beint í inngangi og útgangi falls. Allar tilgátur sem við (eða ChatGPT) gátum hugsað okkur höfðu sterk gögn gegn sér, svo villan virtist ómöguleg.

Það sem við héldum að væri eitt vandamál reyndist að lokum vera tvær ótengdar villur sem fundust fyrir tilviljun á sama tíma. Í fyrsta lagi þögul vélbúnaðarspilling á einum Azure-hýsli, þar sem örgjörvinn reiknaði einfaldlega ekki rétt. Í öðru lagi 18 ára gamalt kapphlaup í GNU libunwind, óuppgötvuð villa í víða notuðu opnu hugbúnaðarsafni.

Þessi færsla segir frá því hvernig við greindum og löguðum hrun sem virtust óútskýranleg með því að hugsa eins og faraldsfræðingar og byggja vandað gagnasafn um allt þýði hruna.

Fyrsta villuleitartilraun: að skoða nokkur core dump gaumgæfilega

Fyrst skulum við kafa dýpra í Rockset. Það er skýjamiðað gagnakerfi fyrir leit og rauntímagreiningu sem við notum í mörgum innri notkunartilvikum hjá OpenAI, til dæmis samstillingartengjum (Rockset var keypt af OpenAI árið 2024). Straumuppfærslur eru notaðar til að halda úreldingarlausri vísitölu yfir þekkingargrunn vinnusvæðis svo ChatGPT geti leitað að viðeigandi upplýsingum þegar það svarar spurningum eða framkvæmir aðgerðir.

Keyrslulag Rockset er skrifað í C++. C++-málið veitir lágstigs aðgang að örgjörvanum, sem er gott fyrir afköst og skilvirkni, en þýðir að forritsvillur geta leitt til ógilds minnisaðgangs og segfault-hruna. Til að hjálpa okkur að rekja þetta notum við fatal signal handler frá folly til að skrá staflaslóð þegar hrun verður, og við sendum samsvarandi core dump (skyndimynd af stöðu forritsins þegar það hrundi) í Azure blob-geymslu til síðari greiningar. Öll blöð fyrirspurnavinnslu Rockset eru afrituð, sem lágmarkar áhrif hruns á viðskiptavini. Hvert segfault samsvarar þó villu sem þarf að laga til að uppfylla markmið okkar um áreiðanleika og gæði.

Upphaflega nálgunin var að meðhöndla þessi core eins og hefðbundið villuleitarvandamál: skoða nokkur core dump mjög náið, mynda tilgátur og útiloka þær eina af annarri.

Flest hrunin urðu í aðferð sem heitir DocumentTree::updateDocument. Í þessum hrunum virtist updateDocument hafa kallað á eitthvert óþekkt fall X, staflinn spillst á meðan X var virkt, og síðan hafði X skilað sér á vistfang sem var ekki keyranlegur kóði. Í sumum tilvikum virtist rammi X, sem nýlega hafði verið tekinn af staflanum, gildur nema að vistaða skilavistfangið var NULL. Í öðrum tilvikum virtist staflabendillinn sjálfur rangur, en næsti gildi rammi virtist samt vera updateDocument.

Við vissum ekki hvenær staflinn spilltist, sem skildi eftir gífurlega stórt leitarsvæði. updateDocument er stór aðferð sem er mikið innfelld, svo fjöldi umsækjenda fyrir X var yfirþyrmandi.

Var þetta villa í C++-kóðanum okkar? Vandamál í þýðanda eða tengingu? Vandamál í einu keyrslusafnanna okkar? Villa í Linux-kjarna í kringum merkjasendingu eða samhengisskipti? Eitthvað enn sjaldgæfara? Ef þetta var villt skrif, hvers vegna greip ASAN-stigunarumhverfið okkar það ekki?

Við reyndum að nota forritaskrárnar okkar til að finna öll tilvik vandans, en staflaspillivillur eru erfiðar að flokka út frá skrám einum saman því skráðu staflaslóðirnar eru sjálfar skemmdar eða vantar. Okkur tókst ekki að búa til skráafyrirspurn sem var laus við bæði falskar jákvæðar og falskar neikvæðar niðurstöður. Við skoðuðum fleiri core handvirkt og fundum nokkur viðbótardæmi, en það ferli var of vinnufrekt til að skila traustu gagnasafni.

Á þessu stigi rannsóknarinnar útilokuðum við (ranglega) vélbúnaðarvillu, því við sáum hrun á mörgum svæðum og mörgum vélbúnaðargerðum, svo við leituðum enn að orsökum sem væru eingöngu í hugbúnaði. Í nokkra daga fórum við mjög djúpt í eitt hrun með rangstilltum %rsp og endurgerðum söguna fyrir hrunið með innihaldi stafla og skráa. Þetta gaf nokkrar mögulegar vísbendingar, en þar sem við héldum fast í upphaflegu niðurstöðuna um að allar villurnar hefðu sömu orsök kom það okkur ekki úr strandinu.

Vísbendingar úr staflanum

Áður en við komum að vendipunkti rannsóknarinnar er mikilvægt að útskýra hvers konar upplýsingar við vorum að draga út úr core-skránum.

Rockset er þýtt með -fno-omit-frame-pointer, þannig að virki staflaramminn er alltaf aðgengilegur í gegnum %rbp, og kallarar mynda tengdan lista af rammabendulum.

Á Linux x86_64 áskilur AMD64 System V ABI einnig 128 bæti fyrir neðan %rsp sem rauða svæðið. Það svæði er aðgengilegt kóða í notendarými og, sem skiptir máli, kjarninn lofar að skemma það ekki þegar hann afhendir merki, sem hluta af ABI-samningnum.

Rauða svæðið var lykilatriði í villuleit okkar á hruni eftir skil, því það varðveitir nokkrar upplýsingar frá því fyrir skilin. Þegar SIGSEGV fer af stað keyrir fatal signal handler frá folly á stafla þráðarins sem hrynur. Staflarammar sem eru ekki lengur virkir (af því að fallið þeirra hefur skilað sér) verða yfirskrifaðir af merkjameðhöndlaranum, nema síðustu 128 bætin. Þess vegna getum við sagt hluti eins og „staflarammi X, nýtekinn af staflanum, virtist gildur nema NULL skilavistfang.“ Rauða svæðið varðveitir hluta óvirku rammanna, eða stundum aðeins endann á einum óvirkum ramma.

Staflamynd sem sýnir spillta staflaramma sem geta yfirskrifað skilavistföng og valdið hrunum.

Við fundum eitt hrun með rangstilltum stafla þar sem öll föllin sem komu við sögu voru mjög lítil. Það gerði okkur kleift að sjá að %rsp hafði rangstillst við keyrslu tiltölulega einfalds falls, og að fleiri köll höfðu tekist eftir það. Forritið hrundi aðeins þegar virka fallið reyndi loks að skila sér. Engar þessara kóðaleiða notuðu frávik, innfellt assembly, setcontext eða longjmp, svo ef staflabendillinn breyttist raunverulega eins og core-ið gaf til kynna útskýrði engin trúverðug villa í notendarýmiskóða vandann.

Það ýtti okkur í átt að kjarnanum.

Rockset notar merki árásargjarnara en flest forrit. Keyrsla fyrirspurna er brotin niður í mörg létt verkefni sem skiptast á gögnum. Þetta er mikilvægt til að meðhöndla hátt QPS-álag á skilvirkan hátt, en gerir CPU-bókhald á hverja fyrirspurn óþjált þar sem vinna margra fyrirspurna er fléttuð saman á sama þráðapotti.

Lausnin okkar er eitthvað sem við köllum coarse_thread_cputime_clock, sem nálgar clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...) nógu ódýrt til að taka sýni við hvert verkefnamark. Hægt er að nota timer_create-API-ið til að tímasetja reglulega merkjasendingu byggða á nokkrum hugmyndum um framvindu tíma, þar á meðal uppsöfnun CPU-tíma. Við tímasetjum merki (SIGUSR2) til afhendingar á nokkurra millisekúndna fresti af CPU-tíma, og þá uppfærir merkjameðhöndlarinn gildi sem er staðbundið fyrir þráðinn. Þótt mörg verkefni sjái ekki grófu klukkuna færast áfram á meðan þau keyra gefur summa allra delta-gilda óhlutdrægt mat á raunverulegum CPU-tíma fyrirspurnar.

Þar sem við sendum merki svo oft virtist sjaldgæf kjarnavilla í kringum samhengisskipti eða merkjasendingu trúverðug. Við vörðum tíma í að lesa villuskýrslur, frumkóða kjarnans og Azure-sértækar kjarnabætur. Við prófuðum álagspróf. Okkur tókst ekki að finna neitt sem virtist tengjast.

Á þeim tímapunkti ákváðum við að stíga til baka og prófa aðra nálgun.

Læknir eða faraldsfræðingur?

Það eru tvær meginaðferðir við að villuleita vandamál af þessu tagi.

Önnur er að haga sér eins og eins konar læknir: einblína á einn sjúkling, gera mörg próf og reyna að greina eitt tilvik út frá ítarlegum gögnum.

Hin er að haga sér meira eins og faraldsfræðingur: skoða allt þýðið og spyrja hvort þar séu mynstur sem eitt tilvik getur ekki afhjúpað. Byrjaði villan í tiltekinni útgáfu? Tengist hún einu vélbúnaðar-SKU (tiltekinni gerð örgjörva og þjóns), einu svæði eða einni kjarnaútgáfu? Eru mörg aðskilin klös falin inni í því sem lítur út eins og eitt heilkenni?

Við höfðum að mestu verið í læknisham. Lykilbreytingin var að ákveða að við þyrftum að safna vönduðum þýðisgögnum.

Hreinsun gagnanna

Fyrri tilraunir okkar til að finna sjálfkrafa öll tilvik vandans mistókust vegna þess að við reyndum að nota textaleit í skránum. Core dump-in sjálf innihalda miklu meiri upplýsingar, en handvirk skoðun á þeim skalaði ekki. Við ákváðum að leggja vinnu í að byggja pípu sem gæti greint core dump sjálfkrafa.

Við létum ChatGPT skrifa skriftu sem sótti forskeyti hverrar core-skrár, dró út skrárnar, síaði þekktar falskar jákvæðar niðurstöður með loggum og merkti hrunið sjálfkrafa sem skil-í-null, rangstilltan stafla eða annað. Síðan keyrðum við skriftuna samhliða yfir öll Rockset core dump úr framleiðslu frá fyrra ári.

Þetta var vendipunkturinn.

Þegar við höfðum hreint gagnasafn birtust fylgnin strax. Það sem við höfðum meðhöndlað sem eina furðulega villu var í raun tvö aðskilin hrunþýði.

Core-in með skilum í null dreifðust yfir marga klasa og landsvæði. Tíðni þeirra hafði aukist nýlega, en það var engin skýr upphafsdagsetning og engin hrein innviðamörk.

Hrunin með rangstilltum stafla litu allt öðruvísi út. Þau komu öll frá einu svæði, höfðu skýra upphafsdagsetningu og urðu aldrei á hnútum sem höfðu keyrt lengi. Þótt þau tengdust mörgum Azure VM-vélum (sýndarvélum hýstum í skýinu) leit mynstrið út eins og ein líkamleg vél með bilaðan vélbúnað ylli vandræðum fyrir hvaða VM sem lenti á henni.

Punktarit af hrunhlutföllum eftir klösum yfir tíma, sem sýnir flest hrun einbeitt í klösum 2, 3 og 6, með toppi í klasa 1 undir lok tímabilsins.

Þá áttuðum við okkur á að við höfðum blandað tveimur villum saman í huganum. Þar sem við höfðum blandað saman gagndæmum frá báðum villunum gátum við ekki fundið eina samhangandi skýringu.

Villa #1: slæmi hýsillinn

Með hreinan lista yfir Kubernetes-hnúta og tímastimpla gátum við rakið hrunin með rangstilltum stafla aftur til eins líkamlegs hýsils, sem auðvelt var að setja á bannlista.

Okkur tókst ekki að endurskapa skráarspillinguna á þessum hýsli í stýrðu umhverfi, jafnvel eftir nokkrar vikur af álagsprófunum. Þegar vandamálahýsillinn var tekinn úr notkun hurfu hrunin með rangstilltum stafla hins vegar.

Að fjarlægja slæma hýsilinn er ekki varanleg lausn í þeim skilningi að það kemur ekki í veg fyrir nýtt tilvik sama vandamáls. Við getum þó breytt hugbúnaðinum þannig að ef svipað vandamál kemur upp aftur sé auðvelt að greina það og meðhöndla. Við bættum fatal signal handler-inn okkar þannig að hann innihaldi stöðu skráa, svo við getum greint endurkomu út frá loggum einum saman (ekkert core dump þarf). Við breyttum stjórnstiginu þannig að VM-vélar eru yfirleitt endurnotaðar í stað þess að vera endurunnar, sem gerir greiningu á slæmum hnútum mun auðveldari á okkar lagi innviðastaflans. Við uppfærðum líka keyrsluleiðbeiningarnar okkar (og hugarlíkön teymisins) til að ná yfir þennan möguleika.

Þegar hrunin frá slæma hýslinum höfðu verið aðgreind varð mun auðveldara að hugsa um core-in sem eftir voru með skilum í null. Áður höfðum við útilokað afvöfnun frávika vegna þess að við töldum okkur hafa gagndæmi: hrun í kóðaleiðum þar sem frávik voru örugglega ekki notuð. En þessi gagndæmi voru öll úr klasanum með vélbúnaðarspillingu.

Þegar við skoðuðum core-in sem eftir voru aftur með þetta í huga kom í ljós að niðurstaðan var nákvæmlega öfug: hrunin urðu öll við afvöfnun frávika.

Meðhöndlun frávika er kvikur stjórnflutningur

Þegar C++ kastar fráviki þarf keyrsluumhverfið að finna hvaða catch-blokk á að taka við því og hvaða eyðarar eða hreinsunarmeðhöndlarar eiga að keyra á leiðinni. Þýðandinn gefur frá sér þessi lýsigögn, en raunveruleg pörun gerist kvikt við keyrslu.

Afvöfnun frávika er í raun ekki framkvæmd af fallinu sem kallar á throw, heldur af hjálparföllum sem þýddi kóðinn sem verður til kallar á. Þessar keyrslurútínur skoða staflann, sækja lýsigögn um föllin sem finnast á staflanum, leita kvikt að hreinsunarmeðhöndlurum og catch-blokkum og flytja síðan stjórn á einn af þeim stöðum. Að flytja stjórn felur í sér að vefja ofan af öllum staflarömmum á milli (þar á meðal römmum hjálparfallanna).

Í rekstri líkist þetta miklu meira longjmp eða fiber-skiptingu en venjulegu kalli og skilum. Endurheimta þarf callee-save skrár, sem og staflarammaskrárnar %rbp og %rsp.

Tvíundarskráin okkar tengir við tvö söfn sem innihalda útfærslur á föllunum sem framkvæma afvöfnun C++-frávika: libgcc og GNU libunwind. Skilgreiningar GNU libunwind voru þær sem kviki tengillinn valdi. Það kom okkur á óvart; við höfðum búist við að útfærsla libgcc yrði valin vegna reglna um táknútgáfur, en skoðun á keyrandi tvíundarskrám sýndi að svo var ekki.

Ein síðasta forsenda tekin til baka

Á þessum tímapunkti breyttist vinnutilgátan okkar, þegar við slökuðum á annarri forsendu sem við höfðum gert þegar við héldum að aðeins ein villa væri til staðar.

Kannski vorum við ekki að sjá venjulegt fall skila sér í NULL. Kannski vorum við að sjá afvöfnunarflutning—í raun endurheimt skráa að hætti setcontext—þar sem áfangabendill skipana hafði orðið NULL áður en stjórn var flutt. Með öðrum orðum röng gögn frá afvöfnunarsafninu, frekar en rangt hólfi skilavistfangs á staflanum.

Það þrengdi vandann gríðarlega. Annaðhvort var GNU libunwind að reikna ranga áfangastöðu, eða það reiknaði rétta stöðu og eitthvað spillti henni áður en hægt var að beita henni.

Við lásum frumkóða GNU libunwind og komumst að því að það býr til ucontext_t á staflanum, fyllir inn æskilega skráarstöðu fyrir ramma hreinsunarmeðhöndlarans og afhendir síðan bendil á þá strúktúru til innri assembly-rútínu: _Ux86_64_setcontext.

Á þessum tímapunkti höfðum við alla bútana.

Tilbúna ucontext_t-strúktúran býr í einum af staflarömmunum sem _Ux86_64_setcontext vefur ofan af við keyrslu fallsins. Var _Ux86_64_setcontext að lesa úr strúktúrunni eftir að það breytti %rsp, þegar strúktúran var ekki lengur hluti af virka staflanum? Það myndi gera hana berskjaldaða fyrir yfirskrift við merkjasendingu, til dæmis tíðu SIGUSR2-merkjunum okkar.

Villa #2: libunwind-villan

Svarið var já.

Hér eru síðustu sex skipanirnar í _Ux86_64_setcontext í útgáfunni af GNU libunwind sem við notuðum; þær samanstanda að mestu af mov-skipunum sem hlaða úr minni í áfangaskrá:

Ósniðinn texti

1
74: mov UC_MCONTEXT_GREGS_RSP(%rdi),%rsp
2
75:
3
76: /* push the return address on the stack */
4
77: mov UC_MCONTEXT_GREGS_RIP(%rdi),%rcx
5
78: push %rcx
6
79:
7
80: mov UC_MCONTEXT_GREGS_RCX(%rdi),%rcx
8
81: mov UC_MCONTEXT_GREGS_RDI(%rdi),%rdi
9
82: retq

(%rdi bendir á ucontext_t sem er úthlutað á staflanum, og UC_MCONTEXT_*-makróin þenjast bara út í fasta hliðrun þar sem tiltekin skrá er geymd.)

Fyrsta skipunin er upphaf kapphlaupsgluggans. Hún uppfærir %rsp þannig að það bendi á nýjan botn virka staflans. Um leið og þetta gerist er strúktúran sem %rdi bendir á ekki lengur hluti af virka staflanum (eða rauða svæðinu), og kjarnanum er ekki lengur bannað að snerta hana.

Yfirleitt veldur þetta ekki vandamálum, en ef merki berst á nákvæmlega réttu (röngu?) augnabliki mun kjarninn byggja merkjarammann við %rsp-128. Það getur yfirskrifað minnið sem %rdi bendir á.

Ef það gerist áður en næsta skipun les UC_MCONTEXT_GREGS_RIP(%rdi) getur endurheimti skipanabendillinn spillst. Í hrununum okkar varð hann NULL.

Það er villan.

Hvers vegna kjarnaafritin, sem virtust venjuleg, gáfu slæmar niðurstöður

Þessi samsetningarkóði skýrir líka eina af athugununum sem rugluðu okkur: hvers vegna fall X var með NULL í skilaaðfangareitnum í undanfarandi staflaramma.

setcontext var skrifað til að endurheimta alla skráa, þar á meðal %rdi, þannig að það getur ekki notað þá skrá til að lesa UC_MCONTEXT_GREGS_RIP(%rdi) á lokastundu stjórnflutningsins. Í staðinn les það gildið fyrr, vistar það á staflann, endurheimtir nokkrar skrár í viðbót og notar síðan retq til að lesa vistaða gildið og flytja stjórn.

Það sem leit út í kjarnaafritunum eins og „fall skilaði til NULL“ var í raun „afvafrarinn smíðaði markskilaaðfang á staflanum, en það mark var skemmt áður en flutningnum lauk.“ Við höfðum gert ráð fyrir að skemmd á skilaaðfangareitnum hlyti að eiga sér stað á staðnum, því við vissum ekki um neina staði þar sem (skemmanleg) gögn voru skrifuð viljandi í skilaaðfangareitinn.

Keppnisgluggi sem spannar eina skipun

Það sem gerir þessa villu fáránlega er hversu þröngur þessi kapphlaupsgluggi er. Í svona kapphlaupsskilyrði þarf ytri atburðurinn (merkið) að eiga sér stað á milli tveggja skrefa sem annar þráður tekur. Því nær sem þessi skref eru hvert öðru, því ólíklegra er að keppnisskilyrði komi upp.

Í þessu tilviki er viðkvæmi glugginn bókstaflega aðeins ein skipun á breidd! Merki verður að berast eftir að %rsp hefur verið breytt, en áður en næsta skipun hleður %rip. Nokkrar einfaldar skipanir eins og þessar er hægt að keyra í hverri lotu á nútímalegum ofur-samhliða, óraðaðri CPU, þannig að kapphlaupsglugginn er um það bil hundrað píkósekúndur.

Þegar við fundum þetta kapphlaup voru fyrstu viðbrögð okkar að það hlyti að vera of sjaldgæft til að skýra árekstratíðnina sem við sáum. Við sáum meira en tugi return-to-null árekstra á dag yfir allan vélaflotann. Gæti kapphlaup sem tekur aðeins eina skipun við hreinsun eftir undantekningu í alvöru skýrt þetta?

Við leituðum í Fermat-áætlun. Ef viðkvæmi glugginn er á stærðargráðunni 101010^{-10} sekúndur og SIGUSR2 berst á hverjum 10210^{-2} sekúndum af örgjörvatíma, þá hefur hver undantekningahreinsunarmeðhöndlari eða catch-blokk um það bil 10810^{-8} líkur á að tapa keppninni.

Rockset notar undantekningar sem hluta af innri bakþrýstingskerfi sínu fyrir innlestur. Einn ofhlaðinn hýsill getur kastað á stærðargráðunni 10410^{4} undantekningum á sekúndu. Það þýðir að meðal tími milli bilana hjá hýsli sem notar bakþrýsting er 10410^{4} sekúndur, eða eitt hrun á nokkurra klukkustunda fresti. Á stærðarskala flota er það meira en nóg til að skýra tíðni hruna sem varð vart við.

Hvers vegna kom libunwind-villan fram núna?

Galli í GNU libunwind er gamall – meira en 18 ára gamall, til staðar í fyrstu x86_64 -útgáfunni sem studdi afvindingu C++-undantekninga.

Svo hvers vegna kom þetta upp núna?

Árekstratíðnin er í grófum dráttum í réttu hlutfalli við fjölda undantekninga sem er kastað og fjölda merkja sem er afhent. Hún ræðst líka af því hversu mikið stafla merkjameðhöndlarinn notar.

Rockset er óvenjulegt á öllum þremur ásum. Við köstum undantekningum á miklum hraða sem hluta af eðlilegri ofhleðslustýringu; við sendum SIGUSR2 óvenju oft vegna coarse_thread_cputime_clock; og fyrr á þessu ári létum við SIGUSR2 -meðhöndlarann nota meira stafla með því að bæta við kalli á timer_getoverrun, svo við gætum gert grein fyrir sameinuðum merkjum.

Síðasta breytingin virðist hafa skipt miklu máli. Ef meðhöndlarinn notar nógu lítið af staflanum gæti hann ekki náð að gömlu og úreltu ucontext_t -minni og yfirskrifað það. Fyrir þá breytingu sjáum við þessa árekstra alls ekki. Eftir breytinguna var tíðnin áfram lág þar til við jukum álag fyrir tiltekin notkunartilvik sem settu bakþrýstingskerfið undir álag.

Með öðrum orðum hefur libunwind-villan alltaf verið til staðar, en margfeldi undantekningatíðni, merkjatíðni og notkunar á stafla meðhöndlara hafði aðeins nýlega farið yfir þau mörk að hún varð sýnileg í rekstri.

Þessi aðferð skýrir líka tilviljunina að bæði vélbúnaðargallinn og libunwind-gallinn hrundu að mestu inni í DocumentTree::updateDocument. Hrun vegna libunwind voru mjög skekkt í átt að þessari aðferð, því hún er alltaf virk á þeim tímapunkti sem við köstum undantekningu til að beita bakþrýstingi á innlestur. Hún var líka mjög oft valin fyrir %rsp-misstillingarhrunin, vegna þess að gallaði vélbúnaðarhnúturinn var af SKU-gerð sem við notum fyrir magninnlestur, sem eyðir meirihluta CPU-tíma síns í þessari aðferð.

Skjótasta mótvægisaðgerðin var að skipta úr GNU libunwind yfir í afvindingarbúnað libgcc. Þetta var góð málamiðlun út af fyrir sig: útfærsla libgcc hefur notið mikillar vinnu við að draga úr læsingarárekstrum, sem skiptir máli þegar skalað er upp í stór sýndarvélar.

Við sendum einnig inn sjálfstætt endurgerðarpróf og lagfæringu(opnast í nýjum glugga) inn í GNU libunwind og staðfestum að hinir afvindingarbúnaðurinn glímir ekki við sambærilegt vandamál.

Máttur greiningar á þýðisstigi

Þessi villuleitarferð kenndi okkur margt um nákvæm atriði í dýnamískri tengingu, DWARF-afvindingarlýsigögnum, afhendingu merkja í Linux, System V ABI og C++-undantekningavélbúnaði. En helsti lærdómurinn var einfaldari en allt þetta.

Mikilvægasta skrefið var ekki snjöll lestur á samsetningarkóða eða djúp þekking á smáatriðunum. Það var að byggja upp hágæða gagnasafn. Án þessa gagnasafns vorum við að blanda tveimur aðskildum fyrirbærum í eina frásögn og reyna að rökræða okkur út úr ruglingnum. Þegar við vorum komin með nákvæm og fullkomin þýðisgögn varð uppbygging vandans augljós: einn hrunhópur tilheyrði gölluðum hýsli og annar tilheyrði kapphlaupi í libunwind. Þegar gögnin urðu betri varð villuleitin auðveldari.

Fyrir innviðakerfi eins og Rockset skiptir það miklu máli. Þessi rannsókn styrkti skuldbindingu okkar við djúpa mælingatöku, sjálfvirkar rannsóknir og stöðugar umbætur á rekstrarverkfærum okkar. Áreiðanleiki snýst ekki bara um að laga villur eftir að þær gerast – hann snýst um að byggja upp gögn, verkferla og færni sem breyta ómögulegum vandamálum í vandamál sem hægt er að greina og leysa.

Höfundar

By Nathan Bronson, Member of Technical Staff