Net-Base Revistë

23.08.2026

Gjueti e rrjedhave të kujtesës: Përdorimi i synuar i FastMM FullDebugMode dhe leximi i saktë i Stacktraces

FastMM FullDebugMode është një nga mjetet më efektive kundër rrjedhjeve të memories në projektet Delphi – por vetëm nëse aktivizohet në mënyrë të qëllimshme, raportet interpretohen saktë dhe shmangen supozimet e zakonshme. Ky artikull praktik tregon një proces të qartë nga...

23.08.2026

Nga tema e revistës në praktikën e projektit

Faqe shërbimi dhe teknike të përshtatshme për artikullin

Kur një Delphi-aplikacion gjatë ekzekutimit ngadalë “fryhet”, ndalon sporadikisht me Access Violations ose pas disa ditësh funksionimi bëhet papritur i paqendrueshëm, shpesh pas tij nuk qëndron një gabim i vetëm, por një model: memoria kërkohet, por nuk lirohet si duhet – ose lirohet tepër herët dhe më vonë përdoret përsëri. Pikërisht këtu FastMM FullDebugMode është i çmueshëm. Jo si gjendje e përhershme, por si një mjet diagnostikues i synuar, që nga “diku në heap diçka është prishur” bën të mundur gjetjen e një shkaku të gjurmueshëm.

Problemi është: FullDebugMode prodhon shumë output, ngadalëson performancën dhe çon lehtë në interpretime të gabuara. Një raport për leak nuk tregon automatikisht vendin ku „gabimi“ është. Dhe një stacktrace është po aq i dobishëm sa rezolucioni i simboleve (MAP-Datei, Debug-Infos, inlining). Në këtë artikull unë kaloj rastin tipik anësor, shpjegoj qasjen e pastër dhe kurthet – në mënyrë që në fund të mos gjesh vetëm leaks, por t’i eliminosh ato në mënyrë të qëndrueshme.

Kur FastMM FullDebugMode është vërtet i dobishëm

FastMM shpesh në versionet moderne të Delphi tashmë është menaxheri i memories parazgjedhje ose përfshihet në shumë projekte. Por FullDebugMode është një konfigurim i veçantë: ai pajis blloqet e memories me modele shtesë kontrolli, mbledh stacktrace-t e allokimeve dhe kontrollon më agresivisht për korruptimin e heap-it (pra të dhëna menaxhuese të dëmtuara në heap, p.sh. për shkak të buffer-overruns).

Unë përdor FullDebugMode në mënyrë të synuar kur paraqitet një nga këto situata:

  • Leak i riprodhueshëm: konsumi i memories rritet gjatë testimit për çdo operacion (p.sh. për çdo kërkesë, për çdo import, për çdo veprim në UI).
  • AV sporadike: veçanërisht ato që ndodhin “herë këtu, herë atje” në të njëjtin zonë (klasike: Use-after-free).
  • Korruptim i heap-it: njoftime si “Invalid pointer operation”, “Access violation in ntdll” ose crash-e gjatë mbylljes/finalizimit.
  • Kërkim i regresioneve: pas refaktorizimit, përditësimit të bibliotekës ose ndryshimit të compiler-it shfaqet papritur paqëndrueshmëri e re.

FullDebugMode nuk është i përshtatshëm si “le ta aktivizojmë në të gjitha build-et”. Overhead-i është i lartë, timing-u ndryshon, dhe veçanërisht race-conditions mund të zhduken ose të zhvendosen për shkak të tij. Për operim të përhershëm më i përshtatshëm është një monitoring i lehtë (p.sh. Working Set i procesit, Private Bytes, numërues për çdo operacion) – FullDebugMode është bisturia, jo pulsometri.

Parimi themelor: Raporti i leak është simptomë, stacktrace është gjurmë

Një raport për leak të tregon së pari: Këto blloqe ende janë allokuar në fund të programit. Kjo është problem vetëm nëse këto blloqe në të vërtetë duhej të ishin lëshuar. Ekzistojnë “leak”-e legjitime: singleton-e globale, cache-e, OS-handle me kohëgjatësi të procesit ose biblioteka third-party që qëllimisht nuk finalizojnë. Këto raste duhen njohur, por jo t’i riparosh pa analizë.

Stacktrace në raport tregon vendin ku blloku u kërkua. Kjo shpesh nuk është vendi ku ke harruar të thërrasësh Free. Realitet i shpeshtë në sisteme të zhvilluara:

  • Allokim në UI- ose service-layer, çlirimi duhet të ndodhë në një shtresë më të thellë (Ownership i paqartë).
  • Allokim në një factory, ownership-i i transferohet caller-it – por caller-i mendon se është “owned”.
  • Objektet mbahen në koleksione (lista, dictionaries), por modeli i ownership-it nuk është konsistent.
  • Një rrugë exception-i anashkalon pastrimin, sepse try/finally mungon ose fillon tepër vonë.

Rrjedha e pastër është prandaj: riprodhoizolozbërthe Stacktracegjej gabimin e pronësisërregullo me test regresioni. FastMM të jep gjurmët, por ti duhet t’i përkthesh ato në arkitekturë dhe cikle jetësore.

FastMM FullDebugMode sauber aktivieren (ohne Nebenwirkungen zu übersehen)

Grafikë skematike me blloqe heap dhe kufij verifikimi, të cilat shndërrohen në një raport rrjedhjeje
Përmbledhje: FullDebugMode punon me kufij verifikimi shtesë dhe me nxjerrjen e raporteve.

FullDebugMode aktivizohet në praktikë përmes opsioneve të FastMM dhe një konfigurimi të përshtatshëm FastMM. Nuk është vendimtare „si quhet saktësisht skedari include“, por çfarë bën konfigurimi dhe në cilat kushte të build-it e përdor.

Kushtet e rekomanduara për Debug-Build

  • DCU-t e debug-ut dhe informacionet e debug: Stacktrace-t janë të dobishme vetëm kur mund të zgjidhen në një Unit/rresht/adresë reale. Sigurohu që të krijohen informacionet e debug dhe që një skedar MAP të jetë i disponueshëm.
  • Zgjedh optimizimin me vetëdije: Për lexueshmërinë e stacktrace-it zakonisht është më mirë një build pa optimizim. Inlining dhe optimizime agresive mund të zbehin stackframes.
  • Të njëjtat kushte të ekzekutimit: Përdor sa më shumë të jetë e mundur të njëjtat të dhëna, të njëjtën konfigurim, të njëjtat të drejta. Shumë rrjedhje memorie varen nga të dhënat (p.sh. formatet e rralla, shtigje të veçanta).
  • Ndani 64-bit dhe 32-bit: Sjellja e memories, alignment-i dhe bibliotekat e treta ndryshojnë. Bëj debug në platformën e synuar ku shfaqet problemi.

Një pikë që adminët dhe drejtuesit teknik shpesh nënvlerësojnë: FullDebugMode mund të ndryshojë edhe timing. Nëse përdor multithreading, kondicionet e garës mund të shfaqen ndryshe. Prandaj është e arsyeshme të kesh paralelisht një ekzekutim pa FullDebugMode që konfirmon vetëm riprodhimin. FullDebugMode është pastaj hapi për diagnostikë.

Kujdes me „ReportMemoryLeaksOnShutdown“

Delphi mund të raportojë rrjedhje në mbyllje të programit përmes ReportMemoryLeaksOnShutdown. Kjo është e dobishme, por në aplikacione komplekse (services, Plug-in Host, kohëzgjatje të gjata) mund të mashtrojë: Gjatë shutdown-it ekzekutohen seksionet finalization, thread-et ndalojnë, cache-t pastrohen. Një rrjedhje që në mes të ekzekutimit është kritike, mund të zhduket deri në fund – ose e kundërta: një rrjedhje e dukshme shfaqet vetëm gjatë shutdown-it, sepse puna në sfond vazhdon.

Për gjuetinë e rrjedhjeve që funksionon në praktikë është më e rëndësishme: matni rrjedhjen për çdo operacion (p.sh. pas 100 kërkesash), jo vetëm gjatë mbylljes. FastMM mund të ndihmojë, por konfigurimi i testit duhet ta pasqyrojë këtë.

Rasti tipik: Raporti i rrjedhjes tregon „ndonjë objekt“, por shkaku është pronësia

Një klasik në aplikacionet e ndërmarrjeve: një proces importi krijon për çdo rekord objekte ndihmëse (p.sh. StringLists, JSON-Parser, lista të përkohshme). Në Happy Path lirohen pastër. Në raste të rralla (skip i shkaktuar nga validimi, Exception, exit i hershëm) një objekt mbetet pezull. Pas 10.000 rekordesh kjo bëhet e dukshme.

FastMM FullDebugMode ndihmon këtu, sepse tregon vendin e allokimit. Por „Fix“-i nuk është „free an die Stelle der Allokation“. Fix-i është një pattern i qëndrueshëm i pronësisë:

  • Ai që krijon një objekt nuk është automatikisht owner.
  • Ownership duhet të jetë e qartë në kontratën e API-së (parametra/vlerë e kthyer, dokumentacion, konventa emrash).
  • Koleksionet duhet të jenë të qarta: owning vs. non-owning. Format e përziera shkaktojnë probleme.
  • Rrugët me Exception kërkojnë blloqe të hershme try/finally.

Nëse nga stacktrace të shfaqet vetëm „TStringList.Create“, informacioni nuk është i pavlerë – por ai të thotë vetëm: këtu po krijohet diçka. Pyetja është: ku duhet të përfundojë? Dhe në këtë rast mendimi arkitekturor ndihmon më shumë se akrobacitë e debugger-it.

Si të lexosh saktë stacktraces: çfarë mund të nxjerrësh vërtet

Detailaufnahme einer Debugging-Analyse mit unscharfem Debugger und handnotierter Call-Chain
Në stacktrace rëndësi ka zinxhiri i thirrjeve – jo rreshti i vetëm.

Një stacktrace nga FastMM është zakonisht një listë adresash kthimi që – me simbole debug – hartohen në Units, procedura dhe, në mënyrë ideale, numra rreshtash. Kur e lexon këtë, tre gjëra janë vendimtare:

  • Top-of-Stack nuk është gjithmonë shkaku: frame-t më të sipërme shpesh janë Memory-Manager/RTL. Interesant bëhet aty ku fillon kodi yt.
  • Call-Chain në vend të një rreshti të vetëm: rreshti është vetëm një pikë. Zinxhiri të tregon se cili shtigj çoi te allokimi.
  • Blloqe identike të shumta: nëse FastMM raporton disa leaks me të njëjtën madhësi, shpesh është një shtigj i përsëritur. Kjo është e mirë: ke reproduktueshmëri.

Kur mungojnë numrat e rreshtave: MAP-Datei, Packages, Release-DCUs

Shumë ekipe pengohen këtu: FullDebugMode është aktiv, raporti i leak-ve vjen, por në vend të Unit/Zeile ka vetëm adresa ose simbole kriptike. Shkaktarë tipikë:

  • Nuk ka skedar MAP ose informacionet e debug-ut nuk janë gjeneruar.
  • Po ekzekuton kundër Release-DCUs ose DLL-ve të palëve të treta pa simbole.
  • Aplikacioni përdor Runtime Packages: atëherë pjesë të kodit ndodhen në BPLs, dhe zgjidhja e simboleve duhet të përshtatet me këtë.
  • Optimizimi/Inlining e ka bërë stacktrace më të vështirë për t’u lexuar.

Në praktikë kjo do të thotë: për gjuetinë e leak-ve duhet një build që është me qëllim „diagnosefähig“. Ky është një objektiv ndryshe nga „sa më i shpejtë“. Leads teknikë duhet ta trajtojnë këtë si një profil build-i të veçantë, në mënyrë që të mos ndryshojë çdo anëtar i ekipit opsionet e projektit ad hoc.

Vlerësimi i frame-ve: „Interessant“ është shpesh një rresht më lart

Një shembull nga realiteti (pa kod konkret të klientit): Stacktrace të tregon në frame-in e parë në kodin tënd një rutinë „LoadConfig“. Atje sheh krijimin e një objekti. Shton një Free, rrjedhja e memories zhduket – dhe papritmas shfaqet një gabim tjetër me Double Free në një vend tjetër. Pse? Sepse „LoadConfig“ vendos objektin në një cache, dhe një rrugë tjetër e kodit është tashmë Owner dhe e pastron më vonë.

Leximi i saktë do të kishte qenë: Stacktrace të tregon, se ku krijohet blloku. Zgjidhja shpesh qëndron në përcaktimin: Kush zotëron objektin pas kthimit? Nëse nuk përgjigjesh qartë kësaj pyetjeje, vetëm ndryshon pamjen e gabimit (rrjedhje memorie → AV).

Korruptimi i heap-it vs. Rrjedhja memorie: Pse FullDebugMode shpesh gjen fajtorin e vërtetë

Grafikë që tregon një mbishkrim të buffer-it që kalon në një zonë memorie fqinje
Korruptimi i heap-it shfaqet shpesh me vonesë – FullDebugMode e bën atë të dukshme më herët.

Shumë „rrjedhje“ në të vërtetë janë probleme pasuese: një mbishkrim i buffer-it e mbishkruan metadatat e heap-it, menaxheri i memories më vonë nuk mund të lirësojë siç duhet, dhe në fund sheh rrjedhje dukshëm të rastësishme ose operacione me tregues të pavlefshëm. FullDebugMode është i fuqishëm këtu, sepse punon me modele verifikimi dhe kryen verifikime shtesë gjatë Free/Reuse.

Është e rëndësishme të bësh dallimin:

  • Rrjedhje memorie: Blloku u allokua dhe nuk u lirua kurrë. Stabiliteti pëson me kalimin e kohës; crash nuk është i domosdoshëm.
  • Use-after-free: Blloku lirohet, por përdoret më vonë. Sjell AV të sporadikë, të vështira për t’u riprodhuar.
  • Double Free: Blloku lirohet dy herë. Mund të shfaqet menjëherë apo më vonë (kur blloku është ripërdorur).
  • Korruptimi i heap-it: Dikush shkruan përtej kufijve të një blloku. Simptomat zakonisht shfaqen me vonesë.

FullDebugMode është veçanërisht i vlefshëm kur sheh simptoma me vonesë. Verifikimi shtesë i bën gabimet të dukshme më herët – shpesh pikërisht në vendin ku ndodh aksesi i gabuar, jo vetëm minuta më vonë gjatë ndonjë Free.

Qasja në projekte: Gjuetia e riprodhueshme e rrjedhjeve në vend të „Debugging im Nebel“

Nëse do të gjuash rrjedhjet e memories, ke nevojë për një proces që mund të përsëritet dhe të ndahet në ekipe. Unë punoj shpesh me një kuadër diagnostikimi të përcaktuar:

1) Riprodhimi në një skenar deterministik

Përcakto një sekencë testimi që tregon rrjedhjen në mënyrë të besueshme: „Starto Service, përpunoni 500 mesazhe, ndaloni Service“ ose „Hap maskën X, kryej veprimin Y 200 herë“. E rëndësishme është që të dokumentosh sekencën me parametra (sasia e të dhënave, mandanti, Feature-Flags), në mënyrë që të tjerët ta riprodhojnë.

2) Minimizo: Bëj rrjedhjen të dukshme për çdo hap

Nëse sekenca zgjat 20 minuta, preroje. Qëllimi: dëshiron të krahasosh sa më shpejt të jetë e mundur „para“ dhe „pas“. Në aplikacione të mëdha kjo shpesh është vërtet humbësi i kohës, jo vetë rregullimi.

3) Aktivizo FullDebugMode dhe interpreto raportin

Tani hyn në lojë FastMM FullDebugMode. Mbledh raportet, grupoje sipas madhësisë së bllokut/Callstack dhe kërko për përsëritje. Një bllok i vetëm i mbetur mund të jetë një cache legjitime. 10.000 blloqe identike janë thuajse gjithmonë një rrjedhje reale.

4) Sqarimi i pronësisë dhe riparimi në shtresën përkatëse

Riparo Leak-et aty ku përcaktohet pronësia: Factory, API-Vertrag, Collection-Wrapper. „Schnell ein Free einbauen“ direkt neben Create është shpesh vendi i gabuar nëse objekti ia jepet më tej dikujt tjetër.

5) Regresioni: e njëjta sekuencë, i njëjti Build, i njëjti Report

Riparimi është i mirë vetëm kur sekuenca rilidon dhe nuk shfaqen as Leak-e as gabime të reja të memories. Veçanërisht te Use-after-free, një “Leak weg” nuk është provë, por vetëm një simptomë e re.

Rreziqet tipike në kodin Delphi që FastMM i bën të dukshme

Koleksionet dhe pronësia (Listat, Dictionaries, Interfaces)

Shumë Leak-e nuk vijnë nga algoritme të komplikuara, por nga strukturat e të dhënave të përditshme. Dy skenare klasike të gabimeve:

  • Një listë përmban objekte, por askush nuk di se kush i lirëzon ato. Zgjidhje: përdor një listë me pronësi (owning list) ose zbraz atë konsekuentisht në finally.
  • Një Dictionary mban objekte si values; kur bëhet Remove vlera nuk lirohet ose harrohet gjatë Clear.

Shtesë problematike janë Interfaces: numërimi i referencave (i ngjashëm me ARC) është i rehatshëm, por përzierja me objekt-ownership mund të prodhojë Leak-e në rast referencash ciklike ose eventesh. FullDebugMode shpesh të tregon rrugën e alokimit, por shkaku është një cikël referencash (A mban B përmes Interface, B mban A përmes callback).

Exceptions dhe daljet e hershme

Në sisteme business-software që kanë evoluar, Exceptions shpesh janë pjesë e kontrollit normal (p.sh. validim, ndërprerje, retry). Problemi rrallë është Exception vetë, por rruga që e rrethon: një objekt krijohet përpara try/finally, pastaj hidhet Exception dhe cleanup-i anashkalohet. FullDebugMode të jep stacktrace-in e alokimit – dhe duhet të verifikosh nëse ekziston një rrugë lirimi që garantohet të ekzekutohet.

Threads dhe kohëzgjatja: „Lirëzo në thread-in e gabuar“

Në VCL/FMX dhe services me worker-threads lind një rast anësor tjetër: një objekt krijohet në një thread, por lirohet në UI-thread (ose anasjelltas), sepse me Queue/Synchronize dërgosh “vetëm shpejt” diçka për t’u përpunuar. Kjo mund të funksionojë, por mund të çojë në Use-after-free nëse producenti vazhdon punën ndërsa konsumatori tashmë e lirëzon objektin.

FastMM FullDebugMode mund të ndihmojë këtu, sepse detekton më herët gabimet kohore. Riparimi i vërtetë është megjithatë një model i pastër i kohëzgjatjes: marrëdhënie pronësie të qarta, dorëzim vetëm përmes të dhënave immutable ose pika të qarta të transferimit të pronësisë (Ownership-Transfer-Punkte).

Si t’i bësh raportet të përdorshme: filtrimi, krahasimi, dokumentimi

Në ekipe ia vlen që Leak-Reports të mos jenë vetëm për t’u shikuar, por të trajtohen si artefakte. Tre masa pragmatike që kanë provuar veten:

  • Baseline-Report: Një “gjendje e njohur” (p.sh. versione aktuale të produktit) ekzekutohet një herë me FullDebugMode dhe ruhet si referencë. Kështu do të dallosh menjëherë Leak-et e reja.
  • Krahasim sipas Use-Case: Për workflow-e kritike (Import, Export, API-Request, UI-Massenoperation) përcakto një sekuencë të shkurtër që mund të përsëritet rregullisht.
  • Rrjedhje “legjitime” të dokumentuara: Nëse një cache qëllimisht nuk finalizohet, dokumentoje këtë fakt. Përndryshe pas gjashtë muajsh dikush do të kërkojë përsëri të njëjtat hyrje.

Kjo nuk është burokraci, por kursim kohe: gjuetia e rrjedhjeve të memories shndërrohet shpejt në një cikël të pafund, sepse të njëjtat modele shfaqen përsëri në çdo sprint.

Kur ia vlen përpjekja – dhe kur duhet të veprosh ndryshe

FastMM FullDebugMode është një mjet diagnostikimi që ka kosto. Përpjekja ia vlen veçanërisht kur:

  • Aplikacioni funksionon për një kohë të gjatë (shërbim, klient Terminalserver, sistem turnesh, procese 24/7).
  • Ti përpunon rrjedha reale të të dhënave të klientëve dhe nuk mbulon të gjitha rrugët në test.
  • Stabiliteti është më i rëndësishëm se shpejtësia e lëshimit të funksioneve afatshkurtër (tipike për zgjidhjet software që lidhen drejtpërdrejt me proceset).

Nëse përkundrazi ke vetëm një ndihmës të vogël desktopi që mbaron pas 30 sekondash, gjuetia e rrjedhjeve shpesh është e rendit të dytë. Po ashtu: nëse ke një problem të përkohshëm me rritje të përdorimit të memories (p.sh. eksport i madh), shpesh nuk është një rrjedhje, por çështje e strategjisë së streaming-ut dhe e ngarkesës maksimale në heap.

Përfundim praktik: FullDebugMode nuk është një çelës, por një proces

FastMM FullDebugMode sjell strukturë në kërkimin e gabimeve të memories: bën të dukshme alokimet, zbulon korruptimin e heap më herët dhe jep Stacktraces me të cilat mund të rregullosh shkakun në vend të simptomës. Levë vendimtare nuk është mjeti, por procesi: skenarë të riprodhueshëm, build-e që mund të diagnostikohen, marrëveshje të qarta të pronësisë dhe regresion kundrejt një baseline.

Nëse ngec në një rrjedhje të këmbëngulshme ose në gabime sporadike të heap dhe dëshiron ta stabilizosh çështjen në mënyrë të qëndrueshme në një sistem më të madh Delphi, ia vlen një konfigurim diagnostikues i shkurtër dhe i pastër me sekuencë të qartë dhe raporte të analizueshme. Nëse ke nevojë për mbështetje në analizë, profile build-esh ose refaktorizim të arkitekturës: Kontakto Net-Base Software GmbH.

Për këtë temë janë gjithashtu të rëndësishme Gjetja e rrjedhjeve të memories në Delphi dhe Leximi i Raportit të Leak-ut të Fastmm. Artikulli vendos këto aspekte në kontekst dhe tregon çfarë ka rëndësi në praktikë.

Diskuto projektin ose nismën e modernizimit me Net-Base.

Hapi tjetër

Kur nga një temë lind një projekt real, arkitektura, sistemi ekzistues dhe operimi duhet të vlerësohen së bashku që në fillim.

Ne nuk mbështesim vetëm në çështje të veçanta, por edhe kur nga fragmente të kodit burimor, temat legacy ose idetë për portale duhet të zhvillohen në një projekt korporativ të qëndrueshëm.

  • Gjendja ekzistuese, imazhi i synuar dhe rreziqet teknike vlerësohen së bashku.
  • REST, qasja në të dhëna, portalet dhe implementimi nuk shtyhen si pasojë e mëvonshme.
  • Ju e shihni herët se cila rrugë është e qëndrueshme ekonomikisht dhe operativisht.

Ndaje postimin

Shpërndaj këtë postim drejtpërdrejt

LinkedIn, X, XING, Facebook, WhatsApp dhe E-Mail janë menjëherë të disponueshme. Për Instagram po përgatisim lidhjen dhe tekstin e shkurtër.

Postë elektronike

Instagram hapet në një skedë të re. Linku dhe teksti i shkurtër kopjohen më parë në memorjen e kopjimit.