Net-Base Магазин

23.08.2026

Откривање цурења меморије: циљено коришћење FastMM FullDebugMode и правилно читање stack trace-ова

FastMM FullDebugMode је у Delphi-пројектима један од најефикаснијих алата против цурења меморије — али само ако се циљано укључи, извештаји правилно протумаче и избегну типичне погрешне претпоставке. Овај практични чланак приказује уредан ток од...

23.08.2026

Од теме часописа до пројектне праксе

Одговарајуће странице услуга и техничке странице за чланак

Ako se Delphi-aplikacija u radu postepeno „naduva“, povremeno prekine sa Access Violations ili nakon nekoliko dana rada iznenada postane nestabilna, iza toga često ne stoji pojedinačan bug, već obrazac: memorija se zahteva, ali se ne oslobodi uredno – ili se oslobodi prerano i kasnije se koristi. Upravo tu je FastMM FullDebugMode izuzetno vredan. Ne kao trajno stanje, već kao ciljani dijagnostički alat koji iz „negde u heap-u je nešto pokvareno“ ponovo pravi razumljiv uzrok.

Problem je u tome što FullDebugMode proizvodi mnogo izlaza, košta performanse i brzo vodi do pogrešnih tumačenja. Izveštaj o curenju memorije ne pokazuje automatski mesto gde je „greška“. A stacktrace je dobar onoliko koliko je dobra razrešavanje simbola (MAP-Datei, Debug-Infos, inlining). U ovom tekstu prolazim tipični rubni slučaj, objašnjavam uredan pristup i zamke – tako da na kraju ne samo da pronađeš leakove, već ih i trajno otkloniš.

Kada je FastMM FullDebugMode zaista koristan

FastMM je u modernim Delphi-verzijama često već podrazumevani memory manager ili se u mnogim projektima ionako koristi. FullDebugMode je međutim posebna konfiguracija: on oprema memorijske blokove dodatnim proverama, prikuplja stacktrace-ove prilikom alokacije i agresivnije proverava korupciju heap-a (tj. oštećene upravljačke podatke u heap-u, npr. zbog buffer-overrun-a).

FullDebugMode koristim ciljano kada je prisutna jedna od sledećih situacija:

  • Reprodukovano curenje memorije: potrošnja memorije raste tokom testa po operaciji (npr. po zahtevu, po importu, po UI-akciji).
  • Povremene AV-ove: naročito one koje se „ponekad ovde, ponekad tamo“ dešavaju u istom području (klasično: Use-after-free).
  • Heap-korupcija: poruke poput „Invalid pointer operation“, „Access violation in ntdll“ ili padovi pri zatvaranju/finalizaciji.
  • Traženje regresije: nakon refaktorisanja, ažuriranja biblioteke ili promene kompajlera iznenada nova nestabilnost.

Nije smisleno koristiti FullDebugMode kao „uključićemo to u svim buildovima“. Režija je visoka, tajming se menja, i posebno race-condition-i mogu zbog toga nestati ili se pomeriti. Za stalni rad više odgovara lagani monitoring (npr. Process-Working-Set, Private Bytes, brojači po operaciji) – FullDebugMode je skalpel, ne pulsni senzor.

Osnovno načelo: izveštaj o curenju je simptom, stacktrace je trag

Izveštaj o curenju memorije prvo pokazuje: ovi blokovi su na kraju programa još uvek alocirani. To je problem samo ako ti blokovi zapravo trebaju biti oslobođeni. Postoje legitimna „curenja“: globalni singletoni, keševi, OS-handle-ovi sa životnim vekom procesa ili biblioteke trećih strana koje namerno ne finalizuju. Te slučajeve želiš da znaš, ali ih ne treba slepo „popravljati”.

Stacktrace u izveštaju pokazuje mesto gde je blok zatražen. To često nije mesto gde si „zaboravio Free“. U praksi u starijim, razvijenim sistemima često se dešava:

  • Alokacija u UI- ili servisnom sloju, oslobađanje bi trebalo da se izvrši u dubljem sloju (vlasništvo nejasno).
  • Alokacija u fabrici, vlasništvo se prenese pozivaocu – ali pozivalac misli da je on primarno vlasnik.
  • Objekti se drže u kolekcijama (liste, dictionaries), ali model vlasništva nije konzistentan.
  • Putanja sa izuzetkom preskače čišćenje, jer try/finally nedostaje ili počinje prekasno.

Ispravan postupak је стога: reprodukovatiizolovatirazrešiti Stacktracepronaći grešku u vlasništvuispravka uz regresioni test. FastMM ти даје трагове, али их мораш превести у архитектуру и животне циклусе.

FastMM FullDebugMode чисто активирати (без пропуштања нежељених последица)

Shematski prikaz blokova heapa i kontrolnih ivica koji se pretvaraju u izveštaj o curenju memorije
Apstrakt: FullDebugMode radi sa dodatnim kontrolnim ivicama i generisanjem izveštaja.

FullDebugMode се у пракси активира преко FastMM-опција и одговарајуће FastMM-конфигурације. Кључно није толико „како се тачно зове include-Datei“, колико шта конфигурација уради и под којим build-условима је користиш.

Препоручени оквирни услови за Debug-Build

  • Debug DCUs und Debug-Infos: Stacktrace-ови су корисни само ако се могу разрешити на праву јединицу/линију/адресу. Обезбедите да се генеришу debug-информације и да је MAP-Datei доступан.
  • Свестан избор оптимизације: За читљивост Stacktrace-а обично је бољи неоптимизован build. Inlining и агресивна оптимизација могу „замутити“ stackframe-ове.
  • Исти услови извршавања: Користи што је могуће исте податке, исту конфигурацију, исте привилегије. Много curenja меморије зависи од података (нпр. ретки формати, посебни путеви).
  • Разликовати 64-bit и 32-bit: Понашање меморије, поравнање и треће-библиотеке се разликују. Debug-уј на циљној платформи на којој се проблем јавља.

Једна ствар коју админи и технички лидери често потцењују: FullDebugMode може такође изменити тајминг. Ако имаш threading у игри, race-condition ситуације могу да се појаве другачије. Због тога је смислено паралелно имати и једно покретање без FullDebugMode-а које само потврђује репродукцију. FullDebugMode је онда корак ка дијагнози.

Пажња при „ReportMemoryLeaksOnShutdown“

Delphi може преко ReportMemoryLeaksOnShutdown пријавити curenja меморије при завршетку програма. То је практично, али у комплексним апликацијама (services, plug-in host, дуго трајање) може преварити: при shutdown-у се извршавају делови за finalizaciju, thread-ови се заустављају, кешеви се чисте. Curење које је током рада критично може до краја изаћи из сцене — или обрнуто: наизгледно curenje настаје тек при shutdown-у зато што још траје позадински рад.

За применљиво трагање за curenjima важније је: мерити curenje по операцији (нпр. након 100 захтева), а не само при гашењу. FastMM може помоћи у томе, али сет апарата за тест мора то да емитира.

Типичан рубни случај: Leak-Report показује „неки објекат“, али узрок је у власништву

Класик међу корпоративним апликацијама: процес увоза генерише по запису помоћне објекте (нпр. StringLists, JSON-parser, привремене листе). На Happy Path-у се правилно ослобађају. У ретким случајевима (preskakanje због валидације, изузетак, рани излаз) неки објекат остане заглављен. Након 10.000 записа то постане видљиво.

FastMM FullDebugMode овде помаже јер показује место алокације. Али „Fix“ није „free на месту алокације“. Решење је робустан образац власништва:

  • Ко створи објекат, није аутоматски власник.
  • Vlasništvo мора бити јасно дефинисано у API-уговору (параметар/повратна вредност, документација, конвенције именовања).
  • Колекције морају бити недвосмислене: owning vs. non-owning. Мешовити облици доводе до проблема.
  • Путеви са изузецима захтевају ране try/finally блокове.

Ако из stacktrace-а видиш само „TStringList.Create“, та информација није бескорисна – али ти говори само: овде настаје нешто. Питање гласи: где би то требало да се заврши? За то више помаже архитектонско размишљање него акробатска рутина са debugger-ом.

Како правилно читати stacktrace-ове: шта заиста можете из њих извући

Detailaufnahme einer Debugging-Analyse mit unscharfem Debugger und handnotierter Call-Chain
Код stacktrace-а важан је ланац позива – не појединачна линија.

Stacktrace из FastMM-а је обично листа адреса за повратак које се – уз debug-симболе – мапирају на Units, процедуре и, идеално, бројеве линија. Када то читаш, три ствари су пресудне:

  • Врх стека није увек узрок: Горњи фрејмови су често memory-manager/RTL. Интересантно постаје тамо где почиње ваш код.
  • Ланац позива, не једна линија: Линија је само једна тачка. Ланац вам показује пут који је довео до алокације.
  • Више идентичних блокова: Ако FastMM пријави више curenja памћења исте величине, то често указује на поновљени пут. То је корисно: имате репродуцибилност.

Када недостају бројеви линија: MAP-Datei, Packages, Release-DCUs

Многи тимови се ту спотакну: FullDebugMode је активан, долази leak-репорт, али уместо Unit/лине само видиш адресе или криптичне симболе. Типични узроци:

  • Нема MAP-Datei или нису генерисани debug-инфо подаци.
  • Покрећеш се против Release-DCUs или трећих DLL-ова без симбола.
  • Апликација користи Runtime Packages: делови кода налазе се у BPL-овима и резолуција симбола мора томе одговарати.
  • Оптимизација/Inlining учинили су stacktrace теже читљивим.

У пракси то значи: за трагање curenja треба ти билд који је свесно „дијагностички“. То је другачији циљ од „што брже“. Технички лидери треба да то третирају као посебан build-профил, да се не би сваки члан тима ад hoc мењао опције пројекта.

Процена фрејмова: „Интересантно“ је често линија више горе

Primer iz prakse (bez konkretnog koda klijenta): Stacktrace ti kao prvi frame u tvom kodu prikazuje rutinu „LoadConfig“. Vidiš tamo stvaranje objekta. Dodaješ Free, leak nestaje – i odjednom pukne negde drugo sa Double Free. Zašto? Zato što „LoadConfig“ stavlja objekat u keš, i neki drugi put koda je već vlasnik i kasnije oslobađa.

Ispravna interpretacija bi bila: Stacktrace ti pokazuje, gde nastaje blok. Popravka često leži u definiciji: Ko poseduje objekat nakon Return-a? Ako na ovo pitanje ne odgovoriš jasno, samo menjaš sliku greške (Leak → AV).

Heap-korupcija vs. Leak: Zašto FullDebugMode često otkriva pravog krivca

Grafika koja prikazuje buffer-overrun koji prelazi u susedni memorijski prostor
Heap-korupcija se često manifestuje sa vremenskim odmakom – FullDebugMode je ranije čini uočljivom.

Mnogi „Leaks“ su ustvari posledični problemi: buffer-overrun prepisuje heap-metapodatke, Memory-Manager kasnije ne može pravilno osloboditi, i na kraju vidiš naizgled nasumična curenja ili neispravne operacije sa pokazivačima. FullDebugMode je ovde snažan jer radi sa testnim obrascima i pri Free/Reuse vrši dodatne validacije.

Važno je razdvojiti pojmove:

  • Leak: Blok je alociran i nikada nije oslobođen. Stabilnost se vremenom narušava, pad sistema nije nužan.
  • Use-after-free: Blok je oslobođen, ali se kasnije i dalje koristi. Dovodi do sporadičnih AV-a koji su teško reproducibilni.
  • Double Free: Blok se oslobađa dvaput. Može odmah puknuti ili tek kasnije (ako je blok ponovo upotrebljen).
  • Heap-Korruption: Neko upisuje preko granica bloka. Simptomi su često vremenski odloženi.

FullDebugMode je posebno koristan kada simptome vidiš sa vremenskim odmakom. Dodatne validacije otkrivaju greške ranije – često tačno na mestu gde se dešava pogrešan pristup, a ne tek minuta kasnije u nekom proizvoljnom Free.

Pristup u projektima: Reproducibilna potera za Leak umesto „Debugging im Nebel“

Ako želiš da loviš memorijska curenja, potreban ti je proces koji je ponovljiv i koji se može deliti u timu. Rado radim sa fiksnim dijagnostičkim okvirom:

1) Reprodukcija u determinističkom scenariju

Definiši testnu sekvencu koja pouzdano pokazuje leak: „Startuj Service, obradi 500 Nachrichten, stoppe Service“ ili „Otvori Maske X, izvrši Aktion Y 200 puta“. Važno je da sekvencu dokumentuješ sa parametrima (Datenbestand, Mandant, Feature-Flags), kako bi drugi mogli da je ponove.

2) Smanjivanje: učini curenje vidljivim po koraku

Ako sekvenca traje 20 minuta, podeli je. Cilj je: želiš što brže uporediti „pre“ i „posle“. U velikim aplikacijama to je često glavni potrošač vremena, ne popravka sama po sebi.

3) Uključiti FullDebugMode i interpretirati izveštaj

Тек сада у игру ступа FastMM FullDebugMode. Сакупи извештаје, групиши по величини блока/callstack-у и тражи понављања. Један појединачни преостали блок може бити легитимни cache. 10.000 идентичних блокова готово увек указује на стварно цурење меморије.

4) Разјашњење власништва и исправка у одговарајућем слоју

Поправке цурења врши тамо где се дефинише власништво: фабрика, API-уговор, wrapper за колекцију. „Брзо убаци Free“ непосредно поред Create често је погрешно место ако се објекат прослеђује даље.

5) Регресија: иста секвенца, исти билд, исти извештај

Поправка је добра тек када се секвенца опет покреће и не појављују се ни цурења ни нове грешке у меморији. Посебно код use-after-free случајева „цурење нестало“ није доказ, већ само нови симптом.

Типичне замке у Delphi коду које FastMM чини видљивим

Колекције и власништво (листе, речници, интерфејси)

Много цурења не потиче од компликованих алгоритама, већ од свакодневних структура података. Два класична сценарија:

  • Листа садржи објекте, али нико не зна ко их ослобађа. Решење: користити власничку листу или доследно очистити у finally блоку.
  • Речник држи објекте као вредности; при Remove вредност се не ослобађа или се при Clear заборави.

Додатно проблематични су интерфејси: референтно бројачење (слично ARC) је згодно, али мешавина са власништвом објеката може створити цурења због цикличних референци или догађаја. FullDebugMode вам често приказује пут алокације, али узрок је референтни циклус (A држи B преко интерфејса, B држи A преко callback-а).

Изузеци и рани излази

У развијеним бизнис-системима изузеци су често део нормалне контроле (нпр. валидација, прекид, retry). Проблем ретко лежи у самом изузетку, већ у путу око њега: објекат је створен пре try/finally, затим се баци изузетак и cleanup се прескаче. FullDebugMode ти даје stacktrace алокације – и мораш проверити да ли постоји гарантован пут за ослобађање.

Нити и животни век: „ослобађање у погрешној нити“

Код VCL/FMX и сервиса са радним нитима јавља се још један рубни случај: објекат је створен у једној нити, али се ослобађа у UI-нити (или обрнуто), јер се преко Queue/Synchronize „брзо“ нешто прослеђује. То може радити, али може довести и до use-after-free ако произвођач настави да ради док потрошач већ ослобађа.

FastMM FullDebugMode овде може помоћи јер раније детектује временски померене грешке. Међутим, стварно решење је чист модел животног века: јасни односи власништва, прослеђивање само преко неизмењивих података или јасне тачке трансфера власништва.

Како да извештаје учиниш корисним: филтрирати, упоређивати, документовати

У тимовима вреди третирати извештаје о цурењу меморије не само као „погледати“, већ као артефакт. Три прагматичне мере које су се показале:

  • Основни извештај: „познато стање“ (нпр. тренутна верзија производа) се једном покреће са FullDebugMode и чува као референца. На тај начин нова цурења препознајеш одмах.
  • Поређење по случају употребе: За критичне токове (импорт, экспорт, API-захтев, масовне UI-операције) дефинишеш кратку, понављачку секвенцу коју редовно покрећеш.
  • Документована „легитимна цурења“: Ако је cache свесно нефинализован, то документуј. У супротном ће за шест месеци неко поново гони исте уносе.

Ово није бирократија, већ уштеда времена: трагање за цурењем меморије у супротном брзо прерасте у бескрајну петљу, јер се исти обрасци понављају у сваком спринту.

Када се трошак исплати – а када треба приступити другачије

FastMM FullDebugMode је дијагностички алат који носи своје трошкове. Улагање се посебно исплати када:

  • Апликација ради дуго (service, Terminalserver-клијент, сменски систем, 24/7-процеси).
  • Обрађујеш стварне токове података клијената и не покриваш све путеве у тестовима.
  • Стабилност је важнија од краткорочне брзине испоруке функција (типично за софтверна решења блиска процесима).

Ако пак имаш само мали десктоп помоћни програм који се након 30 секунди заврши, трагање за цурењем меморије често је секундарно. Исто важи ако имаш једнократно појављивање пикова у меморији (нпр. велики извоз) — често није реч о цурењу, већ о стратегији стримовања и пику оптерећења у heap-у.

Практичан закључак: FullDebugMode није прекидач, већ процес

FastMM FullDebugMode уноси структуру у претрагу грешака у меморији: чини алокације видљивим, открива корупцију хипа раније и даје стек-трагове помоћу којих можеш отклонити узрок, а не симптом. Кључна полуга није сам алат, него поступак: репродуцибилни сценарији, билдови погодни за дијагностику, јасни уговори о власништву и регресија према базној линији.

Ако запнеш на упорном цурењу или спорадичној грешци у heap-у и тему желиш трајно стабилизовати у већем Delphi-систему, исплати се кратак, чист дијагностички сетап са јасном секвенцом и изводљивим извештајима. Ако ти треба подршка у анализи, профилима билдова или архитектонском рефакторингу: контакт до Net-Base Software GmbH.

За ову тему су такође важни Delphi Pronalaženje Speicherleck и Fastmm Leak Report читање. Чланак ове аспекте смирује и показује на шта је у свакодневном раду потребно обратити пажњу.

Разгoвoр о пројекту или плану модернизације са Net-Base.

Следећи корак

Када из теме настане реалан пројекат, архитектуру, постојеће стање и операције треба рано разматрати заједно.

Подржавамо не само у појединачним питањима, већ и када из исечака изворног кода, застарелих тема или идеја за портале треба да настане поуздан корпоративни пројекат.

  • Постојеће стање, циљано стање и технички ризици оцењују се заједно.
  • REST, приступ подацима, портали и увођење неће бити одложени за касније фазе.
  • Ви рано увидите који пут је економски и оперативно одржив.

Подели објаву

Поделите ову објаву директно

LinkedIn, X, XING, Facebook, WhatsApp и е-пошта су одмах доступни. За Instagram одмах припремамо линк и кратак текст.

Е-пошта

Инстаграм се отвара у новој картици. Линк и кратак текст се претходно копирају у међуспремник.